Add tests for the new code on this branch.
[sqlite.git] / ext / session / sessionB.test
blob2c103d5e36dd7adcc064ba338464108562b70f4f
1 # 2014 August 16
3 # The author disclaims copyright to this source code.  In place of
4 # a legal notice, here is a blessing:
6 #    May you do good and not evil.
7 #    May you find forgiveness for yourself and forgive others.
8 #    May you share freely, never taking more than you give.
10 #***********************************************************************
12 # This file implements regression tests for sessions SQLite extension.
13 # Specifically, this file contains tests for "patchset" changes.
16 if {![info exists testdir]} {
17   set testdir [file join [file dirname [info script]] .. .. test]
18
19 source [file join [file dirname [info script]] session_common.tcl]
20 source $testdir/tester.tcl
21 ifcapable !session {finish_test; return}
23 set testprefix sessionB
26 # 1.*: Test that the blobs returned by the session_patchset() API are 
27 #      as expected. Also the sqlite3_changeset_iter functions.
29 # 2.*: Test that patchset blobs are handled by sqlite3changeset_apply().
31 # 3.*: Test that sqlite3changeset_invert() works with patchset blobs. 
32 #      Correct behaviour is to return SQLITE_CORRUPT.
34 proc do_sql2patchset_test {tn sql res} {
35   sqlite3session S db main
36   S attach *
37   execsql $sql
38   uplevel [list do_patchset_test $tn S $res]
39   S delete
42 #-------------------------------------------------------------------------
43 # Run simple tests of the _patchset() API.
45 do_execsql_test 1.0 {
46   CREATE TABLE t1(a, b, c, d, PRIMARY KEY(d, a));
47   INSERT INTO t1 VALUES(1, 2, 3, 4);
48   INSERT INTO t1 VALUES(5, 6, 7, 8);
49   INSERT INTO t1 VALUES(9, 10, 11, 12);
52 do_test 1.1 {
53   sqlite3session S db main
54   S attach t1
55   execsql {
56     INSERT INTO t1 VALUES('w', 'x', 'y', 'z');
57     DELETE FROM t1 WHERE d=4;
58     UPDATE t1 SET c = 14 WHERE a=5;
59   }
60 } {}
62 do_patchset_test 1.2 S {
63   {UPDATE t1 0 X..X {i 5 {} {} {} {} i 8} {{} {} {} {} i 14 {} {}}}
64   {INSERT t1 0 X..X {} {t w t x t y t z}}
65   {DELETE t1 0 X..X {i 1 {} {} {} {} i 4} {}}
68 do_test 1.3 {
69   S delete
70 } {}
72 do_sql2patchset_test 1.4 {
73   DELETE FROM t1;
74 } {
75   {DELETE t1 0 X..X {i 5 {} {} {} {} i 8} {}}
76   {DELETE t1 0 X..X {t w {} {} {} {} t z} {}}
77   {DELETE t1 0 X..X {i 9 {} {} {} {} i 12} {}}
80 do_sql2patchset_test 1.5 {
81   INSERT INTO t1 VALUES(X'61626364', NULL, NULL, 4.2);
82   INSERT INTO t1 VALUES(4.2, NULL, NULL, X'61626364');
83 } {
84   {INSERT t1 0 X..X {} {f 4.2 n {} n {} b abcd}} 
85   {INSERT t1 0 X..X {} {b abcd n {} n {} f 4.2}}
88 do_sql2patchset_test 1.6 {
89   UPDATE t1 SET b=45 WHERE typeof(a)=='blob';
90   UPDATE t1 SET c='zzzz' WHERE typeof(a)!='blob';
91 } {
92   {UPDATE t1 0 X..X {f 4.2 {} {} {} {} b abcd} {{} {} {} {} t zzzz {} {}}}
93   {UPDATE t1 0 X..X {b abcd {} {} {} {} f 4.2} {{} {} i 45 {} {} {} {}}}
96 do_sql2patchset_test 1.7 {
97   UPDATE t1 SET b='xyz' WHERE typeof(a)=='blob';
98   UPDATE t1 SET c='xyz' WHERE typeof(a)!='blob';
99   UPDATE t1 SET b=45 WHERE typeof(a)=='blob';
100   UPDATE t1 SET c='zzzz' WHERE typeof(a)!='blob';
101 } {
104 do_sql2patchset_test 1.8 {
105   DELETE FROM t1;
106 } {
107   {DELETE t1 0 X..X {f 4.2 {} {} {} {} b abcd} {}} 
108   {DELETE t1 0 X..X {b abcd {} {} {} {} f 4.2} {}}
111 #-------------------------------------------------------------------------
112 # Run simple tests of _apply() with patchset objects.
114 reset_db
116 proc noop {args} { error $args }
117 proc exec_rollback_replay {sql} {
118   sqlite3session S db main
119   S attach *
120   execsql BEGIN
121   execsql $sql
122   set patchset [S patchset]
123   S delete
124   execsql ROLLBACK
125   sqlite3changeset_apply db $patchset noop
128 do_execsql_test 2.0 {
129   CREATE TABLE t2(a, b, c, d, PRIMARY KEY(b,c));
130   CREATE TABLE t3(w, x, y, z, PRIMARY KEY(w));
133 do_test 2.1 {
134   exec_rollback_replay {
135     INSERT INTO t2 VALUES(1, 2, 3, 4);
136     INSERT INTO t2 VALUES('w', 'x', 'y', 'z');
137   }
138   execsql { SELECT * FROM t2 }
139 } {1 2 3 4 w x y z}
141 do_test 2.2 {
142   exec_rollback_replay {
143     DELETE FROM t2 WHERE a=1;
144     UPDATE t2 SET d = 'a';
145   }
146   execsql { SELECT * FROM t2 }
147 } {w x y a}
149 #-------------------------------------------------------------------------
150 # sqlite3changeset_invert()
152 reset_db
154 do_execsql_test 3.1 { CREATE TABLE t1(x PRIMARY KEY, y) }
155 do_test 3.2 {
156   sqlite3session S db main
157   S attach *
158   execsql { INSERT INTO t1 VALUES(1, 2) }
159   set patchset [S patchset]
160   S delete
161   list [catch { sqlite3changeset_invert $patchset } msg] [set msg]
162 } {1 SQLITE_CORRUPT}
165 #-------------------------------------------------------------------------
166 # sqlite3changeset_concat()
168 reset_db
170 proc do_patchconcat_test {tn args} {
171   set bRevert 0
172   if {[lindex $args 0] == "-revert"} {
173     set bRevert 1
174     set args [lrange $args 1 end]
175   }
176   set nSql [expr [llength $args]-1]
177   set res [lindex $args $nSql]
178   set patchlist [list]
180   execsql BEGIN
181   if {$bRevert} { execsql { SAVEPOINT x } }
182   foreach sql [lrange $args 0 end-1] {
183     sqlite3session S db main
184     S attach *
185     execsql $sql
186     lappend patchlist [S patchset]
187     S delete
188     if {$bRevert} { execsql { ROLLBACK TO x } }
189   }
190   execsql ROLLBACK
192   set patch [lindex $patchlist 0]
193   foreach p [lrange $patchlist 1 end] {
194     set patch [sqlite3changeset_concat $patch $p]
195   }
197   set x [list]
198   sqlite3session_foreach c $patch { lappend x $c }
200   uplevel [list do_test $tn [list set {} $x] [list {*}$res]]
203 do_execsql_test 4.1.1 {
204   CREATE TABLE t1(x PRIMARY KEY, y, z);
206 do_patchconcat_test 4.1.2 {
207   INSERT INTO t1 VALUES(1, 2, 3);
208 } {
209   INSERT INTO t1 VALUES(4, 5, 6);
210 } {
211   {INSERT t1 0 X.. {} {i 1 i 2 i 3}} 
212   {INSERT t1 0 X.. {} {i 4 i 5 i 6}}
215 do_execsql_test 4.2.1 {
216   INSERT INTO t1 VALUES(1, 2, 3);
217   INSERT INTO t1 VALUES(4, 5, 6);
220 do_patchconcat_test 4.2.2 {
221   UPDATE t1 SET z = 'abc' WHERE x=1
222 } {
223   UPDATE t1 SET z = 'def' WHERE x=4
224 } {
225   {UPDATE t1 0 X.. {i 1 {} {} {} {}} {{} {} {} {} t abc}} 
226   {UPDATE t1 0 X.. {i 4 {} {} {} {}} {{} {} {} {} t def}}
229 do_patchconcat_test 4.2.3 {
230   DELETE FROM t1 WHERE x=1;
231 } {
232   DELETE FROM t1 WHERE x=4;
233 } {
234   {DELETE t1 0 X.. {i 1 {} {} {} {}} {}} 
235   {DELETE t1 0 X.. {i 4 {} {} {} {}} {}}
239 do_execsql_test 4.3.1 {
240   CREATE TABLE t2(a, b, c, d, PRIMARY KEY(c, b));
241   INSERT INTO t2 VALUES('.', 1, 1, '.');
242   INSERT INTO t2 VALUES('.', 1, 2, '.');
243   INSERT INTO t2 VALUES('.', 2, 1, '.');
244   INSERT INTO t2 VALUES('.', 2, 2, '.');
247 # INSERT + INSERT 
248 do_patchconcat_test 4.3.2 -revert {
249   INSERT INTO t2 VALUES('a', 'a', 'a', 'a');
250 } {
251   INSERT INTO t2 VALUES('b', 'a', 'a', 'b');
252 } {
253   {INSERT t2 0 .XX. {} {t a t a t a t a}}
256 # INSERT + DELETE 
257 do_patchconcat_test 4.3.3 {
258   INSERT INTO t2 VALUES('a', 'a', 'a', 'a');
259 } {
260   DELETE FROM t2 WHERE c = 'a';
261 } {}
263 # INSERT + UPDATE
264 do_patchconcat_test 4.3.4 {
265   INSERT INTO t2 VALUES('a', 'a', 'a', 'a');
266 } {
267   UPDATE t2 SET d = 'b' WHERE c='a';
268 } {
269   {INSERT t2 0 .XX. {} {t a t a t a t b}}
272 # UPDATE + UPDATE
273 do_patchconcat_test 4.3.5 {
274   UPDATE t2 SET a = 'a' WHERE c=1 AND b=2;
275 } {
276   UPDATE t2 SET d = 'd' WHERE c=1 AND b=2;
277 } {
278   {UPDATE t2 0 .XX. {{} {} i 2 i 1 {} {}} {t a {} {} {} {} t d}}
281 # UPDATE + DELETE
282 do_patchconcat_test 4.3.6 {
283   UPDATE t2 SET a = 'a' WHERE c=1 AND b=2;
284 } {
285   DELETE FROM t2 WHERE c=1 AND b=2;
286 } {
287   {DELETE t2 0 .XX. {{} {} i 2 i 1 {} {}} {}}
290 # DELETE + INSERT
291 do_patchconcat_test 4.3.7 {
292   DELETE FROM t2 WHERE b=1;
293 } {
294   INSERT INTO t2 VALUES('x', 1, 2, '.');
295 } {
296   {DELETE t2 0 .XX. {{} {} i 1 i 1 {} {}} {}} 
297   {UPDATE t2 0 .XX. {{} {} i 1 i 2 {} {}} {t x {} {} {} {} t .}}
300 # DELETE + UPDATE
301 do_patchconcat_test 4.3.8 -revert {
302   DELETE FROM t2 WHERE b=1 AND c=2;
303 } {
304   UPDATE t2 SET a=5 WHERE b=1 AND c=2;
305 } {
306   {DELETE t2 0 .XX. {{} {} i 1 i 2 {} {}} {}} 
309 # DELETE + UPDATE
310 do_patchconcat_test 4.3.9 -revert {
311   DELETE FROM t2 WHERE b=1 AND c=2;
312 } {
313   DELETE FROM t2 WHERE b=1;
314 } {
315   {DELETE t2 0 .XX. {{} {} i 1 i 1 {} {}} {}} 
316   {DELETE t2 0 .XX. {{} {} i 1 i 2 {} {}} {}} 
319 #-------------------------------------------------------------------------
320 # More rigorous testing of the _patchset(), _apply and _concat() APIs.
322 # The inputs to each test are a populate database and a list of DML 
323 # statements. This test determines that the final database is the same
324 # if:
326 #   1) the statements are executed directly on the database.
328 #   2) a single patchset is collected while executing the statements and
329 #      then applied to a copy of the original database file.
331 #   3) individual patchsets are collected for statement while executing
332 #      them and concatenated together before being applied to a copy of
333 #      the original database. The concatenation is done in a couple of
334 #      different ways - linear, pairwise etc.
336 # All tests, as it happens, are run with both changesets and patchsets.
337 # But the focus is on patchset capabilities.
340 # Return a checksum of the contents of the database file. Implicit IPK
341 # columns are not included in the checksum - just modifying rowids does
342 # not change the database checksum.
344 proc databasecksum {db} {
345   set alltab [$db eval {SELECT name FROM sqlite_master WHERE type='table'}]
346   foreach tab $alltab {
347     $db eval "SELECT * FROM $tab LIMIT 1" res { }
348     set slist [list]
349     foreach col [lsort $res(*)] {
350       lappend slist "quote($col)"
351     }
352     set sql "SELECT [join $slist ,] FROM $tab"
353     append txt "[lsort [$db eval $sql]]\n"
354   }
355   return [md5 $txt]
358 proc do_patchset_test {tn tstcmd lSql} {
359   if {$tstcmd != "patchset" && $tstcmd != "changeset"} {
360     error "have $tstcmd: must be patchset or changeset"
361   }
363   foreach fname {test.db2 test.db3 test.db4 test.db5} {
364     forcedelete $fname
365     forcecopy test.db $fname
366   }
368   # Execute the SQL statements on [db]. Collect a patchset for each 
369   # individual statement, as well as a single patchset for the entire 
370   # operation.
371   sqlite3session S db main
372   S attach *
373   foreach sql $lSql { 
374     sqlite3session T db main
375     T attach *
376     db eval $sql 
377     lappend lPatch [T $tstcmd]
378     T delete
379   }
380   set patchset [S $tstcmd]
381   S delete
383   # Calculate a checksum for the final database.
384   set cksum [databasecksum db]
386   # 1. Apply the single large patchset to test.db2
387   sqlite3 db2 test.db2
388   sqlite3changeset_apply db2 $patchset noop
389   uplevel [list do_test $tn.1 { databasecksum db2 } $cksum ]
390   db2 close
391   
392   # 2. Apply each of the single-statement patchsets to test.db3
393   sqlite3 db2 test.db3
394   foreach p $lPatch {
395     sqlite3changeset_apply db2 $p noop
396   }
397   uplevel [list do_test $tn.2 { databasecksum db2 } $cksum ]
398   db2 close
400   # 3. Concatenate all single-statement patchsets into a single large
401   #    patchset, then apply it to test.db4.
402   #
403   sqlite3 db2 test.db4
404   set big ""
405   foreach p $lPatch {
406     set big [sqlite3changeset_concat $big $p]
407   }
408   sqlite3changeset_apply db2 $big noop
409   uplevel [list do_test $tn.3 { databasecksum db2 } $cksum ]
410   db2 close
412   # 4. Concatenate all single-statement patchsets pairwise into a single
413   #    large patchset, then apply it to test.db5. Pairwise concatenation:
414   #
415   #         a b c d e f g h i j k
416   #      -> {a b} {c d} {e f} {g h} {i j} k
417   #      -> {a b c d} {e f g h} {i j k}
418   #      -> {a b c d e f g h} {i j k}
419   #      -> {a b c d e f g h i j k}
420   #      -> APPLY!
421   #
422   sqlite3 db2 test.db5
423   set L $lPatch
424   while {[llength $L] > 1} {
425     set O [list]
426     for {set i 0} {$i < [llength $L]} {incr i 2} {
427       if {$i==[llength $L]-1} {
428         lappend O [lindex $L $i]
429       } else {
430         set i1 [expr $i+1]
431         lappend O [sqlite3changeset_concat [lindex $L $i] [lindex $L $i1]]
432       }
433     }
434     set L $O
435   }
436   sqlite3changeset_apply db2 [lindex $L 0] noop
437   uplevel [list do_test $tn.4 { databasecksum db2 } $cksum ]
438   db2 close
441 proc do_patchset_changeset_test {tn initsql args} {
442   foreach tstcmd {patchset changeset} {
443     reset_db
444     execsql $initsql
445     set x 0
446     foreach sql $args {
447       incr x
448       set lSql [split $sql ";"]
449       uplevel [list do_patchset_test $tn.$tstcmd.$x $tstcmd $lSql]
450     }
451   }
454 do_patchset_changeset_test 5.1 {
455   CREATE TABLE t1(a PRIMARY KEY, b, c);
456   INSERT INTO t1 VALUES(1, 2, 3);
457 } {
458   INSERT INTO t1 VALUES(4, 5, 6);
459   DELETE FROM t1 WHERE a=1;
460 } {
461   INSERT INTO t1 VALUES(7, 8, 9);
462   UPDATE t1 SET c = 5;
463   INSERT INTO t1 VALUES(10, 11, 12);
464   UPDATE t1 SET c = 6;
465   INSERT INTO t1 VALUES(13, 14, 15);
466 } {
467   UPDATE t1 SET c=c+1;
468   DELETE FROM t1 WHERE (a%2);
471 do_patchset_changeset_test 5.2 {
472   CREATE TABLE t1(a PRIMARY KEY, b, c);
473   CREATE TABLE t2(a, b, c, d, PRIMARY KEY(c, b));
474 } {
475   INSERT INTO t1 VALUES(x'00', 0, 'zero');
476   INSERT INTO t1 VALUES(x'01', 1, 'one');
477   INSERT INTO t1 VALUES(x'02', 4, 'four');
478   INSERT INTO t1 VALUES(x'03', 9, 'nine');
479   INSERT INTO t1 VALUES(x'04', 16, 'sixteen');
480   INSERT INTO t1 VALUES(x'05', 25, 'twenty-five');
481 } {
482   UPDATE t1 SET a = b WHERE b<=4;
483   INSERT INTO t2 SELECT NULL, * FROM t1;
484   DELETE FROM t1 WHERE b=25;
485 } {
486   DELETE FROM t2;
487   INSERT INTO t2 SELECT NULL, * FROM t1;
488   DELETE FROM t1;
489   INSERT INTO t1 SELECT b, c, d FROM t2;
490   UPDATE t1 SET b = b+1;
491   UPDATE t1 SET b = b+1;
492   UPDATE t1 SET b = b+1;
495 set initsql { CREATE TABLE t1(a, b, c, PRIMARY KEY(c, b)); }
496 for {set i 0} {$i < 1000} {incr i} {
497   append insert "INSERT INTO t1 VALUES($i, $i, $i);"
498   append delete "DELETE FROM t1 WHERE b=$i;"
500 do_patchset_changeset_test 5.3 \
501   $initsql $insert $delete     \
502   $insert $delete              \
503   "$insert $delete"            \
504   $delete
507 finish_test