]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the SQLITE_CHANGESETAPPLY_NOUPDATELOOP option to the sqlite3_changeset_apply_v2... session-update-loops
authordan <Dan Kennedy>
Fri, 22 May 2026 14:22:14 +0000 (14:22 +0000)
committerdan <Dan Kennedy>
Fri, 22 May 2026 14:22:14 +0000 (14:22 +0000)
FossilOrigin-Name: eba02092bcf1713a646ecf8febd53a83bc803cf1b17e2e4fc590c14d886a8d27

ext/session/sessionconflict2.test
ext/session/sqlite3session.c
ext/session/sqlite3session.h
ext/session/test_session.c
manifest
manifest.uuid

index 012277aa5cb2c08ea694d9322457684983ef588f..d3d28bb5d6a763c3b15f051f97f4e8e3c8fd6a1e 100755 (executable)
@@ -135,8 +135,8 @@ do_execsql_test -db db2 3.3 {
 }
 
 #-------------------------------------------------------------------------
-reset_db
 db2 close
+reset_db
 forcedelete test.db2
 sqlite3 db2 test.db2
 
@@ -146,7 +146,7 @@ proc xConflict {args} {
   return "OMIT"
 }
 
-proc do_conflict_test {tn script clist} {
+proc do_conflict_test {tn bNoUpdateLoop script clist} {
 
   uplevel [list do_test $tn.1 [subst -nocommands {
     sqlite3session S db "main"
@@ -156,11 +156,19 @@ proc do_conflict_test {tn script clist} {
     S delete
   }] {}]
 
-  uplevel [list do_test $tn.2 {
-    set ::conflict_list [list]
-    sqlite3changeset_apply_v2 db2 $::changeset xConflict
-    set ::conflict_list
-  } [list {*}$clist]]
+  if {$bNoUpdateLoop} {
+    uplevel [list do_test $tn.2.no {
+      set ::conflict_list [list]
+      sqlite3changeset_apply_v2 -noupdateloop db2 $::changeset xConflict
+      set ::conflict_list
+    } [list {*}$clist]]
+  } else {
+    uplevel [list do_test $tn.2 {
+      set ::conflict_list [list]
+      sqlite3changeset_apply_v2 db2 $::changeset xConflict
+      set ::conflict_list
+    } [list {*}$clist]]
+  }
 }
 
 do_test 4.0 {
@@ -184,7 +192,7 @@ proc swap {tbl pkcol valcol pk1 pk2} {
   "
 }
 
-do_conflict_test 4.1.1 {
+do_conflict_test 4.1.1 {
   swap t1 a c 4 5
   swap t1 a c 2 3
   swap t1 a c 8 1
@@ -202,7 +210,7 @@ do_execsql_test -db db2 4.1.3 {
   1 8 2 3 3 2 4 5 5 4 6 6 7 7 8 1 9 9 10 10
 }
 
-do_conflict_test 4.2.1 {
+do_conflict_test 4.2.1 {
   swap t1 a d 10 9
   swap t1 a d  8 7
   swap t1 a d  7 6
@@ -226,7 +234,7 @@ do_execsql_test -db db2 4.3 {
   INSERT INTO t1(a, b, c, d) VALUES(11, 11, 11, 11);
 }
 
-do_conflict_test 4.3.1 {
+do_conflict_test 4.3.1 {
   swap t1 a c 3 6
   db eval { UPDATE t1 SET d=11 WHERE a=10; }
   swap t1 a d 4 8
@@ -234,7 +242,7 @@ do_conflict_test 4.3.1 {
   {UPDATE t1 CONSTRAINT {i 10 {} {} {} {} i 9} {{} {} {} {} {} {} i 11}}
 }
 
-do_conflict_test 4.3.2 {
+do_conflict_test 4.3.2 {
   swap t1 a c 1 2
   swap t1 a c 3 4
   db eval { UPDATE t1 SET c=11 WHERE a=10; }
@@ -244,14 +252,47 @@ do_conflict_test 4.3.2 {
   {UPDATE t1 CONSTRAINT {i 10 {} {} i 10 {} {}} {{} {} {} {} i 11 {} {}}}
 }
 
-do_conflict_test 4.3.3 {
+do_conflict_test 4.3.3 {
   swap t1 a c 1 2
   swap t1 a c 2 3
   swap t1 a c 3 4
   swap t1 a c 4 1
 } {
 }
-
 db2 close
+
+#-------------------------------------------------------------------------
+reset_db
+forcedelete test.db2
+sqlite3 db2 test.db2
+
+do_test 5.0 {
+  do_common_sql {
+    CREATE TABLE t1(a INT PRIMARY KEY, b UNIQUE);
+    INSERT INTO t1 VALUES('one', 'one');
+    INSERT INTO t1 VALUES('two', 'two');
+    INSERT INTO t1 VALUES('three', 'three');
+    INSERT INTO t1 VALUES('four', 'four');
+    INSERT INTO t1 VALUES('five', 'five');
+  }
+} {}
+
+do_conflict_test 5.1 1 {
+} {
+}
+
+do_conflict_test 5.2 1 {
+  swap t1 a b one two
+} {
+ {UPDATE t1 CONSTRAINT {t two t two} {{} {} t one}} 
+ {UPDATE t1 CONSTRAINT {t one t one} {{} {} t two}}
+}
+
+do_conflict_test 5.2 0 {
+  swap t1 a b four five
+} {
+}
+
+
 finish_test
 
index 544e18e323714a8970d418aeb5caca9805fb7445..7e914150e94c8f8ac720f0e3fb5febe981e346d2 100644 (file)
@@ -4364,6 +4364,7 @@ struct SessionApplyCtx {
   u8 bRebaseStarted;              /* If table header is already in rebase */
   u8 bRebase;                     /* True to collect rebase information */
   u8 bIgnoreNoop;                 /* True to ignore no-op conflicts */
+  u8 bNoUpdateLoop;               /* No update-loop processing */
   int bRowid;
   char *zErr;                     /* Error message, if any */
 };
@@ -5472,7 +5473,7 @@ static int sessionRetryConstraints(
   }
 
   /* Step (2) */
-  while( rc==SQLITE_OK && pApply->constraints.nBuf ){
+  while( rc==SQLITE_OK && pApply->constraints.nBuf && !pApply->bNoUpdateLoop ){
     SessionBuffer cons = {0, 0, 0};
     sqlite3_changeset_iter *pUp = 0;
     sqlite3_stmt *pInsert = 0;
@@ -5601,6 +5602,7 @@ static int sessionChangesetApply(
   sApply.bRebase = (ppRebase && pnRebase);
   sApply.bInvertConstraints = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
   sApply.bIgnoreNoop = !!(flags & SQLITE_CHANGESETAPPLY_IGNORENOOP);
+  sApply.bNoUpdateLoop = !!(flags & SQLITE_CHANGESETAPPLY_NOUPDATELOOP);
   if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){
     rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
   }
index fb2336d3262285c5098c593a6c1fa72c5aaf9ed1..045a1ffeefab5a03f408161bfbd46d5e2a156728 100644 (file)
@@ -1366,11 +1366,23 @@ int sqlite3changeset_apply_v3(
 **   database behave as if they were declared with "ON UPDATE NO ACTION ON
 **   DELETE NO ACTION", even if they are actually CASCADE, RESTRICT, SET NULL
 **   or SET DEFAULT.
+**
+** <dt>SQLITE_CHANGESETAPPLY_NOUPDATELOOP <dd>
+**   Sometimes, a changeset contains two or more update statements such that
+**   although after applying all updates the database will contain no 
+**   constraint violations, no single update can be applied before the others.
+**   The simplest example of this is a pair of UPDATEs that have "swapped"
+**   two column values with a UNIQUE constraint.
+**   <p>
+**   Usually, sqlite3changeset_apply() and similar functions work hard to try
+**   to find a way to apply such a changeset. However, if this flag is set,
+**   then all such updates are considered CONSTRAINT conflicts.
 */
 #define SQLITE_CHANGESETAPPLY_NOSAVEPOINT   0x0001
 #define SQLITE_CHANGESETAPPLY_INVERT        0x0002
 #define SQLITE_CHANGESETAPPLY_IGNORENOOP    0x0004
 #define SQLITE_CHANGESETAPPLY_FKNOACTION    0x0008
+#define SQLITE_CHANGESETAPPLY_NOUPDATELOOP  0x0010
 
 /* 
 ** CAPI3REF: Constants Passed To The Conflict Handler
index bb8563aabb45b70a7d481028ae1fd4915bcca898..7ede0bb426cd9cfe40d609608963b420c2f9b52f 100644 (file)
@@ -914,6 +914,9 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
       }
       else if( n>2 && n<=11 && 0==sqlite3_strnicmp("-ignorenoop", z1, n) ){
         flags |= SQLITE_CHANGESETAPPLY_IGNORENOOP;
+      }
+      else if( n>3 && n<=13 && 0==sqlite3_strnicmp("-noupdateloop", z1, n) ){
+        flags |= SQLITE_CHANGESETAPPLY_NOUPDATELOOP;
       }else{
         break;
       }
index 54314d7740c61ff869efa2658d0a0d2bc8f5be2e..c20c48221d6d8edc73bbd5d1b25c7c46c5de8cb7 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Merge\slatest\strunk\schanges\swith\sthis\sbranch.
-D 2026-05-21T15:13:17.189
+C Add\sthe\sSQLITE_CHANGESETAPPLY_NOUPDATELOOP\soption\sto\sthe\ssqlite3_changeset_apply_v2/3()\smethod.\sTo\sdisable\sthe\sextra\sprocessing\sto\scommit\schangesets\sthat\sswap\stwo\sor\smore\svalues\ssubject\sto\sa\sUNIQUE\sconstraint\sbetween\srows.
+D 2026-05-22T14:22:14.502
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -559,7 +559,7 @@ F ext/session/sessionblob.test 87faf667870b72f08e91969abd9f52a383ab7b514506ee194
 F ext/session/sessionchange.test 6618cb1c1338a4b6df173b6ac42d09623fb71269962abf23ebb7617fe9f45a50
 F ext/session/sessionchange2.test 8f59185216882adc8b34bb5ba63887459acf3df58493bcffa12e4d05ab6a6b85
 F ext/session/sessionconflict.test 19e4a53795c4c930bfec49e809311e09b2a9e202d9446e56d7a8b139046a0c07 x
-F ext/session/sessionconflict2.test 795768e963c96078b7939f397824e26aede6e5563a0e42f7b11c12795f313f3a x
+F ext/session/sessionconflict2.test d7f4caf59360dbca8a4698b9a3a322adf6f547f810ad41d131cacb730e02dd5e x
 F ext/session/sessiondiff.test e89f7aedcdd89e5ebac3a455224eb553a171e9586fc3e1e6a7b3388d2648ba8d
 F ext/session/sessionfault.test c2b43d01213b389a3f518e90775fca2120812ba51e50444c4066962263e45c11
 F ext/session/sessionfault2.test b0d6a7c1d7398a7e800d84657404909c7d385965ea8576dc79ed344c46fbf41c
@@ -574,9 +574,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 6d3d284fa35e01a68db8d6f1aae0fd83a92c69d4442d6b88aed09b556fc2b707
-F ext/session/sqlite3session.h 063e7bf7be2fff874456f452a224b5b3013b25682d108933b0351c93a1279b9c
-F ext/session/test_session.c 3773e750b5c751956fdbef41a998cc1ba02d59c3dede74e75866e3446a900e69
+F ext/session/sqlite3session.c 9d1cce13a48d821a31b36d99123ab25da87c3ae8b3bb96a926dfcc233a35ba9c
+F ext/session/sqlite3session.h ca7c4422c1514a95056cc8d333217df6b1829d39058126b1de85d10cd62d7a9c
+F ext/session/test_session.c 05c1f90c04de5474158bf8f7712a6f7a1d47477ce0402bbe0e55fc4a9ef1f49b
 F ext/wasm/GNUmakefile 65feef4ec48e62249f90278c4c08a3fe3c69e2461ff560b61c03cd73606e0949
 F ext/wasm/README-dist.txt f01081a850ce38a56706af6b481e3a7878e24e42b314cfcd4b129f0f8427066a
 F ext/wasm/README.md 2e87804e12c98f1d194b7a06162a88441d33bb443efcfe00dc6565a780d2f259
@@ -2206,8 +2206,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
 F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
 F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
-P 07f81e256fde5796c0d8bcedff8fad4368639386f40881c0844e9c86a69b6fad 869a51ae84dfaaf824c872e4b3024f35eea7fa67bb584759a2d42ebf8404ef6e
-R f21e35a31a0e9f095263f7664daa2ccc
+P c6226d508d6af28fcd42fecd482014c3e284923871d3d8f5177f728cbf3457c5
+R 1eabf6c03fde51e4f558b57e68078de8
 U dan
-Z 7d6fcd668efb6e232e66f35845c58a26
+Z 7d1f8058359dfe62c0d29a8d06d235a4
 # Remove this line to create a well-formed Fossil manifest.
index b90b4bd3bdc2d256572c1e0d3d282598a06738ef..5376a6e7b650b0c7af9ac37802b18ec537822f05 100644 (file)
@@ -1 +1 @@
-c6226d508d6af28fcd42fecd482014c3e284923871d3d8f5177f728cbf3457c5
+eba02092bcf1713a646ecf8febd53a83bc803cf1b17e2e4fc590c14d886a8d27