*** src/interfaces/ecpg/ecpglib/execute.c.orig 2007-04-26 23:56:11.000000000 -0700 --- src/interfaces/ecpg/ecpglib/execute.c 2007-05-07 09:53:12.000000000 -0700 *************** *** 31,36 **** --- 31,70 ---- #include "pgtypes_timestamp.h" #include "pgtypes_interval.h" + #include + #include + + void ECPGdispCacheStats(void); + void ECPGdispCache(void); + + typedef struct + { char id[8]; + int lineno; + char stmtID[32]; + char *ecpgQuery; + char *pqQuery; + long execs; /* # of executions */ + double execTime; /* time to execute prepared statement */ + PGconn *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 long long stmtCacheExecs = 0; + static long long stmtCachePrepares = 0; + static long long stmtCacheFrees = 0; + static long long stmtCacheInserts = 0; + + static double totalPrepareTime = 0.0; + static double totalExecTime = 0.0; + static double totalSearchTime = 0.0; + static double totalHashTime = 0.0; + static double totalInsertTime = 0.0; + static double totalFreeTime = 0.0; + /* * This function returns a newly malloced string that has ' and \ * escaped. *************** *** 1034,1066 **** return true; } static bool ! ECPGexecute(struct statement * stmt) { bool status = false; ! char *copiedquery; char *cmdstat; PGresult *results; PGnotify *notify; struct variable *var; int desc_counter = 0; ! copiedquery = ECPGstrdup(stmt->command, stmt->lineno); ! ! /* ! * Now, if the type is one of the fill in types then we take the argument ! * and enter that in the string at the first %s position. Then if there ! * are any more fill in types we fill in at the next and so on. ! */ var = stmt->inlist; ! while (var) { - char *newcopy = NULL; const char *tobeinserted; - char *p; bool malloced = FALSE; - int hostvarl = 0; tobeinserted = NULL; --- 1068,1422 ---- return true; } + /* + * display SQL cache + */ + void + ECPGdispCache(void) + { + int bucketNo, ix, entNo; + stmtCacheEntry *entry; + + for(bucketNo = 1; bucketNo < stmtCacheNBuckets + 1; ++bucketNo) + { printf("-- bucket # %5.5d\n", bucketNo); + for(ix = 0; ix < stmtCacheEntPerBucket; ++ix) + { entNo = bucketNo * stmtCacheEntPerBucket + ix; + entry = &stmtCacheEntries[entNo]; + if(entry->stmtID[0]) + printf(" entry # %d: %s\n", entNo, entry->ecpgQuery); + } + } + fflush(stdout); + } + + /* + * display SQL cache statistics + */ + void + ECPGdispCacheStats(void) + { + int bucketNo, ix, entNo, fillCount, fillMax, noCacheEntries, leng, histo[80]; + char timeStr[50]; + time_t clock; + FILE *otFile; + + otFile = stdout; + + /* display title line with date/time */ + clock = time(0); + strcpy(timeStr, ctime(&clock)); + leng = strlen(timeStr); + if(leng && timeStr[leng - 1] < ' ') + timeStr[leng - 1] = '\0'; + fprintf(otFile, "\n"); + fprintf(otFile, "---- %s ----------------------------\n", timeStr); + + /* gather statistics to display */ + noCacheEntries = 0; + fillMax = 0; + memset(histo, 0, sizeof(histo)); + for(bucketNo = 1; bucketNo < stmtCacheNBuckets + 1; ++bucketNo) + { fillCount = 0; + for(ix = 0; ix < stmtCacheEntPerBucket; ++ix) + { entNo = bucketNo * stmtCacheEntPerBucket + ix; + if(stmtCacheEntries[entNo].stmtID[0]) + ++fillCount; + } + ++histo[fillCount]; /* incr count of buckets w/ this many entries */ + noCacheEntries += fillCount; /* incr total # of entries used */ + if(fillCount > fillMax) /* get value of max cache fill */ + fillMax = fillCount; + } + + /* display the statistics */ + fprintf(otFile, "-- # of cache buckets - %d\n", stmtCacheNBuckets); + fprintf(otFile, "-- # of entries per bucket - %d\n", stmtCacheEntPerBucket); + fprintf(otFile, "-- # of SQL statements in cache - %d\n", noCacheEntries); + fprintf(otFile, "-- # 'insert cache entry' execs - %lld\n", stmtCacheInserts); + fprintf(otFile, "-- # 'free cache entry' execs - %lld\n", stmtCacheFrees); + fprintf(otFile, "--\n"); + for(ix = 0; ix <= fillMax; ++ix) + fprintf(otFile, " %6d buckets contain %d entries\n", histo[ix], ix); + + fprintf(otFile, "-- total # of statements executed - %lld\n", stmtCacheExecs); + fprintf(otFile, "-- # of statements prepared - %lld\n", stmtCachePrepares); + + fprintf(otFile, "-- total 'prepare statement' time %16.6f seconds\n", totalPrepareTime); + fprintf(otFile, "-- total 'execute statement' time %16.6f seconds\n", totalExecTime); + fprintf(otFile, "-- total 'hash statement' time %16.6f seconds\n", totalHashTime); + fprintf(otFile, "-- total 'search cache' time %16.6f seconds\n", totalSearchTime); + fprintf(otFile, "-- total 'insert cache entry' time %16.6f seconds\n", totalInsertTime); + fprintf(otFile, "-- total 'free cache entry' time %16.6f seconds\n", totalFreeTime); + } + + /* + * Build a SQL statement for the server - replace '?' variable placeholders in + * a query from ECPG with '$n' placeholders required by the postgres server. + * Returns # of host variables replaced OR negative error code + */ + static int + ECPGbuildPQstmt(int lineno, + char *ecpgQuery, /* input ECPG format query */ + char **retQuery) /* returned new PQ format query */ + { int varNo, varNameLeng; + char *pqQuery, *hv, varName[20]; + + if(!(pqQuery = (char *) ECPGalloc(strlen(ecpgQuery) + 1000, lineno))) + return(-1); + strcpy(pqQuery, ecpgQuery); + + for(varNo = 0; varNo < 10000; ++varNo) + { + if((hv = next_insert(pqQuery)) == 0) + break; + sprintf(varName, "$%d", varNo + 1); + varNameLeng = strlen(varName); + memmove(hv + varNameLeng, hv + 1, strlen(hv + 1)); + memcpy(hv, varName, varNameLeng); + } + *retQuery = pqQuery; + + return(varNo); + } + + /* + * hash a SQL statement - returns entry # of first entry in the bucket + */ + static int + ECPGhashStmt(char *ecpgQuery, /* statement to hash */ + int *retBucketNo) /* returned bucket # */ + { + int stmtIx, bucketNo, entryNo, hashLeng, stmtLeng; + long long hashVal, rotVal; + double lapseHashTime; + struct timeval startHashTime, endHashTime; + + gettimeofday(&startHashTime, (struct timezone *)0); + + 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 */ + entryNo = bucketNo * stmtCacheEntPerBucket; /* get entry # */ + + gettimeofday(&endHashTime, (struct timezone *)0); + lapseHashTime = (((double) endHashTime.tv_sec) + (((double) endHashTime.tv_usec) / 1000000)) + - (((double) startHashTime.tv_sec) + (((double) startHashTime.tv_usec) / 1000000)); + totalHashTime += lapseHashTime; + + *retBucketNo = bucketNo; + return(entryNo); + } + + /* + * 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 + ECPGsearchStmtCache(char *ecpgQuery, /* stmt to search for */ + int *retBucketNo) /* returned bucket # */ + { + int entNo, entIx, bucketNo; + double lapseSearchTime; + struct timeval startSearchTime, endSearchTime; + + gettimeofday(&startSearchTime, (struct timezone *)0); + + /* hash the statement */ + entNo = ECPGhashStmt(ecpgQuery, &bucketNo); + + /* 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; + + /* done - accumulate statistics and return */ + gettimeofday(&endSearchTime, (struct timezone *)0); + lapseSearchTime = (((double) endSearchTime.tv_sec) + (((double) endSearchTime.tv_usec) / 1000000)) + - (((double) startSearchTime.tv_sec) + (((double) startSearchTime.tv_usec) / 1000000)); + totalSearchTime += lapseSearchTime; + + *retBucketNo = bucketNo; + 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; + long resultSts; + PGresult *results; + double lapseFreeTime; + struct timeval startFreeTime, endFreeTime; + char deallocText[100]; + + entry = &stmtCacheEntries[entNo]; + if(!entry->stmtID[0]) /* return if the entry isn't in use */ + return(0); + + gettimeofday(&startFreeTime, (struct timezone *) 0); + ++stmtCacheFrees; /* incr # of cache free's */ + + /* free the server resources for the statement */ + ECPGlog("ECPGfreeStmtCacheEntry line %d: freeing cache entry #%d\n", entry->lineno, entNo); + sprintf(deallocText, "DEALLOCATE PREPARE %s", entry->stmtID); + if((results = PQexec(entry->connection, deallocText)) == NULL) + { + ECPGlog("ECPGfreeStmtCacheEntry line %d: deallocate stmt, cache entry #%d\n", entry->lineno, entNo); + ECPGlog("ECPGfreeStmtCacheEntry results NULL\n"); + /* continue */ + } + else + { + resultSts = PQresultStatus(results); + if(resultSts != PGRES_COMMAND_OK) + { ECPGlog("ECPGfreeStmtCacheEntry line %d: deallocate stmt, cache entry #%d\n", entry->lineno, entNo); + ECPGlog("resultSts = %ld\n", resultSts); + ECPGlog("message = '%s'\n", PQerrorMessage(entry->connection)); + /* continue */ + } + } + PQclear(results); + entry->stmtID[0] = '\0'; + + /* free the memory used by the cache entry */ + if(entry->ecpgQuery) + { + ECPGfree(entry->ecpgQuery); + entry->ecpgQuery = 0; + } + if(entry->pqQuery) + { ECPGfree(entry->pqQuery); + entry->pqQuery = 0; + } + + /* done - accumulate statistics and return */ + gettimeofday(&endFreeTime, (struct timezone *)0); + lapseFreeTime = (((double) endFreeTime.tv_sec) + (((double) endFreeTime.tv_usec) / 1000000)) + - (((double) startFreeTime.tv_sec) + (((double) startFreeTime.tv_usec) / 1000000)); + totalFreeTime += lapseFreeTime; + + return(entNo); + } + + + /* + * add an entry to the statement cache + * returns entry # in cache used OR negative error code + */ + static int + ECPGaddStmtToCache(int lineno, /* line # of statement */ + char *stmtID, /* statement ID */ + PGconn *connection, /* connection */ + char *ecpgQuery, /* query with '?' for vars */ + char *pqQuery) /* query with '$n' for vars */ + { + int ix, initEntNo, luEntNo, entNo, bucketNo; + stmtCacheEntry *entry; + double lapseInsertTime; + struct timeval startInsertTime, endInsertTime; + + gettimeofday(&startInsertTime, (struct timezone *)0); + ++stmtCacheInserts; /* incr # of cache insert's */ + + /* hash the statement */ + initEntNo = ECPGhashStmt(ecpgQuery, &bucketNo); + + /* 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 */ + ECPGfreeStmtCacheEntry(entNo); + + /* add the query to the entry */ + ECPGlog("ECPGaddStmtToCache, line %d: adding stmt to cache, stmt ID: %s\n", lineno, stmtID); + entry = &stmtCacheEntries[entNo]; + memcpy(entry->id, "ECPGSTMT", 8); + entry->lineno = lineno; + entry->ecpgQuery = ecpgQuery; + entry->pqQuery = pqQuery; + entry->connection = connection; + entry->execs = 0; + entry->execTime = 0.0; + memset(entry->stmtID, 0, sizeof(entry->stmtID)); + strncpy(entry->stmtID, stmtID, sizeof(entry->stmtID) - 1); + + gettimeofday(&endInsertTime, (struct timezone *)0); + lapseInsertTime = (((double) endInsertTime.tv_sec) + (((double) endInsertTime.tv_usec) / 1000000)) + - (((double) startInsertTime.tv_sec) + (((double) startInsertTime.tv_usec) / 1000000)); + totalInsertTime += lapseInsertTime; + + return(entNo); + } + static bool ! ECPGexecute(struct statement *stmt) { + int noParams, paramNo, entNo, bucketNo; + long resultSts; bool status = false; ! char *ecpgQuery; char *cmdstat; PGresult *results; PGnotify *notify; struct variable *var; int desc_counter = 0; + stmtCacheEntry *entry; + double lapsePrepareTime, lapseExecTime; + struct timeval startPrepareTime, endPrepareTime, startExecTime, endExecTime; + char *pqQuery = 0; + char stmtID[40]; + bool paramMalloced[1000]; + const char *paramValues[1000]; ! /* build an array of host variable values */ var = stmt->inlist; ! noParams = 0; while (var) { const char *tobeinserted; bool malloced = FALSE; tobeinserted = NULL; *************** *** 1087,1093 **** if (desc == NULL) { ECPGraise(stmt->lineno, ECPG_UNKNOWN_DESCRIPTOR, ECPG_SQLSTATE_INVALID_SQL_DESCRIPTOR_NAME, var->pointer); - ECPGfree(copiedquery); return false; } --- 1443,1448 ---- *************** *** 1118,1126 **** desc_inlist.ind_varcharsize = desc_inlist.ind_arrsize = 1; desc_inlist.ind_offset = 0; } ! if (!ECPGstore_input(stmt->lineno, stmt->force_indicator, &desc_inlist, &tobeinserted, &malloced, true)) { - ECPGfree(copiedquery); return false; } --- 1473,1480 ---- desc_inlist.ind_varcharsize = desc_inlist.ind_arrsize = 1; desc_inlist.ind_offset = 0; } ! if (!ECPGstore_input(stmt->lineno, stmt->force_indicator, &desc_inlist, &tobeinserted, &malloced, false)) { return false; } *************** *** 1136,1236 **** } else { ! if (!ECPGstore_input(stmt->lineno, stmt->force_indicator, var, &tobeinserted, &malloced, true)) return false; } ! if (tobeinserted) ! { ! /* ! * Now tobeinserted points to an area that is to be inserted at ! * the first %s ! */ ! if (!(newcopy = (char *) ECPGalloc(strlen(copiedquery) ! + strlen(tobeinserted) ! + 1, stmt->lineno))) ! { ! ECPGfree(copiedquery); ! return false; ! } ! ! strcpy(newcopy, copiedquery); ! if ((p = next_insert(newcopy + hostvarl)) == NULL) ! { ! /* ! * We have an argument but we dont have the matched up string ! * in the string ! */ ! ECPGraise(stmt->lineno, ECPG_TOO_MANY_ARGUMENTS, ! ECPG_SQLSTATE_USING_CLAUSE_DOES_NOT_MATCH_PARAMETERS, ! NULL); ! ECPGfree(copiedquery); ! ECPGfree(newcopy); ! return false; ! } ! else ! { ! strcpy(p, tobeinserted); ! hostvarl = strlen(newcopy); ! ! /* ! * The strange thing in the second argument is the rest of the ! * string from the old string ! */ ! strcat(newcopy, ! copiedquery ! + (p - newcopy) ! + sizeof("?") - 1 /* don't count the '\0' */ ); ! } ! ! /* ! * Now everything is safely copied to the newcopy. Lets free the ! * oldcopy and let the copiedquery get the var->value from the ! * newcopy. ! */ ! if (malloced) ! { ! ECPGfree((char *) tobeinserted); ! tobeinserted = NULL; ! } ! ! ECPGfree(copiedquery); ! copiedquery = newcopy; ! } if (desc_counter == 0) var = var->next; ! } ! /* Check if there are unmatched things left. */ ! if (next_insert(copiedquery) != NULL) ! { ! ECPGraise(stmt->lineno, ECPG_TOO_FEW_ARGUMENTS, ! ECPG_SQLSTATE_USING_CLAUSE_DOES_NOT_MATCH_PARAMETERS, NULL); ! ECPGfree(copiedquery); ! return false; ! } ! /* Now the request is built. */ if (stmt->connection->committed && !stmt->connection->autocommit) { ! if ((results = PQexec(stmt->connection->connection, "begin transaction")) == NULL) { ! ECPGraise(stmt->lineno, ECPG_TRANS, ECPG_SQLSTATE_TRANSACTION_RESOLUTION_UNKNOWN, NULL); - ECPGfree(copiedquery); return false; } PQclear(results); stmt->connection->committed = false; } ! ECPGlog("ECPGexecute line %d: QUERY: %s on connection %s\n", stmt->lineno, copiedquery, stmt->connection->name); ! results = PQexec(stmt->connection->connection, copiedquery); ! ECPGfree(copiedquery); ! if (results == NULL) { ECPGlog("ECPGexecute line %d: error: %s", stmt->lineno, PQerrorMessage(stmt->connection->connection)); ECPGraise_backend(stmt->lineno, NULL, stmt->connection->connection, stmt->compat); --- 1490,1607 ---- } else { ! if (!ECPGstore_input(stmt->lineno, stmt->force_indicator, var, &tobeinserted, &malloced, false)) return false; } ! /* at this point, 'tobeinserted' points to memory that contains the value of the */ ! /* current parameter; it has been converted from the caller's type to a string. */ ! if (tobeinserted) ! { ! paramValues[noParams] = tobeinserted; ! ! /* 'malloced' indicates that the memory identified by 'tobeinserted' must be */ ! /* free'd after the query is executed. */ ! if (malloced) ! paramMalloced[noParams] = true; ! else ! paramMalloced[noParams] = false; ! ! /* increment the # of parameters to pass to the server */ ! ++noParams; ! } if (desc_counter == 0) var = var->next; ! } ! /* search the statement cache for this statement */ ! entNo = ECPGsearchStmtCache(stmt->command, &bucketNo); ! /* if not found - add the statement to the cache */ ! if(entNo) ! ECPGlog("ECPGexecute, line %d: stmt found in cache, entry %d\n", stmt->lineno, entNo); ! else ! { ! ECPGlog("ECPGexecute, line %d: stmt not in cache; inserting\n", stmt->lineno); ! ! /* convert the SQL statement to '$nnn' vars instead of the '?' vars from ECPG */ ! ECPGbuildPQstmt(stmt->lineno, stmt->command, &pqQuery); ! ! /* generate a statement ID -- name must be lower-case; "deallocate " */ ! /* converts to lower-case and won't find the name, if it is created as upper case */ ! sprintf(stmtID, "ecpg%d", nextStmtID++); ! ! /* prepare the statement */ ! gettimeofday(&startPrepareTime, (struct timezone *)0); ! ++stmtCachePrepares; ! ! ECPGlog("ECPGexecute line %d: PREPARE: %s on connection %s\n", stmt->lineno, stmt->command, stmt->connection->name); ! results = PQprepare(stmt->connection->connection, stmtID, pqQuery, 0, 0); ! resultSts = PQresultStatus(results); ! if(resultSts != PGRES_COMMAND_OK) ! { ECPGlog("ECPGexecute line %d: preparing stmt, cache entry #%d\n", stmt->lineno, entNo); ! ECPGlog("SQL = '%s'\n", stmt->command); ! ECPGlog("resultSts = %ld\n", PQresultStatus(results)); ! ECPGlog("message = '%s'\n", PQerrorMessage(stmt->connection->connection)); ! PQclear(results); ! return(false); ! } ! PQclear(results); ! ! gettimeofday(&endPrepareTime, (struct timezone *)0); ! lapsePrepareTime = (((double) endPrepareTime.tv_sec) + (((double) endPrepareTime.tv_usec) / 1000000)) ! - (((double) startPrepareTime.tv_sec) + (((double) startPrepareTime.tv_usec) / 1000000)); ! totalPrepareTime += lapsePrepareTime; ! ! /* add the statement to the statement cache */ ! ecpgQuery = ECPGstrdup(stmt->command, stmt->lineno); ! entNo = ECPGaddStmtToCache(stmt->lineno, stmtID, stmt->connection->connection, ecpgQuery, pqQuery); ! ECPGlog("ECPGexecute line %d: stmt inserted into cache entry %d\n", stmt->lineno, entNo); ! } ! ! /* The query was either found in the cache or was prepared and put into the cache */ ! /* In either case, it is now in the cache: 'entNo' is its entry # */ ! entry = &stmtCacheEntries[entNo]; + /* execute a "BEGIN TRANSACTION" if we've just committed and this isn't autocommit mode */ if (stmt->connection->committed && !stmt->connection->autocommit) { ! if ((results = PQexec(stmt->connection->connection, "begin transaction")) == NULL) { ! ECPGraise(stmt->lineno, ECPG_TRANS, ECPG_SQLSTATE_TRANSACTION_RESOLUTION_UNKNOWN, NULL); return false; } PQclear(results); stmt->connection->committed = false; } ! /* execute the query */ ! gettimeofday(&startExecTime, (struct timezone *)0); ! ++stmtCacheExecs; ! ++entry->execs; ! ECPGlog("ECPGexecute line %d: EXEC-PREP: %s on connection %s\n", stmt->lineno, stmt->command, stmt->connection->name); ! results = PQexecPrepared(stmt->connection->connection, entry->stmtID, ! noParams, paramValues, /* parameters */ ! NULL, /* don't need param lengths since text */ ! NULL, /* default to all text params */ ! 0); /* ask for text results */ ! gettimeofday(&endExecTime, (struct timezone *)0); ! lapseExecTime = (((double) endExecTime.tv_sec) + (((double) endExecTime.tv_usec) / 1000000)) ! - (((double) startExecTime.tv_sec) + (((double) startExecTime.tv_usec) / 1000000)); ! totalExecTime += lapseExecTime; ! entry->execTime = lapseExecTime; ! ! /* free any memory allocated to contain parameter values */ ! for (paramNo = 0; paramNo < noParams; ++paramNo) ! { ! if (paramMalloced[paramNo]) ! ECPGfree((char *) paramValues[paramNo]); ! } ! /* check the results */ ! if(results == NULL) { ECPGlog("ECPGexecute line %d: error: %s", stmt->lineno, PQerrorMessage(stmt->connection->connection)); ECPGraise_backend(stmt->lineno, NULL, stmt->connection->connection, stmt->compat); *************** *** 1369,1375 **** PQclear(results); } ! /* check for asynchronous returns */ notify = PQnotifies(stmt->connection->connection); if (notify) { --- 1740,1746 ---- PQclear(results); } ! /* check for asynchronous returns */ notify = PQnotifies(stmt->connection->connection); if (notify) { *************** *** 1391,1396 **** --- 1762,1769 ---- char *oldlocale; enum ECPGttype type; struct variable **list; + char *cacheStats; + static int firstTime = 1; /* Make sure we do NOT honor the locale for numeric input/output */ /* since the database wants the standard decimal point */ *************** *** 1549,1554 **** --- 1922,1938 ---- /* initialize auto_mem struct */ ECPGclear_auto_mem(); + + if(firstTime) + { + firstTime = 0; + cacheStats = getenv("ECPGCACHESTATISTICS"); + if(cacheStats && (!strcasecmp(cacheStats, "yes") || !strcasecmp(cacheStats, "y"))) + { + ECPGlog("ECPGdo line %d: ECPGCACHESTATS=%d\n", stmt->lineno, cacheStats); + atexit(ECPGdispCacheStats); + } + } status = ECPGexecute(stmt); free_statement(stmt);