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