Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -218,10 +218,16 @@ */ sqlite3_mutex_enter(sqlite3GlobalConfig.pInitMutex); if( sqlite3GlobalConfig.isInit==0 && sqlite3GlobalConfig.inProgress==0 ){ FuncDefHash *pHash = &GLOBAL(FuncDefHash, sqlite3GlobalFunctions); sqlite3GlobalConfig.inProgress = 1; +#ifdef SQLITE_INIT_SQLLOG + { + extern void SQLITE_INIT_SQLLOG(void); + SQLITE_INIT_SQLLOG(); + } +#endif memset(pHash, 0, sizeof(sqlite3GlobalFunctions)); sqlite3RegisterGlobalFunctions(); if( sqlite3GlobalConfig.isPCacheInit==0 ){ rc = sqlite3PcacheInitialize(); } Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -525,10 +525,18 @@ */ #ifndef SQLITE_DEFAULT_PCACHE_INITSZ # define SQLITE_DEFAULT_PCACHE_INITSZ 100 #endif +/* +** If SQLITE_INIT_SQLLOG is defined, then SQLITE_ENABLE_SQLLOG is +** automatically defined as well. +*/ +#if defined(SQLITE_INIT_SQLLOG) && !defined(SQLITE_ENABLE_SQLLOG) +# define SQLITE_ENABLE_SQLLOG 1 +#endif + /* ** GCC does not define the offsetof() macro so we'll have to do it ** ourselves. */ Index: src/test1.c ================================================================== --- src/test1.c +++ src/test1.c @@ -2224,10 +2224,32 @@ sqlite3_stmt_scanstatus_reset(pStmt); return TCL_OK; } #endif +#ifdef SQLITE_ENABLE_SQLLOG +/* +** Usage: sqlite3_config_sqllog +** +** Zero the SQLITE_CONFIG_SQLLOG configuration +*/ +static int test_config_sqllog( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + sqlite3_stmt *pStmt; /* First argument */ + if( objc!=1 ){ + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + sqlite3_config(SQLITE_CONFIG_SQLLOG, 0, 0); + return TCL_OK; +} +#endif + /* ** Usage: sqlite3_next_stmt DB STMT ** ** Return the next statment in sequence after STMT. */ @@ -7034,10 +7056,13 @@ #endif #ifdef SQLITE_ENABLE_STMT_SCANSTATUS { "sqlite3_stmt_scanstatus", test_stmt_scanstatus, 0 }, { "sqlite3_stmt_scanstatus_reset", test_stmt_scanstatus_reset, 0 }, #endif +#ifdef SQLITE_ENABLE_SQLLOG + { "sqlite3_config_sqllog", test_config_sqllog, 0 }, +#endif }; static int bitmask_size = sizeof(Bitmask)*8; static int longdouble_size = sizeof(LONGDOUBLE_TYPE); int i; Index: src/test_config.c ================================================================== --- src/test_config.c +++ src/test_config.c @@ -654,10 +654,16 @@ #ifdef YYTRACKMAXSTACKDEPTH Tcl_SetVar2(interp, "sqlite_options", "yytrackmaxstackdepth", "1", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "yytrackmaxstackdepth", "0", TCL_GLOBAL_ONLY); #endif + +#ifdef SQLITE_ENABLE_SQLLOG + Tcl_SetVar2(interp, "sqlite_options", "sqllog", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "sqllog", "0", TCL_GLOBAL_ONLY); +#endif #define LINKVAR(x) { \ static const int cv_ ## x = SQLITE_ ## x; \ Tcl_LinkVar(interp, "SQLITE_" #x, (char *)&(cv_ ## x), \ TCL_LINK_INT | TCL_LINK_READ_ONLY); } Index: src/test_sqllog.c ================================================================== --- src/test_sqllog.c +++ src/test_sqllog.c @@ -44,10 +44,16 @@ ** (either by attaching it or by using more than one database handle), only ** a single copy is made. This behavior may be overridden (so that a ** separate copy is taken each time the database file is opened or attached) ** by setting the environment variable SQLITE_SQLLOG_REUSE_FILES to 0. ** +** If the environment variable SQLITE_SQLLOG_CONDITIONAL is defined, then +** logging is only done for database connections if a file named +** "-sqllog" exists in the same directly as the main database +** file when it is first opened ("" is replaced by the actual +** name of the main database file). +** ** OUTPUT: ** ** The SQLITE_SQLLOG_DIR is populated with three types of files: ** ** sqllog_N.db - Copies of database files. N may be any integer. @@ -86,10 +92,11 @@ } /* Names of environment variables to be used */ #define ENVIRONMENT_VARIABLE1_NAME "SQLITE_SQLLOG_DIR" #define ENVIRONMENT_VARIABLE2_NAME "SQLITE_SQLLOG_REUSE_FILES" +#define ENVIRONMENT_VARIABLE3_NAME "SQLITE_SQLLOG_CONDITIONAL" /* Assume that all database and database file names are shorted than this. */ #define SQLLOG_NAMESZ 512 /* Maximum number of simultaneous database connections the process may @@ -114,10 +121,11 @@ /* Protected by MUTEX_STATIC_MASTER */ sqlite3_mutex *mutex; /* Recursive mutex */ int nConn; /* Size of aConn[] array */ /* Protected by SLGlobal.mutex */ + int bConditional; /* Only trace if *-sqllog file is present */ int bReuse; /* True to avoid extra copies of db files */ char zPrefix[SQLLOG_NAMESZ]; /* Prefix for all created files */ char zIdx[SQLLOG_NAMESZ]; /* Full path to *.idx file */ int iNextLog; /* Used to allocate file names */ int iNextDb; /* Used to allocate database file names */ @@ -213,11 +221,11 @@ fclose(fd); return zRet; } static int sqllogFindAttached( - struct SLConn *p, /* Database connection */ + sqlite3 *db, /* Database connection */ const char *zSearch, /* Name to search for (or NULL) */ char *zName, /* OUT: Name of attached database */ char *zFile /* OUT: Name of attached file */ ){ sqlite3_stmt *pStmt; @@ -226,19 +234,21 @@ /* The "PRAGMA database_list" command returns a list of databases in the ** order that they were attached. So a newly attached database is ** described by the last row returned. */ assert( sqllogglobal.bRec==0 ); sqllogglobal.bRec = 1; - rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); + rc = sqlite3_prepare_v2(db, "PRAGMA database_list", -1, &pStmt, 0); if( rc==SQLITE_OK ){ while( SQLITE_ROW==sqlite3_step(pStmt) ){ const char *zVal1; int nVal1; const char *zVal2; int nVal2; zVal1 = (const char*)sqlite3_column_text(pStmt, 1); nVal1 = sqlite3_column_bytes(pStmt, 1); - memcpy(zName, zVal1, nVal1+1); + if( zName ){ + memcpy(zName, zVal1, nVal1+1); + } zVal2 = (const char*)sqlite3_column_text(pStmt, 2); nVal2 = sqlite3_column_bytes(pStmt, 2); memcpy(zFile, zVal2, nVal2+1); @@ -283,11 +293,11 @@ char zFile[SQLLOG_NAMESZ]; /* Database file name */ char *zFree; char *zInit = 0; int rc; - rc = sqllogFindAttached(p, zSearch, zName, zFile); + rc = sqllogFindAttached(p->db, zSearch, zName, zFile); if( rc!=SQLITE_OK ) return; if( zFile[0]=='\0' ){ zInit = sqlite3_mprintf(""); }else{ @@ -402,10 +412,39 @@ }else{ /* This is an ATTACH statement. Copy the database. */ sqllogCopydb(p, 0, 1); } } + +/* +** The database handle passed as the only argument has just been opened. +** Return true if this module should log initial databases and SQL +** statements for this connection, or false otherwise. +** +** If an error occurs, sqlite3_log() is invoked to report it to the user +** and zero returned. +*/ +static int sqllogTraceDb(sqlite3 *db){ + int bRet = 1; + if( sqllogglobal.bConditional ){ + char zFile[SQLLOG_NAMESZ]; /* Attached database name */ + int rc = sqllogFindAttached(db, "main", 0, zFile); + if( rc==SQLITE_OK ){ + int nFile = strlen(zFile); + if( (SQLLOG_NAMESZ-nFile)<8 ){ + sqlite3_log(SQLITE_IOERR, + "sqllogTraceDb(): database name too long (%d bytes)", nFile + ); + bRet = 0; + }else{ + memcpy(&zFile[nFile], "-sqllog", 8); + bRet = !access(zFile, F_OK); + } + } + } + return bRet; +} /* ** The SQLITE_CONFIG_SQLLOG callback registered by sqlite3_init_sqllog(). ** ** The eType parameter has the following values: @@ -437,19 +476,23 @@ if( eType==0 ){ sqlite3_mutex_enter(master); if( sqllogglobal.mutex==0 ){ sqllogglobal.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_RECURSIVE); } - p = &sqllogglobal.aConn[sqllogglobal.nConn++]; - p->fd = 0; - p->db = db; - p->iLog = sqllogglobal.iNextLog++; sqlite3_mutex_leave(master); - /* Open the log and take a copy of the main database file */ sqlite3_mutex_enter(sqllogglobal.mutex); - if( sqllogglobal.bRec==0 ){ + if( sqllogglobal.bRec==0 && sqllogTraceDb(db) ){ + + sqlite3_mutex_enter(master); + p = &sqllogglobal.aConn[sqllogglobal.nConn++]; + p->fd = 0; + p->db = db; + p->iLog = sqllogglobal.iNextLog++; + sqlite3_mutex_leave(master); + + /* Open the log and take a copy of the main database file */ sqllogOpenlog(p); if( p->fd ) sqllogCopydb(p, "main", 0); } sqlite3_mutex_leave(sqllogglobal.mutex); } @@ -459,33 +502,34 @@ int i; for(i=0; idb==db ) break; } - if( i==sqllogglobal.nConn ) return; /* A database handle close command */ if( eType==2 ){ sqlite3_mutex_enter(master); - if( p->fd ) fclose(p->fd); - p->db = 0; - p->fd = 0; + if( ifd ) fclose(p->fd); + p->db = 0; + p->fd = 0; + sqllogglobal.nConn--; + } - sqllogglobal.nConn--; if( sqllogglobal.nConn==0 ){ sqlite3_mutex_free(sqllogglobal.mutex); sqllogglobal.mutex = 0; - }else{ + }else if( i0 ){ memmove(p, &p[1], nShift*sizeof(struct SLConn)); } } sqlite3_mutex_leave(master); /* An ordinary SQL command. */ - }else if( p->fd ){ + }else if( ifd ){ sqlite3_mutex_enter(sqllogglobal.mutex); if( sqllogglobal.bRec==0 ){ testSqllogStmt(p, zSql); } sqlite3_mutex_leave(sqllogglobal.mutex); @@ -502,8 +546,11 @@ void sqlite3_init_sqllog(void){ if( getenv(ENVIRONMENT_VARIABLE1_NAME) ){ if( SQLITE_OK==sqlite3_config(SQLITE_CONFIG_SQLLOG, testSqllog, 0) ){ memset(&sqllogglobal, 0, sizeof(sqllogglobal)); sqllogglobal.bReuse = 1; + if( getenv(ENVIRONMENT_VARIABLE3_NAME) ){ + sqllogglobal.bConditional = 1; + } } } } ADDED test/sqllog.test Index: test/sqllog.test ================================================================== --- /dev/null +++ test/sqllog.test @@ -0,0 +1,116 @@ +# 2015 November 13 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this file is testing the test_sqllog.c module. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix sqllog + +ifcapable !sqllog { + finish_test + return +} + +proc readfile {f} { + set fd [open $f] + set txt [read $fd] + close $fd + set txt +} + +proc delete_all_sqllog_files {} { + forcedelete {*}[glob -nocomplain sqllog_*.sql] + forcedelete {*}[glob -nocomplain sqllog_*.db] + forcedelete {*}[glob -nocomplain sqllog_*.idx] +} + +proc touch {f} { + set fd [open $f w+] + close $fd +} + +db close +sqlite3_shutdown +set ::env(SQLITE_SQLLOG_DIR) [pwd] + +delete_all_sqllog_files + +sqlite3 db test.db +set a a +set b b +do_execsql_test 1.0 { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES($a, $b); + SELECT * FROM t1; +} {1 2 a b} +db close + +do_test 1.1 { + readfile [lindex [glob sqllog_*.sql] 0] +} [string trimleft { +/-- Main database is '.*/sqllog_.*_0.db' +CREATE TABLE t1\(x, y\);; -- clock=0 +INSERT INTO t1 VALUES\(1, 2\);; -- clock=1 +INSERT INTO t1 VALUES\('a', 'b'\);; -- clock=2 +SELECT . FROM t1;; -- clock=3 +/}] + +do_test 1.2 { + file size [lindex [glob sqllog_*_0.db] 0] +} 1024 + +#------------------------------------------------------------------------- +catch { db close } +sqlite3_shutdown +delete_all_sqllog_files +forcedelete test.db-sqllog + +set ::env(SQLITE_SQLLOG_CONDITIONAL) 1 +sqlite3 db test.db +do_execsql_test 2.1 { + INSERT INTO t1 VALUES(4, 5); + SELECT * FROM t1; +} {1 2 a b 4 5} + +do_test 2.2 { + glob -nocomplain sqllog_* +} {} + +db close +touch test.db-sqllog +sqlite3 db test.db +do_execsql_test 2.3 { + INSERT INTO t1 VALUES(6, 7); + SELECT * FROM t1; +} {1 2 a b 4 5 6 7} +db close + +do_test 2.4 { + readfile [lindex [glob sqllog_*.sql] 0] +} [string trimleft { +/-- Main database is '.*/sqllog_.*_0.db' +INSERT INTO t1 VALUES\(6, 7\);; -- clock=0 +SELECT . FROM t1;; -- clock=1 +/}] + +catch { db close } +sqlite3_shutdown +unset ::env(SQLITE_SQLLOG_DIR) +unset ::env(SQLITE_SQLLOG_CONDITIONAL) +sqlite3_config_sqllog +sqlite3_initialize +breakpoint +finish_test + + Index: test/trigger7.test ================================================================== --- test/trigger7.test +++ test/trigger7.test @@ -18,11 +18,10 @@ source $testdir/tester.tcl ifcapable {!trigger} { finish_test return } - # Error messages resulting from qualified trigger names. # do_test trigger7-1.1 { execsql {