2 #if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \
3 && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
5 #include "sqlite3session.h"
8 #if defined(INCLUDE_SQLITE_TCL_H)
9 # include "sqlite_tcl.h"
12 # ifndef SQLITE_TCLAPI
13 # define SQLITE_TCLAPI
17 #ifndef SQLITE_AMALGAMATION
18 typedef unsigned char u8
;
21 typedef struct TestSession TestSession
;
23 sqlite3_session
*pSession
;
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
){
43 if( 0==Tcl_GetCommandInfo(interp
, Tcl_GetString(pObj
), &info
) ){
44 Tcl_AppendResult(interp
, "no such handle: ", Tcl_GetString(pObj
), 0);
48 *pDb
= *(sqlite3
**)info
.objClientData
;
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.
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;
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 */
91 rc
= sqlite3session_changeset(pSession
, pnChangeset
, ppChangeset
);
94 /* Delete the session object */
95 sqlite3session_delete(pSession
);
99 /************************************************************************/
102 ** Tclcmd: sql_exec_changeset DB SQL
104 static int SQLITE_TCLAPI
test_sql_exec_changeset(
108 Tcl_Obj
*CONST objv
[]
117 Tcl_WrongNumArgs(interp
, 1, objv
, "DB SQL");
120 if( dbHandleFromObj(interp
, objv
[1], &db
) ) return TCL_ERROR
;
121 zSql
= (const char*)Tcl_GetString(objv
[2]);
123 rc
= sql_exec_changeset(db
, zSql
, &nChangeset
, &pChangeset
);
125 Tcl_ResetResult(interp
);
126 Tcl_AppendResult(interp
, "error in sql_exec_changeset()", 0);
130 Tcl_SetObjResult(interp
, Tcl_NewByteArrayObj(pChangeset
, nChangeset
));
131 sqlite3_free(pChangeset
);
137 #define SESSION_STREAM_TCL_VAR "sqlite3session_streams"
140 ** Attempt to find the global variable zVar within interpreter interp
141 ** and extract an integer value from it. Return this value.
143 ** If the named variable cannot be found, or if it cannot be interpreted
144 ** as a integer, return 0.
146 static int test_tcl_integer(Tcl_Interp
*interp
, const char *zVar
){
149 pObj
= Tcl_ObjGetVar2(interp
, Tcl_NewStringObj(zVar
, -1), 0, TCL_GLOBAL_ONLY
);
150 if( pObj
) Tcl_GetIntFromObj(0, pObj
, &iVal
);
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));
158 Tcl_AppendResult(interp
, " - ", zErr
, 0);
164 static int test_table_filter(void *pCtx
, const char *zTbl
){
165 TestSession
*p
= (TestSession
*)pCtx
;
170 pEval
= Tcl_DuplicateObj(p
->pFilterScript
);
171 Tcl_IncrRefCount(pEval
);
172 rc
= Tcl_ListObjAppendElement(p
->interp
, pEval
, Tcl_NewStringObj(zTbl
, -1));
174 rc
= Tcl_EvalObjEx(p
->interp
, pEval
, TCL_EVAL_GLOBAL
);
177 rc
= Tcl_GetBooleanFromObj(p
->interp
, Tcl_GetObjResult(p
->interp
), &bRes
);
180 /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */
181 Tcl_BackgroundError(p
->interp
);
183 Tcl_DecrRefCount(pEval
);
188 struct TestSessionsBlob
{
192 typedef struct TestSessionsBlob TestSessionsBlob
;
194 static int testStreamOutput(
199 TestSessionsBlob
*pBlob
= (TestSessionsBlob
*)pCtx
;
203 pNew
= (char*)sqlite3_realloc(pBlob
->p
, pBlob
->n
+ nData
);
207 pBlob
->p
= (void*)pNew
;
208 memcpy(&pNew
[pBlob
->n
], pData
, nData
);
214 ** Tclcmd: $session attach TABLE
215 ** $session changeset
217 ** $session enable BOOL
218 ** $session indirect INTEGER
220 ** $session table_filter SCRIPT
222 static int SQLITE_TCLAPI
test_session_cmd(
226 Tcl_Obj
*CONST objv
[]
228 TestSession
*p
= (TestSession
*)clientData
;
229 sqlite3_session
*pSession
= p
->pSession
;
230 struct SessionSubcmd
{
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 */
251 Tcl_WrongNumArgs(interp
, 1, objv
, "SUBCOMMAND ...");
254 rc
= Tcl_GetIndexFromObjStruct(interp
,
255 objv
[1], aSub
, sizeof(aSub
[0]), "sub-command", 0, &iSub
257 if( rc
!=TCL_OK
) return rc
;
258 if( objc
!=2+aSub
[iSub
].nArg
){
259 Tcl_WrongNumArgs(interp
, 2, objv
, aSub
[iSub
].zMsg
);
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
);
269 return test_session_error(interp
, rc
, 0);
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
;
280 rc
= sqlite3session_patchset_strm(pSession
, testStreamOutput
, pCtx
);
282 rc
= sqlite3session_changeset_strm(pSession
, testStreamOutput
, pCtx
);
286 rc
= sqlite3session_patchset(pSession
, &o
.n
, &o
.p
);
288 rc
= sqlite3session_changeset(pSession
, &o
.n
, &o
.p
);
292 Tcl_SetObjResult(interp
, Tcl_NewByteArrayObj(o
.p
, o
.n
));
296 return test_session_error(interp
, rc
, 0);
302 Tcl_DeleteCommand(interp
, Tcl_GetString(objv
[0]));
305 case 3: { /* enable */
307 if( Tcl_GetIntFromObj(interp
, objv
[2], &val
) ) return TCL_ERROR
;
308 val
= sqlite3session_enable(pSession
, val
);
309 Tcl_SetObjResult(interp
, Tcl_NewBooleanObj(val
));
313 case 4: { /* indirect */
315 if( Tcl_GetIntFromObj(interp
, objv
[2], &val
) ) return TCL_ERROR
;
316 val
= sqlite3session_indirect(pSession
, val
);
317 Tcl_SetObjResult(interp
, Tcl_NewBooleanObj(val
));
321 case 5: { /* isempty */
323 val
= sqlite3session_isempty(pSession
);
324 Tcl_SetObjResult(interp
, Tcl_NewBooleanObj(val
));
328 case 6: { /* table_filter */
329 if( p
->pFilterScript
) Tcl_DecrRefCount(p
->pFilterScript
);
331 p
->pFilterScript
= Tcl_DuplicateObj(objv
[2]);
332 Tcl_IncrRefCount(p
->pFilterScript
);
333 sqlite3session_table_filter(pSession
, test_table_filter
, clientData
);
339 rc
= sqlite3session_diff(pSession
,
340 Tcl_GetString(objv
[2]),
341 Tcl_GetString(objv
[3]),
344 assert( rc
!=SQLITE_OK
|| zErr
==0 );
346 return test_session_error(interp
, rc
, zErr
);
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
);
363 ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME
365 static int SQLITE_TCLAPI
test_sqlite3session(
369 Tcl_Obj
*CONST objv
[]
373 int rc
; /* sqlite3session_create() return code */
374 TestSession
*p
; /* New wrapper object */
377 Tcl_WrongNumArgs(interp
, 1, objv
, "CMD DB-HANDLE DB-NAME");
381 if( 0==Tcl_GetCommandInfo(interp
, Tcl_GetString(objv
[2]), &info
) ){
382 Tcl_AppendResult(interp
, "no such handle: ", Tcl_GetString(objv
[2]), 0);
385 db
= *(sqlite3
**)info
.objClientData
;
387 p
= (TestSession
*)ckalloc(sizeof(TestSession
));
388 memset(p
, 0, sizeof(TestSession
));
389 rc
= sqlite3session_create(db
, Tcl_GetString(objv
[3]), &p
->pSession
);
392 return test_session_error(interp
, rc
, 0);
395 Tcl_CreateObjCommand(
396 interp
, Tcl_GetString(objv
[1]), test_session_cmd
, (ClientData
)p
,
399 Tcl_SetObjResult(interp
, objv
[1]);
403 static void test_append_value(Tcl_Obj
*pList
, sqlite3_value
*pVal
){
405 Tcl_ListObjAppendElement(0, pList
, Tcl_NewObj());
406 Tcl_ListObjAppendElement(0, pList
, Tcl_NewObj());
409 switch( sqlite3_value_type(pVal
) ){
411 Tcl_ListObjAppendElement(0, pList
, Tcl_NewStringObj("n", 1));
415 Tcl_ListObjAppendElement(0, pList
, Tcl_NewStringObj("i", 1));
416 pObj
= Tcl_NewWideIntObj(sqlite3_value_int64(pVal
));
419 Tcl_ListObjAppendElement(0, pList
, Tcl_NewStringObj("f", 1));
420 pObj
= Tcl_NewDoubleObj(sqlite3_value_double(pVal
));
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
);
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
)
438 Tcl_ListObjAppendElement(0, pList
, pObj
);
442 typedef struct TestConflictHandler TestConflictHandler
;
443 struct TestConflictHandler
{
445 Tcl_Obj
*pConflictScript
;
446 Tcl_Obj
*pFilterScript
;
449 static int test_obj_eq_string(Tcl_Obj
*p
, const char *z
){
455 zObj
= Tcl_GetStringFromObj(p
, &nObj
);
457 return (nObj
==n
&& (n
==0 || 0==memcmp(zObj
, z
, n
)));
460 static int test_filter_handler(
461 void *pCtx
, /* Pointer to TestConflictHandler structure */
462 const char *zTab
/* Table name */
464 TestConflictHandler
*p
= (TestConflictHandler
*)pCtx
;
467 Tcl_Interp
*interp
= p
->interp
;
469 pEval
= Tcl_DuplicateObj(p
->pFilterScript
);
470 Tcl_IncrRefCount(pEval
);
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
)
476 Tcl_BackgroundError(interp
);
479 Tcl_DecrRefCount(pEval
);
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 */
488 TestConflictHandler
*p
= (TestConflictHandler
*)pCtx
;
490 Tcl_Interp
*interp
= p
->interp
;
491 int ret
= 0; /* Return value */
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 */
497 pEval
= Tcl_DuplicateObj(p
->pConflictScript
);
498 Tcl_IncrRefCount(pEval
);
500 sqlite3changeset_op(pIter
, &zTab
, &nCol
, &op
, 0);
502 if( eConf
==SQLITE_CHANGESET_FOREIGN_KEY
){
504 sqlite3changeset_fk_conflicts(pIter
, &nFk
);
505 Tcl_ListObjAppendElement(0, pEval
, Tcl_NewStringObj("FOREIGN_KEY", -1));
506 Tcl_ListObjAppendElement(0, pEval
, Tcl_NewIntObj(nFk
));
509 /* Append the operation type. */
510 Tcl_ListObjAppendElement(0, pEval
, Tcl_NewStringObj(
511 op
==SQLITE_INSERT
? "INSERT" :
512 op
==SQLITE_UPDATE
? "UPDATE" :
516 /* Append the table name. */
517 Tcl_ListObjAppendElement(0, pEval
, Tcl_NewStringObj(zTab
, -1));
519 /* Append the conflict type. */
521 case SQLITE_CHANGESET_DATA
:
522 Tcl_ListObjAppendElement(interp
, pEval
,Tcl_NewStringObj("DATA",-1));
524 case SQLITE_CHANGESET_NOTFOUND
:
525 Tcl_ListObjAppendElement(interp
, pEval
,Tcl_NewStringObj("NOTFOUND",-1));
527 case SQLITE_CHANGESET_CONFLICT
:
528 Tcl_ListObjAppendElement(interp
, pEval
,Tcl_NewStringObj("CONFLICT",-1));
530 case SQLITE_CHANGESET_CONSTRAINT
:
531 Tcl_ListObjAppendElement(interp
, pEval
,Tcl_NewStringObj("CONSTRAINT",-1));
535 /* If this is not an INSERT, append the old row */
536 if( op
!=SQLITE_INSERT
){
538 Tcl_Obj
*pOld
= Tcl_NewObj();
539 for(i
=0; i
<nCol
; i
++){
541 sqlite3changeset_old(pIter
, i
, &pVal
);
542 test_append_value(pOld
, pVal
);
544 Tcl_ListObjAppendElement(0, pEval
, pOld
);
547 /* If this is not a DELETE, append the new row */
548 if( op
!=SQLITE_DELETE
){
550 Tcl_Obj
*pNew
= Tcl_NewObj();
551 for(i
=0; i
<nCol
; i
++){
553 sqlite3changeset_new(pIter
, i
, &pVal
);
554 test_append_value(pNew
, pVal
);
556 Tcl_ListObjAppendElement(0, pEval
, pNew
);
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
){
563 Tcl_Obj
*pConflict
= Tcl_NewObj();
564 for(i
=0; i
<nCol
; i
++){
567 rc
= sqlite3changeset_conflict(pIter
, i
, &pVal
);
568 assert( rc
==SQLITE_OK
);
569 test_append_value(pConflict
, pVal
);
571 Tcl_ListObjAppendElement(0, pEval
, pConflict
);
574 /***********************************************************************
575 ** This block is purely for testing some error conditions.
577 if( eConf
==SQLITE_CHANGESET_CONSTRAINT
578 || eConf
==SQLITE_CHANGESET_NOTFOUND
581 int rc
= sqlite3changeset_conflict(pIter
, 0, &pVal
);
582 assert( rc
==SQLITE_MISUSE
);
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
);
590 if( op
==SQLITE_DELETE
){
592 int rc
= sqlite3changeset_new(pIter
, 0, &pVal
);
593 assert( rc
==SQLITE_MISUSE
);
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
);
601 if( op
==SQLITE_INSERT
){
603 int rc
= sqlite3changeset_old(pIter
, 0, &pVal
);
604 assert( rc
==SQLITE_MISUSE
);
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
);
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. */
616 int rc
= sqlite3changeset_fk_conflicts(pIter
, &nDummy
);
617 assert( rc
==SQLITE_MISUSE
);
619 /* End of testing block
620 ***********************************************************************/
623 if( TCL_OK
!=Tcl_EvalObjEx(interp
, pEval
, TCL_EVAL_GLOBAL
) ){
624 Tcl_BackgroundError(interp
);
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
;
634 Tcl_GetIntFromObj(0, pRes
, &ret
);
638 Tcl_DecrRefCount(pEval
);
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.
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 */
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 */
661 sqlite3changeset_op(pIter
, &zTab
, &nCol
, &op
, 0);
663 if( op
!=SQLITE_INSERT
){
664 for(i
=0; i
<nCol
; i
++){
666 sqlite3changeset_old(pIter
, i
, &pVal
);
667 sqlite3_value_text16(pVal
);
672 if( op
!=SQLITE_DELETE
){
673 for(i
=0; i
<nCol
; i
++){
675 sqlite3changeset_new(pIter
, i
, &pVal
);
676 sqlite3_value_text16(pVal
);
681 if( eConf
==SQLITE_CHANGESET_DATA
){
682 return SQLITE_CHANGESET_REPLACE
;
684 return SQLITE_CHANGESET_OMIT
;
687 static int testStreamInput(
688 void *pCtx
, /* Context pointer */
689 void *pData
, /* Buffer to populate */
690 int *pnData
/* IN/OUT: Bytes requested/supplied */
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 */
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
);
704 if( nRet
>nReq
) nRet
= nReq
;
705 if( nRet
>nRem
) nRet
= nRem
;
709 memcpy(pData
, &p
->aData
[p
->iData
], nRet
);
718 static int SQLITE_TCLAPI
testSqlite3changesetApply(
723 Tcl_Obj
*CONST objv
[]
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
;
734 int flags
= 0; /* Flags for apply_v2() */
736 memset(&sStr
, 0, sizeof(sStr
));
737 sStr
.nStream
= test_tcl_integer(interp
, SESSION_STREAM_TCL_VAR
);
739 /* Check for the -nosavepoint flag */
741 const char *z1
= Tcl_GetString(objv
[1]);
743 if( n
>1 && n
<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1
, n
) ){
744 flags
= SQLITE_CHANGESETAPPLY_NOSAVEPOINT
;
750 if( objc
!=4 && objc
!=5 ){
753 zMsg
= "?-nosavepoint? DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
755 zMsg
= "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
757 Tcl_WrongNumArgs(interp
, 1, objv
, zMsg
);
760 if( 0==Tcl_GetCommandInfo(interp
, Tcl_GetString(objv
[1]), &info
) ){
761 Tcl_AppendResult(interp
, "no such handle: ", Tcl_GetString(objv
[1]), 0);
764 db
= *(sqlite3
**)info
.objClientData
;
765 pChangeset
= (void *)Tcl_GetByteArrayFromObj(objv
[2], &nChangeset
);
766 ctx
.pConflictScript
= objv
[3];
767 ctx
.pFilterScript
= objc
==5 ? objv
[4] : 0;
770 if( sStr
.nStream
==0 ){
772 rc
= sqlite3changeset_apply(db
, nChangeset
, pChangeset
,
773 (objc
==5)?test_filter_handler
:0, test_conflict_handler
, (void *)&ctx
776 rc
= sqlite3changeset_apply_v2(db
, nChangeset
, pChangeset
,
777 (objc
==5)?test_filter_handler
:0, test_conflict_handler
, (void *)&ctx
,
778 &pRebase
, &nRebase
, flags
782 sStr
.aData
= (unsigned char*)pChangeset
;
783 sStr
.nData
= nChangeset
;
785 rc
= sqlite3changeset_apply_strm(db
, testStreamInput
, (void*)&sStr
,
786 (objc
==5) ? test_filter_handler
: 0,
787 test_conflict_handler
, (void *)&ctx
790 rc
= sqlite3changeset_apply_v2_strm(db
, testStreamInput
, (void*)&sStr
,
791 (objc
==5) ? test_filter_handler
: 0,
792 test_conflict_handler
, (void *)&ctx
,
793 &pRebase
, &nRebase
, flags
799 return test_session_error(interp
, rc
, 0);
801 Tcl_ResetResult(interp
);
802 if( bV2
&& pRebase
){
803 Tcl_SetObjResult(interp
, Tcl_NewByteArrayObj(pRebase
, nRebase
));
806 sqlite3_free(pRebase
);
811 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
813 static int SQLITE_TCLAPI
test_sqlite3changeset_apply(
817 Tcl_Obj
*CONST objv
[]
819 return testSqlite3changesetApply(0, clientData
, interp
, objc
, objv
);
822 ** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
824 static int SQLITE_TCLAPI
test_sqlite3changeset_apply_v2(
828 Tcl_Obj
*CONST objv
[]
830 return testSqlite3changesetApply(1, clientData
, interp
, objc
, objv
);
834 ** sqlite3changeset_apply_replace_all DB CHANGESET
836 static int SQLITE_TCLAPI
test_sqlite3changeset_apply_replace_all(
840 Tcl_Obj
*CONST objv
[]
842 sqlite3
*db
; /* Database handle */
843 Tcl_CmdInfo info
; /* Database Tcl command (objv[1]) info */
844 int rc
; /* Return code from changeset_invert() */
845 void *pChangeset
; /* Buffer containing changeset */
846 int nChangeset
; /* Size of buffer aChangeset in bytes */
849 Tcl_WrongNumArgs(interp
, 1, objv
, "DB CHANGESET");
852 if( 0==Tcl_GetCommandInfo(interp
, Tcl_GetString(objv
[1]), &info
) ){
853 Tcl_AppendResult(interp
, "no such handle: ", Tcl_GetString(objv
[2]), 0);
856 db
= *(sqlite3
**)info
.objClientData
;
857 pChangeset
= (void *)Tcl_GetByteArrayFromObj(objv
[2], &nChangeset
);
859 rc
= sqlite3changeset_apply(db
, nChangeset
, pChangeset
, 0, replace_handler
,0);
861 return test_session_error(interp
, rc
, 0);
863 Tcl_ResetResult(interp
);
869 ** sqlite3changeset_invert CHANGESET
871 static int SQLITE_TCLAPI
test_sqlite3changeset_invert(
875 Tcl_Obj
*CONST objv
[]
877 int rc
; /* Return code from changeset_invert() */
878 TestStreamInput sIn
; /* Input stream */
879 TestSessionsBlob sOut
; /* Output blob */
882 Tcl_WrongNumArgs(interp
, 1, objv
, "CHANGESET");
886 memset(&sIn
, 0, sizeof(sIn
));
887 memset(&sOut
, 0, sizeof(sOut
));
888 sIn
.nStream
= test_tcl_integer(interp
, SESSION_STREAM_TCL_VAR
);
889 sIn
.aData
= Tcl_GetByteArrayFromObj(objv
[1], &sIn
.nData
);
892 rc
= sqlite3changeset_invert_strm(
893 testStreamInput
, (void*)&sIn
, testStreamOutput
, (void*)&sOut
896 rc
= sqlite3changeset_invert(sIn
.nData
, sIn
.aData
, &sOut
.n
, &sOut
.p
);
899 rc
= test_session_error(interp
, rc
, 0);
901 Tcl_SetObjResult(interp
,Tcl_NewByteArrayObj((unsigned char*)sOut
.p
,sOut
.n
));
903 sqlite3_free(sOut
.p
);
908 ** sqlite3changeset_concat LEFT RIGHT
910 static int SQLITE_TCLAPI
test_sqlite3changeset_concat(
914 Tcl_Obj
*CONST objv
[]
916 int rc
; /* Return code from changeset_invert() */
918 TestStreamInput sLeft
; /* Input stream */
919 TestStreamInput sRight
; /* Input stream */
920 TestSessionsBlob sOut
= {0,0}; /* Output blob */
923 Tcl_WrongNumArgs(interp
, 1, objv
, "LEFT RIGHT");
927 memset(&sLeft
, 0, sizeof(sLeft
));
928 memset(&sRight
, 0, sizeof(sRight
));
929 sLeft
.aData
= Tcl_GetByteArrayFromObj(objv
[1], &sLeft
.nData
);
930 sRight
.aData
= Tcl_GetByteArrayFromObj(objv
[2], &sRight
.nData
);
931 sLeft
.nStream
= test_tcl_integer(interp
, SESSION_STREAM_TCL_VAR
);
932 sRight
.nStream
= sLeft
.nStream
;
934 if( sLeft
.nStream
>0 ){
935 rc
= sqlite3changeset_concat_strm(
936 testStreamInput
, (void*)&sLeft
,
937 testStreamInput
, (void*)&sRight
,
938 testStreamOutput
, (void*)&sOut
941 rc
= sqlite3changeset_concat(
942 sLeft
.nData
, sLeft
.aData
, sRight
.nData
, sRight
.aData
, &sOut
.n
, &sOut
.p
947 rc
= test_session_error(interp
, rc
, 0);
949 Tcl_SetObjResult(interp
,Tcl_NewByteArrayObj((unsigned char*)sOut
.p
,sOut
.n
));
951 sqlite3_free(sOut
.p
);
956 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT
958 static int SQLITE_TCLAPI
test_sqlite3session_foreach(
962 Tcl_Obj
*CONST objv
[]
966 sqlite3_changeset_iter
*pIter
;
973 TestStreamInput sStr
;
974 memset(&sStr
, 0, sizeof(sStr
));
977 char *zOpt
= Tcl_GetString(objv
[1]);
978 isCheckNext
= (strcmp(zOpt
, "-next")==0);
980 if( objc
!=4+isCheckNext
){
981 Tcl_WrongNumArgs(interp
, 1, objv
, "?-next? VARNAME CHANGESET SCRIPT");
985 pVarname
= objv
[1+isCheckNext
];
986 pCS
= objv
[2+isCheckNext
];
987 pScript
= objv
[3+isCheckNext
];
989 pChangeset
= (void *)Tcl_GetByteArrayFromObj(pCS
, &nChangeset
);
990 sStr
.nStream
= test_tcl_integer(interp
, SESSION_STREAM_TCL_VAR
);
991 if( sStr
.nStream
==0 ){
992 rc
= sqlite3changeset_start(&pIter
, nChangeset
, pChangeset
);
994 sStr
.aData
= (unsigned char*)pChangeset
;
995 sStr
.nData
= nChangeset
;
996 rc
= sqlite3changeset_start_strm(&pIter
, testStreamInput
, (void*)&sStr
);
999 return test_session_error(interp
, rc
, 0);
1002 while( SQLITE_ROW
==sqlite3changeset_next(pIter
) ){
1003 int nCol
; /* Number of columns in table */
1004 int nCol2
; /* Number of columns in table */
1005 int op
; /* SQLITE_INSERT, UPDATE or DELETE */
1006 const char *zTab
; /* Name of table change applies to */
1007 Tcl_Obj
*pVar
; /* Tcl value to set $VARNAME to */
1008 Tcl_Obj
*pOld
; /* Vector of old.* values */
1009 Tcl_Obj
*pNew
; /* Vector of new.* values */
1013 unsigned char *abPK
;
1016 /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
1019 if( SQLITE_MISUSE
!=sqlite3changeset_fk_conflicts(pIter
, &nDummy
) ){
1020 sqlite3changeset_finalize(pIter
);
1024 sqlite3changeset_op(pIter
, &zTab
, &nCol
, &op
, &bIndirect
);
1025 pVar
= Tcl_NewObj();
1026 Tcl_ListObjAppendElement(0, pVar
, Tcl_NewStringObj(
1027 op
==SQLITE_INSERT
? "INSERT" :
1028 op
==SQLITE_UPDATE
? "UPDATE" :
1032 Tcl_ListObjAppendElement(0, pVar
, Tcl_NewStringObj(zTab
, -1));
1033 Tcl_ListObjAppendElement(0, pVar
, Tcl_NewBooleanObj(bIndirect
));
1035 zPK
= ckalloc(nCol
+1);
1036 memset(zPK
, 0, nCol
+1);
1037 sqlite3changeset_pk(pIter
, &abPK
, &nCol2
);
1038 assert( nCol
==nCol2
);
1039 for(i
=0; i
<nCol
; i
++){
1040 zPK
[i
] = (abPK
[i
] ? 'X' : '.');
1042 Tcl_ListObjAppendElement(0, pVar
, Tcl_NewStringObj(zPK
, -1));
1045 pOld
= Tcl_NewObj();
1046 if( op
!=SQLITE_INSERT
){
1047 for(i
=0; i
<nCol
; i
++){
1048 sqlite3_value
*pVal
;
1049 sqlite3changeset_old(pIter
, i
, &pVal
);
1050 test_append_value(pOld
, pVal
);
1053 pNew
= Tcl_NewObj();
1054 if( op
!=SQLITE_DELETE
){
1055 for(i
=0; i
<nCol
; i
++){
1056 sqlite3_value
*pVal
;
1057 sqlite3changeset_new(pIter
, i
, &pVal
);
1058 test_append_value(pNew
, pVal
);
1061 Tcl_ListObjAppendElement(0, pVar
, pOld
);
1062 Tcl_ListObjAppendElement(0, pVar
, pNew
);
1064 Tcl_ObjSetVar2(interp
, pVarname
, 0, pVar
, 0);
1065 rc
= Tcl_EvalObjEx(interp
, pScript
, 0);
1066 if( rc
!=TCL_OK
&& rc
!=TCL_CONTINUE
){
1067 sqlite3changeset_finalize(pIter
);
1068 return rc
==TCL_BREAK
? TCL_OK
: rc
;
1073 int rc2
= sqlite3changeset_next(pIter
);
1074 rc
= sqlite3changeset_finalize(pIter
);
1075 assert( (rc2
==SQLITE_DONE
&& rc
==SQLITE_OK
) || rc2
==rc
);
1077 rc
= sqlite3changeset_finalize(pIter
);
1079 if( rc
!=SQLITE_OK
){
1080 return test_session_error(interp
, rc
, 0);
1087 ** tclcmd: CMD configure REBASE-BLOB
1088 ** tclcmd: CMD rebase CHANGESET
1089 ** tclcmd: CMD delete
1091 static int SQLITE_TCLAPI
test_rebaser_cmd(
1095 Tcl_Obj
*CONST objv
[]
1097 struct RebaseSubcmd
{
1103 { "configure", 1, "REBASE-BLOB" }, /* 0 */
1104 { "delete", 0, "" }, /* 1 */
1105 { "rebase", 1, "CHANGESET" }, /* 2 */
1109 sqlite3_rebaser
*p
= (sqlite3_rebaser
*)clientData
;
1114 Tcl_WrongNumArgs(interp
, 1, objv
, "SUBCOMMAND ...");
1117 rc
= Tcl_GetIndexFromObjStruct(interp
,
1118 objv
[1], aSub
, sizeof(aSub
[0]), "sub-command", 0, &iSub
1120 if( rc
!=TCL_OK
) return rc
;
1121 if( objc
!=2+aSub
[iSub
].nArg
){
1122 Tcl_WrongNumArgs(interp
, 2, objv
, aSub
[iSub
].zMsg
);
1126 assert( iSub
==0 || iSub
==1 || iSub
==2 );
1127 assert( rc
==SQLITE_OK
);
1129 case 0: { /* configure */
1131 unsigned char *pRebase
= Tcl_GetByteArrayFromObj(objv
[2], &nRebase
);
1132 rc
= sqlite3rebaser_configure(p
, nRebase
, pRebase
);
1136 case 1: /* delete */
1137 Tcl_DeleteCommand(interp
, Tcl_GetString(objv
[0]));
1140 default: { /* rebase */
1141 TestStreamInput sStr
; /* Input stream */
1142 TestSessionsBlob sOut
; /* Output blob */
1144 memset(&sStr
, 0, sizeof(sStr
));
1145 memset(&sOut
, 0, sizeof(sOut
));
1146 sStr
.aData
= Tcl_GetByteArrayFromObj(objv
[2], &sStr
.nData
);
1147 sStr
.nStream
= test_tcl_integer(interp
, SESSION_STREAM_TCL_VAR
);
1150 rc
= sqlite3rebaser_rebase_strm(p
,
1151 testStreamInput
, (void*)&sStr
,
1152 testStreamOutput
, (void*)&sOut
1155 rc
= sqlite3rebaser_rebase(p
, sStr
.nData
, sStr
.aData
, &sOut
.n
, &sOut
.p
);
1158 if( rc
==SQLITE_OK
){
1159 Tcl_SetObjResult(interp
, Tcl_NewByteArrayObj(sOut
.p
, sOut
.n
));
1161 sqlite3_free(sOut
.p
);
1166 if( rc
!=SQLITE_OK
){
1167 return test_session_error(interp
, rc
, 0);
1172 static void SQLITE_TCLAPI
test_rebaser_del(void *clientData
){
1173 sqlite3_rebaser
*p
= (sqlite3_rebaser
*)clientData
;
1174 sqlite3rebaser_delete(p
);
1178 ** tclcmd: sqlite3rebaser_create NAME
1180 static int SQLITE_TCLAPI
test_sqlite3rebaser_create(
1184 Tcl_Obj
*CONST objv
[]
1187 sqlite3_rebaser
*pNew
= 0;
1189 Tcl_WrongNumArgs(interp
, 1, objv
, "NAME");
1190 return SQLITE_ERROR
;
1193 rc
= sqlite3rebaser_create(&pNew
);
1194 if( rc
!=SQLITE_OK
){
1195 return test_session_error(interp
, rc
, 0);
1198 Tcl_CreateObjCommand(interp
, Tcl_GetString(objv
[1]), test_rebaser_cmd
,
1199 (ClientData
)pNew
, test_rebaser_del
1201 Tcl_SetObjResult(interp
, objv
[1]);
1205 int TestSession_Init(Tcl_Interp
*interp
){
1208 Tcl_ObjCmdProc
*xProc
;
1210 { "sqlite3session", test_sqlite3session
},
1211 { "sqlite3session_foreach", test_sqlite3session_foreach
},
1212 { "sqlite3changeset_invert", test_sqlite3changeset_invert
},
1213 { "sqlite3changeset_concat", test_sqlite3changeset_concat
},
1214 { "sqlite3changeset_apply", test_sqlite3changeset_apply
},
1215 { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2
},
1216 { "sqlite3changeset_apply_replace_all",
1217 test_sqlite3changeset_apply_replace_all
},
1218 { "sql_exec_changeset", test_sql_exec_changeset
},
1219 { "sqlite3rebaser_create", test_sqlite3rebaser_create
},
1223 for(i
=0; i
<sizeof(aCmd
)/sizeof(struct Cmd
); i
++){
1224 struct Cmd
*p
= &aCmd
[i
];
1225 Tcl_CreateObjCommand(interp
, p
->zCmd
, p
->xProc
, 0, 0);
1231 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */