Added database server example code (untested). Fixed serveral bugs including
the autovacuum compile problem described by ticket #1593. (CVS 2897) FossilOrigin-Name: ec332d8822d1ac9673581a26ab2a2fce5f2554a3
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
eeebc640aaeeb0ab7f730d854069f159aa41968b
|
||||
ec332d8822d1ac9673581a26ab2a2fce5f2554a3
|
||||
+2
-3
@@ -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;
|
||||
|
||||
+4
-3
@@ -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 <ctype.h>
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+7
-8
@@ -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 || nReleased<nReq); p=p->pNext){
|
||||
|
||||
+427
@@ -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 <pthread.h>
|
||||
#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) */
|
||||
+1
-2
@@ -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 */
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
+722
@@ -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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/*
|
||||
** 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; i++){
|
||||
if( threadset[i].busy ){
|
||||
stop_thread(&threadset[i]);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
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;
|
||||
}
|
||||
stop_thread(&threadset[i]);
|
||||
}
|
||||
|
||||
/* If no client threads are still running, also stop the server */
|
||||
for(i=0; i<N_THREAD && threadset[i].busy==0; i++){}
|
||||
if( 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; i<sqlite3_data_count(p->pStmt); i++){
|
||||
p->argv[i] = sqlite3_column_text(p->pStmt, i);
|
||||
}
|
||||
for(i=0; i<p->argc; 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<sizeof(aCmd)/sizeof(aCmd[0]); i++){
|
||||
Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
|
||||
}
|
||||
return TCL_OK;
|
||||
}
|
||||
#else
|
||||
int Sqlitetest7_Init(Tcl_Interp *interp){ return TCL_OK; }
|
||||
#endif /* OS_UNIX */
|
||||
+5
-1
@@ -11,7 +11,7 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this script testing the callback-free C/C++ API.
|
||||
#
|
||||
# $Id: capi3.test,v 1.36 2006/01/03 00:33:50 drh Exp $
|
||||
# $Id: capi3.test,v 1.37 2006/01/09 23:40:25 drh Exp $
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
@@ -462,14 +462,17 @@ set ::ENC [execsql {pragma encoding}]
|
||||
db close
|
||||
|
||||
do_test capi3-6.0 {
|
||||
btree_breakpoint
|
||||
sqlite3 db test.db
|
||||
set DB [sqlite3_connection_pointer db]
|
||||
btree_breakpoint
|
||||
sqlite3_key $DB xyzzy
|
||||
set sql {SELECT a FROM t1 order by rowid}
|
||||
set STMT [sqlite3_prepare $DB $sql -1 TAIL]
|
||||
expr 0
|
||||
} {0}
|
||||
do_test capi3-6.1 {
|
||||
db cache flush
|
||||
sqlite3_close $DB
|
||||
} {SQLITE_BUSY}
|
||||
do_test capi3-6.2 {
|
||||
@@ -480,6 +483,7 @@ do_test capi3-6.3 {
|
||||
sqlite3_finalize $STMT
|
||||
} {SQLITE_OK}
|
||||
do_test capi3-6.4 {
|
||||
db cache flush
|
||||
sqlite3_close $DB
|
||||
} {SQLITE_OK}
|
||||
db close
|
||||
|
||||
+1
-2
@@ -10,7 +10,7 @@
|
||||
#***********************************************************************
|
||||
# This file runs all tests.
|
||||
#
|
||||
# $Id: utf16.test,v 1.4 2005/03/28 00:07:16 danielk1977 Exp $
|
||||
# $Id: utf16.test,v 1.5 2006/01/09 23:40:26 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
@@ -73,4 +73,3 @@ rename really_finish_test2 finish_test
|
||||
rename do_test ""
|
||||
rename really_do_test do_test
|
||||
finish_test
|
||||
|
||||
|
||||
Reference in New Issue
Block a user