xref: /sqlite-3.40.0/ext/session/session2.test (revision 80fe2d93)
1# 2011 Mar 16
2#
3# The author disclaims copyright to this source code.  In place of
4# a legal notice, here is a blessing:
5#
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.
9#
10#***********************************************************************
11#
12# The focus of this file is testing the session module.
13#
14
15if {![info exists testdir]} {
16  set testdir [file join [file dirname [info script]] .. .. test]
17}
18source [file join [file dirname [info script]] session_common.tcl]
19source $testdir/tester.tcl
20ifcapable !session {finish_test; return}
21
22set testprefix session2
23
24proc test_reset {} {
25  catch { db close }
26  catch { db2 close }
27  forcedelete test.db test.db2
28  sqlite3 db test.db
29  sqlite3 db2 test.db2
30}
31
32##########################################################################
33# End of proc definitions. Start of tests.
34##########################################################################
35
36test_reset
37do_execsql_test 1.0 {
38  CREATE TABLE t1(a PRIMARY KEY, b);
39  INSERT INTO t1 VALUES('i', 'one');
40}
41do_iterator_test 1.1 t1 {
42  DELETE FROM t1 WHERE a = 'i';
43  INSERT INTO t1 VALUES('ii', 'two');
44} {
45  {DELETE t1 0 X. {t i t one} {}}
46  {INSERT t1 0 X. {} {t ii t two}}
47}
48
49do_iterator_test 1.2 t1 {
50  INSERT INTO t1 VALUES(1.5, 99.9)
51} {
52  {INSERT t1 0 X. {} {f 1.5 f 99.9}}
53}
54
55do_iterator_test 1.3 t1 {
56  UPDATE t1 SET b = 100.1 WHERE a = 1.5;
57  UPDATE t1 SET b = 99.9 WHERE a = 1.5;
58} { }
59
60do_iterator_test 1.4 t1 {
61  UPDATE t1 SET b = 100.1 WHERE a = 1.5;
62} {
63  {UPDATE t1 0 X. {f 1.5 f 99.9} {{} {} f 100.1}}
64}
65
66
67# Execute each of the following blocks of SQL on database [db1]. Collect
68# changes using a session object. Apply the resulting changeset to
69# database [db2]. Then check that the contents of the two databases are
70# identical.
71#
72
73set set_of_tests {
74  1 { INSERT INTO %T1% VALUES(1, 2) }
75
76  2 {
77    INSERT INTO %T2% VALUES(1, NULL);
78    INSERT INTO %T2% VALUES(2, NULL);
79    INSERT INTO %T2% VALUES(3, NULL);
80    DELETE FROM %T2% WHERE a = 2;
81    INSERT INTO %T2% VALUES(4, NULL);
82    UPDATE %T2% SET b=0 WHERE b=1;
83  }
84
85  3 { INSERT INTO %T3% SELECT *, NULL FROM %T2% }
86
87  4 {
88    INSERT INTO %T3% SELECT a||a, b||b, NULL FROM %T3%;
89    DELETE FROM %T3% WHERE rowid%2;
90  }
91
92  5 { UPDATE %T3% SET c = a||b }
93
94  6 { UPDATE %T1% SET a = 32 }
95
96  7 {
97    INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
98    INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
99    INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
100    INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
101    INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
102    INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
103    INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
104    INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
105    INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
106    INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
107    INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
108    DELETE FROM %T1% WHERE (rowid%3)==0;
109  }
110
111  8 {
112    BEGIN;
113      INSERT INTO %T1% SELECT randomblob(32), randomblob(32) FROM %T1%;
114    ROLLBACK;
115  }
116  9 {
117    BEGIN;
118      UPDATE %T1% SET b = 'xxx';
119    ROLLBACK;
120  }
121  10 {
122    BEGIN;
123      DELETE FROM %T1% WHERE 1;
124    ROLLBACK;
125  }
126  11 {
127    INSERT INTO %T1% VALUES(randomblob(21000), randomblob(0));
128    INSERT INTO %T1% VALUES(1.5, 1.5);
129    INSERT INTO %T1% VALUES(4.56, -99.999999999999999999999);
130  }
131  12 {
132    INSERT INTO %T2% VALUES(NULL, NULL);
133  }
134
135  13 {
136    DELETE FROM %T1% WHERE 1;
137
138    -- Insert many rows with real primary keys. Enough to force the session
139    -- objects hash table to resize.
140    INSERT INTO %T1% VALUES(0.1, 0.1);
141    INSERT INTO %T1% SELECT a+0.1, b+0.1 FROM %T1%;
142    INSERT INTO %T1% SELECT a+0.2, b+0.2 FROM %T1%;
143    INSERT INTO %T1% SELECT a+0.4, b+0.4 FROM %T1%;
144    INSERT INTO %T1% SELECT a+0.8, b+0.8 FROM %T1%;
145    INSERT INTO %T1% SELECT a+1.6, b+1.6 FROM %T1%;
146    INSERT INTO %T1% SELECT a+3.2, b+3.2 FROM %T1%;
147    INSERT INTO %T1% SELECT a+6.4, b+6.4 FROM %T1%;
148    INSERT INTO %T1% SELECT a+12.8, b+12.8 FROM %T1%;
149    INSERT INTO %T1% SELECT a+25.6, b+25.6 FROM %T1%;
150    INSERT INTO %T1% SELECT a+51.2, b+51.2 FROM %T1%;
151    INSERT INTO %T1% SELECT a+102.4, b+102.4 FROM %T1%;
152    INSERT INTO %T1% SELECT a+204.8, b+204.8 FROM %T1%;
153  }
154
155  14 {
156    DELETE FROM %T1% WHERE 1;
157  }
158
159  15 {
160    INSERT INTO %T1% VALUES(1, 1);
161    INSERT INTO %T1% SELECT a+2, b+2 FROM %T1%;
162    INSERT INTO %T1% SELECT a+4, b+4 FROM %T1%;
163    INSERT INTO %T1% SELECT a+8, b+8 FROM %T1%;
164    INSERT INTO %T1% SELECT a+256, b+256 FROM %T1%;
165  }
166
167  16 {
168    INSERT INTO %T4% VALUES('abc', 'def');
169    INSERT INTO %T4% VALUES('def', 'abc');
170  }
171  17 { UPDATE %T4% SET b = 1 }
172  18 { DELETE FROM %T4% WHERE 1 }
173}
174
175test_reset
176do_common_sql {
177  CREATE TABLE t1(a PRIMARY KEY, b);
178  CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
179  CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
180  CREATE TABLE t4(a, b, PRIMARY KEY(b, a));
181}
182
183foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3 %T4% t4} $set_of_tests] {
184  do_then_apply_sql $sql
185  do_test 2.$tn { compare_db db db2 } {}
186}
187
188# The following block of tests is similar to the last, except that the
189# session object is recording changes made to an attached database. The
190# main database contains a table of the same name as the table being
191# modified within the attached db.
192#
193test_reset
194forcedelete test.db3
195sqlite3 db3 test.db3
196do_test 3.0 {
197  execsql {
198    ATTACH 'test.db3' AS 'aux';
199    CREATE TABLE t1(a, b PRIMARY KEY);
200    CREATE TABLE t2(x, y, z);
201    CREATE TABLE t3(a);
202
203    CREATE TABLE aux.t1(a PRIMARY KEY, b);
204    CREATE TABLE aux.t2(a, b INTEGER PRIMARY KEY);
205    CREATE TABLE aux.t3(a, b, c, PRIMARY KEY(a, b));
206    CREATE TABLE aux.t4(a, b, PRIMARY KEY(b, a));
207  }
208  execsql {
209    CREATE TABLE t1(a PRIMARY KEY, b);
210    CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
211    CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
212    CREATE TABLE t4(a, b, PRIMARY KEY(b, a));
213  } db2
214} {}
215
216proc xTrace {args} { puts $args }
217
218foreach {tn sql} [
219  string map {%T1% aux.t1 %T2% aux.t2 %T3% aux.t3 %T4% aux.t4} $set_of_tests
220] {
221  do_then_apply_sql $sql aux
222  do_test 3.$tn { compare_db db2 db3 } {}
223}
224catch {db3 close}
225
226
227#-------------------------------------------------------------------------
228# The following tests verify that NULL values in primary key columns are
229# handled correctly by the session module.
230#
231test_reset
232do_execsql_test 4.0 {
233  CREATE TABLE t1(a PRIMARY KEY);
234  CREATE TABLE t2(a, b, c, PRIMARY KEY(c, b));
235  CREATE TABLE t3(a, b INTEGER PRIMARY KEY);
236}
237
238foreach {tn sql changeset} {
239  1 {
240    INSERT INTO t1 VALUES(123);
241    INSERT INTO t1 VALUES(NULL);
242    INSERT INTO t1 VALUES(456);
243  } {
244    {INSERT t1 0 X {} {i 456}}
245    {INSERT t1 0 X {} {i 123}}
246  }
247
248  2 {
249    UPDATE t1 SET a = NULL;
250  } {
251    {DELETE t1 0 X {i 456} {}}
252    {DELETE t1 0 X {i 123} {}}
253  }
254
255  3 { DELETE FROM t1 } { }
256
257  4 {
258    INSERT INTO t3 VALUES(NULL, NULL)
259  } {
260    {INSERT t3 0 .X {} {n {} i 1}}
261  }
262
263  5 { INSERT INTO t2 VALUES(1, 2, NULL) }    { }
264  6 { INSERT INTO t2 VALUES(1, NULL, 3) }    { }
265  7 { INSERT INTO t2 VALUES(1, NULL, NULL) } { }
266  8 { INSERT INTO t2 VALUES(1, 2, 3) }    { {INSERT t2 0 .XX {} {i 1 i 2 i 3}} }
267  9 { DELETE FROM t2 WHERE 1 }            { {DELETE t2 0 .XX {i 1 i 2 i 3} {}} }
268
269} {
270  do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset
271}
272
273
274#-------------------------------------------------------------------------
275# Test that if NULL is passed to sqlite3session_attach(), all database
276# tables are attached to the session object.
277#
278test_reset
279do_execsql_test 5.0 {
280  CREATE TABLE t1(a PRIMARY KEY);
281  CREATE TABLE t2(x, y PRIMARY KEY);
282}
283
284foreach {tn sql changeset} {
285  1 { INSERT INTO t1 VALUES(35) }     { {INSERT t1 0 X {} {i 35}} }
286  2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 0 .X {} {i 36 i 37}} }
287  3 {
288    DELETE FROM t1 WHERE 1;
289    UPDATE t2 SET x = 34;
290  } {
291    {UPDATE t2 0 .X {i 36 i 37} {i 34 {} {}}}
292    {DELETE t1 0 X {i 35} {}}
293  }
294} {
295  do_iterator_test 5.$tn * $sql $changeset
296}
297
298#-------------------------------------------------------------------------
299# The next block of tests verify that the "indirect" flag is set
300# correctly within changesets. The indirect flag is set for a change
301# if either of the following are true:
302#
303#   * The sqlite3session_indirect() API has been used to set the session
304#     indirect flag to true, or
305#   * The change was made by a trigger.
306#
307# If the same row is updated more than once during a session, then the
308# change is considered indirect only if all changes meet the criteria
309# above.
310#
311test_reset
312db function indirect [list S indirect]
313
314do_execsql_test 6.0 {
315  CREATE TABLE t1(a PRIMARY KEY, b, c);
316
317  CREATE TABLE t2(x PRIMARY KEY, y);
318  CREATE TRIGGER AFTER INSERT ON t2 WHEN new.x%2 BEGIN
319    INSERT INTO t2 VALUES(new.x+1, NULL);
320  END;
321}
322
323do_iterator_test 6.1.1 * {
324  INSERT INTO t1 VALUES(1, 'one', 'i');
325  SELECT indirect(1);
326  INSERT INTO t1 VALUES(2, 'two', 'ii');
327  SELECT indirect(0);
328  INSERT INTO t1 VALUES(3, 'three', 'iii');
329} {
330  {INSERT t1 0 X.. {} {i 1 t one t i}}
331  {INSERT t1 1 X.. {} {i 2 t two t ii}}
332  {INSERT t1 0 X.. {} {i 3 t three t iii}}
333}
334
335do_iterator_test 6.1.2 * {
336  SELECT indirect(1);
337  UPDATE t1 SET c = 'I' WHERE a = 1;
338  SELECT indirect(0);
339} {
340  {UPDATE t1 1 X.. {i 1 {} {} t i} {{} {} {} {} t I}}
341}
342do_iterator_test 6.1.3 * {
343  SELECT indirect(1);
344  UPDATE t1 SET c = '.' WHERE a = 1;
345  SELECT indirect(0);
346  UPDATE t1 SET c = 'o' WHERE a = 1;
347} {
348  {UPDATE t1 0 X.. {i 1 {} {} t I} {{} {} {} {} t o}}
349}
350do_iterator_test 6.1.4 * {
351  SELECT indirect(0);
352  UPDATE t1 SET c = 'x' WHERE a = 1;
353  SELECT indirect(1);
354  UPDATE t1 SET c = 'i' WHERE a = 1;
355} {
356  {UPDATE t1 0 X.. {i 1 {} {} t o} {{} {} {} {} t i}}
357}
358do_iterator_test 6.1.4 * {
359  SELECT indirect(1);
360  UPDATE t1 SET c = 'y' WHERE a = 1;
361  SELECT indirect(1);
362  UPDATE t1 SET c = 'I' WHERE a = 1;
363} {
364  {UPDATE t1 1 X.. {i 1 {} {} t i} {{} {} {} {} t I}}
365}
366
367do_iterator_test 6.1.5 * {
368  INSERT INTO t2 VALUES(1, 'x');
369} {
370  {INSERT t2 0 X. {} {i 1 t x}}
371  {INSERT t2 1 X. {} {i 2 n {}}}
372}
373
374do_iterator_test 6.1.6 * {
375  SELECT indirect(1);
376  INSERT INTO t2 VALUES(3, 'x');
377  SELECT indirect(0);
378  UPDATE t2 SET y = 'y' WHERE x>2;
379} {
380  {INSERT t2 0 X. {} {i 3 t y}}
381  {INSERT t2 0 X. {} {i 4 t y}}
382}
383
384do_iterator_test 6.1.7 * {
385  SELECT indirect(1);
386  DELETE FROM t2 WHERE x = 4;
387  SELECT indirect(0);
388  INSERT INTO t2 VALUES(4, 'new');
389} {
390  {UPDATE t2 0 X. {i 4 t y} {{} {} t new}}
391}
392
393do_iterator_test 6.1.8 * {
394  CREATE TABLE t3(a, b PRIMARY KEY);
395  CREATE TABLE t4(a, b PRIMARY KEY);
396  CREATE TRIGGER t4t AFTER UPDATE ON t4 BEGIN
397    UPDATE t3 SET a = new.a WHERE b = new.b;
398  END;
399
400  SELECT indirect(1);
401  INSERT INTO t3 VALUES('one', 1);
402  INSERT INTO t4 VALUES('one', 1);
403  SELECT indirect(0);
404  UPDATE t4 SET a = 'two' WHERE b = 1;
405} {
406  {INSERT t4 0 .X {} {t two i 1}}
407  {INSERT t3 1 .X {} {t two i 1}}
408}
409
410sqlite3session S db main
411do_execsql_test 6.2.1 {
412  SELECT indirect(0);
413  SELECT indirect(-1);
414  SELECT indirect(45);
415  SELECT indirect(-100);
416} {0 0 1 1}
417S delete
418
419#-------------------------------------------------------------------------
420# Test that if a conflict-handler that has been passed either NOTFOUND or
421# CONSTRAINT returns REPLACE - the sqlite3changeset_apply() call returns
422# MISUSE and rolls back any changes made so far.
423#
424#   7.1.*: NOTFOUND conflict-callback.
425#   7.2.*: CONSTRAINT conflict-callback.
426#
427proc xConflict {args} {return REPLACE}
428test_reset
429
430do_execsql_test 7.1.1 {
431  CREATE TABLE t1(a PRIMARY KEY, b);
432  INSERT INTO t1 VALUES(1, 'one');
433  INSERT INTO t1 VALUES(2, 'two');
434}
435do_test 7.1.2 {
436  execsql {
437    CREATE TABLE t1(a PRIMARY KEY, b NOT NULL);
438    INSERT INTO t1 VALUES(1, 'one');
439  } db2
440} {}
441do_test 7.1.3 {
442  set changeset [changeset_from_sql {
443    UPDATE t1 SET b = 'five' WHERE a = 1;
444    UPDATE t1 SET b = 'six' WHERE a = 2;
445  }]
446  set x [list]
447  sqlite3session_foreach c $changeset { lappend x $c }
448  set x
449} [list \
450  {UPDATE t1 0 X. {i 1 t one} {{} {} t five}} \
451  {UPDATE t1 0 X. {i 2 t two} {{} {} t six}}  \
452]
453do_test 7.1.4 {
454  list [catch {sqlite3changeset_apply db2 $changeset xConflict} msg] $msg
455} {1 SQLITE_MISUSE}
456do_test 7.1.5 { execsql { SELECT * FROM t1 } db2 } {1 one}
457
458do_test 7.2.1 {
459  set changeset [changeset_from_sql { UPDATE t1 SET b = NULL WHERE a = 1 }]
460
461  set x [list]
462  sqlite3session_foreach c $changeset { lappend x $c }
463  set x
464} [list \
465  {UPDATE t1 0 X. {i 1 t five} {{} {} n {}}} \
466]
467do_test 7.2.2 {
468  list [catch {sqlite3changeset_apply db2 $changeset xConflict} msg] $msg
469} {1 SQLITE_MISUSE}
470do_test 7.2.3 { execsql { SELECT * FROM t1 } db2 } {1 one}
471
472#-------------------------------------------------------------------------
473# Test that if a conflict-handler returns ABORT, application of the
474# changeset is rolled back and the sqlite3changeset_apply() method returns
475# SQLITE_ABORT.
476#
477# Also test that the same thing happens if a conflict handler returns an
478# unrecognized integer value. Except, in this case SQLITE_MISUSE is returned
479# instead of SQLITE_ABORT.
480#
481foreach {tn conflict_return apply_return} {
482  1    ABORT   SQLITE_ABORT
483  2    567     SQLITE_MISUSE
484} {
485  test_reset
486  proc xConflict {args} [list return $conflict_return]
487
488  do_test 8.$tn.0 {
489    do_common_sql {
490      CREATE TABLE t1(x, y, PRIMARY KEY(x, y));
491      INSERT INTO t1 VALUES('x', 'y');
492    }
493    execsql { INSERT INTO t1 VALUES('w', 'w') }
494
495    set changeset [changeset_from_sql { DELETE FROM t1 WHERE 1 }]
496
497    set x [list]
498    sqlite3session_foreach c $changeset { lappend x $c }
499    set x
500  } [list \
501    {DELETE t1 0 XX {t w t w} {}} \
502    {DELETE t1 0 XX {t x t y} {}} \
503  ]
504
505  do_test 8.$tn.1 {
506    list [catch {sqlite3changeset_apply db2 $changeset xConflict} msg] $msg
507  } [list 1 $apply_return]
508
509  do_test 8.$tn.2 {
510    execsql {SELECT * FROM t1} db2
511  } {x y}
512}
513
514
515#-------------------------------------------------------------------------
516# Try to cause an infinite loop as follows:
517#
518#   1. Have a changeset insert a row that causes a CONFLICT callback,
519#   2. Have the conflict handler return REPLACE,
520#   3. After the session module deletes the conflicting row, have a trigger
521#      re-insert it.
522#   4. Goto step 1...
523#
524# This doesn't work, as the second invocation of the conflict handler is a
525# CONSTRAINT, not a CONFLICT. There is at most one CONFLICT callback for
526# each change in the changeset.
527#
528test_reset
529proc xConflict {type args} {
530  if {$type == "CONFLICT"} { return REPLACE }
531  return OMIT
532}
533do_test 9.1 {
534  execsql {
535    CREATE TABLE t1(a PRIMARY KEY, b);
536  }
537  execsql {
538    CREATE TABLE t1(a PRIMARY KEY, b);
539    INSERT INTO t1 VALUES('x', 2);
540    CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN
541      INSERT INTO t1 VALUES(old.a, old.b);
542    END;
543  } db2
544} {}
545do_test 9.2 {
546  set changeset [changeset_from_sql { INSERT INTO t1 VALUES('x', 1) }]
547  sqlite3changeset_apply db2 $changeset xConflict
548} {}
549do_test 9.3 {
550  execsql { SELECT * FROM t1 } db2
551} {x 2}
552
553#-------------------------------------------------------------------------
554#
555test_reset
556db function enable [list S enable]
557
558do_common_sql {
559  CREATE TABLE t1(a PRIMARY KEY, b);
560  INSERT INTO t1 VALUES('x', 'X');
561}
562
563do_iterator_test 10.1 t1 {
564  INSERT INTO t1 VALUES('y', 'Y');
565  SELECT enable(0);
566  INSERT INTO t1 VALUES('z', 'Z');
567  SELECT enable(1);
568} {
569  {INSERT t1 0 X. {} {t y t Y}}
570}
571
572sqlite3session S db main
573do_execsql_test 10.2 {
574  SELECT enable(0);
575  SELECT enable(-1);
576  SELECT enable(1);
577  SELECT enable(-1);
578} {0 0 1 1}
579S delete
580
581finish_test
582