#if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \ && defined(SQLITE_ENABLE_PREUPDATE_HOOK) #include "sqlite3session.h" #include #include #include static int test_session_error(Tcl_Interp *interp, int rc){ extern const char *sqlite3ErrName(int); Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); return TCL_ERROR; } /* ** Tclcmd: $session attach TABLE ** $session changeset ** $session delete ** $session enable BOOL ** $session indirect INTEGER */ static int test_session_cmd( void *clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ sqlite3_session *pSession = (sqlite3_session *)clientData; 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 */ { 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); } } break; case 1: { /* changeset */ int nChange; void *pChange; rc = sqlite3session_changeset(pSession, &nChange, &pChange); if( rc==SQLITE_OK ){ Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChange, nChange)); sqlite3_free(pChange); }else{ return test_session_error(interp, rc); } 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; } } return TCL_OK; } static void test_session_del(void *clientData){ sqlite3_session *pSession = (sqlite3_session *)clientData; sqlite3session_delete(pSession); } /* ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME */ static int test_sqlite3session( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ sqlite3 *db; Tcl_CmdInfo info; int rc; /* sqlite3session_create() return code */ sqlite3_session *pSession; /* New session object */ 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; rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &pSession); if( rc!=SQLITE_OK ){ return test_session_error(interp, rc); } Tcl_CreateObjCommand( interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)pSession, 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: Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("t", 1)); pObj = Tcl_NewStringObj((char *)sqlite3_value_text(pVal), -1); break; case 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 = 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; i1 ){ char *zOpt = Tcl_GetString(objv[1]); isCheckNext = (strcmp(zOpt, "-next")==0); } if( objc!=4+isCheckNext ){ Tcl_WrongNumArgs(interp, 1, objv, "?-next? VARNAME CHANGESET SCRIPT"); return TCL_ERROR; } pVarname = objv[1+isCheckNext]; pCS = objv[2+isCheckNext]; pScript = objv[3+isCheckNext]; pChangeSet = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeSet); rc = sqlite3changeset_start(&pIter, nChangeSet, pChangeSet); if( rc!=SQLITE_OK ){ return test_session_error(interp, rc); } 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; 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