1
2 #if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \
3 && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
4
5 #include "sqlite3session.h"
6 #include <assert.h>
7 #include <string.h>
8 #if defined(INCLUDE_SQLITE_TCL_H)
9 # include "sqlite_tcl.h"
10 #else
11 # include "tcl.h"
12 # ifndef SQLITE_TCLAPI
13 # define SQLITE_TCLAPI
14 # endif
15 #endif
16
17 typedef struct TestSession TestSession;
18 struct TestSession {
19 sqlite3_session *pSession;
20 Tcl_Interp *interp;
21 Tcl_Obj *pFilterScript;
22 };
23
24 typedef struct TestStreamInput TestStreamInput;
25 struct TestStreamInput {
26 int nStream; /* Maximum chunk size */
27 unsigned char *aData; /* Pointer to buffer containing data */
28 int nData; /* Size of buffer aData in bytes */
29 int iData; /* Bytes of data already read by sessions */
30 };
31
32 /*
33 ** Extract an sqlite3* db handle from the object passed as the second
34 ** argument. If successful, set *pDb to point to the db handle and return
35 ** TCL_OK. Otherwise, return TCL_ERROR.
36 */
dbHandleFromObj(Tcl_Interp * interp,Tcl_Obj * pObj,sqlite3 ** pDb)37 static int dbHandleFromObj(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){
38 Tcl_CmdInfo info;
39 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){
40 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(pObj), 0);
41 return TCL_ERROR;
42 }
43
44 *pDb = *(sqlite3 **)info.objClientData;
45 return TCL_OK;
46 }
47
48 /*************************************************************************
49 ** The following code is copied byte-for-byte from the sessions module
50 ** documentation. It is used by some of the sessions modules tests to
51 ** ensure that the example in the documentation does actually work.
52 */
53 /*
54 ** Argument zSql points to a buffer containing an SQL script to execute
55 ** against the database handle passed as the first argument. As well as
56 ** executing the SQL script, this function collects a changeset recording
57 ** all changes made to the "main" database file. Assuming no error occurs,
58 ** output variables (*ppChangeset) and (*pnChangeset) are set to point
59 ** to a buffer containing the changeset and the size of the changeset in
60 ** bytes before returning SQLITE_OK. In this case it is the responsibility
61 ** of the caller to eventually free the changeset blob by passing it to
62 ** the sqlite3_free function.
63 **
64 ** Or, if an error does occur, return an SQLite error code. The final
65 ** value of (*pChangeset) and (*pnChangeset) are undefined in this case.
66 */
sql_exec_changeset(sqlite3 * db,const char * zSql,int * pnChangeset,void ** ppChangeset)67 int sql_exec_changeset(
68 sqlite3 *db, /* Database handle */
69 const char *zSql, /* SQL script to execute */
70 int *pnChangeset, /* OUT: Size of changeset blob in bytes */
71 void **ppChangeset /* OUT: Pointer to changeset blob */
72 ){
73 sqlite3_session *pSession = 0;
74 int rc;
75
76 /* Create a new session object */
77 rc = sqlite3session_create(db, "main", &pSession);
78
79 /* Configure the session object to record changes to all tables */
80 if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL);
81
82 /* Execute the SQL script */
83 if( rc==SQLITE_OK ) rc = sqlite3_exec(db, zSql, 0, 0, 0);
84
85 /* Collect the changeset */
86 if( rc==SQLITE_OK ){
87 rc = sqlite3session_changeset(pSession, pnChangeset, ppChangeset);
88 }
89
90 /* Delete the session object */
91 sqlite3session_delete(pSession);
92
93 return rc;
94 }
95 /************************************************************************/
96
97 /*
98 ** Tclcmd: sql_exec_changeset DB SQL
99 */
test_sql_exec_changeset(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])100 static int SQLITE_TCLAPI test_sql_exec_changeset(
101 void * clientData,
102 Tcl_Interp *interp,
103 int objc,
104 Tcl_Obj *CONST objv[]
105 ){
106 const char *zSql;
107 sqlite3 *db;
108 void *pChangeset;
109 int nChangeset;
110 int rc;
111
112 if( objc!=3 ){
113 Tcl_WrongNumArgs(interp, 1, objv, "DB SQL");
114 return TCL_ERROR;
115 }
116 if( dbHandleFromObj(interp, objv[1], &db) ) return TCL_ERROR;
117 zSql = (const char*)Tcl_GetString(objv[2]);
118
119 rc = sql_exec_changeset(db, zSql, &nChangeset, &pChangeset);
120 if( rc!=SQLITE_OK ){
121 Tcl_ResetResult(interp);
122 Tcl_AppendResult(interp, "error in sql_exec_changeset()", 0);
123 return TCL_ERROR;
124 }
125
126 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChangeset, nChangeset));
127 sqlite3_free(pChangeset);
128 return TCL_OK;
129 }
130
131
132
133 #define SESSION_STREAM_TCL_VAR "sqlite3session_streams"
134
135 /*
136 ** Attempt to find the global variable zVar within interpreter interp
137 ** and extract an integer value from it. Return this value.
138 **
139 ** If the named variable cannot be found, or if it cannot be interpreted
140 ** as a integer, return 0.
141 */
test_tcl_integer(Tcl_Interp * interp,const char * zVar)142 static int test_tcl_integer(Tcl_Interp *interp, const char *zVar){
143 Tcl_Obj *pObj;
144 int iVal = 0;
145 pObj = Tcl_ObjGetVar2(interp, Tcl_NewStringObj(zVar, -1), 0, TCL_GLOBAL_ONLY);
146 if( pObj ) Tcl_GetIntFromObj(0, pObj, &iVal);
147 return iVal;
148 }
149
test_session_error(Tcl_Interp * interp,int rc,char * zErr)150 static int test_session_error(Tcl_Interp *interp, int rc, char *zErr){
151 extern const char *sqlite3ErrName(int);
152 Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
153 if( zErr ){
154 Tcl_AppendResult(interp, " - ", zErr, 0);
155 sqlite3_free(zErr);
156 }
157 return TCL_ERROR;
158 }
159
test_table_filter(void * pCtx,const char * zTbl)160 static int test_table_filter(void *pCtx, const char *zTbl){
161 TestSession *p = (TestSession*)pCtx;
162 Tcl_Obj *pEval;
163 int rc;
164 int bRes = 0;
165
166 pEval = Tcl_DuplicateObj(p->pFilterScript);
167 Tcl_IncrRefCount(pEval);
168 rc = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zTbl, -1));
169 if( rc==TCL_OK ){
170 rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
171 }
172 if( rc==TCL_OK ){
173 rc = Tcl_GetBooleanFromObj(p->interp, Tcl_GetObjResult(p->interp), &bRes);
174 }
175 if( rc!=TCL_OK ){
176 /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */
177 Tcl_BackgroundError(p->interp);
178 }
179 Tcl_DecrRefCount(pEval);
180
181 return bRes;
182 }
183
184 struct TestSessionsBlob {
185 void *p;
186 int n;
187 };
188 typedef struct TestSessionsBlob TestSessionsBlob;
189
testStreamOutput(void * pCtx,const void * pData,int nData)190 static int testStreamOutput(
191 void *pCtx,
192 const void *pData,
193 int nData
194 ){
195 TestSessionsBlob *pBlob = (TestSessionsBlob*)pCtx;
196 char *pNew;
197
198 assert( nData>0 );
199 pNew = (char*)sqlite3_realloc(pBlob->p, pBlob->n + nData);
200 if( pNew==0 ){
201 return SQLITE_NOMEM;
202 }
203 pBlob->p = (void*)pNew;
204 memcpy(&pNew[pBlob->n], pData, nData);
205 pBlob->n += nData;
206 return SQLITE_OK;
207 }
208
209 /*
210 ** Tclcmd: $session attach TABLE
211 ** $session changeset
212 ** $session delete
213 ** $session enable BOOL
214 ** $session indirect INTEGER
215 ** $session patchset
216 ** $session table_filter SCRIPT
217 */
test_session_cmd(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])218 static int SQLITE_TCLAPI test_session_cmd(
219 void *clientData,
220 Tcl_Interp *interp,
221 int objc,
222 Tcl_Obj *CONST objv[]
223 ){
224 TestSession *p = (TestSession*)clientData;
225 sqlite3_session *pSession = p->pSession;
226 struct SessionSubcmd {
227 const char *zSub;
228 int nArg;
229 const char *zMsg;
230 int iSub;
231 } aSub[] = {
232 { "attach", 1, "TABLE", }, /* 0 */
233 { "changeset", 0, "", }, /* 1 */
234 { "delete", 0, "", }, /* 2 */
235 { "enable", 1, "BOOL", }, /* 3 */
236 { "indirect", 1, "BOOL", }, /* 4 */
237 { "isempty", 0, "", }, /* 5 */
238 { "table_filter", 1, "SCRIPT", }, /* 6 */
239 { "patchset", 0, "", }, /* 7 */
240 { "diff", 2, "FROMDB TBL", }, /* 8 */
241 { 0 }
242 };
243 int iSub;
244 int rc;
245
246 if( objc<2 ){
247 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
248 return TCL_ERROR;
249 }
250 rc = Tcl_GetIndexFromObjStruct(interp,
251 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
252 );
253 if( rc!=TCL_OK ) return rc;
254 if( objc!=2+aSub[iSub].nArg ){
255 Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
256 return TCL_ERROR;
257 }
258
259 switch( iSub ){
260 case 0: { /* attach */
261 char *zArg = Tcl_GetString(objv[2]);
262 if( zArg[0]=='*' && zArg[1]=='\0' ) zArg = 0;
263 rc = sqlite3session_attach(pSession, zArg);
264 if( rc!=SQLITE_OK ){
265 return test_session_error(interp, rc, 0);
266 }
267 break;
268 }
269
270 case 7: /* patchset */
271 case 1: { /* changeset */
272 TestSessionsBlob o = {0, 0};
273 if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){
274 void *pCtx = (void*)&o;
275 if( iSub==7 ){
276 rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx);
277 }else{
278 rc = sqlite3session_changeset_strm(pSession, testStreamOutput, pCtx);
279 }
280 }else{
281 if( iSub==7 ){
282 rc = sqlite3session_patchset(pSession, &o.n, &o.p);
283 }else{
284 rc = sqlite3session_changeset(pSession, &o.n, &o.p);
285 }
286 }
287 if( rc==SQLITE_OK ){
288 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n));
289 }
290 sqlite3_free(o.p);
291 if( rc!=SQLITE_OK ){
292 return test_session_error(interp, rc, 0);
293 }
294 break;
295 }
296
297 case 2: /* delete */
298 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
299 break;
300
301 case 3: { /* enable */
302 int val;
303 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
304 val = sqlite3session_enable(pSession, val);
305 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
306 break;
307 }
308
309 case 4: { /* indirect */
310 int val;
311 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
312 val = sqlite3session_indirect(pSession, val);
313 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
314 break;
315 }
316
317 case 5: { /* isempty */
318 int val;
319 val = sqlite3session_isempty(pSession);
320 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
321 break;
322 }
323
324 case 6: { /* table_filter */
325 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
326 p->interp = interp;
327 p->pFilterScript = Tcl_DuplicateObj(objv[2]);
328 Tcl_IncrRefCount(p->pFilterScript);
329 sqlite3session_table_filter(pSession, test_table_filter, clientData);
330 break;
331 }
332
333 case 8: { /* diff */
334 char *zErr = 0;
335 rc = sqlite3session_diff(pSession,
336 Tcl_GetString(objv[2]),
337 Tcl_GetString(objv[3]),
338 &zErr
339 );
340 assert( rc!=SQLITE_OK || zErr==0 );
341 if( rc ){
342 return test_session_error(interp, rc, zErr);
343 }
344 break;
345 }
346 }
347
348 return TCL_OK;
349 }
350
test_session_del(void * clientData)351 static void SQLITE_TCLAPI test_session_del(void *clientData){
352 TestSession *p = (TestSession*)clientData;
353 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
354 sqlite3session_delete(p->pSession);
355 ckfree((char*)p);
356 }
357
358 /*
359 ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME
360 */
test_sqlite3session(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])361 static int SQLITE_TCLAPI test_sqlite3session(
362 void * clientData,
363 Tcl_Interp *interp,
364 int objc,
365 Tcl_Obj *CONST objv[]
366 ){
367 sqlite3 *db;
368 Tcl_CmdInfo info;
369 int rc; /* sqlite3session_create() return code */
370 TestSession *p; /* New wrapper object */
371
372 if( objc!=4 ){
373 Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME");
374 return TCL_ERROR;
375 }
376
377 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
378 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
379 return TCL_ERROR;
380 }
381 db = *(sqlite3 **)info.objClientData;
382
383 p = (TestSession*)ckalloc(sizeof(TestSession));
384 memset(p, 0, sizeof(TestSession));
385 rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &p->pSession);
386 if( rc!=SQLITE_OK ){
387 ckfree((char*)p);
388 return test_session_error(interp, rc, 0);
389 }
390
391 Tcl_CreateObjCommand(
392 interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)p,
393 test_session_del
394 );
395 Tcl_SetObjResult(interp, objv[1]);
396 return TCL_OK;
397 }
398
test_append_value(Tcl_Obj * pList,sqlite3_value * pVal)399 static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){
400 if( pVal==0 ){
401 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
402 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
403 }else{
404 Tcl_Obj *pObj;
405 switch( sqlite3_value_type(pVal) ){
406 case SQLITE_NULL:
407 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("n", 1));
408 pObj = Tcl_NewObj();
409 break;
410 case SQLITE_INTEGER:
411 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("i", 1));
412 pObj = Tcl_NewWideIntObj(sqlite3_value_int64(pVal));
413 break;
414 case SQLITE_FLOAT:
415 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("f", 1));
416 pObj = Tcl_NewDoubleObj(sqlite3_value_double(pVal));
417 break;
418 case SQLITE_TEXT: {
419 const char *z = (char*)sqlite3_value_blob(pVal);
420 int n = sqlite3_value_bytes(pVal);
421 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("t", 1));
422 pObj = Tcl_NewStringObj(z, n);
423 break;
424 }
425 default:
426 assert( sqlite3_value_type(pVal)==SQLITE_BLOB );
427 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("b", 1));
428 pObj = Tcl_NewByteArrayObj(
429 sqlite3_value_blob(pVal),
430 sqlite3_value_bytes(pVal)
431 );
432 break;
433 }
434 Tcl_ListObjAppendElement(0, pList, pObj);
435 }
436 }
437
438 typedef struct TestConflictHandler TestConflictHandler;
439 struct TestConflictHandler {
440 Tcl_Interp *interp;
441 Tcl_Obj *pConflictScript;
442 Tcl_Obj *pFilterScript;
443 };
444
test_obj_eq_string(Tcl_Obj * p,const char * z)445 static int test_obj_eq_string(Tcl_Obj *p, const char *z){
446 int n;
447 int nObj;
448 char *zObj;
449
450 n = (int)strlen(z);
451 zObj = Tcl_GetStringFromObj(p, &nObj);
452
453 return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
454 }
455
test_filter_handler(void * pCtx,const char * zTab)456 static int test_filter_handler(
457 void *pCtx, /* Pointer to TestConflictHandler structure */
458 const char *zTab /* Table name */
459 ){
460 TestConflictHandler *p = (TestConflictHandler *)pCtx;
461 int res = 1;
462 Tcl_Obj *pEval;
463 Tcl_Interp *interp = p->interp;
464
465 pEval = Tcl_DuplicateObj(p->pFilterScript);
466 Tcl_IncrRefCount(pEval);
467
468 if( TCL_OK!=Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1))
469 || TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL)
470 || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res)
471 ){
472 Tcl_BackgroundError(interp);
473 }
474
475 Tcl_DecrRefCount(pEval);
476 return res;
477 }
478
test_conflict_handler(void * pCtx,int eConf,sqlite3_changeset_iter * pIter)479 static int test_conflict_handler(
480 void *pCtx, /* Pointer to TestConflictHandler structure */
481 int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
482 sqlite3_changeset_iter *pIter /* Handle describing change and conflict */
483 ){
484 TestConflictHandler *p = (TestConflictHandler *)pCtx;
485 Tcl_Obj *pEval;
486 Tcl_Interp *interp = p->interp;
487 int ret = 0; /* Return value */
488
489 int op; /* SQLITE_UPDATE, DELETE or INSERT */
490 const char *zTab; /* Name of table conflict is on */
491 int nCol; /* Number of columns in table zTab */
492
493 pEval = Tcl_DuplicateObj(p->pConflictScript);
494 Tcl_IncrRefCount(pEval);
495
496 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
497
498 if( eConf==SQLITE_CHANGESET_FOREIGN_KEY ){
499 int nFk;
500 sqlite3changeset_fk_conflicts(pIter, &nFk);
501 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj("FOREIGN_KEY", -1));
502 Tcl_ListObjAppendElement(0, pEval, Tcl_NewIntObj(nFk));
503 }else{
504
505 /* Append the operation type. */
506 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
507 op==SQLITE_INSERT ? "INSERT" :
508 op==SQLITE_UPDATE ? "UPDATE" :
509 "DELETE", -1
510 ));
511
512 /* Append the table name. */
513 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1));
514
515 /* Append the conflict type. */
516 switch( eConf ){
517 case SQLITE_CHANGESET_DATA:
518 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1));
519 break;
520 case SQLITE_CHANGESET_NOTFOUND:
521 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1));
522 break;
523 case SQLITE_CHANGESET_CONFLICT:
524 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1));
525 break;
526 case SQLITE_CHANGESET_CONSTRAINT:
527 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1));
528 break;
529 }
530
531 /* If this is not an INSERT, append the old row */
532 if( op!=SQLITE_INSERT ){
533 int i;
534 Tcl_Obj *pOld = Tcl_NewObj();
535 for(i=0; i<nCol; i++){
536 sqlite3_value *pVal;
537 sqlite3changeset_old(pIter, i, &pVal);
538 test_append_value(pOld, pVal);
539 }
540 Tcl_ListObjAppendElement(0, pEval, pOld);
541 }
542
543 /* If this is not a DELETE, append the new row */
544 if( op!=SQLITE_DELETE ){
545 int i;
546 Tcl_Obj *pNew = Tcl_NewObj();
547 for(i=0; i<nCol; i++){
548 sqlite3_value *pVal;
549 sqlite3changeset_new(pIter, i, &pVal);
550 test_append_value(pNew, pVal);
551 }
552 Tcl_ListObjAppendElement(0, pEval, pNew);
553 }
554
555 /* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append
556 ** the conflicting row. */
557 if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){
558 int i;
559 Tcl_Obj *pConflict = Tcl_NewObj();
560 for(i=0; i<nCol; i++){
561 int rc;
562 sqlite3_value *pVal;
563 rc = sqlite3changeset_conflict(pIter, i, &pVal);
564 assert( rc==SQLITE_OK );
565 test_append_value(pConflict, pVal);
566 }
567 Tcl_ListObjAppendElement(0, pEval, pConflict);
568 }
569
570 /***********************************************************************
571 ** This block is purely for testing some error conditions.
572 */
573 if( eConf==SQLITE_CHANGESET_CONSTRAINT
574 || eConf==SQLITE_CHANGESET_NOTFOUND
575 ){
576 sqlite3_value *pVal;
577 int rc = sqlite3changeset_conflict(pIter, 0, &pVal);
578 assert( rc==SQLITE_MISUSE );
579 }else{
580 sqlite3_value *pVal;
581 int rc = sqlite3changeset_conflict(pIter, -1, &pVal);
582 assert( rc==SQLITE_RANGE );
583 rc = sqlite3changeset_conflict(pIter, nCol, &pVal);
584 assert( rc==SQLITE_RANGE );
585 }
586 if( op==SQLITE_DELETE ){
587 sqlite3_value *pVal;
588 int rc = sqlite3changeset_new(pIter, 0, &pVal);
589 assert( rc==SQLITE_MISUSE );
590 }else{
591 sqlite3_value *pVal;
592 int rc = sqlite3changeset_new(pIter, -1, &pVal);
593 assert( rc==SQLITE_RANGE );
594 rc = sqlite3changeset_new(pIter, nCol, &pVal);
595 assert( rc==SQLITE_RANGE );
596 }
597 if( op==SQLITE_INSERT ){
598 sqlite3_value *pVal;
599 int rc = sqlite3changeset_old(pIter, 0, &pVal);
600 assert( rc==SQLITE_MISUSE );
601 }else{
602 sqlite3_value *pVal;
603 int rc = sqlite3changeset_old(pIter, -1, &pVal);
604 assert( rc==SQLITE_RANGE );
605 rc = sqlite3changeset_old(pIter, nCol, &pVal);
606 assert( rc==SQLITE_RANGE );
607 }
608 if( eConf!=SQLITE_CHANGESET_FOREIGN_KEY ){
609 /* eConf!=FOREIGN_KEY is always true at this point. The condition is
610 ** just there to make it clearer what is being tested. */
611 int nDummy;
612 int rc = sqlite3changeset_fk_conflicts(pIter, &nDummy);
613 assert( rc==SQLITE_MISUSE );
614 }
615 /* End of testing block
616 ***********************************************************************/
617 }
618
619 if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){
620 Tcl_BackgroundError(interp);
621 }else{
622 Tcl_Obj *pRes = Tcl_GetObjResult(interp);
623 if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){
624 ret = SQLITE_CHANGESET_OMIT;
625 }else if( test_obj_eq_string(pRes, "REPLACE") ){
626 ret = SQLITE_CHANGESET_REPLACE;
627 }else if( test_obj_eq_string(pRes, "ABORT") ){
628 ret = SQLITE_CHANGESET_ABORT;
629 }else{
630 Tcl_GetIntFromObj(0, pRes, &ret);
631 }
632 }
633
634 Tcl_DecrRefCount(pEval);
635 return ret;
636 }
637
638 /*
639 ** The conflict handler used by sqlite3changeset_apply_replace_all().
640 ** This conflict handler calls sqlite3_value_text16() on all available
641 ** sqlite3_value objects and then returns CHANGESET_REPLACE, or
642 ** CHANGESET_OMIT if REPLACE is not applicable. This is used to test the
643 ** effect of a malloc failure within an sqlite3_value_xxx() function
644 ** invoked by a conflict-handler callback.
645 */
replace_handler(void * pCtx,int eConf,sqlite3_changeset_iter * pIter)646 static int replace_handler(
647 void *pCtx, /* Pointer to TestConflictHandler structure */
648 int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
649 sqlite3_changeset_iter *pIter /* Handle describing change and conflict */
650 ){
651 int op; /* SQLITE_UPDATE, DELETE or INSERT */
652 const char *zTab; /* Name of table conflict is on */
653 int nCol; /* Number of columns in table zTab */
654 int i;
655 int x = 0;
656
657 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
658
659 if( op!=SQLITE_INSERT ){
660 for(i=0; i<nCol; i++){
661 sqlite3_value *pVal;
662 sqlite3changeset_old(pIter, i, &pVal);
663 sqlite3_value_text16(pVal);
664 x++;
665 }
666 }
667
668 if( op!=SQLITE_DELETE ){
669 for(i=0; i<nCol; i++){
670 sqlite3_value *pVal;
671 sqlite3changeset_new(pIter, i, &pVal);
672 sqlite3_value_text16(pVal);
673 x++;
674 }
675 }
676
677 if( eConf==SQLITE_CHANGESET_DATA ){
678 return SQLITE_CHANGESET_REPLACE;
679 }
680 return SQLITE_CHANGESET_OMIT;
681 }
682
testStreamInput(void * pCtx,void * pData,int * pnData)683 static int testStreamInput(
684 void *pCtx, /* Context pointer */
685 void *pData, /* Buffer to populate */
686 int *pnData /* IN/OUT: Bytes requested/supplied */
687 ){
688 TestStreamInput *p = (TestStreamInput*)pCtx;
689 int nReq = *pnData; /* Bytes of data requested */
690 int nRem = p->nData - p->iData; /* Bytes of data available */
691 int nRet = p->nStream; /* Bytes actually returned */
692
693 /* Allocate and free some space. There is no point to this, other than
694 ** that it allows the regular OOM fault-injection tests to cause an error
695 ** in this function. */
696 void *pAlloc = sqlite3_malloc(10);
697 if( pAlloc==0 ) return SQLITE_NOMEM;
698 sqlite3_free(pAlloc);
699
700 if( nRet>nReq ) nRet = nReq;
701 if( nRet>nRem ) nRet = nRem;
702
703 assert( nRet>=0 );
704 if( nRet>0 ){
705 memcpy(pData, &p->aData[p->iData], nRet);
706 p->iData += nRet;
707 }
708
709 *pnData = nRet;
710 return SQLITE_OK;
711 }
712
713
714 /*
715 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
716 */
test_sqlite3changeset_apply(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])717 static int SQLITE_TCLAPI test_sqlite3changeset_apply(
718 void * clientData,
719 Tcl_Interp *interp,
720 int objc,
721 Tcl_Obj *CONST objv[]
722 ){
723 sqlite3 *db; /* Database handle */
724 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */
725 int rc; /* Return code from changeset_invert() */
726 void *pChangeset; /* Buffer containing changeset */
727 int nChangeset; /* Size of buffer aChangeset in bytes */
728 TestConflictHandler ctx;
729 TestStreamInput sStr;
730
731 memset(&sStr, 0, sizeof(sStr));
732 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
733
734 if( objc!=4 && objc!=5 ){
735 Tcl_WrongNumArgs(interp, 1, objv,
736 "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"
737 );
738 return TCL_ERROR;
739 }
740 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
741 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
742 return TCL_ERROR;
743 }
744 db = *(sqlite3 **)info.objClientData;
745 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
746 ctx.pConflictScript = objv[3];
747 ctx.pFilterScript = objc==5 ? objv[4] : 0;
748 ctx.interp = interp;
749
750 if( sStr.nStream==0 ){
751 rc = sqlite3changeset_apply(db, nChangeset, pChangeset,
752 (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
753 );
754 }else{
755 sStr.aData = (unsigned char*)pChangeset;
756 sStr.nData = nChangeset;
757 rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
758 (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
759 );
760 }
761
762 if( rc!=SQLITE_OK ){
763 return test_session_error(interp, rc, 0);
764 }
765 Tcl_ResetResult(interp);
766 return TCL_OK;
767 }
768
769 /*
770 ** sqlite3changeset_apply_replace_all DB CHANGESET
771 */
test_sqlite3changeset_apply_replace_all(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])772 static int SQLITE_TCLAPI test_sqlite3changeset_apply_replace_all(
773 void * clientData,
774 Tcl_Interp *interp,
775 int objc,
776 Tcl_Obj *CONST objv[]
777 ){
778 sqlite3 *db; /* Database handle */
779 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */
780 int rc; /* Return code from changeset_invert() */
781 void *pChangeset; /* Buffer containing changeset */
782 int nChangeset; /* Size of buffer aChangeset in bytes */
783
784 if( objc!=3 ){
785 Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET");
786 return TCL_ERROR;
787 }
788 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
789 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
790 return TCL_ERROR;
791 }
792 db = *(sqlite3 **)info.objClientData;
793 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
794
795 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0);
796 if( rc!=SQLITE_OK ){
797 return test_session_error(interp, rc, 0);
798 }
799 Tcl_ResetResult(interp);
800 return TCL_OK;
801 }
802
803
804 /*
805 ** sqlite3changeset_invert CHANGESET
806 */
test_sqlite3changeset_invert(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])807 static int SQLITE_TCLAPI test_sqlite3changeset_invert(
808 void * clientData,
809 Tcl_Interp *interp,
810 int objc,
811 Tcl_Obj *CONST objv[]
812 ){
813 int rc; /* Return code from changeset_invert() */
814 TestStreamInput sIn; /* Input stream */
815 TestSessionsBlob sOut; /* Output blob */
816
817 if( objc!=2 ){
818 Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET");
819 return TCL_ERROR;
820 }
821
822 memset(&sIn, 0, sizeof(sIn));
823 memset(&sOut, 0, sizeof(sOut));
824 sIn.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
825 sIn.aData = Tcl_GetByteArrayFromObj(objv[1], &sIn.nData);
826
827 if( sIn.nStream ){
828 rc = sqlite3changeset_invert_strm(
829 testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut
830 );
831 }else{
832 rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p);
833 }
834 if( rc!=SQLITE_OK ){
835 rc = test_session_error(interp, rc, 0);
836 }else{
837 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
838 }
839 sqlite3_free(sOut.p);
840 return rc;
841 }
842
843 /*
844 ** sqlite3changeset_concat LEFT RIGHT
845 */
test_sqlite3changeset_concat(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])846 static int SQLITE_TCLAPI test_sqlite3changeset_concat(
847 void * clientData,
848 Tcl_Interp *interp,
849 int objc,
850 Tcl_Obj *CONST objv[]
851 ){
852 int rc; /* Return code from changeset_invert() */
853
854 TestStreamInput sLeft; /* Input stream */
855 TestStreamInput sRight; /* Input stream */
856 TestSessionsBlob sOut = {0,0}; /* Output blob */
857
858 if( objc!=3 ){
859 Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT");
860 return TCL_ERROR;
861 }
862
863 memset(&sLeft, 0, sizeof(sLeft));
864 memset(&sRight, 0, sizeof(sRight));
865 sLeft.aData = Tcl_GetByteArrayFromObj(objv[1], &sLeft.nData);
866 sRight.aData = Tcl_GetByteArrayFromObj(objv[2], &sRight.nData);
867 sLeft.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
868 sRight.nStream = sLeft.nStream;
869
870 if( sLeft.nStream>0 ){
871 rc = sqlite3changeset_concat_strm(
872 testStreamInput, (void*)&sLeft,
873 testStreamInput, (void*)&sRight,
874 testStreamOutput, (void*)&sOut
875 );
876 }else{
877 rc = sqlite3changeset_concat(
878 sLeft.nData, sLeft.aData, sRight.nData, sRight.aData, &sOut.n, &sOut.p
879 );
880 }
881
882 if( rc!=SQLITE_OK ){
883 rc = test_session_error(interp, rc, 0);
884 }else{
885 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
886 }
887 sqlite3_free(sOut.p);
888 return rc;
889 }
890
891 /*
892 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT
893 */
test_sqlite3session_foreach(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])894 static int SQLITE_TCLAPI test_sqlite3session_foreach(
895 void * clientData,
896 Tcl_Interp *interp,
897 int objc,
898 Tcl_Obj *CONST objv[]
899 ){
900 void *pChangeset;
901 int nChangeset;
902 sqlite3_changeset_iter *pIter;
903 int rc;
904 Tcl_Obj *pVarname;
905 Tcl_Obj *pCS;
906 Tcl_Obj *pScript;
907 int isCheckNext = 0;
908
909 TestStreamInput sStr;
910 memset(&sStr, 0, sizeof(sStr));
911
912 if( objc>1 ){
913 char *zOpt = Tcl_GetString(objv[1]);
914 isCheckNext = (strcmp(zOpt, "-next")==0);
915 }
916 if( objc!=4+isCheckNext ){
917 Tcl_WrongNumArgs(interp, 1, objv, "?-next? VARNAME CHANGESET SCRIPT");
918 return TCL_ERROR;
919 }
920
921 pVarname = objv[1+isCheckNext];
922 pCS = objv[2+isCheckNext];
923 pScript = objv[3+isCheckNext];
924
925 pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset);
926 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
927 if( sStr.nStream==0 ){
928 rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
929 }else{
930 sStr.aData = (unsigned char*)pChangeset;
931 sStr.nData = nChangeset;
932 rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr);
933 }
934 if( rc!=SQLITE_OK ){
935 return test_session_error(interp, rc, 0);
936 }
937
938 while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
939 int nCol; /* Number of columns in table */
940 int nCol2; /* Number of columns in table */
941 int op; /* SQLITE_INSERT, UPDATE or DELETE */
942 const char *zTab; /* Name of table change applies to */
943 Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */
944 Tcl_Obj *pOld; /* Vector of old.* values */
945 Tcl_Obj *pNew; /* Vector of new.* values */
946 int bIndirect;
947
948 char *zPK;
949 unsigned char *abPK;
950 int i;
951
952 /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
953 ** iterator. */
954 int nDummy;
955 if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){
956 sqlite3changeset_finalize(pIter);
957 return TCL_ERROR;
958 }
959
960 sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
961 pVar = Tcl_NewObj();
962 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
963 op==SQLITE_INSERT ? "INSERT" :
964 op==SQLITE_UPDATE ? "UPDATE" :
965 "DELETE", -1
966 ));
967
968 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
969 Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
970
971 zPK = ckalloc(nCol+1);
972 memset(zPK, 0, nCol+1);
973 sqlite3changeset_pk(pIter, &abPK, &nCol2);
974 assert( nCol==nCol2 );
975 for(i=0; i<nCol; i++){
976 zPK[i] = (abPK[i] ? 'X' : '.');
977 }
978 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
979 ckfree(zPK);
980
981 pOld = Tcl_NewObj();
982 if( op!=SQLITE_INSERT ){
983 for(i=0; i<nCol; i++){
984 sqlite3_value *pVal;
985 sqlite3changeset_old(pIter, i, &pVal);
986 test_append_value(pOld, pVal);
987 }
988 }
989 pNew = Tcl_NewObj();
990 if( op!=SQLITE_DELETE ){
991 for(i=0; i<nCol; i++){
992 sqlite3_value *pVal;
993 sqlite3changeset_new(pIter, i, &pVal);
994 test_append_value(pNew, pVal);
995 }
996 }
997 Tcl_ListObjAppendElement(0, pVar, pOld);
998 Tcl_ListObjAppendElement(0, pVar, pNew);
999
1000 Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
1001 rc = Tcl_EvalObjEx(interp, pScript, 0);
1002 if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
1003 sqlite3changeset_finalize(pIter);
1004 return rc==TCL_BREAK ? TCL_OK : rc;
1005 }
1006 }
1007
1008 if( isCheckNext ){
1009 int rc2 = sqlite3changeset_next(pIter);
1010 rc = sqlite3changeset_finalize(pIter);
1011 assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
1012 }else{
1013 rc = sqlite3changeset_finalize(pIter);
1014 }
1015 if( rc!=SQLITE_OK ){
1016 return test_session_error(interp, rc, 0);
1017 }
1018
1019 return TCL_OK;
1020 }
1021
TestSession_Init(Tcl_Interp * interp)1022 int TestSession_Init(Tcl_Interp *interp){
1023 struct Cmd {
1024 const char *zCmd;
1025 Tcl_ObjCmdProc *xProc;
1026 } aCmd[] = {
1027 { "sqlite3session", test_sqlite3session },
1028 { "sqlite3session_foreach", test_sqlite3session_foreach },
1029 { "sqlite3changeset_invert", test_sqlite3changeset_invert },
1030 { "sqlite3changeset_concat", test_sqlite3changeset_concat },
1031 { "sqlite3changeset_apply", test_sqlite3changeset_apply },
1032 { "sqlite3changeset_apply_replace_all",
1033 test_sqlite3changeset_apply_replace_all },
1034 { "sql_exec_changeset", test_sql_exec_changeset },
1035 };
1036 int i;
1037
1038 for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){
1039 struct Cmd *p = &aCmd[i];
1040 Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0);
1041 }
1042
1043 return TCL_OK;
1044 }
1045
1046 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */
1047