--- /dev/null
+# 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
+
+
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 */
};
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;
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.
*/
void sqlite3session_delete(sqlite3_session *pSession);
+
/*
** CAPI3REF: Enable Or Disable A Session Object
**
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
**
#include <string.h>
#include <tcl.h>
+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,
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;
if( rc!=SQLITE_OK ){
return test_session_error(interp, rc);
}
- }
break;
+ }
case 1: { /* changeset */
int nChange;
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);
}
/*
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");
}
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]);
-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
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
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
-831492dca8bcfb1a1f83a8bb15de9cc94f29f07e
\ No newline at end of file
+b7e4dd889d37c8f57c2d3c7900e802f644aac3ea
\ No newline at end of file