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 16b9830a14Sdrh ** SQLite applications that may be useful for offline analysis. 17b9830a14Sdrh ** Specifically, this module can be used to capture the following 18b9830a14Sdrh ** information: 19ac455939Sdan ** 20ac455939Sdan ** 1) The initial contents of all database files opened by the 21ac455939Sdan ** application, and 22ac455939Sdan ** 23ac455939Sdan ** 2) All SQL statements executed by the application. 24ac455939Sdan ** 25b9830a14Sdrh ** The captured information can then be used to run (for example) 26b9830a14Sdrh ** performance analysis looking for slow queries or to look for 27b9830a14Sdrh ** optimization opportunities in either the application or in SQLite 28b9830a14Sdrh ** itself. 29b9830a14Sdrh ** 30ac455939Sdan ** USAGE 31ac455939Sdan ** 32ac455939Sdan ** To use this module, SQLite must be compiled with the SQLITE_ENABLE_SQLLOG 33b9830a14Sdrh ** pre-processor symbol defined and this file linked into the application. 34b9830a14Sdrh ** One way to link this file into the application is to append the content 35b9830a14Sdrh ** of this file onto the end of the "sqlite3.c" amalgamation and then 36b9830a14Sdrh ** recompile the application as normal except with the addition of the 37b9830a14Sdrh ** -DSQLITE_ENABLE_SQLLOG option. 38ac455939Sdan ** 39ac455939Sdan ** At runtime, logging is enabled by setting environment variable 40ac455939Sdan ** SQLITE_SQLLOG_DIR to the name of a directory in which to store logged 41b9830a14Sdrh ** data. The logging directory must already exist. 42ac455939Sdan ** 43ac455939Sdan ** Usually, if the application opens the same database file more than once 44ac455939Sdan ** (either by attaching it or by using more than one database handle), only 4548864df9Smistachkin ** a single copy is made. This behavior may be overridden (so that a 46ac455939Sdan ** separate copy is taken each time the database file is opened or attached) 47ac455939Sdan ** by setting the environment variable SQLITE_SQLLOG_REUSE_FILES to 0. 48ac455939Sdan ** 49d83f7ca1Sdan ** If the environment variable SQLITE_SQLLOG_CONDITIONAL is defined, then 50d83f7ca1Sdan ** logging is only done for database connections if a file named 51d83f7ca1Sdan ** "<database>-sqllog" exists in the same directly as the main database 52d83f7ca1Sdan ** file when it is first opened ("<database>" is replaced by the actual 53d83f7ca1Sdan ** name of the main database file). 54d83f7ca1Sdan ** 55ac455939Sdan ** OUTPUT: 56ac455939Sdan ** 57ac455939Sdan ** The SQLITE_SQLLOG_DIR is populated with three types of files: 58ac455939Sdan ** 59ac455939Sdan ** sqllog_N.db - Copies of database files. N may be any integer. 60ac455939Sdan ** 61ac455939Sdan ** sqllog_N.sql - A list of SQL statements executed by a single 62ac455939Sdan ** connection. N may be any integer. 63ac455939Sdan ** 64ac455939Sdan ** sqllog.idx - An index mapping from integer N to a database 65ac455939Sdan ** file name - indicating the full path of the 66ac455939Sdan ** database from which sqllog_N.db was copied. 6771ba10d3Sdan ** 6871ba10d3Sdan ** ERROR HANDLING: 6971ba10d3Sdan ** 7071ba10d3Sdan ** This module attempts to make a best effort to continue logging if an 7171ba10d3Sdan ** IO or other error is encountered. For example, if a log file cannot 7271ba10d3Sdan ** be opened logs are not collected for that connection, but other 7371ba10d3Sdan ** logging proceeds as expected. Errors are logged by calling sqlite3_log(). 74ac455939Sdan */ 75ac455939Sdan 76b9830a14Sdrh #ifndef _SQLITE3_H_ 77ac455939Sdan #include "sqlite3.h" 78b9830a14Sdrh #endif 79b9830a14Sdrh #include <stdio.h> 80b9830a14Sdrh #include <stdlib.h> 81b9830a14Sdrh #include <string.h> 82b9830a14Sdrh #include <assert.h> 83ac455939Sdan 84b9830a14Sdrh #include <sys/types.h> 85b9830a14Sdrh #include <unistd.h> 8671ba10d3Sdan static int getProcessId(void){ 877d1f0c79Sdrh #if SQLITE_OS_WIN 887d1f0c79Sdrh return (int)_getpid(); 897d1f0c79Sdrh #else 9071ba10d3Sdan return (int)getpid(); 917d1f0c79Sdrh #endif 9271ba10d3Sdan } 9371ba10d3Sdan 94b9830a14Sdrh /* Names of environment variables to be used */ 95ac455939Sdan #define ENVIRONMENT_VARIABLE1_NAME "SQLITE_SQLLOG_DIR" 96ac455939Sdan #define ENVIRONMENT_VARIABLE2_NAME "SQLITE_SQLLOG_REUSE_FILES" 97d83f7ca1Sdan #define ENVIRONMENT_VARIABLE3_NAME "SQLITE_SQLLOG_CONDITIONAL" 98ac455939Sdan 99ac455939Sdan /* Assume that all database and database file names are shorted than this. */ 100ac455939Sdan #define SQLLOG_NAMESZ 512 101ac455939Sdan 102ac455939Sdan /* Maximum number of simultaneous database connections the process may 103ac455939Sdan ** open (if any more are opened an error is logged using sqlite3_log() 104ac455939Sdan ** and processing is halted). 105ac455939Sdan */ 106ac455939Sdan #define MAX_CONNECTIONS 256 107ac455939Sdan 108b9830a14Sdrh /* There is one instance of this object for each SQLite database connection 109b9830a14Sdrh ** that is being logged. 110b9830a14Sdrh */ 111ac455939Sdan struct SLConn { 112ac455939Sdan int isErr; /* True if an error has occurred */ 113ac455939Sdan sqlite3 *db; /* Connection handle */ 114ac455939Sdan int iLog; /* First integer value used in file names */ 115ac455939Sdan FILE *fd; /* File descriptor for log file */ 116ac455939Sdan }; 117ac455939Sdan 118b9830a14Sdrh /* This object is a singleton that keeps track of all data loggers. 119b9830a14Sdrh */ 120b9830a14Sdrh static struct SLGlobal { 121ac455939Sdan /* Protected by MUTEX_STATIC_MASTER */ 122ac455939Sdan sqlite3_mutex *mutex; /* Recursive mutex */ 123ac455939Sdan int nConn; /* Size of aConn[] array */ 124ac455939Sdan 125ac455939Sdan /* Protected by SLGlobal.mutex */ 126d83f7ca1Sdan int bConditional; /* Only trace if *-sqllog file is present */ 127ac455939Sdan int bReuse; /* True to avoid extra copies of db files */ 12871ba10d3Sdan char zPrefix[SQLLOG_NAMESZ]; /* Prefix for all created files */ 129ac455939Sdan char zIdx[SQLLOG_NAMESZ]; /* Full path to *.idx file */ 130ac455939Sdan int iNextLog; /* Used to allocate file names */ 131ac455939Sdan int iNextDb; /* Used to allocate database file names */ 132ac455939Sdan int bRec; /* True if testSqllog() is called rec. */ 133ac455939Sdan int iClock; /* Clock value */ 134ac455939Sdan struct SLConn aConn[MAX_CONNECTIONS]; 135ac455939Sdan } sqllogglobal; 136ac455939Sdan 137ac455939Sdan /* 138ac455939Sdan ** Return true if c is an ASCII whitespace character. 139ac455939Sdan */ 140ac455939Sdan static int sqllog_isspace(char c){ 141ac455939Sdan return (c==' ' || c=='\t' || c=='\n' || c=='\v' || c=='\f' || c=='\r'); 142ac455939Sdan } 143ac455939Sdan 144ac455939Sdan /* 145ac455939Sdan ** The first argument points to a nul-terminated string containing an SQL 146ac455939Sdan ** command. Before returning, this function sets *pz to point to the start 147ac455939Sdan ** of the first token in this command, and *pn to the number of bytes in 148ac455939Sdan ** the token. This is used to check if the SQL command is an "ATTACH" or 149ac455939Sdan ** not. 150ac455939Sdan */ 151ac455939Sdan static void sqllogTokenize(const char *z, const char **pz, int *pn){ 152ac455939Sdan const char *p = z; 153ac455939Sdan int n; 154ac455939Sdan 155ac455939Sdan /* Skip past any whitespace */ 156ac455939Sdan while( sqllog_isspace(*p) ){ 157ac455939Sdan p++; 158ac455939Sdan } 159ac455939Sdan 160ac455939Sdan /* Figure out how long the first token is */ 161ac455939Sdan *pz = p; 162ac455939Sdan n = 0; 163ac455939Sdan while( (p[n]>='a' && p[n]<='z') || (p[n]>='A' && p[n]<='Z') ) n++; 164ac455939Sdan *pn = n; 165ac455939Sdan } 166ac455939Sdan 167ac455939Sdan /* 168ac455939Sdan ** Check if the logs directory already contains a copy of database file 169ac455939Sdan ** zFile. If so, return a pointer to the full path of the copy. Otherwise, 170ac455939Sdan ** return NULL. 171ac455939Sdan ** 172ac455939Sdan ** If a non-NULL value is returned, then the caller must arrange to 173ac455939Sdan ** eventually free it using sqlite3_free(). 174ac455939Sdan */ 175ac455939Sdan static char *sqllogFindFile(const char *zFile){ 176ac455939Sdan char *zRet = 0; 177ac455939Sdan FILE *fd = 0; 178ac455939Sdan 179ac455939Sdan /* Open the index file for reading */ 180ac455939Sdan fd = fopen(sqllogglobal.zIdx, "r"); 18171ba10d3Sdan if( fd==0 ){ 18271ba10d3Sdan sqlite3_log(SQLITE_IOERR, "sqllogFindFile(): error in fopen()"); 18371ba10d3Sdan return 0; 18471ba10d3Sdan } 185ac455939Sdan 18671ba10d3Sdan /* Loop through each entry in the index file. If zFile is not NULL and the 18771ba10d3Sdan ** entry is a match, then set zRet to point to the filename of the existing 18871ba10d3Sdan ** copy and break out of the loop. */ 189ac455939Sdan while( feof(fd)==0 ){ 190ac455939Sdan char zLine[SQLLOG_NAMESZ*2+5]; 19171ba10d3Sdan if( fgets(zLine, sizeof(zLine), fd) ){ 19271ba10d3Sdan int n; 19371ba10d3Sdan char *z; 194ac455939Sdan 19571ba10d3Sdan zLine[sizeof(zLine)-1] = '\0'; 196ac455939Sdan z = zLine; 197ac455939Sdan while( *z>='0' && *z<='9' ) z++; 198ac455939Sdan while( *z==' ' ) z++; 199ac455939Sdan 200ac455939Sdan n = strlen(z); 201ac455939Sdan while( n>0 && sqllog_isspace(z[n-1]) ) n--; 20271ba10d3Sdan 203ac455939Sdan if( n==strlen(zFile) && 0==memcmp(zFile, z, n) ){ 204ac455939Sdan char zBuf[16]; 20571ba10d3Sdan memset(zBuf, 0, sizeof(zBuf)); 206ac455939Sdan z = zLine; 207ac455939Sdan while( *z>='0' && *z<='9' ){ 208ac455939Sdan zBuf[z-zLine] = *z; 209ac455939Sdan z++; 210ac455939Sdan } 21171ba10d3Sdan zRet = sqlite3_mprintf("%s_%s.db", sqllogglobal.zPrefix, zBuf); 212ac455939Sdan break; 213ac455939Sdan } 214ac455939Sdan } 21571ba10d3Sdan } 21671ba10d3Sdan 21771ba10d3Sdan if( ferror(fd) ){ 21871ba10d3Sdan sqlite3_log(SQLITE_IOERR, "sqllogFindFile(): error reading index file"); 21971ba10d3Sdan } 220ac455939Sdan 221ac455939Sdan fclose(fd); 222ac455939Sdan return zRet; 223ac455939Sdan } 224ac455939Sdan 22571ba10d3Sdan static int sqllogFindAttached( 226d83f7ca1Sdan sqlite3 *db, /* Database connection */ 227ac455939Sdan const char *zSearch, /* Name to search for (or NULL) */ 228ac455939Sdan char *zName, /* OUT: Name of attached database */ 229ac455939Sdan char *zFile /* OUT: Name of attached file */ 230ac455939Sdan ){ 231ac455939Sdan sqlite3_stmt *pStmt; 232ac455939Sdan int rc; 233ac455939Sdan 234ac455939Sdan /* The "PRAGMA database_list" command returns a list of databases in the 235ac455939Sdan ** order that they were attached. So a newly attached database is 236ac455939Sdan ** described by the last row returned. */ 237ac455939Sdan assert( sqllogglobal.bRec==0 ); 238ac455939Sdan sqllogglobal.bRec = 1; 239d83f7ca1Sdan rc = sqlite3_prepare_v2(db, "PRAGMA database_list", -1, &pStmt, 0); 240ac455939Sdan if( rc==SQLITE_OK ){ 241ac455939Sdan while( SQLITE_ROW==sqlite3_step(pStmt) ){ 2427d1f0c79Sdrh const char *zVal1; int nVal1; 2437d1f0c79Sdrh const char *zVal2; int nVal2; 244ac455939Sdan 2457d1f0c79Sdrh zVal1 = (const char*)sqlite3_column_text(pStmt, 1); 246ac455939Sdan nVal1 = sqlite3_column_bytes(pStmt, 1); 247d83f7ca1Sdan if( zName ){ 248ac455939Sdan memcpy(zName, zVal1, nVal1+1); 249d83f7ca1Sdan } 250ac455939Sdan 2517d1f0c79Sdrh zVal2 = (const char*)sqlite3_column_text(pStmt, 2); 252ac455939Sdan nVal2 = sqlite3_column_bytes(pStmt, 2); 253ac455939Sdan memcpy(zFile, zVal2, nVal2+1); 254ac455939Sdan 255ac455939Sdan if( zSearch && strlen(zSearch)==nVal1 256ac455939Sdan && 0==sqlite3_strnicmp(zSearch, zVal1, nVal1) 257ac455939Sdan ){ 258ac455939Sdan break; 259ac455939Sdan } 260ac455939Sdan } 261ac455939Sdan rc = sqlite3_finalize(pStmt); 262ac455939Sdan } 263ac455939Sdan sqllogglobal.bRec = 0; 26471ba10d3Sdan 26571ba10d3Sdan if( rc!=SQLITE_OK ){ 26671ba10d3Sdan sqlite3_log(rc, "sqllogFindAttached(): error in \"PRAGMA database_list\""); 26771ba10d3Sdan } 26871ba10d3Sdan return rc; 269ac455939Sdan } 270ac455939Sdan 271ac455939Sdan 272ac455939Sdan /* 273ac455939Sdan ** Parameter zSearch is the name of a database attached to the database 274ac455939Sdan ** connection associated with the first argument. This function creates 275ac455939Sdan ** a backup of this database in the logs directory. 276ac455939Sdan ** 277ac455939Sdan ** The name used for the backup file is automatically generated. Call 278ac455939Sdan ** it zFile. 279ac455939Sdan ** 280ac455939Sdan ** If the bLog parameter is true, then a statement of the following form 281ac455939Sdan ** is written to the log file associated with *p: 282ac455939Sdan ** 283ac455939Sdan ** ATTACH 'zFile' AS 'zName'; 284ac455939Sdan ** 285ac455939Sdan ** Otherwise, if bLog is false, a comment is added to the log file: 286ac455939Sdan ** 287ac455939Sdan ** -- Main database file is 'zFile' 288ac455939Sdan ** 289ac455939Sdan ** The SLGlobal.mutex mutex is always held when this function is called. 290ac455939Sdan */ 291ac455939Sdan static void sqllogCopydb(struct SLConn *p, const char *zSearch, int bLog){ 292ac455939Sdan char zName[SQLLOG_NAMESZ]; /* Attached database name */ 293ac455939Sdan char zFile[SQLLOG_NAMESZ]; /* Database file name */ 294ac455939Sdan char *zFree; 295ac455939Sdan char *zInit = 0; 29671ba10d3Sdan int rc; 297ac455939Sdan 298d83f7ca1Sdan rc = sqllogFindAttached(p->db, zSearch, zName, zFile); 29971ba10d3Sdan if( rc!=SQLITE_OK ) return; 30071ba10d3Sdan 301ac455939Sdan if( zFile[0]=='\0' ){ 302ac455939Sdan zInit = sqlite3_mprintf(""); 303ac455939Sdan }else{ 304ac455939Sdan if( sqllogglobal.bReuse ){ 305ac455939Sdan zInit = sqllogFindFile(zFile); 306ac455939Sdan }else{ 307ac455939Sdan zInit = 0; 308ac455939Sdan } 309ac455939Sdan if( zInit==0 ){ 310ac455939Sdan int rc; 311ac455939Sdan sqlite3 *copy = 0; 312ac455939Sdan int iDb; 313ac455939Sdan 314ac455939Sdan /* Generate a file-name to use for the copy of this database */ 315ac455939Sdan iDb = sqllogglobal.iNextDb++; 316*87e1e56bSdrh zInit = sqlite3_mprintf("%s_%02d.db", sqllogglobal.zPrefix, iDb); 317ac455939Sdan 318ac455939Sdan /* Create the backup */ 319ac455939Sdan assert( sqllogglobal.bRec==0 ); 320ac455939Sdan sqllogglobal.bRec = 1; 321ac455939Sdan rc = sqlite3_open(zInit, ©); 322ac455939Sdan if( rc==SQLITE_OK ){ 323ac455939Sdan sqlite3_backup *pBak; 324ac455939Sdan sqlite3_exec(copy, "PRAGMA synchronous = 0", 0, 0, 0); 325ac455939Sdan pBak = sqlite3_backup_init(copy, "main", p->db, zName); 326ac455939Sdan if( pBak ){ 327ac455939Sdan sqlite3_backup_step(pBak, -1); 32871ba10d3Sdan rc = sqlite3_backup_finish(pBak); 32971ba10d3Sdan }else{ 33071ba10d3Sdan rc = sqlite3_errcode(copy); 331ac455939Sdan } 332ac455939Sdan sqlite3_close(copy); 333ac455939Sdan } 334ac455939Sdan sqllogglobal.bRec = 0; 335ac455939Sdan 33671ba10d3Sdan if( rc==SQLITE_OK ){ 337ac455939Sdan /* Write an entry into the database index file */ 33871ba10d3Sdan FILE *fd = fopen(sqllogglobal.zIdx, "a"); 33971ba10d3Sdan if( fd ){ 340ac455939Sdan fprintf(fd, "%d %s\n", iDb, zFile); 341ac455939Sdan fclose(fd); 342ac455939Sdan } 34371ba10d3Sdan }else{ 34471ba10d3Sdan sqlite3_log(rc, "sqllogCopydb(): error backing up database"); 34571ba10d3Sdan } 34671ba10d3Sdan } 347ac455939Sdan } 348ac455939Sdan 349ac455939Sdan if( bLog ){ 35071ba10d3Sdan zFree = sqlite3_mprintf("ATTACH '%q' AS '%q'; -- clock=%d\n", 351ac455939Sdan zInit, zName, sqllogglobal.iClock++ 352ac455939Sdan ); 353ac455939Sdan }else{ 354ac455939Sdan zFree = sqlite3_mprintf("-- Main database is '%q'\n", zInit); 355ac455939Sdan } 356ac455939Sdan fprintf(p->fd, "%s", zFree); 357ac455939Sdan sqlite3_free(zFree); 358ac455939Sdan 359ac455939Sdan sqlite3_free(zInit); 360ac455939Sdan } 361ac455939Sdan 362ac455939Sdan /* 363ac455939Sdan ** If it is not already open, open the log file for connection *p. 364ac455939Sdan ** 365ac455939Sdan ** The SLGlobal.mutex mutex is always held when this function is called. 366ac455939Sdan */ 367ac455939Sdan static void sqllogOpenlog(struct SLConn *p){ 368ac455939Sdan /* If the log file has not yet been opened, open it now. */ 369ac455939Sdan if( p->fd==0 ){ 370ac455939Sdan char *zLog; 371ac455939Sdan 37271ba10d3Sdan /* If it is still NULL, have global.zPrefix point to a copy of 37371ba10d3Sdan ** environment variable $ENVIRONMENT_VARIABLE1_NAME. */ 37471ba10d3Sdan if( sqllogglobal.zPrefix[0]==0 ){ 375ac455939Sdan FILE *fd; 376ac455939Sdan char *zVar = getenv(ENVIRONMENT_VARIABLE1_NAME); 37771ba10d3Sdan if( zVar==0 || strlen(zVar)+10>=(sizeof(sqllogglobal.zPrefix)) ) return; 37865545b59Sdrh sqlite3_snprintf(sizeof(sqllogglobal.zPrefix), sqllogglobal.zPrefix, 379*87e1e56bSdrh "%s/sqllog_%05d", zVar, getProcessId()); 38065545b59Sdrh sqlite3_snprintf(sizeof(sqllogglobal.zIdx), sqllogglobal.zIdx, 38165545b59Sdrh "%s.idx", sqllogglobal.zPrefix); 382ac455939Sdan if( getenv(ENVIRONMENT_VARIABLE2_NAME) ){ 383ac455939Sdan sqllogglobal.bReuse = atoi(getenv(ENVIRONMENT_VARIABLE2_NAME)); 384ac455939Sdan } 385ac455939Sdan fd = fopen(sqllogglobal.zIdx, "w"); 38671ba10d3Sdan if( fd ) fclose(fd); 387ac455939Sdan } 388ac455939Sdan 389ac455939Sdan /* Open the log file */ 390*87e1e56bSdrh zLog = sqlite3_mprintf("%s_%05d.sql", sqllogglobal.zPrefix, p->iLog); 391ac455939Sdan p->fd = fopen(zLog, "w"); 392ac455939Sdan sqlite3_free(zLog); 39371ba10d3Sdan if( p->fd==0 ){ 39471ba10d3Sdan sqlite3_log(SQLITE_IOERR, "sqllogOpenlog(): Failed to open log file"); 39571ba10d3Sdan } 396ac455939Sdan } 397ac455939Sdan } 398ac455939Sdan 399ac455939Sdan /* 400ac455939Sdan ** This function is called if the SQLLOG callback is invoked to report 401ac455939Sdan ** execution of an SQL statement. Parameter p is the connection the statement 40271ba10d3Sdan ** was executed by and parameter zSql is the text of the statement itself. 403ac455939Sdan */ 40471ba10d3Sdan static void testSqllogStmt(struct SLConn *p, const char *zSql){ 405ac455939Sdan const char *zFirst; /* Pointer to first token in zSql */ 406ac455939Sdan int nFirst; /* Size of token zFirst in bytes */ 407ac455939Sdan 408ac455939Sdan sqllogTokenize(zSql, &zFirst, &nFirst); 409ac455939Sdan if( nFirst!=6 || 0!=sqlite3_strnicmp("ATTACH", zFirst, 6) ){ 410ac455939Sdan /* Not an ATTACH statement. Write this directly to the log. */ 41171ba10d3Sdan fprintf(p->fd, "%s; -- clock=%d\n", zSql, sqllogglobal.iClock++); 412ac455939Sdan }else{ 413ac455939Sdan /* This is an ATTACH statement. Copy the database. */ 414ac455939Sdan sqllogCopydb(p, 0, 1); 415ac455939Sdan } 416ac455939Sdan } 417ac455939Sdan 418ac455939Sdan /* 419d83f7ca1Sdan ** The database handle passed as the only argument has just been opened. 420d83f7ca1Sdan ** Return true if this module should log initial databases and SQL 421d83f7ca1Sdan ** statements for this connection, or false otherwise. 422d83f7ca1Sdan ** 423d83f7ca1Sdan ** If an error occurs, sqlite3_log() is invoked to report it to the user 424d83f7ca1Sdan ** and zero returned. 425d83f7ca1Sdan */ 426d83f7ca1Sdan static int sqllogTraceDb(sqlite3 *db){ 427d83f7ca1Sdan int bRet = 1; 428d83f7ca1Sdan if( sqllogglobal.bConditional ){ 429d83f7ca1Sdan char zFile[SQLLOG_NAMESZ]; /* Attached database name */ 430d83f7ca1Sdan int rc = sqllogFindAttached(db, "main", 0, zFile); 431d83f7ca1Sdan if( rc==SQLITE_OK ){ 432d83f7ca1Sdan int nFile = strlen(zFile); 433d83f7ca1Sdan if( (SQLLOG_NAMESZ-nFile)<8 ){ 434d83f7ca1Sdan sqlite3_log(SQLITE_IOERR, 435d83f7ca1Sdan "sqllogTraceDb(): database name too long (%d bytes)", nFile 436d83f7ca1Sdan ); 437d83f7ca1Sdan bRet = 0; 438d83f7ca1Sdan }else{ 439d83f7ca1Sdan memcpy(&zFile[nFile], "-sqllog", 8); 440d83f7ca1Sdan bRet = !access(zFile, F_OK); 441d83f7ca1Sdan } 442d83f7ca1Sdan } 443d83f7ca1Sdan } 444d83f7ca1Sdan return bRet; 445d83f7ca1Sdan } 446d83f7ca1Sdan 447d83f7ca1Sdan /* 448ac455939Sdan ** The SQLITE_CONFIG_SQLLOG callback registered by sqlite3_init_sqllog(). 449b9830a14Sdrh ** 450b9830a14Sdrh ** The eType parameter has the following values: 451b9830a14Sdrh ** 452b9830a14Sdrh ** 0: Opening a new database connection. zSql is the name of the 453b9830a14Sdrh ** file being opened. db is a pointer to the newly created database 454b9830a14Sdrh ** connection. 455b9830a14Sdrh ** 456b9830a14Sdrh ** 1: An SQL statement has run to completion. zSql is the text of the 457b9830a14Sdrh ** SQL statement with all parameters expanded to their actual values. 458b9830a14Sdrh ** 459b9830a14Sdrh ** 2: Closing a database connection. zSql is NULL. The db pointer to 460b9830a14Sdrh ** the database connection being closed has already been shut down 461b9830a14Sdrh ** and cannot be used for any further SQL. 462b9830a14Sdrh ** 463b9830a14Sdrh ** The pCtx parameter is a copy of the pointer that was originally passed 464b9830a14Sdrh ** into the sqlite3_config(SQLITE_CONFIG_SQLLOG) statement. In this 465b9830a14Sdrh ** particular implementation, pCtx is always a pointer to the 466b9830a14Sdrh ** sqllogglobal global variable define above. 467ac455939Sdan */ 46871ba10d3Sdan static void testSqllog(void *pCtx, sqlite3 *db, const char *zSql, int eType){ 469696b33e6Sdrh struct SLConn *p = 0; 470ac455939Sdan sqlite3_mutex *master = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); 471ac455939Sdan 47271ba10d3Sdan assert( eType==0 || eType==1 || eType==2 ); 47371ba10d3Sdan assert( (eType==2)==(zSql==0) ); 474ac455939Sdan 475ac455939Sdan /* This is a database open command. */ 47671ba10d3Sdan if( eType==0 ){ 477ac455939Sdan sqlite3_mutex_enter(master); 478ac455939Sdan if( sqllogglobal.mutex==0 ){ 479ac455939Sdan sqllogglobal.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_RECURSIVE); 480ac455939Sdan } 481d83f7ca1Sdan sqlite3_mutex_leave(master); 482d83f7ca1Sdan 483d83f7ca1Sdan sqlite3_mutex_enter(sqllogglobal.mutex); 484d83f7ca1Sdan if( sqllogglobal.bRec==0 && sqllogTraceDb(db) ){ 485d83f7ca1Sdan 486d83f7ca1Sdan sqlite3_mutex_enter(master); 487ac455939Sdan p = &sqllogglobal.aConn[sqllogglobal.nConn++]; 488ac455939Sdan p->fd = 0; 489ac455939Sdan p->db = db; 490ac455939Sdan p->iLog = sqllogglobal.iNextLog++; 491ac455939Sdan sqlite3_mutex_leave(master); 492ac455939Sdan 493ac455939Sdan /* Open the log and take a copy of the main database file */ 494ac455939Sdan sqllogOpenlog(p); 49571ba10d3Sdan if( p->fd ) sqllogCopydb(p, "main", 0); 496ac455939Sdan } 497ac455939Sdan sqlite3_mutex_leave(sqllogglobal.mutex); 498ac455939Sdan } 499ac455939Sdan 500ac455939Sdan else{ 501ac455939Sdan 502ac455939Sdan int i; 503ac455939Sdan for(i=0; i<sqllogglobal.nConn; i++){ 504ac455939Sdan p = &sqllogglobal.aConn[i]; 505ac455939Sdan if( p->db==db ) break; 506ac455939Sdan } 507ac455939Sdan 508ac455939Sdan /* A database handle close command */ 50971ba10d3Sdan if( eType==2 ){ 510ac455939Sdan sqlite3_mutex_enter(master); 511d83f7ca1Sdan if( i<sqllogglobal.nConn ){ 512ac455939Sdan if( p->fd ) fclose(p->fd); 513ac455939Sdan p->db = 0; 514ac455939Sdan p->fd = 0; 515ac455939Sdan sqllogglobal.nConn--; 516d83f7ca1Sdan } 517d83f7ca1Sdan 518ac455939Sdan if( sqllogglobal.nConn==0 ){ 519ac455939Sdan sqlite3_mutex_free(sqllogglobal.mutex); 520ac455939Sdan sqllogglobal.mutex = 0; 521d83f7ca1Sdan }else if( i<sqllogglobal.nConn ){ 522ac455939Sdan int nShift = &sqllogglobal.aConn[sqllogglobal.nConn] - p; 523ac455939Sdan if( nShift>0 ){ 524ac455939Sdan memmove(p, &p[1], nShift*sizeof(struct SLConn)); 525ac455939Sdan } 526ac455939Sdan } 527ac455939Sdan sqlite3_mutex_leave(master); 528ac455939Sdan 529ac455939Sdan /* An ordinary SQL command. */ 530d83f7ca1Sdan }else if( i<sqllogglobal.nConn && p->fd ){ 531ac455939Sdan sqlite3_mutex_enter(sqllogglobal.mutex); 532ac455939Sdan if( sqllogglobal.bRec==0 ){ 53371ba10d3Sdan testSqllogStmt(p, zSql); 534ac455939Sdan } 535ac455939Sdan sqlite3_mutex_leave(sqllogglobal.mutex); 536ac455939Sdan } 537ac455939Sdan } 538ac455939Sdan } 539ac455939Sdan 540ac455939Sdan /* 541ac455939Sdan ** This function is called either before sqlite3_initialized() or by it. 542ac455939Sdan ** It checks if the SQLITE_SQLLOG_DIR variable is defined, and if so 543ac455939Sdan ** registers an SQLITE_CONFIG_SQLLOG callback to record the applications 544ac455939Sdan ** database activity. 545ac455939Sdan */ 546ac455939Sdan void sqlite3_init_sqllog(void){ 547ac455939Sdan if( getenv(ENVIRONMENT_VARIABLE1_NAME) ){ 548ac455939Sdan if( SQLITE_OK==sqlite3_config(SQLITE_CONFIG_SQLLOG, testSqllog, 0) ){ 549ac455939Sdan memset(&sqllogglobal, 0, sizeof(sqllogglobal)); 550ac455939Sdan sqllogglobal.bReuse = 1; 551d83f7ca1Sdan if( getenv(ENVIRONMENT_VARIABLE3_NAME) ){ 552d83f7ca1Sdan sqllogglobal.bConditional = 1; 553d83f7ca1Sdan } 554ac455939Sdan } 555ac455939Sdan } 556ac455939Sdan } 557