OLD | NEW |
| (Empty) |
1 /* | |
2 ** 2007 September 9 | |
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 ** | |
13 ** This file contains the implementation of some Tcl commands used to | |
14 ** test that sqlite3 database handles may be concurrently accessed by | |
15 ** multiple threads. Right now this only works on unix. | |
16 */ | |
17 | |
18 #include "sqliteInt.h" | |
19 #include <tcl.h> | |
20 | |
21 #if SQLITE_THREADSAFE | |
22 | |
23 #include <errno.h> | |
24 | |
25 #if !defined(_MSC_VER) | |
26 #include <unistd.h> | |
27 #endif | |
28 | |
29 /* | |
30 ** One of these is allocated for each thread created by [sqlthread spawn]. | |
31 */ | |
32 typedef struct SqlThread SqlThread; | |
33 struct SqlThread { | |
34 Tcl_ThreadId parent; /* Thread id of parent thread */ | |
35 Tcl_Interp *interp; /* Parent interpreter */ | |
36 char *zScript; /* The script to execute. */ | |
37 char *zVarname; /* Varname in parent script */ | |
38 }; | |
39 | |
40 /* | |
41 ** A custom Tcl_Event type used by this module. When the event is | |
42 ** handled, script zScript is evaluated in interpreter interp. If | |
43 ** the evaluation throws an exception (returns TCL_ERROR), then the | |
44 ** error is handled by Tcl_BackgroundError(). If no error occurs, | |
45 ** the result is simply discarded. | |
46 */ | |
47 typedef struct EvalEvent EvalEvent; | |
48 struct EvalEvent { | |
49 Tcl_Event base; /* Base class of type Tcl_Event */ | |
50 char *zScript; /* The script to execute. */ | |
51 Tcl_Interp *interp; /* The interpreter to execute it in. */ | |
52 }; | |
53 | |
54 static Tcl_ObjCmdProc sqlthread_proc; | |
55 static Tcl_ObjCmdProc clock_seconds_proc; | |
56 #if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) | |
57 static Tcl_ObjCmdProc blocking_step_proc; | |
58 static Tcl_ObjCmdProc blocking_prepare_v2_proc; | |
59 #endif | |
60 int Sqlitetest1_Init(Tcl_Interp *); | |
61 int Sqlite3_Init(Tcl_Interp *); | |
62 | |
63 /* Functions from main.c */ | |
64 extern const char *sqlite3ErrName(int); | |
65 | |
66 /* Functions from test1.c */ | |
67 extern void *sqlite3TestTextToPtr(const char *); | |
68 extern int getDbPointer(Tcl_Interp *, const char *, sqlite3 **); | |
69 extern int sqlite3TestMakePointerStr(Tcl_Interp *, char *, void *); | |
70 extern int sqlite3TestErrCode(Tcl_Interp *, sqlite3 *, int); | |
71 | |
72 /* | |
73 ** Handler for events of type EvalEvent. | |
74 */ | |
75 static int tclScriptEvent(Tcl_Event *evPtr, int flags){ | |
76 int rc; | |
77 EvalEvent *p = (EvalEvent *)evPtr; | |
78 rc = Tcl_Eval(p->interp, p->zScript); | |
79 if( rc!=TCL_OK ){ | |
80 Tcl_BackgroundError(p->interp); | |
81 } | |
82 UNUSED_PARAMETER(flags); | |
83 return 1; | |
84 } | |
85 | |
86 /* | |
87 ** Register an EvalEvent to evaluate the script pScript in the | |
88 ** parent interpreter/thread of SqlThread p. | |
89 */ | |
90 static void postToParent(SqlThread *p, Tcl_Obj *pScript){ | |
91 EvalEvent *pEvent; | |
92 char *zMsg; | |
93 int nMsg; | |
94 | |
95 zMsg = Tcl_GetStringFromObj(pScript, &nMsg); | |
96 pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1); | |
97 pEvent->base.nextPtr = 0; | |
98 pEvent->base.proc = tclScriptEvent; | |
99 pEvent->zScript = (char *)&pEvent[1]; | |
100 memcpy(pEvent->zScript, zMsg, nMsg+1); | |
101 pEvent->interp = p->interp; | |
102 | |
103 Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL); | |
104 Tcl_ThreadAlert(p->parent); | |
105 } | |
106 | |
107 /* | |
108 ** The main function for threads created with [sqlthread spawn]. | |
109 */ | |
110 static Tcl_ThreadCreateType tclScriptThread(ClientData pSqlThread){ | |
111 Tcl_Interp *interp; | |
112 Tcl_Obj *pRes; | |
113 Tcl_Obj *pList; | |
114 int rc; | |
115 SqlThread *p = (SqlThread *)pSqlThread; | |
116 extern int Sqlitetest_mutex_Init(Tcl_Interp*); | |
117 | |
118 interp = Tcl_CreateInterp(); | |
119 Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0); | |
120 Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, pSqlThread, 0); | |
121 #if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) | |
122 Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0); | |
123 Tcl_CreateObjCommand(interp, | |
124 "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0); | |
125 Tcl_CreateObjCommand(interp, | |
126 "sqlite3_nonblocking_prepare_v2", blocking_prepare_v2_proc, 0, 0); | |
127 #endif | |
128 Sqlitetest1_Init(interp); | |
129 Sqlitetest_mutex_Init(interp); | |
130 Sqlite3_Init(interp); | |
131 | |
132 rc = Tcl_Eval(interp, p->zScript); | |
133 pRes = Tcl_GetObjResult(interp); | |
134 pList = Tcl_NewObj(); | |
135 Tcl_IncrRefCount(pList); | |
136 Tcl_IncrRefCount(pRes); | |
137 | |
138 if( rc!=TCL_OK ){ | |
139 Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("error", -1)); | |
140 Tcl_ListObjAppendElement(interp, pList, pRes); | |
141 postToParent(p, pList); | |
142 Tcl_DecrRefCount(pList); | |
143 pList = Tcl_NewObj(); | |
144 } | |
145 | |
146 Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("set", -1)); | |
147 Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj(p->zVarname, -1)); | |
148 Tcl_ListObjAppendElement(interp, pList, pRes); | |
149 postToParent(p, pList); | |
150 | |
151 ckfree((void *)p); | |
152 Tcl_DecrRefCount(pList); | |
153 Tcl_DecrRefCount(pRes); | |
154 Tcl_DeleteInterp(interp); | |
155 while( Tcl_DoOneEvent(TCL_ALL_EVENTS|TCL_DONT_WAIT) ); | |
156 Tcl_ExitThread(0); | |
157 TCL_THREAD_CREATE_RETURN; | |
158 } | |
159 | |
160 /* | |
161 ** sqlthread spawn VARNAME SCRIPT | |
162 ** | |
163 ** Spawn a new thread with its own Tcl interpreter and run the | |
164 ** specified SCRIPT(s) in it. The thread terminates after running | |
165 ** the script. The result of the script is stored in the variable | |
166 ** VARNAME. | |
167 ** | |
168 ** The caller can wait for the script to terminate using [vwait VARNAME]. | |
169 */ | |
170 static int sqlthread_spawn( | |
171 ClientData clientData, | |
172 Tcl_Interp *interp, | |
173 int objc, | |
174 Tcl_Obj *CONST objv[] | |
175 ){ | |
176 Tcl_ThreadId x; | |
177 SqlThread *pNew; | |
178 int rc; | |
179 | |
180 int nVarname; char *zVarname; | |
181 int nScript; char *zScript; | |
182 | |
183 /* Parameters for thread creation */ | |
184 const int nStack = TCL_THREAD_STACK_DEFAULT; | |
185 const int flags = TCL_THREAD_NOFLAGS; | |
186 | |
187 assert(objc==4); | |
188 UNUSED_PARAMETER(clientData); | |
189 UNUSED_PARAMETER(objc); | |
190 | |
191 zVarname = Tcl_GetStringFromObj(objv[2], &nVarname); | |
192 zScript = Tcl_GetStringFromObj(objv[3], &nScript); | |
193 | |
194 pNew = (SqlThread *)ckalloc(sizeof(SqlThread)+nVarname+nScript+2); | |
195 pNew->zVarname = (char *)&pNew[1]; | |
196 pNew->zScript = (char *)&pNew->zVarname[nVarname+1]; | |
197 memcpy(pNew->zVarname, zVarname, nVarname+1); | |
198 memcpy(pNew->zScript, zScript, nScript+1); | |
199 pNew->parent = Tcl_GetCurrentThread(); | |
200 pNew->interp = interp; | |
201 | |
202 rc = Tcl_CreateThread(&x, tclScriptThread, (void *)pNew, nStack, flags); | |
203 if( rc!=TCL_OK ){ | |
204 Tcl_AppendResult(interp, "Error in Tcl_CreateThread()", 0); | |
205 ckfree((char *)pNew); | |
206 return TCL_ERROR; | |
207 } | |
208 | |
209 return TCL_OK; | |
210 } | |
211 | |
212 /* | |
213 ** sqlthread parent SCRIPT | |
214 ** | |
215 ** This can be called by spawned threads only. It sends the specified | |
216 ** script back to the parent thread for execution. The result of | |
217 ** evaluating the SCRIPT is returned. The parent thread must enter | |
218 ** the event loop for this to work - otherwise the caller will | |
219 ** block indefinitely. | |
220 ** | |
221 ** NOTE: At the moment, this doesn't work. FIXME. | |
222 */ | |
223 static int sqlthread_parent( | |
224 ClientData clientData, | |
225 Tcl_Interp *interp, | |
226 int objc, | |
227 Tcl_Obj *CONST objv[] | |
228 ){ | |
229 EvalEvent *pEvent; | |
230 char *zMsg; | |
231 int nMsg; | |
232 SqlThread *p = (SqlThread *)clientData; | |
233 | |
234 assert(objc==3); | |
235 UNUSED_PARAMETER(objc); | |
236 | |
237 if( p==0 ){ | |
238 Tcl_AppendResult(interp, "no parent thread", 0); | |
239 return TCL_ERROR; | |
240 } | |
241 | |
242 zMsg = Tcl_GetStringFromObj(objv[2], &nMsg); | |
243 pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1); | |
244 pEvent->base.nextPtr = 0; | |
245 pEvent->base.proc = tclScriptEvent; | |
246 pEvent->zScript = (char *)&pEvent[1]; | |
247 memcpy(pEvent->zScript, zMsg, nMsg+1); | |
248 pEvent->interp = p->interp; | |
249 Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL); | |
250 Tcl_ThreadAlert(p->parent); | |
251 | |
252 return TCL_OK; | |
253 } | |
254 | |
255 static int xBusy(void *pArg, int nBusy){ | |
256 UNUSED_PARAMETER(pArg); | |
257 UNUSED_PARAMETER(nBusy); | |
258 sqlite3_sleep(50); | |
259 return 1; /* Try again... */ | |
260 } | |
261 | |
262 /* | |
263 ** sqlthread open | |
264 ** | |
265 ** Open a database handle and return the string representation of | |
266 ** the pointer value. | |
267 */ | |
268 static int sqlthread_open( | |
269 ClientData clientData, | |
270 Tcl_Interp *interp, | |
271 int objc, | |
272 Tcl_Obj *CONST objv[] | |
273 ){ | |
274 int sqlite3TestMakePointerStr(Tcl_Interp *interp, char *zPtr, void *p); | |
275 | |
276 const char *zFilename; | |
277 sqlite3 *db; | |
278 char zBuf[100]; | |
279 extern void Md5_Register(sqlite3*); | |
280 | |
281 UNUSED_PARAMETER(clientData); | |
282 UNUSED_PARAMETER(objc); | |
283 | |
284 zFilename = Tcl_GetString(objv[2]); | |
285 sqlite3_open(zFilename, &db); | |
286 #ifdef SQLITE_HAS_CODEC | |
287 if( db && objc>=4 ){ | |
288 const char *zKey; | |
289 int nKey; | |
290 int rc; | |
291 zKey = Tcl_GetStringFromObj(objv[3], &nKey); | |
292 rc = sqlite3_key(db, zKey, nKey); | |
293 if( rc!=SQLITE_OK ){ | |
294 char *zErrMsg = sqlite3_mprintf("error %d: %s", rc, sqlite3_errmsg(db)); | |
295 sqlite3_close(db); | |
296 Tcl_AppendResult(interp, zErrMsg, (char*)0); | |
297 sqlite3_free(zErrMsg); | |
298 return TCL_ERROR; | |
299 } | |
300 } | |
301 #endif | |
302 Md5_Register(db); | |
303 sqlite3_busy_handler(db, xBusy, 0); | |
304 | |
305 if( sqlite3TestMakePointerStr(interp, zBuf, db) ) return TCL_ERROR; | |
306 Tcl_AppendResult(interp, zBuf, 0); | |
307 | |
308 return TCL_OK; | |
309 } | |
310 | |
311 | |
312 /* | |
313 ** sqlthread open | |
314 ** | |
315 ** Return the current thread-id (Tcl_GetCurrentThread()) cast to | |
316 ** an integer. | |
317 */ | |
318 static int sqlthread_id( | |
319 ClientData clientData, | |
320 Tcl_Interp *interp, | |
321 int objc, | |
322 Tcl_Obj *CONST objv[] | |
323 ){ | |
324 Tcl_ThreadId id = Tcl_GetCurrentThread(); | |
325 Tcl_SetObjResult(interp, Tcl_NewIntObj(SQLITE_PTR_TO_INT(id))); | |
326 UNUSED_PARAMETER(clientData); | |
327 UNUSED_PARAMETER(objc); | |
328 UNUSED_PARAMETER(objv); | |
329 return TCL_OK; | |
330 } | |
331 | |
332 | |
333 /* | |
334 ** Dispatch routine for the sub-commands of [sqlthread]. | |
335 */ | |
336 static int sqlthread_proc( | |
337 ClientData clientData, | |
338 Tcl_Interp *interp, | |
339 int objc, | |
340 Tcl_Obj *CONST objv[] | |
341 ){ | |
342 struct SubCommand { | |
343 char *zName; | |
344 Tcl_ObjCmdProc *xProc; | |
345 int nArg; | |
346 char *zUsage; | |
347 } aSub[] = { | |
348 {"parent", sqlthread_parent, 1, "SCRIPT"}, | |
349 {"spawn", sqlthread_spawn, 2, "VARNAME SCRIPT"}, | |
350 {"open", sqlthread_open, 1, "DBNAME"}, | |
351 {"id", sqlthread_id, 0, ""}, | |
352 {0, 0, 0} | |
353 }; | |
354 struct SubCommand *pSub; | |
355 int rc; | |
356 int iIndex; | |
357 | |
358 if( objc<2 ){ | |
359 Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND"); | |
360 return TCL_ERROR; | |
361 } | |
362 | |
363 rc = Tcl_GetIndexFromObjStruct( | |
364 interp, objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iIndex | |
365 ); | |
366 if( rc!=TCL_OK ) return rc; | |
367 pSub = &aSub[iIndex]; | |
368 | |
369 if( objc<(pSub->nArg+2) ){ | |
370 Tcl_WrongNumArgs(interp, 2, objv, pSub->zUsage); | |
371 return TCL_ERROR; | |
372 } | |
373 | |
374 return pSub->xProc(clientData, interp, objc, objv); | |
375 } | |
376 | |
377 /* | |
378 ** The [clock_seconds] command. This is more or less the same as the | |
379 ** regular tcl [clock seconds], except that it is available in testfixture | |
380 ** when linked against both Tcl 8.4 and 8.5. Because [clock seconds] is | |
381 ** implemented as a script in Tcl 8.5, it is not usually available to | |
382 ** testfixture. | |
383 */ | |
384 static int clock_seconds_proc( | |
385 ClientData clientData, | |
386 Tcl_Interp *interp, | |
387 int objc, | |
388 Tcl_Obj *CONST objv[] | |
389 ){ | |
390 Tcl_Time now; | |
391 Tcl_GetTime(&now); | |
392 Tcl_SetObjResult(interp, Tcl_NewIntObj(now.sec)); | |
393 UNUSED_PARAMETER(clientData); | |
394 UNUSED_PARAMETER(objc); | |
395 UNUSED_PARAMETER(objv); | |
396 return TCL_OK; | |
397 } | |
398 | |
399 /************************************************************************* | |
400 ** This block contains the implementation of the [sqlite3_blocking_step] | |
401 ** command available to threads created by [sqlthread spawn] commands. It | |
402 ** is only available on UNIX for now. This is because pthread condition | |
403 ** variables are used. | |
404 ** | |
405 ** The source code for the C functions sqlite3_blocking_step(), | |
406 ** blocking_step_notify() and the structure UnlockNotification is | |
407 ** automatically extracted from this file and used as part of the | |
408 ** documentation for the sqlite3_unlock_notify() API function. This | |
409 ** should be considered if these functions are to be extended (i.e. to | |
410 ** support windows) in the future. | |
411 */ | |
412 #if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) | |
413 | |
414 /* BEGIN_SQLITE_BLOCKING_STEP */ | |
415 /* This example uses the pthreads API */ | |
416 #include <pthread.h> | |
417 | |
418 /* | |
419 ** A pointer to an instance of this structure is passed as the user-context | |
420 ** pointer when registering for an unlock-notify callback. | |
421 */ | |
422 typedef struct UnlockNotification UnlockNotification; | |
423 struct UnlockNotification { | |
424 int fired; /* True after unlock event has occurred */ | |
425 pthread_cond_t cond; /* Condition variable to wait on */ | |
426 pthread_mutex_t mutex; /* Mutex to protect structure */ | |
427 }; | |
428 | |
429 /* | |
430 ** This function is an unlock-notify callback registered with SQLite. | |
431 */ | |
432 static void unlock_notify_cb(void **apArg, int nArg){ | |
433 int i; | |
434 for(i=0; i<nArg; i++){ | |
435 UnlockNotification *p = (UnlockNotification *)apArg[i]; | |
436 pthread_mutex_lock(&p->mutex); | |
437 p->fired = 1; | |
438 pthread_cond_signal(&p->cond); | |
439 pthread_mutex_unlock(&p->mutex); | |
440 } | |
441 } | |
442 | |
443 /* | |
444 ** This function assumes that an SQLite API call (either sqlite3_prepare_v2() | |
445 ** or sqlite3_step()) has just returned SQLITE_LOCKED. The argument is the | |
446 ** associated database connection. | |
447 ** | |
448 ** This function calls sqlite3_unlock_notify() to register for an | |
449 ** unlock-notify callback, then blocks until that callback is delivered | |
450 ** and returns SQLITE_OK. The caller should then retry the failed operation. | |
451 ** | |
452 ** Or, if sqlite3_unlock_notify() indicates that to block would deadlock | |
453 ** the system, then this function returns SQLITE_LOCKED immediately. In | |
454 ** this case the caller should not retry the operation and should roll | |
455 ** back the current transaction (if any). | |
456 */ | |
457 static int wait_for_unlock_notify(sqlite3 *db){ | |
458 int rc; | |
459 UnlockNotification un; | |
460 | |
461 /* Initialize the UnlockNotification structure. */ | |
462 un.fired = 0; | |
463 pthread_mutex_init(&un.mutex, 0); | |
464 pthread_cond_init(&un.cond, 0); | |
465 | |
466 /* Register for an unlock-notify callback. */ | |
467 rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un); | |
468 assert( rc==SQLITE_LOCKED || rc==SQLITE_OK ); | |
469 | |
470 /* The call to sqlite3_unlock_notify() always returns either SQLITE_LOCKED | |
471 ** or SQLITE_OK. | |
472 ** | |
473 ** If SQLITE_LOCKED was returned, then the system is deadlocked. In this | |
474 ** case this function needs to return SQLITE_LOCKED to the caller so | |
475 ** that the current transaction can be rolled back. Otherwise, block | |
476 ** until the unlock-notify callback is invoked, then return SQLITE_OK. | |
477 */ | |
478 if( rc==SQLITE_OK ){ | |
479 pthread_mutex_lock(&un.mutex); | |
480 if( !un.fired ){ | |
481 pthread_cond_wait(&un.cond, &un.mutex); | |
482 } | |
483 pthread_mutex_unlock(&un.mutex); | |
484 } | |
485 | |
486 /* Destroy the mutex and condition variables. */ | |
487 pthread_cond_destroy(&un.cond); | |
488 pthread_mutex_destroy(&un.mutex); | |
489 | |
490 return rc; | |
491 } | |
492 | |
493 /* | |
494 ** This function is a wrapper around the SQLite function sqlite3_step(). | |
495 ** It functions in the same way as step(), except that if a required | |
496 ** shared-cache lock cannot be obtained, this function may block waiting for | |
497 ** the lock to become available. In this scenario the normal API step() | |
498 ** function always returns SQLITE_LOCKED. | |
499 ** | |
500 ** If this function returns SQLITE_LOCKED, the caller should rollback | |
501 ** the current transaction (if any) and try again later. Otherwise, the | |
502 ** system may become deadlocked. | |
503 */ | |
504 int sqlite3_blocking_step(sqlite3_stmt *pStmt){ | |
505 int rc; | |
506 while( SQLITE_LOCKED==(rc = sqlite3_step(pStmt)) ){ | |
507 rc = wait_for_unlock_notify(sqlite3_db_handle(pStmt)); | |
508 if( rc!=SQLITE_OK ) break; | |
509 sqlite3_reset(pStmt); | |
510 } | |
511 return rc; | |
512 } | |
513 | |
514 /* | |
515 ** This function is a wrapper around the SQLite function sqlite3_prepare_v2(). | |
516 ** It functions in the same way as prepare_v2(), except that if a required | |
517 ** shared-cache lock cannot be obtained, this function may block waiting for | |
518 ** the lock to become available. In this scenario the normal API prepare_v2() | |
519 ** function always returns SQLITE_LOCKED. | |
520 ** | |
521 ** If this function returns SQLITE_LOCKED, the caller should rollback | |
522 ** the current transaction (if any) and try again later. Otherwise, the | |
523 ** system may become deadlocked. | |
524 */ | |
525 int sqlite3_blocking_prepare_v2( | |
526 sqlite3 *db, /* Database handle. */ | |
527 const char *zSql, /* UTF-8 encoded SQL statement. */ | |
528 int nSql, /* Length of zSql in bytes. */ | |
529 sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ | |
530 const char **pz /* OUT: End of parsed string */ | |
531 ){ | |
532 int rc; | |
533 while( SQLITE_LOCKED==(rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, pz)) ){ | |
534 rc = wait_for_unlock_notify(db); | |
535 if( rc!=SQLITE_OK ) break; | |
536 } | |
537 return rc; | |
538 } | |
539 /* END_SQLITE_BLOCKING_STEP */ | |
540 | |
541 /* | |
542 ** Usage: sqlite3_blocking_step STMT | |
543 ** | |
544 ** Advance the statement to the next row. | |
545 */ | |
546 static int blocking_step_proc( | |
547 void * clientData, | |
548 Tcl_Interp *interp, | |
549 int objc, | |
550 Tcl_Obj *CONST objv[] | |
551 ){ | |
552 | |
553 sqlite3_stmt *pStmt; | |
554 int rc; | |
555 | |
556 if( objc!=2 ){ | |
557 Tcl_WrongNumArgs(interp, 1, objv, "STMT"); | |
558 return TCL_ERROR; | |
559 } | |
560 | |
561 pStmt = (sqlite3_stmt*)sqlite3TestTextToPtr(Tcl_GetString(objv[1])); | |
562 rc = sqlite3_blocking_step(pStmt); | |
563 | |
564 Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), 0); | |
565 return TCL_OK; | |
566 } | |
567 | |
568 /* | |
569 ** Usage: sqlite3_blocking_prepare_v2 DB sql bytes ?tailvar? | |
570 ** Usage: sqlite3_nonblocking_prepare_v2 DB sql bytes ?tailvar? | |
571 */ | |
572 static int blocking_prepare_v2_proc( | |
573 void * clientData, | |
574 Tcl_Interp *interp, | |
575 int objc, | |
576 Tcl_Obj *CONST objv[] | |
577 ){ | |
578 sqlite3 *db; | |
579 const char *zSql; | |
580 int bytes; | |
581 const char *zTail = 0; | |
582 sqlite3_stmt *pStmt = 0; | |
583 char zBuf[50]; | |
584 int rc; | |
585 int isBlocking = !(clientData==0); | |
586 | |
587 if( objc!=5 && objc!=4 ){ | |
588 Tcl_AppendResult(interp, "wrong # args: should be \"", | |
589 Tcl_GetString(objv[0]), " DB sql bytes tailvar", 0); | |
590 return TCL_ERROR; | |
591 } | |
592 if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; | |
593 zSql = Tcl_GetString(objv[2]); | |
594 if( Tcl_GetIntFromObj(interp, objv[3], &bytes) ) return TCL_ERROR; | |
595 | |
596 if( isBlocking ){ | |
597 rc = sqlite3_blocking_prepare_v2(db, zSql, bytes, &pStmt, &zTail); | |
598 }else{ | |
599 rc = sqlite3_prepare_v2(db, zSql, bytes, &pStmt, &zTail); | |
600 } | |
601 | |
602 assert(rc==SQLITE_OK || pStmt==0); | |
603 if( zTail && objc>=5 ){ | |
604 if( bytes>=0 ){ | |
605 bytes = bytes - (zTail-zSql); | |
606 } | |
607 Tcl_ObjSetVar2(interp, objv[4], 0, Tcl_NewStringObj(zTail, bytes), 0); | |
608 } | |
609 if( rc!=SQLITE_OK ){ | |
610 assert( pStmt==0 ); | |
611 sprintf(zBuf, "%s ", (char *)sqlite3ErrName(rc)); | |
612 Tcl_AppendResult(interp, zBuf, sqlite3_errmsg(db), 0); | |
613 return TCL_ERROR; | |
614 } | |
615 | |
616 if( pStmt ){ | |
617 if( sqlite3TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR; | |
618 Tcl_AppendResult(interp, zBuf, 0); | |
619 } | |
620 return TCL_OK; | |
621 } | |
622 | |
623 #endif /* SQLITE_OS_UNIX && SQLITE_ENABLE_UNLOCK_NOTIFY */ | |
624 /* | |
625 ** End of implementation of [sqlite3_blocking_step]. | |
626 ************************************************************************/ | |
627 | |
628 /* | |
629 ** Register commands with the TCL interpreter. | |
630 */ | |
631 int SqlitetestThread_Init(Tcl_Interp *interp){ | |
632 Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, 0, 0); | |
633 Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0); | |
634 #if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) | |
635 Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0); | |
636 Tcl_CreateObjCommand(interp, | |
637 "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0); | |
638 Tcl_CreateObjCommand(interp, | |
639 "sqlite3_nonblocking_prepare_v2", blocking_prepare_v2_proc, 0, 0); | |
640 #endif | |
641 return TCL_OK; | |
642 } | |
643 #else | |
644 int SqlitetestThread_Init(Tcl_Interp *interp){ | |
645 return TCL_OK; | |
646 } | |
647 #endif | |
OLD | NEW |