#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 *sqlite3TestErrorName(int); Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3TestErrorName(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 */ { 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; } } 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 *pScript; }; 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_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->pScript); Tcl_IncrRefCount(pEval); sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); /* 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