4 ** The author disclaims copyright to this source code. In place of
5 ** a legal notice, here is a blessing:
7 ** May you do good and not evil.
8 ** May you find forgiveness for yourself and forgive others.
9 ** May you share freely, never taking more than you give.
11 *************************************************************************
12 ** Code for testing the client/server version of the SQLite library.
13 ** Derived from test4.c.
15 #include "sqliteInt.h"
16 #if defined(INCLUDE_SQLITE_TCL_H)
17 # include "sqlite_tcl.h"
23 ** This test only works on UNIX with a SQLITE_THREADSAFE build that includes
24 ** the SQLITE_SERVER option.
26 #if defined(SQLITE_SERVER) && !defined(SQLITE_OMIT_SHARED_CACHE) && \
27 SQLITE_OS_UNIX && SQLITE_THREADSAFE
36 ** Interfaces defined in server.c
38 int sqlite3_client_open(const char*, sqlite3
**);
39 int sqlite3_client_prepare(sqlite3
*,const char*,int,
40 sqlite3_stmt
**,const char**);
41 int sqlite3_client_step(sqlite3_stmt
*);
42 int sqlite3_client_reset(sqlite3_stmt
*);
43 int sqlite3_client_finalize(sqlite3_stmt
*);
44 int sqlite3_client_close(sqlite3
*);
45 int sqlite3_server_start(void);
46 int sqlite3_server_stop(void);
47 void sqlite3_server_start2(int *pnDecr
);
50 ** Each thread is controlled by an instance of the following
53 typedef struct Thread Thread
;
55 /* The first group of fields are writable by the supervisor thread
56 ** and read-only to the client threads
58 char *zFilename
; /* Name of database file */
59 void (*xOp
)(Thread
*); /* next operation to do */
60 char *zArg
; /* argument usable by xOp */
61 volatile int opnum
; /* Operation number */
62 volatile int busy
; /* True if this thread is in use */
64 /* The next group of fields are writable by the client threads
65 ** but read-only to the superviser thread.
67 volatile int completed
; /* Number of operations completed */
68 sqlite3
*db
; /* Open database */
69 sqlite3_stmt
*pStmt
; /* Pending operation */
70 char *zErr
; /* operation error */
71 char *zStaticErr
; /* Static error message */
72 int rc
; /* operation return code */
73 int argc
; /* number of columns in result */
74 const char *argv
[100]; /* result columns */
75 const char *colv
[100]; /* result column names */
77 /* Initialized to 1 by the supervisor thread when the client is
78 ** created, and then deemed read-only to the supervisor thread.
79 ** Is set to 0 by the server thread belonging to this client
80 ** just before it exits.
82 int nServer
; /* Number of server threads running */
86 ** There can be as many as 26 threads running at once. Each is named
87 ** by a capital letter: A, B, C, ..., Y, Z.
90 static Thread threadset
[N_THREAD
];
93 ** The main loop for a thread. Threads use busy waiting.
95 static void *client_main(void *pArg
){
96 Thread
*p
= (Thread
*)pArg
;
98 sqlite3_client_close(p
->db
);
100 sqlite3_client_open(p
->zFilename
, &p
->db
);
101 if( SQLITE_OK
!=sqlite3_errcode(p
->db
) ){
102 p
->zErr
= strdup(sqlite3_errmsg(p
->db
));
103 sqlite3_client_close(p
->db
);
108 while( p
->opnum
<=p
->completed
) sched_yield();
110 if( p
->zErr
&& p
->zErr
!=p
->zStaticErr
){
111 sqlite3_free(p
->zErr
);
116 while( p
->opnum
<=p
->completed
) sched_yield();
119 sqlite3_client_finalize(p
->pStmt
);
123 sqlite3_client_close(p
->db
);
126 if( p
->zErr
&& p
->zErr
!=p
->zStaticErr
){
127 sqlite3_free(p
->zErr
);
131 #ifndef SQLITE_OMIT_DEPRECATED
132 sqlite3_thread_cleanup();
138 ** Get a thread ID which is an upper case letter. Return the index.
139 ** If the argument is not a valid thread ID put an error message in
140 ** the interpreter and return -1.
142 static int parse_client_id(Tcl_Interp
*interp
, const char *zArg
){
143 if( zArg
==0 || zArg
[0]==0 || zArg
[1]!=0 || !isupper((unsigned char)zArg
[0]) ){
144 Tcl_AppendResult(interp
, "thread ID must be an upper case letter", 0);
147 return zArg
[0] - 'A';
151 ** Usage: client_create NAME FILENAME
153 ** NAME should be an upper case letter. Start the thread running with
154 ** an open connection to the given database.
156 static int SQLITE_TCLAPI
tcl_client_create(
158 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
159 int argc
, /* Number of arguments */
160 const char **argv
/* Text of each argument */
167 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
171 i
= parse_client_id(interp
, argv
[1]);
172 if( i
<0 ) return TCL_ERROR
;
173 if( threadset
[i
].busy
){
174 Tcl_AppendResult(interp
, "thread ", argv
[1], " is already running", 0);
177 threadset
[i
].busy
= 1;
178 sqlite3_free(threadset
[i
].zFilename
);
179 threadset
[i
].zFilename
= sqlite3_mprintf("%s", argv
[2]);
180 threadset
[i
].opnum
= 1;
181 threadset
[i
].completed
= 0;
182 rc
= pthread_create(&x
, 0, client_main
, &threadset
[i
]);
184 Tcl_AppendResult(interp
, "failed to create the thread", 0);
185 sqlite3_free(threadset
[i
].zFilename
);
186 threadset
[i
].busy
= 0;
190 if( threadset
[i
].nServer
==0 ){
191 threadset
[i
].nServer
= 1;
192 sqlite3_server_start2(&threadset
[i
].nServer
);
198 ** Wait for a thread to reach its idle state.
200 static void client_wait(Thread
*p
){
201 while( p
->opnum
>p
->completed
) sched_yield();
205 ** Usage: client_wait ID
207 ** Wait on thread ID to reach its idle state.
209 static int SQLITE_TCLAPI
tcl_client_wait(
211 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
212 int argc
, /* Number of arguments */
213 const char **argv
/* Text of each argument */
218 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
222 i
= parse_client_id(interp
, argv
[1]);
223 if( i
<0 ) return TCL_ERROR
;
224 if( !threadset
[i
].busy
){
225 Tcl_AppendResult(interp
, "no such thread", 0);
228 client_wait(&threadset
[i
]);
235 static void stop_thread(Thread
*p
){
240 sqlite3_free(p
->zArg
);
242 sqlite3_free(p
->zFilename
);
248 ** Usage: client_halt ID
250 ** Cause a client thread to shut itself down. Wait for the shutdown to be
251 ** completed. If ID is "*" then stop all client threads.
253 static int SQLITE_TCLAPI
tcl_client_halt(
255 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
256 int argc
, /* Number of arguments */
257 const char **argv
/* Text of each argument */
262 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
266 if( argv
[1][0]=='*' && argv
[1][1]==0 ){
267 for(i
=0; i
<N_THREAD
; i
++){
268 if( threadset
[i
].busy
){
269 stop_thread(&threadset
[i
]);
273 i
= parse_client_id(interp
, argv
[1]);
274 if( i
<0 ) return TCL_ERROR
;
275 if( !threadset
[i
].busy
){
276 Tcl_AppendResult(interp
, "no such thread", 0);
279 stop_thread(&threadset
[i
]);
282 /* If no client threads are still running, also stop the server */
283 for(i
=0; i
<N_THREAD
&& threadset
[i
].busy
==0; i
++){}
285 sqlite3_server_stop();
287 for(i
=0; i
<N_THREAD
&& threadset
[i
].nServer
==0; i
++);
288 if( i
==N_THREAD
) break;
296 ** Usage: client_argc ID
298 ** Wait on the most recent client_step to complete, then return the
299 ** number of columns in the result set.
301 static int SQLITE_TCLAPI
tcl_client_argc(
303 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
304 int argc
, /* Number of arguments */
305 const char **argv
/* Text of each argument */
311 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
315 i
= parse_client_id(interp
, argv
[1]);
316 if( i
<0 ) return TCL_ERROR
;
317 if( !threadset
[i
].busy
){
318 Tcl_AppendResult(interp
, "no such thread", 0);
321 client_wait(&threadset
[i
]);
322 sqlite3_snprintf(sizeof(zBuf
), zBuf
, "%d", threadset
[i
].argc
);
323 Tcl_AppendResult(interp
, zBuf
, 0);
328 ** Usage: client_argv ID N
330 ** Wait on the most recent client_step to complete, then return the
331 ** value of the N-th columns in the result set.
333 static int SQLITE_TCLAPI
tcl_client_argv(
335 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
336 int argc
, /* Number of arguments */
337 const char **argv
/* Text of each argument */
343 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
347 i
= parse_client_id(interp
, argv
[1]);
348 if( i
<0 ) return TCL_ERROR
;
349 if( !threadset
[i
].busy
){
350 Tcl_AppendResult(interp
, "no such thread", 0);
353 if( Tcl_GetInt(interp
, argv
[2], &n
) ) return TCL_ERROR
;
354 client_wait(&threadset
[i
]);
355 if( n
<0 || n
>=threadset
[i
].argc
){
356 Tcl_AppendResult(interp
, "column number out of range", 0);
359 Tcl_AppendResult(interp
, threadset
[i
].argv
[n
], 0);
364 ** Usage: client_colname ID N
366 ** Wait on the most recent client_step to complete, then return the
367 ** name of the N-th columns in the result set.
369 static int SQLITE_TCLAPI
tcl_client_colname(
371 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
372 int argc
, /* Number of arguments */
373 const char **argv
/* Text of each argument */
379 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
383 i
= parse_client_id(interp
, argv
[1]);
384 if( i
<0 ) return TCL_ERROR
;
385 if( !threadset
[i
].busy
){
386 Tcl_AppendResult(interp
, "no such thread", 0);
389 if( Tcl_GetInt(interp
, argv
[2], &n
) ) return TCL_ERROR
;
390 client_wait(&threadset
[i
]);
391 if( n
<0 || n
>=threadset
[i
].argc
){
392 Tcl_AppendResult(interp
, "column number out of range", 0);
395 Tcl_AppendResult(interp
, threadset
[i
].colv
[n
], 0);
399 extern const char *sqlite3ErrName(int);
402 ** Usage: client_result ID
404 ** Wait on the most recent operation to complete, then return the
405 ** result code from that operation.
407 static int SQLITE_TCLAPI
tcl_client_result(
409 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
410 int argc
, /* Number of arguments */
411 const char **argv
/* Text of each argument */
417 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
421 i
= parse_client_id(interp
, argv
[1]);
422 if( i
<0 ) return TCL_ERROR
;
423 if( !threadset
[i
].busy
){
424 Tcl_AppendResult(interp
, "no such thread", 0);
427 client_wait(&threadset
[i
]);
428 zName
= sqlite3ErrName(threadset
[i
].rc
);
429 Tcl_AppendResult(interp
, zName
, 0);
434 ** Usage: client_error ID
436 ** Wait on the most recent operation to complete, then return the
439 static int SQLITE_TCLAPI
tcl_client_error(
441 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
442 int argc
, /* Number of arguments */
443 const char **argv
/* Text of each argument */
448 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
452 i
= parse_client_id(interp
, argv
[1]);
453 if( i
<0 ) return TCL_ERROR
;
454 if( !threadset
[i
].busy
){
455 Tcl_AppendResult(interp
, "no such thread", 0);
458 client_wait(&threadset
[i
]);
459 Tcl_AppendResult(interp
, threadset
[i
].zErr
, 0);
464 ** This procedure runs in the thread to compile an SQL statement.
466 static void do_compile(Thread
*p
){
468 p
->zErr
= p
->zStaticErr
= "no database is open";
469 p
->rc
= SQLITE_ERROR
;
473 sqlite3_client_finalize(p
->pStmt
);
476 p
->rc
= sqlite3_client_prepare(p
->db
, p
->zArg
, -1, &p
->pStmt
, 0);
480 ** Usage: client_compile ID SQL
482 ** Compile a new virtual machine.
484 static int SQLITE_TCLAPI
tcl_client_compile(
486 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
487 int argc
, /* Number of arguments */
488 const char **argv
/* Text of each argument */
492 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
496 i
= parse_client_id(interp
, argv
[1]);
497 if( i
<0 ) return TCL_ERROR
;
498 if( !threadset
[i
].busy
){
499 Tcl_AppendResult(interp
, "no such thread", 0);
502 client_wait(&threadset
[i
]);
503 threadset
[i
].xOp
= do_compile
;
504 sqlite3_free(threadset
[i
].zArg
);
505 threadset
[i
].zArg
= sqlite3_mprintf("%s", argv
[2]);
506 threadset
[i
].opnum
++;
511 ** This procedure runs in the thread to step the virtual machine.
513 static void do_step(Thread
*p
){
516 p
->zErr
= p
->zStaticErr
= "no virtual machine available";
517 p
->rc
= SQLITE_ERROR
;
520 p
->rc
= sqlite3_client_step(p
->pStmt
);
521 if( p
->rc
==SQLITE_ROW
){
522 p
->argc
= sqlite3_column_count(p
->pStmt
);
523 for(i
=0; i
<sqlite3_data_count(p
->pStmt
); i
++){
524 p
->argv
[i
] = (char*)sqlite3_column_text(p
->pStmt
, i
);
526 for(i
=0; i
<p
->argc
; i
++){
527 p
->colv
[i
] = sqlite3_column_name(p
->pStmt
, i
);
533 ** Usage: client_step ID
535 ** Advance the virtual machine by one step
537 static int SQLITE_TCLAPI
tcl_client_step(
539 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
540 int argc
, /* Number of arguments */
541 const char **argv
/* Text of each argument */
545 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
549 i
= parse_client_id(interp
, argv
[1]);
550 if( i
<0 ) return TCL_ERROR
;
551 if( !threadset
[i
].busy
){
552 Tcl_AppendResult(interp
, "no such thread", 0);
555 client_wait(&threadset
[i
]);
556 threadset
[i
].xOp
= do_step
;
557 threadset
[i
].opnum
++;
562 ** This procedure runs in the thread to finalize a virtual machine.
564 static void do_finalize(Thread
*p
){
566 p
->zErr
= p
->zStaticErr
= "no virtual machine available";
567 p
->rc
= SQLITE_ERROR
;
570 p
->rc
= sqlite3_client_finalize(p
->pStmt
);
575 ** Usage: client_finalize ID
577 ** Finalize the virtual machine.
579 static int SQLITE_TCLAPI
tcl_client_finalize(
581 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
582 int argc
, /* Number of arguments */
583 const char **argv
/* Text of each argument */
587 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
591 i
= parse_client_id(interp
, argv
[1]);
592 if( i
<0 ) return TCL_ERROR
;
593 if( !threadset
[i
].busy
){
594 Tcl_AppendResult(interp
, "no such thread", 0);
597 client_wait(&threadset
[i
]);
598 threadset
[i
].xOp
= do_finalize
;
599 sqlite3_free(threadset
[i
].zArg
);
600 threadset
[i
].zArg
= 0;
601 threadset
[i
].opnum
++;
606 ** This procedure runs in the thread to reset a virtual machine.
608 static void do_reset(Thread
*p
){
610 p
->zErr
= p
->zStaticErr
= "no virtual machine available";
611 p
->rc
= SQLITE_ERROR
;
614 p
->rc
= sqlite3_client_reset(p
->pStmt
);
619 ** Usage: client_reset ID
621 ** Finalize the virtual machine.
623 static int SQLITE_TCLAPI
tcl_client_reset(
625 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
626 int argc
, /* Number of arguments */
627 const char **argv
/* Text of each argument */
631 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
635 i
= parse_client_id(interp
, argv
[1]);
636 if( i
<0 ) return TCL_ERROR
;
637 if( !threadset
[i
].busy
){
638 Tcl_AppendResult(interp
, "no such thread", 0);
641 client_wait(&threadset
[i
]);
642 threadset
[i
].xOp
= do_reset
;
643 sqlite3_free(threadset
[i
].zArg
);
644 threadset
[i
].zArg
= 0;
645 threadset
[i
].opnum
++;
650 ** Usage: client_swap ID ID
652 ** Interchange the sqlite* pointer between two threads.
654 static int SQLITE_TCLAPI
tcl_client_swap(
656 Tcl_Interp
*interp
, /* The TCL interpreter that invoked this command */
657 int argc
, /* Number of arguments */
658 const char **argv
/* Text of each argument */
663 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
667 i
= parse_client_id(interp
, argv
[1]);
668 if( i
<0 ) return TCL_ERROR
;
669 if( !threadset
[i
].busy
){
670 Tcl_AppendResult(interp
, "no such thread", 0);
673 client_wait(&threadset
[i
]);
674 j
= parse_client_id(interp
, argv
[2]);
675 if( j
<0 ) return TCL_ERROR
;
676 if( !threadset
[j
].busy
){
677 Tcl_AppendResult(interp
, "no such thread", 0);
680 client_wait(&threadset
[j
]);
681 temp
= threadset
[i
].db
;
682 threadset
[i
].db
= threadset
[j
].db
;
683 threadset
[j
].db
= temp
;
688 ** Register commands with the TCL interpreter.
690 int Sqlitetest7_Init(Tcl_Interp
*interp
){
695 { "client_create", (Tcl_CmdProc
*)tcl_client_create
},
696 { "client_wait", (Tcl_CmdProc
*)tcl_client_wait
},
697 { "client_halt", (Tcl_CmdProc
*)tcl_client_halt
},
698 { "client_argc", (Tcl_CmdProc
*)tcl_client_argc
},
699 { "client_argv", (Tcl_CmdProc
*)tcl_client_argv
},
700 { "client_colname", (Tcl_CmdProc
*)tcl_client_colname
},
701 { "client_result", (Tcl_CmdProc
*)tcl_client_result
},
702 { "client_error", (Tcl_CmdProc
*)tcl_client_error
},
703 { "client_compile", (Tcl_CmdProc
*)tcl_client_compile
},
704 { "client_step", (Tcl_CmdProc
*)tcl_client_step
},
705 { "client_reset", (Tcl_CmdProc
*)tcl_client_reset
},
706 { "client_finalize", (Tcl_CmdProc
*)tcl_client_finalize
},
707 { "client_swap", (Tcl_CmdProc
*)tcl_client_swap
},
711 for(i
=0; i
<sizeof(aCmd
)/sizeof(aCmd
[0]); i
++){
712 Tcl_CreateCommand(interp
, aCmd
[i
].zName
, aCmd
[i
].xProc
, 0, 0);
717 int Sqlitetest7_Init(Tcl_Interp
*interp
){ return TCL_OK
; }
718 #endif /* SQLITE_OS_UNIX */