diff options
Diffstat (limited to 'src/interfaces/ecpg/ecpglib/prepare.c')
-rw-r--r-- | src/interfaces/ecpg/ecpglib/prepare.c | 367 |
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); +} + |