summaryrefslogtreecommitdiff
path: root/src/interfaces/ecpg/ecpglib/prepare.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/interfaces/ecpg/ecpglib/prepare.c')
-rw-r--r--src/interfaces/ecpg/ecpglib/prepare.c367
1 files changed, 316 insertions, 51 deletions
diff --git a/src/interfaces/ecpg/ecpglib/prepare.c b/src/interfaces/ecpg/ecpglib/prepare.c
index f1a9db0729f..bad7b359b45 100644
--- a/src/interfaces/ecpg/ecpglib/prepare.c
+++ b/src/interfaces/ecpg/ecpglib/prepare.c
@@ -1,4 +1,4 @@
-/* $PostgreSQL: pgsql/src/interfaces/ecpg/ecpglib/prepare.c,v 1.18 2006/10/04 00:30:11 momjian Exp $ */
+/* $PostgreSQL: pgsql/src/interfaces/ecpg/ecpglib/prepare.c,v 1.19 2007/08/14 10:01:52 meskes Exp $ */
#define POSTGRES_ECPG_INTERNAL
#include "postgres_fe.h"
@@ -13,11 +13,28 @@
static struct prepared_statement
{
- char *name;
+ char *name;
+ bool prepared;
struct statement *stmt;
struct prepared_statement *next;
} *prep_stmts = NULL;
+#define STMTID_SIZE 32
+
+typedef struct
+{
+ int lineno;
+ char stmtID[STMTID_SIZE];
+ char *ecpgQuery;
+ long execs; /* # of executions */
+ char *connection; /* connection for the statement */
+} stmtCacheEntry;
+
+static int nextStmtID = 1;
+static int stmtCacheNBuckets = 2039; /* # buckets - a prime # */
+static int stmtCacheEntPerBucket = 8; /* # entries/bucket */
+static stmtCacheEntry stmtCacheEntries[16384] = {{0,{0},0,0,0}};
+
static bool
isvarchar(unsigned char c)
{
@@ -33,43 +50,68 @@ isvarchar(unsigned char c)
return (false);
}
-static void
-replace_variables(char *text)
+static bool
+replace_variables(char **text, int lineno, bool questionmarks)
{
- char *ptr = text;
- bool string = false;
+ bool string = false;
+ int counter = 1, ptr = 0;
- for (; *ptr != '\0'; ptr++)
+ for (; (*text)[ptr] != '\0'; ptr++)
{
- if (*ptr == '\'')
+ if ((*text)[ptr] == '\'')
string = string ? false : true;
- if (!string && *ptr == ':')
+ if (string || (((*text)[ptr] != ':') && ((*text)[ptr] != '?')))
+ continue;
+
+ if (((*text)[ptr] == ':') && ((*text)[ptr+1] == ':'))
+ ptr += 2; /* skip '::' */
+ else
{
- if (ptr[1] == ':')
- ptr += 2; /* skip '::' */
- else
+ int len;
+ int buffersize = sizeof(int) * CHAR_BIT * 10 / 3; /* a rough guess of the size we need */
+ char *buffer, *newcopy;
+
+ if (!(buffer = (char *) ECPGalloc(buffersize, lineno)))
+ return false;
+
+ snprintf(buffer, buffersize, "$%d", counter++);
+
+ for (len=1; (*text)[ptr+len] && isvarchar((*text)[ptr+len]); len++);
+ if (!(newcopy = (char *) ECPGalloc(strlen(*text) - len + strlen(buffer) + 1, lineno)))
{
- *ptr = '?';
- for (++ptr; *ptr && isvarchar(*ptr); ptr++)
- *ptr = ' ';
- if (*ptr == '\0') /* we reached the end */
- ptr--; /* since we will ptr++ in the top level for
- * loop */
+ ECPGfree(buffer);
+ return false;
}
+
+ strncpy(newcopy, *text, ptr);
+ strcpy(newcopy + ptr, buffer);
+ strcat(newcopy, (*text) + ptr + len);
+
+ ECPGfree(*text);
+ ECPGfree(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;
}
/* handle the EXEC SQL PREPARE statement */
bool
-ECPGprepare(int lineno, const char *name, const char *variable)
+ECPGprepare(int lineno, const char *connection_name, const int questionmarks, const char *name, const char *variable)
{
struct statement *stmt;
struct prepared_statement *this;
struct sqlca_t *sqlca = ECPGget_sqlca();
+ PGresult *query;
ECPGinit_sqlca(sqlca);
+
/* check if we already have prepared this statement */
for (this = prep_stmts; this != NULL && strcmp(this->name, name) != 0; this = this->next);
if (this)
@@ -93,18 +135,30 @@ ECPGprepare(int lineno, const char *name, const char *variable)
/* create statement */
stmt->lineno = lineno;
- stmt->connection = NULL;
+ stmt->connection = ECPGget_connection(connection_name);
stmt->command = ECPGstrdup(variable, lineno);
stmt->inlist = stmt->outlist = NULL;
/* if we have C variables in our statment replace them with '?' */
- replace_variables(stmt->command);
+ replace_variables(&(stmt->command), lineno, questionmarks);
/* add prepared statement to our list */
- this->name = ECPGstrdup(name, lineno);
+ this->name = (char *) name;
this->stmt = stmt;
- ECPGlog("ECPGprepare line %d: QUERY: %s\n", stmt->lineno, stmt->command);
+ /* and finally really prepare the statement */
+ query = PQprepare(stmt->connection->connection, name, stmt->command, 0, NULL);
+ if (!ECPGcheck_PQresult(query, stmt->lineno, stmt->connection->connection, stmt->compat))
+ {
+ ECPGfree(stmt->command);
+ ECPGfree(this);
+ ECPGfree(stmt);
+ return false;
+ }
+
+ ECPGlog("ECPGprepare line %d: NAME: %s QUERY: %s\n", stmt->lineno, name, stmt->command);
+ PQclear(query);
+ this->prepared = true;
if (prep_stmts == NULL)
this->next = NULL;
@@ -115,30 +169,8 @@ ECPGprepare(int lineno, const char *name, const char *variable)
return true;
}
-/* handle the EXEC SQL DEALLOCATE PREPARE statement */
-bool
-ECPGdeallocate(int lineno, int c, const char *name)
-{
- bool ret = ECPGdeallocate_one(lineno, name);
- enum COMPAT_MODE compat = c;
-
- if (INFORMIX_MODE(compat))
- {
- /*
- * Just ignore all errors since we do not know the list of cursors we
- * are allowed to free. We have to trust the software.
- */
- return true;
- }
-
- if (!ret)
- ECPGraise(lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, name);
-
- return ret;
-}
-
-bool
-ECPGdeallocate_one(int lineno, const char *name)
+static bool
+deallocate_one(int lineno, const char *name)
{
struct prepared_statement *this,
*prev;
@@ -147,8 +179,26 @@ ECPGdeallocate_one(int lineno, const char *name)
for (this = prep_stmts, prev = NULL; this != NULL && strcmp(this->name, name) != 0; prev = this, this = this->next);
if (this)
{
+ /* first deallocate the statement in the backend */
+ if (this->prepared)
+ {
+ char *text;
+ PGresult *query;
+
+ if (!(text = (char *) ECPGalloc(strlen("deallocate \"\" ") + strlen(this->name), this->stmt->lineno)))
+ return false;
+ else
+ {
+ sprintf(text, "deallocate \"%s\"", this->name);
+ query = PQexec(this->stmt->connection->connection, text);
+ ECPGfree(text);
+ if (!ECPGcheck_PQresult(query, lineno, this->stmt->connection->connection, this->stmt->compat))
+ return false;
+ PQclear(query);
+ }
+ }
+
/* okay, free all the resources */
- ECPGfree(this->name);
ECPGfree(this->stmt->command);
ECPGfree(this->stmt);
if (prev != NULL)
@@ -162,13 +212,36 @@ ECPGdeallocate_one(int lineno, const char *name)
return false;
}
+/* handle the EXEC SQL DEALLOCATE PREPARE statement */
bool
-ECPGdeallocate_all(int lineno)
+ECPGdeallocate(int lineno, int c, const char *name)
+{
+ bool ret = deallocate_one(lineno, name);
+ enum COMPAT_MODE compat = c;
+
+ ECPGlog("ECPGdeallocate line %d: NAME: %s\n", lineno, name);
+ if (INFORMIX_MODE(compat))
+ {
+ /*
+ * Just ignore all errors since we do not know the list of cursors we
+ * are allowed to free. We have to trust the software.
+ */
+ return true;
+ }
+
+ if (!ret)
+ ECPGraise(lineno, ECPG_INVALID_STMT, ECPG_SQLSTATE_INVALID_SQL_STATEMENT_NAME, name);
+
+ return ret;
+}
+
+bool
+ECPGdeallocate_all(int lineno, int compat)
{
/* deallocate all prepared statements */
while (prep_stmts != NULL)
{
- bool b = ECPGdeallocate(lineno, ECPG_COMPAT_PGSQL, prep_stmts->name);
+ bool b = ECPGdeallocate(lineno, compat, prep_stmts->name);
if (!b)
return false;
@@ -177,12 +250,204 @@ ECPGdeallocate_all(int lineno)
return true;
}
+char *
+ECPGprepared(const char *name, int lineno)
+{
+ struct prepared_statement *this;
+
+ for (this = prep_stmts; this != NULL && ((strcmp(this->name, name) != 0) || this->prepared == false); this = this->next);
+ return (this) ? this->stmt->command : NULL;
+}
+
/* return the prepared statement */
char *
-ECPGprepared_statement(const char *name)
+ECPGprepared_statement(const char *name, int lineno)
{
struct prepared_statement *this;
for (this = prep_stmts; this != NULL && strcmp(this->name, name) != 0; this = this->next);
return (this) ? this->stmt->command : NULL;
}
+
+/*
+ * hash a SQL statement - returns entry # of first entry in the bucket
+ */
+static int
+HashStmt(const char *ecpgQuery)
+{
+ int stmtIx, bucketNo, hashLeng, stmtLeng;
+ long long 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 + (int) ecpgQuery[stmtIx];
+ hashVal = hashVal << 13;
+ rotVal = (hashVal & 0x1fff00000000LL) >> 32;
+ hashVal = (hashVal & 0xffffffffLL) | rotVal;
+ }
+
+ bucketNo = hashVal % stmtCacheNBuckets;
+ bucketNo += 1; /* don't use bucket # 0 */
+
+ return (bucketNo * stmtCacheEntPerBucket);
+}
+
+/*
+ * 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;
+
+/* 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))
+ 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
+ECPGfreeStmtCacheEntry(int entNo) /* entry # to free */
+{
+ stmtCacheEntry *entry;
+ PGresult *results;
+ char deallocText[100];
+ struct connection *con;
+
+ entry = &stmtCacheEntries[entNo];
+ if(!entry->stmtID[0]) /* return if the entry isn't in use */
+ return(0);
+
+ con = ECPGget_connection(entry->connection);
+/* free the server resources for the statement */
+ ECPGlog("ECPGfreeStmtCacheEntry line %d: deallocate %s, cache entry #%d\n", entry->lineno, entry->stmtID, entNo);
+ sprintf(deallocText, "DEALLOCATE PREPARE %s", entry->stmtID);
+ results = PQexec(con->connection, deallocText);
+
+ if (!ECPGcheck_PQresult(results, entry->lineno, con->connection, ECPG_COMPAT_PGSQL))
+ return(-1);
+ PQclear(results);
+
+ entry->stmtID[0] = '\0';
+
+/* free the memory used by the cache entry */
+ if(entry->ecpgQuery)
+ {
+ ECPGfree(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 */
+ char *stmtID, /* statement ID */
+ const char *connection, /* connection */
+ const char *ecpgQuery) /* query */
+{
+ int ix, initEntNo, luEntNo, entNo;
+ stmtCacheEntry *entry;
+
+/* 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 - use the 'least used' entry found in the bucket */
+ if(ix >= stmtCacheEntPerBucket) /* if no unused entries were found */
+ entNo = luEntNo; /* re-use the 'least used' entry */
+
+/* 'entNo' is the entry to use - make sure its free */
+ if (ECPGfreeStmtCacheEntry(entNo) < 0)
+ return (-1);
+
+/* add the query to the entry */
+ entry = &stmtCacheEntries[entNo];
+ entry->lineno = lineno;
+ entry->ecpgQuery = ECPGstrdup(ecpgQuery, lineno);
+ entry->connection = (char *)connection;
+ entry->execs = 0;
+ memcpy(entry->stmtID, stmtID, sizeof(entry->stmtID));
+
+ return(entNo);
+}
+
+/* handle cache and preparation of statments in auto-prepare mode */
+bool
+ECPGauto_prepare(int lineno, const char *connection_name, const int questionmarks, 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)
+ {
+ ECPGlog("ECPGauto_prepare line %d: stmt found in cache, entry %d\n", lineno, entNo);
+ *name = ECPGstrdup(stmtCacheEntries[entNo].stmtID, lineno);
+ }
+ else
+ {
+ ECPGlog("ECPGauto_prepare line %d: stmt not in cache; inserting\n", lineno);
+
+ /* generate a statement ID */
+ *name = (char *) ECPGalloc(STMTID_SIZE, lineno);
+ sprintf(*name, "ecpg%d", nextStmtID++);
+
+ if (!ECPGprepare(lineno, connection_name, questionmarks, ECPGstrdup(*name, lineno), query))
+ return(false);
+ if (AddStmtToCache(lineno, *name, connection_name, query) < 0)
+ return(false);
+ }
+
+ /* increase usage counter */
+ stmtCacheEntries[entNo].execs++;
+
+ return(true);
+}
+