# 2014 August 16 # # 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 sessions SQLite extension. # Specifically, this file contains tests for "patchset" changes. # 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 sessionB # # 1.*: Test that the blobs returned by the session_patchset() API are # as expected. Also the sqlite3_changeset_iter functions. # # 2.*: Test that patchset blobs are handled by sqlite3changeset_apply(). # # 3.*: Test that sqlite3changeset_invert() works with patchset blobs. # Correct behaviour is to return SQLITE_CORRUPT. proc do_patchset_test {tn session res} { set r [list] foreach x $res {lappend r $x} uplevel do_test $tn [list [subst -nocommands { set x [list] sqlite3session_foreach c [$session patchset] { lappend x [set c] } set x }]] [list $r] } proc do_sql2patchset_test {tn sql res} { sqlite3session S db main S attach * execsql $sql uplevel [list do_patchset_test $tn S $res] S delete } #------------------------------------------------------------------------- # Run simple tests of the _patchset() API. # do_execsql_test 1.0 { CREATE TABLE t1(a, b, c, d, PRIMARY KEY(d, a)); INSERT INTO t1 VALUES(1, 2, 3, 4); INSERT INTO t1 VALUES(5, 6, 7, 8); INSERT INTO t1 VALUES(9, 10, 11, 12); } do_test 1.1 { sqlite3session S db main S attach t1 execsql { INSERT INTO t1 VALUES('w', 'x', 'y', 'z'); DELETE FROM t1 WHERE d=4; UPDATE t1 SET c = 14 WHERE a=5; } } {} do_patchset_test 1.2 S { {UPDATE t1 0 X..X {i 5 {} {} {} {} i 8} {{} {} {} {} i 14 {} {}}} {INSERT t1 0 X..X {} {t w t x t y t z}} {DELETE t1 0 X..X {i 1 {} {} {} {} i 4} {}} } do_test 1.3 { S delete } {} do_sql2patchset_test 1.4 { DELETE FROM t1; } { {DELETE t1 0 X..X {i 5 {} {} {} {} i 8} {}} {DELETE t1 0 X..X {t w {} {} {} {} t z} {}} {DELETE t1 0 X..X {i 9 {} {} {} {} i 12} {}} } do_sql2patchset_test 1.5 { INSERT INTO t1 VALUES(X'61626364', NULL, NULL, 4.2); INSERT INTO t1 VALUES(4.2, NULL, NULL, X'61626364'); } { {INSERT t1 0 X..X {} {f 4.2 n {} n {} b abcd}} {INSERT t1 0 X..X {} {b abcd n {} n {} f 4.2}} } do_sql2patchset_test 1.6 { UPDATE t1 SET b=45 WHERE typeof(a)=='blob'; UPDATE t1 SET c='zzzz' WHERE typeof(a)!='blob'; } { {UPDATE t1 0 X..X {f 4.2 {} {} {} {} b abcd} {{} {} {} {} t zzzz {} {}}} {UPDATE t1 0 X..X {b abcd {} {} {} {} f 4.2} {{} {} i 45 {} {} {} {}}} } do_sql2patchset_test 1.7 { UPDATE t1 SET b='xyz' WHERE typeof(a)=='blob'; UPDATE t1 SET c='xyz' WHERE typeof(a)!='blob'; UPDATE t1 SET b=45 WHERE typeof(a)=='blob'; UPDATE t1 SET c='zzzz' WHERE typeof(a)!='blob'; } { } do_sql2patchset_test 1.8 { DELETE FROM t1; } { {DELETE t1 0 X..X {f 4.2 {} {} {} {} b abcd} {}} {DELETE t1 0 X..X {b abcd {} {} {} {} f 4.2} {}} } #------------------------------------------------------------------------- # Run simple tests of _apply() with patchset objects. # reset_db proc noop {args} { error $args } proc exec_rollback_replay {sql} { sqlite3session S db main S attach * execsql BEGIN execsql $sql set patchset [S patchset] S delete execsql ROLLBACK sqlite3changeset_apply db $patchset noop } do_execsql_test 2.0 { CREATE TABLE t2(a, b, c, d, PRIMARY KEY(b,c)); CREATE TABLE t3(w, x, y, z, PRIMARY KEY(w)); } do_test 2.1 { exec_rollback_replay { INSERT INTO t2 VALUES(1, 2, 3, 4); INSERT INTO t2 VALUES('w', 'x', 'y', 'z'); } execsql { SELECT * FROM t2 } } {1 2 3 4 w x y z} do_test 2.2 { exec_rollback_replay { DELETE FROM t2 WHERE a=1; UPDATE t2 SET d = 'a'; } execsql { SELECT * FROM t2 } } {w x y a} #------------------------------------------------------------------------- # sqlite3changeset_invert() # reset_db do_execsql_test 3.1 { CREATE TABLE t1(x PRIMARY KEY, y) } do_test 3.2 { sqlite3session S db main S attach * execsql { INSERT INTO t1 VALUES(1, 2) } set patchset [S patchset] S delete list [catch { sqlite3changeset_invert $patchset } msg] [set msg] } {1 SQLITE_CORRUPT} #------------------------------------------------------------------------- # sqlite3changeset_concat() # reset_db proc do_patchconcat_test {tn args} { set bRevert 0 if {[lindex $args 0] == "-revert"} { set bRevert 1 set args [lrange $args 1 end] } set nSql [expr [llength $args]-1] set res [lindex $args $nSql] set patchlist [list] execsql BEGIN if {$bRevert} { execsql { SAVEPOINT x } } foreach sql [lrange $args 0 end-1] { sqlite3session S db main S attach * execsql $sql lappend patchlist [S patchset] S delete if {$bRevert} { execsql { ROLLBACK TO x } } } execsql ROLLBACK set patch [lindex $patchlist 0] foreach p [lrange $patchlist 1 end] { set patch [sqlite3changeset_concat $patch $p] } set x [list] sqlite3session_foreach c $patch { lappend x $c } uplevel [list do_test $tn [list set {} $x] [list {*}$res]] } do_execsql_test 4.1.1 { CREATE TABLE t1(x PRIMARY KEY, y, z); } do_patchconcat_test 4.1.2 { INSERT INTO t1 VALUES(1, 2, 3); } { INSERT INTO t1 VALUES(4, 5, 6); } { {INSERT t1 0 X.. {} {i 1 i 2 i 3}} {INSERT t1 0 X.. {} {i 4 i 5 i 6}} } do_execsql_test 4.2.1 { INSERT INTO t1 VALUES(1, 2, 3); INSERT INTO t1 VALUES(4, 5, 6); } do_patchconcat_test 4.2.2 { UPDATE t1 SET z = 'abc' WHERE x=1 } { UPDATE t1 SET z = 'def' WHERE x=4 } { {UPDATE t1 0 X.. {i 1 {} {} {} {}} {{} {} {} {} t abc}} {UPDATE t1 0 X.. {i 4 {} {} {} {}} {{} {} {} {} t def}} } do_patchconcat_test 4.2.3 { DELETE FROM t1 WHERE x=1; } { DELETE FROM t1 WHERE x=4; } { {DELETE t1 0 X.. {i 1 {} {} {} {}} {}} {DELETE t1 0 X.. {i 4 {} {} {} {}} {}} } do_execsql_test 4.3.1 { CREATE TABLE t2(a, b, c, d, PRIMARY KEY(c, b)); INSERT INTO t2 VALUES('.', 1, 1, '.'); INSERT INTO t2 VALUES('.', 1, 2, '.'); INSERT INTO t2 VALUES('.', 2, 1, '.'); INSERT INTO t2 VALUES('.', 2, 2, '.'); } # INSERT + INSERT do_patchconcat_test 4.3.2 -revert { INSERT INTO t2 VALUES('a', 'a', 'a', 'a'); } { INSERT INTO t2 VALUES('b', 'a', 'a', 'b'); } { {INSERT t2 0 .XX. {} {t a t a t a t a}} } # INSERT + DELETE do_patchconcat_test 4.3.3 { INSERT INTO t2 VALUES('a', 'a', 'a', 'a'); } { DELETE FROM t2 WHERE c = 'a'; } { } # INSERT + UPDATE do_patchconcat_test 4.3.4 { INSERT INTO t2 VALUES('a', 'a', 'a', 'a'); } { UPDATE t2 SET d = 'b' WHERE c='a'; } { {INSERT t2 0 .XX. {} {t a t a t a t b}} } # UPDATE + UPDATE do_patchconcat_test 4.3.5 { UPDATE t2 SET a = 'a' WHERE c=1 AND b=2; } { UPDATE t2 SET d = 'd' WHERE c=1 AND b=2; } { {UPDATE t2 0 .XX. {{} {} i 2 i 1 {} {}} {t a {} {} {} {} t d}} } # UPDATE + DELETE do_patchconcat_test 4.3.6 { UPDATE t2 SET a = 'a' WHERE c=1 AND b=2; } { DELETE FROM t2 WHERE c=1 AND b=2; } { {DELETE t2 0 .XX. {{} {} i 2 i 1 {} {}} {}} } # DELETE + INSERT do_patchconcat_test 4.3.7 { DELETE FROM t2 WHERE b=1; } { INSERT INTO t2 VALUES('x', 1, 2, '.'); } { {DELETE t2 0 .XX. {{} {} i 1 i 1 {} {}} {}} {UPDATE t2 0 .XX. {{} {} i 1 i 2 {} {}} {t x {} {} {} {} t .}} } # DELETE + UPDATE do_patchconcat_test 4.3.8 -revert { DELETE FROM t2 WHERE b=1 AND c=2; } { UPDATE t2 SET a=5 WHERE b=1 AND c=2; } { {DELETE t2 0 .XX. {{} {} i 1 i 2 {} {}} {}} } # DELETE + UPDATE do_patchconcat_test 4.3.9 -revert { DELETE FROM t2 WHERE b=1 AND c=2; } { DELETE FROM t2 WHERE b=1; } { {DELETE t2 0 .XX. {{} {} i 1 i 1 {} {}} {}} {DELETE t2 0 .XX. {{} {} i 1 i 2 {} {}} {}} } finish_test