diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
commit | 46651ce6fe013220ed397add242004d764fc0153 (patch) | |
tree | 6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/interfaces/ecpg/ecpglib/prepare.c | |
parent | Initial commit. (diff) | |
download | postgresql-14-46651ce6fe013220ed397add242004d764fc0153.tar.xz postgresql-14-46651ce6fe013220ed397add242004d764fc0153.zip |
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/interfaces/ecpg/ecpglib/prepare.c')
-rw-r--r-- | src/interfaces/ecpg/ecpglib/prepare.c | 602 |
1 files changed, 602 insertions, 0 deletions
diff --git a/src/interfaces/ecpg/ecpglib/prepare.c b/src/interfaces/ecpg/ecpglib/prepare.c new file mode 100644 index 0000000..ea1146f --- /dev/null +++ b/src/interfaces/ecpg/ecpglib/prepare.c @@ -0,0 +1,602 @@ +/* src/interfaces/ecpg/ecpglib/prepare.c */ + +#define POSTGRES_ECPG_INTERNAL +#include "postgres_fe.h" + +#include <ctype.h> + +#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; +} |