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