sqlite/ext/session/sessionrebase.test
dan 655a1b7b58 Allow the sessions module to be configured to capture changes from tables with no explicit PRIMARY KEY.
FossilOrigin-Name: 8a612f0860126c0c8473b1e65fcabb9a8821d8bf09fdf3f6018acfc99df9af71
2023-04-24 19:22:21 +00:00

553 lines
13 KiB
Plaintext

# 2018 March 14
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library.
#
if {![info exists testdir]} {
set testdir [file join [file dirname [info script]] .. .. test]
}
source [file join [file dirname [info script]] session_common.tcl]
source $testdir/tester.tcl
ifcapable !session {finish_test; return}
set testprefix sessionrebase
set ::lConflict [list]
proc xConflict {args} {
set res [lindex $::lConflict 0]
set ::lConflict [lrange $::lConflict 1 end]
return $res
}
#-------------------------------------------------------------------------
# The following test cases - 1.* - test that the rebase blobs output by
# sqlite3_changeset_apply_v2 look correct in some simple cases. The blob
# is itself a changeset, containing records determined as follows:
#
# * For each conflict resolved with REPLACE, the rebase blob contains
# a DELETE record. All fields other than the PK fields are undefined.
#
# * For each conflict resolved with OMIT, the rebase blob contains an
# INSERT record. For an INSERT or UPDATE operation, the indirect flag
# is clear and all updated fields are defined. For a DELETE operation,
# the indirect flag is set and all non-PK fields left undefined.
#
proc do_apply_v2_test {tn sql modsql conflict_handler res} {
execsql BEGIN
sqlite3session S db main
S attach *
execsql $sql
set changeset [S changeset]
S delete
execsql ROLLBACK
execsql BEGIN
execsql $modsql
set ::lConflict $conflict_handler
set blob [sqlite3changeset_apply_v2 db $changeset xConflict]
execsql ROLLBACK
uplevel [list do_test $tn [list changeset_to_list $blob] [list {*}$res]]
}
set ::lConflict [list]
proc xConflict {args} {
set res [lindex $::lConflict 0]
set ::lConflict [lrange $::lConflict 1 end]
return $res
}
# Take a copy of database test.db in file test.db2. Execute $sql1
# against test.db and $sql2 against test.db2. Capture a changeset
# for each. Then send the test.db2 changeset to test.db and apply
# it with the conflict handlers in $conflict_handler. Patch the
# test.db changeset and then execute it against test.db2. Test that
# the two databases come out the same.
#
proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} {
for {set i 1} {$i <= 2} {incr i} {
forcedelete test.db2 test.db2-journal test.db2-wal
forcecopy test.db test.db2
sqlite3 db2 test.db2
db eval BEGIN
sqlite3session S1 db main
S1 object_config rowid 1
S1 attach *
execsql $sql1 db
set c1 [S1 changeset]
S1 delete
if {$i==1} {
sqlite3session S2 db2 main
S2 object_config rowid 1
S2 attach *
execsql $sql2 db2
set c2 [S2 changeset]
S2 delete
} else {
set c2 [list]
foreach sql [split $sql2 ";"] {
if {[string is space $sql]} continue
sqlite3session S2 db2 main
S2 object_config rowid 1
S2 attach *
execsql $sql db2
lappend c2 [S2 changeset]
S2 delete
}
}
set ::lConflict $conflict_handler
set rebase [list]
if {$i==1} {
lappend rebase [sqlite3changeset_apply_v2 db $c2 xConflict]
} else {
foreach c $c2 {
#puts "apply_v2: [changeset_to_list $c]"
lappend rebase [sqlite3changeset_apply_v2 db $c xConflict]
}
#puts "llength: [llength $rebase]"
}
#if {$tn=="2.1.4"} { puts [changeset_to_list $rebase] ; breakpoint }
#puts [changeset_to_list [lindex $rebase 0]] ; breakpoint
#puts [llength $rebase]
sqlite3rebaser_create R
foreach r $rebase {
#puts [changeset_to_list $r]
R configure $r
}
set c1r [R rebase $c1]
R delete
#if {$tn=="2.1.4"} { puts [changeset_to_list $c1r] }
sqlite3changeset_apply_v2 db2 $c1r xConflictAbort
if {[string range $tn end end]!="*"} {
uplevel [list do_test $tn.$i.1 [list compare_db db db2] {}]
}
db2 close
if {$testsql!=""} {
uplevel [list do_execsql_test $tn.$i.2 $testsql $testres]
}
db eval ROLLBACK
}
}
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
INSERT INTO t1 VALUES(1, 'value A');
}
do_apply_v2_test 1.1.1 {
UPDATE t1 SET b = 'value B' WHERE a=1;
} {
UPDATE t1 SET b = 'value C' WHERE a=1;
} {
OMIT
} {
{INSERT t1 0 X. {} {i 1 t {value B}}}
}
do_apply_v2_test 1.1.2 {
UPDATE t1 SET b = 'value B' WHERE a=1;
} {
UPDATE t1 SET b = 'value C' WHERE a=1;
} {
REPLACE
} {
{INSERT t1 1 X. {} {i 1 t {value B}}}
}
do_apply_v2_test 1.2.1 {
INSERT INTO t1 VALUES(2, 'first');
} {
INSERT INTO t1 VALUES(2, 'second');
} {
OMIT
} {
{INSERT t1 0 X. {} {i 2 t first}}
}
do_apply_v2_test 1.2.2 {
INSERT INTO t1 VALUES(2, 'first');
} {
INSERT INTO t1 VALUES(2, 'second');
} {
REPLACE
} {
{INSERT t1 1 X. {} {i 2 t first}}
}
do_apply_v2_test 1.3.1 {
DELETE FROM t1 WHERE a=1;
} {
UPDATE t1 SET b='value D' WHERE a=1;
} {
OMIT
} {
{DELETE t1 0 X. {i 1 t {value A}} {}}
}
do_apply_v2_test 1.3.2 {
DELETE FROM t1 WHERE a=1;
} {
UPDATE t1 SET b='value D' WHERE a=1;
} {
REPLACE
} {
{DELETE t1 1 X. {i 1 t {value A}} {}}
}
#-------------------------------------------------------------------------
# Test cases 2.* - simple tests of rebasing actual changesets.
#
# 2.1.1 - 1u2u1r
# 2.1.2 - 1u2u2r
# 2.1.3 - 1d2d
# 2.1.4 - 1d2u1r
# 2.1.5 - 1d2u2r !!
# 2.1.6 - 1u2d1r
# 2.1.7 - 1u2d2r
#
# 2.1.8 - 1i2i2r
# 2.1.9 - 1i2i1r
#
proc xConflictAbort {args} {
return "ABORT"
}
reset_db
do_execsql_test 2.1.0 {
CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
INSERT INTO t1 VALUES(3, 'three');
}
do_rebase_test 2.1.1 {
UPDATE t1 SET b = 'two.1' WHERE a=2
} {
UPDATE t1 SET b = 'two.2' WHERE a=2;
} {
OMIT
} { SELECT * FROM t1 } {1 one 2 two.1 3 three}
do_rebase_test 2.1.2 {
UPDATE t1 SET b = 'two.1' WHERE a=2
} {
UPDATE t1 SET b = 'two.2' WHERE a=2;
} {
REPLACE
} { SELECT * FROM t1 } {1 one 2 two.2 3 three}
do_rebase_test 2.1.3 {
DELETE FROM t1 WHERE a=3
} {
DELETE FROM t1 WHERE a=3;
} {
OMIT
} { SELECT * FROM t1 } {1 one 2 two}
do_rebase_test 2.1.4 {
DELETE FROM t1 WHERE a=1
} {
UPDATE t1 SET b='one.2' WHERE a=1
} {
OMIT
} { SELECT * FROM t1 } {2 two 3 three}
#do_rebase_test 2.1.5 {
# DELETE FROM t1 WHERE a=1;
#} {
# UPDATE t1 SET b='one.2' WHERE a=1
#} {
# REPLACE
#} { SELECT * FROM t1 } {2 two 3 three}
do_rebase_test 2.1.6 {
UPDATE t1 SET b='three.1' WHERE a=3
} {
DELETE FROM t1 WHERE a=3;
} {
OMIT
} { SELECT * FROM t1 } {1 one 2 two 3 three.1}
do_rebase_test 2.1.7 {
UPDATE t1 SET b='three.1' WHERE a=3
} {
DELETE FROM t1 WHERE a=3;
} {
REPLACE
} { SELECT * FROM t1 } {1 one 2 two}
do_rebase_test 2.1.8 {
INSERT INTO t1 VALUES(4, 'four.1')
} {
INSERT INTO t1 VALUES(4, 'four.2');
} {
REPLACE
} { SELECT * FROM t1 } {1 one 2 two 3 three 4 four.2}
do_rebase_test 2.1.9 {
INSERT INTO t1 VALUES(4, 'four.1')
} {
INSERT INTO t1 VALUES(4, 'four.2');
} {
OMIT
} { SELECT * FROM t1 } {1 one 2 two 3 three 4 four.1}
do_execsql_test 2.2.0 {
CREATE TABLE t2(x, y, z PRIMARY KEY);
INSERT INTO t2 VALUES('i', 'a', 'A');
INSERT INTO t2 VALUES('ii', 'b', 'B');
INSERT INTO t2 VALUES('iii', 'c', 'C');
CREATE TABLE t3(a INTEGER PRIMARY KEY, b, c);
INSERT INTO t3 VALUES(-1, 'z', 'Z');
INSERT INTO t3 VALUES(-2, 'y', 'Y');
}
do_rebase_test 2.2.1 {
UPDATE t2 SET x=1 WHERE z='A'
} {
UPDATE t2 SET y='one' WHERE z='A';
} {
} { SELECT * FROM t2 WHERE z='A' } { 1 one A }
do_rebase_test 2.2.2 {
UPDATE t2 SET x=1, y='one' WHERE z='B'
} {
UPDATE t2 SET y='two' WHERE z='B';
} {
REPLACE
} { SELECT * FROM t2 WHERE z='B' } { 1 two B }
do_rebase_test 2.2.3 {
UPDATE t2 SET x=1, y='one' WHERE z='B'
} {
UPDATE t2 SET y='two' WHERE z='B';
} {
OMIT
} { SELECT * FROM t2 WHERE z='B' } { 1 one B }
reset_db
do_execsql_test 2.3.0 {
CREATE TABLE t1 (b TEXT);
INSERT INTO t1(rowid, b) VALUES(1, 'one');
INSERT INTO t1(rowid, b) VALUES(2, 'two');
INSERT INTO t1(rowid, b) VALUES(3, 'three');
}
do_rebase_test 2.3.1 {
UPDATE t1 SET b = 'two.1' WHERE rowid=2
} {
UPDATE t1 SET b = 'two.2' WHERE rowid=2;
} {
OMIT
} { SELECT rowid, * FROM t1 } {1 one 2 two.1 3 three}
do_rebase_test 2.3.2 {
UPDATE t1 SET b = 'two.1' WHERE rowid=2
} {
UPDATE t1 SET b = 'two.2' WHERE rowid=2;
} {
REPLACE
} { SELECT rowid, * FROM t1 } {1 one 2 two.2 3 three}
do_rebase_test 2.3.3 {
DELETE FROM t1 WHERE rowid=3
} {
DELETE FROM t1 WHERE rowid=3;
} {
OMIT
} { SELECT rowid, * FROM t1 } {1 one 2 two}
do_rebase_test 2.3.4 {
DELETE FROM t1 WHERE rowid=1
} {
UPDATE t1 SET b='one.2' WHERE rowid=1
} {
OMIT
} { SELECT rowid, * FROM t1 } {2 two 3 three}
do_rebase_test 2.3.6 {
UPDATE t1 SET b='three.1' WHERE rowid=3
} {
DELETE FROM t1 WHERE rowid=3;
} {
OMIT
} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three.1}
do_rebase_test 2.3.7 {
UPDATE t1 SET b='three.1' WHERE rowid=3
} {
DELETE FROM t1 WHERE rowid=3;
} {
REPLACE
} { SELECT rowid, * FROM t1 } {1 one 2 two}
do_rebase_test 2.3.8 {
INSERT INTO t1(rowid, b) VALUES(4, 'four.1')
} {
INSERT INTO t1(rowid, b) VALUES(4, 'four.2');
} {
REPLACE
} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three 4 four.2}
do_rebase_test 2.3.9 {
INSERT INTO t1(rowid, b) VALUES(4, 'four.1')
} {
INSERT INTO t1(rowid, b) VALUES(4, 'four.2');
} {
OMIT
} { SELECT rowid, * FROM t1 } {1 one 2 two 3 three 4 four.1}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
CREATE TABLE t3(a, b, c, PRIMARY KEY(b, c));
CREATE TABLE abcdefghijkl(x PRIMARY KEY, y, z);
INSERT INTO t3 VALUES(1, 2, 3);
INSERT INTO t3 VALUES(4, 2, 5);
INSERT INTO t3 VALUES(7, 2, 9);
INSERT INTO abcdefghijkl VALUES('a', 'b', 'c');
INSERT INTO abcdefghijkl VALUES('d', 'e', 'f');
INSERT INTO abcdefghijkl VALUES('g', 'h', 'i');
}
breakpoint
# do_rebase_test 3.6.tn {
# UPDATE abcdefghijkl SET z='X', y='X' WHERE x='d';
# } {
# UPDATE abcdefghijkl SET y=1 WHERE x='d';
# UPDATE abcdefghijkl SET z=1 WHERE x='d';
# } [list REPLACE REPLACE REPLACE]
foreach {tn p} {
1 OMIT 2 REPLACE
} {
do_rebase_test 3.1.$tn {
INSERT INTO t3 VALUES(1, 1, 1);
UPDATE abcdefghijkl SET y=2;
} {
INSERT INTO t3 VALUES(4, 1, 1);
DELETE FROM abcdefghijkl;
} [list $p $p $p $p $p $p $p $p]
do_rebase_test 3.2.$tn {
INSERT INTO abcdefghijkl SELECT * FROM t3;
UPDATE t3 SET b=b+1;
} {
INSERT INTO t3 VALUES(3, 3, 3);
INSERT INTO abcdefghijkl SELECT * FROM t3;
} [list $p $p $p $p $p $p $p $p]
do_rebase_test 3.3.$tn {
INSERT INTO abcdefghijkl VALUES(22, 23, 24);
} {
INSERT INTO abcdefghijkl VALUES(22, 25, 26);
UPDATE abcdefghijkl SET y=400 WHERE x=22;
} [list $p $p $p $p $p $p $p $p]
do_rebase_test 3.4.$tn {
INSERT INTO abcdefghijkl VALUES(22, 23, 24);
} {
INSERT INTO abcdefghijkl VALUES(22, 25, 26);
UPDATE abcdefghijkl SET y=400 WHERE x=22;
} [list REPLACE $p]
do_rebase_test 3.5.$tn* {
UPDATE abcdefghijkl SET y='X' WHERE x='d';
} {
DELETE FROM abcdefghijkl WHERE x='d';
INSERT INTO abcdefghijkl VALUES('d', NULL, NULL);
} [list $p $p $p]
do_rebase_test 3.5.$tn {
UPDATE abcdefghijkl SET y='X' WHERE x='d';
} {
DELETE FROM abcdefghijkl WHERE x='d';
INSERT INTO abcdefghijkl VALUES('d', NULL, NULL);
} [list REPLACE $p $p]
do_rebase_test 3.6.$tn {
UPDATE abcdefghijkl SET z='X', y='X' WHERE x='d';
} {
UPDATE abcdefghijkl SET y=1 WHERE x='d';
UPDATE abcdefghijkl SET z=1 WHERE x='d';
} [list REPLACE $p $p]
}
#-------------------------------------------------------------------------
# Check that apply_v2() does not create a rebase buffer for a patchset.
# And that it is not possible to rebase a patchset.
#
do_execsql_test 4.0 {
CREATE TABLE t5(o PRIMARY KEY, p, q);
INSERT INTO t5 VALUES(1, 2, 3);
INSERT INTO t5 VALUES(4, 5, 6);
}
foreach {tn cmd rebasable} {
1 patchset 0
2 changeset 1
} {
proc xConflict {args} { return "OMIT" }
do_test 4.1.$tn {
execsql {
BEGIN;
DELETE FROM t5 WHERE o=4;
}
sqlite3session S db main
S attach *
execsql {
INSERT INTO t5 VALUES(4, 'five', 'six');
}
set P [S $cmd]
S delete
execsql ROLLBACK;
set ::rebase [sqlite3changeset_apply_v2 db $P xConflict]
expr [llength $::rebase]>0
} $rebasable
}
foreach {tn cmd rebasable} {
1 patchset 0
2 changeset 1
} {
do_test 4.2.$tn {
sqlite3session S db main
S attach *
execsql {
INSERT INTO t5 VALUES(5+$tn, 'five', 'six');
}
set P [S $cmd]
S delete
sqlite3rebaser_create R
R configure $::rebase
expr [catch {R rebase $P}]==0
} $rebasable
catch { R delete }
}
finish_test