Download ext/session/test_session.c from AryaWu/sqlite: direct link, hf CLI and curl.
- Browser
- Download file 53.1 kB
-
https://huggingface.co/AryaWu/sqlite/resolve/main/ext/session/test_session.c
- Command line
-
hf download hf://AryaWu/sqlite/ext/session/test_session.c
-
curl -L -o test_session.c https://huggingface.co/AryaWu/sqlite/resolve/main/ext/session/test_session.c
53.1 kB
| typedef unsigned char u8; | |
| typedef struct TestSession TestSession; | |
| struct TestSession { | |
| sqlite3_session *pSession; | |
| Tcl_Interp *interp; | |
| Tcl_Obj *pFilterScript; | |
| }; | |
| typedef struct TestStreamInput TestStreamInput; | |
| struct TestStreamInput { | |
| int nStream; /* Maximum chunk size */ | |
| unsigned char *aData; /* Pointer to buffer containing data */ | |
| int nData; /* Size of buffer aData in bytes */ | |
| int iData; /* Bytes of data already read by sessions */ | |
| }; | |
| /* | |
| ** Extract an sqlite3* db handle from the object passed as the second | |
| ** argument. If successful, set *pDb to point to the db handle and return | |
| ** TCL_OK. Otherwise, return TCL_ERROR. | |
| */ | |
| static int dbHandleFromObj(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){ | |
| Tcl_CmdInfo info; | |
| if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){ | |
| Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(pObj), NULL); | |
| return TCL_ERROR; | |
| } | |
| *pDb = *(sqlite3 **)info.objClientData; | |
| return TCL_OK; | |
| } | |
| /************************************************************************* | |
| ** The following code is copied byte-for-byte from the sessions module | |
| ** documentation. It is used by some of the sessions modules tests to | |
| ** ensure that the example in the documentation does actually work. | |
| */ | |
| /* | |
| ** Argument zSql points to a buffer containing an SQL script to execute | |
| ** against the database handle passed as the first argument. As well as | |
| ** executing the SQL script, this function collects a changeset recording | |
| ** all changes made to the "main" database file. Assuming no error occurs, | |
| ** output variables (*ppChangeset) and (*pnChangeset) are set to point | |
| ** to a buffer containing the changeset and the size of the changeset in | |
| ** bytes before returning SQLITE_OK. In this case it is the responsibility | |
| ** of the caller to eventually free the changeset blob by passing it to | |
| ** the sqlite3_free function. | |
| ** | |
| ** Or, if an error does occur, return an SQLite error code. The final | |
| ** value of (*pChangeset) and (*pnChangeset) are undefined in this case. | |
| */ | |
| int sql_exec_changeset( | |
| sqlite3 *db, /* Database handle */ | |
| const char *zSql, /* SQL script to execute */ | |
| int *pnChangeset, /* OUT: Size of changeset blob in bytes */ | |
| void **ppChangeset /* OUT: Pointer to changeset blob */ | |
| ){ | |
| sqlite3_session *pSession = 0; | |
| int rc; | |
| int val = 1; | |
| /* Create a new session object */ | |
| rc = sqlite3session_create(db, "main", &pSession); | |
| sqlite3session_object_config(pSession, SQLITE_SESSION_OBJCONFIG_ROWID, &val); | |
| /* Configure the session object to record changes to all tables */ | |
| if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL); | |
| /* Execute the SQL script */ | |
| if( rc==SQLITE_OK ) rc = sqlite3_exec(db, zSql, 0, 0, 0); | |
| /* Collect the changeset */ | |
| if( rc==SQLITE_OK ){ | |
| rc = sqlite3session_changeset(pSession, pnChangeset, ppChangeset); | |
| } | |
| /* Delete the session object */ | |
| sqlite3session_delete(pSession); | |
| return rc; | |
| } | |
| /************************************************************************/ | |
| static int sqlite3_test_changeset(int, void *, char **); | |
| static void assert_changeset_is_ok(int n, void *p){ | |
| char *z = 0; | |
| (void)sqlite3_test_changeset(n, p, &z); | |
| assert( z==0 ); | |
| } | |
| /* | |
| ** Tclcmd: sql_exec_changeset DB SQL | |
| */ | |
| static int SQLITE_TCLAPI test_sql_exec_changeset( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| const char *zSql; | |
| sqlite3 *db; | |
| void *pChangeset; | |
| int nChangeset; | |
| int rc; | |
| if( objc!=3 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "DB SQL"); | |
| return TCL_ERROR; | |
| } | |
| if( dbHandleFromObj(interp, objv[1], &db) ) return TCL_ERROR; | |
| zSql = (const char*)Tcl_GetString(objv[2]); | |
| rc = sql_exec_changeset(db, zSql, &nChangeset, &pChangeset); | |
| if( rc!=SQLITE_OK ){ | |
| Tcl_ResetResult(interp); | |
| Tcl_AppendResult(interp, "error in sql_exec_changeset()", NULL); | |
| return TCL_ERROR; | |
| } | |
| assert_changeset_is_ok(nChangeset, pChangeset); | |
| Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChangeset, nChangeset)); | |
| sqlite3_free(pChangeset); | |
| return TCL_OK; | |
| } | |
| /* | |
| ** Attempt to find the global variable zVar within interpreter interp | |
| ** and extract an integer value from it. Return this value. | |
| ** | |
| ** If the named variable cannot be found, or if it cannot be interpreted | |
| ** as a integer, return 0. | |
| */ | |
| static int test_tcl_integer(Tcl_Interp *interp, const char *zVar){ | |
| Tcl_Obj *pObj; | |
| int iVal = 0; | |
| Tcl_Obj *pName = Tcl_NewStringObj(zVar, -1); | |
| Tcl_IncrRefCount(pName); | |
| pObj = Tcl_ObjGetVar2(interp, pName, 0, TCL_GLOBAL_ONLY); | |
| Tcl_DecrRefCount(pName); | |
| if( pObj ) Tcl_GetIntFromObj(0, pObj, &iVal); | |
| return iVal; | |
| } | |
| static int test_session_error(Tcl_Interp *interp, int rc, char *zErr){ | |
| extern const char *sqlite3ErrName(int); | |
| Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); | |
| if( zErr ){ | |
| Tcl_AppendResult(interp, " - ", zErr, NULL); | |
| sqlite3_free(zErr); | |
| } | |
| return TCL_ERROR; | |
| } | |
| static int test_table_filter(void *pCtx, const char *zTbl){ | |
| TestSession *p = (TestSession*)pCtx; | |
| Tcl_Obj *pEval; | |
| int rc; | |
| int bRes = 0; | |
| pEval = Tcl_DuplicateObj(p->pFilterScript); | |
| Tcl_IncrRefCount(pEval); | |
| rc = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zTbl, -1)); | |
| if( rc==TCL_OK ){ | |
| rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL); | |
| } | |
| if( rc==TCL_OK ){ | |
| rc = Tcl_GetBooleanFromObj(p->interp, Tcl_GetObjResult(p->interp), &bRes); | |
| } | |
| if( rc!=TCL_OK ){ | |
| /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */ | |
| Tcl_BackgroundError(p->interp); | |
| } | |
| Tcl_DecrRefCount(pEval); | |
| return bRes; | |
| } | |
| struct TestSessionsBlob { | |
| void *p; | |
| int n; | |
| }; | |
| typedef struct TestSessionsBlob TestSessionsBlob; | |
| static int testStreamOutput( | |
| void *pCtx, | |
| const void *pData, | |
| int nData | |
| ){ | |
| TestSessionsBlob *pBlob = (TestSessionsBlob*)pCtx; | |
| char *pNew; | |
| assert( nData>0 ); | |
| pNew = (char*)sqlite3_realloc(pBlob->p, pBlob->n + nData); | |
| if( pNew==0 ){ | |
| return SQLITE_NOMEM; | |
| } | |
| pBlob->p = (void*)pNew; | |
| memcpy(&pNew[pBlob->n], pData, nData); | |
| pBlob->n += nData; | |
| return SQLITE_OK; | |
| } | |
| /* | |
| ** Tclcmd: $session attach TABLE | |
| ** $session changeset | |
| ** $session delete | |
| ** $session enable BOOL | |
| ** $session indirect INTEGER | |
| ** $session patchset | |
| ** $session table_filter SCRIPT | |
| */ | |
| static int SQLITE_TCLAPI test_session_cmd( | |
| void *clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| TestSession *p = (TestSession*)clientData; | |
| sqlite3_session *pSession = p->pSession; | |
| static struct SessionSubcmd { | |
| const char *zSub; | |
| int nArg; | |
| const char *zMsg; | |
| int iSub; | |
| } aSub[] = { | |
| { "attach", 1, "TABLE", }, /* 0 */ | |
| { "changeset", 0, "", }, /* 1 */ | |
| { "delete", 0, "", }, /* 2 */ | |
| { "enable", 1, "BOOL", }, /* 3 */ | |
| { "indirect", 1, "BOOL", }, /* 4 */ | |
| { "isempty", 0, "", }, /* 5 */ | |
| { "table_filter", 1, "SCRIPT", }, /* 6 */ | |
| { "patchset", 0, "", }, /* 7 */ | |
| { "diff", 2, "FROMDB TBL", }, /* 8 */ | |
| { "memory_used", 0, "", }, /* 9 */ | |
| { "changeset_size", 0, "", }, /* 10 */ | |
| { "object_config", 2, "OPTION INTEGER", }, /* 11 */ | |
| { 0 } | |
| }; | |
| int iSub; | |
| int rc; | |
| if( objc<2 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); | |
| return TCL_ERROR; | |
| } | |
| rc = Tcl_GetIndexFromObjStruct(interp, | |
| objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub | |
| ); | |
| if( rc!=TCL_OK ) return rc; | |
| if( objc!=2+aSub[iSub].nArg ){ | |
| Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg); | |
| return TCL_ERROR; | |
| } | |
| switch( iSub ){ | |
| case 0: { /* attach */ | |
| char *zArg = Tcl_GetString(objv[2]); | |
| if( zArg[0]=='*' && zArg[1]=='\0' ) zArg = 0; | |
| rc = sqlite3session_attach(pSession, zArg); | |
| if( rc!=SQLITE_OK ){ | |
| return test_session_error(interp, rc, 0); | |
| } | |
| break; | |
| } | |
| case 7: /* patchset */ | |
| case 1: { /* changeset */ | |
| TestSessionsBlob o = {0, 0}; | |
| if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){ | |
| void *pCtx = (void*)&o; | |
| if( iSub==7 ){ | |
| rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx); | |
| }else{ | |
| rc = sqlite3session_changeset_strm(pSession, testStreamOutput, pCtx); | |
| } | |
| }else{ | |
| if( iSub==7 ){ | |
| rc = sqlite3session_patchset(pSession, &o.n, &o.p); | |
| }else{ | |
| rc = sqlite3session_changeset(pSession, &o.n, &o.p); | |
| } | |
| } | |
| if( rc==SQLITE_OK ){ | |
| assert_changeset_is_ok(o.n, o.p); | |
| Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n)); | |
| } | |
| sqlite3_free(o.p); | |
| if( rc!=SQLITE_OK ){ | |
| return test_session_error(interp, rc, 0); | |
| } | |
| break; | |
| } | |
| case 2: /* delete */ | |
| Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); | |
| break; | |
| case 3: { /* enable */ | |
| int val; | |
| if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR; | |
| val = sqlite3session_enable(pSession, val); | |
| Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); | |
| break; | |
| } | |
| case 4: { /* indirect */ | |
| int val; | |
| if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR; | |
| val = sqlite3session_indirect(pSession, val); | |
| Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); | |
| break; | |
| } | |
| case 5: { /* isempty */ | |
| int val; | |
| val = sqlite3session_isempty(pSession); | |
| Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); | |
| break; | |
| } | |
| case 6: { /* table_filter */ | |
| if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript); | |
| p->interp = interp; | |
| p->pFilterScript = Tcl_DuplicateObj(objv[2]); | |
| Tcl_IncrRefCount(p->pFilterScript); | |
| sqlite3session_table_filter(pSession, test_table_filter, clientData); | |
| break; | |
| } | |
| case 8: { /* diff */ | |
| char *zErr = 0; | |
| rc = sqlite3session_diff(pSession, | |
| Tcl_GetString(objv[2]), | |
| Tcl_GetString(objv[3]), | |
| &zErr | |
| ); | |
| assert( rc!=SQLITE_OK || zErr==0 ); | |
| if( rc ){ | |
| return test_session_error(interp, rc, zErr); | |
| } | |
| break; | |
| } | |
| case 9: { /* memory_used */ | |
| sqlite3_int64 nMalloc = sqlite3session_memory_used(pSession); | |
| Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nMalloc)); | |
| break; | |
| } | |
| case 10: { | |
| sqlite3_int64 nSize = sqlite3session_changeset_size(pSession); | |
| Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nSize)); | |
| break; | |
| } | |
| case 11: { /* object_config */ | |
| struct ObjConfOpt { | |
| const char *zName; | |
| int opt; | |
| } aOpt[] = { | |
| { "size", SQLITE_SESSION_OBJCONFIG_SIZE }, | |
| { "rowid", SQLITE_SESSION_OBJCONFIG_ROWID }, | |
| { 0, 0 } | |
| }; | |
| int sz = (int)sizeof(aOpt[0]); | |
| int iArg; | |
| Tcl_Size iOpt; | |
| if( Tcl_GetIndexFromObjStruct(interp,objv[2],aOpt,sz,"option",0,&iOpt) ){ | |
| return TCL_ERROR; | |
| } | |
| if( Tcl_GetIntFromObj(interp, objv[3], &iArg) ){ | |
| return TCL_ERROR; | |
| } | |
| rc = sqlite3session_object_config(pSession, aOpt[iOpt].opt, &iArg); | |
| if( rc!=SQLITE_OK ){ | |
| extern const char *sqlite3ErrName(int); | |
| Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); | |
| }else{ | |
| Tcl_SetObjResult(interp, Tcl_NewIntObj(iArg)); | |
| } | |
| break; | |
| } | |
| } | |
| return TCL_OK; | |
| } | |
| static void SQLITE_TCLAPI test_session_del(void *clientData){ | |
| TestSession *p = (TestSession*)clientData; | |
| if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript); | |
| sqlite3session_delete(p->pSession); | |
| ckfree((char*)p); | |
| } | |
| /* | |
| ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME | |
| */ | |
| static int SQLITE_TCLAPI test_sqlite3session( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| sqlite3 *db; | |
| Tcl_CmdInfo info; | |
| int rc; /* sqlite3session_create() return code */ | |
| TestSession *p; /* New wrapper object */ | |
| int iArg = -1; | |
| if( objc!=4 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME"); | |
| return TCL_ERROR; | |
| } | |
| if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){ | |
| Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), NULL); | |
| return TCL_ERROR; | |
| } | |
| db = *(sqlite3 **)info.objClientData; | |
| p = (TestSession*)ckalloc(sizeof(TestSession)); | |
| memset(p, 0, sizeof(TestSession)); | |
| rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &p->pSession); | |
| if( rc!=SQLITE_OK ){ | |
| ckfree((char*)p); | |
| return test_session_error(interp, rc, 0); | |
| } | |
| /* Query the SQLITE_SESSION_OBJCONFIG_SIZE option to ensure that it | |
| ** is clear by default. Then set it. */ | |
| sqlite3session_object_config(p->pSession,SQLITE_SESSION_OBJCONFIG_SIZE,&iArg); | |
| assert( iArg==0 ); | |
| iArg = 1; | |
| sqlite3session_object_config(p->pSession,SQLITE_SESSION_OBJCONFIG_SIZE,&iArg); | |
| Tcl_CreateObjCommand( | |
| interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)p, | |
| test_session_del | |
| ); | |
| Tcl_SetObjResult(interp, objv[1]); | |
| return TCL_OK; | |
| } | |
| static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){ | |
| if( pVal==0 ){ | |
| Tcl_ListObjAppendElement(0, pList, Tcl_NewObj()); | |
| Tcl_ListObjAppendElement(0, pList, Tcl_NewObj()); | |
| }else{ | |
| Tcl_Obj *pObj; | |
| switch( sqlite3_value_type(pVal) ){ | |
| case SQLITE_NULL: | |
| Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("n", 1)); | |
| pObj = Tcl_NewObj(); | |
| break; | |
| case SQLITE_INTEGER: | |
| Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("i", 1)); | |
| pObj = Tcl_NewWideIntObj(sqlite3_value_int64(pVal)); | |
| break; | |
| case SQLITE_FLOAT: | |
| Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("f", 1)); | |
| pObj = Tcl_NewDoubleObj(sqlite3_value_double(pVal)); | |
| break; | |
| case SQLITE_TEXT: { | |
| const char *z = (char*)sqlite3_value_blob(pVal); | |
| int n = sqlite3_value_bytes(pVal); | |
| Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("t", 1)); | |
| pObj = Tcl_NewStringObj(z, n); | |
| break; | |
| } | |
| default: | |
| assert( sqlite3_value_type(pVal)==SQLITE_BLOB ); | |
| Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("b", 1)); | |
| pObj = Tcl_NewByteArrayObj( | |
| sqlite3_value_blob(pVal), | |
| sqlite3_value_bytes(pVal) | |
| ); | |
| break; | |
| } | |
| Tcl_ListObjAppendElement(0, pList, pObj); | |
| } | |
| } | |
| typedef struct TestConflictHandler TestConflictHandler; | |
| struct TestConflictHandler { | |
| Tcl_Interp *interp; | |
| Tcl_Obj *pConflictScript; | |
| Tcl_Obj *pFilterScript; | |
| }; | |
| static int test_obj_eq_string(Tcl_Obj *p, const char *z){ | |
| Tcl_Size n; | |
| Tcl_Size nObj; | |
| char *zObj; | |
| n = (Tcl_Size)strlen(z); | |
| zObj = Tcl_GetStringFromObj(p, &nObj); | |
| return (nObj==n && (n==0 || 0==memcmp(zObj, z, n))); | |
| } | |
| static Tcl_Obj *testIterData(sqlite3_changeset_iter *pIter){ | |
| Tcl_Obj *pVar = 0; | |
| int nCol; /* Number of columns in table */ | |
| int nCol2; /* Number of columns in table */ | |
| int op; /* SQLITE_INSERT, UPDATE or DELETE */ | |
| const char *zTab; /* Name of table change applies to */ | |
| Tcl_Obj *pOld; /* Vector of old.* values */ | |
| Tcl_Obj *pNew; /* Vector of new.* values */ | |
| int bIndirect; | |
| char *zPK; | |
| unsigned char *abPK; | |
| int i; | |
| sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); | |
| pVar = Tcl_NewObj(); | |
| Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj( | |
| op==SQLITE_INSERT ? "INSERT" : | |
| op==SQLITE_UPDATE ? "UPDATE" : | |
| "DELETE", -1 | |
| )); | |
| Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1)); | |
| Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect)); | |
| zPK = ckalloc(nCol+1); | |
| memset(zPK, 0, nCol+1); | |
| sqlite3changeset_pk(pIter, &abPK, &nCol2); | |
| assert( nCol==nCol2 ); | |
| for(i=0; i<nCol; i++){ | |
| zPK[i] = (abPK[i] ? 'X' : '.'); | |
| } | |
| Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1)); | |
| ckfree(zPK); | |
| pOld = Tcl_NewObj(); | |
| if( op!=SQLITE_INSERT ){ | |
| for(i=0; i<nCol; i++){ | |
| sqlite3_value *pVal; | |
| sqlite3changeset_old(pIter, i, &pVal); | |
| test_append_value(pOld, pVal); | |
| } | |
| } | |
| pNew = Tcl_NewObj(); | |
| if( op!=SQLITE_DELETE ){ | |
| for(i=0; i<nCol; i++){ | |
| sqlite3_value *pVal; | |
| sqlite3changeset_new(pIter, i, &pVal); | |
| test_append_value(pNew, pVal); | |
| } | |
| } | |
| Tcl_ListObjAppendElement(0, pVar, pOld); | |
| Tcl_ListObjAppendElement(0, pVar, pNew); | |
| return pVar; | |
| } | |
| static int test_filter_handler( | |
| void *pCtx, /* Pointer to TestConflictHandler structure */ | |
| const char *zTab /* Table name */ | |
| ){ | |
| TestConflictHandler *p = (TestConflictHandler *)pCtx; | |
| int res = 1; | |
| Tcl_Obj *pEval; | |
| Tcl_Interp *interp = p->interp; | |
| pEval = Tcl_DuplicateObj(p->pFilterScript); | |
| Tcl_IncrRefCount(pEval); | |
| if( TCL_OK!=Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1)) | |
| || TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) | |
| || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res) | |
| ){ | |
| Tcl_BackgroundError(interp); | |
| } | |
| Tcl_DecrRefCount(pEval); | |
| return res; | |
| } | |
| static int test_filter_v3_handler( | |
| void *pCtx, /* Pointer to TestConflictHandler structure */ | |
| sqlite3_changeset_iter *pIter | |
| ){ | |
| TestConflictHandler *p = (TestConflictHandler *)pCtx; | |
| int res = 1; | |
| Tcl_Obj *pEval = 0; | |
| Tcl_Interp *interp = p->interp; | |
| pEval = Tcl_DuplicateObj(p->pFilterScript); | |
| Tcl_IncrRefCount(pEval); | |
| Tcl_ListObjAppendElement(0, pEval, testIterData(pIter)); | |
| if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) | |
| || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res) | |
| ){ | |
| Tcl_BackgroundError(interp); | |
| } | |
| Tcl_DecrRefCount(pEval); | |
| return res; | |
| } | |
| static int test_conflict_handler( | |
| void *pCtx, /* Pointer to TestConflictHandler structure */ | |
| int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */ | |
| sqlite3_changeset_iter *pIter /* Handle describing change and conflict */ | |
| ){ | |
| TestConflictHandler *p = (TestConflictHandler *)pCtx; | |
| Tcl_Obj *pEval; | |
| Tcl_Interp *interp = p->interp; | |
| int ret = 0; /* Return value */ | |
| int op; /* SQLITE_UPDATE, DELETE or INSERT */ | |
| const char *zTab; /* Name of table conflict is on */ | |
| int nCol; /* Number of columns in table zTab */ | |
| pEval = Tcl_DuplicateObj(p->pConflictScript); | |
| Tcl_IncrRefCount(pEval); | |
| sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); | |
| if( eConf==SQLITE_CHANGESET_FOREIGN_KEY ){ | |
| int nFk; | |
| sqlite3changeset_fk_conflicts(pIter, &nFk); | |
| Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj("FOREIGN_KEY", -1)); | |
| Tcl_ListObjAppendElement(0, pEval, Tcl_NewIntObj(nFk)); | |
| }else{ | |
| /* Append the operation type. */ | |
| Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj( | |
| op==SQLITE_INSERT ? "INSERT" : | |
| op==SQLITE_UPDATE ? "UPDATE" : | |
| "DELETE", -1 | |
| )); | |
| /* Append the table name. */ | |
| Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1)); | |
| /* Append the conflict type. */ | |
| switch( eConf ){ | |
| case SQLITE_CHANGESET_DATA: | |
| Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1)); | |
| break; | |
| case SQLITE_CHANGESET_NOTFOUND: | |
| Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1)); | |
| break; | |
| case SQLITE_CHANGESET_CONFLICT: | |
| Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1)); | |
| break; | |
| case SQLITE_CHANGESET_CONSTRAINT: | |
| Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1)); | |
| break; | |
| } | |
| /* If this is not an INSERT, append the old row */ | |
| if( op!=SQLITE_INSERT ){ | |
| int i; | |
| Tcl_Obj *pOld = Tcl_NewObj(); | |
| for(i=0; i<nCol; i++){ | |
| sqlite3_value *pVal; | |
| sqlite3changeset_old(pIter, i, &pVal); | |
| test_append_value(pOld, pVal); | |
| } | |
| Tcl_ListObjAppendElement(0, pEval, pOld); | |
| } | |
| /* If this is not a DELETE, append the new row */ | |
| if( op!=SQLITE_DELETE ){ | |
| int i; | |
| Tcl_Obj *pNew = Tcl_NewObj(); | |
| for(i=0; i<nCol; i++){ | |
| sqlite3_value *pVal; | |
| sqlite3changeset_new(pIter, i, &pVal); | |
| test_append_value(pNew, pVal); | |
| } | |
| Tcl_ListObjAppendElement(0, pEval, pNew); | |
| } | |
| /* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append | |
| ** the conflicting row. */ | |
| if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){ | |
| int i; | |
| Tcl_Obj *pConflict = Tcl_NewObj(); | |
| for(i=0; i<nCol; i++){ | |
| int rc; | |
| sqlite3_value *pVal; | |
| rc = sqlite3changeset_conflict(pIter, i, &pVal); | |
| assert( rc==SQLITE_OK ); | |
| test_append_value(pConflict, pVal); | |
| } | |
| Tcl_ListObjAppendElement(0, pEval, pConflict); | |
| } | |
| /*********************************************************************** | |
| ** This block is purely for testing some error conditions. | |
| */ | |
| if( eConf==SQLITE_CHANGESET_CONSTRAINT | |
| || eConf==SQLITE_CHANGESET_NOTFOUND | |
| ){ | |
| sqlite3_value *pVal; | |
| int rc = sqlite3changeset_conflict(pIter, 0, &pVal); | |
| assert( rc==SQLITE_MISUSE ); | |
| }else{ | |
| sqlite3_value *pVal; | |
| int rc = sqlite3changeset_conflict(pIter, -1, &pVal); | |
| assert( rc==SQLITE_RANGE ); | |
| rc = sqlite3changeset_conflict(pIter, nCol, &pVal); | |
| assert( rc==SQLITE_RANGE ); | |
| } | |
| if( op==SQLITE_DELETE ){ | |
| sqlite3_value *pVal; | |
| int rc = sqlite3changeset_new(pIter, 0, &pVal); | |
| assert( rc==SQLITE_MISUSE ); | |
| }else{ | |
| sqlite3_value *pVal; | |
| int rc = sqlite3changeset_new(pIter, -1, &pVal); | |
| assert( rc==SQLITE_RANGE ); | |
| rc = sqlite3changeset_new(pIter, nCol, &pVal); | |
| assert( rc==SQLITE_RANGE ); | |
| } | |
| if( op==SQLITE_INSERT ){ | |
| sqlite3_value *pVal; | |
| int rc = sqlite3changeset_old(pIter, 0, &pVal); | |
| assert( rc==SQLITE_MISUSE ); | |
| }else{ | |
| sqlite3_value *pVal; | |
| int rc = sqlite3changeset_old(pIter, -1, &pVal); | |
| assert( rc==SQLITE_RANGE ); | |
| rc = sqlite3changeset_old(pIter, nCol, &pVal); | |
| assert( rc==SQLITE_RANGE ); | |
| } | |
| if( eConf!=SQLITE_CHANGESET_FOREIGN_KEY ){ | |
| /* eConf!=FOREIGN_KEY is always true at this point. The condition is | |
| ** just there to make it clearer what is being tested. */ | |
| int nDummy; | |
| int rc = sqlite3changeset_fk_conflicts(pIter, &nDummy); | |
| assert( rc==SQLITE_MISUSE ); | |
| } | |
| /* End of testing block | |
| ***********************************************************************/ | |
| } | |
| if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){ | |
| Tcl_BackgroundError(interp); | |
| }else{ | |
| Tcl_Obj *pRes = Tcl_GetObjResult(interp); | |
| if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){ | |
| ret = SQLITE_CHANGESET_OMIT; | |
| }else if( test_obj_eq_string(pRes, "REPLACE") ){ | |
| ret = SQLITE_CHANGESET_REPLACE; | |
| }else if( test_obj_eq_string(pRes, "ABORT") ){ | |
| ret = SQLITE_CHANGESET_ABORT; | |
| }else{ | |
| Tcl_GetIntFromObj(0, pRes, &ret); | |
| } | |
| } | |
| Tcl_DecrRefCount(pEval); | |
| return ret; | |
| } | |
| /* | |
| ** The conflict handler used by sqlite3changeset_apply_replace_all(). | |
| ** This conflict handler calls sqlite3_value_text16() on all available | |
| ** sqlite3_value objects and then returns CHANGESET_REPLACE, or | |
| ** CHANGESET_OMIT if REPLACE is not applicable. This is used to test the | |
| ** effect of a malloc failure within an sqlite3_value_xxx() function | |
| ** invoked by a conflict-handler callback. | |
| */ | |
| static int replace_handler( | |
| void *pCtx, /* Pointer to TestConflictHandler structure */ | |
| int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */ | |
| sqlite3_changeset_iter *pIter /* Handle describing change and conflict */ | |
| ){ | |
| int op; /* SQLITE_UPDATE, DELETE or INSERT */ | |
| const char *zTab; /* Name of table conflict is on */ | |
| int nCol; /* Number of columns in table zTab */ | |
| int i; | |
| sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); | |
| if( op!=SQLITE_INSERT ){ | |
| for(i=0; i<nCol; i++){ | |
| sqlite3_value *pVal; | |
| sqlite3changeset_old(pIter, i, &pVal); | |
| sqlite3_value_text16(pVal); | |
| } | |
| } | |
| if( op!=SQLITE_DELETE ){ | |
| for(i=0; i<nCol; i++){ | |
| sqlite3_value *pVal; | |
| sqlite3changeset_new(pIter, i, &pVal); | |
| sqlite3_value_text16(pVal); | |
| } | |
| } | |
| if( eConf==SQLITE_CHANGESET_DATA ){ | |
| return SQLITE_CHANGESET_REPLACE; | |
| } | |
| return SQLITE_CHANGESET_OMIT; | |
| } | |
| static int testStreamInput( | |
| void *pCtx, /* Context pointer */ | |
| void *pData, /* Buffer to populate */ | |
| int *pnData /* IN/OUT: Bytes requested/supplied */ | |
| ){ | |
| TestStreamInput *p = (TestStreamInput*)pCtx; | |
| int nReq = *pnData; /* Bytes of data requested */ | |
| int nRem = p->nData - p->iData; /* Bytes of data available */ | |
| int nRet = p->nStream; /* Bytes actually returned */ | |
| /* Allocate and free some space. There is no point to this, other than | |
| ** that it allows the regular OOM fault-injection tests to cause an error | |
| ** in this function. */ | |
| void *pAlloc = sqlite3_malloc(10); | |
| if( pAlloc==0 ) return SQLITE_NOMEM; | |
| sqlite3_free(pAlloc); | |
| if( nRet>nReq ) nRet = nReq; | |
| if( nRet>nRem ) nRet = nRem; | |
| assert( nRet>=0 ); | |
| if( nRet>0 ){ | |
| memcpy(pData, &p->aData[p->iData], nRet); | |
| p->iData += nRet; | |
| } | |
| *pnData = nRet; | |
| return SQLITE_OK; | |
| } | |
| static int SQLITE_TCLAPI testSqlite3changesetApply( | |
| int iVersion, | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| sqlite3 *db; /* Database handle */ | |
| Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */ | |
| int rc; /* Return code from changeset_invert() */ | |
| void *pChangeset; /* Buffer containing changeset */ | |
| Tcl_Size nChangeset; /* Size of buffer aChangeset in bytes */ | |
| TestConflictHandler ctx; | |
| TestStreamInput sStr; | |
| void *pRebase = 0; | |
| int nRebase = 0; | |
| int flags = 0; /* Flags for apply_v2() */ | |
| assert( iVersion==1 || iVersion==2 || iVersion==3 ); | |
| memset(&sStr, 0, sizeof(sStr)); | |
| sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); | |
| /* Check for the -nosavepoint, -invert or -ignorenoop switches */ | |
| if( iVersion==2 || iVersion==3 ){ | |
| while( objc>1 ){ | |
| const char *z1 = Tcl_GetString(objv[1]); | |
| int n = (int)strlen(z1); | |
| if( n>3 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){ | |
| flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT; | |
| } | |
| else if( n>3 && n<=9 && 0==sqlite3_strnicmp("-noaction", z1, n) ){ | |
| flags |= SQLITE_CHANGESETAPPLY_FKNOACTION; | |
| } | |
| else if( n>2 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){ | |
| flags |= SQLITE_CHANGESETAPPLY_INVERT; | |
| } | |
| else if( n>2 && n<=11 && 0==sqlite3_strnicmp("-ignorenoop", z1, n) ){ | |
| flags |= SQLITE_CHANGESETAPPLY_IGNORENOOP; | |
| }else{ | |
| break; | |
| } | |
| objc--; | |
| objv++; | |
| } | |
| } | |
| if( objc!=4 && objc!=5 ){ | |
| const char *zMsg; | |
| if( iVersion==2 || iVersion==3 ){ | |
| zMsg = "?-nosavepoint? ?-inverse? ?-ignorenoop? " | |
| "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"; | |
| }else{ | |
| zMsg = "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"; | |
| } | |
| Tcl_WrongNumArgs(interp, 1, objv, zMsg); | |
| return TCL_ERROR; | |
| } | |
| if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){ | |
| Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[1]), NULL); | |
| return TCL_ERROR; | |
| } | |
| db = *(sqlite3 **)info.objClientData; | |
| pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset); | |
| ctx.pConflictScript = objv[3]; | |
| ctx.pFilterScript = objc==5 ? objv[4] : 0; | |
| ctx.interp = interp; | |
| if( sStr.nStream==0 ){ | |
| switch( iVersion ){ | |
| case 1: | |
| rc = sqlite3changeset_apply(db, (int)nChangeset, pChangeset, | |
| (objc==5)?test_filter_handler:0, test_conflict_handler, (void*)&ctx | |
| ); | |
| break; | |
| case 2: | |
| rc = sqlite3changeset_apply_v2(db, (int)nChangeset, pChangeset, | |
| (objc==5)?test_filter_handler:0, test_conflict_handler, (void*)&ctx, | |
| &pRebase, &nRebase, flags | |
| ); | |
| break; | |
| case 3: | |
| rc = sqlite3changeset_apply_v3(db, (int)nChangeset, pChangeset, | |
| (objc==5)?test_filter_v3_handler:0, test_conflict_handler, | |
| (void*)&ctx, &pRebase, &nRebase, flags | |
| ); | |
| break; | |
| } | |
| }else{ | |
| sStr.aData = (unsigned char*)pChangeset; | |
| sStr.nData = (int)nChangeset; | |
| switch( iVersion ){ | |
| case 1: | |
| rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr, | |
| (objc==5) ? test_filter_handler : 0, | |
| test_conflict_handler, (void *)&ctx | |
| ); | |
| break; | |
| case 2: | |
| rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr, | |
| (objc==5) ? test_filter_handler : 0, | |
| test_conflict_handler, (void *)&ctx, | |
| &pRebase, &nRebase, flags | |
| ); | |
| break; | |
| case 3: | |
| rc = sqlite3changeset_apply_v3_strm(db, testStreamInput, (void*)&sStr, | |
| (objc==5) ? test_filter_v3_handler : 0, | |
| test_conflict_handler, (void *)&ctx, | |
| &pRebase, &nRebase, flags | |
| ); | |
| break; | |
| } | |
| } | |
| if( rc!=SQLITE_OK ){ | |
| return test_session_error(interp, rc, 0); | |
| }else{ | |
| Tcl_ResetResult(interp); | |
| if( (iVersion==2 || iVersion==3) && pRebase ){ | |
| Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase)); | |
| } | |
| } | |
| sqlite3_free(pRebase); | |
| return TCL_OK; | |
| } | |
| /* | |
| ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? | |
| */ | |
| static int SQLITE_TCLAPI test_sqlite3changeset_apply( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| return testSqlite3changesetApply(1, clientData, interp, objc, objv); | |
| } | |
| /* | |
| ** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? | |
| */ | |
| static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| return testSqlite3changesetApply(2, clientData, interp, objc, objv); | |
| } | |
| /* | |
| ** sqlite3changeset_apply_v3 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? | |
| */ | |
| static int SQLITE_TCLAPI test_sqlite3changeset_apply_v3( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| return testSqlite3changesetApply(3, clientData, interp, objc, objv); | |
| } | |
| /* | |
| ** sqlite3changeset_apply_replace_all DB CHANGESET | |
| */ | |
| static int SQLITE_TCLAPI test_sqlite3changeset_apply_replace_all( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| sqlite3 *db; /* Database handle */ | |
| Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */ | |
| int rc; /* Return code from changeset_invert() */ | |
| void *pChangeset; /* Buffer containing changeset */ | |
| Tcl_Size nChangeset; /* Size of buffer aChangeset in bytes */ | |
| if( objc!=3 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET"); | |
| return TCL_ERROR; | |
| } | |
| if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){ | |
| Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), NULL); | |
| return TCL_ERROR; | |
| } | |
| db = *(sqlite3 **)info.objClientData; | |
| pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset); | |
| rc = sqlite3changeset_apply(db, (int)nChangeset, pChangeset, | |
| 0, replace_handler,0); | |
| if( rc!=SQLITE_OK ){ | |
| return test_session_error(interp, rc, 0); | |
| } | |
| Tcl_ResetResult(interp); | |
| return TCL_OK; | |
| } | |
| /* | |
| ** sqlite3changeset_invert CHANGESET | |
| */ | |
| static int SQLITE_TCLAPI test_sqlite3changeset_invert( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| int rc; /* Return code from changeset_invert() */ | |
| Tcl_Size nn; | |
| TestStreamInput sIn; /* Input stream */ | |
| TestSessionsBlob sOut; /* Output blob */ | |
| if( objc!=2 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET"); | |
| return TCL_ERROR; | |
| } | |
| memset(&sIn, 0, sizeof(sIn)); | |
| memset(&sOut, 0, sizeof(sOut)); | |
| sIn.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); | |
| sIn.aData = Tcl_GetByteArrayFromObj(objv[1], &nn); | |
| sIn.nData = (int)nn; | |
| if( sIn.nStream ){ | |
| rc = sqlite3changeset_invert_strm( | |
| testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut | |
| ); | |
| }else{ | |
| rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p); | |
| } | |
| if( rc!=SQLITE_OK ){ | |
| rc = test_session_error(interp, rc, 0); | |
| }else{ | |
| assert_changeset_is_ok(sOut.n, sOut.p); | |
| Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); | |
| } | |
| sqlite3_free(sOut.p); | |
| return rc; | |
| } | |
| /* | |
| ** sqlite3changeset_concat LEFT RIGHT | |
| */ | |
| static int SQLITE_TCLAPI test_sqlite3changeset_concat( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| int rc; /* Return code from changeset_invert() */ | |
| Tcl_Size nn; | |
| TestStreamInput sLeft; /* Input stream */ | |
| TestStreamInput sRight; /* Input stream */ | |
| TestSessionsBlob sOut = {0,0}; /* Output blob */ | |
| if( objc!=3 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT"); | |
| return TCL_ERROR; | |
| } | |
| memset(&sLeft, 0, sizeof(sLeft)); | |
| memset(&sRight, 0, sizeof(sRight)); | |
| sLeft.aData = Tcl_GetByteArrayFromObj(objv[1], &nn); | |
| sLeft.nData = (int)nn; | |
| sRight.aData = Tcl_GetByteArrayFromObj(objv[2], &nn); | |
| sRight.nData = (int)nn; | |
| sLeft.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); | |
| sRight.nStream = sLeft.nStream; | |
| if( sLeft.nStream>0 ){ | |
| rc = sqlite3changeset_concat_strm( | |
| testStreamInput, (void*)&sLeft, | |
| testStreamInput, (void*)&sRight, | |
| testStreamOutput, (void*)&sOut | |
| ); | |
| }else{ | |
| rc = sqlite3changeset_concat( | |
| sLeft.nData, sLeft.aData, sRight.nData, sRight.aData, &sOut.n, &sOut.p | |
| ); | |
| } | |
| if( rc!=SQLITE_OK ){ | |
| rc = test_session_error(interp, rc, 0); | |
| }else{ | |
| assert_changeset_is_ok(sOut.n, sOut.p); | |
| Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n)); | |
| } | |
| sqlite3_free(sOut.p); | |
| return rc; | |
| } | |
| /* | |
| ** sqlite3session_foreach VARNAME CHANGESET SCRIPT | |
| */ | |
| static int SQLITE_TCLAPI test_sqlite3session_foreach( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| void *pChangeset; | |
| Tcl_Size nChangeset; | |
| sqlite3_changeset_iter *pIter; | |
| int rc; | |
| Tcl_Obj *pVarname; | |
| Tcl_Obj *pCS; | |
| Tcl_Obj *pScript; | |
| int isCheckNext = 0; | |
| int isInvert = 0; | |
| TestStreamInput sStr; | |
| memset(&sStr, 0, sizeof(sStr)); | |
| while( objc>1 ){ | |
| char *zOpt = Tcl_GetString(objv[1]); | |
| int nOpt = (int)strlen(zOpt); | |
| if( zOpt[0]!='-' ) break; | |
| if( nOpt<=7 && 0==sqlite3_strnicmp(zOpt, "-invert", nOpt) ){ | |
| isInvert = 1; | |
| }else | |
| if( nOpt<=5 && 0==sqlite3_strnicmp(zOpt, "-next", nOpt) ){ | |
| isCheckNext = 1; | |
| }else{ | |
| break; | |
| } | |
| objv++; | |
| objc--; | |
| } | |
| if( objc!=4 ){ | |
| Tcl_WrongNumArgs( | |
| interp, 1, objv, "?-next? ?-invert? VARNAME CHANGESET SCRIPT"); | |
| return TCL_ERROR; | |
| } | |
| pVarname = objv[1]; | |
| pCS = objv[2]; | |
| pScript = objv[3]; | |
| pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset); | |
| sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); | |
| if( isInvert ){ | |
| int f = SQLITE_CHANGESETSTART_INVERT; | |
| if( sStr.nStream==0 ){ | |
| rc = sqlite3changeset_start_v2(&pIter, (int)nChangeset, pChangeset, f); | |
| }else{ | |
| void *pCtx = (void*)&sStr; | |
| sStr.aData = (unsigned char*)pChangeset; | |
| sStr.nData = (int)nChangeset; | |
| rc = sqlite3changeset_start_v2_strm(&pIter, testStreamInput, pCtx, f); | |
| } | |
| }else{ | |
| if( sStr.nStream==0 ){ | |
| rc = sqlite3changeset_start(&pIter, (int)nChangeset, pChangeset); | |
| }else{ | |
| sStr.aData = (unsigned char*)pChangeset; | |
| sStr.nData = (int)nChangeset; | |
| rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr); | |
| } | |
| } | |
| if( rc!=SQLITE_OK ){ | |
| return test_session_error(interp, rc, 0); | |
| } | |
| while( SQLITE_ROW==sqlite3changeset_next(pIter) ){ | |
| Tcl_Obj *pVar = 0; /* Tcl value to set $VARNAME to */ | |
| pVar = testIterData(pIter); | |
| Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0); | |
| rc = Tcl_EvalObjEx(interp, pScript, 0); | |
| if( rc!=TCL_OK && rc!=TCL_CONTINUE ){ | |
| sqlite3changeset_finalize(pIter); | |
| return rc==TCL_BREAK ? TCL_OK : rc; | |
| } | |
| } | |
| if( isCheckNext ){ | |
| int rc2 = sqlite3changeset_next(pIter); | |
| rc = sqlite3changeset_finalize(pIter); | |
| assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc ); | |
| }else{ | |
| rc = sqlite3changeset_finalize(pIter); | |
| } | |
| if( rc!=SQLITE_OK ){ | |
| return test_session_error(interp, rc, 0); | |
| } | |
| return TCL_OK; | |
| } | |
| /* | |
| ** tclcmd: CMD configure REBASE-BLOB | |
| ** tclcmd: CMD rebase CHANGESET | |
| ** tclcmd: CMD delete | |
| */ | |
| static int SQLITE_TCLAPI test_rebaser_cmd( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| static struct RebaseSubcmd { | |
| const char *zSub; | |
| int nArg; | |
| const char *zMsg; | |
| int iSub; | |
| } aSub[] = { | |
| { "configure", 1, "REBASE-BLOB" }, /* 0 */ | |
| { "delete", 0, "" }, /* 1 */ | |
| { "rebase", 1, "CHANGESET" }, /* 2 */ | |
| { 0 } | |
| }; | |
| sqlite3_rebaser *p = (sqlite3_rebaser*)clientData; | |
| int iSub; | |
| int rc; | |
| if( objc<2 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); | |
| return TCL_ERROR; | |
| } | |
| rc = Tcl_GetIndexFromObjStruct(interp, | |
| objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub | |
| ); | |
| if( rc!=TCL_OK ) return rc; | |
| if( objc!=2+aSub[iSub].nArg ){ | |
| Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg); | |
| return TCL_ERROR; | |
| } | |
| assert( iSub==0 || iSub==1 || iSub==2 ); | |
| assert( rc==SQLITE_OK ); | |
| switch( iSub ){ | |
| case 0: { /* configure */ | |
| Tcl_Size nRebase = 0; | |
| unsigned char *pRebase = Tcl_GetByteArrayFromObj(objv[2], &nRebase); | |
| rc = sqlite3rebaser_configure(p, (int)nRebase, pRebase); | |
| break; | |
| } | |
| case 1: /* delete */ | |
| Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); | |
| break; | |
| default: { /* rebase */ | |
| TestStreamInput sStr; /* Input stream */ | |
| TestSessionsBlob sOut; /* Output blob */ | |
| Tcl_Size nn; | |
| memset(&sStr, 0, sizeof(sStr)); | |
| memset(&sOut, 0, sizeof(sOut)); | |
| sStr.aData = Tcl_GetByteArrayFromObj(objv[2], &nn); | |
| sStr.nData = nn; | |
| sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); | |
| if( sStr.nStream ){ | |
| rc = sqlite3rebaser_rebase_strm(p, | |
| testStreamInput, (void*)&sStr, | |
| testStreamOutput, (void*)&sOut | |
| ); | |
| }else{ | |
| rc = sqlite3rebaser_rebase(p, sStr.nData, sStr.aData, &sOut.n, &sOut.p); | |
| } | |
| if( rc==SQLITE_OK ){ | |
| assert_changeset_is_ok(sOut.n, sOut.p); | |
| Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(sOut.p, sOut.n)); | |
| } | |
| sqlite3_free(sOut.p); | |
| break; | |
| } | |
| } | |
| if( rc!=SQLITE_OK ){ | |
| return test_session_error(interp, rc, 0); | |
| } | |
| return TCL_OK; | |
| } | |
| static void SQLITE_TCLAPI test_rebaser_del(void *clientData){ | |
| sqlite3_rebaser *p = (sqlite3_rebaser*)clientData; | |
| sqlite3rebaser_delete(p); | |
| } | |
| /* | |
| ** tclcmd: sqlite3rebaser_create NAME | |
| */ | |
| static int SQLITE_TCLAPI test_sqlite3rebaser_create( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| int rc; | |
| sqlite3_rebaser *pNew = 0; | |
| if( objc!=2 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "NAME"); | |
| return SQLITE_ERROR; | |
| } | |
| rc = sqlite3rebaser_create(&pNew); | |
| if( rc!=SQLITE_OK ){ | |
| return test_session_error(interp, rc, 0); | |
| } | |
| Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]), test_rebaser_cmd, | |
| (ClientData)pNew, test_rebaser_del | |
| ); | |
| Tcl_SetObjResult(interp, objv[1]); | |
| return TCL_OK; | |
| } | |
| /* | |
| ** Run some sanity checks on the changeset in nChangeset byte buffer | |
| ** pChangeset. If any fail, return a non-zero value and, optionally, | |
| ** set output variable (*pzErr) to point to a buffer containing an | |
| ** English language error message describing the problem. In this | |
| ** case it is the responsibility of the caller to free the buffer | |
| ** using sqlite3_free(). | |
| ** | |
| ** Or, if the changeset appears to be well-formed, this function | |
| ** returns SQLITE_OK and sets (*pzErr) to NULL. | |
| */ | |
| static int sqlite3_test_changeset( | |
| int nChangeset, | |
| void *pChangeset, | |
| char **pzErr | |
| ){ | |
| sqlite3_changeset_iter *pIter = 0; | |
| char *zErr = 0; | |
| int rc = SQLITE_OK; | |
| int bPatch = (nChangeset>0 && ((char*)pChangeset)[0]=='P'); | |
| rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); | |
| if( rc==SQLITE_OK ){ | |
| int rc2; | |
| while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){ | |
| unsigned char *aPk = 0; | |
| int nCol = 0; | |
| int op = 0; | |
| const char *zTab = 0; | |
| sqlite3changeset_pk(pIter, &aPk, &nCol); | |
| sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); | |
| if( op==SQLITE_UPDATE ){ | |
| int iCol; | |
| for(iCol=0; iCol<nCol; iCol++){ | |
| sqlite3_value *pNew = 0; | |
| sqlite3_value *pOld = 0; | |
| sqlite3changeset_new(pIter, iCol, &pNew); | |
| sqlite3changeset_old(pIter, iCol, &pOld); | |
| if( aPk[iCol] ){ | |
| if( pOld==0 ) rc = SQLITE_ERROR; | |
| }else if( bPatch ){ | |
| if( pOld ) rc = SQLITE_ERROR; | |
| }else{ | |
| if( (pOld==0)!=(pNew==0) ) rc = SQLITE_ERROR; | |
| } | |
| if( rc!=SQLITE_OK ){ | |
| zErr = sqlite3_mprintf( | |
| "unexpected SQLITE_UPDATE (bPatch=%d pk=%d pOld=%d pNew=%d)", | |
| bPatch, (int)aPk[iCol], pOld!=0, pNew!=0 | |
| ); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| rc2 = sqlite3changeset_finalize(pIter); | |
| if( rc==SQLITE_OK ){ | |
| rc = rc2; | |
| } | |
| } | |
| *pzErr = zErr; | |
| return rc; | |
| } | |
| /* | |
| ** test_changeset CHANGESET | |
| */ | |
| static int SQLITE_TCLAPI test_changeset( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| void *pChangeset = 0; /* Buffer containing changeset */ | |
| Tcl_Size nChangeset = 0; /* Size of buffer aChangeset in bytes */ | |
| int rc = SQLITE_OK; | |
| char *z = 0; | |
| if( objc!=2 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET"); | |
| return TCL_ERROR; | |
| } | |
| pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[1], &nChangeset); | |
| Tcl_ResetResult(interp); | |
| rc = sqlite3_test_changeset((int)nChangeset, pChangeset, &z); | |
| if( rc!=SQLITE_OK ){ | |
| char *zErr = sqlite3_mprintf("(%d) - \"%s\"", rc, z); | |
| Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1)); | |
| sqlite3_free(zErr); | |
| } | |
| sqlite3_free(z); | |
| return rc ? TCL_ERROR : TCL_OK; | |
| } | |
| /* | |
| ** tclcmd: sqlite3rebaser_configure OP VALUE | |
| */ | |
| static int SQLITE_TCLAPI test_sqlite3session_config( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| static struct ConfigOpt { | |
| const char *zSub; | |
| int op; | |
| } aSub[] = { | |
| { "strm_size", SQLITE_SESSION_CONFIG_STRMSIZE }, | |
| { "invalid", 0 }, | |
| { 0 } | |
| }; | |
| int rc; | |
| int iSub; | |
| int iVal; | |
| if( objc!=3 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "OP VALUE"); | |
| return SQLITE_ERROR; | |
| } | |
| rc = Tcl_GetIndexFromObjStruct(interp, | |
| objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub | |
| ); | |
| if( rc!=TCL_OK ) return rc; | |
| if( Tcl_GetIntFromObj(interp, objv[2], &iVal) ) return TCL_ERROR; | |
| rc = sqlite3session_config(aSub[iSub].op, (void*)&iVal); | |
| if( rc!=SQLITE_OK ){ | |
| return test_session_error(interp, rc, 0); | |
| } | |
| Tcl_SetObjResult(interp, Tcl_NewIntObj(iVal)); | |
| return TCL_OK; | |
| } | |
| typedef struct TestChangegroup TestChangegroup; | |
| struct TestChangegroup { | |
| sqlite3_changegroup *pGrp; | |
| }; | |
| typedef struct TestChangeIter TestChangeIter; | |
| struct TestChangeIter { | |
| sqlite3_changeset_iter *pIter; | |
| /* If this iter uses streaming. */ | |
| TestStreamInput in; | |
| }; | |
| /* | |
| ** Destructor for Tcl changegroup command object. | |
| */ | |
| static void test_changegroup_del(void *clientData){ | |
| TestChangegroup *pGrp = (TestChangegroup*)clientData; | |
| sqlite3changegroup_delete(pGrp->pGrp); | |
| ckfree(pGrp); | |
| } | |
| /* | |
| ** Tclcmd: $changegroup schema DB DBNAME | |
| ** Tclcmd: $changegroup add CHANGESET | |
| ** Tclcmd: $changegroup output | |
| ** Tclcmd: $changegroup delete | |
| */ | |
| static int SQLITE_TCLAPI test_changegroup_cmd( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| TestChangegroup *p = (TestChangegroup*)clientData; | |
| static struct ChangegroupCmd { | |
| const char *zSub; | |
| int nArg; | |
| const char *zMsg; | |
| int iSub; | |
| } aSub[] = { | |
| { "schema", 2, "DB DBNAME", }, /* 0 */ | |
| { "add", 1, "CHANGESET", }, /* 1 */ | |
| { "output", 0, "", }, /* 2 */ | |
| { "delete", 0, "", }, /* 3 */ | |
| { "add_change", 1, "ITERATOR", }, /* 4 */ | |
| { 0 } | |
| }; | |
| int rc = TCL_OK; | |
| int iSub = 0; | |
| if( objc<2 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); | |
| return TCL_ERROR; | |
| } | |
| rc = Tcl_GetIndexFromObjStruct(interp, | |
| objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub | |
| ); | |
| if( rc!=TCL_OK ) return rc; | |
| if( objc!=2+aSub[iSub].nArg ){ | |
| Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg); | |
| return TCL_ERROR; | |
| } | |
| switch( iSub ){ | |
| case 0: { /* schema */ | |
| sqlite3 *db = 0; | |
| const char *zDb = Tcl_GetString(objv[3]); | |
| if( dbHandleFromObj(interp, objv[2], &db) ){ | |
| return TCL_ERROR; | |
| } | |
| rc = sqlite3changegroup_schema(p->pGrp, db, zDb); | |
| if( rc!=SQLITE_OK ) rc = test_session_error(interp, rc, 0); | |
| break; | |
| }; | |
| case 1: { /* add */ | |
| Tcl_Size nByte = 0; | |
| const u8 *aByte = Tcl_GetByteArrayFromObj(objv[2], &nByte); | |
| rc = sqlite3changegroup_add(p->pGrp, (int)nByte, (void*)aByte); | |
| if( rc!=SQLITE_OK ) rc = test_session_error(interp, rc, 0); | |
| break; | |
| }; | |
| case 2: { /* output */ | |
| int nByte = 0; | |
| u8 *aByte = 0; | |
| rc = sqlite3changegroup_output(p->pGrp, &nByte, (void**)&aByte); | |
| if( rc!=SQLITE_OK ){ | |
| rc = test_session_error(interp, rc, 0); | |
| }else{ | |
| Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(aByte, nByte)); | |
| } | |
| sqlite3_free(aByte); | |
| break; | |
| }; | |
| case 4: { /* add_change */ | |
| Tcl_CmdInfo cmdInfo; /* Database Tcl command (objv[2]) info */ | |
| TestChangeIter *pIter = 0; | |
| const char *zIter = Tcl_GetString(objv[2]); | |
| if( 0==Tcl_GetCommandInfo(interp, zIter, &cmdInfo) ){ | |
| Tcl_AppendResult(interp, "no such iter: ", Tcl_GetString(objv[2]), NULL); | |
| return TCL_ERROR; | |
| } | |
| pIter = (struct TestChangeIter*)cmdInfo.objClientData; | |
| rc = sqlite3changegroup_add_change(p->pGrp, pIter->pIter); | |
| if( rc!=SQLITE_OK ){ | |
| rc = test_session_error(interp, rc, 0); | |
| } | |
| break; | |
| }; | |
| default: { /* delete */ | |
| assert( iSub==3 ); | |
| Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); | |
| break; | |
| } | |
| } | |
| return rc; | |
| } | |
| /* | |
| ** Tclcmd: sqlite3changegroup CMD | |
| */ | |
| static int SQLITE_TCLAPI test_sqlite3changegroup( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| int rc; /* sqlite3changegroup_new() return code */ | |
| TestChangegroup *p; /* New wrapper object */ | |
| if( objc!=2 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "CMD"); | |
| return TCL_ERROR; | |
| } | |
| p = (TestChangegroup*)ckalloc(sizeof(TestChangegroup)); | |
| memset(p, 0, sizeof(TestChangegroup)); | |
| rc = sqlite3changegroup_new(&p->pGrp); | |
| if( rc!=SQLITE_OK ){ | |
| ckfree((char*)p); | |
| return test_session_error(interp, rc, 0); | |
| } | |
| Tcl_CreateObjCommand( | |
| interp, Tcl_GetString(objv[1]), test_changegroup_cmd, (ClientData)p, | |
| test_changegroup_del | |
| ); | |
| Tcl_SetObjResult(interp, objv[1]); | |
| return TCL_OK; | |
| } | |
| extern const char *sqlite3ErrName(int); | |
| /* | |
| ** Destructor for Tcl iterator command object. | |
| */ | |
| static void test_iter_del(void *clientData){ | |
| TestChangeIter *p = (TestChangeIter*)clientData; | |
| sqlite3changeset_finalize(p->pIter); | |
| ckfree(p); | |
| } | |
| static int SQLITE_TCLAPI test_iter_cmd( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| static const char *aSub[] = { | |
| "next", /* 0 */ | |
| "data", /* 1 */ | |
| "finalize", /* 2 */ | |
| 0 | |
| }; | |
| int iSub = 0; | |
| TestChangeIter *p = (TestChangeIter*)clientData; | |
| int rc = SQLITE_OK; | |
| if( objc<2 ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "CMD"); | |
| return TCL_ERROR; | |
| } | |
| if( Tcl_GetIndexFromObj(interp, objv[1], aSub, "sub-command", 0, &iSub) ){ | |
| return TCL_ERROR; | |
| } | |
| switch( iSub ){ | |
| case 0: | |
| rc = sqlite3changeset_next(p->pIter); | |
| Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); | |
| break; | |
| case 1: | |
| Tcl_SetObjResult(interp, testIterData(p->pIter)); | |
| break; | |
| case 2: | |
| rc = sqlite3changeset_finalize(p->pIter); | |
| p->pIter = 0; | |
| Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); | |
| Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); | |
| break; | |
| default: | |
| assert( 0 ); | |
| break; | |
| } | |
| return TCL_OK; | |
| } | |
| /* | |
| ** Tclcmd: sqlite3changeset_start ?-invert? CHANGESET | |
| */ | |
| static int SQLITE_TCLAPI test_sqlite3changeset_start( | |
| void * clientData, | |
| Tcl_Interp *interp, | |
| int objc, | |
| Tcl_Obj *CONST objv[] | |
| ){ | |
| int isInvert = 0; | |
| void *pChangeset = 0; /* Buffer containing changeset */ | |
| Tcl_Size nChangeset = 0; /* Size of buffer aChangeset in bytes */ | |
| TestChangeIter *pNew = 0; | |
| sqlite3_changeset_iter *pIter = 0; | |
| int flags = 0; | |
| int rc = SQLITE_OK; | |
| int nAlloc = 0; /* Bytes of space to allocate */ | |
| static int iCmd = 1; | |
| char zCmd[64]; | |
| if( objc==3 ){ | |
| Tcl_Size n = 0; | |
| const char *z = Tcl_GetStringFromObj(objv[1], &n); | |
| isInvert = (n>=2 && sqlite3_strnicmp(z, "-invert", (int)n)==0); | |
| } | |
| if( objc!=2 && (objc!=3 || !isInvert) ){ | |
| Tcl_WrongNumArgs(interp, 1, objv, "?-invert? CHANGESET"); | |
| return TCL_ERROR; | |
| } | |
| pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[objc-1], &nChangeset); | |
| flags = isInvert ? SQLITE_CHANGESETSTART_INVERT : 0; | |
| nAlloc = sizeof(TestChangeIter); | |
| if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){ | |
| nAlloc += nChangeset; | |
| } | |
| pNew = (TestChangeIter*)ckalloc(nAlloc); | |
| memset(pNew, 0, nAlloc); | |
| if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){ | |
| pNew->in.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); | |
| pNew->in.nData = nChangeset; | |
| pNew->in.aData = (unsigned char*)&pNew[1]; | |
| memcpy(pNew->in.aData, pChangeset, nChangeset); | |
| } | |
| if( pNew->in.nStream ){ | |
| void *pCtx = (void*)&pNew->in; | |
| rc = sqlite3changeset_start_v2_strm(&pIter, testStreamInput, pCtx, flags); | |
| }else{ | |
| rc = sqlite3changeset_start_v2(&pIter, (int)nChangeset, pChangeset, flags); | |
| } | |
| if( rc!=SQLITE_OK ){ | |
| char *zErr = sqlite3_mprintf( | |
| "error in sqlite3changeset_start_v2() - %d", rc | |
| ); | |
| Tcl_AppendResult(interp, zErr, (char*)0); | |
| ckfree(pNew); | |
| return TCL_ERROR; | |
| } | |
| pNew->pIter = pIter; | |
| sprintf(zCmd, "csiter%d", iCmd++); | |
| Tcl_CreateObjCommand(interp, zCmd, test_iter_cmd, (void*)pNew, test_iter_del); | |
| Tcl_SetObjResult(interp, Tcl_NewStringObj(zCmd, -1)); | |
| return TCL_OK; | |
| } | |
| int TestSession_Init(Tcl_Interp *interp){ | |
| struct Cmd { | |
| const char *zCmd; | |
| Tcl_ObjCmdProc *xProc; | |
| } aCmd[] = { | |
| { "sqlite3session", test_sqlite3session }, | |
| { "sqlite3changegroup", test_sqlite3changegroup }, | |
| { "sqlite3changeset_start", test_sqlite3changeset_start }, | |
| { "sqlite3session_foreach", test_sqlite3session_foreach }, | |
| { "sqlite3changeset_invert", test_sqlite3changeset_invert }, | |
| { "sqlite3changeset_concat", test_sqlite3changeset_concat }, | |
| { "sqlite3changeset_apply", test_sqlite3changeset_apply }, | |
| { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 }, | |
| { "sqlite3changeset_apply_v3", test_sqlite3changeset_apply_v3 }, | |
| { "sqlite3changeset_apply_replace_all", | |
| test_sqlite3changeset_apply_replace_all }, | |
| { "sql_exec_changeset", test_sql_exec_changeset }, | |
| { "sqlite3rebaser_create", test_sqlite3rebaser_create }, | |
| { "sqlite3session_config", test_sqlite3session_config }, | |
| { "test_changeset", test_changeset }, | |
| }; | |
| int i; | |
| for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){ | |
| struct Cmd *p = &aCmd[i]; | |
| Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0); | |
| } | |
| return TCL_OK; | |
| } | |