1ac455939Sdan /* 2ac455939Sdan ** 2012 November 26 3ac455939Sdan ** 4ac455939Sdan ** The author disclaims copyright to this source code. In place of 5ac455939Sdan ** a legal notice, here is a blessing: 6ac455939Sdan ** 7ac455939Sdan ** May you do good and not evil. 8ac455939Sdan ** May you find forgiveness for yourself and forgive others. 9ac455939Sdan ** May you share freely, never taking more than you give. 10ac455939Sdan ** 11ac455939Sdan ************************************************************************* 12ac455939Sdan ** 13ac455939Sdan ** OVERVIEW 14ac455939Sdan ** 15ac455939Sdan ** This file contains experimental code used to record data from live 16ac455939Sdan ** SQLite applications that may be useful for offline analysis. Specifically: 17ac455939Sdan ** 18ac455939Sdan ** 1) The initial contents of all database files opened by the 19ac455939Sdan ** application, and 20ac455939Sdan ** 21ac455939Sdan ** 2) All SQL statements executed by the application. 22ac455939Sdan ** 23ac455939Sdan ** USAGE 24ac455939Sdan ** 25ac455939Sdan ** To use this module, SQLite must be compiled with the SQLITE_ENABLE_SQLLOG 26ac455939Sdan ** pre-processor symbol defined and this file linked into the application 27ac455939Sdan ** somehow. 28ac455939Sdan ** 29ac455939Sdan ** At runtime, logging is enabled by setting environment variable 30ac455939Sdan ** SQLITE_SQLLOG_DIR to the name of a directory in which to store logged 31ac455939Sdan ** data. The directory must already exist. 32ac455939Sdan ** 33ac455939Sdan ** Usually, if the application opens the same database file more than once 34ac455939Sdan ** (either by attaching it or by using more than one database handle), only 35ac455939Sdan ** a single copy is made. This behaviour may be overridden (so that a 36ac455939Sdan ** separate copy is taken each time the database file is opened or attached) 37ac455939Sdan ** by setting the environment variable SQLITE_SQLLOG_REUSE_FILES to 0. 38ac455939Sdan ** 39ac455939Sdan ** OUTPUT: 40ac455939Sdan ** 41ac455939Sdan ** The SQLITE_SQLLOG_DIR is populated with three types of files: 42ac455939Sdan ** 43ac455939Sdan ** sqllog_N.db - Copies of database files. N may be any integer. 44ac455939Sdan ** 45ac455939Sdan ** sqllog_N.sql - A list of SQL statements executed by a single 46ac455939Sdan ** connection. N may be any integer. 47ac455939Sdan ** 48ac455939Sdan ** sqllog.idx - An index mapping from integer N to a database 49ac455939Sdan ** file name - indicating the full path of the 50ac455939Sdan ** database from which sqllog_N.db was copied. 51*71ba10d3Sdan ** 52*71ba10d3Sdan ** ERROR HANDLING: 53*71ba10d3Sdan ** 54*71ba10d3Sdan ** This module attempts to make a best effort to continue logging if an 55*71ba10d3Sdan ** IO or other error is encountered. For example, if a log file cannot 56*71ba10d3Sdan ** be opened logs are not collected for that connection, but other 57*71ba10d3Sdan ** logging proceeds as expected. Errors are logged by calling sqlite3_log(). 58ac455939Sdan */ 59ac455939Sdan 60ac455939Sdan #include "sqlite3.h" 61ac455939Sdan #include "stdio.h" 62ac455939Sdan #include "stdlib.h" 63ac455939Sdan #include "string.h" 64ac455939Sdan #include "assert.h" 65ac455939Sdan 66*71ba10d3Sdan #include "sys/types.h" 67*71ba10d3Sdan #include "unistd.h" 68*71ba10d3Sdan static int getProcessId(void){ 69*71ba10d3Sdan return (int)getpid(); 70*71ba10d3Sdan } 71*71ba10d3Sdan 72*71ba10d3Sdan 73ac455939Sdan #define ENVIRONMENT_VARIABLE1_NAME "SQLITE_SQLLOG_DIR" 74ac455939Sdan #define ENVIRONMENT_VARIABLE2_NAME "SQLITE_SQLLOG_REUSE_FILES" 75ac455939Sdan 76ac455939Sdan /* Assume that all database and database file names are shorted than this. */ 77ac455939Sdan #define SQLLOG_NAMESZ 512 78ac455939Sdan 79ac455939Sdan /* Maximum number of simultaneous database connections the process may 80ac455939Sdan ** open (if any more are opened an error is logged using sqlite3_log() 81ac455939Sdan ** and processing is halted). 82ac455939Sdan */ 83ac455939Sdan #define MAX_CONNECTIONS 256 84ac455939Sdan 85ac455939Sdan struct SLConn { 86ac455939Sdan int isErr; /* True if an error has occurred */ 87ac455939Sdan sqlite3 *db; /* Connection handle */ 88ac455939Sdan int iLog; /* First integer value used in file names */ 89ac455939Sdan FILE *fd; /* File descriptor for log file */ 90ac455939Sdan }; 91ac455939Sdan 92ac455939Sdan struct SLGlobal { 93ac455939Sdan /* Protected by MUTEX_STATIC_MASTER */ 94ac455939Sdan sqlite3_mutex *mutex; /* Recursive mutex */ 95ac455939Sdan int nConn; /* Size of aConn[] array */ 96ac455939Sdan 97ac455939Sdan /* Protected by SLGlobal.mutex */ 98ac455939Sdan int bReuse; /* True to avoid extra copies of db files */ 99*71ba10d3Sdan char zPrefix[SQLLOG_NAMESZ]; /* Prefix for all created files */ 100ac455939Sdan char zIdx[SQLLOG_NAMESZ]; /* Full path to *.idx file */ 101ac455939Sdan int iNextLog; /* Used to allocate file names */ 102ac455939Sdan int iNextDb; /* Used to allocate database file names */ 103ac455939Sdan int bRec; /* True if testSqllog() is called rec. */ 104ac455939Sdan int iClock; /* Clock value */ 105ac455939Sdan struct SLConn aConn[MAX_CONNECTIONS]; 106ac455939Sdan } sqllogglobal; 107ac455939Sdan 108ac455939Sdan /* 109ac455939Sdan ** Return true if c is an ASCII whitespace character. 110ac455939Sdan */ 111ac455939Sdan static int sqllog_isspace(char c){ 112ac455939Sdan return (c==' ' || c=='\t' || c=='\n' || c=='\v' || c=='\f' || c=='\r'); 113ac455939Sdan } 114ac455939Sdan 115ac455939Sdan /* 116ac455939Sdan ** The first argument points to a nul-terminated string containing an SQL 117ac455939Sdan ** command. Before returning, this function sets *pz to point to the start 118ac455939Sdan ** of the first token in this command, and *pn to the number of bytes in 119ac455939Sdan ** the token. This is used to check if the SQL command is an "ATTACH" or 120ac455939Sdan ** not. 121ac455939Sdan */ 122ac455939Sdan static void sqllogTokenize(const char *z, const char **pz, int *pn){ 123ac455939Sdan const char *p = z; 124ac455939Sdan int n; 125ac455939Sdan 126ac455939Sdan /* Skip past any whitespace */ 127ac455939Sdan while( sqllog_isspace(*p) ){ 128ac455939Sdan p++; 129ac455939Sdan } 130ac455939Sdan 131ac455939Sdan /* Figure out how long the first token is */ 132ac455939Sdan *pz = p; 133ac455939Sdan n = 0; 134ac455939Sdan while( (p[n]>='a' && p[n]<='z') || (p[n]>='A' && p[n]<='Z') ) n++; 135ac455939Sdan *pn = n; 136ac455939Sdan } 137ac455939Sdan 138ac455939Sdan /* 139ac455939Sdan ** Check if the logs directory already contains a copy of database file 140ac455939Sdan ** zFile. If so, return a pointer to the full path of the copy. Otherwise, 141ac455939Sdan ** return NULL. 142ac455939Sdan ** 143ac455939Sdan ** If a non-NULL value is returned, then the caller must arrange to 144ac455939Sdan ** eventually free it using sqlite3_free(). 145ac455939Sdan */ 146ac455939Sdan static char *sqllogFindFile(const char *zFile){ 147ac455939Sdan char *zRet = 0; 148ac455939Sdan FILE *fd = 0; 149ac455939Sdan 150ac455939Sdan /* Open the index file for reading */ 151ac455939Sdan fd = fopen(sqllogglobal.zIdx, "r"); 152*71ba10d3Sdan if( fd==0 ){ 153*71ba10d3Sdan sqlite3_log(SQLITE_IOERR, "sqllogFindFile(): error in fopen()"); 154*71ba10d3Sdan return 0; 155*71ba10d3Sdan } 156ac455939Sdan 157*71ba10d3Sdan /* Loop through each entry in the index file. If zFile is not NULL and the 158*71ba10d3Sdan ** entry is a match, then set zRet to point to the filename of the existing 159*71ba10d3Sdan ** copy and break out of the loop. */ 160ac455939Sdan while( feof(fd)==0 ){ 161ac455939Sdan char zLine[SQLLOG_NAMESZ*2+5]; 162*71ba10d3Sdan if( fgets(zLine, sizeof(zLine), fd) ){ 163*71ba10d3Sdan int n; 164*71ba10d3Sdan char *z; 165ac455939Sdan 166*71ba10d3Sdan zLine[sizeof(zLine)-1] = '\0'; 167ac455939Sdan z = zLine; 168ac455939Sdan while( *z>='0' && *z<='9' ) z++; 169ac455939Sdan while( *z==' ' ) z++; 170ac455939Sdan 171ac455939Sdan n = strlen(z); 172ac455939Sdan while( n>0 && sqllog_isspace(z[n-1]) ) n--; 173*71ba10d3Sdan 174ac455939Sdan if( n==strlen(zFile) && 0==memcmp(zFile, z, n) ){ 175ac455939Sdan char zBuf[16]; 176*71ba10d3Sdan memset(zBuf, 0, sizeof(zBuf)); 177ac455939Sdan z = zLine; 178ac455939Sdan while( *z>='0' && *z<='9' ){ 179ac455939Sdan zBuf[z-zLine] = *z; 180ac455939Sdan z++; 181ac455939Sdan } 182*71ba10d3Sdan zRet = sqlite3_mprintf("%s_%s.db", sqllogglobal.zPrefix, zBuf); 183ac455939Sdan break; 184ac455939Sdan } 185ac455939Sdan } 186*71ba10d3Sdan } 187*71ba10d3Sdan 188*71ba10d3Sdan if( ferror(fd) ){ 189*71ba10d3Sdan sqlite3_log(SQLITE_IOERR, "sqllogFindFile(): error reading index file"); 190*71ba10d3Sdan } 191ac455939Sdan 192ac455939Sdan fclose(fd); 193ac455939Sdan return zRet; 194ac455939Sdan } 195ac455939Sdan 196*71ba10d3Sdan static int sqllogFindAttached( 197ac455939Sdan struct SLConn *p, /* Database connection */ 198ac455939Sdan const char *zSearch, /* Name to search for (or NULL) */ 199ac455939Sdan char *zName, /* OUT: Name of attached database */ 200ac455939Sdan char *zFile /* OUT: Name of attached file */ 201ac455939Sdan ){ 202ac455939Sdan sqlite3_stmt *pStmt; 203ac455939Sdan int rc; 204ac455939Sdan 205ac455939Sdan /* The "PRAGMA database_list" command returns a list of databases in the 206ac455939Sdan ** order that they were attached. So a newly attached database is 207ac455939Sdan ** described by the last row returned. */ 208ac455939Sdan assert( sqllogglobal.bRec==0 ); 209ac455939Sdan sqllogglobal.bRec = 1; 210ac455939Sdan rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); 211ac455939Sdan if( rc==SQLITE_OK ){ 212ac455939Sdan while( SQLITE_ROW==sqlite3_step(pStmt) ){ 213ac455939Sdan char *zVal1; int nVal1; 214ac455939Sdan char *zVal2; int nVal2; 215ac455939Sdan 216ac455939Sdan zVal1 = sqlite3_column_text(pStmt, 1); 217ac455939Sdan nVal1 = sqlite3_column_bytes(pStmt, 1); 218ac455939Sdan memcpy(zName, zVal1, nVal1+1); 219ac455939Sdan 220ac455939Sdan zVal2 = sqlite3_column_text(pStmt, 2); 221ac455939Sdan nVal2 = sqlite3_column_bytes(pStmt, 2); 222ac455939Sdan memcpy(zFile, zVal2, nVal2+1); 223ac455939Sdan 224ac455939Sdan if( zSearch && strlen(zSearch)==nVal1 225ac455939Sdan && 0==sqlite3_strnicmp(zSearch, zVal1, nVal1) 226ac455939Sdan ){ 227ac455939Sdan break; 228ac455939Sdan } 229ac455939Sdan } 230ac455939Sdan rc = sqlite3_finalize(pStmt); 231ac455939Sdan } 232ac455939Sdan sqllogglobal.bRec = 0; 233*71ba10d3Sdan 234*71ba10d3Sdan if( rc!=SQLITE_OK ){ 235*71ba10d3Sdan sqlite3_log(rc, "sqllogFindAttached(): error in \"PRAGMA database_list\""); 236*71ba10d3Sdan } 237*71ba10d3Sdan return rc; 238ac455939Sdan } 239ac455939Sdan 240ac455939Sdan 241ac455939Sdan /* 242ac455939Sdan ** Parameter zSearch is the name of a database attached to the database 243ac455939Sdan ** connection associated with the first argument. This function creates 244ac455939Sdan ** a backup of this database in the logs directory. 245ac455939Sdan ** 246ac455939Sdan ** The name used for the backup file is automatically generated. Call 247ac455939Sdan ** it zFile. 248ac455939Sdan ** 249ac455939Sdan ** If the bLog parameter is true, then a statement of the following form 250ac455939Sdan ** is written to the log file associated with *p: 251ac455939Sdan ** 252ac455939Sdan ** ATTACH 'zFile' AS 'zName'; 253ac455939Sdan ** 254ac455939Sdan ** Otherwise, if bLog is false, a comment is added to the log file: 255ac455939Sdan ** 256ac455939Sdan ** -- Main database file is 'zFile' 257ac455939Sdan ** 258ac455939Sdan ** The SLGlobal.mutex mutex is always held when this function is called. 259ac455939Sdan */ 260ac455939Sdan static void sqllogCopydb(struct SLConn *p, const char *zSearch, int bLog){ 261ac455939Sdan char zName[SQLLOG_NAMESZ]; /* Attached database name */ 262ac455939Sdan char zFile[SQLLOG_NAMESZ]; /* Database file name */ 263ac455939Sdan char *zFree; 264ac455939Sdan char *zInit = 0; 265*71ba10d3Sdan int rc; 266ac455939Sdan 267*71ba10d3Sdan rc = sqllogFindAttached(p, zSearch, zName, zFile); 268*71ba10d3Sdan if( rc!=SQLITE_OK ) return; 269*71ba10d3Sdan 270ac455939Sdan if( zFile[0]=='\0' ){ 271ac455939Sdan zInit = sqlite3_mprintf(""); 272ac455939Sdan }else{ 273ac455939Sdan if( sqllogglobal.bReuse ){ 274ac455939Sdan zInit = sqllogFindFile(zFile); 275ac455939Sdan }else{ 276ac455939Sdan zInit = 0; 277ac455939Sdan } 278ac455939Sdan if( zInit==0 ){ 279ac455939Sdan int rc; 280ac455939Sdan sqlite3 *copy = 0; 281ac455939Sdan int iDb; 282ac455939Sdan 283ac455939Sdan /* Generate a file-name to use for the copy of this database */ 284ac455939Sdan iDb = sqllogglobal.iNextDb++; 285*71ba10d3Sdan zInit = sqlite3_mprintf("%s_%d.db", sqllogglobal.zPrefix, iDb); 286ac455939Sdan 287ac455939Sdan /* Create the backup */ 288ac455939Sdan assert( sqllogglobal.bRec==0 ); 289ac455939Sdan sqllogglobal.bRec = 1; 290ac455939Sdan rc = sqlite3_open(zInit, ©); 291ac455939Sdan if( rc==SQLITE_OK ){ 292ac455939Sdan sqlite3_backup *pBak; 293ac455939Sdan sqlite3_exec(copy, "PRAGMA synchronous = 0", 0, 0, 0); 294ac455939Sdan pBak = sqlite3_backup_init(copy, "main", p->db, zName); 295ac455939Sdan if( pBak ){ 296ac455939Sdan sqlite3_backup_step(pBak, -1); 297*71ba10d3Sdan rc = sqlite3_backup_finish(pBak); 298*71ba10d3Sdan }else{ 299*71ba10d3Sdan rc = sqlite3_errcode(copy); 300ac455939Sdan } 301ac455939Sdan sqlite3_close(copy); 302ac455939Sdan } 303ac455939Sdan sqllogglobal.bRec = 0; 304ac455939Sdan 305*71ba10d3Sdan if( rc==SQLITE_OK ){ 306ac455939Sdan /* Write an entry into the database index file */ 307*71ba10d3Sdan FILE *fd = fopen(sqllogglobal.zIdx, "a"); 308*71ba10d3Sdan if( fd ){ 309ac455939Sdan fprintf(fd, "%d %s\n", iDb, zFile); 310ac455939Sdan fclose(fd); 311ac455939Sdan } 312*71ba10d3Sdan }else{ 313*71ba10d3Sdan sqlite3_log(rc, "sqllogCopydb(): error backing up database"); 314*71ba10d3Sdan } 315*71ba10d3Sdan } 316ac455939Sdan } 317ac455939Sdan 318ac455939Sdan if( bLog ){ 319*71ba10d3Sdan zFree = sqlite3_mprintf("ATTACH '%q' AS '%q'; -- clock=%d\n", 320ac455939Sdan zInit, zName, sqllogglobal.iClock++ 321ac455939Sdan ); 322ac455939Sdan }else{ 323ac455939Sdan zFree = sqlite3_mprintf("-- Main database is '%q'\n", zInit); 324ac455939Sdan } 325ac455939Sdan fprintf(p->fd, "%s", zFree); 326ac455939Sdan sqlite3_free(zFree); 327ac455939Sdan 328ac455939Sdan sqlite3_free(zInit); 329ac455939Sdan } 330ac455939Sdan 331ac455939Sdan /* 332ac455939Sdan ** If it is not already open, open the log file for connection *p. 333ac455939Sdan ** 334ac455939Sdan ** The SLGlobal.mutex mutex is always held when this function is called. 335ac455939Sdan */ 336ac455939Sdan static void sqllogOpenlog(struct SLConn *p){ 337ac455939Sdan /* If the log file has not yet been opened, open it now. */ 338ac455939Sdan if( p->fd==0 ){ 339ac455939Sdan char *zLog; 340ac455939Sdan 341*71ba10d3Sdan /* If it is still NULL, have global.zPrefix point to a copy of 342*71ba10d3Sdan ** environment variable $ENVIRONMENT_VARIABLE1_NAME. */ 343*71ba10d3Sdan if( sqllogglobal.zPrefix[0]==0 ){ 344ac455939Sdan FILE *fd; 345ac455939Sdan char *zVar = getenv(ENVIRONMENT_VARIABLE1_NAME); 346*71ba10d3Sdan if( zVar==0 || strlen(zVar)+10>=(sizeof(sqllogglobal.zPrefix)) ) return; 347*71ba10d3Sdan sprintf(sqllogglobal.zPrefix, "%s/sqllog_%d", zVar, getProcessId()); 348*71ba10d3Sdan sprintf(sqllogglobal.zIdx, "%s.idx", sqllogglobal.zPrefix); 349ac455939Sdan if( getenv(ENVIRONMENT_VARIABLE2_NAME) ){ 350ac455939Sdan sqllogglobal.bReuse = atoi(getenv(ENVIRONMENT_VARIABLE2_NAME)); 351ac455939Sdan } 352ac455939Sdan fd = fopen(sqllogglobal.zIdx, "w"); 353*71ba10d3Sdan if( fd ) fclose(fd); 354ac455939Sdan } 355ac455939Sdan 356ac455939Sdan /* Open the log file */ 357*71ba10d3Sdan zLog = sqlite3_mprintf("%s_%d.sql", sqllogglobal.zPrefix, p->iLog); 358ac455939Sdan p->fd = fopen(zLog, "w"); 359ac455939Sdan sqlite3_free(zLog); 360*71ba10d3Sdan if( p->fd==0 ){ 361*71ba10d3Sdan sqlite3_log(SQLITE_IOERR, "sqllogOpenlog(): Failed to open log file"); 362*71ba10d3Sdan } 363ac455939Sdan } 364ac455939Sdan } 365ac455939Sdan 366ac455939Sdan /* 367ac455939Sdan ** This function is called if the SQLLOG callback is invoked to report 368ac455939Sdan ** execution of an SQL statement. Parameter p is the connection the statement 369*71ba10d3Sdan ** was executed by and parameter zSql is the text of the statement itself. 370ac455939Sdan */ 371*71ba10d3Sdan static void testSqllogStmt(struct SLConn *p, const char *zSql){ 372ac455939Sdan const char *zFirst; /* Pointer to first token in zSql */ 373ac455939Sdan int nFirst; /* Size of token zFirst in bytes */ 374ac455939Sdan 375ac455939Sdan sqllogTokenize(zSql, &zFirst, &nFirst); 376ac455939Sdan if( nFirst!=6 || 0!=sqlite3_strnicmp("ATTACH", zFirst, 6) ){ 377ac455939Sdan /* Not an ATTACH statement. Write this directly to the log. */ 378*71ba10d3Sdan fprintf(p->fd, "%s; -- clock=%d\n", zSql, sqllogglobal.iClock++); 379ac455939Sdan }else{ 380ac455939Sdan /* This is an ATTACH statement. Copy the database. */ 381ac455939Sdan sqllogCopydb(p, 0, 1); 382ac455939Sdan } 383ac455939Sdan } 384ac455939Sdan 385ac455939Sdan /* 386ac455939Sdan ** The SQLITE_CONFIG_SQLLOG callback registered by sqlite3_init_sqllog(). 387ac455939Sdan */ 388*71ba10d3Sdan static void testSqllog(void *pCtx, sqlite3 *db, const char *zSql, int eType){ 389ac455939Sdan struct SLConn *p; 390ac455939Sdan sqlite3_mutex *master = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); 391ac455939Sdan 392*71ba10d3Sdan assert( eType==0 || eType==1 || eType==2 ); 393*71ba10d3Sdan assert( (eType==2)==(zSql==0) ); 394ac455939Sdan 395ac455939Sdan /* This is a database open command. */ 396*71ba10d3Sdan if( eType==0 ){ 397ac455939Sdan sqlite3_mutex_enter(master); 398ac455939Sdan if( sqllogglobal.mutex==0 ){ 399ac455939Sdan sqllogglobal.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_RECURSIVE); 400ac455939Sdan } 401ac455939Sdan p = &sqllogglobal.aConn[sqllogglobal.nConn++]; 402ac455939Sdan p->fd = 0; 403ac455939Sdan p->db = db; 404ac455939Sdan p->iLog = sqllogglobal.iNextLog++; 405ac455939Sdan sqlite3_mutex_leave(master); 406ac455939Sdan 407ac455939Sdan /* Open the log and take a copy of the main database file */ 408ac455939Sdan sqlite3_mutex_enter(sqllogglobal.mutex); 409ac455939Sdan if( sqllogglobal.bRec==0 ){ 410ac455939Sdan sqllogOpenlog(p); 411*71ba10d3Sdan if( p->fd ) sqllogCopydb(p, "main", 0); 412ac455939Sdan } 413ac455939Sdan sqlite3_mutex_leave(sqllogglobal.mutex); 414ac455939Sdan } 415ac455939Sdan 416ac455939Sdan else{ 417ac455939Sdan 418ac455939Sdan int i; 419ac455939Sdan for(i=0; i<sqllogglobal.nConn; i++){ 420ac455939Sdan p = &sqllogglobal.aConn[i]; 421ac455939Sdan if( p->db==db ) break; 422ac455939Sdan } 423ac455939Sdan if( i==sqllogglobal.nConn ) return; 424ac455939Sdan 425ac455939Sdan /* A database handle close command */ 426*71ba10d3Sdan if( eType==2 ){ 427ac455939Sdan sqlite3_mutex_enter(master); 428ac455939Sdan if( p->fd ) fclose(p->fd); 429ac455939Sdan p->db = 0; 430ac455939Sdan p->fd = 0; 431ac455939Sdan 432ac455939Sdan sqllogglobal.nConn--; 433ac455939Sdan if( sqllogglobal.nConn==0 ){ 434ac455939Sdan sqlite3_mutex_free(sqllogglobal.mutex); 435ac455939Sdan sqllogglobal.mutex = 0; 436ac455939Sdan }else{ 437ac455939Sdan int nShift = &sqllogglobal.aConn[sqllogglobal.nConn] - p; 438ac455939Sdan if( nShift>0 ){ 439ac455939Sdan memmove(p, &p[1], nShift*sizeof(struct SLConn)); 440ac455939Sdan } 441ac455939Sdan } 442ac455939Sdan sqlite3_mutex_leave(master); 443ac455939Sdan 444ac455939Sdan /* An ordinary SQL command. */ 445*71ba10d3Sdan }else if( p->fd ){ 446ac455939Sdan sqlite3_mutex_enter(sqllogglobal.mutex); 447ac455939Sdan if( sqllogglobal.bRec==0 ){ 448*71ba10d3Sdan testSqllogStmt(p, zSql); 449ac455939Sdan } 450ac455939Sdan sqlite3_mutex_leave(sqllogglobal.mutex); 451ac455939Sdan } 452ac455939Sdan } 453ac455939Sdan } 454ac455939Sdan 455ac455939Sdan /* 456ac455939Sdan ** This function is called either before sqlite3_initialized() or by it. 457ac455939Sdan ** It checks if the SQLITE_SQLLOG_DIR variable is defined, and if so 458ac455939Sdan ** registers an SQLITE_CONFIG_SQLLOG callback to record the applications 459ac455939Sdan ** database activity. 460ac455939Sdan */ 461ac455939Sdan void sqlite3_init_sqllog(void){ 462ac455939Sdan if( getenv(ENVIRONMENT_VARIABLE1_NAME) ){ 463ac455939Sdan if( SQLITE_OK==sqlite3_config(SQLITE_CONFIG_SQLLOG, testSqllog, 0) ){ 464ac455939Sdan memset(&sqllogglobal, 0, sizeof(sqllogglobal)); 465ac455939Sdan sqllogglobal.bReuse = 1; 466ac455939Sdan } 467ac455939Sdan } 468ac455939Sdan } 469ac455939Sdan 470