From 7531a5a37840f18cdb39407c69bd200e0b349569 Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 23 Aug 2013 17:43:32 +0000 Subject: [PATCH] Add the sqlite3session_table_filter API to the sessions extension. FossilOrigin-Name: b7e4dd889d37c8f57c2d3c7900e802f644aac3ea --- ext/session/sessionA.test | 69 +++++++++++++++++++++++++++++++++ ext/session/sqlite3session.c | 25 ++++++++++++ ext/session/sqlite3session.h | 19 +++++++++ ext/session/test_session.c | 74 +++++++++++++++++++++++++++++------- manifest | 19 ++++----- manifest.uuid | 2 +- 6 files changed, 185 insertions(+), 23 deletions(-) create mode 100644 ext/session/sessionA.test diff --git a/ext/session/sessionA.test b/ext/session/sessionA.test new file mode 100644 index 0000000000..1ca0f13709 --- /dev/null +++ b/ext/session/sessionA.test @@ -0,0 +1,69 @@ +# 2013 July 04 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# This file tests that the sessions module handles foreign key constraint +# violations when applying changesets as required. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} +set testprefix sessionA + + +forcedelete test.db2 +sqlite3 db2 test.db2 +foreach {tn db} {1 db 2 db2} { + do_test 1.$tn.1 { + execsql { + CREATE TABLE t1(a PRIMARY KEY, b); + CREATE TABLE t2(a PRIMARY KEY, b); + CREATE TABLE t3(a PRIMARY KEY, b); + } $db + } {} +} + +proc tbl_filter {zTbl} { + return $::table_filter($zTbl) +} + +do_test 2.1 { + set ::table_filter(t1) 1 + set ::table_filter(t2) 0 + set ::table_filter(t3) 1 + + sqlite3session S db main + S table_filter tbl_filter + + execsql { + INSERT INTO t1 VALUES('a', 'b'); + INSERT INTO t2 VALUES('c', 'd'); + INSERT INTO t3 VALUES('e', 'f'); + } + + set changeset [S changeset] + S delete + sqlite3changeset_apply db2 $changeset xConflict + + execsql { + SELECT * FROM t1; + SELECT * FROM t2; + SELECT * FROM t3; + } db2 +} {a b e f} + + +finish_test + + diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 6fa8107408..801ea60717 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -23,6 +23,8 @@ struct sqlite3_session { int bIndirect; /* True if all changes are indirect */ int bAutoAttach; /* True to auto-attach tables */ int rc; /* Non-zero if an error has occurred */ + void *pFilterCtx; /* First argument to pass to xTableFilter */ + int (*xTableFilter)(void *pCtx, const char *zTab); sqlite3_session *pNext; /* Next session object on same db. */ SessionTable *pTable; /* List of attached tables */ }; @@ -1066,6 +1068,16 @@ static void xPreUpdate( if( !pTab ){ /* This branch is taken if table zName has not yet been attached to ** this session and the auto-attach flag is set. */ + + /* If there is a table-filter configured, invoke it. If it returns 0, + ** this change will not be recorded. Break out of the loop early in + ** this case. */ + if( pSession->xTableFilter + && pSession->xTableFilter(pSession->pFilterCtx, zName)==0 + ){ + break; + } + pSession->rc = sqlite3session_attach(pSession,zName); if( pSession->rc ) break; pTab = pSession->pTable; @@ -1170,6 +1182,19 @@ void sqlite3session_delete(sqlite3_session *pSession){ sqlite3_free(pSession); } +/* +** Set a table filter on a Session Object. +*/ +void sqlite3session_table_filter( + sqlite3_session *pSession, + int(*xFilter)(void*, const char*), + void *pCtx /* First argument passed to xFilter */ +){ + pSession->bAutoAttach = 1; + pSession->pFilterCtx = pCtx; + pSession->xTableFilter = xFilter; +} + /* ** Attach a table to a session. All subsequent changes made to the table ** while the session object is enabled will be recorded. diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index f1a7052bcd..974a770c7b 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -71,6 +71,7 @@ int sqlite3session_create( */ void sqlite3session_delete(sqlite3_session *pSession); + /* ** CAPI3REF: Enable Or Disable A Session Object ** @@ -152,6 +153,24 @@ int sqlite3session_attach( const char *zTab /* Table name */ ); +/* +** CAPI3REF: Set a table filter on a Session Object. +** +** The second argument (xFilter) is the "filter callback". For changes to rows +** in tables that are not attached to the Session oject, the filter is called +** to determine whether changes to the table's rows should be tracked or not. +** If xFilter returns 0, changes is not tracked. Note that once a table is +** attached, xFilter will not be called again. +*/ +void sqlite3session_table_filter( + sqlite3_session *pSession, /* Session object */ + int(*xFilter)( + void *pCtx, /* Copy of third arg to _filter_table() */ + const char *zTab /* Table name */ + ), + void *pCtx /* First argument passed to xFilter */ +); + /* ** CAPI3REF: Generate A Changeset From A Session Object ** diff --git a/ext/session/test_session.c b/ext/session/test_session.c index 49e45c5fb3..4340921a67 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -7,18 +7,50 @@ #include #include +typedef struct TestSession TestSession; +struct TestSession { + sqlite3_session *pSession; + Tcl_Interp *interp; + Tcl_Obj *pFilterScript; +}; + 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; } +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; +} + /* ** Tclcmd: $session attach TABLE ** $session changeset ** $session delete ** $session enable BOOL ** $session indirect INTEGER +** $session table_filter SCRIPT */ static int test_session_cmd( void *clientData, @@ -26,19 +58,21 @@ static int test_session_cmd( int objc, Tcl_Obj *CONST objv[] ){ - sqlite3_session *pSession = (sqlite3_session *)clientData; + 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 */ + { "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 */ { 0 } }; int iSub; @@ -65,8 +99,8 @@ static int test_session_cmd( if( rc!=SQLITE_OK ){ return test_session_error(interp, rc); } - } break; + } case 1: { /* changeset */ int nChange; @@ -107,14 +141,25 @@ static int test_session_cmd( 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; + } } return TCL_OK; } static void test_session_del(void *clientData){ - sqlite3_session *pSession = (sqlite3_session *)clientData; - sqlite3session_delete(pSession); + TestSession *p = (TestSession*)clientData; + if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript); + sqlite3session_delete(p->pSession); + ckfree(p); } /* @@ -129,7 +174,7 @@ static int test_sqlite3session( sqlite3 *db; Tcl_CmdInfo info; int rc; /* sqlite3session_create() return code */ - sqlite3_session *pSession; /* New session object */ + TestSession *p; /* New wrapper object */ if( objc!=4 ){ Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME"); @@ -142,13 +187,16 @@ static int test_sqlite3session( } db = *(sqlite3 **)info.objClientData; - rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &pSession); + 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(p); return test_session_error(interp, rc); } Tcl_CreateObjCommand( - interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)pSession, + interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)p, test_session_del ); Tcl_SetObjResult(interp, objv[1]); diff --git a/manifest b/manifest index eeb634ab67..11155359af 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\sin\sminor\sbug\sfixes\sand\sperformance\stweaks\sfrom\strunk\sleading\sup\sto\nthe\sversion\s3.8.0\srelease. -D 2013-08-22T15:07:08.266 +C Add\sthe\ssqlite3session_table_filter\sAPI\sto\sthe\ssessions\sextension. +D 2013-08-23T17:43:32.715 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in aff38bc64c582dd147f18739532198372587b0f0 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -144,11 +144,12 @@ F ext/session/session5.test 8fdfaf9dba28a2f1c6b89b06168bdab1fef2d478 F ext/session/session6.test 443789bc2fca12e4f7075cf692c60b8a2bea1a26 F ext/session/session8.test 7d35947ad329b8966f095d34f9617a9eff52dc65 F ext/session/session9.test 43acfdc57647c2ce4b36dbd0769112520ea6af14 +F ext/session/sessionA.test eb05c13e4ef1ca8046a3a6dbf2d5f6f5b04a11d4 F ext/session/session_common.tcl 1539d8973b2aea0025c133eb0cc4c89fcef541a5 F ext/session/sessionfault.test 496291b287ba3c0b14ca2e074425e29cc92a64a6 -F ext/session/sqlite3session.c e0345e8425a36fb8ac107175ebae46b4af8873e4 -F ext/session/sqlite3session.h c7db3d8515eba7f41eeb8698a25e58d24cd384bf -F ext/session/test_session.c 12053e9190653164fa624427cf90d1f46ca7f179 +F ext/session/sqlite3session.c 63eea3741e8ac1574d4c183fd92a6a50b1415357 +F ext/session/sqlite3session.h 6c35057241567ed6319f750ee504a81c459225e1 +F ext/session/test_session.c d38968307c05229cc8cd603722cf305d6f768832 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt f2b23a6bde8f1c6e86b957e4d94eab0add520b0d @@ -1119,7 +1120,7 @@ F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 F tool/wherecosttest.c f407dc4c79786982a475261866a161cd007947ae F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac -P 3e4033285deb417bd72c008917729dbf3bf4e90d 0775501acf152dcbf4dd039f4511f3d8c4330d85 -R f6e422c39c44105817eb40d9d54618de -U drh -Z f9597afebde4efa08f5ab31e24bede7f +P 831492dca8bcfb1a1f83a8bb15de9cc94f29f07e +R cbce615b433685cc6c1cb9210155f0f8 +U dan +Z 2d1cb3afe326cb66267c2981ea353dde diff --git a/manifest.uuid b/manifest.uuid index 8fadc4e3f6..023cc42f5d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -831492dca8bcfb1a1f83a8bb15de9cc94f29f07e \ No newline at end of file +b7e4dd889d37c8f57c2d3c7900e802f644aac3ea \ No newline at end of file