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