Index: third_party/sqlite/src/ext/session/test_session.c |
diff --git a/third_party/sqlite/src/ext/session/test_session.c b/third_party/sqlite/src/ext/session/test_session.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..411354cc02d41415bc18f8b6924d8d413cea6799 |
--- /dev/null |
+++ b/third_party/sqlite/src/ext/session/test_session.c |
@@ -0,0 +1,1046 @@ |
+ |
+#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 |
+ |
+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; |
+} |
+/************************************************************************/ |
+ |
+/* |
+** 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; |
+ } |
+ |
+ 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; |
+ pObj = Tcl_ObjGetVar2(interp, Tcl_NewStringObj(zVar, -1), 0, TCL_GLOBAL_ONLY); |
+ 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; |
+ 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 */ |
+ { 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 ){ |
+ 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; |
+ } |
+ } |
+ |
+ 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 */ |
+ |
+ 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); |
+ } |
+ |
+ 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; |
+} |
+ |
+ |
+/* |
+** 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[] |
+){ |
+ 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; |
+ |
+ memset(&sStr, 0, sizeof(sStr)); |
+ sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); |
+ |
+ if( objc!=4 && objc!=5 ){ |
+ Tcl_WrongNumArgs(interp, 1, objv, |
+ "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?" |
+ ); |
+ 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); |
+ ctx.pConflictScript = objv[3]; |
+ ctx.pFilterScript = objc==5 ? objv[4] : 0; |
+ ctx.interp = interp; |
+ |
+ if( sStr.nStream==0 ){ |
+ rc = sqlite3changeset_apply(db, nChangeset, pChangeset, |
+ (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx |
+ ); |
+ }else{ |
+ sStr.aData = (unsigned char*)pChangeset; |
+ sStr.nData = nChangeset; |
+ rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr, |
+ (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx |
+ ); |
+ } |
+ |
+ if( rc!=SQLITE_OK ){ |
+ return test_session_error(interp, rc, 0); |
+ } |
+ Tcl_ResetResult(interp); |
+ return TCL_OK; |
+} |
+ |
+/* |
+** 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{ |
+ 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{ |
+ 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; |
+ |
+ TestStreamInput sStr; |
+ memset(&sStr, 0, sizeof(sStr)); |
+ |
+ if( objc>1 ){ |
+ 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); |
+ sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); |
+ 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; |
+} |
+ |
+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_replace_all", |
+ test_sqlite3changeset_apply_replace_all }, |
+ { "sql_exec_changeset", test_sql_exec_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 */ |