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