*** 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 <time.h>
+ #include <sys/time.h>
+ 
+ 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 <stmt-id>"  */
!     /* 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);
  
