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 #include <tcl.h> 9 10 typedef struct TestSession TestSession; 11 struct TestSession { 12 sqlite3_session *pSession; 13 Tcl_Interp *interp; 14 Tcl_Obj *pFilterScript; 15 }; 16 17 typedef struct TestStreamInput TestStreamInput; 18 struct TestStreamInput { 19 int nStream; /* Maximum chunk size */ 20 unsigned char *aData; /* Pointer to buffer containing data */ 21 int nData; /* Size of buffer aData in bytes */ 22 int iData; /* Bytes of data already read by sessions */ 23 }; 24 25 #define SESSION_STREAM_TCL_VAR "sqlite3session_streams" 26 27 /* 28 ** Attempt to find the global variable zVar within interpreter interp 29 ** and extract an integer value from it. Return this value. 30 ** 31 ** If the named variable cannot be found, or if it cannot be interpreted 32 ** as a integer, return 0. 33 */ 34 static int test_tcl_integer(Tcl_Interp *interp, const char *zVar){ 35 Tcl_Obj *pObj; 36 int iVal = 0; 37 pObj = Tcl_ObjGetVar2(interp, Tcl_NewStringObj(zVar, -1), 0, TCL_GLOBAL_ONLY); 38 if( pObj ) Tcl_GetIntFromObj(0, pObj, &iVal); 39 return iVal; 40 } 41 42 static int test_session_error(Tcl_Interp *interp, int rc, char *zErr){ 43 extern const char *sqlite3ErrName(int); 44 Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); 45 if( zErr ){ 46 Tcl_AppendResult(interp, " - ", zErr, 0); 47 sqlite3_free(zErr); 48 } 49 return TCL_ERROR; 50 } 51 52 static int test_table_filter(void *pCtx, const char *zTbl){ 53 TestSession *p = (TestSession*)pCtx; 54 Tcl_Obj *pEval; 55 int rc; 56 int bRes = 0; 57 58 pEval = Tcl_DuplicateObj(p->pFilterScript); 59 Tcl_IncrRefCount(pEval); 60 rc = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zTbl, -1)); 61 if( rc==TCL_OK ){ 62 rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL); 63 } 64 if( rc==TCL_OK ){ 65 rc = Tcl_GetBooleanFromObj(p->interp, Tcl_GetObjResult(p->interp), &bRes); 66 } 67 if( rc!=TCL_OK ){ 68 /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */ 69 Tcl_BackgroundError(p->interp); 70 } 71 Tcl_DecrRefCount(pEval); 72 73 return bRes; 74 } 75 76 struct TestSessionsBlob { 77 void *p; 78 int n; 79 }; 80 typedef struct TestSessionsBlob TestSessionsBlob; 81 82 static int testStreamOutput( 83 void *pCtx, 84 const void *pData, 85 int nData 86 ){ 87 TestSessionsBlob *pBlob = (TestSessionsBlob*)pCtx; 88 char *pNew; 89 90 assert( nData>0 ); 91 pNew = (char*)sqlite3_realloc(pBlob->p, pBlob->n + nData); 92 if( pNew==0 ){ 93 return SQLITE_NOMEM; 94 } 95 pBlob->p = (void*)pNew; 96 memcpy(&pNew[pBlob->n], pData, nData); 97 pBlob->n += nData; 98 return SQLITE_OK; 99 } 100 101 /* 102 ** Tclcmd: $session attach TABLE 103 ** $session changeset 104 ** $session delete 105 ** $session enable BOOL 106 ** $session indirect INTEGER 107 ** $session patchset 108 ** $session table_filter SCRIPT 109 */ 110 static int test_session_cmd( 111 void *clientData, 112 Tcl_Interp *interp, 113 int objc, 114 Tcl_Obj *CONST objv[] 115 ){ 116 TestSession *p = (TestSession*)clientData; 117 sqlite3_session *pSession = p->pSession; 118 struct SessionSubcmd { 119 const char *zSub; 120 int nArg; 121 const char *zMsg; 122 int iSub; 123 } aSub[] = { 124 { "attach", 1, "TABLE", }, /* 0 */ 125 { "changeset", 0, "", }, /* 1 */ 126 { "delete", 0, "", }, /* 2 */ 127 { "enable", 1, "BOOL", }, /* 3 */ 128 { "indirect", 1, "BOOL", }, /* 4 */ 129 { "isempty", 0, "", }, /* 5 */ 130 { "table_filter", 1, "SCRIPT", }, /* 6 */ 131 { "patchset", 0, "", }, /* 7 */ 132 { "diff", 2, "FROMDB TBL", }, /* 8 */ 133 { 0 } 134 }; 135 int iSub; 136 int rc; 137 138 if( objc<2 ){ 139 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); 140 return TCL_ERROR; 141 } 142 rc = Tcl_GetIndexFromObjStruct(interp, 143 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub 144 ); 145 if( rc!=TCL_OK ) return rc; 146 if( objc!=2+aSub[iSub].nArg ){ 147 Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg); 148 return TCL_ERROR; 149 } 150 151 switch( iSub ){ 152 case 0: { /* attach */ 153 char *zArg = Tcl_GetString(objv[2]); 154 if( zArg[0]=='*' && zArg[1]=='\0' ) zArg = 0; 155 rc = sqlite3session_attach(pSession, zArg); 156 if( rc!=SQLITE_OK ){ 157 return test_session_error(interp, rc, 0); 158 } 159 break; 160 } 161 162 case 7: /* patchset */ 163 case 1: { /* changeset */ 164 TestSessionsBlob o = {0, 0}; 165 if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){ 166 void *pCtx = (void*)&o; 167 if( iSub==7 ){ 168 rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx); 169 }else{ 170 rc = sqlite3session_changeset_strm(pSession, testStreamOutput, pCtx); 171 } 172 }else{ 173 if( iSub==7 ){ 174 rc = sqlite3session_patchset(pSession, &o.n, &o.p); 175 }else{ 176 rc = sqlite3session_changeset(pSession, &o.n, &o.p); 177 } 178 } 179 if( rc==SQLITE_OK ){ 180 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n)); 181 } 182 sqlite3_free(o.p); 183 if( rc!=SQLITE_OK ){ 184 return test_session_error(interp, rc, 0); 185 } 186 break; 187 } 188 189 case 2: /* delete */ 190 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); 191 break; 192 193 case 3: { /* enable */ 194 int val; 195 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR; 196 val = sqlite3session_enable(pSession, val); 197 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); 198 break; 199 } 200 201 case 4: { /* indirect */ 202 int val; 203 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR; 204 val = sqlite3session_indirect(pSession, val); 205 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); 206 break; 207 } 208 209 case 5: { /* isempty */ 210 int val; 211 val = sqlite3session_isempty(pSession); 212 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); 213 break; 214 } 215 216 case 6: { /* table_filter */ 217 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript); 218 p->interp = interp; 219 p->pFilterScript = Tcl_DuplicateObj(objv[2]); 220 Tcl_IncrRefCount(p->pFilterScript); 221 sqlite3session_table_filter(pSession, test_table_filter, clientData); 222 break; 223 } 224 225 case 8: { /* diff */ 226 char *zErr = 0; 227 rc = sqlite3session_diff(pSession, 228 Tcl_GetString(objv[2]), 229 Tcl_GetString(objv[3]), 230 &zErr 231 ); 232 assert( rc!=SQLITE_OK || zErr==0 ); 233 if( rc ){ 234 return test_session_error(interp, rc, zErr); 235 } 236 break; 237 } 238 } 239 240 return TCL_OK; 241 } 242 243 static void test_session_del(void *clientData){ 244 TestSession *p = (TestSession*)clientData; 245 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript); 246 sqlite3session_delete(p->pSession); 247 ckfree((char*)p); 248 } 249 250 /* 251 ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME 252 */ 253 static int test_sqlite3session( 254 void * clientData, 255 Tcl_Interp *interp, 256 int objc, 257 Tcl_Obj *CONST objv[] 258 ){ 259 sqlite3 *db; 260 Tcl_CmdInfo info; 261 int rc; /* sqlite3session_create() return code */ 262 TestSession *p; /* New wrapper object */ 263 264 if( objc!=4 ){ 265 Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME"); 266 return TCL_ERROR; 267 } 268 269 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){ 270 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0); 271 return TCL_ERROR; 272 } 273 db = *(sqlite3 **)info.objClientData; 274 275 p = (TestSession*)ckalloc(sizeof(TestSession)); 276 memset(p, 0, sizeof(TestSession)); 277 rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &p->pSession); 278 if( rc!=SQLITE_OK ){ 279 ckfree((char*)p); 280 return test_session_error(interp, rc, 0); 281 } 282 283 Tcl_CreateObjCommand( 284 interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)p, 285 test_session_del 286 ); 287 Tcl_SetObjResult(interp, objv[1]); 288 return TCL_OK; 289 } 290 291 static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){ 292 if( pVal==0 ){ 293 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj()); 294 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj()); 295 }else{ 296 Tcl_Obj *pObj; 297 switch( sqlite3_value_type(pVal) ){ 298 case SQLITE_NULL: 299 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("n", 1)); 300 pObj = Tcl_NewObj(); 301 break; 302 case SQLITE_INTEGER: 303 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("i", 1)); 304 pObj = Tcl_NewWideIntObj(sqlite3_value_int64(pVal)); 305 break; 306 case SQLITE_FLOAT: 307 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("f", 1)); 308 pObj = Tcl_NewDoubleObj(sqlite3_value_double(pVal)); 309 break; 310 case SQLITE_TEXT: { 311 const char *z = (char*)sqlite3_value_blob(pVal); 312 int n = sqlite3_value_bytes(pVal); 313 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("t", 1)); 314 pObj = Tcl_NewStringObj(z, n); 315 break; 316 } 317 case SQLITE_BLOB: 318 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("b", 1)); 319 pObj = Tcl_NewByteArrayObj( 320 sqlite3_value_blob(pVal), 321 sqlite3_value_bytes(pVal) 322 ); 323 break; 324 } 325 Tcl_ListObjAppendElement(0, pList, pObj); 326 } 327 } 328 329 typedef struct TestConflictHandler TestConflictHandler; 330 struct TestConflictHandler { 331 Tcl_Interp *interp; 332 Tcl_Obj *pConflictScript; 333 Tcl_Obj *pFilterScript; 334 }; 335 336 static int test_obj_eq_string(Tcl_Obj *p, const char *z){ 337 int n; 338 int nObj; 339 char *zObj; 340 341 n = (int)strlen(z); 342 zObj = Tcl_GetStringFromObj(p, &nObj); 343 344 return (nObj==n && (n==0 || 0==memcmp(zObj, z, n))); 345 } 346 347 static int test_filter_handler( 348 void *pCtx, /* Pointer to TestConflictHandler structure */ 349 const char *zTab /* Table name */ 350 ){ 351 TestConflictHandler *p = (TestConflictHandler *)pCtx; 352 int res = 1; 353 Tcl_Obj *pEval; 354 Tcl_Interp *interp = p->interp; 355 356 pEval = Tcl_DuplicateObj(p->pFilterScript); 357 Tcl_IncrRefCount(pEval); 358 359 if( TCL_OK!=Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1)) 360 || TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) 361 || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res) 362 ){ 363 Tcl_BackgroundError(interp); 364 } 365 366 Tcl_DecrRefCount(pEval); 367 return res; 368 } 369 370 static int test_conflict_handler( 371 void *pCtx, /* Pointer to TestConflictHandler structure */ 372 int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */ 373 sqlite3_changeset_iter *pIter /* Handle describing change and conflict */ 374 ){ 375 TestConflictHandler *p = (TestConflictHandler *)pCtx; 376 Tcl_Obj *pEval; 377 Tcl_Interp *interp = p->interp; 378 int ret = 0; /* Return value */ 379 380 int op; /* SQLITE_UPDATE, DELETE or INSERT */ 381 const char *zTab; /* Name of table conflict is on */ 382 int nCol; /* Number of columns in table zTab */ 383 384 pEval = Tcl_DuplicateObj(p->pConflictScript); 385 Tcl_IncrRefCount(pEval); 386 387 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); 388 389 if( eConf==SQLITE_CHANGESET_FOREIGN_KEY ){ 390 int nFk; 391 sqlite3changeset_fk_conflicts(pIter, &nFk); 392 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj("FOREIGN_KEY", -1)); 393 Tcl_ListObjAppendElement(0, pEval, Tcl_NewIntObj(nFk)); 394 }else{ 395 396 /* Append the operation type. */ 397 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj( 398 op==SQLITE_INSERT ? "INSERT" : 399 op==SQLITE_UPDATE ? "UPDATE" : 400 "DELETE", -1 401 )); 402 403 /* Append the table name. */ 404 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1)); 405 406 /* Append the conflict type. */ 407 switch( eConf ){ 408 case SQLITE_CHANGESET_DATA: 409 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1)); 410 break; 411 case SQLITE_CHANGESET_NOTFOUND: 412 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1)); 413 break; 414 case SQLITE_CHANGESET_CONFLICT: 415 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1)); 416 break; 417 case SQLITE_CHANGESET_CONSTRAINT: 418 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1)); 419 break; 420 } 421 422 /* If this is not an INSERT, append the old row */ 423 if( op!=SQLITE_INSERT ){ 424 int i; 425 Tcl_Obj *pOld = Tcl_NewObj(); 426 for(i=0; i<nCol; i++){ 427 sqlite3_value *pVal; 428 sqlite3changeset_old(pIter, i, &pVal); 429 test_append_value(pOld, pVal); 430 } 431 Tcl_ListObjAppendElement(0, pEval, pOld); 432 } 433 434 /* If this is not a DELETE, append the new row */ 435 if( op!=SQLITE_DELETE ){ 436 int i; 437 Tcl_Obj *pNew = Tcl_NewObj(); 438 for(i=0; i<nCol; i++){ 439 sqlite3_value *pVal; 440 sqlite3changeset_new(pIter, i, &pVal); 441 test_append_value(pNew, pVal); 442 } 443 Tcl_ListObjAppendElement(0, pEval, pNew); 444 } 445 446 /* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append 447 ** the conflicting row. */ 448 if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){ 449 int i; 450 Tcl_Obj *pConflict = Tcl_NewObj(); 451 for(i=0; i<nCol; i++){ 452 int rc; 453 sqlite3_value *pVal; 454 rc = sqlite3changeset_conflict(pIter, i, &pVal); 455 assert( rc==SQLITE_OK ); 456 test_append_value(pConflict, pVal); 457 } 458 Tcl_ListObjAppendElement(0, pEval, pConflict); 459 } 460 461 /*********************************************************************** 462 ** This block is purely for testing some error conditions. 463 */ 464 if( eConf==SQLITE_CHANGESET_CONSTRAINT 465 || eConf==SQLITE_CHANGESET_NOTFOUND 466 ){ 467 sqlite3_value *pVal; 468 int rc = sqlite3changeset_conflict(pIter, 0, &pVal); 469 assert( rc==SQLITE_MISUSE ); 470 }else{ 471 sqlite3_value *pVal; 472 int rc = sqlite3changeset_conflict(pIter, -1, &pVal); 473 assert( rc==SQLITE_RANGE ); 474 rc = sqlite3changeset_conflict(pIter, nCol, &pVal); 475 assert( rc==SQLITE_RANGE ); 476 } 477 if( op==SQLITE_DELETE ){ 478 sqlite3_value *pVal; 479 int rc = sqlite3changeset_new(pIter, 0, &pVal); 480 assert( rc==SQLITE_MISUSE ); 481 }else{ 482 sqlite3_value *pVal; 483 int rc = sqlite3changeset_new(pIter, -1, &pVal); 484 assert( rc==SQLITE_RANGE ); 485 rc = sqlite3changeset_new(pIter, nCol, &pVal); 486 assert( rc==SQLITE_RANGE ); 487 } 488 if( op==SQLITE_INSERT ){ 489 sqlite3_value *pVal; 490 int rc = sqlite3changeset_old(pIter, 0, &pVal); 491 assert( rc==SQLITE_MISUSE ); 492 }else{ 493 sqlite3_value *pVal; 494 int rc = sqlite3changeset_old(pIter, -1, &pVal); 495 assert( rc==SQLITE_RANGE ); 496 rc = sqlite3changeset_old(pIter, nCol, &pVal); 497 assert( rc==SQLITE_RANGE ); 498 } 499 if( eConf!=SQLITE_CHANGESET_FOREIGN_KEY ){ 500 /* eConf!=FOREIGN_KEY is always true at this point. The condition is 501 ** just there to make it clearer what is being tested. */ 502 int nDummy; 503 int rc = sqlite3changeset_fk_conflicts(pIter, &nDummy); 504 assert( rc==SQLITE_MISUSE ); 505 } 506 /* End of testing block 507 ***********************************************************************/ 508 } 509 510 if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){ 511 Tcl_BackgroundError(interp); 512 }else{ 513 Tcl_Obj *pRes = Tcl_GetObjResult(interp); 514 if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){ 515 ret = SQLITE_CHANGESET_OMIT; 516 }else if( test_obj_eq_string(pRes, "REPLACE") ){ 517 ret = SQLITE_CHANGESET_REPLACE; 518 }else if( test_obj_eq_string(pRes, "ABORT") ){ 519 ret = SQLITE_CHANGESET_ABORT; 520 }else{ 521 Tcl_GetIntFromObj(0, pRes, &ret); 522 } 523 } 524 525 Tcl_DecrRefCount(pEval); 526 return ret; 527 } 528 529 /* 530 ** The conflict handler used by sqlite3changeset_apply_replace_all(). 531 ** This conflict handler calls sqlite3_value_text16() on all available 532 ** sqlite3_value objects and then returns CHANGESET_REPLACE, or 533 ** CHANGESET_OMIT if REPLACE is not applicable. This is used to test the 534 ** effect of a malloc failure within an sqlite3_value_xxx() function 535 ** invoked by a conflict-handler callback. 536 */ 537 static int replace_handler( 538 void *pCtx, /* Pointer to TestConflictHandler structure */ 539 int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */ 540 sqlite3_changeset_iter *pIter /* Handle describing change and conflict */ 541 ){ 542 int op; /* SQLITE_UPDATE, DELETE or INSERT */ 543 const char *zTab; /* Name of table conflict is on */ 544 int nCol; /* Number of columns in table zTab */ 545 int i; 546 int x = 0; 547 548 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); 549 550 if( op!=SQLITE_INSERT ){ 551 for(i=0; i<nCol; i++){ 552 sqlite3_value *pVal; 553 sqlite3changeset_old(pIter, i, &pVal); 554 sqlite3_value_text16(pVal); 555 x++; 556 } 557 } 558 559 if( op!=SQLITE_DELETE ){ 560 for(i=0; i<nCol; i++){ 561 sqlite3_value *pVal; 562 sqlite3changeset_new(pIter, i, &pVal); 563 sqlite3_value_text16(pVal); 564 x++; 565 } 566 } 567 568 if( eConf==SQLITE_CHANGESET_DATA ){ 569 return SQLITE_CHANGESET_REPLACE; 570 } 571 return SQLITE_CHANGESET_OMIT; 572 } 573 574 static int testStreamInput( 575 void *pCtx, /* Context pointer */ 576 void *pData, /* Buffer to populate */ 577 int *pnData /* IN/OUT: Bytes requested/supplied */ 578 ){ 579 TestStreamInput *p = (TestStreamInput*)pCtx; 580 int nReq = *pnData; /* Bytes of data requested */ 581 int nRem = p->nData - p->iData; /* Bytes of data available */ 582 int nRet = p->nStream; /* Bytes actually returned */ 583 584 /* Allocate and free some space. There is no point to this, other than 585 ** that it allows the regular OOM fault-injection tests to cause an error 586 ** in this function. */ 587 void *pAlloc = sqlite3_malloc(10); 588 if( pAlloc==0 ) return SQLITE_NOMEM; 589 sqlite3_free(pAlloc); 590 591 if( nRet>nReq ) nRet = nReq; 592 if( nRet>nRem ) nRet = nRem; 593 594 assert( nRet>=0 ); 595 if( nRet>0 ){ 596 memcpy(pData, &p->aData[p->iData], nRet); 597 p->iData += nRet; 598 } 599 600 *pnData = nRet; 601 return SQLITE_OK; 602 } 603 604 605 /* 606 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? 607 */ 608 static int test_sqlite3changeset_apply( 609 void * clientData, 610 Tcl_Interp *interp, 611 int objc, 612 Tcl_Obj *CONST objv[] 613 ){ 614 sqlite3 *db; /* Database handle */ 615 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */ 616 int rc; /* Return code from changeset_invert() */ 617 void *pChangeset; /* Buffer containing changeset */ 618 int nChangeset; /* Size of buffer aChangeset in bytes */ 619 TestConflictHandler ctx; 620 TestStreamInput sStr; 621 622 memset(&sStr, 0, sizeof(sStr)); 623 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 624 625 if( objc!=4 && objc!=5 ){ 626 Tcl_WrongNumArgs(interp, 1, objv, 627 "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?" 628 ); 629 return TCL_ERROR; 630 } 631 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){ 632 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0); 633 return TCL_ERROR; 634 } 635 db = *(sqlite3 **)info.objClientData; 636 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset); 637 ctx.pConflictScript = objv[3]; 638 ctx.pFilterScript = objc==5 ? objv[4] : 0; 639 ctx.interp = interp; 640 641 if( sStr.nStream==0 ){ 642 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 643 (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx 644 ); 645 }else{ 646 sStr.aData = (unsigned char*)pChangeset; 647 sStr.nData = nChangeset; 648 rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr, 649 (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx 650 ); 651 } 652 653 if( rc!=SQLITE_OK ){ 654 return test_session_error(interp, rc, 0); 655 } 656 Tcl_ResetResult(interp); 657 return TCL_OK; 658 } 659 660 /* 661 ** sqlite3changeset_apply_replace_all DB CHANGESET 662 */ 663 static int test_sqlite3changeset_apply_replace_all( 664 void * clientData, 665 Tcl_Interp *interp, 666 int objc, 667 Tcl_Obj *CONST objv[] 668 ){ 669 sqlite3 *db; /* Database handle */ 670 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */ 671 int rc; /* Return code from changeset_invert() */ 672 void *pChangeset; /* Buffer containing changeset */ 673 int nChangeset; /* Size of buffer aChangeset in bytes */ 674 675 if( objc!=3 ){ 676 Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET"); 677 return TCL_ERROR; 678 } 679 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){ 680 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0); 681 return TCL_ERROR; 682 } 683 db = *(sqlite3 **)info.objClientData; 684 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset); 685 686 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0); 687 if( rc!=SQLITE_OK ){ 688 return test_session_error(interp, rc, 0); 689 } 690 Tcl_ResetResult(interp); 691 return TCL_OK; 692 } 693 694 695 /* 696 ** sqlite3changeset_invert CHANGESET 697 */ 698 static int test_sqlite3changeset_invert( 699 void * clientData, 700 Tcl_Interp *interp, 701 int objc, 702 Tcl_Obj *CONST objv[] 703 ){ 704 int rc; /* Return code from changeset_invert() */ 705 TestStreamInput sIn; /* Input stream */ 706 TestSessionsBlob sOut; /* Output blob */ 707 708 if( objc!=2 ){ 709 Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET"); 710 return TCL_ERROR; 711 } 712 713 memset(&sIn, 0, sizeof(sIn)); 714 memset(&sOut, 0, sizeof(sOut)); 715 sIn.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 716 sIn.aData = Tcl_GetByteArrayFromObj(objv[1], &sIn.nData); 717 718 if( sIn.nStream ){ 719 rc = sqlite3changeset_invert_strm( 720 testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut 721 ); 722 }else{ 723 rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p); 724 } 725 if( rc!=SQLITE_OK ){ 726 rc = test_session_error(interp, rc, 0); 727 }else{ 728 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); 729 } 730 sqlite3_free(sOut.p); 731 return rc; 732 } 733 734 /* 735 ** sqlite3changeset_concat LEFT RIGHT 736 */ 737 static int test_sqlite3changeset_concat( 738 void * clientData, 739 Tcl_Interp *interp, 740 int objc, 741 Tcl_Obj *CONST objv[] 742 ){ 743 int rc; /* Return code from changeset_invert() */ 744 745 TestStreamInput sLeft; /* Input stream */ 746 TestStreamInput sRight; /* Input stream */ 747 TestSessionsBlob sOut = {0,0}; /* Output blob */ 748 749 if( objc!=3 ){ 750 Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT"); 751 return TCL_ERROR; 752 } 753 754 memset(&sLeft, 0, sizeof(sLeft)); 755 memset(&sRight, 0, sizeof(sRight)); 756 sLeft.aData = Tcl_GetByteArrayFromObj(objv[1], &sLeft.nData); 757 sRight.aData = Tcl_GetByteArrayFromObj(objv[2], &sRight.nData); 758 sLeft.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 759 sRight.nStream = sLeft.nStream; 760 761 if( sLeft.nStream>0 ){ 762 rc = sqlite3changeset_concat_strm( 763 testStreamInput, (void*)&sLeft, 764 testStreamInput, (void*)&sRight, 765 testStreamOutput, (void*)&sOut 766 ); 767 }else{ 768 rc = sqlite3changeset_concat( 769 sLeft.nData, sLeft.aData, sRight.nData, sRight.aData, &sOut.n, &sOut.p 770 ); 771 } 772 773 if( rc!=SQLITE_OK ){ 774 rc = test_session_error(interp, rc, 0); 775 }else{ 776 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); 777 } 778 sqlite3_free(sOut.p); 779 return rc; 780 } 781 782 /* 783 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT 784 */ 785 static int test_sqlite3session_foreach( 786 void * clientData, 787 Tcl_Interp *interp, 788 int objc, 789 Tcl_Obj *CONST objv[] 790 ){ 791 void *pChangeset; 792 int nChangeset; 793 sqlite3_changeset_iter *pIter; 794 int rc; 795 Tcl_Obj *pVarname; 796 Tcl_Obj *pCS; 797 Tcl_Obj *pScript; 798 int isCheckNext = 0; 799 800 TestStreamInput sStr; 801 memset(&sStr, 0, sizeof(sStr)); 802 803 if( objc>1 ){ 804 char *zOpt = Tcl_GetString(objv[1]); 805 isCheckNext = (strcmp(zOpt, "-next")==0); 806 } 807 if( objc!=4+isCheckNext ){ 808 Tcl_WrongNumArgs(interp, 1, objv, "?-next? VARNAME CHANGESET SCRIPT"); 809 return TCL_ERROR; 810 } 811 812 pVarname = objv[1+isCheckNext]; 813 pCS = objv[2+isCheckNext]; 814 pScript = objv[3+isCheckNext]; 815 816 pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset); 817 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); 818 if( sStr.nStream==0 ){ 819 rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); 820 }else{ 821 sStr.aData = (unsigned char*)pChangeset; 822 sStr.nData = nChangeset; 823 rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr); 824 } 825 if( rc!=SQLITE_OK ){ 826 return test_session_error(interp, rc, 0); 827 } 828 829 while( SQLITE_ROW==sqlite3changeset_next(pIter) ){ 830 int nCol; /* Number of columns in table */ 831 int nCol2; /* Number of columns in table */ 832 int op; /* SQLITE_INSERT, UPDATE or DELETE */ 833 const char *zTab; /* Name of table change applies to */ 834 Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */ 835 Tcl_Obj *pOld; /* Vector of old.* values */ 836 Tcl_Obj *pNew; /* Vector of new.* values */ 837 int bIndirect; 838 839 char *zPK; 840 unsigned char *abPK; 841 int i; 842 843 /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this 844 ** iterator. */ 845 int nDummy; 846 if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){ 847 sqlite3changeset_finalize(pIter); 848 return TCL_ERROR; 849 } 850 851 sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); 852 pVar = Tcl_NewObj(); 853 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj( 854 op==SQLITE_INSERT ? "INSERT" : 855 op==SQLITE_UPDATE ? "UPDATE" : 856 "DELETE", -1 857 )); 858 859 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1)); 860 Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect)); 861 862 zPK = ckalloc(nCol+1); 863 memset(zPK, 0, nCol+1); 864 sqlite3changeset_pk(pIter, &abPK, &nCol2); 865 assert( nCol==nCol2 ); 866 for(i=0; i<nCol; i++){ 867 zPK[i] = (abPK[i] ? 'X' : '.'); 868 } 869 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1)); 870 ckfree(zPK); 871 872 pOld = Tcl_NewObj(); 873 if( op!=SQLITE_INSERT ){ 874 int i; 875 for(i=0; i<nCol; i++){ 876 sqlite3_value *pVal; 877 sqlite3changeset_old(pIter, i, &pVal); 878 test_append_value(pOld, pVal); 879 } 880 } 881 pNew = Tcl_NewObj(); 882 if( op!=SQLITE_DELETE ){ 883 int i; 884 for(i=0; i<nCol; i++){ 885 sqlite3_value *pVal; 886 sqlite3changeset_new(pIter, i, &pVal); 887 test_append_value(pNew, pVal); 888 } 889 } 890 Tcl_ListObjAppendElement(0, pVar, pOld); 891 Tcl_ListObjAppendElement(0, pVar, pNew); 892 893 Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0); 894 rc = Tcl_EvalObjEx(interp, pScript, 0); 895 if( rc!=TCL_OK && rc!=TCL_CONTINUE ){ 896 sqlite3changeset_finalize(pIter); 897 return rc==TCL_BREAK ? TCL_OK : rc; 898 } 899 } 900 901 if( isCheckNext ){ 902 int rc2 = sqlite3changeset_next(pIter); 903 rc = sqlite3changeset_finalize(pIter); 904 assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc ); 905 }else{ 906 rc = sqlite3changeset_finalize(pIter); 907 } 908 if( rc!=SQLITE_OK ){ 909 return test_session_error(interp, rc, 0); 910 } 911 912 return TCL_OK; 913 } 914 915 int TestSession_Init(Tcl_Interp *interp){ 916 Tcl_CreateObjCommand(interp, "sqlite3session", test_sqlite3session, 0, 0); 917 Tcl_CreateObjCommand( 918 interp, "sqlite3session_foreach", test_sqlite3session_foreach, 0, 0 919 ); 920 Tcl_CreateObjCommand( 921 interp, "sqlite3changeset_invert", test_sqlite3changeset_invert, 0, 0 922 ); 923 Tcl_CreateObjCommand( 924 interp, "sqlite3changeset_concat", test_sqlite3changeset_concat, 0, 0 925 ); 926 Tcl_CreateObjCommand( 927 interp, "sqlite3changeset_apply", test_sqlite3changeset_apply, 0, 0 928 ); 929 Tcl_CreateObjCommand( 930 interp, "sqlite3changeset_apply_replace_all", 931 test_sqlite3changeset_apply_replace_all, 0, 0 932 ); 933 return TCL_OK; 934 } 935 936 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */ 937