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