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
){
106 (void)sqlite3_test_changeset(n
, p
, &z
);
110 # define assert_changeset_is_ok(n,p)
114 ** Tclcmd: sql_exec_changeset DB SQL
116 static int SQLITE_TCLAPI
test_sql_exec_changeset(
120 Tcl_Obj
*CONST objv
[]
129 Tcl_WrongNumArgs(interp
, 1, objv
, "DB SQL");
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
);
137 Tcl_ResetResult(interp
);
138 Tcl_AppendResult(interp
, "error in sql_exec_changeset()", 0);
142 assert_changeset_is_ok(nChangeset
, pChangeset
);
143 Tcl_SetObjResult(interp
, Tcl_NewByteArrayObj(pChangeset
, nChangeset
));
144 sqlite3_free(pChangeset
);
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
){
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
);
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));
174 Tcl_AppendResult(interp
, " - ", zErr
, 0);
180 static int test_table_filter(void *pCtx
, const char *zTbl
){
181 TestSession
*p
= (TestSession
*)pCtx
;
186 pEval
= Tcl_DuplicateObj(p
->pFilterScript
);
187 Tcl_IncrRefCount(pEval
);
188 rc
= Tcl_ListObjAppendElement(p
->interp
, pEval
, Tcl_NewStringObj(zTbl
, -1));
190 rc
= Tcl_EvalObjEx(p
->interp
, pEval
, TCL_EVAL_GLOBAL
);
193 rc
= Tcl_GetBooleanFromObj(p
->interp
, Tcl_GetObjResult(p
->interp
), &bRes
);
196 /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */
197 Tcl_BackgroundError(p
->interp
);
199 Tcl_DecrRefCount(pEval
);
204 struct TestSessionsBlob
{
208 typedef struct TestSessionsBlob TestSessionsBlob
;
210 static int testStreamOutput(
215 TestSessionsBlob
*pBlob
= (TestSessionsBlob
*)pCtx
;
219 pNew
= (char*)sqlite3_realloc(pBlob
->p
, pBlob
->n
+ nData
);
223 pBlob
->p
= (void*)pNew
;
224 memcpy(&pNew
[pBlob
->n
], pData
, nData
);
230 ** Tclcmd: $session attach TABLE
231 ** $session changeset
233 ** $session enable BOOL
234 ** $session indirect INTEGER
236 ** $session table_filter SCRIPT
238 static int SQLITE_TCLAPI
test_session_cmd(
242 Tcl_Obj
*CONST objv
[]
244 TestSession
*p
= (TestSession
*)clientData
;
245 sqlite3_session
*pSession
= p
->pSession
;
246 static struct SessionSubcmd
{
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 */
270 Tcl_WrongNumArgs(interp
, 1, objv
, "SUBCOMMAND ...");
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
);
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
);
288 return test_session_error(interp
, rc
, 0);
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
;
299 rc
= sqlite3session_patchset_strm(pSession
, testStreamOutput
, pCtx
);
301 rc
= sqlite3session_changeset_strm(pSession
, testStreamOutput
, pCtx
);
305 rc
= sqlite3session_patchset(pSession
, &o
.n
, &o
.p
);
307 rc
= sqlite3session_changeset(pSession
, &o
.n
, &o
.p
);
311 assert_changeset_is_ok(o
.n
, o
.p
);
312 Tcl_SetObjResult(interp
, Tcl_NewByteArrayObj(o
.p
, o
.n
));
316 return test_session_error(interp
, rc
, 0);
322 Tcl_DeleteCommand(interp
, Tcl_GetString(objv
[0]));
325 case 3: { /* enable */
327 if( Tcl_GetIntFromObj(interp
, objv
[2], &val
) ) return TCL_ERROR
;
328 val
= sqlite3session_enable(pSession
, val
);
329 Tcl_SetObjResult(interp
, Tcl_NewBooleanObj(val
));
333 case 4: { /* indirect */
335 if( Tcl_GetIntFromObj(interp
, objv
[2], &val
) ) return TCL_ERROR
;
336 val
= sqlite3session_indirect(pSession
, val
);
337 Tcl_SetObjResult(interp
, Tcl_NewBooleanObj(val
));
341 case 5: { /* isempty */
343 val
= sqlite3session_isempty(pSession
);
344 Tcl_SetObjResult(interp
, Tcl_NewBooleanObj(val
));
348 case 6: { /* table_filter */
349 if( p
->pFilterScript
) Tcl_DecrRefCount(p
->pFilterScript
);
351 p
->pFilterScript
= Tcl_DuplicateObj(objv
[2]);
352 Tcl_IncrRefCount(p
->pFilterScript
);
353 sqlite3session_table_filter(pSession
, test_table_filter
, clientData
);
359 rc
= sqlite3session_diff(pSession
,
360 Tcl_GetString(objv
[2]),
361 Tcl_GetString(objv
[3]),
364 assert( rc
!=SQLITE_OK
|| zErr
==0 );
366 return test_session_error(interp
, rc
, zErr
);
371 case 9: { /* memory_used */
372 sqlite3_int64 nMalloc
= sqlite3session_memory_used(pSession
);
373 Tcl_SetObjResult(interp
, Tcl_NewWideIntObj(nMalloc
));
378 sqlite3_int64 nSize
= sqlite3session_changeset_size(pSession
);
379 Tcl_SetObjResult(interp
, Tcl_NewWideIntObj(nSize
));
385 if( Tcl_GetIntFromObj(interp
, objv
[2], &iArg
) ){
388 rc
= sqlite3session_object_config(
389 pSession
, SQLITE_SESSION_OBJCONFIG_SIZE
, &iArg
392 extern const char *sqlite3ErrName(int);
393 Tcl_SetObjResult(interp
, Tcl_NewStringObj(sqlite3ErrName(rc
), -1));
395 Tcl_SetObjResult(interp
, Tcl_NewIntObj(iArg
));
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
);
412 ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME
414 static int SQLITE_TCLAPI
test_sqlite3session(
418 Tcl_Obj
*CONST objv
[]
422 int rc
; /* sqlite3session_create() return code */
423 TestSession
*p
; /* New wrapper object */
427 Tcl_WrongNumArgs(interp
, 1, objv
, "CMD DB-HANDLE DB-NAME");
431 if( 0==Tcl_GetCommandInfo(interp
, Tcl_GetString(objv
[2]), &info
) ){
432 Tcl_AppendResult(interp
, "no such handle: ", Tcl_GetString(objv
[2]), 0);
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
);
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
);
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
,
456 Tcl_SetObjResult(interp
, objv
[1]);
460 static void test_append_value(Tcl_Obj
*pList
, sqlite3_value
*pVal
){
462 Tcl_ListObjAppendElement(0, pList
, Tcl_NewObj());
463 Tcl_ListObjAppendElement(0, pList
, Tcl_NewObj());
466 switch( sqlite3_value_type(pVal
) ){
468 Tcl_ListObjAppendElement(0, pList
, Tcl_NewStringObj("n", 1));
472 Tcl_ListObjAppendElement(0, pList
, Tcl_NewStringObj("i", 1));
473 pObj
= Tcl_NewWideIntObj(sqlite3_value_int64(pVal
));
476 Tcl_ListObjAppendElement(0, pList
, Tcl_NewStringObj("f", 1));
477 pObj
= Tcl_NewDoubleObj(sqlite3_value_double(pVal
));
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
);
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
)
495 Tcl_ListObjAppendElement(0, pList
, pObj
);
499 typedef struct TestConflictHandler TestConflictHandler
;
500 struct TestConflictHandler
{
502 Tcl_Obj
*pConflictScript
;
503 Tcl_Obj
*pFilterScript
;
506 static int test_obj_eq_string(Tcl_Obj
*p
, const char *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
;
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
);
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
;
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
){
561 sqlite3changeset_fk_conflicts(pIter
, &nFk
);
562 Tcl_ListObjAppendElement(0, pEval
, Tcl_NewStringObj("FOREIGN_KEY", -1));
563 Tcl_ListObjAppendElement(0, pEval
, Tcl_NewIntObj(nFk
));
566 /* Append the operation type. */
567 Tcl_ListObjAppendElement(0, pEval
, Tcl_NewStringObj(
568 op
==SQLITE_INSERT
? "INSERT" :
569 op
==SQLITE_UPDATE
? "UPDATE" :
573 /* Append the table name. */
574 Tcl_ListObjAppendElement(0, pEval
, Tcl_NewStringObj(zTab
, -1));
576 /* Append the conflict type. */
578 case SQLITE_CHANGESET_DATA
:
579 Tcl_ListObjAppendElement(interp
, pEval
,Tcl_NewStringObj("DATA",-1));
581 case SQLITE_CHANGESET_NOTFOUND
:
582 Tcl_ListObjAppendElement(interp
, pEval
,Tcl_NewStringObj("NOTFOUND",-1));
584 case SQLITE_CHANGESET_CONFLICT
:
585 Tcl_ListObjAppendElement(interp
, pEval
,Tcl_NewStringObj("CONFLICT",-1));
587 case SQLITE_CHANGESET_CONSTRAINT
:
588 Tcl_ListObjAppendElement(interp
, pEval
,Tcl_NewStringObj("CONSTRAINT",-1));
592 /* If this is not an INSERT, append the old row */
593 if( op
!=SQLITE_INSERT
){
595 Tcl_Obj
*pOld
= Tcl_NewObj();
596 for(i
=0; i
<nCol
; i
++){
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
){
607 Tcl_Obj
*pNew
= Tcl_NewObj();
608 for(i
=0; i
<nCol
; i
++){
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
){
620 Tcl_Obj
*pConflict
= Tcl_NewObj();
621 for(i
=0; i
<nCol
; i
++){
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
638 int rc
= sqlite3changeset_conflict(pIter
, 0, &pVal
);
639 assert( rc
==SQLITE_MISUSE
);
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
){
649 int rc
= sqlite3changeset_new(pIter
, 0, &pVal
);
650 assert( rc
==SQLITE_MISUSE
);
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
){
660 int rc
= sqlite3changeset_old(pIter
, 0, &pVal
);
661 assert( rc
==SQLITE_MISUSE
);
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. */
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
);
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
;
691 Tcl_GetIntFromObj(0, pRes
, &ret
);
695 Tcl_DecrRefCount(pEval
);
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 */
718 sqlite3changeset_op(pIter
, &zTab
, &nCol
, &op
, 0);
720 if( op
!=SQLITE_INSERT
){
721 for(i
=0; i
<nCol
; i
++){
723 sqlite3changeset_old(pIter
, i
, &pVal
);
724 sqlite3_value_text16(pVal
);
729 if( op
!=SQLITE_DELETE
){
730 for(i
=0; i
<nCol
; i
++){
732 sqlite3changeset_new(pIter
, i
, &pVal
);
733 sqlite3_value_text16(pVal
);
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
;
766 memcpy(pData
, &p
->aData
[p
->iData
], nRet
);
775 static int SQLITE_TCLAPI
testSqlite3changesetApply(
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
;
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 */
799 const char *z1
= Tcl_GetString(objv
[1]);
801 if( n
>1 && n
<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1
, n
) ){
802 flags
|= SQLITE_CHANGESETAPPLY_NOSAVEPOINT
;
808 const char *z1
= Tcl_GetString(objv
[1]);
810 if( n
>1 && n
<=7 && 0==sqlite3_strnicmp("-invert", z1
, n
) ){
811 flags
|= SQLITE_CHANGESETAPPLY_INVERT
;
818 if( objc
!=4 && objc
!=5 ){
821 zMsg
= "?-nosavepoint? ?-inverse? "
822 "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
824 zMsg
= "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
826 Tcl_WrongNumArgs(interp
, 1, objv
, zMsg
);
829 if( 0==Tcl_GetCommandInfo(interp
, Tcl_GetString(objv
[1]), &info
) ){
830 Tcl_AppendResult(interp
, "no such handle: ", Tcl_GetString(objv
[1]), 0);
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;
839 if( sStr
.nStream
==0 ){
841 rc
= sqlite3changeset_apply(db
, nChangeset
, pChangeset
,
842 (objc
==5)?test_filter_handler
:0, test_conflict_handler
, (void *)&ctx
845 rc
= sqlite3changeset_apply_v2(db
, nChangeset
, pChangeset
,
846 (objc
==5)?test_filter_handler
:0, test_conflict_handler
, (void *)&ctx
,
847 &pRebase
, &nRebase
, flags
851 sStr
.aData
= (unsigned char*)pChangeset
;
852 sStr
.nData
= nChangeset
;
854 rc
= sqlite3changeset_apply_strm(db
, testStreamInput
, (void*)&sStr
,
855 (objc
==5) ? test_filter_handler
: 0,
856 test_conflict_handler
, (void *)&ctx
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
868 return test_session_error(interp
, rc
, 0);
870 Tcl_ResetResult(interp
);
871 if( bV2
&& pRebase
){
872 Tcl_SetObjResult(interp
, Tcl_NewByteArrayObj(pRebase
, nRebase
));
875 sqlite3_free(pRebase
);
880 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
882 static int SQLITE_TCLAPI
test_sqlite3changeset_apply(
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(
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(
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 */
918 Tcl_WrongNumArgs(interp
, 1, objv
, "DB CHANGESET");
921 if( 0==Tcl_GetCommandInfo(interp
, Tcl_GetString(objv
[1]), &info
) ){
922 Tcl_AppendResult(interp
, "no such handle: ", Tcl_GetString(objv
[2]), 0);
925 db
= *(sqlite3
**)info
.objClientData
;
926 pChangeset
= (void *)Tcl_GetByteArrayFromObj(objv
[2], &nChangeset
);
928 rc
= sqlite3changeset_apply(db
, nChangeset
, pChangeset
, 0, replace_handler
,0);
930 return test_session_error(interp
, rc
, 0);
932 Tcl_ResetResult(interp
);
938 ** sqlite3changeset_invert CHANGESET
940 static int SQLITE_TCLAPI
test_sqlite3changeset_invert(
944 Tcl_Obj
*CONST objv
[]
946 int rc
; /* Return code from changeset_invert() */
947 TestStreamInput sIn
; /* Input stream */
948 TestSessionsBlob sOut
; /* Output blob */
951 Tcl_WrongNumArgs(interp
, 1, objv
, "CHANGESET");
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
);
961 rc
= sqlite3changeset_invert_strm(
962 testStreamInput
, (void*)&sIn
, testStreamOutput
, (void*)&sOut
965 rc
= sqlite3changeset_invert(sIn
.nData
, sIn
.aData
, &sOut
.n
, &sOut
.p
);
968 rc
= test_session_error(interp
, rc
, 0);
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
);
978 ** sqlite3changeset_concat LEFT RIGHT
980 static int SQLITE_TCLAPI
test_sqlite3changeset_concat(
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 */
993 Tcl_WrongNumArgs(interp
, 1, objv
, "LEFT RIGHT");
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
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);
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
);
1027 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT
1029 static int SQLITE_TCLAPI
test_sqlite3session_foreach(
1033 Tcl_Obj
*CONST objv
[]
1037 sqlite3_changeset_iter
*pIter
;
1042 int isCheckNext
= 0;
1045 TestStreamInput sStr
;
1046 memset(&sStr
, 0, sizeof(sStr
));
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
) ){
1055 if( nOpt
<=5 && 0==sqlite3_strnicmp(zOpt
, "-next", nOpt
) ){
1065 interp
, 1, objv
, "?-next? ?-invert? VARNAME CHANGESET SCRIPT");
1073 pChangeset
= (void *)Tcl_GetByteArrayFromObj(pCS
, &nChangeset
);
1074 sStr
.nStream
= test_tcl_integer(interp
, SESSION_STREAM_TCL_VAR
);
1076 int f
= SQLITE_CHANGESETSTART_INVERT
;
1077 if( sStr
.nStream
==0 ){
1078 rc
= sqlite3changeset_start_v2(&pIter
, nChangeset
, pChangeset
, f
);
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
);
1086 if( sStr
.nStream
==0 ){
1087 rc
= sqlite3changeset_start(&pIter
, nChangeset
, pChangeset
);
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 */
1109 unsigned char *abPK
;
1112 /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
1115 if( SQLITE_MISUSE
!=sqlite3changeset_fk_conflicts(pIter
, &nDummy
) ){
1116 sqlite3changeset_finalize(pIter
);
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" :
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));
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
;
1169 int rc2
= sqlite3changeset_next(pIter
);
1170 rc
= sqlite3changeset_finalize(pIter
);
1171 assert( (rc2
==SQLITE_DONE
&& rc
==SQLITE_OK
) || rc2
==rc
);
1173 rc
= sqlite3changeset_finalize(pIter
);
1175 if( rc
!=SQLITE_OK
){
1176 return test_session_error(interp
, rc
, 0);
1183 ** tclcmd: CMD configure REBASE-BLOB
1184 ** tclcmd: CMD rebase CHANGESET
1185 ** tclcmd: CMD delete
1187 static int SQLITE_TCLAPI
test_rebaser_cmd(
1191 Tcl_Obj
*CONST objv
[]
1193 static struct RebaseSubcmd
{
1199 { "configure", 1, "REBASE-BLOB" }, /* 0 */
1200 { "delete", 0, "" }, /* 1 */
1201 { "rebase", 1, "CHANGESET" }, /* 2 */
1205 sqlite3_rebaser
*p
= (sqlite3_rebaser
*)clientData
;
1210 Tcl_WrongNumArgs(interp
, 1, objv
, "SUBCOMMAND ...");
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
);
1222 assert( iSub
==0 || iSub
==1 || iSub
==2 );
1223 assert( rc
==SQLITE_OK
);
1225 case 0: { /* configure */
1227 unsigned char *pRebase
= Tcl_GetByteArrayFromObj(objv
[2], &nRebase
);
1228 rc
= sqlite3rebaser_configure(p
, nRebase
, pRebase
);
1232 case 1: /* delete */
1233 Tcl_DeleteCommand(interp
, Tcl_GetString(objv
[0]));
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
);
1246 rc
= sqlite3rebaser_rebase_strm(p
,
1247 testStreamInput
, (void*)&sStr
,
1248 testStreamOutput
, (void*)&sOut
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
);
1263 if( rc
!=SQLITE_OK
){
1264 return test_session_error(interp
, rc
, 0);
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(
1281 Tcl_Obj
*CONST objv
[]
1284 sqlite3_rebaser
*pNew
= 0;
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]);
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(
1318 sqlite3_changeset_iter
*pIter
= 0;
1321 int bPatch
= (nChangeset
>0 && ((char*)pChangeset
)[0]=='P');
1323 rc
= sqlite3changeset_start(&pIter
, nChangeset
, pChangeset
);
1324 if( rc
==SQLITE_OK
){
1326 while( rc
==SQLITE_OK
&& SQLITE_ROW
==sqlite3changeset_next(pIter
) ){
1327 unsigned char *aPk
= 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
){
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
);
1344 if( pOld
==0 ) rc
= SQLITE_ERROR
;
1346 if( pOld
) rc
= SQLITE_ERROR
;
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
1361 rc2
= sqlite3changeset_finalize(pIter
);
1362 if( rc
==SQLITE_OK
){
1372 ** test_changeset CHANGESET
1374 static int SQLITE_TCLAPI
test_changeset(
1378 Tcl_Obj
*CONST objv
[]
1380 void *pChangeset
= 0; /* Buffer containing changeset */
1381 int nChangeset
= 0; /* Size of buffer aChangeset in bytes */
1386 Tcl_WrongNumArgs(interp
, 1, objv
, "CHANGESET");
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));
1400 return rc
? TCL_ERROR
: TCL_OK
;
1404 ** tclcmd: sqlite3rebaser_configure OP VALUE
1406 static int SQLITE_TCLAPI
test_sqlite3session_config(
1410 Tcl_Obj
*CONST objv
[]
1412 static struct ConfigOpt
{
1416 { "strm_size", SQLITE_SESSION_CONFIG_STRMSIZE
},
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
));
1442 int TestSession_Init(Tcl_Interp
*interp
){
1445 Tcl_ObjCmdProc
*xProc
;
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
},
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);
1470 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */