1 2 #if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \ 3 && defined(SQLITE_ENABLE_PREUPDATE_HOOK) 4 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 16 17 #ifndef SQLITE_AMALGAMATION 18 typedef unsigned char u8; 19 #endif 20 21 typedef struct TestSession TestSession; 22 struct TestSession { 23 sqlite3_session *pSession; 24 Tcl_Interp *interp; 25 Tcl_Obj *pFilterScript; 26 }; 27 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 */ 34 }; 35 36 /* 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. 40 */ 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; 46 } 47 48 *pDb = *(sqlite3 **)info.objClientData; 49 return TCL_OK; 50 } 51 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 */ 57 /* 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. 67 ** 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. 70 */ 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 */ 76 ){ 77 sqlite3_session *pSession = 0; 78 int rc; 79 80 /* Create a new session object */ 81 rc = sqlite3session_create(db, "main", &pSession); 82 83 /* Configure the session object to record changes to all tables */ 84 if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL); 85 86 /* Execute the SQL script */ 87 if( rc==SQLITE_OK ) rc = sqlite3_exec(db, zSql, 0, 0, 0); 88 89 /* Collect the changeset */ 90 if( rc==SQLITE_OK ){ 91 rc = sqlite3session_changeset(pSession, pnChangeset, ppChangeset); 92 } 93 94 /* Delete the session object */ 95 sqlite3session_delete(pSession); 96 97 return rc; 98 } 99 /************************************************************************/ 100 101 /* 102 ** Tclcmd: sql_exec_changeset DB SQL 103 */ 104 static int SQLITE_TCLAPI test_sql_exec_changeset( 105 void * clientData, 106 Tcl_Interp *interp, 107 int objc, 108 Tcl_Obj *CONST objv[] 109 ){ 110 const char *zSql; 111 sqlite3 *db; 112 void *pChangeset; 113 int nChangeset; 114 int rc; 115 116 if( objc!=3 ){ 117 Tcl_WrongNumArgs(interp, 1, objv, "DB SQL"); 118 return TCL_ERROR; 119 } 120 if( dbHandleFromObj(interp, objv[1], &db) ) return TCL_ERROR; 121 zSql = (const char*)Tcl_GetString(objv[2]); 122 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; 128 } 129 130 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChangeset, nChangeset)); 131 sqlite3_free(pChangeset); 132 return TCL_OK; 133 } 134 135 136 137 #define SESSION_STREAM_TCL_VAR "sqlite3session_streams" 138 139 /* 140 ** Attempt to find the global variable zVar within interpreter interp 141 ** and extract an integer value from it. Return this value. 142 ** 143 ** If the named variable cannot be found, or if it cannot be interpreted 144 ** as a integer, return 0. 145 */ 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; 152 } 153 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); 160 } 161 return TCL_ERROR; 162 } 163 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; 169 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); 175 } 176 if( rc==TCL_OK ){ 177 rc = Tcl_GetBooleanFromObj(p->interp, Tcl_GetObjResult(p->interp), &bRes); 178 } 179 if( rc!=TCL_OK ){ 180 /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */ 181 Tcl_BackgroundError(p->interp); 182 } 183 Tcl_DecrRefCount(pEval); 184 185 return bRes; 186 } 187 188 struct TestSessionsBlob { 189 void *p; 190 int n; 191 }; 192 typedef struct TestSessionsBlob TestSessionsBlob; 193 194 static int testStreamOutput( 195 void *pCtx, 196 const void *pData, 197 int nData 198 ){ 199 TestSessionsBlob *pBlob = (TestSessionsBlob*)pCtx; 200 char *pNew; 201 202 assert( nData>0 ); 203 pNew = (char*)sqlite3_realloc(pBlob->p, pBlob->n + nData); 204 if( pNew==0 ){ 205 return SQLITE_NOMEM; 206 } 207 pBlob->p = (void*)pNew; 208 memcpy(&pNew[pBlob->n], pData, nData); 209 pBlob->n += nData; 210 return SQLITE_OK; 211 } 212 213 /* 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 221 */ 222 static int SQLITE_TCLAPI test_session_cmd( 223 void *clientData, 224 Tcl_Interp *interp, 225 int objc, 226 Tcl_Obj *CONST objv[] 227 ){ 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 } 246 }; 247 int iSub; 248 int rc; 249 250 if( objc<2 ){ 251 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); 252 return TCL_ERROR; 253 } 254 rc = Tcl_GetIndexFromObjStruct(interp, 255 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub 256 ); 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; 261 } 262 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); 270 } 271 break; 272 } 273 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); 283 } 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); 289 } 290 } 291 if( rc==SQLITE_OK ){ 292 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n)); 293 } 294 sqlite3_free(o.p); 295 if( rc!=SQLITE_OK ){ 296 return test_session_error(interp, rc, 0); 297 } 298 break; 299 } 300 301 case 2: /* delete */ 302 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); 303 break; 304 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; 311 } 312 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; 319 } 320 321 case 5: { /* isempty */ 322 int val; 323 val = sqlite3session_isempty(pSession); 324 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); 325 break; 326 } 327 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; 335 } 336 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 343 ); 344 assert( rc!=SQLITE_OK || zErr==0 ); 345 if( rc ){ 346 return test_session_error(interp, rc, zErr); 347 } 348 break; 349 } 350 } 351 352 return TCL_OK; 353 } 354 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); 360 } 361 362 /* 363 ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME 364 */ 365 static int SQLITE_TCLAPI test_sqlite3session( 366 void * clientData, 367 Tcl_Interp *interp, 368 int objc, 369 Tcl_Obj *CONST objv[] 370 ){ 371 sqlite3 *db; 372 Tcl_CmdInfo info; 373 int rc; /* sqlite3session_create() return code */ 374 TestSession *p; /* New wrapper object */ 375 376 if( objc!=4 ){ 377 Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME"); 378 return TCL_ERROR; 379 } 380 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; 384 } 385 db = *(sqlite3 **)info.objClientData; 386 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); 393 } 394 395 Tcl_CreateObjCommand( 396 interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)p, 397 test_session_del 398 ); 399 Tcl_SetObjResult(interp, objv[1]); 400 return TCL_OK; 401 } 402 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; 428 } 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) 435 ); 436 break; 437 } 438 Tcl_ListObjAppendElement(0, pList, pObj); 439 } 440 } 441 442 typedef struct TestConflictHandler TestConflictHandler; 443 struct TestConflictHandler { 444 Tcl_Interp *interp; 445 Tcl_Obj *pConflictScript; 446 Tcl_Obj *pFilterScript; 447 }; 448 449 static int test_obj_eq_string(Tcl_Obj *p, const char *z){ 450 int n; 451 int nObj; 452 char *zObj; 453 454 n = (int)strlen(z); 455 zObj = Tcl_GetStringFromObj(p, &nObj); 456 457 return (nObj==n && (n==0 || 0==memcmp(zObj, z, n))); 458 } 459 460 static int test_filter_handler( 461 void *pCtx, /* Pointer to TestConflictHandler structure */ 462 const char *zTab /* Table name */ 463 ){ 464 TestConflictHandler *p = (TestConflictHandler *)pCtx; 465 int res = 1; 466 Tcl_Obj *pEval; 467 Tcl_Interp *interp = p->interp; 468 469 pEval = Tcl_DuplicateObj(p->pFilterScript); 470 Tcl_IncrRefCount(pEval); 471 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) 475 ){ 476 Tcl_BackgroundError(interp); 477 } 478 479 Tcl_DecrRefCount(pEval); 480 return res; 481 } 482 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 */ 487 ){ 488 TestConflictHandler *p = (TestConflictHandler *)pCtx; 489 Tcl_Obj *pEval; 490 Tcl_Interp *interp = p->interp; 491 int ret = 0; /* Return value */ 492 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 */ 496 497 pEval = Tcl_DuplicateObj(p->pConflictScript); 498 Tcl_IncrRefCount(pEval); 499 500 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); 501 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{ 508 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 514 )); 515 516 /* Append the table name. */ 517 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1)); 518 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; 533 } 534 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); 543 } 544 Tcl_ListObjAppendElement(0, pEval, pOld); 545 } 546 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); 555 } 556 Tcl_ListObjAppendElement(0, pEval, pNew); 557 } 558 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); 570 } 571 Tcl_ListObjAppendElement(0, pEval, pConflict); 572 } 573 574 /*********************************************************************** 575 ** This block is purely for testing some error conditions. 576 */ 577 if( eConf==SQLITE_CHANGESET_CONSTRAINT 578 || eConf==SQLITE_CHANGESET_NOTFOUND 579 ){ 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 ); 589 } 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 ); 600 } 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 ); 611 } 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 ); 618 } 619 /* End of testing block 620 ***********************************************************************/ 621 } 622 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); 635 } 636 } 637 638 Tcl_DecrRefCount(pEval); 639 return ret; 640 } 641 642 /* 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. 649 */ 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 */ 654 ){ 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; 660 661 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); 662 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++; 669 } 670 } 671 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++; 678 } 679 } 680 681 if( eConf==SQLITE_CHANGESET_DATA ){ 682 return SQLITE_CHANGESET_REPLACE; 683 } 684 return SQLITE_CHANGESET_OMIT; 685 } 686 687 static int testStreamInput( 688 void *pCtx, /* Context pointer */ 689 void *pData, /* Buffer to populate */ 690 int *pnData /* IN/OUT: Bytes requested/supplied */ 691 ){ 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 */ 696 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); 703 704 if( nRet>nReq ) nRet = nReq; 705 if( nRet>nRem ) nRet = nRem; 706 707 assert( nRet>=0 ); 708 if( nRet>0 ){ 709 memcpy(pData, &p->aData[p->iData], nRet); 710 p->iData += nRet; 711 } 712 713 *pnData = nRet; 714 return SQLITE_OK; 715 } 716 717 718 static int SQLITE_TCLAPI testSqlite3changesetApply( 719 int bV2, 720 void * clientData, 721 Tcl_Interp *interp, 722 int objc, 723 Tcl_Obj *CONST objv[] 724 ){ 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() */ 735 736 memset(&sStr, 0, sizeof(sStr)); 737 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 738 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++; 747 } 748 } 749 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?"; 756 } 757 Tcl_WrongNumArgs(interp, 1, objv, zMsg); 758 return TCL_ERROR; 759 } 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; 763 } 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; 769 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 774 ); 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 779 ); 780 } 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 788 ); 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 794 ); 795 } 796 } 797 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)); 804 } 805 } 806 sqlite3_free(pRebase); 807 return TCL_OK; 808 } 809 810 /* 811 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? 812 */ 813 static int SQLITE_TCLAPI test_sqlite3changeset_apply( 814 void * clientData, 815 Tcl_Interp *interp, 816 int objc, 817 Tcl_Obj *CONST objv[] 818 ){ 819 return testSqlite3changesetApply(0, clientData, interp, objc, objv); 820 } 821 /* 822 ** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? 823 */ 824 static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2( 825 void * clientData, 826 Tcl_Interp *interp, 827 int objc, 828 Tcl_Obj *CONST objv[] 829 ){ 830 return testSqlite3changesetApply(1, clientData, interp, objc, objv); 831 } 832 833 /* 834 ** sqlite3changeset_apply_replace_all DB CHANGESET 835 */ 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[] 841 ){ 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 */ 847 848 if( objc!=3 ){ 849 Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET"); 850 return TCL_ERROR; 851 } 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; 855 } 856 db = *(sqlite3 **)info.objClientData; 857 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset); 858 859 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0); 860 if( rc!=SQLITE_OK ){ 861 return test_session_error(interp, rc, 0); 862 } 863 Tcl_ResetResult(interp); 864 return TCL_OK; 865 } 866 867 868 /* 869 ** sqlite3changeset_invert CHANGESET 870 */ 871 static int SQLITE_TCLAPI test_sqlite3changeset_invert( 872 void * clientData, 873 Tcl_Interp *interp, 874 int objc, 875 Tcl_Obj *CONST objv[] 876 ){ 877 int rc; /* Return code from changeset_invert() */ 878 TestStreamInput sIn; /* Input stream */ 879 TestSessionsBlob sOut; /* Output blob */ 880 881 if( objc!=2 ){ 882 Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET"); 883 return TCL_ERROR; 884 } 885 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); 890 891 if( sIn.nStream ){ 892 rc = sqlite3changeset_invert_strm( 893 testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut 894 ); 895 }else{ 896 rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p); 897 } 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)); 902 } 903 sqlite3_free(sOut.p); 904 return rc; 905 } 906 907 /* 908 ** sqlite3changeset_concat LEFT RIGHT 909 */ 910 static int SQLITE_TCLAPI test_sqlite3changeset_concat( 911 void * clientData, 912 Tcl_Interp *interp, 913 int objc, 914 Tcl_Obj *CONST objv[] 915 ){ 916 int rc; /* Return code from changeset_invert() */ 917 918 TestStreamInput sLeft; /* Input stream */ 919 TestStreamInput sRight; /* Input stream */ 920 TestSessionsBlob sOut = {0,0}; /* Output blob */ 921 922 if( objc!=3 ){ 923 Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT"); 924 return TCL_ERROR; 925 } 926 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; 933 934 if( sLeft.nStream>0 ){ 935 rc = sqlite3changeset_concat_strm( 936 testStreamInput, (void*)&sLeft, 937 testStreamInput, (void*)&sRight, 938 testStreamOutput, (void*)&sOut 939 ); 940 }else{ 941 rc = sqlite3changeset_concat( 942 sLeft.nData, sLeft.aData, sRight.nData, sRight.aData, &sOut.n, &sOut.p 943 ); 944 } 945 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)); 950 } 951 sqlite3_free(sOut.p); 952 return rc; 953 } 954 955 /* 956 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT 957 */ 958 static int SQLITE_TCLAPI test_sqlite3session_foreach( 959 void * clientData, 960 Tcl_Interp *interp, 961 int objc, 962 Tcl_Obj *CONST objv[] 963 ){ 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; 972 973 TestStreamInput sStr; 974 memset(&sStr, 0, sizeof(sStr)); 975 976 if( objc>1 ){ 977 char *zOpt = Tcl_GetString(objv[1]); 978 isCheckNext = (strcmp(zOpt, "-next")==0); 979 } 980 if( objc!=4+isCheckNext ){ 981 Tcl_WrongNumArgs(interp, 1, objv, "?-next? VARNAME CHANGESET SCRIPT"); 982 return TCL_ERROR; 983 } 984 985 pVarname = objv[1+isCheckNext]; 986 pCS = objv[2+isCheckNext]; 987 pScript = objv[3+isCheckNext]; 988 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); 997 } 998 if( rc!=SQLITE_OK ){ 999 return test_session_error(interp, rc, 0); 1000 } 1001 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; 1011 1012 char *zPK; 1013 unsigned char *abPK; 1014 int i; 1015 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; 1022 } 1023 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 1030 )); 1031 1032 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1)); 1033 Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect)); 1034 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' : '.'); 1041 } 1042 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1)); 1043 ckfree(zPK); 1044 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); 1051 } 1052 } 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); 1059 } 1060 } 1061 Tcl_ListObjAppendElement(0, pVar, pOld); 1062 Tcl_ListObjAppendElement(0, pVar, pNew); 1063 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; 1069 } 1070 } 1071 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); 1078 } 1079 if( rc!=SQLITE_OK ){ 1080 return test_session_error(interp, rc, 0); 1081 } 1082 1083 return TCL_OK; 1084 } 1085 1086 /* 1087 ** tclcmd: CMD configure REBASE-BLOB 1088 ** tclcmd: CMD rebase CHANGESET 1089 ** tclcmd: CMD delete 1090 */ 1091 static int SQLITE_TCLAPI test_rebaser_cmd( 1092 void * clientData, 1093 Tcl_Interp *interp, 1094 int objc, 1095 Tcl_Obj *CONST objv[] 1096 ){ 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 } 1107 }; 1108 1109 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData; 1110 int iSub; 1111 int rc; 1112 1113 if( objc<2 ){ 1114 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); 1115 return TCL_ERROR; 1116 } 1117 rc = Tcl_GetIndexFromObjStruct(interp, 1118 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub 1119 ); 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; 1124 } 1125 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; 1134 } 1135 1136 case 1: /* delete */ 1137 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); 1138 break; 1139 1140 default: { /* rebase */ 1141 TestStreamInput sStr; /* Input stream */ 1142 TestSessionsBlob sOut; /* Output blob */ 1143 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); 1148 1149 if( sStr.nStream ){ 1150 rc = sqlite3rebaser_rebase_strm(p, 1151 testStreamInput, (void*)&sStr, 1152 testStreamOutput, (void*)&sOut 1153 ); 1154 }else{ 1155 rc = sqlite3rebaser_rebase(p, sStr.nData, sStr.aData, &sOut.n, &sOut.p); 1156 } 1157 1158 if( rc==SQLITE_OK ){ 1159 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(sOut.p, sOut.n)); 1160 } 1161 sqlite3_free(sOut.p); 1162 break; 1163 } 1164 } 1165 1166 if( rc!=SQLITE_OK ){ 1167 return test_session_error(interp, rc, 0); 1168 } 1169 return TCL_OK; 1170 } 1171 1172 static void SQLITE_TCLAPI test_rebaser_del(void *clientData){ 1173 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData; 1174 sqlite3rebaser_delete(p); 1175 } 1176 1177 /* 1178 ** tclcmd: sqlite3rebaser_create NAME 1179 */ 1180 static int SQLITE_TCLAPI test_sqlite3rebaser_create( 1181 void * clientData, 1182 Tcl_Interp *interp, 1183 int objc, 1184 Tcl_Obj *CONST objv[] 1185 ){ 1186 int rc; 1187 sqlite3_rebaser *pNew = 0; 1188 if( objc!=2 ){ 1189 Tcl_WrongNumArgs(interp, 1, objv, "NAME"); 1190 return SQLITE_ERROR; 1191 } 1192 1193 rc = sqlite3rebaser_create(&pNew); 1194 if( rc!=SQLITE_OK ){ 1195 return test_session_error(interp, rc, 0); 1196 } 1197 1198 Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]), test_rebaser_cmd, 1199 (ClientData)pNew, test_rebaser_del 1200 ); 1201 Tcl_SetObjResult(interp, objv[1]); 1202 return TCL_OK; 1203 } 1204 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 }, 1220 }; 1221 int i; 1222 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); 1226 } 1227 1228 return TCL_OK; 1229 } 1230 1231 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */ 1232