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