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 /************************************************************************/
103 static int sqlite3_test_changeset(int, void *, char **);
104 static void assert_changeset_is_ok(int n
, void *p
){
107 rc
= sqlite3_test_changeset(n
, p
, &z
);
111 # define assert_changeset_is_ok(n,p)
115 ** Tclcmd: sql_exec_changeset DB SQL
117 static int SQLITE_TCLAPI
test_sql_exec_changeset(
121 Tcl_Obj
*CONST objv
[]
130 Tcl_WrongNumArgs(interp
, 1, objv
, "DB SQL");
133 if( dbHandleFromObj(interp
, objv
[1], &db
) ) return TCL_ERROR
;
134 zSql
= (const char*)Tcl_GetString(objv
[2]);
136 rc
= sql_exec_changeset(db
, zSql
, &nChangeset
, &pChangeset
);
138 Tcl_ResetResult(interp
);
139 Tcl_AppendResult(interp
, "error in sql_exec_changeset()", 0);
143 assert_changeset_is_ok(nChangeset
, pChangeset
);
144 Tcl_SetObjResult(interp
, Tcl_NewByteArrayObj(pChangeset
, nChangeset
));
145 sqlite3_free(pChangeset
);
151 #define SESSION_STREAM_TCL_VAR "sqlite3session_streams"
154 ** Attempt to find the global variable zVar within interpreter interp
155 ** and extract an integer value from it. Return this value.
157 ** If the named variable cannot be found, or if it cannot be interpreted
158 ** as a integer, return 0.
160 static int test_tcl_integer(Tcl_Interp
*interp
, const char *zVar
){
163 Tcl_Obj
*pName
= Tcl_NewStringObj(zVar
, -1);
164 Tcl_IncrRefCount(pName
);
165 pObj
= Tcl_ObjGetVar2(interp
, pName
, 0, TCL_GLOBAL_ONLY
);
166 Tcl_DecrRefCount(pName
);
167 if( pObj
) Tcl_GetIntFromObj(0, pObj
, &iVal
);
171 static int test_session_error(Tcl_Interp
*interp
, int rc
, char *zErr
){
172 extern const char *sqlite3ErrName(int);
173 Tcl_SetObjResult(interp
, Tcl_NewStringObj(sqlite3ErrName(rc
), -1));
175 Tcl_AppendResult(interp
, " - ", zErr
, 0);
181 static int test_table_filter(void *pCtx
, const char *zTbl
){
182 TestSession
*p
= (TestSession
*)pCtx
;
187 pEval
= Tcl_DuplicateObj(p
->pFilterScript
);
188 Tcl_IncrRefCount(pEval
);
189 rc
= Tcl_ListObjAppendElement(p
->interp
, pEval
, Tcl_NewStringObj(zTbl
, -1));
191 rc
= Tcl_EvalObjEx(p
->interp
, pEval
, TCL_EVAL_GLOBAL
);
194 rc
= Tcl_GetBooleanFromObj(p
->interp
, Tcl_GetObjResult(p
->interp
), &bRes
);
197 /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */
198 Tcl_BackgroundError(p
->interp
);
200 Tcl_DecrRefCount(pEval
);
205 struct TestSessionsBlob
{
209 typedef struct TestSessionsBlob TestSessionsBlob
;
211 static int testStreamOutput(
216 TestSessionsBlob
*pBlob
= (TestSessionsBlob
*)pCtx
;
220 pNew
= (char*)sqlite3_realloc(pBlob
->p
, pBlob
->n
+ nData
);
224 pBlob
->p
= (void*)pNew
;
225 memcpy(&pNew
[pBlob
->n
], pData
, nData
);
231 ** Tclcmd: $session attach TABLE
232 ** $session changeset
234 ** $session enable BOOL
235 ** $session indirect INTEGER
237 ** $session table_filter SCRIPT
239 static int SQLITE_TCLAPI
test_session_cmd(
243 Tcl_Obj
*CONST objv
[]
245 TestSession
*p
= (TestSession
*)clientData
;
246 sqlite3_session
*pSession
= p
->pSession
;
247 static struct SessionSubcmd
{
253 { "attach", 1, "TABLE", }, /* 0 */
254 { "changeset", 0, "", }, /* 1 */
255 { "delete", 0, "", }, /* 2 */
256 { "enable", 1, "BOOL", }, /* 3 */
257 { "indirect", 1, "BOOL", }, /* 4 */
258 { "isempty", 0, "", }, /* 5 */
259 { "table_filter", 1, "SCRIPT", }, /* 6 */
260 { "patchset", 0, "", }, /* 7 */
261 { "diff", 2, "FROMDB TBL", }, /* 8 */
262 { "memory_used", 0, "", }, /* 9 */
263 { "changeset_size", 0, "", }, /* 10 */
264 { "object_config_size", 1, "INTEGER", }, /* 11 */
271 Tcl_WrongNumArgs(interp
, 1, objv
, "SUBCOMMAND ...");
274 rc
= Tcl_GetIndexFromObjStruct(interp
,
275 objv
[1], aSub
, sizeof(aSub
[0]), "sub-command", 0, &iSub
277 if( rc
!=TCL_OK
) return rc
;
278 if( objc
!=2+aSub
[iSub
].nArg
){
279 Tcl_WrongNumArgs(interp
, 2, objv
, aSub
[iSub
].zMsg
);
284 case 0: { /* attach */
285 char *zArg
= Tcl_GetString(objv
[2]);
286 if( zArg
[0]=='*' && zArg
[1]=='\0' ) zArg
= 0;
287 rc
= sqlite3session_attach(pSession
, zArg
);
289 return test_session_error(interp
, rc
, 0);
294 case 7: /* patchset */
295 case 1: { /* changeset */
296 TestSessionsBlob o
= {0, 0};
297 if( test_tcl_integer(interp
, SESSION_STREAM_TCL_VAR
) ){
298 void *pCtx
= (void*)&o
;
300 rc
= sqlite3session_patchset_strm(pSession
, testStreamOutput
, pCtx
);
302 rc
= sqlite3session_changeset_strm(pSession
, testStreamOutput
, pCtx
);
306 rc
= sqlite3session_patchset(pSession
, &o
.n
, &o
.p
);
308 rc
= sqlite3session_changeset(pSession
, &o
.n
, &o
.p
);
312 assert_changeset_is_ok(o
.n
, o
.p
);
313 Tcl_SetObjResult(interp
, Tcl_NewByteArrayObj(o
.p
, o
.n
));
317 return test_session_error(interp
, rc
, 0);
323 Tcl_DeleteCommand(interp
, Tcl_GetString(objv
[0]));
326 case 3: { /* enable */
328 if( Tcl_GetIntFromObj(interp
, objv
[2], &val
) ) return TCL_ERROR
;
329 val
= sqlite3session_enable(pSession
, val
);
330 Tcl_SetObjResult(interp
, Tcl_NewBooleanObj(val
));
334 case 4: { /* indirect */
336 if( Tcl_GetIntFromObj(interp
, objv
[2], &val
) ) return TCL_ERROR
;
337 val
= sqlite3session_indirect(pSession
, val
);
338 Tcl_SetObjResult(interp
, Tcl_NewBooleanObj(val
));
342 case 5: { /* isempty */
344 val
= sqlite3session_isempty(pSession
);
345 Tcl_SetObjResult(interp
, Tcl_NewBooleanObj(val
));
349 case 6: { /* table_filter */
350 if( p
->pFilterScript
) Tcl_DecrRefCount(p
->pFilterScript
);
352 p
->pFilterScript
= Tcl_DuplicateObj(objv
[2]);
353 Tcl_IncrRefCount(p
->pFilterScript
);
354 sqlite3session_table_filter(pSession
, test_table_filter
, clientData
);
360 rc
= sqlite3session_diff(pSession
,
361 Tcl_GetString(objv
[2]),
362 Tcl_GetString(objv
[3]),
365 assert( rc
!=SQLITE_OK
|| zErr
==0 );
367 return test_session_error(interp
, rc
, zErr
);
372 case 9: { /* memory_used */
373 sqlite3_int64 nMalloc
= sqlite3session_memory_used(pSession
);
374 Tcl_SetObjResult(interp
, Tcl_NewWideIntObj(nMalloc
));
379 sqlite3_int64 nSize
= sqlite3session_changeset_size(pSession
);
380 Tcl_SetObjResult(interp
, Tcl_NewWideIntObj(nSize
));
386 if( Tcl_GetIntFromObj(interp
, objv
[2], &iArg
) ){
389 rc
= sqlite3session_object_config(
390 pSession
, SQLITE_SESSION_OBJCONFIG_SIZE
, &iArg
393 extern const char *sqlite3ErrName(int);
394 Tcl_SetObjResult(interp
, Tcl_NewStringObj(sqlite3ErrName(rc
), -1));
396 Tcl_SetObjResult(interp
, Tcl_NewIntObj(iArg
));
405 static void SQLITE_TCLAPI
test_session_del(void *clientData
){
406 TestSession
*p
= (TestSession
*)clientData
;
407 if( p
->pFilterScript
) Tcl_DecrRefCount(p
->pFilterScript
);
408 sqlite3session_delete(p
->pSession
);
413 ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME
415 static int SQLITE_TCLAPI
test_sqlite3session(
419 Tcl_Obj
*CONST objv
[]
423 int rc
; /* sqlite3session_create() return code */
424 TestSession
*p
; /* New wrapper object */
428 Tcl_WrongNumArgs(interp
, 1, objv
, "CMD DB-HANDLE DB-NAME");
432 if( 0==Tcl_GetCommandInfo(interp
, Tcl_GetString(objv
[2]), &info
) ){
433 Tcl_AppendResult(interp
, "no such handle: ", Tcl_GetString(objv
[2]), 0);
436 db
= *(sqlite3
**)info
.objClientData
;
438 p
= (TestSession
*)ckalloc(sizeof(TestSession
));
439 memset(p
, 0, sizeof(TestSession
));
440 rc
= sqlite3session_create(db
, Tcl_GetString(objv
[3]), &p
->pSession
);
443 return test_session_error(interp
, rc
, 0);
446 /* Query the SQLITE_SESSION_OBJCONFIG_SIZE option to ensure that it
447 ** is clear by default. Then set it. */
448 sqlite3session_object_config(p
->pSession
,SQLITE_SESSION_OBJCONFIG_SIZE
,&iArg
);
451 sqlite3session_object_config(p
->pSession
,SQLITE_SESSION_OBJCONFIG_SIZE
,&iArg
);
453 Tcl_CreateObjCommand(
454 interp
, Tcl_GetString(objv
[1]), test_session_cmd
, (ClientData
)p
,
457 Tcl_SetObjResult(interp
, objv
[1]);
461 static void test_append_value(Tcl_Obj
*pList
, sqlite3_value
*pVal
){
463 Tcl_ListObjAppendElement(0, pList
, Tcl_NewObj());
464 Tcl_ListObjAppendElement(0, pList
, Tcl_NewObj());
467 switch( sqlite3_value_type(pVal
) ){
469 Tcl_ListObjAppendElement(0, pList
, Tcl_NewStringObj("n", 1));
473 Tcl_ListObjAppendElement(0, pList
, Tcl_NewStringObj("i", 1));
474 pObj
= Tcl_NewWideIntObj(sqlite3_value_int64(pVal
));
477 Tcl_ListObjAppendElement(0, pList
, Tcl_NewStringObj("f", 1));
478 pObj
= Tcl_NewDoubleObj(sqlite3_value_double(pVal
));
481 const char *z
= (char*)sqlite3_value_blob(pVal
);
482 int n
= sqlite3_value_bytes(pVal
);
483 Tcl_ListObjAppendElement(0, pList
, Tcl_NewStringObj("t", 1));
484 pObj
= Tcl_NewStringObj(z
, n
);
488 assert( sqlite3_value_type(pVal
)==SQLITE_BLOB
);
489 Tcl_ListObjAppendElement(0, pList
, Tcl_NewStringObj("b", 1));
490 pObj
= Tcl_NewByteArrayObj(
491 sqlite3_value_blob(pVal
),
492 sqlite3_value_bytes(pVal
)
496 Tcl_ListObjAppendElement(0, pList
, pObj
);
500 typedef struct TestConflictHandler TestConflictHandler
;
501 struct TestConflictHandler
{
503 Tcl_Obj
*pConflictScript
;
504 Tcl_Obj
*pFilterScript
;
507 static int test_obj_eq_string(Tcl_Obj
*p
, const char *z
){
513 zObj
= Tcl_GetStringFromObj(p
, &nObj
);
515 return (nObj
==n
&& (n
==0 || 0==memcmp(zObj
, z
, n
)));
518 static int test_filter_handler(
519 void *pCtx
, /* Pointer to TestConflictHandler structure */
520 const char *zTab
/* Table name */
522 TestConflictHandler
*p
= (TestConflictHandler
*)pCtx
;
525 Tcl_Interp
*interp
= p
->interp
;
527 pEval
= Tcl_DuplicateObj(p
->pFilterScript
);
528 Tcl_IncrRefCount(pEval
);
530 if( TCL_OK
!=Tcl_ListObjAppendElement(0, pEval
, Tcl_NewStringObj(zTab
, -1))
531 || TCL_OK
!=Tcl_EvalObjEx(interp
, pEval
, TCL_EVAL_GLOBAL
)
532 || TCL_OK
!=Tcl_GetIntFromObj(interp
, Tcl_GetObjResult(interp
), &res
)
534 Tcl_BackgroundError(interp
);
537 Tcl_DecrRefCount(pEval
);
541 static int test_conflict_handler(
542 void *pCtx
, /* Pointer to TestConflictHandler structure */
543 int eConf
, /* DATA, MISSING, CONFLICT, CONSTRAINT */
544 sqlite3_changeset_iter
*pIter
/* Handle describing change and conflict */
546 TestConflictHandler
*p
= (TestConflictHandler
*)pCtx
;
548 Tcl_Interp
*interp
= p
->interp
;
549 int ret
= 0; /* Return value */
551 int op
; /* SQLITE_UPDATE, DELETE or INSERT */
552 const char *zTab
; /* Name of table conflict is on */
553 int nCol
; /* Number of columns in table zTab */
555 pEval
= Tcl_DuplicateObj(p
->pConflictScript
);
556 Tcl_IncrRefCount(pEval
);
558 sqlite3changeset_op(pIter
, &zTab
, &nCol
, &op
, 0);
560 if( eConf
==SQLITE_CHANGESET_FOREIGN_KEY
){
562 sqlite3changeset_fk_conflicts(pIter
, &nFk
);
563 Tcl_ListObjAppendElement(0, pEval
, Tcl_NewStringObj("FOREIGN_KEY", -1));
564 Tcl_ListObjAppendElement(0, pEval
, Tcl_NewIntObj(nFk
));
567 /* Append the operation type. */
568 Tcl_ListObjAppendElement(0, pEval
, Tcl_NewStringObj(
569 op
==SQLITE_INSERT
? "INSERT" :
570 op
==SQLITE_UPDATE
? "UPDATE" :
574 /* Append the table name. */
575 Tcl_ListObjAppendElement(0, pEval
, Tcl_NewStringObj(zTab
, -1));
577 /* Append the conflict type. */
579 case SQLITE_CHANGESET_DATA
:
580 Tcl_ListObjAppendElement(interp
, pEval
,Tcl_NewStringObj("DATA",-1));
582 case SQLITE_CHANGESET_NOTFOUND
:
583 Tcl_ListObjAppendElement(interp
, pEval
,Tcl_NewStringObj("NOTFOUND",-1));
585 case SQLITE_CHANGESET_CONFLICT
:
586 Tcl_ListObjAppendElement(interp
, pEval
,Tcl_NewStringObj("CONFLICT",-1));
588 case SQLITE_CHANGESET_CONSTRAINT
:
589 Tcl_ListObjAppendElement(interp
, pEval
,Tcl_NewStringObj("CONSTRAINT",-1));
593 /* If this is not an INSERT, append the old row */
594 if( op
!=SQLITE_INSERT
){
596 Tcl_Obj
*pOld
= Tcl_NewObj();
597 for(i
=0; i
<nCol
; i
++){
599 sqlite3changeset_old(pIter
, i
, &pVal
);
600 test_append_value(pOld
, pVal
);
602 Tcl_ListObjAppendElement(0, pEval
, pOld
);
605 /* If this is not a DELETE, append the new row */
606 if( op
!=SQLITE_DELETE
){
608 Tcl_Obj
*pNew
= Tcl_NewObj();
609 for(i
=0; i
<nCol
; i
++){
611 sqlite3changeset_new(pIter
, i
, &pVal
);
612 test_append_value(pNew
, pVal
);
614 Tcl_ListObjAppendElement(0, pEval
, pNew
);
617 /* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append
618 ** the conflicting row. */
619 if( eConf
==SQLITE_CHANGESET_DATA
|| eConf
==SQLITE_CHANGESET_CONFLICT
){
621 Tcl_Obj
*pConflict
= Tcl_NewObj();
622 for(i
=0; i
<nCol
; i
++){
625 rc
= sqlite3changeset_conflict(pIter
, i
, &pVal
);
626 assert( rc
==SQLITE_OK
);
627 test_append_value(pConflict
, pVal
);
629 Tcl_ListObjAppendElement(0, pEval
, pConflict
);
632 /***********************************************************************
633 ** This block is purely for testing some error conditions.
635 if( eConf
==SQLITE_CHANGESET_CONSTRAINT
636 || eConf
==SQLITE_CHANGESET_NOTFOUND
639 int rc
= sqlite3changeset_conflict(pIter
, 0, &pVal
);
640 assert( rc
==SQLITE_MISUSE
);
643 int rc
= sqlite3changeset_conflict(pIter
, -1, &pVal
);
644 assert( rc
==SQLITE_RANGE
);
645 rc
= sqlite3changeset_conflict(pIter
, nCol
, &pVal
);
646 assert( rc
==SQLITE_RANGE
);
648 if( op
==SQLITE_DELETE
){
650 int rc
= sqlite3changeset_new(pIter
, 0, &pVal
);
651 assert( rc
==SQLITE_MISUSE
);
654 int rc
= sqlite3changeset_new(pIter
, -1, &pVal
);
655 assert( rc
==SQLITE_RANGE
);
656 rc
= sqlite3changeset_new(pIter
, nCol
, &pVal
);
657 assert( rc
==SQLITE_RANGE
);
659 if( op
==SQLITE_INSERT
){
661 int rc
= sqlite3changeset_old(pIter
, 0, &pVal
);
662 assert( rc
==SQLITE_MISUSE
);
665 int rc
= sqlite3changeset_old(pIter
, -1, &pVal
);
666 assert( rc
==SQLITE_RANGE
);
667 rc
= sqlite3changeset_old(pIter
, nCol
, &pVal
);
668 assert( rc
==SQLITE_RANGE
);
670 if( eConf
!=SQLITE_CHANGESET_FOREIGN_KEY
){
671 /* eConf!=FOREIGN_KEY is always true at this point. The condition is
672 ** just there to make it clearer what is being tested. */
674 int rc
= sqlite3changeset_fk_conflicts(pIter
, &nDummy
);
675 assert( rc
==SQLITE_MISUSE
);
677 /* End of testing block
678 ***********************************************************************/
681 if( TCL_OK
!=Tcl_EvalObjEx(interp
, pEval
, TCL_EVAL_GLOBAL
) ){
682 Tcl_BackgroundError(interp
);
684 Tcl_Obj
*pRes
= Tcl_GetObjResult(interp
);
685 if( test_obj_eq_string(pRes
, "OMIT") || test_obj_eq_string(pRes
, "") ){
686 ret
= SQLITE_CHANGESET_OMIT
;
687 }else if( test_obj_eq_string(pRes
, "REPLACE") ){
688 ret
= SQLITE_CHANGESET_REPLACE
;
689 }else if( test_obj_eq_string(pRes
, "ABORT") ){
690 ret
= SQLITE_CHANGESET_ABORT
;
692 Tcl_GetIntFromObj(0, pRes
, &ret
);
696 Tcl_DecrRefCount(pEval
);
701 ** The conflict handler used by sqlite3changeset_apply_replace_all().
702 ** This conflict handler calls sqlite3_value_text16() on all available
703 ** sqlite3_value objects and then returns CHANGESET_REPLACE, or
704 ** CHANGESET_OMIT if REPLACE is not applicable. This is used to test the
705 ** effect of a malloc failure within an sqlite3_value_xxx() function
706 ** invoked by a conflict-handler callback.
708 static int replace_handler(
709 void *pCtx
, /* Pointer to TestConflictHandler structure */
710 int eConf
, /* DATA, MISSING, CONFLICT, CONSTRAINT */
711 sqlite3_changeset_iter
*pIter
/* Handle describing change and conflict */
713 int op
; /* SQLITE_UPDATE, DELETE or INSERT */
714 const char *zTab
; /* Name of table conflict is on */
715 int nCol
; /* Number of columns in table zTab */
719 sqlite3changeset_op(pIter
, &zTab
, &nCol
, &op
, 0);
721 if( op
!=SQLITE_INSERT
){
722 for(i
=0; i
<nCol
; i
++){
724 sqlite3changeset_old(pIter
, i
, &pVal
);
725 sqlite3_value_text16(pVal
);
730 if( op
!=SQLITE_DELETE
){
731 for(i
=0; i
<nCol
; i
++){
733 sqlite3changeset_new(pIter
, i
, &pVal
);
734 sqlite3_value_text16(pVal
);
739 if( eConf
==SQLITE_CHANGESET_DATA
){
740 return SQLITE_CHANGESET_REPLACE
;
742 return SQLITE_CHANGESET_OMIT
;
745 static int testStreamInput(
746 void *pCtx
, /* Context pointer */
747 void *pData
, /* Buffer to populate */
748 int *pnData
/* IN/OUT: Bytes requested/supplied */
750 TestStreamInput
*p
= (TestStreamInput
*)pCtx
;
751 int nReq
= *pnData
; /* Bytes of data requested */
752 int nRem
= p
->nData
- p
->iData
; /* Bytes of data available */
753 int nRet
= p
->nStream
; /* Bytes actually returned */
755 /* Allocate and free some space. There is no point to this, other than
756 ** that it allows the regular OOM fault-injection tests to cause an error
757 ** in this function. */
758 void *pAlloc
= sqlite3_malloc(10);
759 if( pAlloc
==0 ) return SQLITE_NOMEM
;
760 sqlite3_free(pAlloc
);
762 if( nRet
>nReq
) nRet
= nReq
;
763 if( nRet
>nRem
) nRet
= nRem
;
767 memcpy(pData
, &p
->aData
[p
->iData
], nRet
);
776 static int SQLITE_TCLAPI
testSqlite3changesetApply(
781 Tcl_Obj
*CONST objv
[]
783 sqlite3
*db
; /* Database handle */
784 Tcl_CmdInfo info
; /* Database Tcl command (objv[1]) info */
785 int rc
; /* Return code from changeset_invert() */
786 void *pChangeset
; /* Buffer containing changeset */
787 int nChangeset
; /* Size of buffer aChangeset in bytes */
788 TestConflictHandler ctx
;
789 TestStreamInput sStr
;
792 int flags
= 0; /* Flags for apply_v2() */
794 memset(&sStr
, 0, sizeof(sStr
));
795 sStr
.nStream
= test_tcl_integer(interp
, SESSION_STREAM_TCL_VAR
);
797 /* Check for the -nosavepoint flag */
800 const char *z1
= Tcl_GetString(objv
[1]);
802 if( n
>1 && n
<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1
, n
) ){
803 flags
|= SQLITE_CHANGESETAPPLY_NOSAVEPOINT
;
809 const char *z1
= Tcl_GetString(objv
[1]);
811 if( n
>1 && n
<=7 && 0==sqlite3_strnicmp("-invert", z1
, n
) ){
812 flags
|= SQLITE_CHANGESETAPPLY_INVERT
;
819 if( objc
!=4 && objc
!=5 ){
822 zMsg
= "?-nosavepoint? ?-inverse? "
823 "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
825 zMsg
= "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
827 Tcl_WrongNumArgs(interp
, 1, objv
, zMsg
);
830 if( 0==Tcl_GetCommandInfo(interp
, Tcl_GetString(objv
[1]), &info
) ){
831 Tcl_AppendResult(interp
, "no such handle: ", Tcl_GetString(objv
[1]), 0);
834 db
= *(sqlite3
**)info
.objClientData
;
835 pChangeset
= (void *)Tcl_GetByteArrayFromObj(objv
[2], &nChangeset
);
836 ctx
.pConflictScript
= objv
[3];
837 ctx
.pFilterScript
= objc
==5 ? objv
[4] : 0;
840 if( sStr
.nStream
==0 ){
842 rc
= sqlite3changeset_apply(db
, nChangeset
, pChangeset
,
843 (objc
==5)?test_filter_handler
:0, test_conflict_handler
, (void *)&ctx
846 rc
= sqlite3changeset_apply_v2(db
, nChangeset
, pChangeset
,
847 (objc
==5)?test_filter_handler
:0, test_conflict_handler
, (void *)&ctx
,
848 &pRebase
, &nRebase
, flags
852 sStr
.aData
= (unsigned char*)pChangeset
;
853 sStr
.nData
= nChangeset
;
855 rc
= sqlite3changeset_apply_strm(db
, testStreamInput
, (void*)&sStr
,
856 (objc
==5) ? test_filter_handler
: 0,
857 test_conflict_handler
, (void *)&ctx
860 rc
= sqlite3changeset_apply_v2_strm(db
, testStreamInput
, (void*)&sStr
,
861 (objc
==5) ? test_filter_handler
: 0,
862 test_conflict_handler
, (void *)&ctx
,
863 &pRebase
, &nRebase
, flags
869 return test_session_error(interp
, rc
, 0);
871 Tcl_ResetResult(interp
);
872 if( bV2
&& pRebase
){
873 Tcl_SetObjResult(interp
, Tcl_NewByteArrayObj(pRebase
, nRebase
));
876 sqlite3_free(pRebase
);
881 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
883 static int SQLITE_TCLAPI
test_sqlite3changeset_apply(
887 Tcl_Obj
*CONST objv
[]
889 return testSqlite3changesetApply(0, clientData
, interp
, objc
, objv
);
892 ** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
894 static int SQLITE_TCLAPI
test_sqlite3changeset_apply_v2(
898 Tcl_Obj
*CONST objv
[]
900 return testSqlite3changesetApply(1, clientData
, interp
, objc
, objv
);
904 ** sqlite3changeset_apply_replace_all DB CHANGESET
906 static int SQLITE_TCLAPI
test_sqlite3changeset_apply_replace_all(
910 Tcl_Obj
*CONST objv
[]
912 sqlite3
*db
; /* Database handle */
913 Tcl_CmdInfo info
; /* Database Tcl command (objv[1]) info */
914 int rc
; /* Return code from changeset_invert() */
915 void *pChangeset
; /* Buffer containing changeset */
916 int nChangeset
; /* Size of buffer aChangeset in bytes */
919 Tcl_WrongNumArgs(interp
, 1, objv
, "DB CHANGESET");
922 if( 0==Tcl_GetCommandInfo(interp
, Tcl_GetString(objv
[1]), &info
) ){
923 Tcl_AppendResult(interp
, "no such handle: ", Tcl_GetString(objv
[2]), 0);
926 db
= *(sqlite3
**)info
.objClientData
;
927 pChangeset
= (void *)Tcl_GetByteArrayFromObj(objv
[2], &nChangeset
);
929 rc
= sqlite3changeset_apply(db
, nChangeset
, pChangeset
, 0, replace_handler
,0);
931 return test_session_error(interp
, rc
, 0);
933 Tcl_ResetResult(interp
);
939 ** sqlite3changeset_invert CHANGESET
941 static int SQLITE_TCLAPI
test_sqlite3changeset_invert(
945 Tcl_Obj
*CONST objv
[]
947 int rc
; /* Return code from changeset_invert() */
948 TestStreamInput sIn
; /* Input stream */
949 TestSessionsBlob sOut
; /* Output blob */
952 Tcl_WrongNumArgs(interp
, 1, objv
, "CHANGESET");
956 memset(&sIn
, 0, sizeof(sIn
));
957 memset(&sOut
, 0, sizeof(sOut
));
958 sIn
.nStream
= test_tcl_integer(interp
, SESSION_STREAM_TCL_VAR
);
959 sIn
.aData
= Tcl_GetByteArrayFromObj(objv
[1], &sIn
.nData
);
962 rc
= sqlite3changeset_invert_strm(
963 testStreamInput
, (void*)&sIn
, testStreamOutput
, (void*)&sOut
966 rc
= sqlite3changeset_invert(sIn
.nData
, sIn
.aData
, &sOut
.n
, &sOut
.p
);
969 rc
= test_session_error(interp
, rc
, 0);
971 assert_changeset_is_ok(sOut
.n
, sOut
.p
);
972 Tcl_SetObjResult(interp
,Tcl_NewByteArrayObj((unsigned char*)sOut
.p
,sOut
.n
));
974 sqlite3_free(sOut
.p
);
979 ** sqlite3changeset_concat LEFT RIGHT
981 static int SQLITE_TCLAPI
test_sqlite3changeset_concat(
985 Tcl_Obj
*CONST objv
[]
987 int rc
; /* Return code from changeset_invert() */
989 TestStreamInput sLeft
; /* Input stream */
990 TestStreamInput sRight
; /* Input stream */
991 TestSessionsBlob sOut
= {0,0}; /* Output blob */
994 Tcl_WrongNumArgs(interp
, 1, objv
, "LEFT RIGHT");
998 memset(&sLeft
, 0, sizeof(sLeft
));
999 memset(&sRight
, 0, sizeof(sRight
));
1000 sLeft
.aData
= Tcl_GetByteArrayFromObj(objv
[1], &sLeft
.nData
);
1001 sRight
.aData
= Tcl_GetByteArrayFromObj(objv
[2], &sRight
.nData
);
1002 sLeft
.nStream
= test_tcl_integer(interp
, SESSION_STREAM_TCL_VAR
);
1003 sRight
.nStream
= sLeft
.nStream
;
1005 if( sLeft
.nStream
>0 ){
1006 rc
= sqlite3changeset_concat_strm(
1007 testStreamInput
, (void*)&sLeft
,
1008 testStreamInput
, (void*)&sRight
,
1009 testStreamOutput
, (void*)&sOut
1012 rc
= sqlite3changeset_concat(
1013 sLeft
.nData
, sLeft
.aData
, sRight
.nData
, sRight
.aData
, &sOut
.n
, &sOut
.p
1017 if( rc
!=SQLITE_OK
){
1018 rc
= test_session_error(interp
, rc
, 0);
1020 assert_changeset_is_ok(sOut
.n
, sOut
.p
);
1021 Tcl_SetObjResult(interp
,Tcl_NewByteArrayObj((unsigned char*)sOut
.p
,sOut
.n
));
1023 sqlite3_free(sOut
.p
);
1028 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT
1030 static int SQLITE_TCLAPI
test_sqlite3session_foreach(
1034 Tcl_Obj
*CONST objv
[]
1038 sqlite3_changeset_iter
*pIter
;
1043 int isCheckNext
= 0;
1046 TestStreamInput sStr
;
1047 memset(&sStr
, 0, sizeof(sStr
));
1050 char *zOpt
= Tcl_GetString(objv
[1]);
1051 int nOpt
= strlen(zOpt
);
1052 if( zOpt
[0]!='-' ) break;
1053 if( nOpt
<=7 && 0==sqlite3_strnicmp(zOpt
, "-invert", nOpt
) ){
1056 if( nOpt
<=5 && 0==sqlite3_strnicmp(zOpt
, "-next", nOpt
) ){
1066 interp
, 1, objv
, "?-next? ?-invert? VARNAME CHANGESET SCRIPT");
1074 pChangeset
= (void *)Tcl_GetByteArrayFromObj(pCS
, &nChangeset
);
1075 sStr
.nStream
= test_tcl_integer(interp
, SESSION_STREAM_TCL_VAR
);
1077 int f
= SQLITE_CHANGESETSTART_INVERT
;
1078 if( sStr
.nStream
==0 ){
1079 rc
= sqlite3changeset_start_v2(&pIter
, nChangeset
, pChangeset
, f
);
1081 void *pCtx
= (void*)&sStr
;
1082 sStr
.aData
= (unsigned char*)pChangeset
;
1083 sStr
.nData
= nChangeset
;
1084 rc
= sqlite3changeset_start_v2_strm(&pIter
, testStreamInput
, pCtx
, f
);
1087 if( sStr
.nStream
==0 ){
1088 rc
= sqlite3changeset_start(&pIter
, nChangeset
, pChangeset
);
1090 sStr
.aData
= (unsigned char*)pChangeset
;
1091 sStr
.nData
= nChangeset
;
1092 rc
= sqlite3changeset_start_strm(&pIter
, testStreamInput
, (void*)&sStr
);
1095 if( rc
!=SQLITE_OK
){
1096 return test_session_error(interp
, rc
, 0);
1099 while( SQLITE_ROW
==sqlite3changeset_next(pIter
) ){
1100 int nCol
; /* Number of columns in table */
1101 int nCol2
; /* Number of columns in table */
1102 int op
; /* SQLITE_INSERT, UPDATE or DELETE */
1103 const char *zTab
; /* Name of table change applies to */
1104 Tcl_Obj
*pVar
; /* Tcl value to set $VARNAME to */
1105 Tcl_Obj
*pOld
; /* Vector of old.* values */
1106 Tcl_Obj
*pNew
; /* Vector of new.* values */
1110 unsigned char *abPK
;
1113 /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
1116 if( SQLITE_MISUSE
!=sqlite3changeset_fk_conflicts(pIter
, &nDummy
) ){
1117 sqlite3changeset_finalize(pIter
);
1121 sqlite3changeset_op(pIter
, &zTab
, &nCol
, &op
, &bIndirect
);
1122 pVar
= Tcl_NewObj();
1123 Tcl_ListObjAppendElement(0, pVar
, Tcl_NewStringObj(
1124 op
==SQLITE_INSERT
? "INSERT" :
1125 op
==SQLITE_UPDATE
? "UPDATE" :
1129 Tcl_ListObjAppendElement(0, pVar
, Tcl_NewStringObj(zTab
, -1));
1130 Tcl_ListObjAppendElement(0, pVar
, Tcl_NewBooleanObj(bIndirect
));
1132 zPK
= ckalloc(nCol
+1);
1133 memset(zPK
, 0, nCol
+1);
1134 sqlite3changeset_pk(pIter
, &abPK
, &nCol2
);
1135 assert( nCol
==nCol2
);
1136 for(i
=0; i
<nCol
; i
++){
1137 zPK
[i
] = (abPK
[i
] ? 'X' : '.');
1139 Tcl_ListObjAppendElement(0, pVar
, Tcl_NewStringObj(zPK
, -1));
1142 pOld
= Tcl_NewObj();
1143 if( op
!=SQLITE_INSERT
){
1144 for(i
=0; i
<nCol
; i
++){
1145 sqlite3_value
*pVal
;
1146 sqlite3changeset_old(pIter
, i
, &pVal
);
1147 test_append_value(pOld
, pVal
);
1150 pNew
= Tcl_NewObj();
1151 if( op
!=SQLITE_DELETE
){
1152 for(i
=0; i
<nCol
; i
++){
1153 sqlite3_value
*pVal
;
1154 sqlite3changeset_new(pIter
, i
, &pVal
);
1155 test_append_value(pNew
, pVal
);
1158 Tcl_ListObjAppendElement(0, pVar
, pOld
);
1159 Tcl_ListObjAppendElement(0, pVar
, pNew
);
1161 Tcl_ObjSetVar2(interp
, pVarname
, 0, pVar
, 0);
1162 rc
= Tcl_EvalObjEx(interp
, pScript
, 0);
1163 if( rc
!=TCL_OK
&& rc
!=TCL_CONTINUE
){
1164 sqlite3changeset_finalize(pIter
);
1165 return rc
==TCL_BREAK
? TCL_OK
: rc
;
1170 int rc2
= sqlite3changeset_next(pIter
);
1171 rc
= sqlite3changeset_finalize(pIter
);
1172 assert( (rc2
==SQLITE_DONE
&& rc
==SQLITE_OK
) || rc2
==rc
);
1174 rc
= sqlite3changeset_finalize(pIter
);
1176 if( rc
!=SQLITE_OK
){
1177 return test_session_error(interp
, rc
, 0);
1184 ** tclcmd: CMD configure REBASE-BLOB
1185 ** tclcmd: CMD rebase CHANGESET
1186 ** tclcmd: CMD delete
1188 static int SQLITE_TCLAPI
test_rebaser_cmd(
1192 Tcl_Obj
*CONST objv
[]
1194 static struct RebaseSubcmd
{
1200 { "configure", 1, "REBASE-BLOB" }, /* 0 */
1201 { "delete", 0, "" }, /* 1 */
1202 { "rebase", 1, "CHANGESET" }, /* 2 */
1206 sqlite3_rebaser
*p
= (sqlite3_rebaser
*)clientData
;
1211 Tcl_WrongNumArgs(interp
, 1, objv
, "SUBCOMMAND ...");
1214 rc
= Tcl_GetIndexFromObjStruct(interp
,
1215 objv
[1], aSub
, sizeof(aSub
[0]), "sub-command", 0, &iSub
1217 if( rc
!=TCL_OK
) return rc
;
1218 if( objc
!=2+aSub
[iSub
].nArg
){
1219 Tcl_WrongNumArgs(interp
, 2, objv
, aSub
[iSub
].zMsg
);
1223 assert( iSub
==0 || iSub
==1 || iSub
==2 );
1224 assert( rc
==SQLITE_OK
);
1226 case 0: { /* configure */
1228 unsigned char *pRebase
= Tcl_GetByteArrayFromObj(objv
[2], &nRebase
);
1229 rc
= sqlite3rebaser_configure(p
, nRebase
, pRebase
);
1233 case 1: /* delete */
1234 Tcl_DeleteCommand(interp
, Tcl_GetString(objv
[0]));
1237 default: { /* rebase */
1238 TestStreamInput sStr
; /* Input stream */
1239 TestSessionsBlob sOut
; /* Output blob */
1241 memset(&sStr
, 0, sizeof(sStr
));
1242 memset(&sOut
, 0, sizeof(sOut
));
1243 sStr
.aData
= Tcl_GetByteArrayFromObj(objv
[2], &sStr
.nData
);
1244 sStr
.nStream
= test_tcl_integer(interp
, SESSION_STREAM_TCL_VAR
);
1247 rc
= sqlite3rebaser_rebase_strm(p
,
1248 testStreamInput
, (void*)&sStr
,
1249 testStreamOutput
, (void*)&sOut
1252 rc
= sqlite3rebaser_rebase(p
, sStr
.nData
, sStr
.aData
, &sOut
.n
, &sOut
.p
);
1255 if( rc
==SQLITE_OK
){
1256 assert_changeset_is_ok(sOut
.n
, sOut
.p
);
1257 Tcl_SetObjResult(interp
, Tcl_NewByteArrayObj(sOut
.p
, sOut
.n
));
1259 sqlite3_free(sOut
.p
);
1264 if( rc
!=SQLITE_OK
){
1265 return test_session_error(interp
, rc
, 0);
1270 static void SQLITE_TCLAPI
test_rebaser_del(void *clientData
){
1271 sqlite3_rebaser
*p
= (sqlite3_rebaser
*)clientData
;
1272 sqlite3rebaser_delete(p
);
1276 ** tclcmd: sqlite3rebaser_create NAME
1278 static int SQLITE_TCLAPI
test_sqlite3rebaser_create(
1282 Tcl_Obj
*CONST objv
[]
1285 sqlite3_rebaser
*pNew
= 0;
1287 Tcl_WrongNumArgs(interp
, 1, objv
, "NAME");
1288 return SQLITE_ERROR
;
1291 rc
= sqlite3rebaser_create(&pNew
);
1292 if( rc
!=SQLITE_OK
){
1293 return test_session_error(interp
, rc
, 0);
1296 Tcl_CreateObjCommand(interp
, Tcl_GetString(objv
[1]), test_rebaser_cmd
,
1297 (ClientData
)pNew
, test_rebaser_del
1299 Tcl_SetObjResult(interp
, objv
[1]);
1306 static int sqlite3_test_changeset(
1311 sqlite3_changeset_iter
*pIter
= 0;
1314 int bPatch
= (nChangeset
>0 && ((char*)pChangeset
)[0]=='P');
1316 rc
= sqlite3changeset_start(&pIter
, nChangeset
, pChangeset
);
1317 if( rc
==SQLITE_OK
){
1319 while( rc
==SQLITE_OK
&& SQLITE_ROW
==sqlite3changeset_next(pIter
) ){
1320 unsigned char *aPk
= 0;
1323 const char *zTab
= 0;
1325 sqlite3changeset_pk(pIter
, &aPk
, &nCol
);
1326 sqlite3changeset_op(pIter
, &zTab
, &nCol
, &op
, 0);
1328 if( op
==SQLITE_UPDATE
){
1330 for(iCol
=0; iCol
<nCol
; iCol
++){
1331 sqlite3_value
*pNew
= 0;
1332 sqlite3_value
*pOld
= 0;
1333 sqlite3changeset_new(pIter
, iCol
, &pNew
);
1334 sqlite3changeset_old(pIter
, iCol
, &pOld
);
1337 if( pOld
==0 ) rc
= SQLITE_ERROR
;
1339 if( pOld
) rc
= SQLITE_ERROR
;
1341 if( (pOld
==0)!=(pNew
==0) ) rc
= SQLITE_ERROR
;
1344 if( rc
!=SQLITE_OK
){
1345 zErr
= sqlite3_mprintf(
1346 "unexpected SQLITE_UPDATE (bPatch=%d pk=%d pOld=%d pNew=%d)",
1347 bPatch
, (int)aPk
[iCol
], pOld
!=0, pNew
!=0
1354 rc2
= sqlite3changeset_finalize(pIter
);
1355 if( rc
==SQLITE_OK
){
1365 ** test_changeset CHANGESET
1367 static int SQLITE_TCLAPI
test_changeset(
1371 Tcl_Obj
*CONST objv
[]
1373 void *pChangeset
= 0; /* Buffer containing changeset */
1374 int nChangeset
= 0; /* Size of buffer aChangeset in bytes */
1379 Tcl_WrongNumArgs(interp
, 1, objv
, "CHANGESET");
1382 pChangeset
= (void *)Tcl_GetByteArrayFromObj(objv
[1], &nChangeset
);
1384 Tcl_ResetResult(interp
);
1385 rc
= sqlite3_test_changeset(nChangeset
, pChangeset
, &z
);
1386 if( rc
!=SQLITE_OK
){
1387 char *zErr
= sqlite3_mprintf("(%d) - \"%s\"", rc
, z
);
1388 Tcl_SetObjResult(interp
, Tcl_NewStringObj(zErr
, -1));
1393 return rc
? TCL_ERROR
: TCL_OK
;
1397 ** tclcmd: sqlite3rebaser_configure OP VALUE
1399 static int SQLITE_TCLAPI
test_sqlite3session_config(
1403 Tcl_Obj
*CONST objv
[]
1405 static struct ConfigOpt
{
1409 { "strm_size", SQLITE_SESSION_CONFIG_STRMSIZE
},
1418 Tcl_WrongNumArgs(interp
, 1, objv
, "OP VALUE");
1419 return SQLITE_ERROR
;
1421 rc
= Tcl_GetIndexFromObjStruct(interp
,
1422 objv
[1], aSub
, sizeof(aSub
[0]), "sub-command", 0, &iSub
1424 if( rc
!=TCL_OK
) return rc
;
1425 if( Tcl_GetIntFromObj(interp
, objv
[2], &iVal
) ) return TCL_ERROR
;
1427 rc
= sqlite3session_config(aSub
[iSub
].op
, (void*)&iVal
);
1428 if( rc
!=SQLITE_OK
){
1429 return test_session_error(interp
, rc
, 0);
1431 Tcl_SetObjResult(interp
, Tcl_NewIntObj(iVal
));
1435 int TestSession_Init(Tcl_Interp
*interp
){
1438 Tcl_ObjCmdProc
*xProc
;
1440 { "sqlite3session", test_sqlite3session
},
1441 { "sqlite3session_foreach", test_sqlite3session_foreach
},
1442 { "sqlite3changeset_invert", test_sqlite3changeset_invert
},
1443 { "sqlite3changeset_concat", test_sqlite3changeset_concat
},
1444 { "sqlite3changeset_apply", test_sqlite3changeset_apply
},
1445 { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2
},
1446 { "sqlite3changeset_apply_replace_all",
1447 test_sqlite3changeset_apply_replace_all
},
1448 { "sql_exec_changeset", test_sql_exec_changeset
},
1449 { "sqlite3rebaser_create", test_sqlite3rebaser_create
},
1450 { "sqlite3session_config", test_sqlite3session_config
},
1451 { "test_changeset", test_changeset
},
1455 for(i
=0; i
<sizeof(aCmd
)/sizeof(struct Cmd
); i
++){
1456 struct Cmd
*p
= &aCmd
[i
];
1457 Tcl_CreateObjCommand(interp
, p
->zCmd
, p
->xProc
, 0, 0);
1463 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */