]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add new sessions function sqlite3changeset_apply_v3() and its streaming equivalent...
authordan <Dan Kennedy>
Mon, 14 Jul 2025 14:51:43 +0000 (14:51 +0000)
committerdan <Dan Kennedy>
Mon, 14 Jul 2025 14:51:43 +0000 (14:51 +0000)
FossilOrigin-Name: 10ebd7a119ef1985755ef143a941fbaed1b5ca1c8a71e011c8bbc70e383fd337

ext/session/sessionI.test [new file with mode: 0644]
ext/session/sqlite3session.c
ext/session/sqlite3session.h
ext/session/test_session.c
manifest
manifest.uuid

diff --git a/ext/session/sessionI.test b/ext/session/sessionI.test
new file mode 100644 (file)
index 0000000..2012f24
--- /dev/null
@@ -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
index 175cacbe86d1b2de5b8958657d8e4724992d82b5..df40fdc1cfeb8dae0a8a8c4b1ca2a8fbb8626332 100644 (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);
   }
 
@@ -5390,17 +5397,64 @@ static int sessionChangesetApply(
 }
 
 /*
-** Apply the changeset passed via pChangeset/nChangeset to the main 
-** database attached to handle "db".
+** 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().
 */
-int sqlite3changeset_apply_v2(
+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 */
@@ -5411,18 +5465,74 @@ int sqlite3changeset_apply_v2(
   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);
-
+  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, xConflict, pCtx, ppRebase, pnRebase, flags
+    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".
+*/
+int sqlite3changeset_apply_v2(
+  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 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, 
+      xFilter, 0, xConflict, pCtx, 
+      ppRebase, pnRebase, flags
+  );
+}
+
+/*
+** 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
+  );
+}
+
 /*
 ** Apply the changeset passed via pChangeset/nChangeset to the main database
 ** attached to handle "db". Invoke the supplied conflict handler callback
@@ -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 rc;
+  return sessionChangesetApplyV23(db, 
+      0, 0, xInput, pIn, 
+      xFilter, 0, xConflict, pCtx, 
+      ppRebase, pnRebase, flags
+  );
 }
 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
   );
 }
 
index 4dff5ce8749ff3420da57dd91507bc138f48c1ab..a3b6987b9c26c5fb6bb7d3f82363a30973ceda8c 100644 (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,
index f28604abc2bf7ae9c37f37df9630445410726a23..6ad5b37749280c940d5c859be4186f63c54e66f0 100644 (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 ){
-      rc = sqlite3changeset_apply(db, (int)nChangeset, pChangeset, 
-          (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx
-      );
-    }else{
-      rc = sqlite3changeset_apply_v2(db, (int)nChangeset, pChangeset, 
-          (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx,
-          &pRebase, &nRebase, flags
-      );
+    switch( iVersion ){
+      case 1:
+        rc = sqlite3changeset_apply(db, (int)nChangeset, pChangeset, 
+            (objc==5)?test_filter_handler:0, test_conflict_handler, (void*)&ctx
+        );
+        break;
+      case 2:
+        rc = sqlite3changeset_apply_v2(db, (int)nChangeset, pChangeset, 
+            (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 ){
-      rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
-          (objc==5) ? test_filter_handler : 0, 
-          test_conflict_handler, (void *)&ctx
-      );
-    }else{
-      rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr,
-          (objc==5) ? test_filter_handler : 0, 
-          test_conflict_handler, (void *)&ctx,
-          &pRebase, &nRebase, flags
-      );
+    switch( iVersion ){
+      case 1:
+        rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
+            (objc==5) ? test_filter_handler : 0, 
+            test_conflict_handler, (void *)&ctx
+        );
+        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 },
index 0ec1a470ab514b9e4ec3978a337f1ee8c4f578fe..9efc9ff73e6958c0039ee8f1324b6a26eedd856e 100644 (file)
--- a/manifest
+++ b/manifest
@@ -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.
index 31169f768ebe38a1ba4f32fc9262d0b37305325d..e0a53a1a22b89adb9e8db3203bae4eb569871994 100644 (file)
@@ -1 +1 @@
-e11fbf9fd630a7de2e0b0e4b67dded05b905b2a0ba04aa7e915ca9df2d9ebe21
+10ebd7a119ef1985755ef143a941fbaed1b5ca1c8a71e011c8bbc70e383fd337