Index: src/func.c ================================================================== --- src/func.c +++ src/func.c @@ -884,21 +884,23 @@ sqlite3_result_text(context, sqlite3_sourceid(), -1, SQLITE_STATIC); } /* ** Implementation of the sqlite_log() function. This is a wrapper around -** sqlite3_log(). The return value is NULL. The function exists purely for -** its side-effects. +** sqlite3_db_log(). The return value is NULL. The function exists purely +** for its side-effects. */ static void errlogFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ UNUSED_PARAMETER(argc); UNUSED_PARAMETER(context); - sqlite3_log(sqlite3_value_int(argv[0]), "%s", sqlite3_value_text(argv[1])); + sqlite3_db_log(sqlite3_context_db_handle(context), + sqlite3_value_int(argv[0]), + "%s", sqlite3_value_text(argv[1])); } /* ** Implementation of the sqlite_compileoption_used() function. ** The result is an integer that identifies if the compiler option Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -732,10 +732,18 @@ void *pBuf = va_arg(ap, void*); /* IMP: R-26835-10964 */ int sz = va_arg(ap, int); /* IMP: R-47871-25994 */ int cnt = va_arg(ap, int); /* IMP: R-04460-53386 */ rc = setupLookaside(db, pBuf, sz, cnt); break; + } + case SQLITE_DBCONFIG_LOG: { + /* MSVC is picky about pulling func ptrs from va lists. + ** http://support.microsoft.com/kb/47961 */ + typedef void(*LOGFUNC_t)(void*,int,const char*); + db->xLog = va_arg(ap, LOGFUNC_t); + db->pLogArg = va_arg(ap, void*); + break; } default: { static const struct { int op; /* The opcode */ u32 mask; /* Mask of the bit in sqlite3.flags to set/clear */ Index: src/printf.c ================================================================== --- src/printf.c +++ src/printf.c @@ -1034,29 +1034,48 @@ ** ** sqlite3_log() must render into a static buffer. It cannot dynamically ** allocate memory because it might be called while the memory allocator ** mutex is held. */ -static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){ +static void renderLogMsg( + void (*xLog)(void*,int,const char*), /* Logging function */ + void *pArg, /* First argument to xLog */ + int iErrCode, /* Error code */ + const char *zFormat, /* Format string for error message */ + va_list ap /* Arguments to format string */ +){ StrAccum acc; /* String accumulator */ char zMsg[SQLITE_PRINT_BUF_SIZE*3]; /* Complete log message */ sqlite3StrAccumInit(&acc, zMsg, sizeof(zMsg), 0); acc.useMalloc = 0; sqlite3VXPrintf(&acc, 0, zFormat, ap); - sqlite3GlobalConfig.xLog(sqlite3GlobalConfig.pLogArg, iErrCode, - sqlite3StrAccumFinish(&acc)); + xLog(pArg, iErrCode, sqlite3StrAccumFinish(&acc)); } /* ** Format and write a message to the log if logging is enabled. */ void sqlite3_log(int iErrCode, const char *zFormat, ...){ va_list ap; /* Vararg list */ if( sqlite3GlobalConfig.xLog ){ va_start(ap, zFormat); - renderLogMsg(iErrCode, zFormat, ap); + renderLogMsg(sqlite3GlobalConfig.xLog, sqlite3GlobalConfig.pLogArg, + iErrCode, zFormat, ap); + va_end(ap); + } +} +void sqlite3_db_log(sqlite3 *db, int iErrCode, const char *zFormat, ...){ + va_list ap; /* Vararg list */ + if( db && db->xLog ){ + va_start(ap, zFormat); + renderLogMsg(db->xLog, db->pLogArg, iErrCode, zFormat, ap); + va_end(ap); + }else if( sqlite3GlobalConfig.xLog ){ + va_start(ap, zFormat); + renderLogMsg(sqlite3GlobalConfig.xLog, sqlite3GlobalConfig.pLogArg, + iErrCode, zFormat, ap); va_end(ap); } } #if defined(SQLITE_DEBUG) Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -1867,15 +1867,40 @@ ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether triggers are disabled or enabled ** following this call. The second parameter may be a NULL pointer, in ** which case the trigger setting is not reported back. ** +** [[SQLITE_DBCONFIG_LOG]]
SQLITE_DBCONFIG_LOG
+**
The SQLITE_DBCONFIG_LOG option is used to configure the SQLite +** [error log] for a specific database connection. See also the +** [SQLITE_CONFIG_LOG] option to [sqlite3_config()] for the global log +** callback. +** (^The SQLITE_DBCONFIG_LOG option takes two arguments: a pointer to a +** function with a call signature of void(*)(void*,int,const char*), +** and a pointer to void. ^If the function pointer is not NULL, it is +** invoked by [sqlite3_db_log()] to process each logging event. ^If the +** function pointer is NULL, then calls to [sqlite3_db_log()] interface try +** to use the [SQLITE_CONFIG_LOG|global error log]. If both the connection +** and global error log callbacks are NULL then [sqlite3_db_log()] is a +** harmless no-op. +** ^The void pointer that is the second argument to SQLITE_DBCONFIG_LOG is +** passed through as the first parameter to the application-defined logger +** function whenever that function is invoked. ^The second parameter to +** the logger function is a copy of the second parameter to the corresponding +** [sqlite3_db_log()] call and is intended to be a [result code] or an +** [extended result code]. ^The third parameter passed to the logger is +** log message after formatting via [sqlite3_snprintf()]. +** The SQLite logging interface is not reentrant; the logger function +** supplied by the application must not invoke any SQLite interface. +**
+** ** */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ #define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ +#define SQLITE_DBCONFIG_LOG 1004 /* xFunc, void* */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes ** METHOD: sqlite3 @@ -7283,14 +7308,23 @@ /* ** CAPI3REF: Error Logging Interface ** ** ^The [sqlite3_log()] interface writes a message into the [error log] ** established by the [SQLITE_CONFIG_LOG] option to [sqlite3_config()]. +** ^The [sqlite3_db_log()] interface writes a message into the [error log] +** established by the [SQLITE_DBCONFIG_LOG] option to [sqlite3_db_config()]. ** ^If logging is enabled, the zFormat string and subsequent arguments are ** used with [sqlite3_snprintf()] to generate the final output string. ** -** The sqlite3_log() interface is intended for use by extensions such as +** ^The [sqlite3_db_log()] interface writes error and warning information +** to the callback identified by the [SQLITE_DBCONFIG_LOG] option if that +** callback is not NULL, falling back to the [SQLITE_CONFIG_LOG] callback +** if the first parameter to [sqlite3_db_log()] is NULL or if the +** [SQLITE_DBCONFIG_LOG] callback is NULL. +** +** The sqlite3_log() and sqlite3_db_log() interfaces are intended for +** use by extensions such as ** virtual tables, collating functions, and SQL functions. While there is ** nothing to prevent an application from calling sqlite3_log(), doing so ** is considered bad form. ** ** The zFormat string must not be NULL. @@ -7300,10 +7334,11 @@ ** a fixed-length buffer on the stack. If the log message is longer than ** a few hundred characters, it will be truncated to the length of the ** buffer. */ void sqlite3_log(int iErrCode, const char *zFormat, ...); +void sqlite3_db_log(sqlite3*, int iErrCode, const char *zFormat, ...); /* ** CAPI3REF: Write-Ahead Log Commit Hook ** METHOD: sqlite3 ** Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -1136,10 +1136,12 @@ union { volatile int isInterrupted; /* True if sqlite3_interrupt has been called */ double notUsed1; /* Spacer */ } u1; Lookaside lookaside; /* Lookaside malloc configuration */ + void (*xLog)(void*,int,const char*); /* Function for logging */ + void *pLogArg; /* First argument to xLog() */ #ifndef SQLITE_OMIT_AUTHORIZATION sqlite3_xauth xAuth; /* Access authorization function */ void *pAuthArg; /* 1st argument to the access auth function */ #endif #ifndef SQLITE_OMIT_PROGRESS_CALLBACK Index: src/test1.c ================================================================== --- src/test1.c +++ src/test1.c @@ -5795,17 +5795,43 @@ Tcl_DecrRefCount(logcallback.pObj); logcallback.pObj = 0; logcallback.pInterp = 0; sqlite3_config(SQLITE_CONFIG_LOG, 0, 0); } - if( objc>1 ){ + if( objc==2 ){ logcallback.pObj = objv[1]; Tcl_IncrRefCount(logcallback.pObj); logcallback.pInterp = interp; sqlite3_config(SQLITE_CONFIG_LOG, xLogcallback, 0); } return TCL_OK; +} +static int test_sqlite3_db_log( + ClientData clientData, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3 *db; + if( objc<1 || objc>3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB SCRIPT"); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; + if( logcallback.pObj ){ + Tcl_DecrRefCount(logcallback.pObj); + logcallback.pObj = 0; + logcallback.pInterp = 0; + sqlite3_db_config(db, SQLITE_DBCONFIG_LOG, 0, 0); + } + if( objc==3 ){ + logcallback.pObj = objv[2]; + Tcl_IncrRefCount(logcallback.pObj); + logcallback.pInterp = interp; + sqlite3_db_config(db, SQLITE_DBCONFIG_LOG, xLogcallback, 0); + } + return TCL_OK; } /* ** tcl_objproc COMMANDNAME ARGS... ** @@ -6904,10 +6930,11 @@ #endif { "sqlite3_wal_checkpoint", test_wal_checkpoint, 0 }, { "sqlite3_wal_checkpoint_v2",test_wal_checkpoint_v2, 0 }, { "sqlite3_wal_autocheckpoint",test_wal_autocheckpoint, 0 }, { "test_sqlite3_log", test_sqlite3_log, 0 }, + { "test_sqlite3_db_log", test_sqlite3_db_log, 0 }, #ifndef SQLITE_OMIT_EXPLAIN { "print_explain_query_plan", test_print_eqp, 0 }, #endif { "sqlite3_test_control", test_test_control }, #if SQLITE_OS_UNIX Index: src/tokenize.c ================================================================== --- src/tokenize.c +++ src/tokenize.c @@ -473,11 +473,11 @@ sqlite3SetString(&pParse->zErrMsg, db, "%s", sqlite3ErrStr(pParse->rc)); } assert( pzErrMsg!=0 ); if( pParse->zErrMsg ){ *pzErrMsg = pParse->zErrMsg; - sqlite3_log(pParse->rc, "%s", *pzErrMsg); + sqlite3_db_log(db, pParse->rc, "%s", *pzErrMsg); pParse->zErrMsg = 0; nErr++; } if( pParse->pVdbe && pParse->nErr>0 && pParse->nested==0 ){ sqlite3VdbeDelete(pParse->pVdbe); Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -972,11 +972,11 @@ }else if( pOp->p4.z ){ sqlite3SetString(&p->zErrMsg, db, "%s", pOp->p4.z); }else{ sqlite3SetString(&p->zErrMsg, db, "%s constraint failed", zType); } - sqlite3_log(pOp->p1, zLogFmt, pcx, p->zSql, p->zErrMsg); + sqlite3_db_log(db, pOp->p1, zLogFmt, pcx, p->zSql, p->zErrMsg); } rc = sqlite3VdbeHalt(p); assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR ); if( rc==SQLITE_BUSY ){ p->rc = rc = SQLITE_BUSY; @@ -6527,11 +6527,11 @@ */ vdbe_error_halt: assert( rc ); p->rc = rc; testcase( sqlite3GlobalConfig.xLog!=0 ); - sqlite3_log(rc, "statement aborts at %d: [%s] %s", + sqlite3_db_log(db, rc, "statement aborts at %d: [%s] %s", (int)(pOp - aOp), p->zSql, p->zErrMsg); sqlite3VdbeHalt(p); if( rc==SQLITE_IOERR_NOMEM ) db->mallocFailed = 1; rc = SQLITE_ERROR; if( resetSchemaOnFault>0 ){ Index: src/vdbeapi.c ================================================================== --- src/vdbeapi.c +++ src/vdbeapi.c @@ -1143,13 +1143,13 @@ return SQLITE_MISUSE_BKPT; } sqlite3_mutex_enter(p->db->mutex); if( p->magic!=VDBE_MAGIC_RUN || p->pc>=0 ){ sqlite3Error(p->db, SQLITE_MISUSE); - sqlite3_mutex_leave(p->db->mutex); - sqlite3_log(SQLITE_MISUSE, + sqlite3_db_log(p->db, SQLITE_MISUSE, "bind on a busy prepared statement: [%s]", p->zSql); + sqlite3_mutex_leave(p->db->mutex); return SQLITE_MISUSE_BKPT; } if( i<1 || i>p->nVar ){ sqlite3Error(p->db, SQLITE_RANGE); sqlite3_mutex_leave(p->db->mutex); Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -2051,15 +2051,15 @@ if( zMaster==0 ) return SQLITE_NOMEM; do { u32 iRandom; if( retryCount ){ if( retryCount>100 ){ - sqlite3_log(SQLITE_FULL, "MJ delete: %s", zMaster); + sqlite3_db_log(db, SQLITE_FULL, "MJ delete: %s", zMaster); sqlite3OsDelete(pVfs, zMaster, 0); break; }else if( retryCount==1 ){ - sqlite3_log(SQLITE_FULL, "MJ collide: %s", zMaster); + sqlite3_db_log(db, SQLITE_FULL, "MJ collide: %s", zMaster); } } retryCount++; sqlite3_randomness(sizeof(iRandom), &iRandom); sqlite3_snprintf(13, &zMaster[nMainFile], "-mj%06X9%02X", Index: src/where.c ================================================================== --- src/where.c +++ src/where.c @@ -1748,11 +1748,11 @@ int iCol = pTerm->u.leftColumn; Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); testcase( iCol==BMS ); testcase( iCol==BMS-1 ); if( !sentWarning ){ - sqlite3_log(SQLITE_WARNING_AUTOINDEX, + sqlite3_db_log(pParse->db, SQLITE_WARNING_AUTOINDEX, "automatic index on %s(%s)", pTable->zName, pTable->aCol[iCol].zName); sentWarning = 1; } if( (idxCols & cMask)==0 ){ ADDED test/errlog01.test Index: test/errlog01.test ================================================================== --- /dev/null +++ test/errlog01.test @@ -0,0 +1,37 @@ +# 2015-04-22 +# +# 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. +# +#************************************************************************* +# Test cases for the sqlite3_log() and sqlite3_db_log() interfaces. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +unset -nocomplain log +set log {} +do_test errlog01-1.0 { + test_sqlite3_db_log db [list lappend ::log] + db eval {SELECT sqlite_log(7,'simulated error')} +} {{}} +do_test errlog01-1.1 { + set ::log +} {SQLITE_NOMEM {simulated error}} +do_test errlog01-1.2 { + sqlite3 db2 :memory: + set ::log {} + db2 eval {SELECT sqlite_log(7,'simulated error')} +} {{}} +do_test errlog01-1.3 { + set ::log +} {} + + + +finish_test