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