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