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