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
This commit is contained in:
dan 2023-03-08 18:03:04 +00:00
parent 7ba63831f8
commit 975f2062da
15 changed files with 364 additions and 227 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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
)

View File

@ -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} {

View File

@ -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 {

View File

@ -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) );

View File

@ -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;

View File

@ -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';

View File

@ -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.
#
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);
}
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]]
}
do_concat_test 1.1 {
UPDATE t1 SET c=c+1;
} {
UPDATE t1 SET c=c-1;
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');
}
#-------------------------------------------------------------------------
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 -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_rebase_test {tn sql_local sql_remote conflict_res expected} {
proc xConflict {args} [list return $conflict_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');
}]
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]
]
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_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_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 {
}
#-------------------------------------------------------------------------
#
reset_db
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
}
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
}
proc xConflict {args} { return "ABORT" }
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 db $C xConflict
do_test $tn.1.4 {
set ::conflict_list [list]
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_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
forcedelete test.db2
sqlite3 db2 test.db2
do_execsql_test $tn.2.0 {
CREATE TABLE t1(a PRIMARY KEY, b) %WO%;
}
do_execsql_test -db db2 $tn.2.1 {
CREATE TABLE t1(a PRIMARY KEY, b, c DEFAULT 'val') %WO%;
}
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
}
} {}
}]
}
db2 close
#-------------------------------------------------------------------------
reset_db
forcedelete test.db2
do_execsql_test 3.0 {
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';
}
set C [changeset_from_sql { ANALYZE }]
sqlite3 db2 test.db2
set ::conflict_list [list]
proc xConflict {args} { lappend ::conflict_list $args ; return "OMIT" }
do_test 3.1 {
sqlite3changeset_apply_v2 db2 $C xConflict
set ::conflict_list
} {}
do_test 3.2 {
sqlite3changeset_apply_v2 -ignorenoop db2 $C xConflict
set ::conflict_list
} {}
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}}}
}]

View File

@ -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
} {}

View File

@ -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 *, <noop-test> FROM zDb.zTab WHERE (pk1, pk2,...) IS (?1, ?2,...)
**
** where <noop-test> is:
**
** 1 AND (?A OR ?1 IS <column>) AND ...
**
** for each non-pk <column>.
*/
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; i<nCol; i++){
if( abPK[i] ){
sessionAppendStr(&pkfield, zSep, &rc);
sessionAppendStr(&pkvar, zSep, &rc);
zSep = ", ";
sessionAppendIdent(&pkfield, azCol[i], &rc);
sessionAppendPrintf(&pkvar, &rc, "?%d", i+1);
}else{
sessionAppendPrintf(&nooptest, &rc,
" AND (?%d OR ?%d IS %w.%w)", i+1+nCol, i+1, zTab, azCol[i]
);
}
}
}
if( rc==SQLITE_OK ){
zSql = sqlite3_mprintf(
"SELECT %s%s FROM %Q.%Q WHERE (%s) IS (%s)",
zCols, (bIgnoreNoop ? (char*)nooptest.aBuf : ""),
zDb, zTab, (char*)pkfield.aBuf, (char*)pkvar.aBuf
);
if( zSql==0 ) rc = SQLITE_NOMEM;
}
#if 0
if( 0==sqlite3_stricmp("sqlite_stat1", zTab) ){
zSql = sqlite3_mprintf(
"SELECT tbl, ?2, stat FROM %Q.sqlite_stat1 WHERE tbl IS ?1 AND "
@ -2408,7 +2481,6 @@ static int sessionSelectStmt(
);
if( zSql==0 ) rc = SQLITE_NOMEM;
}else{
int i;
const char *zSep = "";
SessionBuffer buf = {0, 0, 0};
@ -2429,11 +2501,15 @@ static int sessionSelectStmt(
zSql = (char*)buf.aBuf;
nSql = buf.nBuf;
}
#endif
if( rc==SQLITE_OK ){
rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0);
}
sqlite3_free(zSql);
sqlite3_free(nooptest.aBuf);
sqlite3_free(pkfield.aBuf);
sqlite3_free(pkvar.aBuf);
return rc;
}
@ -2593,7 +2669,8 @@ static int sessionGenerateChangeset(
/* Build and compile a statement to execute: */
if( rc==SQLITE_OK ){
rc = sessionSelectStmt(
db, pSession->zDb, 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 && ii<nCol; ii++){
if( p->abPK[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);

View File

@ -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.
**
** <dt>SQLITE_CHANGESETAPPLY_IGNORENOOP <dd>
** 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:
** <ul>
** <li>a delete change if the row being deleted cannot be found,
** <li>an update change if the modified fields are already set to
** their new values in the conflicting row, or
** <li>an insert change if all fields of the conflicting row match
** the row being inserted.
** </ul>
*/
#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001
#define SQLITE_CHANGESETAPPLY_INVERT 0x0002
#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004
/*
** CAPI3REF: Constants Passed To The Conflict Handler

View File

@ -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?";

View File

@ -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.

View File

@ -1 +1 @@
ca89daef0fcf6cb04aa6fa90dd333d6f2474bf3f458c833d9cd5bd8e59f2a04a
cb023fe28560ce0f8c2fd48042553fcdb9db81eba9552be75165de0d46a2645c