603 lines
15 KiB
C
603 lines
15 KiB
C
/* 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;
|
|
}
|