Index: src/main.c
==================================================================
--- src/main.c
+++ src/main.c
@@ -1031,10 +1031,13 @@
}
if( !sqlite3SafetyCheckSickOrOk(db) ){
return SQLITE_MISUSE_BKPT;
}
sqlite3_mutex_enter(db->mutex);
+ if( db->mTrace & SQLITE_TRACE_CLOSE ){
+ db->xTrace(SQLITE_TRACE_CLOSE, db->pTraceArg, db, 0);
+ }
/* Force xDisconnect calls on all virtual tables */
disconnectAllVtab(db);
/* If a transaction is open, the disconnectAllVtab() call above
@@ -1799,11 +1802,12 @@
**
** A NULL trace function means that no tracing is executes. A non-NULL
** trace is a pointer to a function that is invoked at the start of each
** SQL statement.
*/
-void *sqlite3_trace(sqlite3 *db, void (*xTrace)(void*,const char*), void *pArg){
+#ifndef SQLITE_OMIT_DEPRECATED
+void *sqlite3_trace(sqlite3 *db, void(*xTrace)(void*,const char*), void *pArg){
void *pOld;
#ifdef SQLITE_ENABLE_API_ARMOR
if( !sqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
@@ -1810,15 +1814,39 @@
return 0;
}
#endif
sqlite3_mutex_enter(db->mutex);
pOld = db->pTraceArg;
- db->xTrace = xTrace;
+ db->mTrace = xTrace ? SQLITE_TRACE_LEGACY : 0;
+ db->xTrace = (int(*)(u32,void*,void*,void*))xTrace;
db->pTraceArg = pArg;
sqlite3_mutex_leave(db->mutex);
return pOld;
}
+#endif /* SQLITE_OMIT_DEPRECATED */
+
+/* Register a trace callback using the version-2 interface.
+*/
+int sqlite3_trace_v2(
+ sqlite3 *db, /* Trace this connection */
+ unsigned mTrace, /* Mask of events to be traced */
+ int(*xTrace)(unsigned,void*,void*,void*), /* Callback to invoke */
+ void *pArg /* Context */
+){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !sqlite3SafetyCheckOk(db) ){
+ return SQLITE_MISUSE_BKPT;
+ }
+#endif
+ sqlite3_mutex_enter(db->mutex);
+ db->mTrace = mTrace;
+ db->xTrace = xTrace;
+ db->pTraceArg = pArg;
+ sqlite3_mutex_leave(db->mutex);
+ return SQLITE_OK;
+}
+
/*
** Register a profile function. The pArg from the previously registered
** profile function is returned.
**
** A NULL profile function means that no profiling is executes. A non-NULL
Index: src/sqlite.h.in
==================================================================
--- src/sqlite.h.in
+++ src/sqlite.h.in
@@ -2752,10 +2752,13 @@
#define SQLITE_RECURSIVE 33 /* NULL NULL */
/*
** CAPI3REF: Tracing And Profiling Functions
** METHOD: sqlite3
+**
+** These routines are deprecated. Use the [sqlite3_trace_v2()] interface
+** instead of the routines described here.
**
** These routines register callback functions that can be used for
** tracing and profiling the execution of SQL statements.
**
** ^The callback function registered by sqlite3_trace() is invoked at
@@ -2778,13 +2781,104 @@
** digits in the time are meaningless. Future versions of SQLite
** might provide greater resolution on the profiler callback. The
** sqlite3_profile() function is considered experimental and is
** subject to change in future versions of SQLite.
*/
-void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*);
-SQLITE_EXPERIMENTAL void *sqlite3_profile(sqlite3*,
+SQLITE_DEPRECATED void *sqlite3_trace(sqlite3*,
+ void(*xTrace)(void*,const char*), void*);
+SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
void(*xProfile)(void*,const char*,sqlite3_uint64), void*);
+
+/*
+** CAPI3REF: SQL Trace Event Codes
+** KEYWORDS: SQLITE_TRACE
+**
+** These constants identify classes of events that can be monitored
+** using the [sqlite3_trace_v2()] tracing logic. The third argument
+** to [sqlite3_trace_v2()] is an OR-ed combination of one or more of
+** the following constants. ^The first argument to the trace callback
+** is one of the following constants.
+**
+** New tracing constants may be added in future releases.
+**
+** ^A trace callback has four arguments: xCallback(T,C,P,X).
+** ^The T argument is one of the integer type codes above.
+** ^The C argument is a copy of the context pointer passed in as the
+** fourth argument to [sqlite3_trace_v2()].
+** The P and X arguments are pointers whose meanings depend on T.
+**
+**
+** [[SQLITE_TRACE_STMT]] - SQLITE_TRACE_STMT
+** - ^An SQLITE_TRACE_STMT callback is invoked when a prepared statement
+** first begins running and possibly at other times during the
+** execution of the prepared statement, such as at the start of each
+** trigger subprogram. ^The P argument is a pointer to the
+** [prepared statement]. ^The X argument is a pointer to a string which
+** is the expanded SQL text of the prepared statement or a comment that
+** indicates the invocation of a trigger.
+**
+** [[SQLITE_TRACE_PROFILE]]
- SQLITE_TRACE_PROFILE
+** - ^An SQLITE_TRACE_PROFILE callback provides approximately the same
+** information as is provided by the [sqlite3_profile()] callback.
+** ^The P argument is a pointer to the [prepared statement] and the
+** X argument points to a 64-bit integer which is the estimated of
+** the number of nanosecond that the prepared statement took to run.
+** ^The SQLITE_TRACE_PROFILE callback is invoked when the statement finishes.
+**
+** [[SQLITE_TRACE_ROW]]
- SQLITE_TRACE_ROW
+** - ^An SQLITE_TRACE_ROW callback is invoked whenever a prepared
+** statement generates a single row of result.
+** ^The P argument is a pointer to the [prepared statement] and the
+** X argument is unused.
+**
+** [[SQLITE_TRACE_CLOSE]]
- SQLITE_TRACE_CLOSE
+** - ^An SQLITE_TRACE_CLOSE callback is invoked when a database
+** connection closes.
+** ^The P argument is a pointer to the [database connection] object
+** and the X argument is unused.
+**
+*/
+#define SQLITE_TRACE_STMT 0x01
+#define SQLITE_TRACE_PROFILE 0x02
+#define SQLITE_TRACE_ROW 0x04
+#define SQLITE_TRACE_CLOSE 0x08
+
+/*
+** CAPI3REF: SQL Trace Hook
+** METHOD: sqlite3
+**
+** ^The sqlite3_trace_v2(D,M,X,P) interface registers a trace callback
+** function X against [database connection] D, using property mask M
+** and context pointer P. ^If the X callback is
+** NULL or if the M mask is zero, then tracing is disabled. The
+** M argument should be the bitwise OR-ed combination of
+** zero or more [SQLITE_TRACE] constants.
+**
+** ^Each call to either sqlite3_trace() or sqlite3_trace_v2() overrides
+** (cancels) any prior calls to sqlite3_trace() or sqlite3_trace_v2().
+**
+** ^The X callback is invoked whenever any of the events identified by
+** mask M occur. ^The integer return value from the callback is currently
+** ignored, though this may change in future releases. Callback
+** implementations should return zero to ensure future compatibility.
+**
+** ^A trace callback is invoked with four arguments: callback(T,C,P,X).
+** ^The T argument is one of the [SQLITE_TRACE]
+** constants to indicate why the callback was invoked.
+** ^The C argument is a copy of the context pointer.
+** The P and X arguments are pointers whose meanings depend on T.
+**
+** The sqlite3_trace_v2() interface is intended to replace the legacy
+** interfaces [sqlite3_trace()] and [sqlite3_profile()], both of which
+** are deprecated.
+*/
+int sqlite3_trace_v2(
+ sqlite3*,
+ unsigned uMask,
+ int(*xCallback)(unsigned,void*,void*,void*),
+ void *pCtx
+);
/*
** CAPI3REF: Query Progress Callbacks
** METHOD: sqlite3
**
@@ -3400,15 +3494,39 @@
/*
** CAPI3REF: Retrieving Statement SQL
** METHOD: sqlite3_stmt
**
-** ^This interface can be used to retrieve a saved copy of the original
-** SQL text used to create a [prepared statement] if that statement was
-** compiled using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()].
+** ^The sqlite3_sql(P) interface returns a pointer to a copy of the UTF-8
+** SQL text used to create [prepared statement] P if P was
+** created by either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()].
+** ^The sqlite3_expanded_sql(P) interface returns a pointer to a UTF-8
+** string containing the SQL text of prepared statement P with
+** [bound parameters] expanded.
+**
+** ^(For example, if a prepared statement is created using the SQL
+** text "SELECT $abc,:xyz" and if parameter $abc is bound to integer 2345
+** and parameter :xyz is unbound, then sqlite3_sql() will return
+** the original string, "SELECT $abc,:xyz" but sqlite3_expanded_sql()
+** will return "SELECT 2345,NULL".)^
+**
+** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory
+** is available to hold the result, or if the result would exceed the
+** the maximum string length determined by the [SQLITE_LIMIT_LENGTH].
+**
+** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of
+** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time
+** option causes sqlite3_expanded_sql() to always return NULL.
+**
+** ^The string returned by sqlite3_sql(P) is managed by SQLite and is
+** automatically freed when the prepared statement is finalized.
+** ^The string returned by sqlite3_expanded_sql(P), on the other hand,
+** is obtained from [sqlite3_malloc()] and must be free by the application
+** by passing it to [sqlite3_free()].
*/
const char *sqlite3_sql(sqlite3_stmt *pStmt);
+char *sqlite3_expanded_sql(sqlite3_stmt *pStmt);
/*
** CAPI3REF: Determine If An SQL Statement Writes The Database
** METHOD: sqlite3_stmt
**
Index: src/sqlite3ext.h
==================================================================
--- src/sqlite3ext.h
+++ src/sqlite3ext.h
@@ -279,10 +279,13 @@
int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int);
int (*strlike)(const char*,const char*,unsigned int);
int (*db_cacheflush)(sqlite3*);
/* Version 3.12.0 and later */
int (*system_errno)(sqlite3*);
+ /* Version 3.14.0 and later */
+ int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
+ char *(*expanded_sql)(sqlite3_stmt*);
};
/*
** The following macros redefine the API routines so that they are
** redirected through the global sqlite3_api structure.
@@ -524,10 +527,13 @@
#define sqlite3_status64 sqlite3_api->status64
#define sqlite3_strlike sqlite3_api->strlike
#define sqlite3_db_cacheflush sqlite3_api->db_cacheflush
/* Version 3.12.0 and later */
#define sqlite3_system_errno sqlite3_api->system_errno
+/* Version 3.14.0 and later */
+#define sqlite3_trace_v2 sqlite3_api->trace_v2
+#define sqlite3_expanded_sql sqlite3_api->expanded_sql
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
/* This case when the file really is being compiled as a loadable
** extension */
Index: src/sqliteInt.h
==================================================================
--- src/sqliteInt.h
+++ src/sqliteInt.h
@@ -1239,10 +1239,19 @@
#else
typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*,
const char*);
#endif
+#ifndef SQLITE_OMIT_DEPRECATED
+/* This is an extra SQLITE_TRACE macro that indicates "legacy" tracing
+** in the style of sqlite3_trace()
+*/
+#define SQLITE_TRACE_LEGACY 0x80
+#else
+#define SQLITE_TRACE_LEGACY 0
+#endif /* SQLITE_OMIT_DEPRECATED */
+
/*
** Each database connection is an instance of the following structure.
*/
struct sqlite3 {
@@ -1268,10 +1277,11 @@
u8 dfltLockMode; /* Default locking-mode for attached dbs */
signed char nextAutovac; /* Autovac setting after VACUUM if >=0 */
u8 suppressErr; /* Do not issue error messages if true */
u8 vtabOnConflict; /* Value to return for s3_vtab_on_conflict() */
u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */
+ u8 mTrace; /* zero or more SQLITE_TRACE flags */
int nextPagesize; /* Pagesize after VACUUM if >0 */
u32 magic; /* Magic number for detect library misuse */
int nChange; /* Value returned by sqlite3_changes() */
int nTotalChange; /* Value returned by sqlite3_total_changes() */
int aLimit[SQLITE_N_LIMIT]; /* Limits */
@@ -1288,11 +1298,11 @@
int nVdbeWrite; /* Number of active VDBEs that read and write */
int nVdbeExec; /* Number of nested calls to VdbeExec() */
int nVDestroy; /* Number of active OP_VDestroy operations */
int nExtension; /* Number of loaded extensions */
void **aExtension; /* Array of shared library handles */
- void (*xTrace)(void*,const char*); /* Trace function */
+ int (*xTrace)(u32,void*,void*,void*); /* Trace function */
void *pTraceArg; /* Argument to the trace function */
void (*xProfile)(void*,const char*,u64); /* Profiling function */
void *pProfileArg; /* Argument to profile function */
void *pCommitArg; /* Argument to xCommitCallback() */
int (*xCommitCallback)(void*); /* Invoked at every commit. */
Index: src/tclsqlite.c
==================================================================
--- src/tclsqlite.c
+++ src/tclsqlite.c
@@ -131,10 +131,11 @@
sqlite3 *db; /* The "real" database structure. MUST BE FIRST */
Tcl_Interp *interp; /* The interpreter used for this database */
char *zBusy; /* The busy callback routine */
char *zCommit; /* The commit hook callback routine */
char *zTrace; /* The trace callback routine */
+ char *zTraceV2; /* The trace_v2 callback routine */
char *zProfile; /* The profile callback routine */
char *zProgress; /* The progress callback routine */
char *zAuth; /* The authorization callback routine */
int disableAuth; /* Disable the authorizer if it exists */
char *zNull; /* Text to substitute for an SQL NULL value */
@@ -190,11 +191,11 @@
IncrblobChannel *pNext;
for(p=pDb->pIncrblob; p; p=pNext){
pNext = p->pNext;
- /* Note: Calling unregister here call Tcl_Close on the incrblob channel,
+ /* Note: Calling unregister here call Tcl_Close on the incrblob channel,
** which deletes the IncrblobChannel structure at *p. So do not
** call Tcl_Free() here.
*/
Tcl_UnregisterChannel(pDb->interp, p->channel);
}
@@ -231,12 +232,12 @@
/*
** Read data from an incremental blob channel.
*/
static int incrblobInput(
- ClientData instanceData,
- char *buf,
+ ClientData instanceData,
+ char *buf,
int bufSize,
int *errorCodePtr
){
IncrblobChannel *p = (IncrblobChannel *)instanceData;
int nRead = bufSize; /* Number of bytes to read */
@@ -263,12 +264,12 @@
/*
** Write data to an incremental blob channel.
*/
static int incrblobOutput(
- ClientData instanceData,
- CONST char *buf,
+ ClientData instanceData,
+ CONST char *buf,
int toWrite,
int *errorCodePtr
){
IncrblobChannel *p = (IncrblobChannel *)instanceData;
int nWrite = toWrite; /* Number of bytes to write */
@@ -296,11 +297,11 @@
/*
** Seek an incremental blob channel.
*/
static int incrblobSeek(
- ClientData instanceData,
+ ClientData instanceData,
long offset,
int seekMode,
int *errorCodePtr
){
IncrblobChannel *p = (IncrblobChannel *)instanceData;
@@ -321,12 +322,12 @@
return p->iSeek;
}
-static void incrblobWatch(ClientData instanceData, int mode){
- /* NO-OP */
+static void incrblobWatch(ClientData instanceData, int mode){
+ /* NO-OP */
}
static int incrblobHandle(ClientData instanceData, int dir, ClientData *hPtr){
return TCL_ERROR;
}
@@ -350,15 +351,15 @@
/*
** Create a new incrblob channel.
*/
static int createIncrblobChannel(
- Tcl_Interp *interp,
- SqliteDb *pDb,
+ Tcl_Interp *interp,
+ SqliteDb *pDb,
const char *zDb,
- const char *zTable,
- const char *zColumn,
+ const char *zTable,
+ const char *zColumn,
sqlite_int64 iRow,
int isReadonly
){
IncrblobChannel *p;
sqlite3 *db = pDb->db;
@@ -436,11 +437,11 @@
SqlFunc *p, *pNew;
int nName = strlen30(zName);
pNew = (SqlFunc*)Tcl_Alloc( sizeof(*pNew) + nName + 1 );
pNew->zName = (char*)&pNew[1];
memcpy(pNew->zName, zName, nName+1);
- for(p=pDb->pFunc; p; p=p->pNext){
+ for(p=pDb->pFunc; p; p=p->pNext){
if( sqlite3_stricmp(p->zName, pNew->zName)==0 ){
Tcl_Free((char*)pNew);
return p;
}
}
@@ -506,10 +507,13 @@
Tcl_Free(pDb->zBusy);
}
if( pDb->zTrace ){
Tcl_Free(pDb->zTrace);
}
+ if( pDb->zTraceV2 ){
+ Tcl_Free(pDb->zTraceV2);
+ }
if( pDb->zProfile ){
Tcl_Free(pDb->zProfile);
}
if( pDb->zAuth ){
Tcl_Free(pDb->zAuth);
@@ -584,10 +588,86 @@
Tcl_Eval(pDb->interp, Tcl_DStringValue(&str));
Tcl_DStringFree(&str);
Tcl_ResetResult(pDb->interp);
}
#endif
+
+#ifndef SQLITE_OMIT_TRACE
+/*
+** This routine is called by the SQLite trace_v2 handler whenever a new
+** supported event is generated. Unsupported event types are ignored.
+** The TCL script in pDb->zTraceV2 is executed, with the arguments for
+** the event appended to it (as list elements).
+*/
+static int DbTraceV2Handler(
+ unsigned type, /* One of the SQLITE_TRACE_* event types. */
+ void *cd, /* The original context data pointer. */
+ void *pd, /* Primary event data, depends on event type. */
+ void *xd /* Extra event data, depends on event type. */
+){
+ SqliteDb *pDb = (SqliteDb*)cd;
+ Tcl_Obj *pCmd;
+
+ switch( type ){
+ case SQLITE_TRACE_STMT: {
+ sqlite3_stmt *pStmt = (sqlite3_stmt *)pd;
+ char *zSql = (char *)xd;
+
+ pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1);
+ Tcl_IncrRefCount(pCmd);
+ Tcl_ListObjAppendElement(pDb->interp, pCmd,
+ Tcl_NewWideIntObj((Tcl_WideInt)pStmt));
+ Tcl_ListObjAppendElement(pDb->interp, pCmd,
+ Tcl_NewStringObj(zSql, -1));
+ Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT);
+ Tcl_DecrRefCount(pCmd);
+ Tcl_ResetResult(pDb->interp);
+ break;
+ }
+ case SQLITE_TRACE_PROFILE: {
+ sqlite3_stmt *pStmt = (sqlite3_stmt *)pd;
+ sqlite3_int64 ns = (sqlite3_int64)xd;
+
+ pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1);
+ Tcl_IncrRefCount(pCmd);
+ Tcl_ListObjAppendElement(pDb->interp, pCmd,
+ Tcl_NewWideIntObj((Tcl_WideInt)pStmt));
+ Tcl_ListObjAppendElement(pDb->interp, pCmd,
+ Tcl_NewWideIntObj((Tcl_WideInt)ns));
+ Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT);
+ Tcl_DecrRefCount(pCmd);
+ Tcl_ResetResult(pDb->interp);
+ break;
+ }
+ case SQLITE_TRACE_ROW: {
+ sqlite3_stmt *pStmt = (sqlite3_stmt *)pd;
+
+ pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1);
+ Tcl_IncrRefCount(pCmd);
+ Tcl_ListObjAppendElement(pDb->interp, pCmd,
+ Tcl_NewWideIntObj((Tcl_WideInt)pStmt));
+ Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT);
+ Tcl_DecrRefCount(pCmd);
+ Tcl_ResetResult(pDb->interp);
+ break;
+ }
+ case SQLITE_TRACE_CLOSE: {
+ sqlite3 *db = (sqlite3 *)pd;
+
+ pCmd = Tcl_NewStringObj(pDb->zTraceV2, -1);
+ Tcl_IncrRefCount(pCmd);
+ Tcl_ListObjAppendElement(pDb->interp, pCmd,
+ Tcl_NewWideIntObj((Tcl_WideInt)db));
+ Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT);
+ Tcl_DecrRefCount(pCmd);
+ Tcl_ResetResult(pDb->interp);
+ break;
+ }
+ }
+ return SQLITE_OK;
+}
+#endif
#ifndef SQLITE_OMIT_TRACE
/*
** This routine is called by the SQLite profile handler after a statement
** SQL has executed. The TCL script in pDb->zProfile is evaluated.
@@ -635,13 +715,13 @@
/*
** This procedure handles wal_hook callbacks.
*/
static int DbWalHandler(
- void *clientData,
- sqlite3 *db,
- const char *zDb,
+ void *clientData,
+ sqlite3 *db,
+ const char *zDb,
int nEntry
){
int ret = SQLITE_OK;
Tcl_Obj *p;
SqliteDb *pDb = (SqliteDb*)clientData;
@@ -651,11 +731,11 @@
assert( db==pDb->db );
p = Tcl_DuplicateObj(pDb->pWalHook);
Tcl_IncrRefCount(p);
Tcl_ListObjAppendElement(interp, p, Tcl_NewStringObj(zDb, -1));
Tcl_ListObjAppendElement(interp, p, Tcl_NewIntObj(nEntry));
- if( TCL_OK!=Tcl_EvalObjEx(interp, p, 0)
+ if( TCL_OK!=Tcl_EvalObjEx(interp, p, 0)
|| TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &ret)
){
Tcl_BackgroundError(interp);
}
Tcl_DecrRefCount(p);
@@ -693,15 +773,15 @@
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
/*
** Pre-update hook callback.
*/
static void DbPreUpdateHandler(
- void *p,
+ void *p,
sqlite3 *db,
int op,
- const char *zDb,
- const char *zTbl,
+ const char *zDb,
+ const char *zTbl,
sqlite_int64 iKey1,
sqlite_int64 iKey2
){
SqliteDb *pDb = (SqliteDb *)p;
Tcl_Obj *pCmd;
@@ -725,14 +805,14 @@
Tcl_DecrRefCount(pCmd);
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
static void DbUpdateHandler(
- void *p,
+ void *p,
int op,
- const char *zDb,
- const char *zTbl,
+ const char *zDb,
+ const char *zTbl,
sqlite_int64 rowid
){
SqliteDb *pDb = (SqliteDb *)p;
Tcl_Obj *pCmd;
static const char *azStr[] = {"DELETE", "INSERT", "UPDATE"};
@@ -813,27 +893,27 @@
}else{
/* If there are arguments to the function, make a shallow copy of the
** script object, lappend the arguments, then evaluate the copy.
**
** By "shallow" copy, we mean only the outer list Tcl_Obj is duplicated.
- ** The new Tcl_Obj contains pointers to the original list elements.
+ ** The new Tcl_Obj contains pointers to the original list elements.
** That way, when Tcl_EvalObjv() is run and shimmers the first element
** of the list to tclCmdNameType, that alternate representation will
** be preserved and reused on the next invocation.
*/
Tcl_Obj **aArg;
int nArg;
if( Tcl_ListObjGetElements(p->interp, p->pScript, &nArg, &aArg) ){
- sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
+ sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
return;
- }
+ }
pCmd = Tcl_NewListObj(nArg, aArg);
Tcl_IncrRefCount(pCmd);
for(i=0; iinterp, pCmd, pVal);
if( rc ){
Tcl_DecrRefCount(pCmd);
- sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
+ sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
return;
}
}
if( !p->useEvalObjv ){
/* Tcl_EvalObjEx() will automatically call Tcl_EvalObjv() if pCmd
@@ -879,11 +959,11 @@
rc = Tcl_EvalObjEx(p->interp, pCmd, TCL_EVAL_DIRECT);
Tcl_DecrRefCount(pCmd);
}
if( rc && rc!=TCL_RETURN ){
- sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
+ sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
}else{
Tcl_Obj *pVar = Tcl_GetObjResult(p->interp);
int n;
u8 *data;
const char *zType = (pVar->typePtr ? pVar->typePtr->name : "");
@@ -981,11 +1061,11 @@
Tcl_DStringAppendElement(&str, zArg2 ? zArg2 : "");
Tcl_DStringAppendElement(&str, zArg3 ? zArg3 : "");
Tcl_DStringAppendElement(&str, zArg4 ? zArg4 : "");
#ifdef SQLITE_USER_AUTHENTICATION
Tcl_DStringAppendElement(&str, zArg5 ? zArg5 : "");
-#endif
+#endif
rc = Tcl_GlobalEval(pDb->interp, Tcl_DStringValue(&str));
Tcl_DStringFree(&str);
zReply = rc==TCL_OK ? Tcl_GetStringResult(pDb->interp) : "SQLITE_DENY";
if( strcmp(zReply,"SQLITE_OK")==0 ){
rc = SQLITE_OK;
@@ -1073,16 +1153,16 @@
zEnd = azEnd[(rc==TCL_ERROR)*2 + (pDb->nTransaction==0)];
pDb->disableAuth++;
if( sqlite3_exec(pDb->db, zEnd, 0, 0, 0) ){
/* This is a tricky scenario to handle. The most likely cause of an
- ** error is that the exec() above was an attempt to commit the
+ ** error is that the exec() above was an attempt to commit the
** top-level transaction that returned SQLITE_BUSY. Or, less likely,
** that an IO-error has occurred. In either case, throw a Tcl exception
** and try to rollback the transaction.
**
- ** But it could also be that the user executed one or more BEGIN,
+ ** But it could also be that the user executed one or more BEGIN,
** COMMIT, SAVEPOINT, RELEASE or ROLLBACK commands that are confusing
** this method's logic. Not clear how this would be best handled.
*/
if( rc!=TCL_ERROR ){
Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), (char*)0);
@@ -1097,11 +1177,11 @@
/*
** Unless SQLITE_TEST is defined, this function is a simple wrapper around
** sqlite3_prepare_v2(). If SQLITE_TEST is defined, then it uses either
** sqlite3_prepare_v2() or legacy interface sqlite3_prepare(), depending
-** on whether or not the [db_use_legacy_prepare] command has been used to
+** on whether or not the [db_use_legacy_prepare] command has been used to
** configure the connection.
*/
static int dbPrepare(
SqliteDb *pDb, /* Database object */
const char *zSql, /* SQL to compile */
@@ -1153,11 +1233,11 @@
while( (c = zSql[0])==' ' || c=='\t' || c=='\r' || c=='\n' ){ zSql++; }
nSql = strlen30(zSql);
for(pPreStmt = pDb->stmtList; pPreStmt; pPreStmt=pPreStmt->pNext){
int n = pPreStmt->nSql;
- if( nSql>=n
+ if( nSql>=n
&& memcmp(pPreStmt->zSql, zSql, n)==0
&& (zSql[n]==0 || zSql[n-1]==';')
){
pStmt = pPreStmt->pStmt;
*pzOut = &zSql[pPreStmt->nSql];
@@ -1179,11 +1259,11 @@
pDb->nStmt--;
nVar = sqlite3_bind_parameter_count(pStmt);
break;
}
}
-
+
/* If no prepared statement was found. Compile the SQL text. Also allocate
** a new SqlPreparedStmt structure. */
if( pPreStmt==0 ){
int nByte;
@@ -1225,11 +1305,11 @@
}
assert( pPreStmt );
assert( strlen30(pPreStmt->zSql)==pPreStmt->nSql );
assert( 0==memcmp(pPreStmt->zSql, zSql, pPreStmt->nSql) );
- /* Bind values to parameters that begin with $ or : */
+ /* Bind values to parameters that begin with $ or : */
for(i=1; i<=nVar; i++){
const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
if( zVar!=0 && (zVar[0]=='$' || zVar[0]==':' || zVar[0]=='@') ){
Tcl_Obj *pVar = Tcl_GetVar2Ex(interp, &zVar[1], 0, 0);
if( pVar ){
@@ -1313,12 +1393,12 @@
pDb->stmtLast = pPreStmt;
}else{
assert( pDb->nStmt>0 );
}
pDb->nStmt++;
-
- /* If we have too many statement in cache, remove the surplus from
+
+ /* If we have too many statement in cache, remove the surplus from
** the end of the cache list. */
while( pDb->nStmt>pDb->maxStmt ){
SqlPreparedStmt *pLast = pDb->stmtLast;
pDb->stmtLast = pLast->pPrev;
pDb->stmtLast->pNext = 0;
@@ -1368,12 +1448,12 @@
** Initialize a DbEvalContext structure.
**
** If pArray is not NULL, then it contains the name of a Tcl array
** variable. The "*" member of this array is set to a list containing
** the names of the columns returned by the statement as part of each
-** call to dbEvalStep(), in order from left to right. e.g. if the names
-** of the returned columns are a, b and c, it does the equivalent of the
+** call to dbEvalStep(), in order from left to right. e.g. if the names
+** of the returned columns are a, b and c, it does the equivalent of the
** tcl command:
**
** set ${pArray}(*) {a b c}
*/
static void dbEvalInit(
@@ -1490,11 +1570,11 @@
** the SQL. */
dbReleaseStmt(pDb, pPreStmt, 1);
#if SQLITE_TEST
if( p->pDb->bLegacyPrepare && rcs==SQLITE_SCHEMA && zPrevSql ){
/* If the runtime error was an SQLITE_SCHEMA, and the database
- ** handle is configured to use the legacy sqlite3_prepare()
+ ** handle is configured to use the legacy sqlite3_prepare()
** interface, retry prepare()/step() on the same SQL statement.
** This only happens once. If there is a second SQLITE_SCHEMA
** error, the error will be returned to the caller. */
p->zSql = zPrevSql;
continue;
@@ -1578,15 +1658,15 @@
int major, minor;
Tcl_GetVersion(&major, &minor, 0, 0);
return( (major==8 && minor>=6) || major>8 );
}
#else
-/*
+/*
** Compiling using headers earlier than 8.6. In this case NR cannot be
** used, so DbUseNre() to always return zero. Add #defines for the other
** Tcl_NRxxx() functions to prevent them from causing compilation errors,
-** even though the only invocations of them are within conditional blocks
+** even though the only invocations of them are within conditional blocks
** of the form:
**
** if( DbUseNre() ) { ... }
*/
# define SQLITE_TCL_NRE 0
@@ -1628,15 +1708,15 @@
}else{
Tcl_ObjSetVar2(interp, pArray, apColName[i], pVal, 0);
}
}
- /* The required interpreter variables are now populated with the data
+ /* The required interpreter variables are now populated with the data
** from the current row. If using NRE, schedule callbacks to evaluate
** script pScript, then to invoke this function again to fetch the next
** row (or clean up if there is no next row or the script throws an
- ** exception). After scheduling the callbacks, return control to the
+ ** exception). After scheduling the callbacks, return control to the
** caller.
**
** If not using NRE, evaluate pScript directly and continue with the
** next iteration of this while(...) loop. */
if( DbUseNre() ){
@@ -1657,11 +1737,11 @@
}
return rc;
}
/*
-** This function is used by the implementations of the following database
+** This function is used by the implementations of the following database
** handle sub-commands:
**
** $db update_hook ?SCRIPT?
** $db wal_hook ?SCRIPT?
** $db commit_hook ?SCRIPT?
@@ -1724,13 +1804,14 @@
"function", "incrblob", "interrupt",
"last_insert_rowid", "nullvalue", "onecolumn",
"preupdate", "profile", "progress",
"rekey", "restore", "rollback_hook",
"status", "timeout", "total_changes",
- "trace", "transaction", "unlock_notify",
- "update_hook", "version", "wal_hook",
- 0
+ "trace", "trace_v2", "transaction",
+ "unlock_notify", "update_hook", "version",
+ "wal_hook",
+ 0
};
enum DB_enum {
DB_AUTHORIZER, DB_BACKUP, DB_BUSY,
DB_CACHE, DB_CHANGES, DB_CLOSE,
DB_COLLATE, DB_COLLATION_NEEDED, DB_COMMIT_HOOK,
@@ -1739,12 +1820,13 @@
DB_FUNCTION, DB_INCRBLOB, DB_INTERRUPT,
DB_LAST_INSERT_ROWID, DB_NULLVALUE, DB_ONECOLUMN,
DB_PREUPDATE, DB_PROFILE, DB_PROGRESS,
DB_REKEY, DB_RESTORE, DB_ROLLBACK_HOOK,
DB_STATUS, DB_TIMEOUT, DB_TOTAL_CHANGES,
- DB_TRACE, DB_TRANSACTION, DB_UNLOCK_NOTIFY,
- DB_UPDATE_HOOK, DB_VERSION, DB_WAL_HOOK,
+ DB_TRACE, DB_TRACE_V2, DB_TRANSACTION,
+ DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK, DB_VERSION,
+ DB_WAL_HOOK,
};
/* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
if( objc<2 ){
Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
@@ -1926,11 +2008,11 @@
if( objc!=4 ){
Tcl_WrongNumArgs(interp, 2, objv, "size n");
return TCL_ERROR;
}else{
if( TCL_ERROR==Tcl_GetIntFromObj(interp, objv[3], &n) ){
- Tcl_AppendResult( interp, "cannot convert \"",
+ Tcl_AppendResult( interp, "cannot convert \"",
Tcl_GetStringFromObj(objv[3],0), "\" to integer", (char*)0);
return TCL_ERROR;
}else{
if( n<0 ){
flushStmtCache( pDb );
@@ -1940,11 +2022,11 @@
}
pDb->maxStmt = n;
}
}
}else{
- Tcl_AppendResult( interp, "bad option \"",
+ Tcl_AppendResult( interp, "bad option \"",
Tcl_GetStringFromObj(objv[2],0), "\": must be flush or size",
(char*)0);
return TCL_ERROR;
}
break;
@@ -1951,11 +2033,11 @@
}
/* $db changes
**
** Return the number of rows that were modified, inserted, or deleted by
- ** the most recent INSERT, UPDATE or DELETE statement, not including
+ ** the most recent INSERT, UPDATE or DELETE statement, not including
** any changes made by trigger programs.
*/
case DB_CHANGES: {
Tcl_Obj *pResult;
if( objc!=2 ){
@@ -1998,11 +2080,11 @@
pCollate->interp = interp;
pCollate->pNext = pDb->pCollate;
pCollate->zScript = (char*)&pCollate[1];
pDb->pCollate = pCollate;
memcpy(pCollate->zScript, zScript, nScript+1);
- if( sqlite3_create_collation(pDb->db, zName, SQLITE_UTF8,
+ if( sqlite3_create_collation(pDb->db, zName, SQLITE_UTF8,
pCollate, tclSqlCollate) ){
Tcl_SetResult(interp, (char *)sqlite3_errmsg(pDb->db), TCL_VOLATILE);
return TCL_ERROR;
}
break;
@@ -2124,11 +2206,11 @@
Tcl_Obj *pResult; /* interp result */
const char *zSep;
const char *zNull;
if( objc<5 || objc>7 ){
- Tcl_WrongNumArgs(interp, 2, objv,
+ Tcl_WrongNumArgs(interp, 2, objv,
"CONFLICT-ALGORITHM TABLE FILENAME ?SEPARATOR? ?NULLINDICATOR?");
return TCL_ERROR;
}
if( objc>=6 ){
zSep = Tcl_GetStringFromObj(objv[5], 0);
@@ -2153,11 +2235,11 @@
if(strcmp(zConflict, "rollback") != 0 &&
strcmp(zConflict, "abort" ) != 0 &&
strcmp(zConflict, "fail" ) != 0 &&
strcmp(zConflict, "ignore" ) != 0 &&
strcmp(zConflict, "replace" ) != 0 ) {
- Tcl_AppendResult(interp, "Error: \"", zConflict,
+ Tcl_AppendResult(interp, "Error: \"", zConflict,
"\", conflict-algorithm must be one of: rollback, "
"abort, fail, ignore, or replace", (char*)0);
return TCL_ERROR;
}
zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable);
@@ -2242,11 +2324,11 @@
break;
}
for(i=0; i0 && strcmp(azCol[i], zNull)==0)
- || strlen30(azCol[i])==0
+ || strlen30(azCol[i])==0
){
sqlite3_bind_null(pStmt, i+1);
}else{
sqlite3_bind_text(pStmt, i+1, azCol[i], -1, SQLITE_STATIC);
}
@@ -2321,11 +2403,11 @@
** $db onecolumn $sql
**
** The onecolumn method is the equivalent of:
** lindex [$db eval $sql] 0
*/
- case DB_EXISTS:
+ case DB_EXISTS:
case DB_ONECOLUMN: {
Tcl_Obj *pResult = 0;
DbEvalContext sEval;
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 2, objv, "SQL");
@@ -2349,11 +2431,11 @@
if( rc==TCL_BREAK ){
rc = TCL_OK;
}
break;
}
-
+
/*
** $db eval $sql ?array? ?{ ...code... }?
**
** The SQL statement in $sql is evaluated. For each row, the values are
** placed in elements of the array named "array" and ...code... is executed.
@@ -2395,11 +2477,11 @@
if( objc==5 && *(char *)Tcl_GetString(objv[3]) ){
pArray = objv[3];
}
pScript = objv[objc-1];
Tcl_IncrRefCount(pScript);
-
+
p = (DbEvalContext *)Tcl_Alloc(sizeof(DbEvalContext));
dbEvalInit(p, pDb, objv[2], pArray);
cd2[0] = (void *)p;
cd2[1] = (void *)pScript;
@@ -2442,11 +2524,11 @@
i++;
}else
if( n>2 && strncmp(z, "-deterministic",n)==0 ){
flags |= SQLITE_DETERMINISTIC;
}else{
- Tcl_AppendResult(interp, "bad option \"", z,
+ Tcl_AppendResult(interp, "bad option \"", z,
"\": must be -argcount or -deterministic", 0
);
return TCL_ERROR;
}
}
@@ -2551,11 +2633,11 @@
Tcl_SetObjResult(interp, Tcl_NewStringObj(pDb->zNull, -1));
break;
}
/*
- ** $db last_insert_rowid
+ ** $db last_insert_rowid
**
** Return an integer which is the ROWID for the most recent insert.
*/
case DB_LAST_INSERT_ROWID: {
Tcl_Obj *pResult;
@@ -2573,11 +2655,11 @@
/*
** The DB_ONECOLUMN method is implemented together with DB_EXISTS.
*/
/* $db progress ?N CALLBACK?
- **
+ **
** Invoke the given callback every N virtual machine opcodes while executing
** queries.
*/
case DB_PROGRESS: {
if( objc==2 ){
@@ -2680,11 +2762,11 @@
break;
}
/* $db restore ?DATABASE? FILENAME
**
- ** Open a database file named FILENAME. Transfer the content
+ ** Open a database file named FILENAME. Transfer the content
** of FILENAME into the local database DATABASE (default: "main").
*/
case DB_RESTORE: {
const char *zSrcFile;
const char *zDestDb;
@@ -2741,11 +2823,11 @@
}
/*
** $db status (step|sort|autoindex)
**
- ** Display SQLITE_STMTSTATUS_FULLSCAN_STEP or
+ ** Display SQLITE_STMTSTATUS_FULLSCAN_STEP or
** SQLITE_STMTSTATUS_SORT for the most recent eval.
*/
case DB_STATUS: {
int v;
const char *zOp;
@@ -2759,19 +2841,19 @@
}else if( strcmp(zOp, "sort")==0 ){
v = pDb->nSort;
}else if( strcmp(zOp, "autoindex")==0 ){
v = pDb->nIndex;
}else{
- Tcl_AppendResult(interp,
- "bad argument: should be autoindex, step, or sort",
+ Tcl_AppendResult(interp,
+ "bad argument: should be autoindex, step, or sort",
(char*)0);
return TCL_ERROR;
}
Tcl_SetObjResult(interp, Tcl_NewIntObj(v));
break;
}
-
+
/*
** $db timeout MILLESECONDS
**
** Delay for the number of milliseconds specified when a file is locked.
*/
@@ -2783,15 +2865,15 @@
}
if( Tcl_GetIntFromObj(interp, objv[2], &ms) ) return TCL_ERROR;
sqlite3_busy_timeout(pDb->db, ms);
break;
}
-
+
/*
** $db total_changes
**
- ** Return the number of rows that were modified, inserted, or deleted
+ ** Return the number of rows that were modified, inserted, or deleted
** since the database handle was created.
*/
case DB_TOTAL_CHANGES: {
Tcl_Obj *pResult;
if( objc!=2 ){
@@ -2836,10 +2918,92 @@
sqlite3_trace(pDb->db, DbTraceHandler, pDb);
}else{
sqlite3_trace(pDb->db, 0, 0);
}
#endif
+ }
+ break;
+ }
+
+ /* $db trace_v2 ?CALLBACK? ?MASK?
+ **
+ ** Make arrangements to invoke the CALLBACK routine for each trace event
+ ** matching the mask that is generated. The parameters are appended to
+ ** CALLBACK before it is executed.
+ */
+ case DB_TRACE_V2: {
+ if( objc>4 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK? ?MASK?");
+ return TCL_ERROR;
+ }else if( objc==2 ){
+ if( pDb->zTraceV2 ){
+ Tcl_AppendResult(interp, pDb->zTraceV2, (char*)0);
+ }
+ }else{
+ char *zTraceV2;
+ int len;
+ Tcl_WideInt wMask = 0;
+ if( objc==4 ){
+ static const char *TTYPE_strs[] = {
+ "statement", "profile", "row", "close", 0
+ };
+ enum TTYPE_enum {
+ TTYPE_STMT, TTYPE_PROFILE, TTYPE_ROW, TTYPE_CLOSE
+ };
+ int i;
+ if( TCL_OK!=Tcl_ListObjLength(interp, objv[3], &len) ){
+ return TCL_ERROR;
+ }
+ for(i=0; izTraceV2 ){
+ Tcl_Free(pDb->zTraceV2);
+ }
+ zTraceV2 = Tcl_GetStringFromObj(objv[2], &len);
+ if( zTraceV2 && len>0 ){
+ pDb->zTraceV2 = Tcl_Alloc( len + 1 );
+ memcpy(pDb->zTraceV2, zTraceV2, len+1);
+ }else{
+ pDb->zTraceV2 = 0;
+ }
+#if !defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_OMIT_FLOATING_POINT)
+ if( pDb->zTraceV2 ){
+ pDb->interp = interp;
+ sqlite3_trace_v2(pDb->db, (unsigned)wMask, DbTraceV2Handler, pDb);
+ }else{
+ sqlite3_trace_v2(pDb->db, 0, 0, 0);
+ }
+#endif
}
break;
}
/* $db transaction [-deferred|-immediate|-exclusive] SCRIPT
@@ -2892,11 +3056,11 @@
pDb->nTransaction++;
/* If using NRE, schedule a callback to invoke the script pScript, then
** a second callback to commit (or rollback) the transaction or savepoint
** opened above. If not using NRE, evaluate the script directly, then
- ** call function DbTransPostCmd() to commit (or rollback) the transaction
+ ** call function DbTransPostCmd() to commit (or rollback) the transaction
** or savepoint. */
if( DbUseNre() ){
Tcl_NRAddCallback(interp, DbTransPostCmd, cd, 0, 0, 0);
(void)Tcl_NREvalObj(interp, pScript, 0);
}else{
@@ -2923,18 +3087,18 @@
if( pDb->pUnlockNotify ){
Tcl_DecrRefCount(pDb->pUnlockNotify);
pDb->pUnlockNotify = 0;
}
-
+
if( objc==3 ){
xNotify = DbUnlockNotify;
pNotifyArg = (void *)pDb;
pDb->pUnlockNotify = objv[2];
Tcl_IncrRefCount(pDb->pUnlockNotify);
}
-
+
if( sqlite3_unlock_notify(pDb->db, xNotify, pNotifyArg) ){
Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), (char*)0);
rc = TCL_ERROR;
}
}
@@ -3029,17 +3193,17 @@
/*
** $db wal_hook ?script?
** $db update_hook ?script?
** $db rollback_hook ?script?
*/
- case DB_WAL_HOOK:
- case DB_UPDATE_HOOK:
+ case DB_WAL_HOOK:
+ case DB_UPDATE_HOOK:
case DB_ROLLBACK_HOOK: {
- /* set ppHook to point at pUpdateHook or pRollbackHook, depending on
+ /* set ppHook to point at pUpdateHook or pRollbackHook, depending on
** whether [$db update_hook] or [$db rollback_hook] was invoked.
*/
- Tcl_Obj **ppHook = 0;
+ Tcl_Obj **ppHook = 0;
if( choice==DB_WAL_HOOK ) ppHook = &pDb->pWalHook;
if( choice==DB_UPDATE_HOOK ) ppHook = &pDb->pUpdateHook;
if( choice==DB_ROLLBACK_HOOK ) ppHook = &pDb->pRollbackHook;
if( objc>3 ){
Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
@@ -3196,11 +3360,11 @@
Tcl_AppendResult(interp, "unknown option: ", zArg, (char*)0);
return TCL_ERROR;
}
}
if( objc<3 || (objc&1)!=1 ){
- Tcl_WrongNumArgs(interp, 1, objv,
+ Tcl_WrongNumArgs(interp, 1, objv,
"HANDLE FILENAME ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN?"
" ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN? ?-uri BOOLEAN?"
#if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_CODEC_FROM_TCL)
" ?-key CODECKEY?"
#endif
@@ -3488,11 +3652,11 @@
/*
* Update context to reflect the concatenation of another buffer full
* of bytes.
*/
-static
+static
void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len){
uint32 t;
/* Update bitcount */
@@ -3534,11 +3698,11 @@
memcpy(ctx->in, buf, len);
}
/*
- * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
* 1 0* (64-bit count of bits processed, MSB-first)
*/
static void MD5Final(unsigned char digest[16], MD5Context *ctx){
unsigned count;
unsigned char *p;
@@ -3610,20 +3774,20 @@
zDigest[j] = 0;
}
/*
** A TCL command for md5. The argument is the text to be hashed. The
-** Result is the hash in base64.
+** Result is the hash in base64.
*/
static int md5_cmd(void*cd, Tcl_Interp *interp, int argc, const char **argv){
MD5Context ctx;
unsigned char digest[16];
char zBuf[50];
void (*converter)(unsigned char*, char*);
if( argc!=2 ){
- Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
+ Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
" TEXT\"", (char*)0);
return TCL_ERROR;
}
MD5Init(&ctx);
MD5Update(&ctx, (unsigned char*)argv[1], (unsigned)strlen(argv[1]));
@@ -3644,17 +3808,17 @@
void (*converter)(unsigned char*, char*);
unsigned char digest[16];
char zBuf[10240];
if( argc!=2 ){
- Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
+ Tcl_AppendResult(interp,"wrong # args: should be \"", argv[0],
" FILENAME\"", (char*)0);
return TCL_ERROR;
}
in = fopen(argv[1],"rb");
if( in==0 ){
- Tcl_AppendResult(interp,"unable to open file \"", argv[1],
+ Tcl_AppendResult(interp,"unable to open file \"", argv[1],
"\" for reading", (char*)0);
return TCL_ERROR;
}
MD5Init(&ctx);
for(;;){
@@ -3717,11 +3881,11 @@
MD5Final(digest,p);
MD5DigestToBase16(digest, zBuf);
sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
}
int Md5_Register(sqlite3 *db){
- int rc = sqlite3_create_function(db, "md5sum", -1, SQLITE_UTF8, 0, 0,
+ int rc = sqlite3_create_function(db, "md5sum", -1, SQLITE_UTF8, 0, 0,
md5step, md5finalize);
sqlite3_overload_function(db, "md5sum", -1); /* To exercise this API */
return rc;
}
#endif /* defined(SQLITE_TEST) */
@@ -3868,11 +4032,11 @@
/*
** Configure the interpreter passed as the first argument to have access
** to the commands and linked variables that make up:
**
-** * the [sqlite3] extension itself,
+** * the [sqlite3] extension itself,
**
** * If SQLITE_TCLMD5 or SQLITE_TEST is defined, the Md5 commands, and
**
** * If SQLITE_TEST is set, the various test interfaces used by the Tcl
** test suite.
Index: src/test1.c
==================================================================
--- src/test1.c
+++ src/test1.c
@@ -3631,11 +3631,11 @@
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
sqlite3_stmt *pStmt;
- int idx;
+ int len, idx;
int bytes;
char *value;
int rc;
sqlite3_destructor_type xDestructor = SQLITE_TRANSIENT;
@@ -3650,12 +3650,21 @@
objv++;
}
if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
- value = Tcl_GetString(objv[3]);
+
+ value = (char*)Tcl_GetByteArrayFromObj(objv[3], &len);
if( Tcl_GetIntFromObj(interp, objv[4], &bytes) ) return TCL_ERROR;
+
+ if( bytes>len ){
+ char zBuf[200];
+ sqlite3_snprintf(sizeof(zBuf), zBuf,
+ "cannot use %d blob bytes, have %d", bytes, len);
+ Tcl_AppendResult(interp, zBuf, -1);
+ return TCL_ERROR;
+ }
rc = sqlite3_bind_blob(pStmt, idx, value, bytes, xDestructor);
if( sqlite3TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
if( rc!=SQLITE_OK ){
return TCL_ERROR;
@@ -4383,10 +4392,30 @@
}
if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
Tcl_SetResult(interp, (char *)sqlite3_sql(pStmt), TCL_VOLATILE);
return TCL_OK;
+}
+static int test_ex_sql(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3_stmt *pStmt;
+ char *z;
+
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "STMT");
+ return TCL_ERROR;
+ }
+
+ if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+ z = sqlite3_expanded_sql(pStmt);
+ Tcl_SetResult(interp, z, TCL_VOLATILE);
+ sqlite3_free(z);
+ return TCL_OK;
}
/*
** Usage: sqlite3_column_count STMT
**
@@ -7274,10 +7303,11 @@
{ "sqlite3_expired", test_expired ,0 },
{ "sqlite3_transfer_bindings", test_transfer_bind ,0 },
{ "sqlite3_changes", test_changes ,0 },
{ "sqlite3_step", test_step ,0 },
{ "sqlite3_sql", test_sql ,0 },
+ { "sqlite3_expanded_sql", test_ex_sql ,0 },
{ "sqlite3_next_stmt", test_next_stmt ,0 },
{ "sqlite3_stmt_readonly", test_stmt_readonly ,0 },
{ "sqlite3_stmt_busy", test_stmt_busy ,0 },
{ "uses_stmt_journal", uses_stmt_journal ,0 },
Index: src/vacuum.c
==================================================================
--- src/vacuum.c
+++ src/vacuum.c
@@ -119,11 +119,11 @@
Btree *pTemp; /* The temporary database we vacuum into */
char *zSql = 0; /* SQL statements */
int saved_flags; /* Saved value of the db->flags */
int saved_nChange; /* Saved value of db->nChange */
int saved_nTotalChange; /* Saved value of db->nTotalChange */
- void (*saved_xTrace)(void*,const char*); /* Saved db->xTrace */
+ u8 saved_mTrace; /* Saved trace settings */
Db *pDb = 0; /* Database to detach at end of vacuum */
int isMemDb; /* True if vacuuming a :memory: database */
int nRes; /* Bytes of reserved space at the end of each page */
int nDb; /* Number of attached databases */
@@ -140,14 +140,14 @@
** restored before returning. Then set the writable-schema flag, and
** disable CHECK and foreign key constraints. */
saved_flags = db->flags;
saved_nChange = db->nChange;
saved_nTotalChange = db->nTotalChange;
- saved_xTrace = db->xTrace;
+ saved_mTrace = db->mTrace;
db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks | SQLITE_PreferBuiltin;
db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder);
- db->xTrace = 0;
+ db->mTrace = 0;
pMain = db->aDb[0].pBt;
isMemDb = sqlite3PagerIsMemdb(sqlite3BtreePager(pMain));
/* Attach the temporary database as 'vacuum_db'. The synchronous pragma
@@ -343,11 +343,11 @@
end_of_vacuum:
/* Restore the original value of db->flags */
db->flags = saved_flags;
db->nChange = saved_nChange;
db->nTotalChange = saved_nTotalChange;
- db->xTrace = saved_xTrace;
+ db->mTrace = saved_mTrace;
sqlite3BtreeSetPageSize(pMain, -1, -1, 1);
/* Currently there is an SQL level transaction open on the vacuum
** database. No locks are held on any other files (since the main file
** was committed at the btree level). So it safe to end the transaction
Index: src/vdbe.c
==================================================================
--- src/vdbe.c
+++ src/vdbe.c
@@ -1380,10 +1380,14 @@
|| (pMem[i].flags & (MEM_Str|MEM_Blob))==0 );
sqlite3VdbeMemNulTerminate(&pMem[i]);
REGISTER_TRACE(pOp->p1+i, &pMem[i]);
}
if( db->mallocFailed ) goto no_mem;
+
+ if( db->mTrace & SQLITE_TRACE_ROW ){
+ db->xTrace(SQLITE_TRACE_ROW, db->pTraceArg, p, 0);
+ }
/* Return SQLITE_ROW
*/
p->pc = (int)(pOp - aOp) + 1;
rc = SQLITE_ROW;
@@ -6779,17 +6783,25 @@
case OP_Init: { /* jump */
char *zTrace;
char *z;
#ifndef SQLITE_OMIT_TRACE
- if( db->xTrace
+ if( (db->mTrace & (SQLITE_TRACE_STMT|SQLITE_TRACE_LEGACY))!=0
&& !p->doingRerun
&& (zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql))!=0
){
z = sqlite3VdbeExpandSql(p, zTrace);
- db->xTrace(db->pTraceArg, z);
- sqlite3DbFree(db, z);
+#ifndef SQLITE_OMIT_DEPRECATED
+ if( db->mTrace & SQLITE_TRACE_LEGACY ){
+ void (*x)(void*,const char*) = (void(*)(void*,const char*))db->xTrace;
+ x(db->pTraceArg, z);
+ }else
+#endif
+ {
+ (void)db->xTrace(SQLITE_TRACE_STMT,db->pTraceArg,p,z);
+ }
+ sqlite3_free(z);
}
#ifdef SQLITE_USE_FCNTL_TRACE
zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql);
if( zTrace ){
int i;
Index: src/vdbeapi.c
==================================================================
--- src/vdbeapi.c
+++ src/vdbeapi.c
@@ -58,16 +58,23 @@
** Invoke the profile callback. This routine is only called if we already
** know that the profile callback is defined and needs to be invoked.
*/
static SQLITE_NOINLINE void invokeProfileCallback(sqlite3 *db, Vdbe *p){
sqlite3_int64 iNow;
+ sqlite3_int64 iElapse;
assert( p->startTime>0 );
- assert( db->xProfile!=0 );
+ assert( db->xProfile!=0 || (db->mTrace & SQLITE_TRACE_PROFILE)!=0 );
assert( db->init.busy==0 );
assert( p->zSql!=0 );
sqlite3OsCurrentTimeInt64(db->pVfs, &iNow);
- db->xProfile(db->pProfileArg, p->zSql, (iNow - p->startTime)*1000000);
+ iElapse = (iNow - p->startTime)*1000000;
+ if( db->xProfile ){
+ db->xProfile(db->pProfileArg, p->zSql, iElapse);
+ }
+ if( db->mTrace & SQLITE_TRACE_PROFILE ){
+ db->xTrace(SQLITE_TRACE_PROFILE, db->pTraceArg, p, (void*)&iElapse);
+ }
p->startTime = 0;
}
/*
** The checkProfileCallback(DB,P) macro checks to see if a profile callback
** is needed, and it invokes the callback if it is needed.
@@ -567,11 +574,12 @@
assert( db->nVdbeWrite>0 || db->autoCommit==0
|| (db->nDeferredCons==0 && db->nDeferredImmCons==0)
);
#ifndef SQLITE_OMIT_TRACE
- if( db->xProfile && !db->init.busy && p->zSql ){
+ if( (db->xProfile || (db->mTrace & SQLITE_TRACE_PROFILE)!=0)
+ && !db->init.busy && p->zSql ){
sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime);
}else{
assert( p->startTime==0 );
}
#endif
@@ -1601,10 +1609,43 @@
#endif
v = pVdbe->aCounter[op];
if( resetFlag ) pVdbe->aCounter[op] = 0;
return (int)v;
}
+
+/*
+** Return the SQL associated with a prepared statement
+*/
+const char *sqlite3_sql(sqlite3_stmt *pStmt){
+ Vdbe *p = (Vdbe *)pStmt;
+ return p ? p->zSql : 0;
+}
+
+/*
+** Return the SQL associated with a prepared statement with
+** bound parameters expanded. Space to hold the returned string is
+** obtained from sqlite3_malloc(). The caller is responsible for
+** freeing the returned string by passing it to sqlite3_free().
+**
+** The SQLITE_TRACE_SIZE_LIMIT puts an upper bound on the size of
+** expanded bound parameters.
+*/
+char *sqlite3_expanded_sql(sqlite3_stmt *pStmt){
+#ifdef SQLITE_OMIT_TRACE
+ return 0;
+#else
+ char *z = 0;
+ const char *zSql = sqlite3_sql(pStmt);
+ if( zSql ){
+ Vdbe *p = (Vdbe *)pStmt;
+ sqlite3_mutex_enter(p->db->mutex);
+ z = sqlite3VdbeExpandSql(p, zSql);
+ sqlite3_mutex_leave(p->db->mutex);
+ }
+ return z;
+#endif
+}
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
/*
** Allocate and populate an UnpackedRecord structure based on the serialized
** record in nKey/pKey. Return a pointer to the new UnpackedRecord structure
Index: src/vdbeaux.c
==================================================================
--- src/vdbeaux.c
+++ src/vdbeaux.c
@@ -62,18 +62,10 @@
assert( p->zSql==0 );
p->zSql = sqlite3DbStrNDup(p->db, z, n);
p->isPrepareV2 = (u8)isPrepareV2;
}
-/*
-** Return the SQL associated with a prepared statement
-*/
-const char *sqlite3_sql(sqlite3_stmt *pStmt){
- Vdbe *p = (Vdbe *)pStmt;
- return p ? p->zSql : 0;
-}
-
/*
** Swap all content between two VDBE structures.
*/
void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){
Vdbe tmp, *pTmp;
Index: src/vdbetrace.c
==================================================================
--- src/vdbetrace.c
+++ src/vdbetrace.c
@@ -79,14 +79,17 @@
int n; /* Length of a token prefix */
int nToken; /* Length of the parameter token */
int i; /* Loop counter */
Mem *pVar; /* Value of a host parameter */
StrAccum out; /* Accumulate the output here */
+#ifndef SQLITE_OMIT_UTF16
+ Mem utf8; /* Used to convert UTF16 parameters into UTF8 for display */
+#endif
char zBase[100]; /* Initial working space */
db = p->db;
- sqlite3StrAccumInit(&out, db, zBase, sizeof(zBase),
+ sqlite3StrAccumInit(&out, 0, zBase, sizeof(zBase),
db->aLimit[SQLITE_LIMIT_LENGTH]);
if( db->nVdbeExec>1 ){
while( *zRawSql ){
const char *zStart = zRawSql;
while( *(zRawSql++)!='\n' && *zRawSql );
@@ -133,16 +136,18 @@
sqlite3XPrintf(&out, "%!.15g", pVar->u.r);
}else if( pVar->flags & MEM_Str ){
int nOut; /* Number of bytes of the string text to include in output */
#ifndef SQLITE_OMIT_UTF16
u8 enc = ENC(db);
- Mem utf8;
if( enc!=SQLITE_UTF8 ){
memset(&utf8, 0, sizeof(utf8));
utf8.db = db;
sqlite3VdbeMemSetStr(&utf8, pVar->z, pVar->n, enc, SQLITE_STATIC);
- sqlite3VdbeChangeEncoding(&utf8, SQLITE_UTF8);
+ if( SQLITE_NOMEM==sqlite3VdbeChangeEncoding(&utf8, SQLITE_UTF8) ){
+ out.accError = STRACCUM_NOMEM;
+ out.nAlloc = 0;
+ }
pVar = &utf8;
}
#endif
nOut = pVar->n;
#ifdef SQLITE_TRACE_SIZE_LIMIT
@@ -180,9 +185,10 @@
}
#endif
}
}
}
+ if( out.accError ) sqlite3StrAccumReset(&out);
return sqlite3StrAccumFinish(&out);
}
#endif /* #ifndef SQLITE_OMIT_TRACE */
Index: test/tclsqlite.test
==================================================================
--- test/tclsqlite.test
+++ test/tclsqlite.test
@@ -32,11 +32,11 @@
lappend v $msg
} [list 1 "wrong # args: should be \"$r\""]
do_test tcl-1.2 {
set v [catch {db bogus} msg]
lappend v $msg
-} {1 {bad option "bogus": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, unlock_notify, update_hook, version, or wal_hook}}
+} {1 {bad option "bogus": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, trace_v2, transaction, unlock_notify, update_hook, version, or wal_hook}}
do_test tcl-1.2.1 {
set v [catch {db cache bogus} msg]
lappend v $msg
} {1 {bad option "bogus": must be flush or size}}
do_test tcl-1.2.2 {
ADDED test/trace3.test
Index: test/trace3.test
==================================================================
--- /dev/null
+++ test/trace3.test
@@ -0,0 +1,233 @@
+# 2016 July 14
+#
+# 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 test file is the "sqlite3_trace_v2()" and "sqlite3_expanded_sql()"
+# APIs.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+ifcapable !trace { finish_test ; return }
+set ::testprefix trace3
+
+proc trace_v2_error { args } {
+ lappend ::stmtlist(error) [string trim $args]
+ error "trace error"; # this will be ignored.
+}
+proc trace_v2_record { args } {
+ lappend ::stmtlist(record) [string trim $args]
+}
+proc trace_v2_nop { args } {}; # do nothing.
+
+do_test trace3-1.0 {
+ execsql {
+ CREATE TABLE t1(a,b);
+ INSERT INTO t1 VALUES(1,NULL);
+ INSERT INTO t1 VALUES(2,-1);
+ INSERT INTO t1 VALUES(3,0);
+ INSERT INTO t1 VALUES(4,1);
+ INSERT INTO t1 VALUES(5,-2147483648);
+ INSERT INTO t1 VALUES(6,2147483647);
+ INSERT INTO t1 VALUES(7,-9223372036854775808);
+ INSERT INTO t1 VALUES(8,9223372036854775807);
+ INSERT INTO t1 VALUES(9,-1.0);
+ INSERT INTO t1 VALUES(10,0.0);
+ INSERT INTO t1 VALUES(11,1.0);
+ INSERT INTO t1 VALUES(12,'');
+ INSERT INTO t1 VALUES(13,'1');
+ INSERT INTO t1 VALUES(14,'one');
+ INSERT INTO t1 VALUES(15,x'abcd0123');
+ INSERT INTO t1 VALUES(16,x'4567cdef');
+ }
+} {}
+
+do_test trace3-1.1 {
+ set rc [catch {db trace_v2 1 2 3} msg]
+ lappend rc $msg
+} {1 {wrong # args: should be "db trace_v2 ?CALLBACK? ?MASK?"}}
+do_test trace3-1.2 {
+ set rc [catch {db trace_v2 1 bad} msg]
+ lappend rc $msg
+} {1 {bad trace type "bad": must be statement, profile, row, or close}}
+
+do_test trace3-2.1 {
+ db trace_v2 trace_v2_nop
+ db trace_v2
+} {trace_v2_nop}
+
+do_test trace3-3.1 {
+ unset -nocomplain ::stmtlist
+ db trace_v2 trace_v2_nop
+ execsql {
+ SELECT a, b FROM t1 ORDER BY a;
+ }
+ array get ::stmtlist
+} {}
+do_test trace3-3.2 {
+ set ::stmtlist(error) {}
+ db trace_v2 trace_v2_error
+ execsql {
+ SELECT a, b FROM t1 ORDER BY a;
+ }
+ set ::stmtlist(error)
+} {/^\{-?\d+ \{SELECT a, b FROM t1 ORDER BY a;\}\}$/}
+do_test trace3-3.3 {
+ set ::stmtlist(record) {}
+ db trace_v2 trace_v2_record
+ execsql {
+ SELECT a, b FROM t1 ORDER BY a;
+ }
+ set ::stmtlist(record)
+} {/^\{-?\d+ \{SELECT a, b FROM t1 ORDER BY a;\}\}$/}
+do_test trace3-3.4 {
+ set ::stmtlist(record) {}
+ db trace_v2 trace_v2_record statement
+ execsql {
+ SELECT a, b FROM t1 ORDER BY a;
+ }
+ set ::stmtlist(record)
+} {/^\{-?\d+ \{SELECT a, b FROM t1 ORDER BY a;\}\}$/}
+do_test trace3-3.5 {
+ set ::stmtlist(record) {}
+ db trace_v2 trace_v2_record 1
+ execsql {
+ SELECT a, b FROM t1 ORDER BY a;
+ }
+ set ::stmtlist(record)
+} {/^\{-?\d+ \{SELECT a, b FROM t1 ORDER BY a;\}\}$/}
+
+do_test trace3-4.1 {
+ set ::stmtlist(record) {}
+ db trace_v2 trace_v2_record profile
+ execsql {
+ SELECT a, b FROM t1 ORDER BY a;
+ }
+ set ::stmtlist(record)
+} {/^\{-?\d+ -?\d+\}$/}
+do_test trace3-4.2 {
+ set ::stmtlist(record) {}
+ db trace_v2 trace_v2_record 2
+ execsql {
+ SELECT a, b FROM t1 ORDER BY a;
+ }
+ set ::stmtlist(record)
+} {/^\{-?\d+ -?\d+\}$/}
+
+do_test trace3-5.1 {
+ set ::stmtlist(record) {}
+ db trace_v2 trace_v2_record row
+ execsql {
+ SELECT a, b FROM t1 ORDER BY a;
+ }
+ set ::stmtlist(record)
+} "/^[string trim [string repeat {\d+ } 16]]\$/"
+do_test trace3-5.2 {
+ set ::stmtlist(record) {}
+ db trace_v2 trace_v2_record 4
+ execsql {
+ SELECT a, b FROM t1 ORDER BY a;
+ }
+ set ::stmtlist(record)
+} "/^[string trim [string repeat {\d+ } 16]]\$/"
+
+do_test trace3-6.1 {
+ set ::stmtlist(record) {}
+ db trace_v2 trace_v2_record {profile row}
+ execsql {
+ SELECT a, b FROM t1 ORDER BY a;
+ }
+ set ::stmtlist(record)
+} "/^[string trim [string repeat {-?\d+ } 16]] \\\{-?\\d+ -?\\d+\\\}\$/"
+do_test trace3-6.2 {
+ set ::stmtlist(record) {}
+ db trace_v2 trace_v2_record {statement profile row}
+ execsql {
+ SELECT a, b FROM t1 ORDER BY a;
+ }
+ set ::stmtlist(record)
+} "/^\\\{-?\\d+ \\\{SELECT a, b FROM t1 ORDER BY a;\\\}\\\} [string trim \
+[string repeat {-?\d+ } 16]] \\\{-?\\d+ -?\\d+\\\}\$/"
+
+do_test trace3-7.1 {
+ set DB [sqlite3_connection_pointer db]
+
+ set STMT [sqlite3_prepare_v2 $DB \
+ "SELECT a, b FROM t1 WHERE b = ? ORDER BY a;" -1 TAIL]
+} {/^[0-9A-Fa-f]+$/}
+
+do_test trace3-8.1 {
+ list [sqlite3_bind_null $STMT 1] [sqlite3_expanded_sql $STMT]
+} {{} {SELECT a, b FROM t1 WHERE b = NULL ORDER BY a;}}
+do_test trace3-8.2 {
+ list [sqlite3_bind_int $STMT 1 123] [sqlite3_expanded_sql $STMT]
+} {{} {SELECT a, b FROM t1 WHERE b = 123 ORDER BY a;}}
+do_test trace3-8.3 {
+ list [sqlite3_bind_int64 $STMT 1 123] [sqlite3_expanded_sql $STMT]
+} {{} {SELECT a, b FROM t1 WHERE b = 123 ORDER BY a;}}
+do_test trace3-8.4 {
+ list [sqlite3_bind_text $STMT 1 "some string" 11] \
+ [sqlite3_expanded_sql $STMT]
+} {{} {SELECT a, b FROM t1 WHERE b = 'some string' ORDER BY a;}}
+do_test trace3-8.5 {
+ list [sqlite3_bind_text $STMT 1 "some 'bad' string" 17] \
+ [sqlite3_expanded_sql $STMT]
+} {{} {SELECT a, b FROM t1 WHERE b = 'some ''bad'' string' ORDER BY a;}}
+do_test trace3-8.6 {
+ list [sqlite3_bind_double $STMT 1 123] [sqlite3_expanded_sql $STMT]
+} {{} {SELECT a, b FROM t1 WHERE b = 123.0 ORDER BY a;}}
+do_test trace3-8.7 {
+ list [sqlite3_bind_text16 $STMT 1 \
+ [encoding convertto unicode hi\000yall\000] 16] \
+ [sqlite3_expanded_sql $STMT]
+} {{} {SELECT a, b FROM t1 WHERE b = 'hi' ORDER BY a;}}
+do_test trace3-8.8 {
+ list [sqlite3_bind_blob $STMT 1 "\x12\x34\x56" 3] \
+ [sqlite3_expanded_sql $STMT]
+} {{} {SELECT a, b FROM t1 WHERE b = x'123456' ORDER BY a;}}
+do_test trace3-8.9 {
+ list [sqlite3_bind_blob $STMT 1 "\xAB\xCD\xEF" 3] \
+ [sqlite3_expanded_sql $STMT]
+} {{} {SELECT a, b FROM t1 WHERE b = x'abcdef' ORDER BY a;}}
+
+do_test trace3-9.1 {
+ sqlite3_finalize $STMT
+} {SQLITE_OK}
+
+do_test trace3-10.1 {
+ db trace_v2 ""
+ db trace_v2
+} {}
+do_test trace3-10.2 {
+ unset -nocomplain ::stmtlist
+ db trace_v2 "" {statement profile row}
+ execsql {
+ SELECT a, b FROM t1 ORDER BY a;
+ }
+ array get ::stmtlist
+} {}
+
+do_test trace3-11.1 {
+ set ::stmtlist(record) {}
+ db trace_v2 trace_v2_record close
+ db close
+ set ::stmtlist(record)
+} {/^-?\d+$/}
+
+reset_db
+
+do_test trace3-11.2 {
+ set ::stmtlist(record) {}
+ db trace_v2 trace_v2_record 8
+ db close
+ set ::stmtlist(record)
+} {/^-?\d+$/}
+
+finish_test