}
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 } {}
}
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;
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');
}
} {}
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');
}
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');
}
# 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;
} {}
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;
} {}
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;
} {}
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;
} {}
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;
}
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);
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
)
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} {
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);
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;
};
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);
}
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 {
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 {
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 {
}
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) );
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) );
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;
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';
-# 2021 Februar 20
+# 2011 March 07
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
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}}}
+}]
} {}
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
)
}
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 {
}
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 {
} {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
} {}
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;
}
}
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
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;
}
*zOut++ = '"';
p->nBuf = (int)((u8 *)zOut - p->aBuf);
+ p->aBuf[p->nBuf] = 0x00;
}
}
** 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 */
){
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 "
);
if( zSql==0 ) rc = SQLITE_NOMEM;
}else{
- int i;
const char *zSep = "";
SessionBuffer buf = {0, 0, 0};
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;
}
/* 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;
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. */
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
+ );
}
/*
*/
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);
/* 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 ){
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
);
/* 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);
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);
** 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
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?";
-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
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
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
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.
-ca89daef0fcf6cb04aa6fa90dd333d6f2474bf3f458c833d9cd5bd8e59f2a04a
\ No newline at end of file
+cb023fe28560ce0f8c2fd48042553fcdb9db81eba9552be75165de0d46a2645c
\ No newline at end of file