/* src/interfaces/ecpg/ecpglib/prepare.c */ #define POSTGRES_ECPG_INTERNAL #include "postgres_fe.h" #include #include "ecpgerrno.h" #include "ecpglib.h" #include "ecpglib_extern.h" #include "ecpgtype.h" #include "sqlca.h" #define STMTID_SIZE 32 /* * The statement cache contains stmtCacheNBuckets hash buckets, each * having stmtCacheEntPerBucket entries, which we recycle as needed, * giving up the least-executed entry in the bucket. * stmtCacheEntries[0] is never used, so that zero can be a "not found" * indicator. */ #define stmtCacheNBuckets 2039 /* should be a prime number */ #define stmtCacheEntPerBucket 8 #define stmtCacheArraySize (stmtCacheNBuckets * stmtCacheEntPerBucket + 1) typedef struct { int lineno; char stmtID[STMTID_SIZE]; char *ecpgQuery; long execs; /* # of executions */ const char *connection; /* connection for the statement */ } stmtCacheEntry; static int nextStmtID = 1; static stmtCacheEntry *stmtCacheEntries = NULL; static bool deallocate_one(int lineno, enum COMPAT_MODE c, struct connection *con, struct prepared_statement *prev, struct prepared_statement *this); static bool isvarchar(unsigned char c) { if (isalnum(c)) return true; if (c == '_' || c == '>' || c == '-' || c == '.') return true; if (c >= 128) return true; return false; } bool ecpg_register_prepared_stmt(struct statement *stmt) { struct statement *prep_stmt; struct prepared_statement *this; struct connection *con = stmt->connection; struct prepared_statement *prev = NULL; int lineno = stmt->lineno; /* check if we already have prepared this statement */ this = ecpg_find_prepared_statement(stmt->name, con, &prev); if (this && !deallocate_one(lineno, ECPG_COMPAT_PGSQL, con, prev, this)) return false; /* allocate new statement */ this = (struct prepared_statement *) ecpg_alloc(sizeof(struct prepared_statement), lineno); if (!this) return false; prep_stmt = (struct statement *) ecpg_alloc(sizeof(struct statement), lineno); if (!prep_stmt) { ecpg_free(this); return false; } memset(prep_stmt, 0, sizeof(struct statement)); /* create statement */ prep_stmt->lineno = lineno; prep_stmt->connection = con; prep_stmt->command = ecpg_strdup(stmt->command, lineno); prep_stmt->inlist = prep_stmt->outlist = NULL; this->name = ecpg_strdup(stmt->name, lineno); this->stmt = prep_stmt; this->prepared = true; if (con->prep_stmts == NULL) this->next = NULL; else this->next = con->prep_stmts; con->prep_stmts = this; return true; } static bool replace_variables(char **text, int lineno) { bool string = false; int counter = 1, ptr = 0; for (; (*text)[ptr] != '\0'; ptr++) { if ((*text)[ptr] == '\'') string = string ? false : true; if (string || (((*text)[ptr] != ':') && ((*text)[ptr] != '?'))) continue; if (((*text)[ptr] == ':') && ((*text)[ptr + 1] == ':')) ptr += 2; /* skip '::' */ else { /* a rough guess of the size we need: */ int buffersize = sizeof(int) * CHAR_BIT * 10 / 3; int len; char *buffer, *newcopy; if (!(buffer = (char *) ecpg_alloc(buffersize, lineno))) return false; snprintf(buffer, buffersize, "$%d", counter++); for (len = 1; (*text)[ptr + len] && isvarchar((*text)[ptr + len]); len++) /* skip */ ; if (!(newcopy = (char *) ecpg_alloc(strlen(*text) - len + strlen(buffer) + 1, lineno))) { ecpg_free(buffer); return false; } memcpy(newcopy, *text, ptr); strcpy(newcopy + ptr, buffer); strcat(newcopy, (*text) +ptr + len); ecpg_free(*text); ecpg_free(buffer); *text = newcopy; if ((*text)[ptr] == '\0') /* we reached the end */ ptr--; /* since we will (*text)[ptr]++ in the top * level for loop */ } } return true; } static bool prepare_common(int lineno, struct connection *con, const char *name, const char *variable) { struct statement *stmt; struct prepared_statement *this; PGresult *query; /* allocate new statement */ this = (struct prepared_statement *) ecpg_alloc(sizeof(struct prepared_statement), lineno); if (!this) return false; stmt = (struct statement *) ecpg_alloc(sizeof(struct statement), lineno); if (!stmt) { ecpg_free(this); return false; } /* create statement */ stmt->lineno = lineno; stmt->connection = con; stmt->command = ecpg_strdup(variable, lineno); stmt->inlist = stmt->outlist = NULL; /* if we have C variables in our statement replace them with '?' */ replace_variables(&(stmt->command), lineno); /* add prepared statement to our list */ this->name = ecpg_strdup(name, lineno); this->stmt = stmt; /* and finally really prepare the statement */ query = PQprepare(stmt->connection->connection, name, stmt->command, 0, NULL); if (!ecpg_check_PQresult(query, stmt->lineno, stmt->connection->connection, stmt->compat)) { ecpg_free(stmt->command); ecpg_free(this->name); ecpg_free(this); ecpg_free(stmt); return false; } ecpg_log("prepare_common on line %d: name %s; query: \"%s\"\n", stmt->lineno, name, stmt->command); PQclear(query); this->prepared = true; if (con->prep_stmts == NULL) this->next = NULL; else this->next = con->prep_stmts; con->prep_stmts = this; return true; } /* handle the EXEC SQL PREPARE statement */ /* questionmarks is not needed but remains in there for the time being to not change the API */ bool ECPGprepare(int lineno, const char *connection_name, const bool questionmarks, const char *name, const char *variable) { struct connection *con; struct prepared_statement *this, *prev; (void) questionmarks; /* quiet the compiler */ con = ecpg_get_connection(connection_name); if (!ecpg_init(con, connection_name, lineno)) return false; /* check if we already have prepared this statement */ this = ecpg_find_prepared_statement(name, con, &prev); if (this && !deallocate_one(lineno, ECPG_COMPAT_PGSQL, con, prev, this)) return false; return prepare_common(lineno, con, name, variable); } struct prepared_statement * ecpg_find_prepared_statement(const char *name, struct connection *con, struct prepared_statement **prev_) { struct prepared_statement *this, *prev; for (this = con->prep_stmts, prev = NULL; this != NULL; prev = this, this = this->next) { if (strcmp(this->name, name) == 0) { if (prev_) *prev_ = prev; return this; } } return NULL; } static bool deallocate_one(int lineno, enum COMPAT_MODE c, struct connection *con, struct prepared_statement *prev, struct prepared_statement *this) { bool r = false; ecpg_log("deallocate_one on line %d: name %s\n", lineno, this->name); /* first deallocate the statement in the backend */ if (this->prepared) { char *text; PGresult *query; text = (char *) ecpg_alloc(strlen("deallocate \"\" ") + strlen(this->name), this->stmt->lineno); if (text) { sprintf(text, "deallocate \"%s\"", this->name); query = PQexec(this->stmt->connection->connection, text); ecpg_free(text); if (ecpg_check_PQresult(query, lineno, this->stmt->connection->connection, this->stmt->compat)) { PQclear(query); r = true; } } } /* * Just ignore all errors since we do not know the list of cursors we are * allowed to free. We have to trust the software. */ if (!r && !INFORMIX_MODE(c)) { ecpg_raise(lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, this->name); return false; } /* okay, free all the resources */ ecpg_free(this->stmt->command); ecpg_free(this->stmt); ecpg_free(this->name); if (prev != NULL) prev->next = this->next; else con->prep_stmts = this->next; ecpg_free(this); return true; } /* handle the EXEC SQL DEALLOCATE PREPARE statement */ bool ECPGdeallocate(int lineno, int c, const char *connection_name, const char *name) { struct connection *con; struct prepared_statement *this, *prev; con = ecpg_get_connection(connection_name); if (!ecpg_init(con, connection_name, lineno)) return false; this = ecpg_find_prepared_statement(name, con, &prev); if (this) return deallocate_one(lineno, c, con, prev, this); /* prepared statement is not found */ if (INFORMIX_MODE(c)) return true; ecpg_raise(lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, name); return false; } bool ecpg_deallocate_all_conn(int lineno, enum COMPAT_MODE c, struct connection *con) { /* deallocate all prepared statements */ while (con->prep_stmts) { if (!deallocate_one(lineno, c, con, NULL, con->prep_stmts)) return false; } return true; } bool ECPGdeallocate_all(int lineno, int compat, const char *connection_name) { return ecpg_deallocate_all_conn(lineno, compat, ecpg_get_connection(connection_name)); } char * ecpg_prepared(const char *name, struct connection *con) { struct prepared_statement *this; this = ecpg_find_prepared_statement(name, con, NULL); return this ? this->stmt->command : NULL; } /* return the prepared statement */ /* lineno is not used here, but kept in to not break API */ char * ECPGprepared_statement(const char *connection_name, const char *name, int lineno) { (void) lineno; /* keep the compiler quiet */ return ecpg_prepared(name, ecpg_get_connection(connection_name)); } /* * hash a SQL statement - returns entry # of first entry in the bucket */ static int HashStmt(const char *ecpgQuery) { int stmtIx, bucketNo, hashLeng, stmtLeng; uint64 hashVal, rotVal; stmtLeng = strlen(ecpgQuery); hashLeng = 50; /* use 1st 50 characters of statement */ if (hashLeng > stmtLeng) /* if the statement isn't that long */ hashLeng = stmtLeng; /* use its actual length */ hashVal = 0; for (stmtIx = 0; stmtIx < hashLeng; ++stmtIx) { hashVal = hashVal + (unsigned char) ecpgQuery[stmtIx]; /* rotate 32-bit hash value left 13 bits */ hashVal = hashVal << 13; rotVal = (hashVal & UINT64CONST(0x1fff00000000)) >> 32; hashVal = (hashVal & UINT64CONST(0xffffffff)) | rotVal; } bucketNo = hashVal % stmtCacheNBuckets; /* Add 1 so that array entry 0 is never used */ return bucketNo * stmtCacheEntPerBucket + 1; } /* * search the statement cache - search for entry with matching ECPG-format query * Returns entry # in cache if found * OR zero if not present (zero'th entry isn't used) */ static int SearchStmtCache(const char *ecpgQuery) { int entNo, entIx; /* quick failure if cache not set up */ if (stmtCacheEntries == NULL) return 0; /* hash the statement */ entNo = HashStmt(ecpgQuery); /* search the cache */ for (entIx = 0; entIx < stmtCacheEntPerBucket; ++entIx) { if (stmtCacheEntries[entNo].stmtID[0]) /* check if entry is in use */ { if (strcmp(ecpgQuery, stmtCacheEntries[entNo].ecpgQuery) == 0) break; /* found it */ } ++entNo; /* incr entry # */ } /* if entry wasn't found - set entry # to zero */ if (entIx >= stmtCacheEntPerBucket) entNo = 0; return entNo; } /* * free an entry in the statement cache * Returns entry # in cache used * OR negative error code */ static int ecpg_freeStmtCacheEntry(int lineno, int compat, int entNo) /* entry # to free */ { stmtCacheEntry *entry; struct connection *con; struct prepared_statement *this, *prev; /* fail if cache isn't set up */ if (stmtCacheEntries == NULL) return -1; entry = &stmtCacheEntries[entNo]; if (!entry->stmtID[0]) /* return if the entry isn't in use */ return 0; con = ecpg_get_connection(entry->connection); /* free the 'prepared_statement' list entry */ this = ecpg_find_prepared_statement(entry->stmtID, con, &prev); if (this && !deallocate_one(lineno, compat, con, prev, this)) return -1; entry->stmtID[0] = '\0'; /* free the memory used by the cache entry */ if (entry->ecpgQuery) { ecpg_free(entry->ecpgQuery); entry->ecpgQuery = 0; } return entNo; } /* * add an entry to the statement cache * returns entry # in cache used OR negative error code */ static int AddStmtToCache(int lineno, /* line # of statement */ const char *stmtID, /* statement ID */ const char *connection, /* connection */ int compat, /* compatibility level */ const char *ecpgQuery) /* query */ { int ix, initEntNo, luEntNo, entNo; stmtCacheEntry *entry; /* allocate and zero cache array if we haven't already */ if (stmtCacheEntries == NULL) { stmtCacheEntries = (stmtCacheEntry *) ecpg_alloc(sizeof(stmtCacheEntry) * stmtCacheArraySize, lineno); if (stmtCacheEntries == NULL) return -1; } /* hash the statement */ initEntNo = HashStmt(ecpgQuery); /* search for an unused entry */ entNo = initEntNo; /* start with the initial entry # for the * bucket */ luEntNo = initEntNo; /* use it as the initial 'least used' entry */ for (ix = 0; ix < stmtCacheEntPerBucket; ++ix) { entry = &stmtCacheEntries[entNo]; if (!entry->stmtID[0]) /* unused entry - use it */ break; if (entry->execs < stmtCacheEntries[luEntNo].execs) luEntNo = entNo; /* save new 'least used' entry */ ++entNo; /* increment entry # */ } /* * if no unused entries were found, re-use the 'least used' entry found in * the bucket */ if (ix >= stmtCacheEntPerBucket) entNo = luEntNo; /* 'entNo' is the entry to use - make sure its free */ if (ecpg_freeStmtCacheEntry(lineno, compat, entNo) < 0) return -1; /* add the query to the entry */ entry = &stmtCacheEntries[entNo]; entry->lineno = lineno; entry->ecpgQuery = ecpg_strdup(ecpgQuery, lineno); entry->connection = connection; entry->execs = 0; memcpy(entry->stmtID, stmtID, sizeof(entry->stmtID)); return entNo; } /* handle cache and preparation of statements in auto-prepare mode */ bool ecpg_auto_prepare(int lineno, const char *connection_name, const int compat, char **name, const char *query) { int entNo; /* search the statement cache for this statement */ entNo = SearchStmtCache(query); /* if not found - add the statement to the cache */ if (entNo) { char *stmtID; struct connection *con; struct prepared_statement *prep; ecpg_log("ecpg_auto_prepare on line %d: statement found in cache; entry %d\n", lineno, entNo); stmtID = stmtCacheEntries[entNo].stmtID; con = ecpg_get_connection(connection_name); prep = ecpg_find_prepared_statement(stmtID, con, NULL); /* This prepared name doesn't exist on this connection. */ if (!prep && !prepare_common(lineno, con, stmtID, query)) return false; *name = ecpg_strdup(stmtID, lineno); } else { char stmtID[STMTID_SIZE]; ecpg_log("ecpg_auto_prepare on line %d: statement not in cache; inserting\n", lineno); /* generate a statement ID */ sprintf(stmtID, "ecpg%d", nextStmtID++); if (!ECPGprepare(lineno, connection_name, 0, stmtID, query)) return false; entNo = AddStmtToCache(lineno, stmtID, connection_name, compat, query); if (entNo < 0) return false; *name = ecpg_strdup(stmtID, lineno); } /* increase usage counter */ stmtCacheEntries[entNo].execs++; return true; }