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 Tcl_Obj *pName = Tcl_NewStringObj(zVar, -1); 150 Tcl_IncrRefCount(pName); 151 pObj = Tcl_ObjGetVar2(interp, pName, 0, TCL_GLOBAL_ONLY); 152 Tcl_DecrRefCount(pName); 153 if( pObj ) Tcl_GetIntFromObj(0, pObj, &iVal); 154 return iVal; 155 } 156 157 static int test_session_error(Tcl_Interp *interp, int rc, char *zErr){ 158 extern const char *sqlite3ErrName(int); 159 Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); 160 if( zErr ){ 161 Tcl_AppendResult(interp, " - ", zErr, 0); 162 sqlite3_free(zErr); 163 } 164 return TCL_ERROR; 165 } 166 167 static int test_table_filter(void *pCtx, const char *zTbl){ 168 TestSession *p = (TestSession*)pCtx; 169 Tcl_Obj *pEval; 170 int rc; 171 int bRes = 0; 172 173 pEval = Tcl_DuplicateObj(p->pFilterScript); 174 Tcl_IncrRefCount(pEval); 175 rc = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zTbl, -1)); 176 if( rc==TCL_OK ){ 177 rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL); 178 } 179 if( rc==TCL_OK ){ 180 rc = Tcl_GetBooleanFromObj(p->interp, Tcl_GetObjResult(p->interp), &bRes); 181 } 182 if( rc!=TCL_OK ){ 183 /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */ 184 Tcl_BackgroundError(p->interp); 185 } 186 Tcl_DecrRefCount(pEval); 187 188 return bRes; 189 } 190 191 struct TestSessionsBlob { 192 void *p; 193 int n; 194 }; 195 typedef struct TestSessionsBlob TestSessionsBlob; 196 197 static int testStreamOutput( 198 void *pCtx, 199 const void *pData, 200 int nData 201 ){ 202 TestSessionsBlob *pBlob = (TestSessionsBlob*)pCtx; 203 char *pNew; 204 205 assert( nData>0 ); 206 pNew = (char*)sqlite3_realloc(pBlob->p, pBlob->n + nData); 207 if( pNew==0 ){ 208 return SQLITE_NOMEM; 209 } 210 pBlob->p = (void*)pNew; 211 memcpy(&pNew[pBlob->n], pData, nData); 212 pBlob->n += nData; 213 return SQLITE_OK; 214 } 215 216 /* 217 ** Tclcmd: $session attach TABLE 218 ** $session changeset 219 ** $session delete 220 ** $session enable BOOL 221 ** $session indirect INTEGER 222 ** $session patchset 223 ** $session table_filter SCRIPT 224 */ 225 static int SQLITE_TCLAPI test_session_cmd( 226 void *clientData, 227 Tcl_Interp *interp, 228 int objc, 229 Tcl_Obj *CONST objv[] 230 ){ 231 TestSession *p = (TestSession*)clientData; 232 sqlite3_session *pSession = p->pSession; 233 static struct SessionSubcmd { 234 const char *zSub; 235 int nArg; 236 const char *zMsg; 237 int iSub; 238 } aSub[] = { 239 { "attach", 1, "TABLE", }, /* 0 */ 240 { "changeset", 0, "", }, /* 1 */ 241 { "delete", 0, "", }, /* 2 */ 242 { "enable", 1, "BOOL", }, /* 3 */ 243 { "indirect", 1, "BOOL", }, /* 4 */ 244 { "isempty", 0, "", }, /* 5 */ 245 { "table_filter", 1, "SCRIPT", }, /* 6 */ 246 { "patchset", 0, "", }, /* 7 */ 247 { "diff", 2, "FROMDB TBL", }, /* 8 */ 248 { "memory_used", 0, "", }, /* 9 */ 249 { "changeset_size", 0, "", }, /* 10 */ 250 { "object_config_size", 1, "INTEGER", }, /* 11 */ 251 { 0 } 252 }; 253 int iSub; 254 int rc; 255 256 if( objc<2 ){ 257 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); 258 return TCL_ERROR; 259 } 260 rc = Tcl_GetIndexFromObjStruct(interp, 261 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub 262 ); 263 if( rc!=TCL_OK ) return rc; 264 if( objc!=2+aSub[iSub].nArg ){ 265 Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg); 266 return TCL_ERROR; 267 } 268 269 switch( iSub ){ 270 case 0: { /* attach */ 271 char *zArg = Tcl_GetString(objv[2]); 272 if( zArg[0]=='*' && zArg[1]=='\0' ) zArg = 0; 273 rc = sqlite3session_attach(pSession, zArg); 274 if( rc!=SQLITE_OK ){ 275 return test_session_error(interp, rc, 0); 276 } 277 break; 278 } 279 280 case 7: /* patchset */ 281 case 1: { /* changeset */ 282 TestSessionsBlob o = {0, 0}; 283 if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){ 284 void *pCtx = (void*)&o; 285 if( iSub==7 ){ 286 rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx); 287 }else{ 288 rc = sqlite3session_changeset_strm(pSession, testStreamOutput, pCtx); 289 } 290 }else{ 291 if( iSub==7 ){ 292 rc = sqlite3session_patchset(pSession, &o.n, &o.p); 293 }else{ 294 rc = sqlite3session_changeset(pSession, &o.n, &o.p); 295 } 296 } 297 if( rc==SQLITE_OK ){ 298 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n)); 299 } 300 sqlite3_free(o.p); 301 if( rc!=SQLITE_OK ){ 302 return test_session_error(interp, rc, 0); 303 } 304 break; 305 } 306 307 case 2: /* delete */ 308 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); 309 break; 310 311 case 3: { /* enable */ 312 int val; 313 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR; 314 val = sqlite3session_enable(pSession, val); 315 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); 316 break; 317 } 318 319 case 4: { /* indirect */ 320 int val; 321 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR; 322 val = sqlite3session_indirect(pSession, val); 323 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); 324 break; 325 } 326 327 case 5: { /* isempty */ 328 int val; 329 val = sqlite3session_isempty(pSession); 330 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); 331 break; 332 } 333 334 case 6: { /* table_filter */ 335 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript); 336 p->interp = interp; 337 p->pFilterScript = Tcl_DuplicateObj(objv[2]); 338 Tcl_IncrRefCount(p->pFilterScript); 339 sqlite3session_table_filter(pSession, test_table_filter, clientData); 340 break; 341 } 342 343 case 8: { /* diff */ 344 char *zErr = 0; 345 rc = sqlite3session_diff(pSession, 346 Tcl_GetString(objv[2]), 347 Tcl_GetString(objv[3]), 348 &zErr 349 ); 350 assert( rc!=SQLITE_OK || zErr==0 ); 351 if( rc ){ 352 return test_session_error(interp, rc, zErr); 353 } 354 break; 355 } 356 357 case 9: { /* memory_used */ 358 sqlite3_int64 nMalloc = sqlite3session_memory_used(pSession); 359 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nMalloc)); 360 break; 361 } 362 363 case 10: { 364 sqlite3_int64 nSize = sqlite3session_changeset_size(pSession); 365 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nSize)); 366 break; 367 } 368 case 11: { 369 int rc; 370 int iArg; 371 if( Tcl_GetIntFromObj(interp, objv[2], &iArg) ){ 372 return TCL_ERROR; 373 } 374 rc = sqlite3session_object_config( 375 pSession, SQLITE_SESSION_OBJCONFIG_SIZE, &iArg 376 ); 377 if( rc!=SQLITE_OK ){ 378 extern const char *sqlite3ErrName(int); 379 Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); 380 }else{ 381 Tcl_SetObjResult(interp, Tcl_NewIntObj(iArg)); 382 } 383 break; 384 } 385 } 386 387 return TCL_OK; 388 } 389 390 static void SQLITE_TCLAPI test_session_del(void *clientData){ 391 TestSession *p = (TestSession*)clientData; 392 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript); 393 sqlite3session_delete(p->pSession); 394 ckfree((char*)p); 395 } 396 397 /* 398 ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME 399 */ 400 static int SQLITE_TCLAPI test_sqlite3session( 401 void * clientData, 402 Tcl_Interp *interp, 403 int objc, 404 Tcl_Obj *CONST objv[] 405 ){ 406 sqlite3 *db; 407 Tcl_CmdInfo info; 408 int rc; /* sqlite3session_create() return code */ 409 TestSession *p; /* New wrapper object */ 410 int iArg = -1; 411 412 if( objc!=4 ){ 413 Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME"); 414 return TCL_ERROR; 415 } 416 417 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){ 418 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0); 419 return TCL_ERROR; 420 } 421 db = *(sqlite3 **)info.objClientData; 422 423 p = (TestSession*)ckalloc(sizeof(TestSession)); 424 memset(p, 0, sizeof(TestSession)); 425 rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &p->pSession); 426 if( rc!=SQLITE_OK ){ 427 ckfree((char*)p); 428 return test_session_error(interp, rc, 0); 429 } 430 431 /* Query the SQLITE_SESSION_OBJCONFIG_SIZE option to ensure that it 432 ** is clear by default. Then set it. */ 433 sqlite3session_object_config(p->pSession,SQLITE_SESSION_OBJCONFIG_SIZE,&iArg); 434 assert( iArg==0 ); 435 iArg = 1; 436 sqlite3session_object_config(p->pSession,SQLITE_SESSION_OBJCONFIG_SIZE,&iArg); 437 438 Tcl_CreateObjCommand( 439 interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)p, 440 test_session_del 441 ); 442 Tcl_SetObjResult(interp, objv[1]); 443 return TCL_OK; 444 } 445 446 static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){ 447 if( pVal==0 ){ 448 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj()); 449 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj()); 450 }else{ 451 Tcl_Obj *pObj; 452 switch( sqlite3_value_type(pVal) ){ 453 case SQLITE_NULL: 454 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("n", 1)); 455 pObj = Tcl_NewObj(); 456 break; 457 case SQLITE_INTEGER: 458 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("i", 1)); 459 pObj = Tcl_NewWideIntObj(sqlite3_value_int64(pVal)); 460 break; 461 case SQLITE_FLOAT: 462 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("f", 1)); 463 pObj = Tcl_NewDoubleObj(sqlite3_value_double(pVal)); 464 break; 465 case SQLITE_TEXT: { 466 const char *z = (char*)sqlite3_value_blob(pVal); 467 int n = sqlite3_value_bytes(pVal); 468 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("t", 1)); 469 pObj = Tcl_NewStringObj(z, n); 470 break; 471 } 472 default: 473 assert( sqlite3_value_type(pVal)==SQLITE_BLOB ); 474 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("b", 1)); 475 pObj = Tcl_NewByteArrayObj( 476 sqlite3_value_blob(pVal), 477 sqlite3_value_bytes(pVal) 478 ); 479 break; 480 } 481 Tcl_ListObjAppendElement(0, pList, pObj); 482 } 483 } 484 485 typedef struct TestConflictHandler TestConflictHandler; 486 struct TestConflictHandler { 487 Tcl_Interp *interp; 488 Tcl_Obj *pConflictScript; 489 Tcl_Obj *pFilterScript; 490 }; 491 492 static int test_obj_eq_string(Tcl_Obj *p, const char *z){ 493 int n; 494 int nObj; 495 char *zObj; 496 497 n = (int)strlen(z); 498 zObj = Tcl_GetStringFromObj(p, &nObj); 499 500 return (nObj==n && (n==0 || 0==memcmp(zObj, z, n))); 501 } 502 503 static int test_filter_handler( 504 void *pCtx, /* Pointer to TestConflictHandler structure */ 505 const char *zTab /* Table name */ 506 ){ 507 TestConflictHandler *p = (TestConflictHandler *)pCtx; 508 int res = 1; 509 Tcl_Obj *pEval; 510 Tcl_Interp *interp = p->interp; 511 512 pEval = Tcl_DuplicateObj(p->pFilterScript); 513 Tcl_IncrRefCount(pEval); 514 515 if( TCL_OK!=Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1)) 516 || TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) 517 || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res) 518 ){ 519 Tcl_BackgroundError(interp); 520 } 521 522 Tcl_DecrRefCount(pEval); 523 return res; 524 } 525 526 static int test_conflict_handler( 527 void *pCtx, /* Pointer to TestConflictHandler structure */ 528 int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */ 529 sqlite3_changeset_iter *pIter /* Handle describing change and conflict */ 530 ){ 531 TestConflictHandler *p = (TestConflictHandler *)pCtx; 532 Tcl_Obj *pEval; 533 Tcl_Interp *interp = p->interp; 534 int ret = 0; /* Return value */ 535 536 int op; /* SQLITE_UPDATE, DELETE or INSERT */ 537 const char *zTab; /* Name of table conflict is on */ 538 int nCol; /* Number of columns in table zTab */ 539 540 pEval = Tcl_DuplicateObj(p->pConflictScript); 541 Tcl_IncrRefCount(pEval); 542 543 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); 544 545 if( eConf==SQLITE_CHANGESET_FOREIGN_KEY ){ 546 int nFk; 547 sqlite3changeset_fk_conflicts(pIter, &nFk); 548 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj("FOREIGN_KEY", -1)); 549 Tcl_ListObjAppendElement(0, pEval, Tcl_NewIntObj(nFk)); 550 }else{ 551 552 /* Append the operation type. */ 553 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj( 554 op==SQLITE_INSERT ? "INSERT" : 555 op==SQLITE_UPDATE ? "UPDATE" : 556 "DELETE", -1 557 )); 558 559 /* Append the table name. */ 560 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1)); 561 562 /* Append the conflict type. */ 563 switch( eConf ){ 564 case SQLITE_CHANGESET_DATA: 565 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1)); 566 break; 567 case SQLITE_CHANGESET_NOTFOUND: 568 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1)); 569 break; 570 case SQLITE_CHANGESET_CONFLICT: 571 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1)); 572 break; 573 case SQLITE_CHANGESET_CONSTRAINT: 574 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1)); 575 break; 576 } 577 578 /* If this is not an INSERT, append the old row */ 579 if( op!=SQLITE_INSERT ){ 580 int i; 581 Tcl_Obj *pOld = Tcl_NewObj(); 582 for(i=0; i<nCol; i++){ 583 sqlite3_value *pVal; 584 sqlite3changeset_old(pIter, i, &pVal); 585 test_append_value(pOld, pVal); 586 } 587 Tcl_ListObjAppendElement(0, pEval, pOld); 588 } 589 590 /* If this is not a DELETE, append the new row */ 591 if( op!=SQLITE_DELETE ){ 592 int i; 593 Tcl_Obj *pNew = Tcl_NewObj(); 594 for(i=0; i<nCol; i++){ 595 sqlite3_value *pVal; 596 sqlite3changeset_new(pIter, i, &pVal); 597 test_append_value(pNew, pVal); 598 } 599 Tcl_ListObjAppendElement(0, pEval, pNew); 600 } 601 602 /* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append 603 ** the conflicting row. */ 604 if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){ 605 int i; 606 Tcl_Obj *pConflict = Tcl_NewObj(); 607 for(i=0; i<nCol; i++){ 608 int rc; 609 sqlite3_value *pVal; 610 rc = sqlite3changeset_conflict(pIter, i, &pVal); 611 assert( rc==SQLITE_OK ); 612 test_append_value(pConflict, pVal); 613 } 614 Tcl_ListObjAppendElement(0, pEval, pConflict); 615 } 616 617 /*********************************************************************** 618 ** This block is purely for testing some error conditions. 619 */ 620 if( eConf==SQLITE_CHANGESET_CONSTRAINT 621 || eConf==SQLITE_CHANGESET_NOTFOUND 622 ){ 623 sqlite3_value *pVal; 624 int rc = sqlite3changeset_conflict(pIter, 0, &pVal); 625 assert( rc==SQLITE_MISUSE ); 626 }else{ 627 sqlite3_value *pVal; 628 int rc = sqlite3changeset_conflict(pIter, -1, &pVal); 629 assert( rc==SQLITE_RANGE ); 630 rc = sqlite3changeset_conflict(pIter, nCol, &pVal); 631 assert( rc==SQLITE_RANGE ); 632 } 633 if( op==SQLITE_DELETE ){ 634 sqlite3_value *pVal; 635 int rc = sqlite3changeset_new(pIter, 0, &pVal); 636 assert( rc==SQLITE_MISUSE ); 637 }else{ 638 sqlite3_value *pVal; 639 int rc = sqlite3changeset_new(pIter, -1, &pVal); 640 assert( rc==SQLITE_RANGE ); 641 rc = sqlite3changeset_new(pIter, nCol, &pVal); 642 assert( rc==SQLITE_RANGE ); 643 } 644 if( op==SQLITE_INSERT ){ 645 sqlite3_value *pVal; 646 int rc = sqlite3changeset_old(pIter, 0, &pVal); 647 assert( rc==SQLITE_MISUSE ); 648 }else{ 649 sqlite3_value *pVal; 650 int rc = sqlite3changeset_old(pIter, -1, &pVal); 651 assert( rc==SQLITE_RANGE ); 652 rc = sqlite3changeset_old(pIter, nCol, &pVal); 653 assert( rc==SQLITE_RANGE ); 654 } 655 if( eConf!=SQLITE_CHANGESET_FOREIGN_KEY ){ 656 /* eConf!=FOREIGN_KEY is always true at this point. The condition is 657 ** just there to make it clearer what is being tested. */ 658 int nDummy; 659 int rc = sqlite3changeset_fk_conflicts(pIter, &nDummy); 660 assert( rc==SQLITE_MISUSE ); 661 } 662 /* End of testing block 663 ***********************************************************************/ 664 } 665 666 if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){ 667 Tcl_BackgroundError(interp); 668 }else{ 669 Tcl_Obj *pRes = Tcl_GetObjResult(interp); 670 if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){ 671 ret = SQLITE_CHANGESET_OMIT; 672 }else if( test_obj_eq_string(pRes, "REPLACE") ){ 673 ret = SQLITE_CHANGESET_REPLACE; 674 }else if( test_obj_eq_string(pRes, "ABORT") ){ 675 ret = SQLITE_CHANGESET_ABORT; 676 }else{ 677 Tcl_GetIntFromObj(0, pRes, &ret); 678 } 679 } 680 681 Tcl_DecrRefCount(pEval); 682 return ret; 683 } 684 685 /* 686 ** The conflict handler used by sqlite3changeset_apply_replace_all(). 687 ** This conflict handler calls sqlite3_value_text16() on all available 688 ** sqlite3_value objects and then returns CHANGESET_REPLACE, or 689 ** CHANGESET_OMIT if REPLACE is not applicable. This is used to test the 690 ** effect of a malloc failure within an sqlite3_value_xxx() function 691 ** invoked by a conflict-handler callback. 692 */ 693 static int replace_handler( 694 void *pCtx, /* Pointer to TestConflictHandler structure */ 695 int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */ 696 sqlite3_changeset_iter *pIter /* Handle describing change and conflict */ 697 ){ 698 int op; /* SQLITE_UPDATE, DELETE or INSERT */ 699 const char *zTab; /* Name of table conflict is on */ 700 int nCol; /* Number of columns in table zTab */ 701 int i; 702 int x = 0; 703 704 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); 705 706 if( op!=SQLITE_INSERT ){ 707 for(i=0; i<nCol; i++){ 708 sqlite3_value *pVal; 709 sqlite3changeset_old(pIter, i, &pVal); 710 sqlite3_value_text16(pVal); 711 x++; 712 } 713 } 714 715 if( op!=SQLITE_DELETE ){ 716 for(i=0; i<nCol; i++){ 717 sqlite3_value *pVal; 718 sqlite3changeset_new(pIter, i, &pVal); 719 sqlite3_value_text16(pVal); 720 x++; 721 } 722 } 723 724 if( eConf==SQLITE_CHANGESET_DATA ){ 725 return SQLITE_CHANGESET_REPLACE; 726 } 727 return SQLITE_CHANGESET_OMIT; 728 } 729 730 static int testStreamInput( 731 void *pCtx, /* Context pointer */ 732 void *pData, /* Buffer to populate */ 733 int *pnData /* IN/OUT: Bytes requested/supplied */ 734 ){ 735 TestStreamInput *p = (TestStreamInput*)pCtx; 736 int nReq = *pnData; /* Bytes of data requested */ 737 int nRem = p->nData - p->iData; /* Bytes of data available */ 738 int nRet = p->nStream; /* Bytes actually returned */ 739 740 /* Allocate and free some space. There is no point to this, other than 741 ** that it allows the regular OOM fault-injection tests to cause an error 742 ** in this function. */ 743 void *pAlloc = sqlite3_malloc(10); 744 if( pAlloc==0 ) return SQLITE_NOMEM; 745 sqlite3_free(pAlloc); 746 747 if( nRet>nReq ) nRet = nReq; 748 if( nRet>nRem ) nRet = nRem; 749 750 assert( nRet>=0 ); 751 if( nRet>0 ){ 752 memcpy(pData, &p->aData[p->iData], nRet); 753 p->iData += nRet; 754 } 755 756 *pnData = nRet; 757 return SQLITE_OK; 758 } 759 760 761 static int SQLITE_TCLAPI testSqlite3changesetApply( 762 int bV2, 763 void * clientData, 764 Tcl_Interp *interp, 765 int objc, 766 Tcl_Obj *CONST objv[] 767 ){ 768 sqlite3 *db; /* Database handle */ 769 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */ 770 int rc; /* Return code from changeset_invert() */ 771 void *pChangeset; /* Buffer containing changeset */ 772 int nChangeset; /* Size of buffer aChangeset in bytes */ 773 TestConflictHandler ctx; 774 TestStreamInput sStr; 775 void *pRebase = 0; 776 int nRebase = 0; 777 int flags = 0; /* Flags for apply_v2() */ 778 779 memset(&sStr, 0, sizeof(sStr)); 780 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 781 782 /* Check for the -nosavepoint flag */ 783 if( bV2 ){ 784 if( objc>1 ){ 785 const char *z1 = Tcl_GetString(objv[1]); 786 int n = strlen(z1); 787 if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){ 788 flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT; 789 objc--; 790 objv++; 791 } 792 } 793 if( objc>1 ){ 794 const char *z1 = Tcl_GetString(objv[1]); 795 int n = strlen(z1); 796 if( n>1 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){ 797 flags |= SQLITE_CHANGESETAPPLY_INVERT; 798 objc--; 799 objv++; 800 } 801 } 802 } 803 804 if( objc!=4 && objc!=5 ){ 805 const char *zMsg; 806 if( bV2 ){ 807 zMsg = "?-nosavepoint? ?-inverse? " 808 "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"; 809 }else{ 810 zMsg = "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"; 811 } 812 Tcl_WrongNumArgs(interp, 1, objv, zMsg); 813 return TCL_ERROR; 814 } 815 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){ 816 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[1]), 0); 817 return TCL_ERROR; 818 } 819 db = *(sqlite3 **)info.objClientData; 820 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset); 821 ctx.pConflictScript = objv[3]; 822 ctx.pFilterScript = objc==5 ? objv[4] : 0; 823 ctx.interp = interp; 824 825 if( sStr.nStream==0 ){ 826 if( bV2==0 ){ 827 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 828 (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx 829 ); 830 }else{ 831 rc = sqlite3changeset_apply_v2(db, nChangeset, pChangeset, 832 (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx, 833 &pRebase, &nRebase, flags 834 ); 835 } 836 }else{ 837 sStr.aData = (unsigned char*)pChangeset; 838 sStr.nData = nChangeset; 839 if( bV2==0 ){ 840 rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr, 841 (objc==5) ? test_filter_handler : 0, 842 test_conflict_handler, (void *)&ctx 843 ); 844 }else{ 845 rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr, 846 (objc==5) ? test_filter_handler : 0, 847 test_conflict_handler, (void *)&ctx, 848 &pRebase, &nRebase, flags 849 ); 850 } 851 } 852 853 if( rc!=SQLITE_OK ){ 854 return test_session_error(interp, rc, 0); 855 }else{ 856 Tcl_ResetResult(interp); 857 if( bV2 && pRebase ){ 858 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase)); 859 } 860 } 861 sqlite3_free(pRebase); 862 return TCL_OK; 863 } 864 865 /* 866 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? 867 */ 868 static int SQLITE_TCLAPI test_sqlite3changeset_apply( 869 void * clientData, 870 Tcl_Interp *interp, 871 int objc, 872 Tcl_Obj *CONST objv[] 873 ){ 874 return testSqlite3changesetApply(0, clientData, interp, objc, objv); 875 } 876 /* 877 ** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? 878 */ 879 static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2( 880 void * clientData, 881 Tcl_Interp *interp, 882 int objc, 883 Tcl_Obj *CONST objv[] 884 ){ 885 return testSqlite3changesetApply(1, clientData, interp, objc, objv); 886 } 887 888 /* 889 ** sqlite3changeset_apply_replace_all DB CHANGESET 890 */ 891 static int SQLITE_TCLAPI test_sqlite3changeset_apply_replace_all( 892 void * clientData, 893 Tcl_Interp *interp, 894 int objc, 895 Tcl_Obj *CONST objv[] 896 ){ 897 sqlite3 *db; /* Database handle */ 898 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */ 899 int rc; /* Return code from changeset_invert() */ 900 void *pChangeset; /* Buffer containing changeset */ 901 int nChangeset; /* Size of buffer aChangeset in bytes */ 902 903 if( objc!=3 ){ 904 Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET"); 905 return TCL_ERROR; 906 } 907 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){ 908 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0); 909 return TCL_ERROR; 910 } 911 db = *(sqlite3 **)info.objClientData; 912 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset); 913 914 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0); 915 if( rc!=SQLITE_OK ){ 916 return test_session_error(interp, rc, 0); 917 } 918 Tcl_ResetResult(interp); 919 return TCL_OK; 920 } 921 922 923 /* 924 ** sqlite3changeset_invert CHANGESET 925 */ 926 static int SQLITE_TCLAPI test_sqlite3changeset_invert( 927 void * clientData, 928 Tcl_Interp *interp, 929 int objc, 930 Tcl_Obj *CONST objv[] 931 ){ 932 int rc; /* Return code from changeset_invert() */ 933 TestStreamInput sIn; /* Input stream */ 934 TestSessionsBlob sOut; /* Output blob */ 935 936 if( objc!=2 ){ 937 Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET"); 938 return TCL_ERROR; 939 } 940 941 memset(&sIn, 0, sizeof(sIn)); 942 memset(&sOut, 0, sizeof(sOut)); 943 sIn.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 944 sIn.aData = Tcl_GetByteArrayFromObj(objv[1], &sIn.nData); 945 946 if( sIn.nStream ){ 947 rc = sqlite3changeset_invert_strm( 948 testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut 949 ); 950 }else{ 951 rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p); 952 } 953 if( rc!=SQLITE_OK ){ 954 rc = test_session_error(interp, rc, 0); 955 }else{ 956 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); 957 } 958 sqlite3_free(sOut.p); 959 return rc; 960 } 961 962 /* 963 ** sqlite3changeset_concat LEFT RIGHT 964 */ 965 static int SQLITE_TCLAPI test_sqlite3changeset_concat( 966 void * clientData, 967 Tcl_Interp *interp, 968 int objc, 969 Tcl_Obj *CONST objv[] 970 ){ 971 int rc; /* Return code from changeset_invert() */ 972 973 TestStreamInput sLeft; /* Input stream */ 974 TestStreamInput sRight; /* Input stream */ 975 TestSessionsBlob sOut = {0,0}; /* Output blob */ 976 977 if( objc!=3 ){ 978 Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT"); 979 return TCL_ERROR; 980 } 981 982 memset(&sLeft, 0, sizeof(sLeft)); 983 memset(&sRight, 0, sizeof(sRight)); 984 sLeft.aData = Tcl_GetByteArrayFromObj(objv[1], &sLeft.nData); 985 sRight.aData = Tcl_GetByteArrayFromObj(objv[2], &sRight.nData); 986 sLeft.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 987 sRight.nStream = sLeft.nStream; 988 989 if( sLeft.nStream>0 ){ 990 rc = sqlite3changeset_concat_strm( 991 testStreamInput, (void*)&sLeft, 992 testStreamInput, (void*)&sRight, 993 testStreamOutput, (void*)&sOut 994 ); 995 }else{ 996 rc = sqlite3changeset_concat( 997 sLeft.nData, sLeft.aData, sRight.nData, sRight.aData, &sOut.n, &sOut.p 998 ); 999 } 1000 1001 if( rc!=SQLITE_OK ){ 1002 rc = test_session_error(interp, rc, 0); 1003 }else{ 1004 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); 1005 } 1006 sqlite3_free(sOut.p); 1007 return rc; 1008 } 1009 1010 /* 1011 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT 1012 */ 1013 static int SQLITE_TCLAPI test_sqlite3session_foreach( 1014 void * clientData, 1015 Tcl_Interp *interp, 1016 int objc, 1017 Tcl_Obj *CONST objv[] 1018 ){ 1019 void *pChangeset; 1020 int nChangeset; 1021 sqlite3_changeset_iter *pIter; 1022 int rc; 1023 Tcl_Obj *pVarname; 1024 Tcl_Obj *pCS; 1025 Tcl_Obj *pScript; 1026 int isCheckNext = 0; 1027 int isInvert = 0; 1028 1029 TestStreamInput sStr; 1030 memset(&sStr, 0, sizeof(sStr)); 1031 1032 while( objc>1 ){ 1033 char *zOpt = Tcl_GetString(objv[1]); 1034 int nOpt = strlen(zOpt); 1035 if( zOpt[0]!='-' ) break; 1036 if( nOpt<=7 && 0==sqlite3_strnicmp(zOpt, "-invert", nOpt) ){ 1037 isInvert = 1; 1038 }else 1039 if( nOpt<=5 && 0==sqlite3_strnicmp(zOpt, "-next", nOpt) ){ 1040 isCheckNext = 1; 1041 }else{ 1042 break; 1043 } 1044 objv++; 1045 objc--; 1046 } 1047 if( objc!=4 ){ 1048 Tcl_WrongNumArgs( 1049 interp, 1, objv, "?-next? ?-invert? VARNAME CHANGESET SCRIPT"); 1050 return TCL_ERROR; 1051 } 1052 1053 pVarname = objv[1]; 1054 pCS = objv[2]; 1055 pScript = objv[3]; 1056 1057 pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset); 1058 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 1059 if( isInvert ){ 1060 int f = SQLITE_CHANGESETSTART_INVERT; 1061 if( sStr.nStream==0 ){ 1062 rc = sqlite3changeset_start_v2(&pIter, nChangeset, pChangeset, f); 1063 }else{ 1064 void *pCtx = (void*)&sStr; 1065 sStr.aData = (unsigned char*)pChangeset; 1066 sStr.nData = nChangeset; 1067 rc = sqlite3changeset_start_v2_strm(&pIter, testStreamInput, pCtx, f); 1068 } 1069 }else{ 1070 if( sStr.nStream==0 ){ 1071 rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); 1072 }else{ 1073 sStr.aData = (unsigned char*)pChangeset; 1074 sStr.nData = nChangeset; 1075 rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr); 1076 } 1077 } 1078 if( rc!=SQLITE_OK ){ 1079 return test_session_error(interp, rc, 0); 1080 } 1081 1082 while( SQLITE_ROW==sqlite3changeset_next(pIter) ){ 1083 int nCol; /* Number of columns in table */ 1084 int nCol2; /* Number of columns in table */ 1085 int op; /* SQLITE_INSERT, UPDATE or DELETE */ 1086 const char *zTab; /* Name of table change applies to */ 1087 Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */ 1088 Tcl_Obj *pOld; /* Vector of old.* values */ 1089 Tcl_Obj *pNew; /* Vector of new.* values */ 1090 int bIndirect; 1091 1092 char *zPK; 1093 unsigned char *abPK; 1094 int i; 1095 1096 /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this 1097 ** iterator. */ 1098 int nDummy; 1099 if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){ 1100 sqlite3changeset_finalize(pIter); 1101 return TCL_ERROR; 1102 } 1103 1104 sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); 1105 pVar = Tcl_NewObj(); 1106 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj( 1107 op==SQLITE_INSERT ? "INSERT" : 1108 op==SQLITE_UPDATE ? "UPDATE" : 1109 "DELETE", -1 1110 )); 1111 1112 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1)); 1113 Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect)); 1114 1115 zPK = ckalloc(nCol+1); 1116 memset(zPK, 0, nCol+1); 1117 sqlite3changeset_pk(pIter, &abPK, &nCol2); 1118 assert( nCol==nCol2 ); 1119 for(i=0; i<nCol; i++){ 1120 zPK[i] = (abPK[i] ? 'X' : '.'); 1121 } 1122 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1)); 1123 ckfree(zPK); 1124 1125 pOld = Tcl_NewObj(); 1126 if( op!=SQLITE_INSERT ){ 1127 for(i=0; i<nCol; i++){ 1128 sqlite3_value *pVal; 1129 sqlite3changeset_old(pIter, i, &pVal); 1130 test_append_value(pOld, pVal); 1131 } 1132 } 1133 pNew = Tcl_NewObj(); 1134 if( op!=SQLITE_DELETE ){ 1135 for(i=0; i<nCol; i++){ 1136 sqlite3_value *pVal; 1137 sqlite3changeset_new(pIter, i, &pVal); 1138 test_append_value(pNew, pVal); 1139 } 1140 } 1141 Tcl_ListObjAppendElement(0, pVar, pOld); 1142 Tcl_ListObjAppendElement(0, pVar, pNew); 1143 1144 Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0); 1145 rc = Tcl_EvalObjEx(interp, pScript, 0); 1146 if( rc!=TCL_OK && rc!=TCL_CONTINUE ){ 1147 sqlite3changeset_finalize(pIter); 1148 return rc==TCL_BREAK ? TCL_OK : rc; 1149 } 1150 } 1151 1152 if( isCheckNext ){ 1153 int rc2 = sqlite3changeset_next(pIter); 1154 rc = sqlite3changeset_finalize(pIter); 1155 assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc ); 1156 }else{ 1157 rc = sqlite3changeset_finalize(pIter); 1158 } 1159 if( rc!=SQLITE_OK ){ 1160 return test_session_error(interp, rc, 0); 1161 } 1162 1163 return TCL_OK; 1164 } 1165 1166 /* 1167 ** tclcmd: CMD configure REBASE-BLOB 1168 ** tclcmd: CMD rebase CHANGESET 1169 ** tclcmd: CMD delete 1170 */ 1171 static int SQLITE_TCLAPI test_rebaser_cmd( 1172 void * clientData, 1173 Tcl_Interp *interp, 1174 int objc, 1175 Tcl_Obj *CONST objv[] 1176 ){ 1177 static struct RebaseSubcmd { 1178 const char *zSub; 1179 int nArg; 1180 const char *zMsg; 1181 int iSub; 1182 } aSub[] = { 1183 { "configure", 1, "REBASE-BLOB" }, /* 0 */ 1184 { "delete", 0, "" }, /* 1 */ 1185 { "rebase", 1, "CHANGESET" }, /* 2 */ 1186 { 0 } 1187 }; 1188 1189 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData; 1190 int iSub; 1191 int rc; 1192 1193 if( objc<2 ){ 1194 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); 1195 return TCL_ERROR; 1196 } 1197 rc = Tcl_GetIndexFromObjStruct(interp, 1198 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub 1199 ); 1200 if( rc!=TCL_OK ) return rc; 1201 if( objc!=2+aSub[iSub].nArg ){ 1202 Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg); 1203 return TCL_ERROR; 1204 } 1205 1206 assert( iSub==0 || iSub==1 || iSub==2 ); 1207 assert( rc==SQLITE_OK ); 1208 switch( iSub ){ 1209 case 0: { /* configure */ 1210 int nRebase = 0; 1211 unsigned char *pRebase = Tcl_GetByteArrayFromObj(objv[2], &nRebase); 1212 rc = sqlite3rebaser_configure(p, nRebase, pRebase); 1213 break; 1214 } 1215 1216 case 1: /* delete */ 1217 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); 1218 break; 1219 1220 default: { /* rebase */ 1221 TestStreamInput sStr; /* Input stream */ 1222 TestSessionsBlob sOut; /* Output blob */ 1223 1224 memset(&sStr, 0, sizeof(sStr)); 1225 memset(&sOut, 0, sizeof(sOut)); 1226 sStr.aData = Tcl_GetByteArrayFromObj(objv[2], &sStr.nData); 1227 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 1228 1229 if( sStr.nStream ){ 1230 rc = sqlite3rebaser_rebase_strm(p, 1231 testStreamInput, (void*)&sStr, 1232 testStreamOutput, (void*)&sOut 1233 ); 1234 }else{ 1235 rc = sqlite3rebaser_rebase(p, sStr.nData, sStr.aData, &sOut.n, &sOut.p); 1236 } 1237 1238 if( rc==SQLITE_OK ){ 1239 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(sOut.p, sOut.n)); 1240 } 1241 sqlite3_free(sOut.p); 1242 break; 1243 } 1244 } 1245 1246 if( rc!=SQLITE_OK ){ 1247 return test_session_error(interp, rc, 0); 1248 } 1249 return TCL_OK; 1250 } 1251 1252 static void SQLITE_TCLAPI test_rebaser_del(void *clientData){ 1253 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData; 1254 sqlite3rebaser_delete(p); 1255 } 1256 1257 /* 1258 ** tclcmd: sqlite3rebaser_create NAME 1259 */ 1260 static int SQLITE_TCLAPI test_sqlite3rebaser_create( 1261 void * clientData, 1262 Tcl_Interp *interp, 1263 int objc, 1264 Tcl_Obj *CONST objv[] 1265 ){ 1266 int rc; 1267 sqlite3_rebaser *pNew = 0; 1268 if( objc!=2 ){ 1269 Tcl_WrongNumArgs(interp, 1, objv, "NAME"); 1270 return SQLITE_ERROR; 1271 } 1272 1273 rc = sqlite3rebaser_create(&pNew); 1274 if( rc!=SQLITE_OK ){ 1275 return test_session_error(interp, rc, 0); 1276 } 1277 1278 Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]), test_rebaser_cmd, 1279 (ClientData)pNew, test_rebaser_del 1280 ); 1281 Tcl_SetObjResult(interp, objv[1]); 1282 return TCL_OK; 1283 } 1284 1285 /* 1286 ** tclcmd: sqlite3rebaser_configure OP VALUE 1287 */ 1288 static int SQLITE_TCLAPI test_sqlite3session_config( 1289 void * clientData, 1290 Tcl_Interp *interp, 1291 int objc, 1292 Tcl_Obj *CONST objv[] 1293 ){ 1294 static struct ConfigOpt { 1295 const char *zSub; 1296 int op; 1297 } aSub[] = { 1298 { "strm_size", SQLITE_SESSION_CONFIG_STRMSIZE }, 1299 { "invalid", 0 }, 1300 { 0 } 1301 }; 1302 int rc; 1303 int iSub; 1304 int iVal; 1305 1306 if( objc!=3 ){ 1307 Tcl_WrongNumArgs(interp, 1, objv, "OP VALUE"); 1308 return SQLITE_ERROR; 1309 } 1310 rc = Tcl_GetIndexFromObjStruct(interp, 1311 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub 1312 ); 1313 if( rc!=TCL_OK ) return rc; 1314 if( Tcl_GetIntFromObj(interp, objv[2], &iVal) ) return TCL_ERROR; 1315 1316 rc = sqlite3session_config(aSub[iSub].op, (void*)&iVal); 1317 if( rc!=SQLITE_OK ){ 1318 return test_session_error(interp, rc, 0); 1319 } 1320 Tcl_SetObjResult(interp, Tcl_NewIntObj(iVal)); 1321 return TCL_OK; 1322 } 1323 1324 int TestSession_Init(Tcl_Interp *interp){ 1325 struct Cmd { 1326 const char *zCmd; 1327 Tcl_ObjCmdProc *xProc; 1328 } aCmd[] = { 1329 { "sqlite3session", test_sqlite3session }, 1330 { "sqlite3session_foreach", test_sqlite3session_foreach }, 1331 { "sqlite3changeset_invert", test_sqlite3changeset_invert }, 1332 { "sqlite3changeset_concat", test_sqlite3changeset_concat }, 1333 { "sqlite3changeset_apply", test_sqlite3changeset_apply }, 1334 { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 }, 1335 { "sqlite3changeset_apply_replace_all", 1336 test_sqlite3changeset_apply_replace_all }, 1337 { "sql_exec_changeset", test_sql_exec_changeset }, 1338 { "sqlite3rebaser_create", test_sqlite3rebaser_create }, 1339 { "sqlite3session_config", test_sqlite3session_config }, 1340 }; 1341 int i; 1342 1343 for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){ 1344 struct Cmd *p = &aCmd[i]; 1345 Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0); 1346 } 1347 1348 return TCL_OK; 1349 } 1350 1351 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */ 1352