summaryrefslogtreecommitdiffstats
path: root/src/interfaces/ecpg/ecpglib/prepare.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 13:44:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 13:44:03 +0000
commit293913568e6a7a86fd1479e1cff8e2ecb58d6568 (patch)
treefc3b469a3ec5ab71b36ea97cc7aaddb838423a0c /src/interfaces/ecpg/ecpglib/prepare.c
parentInitial commit. (diff)
downloadpostgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.tar.xz
postgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.zip
Adding upstream version 16.2.upstream/16.2
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.c602
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;
+}