diff options
Diffstat (limited to 'ext/session/test_session.c')
-rw-r--r-- | ext/session/test_session.c | 1463 |
1 files changed, 1463 insertions, 0 deletions
diff --git a/ext/session/test_session.c b/ext/session/test_session.c new file mode 100644 index 0000000..c1feb78 --- /dev/null +++ b/ext/session/test_session.c @@ -0,0 +1,1463 @@ + +#if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \ + && defined(SQLITE_ENABLE_PREUPDATE_HOOK) + +#include "sqlite3session.h" +#include <assert.h> +#include <string.h> +#if defined(INCLUDE_SQLITE_TCL_H) +# include "sqlite_tcl.h" +#else +# include "tcl.h" +# ifndef SQLITE_TCLAPI +# define SQLITE_TCLAPI +# endif +#endif + +#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; + + /* Create a new session object */ + rc = sqlite3session_create(db, "main", &pSession); + + /* 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){ + int rc = 0; + char *z = 0; + rc = 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_size", 1, "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: { + int rc; + int iArg; + if( Tcl_GetIntFromObj(interp, objv[2], &iArg) ){ + return TCL_ERROR; + } + rc = sqlite3session_object_config( + pSession, SQLITE_SESSION_OBJCONFIG_SIZE, &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){ + int n; + int nObj; + char *zObj; + + n = (int)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; + int x = 0; + + 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); + x++; + } + } + + if( op!=SQLITE_DELETE ){ + for(i=0; i<nCol; i++){ + sqlite3_value *pVal; + sqlite3changeset_new(pIter, i, &pVal); + sqlite3_value_text16(pVal); + x++; + } + } + + 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 */ + int 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 flag */ + if( bV2 ){ + if( objc>1 ){ + const char *z1 = Tcl_GetString(objv[1]); + int n = strlen(z1); + if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){ + flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT; + objc--; + objv++; + } + } + if( objc>1 ){ + const char *z1 = Tcl_GetString(objv[1]); + int n = strlen(z1); + if( n>1 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){ + flags |= SQLITE_CHANGESETAPPLY_INVERT; + objc--; + objv++; + } + } + } + + if( objc!=4 && objc!=5 ){ + const char *zMsg; + if( bV2 ){ + zMsg = "?-nosavepoint? ?-inverse? " + "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, nChangeset, pChangeset, + (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx + ); + }else{ + rc = sqlite3changeset_apply_v2(db, nChangeset, pChangeset, + (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx, + &pRebase, &nRebase, flags + ); + } + }else{ + sStr.aData = (unsigned char*)pChangeset; + sStr.nData = 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 */ + int 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, 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() */ + 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], &sIn.nData); + + 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() */ + + 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], &sLeft.nData); + sRight.aData = Tcl_GetByteArrayFromObj(objv[2], &sRight.nData); + 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; + int 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 = 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, nChangeset, pChangeset, f); + }else{ + void *pCtx = (void*)&sStr; + sStr.aData = (unsigned char*)pChangeset; + sStr.nData = nChangeset; + rc = sqlite3changeset_start_v2_strm(&pIter, testStreamInput, pCtx, f); + } + }else{ + if( sStr.nStream==0 ){ + rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); + }else{ + sStr.aData = (unsigned char*)pChangeset; + sStr.nData = 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) ){ + 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 *pVar; /* Tcl value to set $VARNAME to */ + Tcl_Obj *pOld; /* Vector of old.* values */ + Tcl_Obj *pNew; /* Vector of new.* values */ + int bIndirect; + + char *zPK; + unsigned char *abPK; + int i; + + /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this + ** iterator. */ + int nDummy; + if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){ + sqlite3changeset_finalize(pIter); + return TCL_ERROR; + } + + 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); + + 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 */ + int nRebase = 0; + unsigned char *pRebase = Tcl_GetByteArrayFromObj(objv[2], &nRebase); + rc = sqlite3rebaser_configure(p, nRebase, pRebase); + break; + } + + case 1: /* delete */ + Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); + break; + + default: { /* rebase */ + TestStreamInput sStr; /* Input stream */ + TestSessionsBlob sOut; /* Output blob */ + + memset(&sStr, 0, sizeof(sStr)); + memset(&sOut, 0, sizeof(sOut)); + sStr.aData = Tcl_GetByteArrayFromObj(objv[2], &sStr.nData); + 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; +} + +/* +** +*/ +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 */ + int 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(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; +} + +int TestSession_Init(Tcl_Interp *interp){ + struct Cmd { + const char *zCmd; + Tcl_ObjCmdProc *xProc; + } aCmd[] = { + { "sqlite3session", test_sqlite3session }, + { "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 */ |