Index: ext/session/session1.test ================================================================== --- ext/session/session1.test +++ ext/session/session1.test @@ -18,87 +18,102 @@ source $testdir/tester.tcl ifcapable !session {finish_test; return} set testprefix session1 -do_execsql_test 1.0 { - CREATE TABLE t1(x PRIMARY KEY, y); +# Run all tests in this file twice. Once with "WITHOUT ROWID", and once +# with regular rowid tables. +# +foreach {tn trailing} { + 1 "" + 2 " WITHOUT ROWID " +} { +eval [string map [list %WR% $trailing] { + +db close +forcedelete test.db test.db2 +reset_db + +do_execsql_test $tn.1.0 { + CREATE TABLE t1(x PRIMARY KEY, y) %WR%; INSERT INTO t1 VALUES('abc', 'def'); } #------------------------------------------------------------------------- # Test creating, attaching tables to and deleting session objects. # -do_test 1.1 { sqlite3session S db main } {S} -do_test 1.2 { S delete } {} -do_test 1.3 { sqlite3session S db main } {S} -do_test 1.4 { S attach t1 } {} -do_test 1.5 { S delete } {} -do_test 1.6 { sqlite3session S db main } {S} -do_test 1.7 { S attach t1 ; S attach t2 ; S attach t3 } {} -do_test 1.8 { S attach t1 ; S attach t2 ; S attach t3 } {} -do_test 1.9 { S delete } {} -do_test 1.10 { +do_test $tn.1.1 { sqlite3session S db main } {S} +do_test $tn.1.2 { S delete } {} +do_test $tn.1.3 { sqlite3session S db main } {S} +do_test $tn.1.4 { S attach t1 } {} +do_test $tn.1.5 { S delete } {} +do_test $tn.1.6 { sqlite3session S db main } {S} +do_test $tn.1.7 { S attach t1 ; S attach t2 ; S attach t3 } {} +do_test $tn.1.8 { S attach t1 ; S attach t2 ; S attach t3 } {} +do_test $tn.1.9 { S delete } {} +do_test $tn.1.10 { sqlite3session S db main S attach t1 execsql { INSERT INTO t1 VALUES('ghi', 'jkl') } } {} -do_test 1.11 { S delete } {} -do_test 1.12 { - sqlite3session S db main - S attach t1 - execsql { INSERT INTO t1 VALUES('mno', 'pqr') } - execsql { UPDATE t1 SET x = 111 WHERE rowid = 1 } - execsql { DELETE FROM t1 WHERE rowid = 2 } -} {} -do_test 1.13 { - S changeset - S delete -} {} +do_test $tn.1.11 { S delete } {} +if {$tn==1} { + do_test $tn.1.12 { + sqlite3session S db main + S attach t1 + execsql { INSERT INTO t1 VALUES('mno', 'pqr') } + execsql { UPDATE t1 SET x = 111 WHERE rowid = 1 } + execsql { DELETE FROM t1 WHERE rowid = 2 } + } {} + do_test $tn.1.13 { + S changeset + S delete + } {} +} #------------------------------------------------------------------------- # Simple changeset tests. Also test the sqlite3changeset_invert() # function. # -do_test 2.1.1 { +do_test $tn.2.1.1 { execsql { DELETE FROM t1 } sqlite3session S db main S attach t1 execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') } execsql { INSERT INTO t1 VALUES(2, 'Ayutthaya') } execsql { INSERT INTO t1 VALUES(3, 'Thonburi') } } {} -do_changeset_test 2.1.2 S { +do_changeset_test $tn.2.1.2 S { {INSERT t1 0 X. {} {i 1 t Sukhothai}} {INSERT t1 0 X. {} {i 2 t Ayutthaya}} {INSERT t1 0 X. {} {i 3 t Thonburi}} } -do_changeset_invert_test 2.1.3 S { +do_changeset_invert_test $tn.2.1.3 S { {DELETE t1 0 X. {i 1 t Sukhothai} {}} {DELETE t1 0 X. {i 2 t Ayutthaya} {}} {DELETE t1 0 X. {i 3 t Thonburi} {}} } -do_test 2.1.4 { S delete } {} +do_test $tn.2.1.4 { S delete } {} -do_test 2.2.1 { +do_test $tn.2.2.1 { sqlite3session S db main S attach t1 execsql { DELETE FROM t1 WHERE 1 } } {} -do_changeset_test 2.2.2 S { +do_changeset_test $tn.2.2.2 S { {DELETE t1 0 X. {i 1 t Sukhothai} {}} {DELETE t1 0 X. {i 2 t Ayutthaya} {}} {DELETE t1 0 X. {i 3 t Thonburi} {}} } -do_changeset_invert_test 2.2.3 S { +do_changeset_invert_test $tn.2.2.3 S { {INSERT t1 0 X. {} {i 1 t Sukhothai}} {INSERT t1 0 X. {} {i 2 t Ayutthaya}} {INSERT t1 0 X. {} {i 3 t Thonburi}} } -do_test 2.2.4 { S delete } {} +do_test $tn.2.2.4 { S delete } {} -do_test 2.3.1 { +do_test $tn.2.3.1 { execsql { DELETE FROM t1 } sqlite3session S db main execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') } execsql { INSERT INTO t1 VALUES(2, 'Ayutthaya') } execsql { INSERT INTO t1 VALUES(3, 'Thonburi') } @@ -108,36 +123,36 @@ UPDATE t1 SET y = 'Surin' WHERE x = 2; UPDATE t1 SET x = 20, y = 'Thapae' WHERE x = 3; } } {} -do_changeset_test 2.3.2 S { +do_changeset_test $tn.2.3.2 S { {INSERT t1 0 X. {} {i 10 t Sukhothai}} {DELETE t1 0 X. {i 1 t Sukhothai} {}} {UPDATE t1 0 X. {i 2 t Ayutthaya} {{} {} t Surin}} {DELETE t1 0 X. {i 3 t Thonburi} {}} {INSERT t1 0 X. {} {i 20 t Thapae}} } -do_changeset_invert_test 2.3.3 S { +do_changeset_invert_test $tn.2.3.3 S { {DELETE t1 0 X. {i 10 t Sukhothai} {}} {INSERT t1 0 X. {} {i 1 t Sukhothai}} {UPDATE t1 0 X. {i 2 t Surin} {{} {} t Ayutthaya}} {INSERT t1 0 X. {} {i 3 t Thonburi}} {DELETE t1 0 X. {i 20 t Thapae} {}} } -do_test 2.3.4 { S delete } {} +do_test $tn.2.3.4 { S delete } {} -do_test 2.4.1 { +do_test $tn.2.4.1 { sqlite3session S db main S attach t1 execsql { INSERT INTO t1 VALUES(100, 'Bangkok') } execsql { DELETE FROM t1 WHERE x = 100 } } {} -do_changeset_test 2.4.2 S {} -do_changeset_invert_test 2.4.3 S {} -do_test 2.4.4 { S delete } {} +do_changeset_test $tn.2.4.2 S {} +do_changeset_invert_test $tn.2.4.3 S {} +do_test $tn.2.4.4 { S delete } {} #------------------------------------------------------------------------- # Test the application of simple changesets. These tests also test that # the conflict callback is invoked correctly. For these tests, the # conflict callback always returns OMIT. @@ -187,20 +202,20 @@ uplevel do_test $testname [list "execsql {$sql} db2"] [list [list {*}$result]] } # Test INSERT changesets. # -do_test 3.1.0 { - execsql { CREATE TABLE t1(a PRIMARY KEY, b NOT NULL) } db2 +do_test $tn.3.1.0 { + execsql { CREATE TABLE t1(a PRIMARY KEY, b NOT NULL) %WR% } db2 execsql { - CREATE TABLE t1(a PRIMARY KEY, b); + CREATE TABLE t1(a PRIMARY KEY, b) %WR%; INSERT INTO t1 VALUES(1, 'one'); INSERT INTO t1 VALUES(2, 'two'); } db } {} -do_db2_test 3.1.1 "INSERT INTO t1 VALUES(6, 'VI')" -do_conflict_test 3.1.2 -tables t1 -sql { +do_db2_test $tn.3.1.1 "INSERT INTO t1 VALUES(6, 'VI')" +do_conflict_test $tn.3.1.2 -tables t1 -sql { INSERT INTO t1 VALUES(3, 'three'); INSERT INTO t1 VALUES(4, 'four'); INSERT INTO t1 VALUES(5, 'five'); INSERT INTO t1 VALUES(6, 'six'); INSERT INTO t1 VALUES(7, 'seven'); @@ -208,78 +223,78 @@ } -conflicts { {INSERT t1 CONFLICT {i 6 t six} {i 6 t VI}} {INSERT t1 CONSTRAINT {i 8 n {}}} } -do_db2_test 3.1.3 "SELECT * FROM t1" { - 6 VI 3 three 4 four 5 five 7 seven +do_db2_test $tn.3.1.3 "SELECT * FROM t1 ORDER BY a" { + 3 three 4 four 5 five 6 VI 7 seven } -do_execsql_test 3.1.4 "SELECT * FROM t1" { +do_execsql_test $tn.3.1.4 "SELECT * FROM t1" { 1 one 2 two 3 three 4 four 5 five 6 six 7 seven 8 {} } # Test DELETE changesets. # -do_execsql_test 3.2.1 { +do_execsql_test $tn.3.2.1 { PRAGMA foreign_keys = on; - CREATE TABLE t2(a PRIMARY KEY, b); + CREATE TABLE t2(a PRIMARY KEY, b)%WR%; CREATE TABLE t3(c, d REFERENCES t2); INSERT INTO t2 VALUES(1, 'one'); INSERT INTO t2 VALUES(2, 'two'); INSERT INTO t2 VALUES(3, 'three'); INSERT INTO t2 VALUES(4, 'four'); } -do_db2_test 3.2.2 { +do_db2_test $tn.3.2.2 { PRAGMA foreign_keys = on; - CREATE TABLE t2(a PRIMARY KEY, b); + CREATE TABLE t2(a PRIMARY KEY, b)%WR%; CREATE TABLE t3(c, d REFERENCES t2); INSERT INTO t2 VALUES(1, 'one'); INSERT INTO t2 VALUES(2, 'two'); INSERT INTO t2 VALUES(4, 'five'); INSERT INTO t3 VALUES('i', 1); } -do_conflict_test 3.2.3 -tables t2 -sql { +do_conflict_test $tn.3.2.3 -tables t2 -sql { DELETE FROM t2 WHERE a = 1; DELETE FROM t2 WHERE a = 2; DELETE FROM t2 WHERE a = 3; DELETE FROM t2 WHERE a = 4; } -conflicts { {DELETE t2 NOTFOUND {i 3 t three}} {DELETE t2 DATA {i 4 t four} {i 4 t five}} {FOREIGN_KEY 1} } -do_execsql_test 3.2.4 "SELECT * FROM t2" {} -do_db2_test 3.2.5 "SELECT * FROM t2" {4 five} +do_execsql_test $tn.3.2.4 "SELECT * FROM t2" {} +do_db2_test $tn.3.2.5 "SELECT * FROM t2" {4 five} # Test UPDATE changesets. # -do_execsql_test 3.3.1 { - CREATE TABLE t4(a, b, c, PRIMARY KEY(b, c)); +do_execsql_test $tn.3.3.1 { + CREATE TABLE t4(a, b, c, PRIMARY KEY(b, c))%WR%; INSERT INTO t4 VALUES(1, 2, 3); INSERT INTO t4 VALUES(4, 5, 6); INSERT INTO t4 VALUES(7, 8, 9); INSERT INTO t4 VALUES(10, 11, 12); } -do_db2_test 3.3.2 { - CREATE TABLE t4(a NOT NULL, b, c, PRIMARY KEY(b, c)); +do_db2_test $tn.3.3.2 { + CREATE TABLE t4(a NOT NULL, b, c, PRIMARY KEY(b, c))%WR%; INSERT INTO t4 VALUES(0, 2, 3); INSERT INTO t4 VALUES(4, 5, 7); INSERT INTO t4 VALUES(7, 8, 9); INSERT INTO t4 VALUES(10, 11, 12); } -do_conflict_test 3.3.3 -tables t4 -sql { +do_conflict_test $tn.3.3.3 -tables t4 -sql { UPDATE t4 SET a = -1 WHERE b = 2; UPDATE t4 SET a = -1 WHERE b = 5; UPDATE t4 SET a = NULL WHERE c = 9; UPDATE t4 SET a = 'x' WHERE b = 11; } -conflicts { {UPDATE t4 DATA {i 1 i 2 i 3} {i -1 {} {} {} {}} {i 0 i 2 i 3}} {UPDATE t4 NOTFOUND {i 4 i 5 i 6} {i -1 {} {} {} {}}} {UPDATE t4 CONSTRAINT {i 7 i 8 i 9} {n {} {} {} {} {}}} } -do_db2_test 3.3.4 { SELECT * FROM t4 } {0 2 3 4 5 7 7 8 9 x 11 12} -do_execsql_test 3.3.5 { SELECT * FROM t4 } {-1 2 3 -1 5 6 {} 8 9 x 11 12} +do_db2_test $tn.3.3.4 { SELECT * FROM t4 } {0 2 3 4 5 7 7 8 9 x 11 12} +do_execsql_test $tn.3.3.5 { SELECT * FROM t4 } {-1 2 3 -1 5 6 {} 8 9 x 11 12} #------------------------------------------------------------------------- # This next block of tests verifies that values returned by the conflict # handler are intepreted correctly. # @@ -295,20 +310,20 @@ proc xConflict {args} { lappend ::xConflict $args return $::conflict_return } -foreach {tn conflict_return after} { +foreach {tn2 conflict_return after} { 1 OMIT {1 2 value1 4 5 7 10 x x} 2 REPLACE {1 2 value1 4 5 value2 10 8 9} } { test_reset - do_test 4.$tn.1 { + do_test $tn.4.$tn2.1 { foreach db {db db2} { execsql { - CREATE TABLE t1(a, b, c, PRIMARY KEY(a)); + CREATE TABLE t1(a, b, c, PRIMARY KEY(a))%WR%; INSERT INTO t1 VALUES(1, 2, 3); INSERT INTO t1 VALUES(4, 5, 6); INSERT INTO t1 VALUES(7, 8, 9); } $db } @@ -316,32 +331,32 @@ REPLACE INTO t1 VALUES(4, 5, 7); REPLACE INTO t1 VALUES(10, 'x', 'x'); } db2 } {} - do_conflict_test 4.$tn.2 -tables t1 -sql { + do_conflict_test $tn.4.$tn2.2 -tables t1 -sql { UPDATE t1 SET c = 'value1' WHERE a = 1; -- no conflict UPDATE t1 SET c = 'value2' WHERE a = 4; -- DATA conflict UPDATE t1 SET a = 10 WHERE a = 7; -- CONFLICT conflict } -conflicts { {INSERT t1 CONFLICT {i 10 i 8 i 9} {i 10 t x t x}} {UPDATE t1 DATA {i 4 {} {} i 6} {{} {} {} {} t value2} {i 4 i 5 i 7}} } - do_db2_test 4.$tn.3 "SELECT * FROM t1 ORDER BY a" $after + do_db2_test $tn.4.$tn2.3 "SELECT * FROM t1 ORDER BY a" $after } -foreach {tn conflict_return} { +foreach {tn2 conflict_return} { 1 OMIT 2 REPLACE } { test_reset - do_test 5.$tn.1 { + do_test $tn.5.$tn2.1 { # Create an identical schema in both databases. set schema { - CREATE TABLE "'foolish name'"(x, y, z, PRIMARY KEY(x, y)); + CREATE TABLE "'foolish name'"(x, y, z, PRIMARY KEY(x, y))%WR%; } execsql $schema db execsql $schema db2 # Add some rows to [db2]. These rows will cause conflicts later @@ -352,26 +367,26 @@ INSERT INTO "'foolish name'" VALUES('two', 'two', 'ii'); } db2 } {} - do_conflict_test 5.$tn.2 -tables {{'foolish name'}} -sql { + do_conflict_test $tn.5.$tn2.2 -tables {{'foolish name'}} -sql { INSERT INTO "'foolish name'" VALUES('one', 'two', 2); } -conflicts { {INSERT {'foolish name'} CONFLICT {t one t two i 2} {t one t two t i}} } set res(REPLACE) {one one ii one two 2 two two ii} set res(OMIT) {one one ii one two i two two ii} - do_db2_test 5.$tn.3 { + do_db2_test $tn.5.$tn2.3 { SELECT * FROM "'foolish name'" ORDER BY x, y } $res($conflict_return) - do_test 5.$tn.1 { + do_test $tn.5.$tn2.1 { set schema { - CREATE TABLE d1("z""z" PRIMARY KEY, y); + CREATE TABLE d1("z""z" PRIMARY KEY, y)%WR%; INSERT INTO d1 VALUES(1, 'one'); INSERT INTO d1 VALUES(2, 'two'); } execsql $schema db execsql $schema db2 @@ -380,61 +395,61 @@ UPDATE d1 SET y = 'TWO' WHERE "z""z" = 2; } db2 } {} - do_conflict_test 5.$tn.2 -tables d1 -sql { + do_conflict_test $tn.5.$tn2.2 -tables d1 -sql { DELETE FROM d1 WHERE "z""z" = 2; } -conflicts { {DELETE d1 DATA {i 2 t two} {i 2 t TWO}} } set res(REPLACE) {1 one} set res(OMIT) {1 one 2 TWO} - do_db2_test 5.$tn.3 "SELECT * FROM d1" $res($conflict_return) + do_db2_test $tn.5.$tn2.3 "SELECT * FROM d1" $res($conflict_return) } #------------------------------------------------------------------------- # Test that two tables can be monitored by a single session object. # test_reset set schema { - CREATE TABLE t1(a COLLATE nocase PRIMARY KEY, b); - CREATE TABLE t2(a, b PRIMARY KEY); + CREATE TABLE t1(a COLLATE nocase PRIMARY KEY, b)%WR%; + CREATE TABLE t2(a, b PRIMARY KEY)%WR%; } -do_test 6.0 { +do_test $tn.6.0 { execsql $schema db execsql $schema db2 execsql { INSERT INTO t1 VALUES('a', 'b'); INSERT INTO t2 VALUES('a', 'b'); } db2 } {} set conflict_return "" -do_conflict_test 6.1 -tables {t1 t2} -sql { +do_conflict_test $tn.6.1 -tables {t1 t2} -sql { INSERT INTO t1 VALUES('1', '2'); INSERT INTO t1 VALUES('A', 'B'); INSERT INTO t2 VALUES('A', 'B'); } -conflicts { {INSERT t1 CONFLICT {t A t B} {t a t b}} } -do_db2_test 6.2 "SELECT * FROM t1" {a b 1 2} -do_db2_test 6.3 "SELECT * FROM t2" {a b A B} +do_db2_test $tn.6.2 "SELECT * FROM t1 ORDER BY a" {1 2 a b} +do_db2_test $tn.6.3 "SELECT * FROM t2 ORDER BY a" {A B a b} #------------------------------------------------------------------------- # Test that session objects are not confused by changes to table in # other databases. # catch { db2 close } drop_all_tables forcedelete test.db2 -do_iterator_test 7.1 * { +do_iterator_test $tn.7.1 * { ATTACH 'test.db2' AS aux; - CREATE TABLE main.t1(x PRIMARY KEY, y); - CREATE TABLE aux.t1(x PRIMARY KEY, y); + CREATE TABLE main.t1(x PRIMARY KEY, y)%WR%; + CREATE TABLE aux.t1(x PRIMARY KEY, y)%WR%; INSERT INTO main.t1 VALUES('one', 1); INSERT INTO main.t1 VALUES('two', 2); INSERT INTO aux.t1 VALUES('three', 3); INSERT INTO aux.t1 VALUES('four', 4); @@ -444,76 +459,76 @@ } #------------------------------------------------------------------------- # Test the sqlite3session_isempty() function. # -do_test 8.1 { +do_test $tn.8.1 { execsql { - CREATE TABLE t5(x PRIMARY KEY, y); - CREATE TABLE t6(x PRIMARY KEY, y); + CREATE TABLE t5(x PRIMARY KEY, y)%WR%; + CREATE TABLE t6(x PRIMARY KEY, y)%WR%; INSERT INTO t5 VALUES('a', 'b'); INSERT INTO t6 VALUES('a', 'b'); } sqlite3session S db main S attach * S isempty } {1} -do_test 8.2 { +do_test $tn.8.2 { execsql { DELETE FROM t5 } S isempty } {0} -do_test 8.3 { +do_test $tn.8.3 { S delete sqlite3session S db main S attach t5 execsql { DELETE FROM t5 } S isempty } {1} -do_test 8.4 { S delete } {} +do_test $tn.8.4 { S delete } {} -do_test 8.5 { +do_test $tn.8.5 { sqlite3session S db main S attach t5 S attach t6 execsql { INSERT INTO t5 VALUES(1, 2) } S isempty } {0} -do_test 8.6 { +do_test $tn.8.6 { S delete sqlite3session S db main S attach t5 S attach t6 execsql { INSERT INTO t6 VALUES(1, 2) } S isempty } {0} -do_test 8.7 { S delete } {} +do_test $tn.8.7 { S delete } {} #------------------------------------------------------------------------- # -do_execsql_test 9.1 { - CREATE TABLE t7(a, b, c, d, e PRIMARY KEY, f, g); +do_execsql_test $tn.9.1 { + CREATE TABLE t7(a, b, c, d, e PRIMARY KEY, f, g)%WR%; INSERT INTO t7 VALUES(1, 1, 1, 1, 1, 1, 1); } -do_test 9.2 { +do_test $tn.9.2 { sqlite3session S db main S attach * execsql { UPDATE t7 SET b=2, d=2 } } {} -do_changeset_test 9.2 S {{UPDATE t7 0 ....X.. {{} {} i 1 {} {} i 1 i 1 {} {} {} {}} {{} {} i 2 {} {} i 2 {} {} {} {} {} {}}}} +do_changeset_test $tn.9.2 S {{UPDATE t7 0 ....X.. {{} {} i 1 {} {} i 1 i 1 {} {} {} {}} {{} {} i 2 {} {} i 2 {} {} {} {} {} {}}}} S delete catch { db2 close } #------------------------------------------------------------------------- # Test a really long table name. # reset_db set tblname [string repeat tblname123 100] -do_test 10.1.1 { +do_test $tn.10.1.1 { execsql " - CREATE TABLE $tblname (a PRIMARY KEY, b); + CREATE TABLE $tblname (a PRIMARY KEY, b)%WR%; INSERT INTO $tblname VALUES('xyz', 'def'); " sqlite3session S db main S attach $tblname execsql " @@ -520,22 +535,22 @@ INSERT INTO $tblname VALUES('uvw', 'abc'); DELETE FROM $tblname WHERE a = 'xyz'; " } {} breakpoint -do_changeset_test 10.1.2 S " +do_changeset_test $tn.10.1.2 S " {INSERT $tblname 0 X. {} {t uvw t abc}} {DELETE $tblname 0 X. {t xyz t def} {}} " -do_test 10.1.4 { S delete } {} +do_test $tn.10.1.4 { S delete } {} #--------------------------------------------------------------- reset_db -do_execsql_test 11.1 { +do_execsql_test $tn.11.1 { CREATE TABLE t1(a, b); } -do_test 11.2 { +do_test $tn.11.2 { sqlite3session S db main S attach t1 execsql { INSERT INTO t1 VALUES(1, 2); } @@ -548,13 +563,13 @@ #------------------------------------------------------------------------- # Test a really long table name. # reset_db set tblname [string repeat tblname123 100] -do_test 10.1.1 { +do_test $tn.10.1.1 { execsql " - CREATE TABLE $tblname (a PRIMARY KEY, b); + CREATE TABLE $tblname (a PRIMARY KEY, b)%WR%; INSERT INTO $tblname VALUES('xyz', 'def'); " sqlite3session S db main S attach $tblname execsql " @@ -561,44 +576,46 @@ INSERT INTO $tblname VALUES('uvw', 'abc'); DELETE FROM $tblname WHERE a = 'xyz'; " } {} breakpoint -do_changeset_test 10.1.2 S " +do_changeset_test $tn.10.1.2 S " {INSERT $tblname 0 X. {} {t uvw t abc}} {DELETE $tblname 0 X. {t xyz t def} {}} " -do_test 10.1.4 { S delete } {} +do_test $tn.10.1.4 { S delete } {} #------------------------------------------------------------------------- # Test the effect of updating a column from 0.0 to 0.0. # reset_db -do_execsql_test 11.1 { - CREATE TABLE t1(a INTEGER PRIMARY KEY, b REAL); +do_execsql_test $tn.11.1 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b REAL)%WR%; INSERT INTO t1 VALUES(1, 0.0); } -do_iterator_test 11.2 * { +do_iterator_test $tn.11.2 * { UPDATE t1 SET b = 0.0; } { } reset_db -do_execsql_test 12.1 { - CREATE TABLE t1(r INTEGER PRIMARY KEY, a, b); +do_execsql_test $tn.12.1 { + CREATE TABLE t1(r INTEGER PRIMARY KEY, a, b)%WR%; CREATE INDEX i1 ON t1(a); INSERT INTO t1 VALUES(1, 1, 1); INSERT INTO t1 VALUES(2, 1, 2); INSERT INTO t1 VALUES(3, 1, 3); } -do_iterator_test 12.2 * { +do_iterator_test $tn.12.2 * { UPDATE t1 SET b='one' WHERE a=1; } { {UPDATE t1 0 X.. {i 1 {} {} i 1} {{} {} {} {} t one}} {UPDATE t1 0 X.. {i 2 {} {} i 2} {{} {} {} {} t one}} {UPDATE t1 0 X.. {i 3 {} {} i 3} {{} {} {} {} t one}} } +}] +} finish_test ADDED ext/session/session_speed_test.c Index: ext/session/session_speed_test.c ================================================================== --- /dev/null +++ ext/session/session_speed_test.c @@ -0,0 +1,360 @@ +/* +** 2017 January 31 +** +** 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 contains the source code for a standalone program used to +** test the performance of the sessions module. Compile and run: +** +** ./session_speed_test -help +** +** for details. +*/ + +#include "sqlite3.h" +#include +#include +#include +#include +#include + +/************************************************************************* +** Start of generic command line parser. +*/ +#define CMDLINE_BARE 0 +#define CMDLINE_INTEGER 1 +#define CMDLINE_STRING 2 +#define CMDLINE_BOOLEAN 3 + +typedef struct CmdLineOption CmdLineOption; +struct CmdLineOption { + const char *zText; /* Name of command line option */ + const char *zHelp; /* Help text for option */ + int eType; /* One of the CMDLINE_* values */ + int iOff; /* Offset of output variable */ +}; + +#define CMDLINE_INT32(x,y,z) {x, y, CMDLINE_INTEGER, z} +#define CMDLINE_BOOL(x,y,z) {x, y, CMDLINE_BOOLEAN, z} +#define CMDLINE_TEXT(x,y,z) {x, y, CMDLINE_STRING, z} +#define CMDLINE_NONE(x,y,z) {x, y, CMDLINE_BARE, z} + +static void option_requires_argument_error(CmdLineOption *pOpt){ + fprintf(stderr, "Option requires a%s argument: %s\n", + pOpt->eType==CMDLINE_INTEGER ? "n integer" : + pOpt->eType==CMDLINE_STRING ? " string" : " boolean", + pOpt->zText + ); + exit(1); +} + +static void ambiguous_option_error(const char *zArg){ + fprintf(stderr, "Option is ambiguous: %s\n", zArg); + exit(1); +} + +static void unknown_option_error( + const char *zArg, + CmdLineOption *aOpt, + const char *zHelp +){ + int i; + fprintf(stderr, "Unknown option: %s\n", zArg); + fprintf(stderr, "\nOptions are:\n"); + fprintf(stderr, " % -30sEcho command line options\n", "-cmdline:verbose"); + for(i=0; aOpt[i].zText; i++){ + int eType = aOpt[i].eType; + char *zOpt = sqlite3_mprintf("%s %s", aOpt[i].zText, + eType==CMDLINE_BARE ? "" : + eType==CMDLINE_INTEGER ? "N" : + eType==CMDLINE_BOOLEAN ? "BOOLEAN" : "TEXT" + ); + fprintf(stderr, " % -30s%s\n", zOpt, aOpt[i].zHelp); + sqlite3_free(zOpt); + } + if( zHelp ){ + fprintf(stderr, "\n%s\n", zHelp); + } + exit(1); +} + +static int get_integer_option(CmdLineOption *pOpt, const char *zArg){ + int i = 0; + int iRet = 0; + int bSign = 1; + if( zArg[0]=='-' ){ + bSign = -1; + i = 1; + } + while( zArg[i] ){ + if( zArg[i]<'0' || zArg[i]>'9' ) option_requires_argument_error(pOpt); + iRet = iRet*10 + (zArg[i] - '0'); + i++; + } + return (iRet*bSign); +} + +static int get_boolean_option(CmdLineOption *pOpt, const char *zArg){ + if( 0==sqlite3_stricmp(zArg, "true") ) return 1; + if( 0==sqlite3_stricmp(zArg, "1") ) return 1; + if( 0==sqlite3_stricmp(zArg, "0") ) return 0; + if( 0==sqlite3_stricmp(zArg, "false") ) return 0; + option_requires_argument_error(pOpt); + return 0; +} + +static void parse_command_line( + int argc, + char **argv, + int iStart, + CmdLineOption *aOpt, + void *pStruct, + const char *zHelp +){ + char *pOut = (char*)pStruct; + int bVerbose = 0; + int iArg; + + for(iArg=iStart; iArgzText, zArg, nArg) ){ + if( nMatch ){ + ambiguous_option_error(zArg); + } + nMatch++; + if( pOpt->eType==CMDLINE_BARE ){ + *(int*)(&pOut[pOpt->iOff]) = 1; + }else{ + iArg++; + if( iArg==argc ){ + option_requires_argument_error(pOpt); + } + switch( pOpt->eType ){ + case CMDLINE_INTEGER: + *(int*)(&pOut[pOpt->iOff]) = get_integer_option(pOpt, argv[iArg]); + break; + case CMDLINE_STRING: + *(const char**)(&pOut[pOpt->iOff]) = argv[iArg]; + break; + case CMDLINE_BOOLEAN: + *(int*)(&pOut[pOpt->iOff]) = get_boolean_option(pOpt, argv[iArg]); + break; + } + } + } + } + + if( nMatch==0 && 0==sqlite3_strnicmp("-cmdline:verbose", zArg, nArg) ){ + bVerbose = 1; + nMatch = 1; + } + + if( nMatch==0 ){ + unknown_option_error(zArg, aOpt, zHelp); + } + } + + if( bVerbose ){ + int iOpt; + fprintf(stdout, "Options are: "); + for(iOpt=0; aOpt[iOpt].zText; iOpt++){ + CmdLineOption *pOpt = &aOpt[iOpt]; + if( pOpt->eType!=CMDLINE_BARE || *(int*)(&pOut[pOpt->iOff]) ){ + fprintf(stdout, "%s ", pOpt->zText); + } + switch( pOpt->eType ){ + case CMDLINE_INTEGER: + fprintf(stdout, "%d ", *(int*)(&pOut[pOpt->iOff])); + break; + case CMDLINE_BOOLEAN: + fprintf(stdout, "%d ", *(int*)(&pOut[pOpt->iOff])); + break; + case CMDLINE_STRING: + fprintf(stdout, "%s ", *(const char**)(&pOut[pOpt->iOff])); + break; + } + } + fprintf(stdout, "\n"); + } +} +/* +** End of generic command line parser. +*************************************************************************/ + +static void abort_due_to_error(int rc){ + fprintf(stderr, "Error: %d\n"); + exit(-1); +} + +static void execsql(sqlite3 *db, const char *zSql){ + int rc = sqlite3_exec(db, zSql, 0, 0, 0); + if( rc!=SQLITE_OK ) abort_due_to_error(rc); +} + +static int xConflict(void *pCtx, int eConflict, sqlite3_changeset_iter *p){ + return SQLITE_CHANGESET_ABORT; +} + +static void run_test( + sqlite3 *db, + sqlite3 *db2, + int nRow, + const char *zSql +){ + sqlite3_session *pSession = 0; + sqlite3_stmt *pStmt = 0; + int rc; + int i; + int nChangeset; + void *pChangeset; + + /* Attach a session object to database db */ + rc = sqlite3session_create(db, "main", &pSession); + if( rc!=SQLITE_OK ) abort_due_to_error(rc); + + /* Configure the session to capture changes on all tables */ + rc = sqlite3session_attach(pSession, 0); + if( rc!=SQLITE_OK ) abort_due_to_error(rc); + + /* Prepare the SQL statement */ + rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ) abort_due_to_error(rc); + + /* Open a transaction */ + execsql(db, "BEGIN"); + + /* Execute the SQL statement nRow times */ + for(i=0; ipPartIdxWhere ){ sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2); VdbeCoverage(v); } - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i], - aRegIdx[i]+1, - pIdx->uniqNotNull ? pIdx->nKeyCol: pIdx->nColumn); - pik_flags = 0; - if( useSeekResult ) pik_flags = OPFLAG_USESEEKRESULT; + pik_flags = (useSeekResult ? OPFLAG_USESEEKRESULT : 0); if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ assert( pParse->nested==0 ); pik_flags |= OPFLAG_NCHANGE; pik_flags |= (update_flags & OPFLAG_SAVEPOSITION); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( update_flags==0 ){ + sqlite3VdbeAddOp4(v, OP_InsertInt, + iIdxCur+i, aRegIdx[i], 0, (char*)pTab, P4_TABLE + ); + sqlite3VdbeChangeP5(v, OPFLAG_ISNOOP); + } +#endif } + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i], + aRegIdx[i]+1, + pIdx->uniqNotNull ? pIdx->nKeyCol: pIdx->nColumn); sqlite3VdbeChangeP5(v, pik_flags); } if( !HasRowid(pTab) ) return; regData = regNewData + 1; regRec = sqlite3GetTempReg(pParse); Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -4382,11 +4382,11 @@ assert( memIsValid(pData) ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->eCurType==CURTYPE_BTREE ); assert( pC->uc.pCursor!=0 ); - assert( pC->isTable ); + assert( (pOp->p5 & OPFLAG_ISNOOP) || pC->isTable ); assert( pOp->p4type==P4_TABLE || pOp->p4type>=P4_STATIC ); REGISTER_TRACE(pOp->p2, pData); if( pOp->opcode==OP_Insert ){ pKey = &aMem[pOp->p3]; @@ -4398,15 +4398,14 @@ assert( pOp->opcode==OP_InsertInt ); x.nKey = pOp->p3; } if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){ - assert( pC->isTable ); assert( pC->iDb>=0 ); zDb = db->aDb[pC->iDb].zDbSName; pTab = pOp->p4.pTab; - assert( HasRowid(pTab) ); + assert( (pOp->p5 & OPFLAG_ISNOOP) || HasRowid(pTab) ); op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT); }else{ pTab = 0; /* Not needed. Silence a comiler warning. */ zDb = 0; /* Not needed. Silence a compiler warning. */ } @@ -4417,10 +4416,11 @@ && pOp->p4type==P4_TABLE && !(pOp->p5 & OPFLAG_ISUPDATE) ){ sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey, pOp->p2); } + if( pOp->p5 & OPFLAG_ISNOOP ) break; #endif if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey; if( pData->flags & MEM_Null ){ @@ -4529,12 +4529,15 @@ pTab = 0; /* Not needed. Silence a compiler warning. */ } #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* Invoke the pre-update-hook if required. */ - if( db->xPreUpdateCallback && pOp->p4.pTab && HasRowid(pTab) ){ - assert( !(opflags & OPFLAG_ISUPDATE) || (aMem[pOp->p3].flags & MEM_Int) ); + if( db->xPreUpdateCallback && pOp->p4.pTab ){ + assert( !(opflags & OPFLAG_ISUPDATE) + || HasRowid(pTab)==0 + || (aMem[pOp->p3].flags & MEM_Int) + ); sqlite3VdbePreUpdateHook(p, pC, (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, zDb, pTab, pC->movetoTarget, pOp->p3 ); Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -432,10 +432,11 @@ int iNewReg; /* Register for new.* values */ i64 iKey1; /* First key value passed to hook */ i64 iKey2; /* Second key value passed to hook */ Mem *aNew; /* Array of new.* values */ Table *pTab; /* Schema object being upated */ + Index *pPk; /* PK index if pTab is WITHOUT ROWID */ }; /* ** Function prototypes */ Index: src/vdbeapi.c ================================================================== --- src/vdbeapi.c +++ src/vdbeapi.c @@ -1667,10 +1667,13 @@ ** SQLITE_UPDATE pre-update callback, and that iIdx is within range. */ if( !p || p->op==SQLITE_INSERT ){ rc = SQLITE_MISUSE_BKPT; goto preupdate_old_out; } + if( p->pPk ){ + iIdx = sqlite3ColumnOfIndex(p->pPk, iIdx); + } if( iIdx>=p->pCsr->nField || iIdx<0 ){ rc = SQLITE_RANGE; goto preupdate_old_out; } @@ -1752,10 +1755,13 @@ if( !p || p->op==SQLITE_DELETE ){ rc = SQLITE_MISUSE_BKPT; goto preupdate_new_out; } + if( p->pPk && p->op!=SQLITE_UPDATE ){ + iIdx = sqlite3ColumnOfIndex(p->pPk, iIdx); + } if( iIdx>=p->pCsr->nField || iIdx<0 ){ rc = SQLITE_RANGE; goto preupdate_new_out; } Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -4618,14 +4618,19 @@ const char *zTbl = pTab->zName; static const u8 fakeSortOrder = 0; assert( db->pPreUpdate==0 ); memset(&preupdate, 0, sizeof(PreUpdate)); - if( op==SQLITE_UPDATE ){ - iKey2 = v->aMem[iReg].u.i; + if( HasRowid(pTab)==0 ){ + iKey1 = iKey2 = 0; + preupdate.pPk = sqlite3PrimaryKeyIndex(pTab); }else{ - iKey2 = iKey1; + if( op==SQLITE_UPDATE ){ + iKey2 = v->aMem[iReg].u.i; + }else{ + iKey2 = iKey1; + } } assert( pCsr->nField==pTab->nCol || (pCsr->nField==pTab->nCol+1 && op==SQLITE_DELETE && iReg==-1) ); ADDED test/hook2.test Index: test/hook2.test ================================================================== --- /dev/null +++ test/hook2.test @@ -0,0 +1,218 @@ +# 2017 Jan 30 +# +# 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. +# +#*********************************************************************** +# The tests in this file focus on the pre-update hook. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix hook2 + +ifcapable !preupdate { + finish_test + return +} + +#------------------------------------------------------------------------- +proc do_preupdate_test {tn sql x} { + set X [list] + foreach elem $x {lappend X $elem} + uplevel do_test $tn [list " + set ::preupdate \[list\] + execsql { $sql } + set ::preupdate + "] [list $X] +} + +proc preupdate_hook {args} { + set type [lindex $args 0] + eval lappend ::preupdate $args + if {$type != "INSERT"} { + for {set i 0} {$i < [db preupdate count]} {incr i} { + lappend ::preupdate [db preupdate old $i] + } + } + if {$type != "DELETE"} { + for {set i 0} {$i < [db preupdate count]} {incr i} { + set rc [catch { db preupdate new $i } v] + lappend ::preupdate $v + } + } +} + +#------------------------------------------------------------------------- +# Simple tests - INSERT, UPDATE and DELETE on a WITHOUT ROWID table. +# +db preupdate hook preupdate_hook +do_execsql_test 1.0 { + CREATE TABLE t1(a PRIMARY KEY, b) WITHOUT ROWID; +} +do_preupdate_test 1.1 { + INSERT INTO t1 VALUES('one', 1); +} { + INSERT main t1 0 0 one 1 +} +do_preupdate_test 1.2 { + UPDATE t1 SET b=2 WHERE a='one'; +} { + UPDATE main t1 0 0 one 1 one 2 +} +do_preupdate_test 1.3 { + DELETE FROM t1 WHERE a='one'; +} { + DELETE main t1 0 0 one 2 +} + +#------------------------------------------------------------------------- +# Some more complex tests for the pre-update callback on WITHOUT ROWID +# tables. +# +# 2.1.1 - INSERT statement. +# 2.1.2 - INSERT INTO ... SELECT statement. +# 2.1.3 - REPLACE INTO ... (PK conflict) +# 2.1.4 - REPLACE INTO ... (other index conflicts) +# 2.1.5 - REPLACE INTO ... (both PK and other index conflicts) +# +# 2.2.1 - DELETE statement. +# 2.2.2 - DELETE statement that uses the truncate optimization. +# +# 2.3.1 - UPDATE statement. +# 2.3.2 - UPDATE statement that modifies the PK. +# 2.3.3 - UPDATE OR REPLACE ... (PK conflict). +# 2.3.4 - UPDATE OR REPLACE ... (other index conflicts) +# 2.3.4 - UPDATE OR REPLACE ... (both PK and other index conflicts) +# +do_execsql_test 2.0 { + CREATE TABLE t2(a DEFAULT 4, b, c, PRIMARY KEY(b, c)) WITHOUT ROWID; + CREATE UNIQUE INDEX t2a ON t2(a); +} + +do_preupdate_test 2.1.1 { + INSERT INTO t2(b, c) VALUES(1, 1); +} { + INSERT main t2 0 0 4 1 1 +} + +do_execsql_test 2.1.2.0 { + CREATE TABLE d1(a DEFAULT 4, b, c, PRIMARY KEY(b, c)) WITHOUT ROWID; + CREATE UNIQUE INDEX d1a ON d1(a); + INSERT INTO d1 VALUES(1, 2, 3); + INSERT INTO d1 VALUES(11, 12, 13); +} +do_preupdate_test 2.1.2.1 { + INSERT INTO t2 SELECT * FROM d1; +} { + INSERT main t2 0 0 1 2 3 + INSERT main t2 0 0 11 12 13 +} +do_preupdate_test 2.1.2.2 { + INSERT INTO t2 SELECT a+20, b+20, c+20 FROM d1; +} { + INSERT main t2 0 0 21 22 23 + INSERT main t2 0 0 31 32 33 +} +do_execsql_test 2.1.2.3 { + SELECT * FROM t2 ORDER BY b, c; +} { + 4 1 1 + 1 2 3 + 11 12 13 + 21 22 23 + 31 32 33 +} +do_preupdate_test 2.1.3 { + REPLACE INTO t2 VALUES(45, 22, 23); +} { + DELETE main t2 0 0 21 22 23 + INSERT main t2 0 0 45 22 23 +} +do_preupdate_test 2.1.4 { + REPLACE INTO t2 VALUES(11, 100, 100); +} { + DELETE main t2 0 0 11 12 13 + INSERT main t2 0 0 11 100 100 +} +do_preupdate_test 2.1.5 { + REPLACE INTO t2(c, b) VALUES(33, 32) +} { + DELETE main t2 0 0 4 1 1 + DELETE main t2 0 0 31 32 33 + INSERT main t2 0 0 4 32 33 +} + +do_execsql_test 2.2.0 { + SELECT * FROM t2 ORDER BY b,c; +} { + 1 2 3 + 45 22 23 + 4 32 33 + 11 100 100 +} +do_preupdate_test 2.2.1 { + DELETE FROM t2 WHERE b=22; +} { + DELETE main t2 0 0 45 22 23 +} +do_preupdate_test 2.2.2 { + DELETE FROM t2; +} { + DELETE main t2 0 0 1 2 3 + DELETE main t2 0 0 4 32 33 + DELETE main t2 0 0 11 100 100 +} + +do_execsql_test 2.3.0 { + CREATE TABLE t3(x, y PRIMARY KEY, z UNIQUE) WITHOUT ROWID; + INSERT INTO t3 VALUES('a', 'b', 'c'); + INSERT INTO t3 VALUES('d', 'e', 'f'); + + INSERT INTO t3 VALUES(1, 1, 1); + INSERT INTO t3 VALUES(2, 2, 2); + INSERT INTO t3 VALUES(3, 3, 3); +} + +do_preupdate_test 2.3.1 { + UPDATE t3 SET x=4 WHERE y IN ('b', 'e', 'x'); +} { + UPDATE main t3 0 0 a b c 4 b c + UPDATE main t3 0 0 d e f 4 e f +} + +do_preupdate_test 2.3.2 { + UPDATE t3 SET y=y||y WHERE z IN('c', 'f'); +} { + UPDATE main t3 0 0 4 b c 4 bb c + UPDATE main t3 0 0 4 e f 4 ee f +} + +do_preupdate_test 2.3.3 { + UPDATE OR REPLACE t3 SET y='bb' WHERE z='f' +} { + DELETE main t3 0 0 4 bb c + UPDATE main t3 0 0 4 ee f 4 bb f +} + +do_preupdate_test 2.3.4 { + UPDATE OR REPLACE t3 SET z=2 WHERE y=1; +} { + DELETE main t3 0 0 2 2 2 + UPDATE main t3 0 0 1 1 1 1 1 2 +} + +do_preupdate_test 2.3.5 { + UPDATE OR REPLACE t3 SET z=2, y='bb' WHERE y=3; +} { + DELETE main t3 0 0 1 1 2 + DELETE main t3 0 0 4 bb f + UPDATE main t3 0 0 3 3 3 3 bb 2 +} + + +finish_test