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