First cut at logic to perform DO UPDATE for rowid tables.
[sqlite.git] / ext / session / test_session.c
blob3b6c24fd118c938a4be377d02e800ce8dc75f33c
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 ** Tclcmd: sql_exec_changeset DB SQL
104 static int SQLITE_TCLAPI test_sql_exec_changeset(
105 void * clientData,
106 Tcl_Interp *interp,
107 int objc,
108 Tcl_Obj *CONST objv[]
110 const char *zSql;
111 sqlite3 *db;
112 void *pChangeset;
113 int nChangeset;
114 int rc;
116 if( objc!=3 ){
117 Tcl_WrongNumArgs(interp, 1, objv, "DB SQL");
118 return TCL_ERROR;
120 if( dbHandleFromObj(interp, objv[1], &db) ) return TCL_ERROR;
121 zSql = (const char*)Tcl_GetString(objv[2]);
123 rc = sql_exec_changeset(db, zSql, &nChangeset, &pChangeset);
124 if( rc!=SQLITE_OK ){
125 Tcl_ResetResult(interp);
126 Tcl_AppendResult(interp, "error in sql_exec_changeset()", 0);
127 return TCL_ERROR;
130 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChangeset, nChangeset));
131 sqlite3_free(pChangeset);
132 return TCL_OK;
137 #define SESSION_STREAM_TCL_VAR "sqlite3session_streams"
140 ** Attempt to find the global variable zVar within interpreter interp
141 ** and extract an integer value from it. Return this value.
143 ** If the named variable cannot be found, or if it cannot be interpreted
144 ** as a integer, return 0.
146 static int test_tcl_integer(Tcl_Interp *interp, const char *zVar){
147 Tcl_Obj *pObj;
148 int iVal = 0;
149 pObj = Tcl_ObjGetVar2(interp, Tcl_NewStringObj(zVar, -1), 0, TCL_GLOBAL_ONLY);
150 if( pObj ) Tcl_GetIntFromObj(0, pObj, &iVal);
151 return iVal;
154 static int test_session_error(Tcl_Interp *interp, int rc, char *zErr){
155 extern const char *sqlite3ErrName(int);
156 Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
157 if( zErr ){
158 Tcl_AppendResult(interp, " - ", zErr, 0);
159 sqlite3_free(zErr);
161 return TCL_ERROR;
164 static int test_table_filter(void *pCtx, const char *zTbl){
165 TestSession *p = (TestSession*)pCtx;
166 Tcl_Obj *pEval;
167 int rc;
168 int bRes = 0;
170 pEval = Tcl_DuplicateObj(p->pFilterScript);
171 Tcl_IncrRefCount(pEval);
172 rc = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zTbl, -1));
173 if( rc==TCL_OK ){
174 rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
176 if( rc==TCL_OK ){
177 rc = Tcl_GetBooleanFromObj(p->interp, Tcl_GetObjResult(p->interp), &bRes);
179 if( rc!=TCL_OK ){
180 /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */
181 Tcl_BackgroundError(p->interp);
183 Tcl_DecrRefCount(pEval);
185 return bRes;
188 struct TestSessionsBlob {
189 void *p;
190 int n;
192 typedef struct TestSessionsBlob TestSessionsBlob;
194 static int testStreamOutput(
195 void *pCtx,
196 const void *pData,
197 int nData
199 TestSessionsBlob *pBlob = (TestSessionsBlob*)pCtx;
200 char *pNew;
202 assert( nData>0 );
203 pNew = (char*)sqlite3_realloc(pBlob->p, pBlob->n + nData);
204 if( pNew==0 ){
205 return SQLITE_NOMEM;
207 pBlob->p = (void*)pNew;
208 memcpy(&pNew[pBlob->n], pData, nData);
209 pBlob->n += nData;
210 return SQLITE_OK;
214 ** Tclcmd: $session attach TABLE
215 ** $session changeset
216 ** $session delete
217 ** $session enable BOOL
218 ** $session indirect INTEGER
219 ** $session patchset
220 ** $session table_filter SCRIPT
222 static int SQLITE_TCLAPI test_session_cmd(
223 void *clientData,
224 Tcl_Interp *interp,
225 int objc,
226 Tcl_Obj *CONST objv[]
228 TestSession *p = (TestSession*)clientData;
229 sqlite3_session *pSession = p->pSession;
230 struct SessionSubcmd {
231 const char *zSub;
232 int nArg;
233 const char *zMsg;
234 int iSub;
235 } aSub[] = {
236 { "attach", 1, "TABLE", }, /* 0 */
237 { "changeset", 0, "", }, /* 1 */
238 { "delete", 0, "", }, /* 2 */
239 { "enable", 1, "BOOL", }, /* 3 */
240 { "indirect", 1, "BOOL", }, /* 4 */
241 { "isempty", 0, "", }, /* 5 */
242 { "table_filter", 1, "SCRIPT", }, /* 6 */
243 { "patchset", 0, "", }, /* 7 */
244 { "diff", 2, "FROMDB TBL", }, /* 8 */
245 { 0 }
247 int iSub;
248 int rc;
250 if( objc<2 ){
251 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
252 return TCL_ERROR;
254 rc = Tcl_GetIndexFromObjStruct(interp,
255 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
257 if( rc!=TCL_OK ) return rc;
258 if( objc!=2+aSub[iSub].nArg ){
259 Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
260 return TCL_ERROR;
263 switch( iSub ){
264 case 0: { /* attach */
265 char *zArg = Tcl_GetString(objv[2]);
266 if( zArg[0]=='*' && zArg[1]=='\0' ) zArg = 0;
267 rc = sqlite3session_attach(pSession, zArg);
268 if( rc!=SQLITE_OK ){
269 return test_session_error(interp, rc, 0);
271 break;
274 case 7: /* patchset */
275 case 1: { /* changeset */
276 TestSessionsBlob o = {0, 0};
277 if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){
278 void *pCtx = (void*)&o;
279 if( iSub==7 ){
280 rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx);
281 }else{
282 rc = sqlite3session_changeset_strm(pSession, testStreamOutput, pCtx);
284 }else{
285 if( iSub==7 ){
286 rc = sqlite3session_patchset(pSession, &o.n, &o.p);
287 }else{
288 rc = sqlite3session_changeset(pSession, &o.n, &o.p);
291 if( rc==SQLITE_OK ){
292 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n));
294 sqlite3_free(o.p);
295 if( rc!=SQLITE_OK ){
296 return test_session_error(interp, rc, 0);
298 break;
301 case 2: /* delete */
302 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
303 break;
305 case 3: { /* enable */
306 int val;
307 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
308 val = sqlite3session_enable(pSession, val);
309 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
310 break;
313 case 4: { /* indirect */
314 int val;
315 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
316 val = sqlite3session_indirect(pSession, val);
317 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
318 break;
321 case 5: { /* isempty */
322 int val;
323 val = sqlite3session_isempty(pSession);
324 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
325 break;
328 case 6: { /* table_filter */
329 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
330 p->interp = interp;
331 p->pFilterScript = Tcl_DuplicateObj(objv[2]);
332 Tcl_IncrRefCount(p->pFilterScript);
333 sqlite3session_table_filter(pSession, test_table_filter, clientData);
334 break;
337 case 8: { /* diff */
338 char *zErr = 0;
339 rc = sqlite3session_diff(pSession,
340 Tcl_GetString(objv[2]),
341 Tcl_GetString(objv[3]),
342 &zErr
344 assert( rc!=SQLITE_OK || zErr==0 );
345 if( rc ){
346 return test_session_error(interp, rc, zErr);
348 break;
352 return TCL_OK;
355 static void SQLITE_TCLAPI test_session_del(void *clientData){
356 TestSession *p = (TestSession*)clientData;
357 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
358 sqlite3session_delete(p->pSession);
359 ckfree((char*)p);
363 ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME
365 static int SQLITE_TCLAPI test_sqlite3session(
366 void * clientData,
367 Tcl_Interp *interp,
368 int objc,
369 Tcl_Obj *CONST objv[]
371 sqlite3 *db;
372 Tcl_CmdInfo info;
373 int rc; /* sqlite3session_create() return code */
374 TestSession *p; /* New wrapper object */
376 if( objc!=4 ){
377 Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME");
378 return TCL_ERROR;
381 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
382 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
383 return TCL_ERROR;
385 db = *(sqlite3 **)info.objClientData;
387 p = (TestSession*)ckalloc(sizeof(TestSession));
388 memset(p, 0, sizeof(TestSession));
389 rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &p->pSession);
390 if( rc!=SQLITE_OK ){
391 ckfree((char*)p);
392 return test_session_error(interp, rc, 0);
395 Tcl_CreateObjCommand(
396 interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)p,
397 test_session_del
399 Tcl_SetObjResult(interp, objv[1]);
400 return TCL_OK;
403 static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){
404 if( pVal==0 ){
405 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
406 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
407 }else{
408 Tcl_Obj *pObj;
409 switch( sqlite3_value_type(pVal) ){
410 case SQLITE_NULL:
411 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("n", 1));
412 pObj = Tcl_NewObj();
413 break;
414 case SQLITE_INTEGER:
415 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("i", 1));
416 pObj = Tcl_NewWideIntObj(sqlite3_value_int64(pVal));
417 break;
418 case SQLITE_FLOAT:
419 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("f", 1));
420 pObj = Tcl_NewDoubleObj(sqlite3_value_double(pVal));
421 break;
422 case SQLITE_TEXT: {
423 const char *z = (char*)sqlite3_value_blob(pVal);
424 int n = sqlite3_value_bytes(pVal);
425 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("t", 1));
426 pObj = Tcl_NewStringObj(z, n);
427 break;
429 default:
430 assert( sqlite3_value_type(pVal)==SQLITE_BLOB );
431 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("b", 1));
432 pObj = Tcl_NewByteArrayObj(
433 sqlite3_value_blob(pVal),
434 sqlite3_value_bytes(pVal)
436 break;
438 Tcl_ListObjAppendElement(0, pList, pObj);
442 typedef struct TestConflictHandler TestConflictHandler;
443 struct TestConflictHandler {
444 Tcl_Interp *interp;
445 Tcl_Obj *pConflictScript;
446 Tcl_Obj *pFilterScript;
449 static int test_obj_eq_string(Tcl_Obj *p, const char *z){
450 int n;
451 int nObj;
452 char *zObj;
454 n = (int)strlen(z);
455 zObj = Tcl_GetStringFromObj(p, &nObj);
457 return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
460 static int test_filter_handler(
461 void *pCtx, /* Pointer to TestConflictHandler structure */
462 const char *zTab /* Table name */
464 TestConflictHandler *p = (TestConflictHandler *)pCtx;
465 int res = 1;
466 Tcl_Obj *pEval;
467 Tcl_Interp *interp = p->interp;
469 pEval = Tcl_DuplicateObj(p->pFilterScript);
470 Tcl_IncrRefCount(pEval);
472 if( TCL_OK!=Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1))
473 || TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL)
474 || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res)
476 Tcl_BackgroundError(interp);
479 Tcl_DecrRefCount(pEval);
480 return res;
483 static int test_conflict_handler(
484 void *pCtx, /* Pointer to TestConflictHandler structure */
485 int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
486 sqlite3_changeset_iter *pIter /* Handle describing change and conflict */
488 TestConflictHandler *p = (TestConflictHandler *)pCtx;
489 Tcl_Obj *pEval;
490 Tcl_Interp *interp = p->interp;
491 int ret = 0; /* Return value */
493 int op; /* SQLITE_UPDATE, DELETE or INSERT */
494 const char *zTab; /* Name of table conflict is on */
495 int nCol; /* Number of columns in table zTab */
497 pEval = Tcl_DuplicateObj(p->pConflictScript);
498 Tcl_IncrRefCount(pEval);
500 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
502 if( eConf==SQLITE_CHANGESET_FOREIGN_KEY ){
503 int nFk;
504 sqlite3changeset_fk_conflicts(pIter, &nFk);
505 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj("FOREIGN_KEY", -1));
506 Tcl_ListObjAppendElement(0, pEval, Tcl_NewIntObj(nFk));
507 }else{
509 /* Append the operation type. */
510 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
511 op==SQLITE_INSERT ? "INSERT" :
512 op==SQLITE_UPDATE ? "UPDATE" :
513 "DELETE", -1
516 /* Append the table name. */
517 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1));
519 /* Append the conflict type. */
520 switch( eConf ){
521 case SQLITE_CHANGESET_DATA:
522 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1));
523 break;
524 case SQLITE_CHANGESET_NOTFOUND:
525 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1));
526 break;
527 case SQLITE_CHANGESET_CONFLICT:
528 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1));
529 break;
530 case SQLITE_CHANGESET_CONSTRAINT:
531 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1));
532 break;
535 /* If this is not an INSERT, append the old row */
536 if( op!=SQLITE_INSERT ){
537 int i;
538 Tcl_Obj *pOld = Tcl_NewObj();
539 for(i=0; i<nCol; i++){
540 sqlite3_value *pVal;
541 sqlite3changeset_old(pIter, i, &pVal);
542 test_append_value(pOld, pVal);
544 Tcl_ListObjAppendElement(0, pEval, pOld);
547 /* If this is not a DELETE, append the new row */
548 if( op!=SQLITE_DELETE ){
549 int i;
550 Tcl_Obj *pNew = Tcl_NewObj();
551 for(i=0; i<nCol; i++){
552 sqlite3_value *pVal;
553 sqlite3changeset_new(pIter, i, &pVal);
554 test_append_value(pNew, pVal);
556 Tcl_ListObjAppendElement(0, pEval, pNew);
559 /* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append
560 ** the conflicting row. */
561 if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){
562 int i;
563 Tcl_Obj *pConflict = Tcl_NewObj();
564 for(i=0; i<nCol; i++){
565 int rc;
566 sqlite3_value *pVal;
567 rc = sqlite3changeset_conflict(pIter, i, &pVal);
568 assert( rc==SQLITE_OK );
569 test_append_value(pConflict, pVal);
571 Tcl_ListObjAppendElement(0, pEval, pConflict);
574 /***********************************************************************
575 ** This block is purely for testing some error conditions.
577 if( eConf==SQLITE_CHANGESET_CONSTRAINT
578 || eConf==SQLITE_CHANGESET_NOTFOUND
580 sqlite3_value *pVal;
581 int rc = sqlite3changeset_conflict(pIter, 0, &pVal);
582 assert( rc==SQLITE_MISUSE );
583 }else{
584 sqlite3_value *pVal;
585 int rc = sqlite3changeset_conflict(pIter, -1, &pVal);
586 assert( rc==SQLITE_RANGE );
587 rc = sqlite3changeset_conflict(pIter, nCol, &pVal);
588 assert( rc==SQLITE_RANGE );
590 if( op==SQLITE_DELETE ){
591 sqlite3_value *pVal;
592 int rc = sqlite3changeset_new(pIter, 0, &pVal);
593 assert( rc==SQLITE_MISUSE );
594 }else{
595 sqlite3_value *pVal;
596 int rc = sqlite3changeset_new(pIter, -1, &pVal);
597 assert( rc==SQLITE_RANGE );
598 rc = sqlite3changeset_new(pIter, nCol, &pVal);
599 assert( rc==SQLITE_RANGE );
601 if( op==SQLITE_INSERT ){
602 sqlite3_value *pVal;
603 int rc = sqlite3changeset_old(pIter, 0, &pVal);
604 assert( rc==SQLITE_MISUSE );
605 }else{
606 sqlite3_value *pVal;
607 int rc = sqlite3changeset_old(pIter, -1, &pVal);
608 assert( rc==SQLITE_RANGE );
609 rc = sqlite3changeset_old(pIter, nCol, &pVal);
610 assert( rc==SQLITE_RANGE );
612 if( eConf!=SQLITE_CHANGESET_FOREIGN_KEY ){
613 /* eConf!=FOREIGN_KEY is always true at this point. The condition is
614 ** just there to make it clearer what is being tested. */
615 int nDummy;
616 int rc = sqlite3changeset_fk_conflicts(pIter, &nDummy);
617 assert( rc==SQLITE_MISUSE );
619 /* End of testing block
620 ***********************************************************************/
623 if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){
624 Tcl_BackgroundError(interp);
625 }else{
626 Tcl_Obj *pRes = Tcl_GetObjResult(interp);
627 if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){
628 ret = SQLITE_CHANGESET_OMIT;
629 }else if( test_obj_eq_string(pRes, "REPLACE") ){
630 ret = SQLITE_CHANGESET_REPLACE;
631 }else if( test_obj_eq_string(pRes, "ABORT") ){
632 ret = SQLITE_CHANGESET_ABORT;
633 }else{
634 Tcl_GetIntFromObj(0, pRes, &ret);
638 Tcl_DecrRefCount(pEval);
639 return ret;
643 ** The conflict handler used by sqlite3changeset_apply_replace_all().
644 ** This conflict handler calls sqlite3_value_text16() on all available
645 ** sqlite3_value objects and then returns CHANGESET_REPLACE, or
646 ** CHANGESET_OMIT if REPLACE is not applicable. This is used to test the
647 ** effect of a malloc failure within an sqlite3_value_xxx() function
648 ** invoked by a conflict-handler callback.
650 static int replace_handler(
651 void *pCtx, /* Pointer to TestConflictHandler structure */
652 int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
653 sqlite3_changeset_iter *pIter /* Handle describing change and conflict */
655 int op; /* SQLITE_UPDATE, DELETE or INSERT */
656 const char *zTab; /* Name of table conflict is on */
657 int nCol; /* Number of columns in table zTab */
658 int i;
659 int x = 0;
661 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
663 if( op!=SQLITE_INSERT ){
664 for(i=0; i<nCol; i++){
665 sqlite3_value *pVal;
666 sqlite3changeset_old(pIter, i, &pVal);
667 sqlite3_value_text16(pVal);
668 x++;
672 if( op!=SQLITE_DELETE ){
673 for(i=0; i<nCol; i++){
674 sqlite3_value *pVal;
675 sqlite3changeset_new(pIter, i, &pVal);
676 sqlite3_value_text16(pVal);
677 x++;
681 if( eConf==SQLITE_CHANGESET_DATA ){
682 return SQLITE_CHANGESET_REPLACE;
684 return SQLITE_CHANGESET_OMIT;
687 static int testStreamInput(
688 void *pCtx, /* Context pointer */
689 void *pData, /* Buffer to populate */
690 int *pnData /* IN/OUT: Bytes requested/supplied */
692 TestStreamInput *p = (TestStreamInput*)pCtx;
693 int nReq = *pnData; /* Bytes of data requested */
694 int nRem = p->nData - p->iData; /* Bytes of data available */
695 int nRet = p->nStream; /* Bytes actually returned */
697 /* Allocate and free some space. There is no point to this, other than
698 ** that it allows the regular OOM fault-injection tests to cause an error
699 ** in this function. */
700 void *pAlloc = sqlite3_malloc(10);
701 if( pAlloc==0 ) return SQLITE_NOMEM;
702 sqlite3_free(pAlloc);
704 if( nRet>nReq ) nRet = nReq;
705 if( nRet>nRem ) nRet = nRem;
707 assert( nRet>=0 );
708 if( nRet>0 ){
709 memcpy(pData, &p->aData[p->iData], nRet);
710 p->iData += nRet;
713 *pnData = nRet;
714 return SQLITE_OK;
718 static int SQLITE_TCLAPI testSqlite3changesetApply(
719 int bV2,
720 void * clientData,
721 Tcl_Interp *interp,
722 int objc,
723 Tcl_Obj *CONST objv[]
725 sqlite3 *db; /* Database handle */
726 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */
727 int rc; /* Return code from changeset_invert() */
728 void *pChangeset; /* Buffer containing changeset */
729 int nChangeset; /* Size of buffer aChangeset in bytes */
730 TestConflictHandler ctx;
731 TestStreamInput sStr;
732 void *pRebase = 0;
733 int nRebase = 0;
734 int flags = 0; /* Flags for apply_v2() */
736 memset(&sStr, 0, sizeof(sStr));
737 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
739 /* Check for the -nosavepoint flag */
740 if( bV2 && objc>1 ){
741 const char *z1 = Tcl_GetString(objv[1]);
742 int n = strlen(z1);
743 if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){
744 flags = SQLITE_CHANGESETAPPLY_NOSAVEPOINT;
745 objc--;
746 objv++;
750 if( objc!=4 && objc!=5 ){
751 const char *zMsg;
752 if( bV2 ){
753 zMsg = "?-nosavepoint? DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
754 }else{
755 zMsg = "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
757 Tcl_WrongNumArgs(interp, 1, objv, zMsg);
758 return TCL_ERROR;
760 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
761 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[1]), 0);
762 return TCL_ERROR;
764 db = *(sqlite3 **)info.objClientData;
765 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
766 ctx.pConflictScript = objv[3];
767 ctx.pFilterScript = objc==5 ? objv[4] : 0;
768 ctx.interp = interp;
770 if( sStr.nStream==0 ){
771 if( bV2==0 ){
772 rc = sqlite3changeset_apply(db, nChangeset, pChangeset,
773 (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx
775 }else{
776 rc = sqlite3changeset_apply_v2(db, nChangeset, pChangeset,
777 (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx,
778 &pRebase, &nRebase, flags
781 }else{
782 sStr.aData = (unsigned char*)pChangeset;
783 sStr.nData = nChangeset;
784 if( bV2==0 ){
785 rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
786 (objc==5) ? test_filter_handler : 0,
787 test_conflict_handler, (void *)&ctx
789 }else{
790 rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr,
791 (objc==5) ? test_filter_handler : 0,
792 test_conflict_handler, (void *)&ctx,
793 &pRebase, &nRebase, flags
798 if( rc!=SQLITE_OK ){
799 return test_session_error(interp, rc, 0);
800 }else{
801 Tcl_ResetResult(interp);
802 if( bV2 && pRebase ){
803 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase));
806 sqlite3_free(pRebase);
807 return TCL_OK;
811 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
813 static int SQLITE_TCLAPI test_sqlite3changeset_apply(
814 void * clientData,
815 Tcl_Interp *interp,
816 int objc,
817 Tcl_Obj *CONST objv[]
819 return testSqlite3changesetApply(0, clientData, interp, objc, objv);
822 ** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
824 static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2(
825 void * clientData,
826 Tcl_Interp *interp,
827 int objc,
828 Tcl_Obj *CONST objv[]
830 return testSqlite3changesetApply(1, clientData, interp, objc, objv);
834 ** sqlite3changeset_apply_replace_all DB CHANGESET
836 static int SQLITE_TCLAPI test_sqlite3changeset_apply_replace_all(
837 void * clientData,
838 Tcl_Interp *interp,
839 int objc,
840 Tcl_Obj *CONST objv[]
842 sqlite3 *db; /* Database handle */
843 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */
844 int rc; /* Return code from changeset_invert() */
845 void *pChangeset; /* Buffer containing changeset */
846 int nChangeset; /* Size of buffer aChangeset in bytes */
848 if( objc!=3 ){
849 Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET");
850 return TCL_ERROR;
852 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
853 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
854 return TCL_ERROR;
856 db = *(sqlite3 **)info.objClientData;
857 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
859 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0);
860 if( rc!=SQLITE_OK ){
861 return test_session_error(interp, rc, 0);
863 Tcl_ResetResult(interp);
864 return TCL_OK;
869 ** sqlite3changeset_invert CHANGESET
871 static int SQLITE_TCLAPI test_sqlite3changeset_invert(
872 void * clientData,
873 Tcl_Interp *interp,
874 int objc,
875 Tcl_Obj *CONST objv[]
877 int rc; /* Return code from changeset_invert() */
878 TestStreamInput sIn; /* Input stream */
879 TestSessionsBlob sOut; /* Output blob */
881 if( objc!=2 ){
882 Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET");
883 return TCL_ERROR;
886 memset(&sIn, 0, sizeof(sIn));
887 memset(&sOut, 0, sizeof(sOut));
888 sIn.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
889 sIn.aData = Tcl_GetByteArrayFromObj(objv[1], &sIn.nData);
891 if( sIn.nStream ){
892 rc = sqlite3changeset_invert_strm(
893 testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut
895 }else{
896 rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p);
898 if( rc!=SQLITE_OK ){
899 rc = test_session_error(interp, rc, 0);
900 }else{
901 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
903 sqlite3_free(sOut.p);
904 return rc;
908 ** sqlite3changeset_concat LEFT RIGHT
910 static int SQLITE_TCLAPI test_sqlite3changeset_concat(
911 void * clientData,
912 Tcl_Interp *interp,
913 int objc,
914 Tcl_Obj *CONST objv[]
916 int rc; /* Return code from changeset_invert() */
918 TestStreamInput sLeft; /* Input stream */
919 TestStreamInput sRight; /* Input stream */
920 TestSessionsBlob sOut = {0,0}; /* Output blob */
922 if( objc!=3 ){
923 Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT");
924 return TCL_ERROR;
927 memset(&sLeft, 0, sizeof(sLeft));
928 memset(&sRight, 0, sizeof(sRight));
929 sLeft.aData = Tcl_GetByteArrayFromObj(objv[1], &sLeft.nData);
930 sRight.aData = Tcl_GetByteArrayFromObj(objv[2], &sRight.nData);
931 sLeft.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
932 sRight.nStream = sLeft.nStream;
934 if( sLeft.nStream>0 ){
935 rc = sqlite3changeset_concat_strm(
936 testStreamInput, (void*)&sLeft,
937 testStreamInput, (void*)&sRight,
938 testStreamOutput, (void*)&sOut
940 }else{
941 rc = sqlite3changeset_concat(
942 sLeft.nData, sLeft.aData, sRight.nData, sRight.aData, &sOut.n, &sOut.p
946 if( rc!=SQLITE_OK ){
947 rc = test_session_error(interp, rc, 0);
948 }else{
949 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
951 sqlite3_free(sOut.p);
952 return rc;
956 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT
958 static int SQLITE_TCLAPI test_sqlite3session_foreach(
959 void * clientData,
960 Tcl_Interp *interp,
961 int objc,
962 Tcl_Obj *CONST objv[]
964 void *pChangeset;
965 int nChangeset;
966 sqlite3_changeset_iter *pIter;
967 int rc;
968 Tcl_Obj *pVarname;
969 Tcl_Obj *pCS;
970 Tcl_Obj *pScript;
971 int isCheckNext = 0;
973 TestStreamInput sStr;
974 memset(&sStr, 0, sizeof(sStr));
976 if( objc>1 ){
977 char *zOpt = Tcl_GetString(objv[1]);
978 isCheckNext = (strcmp(zOpt, "-next")==0);
980 if( objc!=4+isCheckNext ){
981 Tcl_WrongNumArgs(interp, 1, objv, "?-next? VARNAME CHANGESET SCRIPT");
982 return TCL_ERROR;
985 pVarname = objv[1+isCheckNext];
986 pCS = objv[2+isCheckNext];
987 pScript = objv[3+isCheckNext];
989 pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset);
990 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
991 if( sStr.nStream==0 ){
992 rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
993 }else{
994 sStr.aData = (unsigned char*)pChangeset;
995 sStr.nData = nChangeset;
996 rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr);
998 if( rc!=SQLITE_OK ){
999 return test_session_error(interp, rc, 0);
1002 while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
1003 int nCol; /* Number of columns in table */
1004 int nCol2; /* Number of columns in table */
1005 int op; /* SQLITE_INSERT, UPDATE or DELETE */
1006 const char *zTab; /* Name of table change applies to */
1007 Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */
1008 Tcl_Obj *pOld; /* Vector of old.* values */
1009 Tcl_Obj *pNew; /* Vector of new.* values */
1010 int bIndirect;
1012 char *zPK;
1013 unsigned char *abPK;
1014 int i;
1016 /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
1017 ** iterator. */
1018 int nDummy;
1019 if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){
1020 sqlite3changeset_finalize(pIter);
1021 return TCL_ERROR;
1024 sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
1025 pVar = Tcl_NewObj();
1026 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
1027 op==SQLITE_INSERT ? "INSERT" :
1028 op==SQLITE_UPDATE ? "UPDATE" :
1029 "DELETE", -1
1032 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
1033 Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
1035 zPK = ckalloc(nCol+1);
1036 memset(zPK, 0, nCol+1);
1037 sqlite3changeset_pk(pIter, &abPK, &nCol2);
1038 assert( nCol==nCol2 );
1039 for(i=0; i<nCol; i++){
1040 zPK[i] = (abPK[i] ? 'X' : '.');
1042 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
1043 ckfree(zPK);
1045 pOld = Tcl_NewObj();
1046 if( op!=SQLITE_INSERT ){
1047 for(i=0; i<nCol; i++){
1048 sqlite3_value *pVal;
1049 sqlite3changeset_old(pIter, i, &pVal);
1050 test_append_value(pOld, pVal);
1053 pNew = Tcl_NewObj();
1054 if( op!=SQLITE_DELETE ){
1055 for(i=0; i<nCol; i++){
1056 sqlite3_value *pVal;
1057 sqlite3changeset_new(pIter, i, &pVal);
1058 test_append_value(pNew, pVal);
1061 Tcl_ListObjAppendElement(0, pVar, pOld);
1062 Tcl_ListObjAppendElement(0, pVar, pNew);
1064 Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
1065 rc = Tcl_EvalObjEx(interp, pScript, 0);
1066 if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
1067 sqlite3changeset_finalize(pIter);
1068 return rc==TCL_BREAK ? TCL_OK : rc;
1072 if( isCheckNext ){
1073 int rc2 = sqlite3changeset_next(pIter);
1074 rc = sqlite3changeset_finalize(pIter);
1075 assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
1076 }else{
1077 rc = sqlite3changeset_finalize(pIter);
1079 if( rc!=SQLITE_OK ){
1080 return test_session_error(interp, rc, 0);
1083 return TCL_OK;
1087 ** tclcmd: CMD configure REBASE-BLOB
1088 ** tclcmd: CMD rebase CHANGESET
1089 ** tclcmd: CMD delete
1091 static int SQLITE_TCLAPI test_rebaser_cmd(
1092 void * clientData,
1093 Tcl_Interp *interp,
1094 int objc,
1095 Tcl_Obj *CONST objv[]
1097 struct RebaseSubcmd {
1098 const char *zSub;
1099 int nArg;
1100 const char *zMsg;
1101 int iSub;
1102 } aSub[] = {
1103 { "configure", 1, "REBASE-BLOB" }, /* 0 */
1104 { "delete", 0, "" }, /* 1 */
1105 { "rebase", 1, "CHANGESET" }, /* 2 */
1106 { 0 }
1109 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData;
1110 int iSub;
1111 int rc;
1113 if( objc<2 ){
1114 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
1115 return TCL_ERROR;
1117 rc = Tcl_GetIndexFromObjStruct(interp,
1118 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
1120 if( rc!=TCL_OK ) return rc;
1121 if( objc!=2+aSub[iSub].nArg ){
1122 Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
1123 return TCL_ERROR;
1126 assert( iSub==0 || iSub==1 || iSub==2 );
1127 assert( rc==SQLITE_OK );
1128 switch( iSub ){
1129 case 0: { /* configure */
1130 int nRebase = 0;
1131 unsigned char *pRebase = Tcl_GetByteArrayFromObj(objv[2], &nRebase);
1132 rc = sqlite3rebaser_configure(p, nRebase, pRebase);
1133 break;
1136 case 1: /* delete */
1137 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
1138 break;
1140 default: { /* rebase */
1141 TestStreamInput sStr; /* Input stream */
1142 TestSessionsBlob sOut; /* Output blob */
1144 memset(&sStr, 0, sizeof(sStr));
1145 memset(&sOut, 0, sizeof(sOut));
1146 sStr.aData = Tcl_GetByteArrayFromObj(objv[2], &sStr.nData);
1147 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
1149 if( sStr.nStream ){
1150 rc = sqlite3rebaser_rebase_strm(p,
1151 testStreamInput, (void*)&sStr,
1152 testStreamOutput, (void*)&sOut
1154 }else{
1155 rc = sqlite3rebaser_rebase(p, sStr.nData, sStr.aData, &sOut.n, &sOut.p);
1158 if( rc==SQLITE_OK ){
1159 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(sOut.p, sOut.n));
1161 sqlite3_free(sOut.p);
1162 break;
1166 if( rc!=SQLITE_OK ){
1167 return test_session_error(interp, rc, 0);
1169 return TCL_OK;
1172 static void SQLITE_TCLAPI test_rebaser_del(void *clientData){
1173 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData;
1174 sqlite3rebaser_delete(p);
1178 ** tclcmd: sqlite3rebaser_create NAME
1180 static int SQLITE_TCLAPI test_sqlite3rebaser_create(
1181 void * clientData,
1182 Tcl_Interp *interp,
1183 int objc,
1184 Tcl_Obj *CONST objv[]
1186 int rc;
1187 sqlite3_rebaser *pNew = 0;
1188 if( objc!=2 ){
1189 Tcl_WrongNumArgs(interp, 1, objv, "NAME");
1190 return SQLITE_ERROR;
1193 rc = sqlite3rebaser_create(&pNew);
1194 if( rc!=SQLITE_OK ){
1195 return test_session_error(interp, rc, 0);
1198 Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]), test_rebaser_cmd,
1199 (ClientData)pNew, test_rebaser_del
1201 Tcl_SetObjResult(interp, objv[1]);
1202 return TCL_OK;
1205 int TestSession_Init(Tcl_Interp *interp){
1206 struct Cmd {
1207 const char *zCmd;
1208 Tcl_ObjCmdProc *xProc;
1209 } aCmd[] = {
1210 { "sqlite3session", test_sqlite3session },
1211 { "sqlite3session_foreach", test_sqlite3session_foreach },
1212 { "sqlite3changeset_invert", test_sqlite3changeset_invert },
1213 { "sqlite3changeset_concat", test_sqlite3changeset_concat },
1214 { "sqlite3changeset_apply", test_sqlite3changeset_apply },
1215 { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 },
1216 { "sqlite3changeset_apply_replace_all",
1217 test_sqlite3changeset_apply_replace_all },
1218 { "sql_exec_changeset", test_sql_exec_changeset },
1219 { "sqlite3rebaser_create", test_sqlite3rebaser_create },
1221 int i;
1223 for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){
1224 struct Cmd *p = &aCmd[i];
1225 Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0);
1228 return TCL_OK;
1231 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */