From: drh Date: Mon, 9 Jan 2006 23:40:25 +0000 (+0000) Subject: Added database server example code (untested). Fixed serveral bugs including X-Git-Tag: version-3.6.10~3270 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=29c636bc121744f23918071f303e4b0225a0af07;p=thirdparty%2Fsqlite.git Added database server example code (untested). Fixed serveral bugs including the autovacuum compile problem described by ticket #1593. (CVS 2897) FossilOrigin-Name: ec332d8822d1ac9673581a26ab2a2fce5f2554a3 --- diff --git a/main.mk b/main.mk index 82a940b5ec..5c4205d0b9 100644 --- a/main.mk +++ b/main.mk @@ -127,12 +127,14 @@ TESTSRC = \ $(TOP)/src/pager.c \ $(TOP)/src/pragma.c \ $(TOP)/src/printf.c \ + $(TOP)/src/server.c \ $(TOP)/src/test1.c \ $(TOP)/src/test2.c \ $(TOP)/src/test3.c \ $(TOP)/src/test4.c \ $(TOP)/src/test5.c \ $(TOP)/src/test6.c \ + $(TOP)/src/test7.c \ $(TOP)/src/test_async.c \ $(TOP)/src/utf.c \ $(TOP)/src/util.c \ @@ -357,7 +359,7 @@ tclsqlite3: $(TOP)/src/tclsqlite.c libsqlite3.a testfixture$(EXE): $(TOP)/src/tclsqlite.c libsqlite3.a $(TESTSRC) $(TCCX) $(TCL_FLAGS) -DTCLSH=1 -DSQLITE_TEST=1 -DSQLITE_CRASH_TEST=1 \ - -o testfixture$(EXE) \ + -DSQLITE_SERVER=1 -o testfixture$(EXE) \ $(TESTSRC) $(TOP)/src/tclsqlite.c \ libsqlite3.a $(LIBTCL) $(THREADLIB) diff --git a/manifest b/manifest index d5d1883ad2..0ced8ac74d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Reinstate\sthe\sasynchronous\sI/O\sdemonstration\scode\sand\stests\swith\sminor\nchanges.\s(CVS\s2896) -D 2006-01-09T17:29:53 +C Added\sdatabase\sserver\sexample\scode\s(untested).\s\sFixed\sserveral\sbugs\sincluding\nthe\sautovacuum\scompile\sproblem\sdescribed\sby\sticket\s#1593.\s(CVS\s2897) +D 2006-01-09T23:40:25 F Makefile.in c79fbdaa264c6afcd435f2fb492551de5a8cf80d F Makefile.linux-gcc aee18d8a05546dcf1888bd4547e442008a49a092 F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028 @@ -19,7 +19,7 @@ F doc/lemon.html f0f682f50210928c07e562621c3b7e8ab912a538 F doc/report1.txt a031aaf37b185e4fa540223cb516d3bccec7eeac F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 F ltmain.sh f6b283068efa69f06eb8aa1fe4bddfdbdeb35826 -F main.mk b290d1bbd341da3c8f05471b2b8059488980eee8 +F main.mk f397e2343efec5f89f6481c694439e0f50c30ee1 F mkdll.sh 5ec23622515d5bf8969404e80cfb5e220ddf0512 F mkopcodec.awk bd46ad001c98dfbab07b1713cb8e692fa0e5415d F mkopcodeh.awk 071dbba4eaf56c8d643baf4604a043af35683316 @@ -32,11 +32,11 @@ F sqlite3.def c413e514217736884254739a105c8c942fdf0c2f F sqlite3.pc.in 985b9bf34192a549d7d370e0f0b6b34a4f61369a F src/alter.c d0dd079b9ef0d551ff4a4ce09ee270c07b307bbb F src/analyze.c ed87abad3f6f60e1bd5308ed6ff6e0fa396db52b -F src/attach.c fed27f5430db877012886c4b740a4a42f9176dbd +F src/attach.c b5b63e93ea2cba627033ecc4a671d2d4bc3d06a9 F src/auth.c cdec356a5cd8b217c346f816c5912221537fe87f F src/btree.c 5e5dff4a667d93d49925d38de2d451a5bd1eabfd F src/btree.h 5663c4f43e8521546ccebc8fc95acb013b8f3184 -F src/build.c ff75e783aa638482d2598a443f88e2ff40abc301 +F src/build.c db1d1bd861032a8954030be1fcf3b7decb858dbb F src/callback.c 1c2b78a210fda18cdd4d0b604ed41bf0e1f5125c F src/complete.c df1681cef40dec33a286006981845f87b194e7a4 F src/date.c a927bdbb51296ac398d2f667086a7072c099e5ab @@ -55,11 +55,11 @@ F src/os.h 8710c0068f3386a73a37f8ad242b92c7580464df F src/os_common.h 78bcc34dded9b625b3c16d072b7e5b76d075a674 F src/os_test.c 49833426101f99aee4bb5f6a44b7c4b2029fda1c F src/os_test.h 903c93554c23d88f34f667f1979e4a1cee792af3 -F src/os_unix.c c5d1a3fe88ad93b5f6a29f1b5b889f2a756aaec0 +F src/os_unix.c 8943febbedc79649d21ec1ed14de14ad8214c899 F src/os_unix.h 5768d56d28240d3fe4537fac08cc85e4fb52279e -F src/os_win.c 9cedcb13d6bcd4ab5f50907a99112f45cd335893 +F src/os_win.c 8ef250a0965a55dbcd8ef785e03f9f3292e6ade2 F src/os_win.h 41a946bea10f61c158ce8645e7646b29d44f122b -F src/pager.c 6fbcc25f1f3fbe62cbbda3af3dbb9c3d758f8050 +F src/pager.c f84488fa616f1279761aaf06b0acc42af345d3a5 F src/pager.h e0acb095b3ad0bca48f2ab00c87346665643f64f F src/parse.y 83df51fea35f68f7e07384d75dce83d1ed30434c F src/pragma.c 05abacaa5a1817a44876d9dbb4790aa7c784427b @@ -67,17 +67,19 @@ F src/prepare.c 41d9a8563e2b988932922c9f96a7bb1271932564 F src/printf.c f47a2f4b5387cd2ebb12e9117a1a5d6bd9a2b812 F src/random.c d40f8d356cecbd351ccfab6eaedd7ec1b54f5261 F src/select.c acfeda959fe07eac04950d945ac7ec0518ef4b7d +F src/server.c 934555eb73d9051c2ba1fc5367f0f969516dfec8 F src/shell.c 66b073375efbdee19045e7e0cd38b85f9aff71da F src/sqlite.h.in 537bac9e9ef703695268aaad5c56b6bf64f815be -F src/sqliteInt.h 175c7cc358f15aa4f621257ce035e1f2ec2511f3 +F src/sqliteInt.h 073d84b12a07b25a31e08a3fd5115d44424511c3 F src/table.c 486dcfce532685b53b5a2b5da8bba0ded6fb2316 -F src/tclsqlite.c da11b003e4ef8d4cf2cf9b10dd5ed0e3fbe0229d +F src/tclsqlite.c d650bea0248fc0a310ddc2cb94273a3a5021fddf F src/test1.c 5f634ce9aa452dbcc362993c9612047df800216c F src/test2.c ca74a1d8aeb7d9606e8f6b762c5daf85c1a3f92b F src/test3.c 9742aa146eb750cab81c1d5605286c3a0eb88054 F src/test4.c a8fd681e139e1c61f22a77d07fc3a99cb28fff3f F src/test5.c 7162f8526affb771c4ed256826eee7bb9eca265f F src/test6.c 74d91b487c68154156eded457925d96aa2a3fdbb +F src/test7.c bfe36c62cae189509660acfeeb891ffb9da8ef0c F src/test_async.c 9733deb7fefa18a3596e5234c1ef05b4685c6ad7 F src/tokenize.c 196486012c871cdcad6cc84a820cc988603f1b9d F src/trigger.c c515afa5fe964e9854c54dc66dde179a90d0cfe3 @@ -127,7 +129,7 @@ F test/btree7.test a6d3b842db22af97dd14b989e90a2fd96066b72f F test/btree8.test fadc112bcbd6a0c622d34c813fc8a648eacf8804 F test/busy.test 0271c854738e23ad76e10d4096a698e5af29d211 F test/capi2.test b9354d6c37e6f8f858c08952ebc9709712581221 -F test/capi3.test 1b8afa5c0b1851e3fc8a0de71c2e0de13e84fe60 +F test/capi3.test 523bae084ad1aa0085f458dc0c087a660e11c786 F test/capi3b.test 5f0bc94b104e11086b1103b20277e1910f59c7f4 F test/cast.test 2543165ced4249c89ce5f0352222df503a98b9e5 F test/check.test 8154b8ac0c56c34088168b8d87eee713fba2b31b @@ -260,7 +262,7 @@ F test/types2.test 81dd1897be8ef4b5b73d0006e6076abe40610de3 F test/types3.test e5f789503849294de74a23b433168e8211523a25 F test/unique.test 0253c4227a5dc533e312202ce21ecfad18058d18 F test/update.test 7669ca789d62c258b678e8aa7a22a57eac10f2cf -F test/utf16.test 5fb019e09601774743858ef7380b6c02103ff120 +F test/utf16.test f9c13f4e2b48c42d0bfc96647d82fdf7bc11fc55 F test/vacuum.test 61e2b6e7dcf0eec90cdf6cc63e30321c8b037b1f F test/vacuum2.test 5d77e98c458bcdbeecc6327de5107179ba1aa095 F test/varint.test ab7b110089a08b9926ed7390e7e97bdefeb74102 @@ -337,7 +339,7 @@ F www/tclsqlite.tcl bb0d1357328a42b1993d78573e587c6dcbc964b9 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0 F www/version3.tcl a99cf5f6d8bd4d5537584a2b342f0fb9fa601d8b F www/whentouse.tcl 97e2b5cd296f7d8057e11f44427dea8a4c2db513 -P 3e75d3d5efebc0dfff1adfc13d85e85ec39db3eb -R 897c69b19acbeb3b4a3cc9a16f3fcd4a +P eeebc640aaeeb0ab7f730d854069f159aa41968b +R be8fced19e9aacb1a6894a77c76ca49c U drh -Z 83bea30e8578b8d7e23d4aaa03c329e0 +Z f9c4ad5567aaf51e37a39a8cf2edcc05 diff --git a/manifest.uuid b/manifest.uuid index 94b82aae5e..9473f51699 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -eeebc640aaeeb0ab7f730d854069f159aa41968b \ No newline at end of file +ec332d8822d1ac9673581a26ab2a2fce5f2554a3 \ No newline at end of file diff --git a/src/attach.c b/src/attach.c index 3192ab1492..e2d6234d4f 100644 --- a/src/attach.c +++ b/src/attach.c @@ -11,7 +11,7 @@ ************************************************************************* ** This file contains code used to implement the ATTACH and DETACH commands. ** -** $Id: attach.c,v 1.42 2006/01/09 16:12:05 danielk1977 Exp $ +** $Id: attach.c,v 1.43 2006/01/09 23:40:25 drh Exp $ */ #include "sqliteInt.h" @@ -126,8 +126,7 @@ static void attachFunc( aNew->pSchema = sqlite3SchemaGet(aNew->pBt); if( !aNew->pSchema ){ rc = SQLITE_NOMEM; - } - if( aNew->pSchema->file_format && aNew->pSchema->enc!=ENC(db) ){ + }else if( aNew->pSchema->file_format && aNew->pSchema->enc!=ENC(db) ){ strcpy(zErr, "attached databases must use the same text encoding as main database"); goto attach_error; diff --git a/src/build.c b/src/build.c index e2cee79d38..36fbfd6745 100644 --- a/src/build.c +++ b/src/build.c @@ -22,7 +22,7 @@ ** COMMIT ** ROLLBACK ** -** $Id: build.c,v 1.372 2006/01/09 16:12:05 danielk1977 Exp $ +** $Id: build.c,v 1.373 2006/01/09 23:40:25 drh Exp $ */ #include "sqliteInt.h" #include @@ -1799,9 +1799,10 @@ static void destroyRootPage(Parse *pParse, int iTable, int iDb){ static void destroyTable(Parse *pParse, Table *pTab){ #ifdef SQLITE_OMIT_AUTOVACUUM Index *pIdx; - destroyRootPage(pParse, pTab->tnum, pTab->iDb); + int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); + destroyRootPage(pParse, pTab->tnum, iDb); for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - destroyRootPage(pParse, pIdx->tnum, pIdx->iDb); + destroyRootPage(pParse, pIdx->tnum, iDb); } #else /* If the database may be auto-vacuum capable (if SQLITE_OMIT_AUTOVACUUM diff --git a/src/os_unix.c b/src/os_unix.c index 34da78dd65..42570c6ae8 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -502,14 +502,9 @@ static int findLockInfo( struct stat statbuf; struct lockInfo *pLock; struct openCnt *pOpen; - ThreadData *pTsd = sqlite3ThreadData(); rc = fstat(fd, &statbuf); if( rc!=0 ) return 1; - /* Disable the sqlite3_release_memory() function */ - assert( !pTsd->disableReleaseMemory ); - pTsd->disableReleaseMemory = 1; - memset(&key1, 0, sizeof(key1)); key1.dev = statbuf.st_dev; key1.ino = statbuf.st_ino; @@ -573,8 +568,6 @@ static int findLockInfo( *ppOpen = pOpen; exit_findlockinfo: - /* Re-enable sqlite3_release_memory() */ - pTsd->disableReleaseMemory = 0; return rc; } @@ -1407,7 +1400,6 @@ static int unixUnlock(OsFile *id, int locktype){ ** Close a file. */ static int unixClose(OsFile **pId){ - ThreadData *pTsd = sqlite3ThreadData(); unixFile *id = (unixFile*)*pId; if( !id ) return SQLITE_OK; if( CHECK_THREADID(id) ) return SQLITE_MISUSE; @@ -1416,10 +1408,6 @@ static int unixClose(OsFile **pId){ id->dirfd = -1; sqlite3OsEnterMutex(); - /* Disable the sqlite3_release_memory() function */ - assert( !pTsd->disableReleaseMemory ); - pTsd->disableReleaseMemory = 1; - if( id->pOpen->nLock ){ /* If there are outstanding locks, do not actually close the file just ** yet because that would clear those locks. Instead, add the file @@ -1443,9 +1431,6 @@ static int unixClose(OsFile **pId){ releaseLockInfo(id->pLock); releaseOpenCnt(id->pOpen); - /* Disable the sqlite3_release_memory() function */ - pTsd->disableReleaseMemory = 0; - sqlite3OsLeaveMutex(); id->isOpen = 0; TRACE2("CLOSE %-3d\n", id->h); @@ -1636,9 +1621,6 @@ void sqlite3UnixLeaveMutex(){ /* ** Return TRUE if we are currently within the mutex and FALSE if not. -** This routine is intended for sanity checking only. It is designed -** for use in an assert() to verify that the mutex is held or not held -** in certain routines. */ int sqlite3UnixInMutex(){ return inMutex; diff --git a/src/os_win.c b/src/os_win.c index fdf28e2ae4..38a1a3b7a2 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -1106,9 +1106,6 @@ void sqlite3WinLeaveMutex(){ /* ** Return TRUE if we are currently within the mutex and FALSE if not. -** This routine is intended for sanity checking only. It is designed -** for use in an assert() to verify that the mutex is held or not held -** in certain routines. */ int sqlite3WinInMutex(){ return inMutex; diff --git a/src/pager.c b/src/pager.c index 020477b358..08582ef01f 100644 --- a/src/pager.c +++ b/src/pager.c @@ -18,7 +18,7 @@ ** file simultaneously, or one process from reading the database while ** another is writing. ** -** @(#) $Id: pager.c,v 1.233 2006/01/09 09:59:49 danielk1977 Exp $ +** @(#) $Id: pager.c,v 1.234 2006/01/09 23:40:25 drh Exp $ */ #ifndef SQLITE_OMIT_DISKIO #include "sqliteInt.h" @@ -2468,13 +2468,12 @@ int sqlite3pager_release_memory(int nReq){ int nReleased = 0; int i; - /* If the disableReleaseMemory memory flag is set, this operation is - ** a no-op; zero bytes of memory are freed. The flag is set before - ** malloc() is called while the global mutex (see sqlite3OsEnterMutex) - ** is held. Because some of the code invoked by this function may also - ** try to obtain the mutex, proceding may cause a deadlock. + /* If the the global mutex is held, this subroutine becomes a + ** o-op; zero bytes of memory are freed. This is because + ** some of the code invoked by this function may also + ** try to obtain the mutex, resulting in a deadlock. */ - if( pTsd->disableReleaseMemory ){ + if( sqlite3OsInMutex() ){ return 0; } @@ -2484,7 +2483,7 @@ int sqlite3pager_release_memory(int nReq){ ** memory) is permitted to call fsync(). This is of course much more ** expensive. */ - for(i=0; i==0 || i==1; i++){ + for(i=0; i<=1; i++){ /* Loop through all the SQLite pagers opened by the current thread. */ for(p=pTsd->pPager; p && (nReq<0 || nReleasedpNext){ diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000000..0adcc268e2 --- /dev/null +++ b/src/server.c @@ -0,0 +1,427 @@ +/* +** 2006 January 07 +** +** 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 contains demonstration code. Nothing in this file gets compiled +** or linked into the SQLite library unless you use a non-standard option: +** +** -DSQLITE_SERVER=1 +** +** The configure script will never generate a Makefile with the option +** above. You will need to manually modify the Makefile if you want to +** include any of the code from this file in your project. Or, at your +** option, you may want to copy and paste the code from this file and +** thereby avoiding a recompile of SQLite. +** +** +** This source file demonstrates how to use SQLite to create an SQL database +** server thread in a multiple-threaded program. One or more client threads +** send messages to the server thread and the server thread processes those +** messages in the order received and returns the results to the client. +** +** One might ask: "Why bother? Why not just let each thread connect +** to the database directly?" There are a several of reasons to +** prefer the client/server approach. +** +** (1) Some systems (ex: Redhat9) have broken threading implementations +** that prevent SQLite database connections from being used in +** a thread different from the one where they were created. With +** the client/server approach, all database connections are created +** and used within the server thread. Client calls to the database +** can be made from multiple threads (though not at the same time!) +** +** (2) Beginning with SQLite version 3.3.0, when two or more +** connections to the same database occur within the same thread, +** they will share their database cache. This reduces I/O +** and memory requirements. +** +** (3) Database connections on a shared cache use table-level locking +** instead of file-level locking for improved concurrency. +** +** (4) Database connections on a shared cache can by optionally +** set to READ UNCOMMITTED isolation. (The default isolation for +** SQLite is SERIALIZABLE.) When this occurs, readers will +** never be blocked by a writer and writers will not be +** blocked by readers. There can still only be a single writer +** at a time, but multiple readers can simultaneously exist with +** that writer. This is a huge increase in concurrency. +** +** To summarize the rational for using a client/server approach: prior +** to SQLite version 3.3.0 it probably was not worth the trouble. But +** with SQLite version 3.3.0 and beyond you can get significant performance +** and concurrency improvements and memory usage reductions by going +** client/server. +** +** Note: The extra features of version 3.3.0 described by points (2) +** through (4) above are only available if you compile without the +** option -DSQLITE_OMIT_SHARED_CACHE. For reasons of backwards +** compatibility, SQLite is compile with this option by default. +** +** Here is how the client/server approach works: The database server +** thread is started on this procedure: +** +** void *sqlite3_server(void *NotUsed); +** +** The sqlite_server procedure runs as long as the g.serverHalt variable +** is false. A mutex is used to make sure no more than one server runs +** at a time. The server waits for messages to arrive on a message +** queue and processes the messages in order. +** +** Two convenience routines are provided for starting and stopping the +** server thread: +** +** void sqlite3_server_start(void); +** void sqlite3_server_stop(void); +** +** Both of the convenience routines return immediately. Neither will +** ever give an error. If a server is already started or already halted, +** then the routines are effectively no-ops. +** +** Clients use the following interfaces: +** +** sqlite3_client_open +** sqlite3_client_prepare +** sqlite3_client_step +** sqlite3_client_reset +** sqlite3_client_finalize +** sqlite3_client_close +** +** These interfaces work exactly like the standard core SQLite interfaces +** having the same names without the "_client_" infix. Many other SQLite +** interfaces can be used directly without having to send messages to the +** server. The following interfaces fall into this second category: +** +** sqlite3_bind_* +** sqlite3_changes +** sqlite3_clear_bindings +** sqlite3_column_* +** sqlite3_complete +** sqlite3_create_collation +** sqlite3_create_function +** sqlite3_data_count +** sqlite3_db_handle +** sqlite3_errcode +** sqlite3_errmsg +** sqlite3_last_insert_rowid +** sqlite3_libversion +** sqlite3_mprintf +** sqlite3_total_changes +** sqlite3_transfer_bindings +** sqlite3_vmprintf +** +** A single SQLite connection (an sqlite3* object) or an SQLite statement +** (an sqlite3_stmt* object) should only be passed to a single interface +** function at a time. The connections and statements can be freely used +** by any thread as long as only one thread is using them at a time. +** +** The busy handler for all database connections should remain turned +** off. That means that any lock contention will cause the associated +** sqlite3_client_step() call to return immediately with an SQLITE_BUSY +** error code. If a busy handler is enabled and lock contention occurs, +** then the entire server thread will block. This will cause not only +** the requesting client to block but every other database client as +** well. It is possible to enhance the code below so that lock +** contention will cause the message to be placed back on the top of +** the queue to be tried again later. But such enhanced processing is +** not included here, in order to keep the example simple. +** +** This code assumes the use of pthreads. Pthreads implementations +** are available for windows. (See, for example +** http://sourceware.org/pthreads-win32/announcement.html.) Or, you +** can translate the locking and thread synchronization code to use +** windows primitives easily enough. The details are left as an +** exercise to the reader. +*/ + +/* +** Only compile the code in this file on UNIX with a THREADSAFE build +** and only if the SQLITE_SERVER macro is defined. +*/ +#ifdef SQLITE_SERVER +#if defined(OS_UNIX) && OS_UNIX && defined(THREADSAFE) && THREADSAFE + +/* +** We require only pthreads and the public interface of SQLite. +*/ +#include +#include "sqlite3.h" + +/* +** Messages are passed from client to server and back again as +** instances of the following structure. +*/ +typedef struct SqlMessage SqlMessage; +struct SqlMessage { + int op; /* Opcode for the message */ + sqlite3 *pDb; /* The SQLite connection */ + sqlite3_stmt *pStmt; /* A specific statement */ + int errCode; /* Error code returned */ + const char *zIn; /* Input filename or SQL statement */ + int nByte; /* Size of the zIn parameter for prepare() */ + const char *zOut; /* Tail of the SQL statement */ + SqlMessage *pNext; /* Next message in the queue */ + SqlMessage *pPrev; /* Previous message in the queue */ + pthread_mutex_t clientMutex; /* Hold this mutex to access the message */ + pthread_cond_t clientWakeup; /* Signal to wake up the client */ +}; + +/* +** Legal values for SqlMessage.op +*/ +#define MSG_Open 1 /* sqlite3_open(zIn, &pDb) */ +#define MSG_Prepare 2 /* sqlite3_prepare(pDb, zIn, nByte, &pStmt, &zOut) */ +#define MSG_Step 3 /* sqlite3_step(pStmt) */ +#define MSG_Reset 4 /* sqlite3_reset(pStmt) */ +#define MSG_Finalize 5 /* sqlite3_finalize(pStmt) */ +#define MSG_Close 6 /* sqlite3_close(pDb) */ +#define MSG_Done 7 /* Server has finished with this message */ + + +/* +** State information about the server is stored in a static variable +** named "g" as follows: +*/ +static struct ServerState { + pthread_mutex_t queueMutex; /* Hold this mutex to access the msg queue */ + pthread_mutex_t serverMutex; /* Held by the server while it is running */ + pthread_cond_t serverWakeup; /* Signal this condvar to wake up the server */ + volatile int serverHalt; /* Server halts itself when true */ + SqlMessage *pQueueHead; /* Head of the message queue */ + SqlMessage *pQueueTail; /* Tail of the message queue */ +} g = { + PTHREAD_MUTEX_INITIALIZER, + PTHREAD_MUTEX_INITIALIZER, + PTHREAD_COND_INITIALIZER, +}; + +/* +** Send a message to the server. Block until we get a reply. +** +** The mutex and condition variable in the message are uninitialized +** when this routine is called. This routine takes care of +** initializing them and destroying them when it has finished. +*/ +static void sendToServer(SqlMessage *pMsg){ + /* Initialize the mutex and condition variable on the message + */ + pthread_mutex_init(&pMsg->clientMutex, 0); + pthread_cond_init(&pMsg->clientWakeup, 0); + + /* Add the message to the head of the server's message queue. + */ + pthread_mutex_lock(&g.queueMutex); + pMsg->pNext = g.pQueueHead; + if( g.pQueueHead==0 ){ + g.pQueueTail = pMsg; + }else{ + g.pQueueHead->pPrev = pMsg; + } + pMsg->pPrev = 0; + g.pQueueHead = pMsg; + pthread_mutex_unlock(&g.queueMutex); + + /* Signal the server that the new message has be queued, then + ** block waiting for the server to process the message. + */ + pthread_mutex_lock(&pMsg->clientMutex); + pthread_cond_signal(&g.serverWakeup); + while( pMsg->op!=MSG_Done ){ + pthread_cond_wait(&pMsg->clientWakeup, &pMsg->clientMutex); + } + pthread_mutex_unlock(&pMsg->clientMutex); + + /* Destroy the mutex and condition variable of the message. + */ + pthread_mutex_destroy(&pMsg->clientMutex); + pthread_cond_destroy(&pMsg->clientWakeup); +} + +/* +** The following 6 routines are client-side implementations of the +** core SQLite interfaces: +** +** sqlite3_open +** sqlite3_prepare +** sqlite3_step +** sqlite3_reset +** sqlite3_finalize +** sqlite3_close +** +** Clients should use the following client-side routines instead of +** the core routines. +** +** sqlite3_client_open +** sqlite3_client_prepare +** sqlite3_client_step +** sqlite3_client_reset +** sqlite3_client_finalize +** sqlite3_client_close +** +** Each of these routines creates a message for the desired operation, +** sends that message to the server, waits for the server to process +** then message and return a response. +*/ +int sqlite3_client_open(const char *zDatabaseName, sqlite3 **ppDb){ + SqlMessage msg; + msg.op = MSG_Open; + msg.zIn = zDatabaseName; + sendToServer(&msg); + *ppDb = msg.pDb; + return msg.errCode; +} +int sqlite3_client_prepare( + sqlite3 *pDb, + const char *zSql, + int nByte, + sqlite3_stmt **ppStmt, + const char **pzTail +){ + SqlMessage msg; + msg.op = MSG_Prepare; + msg.pDb = pDb; + msg.zIn = zSql; + msg.nByte = nByte; + sendToServer(&msg); + *ppStmt = msg.pStmt; + if( pzTail ) *pzTail = msg.zOut; + return msg.errCode; +} +int sqlite3_client_step(sqlite3_stmt *pStmt){ + SqlMessage msg; + msg.op = MSG_Step; + msg.pStmt = pStmt; + sendToServer(&msg); + return msg.errCode; +} +int sqlite3_client_reset(sqlite3_stmt *pStmt){ + SqlMessage msg; + msg.op = MSG_Reset; + msg.pStmt = pStmt; + sendToServer(&msg); + return msg.errCode; +} +int sqlite3_client_finalize(sqlite3_stmt *pStmt){ + SqlMessage msg; + msg.op = MSG_Finalize; + msg.pStmt = pStmt; + sendToServer(&msg); + return msg.errCode; +} +int sqlite3_client_close(sqlite3 *pDb){ + SqlMessage msg; + msg.op = MSG_Close; + msg.pDb = pDb; + sendToServer(&msg); + return msg.errCode; +} + +/* +** This routine implements the server. To start the server, first +** make sure g.serverHalt is false, then create a new detached thread +** on this procedure. See the sqlite3_server_start() routine below +** for an example. This procedure loops until g.serverHalt becomes +** true. +*/ +void *sqlite3_server(void *NotUsed){ + if( pthread_mutex_trylock(&g.serverMutex) ){ + return 0; /* Another server is already running */ + } + while( !g.serverHalt ){ + SqlMessage *pMsg; + + /* Remove the last message from the message queue. + */ + pthread_mutex_lock(&g.queueMutex); + while( g.pQueueTail==0 && g.serverHalt==0 ){ + pthread_cond_wait(&g.serverWakeup, &g.queueMutex); + } + pMsg = g.pQueueTail; + if( pMsg ){ + if( pMsg->pPrev ){ + pMsg->pPrev->pNext = 0; + }else{ + g.pQueueHead = 0; + } + g.pQueueTail = pMsg->pPrev; + } + pthread_mutex_unlock(&g.queueMutex); + if( pMsg==0 ) break; + + /* Process the message just removed + */ + pthread_mutex_lock(&pMsg->clientMutex); + switch( pMsg->op ){ + case MSG_Open: { + pMsg->errCode = sqlite3_open(pMsg->zIn, &pMsg->pDb); + break; + } + case MSG_Prepare: { + pMsg->errCode = sqlite3_prepare(pMsg->pDb, pMsg->zIn, pMsg->nByte, + &pMsg->pStmt, &pMsg->zOut); + break; + } + case MSG_Step: { + pMsg->errCode = sqlite3_step(pMsg->pStmt); + break; + } + case MSG_Reset: { + pMsg->errCode = sqlite3_reset(pMsg->pStmt); + break; + } + case MSG_Finalize: { + pMsg->errCode = sqlite3_finalize(pMsg->pStmt); + break; + } + case MSG_Close: { + pMsg->errCode = sqlite3_close(pMsg->pDb); + break; + } + } + + /* Signal the client that the message has been processed. + */ + pMsg->op = MSG_Done; + pthread_mutex_unlock(&pMsg->clientMutex); + pthread_cond_signal(&pMsg->clientWakeup); + } + pthread_mutex_unlock(&g.serverMutex); + return 0; +} + +/* +** Start a server thread if one is not already running. If there +** is aleady a server thread running, the new thread will quickly +** die and this routine is effectively a no-op. +*/ +void sqlite3_server_start(void){ + pthread_t x; + int rc; + g.serverHalt = 0; + rc = pthread_create(&x, 0, sqlite3_server, 0); + if( rc==0 ){ + pthread_detach(x); + } +} + +/* +** If a server thread is running, then stop it. If no server is +** running, this routine is effectively a no-op. +** +** This routine returns immediately without waiting for the server +** thread to stop. But be assured that the server will eventually stop. +*/ +void sqlite3_server_stop(void){ + g.serverHalt = 1; + pthread_cond_broadcast(&g.serverWakeup); +} + +#endif /* defined(OS_UNIX) && OS_UNIX && defined(THREADSAFE) && THREADSAFE */ +#endif /* defined(SQLITE_SERVER) */ diff --git a/src/sqliteInt.h b/src/sqliteInt.h index a24ad3352a..1a6851d32b 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -11,7 +11,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.456 2006/01/09 16:12:05 danielk1977 Exp $ +** @(#) $Id: sqliteInt.h,v 1.457 2006/01/09 23:40:25 drh Exp $ */ #ifndef _SQLITEINT_H_ #define _SQLITEINT_H_ @@ -291,7 +291,6 @@ extern int sqlite3_iMallocReset; /* Set iMallocFail to this when it reaches 0 */ struct ThreadData { u8 isInit; /* True if structure has been initialised */ u8 mallocFailed; /* True after a malloc() has failed */ - u8 disableReleaseMemory; /* True to make sqlite3_release_memory() a no-op */ #ifndef SQLITE_OMIT_MEMORY_MANAGEMENT u8 useMemoryManagement; /* True if memory-management is enabled */ diff --git a/src/tclsqlite.c b/src/tclsqlite.c index f657934283..96e8e274bf 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -11,7 +11,7 @@ ************************************************************************* ** A TCL Interface to SQLite ** -** $Id: tclsqlite.c,v 1.148 2006/01/09 17:29:53 drh Exp $ +** $Id: tclsqlite.c,v 1.149 2006/01/09 23:40:25 drh Exp $ */ #ifndef NO_TCL /* Omit this whole file if TCL is unavailable */ @@ -2151,6 +2151,7 @@ int TCLSH_MAIN(int argc, char **argv){ extern int Sqlitetest4_Init(Tcl_Interp*); extern int Sqlitetest5_Init(Tcl_Interp*); extern int Sqlitetest6_Init(Tcl_Interp*); + extern int Sqlitetest7_Init(Tcl_Interp*); extern int Md5_Init(Tcl_Interp*); extern int Sqlitetestsse_Init(Tcl_Interp*); extern int Sqlitetestasync_Init(Tcl_Interp*); @@ -2161,6 +2162,7 @@ int TCLSH_MAIN(int argc, char **argv){ Sqlitetest4_Init(interp); Sqlitetest5_Init(interp); Sqlitetest6_Init(interp); + Sqlitetest7_Init(interp); Sqlitetestasync_Init(interp); Md5_Init(interp); #ifdef SQLITE_SSE diff --git a/src/test7.c b/src/test7.c new file mode 100644 index 0000000000..2e2cc93c29 --- /dev/null +++ b/src/test7.c @@ -0,0 +1,722 @@ +/* +** 2006 January 09 +** +** 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. +** +************************************************************************* +** Code for testing the client/server version of the SQLite library. +** Derived from test4.c. +** +** $Id: test7.c,v 1.1 2006/01/09 23:40:25 drh Exp $ +*/ +#include "sqliteInt.h" +#include "tcl.h" +#include "os.h" + +/* +** This test only works on UNIX with a THREADSAFE build that includes +** the SQLITE_SERVER option. +*/ +#if OS_UNIX && defined(THREADSAFE) && THREADSAFE==1 && defined(SQLITE_SERVER) + +#include +#include +#include +#include +#include + +/* +** Interfaces defined in server.c +*/ +int sqlite3_client_open(const char*, sqlite3**); +int sqlite3_client_prepare(sqlite3*,const char*,int, + sqlite3_stmt**,const char**); +int sqlite3_client_step(sqlite3_stmt*); +int sqlite3_client_reset(sqlite3_stmt*); +int sqlite3_client_finalize(sqlite3_stmt*); +int sqlite3_client_close(sqlite3*); +int sqlite3_server_start(void); +int sqlite3_server_stop(void); + +/* +** Each thread is controlled by an instance of the following +** structure. +*/ +typedef struct Thread Thread; +struct Thread { + /* The first group of fields are writable by the supervisor thread + ** and read-only to the client threads + */ + char *zFilename; /* Name of database file */ + void (*xOp)(Thread*); /* next operation to do */ + char *zArg; /* argument usable by xOp */ + volatile int opnum; /* Operation number */ + volatile int busy; /* True if this thread is in use */ + + /* The next group of fields are writable by the client threads + ** but read-only to the superviser thread. + */ + volatile int completed; /* Number of operations completed */ + sqlite3 *db; /* Open database */ + sqlite3_stmt *pStmt; /* Pending operation */ + char *zErr; /* operation error */ + char *zStaticErr; /* Static error message */ + int rc; /* operation return code */ + int argc; /* number of columns in result */ + const char *argv[100]; /* result columns */ + const char *colv[100]; /* result column names */ +}; + +/* +** There can be as many as 26 threads running at once. Each is named +** by a capital letter: A, B, C, ..., Y, Z. +*/ +#define N_THREAD 26 +static Thread threadset[N_THREAD]; + +/* +** The main loop for a thread. Threads use busy waiting. +*/ +static void *client_main(void *pArg){ + Thread *p = (Thread*)pArg; + if( p->db ){ + sqlite3_client_close(p->db); + } + sqlite3_client_open(p->zFilename, &p->db); + if( SQLITE_OK!=sqlite3_errcode(p->db) ){ + p->zErr = strdup(sqlite3_errmsg(p->db)); + sqlite3_client_close(p->db); + p->db = 0; + } + p->pStmt = 0; + p->completed = 1; + while( p->opnum<=p->completed ) sched_yield(); + while( p->xOp ){ + if( p->zErr && p->zErr!=p->zStaticErr ){ + sqlite3_free(p->zErr); + p->zErr = 0; + } + (*p->xOp)(p); + p->completed++; + while( p->opnum<=p->completed ) sched_yield(); + } + if( p->pStmt ){ + sqlite3_client_finalize(p->pStmt); + p->pStmt = 0; + } + if( p->db ){ + sqlite3_client_close(p->db); + p->db = 0; + } + if( p->zErr && p->zErr!=p->zStaticErr ){ + sqlite3_free(p->zErr); + p->zErr = 0; + } + p->completed++; + return 0; +} + +/* +** Get a thread ID which is an upper case letter. Return the index. +** If the argument is not a valid thread ID put an error message in +** the interpreter and return -1. +*/ +static int parse_client_id(Tcl_Interp *interp, const char *zArg){ + if( zArg==0 || zArg[0]==0 || zArg[1]!=0 || !isupper((unsigned char)zArg[0]) ){ + Tcl_AppendResult(interp, "thread ID must be an upper case letter", 0); + return -1; + } + return zArg[0] - 'A'; +} + +/* +** Usage: client_create NAME FILENAME +** +** NAME should be an upper case letter. Start the thread running with +** an open connection to the given database. +*/ +static int tcl_client_create( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i; + pthread_t x; + int rc; + + if( argc!=3 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " ID FILENAME", 0); + return TCL_ERROR; + } + i = parse_client_id(interp, argv[1]); + if( i<0 ) return TCL_ERROR; + if( threadset[i].busy ){ + Tcl_AppendResult(interp, "thread ", argv[1], " is already running", 0); + return TCL_ERROR; + } + threadset[i].busy = 1; + sqliteFree(threadset[i].zFilename); + threadset[i].zFilename = sqliteStrDup(argv[2]); + threadset[i].opnum = 1; + threadset[i].completed = 0; + rc = pthread_create(&x, 0, client_main, &threadset[i]); + if( rc ){ + Tcl_AppendResult(interp, "failed to create the thread", 0); + sqliteFree(threadset[i].zFilename); + threadset[i].busy = 0; + return TCL_ERROR; + } + pthread_detach(x); + sqlite3_server_start(); + return TCL_OK; +} + +/* +** Wait for a thread to reach its idle state. +*/ +static void client_wait(Thread *p){ + while( p->opnum>p->completed ) sched_yield(); +} + +/* +** Usage: client_wait ID +** +** Wait on thread ID to reach its idle state. +*/ +static int tcl_client_wait( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i; + + if( argc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " ID", 0); + return TCL_ERROR; + } + i = parse_client_id(interp, argv[1]); + if( i<0 ) return TCL_ERROR; + if( !threadset[i].busy ){ + Tcl_AppendResult(interp, "no such thread", 0); + return TCL_ERROR; + } + client_wait(&threadset[i]); + return TCL_OK; +} + +/* +** Stop a thread. +*/ +static void stop_thread(Thread *p){ + client_wait(p); + p->xOp = 0; + p->opnum++; + client_wait(p); + sqliteFree(p->zArg); + p->zArg = 0; + sqliteFree(p->zFilename); + p->zFilename = 0; + p->busy = 0; +} + +/* +** Usage: client_halt ID +** +** Cause a client thread to shut itself down. Wait for the shutdown to be +** completed. If ID is "*" then stop all client threads. +*/ +static int tcl_client_halt( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i; + + if( argc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " ID", 0); + return TCL_ERROR; + } + if( argv[1][0]=='*' && argv[1][1]==0 ){ + for(i=0; i=N_THREAD ){ + sqlite3_server_stop(); + } + return TCL_OK; +} + +/* +** Usage: client_argc ID +** +** Wait on the most recent client_step to complete, then return the +** number of columns in the result set. +*/ +static int tcl_client_argc( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i; + char zBuf[100]; + + if( argc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " ID", 0); + return TCL_ERROR; + } + i = parse_client_id(interp, argv[1]); + if( i<0 ) return TCL_ERROR; + if( !threadset[i].busy ){ + Tcl_AppendResult(interp, "no such thread", 0); + return TCL_ERROR; + } + client_wait(&threadset[i]); + sprintf(zBuf, "%d", threadset[i].argc); + Tcl_AppendResult(interp, zBuf, 0); + return TCL_OK; +} + +/* +** Usage: client_argv ID N +** +** Wait on the most recent client_step to complete, then return the +** value of the N-th columns in the result set. +*/ +static int tcl_client_argv( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i; + int n; + + if( argc!=3 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " ID N", 0); + return TCL_ERROR; + } + i = parse_client_id(interp, argv[1]); + if( i<0 ) return TCL_ERROR; + if( !threadset[i].busy ){ + Tcl_AppendResult(interp, "no such thread", 0); + return TCL_ERROR; + } + if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR; + client_wait(&threadset[i]); + if( n<0 || n>=threadset[i].argc ){ + Tcl_AppendResult(interp, "column number out of range", 0); + return TCL_ERROR; + } + Tcl_AppendResult(interp, threadset[i].argv[n], 0); + return TCL_OK; +} + +/* +** Usage: client_colname ID N +** +** Wait on the most recent client_step to complete, then return the +** name of the N-th columns in the result set. +*/ +static int tcl_client_colname( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i; + int n; + + if( argc!=3 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " ID N", 0); + return TCL_ERROR; + } + i = parse_client_id(interp, argv[1]); + if( i<0 ) return TCL_ERROR; + if( !threadset[i].busy ){ + Tcl_AppendResult(interp, "no such thread", 0); + return TCL_ERROR; + } + if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR; + client_wait(&threadset[i]); + if( n<0 || n>=threadset[i].argc ){ + Tcl_AppendResult(interp, "column number out of range", 0); + return TCL_ERROR; + } + Tcl_AppendResult(interp, threadset[i].colv[n], 0); + return TCL_OK; +} + +/* +** Usage: client_result ID +** +** Wait on the most recent operation to complete, then return the +** result code from that operation. +*/ +static int tcl_client_result( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i; + const char *zName; + + if( argc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " ID", 0); + return TCL_ERROR; + } + i = parse_client_id(interp, argv[1]); + if( i<0 ) return TCL_ERROR; + if( !threadset[i].busy ){ + Tcl_AppendResult(interp, "no such thread", 0); + return TCL_ERROR; + } + client_wait(&threadset[i]); + switch( threadset[i].rc ){ + case SQLITE_OK: zName = "SQLITE_OK"; break; + case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; + case SQLITE_PERM: zName = "SQLITE_PERM"; break; + case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; + case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; + case SQLITE_LOCKED: zName = "SQLITE_LOCKED"; break; + case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break; + case SQLITE_READONLY: zName = "SQLITE_READONLY"; break; + case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break; + case SQLITE_IOERR: zName = "SQLITE_IOERR"; break; + case SQLITE_CORRUPT: zName = "SQLITE_CORRUPT"; break; + case SQLITE_FULL: zName = "SQLITE_FULL"; break; + case SQLITE_CANTOPEN: zName = "SQLITE_CANTOPEN"; break; + case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break; + case SQLITE_EMPTY: zName = "SQLITE_EMPTY"; break; + case SQLITE_SCHEMA: zName = "SQLITE_SCHEMA"; break; + case SQLITE_CONSTRAINT: zName = "SQLITE_CONSTRAINT"; break; + case SQLITE_MISMATCH: zName = "SQLITE_MISMATCH"; break; + case SQLITE_MISUSE: zName = "SQLITE_MISUSE"; break; + case SQLITE_NOLFS: zName = "SQLITE_NOLFS"; break; + case SQLITE_AUTH: zName = "SQLITE_AUTH"; break; + case SQLITE_FORMAT: zName = "SQLITE_FORMAT"; break; + case SQLITE_RANGE: zName = "SQLITE_RANGE"; break; + case SQLITE_ROW: zName = "SQLITE_ROW"; break; + case SQLITE_DONE: zName = "SQLITE_DONE"; break; + default: zName = "SQLITE_Unknown"; break; + } + Tcl_AppendResult(interp, zName, 0); + return TCL_OK; +} + +/* +** Usage: client_error ID +** +** Wait on the most recent operation to complete, then return the +** error string. +*/ +static int tcl_client_error( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i; + + if( argc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " ID", 0); + return TCL_ERROR; + } + i = parse_client_id(interp, argv[1]); + if( i<0 ) return TCL_ERROR; + if( !threadset[i].busy ){ + Tcl_AppendResult(interp, "no such thread", 0); + return TCL_ERROR; + } + client_wait(&threadset[i]); + Tcl_AppendResult(interp, threadset[i].zErr, 0); + return TCL_OK; +} + +/* +** This procedure runs in the thread to compile an SQL statement. +*/ +static void do_compile(Thread *p){ + if( p->db==0 ){ + p->zErr = p->zStaticErr = "no database is open"; + p->rc = SQLITE_ERROR; + return; + } + if( p->pStmt ){ + sqlite3_client_finalize(p->pStmt); + p->pStmt = 0; + } + p->rc = sqlite3_client_prepare(p->db, p->zArg, -1, &p->pStmt, 0); +} + +/* +** Usage: client_compile ID SQL +** +** Compile a new virtual machine. +*/ +static int tcl_client_compile( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i; + if( argc!=3 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " ID SQL", 0); + return TCL_ERROR; + } + i = parse_client_id(interp, argv[1]); + if( i<0 ) return TCL_ERROR; + if( !threadset[i].busy ){ + Tcl_AppendResult(interp, "no such thread", 0); + return TCL_ERROR; + } + client_wait(&threadset[i]); + threadset[i].xOp = do_compile; + sqliteFree(threadset[i].zArg); + threadset[i].zArg = sqliteStrDup(argv[2]); + threadset[i].opnum++; + return TCL_OK; +} + +/* +** This procedure runs in the thread to step the virtual machine. +*/ +static void do_step(Thread *p){ + int i; + if( p->pStmt==0 ){ + p->zErr = p->zStaticErr = "no virtual machine available"; + p->rc = SQLITE_ERROR; + return; + } + p->rc = sqlite3_client_step(p->pStmt); + if( p->rc==SQLITE_ROW ){ + p->argc = sqlite3_column_count(p->pStmt); + for(i=0; ipStmt); i++){ + p->argv[i] = sqlite3_column_text(p->pStmt, i); + } + for(i=0; iargc; i++){ + p->colv[i] = sqlite3_column_name(p->pStmt, i); + } + } +} + +/* +** Usage: client_step ID +** +** Advance the virtual machine by one step +*/ +static int tcl_client_step( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i; + if( argc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " IDL", 0); + return TCL_ERROR; + } + i = parse_client_id(interp, argv[1]); + if( i<0 ) return TCL_ERROR; + if( !threadset[i].busy ){ + Tcl_AppendResult(interp, "no such thread", 0); + return TCL_ERROR; + } + client_wait(&threadset[i]); + threadset[i].xOp = do_step; + threadset[i].opnum++; + return TCL_OK; +} + +/* +** This procedure runs in the thread to finalize a virtual machine. +*/ +static void do_finalize(Thread *p){ + if( p->pStmt==0 ){ + p->zErr = p->zStaticErr = "no virtual machine available"; + p->rc = SQLITE_ERROR; + return; + } + p->rc = sqlite3_client_finalize(p->pStmt); + p->pStmt = 0; +} + +/* +** Usage: client_finalize ID +** +** Finalize the virtual machine. +*/ +static int tcl_client_finalize( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i; + if( argc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " IDL", 0); + return TCL_ERROR; + } + i = parse_client_id(interp, argv[1]); + if( i<0 ) return TCL_ERROR; + if( !threadset[i].busy ){ + Tcl_AppendResult(interp, "no such thread", 0); + return TCL_ERROR; + } + client_wait(&threadset[i]); + threadset[i].xOp = do_finalize; + sqliteFree(threadset[i].zArg); + threadset[i].zArg = 0; + threadset[i].opnum++; + return TCL_OK; +} + +/* +** This procedure runs in the thread to reset a virtual machine. +*/ +static void do_reset(Thread *p){ + if( p->pStmt==0 ){ + p->zErr = p->zStaticErr = "no virtual machine available"; + p->rc = SQLITE_ERROR; + return; + } + p->rc = sqlite3_client_reset(p->pStmt); + p->pStmt = 0; +} + +/* +** Usage: client_reset ID +** +** Finalize the virtual machine. +*/ +static int tcl_client_reset( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i; + if( argc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " IDL", 0); + return TCL_ERROR; + } + i = parse_client_id(interp, argv[1]); + if( i<0 ) return TCL_ERROR; + if( !threadset[i].busy ){ + Tcl_AppendResult(interp, "no such thread", 0); + return TCL_ERROR; + } + client_wait(&threadset[i]); + threadset[i].xOp = do_reset; + sqliteFree(threadset[i].zArg); + threadset[i].zArg = 0; + threadset[i].opnum++; + return TCL_OK; +} + +/* +** Usage: client_swap ID ID +** +** Interchange the sqlite* pointer between two threads. +*/ +static int tcl_client_swap( + void *NotUsed, + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int argc, /* Number of arguments */ + const char **argv /* Text of each argument */ +){ + int i, j; + sqlite3 *temp; + if( argc!=3 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], + " ID1 ID2", 0); + return TCL_ERROR; + } + i = parse_client_id(interp, argv[1]); + if( i<0 ) return TCL_ERROR; + if( !threadset[i].busy ){ + Tcl_AppendResult(interp, "no such thread", 0); + return TCL_ERROR; + } + client_wait(&threadset[i]); + j = parse_client_id(interp, argv[2]); + if( j<0 ) return TCL_ERROR; + if( !threadset[j].busy ){ + Tcl_AppendResult(interp, "no such thread", 0); + return TCL_ERROR; + } + client_wait(&threadset[j]); + temp = threadset[i].db; + threadset[i].db = threadset[j].db; + threadset[j].db = temp; + return TCL_OK; +} + +/* +** Register commands with the TCL interpreter. +*/ +int Sqlitetest7_Init(Tcl_Interp *interp){ + static struct { + char *zName; + Tcl_CmdProc *xProc; + } aCmd[] = { + { "client_create", (Tcl_CmdProc*)tcl_client_create }, + { "client_wait", (Tcl_CmdProc*)tcl_client_wait }, + { "client_halt", (Tcl_CmdProc*)tcl_client_halt }, + { "client_argc", (Tcl_CmdProc*)tcl_client_argc }, + { "client_argv", (Tcl_CmdProc*)tcl_client_argv }, + { "client_colname", (Tcl_CmdProc*)tcl_client_colname }, + { "client_result", (Tcl_CmdProc*)tcl_client_result }, + { "client_error", (Tcl_CmdProc*)tcl_client_error }, + { "client_compile", (Tcl_CmdProc*)tcl_client_compile }, + { "client_step", (Tcl_CmdProc*)tcl_client_step }, + { "client_reset", (Tcl_CmdProc*)tcl_client_reset }, + { "client_finalize", (Tcl_CmdProc*)tcl_client_finalize }, + { "client_swap", (Tcl_CmdProc*)tcl_client_swap }, + }; + int i; + + for(i=0; i