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