From: drh Date: Mon, 29 Aug 2005 23:00:03 +0000 (+0000) Subject: Increase resolution of time-of-day on unix. Add an experimental X-Git-Tag: version-3.6.10~3520 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=19e2d37f1de2459dad62416124c26cdaa84e82db;p=thirdparty%2Fsqlite.git Increase resolution of time-of-day on unix. Add an experimental sqlite3_profile() API. (CVS 2639) FossilOrigin-Name: ed2ca0873fa89d6cfd123541d5d1c6b92c72b6ab --- diff --git a/manifest b/manifest index 799cedf05c..5ecf55d30d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Initialize\sa\slocal\svariable\sto\savoid\sa\snuisance\scompiler\swarning.\nTicket\s#1394.\s(CVS\s2638) -D 2005-08-29T16:40:53 +C Increase\sresolution\sof\stime-of-day\son\sunix.\s\sAdd\san\sexperimental\nsqlite3_profile()\sAPI.\s(CVS\s2639) +D 2005-08-29T23:00:04 F Makefile.in 12784cdce5ffc8dfb707300c34e4f1eb3b8a14f1 F Makefile.linux-gcc 06be33b2a9ad4f005a5f42b22c4a19dab3cbb5c7 F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028 @@ -34,7 +34,7 @@ F src/attach.c 4b21689700a72ae281fa85dbaff06b2a62bd49ee F src/auth.c 31e2304bef67f44d635655f44234387ea7d21454 F src/btree.c 5b3bc015c49a41c025cfdf8ad36051f3007e2cb0 F src/btree.h 1ed561263ca0e335bc3e81d761c9d5ff8c22f61e -F src/build.c 55e7e915a538bb25b1b7448cfb9189cccba554b2 +F src/build.c 150902037cf8f7a9a09b31b753181a1e5aa23633 F src/callback.c 9a1162c8f9dae9fad6d548339669aacb5f6cf76b F src/complete.c 4de937dfdd4c79a501772ab2035b26082f337a79 F src/date.c 7444b0900a28da77e57e3337a636873cff0ae940 @@ -46,13 +46,13 @@ F src/hash.c 2b1b13f7400e179631c83a1be0c664608c8f021f F src/hash.h 1b0c445e1c89ff2aaad9b4605ba61375af001e84 F src/insert.c 484c73bc1309f283a31baa0e114f3ee980536397 F src/legacy.c d58ea507bce885298a2c8c3cbb0f4bff5d47830b -F src/main.c 60eb224fa5fe65e92dcdfdc542c94bae5e4e2e84 +F src/main.c 8bcd1d2ed92dcb24bafb770eae6e4afce8ddcbde F src/md5.c 7ae1c39044b95de2f62e066f47bb1deb880a1070 F src/os.h c4b34bd4d6fea51a420f337468b907f4edecb161 F src/os_common.h 0e7f428ba0a6c40a61bc56c4e96f493231301b73 F src/os_test.c 91e5f22dd89491e5e1554820e715805f43fa4ece F src/os_test.h 903c93554c23d88f34f667f1979e4a1cee792af3 -F src/os_unix.c 7fae44e25c6137340b786b83ecb29db6ba525a64 +F src/os_unix.c b4c4592589113db088662ef7570967ec36022b5d F src/os_unix.h 5768d56d28240d3fe4537fac08cc85e4fb52279e F src/os_win.c fe7b99cfcfb61d9bf54493ddf5857885a657fb89 F src/os_win.h 41a946bea10f61c158ce8645e7646b29d44f122b @@ -65,11 +65,11 @@ F src/printf.c cea584c5888688c650d856563aadc4296b5afac7 F src/random.c 90adff4e73a3b249eb4f1fc2a6ff9cf78c7233a4 F src/select.c f8a9993bcd953eb325c8c3f32985cc52b2947354 F src/shell.c 7fb744da457b0d11e0af7f6a2f6b000fc09fe588 -F src/sqlite.h.in a3b75a6b2e66865fba4ec1b698d00c7d95fe27a2 -F src/sqliteInt.h fe9520e940c46fa6970a9cb7813b44c3f8925638 +F src/sqlite.h.in d6561d51025d08de4f455607f3f9f9aa76e855d5 +F src/sqliteInt.h f379d29ba17d7082e680c092bce0e6d4bd2d839a F src/table.c 25b3ff2b39b7d87e8d4a5da0713d68dfc06cbee9 -F src/tclsqlite.c e86b5483de6cb1ec1154cc5b76e3427d4b214961 -F src/test1.c 6a36fa85e9d0d4f0eaa7eadd087e40ce9cf35074 +F src/tclsqlite.c ac94682f9e601dd373912c46414a5a842db2089a +F src/test1.c 810b7563b03efc797f350e9370955120e7072c6f F src/test2.c 792f203be69fea88668fa221321194f0a28dfdfa F src/test3.c f4e6a16a602091696619a1171bda25c0e3df49f7 F src/test4.c a8fd681e139e1c61f22a77d07fc3a99cb28fff3f @@ -82,8 +82,8 @@ F src/util.c 5650f6fe5ee30e0678985ad7b94da91e3f85752b F src/vacuum.c 829d9e1a6d7c094b80e0899686670932eafd768c F src/vdbe.c 69f33e22c7d0a64b23fbb69e6da95a1bb6869032 F src/vdbe.h 3b29a9af6c7a64ed692bef1fc5f61338f40d2f67 -F src/vdbeInt.h 89a7fa5dc35477bd30ea27b0bf38e9e5c2903812 -F src/vdbeapi.c f1adebb5e3fe4724ed0e1a82c4a61809d7e15e9e +F src/vdbeInt.h e5f2855b0f0b120d870e0459816061b88b603774 +F src/vdbeapi.c f0d36ff0f06bb5315efac5645b62e99db2c175b8 F src/vdbeaux.c 192e0dbeaaa0bfa652b0c2579c19894e5e5626fc F src/vdbefifo.c 9efb94c8c3f4c979ebd0028219483f88e57584f5 F src/vdbemem.c 4732fd4d1a75dc38549493d7f9a81d02bf7c59b5 @@ -210,13 +210,13 @@ F test/subselect.test 3f3f7a940dc3195c3139f4d530385cb54665d614 F test/sync.test d769caaec48456119316775e35e0fdee2fa852d7 F test/table.test d0e05ede3f6e5a8b79f8661ddcc4618cf7e69f8a F test/tableapi.test 6a66d58b37d46dc0f2b3c7d4bd2617d209399bd1 -F test/tclsqlite.test a8d9afe680c466881a40252a86ef0fca457ab08c +F test/tclsqlite.test 2da3e4b3a79b13c1511c9d0cd995e08f8362e782 F test/temptable.test 7927261befdbc7b0a7ffebb85ecc70a74fa7b15b F test/tester.tcl 98ecdc5723b3b2be5a8a5c3a7f38fa53031466ee F test/thread1.test 776c9e459b75ba905193b351926ac4019b049f35 F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b F test/threadtest2.c 97a830d53c24c42290501fdfba4a6e5bdd34748b -F test/trace.test a54fa8df0d01cf827289a7659d78959e8fd2f955 +F test/trace.test 9fd28695c463b90c2d32c387a432e01eb26e8ccf F test/trans.test 10506dc30305cfb8c4098359f7f6f64786f69c5e F test/trigger1.test 152aed5a1fa90709fe171f2ca501a6b7f7901479 F test/trigger2.test f671b922c88f70c3cd2c6f03fe7c256ae7a52fc4 @@ -299,7 +299,7 @@ F www/tclsqlite.tcl 3df553505b6efcad08f91e9b975deb2e6c9bb955 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0 F www/version3.tcl a99cf5f6d8bd4d5537584a2b342f0fb9fa601d8b F www/whentouse.tcl 97e2b5cd296f7d8057e11f44427dea8a4c2db513 -P ef84ff795c85e9d28f1cac84ff42d8d4ef84cfc4 -R 48080c33fd9c8ec18b4d4c5b999ce67c +P 9b914901a18f8ea39c39a51509c0b3b862c13d6a +R 96cb61bb4ce476297e665f65a9bddb8d U drh -Z 3e9d07ef7774554dfa1e6c7f7ef031ab +Z 62e9fd06364aae1ba0c7353d50daae6d diff --git a/manifest.uuid b/manifest.uuid index 4dd7df2eb4..2a803f4f20 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -9b914901a18f8ea39c39a51509c0b3b862c13d6a \ No newline at end of file +ed2ca0873fa89d6cfd123541d5d1c6b92c72b6ab \ No newline at end of file diff --git a/src/build.c b/src/build.c index 50ad5262d2..d79db7e8cc 100644 --- a/src/build.c +++ b/src/build.c @@ -22,7 +22,7 @@ ** COMMIT ** ROLLBACK ** -** $Id: build.c,v 1.342 2005/08/20 03:03:04 drh Exp $ +** $Id: build.c,v 1.343 2005/08/29 23:00:04 drh Exp $ */ #include "sqliteInt.h" #include @@ -85,6 +85,7 @@ void sqlite3FinishCoding(Parse *pParse){ sqlite3VdbeAddOp(v, OP_Goto, 0, pParse->cookieGoto); } +#ifndef SQLITE_OMIT_TRACE /* Add a No-op that contains the complete text of the compiled SQL ** statement as its P3 argument. This does not change the functionality ** of the program. @@ -92,6 +93,7 @@ void sqlite3FinishCoding(Parse *pParse){ ** This is used to implement sqlite3_trace(). */ sqlite3VdbeOp3(v, OP_Noop, 0, 0, pParse->zSql, pParse->zTail-pParse->zSql); +#endif /* SQLITE_OMIT_TRACE */ } diff --git a/src/main.c b/src/main.c index 41d1045fc5..bfc2bd597a 100644 --- a/src/main.c +++ b/src/main.c @@ -14,7 +14,7 @@ ** other files are for internal use by SQLite and should not be ** accessed by users of the library. ** -** $Id: main.c,v 1.299 2005/08/28 17:00:23 drh Exp $ +** $Id: main.c,v 1.300 2005/08/29 23:00:04 drh Exp $ */ #include "sqliteInt.h" #include "os.h" @@ -509,13 +509,14 @@ int sqlite3_create_function16( } #endif +#ifndef SQLITE_OMIT_TRACE /* ** Register a trace function. The pArg from the previously registered trace ** is returned. ** ** 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 -** sqlite3_exec(). +** SQL statement. */ void *sqlite3_trace(sqlite3 *db, void (*xTrace)(void*,const char*), void *pArg){ void *pOld = db->pTraceArg; @@ -523,6 +524,25 @@ void *sqlite3_trace(sqlite3 *db, void (*xTrace)(void*,const char*), void *pArg){ db->pTraceArg = pArg; return pOld; } +/* +** 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 +** profile is a pointer to a function that is invoked at the conclusion of +** each SQL statement that is run. +*/ +void *sqlite3_profile( + sqlite3 *db, + void (*xProfile)(void*,const char*,sqlite_uint64), + void *pArg +){ + void *pOld = db->pProfileArg; + db->xProfile = xProfile; + db->pProfileArg = pArg; + return pOld; +} +#endif /* SQLITE_OMIT_TRACE */ /*** EXPERIMENTAL *** ** diff --git a/src/os_unix.c b/src/os_unix.c index 2ea9aa8e35..836d01fab7 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -18,6 +18,7 @@ #include +#include #include #include @@ -1420,9 +1421,16 @@ int sqlite3_current_time = 0; ** return 0. Return 1 if the time and date cannot be found. */ int sqlite3OsCurrentTime(double *prNow){ +#ifdef NO_GETTOD time_t t; time(&t); *prNow = t/86400.0 + 2440587.5; +#else + struct timeval sNow; + struct timezone sTz; /* Not used */ + gettimeofday(&sNow, &sTz); + *prNow = 2440587.5 + sNow.tv_sec/86400.0 + sNow.tv_usec/86400000000.0; +#endif #ifdef SQLITE_TEST if( sqlite3_current_time ){ *prNow = sqlite3_current_time/86400.0 + 2440587.5; diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 3f18f88e93..30533e4ac4 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -12,7 +12,7 @@ ** This header file defines the interface that the SQLite library ** presents to client programs. ** -** @(#) $Id: sqlite.h.in,v 1.139 2005/08/11 02:10:19 drh Exp $ +** @(#) $Id: sqlite.h.in,v 1.140 2005/08/29 23:00:04 drh Exp $ */ #ifndef _SQLITE3_H_ #define _SQLITE3_H_ @@ -464,11 +464,18 @@ int sqlite3_set_authorizer( #define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */ /* -** Register a function that is called at every invocation of sqlite3_exec() -** or sqlite3_prepare(). This function can be used (for example) to generate -** a log file of all SQL executed against a database. +** Register a function for tracing SQL command evaluation. The function +** registered by sqlite3_trace() is invoked at the first sqlite3_step() +** for the evaluation of an SQL statement. The function registered by +** sqlite3_profile() runs at the end of each SQL statement and includes +** information on how long that statement ran. +** +** The sqlite3_profile() API is currently considered experimental and +** is subject to change. */ void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*); +void *sqlite3_profile(sqlite3*, + void(*xProfile)(void*,const char*,sqlite_uint64), void*); /* ** This routine configures a callback function - the progress callback - that diff --git a/src/sqliteInt.h b/src/sqliteInt.h index dba03e1f1b..8042ae0473 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -11,7 +11,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.404 2005/08/28 17:00:23 drh Exp $ +** @(#) $Id: sqliteInt.h,v 1.405 2005/08/29 23:00:04 drh Exp $ */ #ifndef _SQLITEINT_H_ #define _SQLITEINT_H_ @@ -420,8 +420,10 @@ struct sqlite3 { } init; struct Vdbe *pVdbe; /* List of active virtual machines */ int activeVdbeCnt; /* Number of vdbes currently executing */ - void (*xTrace)(void*,const char*); /* Trace function */ - void *pTraceArg; /* Argument to the trace function */ + void (*xTrace)(void*,const char*); /* 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. */ void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*); diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 6a36437d64..5e5e6268f2 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -11,7 +11,7 @@ ************************************************************************* ** A TCL Interface to SQLite ** -** $Id: tclsqlite.c,v 1.131 2005/08/16 11:11:35 drh Exp $ +** $Id: tclsqlite.c,v 1.132 2005/08/29 23:00:04 drh Exp $ */ #ifndef NO_TCL /* Omit this whole file if TCL is unavailable */ @@ -84,6 +84,7 @@ struct SqliteDb { char *zBusy; /* The busy callback routine */ char *zCommit; /* The commit hook callback routine */ char *zTrace; /* The trace callback routine */ + char *zProfile; /* The profile callback routine */ char *zProgress; /* The progress callback routine */ char *zAuth; /* The authorization callback routine */ char *zNull; /* Text to substitute for an SQL NULL value */ @@ -190,6 +191,9 @@ static void DbDeleteCmd(void *db){ if( pDb->zTrace ){ Tcl_Free(pDb->zTrace); } + if( pDb->zProfile ){ + Tcl_Free(pDb->zProfile); + } if( pDb->zAuth ){ Tcl_Free(pDb->zAuth); } @@ -247,6 +251,25 @@ static void DbTraceHandler(void *cd, const char *zSql){ Tcl_ResetResult(pDb->interp); } +/* +** This routine is called by the SQLite profile handler after a statement +** SQL has executed. The TCL script in pDb->zProfile is evaluated. +*/ +static void DbProfileHandler(void *cd, const char *zSql, sqlite_uint64 tm){ + SqliteDb *pDb = (SqliteDb*)cd; + Tcl_DString str; + char zTm[100]; + + sqlite3_snprintf(sizeof(zTm)-1, zTm, "%lld", tm); + Tcl_DStringInit(&str); + Tcl_DStringAppend(&str, pDb->zProfile, -1); + Tcl_DStringAppendElement(&str, zSql); + Tcl_DStringAppendElement(&str, zTm); + Tcl_Eval(pDb->interp, Tcl_DStringValue(&str)); + Tcl_DStringFree(&str); + Tcl_ResetResult(pDb->interp); +} + /* ** This routine is called when a transaction is committed. The ** TCL script in pDb->zCommit is executed. If it returns non-zero or @@ -589,9 +612,10 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ "collation_needed", "commit_hook", "complete", "copy", "errorcode", "eval", "function", "last_insert_rowid", "nullvalue", - "onecolumn", "progress", "rekey", - "timeout", "total_changes", "trace", - "transaction", "version", 0 + "onecolumn", "profile", "progress", + "rekey", "timeout", "total_changes", + "trace", "transaction", "version", + 0 }; enum DB_enum { DB_AUTHORIZER, DB_BUSY, DB_CACHE, @@ -599,9 +623,9 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ DB_COLLATION_NEEDED, DB_COMMIT_HOOK, DB_COMPLETE, DB_COPY, DB_ERRORCODE, DB_EVAL, DB_FUNCTION, DB_LAST_INSERT_ROWID,DB_NULLVALUE, - DB_ONECOLUMN, DB_PROGRESS, DB_REKEY, - DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE, - DB_TRANSACTION, DB_VERSION, + DB_ONECOLUMN, DB_PROFILE, DB_PROGRESS, + DB_REKEY, DB_TIMEOUT, DB_TOTAL_CHANGES, + DB_TRACE, DB_TRANSACTION, DB_VERSION }; /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */ @@ -780,44 +804,6 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ break; } - /* $db commit_hook ?CALLBACK? - ** - ** Invoke the given callback just before committing every SQL transaction. - ** If the callback throws an exception or returns non-zero, then the - ** transaction is aborted. If CALLBACK is an empty string, the callback - ** is disabled. - */ - case DB_COMMIT_HOOK: { - if( objc>3 ){ - Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?"); - return TCL_ERROR; - }else if( objc==2 ){ - if( pDb->zCommit ){ - Tcl_AppendResult(interp, pDb->zCommit, 0); - } - }else{ - char *zCommit; - int len; - if( pDb->zCommit ){ - Tcl_Free(pDb->zCommit); - } - zCommit = Tcl_GetStringFromObj(objv[2], &len); - if( zCommit && len>0 ){ - pDb->zCommit = Tcl_Alloc( len + 1 ); - strcpy(pDb->zCommit, zCommit); - }else{ - pDb->zCommit = 0; - } - if( pDb->zCommit ){ - pDb->interp = interp; - sqlite3_commit_hook(pDb->db, DbCommitHandler, pDb); - }else{ - sqlite3_commit_hook(pDb->db, 0, 0); - } - } - break; - } - /* ** $db collate NAME SCRIPT ** @@ -870,6 +856,44 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ break; } + /* $db commit_hook ?CALLBACK? + ** + ** Invoke the given callback just before committing every SQL transaction. + ** If the callback throws an exception or returns non-zero, then the + ** transaction is aborted. If CALLBACK is an empty string, the callback + ** is disabled. + */ + case DB_COMMIT_HOOK: { + if( objc>3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?"); + return TCL_ERROR; + }else if( objc==2 ){ + if( pDb->zCommit ){ + Tcl_AppendResult(interp, pDb->zCommit, 0); + } + }else{ + char *zCommit; + int len; + if( pDb->zCommit ){ + Tcl_Free(pDb->zCommit); + } + zCommit = Tcl_GetStringFromObj(objv[2], &len); + if( zCommit && len>0 ){ + pDb->zCommit = Tcl_Alloc( len + 1 ); + strcpy(pDb->zCommit, zCommit); + }else{ + pDb->zCommit = 0; + } + if( pDb->zCommit ){ + pDb->interp = interp; + sqlite3_commit_hook(pDb->db, DbCommitHandler, pDb); + }else{ + sqlite3_commit_hook(pDb->db, 0, 0); + } + } + break; + } + /* $db complete SQL ** ** Return TRUE if SQL is a complete SQL statement. Return FALSE if @@ -891,137 +915,323 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ break; } - /* - ** $db errorcode + /* $db copy conflict-algorithm table filename ?SEPARATOR? ?NULLINDICATOR? ** - ** Return the numeric error code that was returned by the most recent - ** call to sqlite3_exec(). - */ - case DB_ERRORCODE: { - Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_errcode(pDb->db))); - break; - } - - /* - ** $db eval $sql ?array? ?{ ...code... }? - ** $db onecolumn $sql + ** Copy data into table from filename, optionally using SEPARATOR + ** as column separators. If a column contains a null string, or the + ** value of NULLINDICATOR, a NULL is inserted for the column. + ** conflict-algorithm is one of the sqlite conflict algorithms: + ** rollback, abort, fail, ignore, replace + ** On success, return the number of lines processed, not necessarily same + ** as 'db changes' due to conflict-algorithm selected. ** - ** 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. - ** If "array" and "code" are omitted, then no callback is every invoked. - ** If "array" is an empty string, then the values are placed in variables - ** that have the same name as the fields extracted by the query. + ** This code is basically an implementation/enhancement of + ** the sqlite3 shell.c ".import" command. ** - ** The onecolumn method is the equivalent of: - ** lindex [$db eval $sql] 0 + ** This command usage is equivalent to the sqlite2.x COPY statement, + ** which imports file data into a table using the PostgreSQL COPY file format: + ** $db copy $conflit_algo $table_name $filename \t \\N */ - case DB_ONECOLUMN: - case DB_EVAL: { - char const *zSql; /* Next SQL statement to execute */ - char const *zLeft; /* What is left after first stmt in zSql */ - sqlite3_stmt *pStmt; /* Compiled SQL statment */ - Tcl_Obj *pArray; /* Name of array into which results are written */ - Tcl_Obj *pScript; /* Script to run for each result set */ - Tcl_Obj **apParm; /* Parameters that need a Tcl_DecrRefCount() */ - int nParm; /* Number of entries used in apParm[] */ - Tcl_Obj *aParm[10]; /* Static space for apParm[] in the common case */ - Tcl_Obj *pRet; /* Value to be returned */ - SqlPreparedStmt *pPreStmt; /* Pointer to a prepared statement */ - int rc2; + case DB_COPY: { + char *zTable; /* Insert data into this table */ + char *zFile; /* The file from which to extract data */ + char *zConflict; /* The conflict algorithm to use */ + sqlite3_stmt *pStmt; /* A statement */ + int rc; /* Result code */ + int nCol; /* Number of columns in the table */ + int nByte; /* Number of bytes in an SQL string */ + int i, j; /* Loop counters */ + int nSep; /* Number of bytes in zSep[] */ + int nNull; /* Number of bytes in zNull[] */ + char *zSql; /* An SQL statement */ + char *zLine; /* A single line of input from the file */ + char **azCol; /* zLine[] broken up into columns */ + char *zCommit; /* How to commit changes */ + FILE *in; /* The input file */ + int lineno = 0; /* Line number of input file */ + char zLineNum[80]; /* Line number print buffer */ + Tcl_Obj *pResult; /* interp result */ - if( choice==DB_ONECOLUMN ){ - if( objc!=3 ){ - Tcl_WrongNumArgs(interp, 2, objv, "SQL"); - return TCL_ERROR; - } - pRet = 0; + char *zSep; + char *zNull; + if( objc<5 || objc>7 ){ + Tcl_WrongNumArgs(interp, 2, objv, + "CONFLICT-ALGORITHM TABLE FILENAME ?SEPARATOR? ?NULLINDICATOR?"); + return TCL_ERROR; + } + if( objc>=6 ){ + zSep = Tcl_GetStringFromObj(objv[5], 0); }else{ - if( objc<3 || objc>5 ){ - Tcl_WrongNumArgs(interp, 2, objv, "SQL ?ARRAY-NAME? ?SCRIPT?"); - return TCL_ERROR; - } - pRet = Tcl_NewObj(); - Tcl_IncrRefCount(pRet); + zSep = "\t"; } - if( objc==3 ){ - pArray = pScript = 0; - }else if( objc==4 ){ - pArray = 0; - pScript = objv[3]; + if( objc>=7 ){ + zNull = Tcl_GetStringFromObj(objv[6], 0); }else{ - pArray = objv[3]; - if( Tcl_GetString(pArray)[0]==0 ) pArray = 0; - pScript = objv[4]; + zNull = ""; } - - Tcl_IncrRefCount(objv[2]); - zSql = Tcl_GetStringFromObj(objv[2], 0); - while( rc==TCL_OK && zSql[0] ){ - int i; /* Loop counter */ - int nVar; /* Number of bind parameters in the pStmt */ - int nCol; /* Number of columns in the result set */ - Tcl_Obj **apColName = 0; /* Array of column names */ - int len; /* String length of zSql */ - - /* Try to find a SQL statement that has already been compiled and - ** which matches the next sequence of SQL. - */ - pStmt = 0; - pPreStmt = pDb->stmtList; - len = strlen(zSql); - if( pPreStmt && sqlite3_expired(pPreStmt->pStmt) ){ - flushStmtCache(pDb); - pPreStmt = 0; - } - for(; pPreStmt; pPreStmt=pPreStmt->pNext){ - int n = pPreStmt->nSql; - if( len>=n - && memcmp(pPreStmt->zSql, zSql, n)==0 - && (zSql[n]==0 || zSql[n-1]==';') - ){ - pStmt = pPreStmt->pStmt; - zLeft = &zSql[pPreStmt->nSql]; - - /* When a prepared statement is found, unlink it from the - ** cache list. It will later be added back to the beginning - ** of the cache list in order to implement LRU replacement. - */ - if( pPreStmt->pPrev ){ - pPreStmt->pPrev->pNext = pPreStmt->pNext; - }else{ - pDb->stmtList = pPreStmt->pNext; - } - if( pPreStmt->pNext ){ - pPreStmt->pNext->pPrev = pPreStmt->pPrev; - }else{ - pDb->stmtLast = pPreStmt->pPrev; - } - pDb->nStmt--; - break; - } - } - - /* If no prepared statement was found. Compile the SQL text - */ - if( pStmt==0 ){ - if( SQLITE_OK!=sqlite3_prepare(pDb->db, zSql, -1, &pStmt, &zLeft) ){ - Tcl_SetObjResult(interp, dbTextToObj(sqlite3_errmsg(pDb->db))); - rc = TCL_ERROR; - break; - } - if( pStmt==0 ){ - if( SQLITE_OK!=sqlite3_errcode(pDb->db) ){ - /* A compile-time error in the statement - */ - Tcl_SetObjResult(interp, dbTextToObj(sqlite3_errmsg(pDb->db))); - rc = TCL_ERROR; - break; - }else{ - /* The statement was a no-op. Continue to the next statement - ** in the SQL string. - */ - zSql = zLeft; - continue; + zConflict = Tcl_GetStringFromObj(objv[2], 0); + zTable = Tcl_GetStringFromObj(objv[3], 0); + zFile = Tcl_GetStringFromObj(objv[4], 0); + nSep = strlen(zSep); + nNull = strlen(zNull); + if( nSep==0 ){ + Tcl_AppendResult(interp, "Error: non-null separator required for copy", 0); + return TCL_ERROR; + } + if(sqlite3StrICmp(zConflict, "rollback") != 0 && + sqlite3StrICmp(zConflict, "abort" ) != 0 && + sqlite3StrICmp(zConflict, "fail" ) != 0 && + sqlite3StrICmp(zConflict, "ignore" ) != 0 && + sqlite3StrICmp(zConflict, "replace" ) != 0 ) { + Tcl_AppendResult(interp, "Error: \"", zConflict, + "\", conflict-algorithm must be one of: rollback, " + "abort, fail, ignore, or replace", 0); + return TCL_ERROR; + } + zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable); + if( zSql==0 ){ + Tcl_AppendResult(interp, "Error: no such table: ", zTable, 0); + return TCL_ERROR; + } + nByte = strlen(zSql); + rc = sqlite3_prepare(pDb->db, zSql, 0, &pStmt, 0); + sqlite3_free(zSql); + if( rc ){ + Tcl_AppendResult(interp, "Error: ", sqlite3_errmsg(pDb->db), 0); + nCol = 0; + }else{ + nCol = sqlite3_column_count(pStmt); + } + sqlite3_finalize(pStmt); + if( nCol==0 ) { + return TCL_ERROR; + } + zSql = malloc( nByte + 50 + nCol*2 ); + if( zSql==0 ) { + Tcl_AppendResult(interp, "Error: can't malloc()", 0); + return TCL_ERROR; + } + sqlite3_snprintf(nByte+50, zSql, "INSERT OR %q INTO '%q' VALUES(?", + zConflict, zTable); + j = strlen(zSql); + for(i=1; idb, zSql, 0, &pStmt, 0); + free(zSql); + if( rc ){ + Tcl_AppendResult(interp, "Error: ", sqlite3_errmsg(pDb->db), 0); + sqlite3_finalize(pStmt); + return TCL_ERROR; + } + in = fopen(zFile, "rb"); + if( in==0 ){ + Tcl_AppendResult(interp, "Error: cannot open file: ", zFile, NULL); + sqlite3_finalize(pStmt); + return TCL_ERROR; + } + azCol = malloc( sizeof(azCol[0])*(nCol+1) ); + if( azCol==0 ) { + Tcl_AppendResult(interp, "Error: can't malloc()", 0); + return TCL_ERROR; + } + sqlite3_exec(pDb->db, "BEGIN", 0, 0, 0); + zCommit = "COMMIT"; + while( (zLine = local_getline(0, in))!=0 ){ + char *z; + i = 0; + lineno++; + azCol[0] = zLine; + for(i=0, z=zLine; *z; z++){ + if( *z==zSep[0] && strncmp(z, zSep, nSep)==0 ){ + *z = 0; + i++; + if( i0 && strcmp(azCol[i], zNull)==0) || strlen(azCol[i])==0) { + sqlite3_bind_null(pStmt, i+1); + }else{ + sqlite3_bind_text(pStmt, i+1, azCol[i], -1, SQLITE_STATIC); + } + } + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + free(zLine); + if( rc!=SQLITE_OK ){ + Tcl_AppendResult(interp,"Error: ", sqlite3_errmsg(pDb->db), 0); + zCommit = "ROLLBACK"; + break; + } + } + free(azCol); + fclose(in); + sqlite3_finalize(pStmt); + sqlite3_exec(pDb->db, zCommit, 0, 0, 0); + + if( zCommit[0] == 'C' ){ + /* success, set result as number of lines processed */ + pResult = Tcl_GetObjResult(interp); + Tcl_SetIntObj(pResult, lineno); + rc = TCL_OK; + }else{ + /* failure, append lineno where failed */ + sprintf(zLineNum,"%d",lineno); + Tcl_AppendResult(interp,", failed while processing line: ",zLineNum,0); + rc = TCL_ERROR; + } + break; + } + + /* + ** $db errorcode + ** + ** Return the numeric error code that was returned by the most recent + ** call to sqlite3_exec(). + */ + case DB_ERRORCODE: { + Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_errcode(pDb->db))); + break; + } + + /* + ** $db eval $sql ?array? ?{ ...code... }? + ** $db onecolumn $sql + ** + ** 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. + ** If "array" and "code" are omitted, then no callback is every invoked. + ** If "array" is an empty string, then the values are placed in variables + ** that have the same name as the fields extracted by the query. + ** + ** The onecolumn method is the equivalent of: + ** lindex [$db eval $sql] 0 + */ + case DB_ONECOLUMN: + case DB_EVAL: { + char const *zSql; /* Next SQL statement to execute */ + char const *zLeft; /* What is left after first stmt in zSql */ + sqlite3_stmt *pStmt; /* Compiled SQL statment */ + Tcl_Obj *pArray; /* Name of array into which results are written */ + Tcl_Obj *pScript; /* Script to run for each result set */ + Tcl_Obj **apParm; /* Parameters that need a Tcl_DecrRefCount() */ + int nParm; /* Number of entries used in apParm[] */ + Tcl_Obj *aParm[10]; /* Static space for apParm[] in the common case */ + Tcl_Obj *pRet; /* Value to be returned */ + SqlPreparedStmt *pPreStmt; /* Pointer to a prepared statement */ + int rc2; + + if( choice==DB_ONECOLUMN ){ + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "SQL"); + return TCL_ERROR; + } + pRet = 0; + }else{ + if( objc<3 || objc>5 ){ + Tcl_WrongNumArgs(interp, 2, objv, "SQL ?ARRAY-NAME? ?SCRIPT?"); + return TCL_ERROR; + } + pRet = Tcl_NewObj(); + Tcl_IncrRefCount(pRet); + } + if( objc==3 ){ + pArray = pScript = 0; + }else if( objc==4 ){ + pArray = 0; + pScript = objv[3]; + }else{ + pArray = objv[3]; + if( Tcl_GetString(pArray)[0]==0 ) pArray = 0; + pScript = objv[4]; + } + + Tcl_IncrRefCount(objv[2]); + zSql = Tcl_GetStringFromObj(objv[2], 0); + while( rc==TCL_OK && zSql[0] ){ + int i; /* Loop counter */ + int nVar; /* Number of bind parameters in the pStmt */ + int nCol; /* Number of columns in the result set */ + Tcl_Obj **apColName = 0; /* Array of column names */ + int len; /* String length of zSql */ + + /* Try to find a SQL statement that has already been compiled and + ** which matches the next sequence of SQL. + */ + pStmt = 0; + pPreStmt = pDb->stmtList; + len = strlen(zSql); + if( pPreStmt && sqlite3_expired(pPreStmt->pStmt) ){ + flushStmtCache(pDb); + pPreStmt = 0; + } + for(; pPreStmt; pPreStmt=pPreStmt->pNext){ + int n = pPreStmt->nSql; + if( len>=n + && memcmp(pPreStmt->zSql, zSql, n)==0 + && (zSql[n]==0 || zSql[n-1]==';') + ){ + pStmt = pPreStmt->pStmt; + zLeft = &zSql[pPreStmt->nSql]; + + /* When a prepared statement is found, unlink it from the + ** cache list. It will later be added back to the beginning + ** of the cache list in order to implement LRU replacement. + */ + if( pPreStmt->pPrev ){ + pPreStmt->pPrev->pNext = pPreStmt->pNext; + }else{ + pDb->stmtList = pPreStmt->pNext; + } + if( pPreStmt->pNext ){ + pPreStmt->pNext->pPrev = pPreStmt->pPrev; + }else{ + pDb->stmtLast = pPreStmt->pPrev; + } + pDb->nStmt--; + break; + } + } + + /* If no prepared statement was found. Compile the SQL text + */ + if( pStmt==0 ){ + if( SQLITE_OK!=sqlite3_prepare(pDb->db, zSql, -1, &pStmt, &zLeft) ){ + Tcl_SetObjResult(interp, dbTextToObj(sqlite3_errmsg(pDb->db))); + rc = TCL_ERROR; + break; + } + if( pStmt==0 ){ + if( SQLITE_OK!=sqlite3_errcode(pDb->db) ){ + /* A compile-time error in the statement + */ + Tcl_SetObjResult(interp, dbTextToObj(sqlite3_errmsg(pDb->db))); + rc = TCL_ERROR; + break; + }else{ + /* The statement was a no-op. Continue to the next statement + ** in the SQL string. + */ + zSql = zLeft; + continue; } } assert( pPreStmt==0 ); @@ -1301,6 +1511,37 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ break; } + /* + ** $db nullvalue ?STRING? + ** + ** Change text used when a NULL comes back from the database. If ?STRING? + ** is not present, then the current string used for NULL is returned. + ** If STRING is present, then STRING is returned. + ** + */ + case DB_NULLVALUE: { + if( objc!=2 && objc!=3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "NULLVALUE"); + return TCL_ERROR; + } + if( objc==3 ){ + int len; + char *zNull = Tcl_GetStringFromObj(objv[2], &len); + if( pDb->zNull ){ + Tcl_Free(pDb->zNull); + } + if( zNull && len>0 ){ + pDb->zNull = Tcl_Alloc( len + 1 ); + strncpy(pDb->zNull, zNull, len); + pDb->zNull[len] = '\0'; + }else{ + pDb->zNull = 0; + } + } + Tcl_SetObjResult(interp, dbTextToObj(pDb->zNull)); + break; + } + /* ** $db last_insert_rowid ** @@ -1365,6 +1606,45 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ break; } + /* $db profile ?CALLBACK? + ** + ** Make arrangements to invoke the CALLBACK routine after each SQL statement + ** that has run. The text of the SQL and the amount of elapse time are + ** appended to CALLBACK before the script is run. + */ + case DB_PROFILE: { + if( objc>3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?"); + return TCL_ERROR; + }else if( objc==2 ){ + if( pDb->zProfile ){ + Tcl_AppendResult(interp, pDb->zProfile, 0); + } + }else{ + char *zProfile; + int len; + if( pDb->zProfile ){ + Tcl_Free(pDb->zProfile); + } + zProfile = Tcl_GetStringFromObj(objv[2], &len); + if( zProfile && len>0 ){ + pDb->zProfile = Tcl_Alloc( len + 1 ); + strcpy(pDb->zProfile, zProfile); + }else{ + pDb->zProfile = 0; + } +#ifndef SQLITE_OMIT_TRACE + if( pDb->zProfile ){ + pDb->interp = interp; + sqlite3_profile(pDb->db, DbProfileHandler, pDb); + }else{ + sqlite3_profile(pDb->db, 0, 0); + } +#endif + } + break; + } + /* ** $db rekey KEY ** @@ -1403,37 +1683,6 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ sqlite3_busy_timeout(pDb->db, ms); break; } - - /* - ** $db nullvalue ?STRING? - ** - ** Change text used when a NULL comes back from the database. If ?STRING? - ** is not present, then the current string used for NULL is returned. - ** If STRING is present, then STRING is returned. - ** - */ - case DB_NULLVALUE: { - if( objc!=2 && objc!=3 ){ - Tcl_WrongNumArgs(interp, 2, objv, "NULLVALUE"); - return TCL_ERROR; - } - if( objc==3 ){ - int len; - char *zNull = Tcl_GetStringFromObj(objv[2], &len); - if( pDb->zNull ){ - Tcl_Free(pDb->zNull); - } - if( zNull && len>0 ){ - pDb->zNull = Tcl_Alloc( len + 1 ); - strncpy(pDb->zNull, zNull, len); - pDb->zNull[len] = '\0'; - }else{ - pDb->zNull = 0; - } - } - Tcl_SetObjResult(interp, dbTextToObj(pDb->zNull)); - break; - } /* ** $db total_changes @@ -1479,12 +1728,14 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ }else{ pDb->zTrace = 0; } +#ifndef SQLITE_OMIT_TRACE if( pDb->zTrace ){ pDb->interp = interp; sqlite3_trace(pDb->db, DbTraceHandler, pDb); }else{ sqlite3_trace(pDb->db, 0, 0); } +#endif } break; } @@ -1546,192 +1797,6 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ break; } - /* $db copy conflict-algorithm table filename ?SEPARATOR? ?NULLINDICATOR? - ** - ** Copy data into table from filename, optionally using SEPARATOR - ** as column separators. If a column contains a null string, or the - ** value of NULLINDICATOR, a NULL is inserted for the column. - ** conflict-algorithm is one of the sqlite conflict algorithms: - ** rollback, abort, fail, ignore, replace - ** On success, return the number of lines processed, not necessarily same - ** as 'db changes' due to conflict-algorithm selected. - ** - ** This code is basically an implementation/enhancement of - ** the sqlite3 shell.c ".import" command. - ** - ** This command usage is equivalent to the sqlite2.x COPY statement, - ** which imports file data into a table using the PostgreSQL COPY file format: - ** $db copy $conflit_algo $table_name $filename \t \\N - */ - case DB_COPY: { - char *zTable; /* Insert data into this table */ - char *zFile; /* The file from which to extract data */ - char *zConflict; /* The conflict algorithm to use */ - sqlite3_stmt *pStmt; /* A statement */ - int rc; /* Result code */ - int nCol; /* Number of columns in the table */ - int nByte; /* Number of bytes in an SQL string */ - int i, j; /* Loop counters */ - int nSep; /* Number of bytes in zSep[] */ - int nNull; /* Number of bytes in zNull[] */ - char *zSql; /* An SQL statement */ - char *zLine; /* A single line of input from the file */ - char **azCol; /* zLine[] broken up into columns */ - char *zCommit; /* How to commit changes */ - FILE *in; /* The input file */ - int lineno = 0; /* Line number of input file */ - char zLineNum[80]; /* Line number print buffer */ - Tcl_Obj *pResult; /* interp result */ - - char *zSep; - char *zNull; - if( objc<5 || objc>7 ){ - Tcl_WrongNumArgs(interp, 2, objv, - "CONFLICT-ALGORITHM TABLE FILENAME ?SEPARATOR? ?NULLINDICATOR?"); - return TCL_ERROR; - } - if( objc>=6 ){ - zSep = Tcl_GetStringFromObj(objv[5], 0); - }else{ - zSep = "\t"; - } - if( objc>=7 ){ - zNull = Tcl_GetStringFromObj(objv[6], 0); - }else{ - zNull = ""; - } - zConflict = Tcl_GetStringFromObj(objv[2], 0); - zTable = Tcl_GetStringFromObj(objv[3], 0); - zFile = Tcl_GetStringFromObj(objv[4], 0); - nSep = strlen(zSep); - nNull = strlen(zNull); - if( nSep==0 ){ - Tcl_AppendResult(interp, "Error: non-null separator required for copy", 0); - return TCL_ERROR; - } - if(sqlite3StrICmp(zConflict, "rollback") != 0 && - sqlite3StrICmp(zConflict, "abort" ) != 0 && - sqlite3StrICmp(zConflict, "fail" ) != 0 && - sqlite3StrICmp(zConflict, "ignore" ) != 0 && - sqlite3StrICmp(zConflict, "replace" ) != 0 ) { - Tcl_AppendResult(interp, "Error: \"", zConflict, - "\", conflict-algorithm must be one of: rollback, " - "abort, fail, ignore, or replace", 0); - return TCL_ERROR; - } - zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable); - if( zSql==0 ){ - Tcl_AppendResult(interp, "Error: no such table: ", zTable, 0); - return TCL_ERROR; - } - nByte = strlen(zSql); - rc = sqlite3_prepare(pDb->db, zSql, 0, &pStmt, 0); - sqlite3_free(zSql); - if( rc ){ - Tcl_AppendResult(interp, "Error: ", sqlite3_errmsg(pDb->db), 0); - nCol = 0; - }else{ - nCol = sqlite3_column_count(pStmt); - } - sqlite3_finalize(pStmt); - if( nCol==0 ) { - return TCL_ERROR; - } - zSql = malloc( nByte + 50 + nCol*2 ); - if( zSql==0 ) { - Tcl_AppendResult(interp, "Error: can't malloc()", 0); - return TCL_ERROR; - } - sqlite3_snprintf(nByte+50, zSql, "INSERT OR %q INTO '%q' VALUES(?", - zConflict, zTable); - j = strlen(zSql); - for(i=1; idb, zSql, 0, &pStmt, 0); - free(zSql); - if( rc ){ - Tcl_AppendResult(interp, "Error: ", sqlite3_errmsg(pDb->db), 0); - sqlite3_finalize(pStmt); - return TCL_ERROR; - } - in = fopen(zFile, "rb"); - if( in==0 ){ - Tcl_AppendResult(interp, "Error: cannot open file: ", zFile, NULL); - sqlite3_finalize(pStmt); - return TCL_ERROR; - } - azCol = malloc( sizeof(azCol[0])*(nCol+1) ); - if( azCol==0 ) { - Tcl_AppendResult(interp, "Error: can't malloc()", 0); - return TCL_ERROR; - } - sqlite3_exec(pDb->db, "BEGIN", 0, 0, 0); - zCommit = "COMMIT"; - while( (zLine = local_getline(0, in))!=0 ){ - char *z; - i = 0; - lineno++; - azCol[0] = zLine; - for(i=0, z=zLine; *z; z++){ - if( *z==zSep[0] && strncmp(z, zSep, nSep)==0 ){ - *z = 0; - i++; - if( i0 && strcmp(azCol[i], zNull)==0) || strlen(azCol[i])==0) { - sqlite3_bind_null(pStmt, i+1); - }else{ - sqlite3_bind_text(pStmt, i+1, azCol[i], -1, SQLITE_STATIC); - } - } - sqlite3_step(pStmt); - rc = sqlite3_reset(pStmt); - free(zLine); - if( rc!=SQLITE_OK ){ - Tcl_AppendResult(interp,"Error: ", sqlite3_errmsg(pDb->db), 0); - zCommit = "ROLLBACK"; - break; - } - } - free(azCol); - fclose(in); - sqlite3_finalize(pStmt); - sqlite3_exec(pDb->db, zCommit, 0, 0, 0); - - if( zCommit[0] == 'C' ){ - /* success, set result as number of lines processed */ - pResult = Tcl_GetObjResult(interp); - Tcl_SetIntObj(pResult, lineno); - rc = TCL_OK; - }else{ - /* failure, append lineno where failed */ - sprintf(zLineNum,"%d",lineno); - Tcl_AppendResult(interp,", failed while processing line: ",zLineNum,0); - rc = TCL_ERROR; - } - break; - } - /* $db version ** ** Return the version string for this database. diff --git a/src/test1.c b/src/test1.c index e181f09e10..cd0c65585c 100644 --- a/src/test1.c +++ b/src/test1.c @@ -13,7 +13,7 @@ ** is not included in the SQLite library. It is used for automated ** testing of the SQLite library. ** -** $Id: test1.c,v 1.155 2005/08/19 00:14:42 drh Exp $ +** $Id: test1.c,v 1.156 2005/08/29 23:00:05 drh Exp $ */ #include "sqliteInt.h" #include "tcl.h" @@ -2954,6 +2954,12 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "threadsafe", "0", TCL_GLOBAL_ONLY); #endif +#ifdef SQLITE_OMIT_TRACE + Tcl_SetVar2(interp, "sqlite_options", "trace", "0", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "trace", "1", TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_OMIT_TRIGGER Tcl_SetVar2(interp, "sqlite_options", "trigger", "0", TCL_GLOBAL_ONLY); #else diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 7bf2a0ec5d..34e8d230be 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -358,6 +358,7 @@ struct Vdbe { u8 aborted; /* True if ROLLBACK in another VM causes an abort */ u8 expired; /* True if the VM needs to be recompiled */ int nChange; /* Number of db changes made since last reset */ + u64 startTime; /* Time when query started - used for profiling */ }; /* diff --git a/src/vdbeapi.c b/src/vdbeapi.c index dfe8f12c41..75af8a9b65 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -15,6 +15,7 @@ */ #include "sqliteInt.h" #include "vdbeInt.h" +#include "os.h" /* ** Return TRUE (non-zero) of the statement supplied as an argument needs @@ -173,9 +174,10 @@ int sqlite3_step(sqlite3_stmt *pStmt){ return SQLITE_MISUSE; } if( p->pc<0 ){ +#ifndef SQLITE_OMIT_TRACE /* Invoke the trace callback if there is one */ - if( (db = p->db)->xTrace && !db->init.busy ){ + if( db->xTrace && !db->init.busy ){ assert( p->nOp>0 ); assert( p->aOp[p->nOp-1].opcode==OP_Noop ); assert( p->aOp[p->nOp-1].p3!=0 ); @@ -187,6 +189,12 @@ int sqlite3_step(sqlite3_stmt *pStmt){ return SQLITE_MISUSE; } } + if( db->xProfile && !db->init.busy ){ + double rNow; + sqlite3OsCurrentTime(&rNow); + p->startTime = (rNow - (int)rNow)*3600.0*24.0*1000000000.0; + } +#endif /* Print a copy of SQL as it is executed if the SQL_TRACE pragma is turned ** on in debugging mode. @@ -213,6 +221,23 @@ int sqlite3_step(sqlite3_stmt *pStmt){ rc = SQLITE_MISUSE; } +#ifndef SQLITE_OMIT_TRACE + /* Invoke the profile callback if there is one + */ + if( rc!=SQLITE_ROW && db->xProfile && !db->init.busy ){ + double rNow; + u64 elapseTime; + + sqlite3OsCurrentTime(&rNow); + elapseTime = (rNow - (int)rNow)*3600.0*24.0*1000000000.0 - p->startTime; + assert( p->nOp>0 ); + assert( p->aOp[p->nOp-1].opcode==OP_Noop ); + assert( p->aOp[p->nOp-1].p3!=0 ); + assert( p->aOp[p->nOp-1].p3type==P3_DYNAMIC ); + db->xProfile(db->pProfileArg, p->aOp[p->nOp-1].p3, elapseTime); + } +#endif + sqlite3Error(p->db, rc, p->zErrMsg ? "%s" : 0, p->zErrMsg); return rc; } diff --git a/test/tclsqlite.test b/test/tclsqlite.test index fd52b245df..9de166915e 100644 --- a/test/tclsqlite.test +++ b/test/tclsqlite.test @@ -15,7 +15,7 @@ # interface is pretty well tested. This file contains some addition # tests for fringe issues that the main test suite does not cover. # -# $Id: tclsqlite.test,v 1.43 2005/08/02 17:15:16 drh Exp $ +# $Id: tclsqlite.test,v 1.44 2005/08/29 23:00:05 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -34,7 +34,7 @@ do_test tcl-1.1 { do_test tcl-1.2 { set v [catch {db bogus} msg] lappend v $msg -} {1 {bad option "bogus": must be authorizer, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, errorcode, eval, function, last_insert_rowid, nullvalue, onecolumn, progress, rekey, timeout, total_changes, trace, transaction, or version}} +} {1 {bad option "bogus": must be authorizer, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, errorcode, eval, function, last_insert_rowid, nullvalue, onecolumn, profile, progress, rekey, timeout, total_changes, trace, transaction, or version}} do_test tcl-1.3 { execsql {CREATE TABLE t1(a int, b int)} execsql {INSERT INTO t1 VALUES(10,20)} diff --git a/test/trace.test b/test/trace.test index ea1756076a..da81904ec7 100644 --- a/test/trace.test +++ b/test/trace.test @@ -12,11 +12,16 @@ # # This file implements tests for the "sqlite3_trace()" API. # -# $Id: trace.test,v 1.4 2004/09/17 20:46:55 drh Exp $ +# $Id: trace.test,v 1.5 2005/08/29 23:00:05 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl +ifcapable !trace { + finish_test + return +} + set ::stmtlist {} do_test trace-1.1 { set rc [catch {db trace 1 2 3} msg] @@ -77,5 +82,67 @@ do_test trace-2.5 { } {SELECT * FROM t1} catch {sqlite3_finalize $STMT} +# Similar tests, but this time for profiling. +# +do_test trace-3.1 { + set rc [catch {db profile 1 2 3} msg] + lappend rc $msg +} {1 {wrong # args: should be "db profile ?CALLBACK?"}} +set ::stmtlist {} +proc profile_proc {cmd tm} { + lappend ::stmtlist [string trim $cmd] +} +do_test trace-3.2 { + db trace {} + db profile profile_proc + db profile +} {profile_proc} +do_test trace-3.3 { + execsql { + CREATE TABLE t2(a,b); + INSERT INTO t2 VALUES(1,2); + SELECT * FROM t2; + } +} {1 2} +do_test trace-3.4 { + set ::stmtlist +} {{CREATE TABLE t2(a,b);} {INSERT INTO t2 VALUES(1,2);} {SELECT * FROM t2;}} +do_test trace-3.5 { + db profile {} + db profile +} {} + +# If we prepare a statement and execute it multiple times, the profile +# happens on each execution. +# +db close +set DB [sqlite3 db test.db] +do_test trace-4.1 { + set STMT [sqlite3_prepare $DB {INSERT INTO t2 VALUES(2,3)} -1 TAIL] + db trace trace_proc + proc profile_proc {sql tm} { + global TRACE_OUT + set TRACE_OUT $sql + } + set TRACE_OUT {} + sqlite3_step $STMT + set TRACE_OUT +} {INSERT INTO t2 VALUES(2,3)} +do_test trace-4.2 { + set TRACE_OUT {} + sqlite3_reset $STMT + set TRACE_OUT +} {} +do_test trace-4.3 { + sqlite3_step $STMT + set TRACE_OUT +} {INSERT INTO t2 VALUES(2,3)} +do_test trace-4.4 { + execsql {SELECT * FROM t1} +} {1 2 2 3 2 3} +do_test trace-4.5 { + set TRACE_OUT +} {SELECT * FROM t1} +catch {sqlite3_finalize $STMT} finish_test