]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the SQLITE_CHANGESETAPPLY_INVERT flag to sessions. For inverting and applying...
authordan <dan@noemail.net>
Thu, 18 Oct 2018 14:59:21 +0000 (14:59 +0000)
committerdan <dan@noemail.net>
Thu, 18 Oct 2018 14:59:21 +0000 (14:59 +0000)
FossilOrigin-Name: d4b6406e7f5ba06ac73ab9fdef57232b2459e0af12420ed946ebed6aef46f0b1

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

index 543b97032776431a4acc42b157effe8b5ae1f9a6..ceffdad4badbbc165d8c68a86d9ee994f3bd109f 100644 (file)
@@ -95,6 +95,23 @@ proc changeset_from_sql {sql {dbname main}} {
   return $changeset
 }
 
+proc patchset_from_sql {sql {dbname main}} {
+  set rc [catch {
+    sqlite3session S db $dbname
+    db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
+      S attach $name
+    }
+    db eval $sql
+    S patchset
+  } patchset]
+  catch { S delete }
+
+  if {$rc} {
+    error $patchset
+  }
+  return $patchset
+}
+
 proc do_then_apply_sql {sql {dbname main}} {
   proc xConflict args { return "OMIT" }
   set rc [catch {
diff --git a/ext/session/sessioninvert.test b/ext/session/sessioninvert.test
new file mode 100644 (file)
index 0000000..52260af
--- /dev/null
@@ -0,0 +1,120 @@
+# 2018 October 18
+#
+# 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 implements regression tests for SQLite library.
+#
+
+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 sessioninvert
+
+proc do_invert_test {tn sql} {
+
+  forcecopy test.db test.db2
+  sqlite3 db2 test.db2
+
+  set C [changeset_from_sql $sql]
+
+  forcecopy test.db test.db3
+  sqlite3 db3 test.db3
+  uplevel [list do_test $tn.1 [list compare_db db db3] {}]
+
+  set I [sqlite3changeset_invert $C]
+  sqlite3changeset_apply db $I {}
+  uplevel [list do_test $tn.2 [list compare_db db db2] {}]
+  
+  sqlite3changeset_apply_v2 -invert db3 $C {}
+  uplevel [list do_test $tn.3 [list compare_db db db3] {}]
+
+  catch { db2 close }
+  catch { db3 close }
+}
+
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a PRIMARY KEY, b, c);
+  CREATE TABLE t2(d, e, f, PRIMARY KEY(e, f));
+
+  INSERT INTO t1 VALUES(1, 'one', 'i');
+  INSERT INTO t1 VALUES(2, 'two', 'ii');
+  INSERT INTO t1 VALUES(3, 'three', 'iii');
+  INSERT INTO t1 VALUES(4, 'four', 'iv');
+  INSERT INTO t1 VALUES(5, 'five', 'v');
+  INSERT INTO t1 VALUES(6, 'six', 'vi');
+
+  INSERT INTO t2 SELECT * FROM t1;
+}
+
+do_invert_test 1.1 {
+  INSERT INTO t1 VALUES(7, 'seven', 'vii');
+}
+
+do_invert_test 1.2 {
+  DELETE FROM t1 WHERE a<4;
+}
+
+do_invert_test 1.2 {
+  UPDATE t1 SET c=5;
+}
+
+do_invert_test 1.3 {
+  UPDATE t1 SET b = a+1 WHERE a%2;
+  DELETE FROM t2;
+  INSERT INTO t1 VALUES(10, 'ten', NULL);
+}
+
+do_invert_test 1.4 {
+  UPDATE t2 SET d = d-1;
+}
+
+do_execsql_test 2.0 { 
+  ANALYZE;
+  PRAGMA writable_schema = 1;
+  DROP TABLE IF EXISTS sqlite_stat4;
+  SELECT * FROM sqlite_stat1;
+} {
+  t2 sqlite_autoindex_t2_1 {6 1 1} 
+  t1 sqlite_autoindex_t1_1 {6 1}
+}
+
+do_invert_test 2.1 {
+  INSERT INTO sqlite_stat1 VALUES('t3', 'idx2', '1 2 3');
+}
+
+do_invert_test 2.2 {
+  DELETE FROM sqlite_stat1;
+}
+
+do_invert_test 2.3 {
+  UPDATE sqlite_stat1 SET stat = 'hello world';
+}
+
+do_test 3.0 {
+  forcecopy test.db test.db2
+  sqlite3 db2 test.db2
+  set P [patchset_from_sql {
+    INSERT INTO t2 VALUES(1, 2, 3);
+    DELETE FROM t2 WHERE d = 3;
+  }]
+
+  list [catch { sqlite3changeset_apply_v2 -invert db2 $P {} } msg] $msg
+} {1 SQLITE_CORRUPT}
+
+do_test 3.1 {
+  sqlite3changeset_apply_v2 db2 $P {} 
+  compare_db db db2
+} {}
+
+
+finish_test
index abbf16dc75ccdecd5a1769c91477996283e5e3e8..20810ee4f1334856890de33f75f1070e6316ef14 100644 (file)
@@ -87,6 +87,7 @@ struct sqlite3_changeset_iter {
   SessionInput in;                /* Input buffer or stream */
   SessionBuffer tblhdr;           /* Buffer to hold apValue/zTab/abPK/ */
   int bPatchset;                  /* True if this is a patchset */
+  int bInvert;                    /* True to invert changeset */
   int rc;                         /* Iterator error code */
   sqlite3_stmt *pConflict;        /* Points to conflicting row, if any */
   char *zTab;                     /* Current table */
@@ -2540,7 +2541,8 @@ static int sessionChangesetStart(
   int (*xInput)(void *pIn, void *pData, int *pnData),
   void *pIn,
   int nChangeset,                 /* Size of buffer pChangeset in bytes */
-  void *pChangeset                /* Pointer to buffer containing changeset */
+  void *pChangeset,               /* Pointer to buffer containing changeset */
+  int bInvert                     /* True to invert changeset */
 ){
   sqlite3_changeset_iter *pRet;   /* Iterator to return */
   int nByte;                      /* Number of bytes to allocate for iterator */
@@ -2560,6 +2562,7 @@ static int sessionChangesetStart(
   pRet->in.xInput = xInput;
   pRet->in.pIn = pIn;
   pRet->in.bEof = (xInput ? 0 : 1);
+  pRet->bInvert = bInvert;
 
   /* Populate the output variable and return success. */
   *pp = pRet;
@@ -2574,7 +2577,7 @@ int sqlite3changeset_start(
   int nChangeset,                 /* Size of buffer pChangeset in bytes */
   void *pChangeset                /* Pointer to buffer containing changeset */
 ){
-  return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset);
+  return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0);
 }
 
 /*
@@ -2585,7 +2588,7 @@ int sqlite3changeset_start_strm(
   int (*xInput)(void *pIn, void *pData, int *pnData),
   void *pIn
 ){
-  return sessionChangesetStart(pp, xInput, pIn, 0, 0);
+  return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0);
 }
 
 /*
@@ -2964,10 +2967,10 @@ static int sessionChangesetNext(
     op = p->in.aData[p->in.iNext++];
   }
 
-  if( p->zTab==0 ){
+  if( p->zTab==0 || (p->bPatchset && p->bInvert) ){
     /* The first record in the changeset is not a table header. Must be a
     ** corrupt changeset. */
-    assert( p->in.iNext==1 );
+    assert( p->in.iNext==1 || p->zTab );
     return (p->rc = SQLITE_CORRUPT_BKPT);
   }
 
@@ -2992,33 +2995,39 @@ static int sessionChangesetNext(
     *paRec = &p->in.aData[p->in.iNext];
     p->in.iNext += *pnRec;
   }else{
+    sqlite3_value **apOld = (p->bInvert ? &p->apValue[p->nCol] : p->apValue);
+    sqlite3_value **apNew = (p->bInvert ? p->apValue : &p->apValue[p->nCol]);
 
     /* If this is an UPDATE or DELETE, read the old.* record. */
     if( p->op!=SQLITE_INSERT && (p->bPatchset==0 || p->op==SQLITE_DELETE) ){
       u8 *abPK = p->bPatchset ? p->abPK : 0;
-      p->rc = sessionReadRecord(&p->in, p->nCol, abPK, p->apValue);
+      p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld);
       if( p->rc!=SQLITE_OK ) return p->rc;
     }
 
     /* If this is an INSERT or UPDATE, read the new.* record. */
     if( p->op!=SQLITE_DELETE ){
-      p->rc = sessionReadRecord(&p->in, p->nCol, 0, &p->apValue[p->nCol]);
+      p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew);
       if( p->rc!=SQLITE_OK ) return p->rc;
     }
 
-    if( p->bPatchset && p->op==SQLITE_UPDATE ){
+    if( (p->bPatchset || p->bInvert) && p->op==SQLITE_UPDATE ){
       /* If this is an UPDATE that is part of a patchset, then all PK and
       ** modified fields are present in the new.* record. The old.* record
       ** is currently completely empty. This block shifts the PK fields from
       ** new.* to old.*, to accommodate the code that reads these arrays.  */
       for(i=0; i<p->nCol; i++){
-        assert( p->apValue[i]==0 );
+        assert( p->bPatchset==0 || p->apValue[i]==0 );
         if( p->abPK[i] ){
+          assert( p->apValue[i]==0 );
           p->apValue[i] = p->apValue[i+p->nCol];
           if( p->apValue[i]==0 ) return (p->rc = SQLITE_CORRUPT_BKPT);
           p->apValue[i+p->nCol] = 0;
         }
       }
+    }else if( p->bInvert ){
+      if( p->op==SQLITE_INSERT ) p->op = SQLITE_DELETE;
+      else if( p->op==SQLITE_DELETE ) p->op = SQLITE_INSERT;
     }
   }
 
@@ -4182,7 +4191,7 @@ static int sessionRetryConstraints(
     SessionBuffer cons = pApply->constraints;
     memset(&pApply->constraints, 0, sizeof(SessionBuffer));
 
-    rc = sessionChangesetStart(&pIter2, 0, 0, cons.nBuf, cons.aBuf);
+    rc = sessionChangesetStart(&pIter2, 0, 0, cons.nBuf, cons.aBuf, 0);
     if( rc==SQLITE_OK ){
       int nByte = 2*pApply->nCol*sizeof(sqlite3_value*);
       int rc2;
@@ -4436,7 +4445,8 @@ int sqlite3changeset_apply_v2(
   int flags
 ){
   sqlite3_changeset_iter *pIter;  /* Iterator to skip through changeset */  
-  int rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
+  int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
+  int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset,bInverse);
   if( rc==SQLITE_OK ){
     rc = sessionChangesetApply(
         db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
@@ -4493,7 +4503,8 @@ int sqlite3changeset_apply_v2_strm(
   int flags
 ){
   sqlite3_changeset_iter *pIter;  /* Iterator to skip through changeset */  
-  int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
+  int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
+  int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse);
   if( rc==SQLITE_OK ){
     rc = sessionChangesetApply(
         db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
index a3def5f1df1bf1359ef9d3d2407cadbf5c32a3b1..a9fbed94ef10bee0f9d676158cad5cc3aa3fb400 100644 (file)
@@ -1151,8 +1151,14 @@ int sqlite3changeset_apply_v2(
 **   causes the sessions module to omit this savepoint. In this case, if the
 **   caller has an open transaction or savepoint when apply_v2() is called, 
 **   it may revert the partially applied changeset by rolling it back.
+**
+** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
+**   Invert the changeset before applying it. This is equivalent to inverting
+**   a changeset using sqlite3changeset_invert() before applying it. It is
+**   an error to specify this flag with a patchset.
 */
 #define SQLITE_CHANGESETAPPLY_NOSAVEPOINT   0x0001
+#define SQLITE_CHANGESETAPPLY_INVERT        0x0002
 
 /* 
 ** CAPI3REF: Constants Passed To The Conflict Handler
index 3b6c24fd118c938a4be377d02e800ce8dc75f33c..6eba2114c7449c6b434170c2203030fb5cb91bf7 100644 (file)
@@ -737,20 +737,32 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
   sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
 
   /* Check for the -nosavepoint flag */
-  if( bV2 && objc>1 ){
-    const char *z1 = Tcl_GetString(objv[1]);
-    int n = strlen(z1);
-    if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){
-      flags = SQLITE_CHANGESETAPPLY_NOSAVEPOINT;
-      objc--;
-      objv++;
+  if( bV2 ){
+    if( objc>1 ){
+      const char *z1 = Tcl_GetString(objv[1]);
+      int n = strlen(z1);
+      if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){
+        flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT;
+        objc--;
+        objv++;
+      }
+    }
+    if( objc>1 ){
+      const char *z1 = Tcl_GetString(objv[1]);
+      int n = strlen(z1);
+      if( n>1 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){
+        flags |= SQLITE_CHANGESETAPPLY_INVERT;
+        objc--;
+        objv++;
+      }
     }
   }
 
   if( objc!=4 && objc!=5 ){
     const char *zMsg;
     if( bV2 ){
-      zMsg = "?-nosavepoint? DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
+      zMsg = "?-nosavepoint? ?-inverse? "
+        "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
     }else{
       zMsg = "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
     }
index 2ee3c8fa1d3269e41a23d34143b37339909bde9c..5231fee516d5535bf858465d6f107d53895f912e 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Test\scase\smodifications\sto\ssupport\sSEE.
-D 2018-10-12T15:01:56.030
+C Add\sthe\sSQLITE_CHANGESETAPPLY_INVERT\sflag\sto\ssessions.\sFor\sinverting\sand\sapplying\sa\schangeset\sin\sa\ssingle\sstep.
+D 2018-10-18T14:59:21.849
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F Makefile.in 01e95208a78b57d056131382c493c963518f36da4c42b12a97eb324401b3a334
@@ -407,18 +407,19 @@ F ext/session/sessionE.test 0a616c4ad8fd2c05f23217ebb6212ef80b7fef30f5f086a6633a
 F ext/session/sessionF.test c2f178d4dfd723a5fd94a730ea2ccb44c669e3ce
 F ext/session/sessionG.test 3edde849c4071078d92bd682c836186f6e4e5a3fb6bcf3fc1de1a7caa5e4427d
 F ext/session/sessionH.test 332b60e4c2e0a680105e11936201cabe378216f307e2747803cea56fa7d9ebae
-F ext/session/session_common.tcl ee925e0d233677e45e395fb1f559b84068ce7baa8aa1034441739d3e87ee249c
+F ext/session/session_common.tcl 29ec9910aca1e996ca1c8531b8cecabf96eb576aa53de65a8ff03d848b9a2a8b
 F ext/session/session_speed_test.c edc1f96fd5e0e4b16eb03e2a73041013d59e8723
 F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28f0c1cc142c3ec
 F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec
 F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7
 F ext/session/sessionfault2.test 555a8504de03d59b369ef20209585da5aeb2671dedabc4584e9ffe6269689185
+F ext/session/sessioninvert.test d4d8a89990de35e8e56d4d14d14bc7f191aa6f4c2b3731c7ce0fe64b640d29d3
 F ext/session/sessionrebase.test 4e1bcfd26fd8ed8ac571746f56cceeb45184f4d65490ea0d405227cfc8a9cba8
 F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e
 F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc
-F ext/session/sqlite3session.c ba76c7f01d4c71ab4d134cfda0ba43faae04bff01b8e81d1279a6101c706e3b5
-F ext/session/sqlite3session.h c01820d5b6e73e86d88008f4d1c1c7dfb83422963018292b864028a0400ceccf
-F ext/session/test_session.c dba36c6c0153b22501112d3e8882b5c946cf617c955153b6712bd2f8ba1428c0
+F ext/session/sqlite3session.c db0eb1bdadedf9905076fbff66ab7979d92a5d8649f09f39d9268c0d035aeeba
+F ext/session/sqlite3session.h 1b0b2bd69ae4cba5fd5fee050ef79707d45a1a3eed41077a92d14556fdcc1f6e
+F ext/session/test_session.c 9447482597c7569e49b3db152a300920a4b634d5de86508a94e4338df99b3fda
 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
 F ext/userauth/userauth.c f81aa5a3ecacf406f170c62a144405858f6f6de51dbdc0920134e629edbe2648
@@ -1771,7 +1772,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P f03164d45450cd7ff2162999aa9e51eec7fb5e7cab1fa83d876b544f8f841097
-R b06a0e84dfe3f0b011ce56cc2f8b5cd1
-U drh
-Z f26fa73bb00e67aa8b5d34053794de3a
+P 02b6f8f2778c371130c512e980c3db07c7e76dcf7dd92a878b86e4b6a47ca307
+R af6e82cd2e535125553a7eab8caf31bb
+U dan
+Z 5cdef990913d6faf4879f9cb06bcd872
index 2ce3280c9c1efdc7e26f873097900958b617d2c7..7f89b9f2dc5547ced61e64031e1b252d9d0913bb 100644 (file)
@@ -1 +1 @@
-02b6f8f2778c371130c512e980c3db07c7e76dcf7dd92a878b86e4b6a47ca307
\ No newline at end of file
+d4b6406e7f5ba06ac73ab9fdef57232b2459e0af12420ed946ebed6aef46f0b1
\ No newline at end of file