Enhance the command-line completion extension to return the names of
[sqlite.git] / ext / session / sessionB.test
blobeae41f76ad17913e71f202cdcd001e5e72d40274
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 } {
264 # INSERT + UPDATE
265 do_patchconcat_test 4.3.4 {
266   INSERT INTO t2 VALUES('a', 'a', 'a', 'a');
267 } {
268   UPDATE t2 SET d = 'b' WHERE c='a';
269 } {
270   {INSERT t2 0 .XX. {} {t a t a t a t b}}
273 # UPDATE + UPDATE
274 do_patchconcat_test 4.3.5 {
275   UPDATE t2 SET a = 'a' WHERE c=1 AND b=2;
276 } {
277   UPDATE t2 SET d = 'd' WHERE c=1 AND b=2;
278 } {
279   {UPDATE t2 0 .XX. {{} {} i 2 i 1 {} {}} {t a {} {} {} {} t d}}
282 # UPDATE + DELETE
283 do_patchconcat_test 4.3.6 {
284   UPDATE t2 SET a = 'a' WHERE c=1 AND b=2;
285 } {
286   DELETE FROM t2 WHERE c=1 AND b=2;
287 } {
288   {DELETE t2 0 .XX. {{} {} i 2 i 1 {} {}} {}}
291 # DELETE + INSERT
292 do_patchconcat_test 4.3.7 {
293   DELETE FROM t2 WHERE b=1;
294 } {
295   INSERT INTO t2 VALUES('x', 1, 2, '.');
296 } {
297   {DELETE t2 0 .XX. {{} {} i 1 i 1 {} {}} {}} 
298   {UPDATE t2 0 .XX. {{} {} i 1 i 2 {} {}} {t x {} {} {} {} t .}}
301 # DELETE + UPDATE
302 do_patchconcat_test 4.3.8 -revert {
303   DELETE FROM t2 WHERE b=1 AND c=2;
304 } {
305   UPDATE t2 SET a=5 WHERE b=1 AND c=2;
306 } {
307   {DELETE t2 0 .XX. {{} {} i 1 i 2 {} {}} {}} 
310 # DELETE + UPDATE
311 do_patchconcat_test 4.3.9 -revert {
312   DELETE FROM t2 WHERE b=1 AND c=2;
313 } {
314   DELETE FROM t2 WHERE b=1;
315 } {
316   {DELETE t2 0 .XX. {{} {} i 1 i 1 {} {}} {}} 
317   {DELETE t2 0 .XX. {{} {} i 1 i 2 {} {}} {}} 
320 #-------------------------------------------------------------------------
321 # More rigorous testing of the _patchset(), _apply and _concat() APIs.
323 # The inputs to each test are a populate database and a list of DML 
324 # statements. This test determines that the final database is the same
325 # if:
327 #   1) the statements are executed directly on the database.
329 #   2) a single patchset is collected while executing the statements and
330 #      then applied to a copy of the original database file.
332 #   3) individual patchsets are collected for statement while executing
333 #      them and concatenated together before being applied to a copy of
334 #      the original database. The concatenation is done in a couple of
335 #      different ways - linear, pairwise etc.
337 # All tests, as it happens, are run with both changesets and patchsets.
338 # But the focus is on patchset capabilities.
341 # Return a checksum of the contents of the database file. Implicit IPK
342 # columns are not included in the checksum - just modifying rowids does
343 # not change the database checksum.
345 proc databasecksum {db} {
346   set alltab [$db eval {SELECT name FROM sqlite_master WHERE type='table'}]
347   foreach tab $alltab {
348     $db eval "SELECT * FROM $tab LIMIT 1" res { }
349     set slist [list]
350     foreach col [lsort $res(*)] {
351       lappend slist "quote($col)"
352     }
353     set sql "SELECT [join $slist ,] FROM $tab"
354     append txt "[lsort [$db eval $sql]]\n"
355   }
356   return [md5 $txt]
359 proc do_patchset_test {tn tstcmd lSql} {
360   if {$tstcmd != "patchset" && $tstcmd != "changeset"} {
361     error "have $tstcmd: must be patchset or changeset"
362   }
364   foreach fname {test.db2 test.db3 test.db4 test.db5} {
365     forcedelete $fname
366     forcecopy test.db $fname
367   }
369   # Execute the SQL statements on [db]. Collect a patchset for each 
370   # individual statement, as well as a single patchset for the entire 
371   # operation.
372   sqlite3session S db main
373   S attach *
374   foreach sql $lSql { 
375     sqlite3session T db main
376     T attach *
377     db eval $sql 
378     lappend lPatch [T $tstcmd]
379     T delete
380   }
381   set patchset [S $tstcmd]
382   S delete
384   # Calculate a checksum for the final database.
385   set cksum [databasecksum db]
387   # 1. Apply the single large patchset to test.db2
388   sqlite3 db2 test.db2
389   sqlite3changeset_apply db2 $patchset noop
390   uplevel [list do_test $tn.1 { databasecksum db2 } $cksum ]
391   db2 close
392   
393   # 2. Apply each of the single-statement patchsets to test.db3
394   sqlite3 db2 test.db3
395   foreach p $lPatch {
396     sqlite3changeset_apply db2 $p noop
397   }
398   uplevel [list do_test $tn.2 { databasecksum db2 } $cksum ]
399   db2 close
401   # 3. Concatenate all single-statement patchsets into a single large
402   #    patchset, then apply it to test.db4.
403   #
404   sqlite3 db2 test.db4
405   set big ""
406   foreach p $lPatch {
407     set big [sqlite3changeset_concat $big $p]
408   }
409   sqlite3changeset_apply db2 $big noop
410   uplevel [list do_test $tn.3 { databasecksum db2 } $cksum ]
411   db2 close
413   # 4. Concatenate all single-statement patchsets pairwise into a single
414   #    large patchset, then apply it to test.db5. Pairwise concatenation:
415   #
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   #      -> {a b c d e f g h i j k}
421   #      -> APPLY!
422   #
423   sqlite3 db2 test.db5
424   set L $lPatch
425   while {[llength $L] > 1} {
426     set O [list]
427     for {set i 0} {$i < [llength $L]} {incr i 2} {
428       if {$i==[llength $L]-1} {
429         lappend O [lindex $L $i]
430       } else {
431         set i1 [expr $i+1]
432         lappend O [sqlite3changeset_concat [lindex $L $i] [lindex $L $i1]]
433       }
434     }
435     set L $O
436   }
437   sqlite3changeset_apply db2 [lindex $L 0] noop
438   uplevel [list do_test $tn.4 { databasecksum db2 } $cksum ]
439   db2 close
442 proc do_patchset_changeset_test {tn initsql args} {
443   foreach tstcmd {patchset changeset} {
444     reset_db
445     execsql $initsql
446     set x 0
447     foreach sql $args {
448       incr x
449       set lSql [split $sql ";"]
450       uplevel [list do_patchset_test $tn.$tstcmd.$x $tstcmd $lSql]
451     }
452   }
455 do_patchset_changeset_test 5.1 {
456   CREATE TABLE t1(a PRIMARY KEY, b, c);
457   INSERT INTO t1 VALUES(1, 2, 3);
458 } {
459   INSERT INTO t1 VALUES(4, 5, 6);
460   DELETE FROM t1 WHERE a=1;
461 } {
462   INSERT INTO t1 VALUES(7, 8, 9);
463   UPDATE t1 SET c = 5;
464   INSERT INTO t1 VALUES(10, 11, 12);
465   UPDATE t1 SET c = 6;
466   INSERT INTO t1 VALUES(13, 14, 15);
467 } {
468   UPDATE t1 SET c=c+1;
469   DELETE FROM t1 WHERE (a%2);
472 do_patchset_changeset_test 5.2 {
473   CREATE TABLE t1(a PRIMARY KEY, b, c);
474   CREATE TABLE t2(a, b, c, d, PRIMARY KEY(c, b));
475 } {
476   INSERT INTO t1 VALUES(x'00', 0, 'zero');
477   INSERT INTO t1 VALUES(x'01', 1, 'one');
478   INSERT INTO t1 VALUES(x'02', 4, 'four');
479   INSERT INTO t1 VALUES(x'03', 9, 'nine');
480   INSERT INTO t1 VALUES(x'04', 16, 'sixteen');
481   INSERT INTO t1 VALUES(x'05', 25, 'twenty-five');
482 } {
483   UPDATE t1 SET a = b WHERE b<=4;
484   INSERT INTO t2 SELECT NULL, * FROM t1;
485   DELETE FROM t1 WHERE b=25;
486 } {
487   DELETE FROM t2;
488   INSERT INTO t2 SELECT NULL, * FROM t1;
489   DELETE FROM t1;
490   INSERT INTO t1 SELECT b, c, d FROM t2;
491   UPDATE t1 SET b = b+1;
492   UPDATE t1 SET b = b+1;
493   UPDATE t1 SET b = b+1;
496 set initsql { CREATE TABLE t1(a, b, c, PRIMARY KEY(c, b)); }
497 for {set i 0} {$i < 1000} {incr i} {
498   append insert "INSERT INTO t1 VALUES($i, $i, $i);"
499   append delete "DELETE FROM t1 WHERE b=$i;"
501 do_patchset_changeset_test 5.3 \
502   $initsql $insert $delete     \
503   $insert $delete              \
504   "$insert $delete"            \
505   $delete
508 finish_test