OLD | NEW |
(Empty) | |
| 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 /* |
| 715 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? |
| 716 */ |
| 717 static int SQLITE_TCLAPI test_sqlite3changeset_apply( |
| 718 void * clientData, |
| 719 Tcl_Interp *interp, |
| 720 int objc, |
| 721 Tcl_Obj *CONST objv[] |
| 722 ){ |
| 723 sqlite3 *db; /* Database handle */ |
| 724 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */ |
| 725 int rc; /* Return code from changeset_invert() */ |
| 726 void *pChangeset; /* Buffer containing changeset */ |
| 727 int nChangeset; /* Size of buffer aChangeset in bytes */ |
| 728 TestConflictHandler ctx; |
| 729 TestStreamInput sStr; |
| 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 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, |
| 752 (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx |
| 753 ); |
| 754 }else{ |
| 755 sStr.aData = (unsigned char*)pChangeset; |
| 756 sStr.nData = nChangeset; |
| 757 rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr, |
| 758 (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx |
| 759 ); |
| 760 } |
| 761 |
| 762 if( rc!=SQLITE_OK ){ |
| 763 return test_session_error(interp, rc, 0); |
| 764 } |
| 765 Tcl_ResetResult(interp); |
| 766 return TCL_OK; |
| 767 } |
| 768 |
| 769 /* |
| 770 ** sqlite3changeset_apply_replace_all DB CHANGESET |
| 771 */ |
| 772 static int SQLITE_TCLAPI test_sqlite3changeset_apply_replace_all( |
| 773 void * clientData, |
| 774 Tcl_Interp *interp, |
| 775 int objc, |
| 776 Tcl_Obj *CONST objv[] |
| 777 ){ |
| 778 sqlite3 *db; /* Database handle */ |
| 779 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */ |
| 780 int rc; /* Return code from changeset_invert() */ |
| 781 void *pChangeset; /* Buffer containing changeset */ |
| 782 int nChangeset; /* Size of buffer aChangeset in bytes */ |
| 783 |
| 784 if( objc!=3 ){ |
| 785 Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET"); |
| 786 return TCL_ERROR; |
| 787 } |
| 788 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){ |
| 789 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0); |
| 790 return TCL_ERROR; |
| 791 } |
| 792 db = *(sqlite3 **)info.objClientData; |
| 793 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset); |
| 794 |
| 795 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0); |
| 796 if( rc!=SQLITE_OK ){ |
| 797 return test_session_error(interp, rc, 0); |
| 798 } |
| 799 Tcl_ResetResult(interp); |
| 800 return TCL_OK; |
| 801 } |
| 802 |
| 803 |
| 804 /* |
| 805 ** sqlite3changeset_invert CHANGESET |
| 806 */ |
| 807 static int SQLITE_TCLAPI test_sqlite3changeset_invert( |
| 808 void * clientData, |
| 809 Tcl_Interp *interp, |
| 810 int objc, |
| 811 Tcl_Obj *CONST objv[] |
| 812 ){ |
| 813 int rc; /* Return code from changeset_invert() */ |
| 814 TestStreamInput sIn; /* Input stream */ |
| 815 TestSessionsBlob sOut; /* Output blob */ |
| 816 |
| 817 if( objc!=2 ){ |
| 818 Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET"); |
| 819 return TCL_ERROR; |
| 820 } |
| 821 |
| 822 memset(&sIn, 0, sizeof(sIn)); |
| 823 memset(&sOut, 0, sizeof(sOut)); |
| 824 sIn.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); |
| 825 sIn.aData = Tcl_GetByteArrayFromObj(objv[1], &sIn.nData); |
| 826 |
| 827 if( sIn.nStream ){ |
| 828 rc = sqlite3changeset_invert_strm( |
| 829 testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut |
| 830 ); |
| 831 }else{ |
| 832 rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p); |
| 833 } |
| 834 if( rc!=SQLITE_OK ){ |
| 835 rc = test_session_error(interp, rc, 0); |
| 836 }else{ |
| 837 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); |
| 838 } |
| 839 sqlite3_free(sOut.p); |
| 840 return rc; |
| 841 } |
| 842 |
| 843 /* |
| 844 ** sqlite3changeset_concat LEFT RIGHT |
| 845 */ |
| 846 static int SQLITE_TCLAPI test_sqlite3changeset_concat( |
| 847 void * clientData, |
| 848 Tcl_Interp *interp, |
| 849 int objc, |
| 850 Tcl_Obj *CONST objv[] |
| 851 ){ |
| 852 int rc; /* Return code from changeset_invert() */ |
| 853 |
| 854 TestStreamInput sLeft; /* Input stream */ |
| 855 TestStreamInput sRight; /* Input stream */ |
| 856 TestSessionsBlob sOut = {0,0}; /* Output blob */ |
| 857 |
| 858 if( objc!=3 ){ |
| 859 Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT"); |
| 860 return TCL_ERROR; |
| 861 } |
| 862 |
| 863 memset(&sLeft, 0, sizeof(sLeft)); |
| 864 memset(&sRight, 0, sizeof(sRight)); |
| 865 sLeft.aData = Tcl_GetByteArrayFromObj(objv[1], &sLeft.nData); |
| 866 sRight.aData = Tcl_GetByteArrayFromObj(objv[2], &sRight.nData); |
| 867 sLeft.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); |
| 868 sRight.nStream = sLeft.nStream; |
| 869 |
| 870 if( sLeft.nStream>0 ){ |
| 871 rc = sqlite3changeset_concat_strm( |
| 872 testStreamInput, (void*)&sLeft, |
| 873 testStreamInput, (void*)&sRight, |
| 874 testStreamOutput, (void*)&sOut |
| 875 ); |
| 876 }else{ |
| 877 rc = sqlite3changeset_concat( |
| 878 sLeft.nData, sLeft.aData, sRight.nData, sRight.aData, &sOut.n, &sOut.p |
| 879 ); |
| 880 } |
| 881 |
| 882 if( rc!=SQLITE_OK ){ |
| 883 rc = test_session_error(interp, rc, 0); |
| 884 }else{ |
| 885 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); |
| 886 } |
| 887 sqlite3_free(sOut.p); |
| 888 return rc; |
| 889 } |
| 890 |
| 891 /* |
| 892 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT |
| 893 */ |
| 894 static int SQLITE_TCLAPI test_sqlite3session_foreach( |
| 895 void * clientData, |
| 896 Tcl_Interp *interp, |
| 897 int objc, |
| 898 Tcl_Obj *CONST objv[] |
| 899 ){ |
| 900 void *pChangeset; |
| 901 int nChangeset; |
| 902 sqlite3_changeset_iter *pIter; |
| 903 int rc; |
| 904 Tcl_Obj *pVarname; |
| 905 Tcl_Obj *pCS; |
| 906 Tcl_Obj *pScript; |
| 907 int isCheckNext = 0; |
| 908 |
| 909 TestStreamInput sStr; |
| 910 memset(&sStr, 0, sizeof(sStr)); |
| 911 |
| 912 if( objc>1 ){ |
| 913 char *zOpt = Tcl_GetString(objv[1]); |
| 914 isCheckNext = (strcmp(zOpt, "-next")==0); |
| 915 } |
| 916 if( objc!=4+isCheckNext ){ |
| 917 Tcl_WrongNumArgs(interp, 1, objv, "?-next? VARNAME CHANGESET SCRIPT"); |
| 918 return TCL_ERROR; |
| 919 } |
| 920 |
| 921 pVarname = objv[1+isCheckNext]; |
| 922 pCS = objv[2+isCheckNext]; |
| 923 pScript = objv[3+isCheckNext]; |
| 924 |
| 925 pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset); |
| 926 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); |
| 927 if( sStr.nStream==0 ){ |
| 928 rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); |
| 929 }else{ |
| 930 sStr.aData = (unsigned char*)pChangeset; |
| 931 sStr.nData = nChangeset; |
| 932 rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr); |
| 933 } |
| 934 if( rc!=SQLITE_OK ){ |
| 935 return test_session_error(interp, rc, 0); |
| 936 } |
| 937 |
| 938 while( SQLITE_ROW==sqlite3changeset_next(pIter) ){ |
| 939 int nCol; /* Number of columns in table */ |
| 940 int nCol2; /* Number of columns in table */ |
| 941 int op; /* SQLITE_INSERT, UPDATE or DELETE */ |
| 942 const char *zTab; /* Name of table change applies to */ |
| 943 Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */ |
| 944 Tcl_Obj *pOld; /* Vector of old.* values */ |
| 945 Tcl_Obj *pNew; /* Vector of new.* values */ |
| 946 int bIndirect; |
| 947 |
| 948 char *zPK; |
| 949 unsigned char *abPK; |
| 950 int i; |
| 951 |
| 952 /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this |
| 953 ** iterator. */ |
| 954 int nDummy; |
| 955 if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){ |
| 956 sqlite3changeset_finalize(pIter); |
| 957 return TCL_ERROR; |
| 958 } |
| 959 |
| 960 sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); |
| 961 pVar = Tcl_NewObj(); |
| 962 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj( |
| 963 op==SQLITE_INSERT ? "INSERT" : |
| 964 op==SQLITE_UPDATE ? "UPDATE" : |
| 965 "DELETE", -1 |
| 966 )); |
| 967 |
| 968 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1)); |
| 969 Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect)); |
| 970 |
| 971 zPK = ckalloc(nCol+1); |
| 972 memset(zPK, 0, nCol+1); |
| 973 sqlite3changeset_pk(pIter, &abPK, &nCol2); |
| 974 assert( nCol==nCol2 ); |
| 975 for(i=0; i<nCol; i++){ |
| 976 zPK[i] = (abPK[i] ? 'X' : '.'); |
| 977 } |
| 978 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1)); |
| 979 ckfree(zPK); |
| 980 |
| 981 pOld = Tcl_NewObj(); |
| 982 if( op!=SQLITE_INSERT ){ |
| 983 for(i=0; i<nCol; i++){ |
| 984 sqlite3_value *pVal; |
| 985 sqlite3changeset_old(pIter, i, &pVal); |
| 986 test_append_value(pOld, pVal); |
| 987 } |
| 988 } |
| 989 pNew = Tcl_NewObj(); |
| 990 if( op!=SQLITE_DELETE ){ |
| 991 for(i=0; i<nCol; i++){ |
| 992 sqlite3_value *pVal; |
| 993 sqlite3changeset_new(pIter, i, &pVal); |
| 994 test_append_value(pNew, pVal); |
| 995 } |
| 996 } |
| 997 Tcl_ListObjAppendElement(0, pVar, pOld); |
| 998 Tcl_ListObjAppendElement(0, pVar, pNew); |
| 999 |
| 1000 Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0); |
| 1001 rc = Tcl_EvalObjEx(interp, pScript, 0); |
| 1002 if( rc!=TCL_OK && rc!=TCL_CONTINUE ){ |
| 1003 sqlite3changeset_finalize(pIter); |
| 1004 return rc==TCL_BREAK ? TCL_OK : rc; |
| 1005 } |
| 1006 } |
| 1007 |
| 1008 if( isCheckNext ){ |
| 1009 int rc2 = sqlite3changeset_next(pIter); |
| 1010 rc = sqlite3changeset_finalize(pIter); |
| 1011 assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc ); |
| 1012 }else{ |
| 1013 rc = sqlite3changeset_finalize(pIter); |
| 1014 } |
| 1015 if( rc!=SQLITE_OK ){ |
| 1016 return test_session_error(interp, rc, 0); |
| 1017 } |
| 1018 |
| 1019 return TCL_OK; |
| 1020 } |
| 1021 |
| 1022 int TestSession_Init(Tcl_Interp *interp){ |
| 1023 struct Cmd { |
| 1024 const char *zCmd; |
| 1025 Tcl_ObjCmdProc *xProc; |
| 1026 } aCmd[] = { |
| 1027 { "sqlite3session", test_sqlite3session }, |
| 1028 { "sqlite3session_foreach", test_sqlite3session_foreach }, |
| 1029 { "sqlite3changeset_invert", test_sqlite3changeset_invert }, |
| 1030 { "sqlite3changeset_concat", test_sqlite3changeset_concat }, |
| 1031 { "sqlite3changeset_apply", test_sqlite3changeset_apply }, |
| 1032 { "sqlite3changeset_apply_replace_all", |
| 1033 test_sqlite3changeset_apply_replace_all }, |
| 1034 { "sql_exec_changeset", test_sql_exec_changeset }, |
| 1035 }; |
| 1036 int i; |
| 1037 |
| 1038 for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){ |
| 1039 struct Cmd *p = &aCmd[i]; |
| 1040 Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0); |
| 1041 } |
| 1042 |
| 1043 return TCL_OK; |
| 1044 } |
| 1045 |
| 1046 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */ |
OLD | NEW |