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 static 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 ){ 741 if( objc>1 ){ 742 const char *z1 = Tcl_GetString(objv[1]); 743 int n = strlen(z1); 744 if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){ 745 flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT; 746 objc--; 747 objv++; 748 } 749 } 750 if( objc>1 ){ 751 const char *z1 = Tcl_GetString(objv[1]); 752 int n = strlen(z1); 753 if( n>1 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){ 754 flags |= SQLITE_CHANGESETAPPLY_INVERT; 755 objc--; 756 objv++; 757 } 758 } 759 } 760 761 if( objc!=4 && objc!=5 ){ 762 const char *zMsg; 763 if( bV2 ){ 764 zMsg = "?-nosavepoint? ?-inverse? " 765 "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"; 766 }else{ 767 zMsg = "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"; 768 } 769 Tcl_WrongNumArgs(interp, 1, objv, zMsg); 770 return TCL_ERROR; 771 } 772 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){ 773 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[1]), 0); 774 return TCL_ERROR; 775 } 776 db = *(sqlite3 **)info.objClientData; 777 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset); 778 ctx.pConflictScript = objv[3]; 779 ctx.pFilterScript = objc==5 ? objv[4] : 0; 780 ctx.interp = interp; 781 782 if( sStr.nStream==0 ){ 783 if( bV2==0 ){ 784 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 785 (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx 786 ); 787 }else{ 788 rc = sqlite3changeset_apply_v2(db, nChangeset, pChangeset, 789 (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx, 790 &pRebase, &nRebase, flags 791 ); 792 } 793 }else{ 794 sStr.aData = (unsigned char*)pChangeset; 795 sStr.nData = nChangeset; 796 if( bV2==0 ){ 797 rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr, 798 (objc==5) ? test_filter_handler : 0, 799 test_conflict_handler, (void *)&ctx 800 ); 801 }else{ 802 rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr, 803 (objc==5) ? test_filter_handler : 0, 804 test_conflict_handler, (void *)&ctx, 805 &pRebase, &nRebase, flags 806 ); 807 } 808 } 809 810 if( rc!=SQLITE_OK ){ 811 return test_session_error(interp, rc, 0); 812 }else{ 813 Tcl_ResetResult(interp); 814 if( bV2 && pRebase ){ 815 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase)); 816 } 817 } 818 sqlite3_free(pRebase); 819 return TCL_OK; 820 } 821 822 /* 823 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? 824 */ 825 static int SQLITE_TCLAPI test_sqlite3changeset_apply( 826 void * clientData, 827 Tcl_Interp *interp, 828 int objc, 829 Tcl_Obj *CONST objv[] 830 ){ 831 return testSqlite3changesetApply(0, clientData, interp, objc, objv); 832 } 833 /* 834 ** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? 835 */ 836 static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2( 837 void * clientData, 838 Tcl_Interp *interp, 839 int objc, 840 Tcl_Obj *CONST objv[] 841 ){ 842 return testSqlite3changesetApply(1, clientData, interp, objc, objv); 843 } 844 845 /* 846 ** sqlite3changeset_apply_replace_all DB CHANGESET 847 */ 848 static int SQLITE_TCLAPI test_sqlite3changeset_apply_replace_all( 849 void * clientData, 850 Tcl_Interp *interp, 851 int objc, 852 Tcl_Obj *CONST objv[] 853 ){ 854 sqlite3 *db; /* Database handle */ 855 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */ 856 int rc; /* Return code from changeset_invert() */ 857 void *pChangeset; /* Buffer containing changeset */ 858 int nChangeset; /* Size of buffer aChangeset in bytes */ 859 860 if( objc!=3 ){ 861 Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET"); 862 return TCL_ERROR; 863 } 864 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){ 865 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0); 866 return TCL_ERROR; 867 } 868 db = *(sqlite3 **)info.objClientData; 869 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset); 870 871 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0); 872 if( rc!=SQLITE_OK ){ 873 return test_session_error(interp, rc, 0); 874 } 875 Tcl_ResetResult(interp); 876 return TCL_OK; 877 } 878 879 880 /* 881 ** sqlite3changeset_invert CHANGESET 882 */ 883 static int SQLITE_TCLAPI test_sqlite3changeset_invert( 884 void * clientData, 885 Tcl_Interp *interp, 886 int objc, 887 Tcl_Obj *CONST objv[] 888 ){ 889 int rc; /* Return code from changeset_invert() */ 890 TestStreamInput sIn; /* Input stream */ 891 TestSessionsBlob sOut; /* Output blob */ 892 893 if( objc!=2 ){ 894 Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET"); 895 return TCL_ERROR; 896 } 897 898 memset(&sIn, 0, sizeof(sIn)); 899 memset(&sOut, 0, sizeof(sOut)); 900 sIn.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 901 sIn.aData = Tcl_GetByteArrayFromObj(objv[1], &sIn.nData); 902 903 if( sIn.nStream ){ 904 rc = sqlite3changeset_invert_strm( 905 testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut 906 ); 907 }else{ 908 rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p); 909 } 910 if( rc!=SQLITE_OK ){ 911 rc = test_session_error(interp, rc, 0); 912 }else{ 913 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); 914 } 915 sqlite3_free(sOut.p); 916 return rc; 917 } 918 919 /* 920 ** sqlite3changeset_concat LEFT RIGHT 921 */ 922 static int SQLITE_TCLAPI test_sqlite3changeset_concat( 923 void * clientData, 924 Tcl_Interp *interp, 925 int objc, 926 Tcl_Obj *CONST objv[] 927 ){ 928 int rc; /* Return code from changeset_invert() */ 929 930 TestStreamInput sLeft; /* Input stream */ 931 TestStreamInput sRight; /* Input stream */ 932 TestSessionsBlob sOut = {0,0}; /* Output blob */ 933 934 if( objc!=3 ){ 935 Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT"); 936 return TCL_ERROR; 937 } 938 939 memset(&sLeft, 0, sizeof(sLeft)); 940 memset(&sRight, 0, sizeof(sRight)); 941 sLeft.aData = Tcl_GetByteArrayFromObj(objv[1], &sLeft.nData); 942 sRight.aData = Tcl_GetByteArrayFromObj(objv[2], &sRight.nData); 943 sLeft.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 944 sRight.nStream = sLeft.nStream; 945 946 if( sLeft.nStream>0 ){ 947 rc = sqlite3changeset_concat_strm( 948 testStreamInput, (void*)&sLeft, 949 testStreamInput, (void*)&sRight, 950 testStreamOutput, (void*)&sOut 951 ); 952 }else{ 953 rc = sqlite3changeset_concat( 954 sLeft.nData, sLeft.aData, sRight.nData, sRight.aData, &sOut.n, &sOut.p 955 ); 956 } 957 958 if( rc!=SQLITE_OK ){ 959 rc = test_session_error(interp, rc, 0); 960 }else{ 961 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); 962 } 963 sqlite3_free(sOut.p); 964 return rc; 965 } 966 967 /* 968 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT 969 */ 970 static int SQLITE_TCLAPI test_sqlite3session_foreach( 971 void * clientData, 972 Tcl_Interp *interp, 973 int objc, 974 Tcl_Obj *CONST objv[] 975 ){ 976 void *pChangeset; 977 int nChangeset; 978 sqlite3_changeset_iter *pIter; 979 int rc; 980 Tcl_Obj *pVarname; 981 Tcl_Obj *pCS; 982 Tcl_Obj *pScript; 983 int isCheckNext = 0; 984 int isInvert = 0; 985 986 TestStreamInput sStr; 987 memset(&sStr, 0, sizeof(sStr)); 988 989 while( objc>1 ){ 990 char *zOpt = Tcl_GetString(objv[1]); 991 int nOpt = strlen(zOpt); 992 if( zOpt[0]!='-' ) break; 993 if( nOpt<=7 && 0==sqlite3_strnicmp(zOpt, "-invert", nOpt) ){ 994 isInvert = 1; 995 }else 996 if( nOpt<=5 && 0==sqlite3_strnicmp(zOpt, "-next", nOpt) ){ 997 isCheckNext = 1; 998 }else{ 999 break; 1000 } 1001 objv++; 1002 objc--; 1003 } 1004 if( objc!=4 ){ 1005 Tcl_WrongNumArgs( 1006 interp, 1, objv, "?-next? ?-invert? VARNAME CHANGESET SCRIPT"); 1007 return TCL_ERROR; 1008 } 1009 1010 pVarname = objv[1]; 1011 pCS = objv[2]; 1012 pScript = objv[3]; 1013 1014 pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset); 1015 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 1016 if( isInvert ){ 1017 int f = SQLITE_CHANGESETSTART_INVERT; 1018 if( sStr.nStream==0 ){ 1019 rc = sqlite3changeset_start_v2(&pIter, nChangeset, pChangeset, f); 1020 }else{ 1021 void *pCtx = (void*)&sStr; 1022 sStr.aData = (unsigned char*)pChangeset; 1023 sStr.nData = nChangeset; 1024 rc = sqlite3changeset_start_v2_strm(&pIter, testStreamInput, pCtx, f); 1025 } 1026 }else{ 1027 if( sStr.nStream==0 ){ 1028 rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); 1029 }else{ 1030 sStr.aData = (unsigned char*)pChangeset; 1031 sStr.nData = nChangeset; 1032 rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr); 1033 } 1034 } 1035 if( rc!=SQLITE_OK ){ 1036 return test_session_error(interp, rc, 0); 1037 } 1038 1039 while( SQLITE_ROW==sqlite3changeset_next(pIter) ){ 1040 int nCol; /* Number of columns in table */ 1041 int nCol2; /* Number of columns in table */ 1042 int op; /* SQLITE_INSERT, UPDATE or DELETE */ 1043 const char *zTab; /* Name of table change applies to */ 1044 Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */ 1045 Tcl_Obj *pOld; /* Vector of old.* values */ 1046 Tcl_Obj *pNew; /* Vector of new.* values */ 1047 int bIndirect; 1048 1049 char *zPK; 1050 unsigned char *abPK; 1051 int i; 1052 1053 /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this 1054 ** iterator. */ 1055 int nDummy; 1056 if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){ 1057 sqlite3changeset_finalize(pIter); 1058 return TCL_ERROR; 1059 } 1060 1061 sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); 1062 pVar = Tcl_NewObj(); 1063 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj( 1064 op==SQLITE_INSERT ? "INSERT" : 1065 op==SQLITE_UPDATE ? "UPDATE" : 1066 "DELETE", -1 1067 )); 1068 1069 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1)); 1070 Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect)); 1071 1072 zPK = ckalloc(nCol+1); 1073 memset(zPK, 0, nCol+1); 1074 sqlite3changeset_pk(pIter, &abPK, &nCol2); 1075 assert( nCol==nCol2 ); 1076 for(i=0; i<nCol; i++){ 1077 zPK[i] = (abPK[i] ? 'X' : '.'); 1078 } 1079 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1)); 1080 ckfree(zPK); 1081 1082 pOld = Tcl_NewObj(); 1083 if( op!=SQLITE_INSERT ){ 1084 for(i=0; i<nCol; i++){ 1085 sqlite3_value *pVal; 1086 sqlite3changeset_old(pIter, i, &pVal); 1087 test_append_value(pOld, pVal); 1088 } 1089 } 1090 pNew = Tcl_NewObj(); 1091 if( op!=SQLITE_DELETE ){ 1092 for(i=0; i<nCol; i++){ 1093 sqlite3_value *pVal; 1094 sqlite3changeset_new(pIter, i, &pVal); 1095 test_append_value(pNew, pVal); 1096 } 1097 } 1098 Tcl_ListObjAppendElement(0, pVar, pOld); 1099 Tcl_ListObjAppendElement(0, pVar, pNew); 1100 1101 Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0); 1102 rc = Tcl_EvalObjEx(interp, pScript, 0); 1103 if( rc!=TCL_OK && rc!=TCL_CONTINUE ){ 1104 sqlite3changeset_finalize(pIter); 1105 return rc==TCL_BREAK ? TCL_OK : rc; 1106 } 1107 } 1108 1109 if( isCheckNext ){ 1110 int rc2 = sqlite3changeset_next(pIter); 1111 rc = sqlite3changeset_finalize(pIter); 1112 assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc ); 1113 }else{ 1114 rc = sqlite3changeset_finalize(pIter); 1115 } 1116 if( rc!=SQLITE_OK ){ 1117 return test_session_error(interp, rc, 0); 1118 } 1119 1120 return TCL_OK; 1121 } 1122 1123 /* 1124 ** tclcmd: CMD configure REBASE-BLOB 1125 ** tclcmd: CMD rebase CHANGESET 1126 ** tclcmd: CMD delete 1127 */ 1128 static int SQLITE_TCLAPI test_rebaser_cmd( 1129 void * clientData, 1130 Tcl_Interp *interp, 1131 int objc, 1132 Tcl_Obj *CONST objv[] 1133 ){ 1134 static struct RebaseSubcmd { 1135 const char *zSub; 1136 int nArg; 1137 const char *zMsg; 1138 int iSub; 1139 } aSub[] = { 1140 { "configure", 1, "REBASE-BLOB" }, /* 0 */ 1141 { "delete", 0, "" }, /* 1 */ 1142 { "rebase", 1, "CHANGESET" }, /* 2 */ 1143 { 0 } 1144 }; 1145 1146 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData; 1147 int iSub; 1148 int rc; 1149 1150 if( objc<2 ){ 1151 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); 1152 return TCL_ERROR; 1153 } 1154 rc = Tcl_GetIndexFromObjStruct(interp, 1155 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub 1156 ); 1157 if( rc!=TCL_OK ) return rc; 1158 if( objc!=2+aSub[iSub].nArg ){ 1159 Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg); 1160 return TCL_ERROR; 1161 } 1162 1163 assert( iSub==0 || iSub==1 || iSub==2 ); 1164 assert( rc==SQLITE_OK ); 1165 switch( iSub ){ 1166 case 0: { /* configure */ 1167 int nRebase = 0; 1168 unsigned char *pRebase = Tcl_GetByteArrayFromObj(objv[2], &nRebase); 1169 rc = sqlite3rebaser_configure(p, nRebase, pRebase); 1170 break; 1171 } 1172 1173 case 1: /* delete */ 1174 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); 1175 break; 1176 1177 default: { /* rebase */ 1178 TestStreamInput sStr; /* Input stream */ 1179 TestSessionsBlob sOut; /* Output blob */ 1180 1181 memset(&sStr, 0, sizeof(sStr)); 1182 memset(&sOut, 0, sizeof(sOut)); 1183 sStr.aData = Tcl_GetByteArrayFromObj(objv[2], &sStr.nData); 1184 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 1185 1186 if( sStr.nStream ){ 1187 rc = sqlite3rebaser_rebase_strm(p, 1188 testStreamInput, (void*)&sStr, 1189 testStreamOutput, (void*)&sOut 1190 ); 1191 }else{ 1192 rc = sqlite3rebaser_rebase(p, sStr.nData, sStr.aData, &sOut.n, &sOut.p); 1193 } 1194 1195 if( rc==SQLITE_OK ){ 1196 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(sOut.p, sOut.n)); 1197 } 1198 sqlite3_free(sOut.p); 1199 break; 1200 } 1201 } 1202 1203 if( rc!=SQLITE_OK ){ 1204 return test_session_error(interp, rc, 0); 1205 } 1206 return TCL_OK; 1207 } 1208 1209 static void SQLITE_TCLAPI test_rebaser_del(void *clientData){ 1210 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData; 1211 sqlite3rebaser_delete(p); 1212 } 1213 1214 /* 1215 ** tclcmd: sqlite3rebaser_create NAME 1216 */ 1217 static int SQLITE_TCLAPI test_sqlite3rebaser_create( 1218 void * clientData, 1219 Tcl_Interp *interp, 1220 int objc, 1221 Tcl_Obj *CONST objv[] 1222 ){ 1223 int rc; 1224 sqlite3_rebaser *pNew = 0; 1225 if( objc!=2 ){ 1226 Tcl_WrongNumArgs(interp, 1, objv, "NAME"); 1227 return SQLITE_ERROR; 1228 } 1229 1230 rc = sqlite3rebaser_create(&pNew); 1231 if( rc!=SQLITE_OK ){ 1232 return test_session_error(interp, rc, 0); 1233 } 1234 1235 Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]), test_rebaser_cmd, 1236 (ClientData)pNew, test_rebaser_del 1237 ); 1238 Tcl_SetObjResult(interp, objv[1]); 1239 return TCL_OK; 1240 } 1241 1242 /* 1243 ** tclcmd: sqlite3rebaser_configure OP VALUE 1244 */ 1245 static int SQLITE_TCLAPI test_sqlite3session_config( 1246 void * clientData, 1247 Tcl_Interp *interp, 1248 int objc, 1249 Tcl_Obj *CONST objv[] 1250 ){ 1251 static struct ConfigOpt { 1252 const char *zSub; 1253 int op; 1254 } aSub[] = { 1255 { "strm_size", SQLITE_SESSION_CONFIG_STRMSIZE }, 1256 { "invalid", 0 }, 1257 { 0 } 1258 }; 1259 int rc; 1260 int iSub; 1261 int iVal; 1262 1263 if( objc!=3 ){ 1264 Tcl_WrongNumArgs(interp, 1, objv, "OP VALUE"); 1265 return SQLITE_ERROR; 1266 } 1267 rc = Tcl_GetIndexFromObjStruct(interp, 1268 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub 1269 ); 1270 if( rc!=TCL_OK ) return rc; 1271 if( Tcl_GetIntFromObj(interp, objv[2], &iVal) ) return TCL_ERROR; 1272 1273 rc = sqlite3session_config(aSub[iSub].op, (void*)&iVal); 1274 if( rc!=SQLITE_OK ){ 1275 return test_session_error(interp, rc, 0); 1276 } 1277 Tcl_SetObjResult(interp, Tcl_NewIntObj(iVal)); 1278 return TCL_OK; 1279 } 1280 1281 int TestSession_Init(Tcl_Interp *interp){ 1282 struct Cmd { 1283 const char *zCmd; 1284 Tcl_ObjCmdProc *xProc; 1285 } aCmd[] = { 1286 { "sqlite3session", test_sqlite3session }, 1287 { "sqlite3session_foreach", test_sqlite3session_foreach }, 1288 { "sqlite3changeset_invert", test_sqlite3changeset_invert }, 1289 { "sqlite3changeset_concat", test_sqlite3changeset_concat }, 1290 { "sqlite3changeset_apply", test_sqlite3changeset_apply }, 1291 { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 }, 1292 { "sqlite3changeset_apply_replace_all", 1293 test_sqlite3changeset_apply_replace_all }, 1294 { "sql_exec_changeset", test_sql_exec_changeset }, 1295 { "sqlite3rebaser_create", test_sqlite3rebaser_create }, 1296 { "sqlite3session_config", test_sqlite3session_config }, 1297 }; 1298 int i; 1299 1300 for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){ 1301 struct Cmd *p = &aCmd[i]; 1302 Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0); 1303 } 1304 1305 return TCL_OK; 1306 } 1307 1308 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */ 1309