diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 50c436b8c4..78d2b6d86c 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -2756,6 +2756,10 @@ int sqlite3changeset_apply( sqlite3 *db, /* Apply change to "main" db of this handle */ int nChangeset, /* Size of changeset in bytes */ void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), int(*xConflict)( void *pCtx, /* Copy of fifth arg to _apply() */ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ @@ -2788,7 +2792,6 @@ int sqlite3changeset_apply( if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){ u8 *abPK; - schemaMismatch = 0; sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */ sqlite3_finalize(sApply.pDelete); sqlite3_finalize(sApply.pUpdate); @@ -2797,40 +2800,50 @@ int sqlite3changeset_apply( memset(&sApply, 0, sizeof(sApply)); sApply.db = db; - sqlite3changeset_pk(pIter, &abPK, 0); - rc = sessionTableInfo( - db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK - ); - if( rc!=SQLITE_OK ) break; - - if( sApply.nCol==0 ){ - schemaMismatch = 1; - sqlite3_log(SQLITE_SCHEMA, - "sqlite3changeset_apply(): no such table: %s", zTab + /* If an xFilter() callback was specified, invoke it now. If the + ** xFilter callback returns zero, skip this table. If it returns + ** non-zero, proceed. */ + schemaMismatch = (xFilter && (0==xFilter(pCtx, zNew))); + if( schemaMismatch ){ + zTab = sqlite3_mprintf("%s", zNew); + nTab = strlen(zTab); + sApply.azCol = (const char **)zTab; + }else{ + sqlite3changeset_pk(pIter, &abPK, 0); + rc = sessionTableInfo( + db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK ); + if( rc!=SQLITE_OK ) break; + + if( sApply.nCol==0 ){ + schemaMismatch = 1; + sqlite3_log(SQLITE_SCHEMA, + "sqlite3changeset_apply(): no such table: %s", zTab + ); + } + else if( sApply.nCol!=nCol ){ + schemaMismatch = 1; + sqlite3_log(SQLITE_SCHEMA, + "sqlite3changeset_apply(): table %s has %d columns, expected %d", + zTab, sApply.nCol, nCol + ); + } + else if( memcmp(sApply.abPK, abPK, nCol)!=0 ){ + schemaMismatch = 1; + sqlite3_log(SQLITE_SCHEMA, "sqlite3changeset_apply(): " + "primary key mismatch for table %s", zTab + ); + } + else if( + (rc = sessionSelectRow(db, zTab, &sApply)) + || (rc = sessionUpdateRow(db, zTab, &sApply)) + || (rc = sessionDeleteRow(db, zTab, &sApply)) + || (rc = sessionInsertRow(db, zTab, &sApply)) + ){ + break; + } + nTab = sqlite3Strlen30(zTab); } - else if( sApply.nCol!=nCol ){ - schemaMismatch = 1; - sqlite3_log(SQLITE_SCHEMA, - "sqlite3changeset_apply(): table %s has %d columns, expected %d", - zTab, sApply.nCol, nCol - ); - } - else if( memcmp(sApply.abPK, abPK, nCol)!=0 ){ - schemaMismatch = 1; - sqlite3_log(SQLITE_SCHEMA, - "sqlite3changeset_apply(): primary key mismatch for table %s", zTab - ); - } - else if( - (rc = sessionSelectRow(db, zTab, &sApply)) - || (rc = sessionUpdateRow(db, zTab, &sApply)) - || (rc = sessionDeleteRow(db, zTab, &sApply)) - || (rc = sessionInsertRow(db, zTab, &sApply)) - ){ - break; - } - nTab = sqlite3Strlen30(zTab); } /* If there is a schema mismatch on the current table, proceed to the diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index 38164401f4..d8db1d6137 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -610,9 +610,19 @@ int sqlite3changeset_concat( ** "main" database attached to handle db with the changes found in the ** changeset passed via the second and third arguments. ** -** For each change in the changeset, this function tests that the target -** database contains a compatible table. A table is considered compatible -** if all of the following are true: +** The fourth argument (xFilter) passed to this function is the "filter +** callback". If it is not NULL, then for each table affected by at least one +** change in the changeset, the filter callback is invoked with +** the table name as the second argument, and a copy of the context pointer +** passed as the sixth argument to this function as the first. If the "filter +** callback" returns zero, then no attempt is made to apply any changes to +** the table. Otherwise, if the return value is non-zero or the xFilter +** argument to this function is NULL, all changes related to the table are +** attempted. +** +** For each table that is not excluded by the filter callback, this function +** tests that the target database contains a compatible table. A table is +** considered compatible if all of the following are true: ** ** ** -** If there is no compatible table, it is not an error, but the change is -** not applied. A warning message is issued via the sqlite3_log() mechanism -** with the error code SQLITE_SCHEMA. At most one such warning is issued for -** each table in the changeset. +** If there is no compatible table, it is not an error, but none of the +** changes associated with the table are applied. A warning message is issued +** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most +** one such warning is issued for each table in the changeset. ** -** Otherwise, if there is a compatible table, an attempt is made to modify -** the table contents according to the UPDATE, INSERT or DELETE change. -** If a change cannot be applied cleanly, the conflict handler function -** passed as the fourth argument to sqlite3changeset_apply() may be invoked. -** A description of exactly when the conflict handler is invoked for each -** type of change is below. +** For each change for which there is a compatible table, an attempt is made +** to modify the table contents according to the UPDATE, INSERT or DELETE +** change. If a change cannot be applied cleanly, the conflict handler +** function passed as the fifth argument to sqlite3changeset_apply() may be +** invoked. A description of exactly when the conflict handler is invoked for +** each type of change is below. ** ** Each time the conflict handler function is invoked, it must return one ** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or @@ -729,8 +739,12 @@ int sqlite3changeset_apply( sqlite3 *db, /* Apply change to "main" db of this handle */ int nChangeset, /* Size of changeset in bytes */ void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), int(*xConflict)( - void *pCtx, /* Copy of fifth arg to _apply() */ + void *pCtx, /* Copy of sixth arg to _apply() */ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ sqlite3_changeset_iter *p /* Handle describing change and conflict */ ), diff --git a/ext/session/test_session.c b/ext/session/test_session.c index 0993073328..9817b3c2f4 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -185,7 +185,8 @@ static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){ typedef struct TestConflictHandler TestConflictHandler; struct TestConflictHandler { Tcl_Interp *interp; - Tcl_Obj *pScript; + Tcl_Obj *pConflictScript; + Tcl_Obj *pFilterScript; }; static int test_obj_eq_string(Tcl_Obj *p, const char *z){ @@ -199,6 +200,29 @@ static int test_obj_eq_string(Tcl_Obj *p, const char *z){ 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 */ @@ -213,7 +237,7 @@ static int test_conflict_handler( const char *zTab; /* Name of table conflict is on */ int nCol; /* Number of columns in table zTab */ - pEval = Tcl_DuplicateObj(p->pScript); + pEval = Tcl_DuplicateObj(p->pConflictScript); Tcl_IncrRefCount(pEval); sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); @@ -342,7 +366,7 @@ static int test_conflict_handler( } /* -** sqlite3changeset_apply DB CHANGESET SCRIPT +** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? */ static int test_sqlite3changeset_apply( void * clientData, @@ -357,8 +381,10 @@ static int test_sqlite3changeset_apply( int nChangeset; /* Size of buffer aChangeset in bytes */ TestConflictHandler ctx; - if( objc!=4 ){ - Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET SCRIPT"); + 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) ){ @@ -367,11 +393,12 @@ static int test_sqlite3changeset_apply( } db = *(sqlite3 **)info.objClientData; pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset); - ctx.pScript = objv[3]; + ctx.pConflictScript = objv[3]; + ctx.pFilterScript = objc==5 ? objv[4] : 0; ctx.interp = interp; - rc = sqlite3changeset_apply( - db, nChangeset, pChangeset, test_conflict_handler, (void *)&ctx + rc = sqlite3changeset_apply(db, nChangeset, pChangeset, + (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx ); if( rc!=SQLITE_OK ){ return test_session_error(interp, rc); diff --git a/manifest b/manifest index 31ce25fa38..f77afa1ffe 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Modifications\sso\sthat\sthe\ssessions\sextension\sworks\swith\sblob\shandles. -D 2011-07-11T19:45:38.954 +C Add\sthe\sxFilter\scallback\sto\sthe\ssqlite3changeset_apply()\sfunction.\sThis\scallback\sallows\sthe\sapplication\sto\saccept\sor\sreject\schanges\son\sa\sper-table\sbasis\swhen\sapplying\sa\schangeset. +D 2011-07-13T15:21:02.741 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in c1d7a7f4fd8da6b1815032efca950e3d5125407e F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -110,9 +110,9 @@ F ext/session/session5.test 8fdfaf9dba28a2f1c6b89b06168bdab1fef2d478 F ext/session/session6.test 443789bc2fca12e4f7075cf692c60b8a2bea1a26 F ext/session/session_common.tcl 1539d8973b2aea0025c133eb0cc4c89fcef541a5 F ext/session/sessionfault.test 401045278298a242cbc2e4bc986c102f01ff2180 -F ext/session/sqlite3session.c 26de50c3e34d89ae62e97024ad07e772e1c52db2 -F ext/session/sqlite3session.h 665f5591562e3c71eb3d0da26f1a1efae26f7bcf -F ext/session/test_session.c 311e5b9228374d0b5780448f289847ff1cf7d388 +F ext/session/sqlite3session.c c05d28332fcdd66c82de85f5459d71554e2259d7 +F ext/session/sqlite3session.h f34905c818569779ddaea1bbef43469177614c69 +F ext/session/test_session.c 209f13fa8f4a597ffcc15fd0f8a3f27ed079c5e5 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F main.mk f56d9895882f5cdd9f9f9ba8f8a679e9202288c1 @@ -961,7 +961,7 @@ F tool/symbols.sh bc2a3709940d47c8ac8e0a1fdf17ec801f015a00 F tool/tostr.awk 11760e1b94a5d3dcd42378f3cc18544c06cfa576 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f F tool/warnings.sh 347d974d143cf132f953b565fbc03026f19fcb4d -P 840bf9c2d92192ee3cc2aa7c0e9bdb805a066fd4 -R c823c2479e73fc426d6fab6fb150638b +P 82ac16c4f873d3bd7c22f36ba7b974b4903a2d50 +R c22fa4ebad64b5fe1347a3f0a2dcb7e8 U dan -Z 6339a08530525dea9c6675e0fc9a27f5 +Z 6a509799e9e665bc4a33861bc6dba39f diff --git a/manifest.uuid b/manifest.uuid index fecbc08bba..527039d08d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -82ac16c4f873d3bd7c22f36ba7b974b4903a2d50 \ No newline at end of file +282474c42f24f0e66c69b576b72ef8ce764d49e2 \ No newline at end of file