| |
| #if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \ |
| && defined(SQLITE_ENABLE_PREUPDATE_HOOK) |
| |
| #include "sqlite3session.h" |
| #include <assert.h> |
| #include <string.h> |
| #include "tclsqlite.h" |
| |
| #ifndef SQLITE_AMALGAMATION |
| typedef unsigned char u8; |
| #endif |
| |
| 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), 0); |
| 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; |
| } |
| /************************************************************************/ |
| |
| |
| #ifdef SQLITE_DEBUG |
| 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 ); |
| } |
| #else |
| # define assert_changeset_is_ok(n,p) |
| #endif |
| |
| /* |
| ** 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()", 0); |
| return TCL_ERROR; |
| } |
| |
| assert_changeset_is_ok(nChangeset, pChangeset); |
| Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChangeset, nChangeset)); |
| sqlite3_free(pChangeset); |
| return TCL_OK; |
| } |
| |
| |
| |
| #define SESSION_STREAM_TCL_VAR "sqlite3session_streams" |
| |
| /* |
| ** 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, 0); |
| 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]), 0); |
| 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 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_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 bV2, |
| 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() */ |
| |
| memset(&sStr, 0, sizeof(sStr)); |
| sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); |
| |
| /* Check for the -nosavepoint, -invert or -ignorenoop switches */ |
| if( bV2 ){ |
| 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( bV2 ){ |
| 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]), 0); |
| 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 ){ |
| if( bV2==0 ){ |
| rc = sqlite3changeset_apply(db, (int)nChangeset, pChangeset, |
| (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx |
| ); |
| }else{ |
| rc = sqlite3changeset_apply_v2(db, (int)nChangeset, pChangeset, |
| (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx, |
| &pRebase, &nRebase, flags |
| ); |
| } |
| }else{ |
| sStr.aData = (unsigned char*)pChangeset; |
| sStr.nData = (int)nChangeset; |
| if( bV2==0 ){ |
| rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr, |
| (objc==5) ? test_filter_handler : 0, |
| test_conflict_handler, (void *)&ctx |
| ); |
| }else{ |
| rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr, |
| (objc==5) ? test_filter_handler : 0, |
| test_conflict_handler, (void *)&ctx, |
| &pRebase, &nRebase, flags |
| ); |
| } |
| } |
| |
| if( rc!=SQLITE_OK ){ |
| return test_session_error(interp, rc, 0); |
| }else{ |
| Tcl_ResetResult(interp); |
| if( bV2 && 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(0, 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(1, 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]), 0); |
| 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; |
| } |
| |
| 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; |
| } |
| |
| /* |
| ** 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; |
| }; |
| |
| |
| /* |
| ** 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]), 0); |
| 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; |
| |
| 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; |
| } |
| |
| flags = isInvert ? SQLITE_CHANGESETSTART_INVERT : 0; |
| pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[objc-1], &nChangeset); |
| 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); |
| return TCL_ERROR; |
| } |
| |
| pNew = (TestChangeIter*)ckalloc(sizeof(TestChangeIter)); |
| 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_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; |
| } |
| |
| #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */ |