Snapshot of upstream SQLite 3.41.0
[sqlcipher.git] / ext / session / test_session.c
blob242e0fb0fbc42e801199ad23970bb3a3714988fd
2 #if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \
3 && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
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
17 #ifndef SQLITE_AMALGAMATION
18 typedef unsigned char u8;
19 #endif
21 typedef struct TestSession TestSession;
22 struct TestSession {
23 sqlite3_session *pSession;
24 Tcl_Interp *interp;
25 Tcl_Obj *pFilterScript;
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 */
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.
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;
48 *pDb = *(sqlite3 **)info.objClientData;
49 return TCL_OK;
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 */
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.
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.
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 */
77 sqlite3_session *pSession = 0;
78 int rc;
80 /* Create a new session object */
81 rc = sqlite3session_create(db, "main", &pSession);
83 /* Configure the session object to record changes to all tables */
84 if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL);
86 /* Execute the SQL script */
87 if( rc==SQLITE_OK ) rc = sqlite3_exec(db, zSql, 0, 0, 0);
89 /* Collect the changeset */
90 if( rc==SQLITE_OK ){
91 rc = sqlite3session_changeset(pSession, pnChangeset, ppChangeset);
94 /* Delete the session object */
95 sqlite3session_delete(pSession);
97 return rc;
99 /************************************************************************/
102 #ifdef SQLITE_DEBUG
103 static int sqlite3_test_changeset(int, void *, char **);
104 static void assert_changeset_is_ok(int n, void *p){
105 char *z = 0;
106 (void)sqlite3_test_changeset(n, p, &z);
107 assert( z==0 );
109 #else
110 # define assert_changeset_is_ok(n,p)
111 #endif
114 ** Tclcmd: sql_exec_changeset DB SQL
116 static int SQLITE_TCLAPI test_sql_exec_changeset(
117 void * clientData,
118 Tcl_Interp *interp,
119 int objc,
120 Tcl_Obj *CONST objv[]
122 const char *zSql;
123 sqlite3 *db;
124 void *pChangeset;
125 int nChangeset;
126 int rc;
128 if( objc!=3 ){
129 Tcl_WrongNumArgs(interp, 1, objv, "DB SQL");
130 return TCL_ERROR;
132 if( dbHandleFromObj(interp, objv[1], &db) ) return TCL_ERROR;
133 zSql = (const char*)Tcl_GetString(objv[2]);
135 rc = sql_exec_changeset(db, zSql, &nChangeset, &pChangeset);
136 if( rc!=SQLITE_OK ){
137 Tcl_ResetResult(interp);
138 Tcl_AppendResult(interp, "error in sql_exec_changeset()", 0);
139 return TCL_ERROR;
142 assert_changeset_is_ok(nChangeset, pChangeset);
143 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChangeset, nChangeset));
144 sqlite3_free(pChangeset);
145 return TCL_OK;
150 #define SESSION_STREAM_TCL_VAR "sqlite3session_streams"
153 ** Attempt to find the global variable zVar within interpreter interp
154 ** and extract an integer value from it. Return this value.
156 ** If the named variable cannot be found, or if it cannot be interpreted
157 ** as a integer, return 0.
159 static int test_tcl_integer(Tcl_Interp *interp, const char *zVar){
160 Tcl_Obj *pObj;
161 int iVal = 0;
162 Tcl_Obj *pName = Tcl_NewStringObj(zVar, -1);
163 Tcl_IncrRefCount(pName);
164 pObj = Tcl_ObjGetVar2(interp, pName, 0, TCL_GLOBAL_ONLY);
165 Tcl_DecrRefCount(pName);
166 if( pObj ) Tcl_GetIntFromObj(0, pObj, &iVal);
167 return iVal;
170 static int test_session_error(Tcl_Interp *interp, int rc, char *zErr){
171 extern const char *sqlite3ErrName(int);
172 Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
173 if( zErr ){
174 Tcl_AppendResult(interp, " - ", zErr, 0);
175 sqlite3_free(zErr);
177 return TCL_ERROR;
180 static int test_table_filter(void *pCtx, const char *zTbl){
181 TestSession *p = (TestSession*)pCtx;
182 Tcl_Obj *pEval;
183 int rc;
184 int bRes = 0;
186 pEval = Tcl_DuplicateObj(p->pFilterScript);
187 Tcl_IncrRefCount(pEval);
188 rc = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zTbl, -1));
189 if( rc==TCL_OK ){
190 rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
192 if( rc==TCL_OK ){
193 rc = Tcl_GetBooleanFromObj(p->interp, Tcl_GetObjResult(p->interp), &bRes);
195 if( rc!=TCL_OK ){
196 /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */
197 Tcl_BackgroundError(p->interp);
199 Tcl_DecrRefCount(pEval);
201 return bRes;
204 struct TestSessionsBlob {
205 void *p;
206 int n;
208 typedef struct TestSessionsBlob TestSessionsBlob;
210 static int testStreamOutput(
211 void *pCtx,
212 const void *pData,
213 int nData
215 TestSessionsBlob *pBlob = (TestSessionsBlob*)pCtx;
216 char *pNew;
218 assert( nData>0 );
219 pNew = (char*)sqlite3_realloc(pBlob->p, pBlob->n + nData);
220 if( pNew==0 ){
221 return SQLITE_NOMEM;
223 pBlob->p = (void*)pNew;
224 memcpy(&pNew[pBlob->n], pData, nData);
225 pBlob->n += nData;
226 return SQLITE_OK;
230 ** Tclcmd: $session attach TABLE
231 ** $session changeset
232 ** $session delete
233 ** $session enable BOOL
234 ** $session indirect INTEGER
235 ** $session patchset
236 ** $session table_filter SCRIPT
238 static int SQLITE_TCLAPI test_session_cmd(
239 void *clientData,
240 Tcl_Interp *interp,
241 int objc,
242 Tcl_Obj *CONST objv[]
244 TestSession *p = (TestSession*)clientData;
245 sqlite3_session *pSession = p->pSession;
246 static struct SessionSubcmd {
247 const char *zSub;
248 int nArg;
249 const char *zMsg;
250 int iSub;
251 } aSub[] = {
252 { "attach", 1, "TABLE", }, /* 0 */
253 { "changeset", 0, "", }, /* 1 */
254 { "delete", 0, "", }, /* 2 */
255 { "enable", 1, "BOOL", }, /* 3 */
256 { "indirect", 1, "BOOL", }, /* 4 */
257 { "isempty", 0, "", }, /* 5 */
258 { "table_filter", 1, "SCRIPT", }, /* 6 */
259 { "patchset", 0, "", }, /* 7 */
260 { "diff", 2, "FROMDB TBL", }, /* 8 */
261 { "memory_used", 0, "", }, /* 9 */
262 { "changeset_size", 0, "", }, /* 10 */
263 { "object_config_size", 1, "INTEGER", }, /* 11 */
264 { 0 }
266 int iSub;
267 int rc;
269 if( objc<2 ){
270 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
271 return TCL_ERROR;
273 rc = Tcl_GetIndexFromObjStruct(interp,
274 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
276 if( rc!=TCL_OK ) return rc;
277 if( objc!=2+aSub[iSub].nArg ){
278 Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
279 return TCL_ERROR;
282 switch( iSub ){
283 case 0: { /* attach */
284 char *zArg = Tcl_GetString(objv[2]);
285 if( zArg[0]=='*' && zArg[1]=='\0' ) zArg = 0;
286 rc = sqlite3session_attach(pSession, zArg);
287 if( rc!=SQLITE_OK ){
288 return test_session_error(interp, rc, 0);
290 break;
293 case 7: /* patchset */
294 case 1: { /* changeset */
295 TestSessionsBlob o = {0, 0};
296 if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){
297 void *pCtx = (void*)&o;
298 if( iSub==7 ){
299 rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx);
300 }else{
301 rc = sqlite3session_changeset_strm(pSession, testStreamOutput, pCtx);
303 }else{
304 if( iSub==7 ){
305 rc = sqlite3session_patchset(pSession, &o.n, &o.p);
306 }else{
307 rc = sqlite3session_changeset(pSession, &o.n, &o.p);
310 if( rc==SQLITE_OK ){
311 assert_changeset_is_ok(o.n, o.p);
312 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n));
314 sqlite3_free(o.p);
315 if( rc!=SQLITE_OK ){
316 return test_session_error(interp, rc, 0);
318 break;
321 case 2: /* delete */
322 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
323 break;
325 case 3: { /* enable */
326 int val;
327 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
328 val = sqlite3session_enable(pSession, val);
329 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
330 break;
333 case 4: { /* indirect */
334 int val;
335 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
336 val = sqlite3session_indirect(pSession, val);
337 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
338 break;
341 case 5: { /* isempty */
342 int val;
343 val = sqlite3session_isempty(pSession);
344 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
345 break;
348 case 6: { /* table_filter */
349 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
350 p->interp = interp;
351 p->pFilterScript = Tcl_DuplicateObj(objv[2]);
352 Tcl_IncrRefCount(p->pFilterScript);
353 sqlite3session_table_filter(pSession, test_table_filter, clientData);
354 break;
357 case 8: { /* diff */
358 char *zErr = 0;
359 rc = sqlite3session_diff(pSession,
360 Tcl_GetString(objv[2]),
361 Tcl_GetString(objv[3]),
362 &zErr
364 assert( rc!=SQLITE_OK || zErr==0 );
365 if( rc ){
366 return test_session_error(interp, rc, zErr);
368 break;
371 case 9: { /* memory_used */
372 sqlite3_int64 nMalloc = sqlite3session_memory_used(pSession);
373 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nMalloc));
374 break;
377 case 10: {
378 sqlite3_int64 nSize = sqlite3session_changeset_size(pSession);
379 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nSize));
380 break;
382 case 11: {
383 int rc;
384 int iArg;
385 if( Tcl_GetIntFromObj(interp, objv[2], &iArg) ){
386 return TCL_ERROR;
388 rc = sqlite3session_object_config(
389 pSession, SQLITE_SESSION_OBJCONFIG_SIZE, &iArg
391 if( rc!=SQLITE_OK ){
392 extern const char *sqlite3ErrName(int);
393 Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
394 }else{
395 Tcl_SetObjResult(interp, Tcl_NewIntObj(iArg));
397 break;
401 return TCL_OK;
404 static void SQLITE_TCLAPI test_session_del(void *clientData){
405 TestSession *p = (TestSession*)clientData;
406 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
407 sqlite3session_delete(p->pSession);
408 ckfree((char*)p);
412 ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME
414 static int SQLITE_TCLAPI test_sqlite3session(
415 void * clientData,
416 Tcl_Interp *interp,
417 int objc,
418 Tcl_Obj *CONST objv[]
420 sqlite3 *db;
421 Tcl_CmdInfo info;
422 int rc; /* sqlite3session_create() return code */
423 TestSession *p; /* New wrapper object */
424 int iArg = -1;
426 if( objc!=4 ){
427 Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME");
428 return TCL_ERROR;
431 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
432 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
433 return TCL_ERROR;
435 db = *(sqlite3 **)info.objClientData;
437 p = (TestSession*)ckalloc(sizeof(TestSession));
438 memset(p, 0, sizeof(TestSession));
439 rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &p->pSession);
440 if( rc!=SQLITE_OK ){
441 ckfree((char*)p);
442 return test_session_error(interp, rc, 0);
445 /* Query the SQLITE_SESSION_OBJCONFIG_SIZE option to ensure that it
446 ** is clear by default. Then set it. */
447 sqlite3session_object_config(p->pSession,SQLITE_SESSION_OBJCONFIG_SIZE,&iArg);
448 assert( iArg==0 );
449 iArg = 1;
450 sqlite3session_object_config(p->pSession,SQLITE_SESSION_OBJCONFIG_SIZE,&iArg);
452 Tcl_CreateObjCommand(
453 interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)p,
454 test_session_del
456 Tcl_SetObjResult(interp, objv[1]);
457 return TCL_OK;
460 static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){
461 if( pVal==0 ){
462 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
463 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
464 }else{
465 Tcl_Obj *pObj;
466 switch( sqlite3_value_type(pVal) ){
467 case SQLITE_NULL:
468 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("n", 1));
469 pObj = Tcl_NewObj();
470 break;
471 case SQLITE_INTEGER:
472 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("i", 1));
473 pObj = Tcl_NewWideIntObj(sqlite3_value_int64(pVal));
474 break;
475 case SQLITE_FLOAT:
476 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("f", 1));
477 pObj = Tcl_NewDoubleObj(sqlite3_value_double(pVal));
478 break;
479 case SQLITE_TEXT: {
480 const char *z = (char*)sqlite3_value_blob(pVal);
481 int n = sqlite3_value_bytes(pVal);
482 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("t", 1));
483 pObj = Tcl_NewStringObj(z, n);
484 break;
486 default:
487 assert( sqlite3_value_type(pVal)==SQLITE_BLOB );
488 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("b", 1));
489 pObj = Tcl_NewByteArrayObj(
490 sqlite3_value_blob(pVal),
491 sqlite3_value_bytes(pVal)
493 break;
495 Tcl_ListObjAppendElement(0, pList, pObj);
499 typedef struct TestConflictHandler TestConflictHandler;
500 struct TestConflictHandler {
501 Tcl_Interp *interp;
502 Tcl_Obj *pConflictScript;
503 Tcl_Obj *pFilterScript;
506 static int test_obj_eq_string(Tcl_Obj *p, const char *z){
507 int n;
508 int nObj;
509 char *zObj;
511 n = (int)strlen(z);
512 zObj = Tcl_GetStringFromObj(p, &nObj);
514 return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
517 static int test_filter_handler(
518 void *pCtx, /* Pointer to TestConflictHandler structure */
519 const char *zTab /* Table name */
521 TestConflictHandler *p = (TestConflictHandler *)pCtx;
522 int res = 1;
523 Tcl_Obj *pEval;
524 Tcl_Interp *interp = p->interp;
526 pEval = Tcl_DuplicateObj(p->pFilterScript);
527 Tcl_IncrRefCount(pEval);
529 if( TCL_OK!=Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1))
530 || TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL)
531 || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res)
533 Tcl_BackgroundError(interp);
536 Tcl_DecrRefCount(pEval);
537 return res;
540 static int test_conflict_handler(
541 void *pCtx, /* Pointer to TestConflictHandler structure */
542 int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
543 sqlite3_changeset_iter *pIter /* Handle describing change and conflict */
545 TestConflictHandler *p = (TestConflictHandler *)pCtx;
546 Tcl_Obj *pEval;
547 Tcl_Interp *interp = p->interp;
548 int ret = 0; /* Return value */
550 int op; /* SQLITE_UPDATE, DELETE or INSERT */
551 const char *zTab; /* Name of table conflict is on */
552 int nCol; /* Number of columns in table zTab */
554 pEval = Tcl_DuplicateObj(p->pConflictScript);
555 Tcl_IncrRefCount(pEval);
557 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
559 if( eConf==SQLITE_CHANGESET_FOREIGN_KEY ){
560 int nFk;
561 sqlite3changeset_fk_conflicts(pIter, &nFk);
562 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj("FOREIGN_KEY", -1));
563 Tcl_ListObjAppendElement(0, pEval, Tcl_NewIntObj(nFk));
564 }else{
566 /* Append the operation type. */
567 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
568 op==SQLITE_INSERT ? "INSERT" :
569 op==SQLITE_UPDATE ? "UPDATE" :
570 "DELETE", -1
573 /* Append the table name. */
574 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1));
576 /* Append the conflict type. */
577 switch( eConf ){
578 case SQLITE_CHANGESET_DATA:
579 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1));
580 break;
581 case SQLITE_CHANGESET_NOTFOUND:
582 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1));
583 break;
584 case SQLITE_CHANGESET_CONFLICT:
585 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1));
586 break;
587 case SQLITE_CHANGESET_CONSTRAINT:
588 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1));
589 break;
592 /* If this is not an INSERT, append the old row */
593 if( op!=SQLITE_INSERT ){
594 int i;
595 Tcl_Obj *pOld = Tcl_NewObj();
596 for(i=0; i<nCol; i++){
597 sqlite3_value *pVal;
598 sqlite3changeset_old(pIter, i, &pVal);
599 test_append_value(pOld, pVal);
601 Tcl_ListObjAppendElement(0, pEval, pOld);
604 /* If this is not a DELETE, append the new row */
605 if( op!=SQLITE_DELETE ){
606 int i;
607 Tcl_Obj *pNew = Tcl_NewObj();
608 for(i=0; i<nCol; i++){
609 sqlite3_value *pVal;
610 sqlite3changeset_new(pIter, i, &pVal);
611 test_append_value(pNew, pVal);
613 Tcl_ListObjAppendElement(0, pEval, pNew);
616 /* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append
617 ** the conflicting row. */
618 if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){
619 int i;
620 Tcl_Obj *pConflict = Tcl_NewObj();
621 for(i=0; i<nCol; i++){
622 int rc;
623 sqlite3_value *pVal;
624 rc = sqlite3changeset_conflict(pIter, i, &pVal);
625 assert( rc==SQLITE_OK );
626 test_append_value(pConflict, pVal);
628 Tcl_ListObjAppendElement(0, pEval, pConflict);
631 /***********************************************************************
632 ** This block is purely for testing some error conditions.
634 if( eConf==SQLITE_CHANGESET_CONSTRAINT
635 || eConf==SQLITE_CHANGESET_NOTFOUND
637 sqlite3_value *pVal;
638 int rc = sqlite3changeset_conflict(pIter, 0, &pVal);
639 assert( rc==SQLITE_MISUSE );
640 }else{
641 sqlite3_value *pVal;
642 int rc = sqlite3changeset_conflict(pIter, -1, &pVal);
643 assert( rc==SQLITE_RANGE );
644 rc = sqlite3changeset_conflict(pIter, nCol, &pVal);
645 assert( rc==SQLITE_RANGE );
647 if( op==SQLITE_DELETE ){
648 sqlite3_value *pVal;
649 int rc = sqlite3changeset_new(pIter, 0, &pVal);
650 assert( rc==SQLITE_MISUSE );
651 }else{
652 sqlite3_value *pVal;
653 int rc = sqlite3changeset_new(pIter, -1, &pVal);
654 assert( rc==SQLITE_RANGE );
655 rc = sqlite3changeset_new(pIter, nCol, &pVal);
656 assert( rc==SQLITE_RANGE );
658 if( op==SQLITE_INSERT ){
659 sqlite3_value *pVal;
660 int rc = sqlite3changeset_old(pIter, 0, &pVal);
661 assert( rc==SQLITE_MISUSE );
662 }else{
663 sqlite3_value *pVal;
664 int rc = sqlite3changeset_old(pIter, -1, &pVal);
665 assert( rc==SQLITE_RANGE );
666 rc = sqlite3changeset_old(pIter, nCol, &pVal);
667 assert( rc==SQLITE_RANGE );
669 if( eConf!=SQLITE_CHANGESET_FOREIGN_KEY ){
670 /* eConf!=FOREIGN_KEY is always true at this point. The condition is
671 ** just there to make it clearer what is being tested. */
672 int nDummy;
673 int rc = sqlite3changeset_fk_conflicts(pIter, &nDummy);
674 assert( rc==SQLITE_MISUSE );
676 /* End of testing block
677 ***********************************************************************/
680 if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){
681 Tcl_BackgroundError(interp);
682 }else{
683 Tcl_Obj *pRes = Tcl_GetObjResult(interp);
684 if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){
685 ret = SQLITE_CHANGESET_OMIT;
686 }else if( test_obj_eq_string(pRes, "REPLACE") ){
687 ret = SQLITE_CHANGESET_REPLACE;
688 }else if( test_obj_eq_string(pRes, "ABORT") ){
689 ret = SQLITE_CHANGESET_ABORT;
690 }else{
691 Tcl_GetIntFromObj(0, pRes, &ret);
695 Tcl_DecrRefCount(pEval);
696 return ret;
700 ** The conflict handler used by sqlite3changeset_apply_replace_all().
701 ** This conflict handler calls sqlite3_value_text16() on all available
702 ** sqlite3_value objects and then returns CHANGESET_REPLACE, or
703 ** CHANGESET_OMIT if REPLACE is not applicable. This is used to test the
704 ** effect of a malloc failure within an sqlite3_value_xxx() function
705 ** invoked by a conflict-handler callback.
707 static int replace_handler(
708 void *pCtx, /* Pointer to TestConflictHandler structure */
709 int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
710 sqlite3_changeset_iter *pIter /* Handle describing change and conflict */
712 int op; /* SQLITE_UPDATE, DELETE or INSERT */
713 const char *zTab; /* Name of table conflict is on */
714 int nCol; /* Number of columns in table zTab */
715 int i;
716 int x = 0;
718 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
720 if( op!=SQLITE_INSERT ){
721 for(i=0; i<nCol; i++){
722 sqlite3_value *pVal;
723 sqlite3changeset_old(pIter, i, &pVal);
724 sqlite3_value_text16(pVal);
725 x++;
729 if( op!=SQLITE_DELETE ){
730 for(i=0; i<nCol; i++){
731 sqlite3_value *pVal;
732 sqlite3changeset_new(pIter, i, &pVal);
733 sqlite3_value_text16(pVal);
734 x++;
738 if( eConf==SQLITE_CHANGESET_DATA ){
739 return SQLITE_CHANGESET_REPLACE;
741 return SQLITE_CHANGESET_OMIT;
744 static int testStreamInput(
745 void *pCtx, /* Context pointer */
746 void *pData, /* Buffer to populate */
747 int *pnData /* IN/OUT: Bytes requested/supplied */
749 TestStreamInput *p = (TestStreamInput*)pCtx;
750 int nReq = *pnData; /* Bytes of data requested */
751 int nRem = p->nData - p->iData; /* Bytes of data available */
752 int nRet = p->nStream; /* Bytes actually returned */
754 /* Allocate and free some space. There is no point to this, other than
755 ** that it allows the regular OOM fault-injection tests to cause an error
756 ** in this function. */
757 void *pAlloc = sqlite3_malloc(10);
758 if( pAlloc==0 ) return SQLITE_NOMEM;
759 sqlite3_free(pAlloc);
761 if( nRet>nReq ) nRet = nReq;
762 if( nRet>nRem ) nRet = nRem;
764 assert( nRet>=0 );
765 if( nRet>0 ){
766 memcpy(pData, &p->aData[p->iData], nRet);
767 p->iData += nRet;
770 *pnData = nRet;
771 return SQLITE_OK;
775 static int SQLITE_TCLAPI testSqlite3changesetApply(
776 int bV2,
777 void * clientData,
778 Tcl_Interp *interp,
779 int objc,
780 Tcl_Obj *CONST objv[]
782 sqlite3 *db; /* Database handle */
783 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */
784 int rc; /* Return code from changeset_invert() */
785 void *pChangeset; /* Buffer containing changeset */
786 int nChangeset; /* Size of buffer aChangeset in bytes */
787 TestConflictHandler ctx;
788 TestStreamInput sStr;
789 void *pRebase = 0;
790 int nRebase = 0;
791 int flags = 0; /* Flags for apply_v2() */
793 memset(&sStr, 0, sizeof(sStr));
794 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
796 /* Check for the -nosavepoint flag */
797 if( bV2 ){
798 if( objc>1 ){
799 const char *z1 = Tcl_GetString(objv[1]);
800 int n = strlen(z1);
801 if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){
802 flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT;
803 objc--;
804 objv++;
807 if( objc>1 ){
808 const char *z1 = Tcl_GetString(objv[1]);
809 int n = strlen(z1);
810 if( n>1 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){
811 flags |= SQLITE_CHANGESETAPPLY_INVERT;
812 objc--;
813 objv++;
818 if( objc!=4 && objc!=5 ){
819 const char *zMsg;
820 if( bV2 ){
821 zMsg = "?-nosavepoint? ?-inverse? "
822 "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
823 }else{
824 zMsg = "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
826 Tcl_WrongNumArgs(interp, 1, objv, zMsg);
827 return TCL_ERROR;
829 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
830 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[1]), 0);
831 return TCL_ERROR;
833 db = *(sqlite3 **)info.objClientData;
834 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
835 ctx.pConflictScript = objv[3];
836 ctx.pFilterScript = objc==5 ? objv[4] : 0;
837 ctx.interp = interp;
839 if( sStr.nStream==0 ){
840 if( bV2==0 ){
841 rc = sqlite3changeset_apply(db, nChangeset, pChangeset,
842 (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx
844 }else{
845 rc = sqlite3changeset_apply_v2(db, nChangeset, pChangeset,
846 (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx,
847 &pRebase, &nRebase, flags
850 }else{
851 sStr.aData = (unsigned char*)pChangeset;
852 sStr.nData = nChangeset;
853 if( bV2==0 ){
854 rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
855 (objc==5) ? test_filter_handler : 0,
856 test_conflict_handler, (void *)&ctx
858 }else{
859 rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr,
860 (objc==5) ? test_filter_handler : 0,
861 test_conflict_handler, (void *)&ctx,
862 &pRebase, &nRebase, flags
867 if( rc!=SQLITE_OK ){
868 return test_session_error(interp, rc, 0);
869 }else{
870 Tcl_ResetResult(interp);
871 if( bV2 && pRebase ){
872 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase));
875 sqlite3_free(pRebase);
876 return TCL_OK;
880 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
882 static int SQLITE_TCLAPI test_sqlite3changeset_apply(
883 void * clientData,
884 Tcl_Interp *interp,
885 int objc,
886 Tcl_Obj *CONST objv[]
888 return testSqlite3changesetApply(0, clientData, interp, objc, objv);
891 ** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
893 static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2(
894 void * clientData,
895 Tcl_Interp *interp,
896 int objc,
897 Tcl_Obj *CONST objv[]
899 return testSqlite3changesetApply(1, clientData, interp, objc, objv);
903 ** sqlite3changeset_apply_replace_all DB CHANGESET
905 static int SQLITE_TCLAPI test_sqlite3changeset_apply_replace_all(
906 void * clientData,
907 Tcl_Interp *interp,
908 int objc,
909 Tcl_Obj *CONST objv[]
911 sqlite3 *db; /* Database handle */
912 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */
913 int rc; /* Return code from changeset_invert() */
914 void *pChangeset; /* Buffer containing changeset */
915 int nChangeset; /* Size of buffer aChangeset in bytes */
917 if( objc!=3 ){
918 Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET");
919 return TCL_ERROR;
921 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
922 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
923 return TCL_ERROR;
925 db = *(sqlite3 **)info.objClientData;
926 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
928 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0);
929 if( rc!=SQLITE_OK ){
930 return test_session_error(interp, rc, 0);
932 Tcl_ResetResult(interp);
933 return TCL_OK;
938 ** sqlite3changeset_invert CHANGESET
940 static int SQLITE_TCLAPI test_sqlite3changeset_invert(
941 void * clientData,
942 Tcl_Interp *interp,
943 int objc,
944 Tcl_Obj *CONST objv[]
946 int rc; /* Return code from changeset_invert() */
947 TestStreamInput sIn; /* Input stream */
948 TestSessionsBlob sOut; /* Output blob */
950 if( objc!=2 ){
951 Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET");
952 return TCL_ERROR;
955 memset(&sIn, 0, sizeof(sIn));
956 memset(&sOut, 0, sizeof(sOut));
957 sIn.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
958 sIn.aData = Tcl_GetByteArrayFromObj(objv[1], &sIn.nData);
960 if( sIn.nStream ){
961 rc = sqlite3changeset_invert_strm(
962 testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut
964 }else{
965 rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p);
967 if( rc!=SQLITE_OK ){
968 rc = test_session_error(interp, rc, 0);
969 }else{
970 assert_changeset_is_ok(sOut.n, sOut.p);
971 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
973 sqlite3_free(sOut.p);
974 return rc;
978 ** sqlite3changeset_concat LEFT RIGHT
980 static int SQLITE_TCLAPI test_sqlite3changeset_concat(
981 void * clientData,
982 Tcl_Interp *interp,
983 int objc,
984 Tcl_Obj *CONST objv[]
986 int rc; /* Return code from changeset_invert() */
988 TestStreamInput sLeft; /* Input stream */
989 TestStreamInput sRight; /* Input stream */
990 TestSessionsBlob sOut = {0,0}; /* Output blob */
992 if( objc!=3 ){
993 Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT");
994 return TCL_ERROR;
997 memset(&sLeft, 0, sizeof(sLeft));
998 memset(&sRight, 0, sizeof(sRight));
999 sLeft.aData = Tcl_GetByteArrayFromObj(objv[1], &sLeft.nData);
1000 sRight.aData = Tcl_GetByteArrayFromObj(objv[2], &sRight.nData);
1001 sLeft.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
1002 sRight.nStream = sLeft.nStream;
1004 if( sLeft.nStream>0 ){
1005 rc = sqlite3changeset_concat_strm(
1006 testStreamInput, (void*)&sLeft,
1007 testStreamInput, (void*)&sRight,
1008 testStreamOutput, (void*)&sOut
1010 }else{
1011 rc = sqlite3changeset_concat(
1012 sLeft.nData, sLeft.aData, sRight.nData, sRight.aData, &sOut.n, &sOut.p
1016 if( rc!=SQLITE_OK ){
1017 rc = test_session_error(interp, rc, 0);
1018 }else{
1019 assert_changeset_is_ok(sOut.n, sOut.p);
1020 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
1022 sqlite3_free(sOut.p);
1023 return rc;
1027 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT
1029 static int SQLITE_TCLAPI test_sqlite3session_foreach(
1030 void * clientData,
1031 Tcl_Interp *interp,
1032 int objc,
1033 Tcl_Obj *CONST objv[]
1035 void *pChangeset;
1036 int nChangeset;
1037 sqlite3_changeset_iter *pIter;
1038 int rc;
1039 Tcl_Obj *pVarname;
1040 Tcl_Obj *pCS;
1041 Tcl_Obj *pScript;
1042 int isCheckNext = 0;
1043 int isInvert = 0;
1045 TestStreamInput sStr;
1046 memset(&sStr, 0, sizeof(sStr));
1048 while( objc>1 ){
1049 char *zOpt = Tcl_GetString(objv[1]);
1050 int nOpt = strlen(zOpt);
1051 if( zOpt[0]!='-' ) break;
1052 if( nOpt<=7 && 0==sqlite3_strnicmp(zOpt, "-invert", nOpt) ){
1053 isInvert = 1;
1054 }else
1055 if( nOpt<=5 && 0==sqlite3_strnicmp(zOpt, "-next", nOpt) ){
1056 isCheckNext = 1;
1057 }else{
1058 break;
1060 objv++;
1061 objc--;
1063 if( objc!=4 ){
1064 Tcl_WrongNumArgs(
1065 interp, 1, objv, "?-next? ?-invert? VARNAME CHANGESET SCRIPT");
1066 return TCL_ERROR;
1069 pVarname = objv[1];
1070 pCS = objv[2];
1071 pScript = objv[3];
1073 pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset);
1074 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
1075 if( isInvert ){
1076 int f = SQLITE_CHANGESETSTART_INVERT;
1077 if( sStr.nStream==0 ){
1078 rc = sqlite3changeset_start_v2(&pIter, nChangeset, pChangeset, f);
1079 }else{
1080 void *pCtx = (void*)&sStr;
1081 sStr.aData = (unsigned char*)pChangeset;
1082 sStr.nData = nChangeset;
1083 rc = sqlite3changeset_start_v2_strm(&pIter, testStreamInput, pCtx, f);
1085 }else{
1086 if( sStr.nStream==0 ){
1087 rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
1088 }else{
1089 sStr.aData = (unsigned char*)pChangeset;
1090 sStr.nData = nChangeset;
1091 rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr);
1094 if( rc!=SQLITE_OK ){
1095 return test_session_error(interp, rc, 0);
1098 while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
1099 int nCol; /* Number of columns in table */
1100 int nCol2; /* Number of columns in table */
1101 int op; /* SQLITE_INSERT, UPDATE or DELETE */
1102 const char *zTab; /* Name of table change applies to */
1103 Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */
1104 Tcl_Obj *pOld; /* Vector of old.* values */
1105 Tcl_Obj *pNew; /* Vector of new.* values */
1106 int bIndirect;
1108 char *zPK;
1109 unsigned char *abPK;
1110 int i;
1112 /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
1113 ** iterator. */
1114 int nDummy;
1115 if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){
1116 sqlite3changeset_finalize(pIter);
1117 return TCL_ERROR;
1120 sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
1121 pVar = Tcl_NewObj();
1122 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
1123 op==SQLITE_INSERT ? "INSERT" :
1124 op==SQLITE_UPDATE ? "UPDATE" :
1125 "DELETE", -1
1128 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
1129 Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
1131 zPK = ckalloc(nCol+1);
1132 memset(zPK, 0, nCol+1);
1133 sqlite3changeset_pk(pIter, &abPK, &nCol2);
1134 assert( nCol==nCol2 );
1135 for(i=0; i<nCol; i++){
1136 zPK[i] = (abPK[i] ? 'X' : '.');
1138 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
1139 ckfree(zPK);
1141 pOld = Tcl_NewObj();
1142 if( op!=SQLITE_INSERT ){
1143 for(i=0; i<nCol; i++){
1144 sqlite3_value *pVal;
1145 sqlite3changeset_old(pIter, i, &pVal);
1146 test_append_value(pOld, pVal);
1149 pNew = Tcl_NewObj();
1150 if( op!=SQLITE_DELETE ){
1151 for(i=0; i<nCol; i++){
1152 sqlite3_value *pVal;
1153 sqlite3changeset_new(pIter, i, &pVal);
1154 test_append_value(pNew, pVal);
1157 Tcl_ListObjAppendElement(0, pVar, pOld);
1158 Tcl_ListObjAppendElement(0, pVar, pNew);
1160 Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
1161 rc = Tcl_EvalObjEx(interp, pScript, 0);
1162 if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
1163 sqlite3changeset_finalize(pIter);
1164 return rc==TCL_BREAK ? TCL_OK : rc;
1168 if( isCheckNext ){
1169 int rc2 = sqlite3changeset_next(pIter);
1170 rc = sqlite3changeset_finalize(pIter);
1171 assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
1172 }else{
1173 rc = sqlite3changeset_finalize(pIter);
1175 if( rc!=SQLITE_OK ){
1176 return test_session_error(interp, rc, 0);
1179 return TCL_OK;
1183 ** tclcmd: CMD configure REBASE-BLOB
1184 ** tclcmd: CMD rebase CHANGESET
1185 ** tclcmd: CMD delete
1187 static int SQLITE_TCLAPI test_rebaser_cmd(
1188 void * clientData,
1189 Tcl_Interp *interp,
1190 int objc,
1191 Tcl_Obj *CONST objv[]
1193 static struct RebaseSubcmd {
1194 const char *zSub;
1195 int nArg;
1196 const char *zMsg;
1197 int iSub;
1198 } aSub[] = {
1199 { "configure", 1, "REBASE-BLOB" }, /* 0 */
1200 { "delete", 0, "" }, /* 1 */
1201 { "rebase", 1, "CHANGESET" }, /* 2 */
1202 { 0 }
1205 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData;
1206 int iSub;
1207 int rc;
1209 if( objc<2 ){
1210 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
1211 return TCL_ERROR;
1213 rc = Tcl_GetIndexFromObjStruct(interp,
1214 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
1216 if( rc!=TCL_OK ) return rc;
1217 if( objc!=2+aSub[iSub].nArg ){
1218 Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
1219 return TCL_ERROR;
1222 assert( iSub==0 || iSub==1 || iSub==2 );
1223 assert( rc==SQLITE_OK );
1224 switch( iSub ){
1225 case 0: { /* configure */
1226 int nRebase = 0;
1227 unsigned char *pRebase = Tcl_GetByteArrayFromObj(objv[2], &nRebase);
1228 rc = sqlite3rebaser_configure(p, nRebase, pRebase);
1229 break;
1232 case 1: /* delete */
1233 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
1234 break;
1236 default: { /* rebase */
1237 TestStreamInput sStr; /* Input stream */
1238 TestSessionsBlob sOut; /* Output blob */
1240 memset(&sStr, 0, sizeof(sStr));
1241 memset(&sOut, 0, sizeof(sOut));
1242 sStr.aData = Tcl_GetByteArrayFromObj(objv[2], &sStr.nData);
1243 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
1245 if( sStr.nStream ){
1246 rc = sqlite3rebaser_rebase_strm(p,
1247 testStreamInput, (void*)&sStr,
1248 testStreamOutput, (void*)&sOut
1250 }else{
1251 rc = sqlite3rebaser_rebase(p, sStr.nData, sStr.aData, &sOut.n, &sOut.p);
1254 if( rc==SQLITE_OK ){
1255 assert_changeset_is_ok(sOut.n, sOut.p);
1256 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(sOut.p, sOut.n));
1258 sqlite3_free(sOut.p);
1259 break;
1263 if( rc!=SQLITE_OK ){
1264 return test_session_error(interp, rc, 0);
1266 return TCL_OK;
1269 static void SQLITE_TCLAPI test_rebaser_del(void *clientData){
1270 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData;
1271 sqlite3rebaser_delete(p);
1275 ** tclcmd: sqlite3rebaser_create NAME
1277 static int SQLITE_TCLAPI test_sqlite3rebaser_create(
1278 void * clientData,
1279 Tcl_Interp *interp,
1280 int objc,
1281 Tcl_Obj *CONST objv[]
1283 int rc;
1284 sqlite3_rebaser *pNew = 0;
1285 if( objc!=2 ){
1286 Tcl_WrongNumArgs(interp, 1, objv, "NAME");
1287 return SQLITE_ERROR;
1290 rc = sqlite3rebaser_create(&pNew);
1291 if( rc!=SQLITE_OK ){
1292 return test_session_error(interp, rc, 0);
1295 Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]), test_rebaser_cmd,
1296 (ClientData)pNew, test_rebaser_del
1298 Tcl_SetObjResult(interp, objv[1]);
1299 return TCL_OK;
1303 ** Run some sanity checks on the changeset in nChangeset byte buffer
1304 ** pChangeset. If any fail, return a non-zero value and, optionally,
1305 ** set output variable (*pzErr) to point to a buffer containing an
1306 ** English language error message describing the problem. In this
1307 ** case it is the responsibility of the caller to free the buffer
1308 ** using sqlite3_free().
1310 ** Or, if the changeset appears to be well-formed, this function
1311 ** returns SQLITE_OK and sets (*pzErr) to NULL.
1313 static int sqlite3_test_changeset(
1314 int nChangeset,
1315 void *pChangeset,
1316 char **pzErr
1318 sqlite3_changeset_iter *pIter = 0;
1319 char *zErr = 0;
1320 int rc = SQLITE_OK;
1321 int bPatch = (nChangeset>0 && ((char*)pChangeset)[0]=='P');
1323 rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
1324 if( rc==SQLITE_OK ){
1325 int rc2;
1326 while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){
1327 unsigned char *aPk = 0;
1328 int nCol = 0;
1329 int op = 0;
1330 const char *zTab = 0;
1332 sqlite3changeset_pk(pIter, &aPk, &nCol);
1333 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
1335 if( op==SQLITE_UPDATE ){
1336 int iCol;
1337 for(iCol=0; iCol<nCol; iCol++){
1338 sqlite3_value *pNew = 0;
1339 sqlite3_value *pOld = 0;
1340 sqlite3changeset_new(pIter, iCol, &pNew);
1341 sqlite3changeset_old(pIter, iCol, &pOld);
1343 if( aPk[iCol] ){
1344 if( pOld==0 ) rc = SQLITE_ERROR;
1345 }else if( bPatch ){
1346 if( pOld ) rc = SQLITE_ERROR;
1347 }else{
1348 if( (pOld==0)!=(pNew==0) ) rc = SQLITE_ERROR;
1351 if( rc!=SQLITE_OK ){
1352 zErr = sqlite3_mprintf(
1353 "unexpected SQLITE_UPDATE (bPatch=%d pk=%d pOld=%d pNew=%d)",
1354 bPatch, (int)aPk[iCol], pOld!=0, pNew!=0
1356 break;
1361 rc2 = sqlite3changeset_finalize(pIter);
1362 if( rc==SQLITE_OK ){
1363 rc = rc2;
1367 *pzErr = zErr;
1368 return rc;
1372 ** test_changeset CHANGESET
1374 static int SQLITE_TCLAPI test_changeset(
1375 void * clientData,
1376 Tcl_Interp *interp,
1377 int objc,
1378 Tcl_Obj *CONST objv[]
1380 void *pChangeset = 0; /* Buffer containing changeset */
1381 int nChangeset = 0; /* Size of buffer aChangeset in bytes */
1382 int rc = SQLITE_OK;
1383 char *z = 0;
1385 if( objc!=2 ){
1386 Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET");
1387 return TCL_ERROR;
1389 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[1], &nChangeset);
1391 Tcl_ResetResult(interp);
1392 rc = sqlite3_test_changeset(nChangeset, pChangeset, &z);
1393 if( rc!=SQLITE_OK ){
1394 char *zErr = sqlite3_mprintf("(%d) - \"%s\"", rc, z);
1395 Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
1396 sqlite3_free(zErr);
1398 sqlite3_free(z);
1400 return rc ? TCL_ERROR : TCL_OK;
1404 ** tclcmd: sqlite3rebaser_configure OP VALUE
1406 static int SQLITE_TCLAPI test_sqlite3session_config(
1407 void * clientData,
1408 Tcl_Interp *interp,
1409 int objc,
1410 Tcl_Obj *CONST objv[]
1412 static struct ConfigOpt {
1413 const char *zSub;
1414 int op;
1415 } aSub[] = {
1416 { "strm_size", SQLITE_SESSION_CONFIG_STRMSIZE },
1417 { "invalid", 0 },
1418 { 0 }
1420 int rc;
1421 int iSub;
1422 int iVal;
1424 if( objc!=3 ){
1425 Tcl_WrongNumArgs(interp, 1, objv, "OP VALUE");
1426 return SQLITE_ERROR;
1428 rc = Tcl_GetIndexFromObjStruct(interp,
1429 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
1431 if( rc!=TCL_OK ) return rc;
1432 if( Tcl_GetIntFromObj(interp, objv[2], &iVal) ) return TCL_ERROR;
1434 rc = sqlite3session_config(aSub[iSub].op, (void*)&iVal);
1435 if( rc!=SQLITE_OK ){
1436 return test_session_error(interp, rc, 0);
1438 Tcl_SetObjResult(interp, Tcl_NewIntObj(iVal));
1439 return TCL_OK;
1442 int TestSession_Init(Tcl_Interp *interp){
1443 struct Cmd {
1444 const char *zCmd;
1445 Tcl_ObjCmdProc *xProc;
1446 } aCmd[] = {
1447 { "sqlite3session", test_sqlite3session },
1448 { "sqlite3session_foreach", test_sqlite3session_foreach },
1449 { "sqlite3changeset_invert", test_sqlite3changeset_invert },
1450 { "sqlite3changeset_concat", test_sqlite3changeset_concat },
1451 { "sqlite3changeset_apply", test_sqlite3changeset_apply },
1452 { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 },
1453 { "sqlite3changeset_apply_replace_all",
1454 test_sqlite3changeset_apply_replace_all },
1455 { "sql_exec_changeset", test_sql_exec_changeset },
1456 { "sqlite3rebaser_create", test_sqlite3rebaser_create },
1457 { "sqlite3session_config", test_sqlite3session_config },
1458 { "test_changeset", test_changeset },
1460 int i;
1462 for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){
1463 struct Cmd *p = &aCmd[i];
1464 Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0);
1467 return TCL_OK;
1470 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */