Snapshot of upstream SQLite 3.40.1
[sqlcipher.git] / ext / session / test_session.c
blobc1feb7802c7d25f35b2d46041cd538460404d3ed
2 #if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \
3 && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
5 #include "sqlite3session.h"
6 #include <assert.h>
7 #include <string.h>
8 #if defined(INCLUDE_SQLITE_TCL_H)
9 # include "sqlite_tcl.h"
10 #else
11 # include "tcl.h"
12 # ifndef SQLITE_TCLAPI
13 # define SQLITE_TCLAPI
14 # endif
15 #endif
17 #ifndef SQLITE_AMALGAMATION
18 typedef unsigned char u8;
19 #endif
21 typedef struct TestSession TestSession;
22 struct TestSession {
23 sqlite3_session *pSession;
24 Tcl_Interp *interp;
25 Tcl_Obj *pFilterScript;
28 typedef struct TestStreamInput TestStreamInput;
29 struct TestStreamInput {
30 int nStream; /* Maximum chunk size */
31 unsigned char *aData; /* Pointer to buffer containing data */
32 int nData; /* Size of buffer aData in bytes */
33 int iData; /* Bytes of data already read by sessions */
37 ** Extract an sqlite3* db handle from the object passed as the second
38 ** argument. If successful, set *pDb to point to the db handle and return
39 ** TCL_OK. Otherwise, return TCL_ERROR.
41 static int dbHandleFromObj(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){
42 Tcl_CmdInfo info;
43 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){
44 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(pObj), 0);
45 return TCL_ERROR;
48 *pDb = *(sqlite3 **)info.objClientData;
49 return TCL_OK;
52 /*************************************************************************
53 ** The following code is copied byte-for-byte from the sessions module
54 ** documentation. It is used by some of the sessions modules tests to
55 ** ensure that the example in the documentation does actually work.
56 */
58 ** Argument zSql points to a buffer containing an SQL script to execute
59 ** against the database handle passed as the first argument. As well as
60 ** executing the SQL script, this function collects a changeset recording
61 ** all changes made to the "main" database file. Assuming no error occurs,
62 ** output variables (*ppChangeset) and (*pnChangeset) are set to point
63 ** to a buffer containing the changeset and the size of the changeset in
64 ** bytes before returning SQLITE_OK. In this case it is the responsibility
65 ** of the caller to eventually free the changeset blob by passing it to
66 ** the sqlite3_free function.
68 ** Or, if an error does occur, return an SQLite error code. The final
69 ** value of (*pChangeset) and (*pnChangeset) are undefined in this case.
71 int sql_exec_changeset(
72 sqlite3 *db, /* Database handle */
73 const char *zSql, /* SQL script to execute */
74 int *pnChangeset, /* OUT: Size of changeset blob in bytes */
75 void **ppChangeset /* OUT: Pointer to changeset blob */
77 sqlite3_session *pSession = 0;
78 int rc;
80 /* Create a new session object */
81 rc = sqlite3session_create(db, "main", &pSession);
83 /* Configure the session object to record changes to all tables */
84 if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL);
86 /* Execute the SQL script */
87 if( rc==SQLITE_OK ) rc = sqlite3_exec(db, zSql, 0, 0, 0);
89 /* Collect the changeset */
90 if( rc==SQLITE_OK ){
91 rc = sqlite3session_changeset(pSession, pnChangeset, ppChangeset);
94 /* Delete the session object */
95 sqlite3session_delete(pSession);
97 return rc;
99 /************************************************************************/
102 #ifdef SQLITE_DEBUG
103 static int sqlite3_test_changeset(int, void *, char **);
104 static void assert_changeset_is_ok(int n, void *p){
105 int rc = 0;
106 char *z = 0;
107 rc = sqlite3_test_changeset(n, p, &z);
108 assert( z==0 );
110 #else
111 # define assert_changeset_is_ok(n,p)
112 #endif
115 ** Tclcmd: sql_exec_changeset DB SQL
117 static int SQLITE_TCLAPI test_sql_exec_changeset(
118 void * clientData,
119 Tcl_Interp *interp,
120 int objc,
121 Tcl_Obj *CONST objv[]
123 const char *zSql;
124 sqlite3 *db;
125 void *pChangeset;
126 int nChangeset;
127 int rc;
129 if( objc!=3 ){
130 Tcl_WrongNumArgs(interp, 1, objv, "DB SQL");
131 return TCL_ERROR;
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);
137 if( rc!=SQLITE_OK ){
138 Tcl_ResetResult(interp);
139 Tcl_AppendResult(interp, "error in sql_exec_changeset()", 0);
140 return TCL_ERROR;
143 assert_changeset_is_ok(nChangeset, pChangeset);
144 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChangeset, nChangeset));
145 sqlite3_free(pChangeset);
146 return TCL_OK;
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){
161 Tcl_Obj *pObj;
162 int iVal = 0;
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);
168 return 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));
174 if( zErr ){
175 Tcl_AppendResult(interp, " - ", zErr, 0);
176 sqlite3_free(zErr);
178 return TCL_ERROR;
181 static int test_table_filter(void *pCtx, const char *zTbl){
182 TestSession *p = (TestSession*)pCtx;
183 Tcl_Obj *pEval;
184 int rc;
185 int bRes = 0;
187 pEval = Tcl_DuplicateObj(p->pFilterScript);
188 Tcl_IncrRefCount(pEval);
189 rc = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zTbl, -1));
190 if( rc==TCL_OK ){
191 rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
193 if( rc==TCL_OK ){
194 rc = Tcl_GetBooleanFromObj(p->interp, Tcl_GetObjResult(p->interp), &bRes);
196 if( rc!=TCL_OK ){
197 /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */
198 Tcl_BackgroundError(p->interp);
200 Tcl_DecrRefCount(pEval);
202 return bRes;
205 struct TestSessionsBlob {
206 void *p;
207 int n;
209 typedef struct TestSessionsBlob TestSessionsBlob;
211 static int testStreamOutput(
212 void *pCtx,
213 const void *pData,
214 int nData
216 TestSessionsBlob *pBlob = (TestSessionsBlob*)pCtx;
217 char *pNew;
219 assert( nData>0 );
220 pNew = (char*)sqlite3_realloc(pBlob->p, pBlob->n + nData);
221 if( pNew==0 ){
222 return SQLITE_NOMEM;
224 pBlob->p = (void*)pNew;
225 memcpy(&pNew[pBlob->n], pData, nData);
226 pBlob->n += nData;
227 return SQLITE_OK;
231 ** Tclcmd: $session attach TABLE
232 ** $session changeset
233 ** $session delete
234 ** $session enable BOOL
235 ** $session indirect INTEGER
236 ** $session patchset
237 ** $session table_filter SCRIPT
239 static int SQLITE_TCLAPI test_session_cmd(
240 void *clientData,
241 Tcl_Interp *interp,
242 int objc,
243 Tcl_Obj *CONST objv[]
245 TestSession *p = (TestSession*)clientData;
246 sqlite3_session *pSession = p->pSession;
247 static struct SessionSubcmd {
248 const char *zSub;
249 int nArg;
250 const char *zMsg;
251 int iSub;
252 } aSub[] = {
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 */
265 { 0 }
267 int iSub;
268 int rc;
270 if( objc<2 ){
271 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
272 return TCL_ERROR;
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);
280 return TCL_ERROR;
283 switch( iSub ){
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);
288 if( rc!=SQLITE_OK ){
289 return test_session_error(interp, rc, 0);
291 break;
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;
299 if( iSub==7 ){
300 rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx);
301 }else{
302 rc = sqlite3session_changeset_strm(pSession, testStreamOutput, pCtx);
304 }else{
305 if( iSub==7 ){
306 rc = sqlite3session_patchset(pSession, &o.n, &o.p);
307 }else{
308 rc = sqlite3session_changeset(pSession, &o.n, &o.p);
311 if( rc==SQLITE_OK ){
312 assert_changeset_is_ok(o.n, o.p);
313 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n));
315 sqlite3_free(o.p);
316 if( rc!=SQLITE_OK ){
317 return test_session_error(interp, rc, 0);
319 break;
322 case 2: /* delete */
323 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
324 break;
326 case 3: { /* enable */
327 int val;
328 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
329 val = sqlite3session_enable(pSession, val);
330 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
331 break;
334 case 4: { /* indirect */
335 int val;
336 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
337 val = sqlite3session_indirect(pSession, val);
338 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
339 break;
342 case 5: { /* isempty */
343 int val;
344 val = sqlite3session_isempty(pSession);
345 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
346 break;
349 case 6: { /* table_filter */
350 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
351 p->interp = interp;
352 p->pFilterScript = Tcl_DuplicateObj(objv[2]);
353 Tcl_IncrRefCount(p->pFilterScript);
354 sqlite3session_table_filter(pSession, test_table_filter, clientData);
355 break;
358 case 8: { /* diff */
359 char *zErr = 0;
360 rc = sqlite3session_diff(pSession,
361 Tcl_GetString(objv[2]),
362 Tcl_GetString(objv[3]),
363 &zErr
365 assert( rc!=SQLITE_OK || zErr==0 );
366 if( rc ){
367 return test_session_error(interp, rc, zErr);
369 break;
372 case 9: { /* memory_used */
373 sqlite3_int64 nMalloc = sqlite3session_memory_used(pSession);
374 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nMalloc));
375 break;
378 case 10: {
379 sqlite3_int64 nSize = sqlite3session_changeset_size(pSession);
380 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nSize));
381 break;
383 case 11: {
384 int rc;
385 int iArg;
386 if( Tcl_GetIntFromObj(interp, objv[2], &iArg) ){
387 return TCL_ERROR;
389 rc = sqlite3session_object_config(
390 pSession, SQLITE_SESSION_OBJCONFIG_SIZE, &iArg
392 if( rc!=SQLITE_OK ){
393 extern const char *sqlite3ErrName(int);
394 Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
395 }else{
396 Tcl_SetObjResult(interp, Tcl_NewIntObj(iArg));
398 break;
402 return TCL_OK;
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);
409 ckfree((char*)p);
413 ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME
415 static int SQLITE_TCLAPI test_sqlite3session(
416 void * clientData,
417 Tcl_Interp *interp,
418 int objc,
419 Tcl_Obj *CONST objv[]
421 sqlite3 *db;
422 Tcl_CmdInfo info;
423 int rc; /* sqlite3session_create() return code */
424 TestSession *p; /* New wrapper object */
425 int iArg = -1;
427 if( objc!=4 ){
428 Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME");
429 return TCL_ERROR;
432 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
433 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
434 return TCL_ERROR;
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);
441 if( rc!=SQLITE_OK ){
442 ckfree((char*)p);
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);
449 assert( iArg==0 );
450 iArg = 1;
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,
455 test_session_del
457 Tcl_SetObjResult(interp, objv[1]);
458 return TCL_OK;
461 static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){
462 if( pVal==0 ){
463 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
464 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
465 }else{
466 Tcl_Obj *pObj;
467 switch( sqlite3_value_type(pVal) ){
468 case SQLITE_NULL:
469 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("n", 1));
470 pObj = Tcl_NewObj();
471 break;
472 case SQLITE_INTEGER:
473 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("i", 1));
474 pObj = Tcl_NewWideIntObj(sqlite3_value_int64(pVal));
475 break;
476 case SQLITE_FLOAT:
477 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("f", 1));
478 pObj = Tcl_NewDoubleObj(sqlite3_value_double(pVal));
479 break;
480 case SQLITE_TEXT: {
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);
485 break;
487 default:
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)
494 break;
496 Tcl_ListObjAppendElement(0, pList, pObj);
500 typedef struct TestConflictHandler TestConflictHandler;
501 struct TestConflictHandler {
502 Tcl_Interp *interp;
503 Tcl_Obj *pConflictScript;
504 Tcl_Obj *pFilterScript;
507 static int test_obj_eq_string(Tcl_Obj *p, const char *z){
508 int n;
509 int nObj;
510 char *zObj;
512 n = (int)strlen(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;
523 int res = 1;
524 Tcl_Obj *pEval;
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);
538 return res;
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;
547 Tcl_Obj *pEval;
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 ){
561 int nFk;
562 sqlite3changeset_fk_conflicts(pIter, &nFk);
563 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj("FOREIGN_KEY", -1));
564 Tcl_ListObjAppendElement(0, pEval, Tcl_NewIntObj(nFk));
565 }else{
567 /* Append the operation type. */
568 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
569 op==SQLITE_INSERT ? "INSERT" :
570 op==SQLITE_UPDATE ? "UPDATE" :
571 "DELETE", -1
574 /* Append the table name. */
575 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1));
577 /* Append the conflict type. */
578 switch( eConf ){
579 case SQLITE_CHANGESET_DATA:
580 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1));
581 break;
582 case SQLITE_CHANGESET_NOTFOUND:
583 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1));
584 break;
585 case SQLITE_CHANGESET_CONFLICT:
586 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1));
587 break;
588 case SQLITE_CHANGESET_CONSTRAINT:
589 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1));
590 break;
593 /* If this is not an INSERT, append the old row */
594 if( op!=SQLITE_INSERT ){
595 int i;
596 Tcl_Obj *pOld = Tcl_NewObj();
597 for(i=0; i<nCol; i++){
598 sqlite3_value *pVal;
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 ){
607 int i;
608 Tcl_Obj *pNew = Tcl_NewObj();
609 for(i=0; i<nCol; i++){
610 sqlite3_value *pVal;
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 ){
620 int i;
621 Tcl_Obj *pConflict = Tcl_NewObj();
622 for(i=0; i<nCol; i++){
623 int rc;
624 sqlite3_value *pVal;
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
638 sqlite3_value *pVal;
639 int rc = sqlite3changeset_conflict(pIter, 0, &pVal);
640 assert( rc==SQLITE_MISUSE );
641 }else{
642 sqlite3_value *pVal;
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 ){
649 sqlite3_value *pVal;
650 int rc = sqlite3changeset_new(pIter, 0, &pVal);
651 assert( rc==SQLITE_MISUSE );
652 }else{
653 sqlite3_value *pVal;
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 ){
660 sqlite3_value *pVal;
661 int rc = sqlite3changeset_old(pIter, 0, &pVal);
662 assert( rc==SQLITE_MISUSE );
663 }else{
664 sqlite3_value *pVal;
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. */
673 int nDummy;
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);
683 }else{
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;
691 }else{
692 Tcl_GetIntFromObj(0, pRes, &ret);
696 Tcl_DecrRefCount(pEval);
697 return ret;
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 */
716 int i;
717 int x = 0;
719 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
721 if( op!=SQLITE_INSERT ){
722 for(i=0; i<nCol; i++){
723 sqlite3_value *pVal;
724 sqlite3changeset_old(pIter, i, &pVal);
725 sqlite3_value_text16(pVal);
726 x++;
730 if( op!=SQLITE_DELETE ){
731 for(i=0; i<nCol; i++){
732 sqlite3_value *pVal;
733 sqlite3changeset_new(pIter, i, &pVal);
734 sqlite3_value_text16(pVal);
735 x++;
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;
765 assert( nRet>=0 );
766 if( nRet>0 ){
767 memcpy(pData, &p->aData[p->iData], nRet);
768 p->iData += nRet;
771 *pnData = nRet;
772 return SQLITE_OK;
776 static int SQLITE_TCLAPI testSqlite3changesetApply(
777 int bV2,
778 void * clientData,
779 Tcl_Interp *interp,
780 int objc,
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;
790 void *pRebase = 0;
791 int nRebase = 0;
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 */
798 if( bV2 ){
799 if( objc>1 ){
800 const char *z1 = Tcl_GetString(objv[1]);
801 int n = strlen(z1);
802 if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){
803 flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT;
804 objc--;
805 objv++;
808 if( objc>1 ){
809 const char *z1 = Tcl_GetString(objv[1]);
810 int n = strlen(z1);
811 if( n>1 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){
812 flags |= SQLITE_CHANGESETAPPLY_INVERT;
813 objc--;
814 objv++;
819 if( objc!=4 && objc!=5 ){
820 const char *zMsg;
821 if( bV2 ){
822 zMsg = "?-nosavepoint? ?-inverse? "
823 "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
824 }else{
825 zMsg = "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
827 Tcl_WrongNumArgs(interp, 1, objv, zMsg);
828 return TCL_ERROR;
830 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
831 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[1]), 0);
832 return TCL_ERROR;
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;
838 ctx.interp = interp;
840 if( sStr.nStream==0 ){
841 if( bV2==0 ){
842 rc = sqlite3changeset_apply(db, nChangeset, pChangeset,
843 (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx
845 }else{
846 rc = sqlite3changeset_apply_v2(db, nChangeset, pChangeset,
847 (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx,
848 &pRebase, &nRebase, flags
851 }else{
852 sStr.aData = (unsigned char*)pChangeset;
853 sStr.nData = nChangeset;
854 if( bV2==0 ){
855 rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
856 (objc==5) ? test_filter_handler : 0,
857 test_conflict_handler, (void *)&ctx
859 }else{
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
868 if( rc!=SQLITE_OK ){
869 return test_session_error(interp, rc, 0);
870 }else{
871 Tcl_ResetResult(interp);
872 if( bV2 && pRebase ){
873 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase));
876 sqlite3_free(pRebase);
877 return TCL_OK;
881 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
883 static int SQLITE_TCLAPI test_sqlite3changeset_apply(
884 void * clientData,
885 Tcl_Interp *interp,
886 int objc,
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(
895 void * clientData,
896 Tcl_Interp *interp,
897 int objc,
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(
907 void * clientData,
908 Tcl_Interp *interp,
909 int objc,
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 */
918 if( objc!=3 ){
919 Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET");
920 return TCL_ERROR;
922 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
923 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
924 return TCL_ERROR;
926 db = *(sqlite3 **)info.objClientData;
927 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
929 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0);
930 if( rc!=SQLITE_OK ){
931 return test_session_error(interp, rc, 0);
933 Tcl_ResetResult(interp);
934 return TCL_OK;
939 ** sqlite3changeset_invert CHANGESET
941 static int SQLITE_TCLAPI test_sqlite3changeset_invert(
942 void * clientData,
943 Tcl_Interp *interp,
944 int objc,
945 Tcl_Obj *CONST objv[]
947 int rc; /* Return code from changeset_invert() */
948 TestStreamInput sIn; /* Input stream */
949 TestSessionsBlob sOut; /* Output blob */
951 if( objc!=2 ){
952 Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET");
953 return TCL_ERROR;
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);
961 if( sIn.nStream ){
962 rc = sqlite3changeset_invert_strm(
963 testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut
965 }else{
966 rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p);
968 if( rc!=SQLITE_OK ){
969 rc = test_session_error(interp, rc, 0);
970 }else{
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);
975 return rc;
979 ** sqlite3changeset_concat LEFT RIGHT
981 static int SQLITE_TCLAPI test_sqlite3changeset_concat(
982 void * clientData,
983 Tcl_Interp *interp,
984 int objc,
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 */
993 if( objc!=3 ){
994 Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT");
995 return TCL_ERROR;
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
1011 }else{
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);
1019 }else{
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);
1024 return rc;
1028 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT
1030 static int SQLITE_TCLAPI test_sqlite3session_foreach(
1031 void * clientData,
1032 Tcl_Interp *interp,
1033 int objc,
1034 Tcl_Obj *CONST objv[]
1036 void *pChangeset;
1037 int nChangeset;
1038 sqlite3_changeset_iter *pIter;
1039 int rc;
1040 Tcl_Obj *pVarname;
1041 Tcl_Obj *pCS;
1042 Tcl_Obj *pScript;
1043 int isCheckNext = 0;
1044 int isInvert = 0;
1046 TestStreamInput sStr;
1047 memset(&sStr, 0, sizeof(sStr));
1049 while( objc>1 ){
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) ){
1054 isInvert = 1;
1055 }else
1056 if( nOpt<=5 && 0==sqlite3_strnicmp(zOpt, "-next", nOpt) ){
1057 isCheckNext = 1;
1058 }else{
1059 break;
1061 objv++;
1062 objc--;
1064 if( objc!=4 ){
1065 Tcl_WrongNumArgs(
1066 interp, 1, objv, "?-next? ?-invert? VARNAME CHANGESET SCRIPT");
1067 return TCL_ERROR;
1070 pVarname = objv[1];
1071 pCS = objv[2];
1072 pScript = objv[3];
1074 pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset);
1075 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
1076 if( isInvert ){
1077 int f = SQLITE_CHANGESETSTART_INVERT;
1078 if( sStr.nStream==0 ){
1079 rc = sqlite3changeset_start_v2(&pIter, nChangeset, pChangeset, f);
1080 }else{
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);
1086 }else{
1087 if( sStr.nStream==0 ){
1088 rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
1089 }else{
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 */
1107 int bIndirect;
1109 char *zPK;
1110 unsigned char *abPK;
1111 int i;
1113 /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
1114 ** iterator. */
1115 int nDummy;
1116 if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){
1117 sqlite3changeset_finalize(pIter);
1118 return TCL_ERROR;
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" :
1126 "DELETE", -1
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));
1140 ckfree(zPK);
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;
1169 if( isCheckNext ){
1170 int rc2 = sqlite3changeset_next(pIter);
1171 rc = sqlite3changeset_finalize(pIter);
1172 assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
1173 }else{
1174 rc = sqlite3changeset_finalize(pIter);
1176 if( rc!=SQLITE_OK ){
1177 return test_session_error(interp, rc, 0);
1180 return TCL_OK;
1184 ** tclcmd: CMD configure REBASE-BLOB
1185 ** tclcmd: CMD rebase CHANGESET
1186 ** tclcmd: CMD delete
1188 static int SQLITE_TCLAPI test_rebaser_cmd(
1189 void * clientData,
1190 Tcl_Interp *interp,
1191 int objc,
1192 Tcl_Obj *CONST objv[]
1194 static struct RebaseSubcmd {
1195 const char *zSub;
1196 int nArg;
1197 const char *zMsg;
1198 int iSub;
1199 } aSub[] = {
1200 { "configure", 1, "REBASE-BLOB" }, /* 0 */
1201 { "delete", 0, "" }, /* 1 */
1202 { "rebase", 1, "CHANGESET" }, /* 2 */
1203 { 0 }
1206 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData;
1207 int iSub;
1208 int rc;
1210 if( objc<2 ){
1211 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
1212 return TCL_ERROR;
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);
1220 return TCL_ERROR;
1223 assert( iSub==0 || iSub==1 || iSub==2 );
1224 assert( rc==SQLITE_OK );
1225 switch( iSub ){
1226 case 0: { /* configure */
1227 int nRebase = 0;
1228 unsigned char *pRebase = Tcl_GetByteArrayFromObj(objv[2], &nRebase);
1229 rc = sqlite3rebaser_configure(p, nRebase, pRebase);
1230 break;
1233 case 1: /* delete */
1234 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
1235 break;
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);
1246 if( sStr.nStream ){
1247 rc = sqlite3rebaser_rebase_strm(p,
1248 testStreamInput, (void*)&sStr,
1249 testStreamOutput, (void*)&sOut
1251 }else{
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);
1260 break;
1264 if( rc!=SQLITE_OK ){
1265 return test_session_error(interp, rc, 0);
1267 return TCL_OK;
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(
1279 void * clientData,
1280 Tcl_Interp *interp,
1281 int objc,
1282 Tcl_Obj *CONST objv[]
1284 int rc;
1285 sqlite3_rebaser *pNew = 0;
1286 if( objc!=2 ){
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]);
1300 return TCL_OK;
1306 static int sqlite3_test_changeset(
1307 int nChangeset,
1308 void *pChangeset,
1309 char **pzErr
1311 sqlite3_changeset_iter *pIter = 0;
1312 char *zErr = 0;
1313 int rc = SQLITE_OK;
1314 int bPatch = (nChangeset>0 && ((char*)pChangeset)[0]=='P');
1316 rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
1317 if( rc==SQLITE_OK ){
1318 int rc2;
1319 while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){
1320 unsigned char *aPk = 0;
1321 int nCol = 0;
1322 int op = 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 ){
1329 int iCol;
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);
1336 if( aPk[iCol] ){
1337 if( pOld==0 ) rc = SQLITE_ERROR;
1338 }else if( bPatch ){
1339 if( pOld ) rc = SQLITE_ERROR;
1340 }else{
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
1349 break;
1354 rc2 = sqlite3changeset_finalize(pIter);
1355 if( rc==SQLITE_OK ){
1356 rc = rc2;
1360 *pzErr = zErr;
1361 return rc;
1365 ** test_changeset CHANGESET
1367 static int SQLITE_TCLAPI test_changeset(
1368 void * clientData,
1369 Tcl_Interp *interp,
1370 int objc,
1371 Tcl_Obj *CONST objv[]
1373 void *pChangeset = 0; /* Buffer containing changeset */
1374 int nChangeset = 0; /* Size of buffer aChangeset in bytes */
1375 int rc = SQLITE_OK;
1376 char *z = 0;
1378 if( objc!=2 ){
1379 Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET");
1380 return TCL_ERROR;
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));
1389 sqlite3_free(zErr);
1391 sqlite3_free(z);
1393 return rc ? TCL_ERROR : TCL_OK;
1397 ** tclcmd: sqlite3rebaser_configure OP VALUE
1399 static int SQLITE_TCLAPI test_sqlite3session_config(
1400 void * clientData,
1401 Tcl_Interp *interp,
1402 int objc,
1403 Tcl_Obj *CONST objv[]
1405 static struct ConfigOpt {
1406 const char *zSub;
1407 int op;
1408 } aSub[] = {
1409 { "strm_size", SQLITE_SESSION_CONFIG_STRMSIZE },
1410 { "invalid", 0 },
1411 { 0 }
1413 int rc;
1414 int iSub;
1415 int iVal;
1417 if( objc!=3 ){
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));
1432 return TCL_OK;
1435 int TestSession_Init(Tcl_Interp *interp){
1436 struct Cmd {
1437 const char *zCmd;
1438 Tcl_ObjCmdProc *xProc;
1439 } aCmd[] = {
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 },
1453 int i;
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);
1460 return TCL_OK;
1463 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */