|
1 /* |
|
2 ** 2006 January 09 |
|
3 ** |
|
4 ** The author disclaims copyright to this source code. In place of |
|
5 ** a legal notice, here is a blessing: |
|
6 ** |
|
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. |
|
10 ** |
|
11 ************************************************************************* |
|
12 ** Code for testing the client/server version of the SQLite library. |
|
13 ** Derived from test4.c. |
|
14 ** |
|
15 ** $Id: test7.c,v 1.12 2008/07/28 19:34:54 drh Exp $ |
|
16 */ |
|
17 #include "sqliteInt.h" |
|
18 #include "tcl.h" |
|
19 |
|
20 /* |
|
21 ** This test only works on UNIX with a SQLITE_THREADSAFE build that includes |
|
22 ** the SQLITE_SERVER option. |
|
23 */ |
|
24 #if defined(SQLITE_SERVER) && !defined(SQLITE_OMIT_SHARED_CACHE) && \ |
|
25 defined(SQLITE_OS_UNIX) && OS_UNIX && SQLITE_THREADSAFE |
|
26 |
|
27 #include <stdlib.h> |
|
28 #include <string.h> |
|
29 #include <pthread.h> |
|
30 #include <sched.h> |
|
31 #include <ctype.h> |
|
32 |
|
33 /* |
|
34 ** Interfaces defined in server.c |
|
35 */ |
|
36 int sqlite3_client_open(const char*, sqlite3**); |
|
37 int sqlite3_client_prepare(sqlite3*,const char*,int, |
|
38 sqlite3_stmt**,const char**); |
|
39 int sqlite3_client_step(sqlite3_stmt*); |
|
40 int sqlite3_client_reset(sqlite3_stmt*); |
|
41 int sqlite3_client_finalize(sqlite3_stmt*); |
|
42 int sqlite3_client_close(sqlite3*); |
|
43 int sqlite3_server_start(void); |
|
44 int sqlite3_server_stop(void); |
|
45 |
|
46 /* |
|
47 ** Each thread is controlled by an instance of the following |
|
48 ** structure. |
|
49 */ |
|
50 typedef struct Thread Thread; |
|
51 struct Thread { |
|
52 /* The first group of fields are writable by the supervisor thread |
|
53 ** and read-only to the client threads |
|
54 */ |
|
55 char *zFilename; /* Name of database file */ |
|
56 void (*xOp)(Thread*); /* next operation to do */ |
|
57 char *zArg; /* argument usable by xOp */ |
|
58 volatile int opnum; /* Operation number */ |
|
59 volatile int busy; /* True if this thread is in use */ |
|
60 |
|
61 /* The next group of fields are writable by the client threads |
|
62 ** but read-only to the superviser thread. |
|
63 */ |
|
64 volatile int completed; /* Number of operations completed */ |
|
65 sqlite3 *db; /* Open database */ |
|
66 sqlite3_stmt *pStmt; /* Pending operation */ |
|
67 char *zErr; /* operation error */ |
|
68 char *zStaticErr; /* Static error message */ |
|
69 int rc; /* operation return code */ |
|
70 int argc; /* number of columns in result */ |
|
71 const char *argv[100]; /* result columns */ |
|
72 const char *colv[100]; /* result column names */ |
|
73 }; |
|
74 |
|
75 /* |
|
76 ** There can be as many as 26 threads running at once. Each is named |
|
77 ** by a capital letter: A, B, C, ..., Y, Z. |
|
78 */ |
|
79 #define N_THREAD 26 |
|
80 static Thread threadset[N_THREAD]; |
|
81 |
|
82 /* |
|
83 ** The main loop for a thread. Threads use busy waiting. |
|
84 */ |
|
85 static void *client_main(void *pArg){ |
|
86 Thread *p = (Thread*)pArg; |
|
87 if( p->db ){ |
|
88 sqlite3_client_close(p->db); |
|
89 } |
|
90 sqlite3_client_open(p->zFilename, &p->db); |
|
91 if( SQLITE_OK!=sqlite3_errcode(p->db) ){ |
|
92 p->zErr = strdup(sqlite3_errmsg(p->db)); |
|
93 sqlite3_client_close(p->db); |
|
94 p->db = 0; |
|
95 } |
|
96 p->pStmt = 0; |
|
97 p->completed = 1; |
|
98 while( p->opnum<=p->completed ) sched_yield(); |
|
99 while( p->xOp ){ |
|
100 if( p->zErr && p->zErr!=p->zStaticErr ){ |
|
101 sqlite3_free(p->zErr); |
|
102 p->zErr = 0; |
|
103 } |
|
104 (*p->xOp)(p); |
|
105 p->completed++; |
|
106 while( p->opnum<=p->completed ) sched_yield(); |
|
107 } |
|
108 if( p->pStmt ){ |
|
109 sqlite3_client_finalize(p->pStmt); |
|
110 p->pStmt = 0; |
|
111 } |
|
112 if( p->db ){ |
|
113 sqlite3_client_close(p->db); |
|
114 p->db = 0; |
|
115 } |
|
116 if( p->zErr && p->zErr!=p->zStaticErr ){ |
|
117 sqlite3_free(p->zErr); |
|
118 p->zErr = 0; |
|
119 } |
|
120 p->completed++; |
|
121 sqlite3_thread_cleanup(); |
|
122 return 0; |
|
123 } |
|
124 |
|
125 /* |
|
126 ** Get a thread ID which is an upper case letter. Return the index. |
|
127 ** If the argument is not a valid thread ID put an error message in |
|
128 ** the interpreter and return -1. |
|
129 */ |
|
130 static int parse_client_id(Tcl_Interp *interp, const char *zArg){ |
|
131 if( zArg==0 || zArg[0]==0 || zArg[1]!=0 || !isupper((unsigned char)zArg[0]) ){ |
|
132 Tcl_AppendResult(interp, "thread ID must be an upper case letter", 0); |
|
133 return -1; |
|
134 } |
|
135 return zArg[0] - 'A'; |
|
136 } |
|
137 |
|
138 /* |
|
139 ** Usage: client_create NAME FILENAME |
|
140 ** |
|
141 ** NAME should be an upper case letter. Start the thread running with |
|
142 ** an open connection to the given database. |
|
143 */ |
|
144 static int tcl_client_create( |
|
145 void *NotUsed, |
|
146 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
147 int argc, /* Number of arguments */ |
|
148 const char **argv /* Text of each argument */ |
|
149 ){ |
|
150 int i; |
|
151 pthread_t x; |
|
152 int rc; |
|
153 |
|
154 if( argc!=3 ){ |
|
155 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
156 " ID FILENAME", 0); |
|
157 return TCL_ERROR; |
|
158 } |
|
159 i = parse_client_id(interp, argv[1]); |
|
160 if( i<0 ) return TCL_ERROR; |
|
161 if( threadset[i].busy ){ |
|
162 Tcl_AppendResult(interp, "thread ", argv[1], " is already running", 0); |
|
163 return TCL_ERROR; |
|
164 } |
|
165 threadset[i].busy = 1; |
|
166 sqlite3_free(threadset[i].zFilename); |
|
167 threadset[i].zFilename = sqlite3DbStrDup(0, argv[2]); |
|
168 threadset[i].opnum = 1; |
|
169 threadset[i].completed = 0; |
|
170 rc = pthread_create(&x, 0, client_main, &threadset[i]); |
|
171 if( rc ){ |
|
172 Tcl_AppendResult(interp, "failed to create the thread", 0); |
|
173 sqlite3_free(threadset[i].zFilename); |
|
174 threadset[i].busy = 0; |
|
175 return TCL_ERROR; |
|
176 } |
|
177 pthread_detach(x); |
|
178 sqlite3_server_start(); |
|
179 return TCL_OK; |
|
180 } |
|
181 |
|
182 /* |
|
183 ** Wait for a thread to reach its idle state. |
|
184 */ |
|
185 static void client_wait(Thread *p){ |
|
186 while( p->opnum>p->completed ) sched_yield(); |
|
187 } |
|
188 |
|
189 /* |
|
190 ** Usage: client_wait ID |
|
191 ** |
|
192 ** Wait on thread ID to reach its idle state. |
|
193 */ |
|
194 static int tcl_client_wait( |
|
195 void *NotUsed, |
|
196 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
197 int argc, /* Number of arguments */ |
|
198 const char **argv /* Text of each argument */ |
|
199 ){ |
|
200 int i; |
|
201 |
|
202 if( argc!=2 ){ |
|
203 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
204 " ID", 0); |
|
205 return TCL_ERROR; |
|
206 } |
|
207 i = parse_client_id(interp, argv[1]); |
|
208 if( i<0 ) return TCL_ERROR; |
|
209 if( !threadset[i].busy ){ |
|
210 Tcl_AppendResult(interp, "no such thread", 0); |
|
211 return TCL_ERROR; |
|
212 } |
|
213 client_wait(&threadset[i]); |
|
214 return TCL_OK; |
|
215 } |
|
216 |
|
217 /* |
|
218 ** Stop a thread. |
|
219 */ |
|
220 static void stop_thread(Thread *p){ |
|
221 client_wait(p); |
|
222 p->xOp = 0; |
|
223 p->opnum++; |
|
224 client_wait(p); |
|
225 sqlite3_free(p->zArg); |
|
226 p->zArg = 0; |
|
227 sqlite3_free(p->zFilename); |
|
228 p->zFilename = 0; |
|
229 p->busy = 0; |
|
230 } |
|
231 |
|
232 /* |
|
233 ** Usage: client_halt ID |
|
234 ** |
|
235 ** Cause a client thread to shut itself down. Wait for the shutdown to be |
|
236 ** completed. If ID is "*" then stop all client threads. |
|
237 */ |
|
238 static int tcl_client_halt( |
|
239 void *NotUsed, |
|
240 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
241 int argc, /* Number of arguments */ |
|
242 const char **argv /* Text of each argument */ |
|
243 ){ |
|
244 int i; |
|
245 |
|
246 if( argc!=2 ){ |
|
247 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
248 " ID", 0); |
|
249 return TCL_ERROR; |
|
250 } |
|
251 if( argv[1][0]=='*' && argv[1][1]==0 ){ |
|
252 for(i=0; i<N_THREAD; i++){ |
|
253 if( threadset[i].busy ){ |
|
254 stop_thread(&threadset[i]); |
|
255 } |
|
256 } |
|
257 }else{ |
|
258 i = parse_client_id(interp, argv[1]); |
|
259 if( i<0 ) return TCL_ERROR; |
|
260 if( !threadset[i].busy ){ |
|
261 Tcl_AppendResult(interp, "no such thread", 0); |
|
262 return TCL_ERROR; |
|
263 } |
|
264 stop_thread(&threadset[i]); |
|
265 } |
|
266 |
|
267 /* If no client threads are still running, also stop the server */ |
|
268 for(i=0; i<N_THREAD && threadset[i].busy==0; i++){} |
|
269 if( i>=N_THREAD ){ |
|
270 sqlite3_server_stop(); |
|
271 } |
|
272 return TCL_OK; |
|
273 } |
|
274 |
|
275 /* |
|
276 ** Usage: client_argc ID |
|
277 ** |
|
278 ** Wait on the most recent client_step to complete, then return the |
|
279 ** number of columns in the result set. |
|
280 */ |
|
281 static int tcl_client_argc( |
|
282 void *NotUsed, |
|
283 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
284 int argc, /* Number of arguments */ |
|
285 const char **argv /* Text of each argument */ |
|
286 ){ |
|
287 int i; |
|
288 char zBuf[100]; |
|
289 |
|
290 if( argc!=2 ){ |
|
291 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
292 " ID", 0); |
|
293 return TCL_ERROR; |
|
294 } |
|
295 i = parse_client_id(interp, argv[1]); |
|
296 if( i<0 ) return TCL_ERROR; |
|
297 if( !threadset[i].busy ){ |
|
298 Tcl_AppendResult(interp, "no such thread", 0); |
|
299 return TCL_ERROR; |
|
300 } |
|
301 client_wait(&threadset[i]); |
|
302 sprintf(zBuf, "%d", threadset[i].argc); |
|
303 Tcl_AppendResult(interp, zBuf, 0); |
|
304 return TCL_OK; |
|
305 } |
|
306 |
|
307 /* |
|
308 ** Usage: client_argv ID N |
|
309 ** |
|
310 ** Wait on the most recent client_step to complete, then return the |
|
311 ** value of the N-th columns in the result set. |
|
312 */ |
|
313 static int tcl_client_argv( |
|
314 void *NotUsed, |
|
315 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
316 int argc, /* Number of arguments */ |
|
317 const char **argv /* Text of each argument */ |
|
318 ){ |
|
319 int i; |
|
320 int n; |
|
321 |
|
322 if( argc!=3 ){ |
|
323 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
324 " ID N", 0); |
|
325 return TCL_ERROR; |
|
326 } |
|
327 i = parse_client_id(interp, argv[1]); |
|
328 if( i<0 ) return TCL_ERROR; |
|
329 if( !threadset[i].busy ){ |
|
330 Tcl_AppendResult(interp, "no such thread", 0); |
|
331 return TCL_ERROR; |
|
332 } |
|
333 if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR; |
|
334 client_wait(&threadset[i]); |
|
335 if( n<0 || n>=threadset[i].argc ){ |
|
336 Tcl_AppendResult(interp, "column number out of range", 0); |
|
337 return TCL_ERROR; |
|
338 } |
|
339 Tcl_AppendResult(interp, threadset[i].argv[n], 0); |
|
340 return TCL_OK; |
|
341 } |
|
342 |
|
343 /* |
|
344 ** Usage: client_colname ID N |
|
345 ** |
|
346 ** Wait on the most recent client_step to complete, then return the |
|
347 ** name of the N-th columns in the result set. |
|
348 */ |
|
349 static int tcl_client_colname( |
|
350 void *NotUsed, |
|
351 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
352 int argc, /* Number of arguments */ |
|
353 const char **argv /* Text of each argument */ |
|
354 ){ |
|
355 int i; |
|
356 int n; |
|
357 |
|
358 if( argc!=3 ){ |
|
359 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
360 " ID N", 0); |
|
361 return TCL_ERROR; |
|
362 } |
|
363 i = parse_client_id(interp, argv[1]); |
|
364 if( i<0 ) return TCL_ERROR; |
|
365 if( !threadset[i].busy ){ |
|
366 Tcl_AppendResult(interp, "no such thread", 0); |
|
367 return TCL_ERROR; |
|
368 } |
|
369 if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR; |
|
370 client_wait(&threadset[i]); |
|
371 if( n<0 || n>=threadset[i].argc ){ |
|
372 Tcl_AppendResult(interp, "column number out of range", 0); |
|
373 return TCL_ERROR; |
|
374 } |
|
375 Tcl_AppendResult(interp, threadset[i].colv[n], 0); |
|
376 return TCL_OK; |
|
377 } |
|
378 |
|
379 /* |
|
380 ** Usage: client_result ID |
|
381 ** |
|
382 ** Wait on the most recent operation to complete, then return the |
|
383 ** result code from that operation. |
|
384 */ |
|
385 static int tcl_client_result( |
|
386 void *NotUsed, |
|
387 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
388 int argc, /* Number of arguments */ |
|
389 const char **argv /* Text of each argument */ |
|
390 ){ |
|
391 int i; |
|
392 const char *zName; |
|
393 |
|
394 if( argc!=2 ){ |
|
395 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
396 " ID", 0); |
|
397 return TCL_ERROR; |
|
398 } |
|
399 i = parse_client_id(interp, argv[1]); |
|
400 if( i<0 ) return TCL_ERROR; |
|
401 if( !threadset[i].busy ){ |
|
402 Tcl_AppendResult(interp, "no such thread", 0); |
|
403 return TCL_ERROR; |
|
404 } |
|
405 client_wait(&threadset[i]); |
|
406 switch( threadset[i].rc ){ |
|
407 case SQLITE_OK: zName = "SQLITE_OK"; break; |
|
408 case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; |
|
409 case SQLITE_PERM: zName = "SQLITE_PERM"; break; |
|
410 case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; |
|
411 case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; |
|
412 case SQLITE_LOCKED: zName = "SQLITE_LOCKED"; break; |
|
413 case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break; |
|
414 case SQLITE_READONLY: zName = "SQLITE_READONLY"; break; |
|
415 case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break; |
|
416 case SQLITE_IOERR: zName = "SQLITE_IOERR"; break; |
|
417 case SQLITE_CORRUPT: zName = "SQLITE_CORRUPT"; break; |
|
418 case SQLITE_FULL: zName = "SQLITE_FULL"; break; |
|
419 case SQLITE_CANTOPEN: zName = "SQLITE_CANTOPEN"; break; |
|
420 case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break; |
|
421 case SQLITE_EMPTY: zName = "SQLITE_EMPTY"; break; |
|
422 case SQLITE_SCHEMA: zName = "SQLITE_SCHEMA"; break; |
|
423 case SQLITE_CONSTRAINT: zName = "SQLITE_CONSTRAINT"; break; |
|
424 case SQLITE_MISMATCH: zName = "SQLITE_MISMATCH"; break; |
|
425 case SQLITE_MISUSE: zName = "SQLITE_MISUSE"; break; |
|
426 case SQLITE_NOLFS: zName = "SQLITE_NOLFS"; break; |
|
427 case SQLITE_AUTH: zName = "SQLITE_AUTH"; break; |
|
428 case SQLITE_FORMAT: zName = "SQLITE_FORMAT"; break; |
|
429 case SQLITE_RANGE: zName = "SQLITE_RANGE"; break; |
|
430 case SQLITE_ROW: zName = "SQLITE_ROW"; break; |
|
431 case SQLITE_DONE: zName = "SQLITE_DONE"; break; |
|
432 default: zName = "SQLITE_Unknown"; break; |
|
433 } |
|
434 Tcl_AppendResult(interp, zName, 0); |
|
435 return TCL_OK; |
|
436 } |
|
437 |
|
438 /* |
|
439 ** Usage: client_error ID |
|
440 ** |
|
441 ** Wait on the most recent operation to complete, then return the |
|
442 ** error string. |
|
443 */ |
|
444 static int tcl_client_error( |
|
445 void *NotUsed, |
|
446 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
447 int argc, /* Number of arguments */ |
|
448 const char **argv /* Text of each argument */ |
|
449 ){ |
|
450 int i; |
|
451 |
|
452 if( argc!=2 ){ |
|
453 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
454 " ID", 0); |
|
455 return TCL_ERROR; |
|
456 } |
|
457 i = parse_client_id(interp, argv[1]); |
|
458 if( i<0 ) return TCL_ERROR; |
|
459 if( !threadset[i].busy ){ |
|
460 Tcl_AppendResult(interp, "no such thread", 0); |
|
461 return TCL_ERROR; |
|
462 } |
|
463 client_wait(&threadset[i]); |
|
464 Tcl_AppendResult(interp, threadset[i].zErr, 0); |
|
465 return TCL_OK; |
|
466 } |
|
467 |
|
468 /* |
|
469 ** This procedure runs in the thread to compile an SQL statement. |
|
470 */ |
|
471 static void do_compile(Thread *p){ |
|
472 if( p->db==0 ){ |
|
473 p->zErr = p->zStaticErr = "no database is open"; |
|
474 p->rc = SQLITE_ERROR; |
|
475 return; |
|
476 } |
|
477 if( p->pStmt ){ |
|
478 sqlite3_client_finalize(p->pStmt); |
|
479 p->pStmt = 0; |
|
480 } |
|
481 p->rc = sqlite3_client_prepare(p->db, p->zArg, -1, &p->pStmt, 0); |
|
482 } |
|
483 |
|
484 /* |
|
485 ** Usage: client_compile ID SQL |
|
486 ** |
|
487 ** Compile a new virtual machine. |
|
488 */ |
|
489 static int tcl_client_compile( |
|
490 void *NotUsed, |
|
491 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
492 int argc, /* Number of arguments */ |
|
493 const char **argv /* Text of each argument */ |
|
494 ){ |
|
495 int i; |
|
496 if( argc!=3 ){ |
|
497 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
498 " ID SQL", 0); |
|
499 return TCL_ERROR; |
|
500 } |
|
501 i = parse_client_id(interp, argv[1]); |
|
502 if( i<0 ) return TCL_ERROR; |
|
503 if( !threadset[i].busy ){ |
|
504 Tcl_AppendResult(interp, "no such thread", 0); |
|
505 return TCL_ERROR; |
|
506 } |
|
507 client_wait(&threadset[i]); |
|
508 threadset[i].xOp = do_compile; |
|
509 sqlite3_free(threadset[i].zArg); |
|
510 threadset[i].zArg = sqlite3DbStrDup(0, argv[2]); |
|
511 threadset[i].opnum++; |
|
512 return TCL_OK; |
|
513 } |
|
514 |
|
515 /* |
|
516 ** This procedure runs in the thread to step the virtual machine. |
|
517 */ |
|
518 static void do_step(Thread *p){ |
|
519 int i; |
|
520 if( p->pStmt==0 ){ |
|
521 p->zErr = p->zStaticErr = "no virtual machine available"; |
|
522 p->rc = SQLITE_ERROR; |
|
523 return; |
|
524 } |
|
525 p->rc = sqlite3_client_step(p->pStmt); |
|
526 if( p->rc==SQLITE_ROW ){ |
|
527 p->argc = sqlite3_column_count(p->pStmt); |
|
528 for(i=0; i<sqlite3_data_count(p->pStmt); i++){ |
|
529 p->argv[i] = (char*)sqlite3_column_text(p->pStmt, i); |
|
530 } |
|
531 for(i=0; i<p->argc; i++){ |
|
532 p->colv[i] = sqlite3_column_name(p->pStmt, i); |
|
533 } |
|
534 } |
|
535 } |
|
536 |
|
537 /* |
|
538 ** Usage: client_step ID |
|
539 ** |
|
540 ** Advance the virtual machine by one step |
|
541 */ |
|
542 static int tcl_client_step( |
|
543 void *NotUsed, |
|
544 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
545 int argc, /* Number of arguments */ |
|
546 const char **argv /* Text of each argument */ |
|
547 ){ |
|
548 int i; |
|
549 if( argc!=2 ){ |
|
550 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
551 " IDL", 0); |
|
552 return TCL_ERROR; |
|
553 } |
|
554 i = parse_client_id(interp, argv[1]); |
|
555 if( i<0 ) return TCL_ERROR; |
|
556 if( !threadset[i].busy ){ |
|
557 Tcl_AppendResult(interp, "no such thread", 0); |
|
558 return TCL_ERROR; |
|
559 } |
|
560 client_wait(&threadset[i]); |
|
561 threadset[i].xOp = do_step; |
|
562 threadset[i].opnum++; |
|
563 return TCL_OK; |
|
564 } |
|
565 |
|
566 /* |
|
567 ** This procedure runs in the thread to finalize a virtual machine. |
|
568 */ |
|
569 static void do_finalize(Thread *p){ |
|
570 if( p->pStmt==0 ){ |
|
571 p->zErr = p->zStaticErr = "no virtual machine available"; |
|
572 p->rc = SQLITE_ERROR; |
|
573 return; |
|
574 } |
|
575 p->rc = sqlite3_client_finalize(p->pStmt); |
|
576 p->pStmt = 0; |
|
577 } |
|
578 |
|
579 /* |
|
580 ** Usage: client_finalize ID |
|
581 ** |
|
582 ** Finalize the virtual machine. |
|
583 */ |
|
584 static int tcl_client_finalize( |
|
585 void *NotUsed, |
|
586 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
587 int argc, /* Number of arguments */ |
|
588 const char **argv /* Text of each argument */ |
|
589 ){ |
|
590 int i; |
|
591 if( argc!=2 ){ |
|
592 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
593 " IDL", 0); |
|
594 return TCL_ERROR; |
|
595 } |
|
596 i = parse_client_id(interp, argv[1]); |
|
597 if( i<0 ) return TCL_ERROR; |
|
598 if( !threadset[i].busy ){ |
|
599 Tcl_AppendResult(interp, "no such thread", 0); |
|
600 return TCL_ERROR; |
|
601 } |
|
602 client_wait(&threadset[i]); |
|
603 threadset[i].xOp = do_finalize; |
|
604 sqlite3_free(threadset[i].zArg); |
|
605 threadset[i].zArg = 0; |
|
606 threadset[i].opnum++; |
|
607 return TCL_OK; |
|
608 } |
|
609 |
|
610 /* |
|
611 ** This procedure runs in the thread to reset a virtual machine. |
|
612 */ |
|
613 static void do_reset(Thread *p){ |
|
614 if( p->pStmt==0 ){ |
|
615 p->zErr = p->zStaticErr = "no virtual machine available"; |
|
616 p->rc = SQLITE_ERROR; |
|
617 return; |
|
618 } |
|
619 p->rc = sqlite3_client_reset(p->pStmt); |
|
620 p->pStmt = 0; |
|
621 } |
|
622 |
|
623 /* |
|
624 ** Usage: client_reset ID |
|
625 ** |
|
626 ** Finalize the virtual machine. |
|
627 */ |
|
628 static int tcl_client_reset( |
|
629 void *NotUsed, |
|
630 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
631 int argc, /* Number of arguments */ |
|
632 const char **argv /* Text of each argument */ |
|
633 ){ |
|
634 int i; |
|
635 if( argc!=2 ){ |
|
636 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
637 " IDL", 0); |
|
638 return TCL_ERROR; |
|
639 } |
|
640 i = parse_client_id(interp, argv[1]); |
|
641 if( i<0 ) return TCL_ERROR; |
|
642 if( !threadset[i].busy ){ |
|
643 Tcl_AppendResult(interp, "no such thread", 0); |
|
644 return TCL_ERROR; |
|
645 } |
|
646 client_wait(&threadset[i]); |
|
647 threadset[i].xOp = do_reset; |
|
648 sqlite3_free(threadset[i].zArg); |
|
649 threadset[i].zArg = 0; |
|
650 threadset[i].opnum++; |
|
651 return TCL_OK; |
|
652 } |
|
653 |
|
654 /* |
|
655 ** Usage: client_swap ID ID |
|
656 ** |
|
657 ** Interchange the sqlite* pointer between two threads. |
|
658 */ |
|
659 static int tcl_client_swap( |
|
660 void *NotUsed, |
|
661 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
|
662 int argc, /* Number of arguments */ |
|
663 const char **argv /* Text of each argument */ |
|
664 ){ |
|
665 int i, j; |
|
666 sqlite3 *temp; |
|
667 if( argc!=3 ){ |
|
668 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], |
|
669 " ID1 ID2", 0); |
|
670 return TCL_ERROR; |
|
671 } |
|
672 i = parse_client_id(interp, argv[1]); |
|
673 if( i<0 ) return TCL_ERROR; |
|
674 if( !threadset[i].busy ){ |
|
675 Tcl_AppendResult(interp, "no such thread", 0); |
|
676 return TCL_ERROR; |
|
677 } |
|
678 client_wait(&threadset[i]); |
|
679 j = parse_client_id(interp, argv[2]); |
|
680 if( j<0 ) return TCL_ERROR; |
|
681 if( !threadset[j].busy ){ |
|
682 Tcl_AppendResult(interp, "no such thread", 0); |
|
683 return TCL_ERROR; |
|
684 } |
|
685 client_wait(&threadset[j]); |
|
686 temp = threadset[i].db; |
|
687 threadset[i].db = threadset[j].db; |
|
688 threadset[j].db = temp; |
|
689 return TCL_OK; |
|
690 } |
|
691 |
|
692 /* |
|
693 ** Register commands with the TCL interpreter. |
|
694 */ |
|
695 int Sqlitetest7_Init(Tcl_Interp *interp){ |
|
696 static struct { |
|
697 char *zName; |
|
698 Tcl_CmdProc *xProc; |
|
699 } aCmd[] = { |
|
700 { "client_create", (Tcl_CmdProc*)tcl_client_create }, |
|
701 { "client_wait", (Tcl_CmdProc*)tcl_client_wait }, |
|
702 { "client_halt", (Tcl_CmdProc*)tcl_client_halt }, |
|
703 { "client_argc", (Tcl_CmdProc*)tcl_client_argc }, |
|
704 { "client_argv", (Tcl_CmdProc*)tcl_client_argv }, |
|
705 { "client_colname", (Tcl_CmdProc*)tcl_client_colname }, |
|
706 { "client_result", (Tcl_CmdProc*)tcl_client_result }, |
|
707 { "client_error", (Tcl_CmdProc*)tcl_client_error }, |
|
708 { "client_compile", (Tcl_CmdProc*)tcl_client_compile }, |
|
709 { "client_step", (Tcl_CmdProc*)tcl_client_step }, |
|
710 { "client_reset", (Tcl_CmdProc*)tcl_client_reset }, |
|
711 { "client_finalize", (Tcl_CmdProc*)tcl_client_finalize }, |
|
712 { "client_swap", (Tcl_CmdProc*)tcl_client_swap }, |
|
713 }; |
|
714 int i; |
|
715 |
|
716 for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){ |
|
717 Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0); |
|
718 } |
|
719 return TCL_OK; |
|
720 } |
|
721 #else |
|
722 int Sqlitetest7_Init(Tcl_Interp *interp){ return TCL_OK; } |
|
723 #endif /* SQLITE_OS_UNIX */ |