1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

Add new sessions function sqlite3changeset_apply_v3() and its streaming equivalent. This allows changesets to be filtered on a per-change basis, not just per-table.

FossilOrigin-Name: 10ebd7a119ef1985755ef143a941fbaed1b5ca1c8a71e011c8bbc70e383fd337
This commit is contained in:
dan
2025-07-14 14:51:43 +00:00
parent f8addcf937
commit 08f8111bd8
6 changed files with 451 additions and 129 deletions

88
ext/session/sessionI.test Normal file
View File

@ -0,0 +1,88 @@
# 2015 July 14
#
# 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.
#
#***********************************************************************
#
#
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 sessionI
forcedelete test.db2
sqlite3 db2 test.db2
do_test 1.0 {
do_common_sql {
CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
}
} {}
set C [changeset_from_sql {
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
INSERT INTO t1 VALUES(3, 'three');
INSERT INTO t1 VALUES(4, 'four');
INSERT INTO t1 VALUES(5, 'five');
INSERT INTO t1 VALUES(6, 'six');
}]
do_execsql_test 1.1 {
SELECT * FROM t1
} {
1 one 2 two 3 three 4 four 5 five 6 six
}
proc xFilter {data} {
foreach {op tname flag pk old new} $data {}
if {$op=="INSERT"} {
set ipk [lindex $new 1]
return [expr $ipk % 2]
}
return 1
}
proc xConflict {args} {
}
sqlite3changeset_apply_v3 db2 $C xConflict xFilter
do_execsql_test -db db2 1.2 {
SELECT * FROM t1
} {
1 one 3 three 5 five
}
do_execsql_test -db db2 1.3 {
DELETE FROM t1
}
sqlite3changeset_apply_v3 db2 $C xConflict
do_execsql_test -db db2 1.4 {
SELECT * FROM t1
} {
1 one 2 two 3 three 4 four 5 five 6 six
}
proc xFilter2 {data} {
return 0
}
do_execsql_test -db db2 1.5 {
DELETE FROM t1
}
sqlite3changeset_apply_v3 db2 $C xConflict xFilter2
do_execsql_test -db db2 1.6 {
SELECT * FROM t1
} { }
finish_test

View File

@ -5182,6 +5182,10 @@ static int sessionChangesetApply(
void *pCtx, /* Copy of sixth arg to _apply() */
const char *zTab /* Table name */
),
int(*xFilterIter)(
void *pCtx, /* Copy of sixth arg to _apply() */
sqlite3_changeset_iter *p
),
int(*xConflict)(
void *pCtx, /* Copy of fifth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
@ -5322,6 +5326,9 @@ static int sessionChangesetApply(
** next change. A log message has already been issued. */
if( schemaMismatch ) continue;
/* If this is a call to apply_v3(), invoke xFilterIter here. */
if( xFilterIter && 0==xFilterIter(pCtx, pIter) ) continue;
rc = sessionApplyOneWithRetry(db, pIter, &sApply, xConflict, pCtx);
}
@ -5389,6 +5396,87 @@ static int sessionChangesetApply(
return rc;
}
/*
** This function is called by all six sqlite3changeset_apply() variants:
**
** + sqlite3changeset_apply()
** + sqlite3changeset_apply_v2()
** + sqlite3changeset_apply_v3()
** + sqlite3changeset_apply_strm()
** + sqlite3changeset_apply_strm_v2()
** + sqlite3changeset_apply_strm_v3()
**
** Arguments passed to this function are as follows:
**
** db:
** Database handle to apply changeset to main database of.
**
** nChangeset/pChangeset:
** These are both passed zero for the streaming variants. For the normal
** apply() functions, these are passed the size of and the buffer containing
** the changeset, respectively.
**
** xInput/pIn:
** These are both passed zero for the normal variants. For the streaming
** apply() functions, these are passed the input callback and context
** pointer, respectively.
**
** xFilter:
** The filter function as passed to apply() or apply_v2() (to filter by
** table name), if any. This is always NULL for apply_v3() calls.
**
** xFilterIter:
** The filter function as passed to apply_v3(), if any.
**
** xConflict:
** The conflict handler callback (must not be NULL).
**
** pCtx:
** The context pointer passed to the xFilter and xConflict handler callbacks.
**
** ppRebase, pnRebase:
** Zero for apply(). The rebase changeset output pointers, if any, for
** apply_v2() and apply_v3().
**
** flags:
** Zero for apply(). The flags parameter for apply_v2() and apply_v3().
*/
static int sessionChangesetApplyV23(
sqlite3 *db, /* Apply change to "main" db of this handle */
int nChangeset, /* Size of changeset in bytes */
void *pChangeset, /* Changeset blob */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
void *pIn, /* First arg for xInput */
int(*xFilter)(
void *pCtx, /* Copy of sixth arg to _apply() */
const char *zTab /* Table name */
),
int(*xFilterIter)(
void *pCtx, /* Copy of sixth arg to _apply() */
sqlite3_changeset_iter *p /* Handle describing current change */
),
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
sqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx, /* First argument passed to xConflict */
void **ppRebase, int *pnRebase,
int flags
){
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
int rc = sessionChangesetStart(
&pIter, xInput, pIn, nChangeset, pChangeset, bInverse, 1
);
if( rc==SQLITE_OK ){
rc = sessionChangesetApply(db, pIter,
xFilter, xFilterIter, xConflict, pCtx, ppRebase, pnRebase, flags
);
}
return rc;
}
/*
** Apply the changeset passed via pChangeset/nChangeset to the main
** database attached to handle "db".
@ -5410,17 +5498,39 @@ int sqlite3changeset_apply_v2(
void **ppRebase, int *pnRebase,
int flags
){
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
int bInv = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset, bInv, 1);
if( rc==SQLITE_OK ){
rc = sessionChangesetApply(
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
return sessionChangesetApplyV23(db,
nChangeset, pChangeset, 0, 0,
xFilter, 0, xConflict, pCtx,
ppRebase, pnRebase, flags
);
}
}
return rc;
/*
** Apply the changeset passed via pChangeset/nChangeset to the main
** database attached to handle "db".
*/
int sqlite3changeset_apply_v3(
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() */
sqlite3_changeset_iter *p /* Handle describing current change */
),
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
sqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx, /* First argument passed to xConflict */
void **ppRebase, int *pnRebase,
int flags
){
return sessionChangesetApplyV23(db,
nChangeset, pChangeset, 0, 0,
0, xFilter, xConflict, pCtx,
ppRebase, pnRebase, flags
);
}
/*
@ -5443,8 +5553,10 @@ int sqlite3changeset_apply(
),
void *pCtx /* First argument passed to xConflict */
){
return sqlite3changeset_apply_v2(
db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0, 0
return sessionChangesetApplyV23(db,
nChangeset, pChangeset, 0, 0,
xFilter, 0, xConflict, pCtx,
0, 0, 0
);
}
@ -5453,6 +5565,29 @@ int sqlite3changeset_apply(
** attached to handle "db". Invoke the supplied conflict handler callback
** to resolve any conflicts encountered while applying the change.
*/
int sqlite3changeset_apply_v3_strm(
sqlite3 *db, /* Apply change to "main" db of this handle */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
void *pIn, /* First arg for xInput */
int(*xFilter)(
void *pCtx, /* Copy of sixth arg to _apply() */
sqlite3_changeset_iter *p
),
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
sqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx, /* First argument passed to xConflict */
void **ppRebase, int *pnRebase,
int flags
){
return sessionChangesetApplyV23(db,
0, 0, xInput, pIn,
0, xFilter, xConflict, pCtx,
ppRebase, pnRebase, flags
);
}
int sqlite3changeset_apply_v2_strm(
sqlite3 *db, /* Apply change to "main" db of this handle */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
@ -5470,15 +5605,11 @@ int sqlite3changeset_apply_v2_strm(
void **ppRebase, int *pnRebase,
int flags
){
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse, 1);
if( rc==SQLITE_OK ){
rc = sessionChangesetApply(
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
return sessionChangesetApplyV23(db,
0, 0, xInput, pIn,
xFilter, 0, xConflict, pCtx,
ppRebase, pnRebase, flags
);
}
return rc;
}
int sqlite3changeset_apply_strm(
sqlite3 *db, /* Apply change to "main" db of this handle */
@ -5495,8 +5626,10 @@ int sqlite3changeset_apply_strm(
),
void *pCtx /* First argument passed to xConflict */
){
return sqlite3changeset_apply_v2_strm(
db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0, 0
return sessionChangesetApplyV23(db,
0, 0, xInput, pIn,
xFilter, 0, xConflict, pCtx,
0, 0, 0
);
}

View File

@ -1115,13 +1115,22 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** the changeset passed via the second and third arguments.
**
** The fourth argument (xFilter) passed to these functions 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 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
** is NULL, all changes related to the table are attempted.
** callback". This may be passed NULL, in which case all changes in the
** changeset are applied to the database. For sqlite3changeset_apply() and
** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once
** for each table affected by at least one change in the changeset. In this
** case the table name is passed as the second argument, and a copy of
** the context pointer passed as the sixth argument to apply() or apply_v2()
** 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, all changes related to the table are attempted.
**
** For sqlite3_changeset_apply_v3(), the xFilter callback is invoked once
** per change. The second argument in this case is an sqlite3_changeset_iter
** that may be queried using the usual APIs for the details of the current
** change. If the "filter callback" returns zero in this case, then no attempt
** is made to apply the current change. If it returns non-zero, the change
** is applied.
**
** 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
@ -1142,11 +1151,11 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** one such warning is issued for each table in the changeset.
**
** 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.
** to modify the table contents according to each UPDATE, INSERT or DELETE
** change that is not excluded by a filter callback. 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.
**
** Unlike the xFilter argument, xConflict may not be passed NULL. The results
** of passing anything other than a valid function pointer as the xConflict
@ -1297,6 +1306,23 @@ int sqlite3changeset_apply_v2(
void **ppRebase, int *pnRebase, /* OUT: Rebase data */
int flags /* SESSION_CHANGESETAPPLY_* flags */
);
int sqlite3changeset_apply_v3(
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() */
sqlite3_changeset_iter *p /* Handle describing change */
),
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
sqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx, /* First argument passed to xConflict */
void **ppRebase, int *pnRebase, /* OUT: Rebase data */
int flags /* SESSION_CHANGESETAPPLY_* flags */
);
/*
** CAPI3REF: Flags for sqlite3changeset_apply_v2
@ -1716,6 +1742,23 @@ int sqlite3changeset_apply_v2_strm(
void **ppRebase, int *pnRebase,
int flags
);
int sqlite3changeset_apply_v3_strm(
sqlite3 *db, /* Apply change to "main" db of this handle */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
void *pIn, /* First arg for xInput */
int(*xFilter)(
void *pCtx, /* Copy of sixth arg to _apply() */
sqlite3_changeset_iter *p
),
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
sqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx, /* First argument passed to xConflict */
void **ppRebase, int *pnRebase,
int flags
);
int sqlite3changeset_concat_strm(
int (*xInputA)(void *pIn, void *pData, int *pnData),
void *pInA,

View File

@ -520,6 +520,65 @@ static int test_obj_eq_string(Tcl_Obj *p, const char *z){
return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
}
static Tcl_Obj *testIterData(sqlite3_changeset_iter *pIter){
Tcl_Obj *pVar = 0;
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 *pOld; /* Vector of old.* values */
Tcl_Obj *pNew; /* Vector of new.* values */
int bIndirect;
char *zPK;
unsigned char *abPK;
int i;
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);
return pVar;
}
static int test_filter_handler(
void *pCtx, /* Pointer to TestConflictHandler structure */
const char *zTab /* Table name */
@ -543,6 +602,29 @@ static int test_filter_handler(
return res;
}
static int test_filter_v3_handler(
void *pCtx, /* Pointer to TestConflictHandler structure */
sqlite3_changeset_iter *pIter
){
TestConflictHandler *p = (TestConflictHandler *)pCtx;
int res = 1;
Tcl_Obj *pEval = 0;
Tcl_Interp *interp = p->interp;
pEval = Tcl_DuplicateObj(p->pFilterScript);
Tcl_IncrRefCount(pEval);
Tcl_ListObjAppendElement(0, pEval, testIterData(pIter));
if( 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 */
@ -776,7 +858,7 @@ static int testStreamInput(
static int SQLITE_TCLAPI testSqlite3changesetApply(
int bV2,
int iVersion,
void * clientData,
Tcl_Interp *interp,
int objc,
@ -793,11 +875,13 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
int nRebase = 0;
int flags = 0; /* Flags for apply_v2() */
assert( iVersion==1 || iVersion==2 || iVersion==3 );
memset(&sStr, 0, sizeof(sStr));
sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
/* Check for the -nosavepoint, -invert or -ignorenoop switches */
if( bV2 ){
if( iVersion==2 || iVersion==3 ){
while( objc>1 ){
const char *z1 = Tcl_GetString(objv[1]);
int n = (int)strlen(z1);
@ -822,7 +906,7 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
if( objc!=4 && objc!=5 ){
const char *zMsg;
if( bV2 ){
if( iVersion==2 || iVersion==3 ){
zMsg = "?-nosavepoint? ?-inverse? ?-ignorenoop? "
"DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
}else{
@ -842,30 +926,49 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
ctx.interp = interp;
if( sStr.nStream==0 ){
if( bV2==0 ){
switch( iVersion ){
case 1:
rc = sqlite3changeset_apply(db, (int)nChangeset, pChangeset,
(objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx
(objc==5)?test_filter_handler:0, test_conflict_handler, (void*)&ctx
);
}else{
break;
case 2:
rc = sqlite3changeset_apply_v2(db, (int)nChangeset, pChangeset,
(objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx,
(objc==5)?test_filter_handler:0, test_conflict_handler, (void*)&ctx,
&pRebase, &nRebase, flags
);
break;
case 3:
rc = sqlite3changeset_apply_v3(db, (int)nChangeset, pChangeset,
(objc==5)?test_filter_v3_handler:0, test_conflict_handler,
(void*)&ctx, &pRebase, &nRebase, flags
);
break;
}
}else{
sStr.aData = (unsigned char*)pChangeset;
sStr.nData = (int)nChangeset;
if( bV2==0 ){
switch( iVersion ){
case 1:
rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
(objc==5) ? test_filter_handler : 0,
test_conflict_handler, (void *)&ctx
);
}else{
break;
case 2:
rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr,
(objc==5) ? test_filter_handler : 0,
test_conflict_handler, (void *)&ctx,
&pRebase, &nRebase, flags
);
break;
case 3:
rc = sqlite3changeset_apply_v3_strm(db, testStreamInput, (void*)&sStr,
(objc==5) ? test_filter_v3_handler : 0,
test_conflict_handler, (void *)&ctx,
&pRebase, &nRebase, flags
);
break;
}
}
@ -873,7 +976,7 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
return test_session_error(interp, rc, 0);
}else{
Tcl_ResetResult(interp);
if( bV2 && pRebase ){
if( (iVersion==2 || iVersion==3) && pRebase ){
Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase));
}
}
@ -890,7 +993,7 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply(
int objc,
Tcl_Obj *CONST objv[]
){
return testSqlite3changesetApply(0, clientData, interp, objc, objv);
return testSqlite3changesetApply(1, clientData, interp, objc, objv);
}
/*
** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
@ -901,7 +1004,18 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2(
int objc,
Tcl_Obj *CONST objv[]
){
return testSqlite3changesetApply(1, clientData, interp, objc, objv);
return testSqlite3changesetApply(2, clientData, interp, objc, objv);
}
/*
** sqlite3changeset_apply_v3 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
*/
static int SQLITE_TCLAPI test_sqlite3changeset_apply_v3(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
return testSqlite3changesetApply(3, clientData, interp, objc, objv);
}
/*
@ -1034,64 +1148,6 @@ static int SQLITE_TCLAPI test_sqlite3changeset_concat(
return rc;
}
static Tcl_Obj *testIterData(sqlite3_changeset_iter *pIter){
Tcl_Obj *pVar = 0;
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 *pOld; /* Vector of old.* values */
Tcl_Obj *pNew; /* Vector of new.* values */
int bIndirect;
char *zPK;
unsigned char *abPK;
int i;
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);
return pVar;
}
/*
** sqlite3session_foreach VARNAME CHANGESET SCRIPT
*/
@ -1751,6 +1807,7 @@ int TestSession_Init(Tcl_Interp *interp){
{ "sqlite3changeset_concat", test_sqlite3changeset_concat },
{ "sqlite3changeset_apply", test_sqlite3changeset_apply },
{ "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 },
{ "sqlite3changeset_apply_v3", test_sqlite3changeset_apply_v3 },
{ "sqlite3changeset_apply_replace_all",
test_sqlite3changeset_apply_replace_all },
{ "sql_exec_changeset", test_sql_exec_changeset },

View File

@ -1,5 +1,5 @@
C Fix\sharmless\scompiler\swarnings\sin\sbuilds\sthat\suse\sSQLITE_OMIT_DATETIME_FUNCS.
D 2025-07-14T09:41:59.400
C Add\snew\ssessions\sfunction\ssqlite3changeset_apply_v3()\sand\sits\sstreaming\sequivalent.\sThis\sallows\schangesets\sto\sbe\sfiltered\son\sa\sper-change\sbasis,\snot\sjust\sper-table.
D 2025-07-14T14:51:43.802
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@ -594,6 +594,7 @@ F ext/session/sessionE.test b2010949c9d7415306f64e3c2072ddabc4b8250c98478d3c0c4d
F ext/session/sessionF.test d37ed800881e742c208df443537bf29aa49fd56eac520d0f0c6df3e6320f3401
F ext/session/sessionG.test 3efe388282d641b65485b5462e67851002cd91a282dc95b685d085eb8efdad0a
F ext/session/sessionH.test 71bbff6b1abb2c4ac62b84dee53273c37e0b21e5fde3aed80929403e091ef859
F ext/session/sessionI.test 11e7b6729fc942982a5104a40132f70a2e964d64d60dc5809b8206465af74822
F ext/session/session_common.tcl a31f537a929a695a852d241c9434f2847cadf329856401921139fbb03a5a7697
F ext/session/session_gen.test 942a0002df10da53c45b40b581cc3ed25e7ff42bda1e7ba497273dc2887aa8e6
F ext/session/session_speed_test.c dcf0ef58d76b70c8fbd9eab3be77cf9deb8bc1638fed8be518b62d6cbdef88b3
@ -617,9 +618,9 @@ F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a
F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795
F ext/session/sessionstat1.test 5e718d5888c0c49bbb33a7a4f816366db85f59f6a4f97544a806421b85dc2dec
F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc
F ext/session/sqlite3session.c 6b0877fe1ab832aa4b85eaca72606dfd1630a1363a1be7af10ee1042a5ec719e
F ext/session/sqlite3session.h 9bb1a6687b467764b35178dc29bbd2c57ab8cd3acdc8a62f088c34ad17e4fe2b
F ext/session/test_session.c 2ddff73ea368d827028c32851b291416e1008845832feb27b751d15e57e13cc3
F ext/session/sqlite3session.c 19e14bcca2fbc63a8022ffd708ea6e6986c4003a1e9bbca9b2989fd230362e15
F ext/session/sqlite3session.h b81e8536ce4b83babafd700f4ff67017804b6c1d71df963b30d3972958e7f4a7
F ext/session/test_session.c 8766b5973a6323934cb51248f621c3dc87ad2a98f023c3cc280d79e7d78d36fb
F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c
F ext/wasm/GNUmakefile d62af1b0914eb2e03fa6e4e75e93acadc8f4faeb2d56335da25d61b9ea144c53
F ext/wasm/README-dist.txt f01081a850ce38a56706af6b481e3a7878e24e42b314cfcd4b129f0f8427066a
@ -2212,8 +2213,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350
F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P e5d079549594ca44852773b8919894866394e47ad725dadc7f65242413a219d3
R 07c15e107127475edb35cdd6de618874
U drh
Z cde724828852556a37035896eff32ef8
P e11fbf9fd630a7de2e0b0e4b67dded05b905b2a0ba04aa7e915ca9df2d9ebe21
R 082d403ad4bd97cbce0234772b2da39f
U dan
Z 455b5bdcd48088e3f2c57d9eeedfc2e8
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
e11fbf9fd630a7de2e0b0e4b67dded05b905b2a0ba04aa7e915ca9df2d9ebe21
10ebd7a119ef1985755ef143a941fbaed1b5ca1c8a71e011c8bbc70e383fd337