mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
Add the xFilter callback to the sqlite3changeset_apply() function. This callback allows the application to accept or reject changes on a per-table basis when applying a changeset.
FossilOrigin-Name: 282474c42f24f0e66c69b576b72ef8ce764d49e2
This commit is contained in:
@ -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
|
||||
|
@ -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:
|
||||
**
|
||||
** <ul>
|
||||
** <li> The table has the same name as the name recorded in the
|
||||
@ -623,17 +633,17 @@ int sqlite3changeset_concat(
|
||||
** recorded in the changeset.
|
||||
** </ul>
|
||||
**
|
||||
** 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 */
|
||||
),
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user