| |
|
| | #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; |
| | unsigned char *aData; |
| | int nData; |
| | int iData; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | 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; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | int sql_exec_changeset( |
| | sqlite3 *db, |
| | const char *zSql, |
| | int *pnChangeset, |
| | void **ppChangeset |
| | ){ |
| | sqlite3_session *pSession = 0; |
| | int rc; |
| | int val = 1; |
| |
|
| | |
| | rc = sqlite3session_create(db, "main", &pSession); |
| | sqlite3session_object_config(pSession, SQLITE_SESSION_OBJCONFIG_ROWID, &val); |
| |
|
| | |
| | if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL); |
| |
|
| | |
| | if( rc==SQLITE_OK ) rc = sqlite3_exec(db, zSql, 0, 0, 0); |
| |
|
| | |
| | if( rc==SQLITE_OK ){ |
| | rc = sqlite3session_changeset(pSession, pnChangeset, ppChangeset); |
| | } |
| |
|
| | |
| | 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 |
| |
|
| | |
| | |
| | |
| | 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; |
| | } |
| |
|
| |
|
| |
|
| | #define SESSION_STREAM_TCL_VAR "sqlite3session_streams" |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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 ){ |
| | |
| | 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; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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", }, |
| | { "changeset", 0, "", }, |
| | { "delete", 0, "", }, |
| | { "enable", 1, "BOOL", }, |
| | { "indirect", 1, "BOOL", }, |
| | { "isempty", 0, "", }, |
| | { "table_filter", 1, "SCRIPT", }, |
| | { "patchset", 0, "", }, |
| | { "diff", 2, "FROMDB TBL", }, |
| | { "memory_used", 0, "", }, |
| | { "changeset_size", 0, "", }, |
| | { "object_config", 2, "OPTION INTEGER", }, |
| | { 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: { |
| | 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: |
| | case 1: { |
| | 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: |
| | Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); |
| | break; |
| |
|
| | case 3: { |
| | 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: { |
| | 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: { |
| | int val; |
| | val = sqlite3session_isempty(pSession); |
| | Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); |
| | break; |
| | } |
| | |
| | case 6: { |
| | 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: { |
| | 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: { |
| | 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: { |
| | 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); |
| | } |
| |
|
| | |
| | |
| | |
| | static int SQLITE_TCLAPI test_sqlite3session( |
| | void * clientData, |
| | Tcl_Interp *interp, |
| | int objc, |
| | Tcl_Obj *CONST objv[] |
| | ){ |
| | sqlite3 *db; |
| | Tcl_CmdInfo info; |
| | int rc; |
| | TestSession *p; |
| | 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); |
| | } |
| |
|
| | |
| | |
| | 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; |
| | int nCol2; |
| | int op; |
| | const char *zTab; |
| | Tcl_Obj *pOld; |
| | Tcl_Obj *pNew; |
| | 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, |
| | const char *zTab |
| | ){ |
| | 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, |
| | 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, |
| | int eConf, |
| | sqlite3_changeset_iter *pIter |
| | ){ |
| | TestConflictHandler *p = (TestConflictHandler *)pCtx; |
| | Tcl_Obj *pEval; |
| | Tcl_Interp *interp = p->interp; |
| | int ret = 0; |
| |
|
| | int op; |
| | const char *zTab; |
| | int nCol; |
| |
|
| | 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{ |
| |
|
| | |
| | Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj( |
| | op==SQLITE_INSERT ? "INSERT" : |
| | op==SQLITE_UPDATE ? "UPDATE" : |
| | "DELETE", -1 |
| | )); |
| | |
| | |
| | Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1)); |
| | |
| | |
| | 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( 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( 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( 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); |
| | } |
| |
|
| | |
| | |
| | |
| | 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 ){ |
| | |
| | |
| | int nDummy; |
| | int rc = sqlite3changeset_fk_conflicts(pIter, &nDummy); |
| | assert( rc==SQLITE_MISUSE ); |
| | } |
| | |
| | |
| | } |
| |
|
| | 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; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | static int replace_handler( |
| | void *pCtx, |
| | int eConf, |
| | sqlite3_changeset_iter *pIter |
| | ){ |
| | int op; |
| | const char *zTab; |
| | int nCol; |
| | 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, |
| | void *pData, |
| | int *pnData |
| | ){ |
| | TestStreamInput *p = (TestStreamInput*)pCtx; |
| | int nReq = *pnData; |
| | int nRem = p->nData - p->iData; |
| | int nRet = p->nStream; |
| |
|
| | |
| | |
| | |
| | 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; |
| | Tcl_CmdInfo info; |
| | int rc; |
| | void *pChangeset; |
| | Tcl_Size nChangeset; |
| | TestConflictHandler ctx; |
| | TestStreamInput sStr; |
| | void *pRebase = 0; |
| | int nRebase = 0; |
| | int flags = 0; |
| |
|
| | assert( iVersion==1 || iVersion==2 || iVersion==3 ); |
| |
|
| | memset(&sStr, 0, sizeof(sStr)); |
| | sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); |
| |
|
| | |
| | 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; |
| | } |
| |
|
| | |
| | |
| | |
| | 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); |
| | } |
| | |
| | |
| | |
| | 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); |
| | } |
| | |
| | |
| | |
| | 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); |
| | } |
| |
|
| | |
| | |
| | |
| | static int SQLITE_TCLAPI test_sqlite3changeset_apply_replace_all( |
| | void * clientData, |
| | Tcl_Interp *interp, |
| | int objc, |
| | Tcl_Obj *CONST objv[] |
| | ){ |
| | sqlite3 *db; |
| | Tcl_CmdInfo info; |
| | int rc; |
| | void *pChangeset; |
| | Tcl_Size nChangeset; |
| |
|
| | 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; |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| | static int SQLITE_TCLAPI test_sqlite3changeset_invert( |
| | void * clientData, |
| | Tcl_Interp *interp, |
| | int objc, |
| | Tcl_Obj *CONST objv[] |
| | ){ |
| | int rc; |
| | Tcl_Size nn; |
| | TestStreamInput sIn; |
| | TestSessionsBlob sOut; |
| |
|
| | 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; |
| | } |
| |
|
| | |
| | |
| | |
| | static int SQLITE_TCLAPI test_sqlite3changeset_concat( |
| | void * clientData, |
| | Tcl_Interp *interp, |
| | int objc, |
| | Tcl_Obj *CONST objv[] |
| | ){ |
| | int rc; |
| | Tcl_Size nn; |
| |
|
| | TestStreamInput sLeft; |
| | TestStreamInput sRight; |
| | TestSessionsBlob sOut = {0,0}; |
| |
|
| | 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 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; |
| | 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; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | 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" }, |
| | { "delete", 0, "" }, |
| | { "rebase", 1, "CHANGESET" }, |
| | { 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: { |
| | Tcl_Size nRebase = 0; |
| | unsigned char *pRebase = Tcl_GetByteArrayFromObj(objv[2], &nRebase); |
| | rc = sqlite3rebaser_configure(p, (int)nRebase, pRebase); |
| | break; |
| | } |
| |
|
| | case 1: |
| | Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); |
| | break; |
| |
|
| | default: { |
| | TestStreamInput sStr; |
| | TestSessionsBlob sOut; |
| | 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); |
| | } |
| |
|
| | |
| | |
| | |
| | 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; |
| | } |
| |
|
| | |
| | |
| | |
| | static int SQLITE_TCLAPI test_changeset( |
| | void * clientData, |
| | Tcl_Interp *interp, |
| | int objc, |
| | Tcl_Obj *CONST objv[] |
| | ){ |
| | void *pChangeset = 0; |
| | Tcl_Size nChangeset = 0; |
| | 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; |
| | } |
| |
|
| | |
| | |
| | |
| | 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; |
| |
|
| | |
| | TestStreamInput in; |
| | }; |
| |
|
| |
|
| | |
| | |
| | |
| | static void test_changegroup_del(void *clientData){ |
| | TestChangegroup *pGrp = (TestChangegroup*)clientData; |
| | sqlite3changegroup_delete(pGrp->pGrp); |
| | ckfree(pGrp); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | 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", }, |
| | { "add", 1, "CHANGESET", }, |
| | { "output", 0, "", }, |
| | { "delete", 0, "", }, |
| | { "add_change", 1, "ITERATOR", }, |
| | { 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: { |
| | 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: { |
| | 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: { |
| | 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: { |
| | Tcl_CmdInfo cmdInfo; |
| | 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: { |
| | assert( iSub==3 ); |
| | Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); |
| | break; |
| | } |
| | } |
| |
|
| | return rc; |
| | } |
| |
|
| | |
| | |
| | |
| | static int SQLITE_TCLAPI test_sqlite3changegroup( |
| | void * clientData, |
| | Tcl_Interp *interp, |
| | int objc, |
| | Tcl_Obj *CONST objv[] |
| | ){ |
| | int rc; |
| | TestChangegroup *p; |
| |
|
| | 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); |
| |
|
| | |
| | |
| | |
| | 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", |
| | "data", |
| | "finalize", |
| | 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; |
| | } |
| |
|
| | |
| | |
| | |
| | static int SQLITE_TCLAPI test_sqlite3changeset_start( |
| | void * clientData, |
| | Tcl_Interp *interp, |
| | int objc, |
| | Tcl_Obj *CONST objv[] |
| | ){ |
| | int isInvert = 0; |
| | void *pChangeset = 0; |
| | Tcl_Size nChangeset = 0; |
| | TestChangeIter *pNew = 0; |
| | sqlite3_changeset_iter *pIter = 0; |
| | int flags = 0; |
| | int rc = SQLITE_OK; |
| | int nAlloc = 0; |
| |
|
| | 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; |
| | } |
| |
|
| | #endif |
| |
|