From: dan Date: Wed, 8 Mar 2023 18:03:04 +0000 (+0000) Subject: Add the SQLITE_CHANGESETAPPLY_IGNORENOOP flag, which may be passed to sqlite3changese... X-Git-Tag: version-3.42.0~274 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=975f2062dad5425b2384265dfca372d39f200c0f;p=thirdparty%2Fsqlite.git Add the SQLITE_CHANGESETAPPLY_IGNORENOOP flag, which may be passed to sqlite3changeset_apply_v2() to have it ignore changes that would be no-ops if applied to the database (e.g. deleting a row that has already been deleted), instead of considering them conflicts. FossilOrigin-Name: cb023fe28560ce0f8c2fd48042553fcdb9db81eba9552be75165de0d46a2645c --- diff --git a/ext/session/session2.test b/ext/session/session2.test index 806687745e..207b98740e 100644 --- a/ext/session/session2.test +++ b/ext/session/session2.test @@ -191,7 +191,7 @@ do_common_sql { } foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3 %T4% t4} $set_of_tests] { - do_then_apply_sql $sql + do_then_apply_sql -ignorenoop $sql do_test 2.$tn { compare_db db db2 } {} } @@ -598,7 +598,7 @@ do_common_sql { INSERT INTO t1 SELECT NULL, 0, 0, 0, 0, 0 FROM s } -do_then_apply_sql { +do_then_apply_sql -ignorenoop { UPDATE t1 SET f=f+1 WHERE a=1; UPDATE t1 SET e=e+1 WHERE a=2; UPDATE t1 SET e=e+1, f=f+1 WHERE a=3; diff --git a/ext/session/sessionG.test b/ext/session/sessionG.test index 58ea17d2ee..1ebcc926a5 100644 --- a/ext/session/sessionG.test +++ b/ext/session/sessionG.test @@ -34,7 +34,7 @@ do_test 1.0 { INSERT INTO t1 VALUES(2, 'two'); INSERT INTO t1 VALUES(3, 'three'); } - do_then_apply_sql { + do_then_apply_sql -ignorenoop { DELETE FROM t1 WHERE a=1; INSERT INTO t1 VALUES(4, 'one'); } @@ -42,7 +42,7 @@ do_test 1.0 { } {} do_test 1.1 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { DELETE FROM t1 WHERE a=4; INSERT INTO t1 VALUES(1, 'one'); } @@ -51,7 +51,7 @@ do_test 1.1 { do_test 1.2 { execsql { INSERT INTO t1 VALUES(5, 'five') } db2 - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t1 VALUES(11, 'eleven'); INSERT INTO t1 VALUES(12, 'five'); } @@ -82,7 +82,7 @@ do_test 2.2.1 { # It is not possible to apply the changeset generated by the following # SQL, as none of the three updated rows may be updated as part of the # first pass. - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t1 SET b=0 WHERE a=1; UPDATE t1 SET b=1 WHERE a=2; UPDATE t1 SET b=2 WHERE a=3; @@ -109,7 +109,7 @@ do_test 3.1 { } {} do_test 3.3 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t1 SET b=4 WHERE a=3; UPDATE t1 SET b=3 WHERE a=2; UPDATE t1 SET b=2 WHERE a=1; @@ -118,7 +118,7 @@ do_test 3.3 { } {} do_test 3.4 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t1 SET b=1 WHERE a=1; UPDATE t1 SET b=2 WHERE a=2; UPDATE t1 SET b=3 WHERE a=3; @@ -148,7 +148,7 @@ do_test 4.1 { } {} do_test 4.2 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t1 SET b=4 WHERE a=3; UPDATE t1 SET b=3 WHERE a=2; UPDATE t1 SET b=2 WHERE a=1; @@ -161,7 +161,7 @@ do_test 4.2 { } {} do_test 4.3 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t1 SET b=1 WHERE a=1; UPDATE t1 SET b=2 WHERE a=2; UPDATE t1 SET b=3 WHERE a=3; @@ -191,7 +191,7 @@ do_execsql_test -db db2 5.0.2 { } do_test 5.1 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t1 VALUES(1, 2, 3); INSERT INTO t2 VALUES(4, 5, 6); INSERT INTO t3 VALUES(7, 8, 9); diff --git a/ext/session/sessionH.test b/ext/session/sessionH.test index 8ba23119c8..e1b12571c6 100644 --- a/ext/session/sessionH.test +++ b/ext/session/sessionH.test @@ -25,7 +25,7 @@ do_test 1.0 { do_common_sql { CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b)); } - do_then_apply_sql { + do_then_apply_sql -ignorenoop { WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERe i<10000 ) diff --git a/ext/session/session_common.tcl b/ext/session/session_common.tcl index c52ac457c0..22e427f2be 100644 --- a/ext/session/session_common.tcl +++ b/ext/session/session_common.tcl @@ -112,20 +112,52 @@ proc patchset_from_sql {sql {dbname main}} { return $patchset } -proc do_then_apply_sql {sql {dbname main}} { - proc xConflict args { return "OMIT" } +# Usage: do_then_apply_sql ?-ignorenoop? SQL ?DBNAME? +# +proc do_then_apply_sql {args} { + + set bIgnoreNoop 0 + set a1 [lindex $args 0] + if {[string length $a1]>1 && [string first $a1 -ignorenoop]==0} { + set bIgnoreNoop 1 + set args [lrange $args 1 end] + } + + if {[llength $args]!=1 && [llength $args]!=2} { + error "usage: do_then_apply_sql ?-ignorenoop? SQL ?DBNAME?" + } + + set sql [lindex $args 0] + if {[llength $args]==1} { + set dbname main + } else { + set dbname [lindex $args 1] + } + + set ::n_conflict 0 + proc xConflict args { incr ::n_conflict ; return "OMIT" } set rc [catch { sqlite3session S db $dbname db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" { S attach $name } db eval $sql - sqlite3changeset_apply db2 [S changeset] xConflict + set ::changeset [S changeset] + sqlite3changeset_apply db2 $::changeset xConflict } msg] catch { S delete } - if {$rc} {error $msg} + + if {$bIgnoreNoop} { + set nSave $::n_conflict + set ::n_conflict 0 + proc xConflict args { incr ::n_conflict ; return "OMIT" } + sqlite3changeset_apply_v2 -ignorenoop db2 $::changeset xConflict + if {$::n_conflict!=$nSave} { + error "-ignorenoop problem ($::n_conflict $nSave)..." + } + } } proc do_iterator_test {tn tbl_list sql res} { diff --git a/ext/session/sessionat.test b/ext/session/sessionat.test index e3f9e31ed7..e14901e8b5 100644 --- a/ext/session/sessionat.test +++ b/ext/session/sessionat.test @@ -110,7 +110,7 @@ eval [string map [list %WR% $trailing] { CREATE TABLE t3(a, b, c DEFAULT 'D', PRIMARY KEY(b)) %WR%; } do_test $tn.3.2 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t3 VALUES(1, 2); INSERT INTO t3 VALUES(3, 4); INSERT INTO t3 VALUES(5, 6); @@ -118,7 +118,7 @@ eval [string map [list %WR% $trailing] { db2 eval {SELECT * FROM t3} } {1 2 D 3 4 D 5 6 D} do_test $tn.3.3 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t3 SET a=45 WHERE b=4; DELETE FROM t3 WHERE a=5; }; @@ -253,7 +253,7 @@ eval [string map [list %WR% $trailing] { CREATE TABLE t8(a PRIMARY KEY, b, c, d DEFAULT 'D', e DEFAULT 'E'); } - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t8 VALUES(1, 2, 3); INSERT INTO t8 VALUES(4, 5, 6); } @@ -264,7 +264,7 @@ eval [string map [list %WR% $trailing] { SELECT * FROM t8 } {1 2 3 D E 4 5 6 D E} - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t8 SET c=45 WHERE a=4; } do_execsql_test $tn.7.3.1 { @@ -282,7 +282,7 @@ eval [string map [list %WR% $trailing] { do_execsql_test -db db2 $tn.8.1 { CREATE TABLE t9(a PRIMARY KEY, b, c, d, e, f, g, h, i, j, k, l); } - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t9 VALUES(1, 2, 3, 4, 5, 6, 7, 8); } do_then_apply_sql { @@ -291,7 +291,7 @@ eval [string map [list %WR% $trailing] { do_execsql_test -db db2 $tn.8.2 { SELECT * FROM t9 } {1 2 3 4 5 6 7 450 {} {} {} {}} - do_then_apply_sql { + do_then_apply_sql -ignorenoop { UPDATE t9 SET h=NULL } do_execsql_test -db db2 $tn.8.2 { diff --git a/ext/session/sessionbig.test b/ext/session/sessionbig.test index 80ce00a0f7..462e21f61f 100644 --- a/ext/session/sessionbig.test +++ b/ext/session/sessionbig.test @@ -43,7 +43,7 @@ do_execsql_test -db db2 1.1 { } do_test 1.2 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); @@ -71,7 +71,7 @@ do_test 1.3 { do_test 1.4 { set rc [catch { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); INSERT INTO t1(b) VALUES( zeroblob(100*1000*1000) ); diff --git a/ext/session/sessionfault.test b/ext/session/sessionfault.test index be6c4568ce..96e966b41d 100644 --- a/ext/session/sessionfault.test +++ b/ext/session/sessionfault.test @@ -44,7 +44,7 @@ do_faultsim_test 1.1 -faults oom-* -prep { faultsim_restore_and_reopen sqlite3 db2 test.db2 } -body { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO t1 VALUES('a string value', 8, 9); UPDATE t1 SET c = 10 WHERE a = 1; DELETE FROM t1 WHERE a = 4; diff --git a/ext/session/sessionfault2.test b/ext/session/sessionfault2.test index dd00eaa1c8..a2dc39e437 100644 --- a/ext/session/sessionfault2.test +++ b/ext/session/sessionfault2.test @@ -132,7 +132,7 @@ do_faultsim_test 1.1 -faults oom-* -prep { faultsim_restore_and_reopen sqlite3 db2 test.db2 } -body { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { INSERT INTO sqlite_stat1 VALUES('x', 'y', 45); UPDATE sqlite_stat1 SET stat = 123 WHERE tbl='t1' AND idx='i1'; UPDATE sqlite_stat1 SET stat = 456 WHERE tbl='t2'; diff --git a/ext/session/sessionnoop.test b/ext/session/sessionnoop.test index 16c60b7abf..5549773440 100644 --- a/ext/session/sessionnoop.test +++ b/ext/session/sessionnoop.test @@ -1,4 +1,4 @@ -# 2021 Februar 20 +# 2011 March 07 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: @@ -20,166 +20,159 @@ ifcapable !session {finish_test; return} set testprefix sessionnoop -#------------------------------------------------------------------------- -# Test plan: -# -# 1.*: Test that concatenating changesets cannot produce a noop UPDATE. -# 2.*: Test that rebasing changesets cannot produce a noop UPDATE. -# 3.*: Test that sqlite3changeset_apply() ignores noop UPDATE changes. -# +foreach {tn wo} { + 1 "" + 2 " WITHOUT ROWID " +} { + reset_db + eval [string map [list %WO% $wo] { +do_execsql_test $tn.1.0 { + CREATE TABLE t1(a PRIMARY KEY, b, c) %WO%; + INSERT INTO t1 VALUES('a', 'A', 'AAA'); + INSERT INTO t1 VALUES('b', 'B', 'BBB'); + INSERT INTO t1 VALUES('c', 'C', 'CCC'); + INSERT INTO t1 VALUES('d', 'D', 'DDD'); + INSERT INTO t1 VALUES('e', 'E', 'EEE'); +} -do_execsql_test 1.0 { - CREATE TABLE t1(a PRIMARY KEY, b, c, d); - INSERT INTO t1 VALUES(1, 1, 1, 1); - INSERT INTO t1 VALUES(2, 2, 2, 2); - INSERT INTO t1 VALUES(3, 3, 3, 3); +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_execsql_test -db db2 $tn.1.1 { + CREATE TABLE t1(a PRIMARY KEY, b, c) %WO%; + INSERT INTO t1 VALUES('a', 'A', 'AAA'); + INSERT INTO t1 VALUES('b', 'B', '123'); + INSERT INTO t1 VALUES('c', 'C', 'CCC'); + INSERT INTO t1 VALUES('e', 'E', 'EEE'); + INSERT INTO t1 VALUES('f', 'F', 'FFF'); } -proc do_concat_test {tn sql1 sql2 res} { - uplevel [list do_test $tn [subst -nocommands { - set C1 [changeset_from_sql {$sql1}] - set C2 [changeset_from_sql {$sql2}] - set C3 [sqlite3changeset_concat [set C1] [set C2]] - set got [list] - sqlite3session_foreach elem [set C3] { lappend got [set elem] } - set got - }] [list {*}$res]] +set C [changeset_from_sql { + UPDATE t1 SET c='123' WHERE a='b'; + DELETE FROM t1 WHERE a='d'; + INSERT INTO t1 VALUES('f', 'F', 'FFF'); +}] + + +set ::conflict_list [list] +proc xConflict {args} { + lappend ::conflict_list $args + return "OMIT" } +do_test $tn.1.2 { + sqlite3changeset_apply_v2 db2 $C xConflict + set ::conflict_list +} [list {*}{ + {UPDATE t1 DATA {t b {} {} t BBB} {{} {} {} {} t 123} {t b t B t 123}} + {INSERT t1 CONFLICT {t f t F t FFF} {t f t F t FFF}} + {DELETE t1 NOTFOUND {t d t D t DDD}} +}] +do_test $tn.1.3 { + set ::conflict_list [list] + sqlite3changeset_apply_v2 db2 $C xConflict + set ::conflict_list +} [list {*}{ + {UPDATE t1 DATA {t b {} {} t BBB} {{} {} {} {} t 123} {t b t B t 123}} + {INSERT t1 CONFLICT {t f t F t FFF} {t f t F t FFF}} + {DELETE t1 NOTFOUND {t d t D t DDD}} +}] + +do_test $tn.1.4 { + set ::conflict_list [list] + sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict + set ::conflict_list +} {} -do_concat_test 1.1 { - UPDATE t1 SET c=c+1; -} { - UPDATE t1 SET c=c-1; -} { +do_execsql_test -db db2 1.5 { + UPDATE t1 SET b='G' WHERE a='f'; + UPDATE t1 SET c='456' WHERE a='b'; } -#------------------------------------------------------------------------- +do_test $tn.1.6 { + set ::conflict_list [list] + sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict + set ::conflict_list +} [list {*}{ + {UPDATE t1 DATA {t b {} {} t BBB} {{} {} {} {} t 123} {t b t B t 456}} + {INSERT t1 CONFLICT {t f t F t FFF} {t f t G t FFF}} +}] + +db2 close + +#-------------------------------------------------------------------------- + reset_db -do_execsql_test 2.0 { - CREATE TABLE t1(a PRIMARY KEY, b, c); - INSERT INTO t1 VALUES(1, 1, 1); - INSERT INTO t1 VALUES(2, 2, 2); - INSERT INTO t1 VALUES(3, 3, 3); +forcedelete test.db2 +sqlite3 db2 test.db2 +do_execsql_test $tn.2.0 { + CREATE TABLE t1(a PRIMARY KEY, b) %WO%; } - -proc do_rebase_test {tn sql_local sql_remote conflict_res expected} { - proc xConflict {args} [list return $conflict_res] - - uplevel [list \ - do_test $tn [subst -nocommands { - execsql BEGIN - set c_remote [changeset_from_sql {$sql_remote}] - execsql ROLLBACK - - execsql BEGIN - set c_local [changeset_from_sql {$sql_local}] - set base [sqlite3changeset_apply_v2 db [set c_remote] xConflict] - execsql ROLLBACK - - sqlite3rebaser_create R - R config [set base] - set res [list] - sqlite3session_foreach elem [R rebase [set c_local]] { - lappend res [set elem] - } - R delete - set res - }] [list {*}$expected] - ] +do_execsql_test -db db2 $tn.2.1 { + CREATE TABLE t1(a PRIMARY KEY, b, c DEFAULT 'val') %WO%; } -do_rebase_test 2.1 { - UPDATE t1 SET c=2 WHERE a=1; -- local -} { - UPDATE t1 SET c=3 WHERE a=1; -- remote -} OMIT { - {UPDATE t1 0 X.. {i 1 {} {} i 3} {{} {} {} {} i 2}} -} +do_test $tn.2.2 { + do_then_apply_sql -ignorenoop { + INSERT INTO t1 VALUES(1, 2); + } + do_then_apply_sql -ignorenoop { + UPDATE t1 SET b=2 WHERE a=1 + } +} {} -do_rebase_test 2.2 { - UPDATE t1 SET c=2 WHERE a=1; -- local -} { - UPDATE t1 SET c=3 WHERE a=1; -- remote -} REPLACE { -} -do_rebase_test 2.3.1 { - UPDATE t1 SET c=4 WHERE a=1; -- local -} { - UPDATE t1 SET c=4 WHERE a=1 -- remote -} OMIT { - {UPDATE t1 0 X.. {i 1 {} {} i 4} {{} {} {} {} i 4}} +}] } -do_rebase_test 2.3.2 { - UPDATE t1 SET c=5 WHERE a=1; -- local -} { - UPDATE t1 SET c=5 WHERE a=1 -- remote -} REPLACE { -} +db2 close #------------------------------------------------------------------------- -# reset_db +forcedelete test.db2 do_execsql_test 3.0 { - CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c); - INSERT INTO t1 VALUES(1, 1, 1); - INSERT INTO t1 VALUES(2, 2, 2); - INSERT INTO t1 VALUES(3, 3, 3); - INSERT INTO t1 VALUES(4, 4, 4); -} - -# Arg $pkstr contains one character for each column in the table. An -# "X" for PK column, or a "." for a non-PK. -# -proc mk_tbl_header {name pkstr} { - set ret [binary format H2c 54 [string length $pkstr]] - foreach i [split $pkstr {}] { - if {$i=="X"} { - append ret [binary format H2 01] - } else { - if {$i!="."} {error "bad pkstr: $pkstr ($i)"} - append ret [binary format H2 00] - } - } - append ret $name - append ret [binary format H2 00] - set ret + CREATE TABLE xyz(a, b, c, PRIMARY KEY(a, b), UNIQUE(c)); + ANALYZE; + WITH s(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<100 + ) + INSERT INTO xyz SELECT i, i, i FROM s; + VACUUM INTO 'test.db2'; } -proc mk_update_change {args} { - set ret [binary format H2H2 17 00] - foreach a $args { - if {$a==""} { - append ret [binary format H2 00] - } else { - append ret [binary format H2W 01 $a] - } - } - set ret -} +set C [changeset_from_sql { ANALYZE }] +sqlite3 db2 test.db2 -proc xConflict {args} { return "ABORT" } +set ::conflict_list [list] +proc xConflict {args} { lappend ::conflict_list $args ; return "OMIT" } do_test 3.1 { - set C [mk_tbl_header t1 X..] - append C [mk_update_change 1 {} 1 {} {} 500] - append C [mk_update_change 2 {} {} {} {} {}] - append C [mk_update_change 3 3 {} {} 600 {}] - append C [mk_update_change 4 {} {} {} {} {}] + sqlite3changeset_apply_v2 db2 $C xConflict + set ::conflict_list +} {} - sqlite3changeset_apply_v2 db $C xConflict +do_test 3.2 { + sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict + set ::conflict_list } {} -do_execsql_test 3.2 { - SELECT * FROM t1 -} { - 1 1 500 - 2 2 2 - 3 600 3 - 4 4 4 -} +do_test 3.3 { + sqlite3changeset_apply_v2 db2 $C xConflict + set ::conflict_list +} [list {*}{ + {INSERT sqlite_stat1 CONFLICT {t xyz t sqlite_autoindex_xyz_1 t {100 1 1}} {t xyz t sqlite_autoindex_xyz_1 t {100 1 1}}} + {INSERT sqlite_stat1 CONFLICT {t xyz t sqlite_autoindex_xyz_2 t {100 1}} {t xyz t sqlite_autoindex_xyz_2 t {100 1}}} +}] +do_execsql_test -db db2 3.4 { + UPDATE sqlite_stat1 SET stat='200 1 1' WHERE idx='sqlite_autoindex_xyz_1'; +} +do_test 3.5 { + set ::conflict_list [list] + sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict + set ::conflict_list +} [list {*}{ + {INSERT sqlite_stat1 CONFLICT {t xyz t sqlite_autoindex_xyz_1 t {100 1 1}} {t xyz t sqlite_autoindex_xyz_1 t {200 1 1}}} +}] diff --git a/ext/session/sessionstat1.test b/ext/session/sessionstat1.test index 774899d96b..2757d60440 100644 --- a/ext/session/sessionstat1.test +++ b/ext/session/sessionstat1.test @@ -82,7 +82,7 @@ do_test 2.0 { } {} do_test 2.1 { - do_then_apply_sql { + do_then_apply_sql -ignorenoop { WITH s(i) AS ( SELECT 0 UNION ALL SELECT i+1 FROM s WHERE (i+1)<32 ) @@ -100,7 +100,7 @@ do_execsql_test -db db2 2.2 { } do_test 2.3 { - do_then_apply_sql { DROP INDEX t1c } + do_then_apply_sql -ignorenoop { DROP INDEX t1c } } {} do_execsql_test -db db2 2.4 { @@ -111,7 +111,7 @@ do_execsql_test -db db2 2.4 { } do_test 2.3 { - do_then_apply_sql { DROP TABLE t1 } + do_then_apply_sql -ignorenoop { DROP TABLE t1 } } {} do_execsql_test -db db2 2.4 { @@ -153,16 +153,16 @@ do_execsql_test 3.2 { } {t1 null 4} do_test 3.3 { execsql { DELETE FROM sqlite_stat1 } - do_then_apply_sql { ANALYZE } + do_then_apply_sql -ignorenoop { ANALYZE } execsql { SELECT * FROM sqlite_stat1 } db2 } {t1 null 4} do_test 3.4 { execsql { INSERT INTO t1 VALUES(5,5,5) } - do_then_apply_sql { ANALYZE } + do_then_apply_sql -ignorenoop { ANALYZE } execsql { SELECT * FROM sqlite_stat1 } db2 } {t1 null 5} do_test 3.5 { - do_then_apply_sql { DROP TABLE t1 } + do_then_apply_sql -ignorenoop { DROP TABLE t1 } execsql { SELECT * FROM sqlite_stat1 } db2 } {} diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index a3f28abe9e..225d0316a8 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -2113,9 +2113,10 @@ static void sessionAppendStr( int *pRc ){ int nStr = sqlite3Strlen30(zStr); - if( 0==sessionBufferGrow(p, nStr, pRc) ){ + if( 0==sessionBufferGrow(p, nStr+1, pRc) ){ memcpy(&p->aBuf[p->nBuf], zStr, nStr); p->nBuf += nStr; + p->aBuf[p->nBuf] = 0x00; } } @@ -2137,6 +2138,27 @@ static void sessionAppendInteger( sessionAppendStr(p, aBuf, pRc); } +static void sessionAppendPrintf( + SessionBuffer *p, /* Buffer to append to */ + int *pRc, + const char *zFmt, + ... +){ + if( *pRc==SQLITE_OK ){ + char *zApp = 0; + va_list ap; + va_start(ap, zFmt); + zApp = sqlite3_vmprintf(zFmt, ap); + if( zApp==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + sessionAppendStr(p, zApp, pRc); + } + va_end(ap); + sqlite3_free(zApp); + } +} + /* ** This function is a no-op if *pRc is other than SQLITE_OK when it is ** called. Otherwise, append the string zStr enclosed in quotes (") and @@ -2151,7 +2173,7 @@ static void sessionAppendIdent( const char *zStr, /* String to quote, escape and append */ int *pRc /* IN/OUT: Error code */ ){ - int nStr = sqlite3Strlen30(zStr)*2 + 2 + 1; + int nStr = sqlite3Strlen30(zStr)*2 + 2 + 2; if( 0==sessionBufferGrow(p, nStr, pRc) ){ char *zOut = (char *)&p->aBuf[p->nBuf]; const char *zIn = zStr; @@ -2162,6 +2184,7 @@ static void sessionAppendIdent( } *zOut++ = '"'; p->nBuf = (int)((u8 *)zOut - p->aBuf); + p->aBuf[p->nBuf] = 0x00; } } @@ -2386,10 +2409,17 @@ static int sessionAppendDelete( ** Formulate and prepare a SELECT statement to retrieve a row from table ** zTab in database zDb based on its primary key. i.e. ** -** SELECT * FROM zDb.zTab WHERE pk1 = ? AND pk2 = ? AND ... +** SELECT *, FROM zDb.zTab WHERE (pk1, pk2,...) IS (?1, ?2,...) +** +** where is: +** +** 1 AND (?A OR ?1 IS ) AND ... +** +** for each non-pk . */ static int sessionSelectStmt( sqlite3 *db, /* Database handle */ + int bIgnoreNoop, const char *zDb, /* Database name */ const char *zTab, /* Table name */ int nCol, /* Number of columns in table */ @@ -2399,8 +2429,51 @@ static int sessionSelectStmt( ){ int rc = SQLITE_OK; char *zSql = 0; + const char *zSep = ""; + const char *zCols = "*"; int nSql = -1; + int i; + + SessionBuffer nooptest = {0, 0, 0}; + SessionBuffer pkfield = {0, 0, 0}; + SessionBuffer pkvar = {0, 0, 0}; + + sessionAppendStr(&nooptest, ", 1", &rc); + + if( 0==sqlite3_stricmp("sqlite_stat1", zTab) ){ + sessionAppendStr(&nooptest, " AND (?6 OR ?3 IS stat)", &rc); + sessionAppendStr(&pkfield, "tbl, idx", &rc); + sessionAppendStr(&pkvar, + "?1, (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", &rc + ); + zCols = "tbl, ?2, stat"; + }else{ + for(i=0; izDb, zName, nCol, azCol, abPK, &pSel); + db, 0, pSession->zDb, zName, nCol, azCol, abPK, &pSel + ); } nNoop = buf.nBuf; @@ -3782,6 +3859,7 @@ struct SessionApplyCtx { SessionBuffer rebase; /* Rebase information (if any) here */ u8 bRebaseStarted; /* If table header is already in rebase */ u8 bRebase; /* True to collect rebase information */ + u8 bIgnoreNoop; /* True to ignore no-op conflicts */ }; /* Number of prepared UPDATE statements to cache. */ @@ -4032,8 +4110,9 @@ static int sessionSelectRow( const char *zTab, /* Table name */ SessionApplyCtx *p /* Session changeset-apply context */ ){ - return sessionSelectStmt( - db, "main", zTab, p->nCol, p->azCol, p->abPK, &p->pSelect); + return sessionSelectStmt(db, p->bIgnoreNoop, + "main", zTab, p->nCol, p->azCol, p->abPK, &p->pSelect + ); } /* @@ -4192,20 +4271,33 @@ static int sessionBindRow( */ static int sessionSeekToRow( sqlite3_changeset_iter *pIter, /* Changeset iterator */ - u8 *abPK, /* Primary key flags array */ - sqlite3_stmt *pSelect /* SELECT statement from sessionSelectRow() */ + SessionApplyCtx *p ){ + sqlite3_stmt *pSelect = p->pSelect; int rc; /* Return code */ int nCol; /* Number of columns in table */ int op; /* Changset operation (SQLITE_UPDATE etc.) */ const char *zDummy; /* Unused */ + sqlite3_clear_bindings(pSelect); sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); rc = sessionBindRow(pIter, op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old, - nCol, abPK, pSelect + nCol, p->abPK, pSelect ); + if( op!=SQLITE_DELETE && p->bIgnoreNoop ){ + int ii; + for(ii=0; rc==SQLITE_OK && iiabPK[ii]==0 ){ + sqlite3_value *pVal = 0; + sqlite3changeset_new(pIter, ii, &pVal); + sqlite3_bind_int(pSelect, ii+1+nCol, (pVal==0)); + if( pVal ) rc = sessionBindValue(pSelect, ii+1, pVal); + } + } + } + if( rc==SQLITE_OK ){ rc = sqlite3_step(pSelect); if( rc!=SQLITE_ROW ) rc = sqlite3_reset(pSelect); @@ -4320,16 +4412,22 @@ static int sessionConflictHandler( /* Bind the new.* PRIMARY KEY values to the SELECT statement. */ if( pbReplace ){ - rc = sessionSeekToRow(pIter, p->abPK, p->pSelect); + rc = sessionSeekToRow(pIter, p); }else{ rc = SQLITE_OK; } if( rc==SQLITE_ROW ){ /* There exists another row with the new.* primary key. */ - pIter->pConflict = p->pSelect; - res = xConflict(pCtx, eType, pIter); - pIter->pConflict = 0; + if( p->bIgnoreNoop + && sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1) + ){ + res = SQLITE_CHANGESET_OMIT; + }else{ + pIter->pConflict = p->pSelect; + res = xConflict(pCtx, eType, pIter); + pIter->pConflict = 0; + } rc = sqlite3_reset(p->pSelect); }else if( rc==SQLITE_OK ){ if( p->bDeferConstraints && eType==SQLITE_CHANGESET_CONFLICT ){ @@ -4437,7 +4535,7 @@ static int sessionApplyOneOp( sqlite3_step(p->pDelete); rc = sqlite3_reset(p->pDelete); - if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){ + if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 && p->bIgnoreNoop==0 ){ rc = sessionConflictHandler( SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry ); @@ -4494,7 +4592,7 @@ static int sessionApplyOneOp( /* Check if there is a conflicting row. For sqlite_stat1, this needs ** to be done using a SELECT, as there is no PRIMARY KEY in the ** database schema to throw an exception if a duplicate is inserted. */ - rc = sessionSeekToRow(pIter, p->abPK, p->pSelect); + rc = sessionSeekToRow(pIter, p); if( rc==SQLITE_ROW ){ rc = SQLITE_CONSTRAINT; sqlite3_reset(p->pSelect); @@ -4671,6 +4769,7 @@ static int sessionChangesetApply( memset(&sApply, 0, sizeof(sApply)); sApply.bRebase = (ppRebase && pnRebase); sApply.bInvertConstraints = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); + sApply.bIgnoreNoop = !!(flags & SQLITE_CHANGESETAPPLY_IGNORENOOP); sqlite3_mutex_enter(sqlite3_db_mutex(db)); if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){ rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0); diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index 10d0133901..f3a98d2ecd 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -1243,9 +1243,23 @@ int sqlite3changeset_apply_v2( ** 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. +** +**
SQLITE_CHANGESETAPPLY_IGNORENOOP
+** Do not invoke the conflict handler callback for any changes that +** would not actually modify the database even if they were applied. +** Specifically, this means that the conflict handler is not invoked +** for: +**
    +**
  • a delete change if the row being deleted cannot be found, +**
  • an update change if the modified fields are already set to +** their new values in the conflicting row, or +**
  • an insert change if all fields of the conflicting row match +** the row being inserted. +**
*/ #define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 #define SQLITE_CHANGESETAPPLY_INVERT 0x0002 +#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004 /* ** CAPI3REF: Constants Passed To The Conflict Handler diff --git a/ext/session/test_session.c b/ext/session/test_session.c index 242e0fb0fb..f10afe03f6 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -793,32 +793,31 @@ static int SQLITE_TCLAPI testSqlite3changesetApply( memset(&sStr, 0, sizeof(sStr)); sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); - /* Check for the -nosavepoint flag */ + /* Check for the -nosavepoint, -invert or -ignorenoop switches */ if( bV2 ){ - if( objc>1 ){ + while( 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) ){ + else if( n>2 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){ flags |= SQLITE_CHANGESETAPPLY_INVERT; - objc--; - objv++; } + else if( n>2 && n<=11 && 0==sqlite3_strnicmp("-ignorenoop", z1, n) ){ + flags |= SQLITE_CHANGESETAPPLY_IGNORENOOP; + }else{ + break; + } + objc--; + objv++; } } if( objc!=4 && objc!=5 ){ const char *zMsg; if( bV2 ){ - zMsg = "?-nosavepoint? ?-inverse? " + zMsg = "?-nosavepoint? ?-inverse? ?-ignorenoop? " "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"; }else{ zMsg = "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"; diff --git a/manifest b/manifest index acf807f0bf..57192a45d7 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Small\sperformance\simprovement\sin\sthe\sOP_MakeRecord\sopcode. -D 2023-03-08T17:09:32.272 +C Add\sthe\sSQLITE_CHANGESETAPPLY_IGNORENOOP\sflag,\swhich\smay\sbe\spassed\sto\ssqlite3changeset_apply_v2()\sto\shave\sit\signore\schanges\sthat\swould\sbe\sno-ops\sif\sapplied\sto\sthe\sdatabase\s(e.g.\sdeleting\sa\srow\sthat\shas\salready\sbeen\sdeleted),\sinstead\sof\sconsidering\sthem\sconflicts. +D 2023-03-08T18:03:04.122 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -432,7 +432,7 @@ F ext/session/changeset.c 7a1e6a14c7e92d36ca177e92e88b5281acd709f3b726298dc34ec0 F ext/session/changesetfuzz.c 227076ab0ae4447d742c01ee88a564da6478bbf26b65108bf8fac9cd8b0b24aa F ext/session/changesetfuzz1.test 2e1b90d888fbf0eea5e1bd2f1e527a48cc85f8e0ff75df1ec4e320b21f580b3a F ext/session/session1.test e94f764fbfb672147c0ef7026b195988133b371dc8cf9e52423eba6cad69717e -F ext/session/session2.test 7f53d755d921e0baf815c4258348e0ed460dfd8a772351bca5ad3ccbb1dc786e +F ext/session/session2.test ee83bb973b9ce17ccce4db931cdcdae65eb40bbb22089b2fe6aa4f6be3b9303f F ext/session/session3.test ce9ce3dfa489473987f899e9f6a0f2db9bde3479 F ext/session/session4.test 6778997065b44d99c51ff9cece047ff9244a32856b328735ae27ddef68979c40 F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169 @@ -445,25 +445,25 @@ F ext/session/sessionC.test f8a5508bc059ae646e5ec9bdbca66ad24bc92fe99fda5790ac57 F ext/session/sessionD.test 4f91d0ca8afc4c3969c72c9f0b5ea9527e21de29039937d0d973f821e8470724 F ext/session/sessionE.test b2010949c9d7415306f64e3c2072ddabc4b8250c98478d3c0c4d064bce83111d F ext/session/sessionF.test d37ed800881e742c208df443537bf29aa49fd56eac520d0f0c6df3e6320f3401 -F ext/session/sessionG.test 3828b944cd1285f4379340fd36f8b64c464fc84df6ff3ccbc95578fd87140b9c -F ext/session/sessionH.test b17afdbd3b8f17e9bab91e235acf167cf35485db2ab2df0ea8893fbb914741a4 -F ext/session/session_common.tcl f613174665456b2d916ae8df3e5735092a1c1712f36f46840172e9a01e8cc53e +F ext/session/sessionG.test 3efe388282d641b65485b5462e67851002cd91a282dc95b685d085eb8efdad0a +F ext/session/sessionH.test 71bbff6b1abb2c4ac62b84dee53273c37e0b21e5fde3aed80929403e091ef859 +F ext/session/session_common.tcl db0dda567c75950604072251744e9a6ad5795a3009963c44eb8510f23a8cda64 F ext/session/session_speed_test.c dcf0ef58d76b70c8fbd9eab3be77cf9deb8bc1638fed8be518b62d6cbdef88b3 -F ext/session/sessionat.test 46fd847f6ed194ebb7ebef9fe68b2e2ec88d9c2383a6846cddc5604b35f1d4ae -F ext/session/sessionbig.test 890ade19e3f80f3d3a3e83821ff79c5e2af906a67ecb5450879f0015cadf101e +F ext/session/sessionat.test 00c8badb35e43a2f12a716d2734a44d614ff62361979b6b85419035bc04b45ee +F ext/session/sessionbig.test 47c381e7acfabeef17d98519a3080d69151723354d220afa2053852182ca7adf F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec -F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7 -F ext/session/sessionfault2.test dd593f80b6b4786f7adfe83c5939620bc505559770cc181332da26f29cddd7bb +F ext/session/sessionfault.test 573bf027fb870d57bd4e7cf50822a3e4b17b2b923407438747aaa918dec57a09 +F ext/session/sessionfault2.test b0d6a7c1d7398a7e800d84657404909c7d385965ea8576dc79ed344c46fbf41c F ext/session/sessioninvert.test 04075517a9497a80d39c495ba6b44f3982c7371129b89e2c52219819bc105a25 F ext/session/sessionmem.test f2a735db84a3e9e19f571033b725b0b2daf847f3f28b1da55a0c1a4e74f1de09 -F ext/session/sessionnoop.test a9366a36a95ef85f8a3687856ebef46983df399541174cb1ede2ee53b8011bc7 +F ext/session/sessionnoop.test 5c9a882219e54711c98dccd2fd81392f189a59325e4fb5d8ed25e33a0c2f0ba2 F ext/session/sessionrebase.test ccfa716b23bd1d3b03217ee58cfd90c78d4b99f53e6a9a2f05e82363b9142810 F ext/session/sessionsize.test 6f644aff31c7f1e4871e9ff3542766e18da68fc7e587b83a347ea9820a002dd8 -F ext/session/sessionstat1.test 218d351cf9fcd6648f125a26b607b140310160184723c2666091b54450a68fb5 +F ext/session/sessionstat1.test b039e38e2ba83767b464baf39b297cc0b1cc6f3292255cb467ea7e12d0d0280c F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc -F ext/session/sqlite3session.c 13bdc093416cd284d4075328dd8599eb59bcedc23a21d561a15d78805c5866bf -F ext/session/sqlite3session.h 0907de79bc13a2e3af30a6dc29acc60792a3eaf7d33d44cf52500d0f3c2b2171 -F ext/session/test_session.c 2de472b4d7e62e85ca1992094612725e2450a77dbf7523db64de94197812462e +F ext/session/sqlite3session.c 1795263b72c1a17e48e95a131a69543af3fa31aa8e81271c7c5cb0911f063604 +F ext/session/sqlite3session.h c367c3043dbb57f69cca35258ebbeadb24e8738980b1a1ae1e281c1b0fac3989 +F ext/session/test_session.c b55a669a2150eb7c491b8b42c69a3eed9bc895cf5fea371a2c813b9618f72163 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb @@ -2049,8 +2049,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 6d5b5896261c62a7e130b47416ee8c25793859a2afcb1646c257600537a5b71b -R 1b5d34199b6078f6cf9502fa50e8d5f3 -U drh -Z 1c424b0c5c8b9aa32605259b67546e86 +P ca89daef0fcf6cb04aa6fa90dd333d6f2474bf3f458c833d9cd5bd8e59f2a04a +R b858992430ed0de53c8f23b82b0d78e8 +U dan +Z efdd331472cbc1f3fc97c7776e84ccdd # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index da2cfe9570..e6427110e8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ca89daef0fcf6cb04aa6fa90dd333d6f2474bf3f458c833d9cd5bd8e59f2a04a \ No newline at end of file +cb023fe28560ce0f8c2fd48042553fcdb9db81eba9552be75165de0d46a2645c \ No newline at end of file