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