Remove the long obsolete "client/server" mode tests.
FossilOrigin-Name: 08e3114caec0633fc54f8febb7f4732b46a1f47508bbca0a6a2bff02591254d5
This commit is contained in:
parent
6896da42c6
commit
da3d655fd8
@ -373,7 +373,6 @@ TESTSRC = \
|
||||
$(TOP)/src/test4.c \
|
||||
$(TOP)/src/test5.c \
|
||||
$(TOP)/src/test6.c \
|
||||
$(TOP)/src/test7.c \
|
||||
$(TOP)/src/test8.c \
|
||||
$(TOP)/src/test9.c \
|
||||
$(TOP)/src/test_autoext.c \
|
||||
@ -402,7 +401,6 @@ TESTSRC = \
|
||||
$(TOP)/src/test_quota.c \
|
||||
$(TOP)/src/test_rtree.c \
|
||||
$(TOP)/src/test_schema.c \
|
||||
$(TOP)/src/test_server.c \
|
||||
$(TOP)/src/test_superlock.c \
|
||||
$(TOP)/src/test_syscall.c \
|
||||
$(TOP)/src/test_tclsh.c \
|
||||
|
@ -1485,7 +1485,6 @@ TESTSRC = \
|
||||
$(TOP)\src\test4.c \
|
||||
$(TOP)\src\test5.c \
|
||||
$(TOP)\src\test6.c \
|
||||
$(TOP)\src\test7.c \
|
||||
$(TOP)\src\test8.c \
|
||||
$(TOP)\src\test9.c \
|
||||
$(TOP)\src\test_autoext.c \
|
||||
@ -1514,7 +1513,6 @@ TESTSRC = \
|
||||
$(TOP)\src\test_quota.c \
|
||||
$(TOP)\src\test_rtree.c \
|
||||
$(TOP)\src\test_schema.c \
|
||||
$(TOP)\src\test_server.c \
|
||||
$(TOP)\src\test_superlock.c \
|
||||
$(TOP)\src\test_syscall.c \
|
||||
$(TOP)\src\test_tclsh.c \
|
||||
|
2
main.mk
2
main.mk
@ -298,7 +298,6 @@ TESTSRC = \
|
||||
$(TOP)/src/test4.c \
|
||||
$(TOP)/src/test5.c \
|
||||
$(TOP)/src/test6.c \
|
||||
$(TOP)/src/test7.c \
|
||||
$(TOP)/src/test8.c \
|
||||
$(TOP)/src/test9.c \
|
||||
$(TOP)/src/test_autoext.c \
|
||||
@ -327,7 +326,6 @@ TESTSRC = \
|
||||
$(TOP)/src/test_quota.c \
|
||||
$(TOP)/src/test_rtree.c \
|
||||
$(TOP)/src/test_schema.c \
|
||||
$(TOP)/src/test_server.c \
|
||||
$(TOP)/src/test_sqllog.c \
|
||||
$(TOP)/src/test_superlock.c \
|
||||
$(TOP)/src/test_syscall.c \
|
||||
|
21
manifest
21
manifest
@ -1,11 +1,11 @@
|
||||
C Update\stest\sscripts\sspeed1.test\sand\sspeed1p.test\sso\sthey\smay\sbe\srun\sby\stestrunner.tcl.
|
||||
D 2023-02-06T15:46:01.294
|
||||
C Remove\sthe\slong\sobsolete\s"client/server"\smode\stests.
|
||||
D 2023-02-06T15:46:34.433
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
F Makefile.in 3c7df00360783e20db13c928cbd2f3561aea14b29da2b78f6ce57393c0643ef3
|
||||
F Makefile.in 03ae384c9efbd8827cbcdce1d2fa57c6c0df264cb408d4e10dc4c57534214d36
|
||||
F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
|
||||
F Makefile.msc 9e0147a280fde917aa4a3337b96da43fab2ed2235b0a0a62b3aa6bd5b8b1774f
|
||||
F Makefile.msc 26e2fa6144907df27487b7c25767a87dbc5c4a312ccc382dbf44648f40073623
|
||||
F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
|
||||
F VERSION 413ec94920a487ae32c9a2a8819544d690662d6f7c7ce025c0d0b8a1e74fa9db
|
||||
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
|
||||
@ -540,7 +540,7 @@ F ext/wasm/wasmfs.make cf9a68162d92ca2bcb0b9528b244cb36d5cc2d84ccc9c2d398461927d
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0
|
||||
F main.mk c8af6b90044e4675a1b2c4000c76429fe6bc0dc0a5b3092d7e036f059a58c030
|
||||
F main.mk ac6b13f8ecc43f377e9912380ea4cf366051d7f784cf61c8886e03e1cf0fbefa
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
|
||||
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
|
||||
@ -637,7 +637,6 @@ F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
|
||||
F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664
|
||||
F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d
|
||||
F src/test6.c ae73a3a42bbc982fb9e301b84d30bda65a307be48c6dff20aba1461e17a9b0ce
|
||||
F src/test7.c 5612e9aecf934d6df7bba6ce861fdf5ba5456010
|
||||
F src/test8.c 0c856d6ff6b0d2ff6696addc467a15ed17c6910f14475302cd5b3b4e54406161
|
||||
F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5
|
||||
F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a
|
||||
@ -670,7 +669,6 @@ F src/test_quota.c 6cb9297115b551f433a9ad1741817a9831abed99
|
||||
F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d
|
||||
F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b
|
||||
F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b
|
||||
F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe
|
||||
F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a
|
||||
F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e
|
||||
F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939
|
||||
@ -1464,7 +1462,6 @@ F test/selectD.test 6d1909b49970bf92f45ce657505befcef5fc7cbc13544e18103a316d3218
|
||||
F test/selectE.test a8730ca330fcf40ace158f134f4fe0eb00c7edbf
|
||||
F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3
|
||||
F test/selectG.test 089f7d3d7e6db91566f00b036cb353107a2cca6220eb1cb264085a836dae8840
|
||||
F test/server1.test c2b00864514a68a0e6fd518659dc95d0050307a357a08969872bef027d785dc4
|
||||
F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be
|
||||
F test/sessionfuzz-data1.db 1f8d5def831f19b1c74571037f0d53a588ea49a6c4ca2a028fc0c27ef896dbcb
|
||||
F test/sessionfuzz.c 5eef09af01eeff6f20250ae4c0112c2e576e4d2f2026cc9a49dc5be6886fa6ee
|
||||
@ -2048,8 +2045,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P b760a7307c453d95cf3b302c9867c84a9c899956c16c2ce1ea6cce8f025db425
|
||||
R 307214cb20044df19132161114770393
|
||||
U dan
|
||||
Z cf0e7b089ab074de1ca2ba831e6ee97f
|
||||
P e761d4b9c8dcacccc927d23f20fdf779a0ae5304281b5bd6aa998bade56f0a37
|
||||
R 011e0ef815119f8bbf074c0e4c1fc189
|
||||
U drh
|
||||
Z 2cf846d6f5b78b2c3d26ce53dcad23b7
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
@ -1 +1 @@
|
||||
e761d4b9c8dcacccc927d23f20fdf779a0ae5304281b5bd6aa998bade56f0a37
|
||||
08e3114caec0633fc54f8febb7f4732b46a1f47508bbca0a6a2bff02591254d5
|
718
src/test7.c
718
src/test7.c
@ -1,718 +0,0 @@
|
||||
/*
|
||||
** 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.
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#if defined(INCLUDE_SQLITE_TCL_H)
|
||||
# include "sqlite_tcl.h"
|
||||
#else
|
||||
# include "tcl.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
** This test only works on UNIX with a SQLITE_THREADSAFE build that includes
|
||||
** the SQLITE_SERVER option.
|
||||
*/
|
||||
#if defined(SQLITE_SERVER) && !defined(SQLITE_OMIT_SHARED_CACHE) && \
|
||||
SQLITE_OS_UNIX && SQLITE_THREADSAFE
|
||||
|
||||
#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);
|
||||
void sqlite3_server_start2(int *pnDecr);
|
||||
|
||||
/*
|
||||
** 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 */
|
||||
|
||||
/* Initialized to 1 by the supervisor thread when the client is
|
||||
** created, and then deemed read-only to the supervisor thread.
|
||||
** Is set to 0 by the server thread belonging to this client
|
||||
** just before it exits.
|
||||
*/
|
||||
int nServer; /* Number of server threads running */
|
||||
};
|
||||
|
||||
/*
|
||||
** 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++;
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
sqlite3_thread_cleanup();
|
||||
#endif
|
||||
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 SQLITE_TCLAPI 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;
|
||||
sqlite3_free(threadset[i].zFilename);
|
||||
threadset[i].zFilename = sqlite3_mprintf("%s", 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);
|
||||
sqlite3_free(threadset[i].zFilename);
|
||||
threadset[i].busy = 0;
|
||||
return TCL_ERROR;
|
||||
}
|
||||
pthread_detach(x);
|
||||
if( threadset[i].nServer==0 ){
|
||||
threadset[i].nServer = 1;
|
||||
sqlite3_server_start2(&threadset[i].nServer);
|
||||
}
|
||||
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 SQLITE_TCLAPI 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);
|
||||
sqlite3_free(p->zArg);
|
||||
p->zArg = 0;
|
||||
sqlite3_free(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 SQLITE_TCLAPI 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();
|
||||
while( 1 ){
|
||||
for(i=0; i<N_THREAD && threadset[i].nServer==0; i++);
|
||||
if( i==N_THREAD ) break;
|
||||
sched_yield();
|
||||
}
|
||||
}
|
||||
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 SQLITE_TCLAPI 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]);
|
||||
sqlite3_snprintf(sizeof(zBuf), 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 SQLITE_TCLAPI 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 SQLITE_TCLAPI 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;
|
||||
}
|
||||
|
||||
extern const char *sqlite3ErrName(int);
|
||||
|
||||
/*
|
||||
** Usage: client_result ID
|
||||
**
|
||||
** Wait on the most recent operation to complete, then return the
|
||||
** result code from that operation.
|
||||
*/
|
||||
static int SQLITE_TCLAPI 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]);
|
||||
zName = sqlite3ErrName(threadset[i].rc);
|
||||
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 SQLITE_TCLAPI 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 SQLITE_TCLAPI 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;
|
||||
sqlite3_free(threadset[i].zArg);
|
||||
threadset[i].zArg = sqlite3_mprintf("%s", 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] = (char*)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 SQLITE_TCLAPI 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 SQLITE_TCLAPI 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;
|
||||
sqlite3_free(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 SQLITE_TCLAPI 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;
|
||||
sqlite3_free(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 SQLITE_TCLAPI 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 /* SQLITE_OS_UNIX */
|
@ -1,516 +0,0 @@
|
||||
/*
|
||||
** 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 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 can optionally share their database cache. This reduces
|
||||
** I/O and memory requirements. Cache shared is controlled using
|
||||
** the sqlite3_enable_shared_cache() API.
|
||||
**
|
||||
** (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.
|
||||
**
|
||||
** 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 as long as SQLITE_ENABLE_MEMORY_MANAGEMENT is not defined.
|
||||
** 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_total_changes
|
||||
** sqlite3_transfer_bindings
|
||||
**
|
||||
** 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 passed from
|
||||
** any thread to any of the functions listed in the second group above as
|
||||
** long as the same connection is not in use by two threads at once and
|
||||
** as long as SQLITE_ENABLE_MEMORY_MANAGEMENT is not defined. Additional
|
||||
** information about the SQLITE_ENABLE_MEMORY_MANAGEMENT constraint is
|
||||
** below.
|
||||
**
|
||||
** 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 example 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.
|
||||
**
|
||||
**** Restrictions Associated With SQLITE_ENABLE_MEMORY_MANAGEMENT ****
|
||||
**
|
||||
** If you compile with SQLITE_ENABLE_MEMORY_MANAGEMENT defined, then
|
||||
** SQLite includes code that tracks how much memory is being used by
|
||||
** each thread. These memory counts can become confused if memory
|
||||
** is allocated by one thread and then freed by another. For that
|
||||
** reason, when SQLITE_ENABLE_MEMORY_MANAGEMENT is used, all operations
|
||||
** that might allocate or free memory should be performanced in the same
|
||||
** thread that originally created the database connection. In that case,
|
||||
** many of the operations that are listed above as safe to be performed
|
||||
** in separate threads would need to be sent over to the server to be
|
||||
** done there. If SQLITE_ENABLE_MEMORY_MANAGEMENT is defined, then
|
||||
** the following functions can be used safely from different threads
|
||||
** without messing up the allocation counts:
|
||||
**
|
||||
** sqlite3_bind_parameter_name
|
||||
** sqlite3_bind_parameter_index
|
||||
** sqlite3_changes
|
||||
** sqlite3_column_blob
|
||||
** sqlite3_column_count
|
||||
** sqlite3_complete
|
||||
** sqlite3_data_count
|
||||
** sqlite3_db_handle
|
||||
** sqlite3_errcode
|
||||
** sqlite3_errmsg
|
||||
** sqlite3_last_insert_rowid
|
||||
** sqlite3_total_changes
|
||||
**
|
||||
** The remaining functions are not thread-safe when memory management
|
||||
** is enabled. So one would have to define some new interface routines
|
||||
** along the following lines:
|
||||
**
|
||||
** sqlite3_client_bind_*
|
||||
** sqlite3_client_clear_bindings
|
||||
** sqlite3_client_column_*
|
||||
** sqlite3_client_create_collation
|
||||
** sqlite3_client_create_function
|
||||
** sqlite3_client_transfer_bindings
|
||||
**
|
||||
** The example code in this file is intended for use with memory
|
||||
** management turned off. So the implementation of these additional
|
||||
** client interfaces is left as an exercise to the reader.
|
||||
**
|
||||
** It may seem surprising to the reader that the list of safe functions
|
||||
** above does not include things like sqlite3_bind_int() or
|
||||
** sqlite3_column_int(). But those routines might, in fact, allocate
|
||||
** or deallocate memory. In the case of sqlite3_bind_int(), if the
|
||||
** parameter was previously bound to a string that string might need
|
||||
** to be deallocated before the new integer value is inserted. In
|
||||
** the case of sqlite3_column_int(), the value of the column might be
|
||||
** a UTF-16 string which will need to be converted to UTF-8 then into
|
||||
** an integer.
|
||||
*/
|
||||
|
||||
/* Include this to get the definition of SQLITE_THREADSAFE, in the
|
||||
** case that default values are used.
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
|
||||
/*
|
||||
** Only compile the code in this file on UNIX with a SQLITE_THREADSAFE build
|
||||
** and only if the SQLITE_SERVER macro is defined.
|
||||
*/
|
||||
#if defined(SQLITE_SERVER) && !defined(SQLITE_OMIT_SHARED_CACHE)
|
||||
#if SQLITE_OS_UNIX && SQLITE_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 above.
|
||||
**
|
||||
** 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 */
|
||||
}
|
||||
sqlite3_enable_shared_cache(1);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around sqlite3_server() that decrements the int variable
|
||||
** pointed to by the first argument after the sqlite3_server() call
|
||||
** returns.
|
||||
*/
|
||||
static void *serverWrapper(void *pnDecr){
|
||||
void *p = sqlite3_server(0);
|
||||
(*(int*)pnDecr)--;
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is the similar to sqlite3_server_start(), except that
|
||||
** the integer pointed to by the first argument is decremented when
|
||||
** the server thread exits.
|
||||
*/
|
||||
void sqlite3_server_start2(int *pnDecr){
|
||||
pthread_t x;
|
||||
int rc;
|
||||
g.serverHalt = 0;
|
||||
rc = pthread_create(&x, 0, serverWrapper, (void*)pnDecr);
|
||||
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 waits until the server has actually stopped before
|
||||
** returning.
|
||||
*/
|
||||
void sqlite3_server_stop(void){
|
||||
g.serverHalt = 1;
|
||||
pthread_cond_broadcast(&g.serverWakeup);
|
||||
pthread_mutex_lock(&g.serverMutex);
|
||||
pthread_mutex_unlock(&g.serverMutex);
|
||||
}
|
||||
|
||||
#endif /* SQLITE_OS_UNIX && SQLITE_THREADSAFE */
|
||||
#endif /* defined(SQLITE_SERVER) */
|
@ -1,180 +0,0 @@
|
||||
# 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this script is testing the server mode of SQLite.
|
||||
#
|
||||
# This file is derived from thread1.test
|
||||
#
|
||||
# $Id: server1.test,v 1.5 2007/08/29 18:20:17 drh Exp $
|
||||
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
# Skip this whole file if the server testing code is not enabled
|
||||
#
|
||||
if {[llength [info command client_step]]==0 || [sqlite3 -has-codec]} {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
# This test does not work on older PPC Macs due to problems in the
|
||||
# pthreads library. So skip it.
|
||||
#
|
||||
if {$tcl_platform(machine)=="Power Macintosh" &&
|
||||
$tcl_platform(byteOrder)=="bigEndian"} {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
# The sample server implementation does not work right when memory
|
||||
# management is enabled.
|
||||
#
|
||||
ifcapable (memorymanage||mutex_noop) {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
# Create some data to work with
|
||||
#
|
||||
do_test server1-1.1 {
|
||||
execsql {
|
||||
CREATE TABLE t1(a,b);
|
||||
INSERT INTO t1 VALUES(1,'abcdefgh');
|
||||
INSERT INTO t1 SELECT a+1, b||b FROM t1;
|
||||
INSERT INTO t1 SELECT a+2, b||b FROM t1;
|
||||
INSERT INTO t1 SELECT a+4, b||b FROM t1;
|
||||
SELECT count(*), max(length(b)) FROM t1;
|
||||
}
|
||||
} {8 64}
|
||||
|
||||
# Interleave two threads on read access. Then make sure a third
|
||||
# thread can write the database. In other words:
|
||||
#
|
||||
# read-lock A
|
||||
# read-lock B
|
||||
# unlock A
|
||||
# unlock B
|
||||
# write-lock C
|
||||
#
|
||||
do_test server1-1.2 {
|
||||
client_create A test.db
|
||||
client_create B test.db
|
||||
client_create C test.db
|
||||
client_compile A {SELECT a FROM t1}
|
||||
client_step A
|
||||
client_result A
|
||||
} SQLITE_ROW
|
||||
do_test server1-1.3 {
|
||||
client_argc A
|
||||
} 1
|
||||
do_test server1-1.4 {
|
||||
client_argv A 0
|
||||
} 1
|
||||
do_test server1-1.5 {
|
||||
client_compile B {SELECT b FROM t1}
|
||||
client_step B
|
||||
client_result B
|
||||
} SQLITE_ROW
|
||||
do_test server1-1.6 {
|
||||
client_argc B
|
||||
} 1
|
||||
do_test server1-1.7 {
|
||||
client_argv B 0
|
||||
} abcdefgh
|
||||
do_test server1-1.8 {
|
||||
client_finalize A
|
||||
client_result A
|
||||
} SQLITE_OK
|
||||
do_test server1-1.9 {
|
||||
client_finalize B
|
||||
client_result B
|
||||
} SQLITE_OK
|
||||
do_test server1-1.10 {
|
||||
client_compile C {CREATE TABLE t2(x,y)}
|
||||
client_step C
|
||||
client_result C
|
||||
} SQLITE_DONE
|
||||
do_test server1-1.11 {
|
||||
client_finalize C
|
||||
client_result C
|
||||
} SQLITE_OK
|
||||
do_test server1-1.12 {
|
||||
catchsql {SELECT name FROM sqlite_master}
|
||||
execsql {SELECT name FROM sqlite_master}
|
||||
} {t1 t2}
|
||||
|
||||
|
||||
# Read from table t1. Do not finalize the statement. This
|
||||
# will leave the lock pending.
|
||||
#
|
||||
do_test server1-2.1 {
|
||||
client_halt *
|
||||
client_create A test.db
|
||||
client_compile A {SELECT a FROM t1}
|
||||
client_step A
|
||||
client_result A
|
||||
} SQLITE_ROW
|
||||
|
||||
# Read from the same table from another thread. This is allows.
|
||||
#
|
||||
do_test server1-2.2 {
|
||||
client_create B test.db
|
||||
client_compile B {SELECT b FROM t1}
|
||||
client_step B
|
||||
client_result B
|
||||
} SQLITE_ROW
|
||||
|
||||
# Write to a different table from another thread. This is allowed
|
||||
# because in server mode with a shared cache we have table-level locking.
|
||||
#
|
||||
do_test server1-2.3 {
|
||||
client_create C test.db
|
||||
client_compile C {INSERT INTO t2 VALUES(98,99)}
|
||||
client_step C
|
||||
client_result C
|
||||
client_finalize C
|
||||
client_result C
|
||||
} SQLITE_OK
|
||||
|
||||
# But we cannot insert into table t1 because threads A and B have it locked.
|
||||
#
|
||||
do_test server1-2.4 {
|
||||
client_compile C {INSERT INTO t1 VALUES(98,99)}
|
||||
client_step C
|
||||
client_result C
|
||||
client_finalize C
|
||||
client_result C
|
||||
} SQLITE_LOCKED
|
||||
do_test server1-2.5 {
|
||||
client_finalize B
|
||||
client_wait B
|
||||
client_compile C {INSERT INTO t1 VALUES(98,99)}
|
||||
client_step C
|
||||
client_result C
|
||||
client_finalize C
|
||||
client_result C
|
||||
} SQLITE_LOCKED
|
||||
|
||||
# Insert into t1 is successful after finishing the other two threads.
|
||||
do_test server1-2.6 {
|
||||
client_finalize A
|
||||
client_wait A
|
||||
client_compile C {INSERT INTO t1 VALUES(98,99)}
|
||||
client_step C
|
||||
client_result C
|
||||
client_finalize C
|
||||
client_result C
|
||||
} SQLITE_OK
|
||||
|
||||
client_halt *
|
||||
sqlite3_enable_shared_cache 0
|
||||
finish_test
|
Loading…
Reference in New Issue
Block a user