1# 2011 Mar 21
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
20
21set testprefix sessionfault
22
23forcedelete test.db2
24sqlite3 db2 test.db2
25do_common_sql {
26  CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b));
27  INSERT INTO t1 VALUES(1, 2, 3);
28  INSERT INTO t1 VALUES(4, 5, 6);
29}
30faultsim_save_and_close
31db2 close
32
33#-------------------------------------------------------------------------
34# Test OOM error handling when collecting and applying a simple changeset.
35#
36# Test 1.1 attaches tables individually by name to the session object.
37# Whereas test 1.2 passes NULL to sqlite3session_attach() to attach all
38# tables.
39#
40do_faultsim_test 1.1 -faults oom-* -prep {
41  catch {db2 close}
42  catch {db close}
43  faultsim_restore_and_reopen
44  sqlite3 db2 test.db2
45} -body {
46  do_then_apply_sql {
47    INSERT INTO t1 VALUES('a string value', 8, 9);
48    UPDATE t1 SET c = 10 WHERE a = 1;
49    DELETE FROM t1 WHERE a = 4;
50  }
51} -test {
52  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
53  faultsim_integrity_check
54  if {$testrc==0} { compare_db db db2 }
55}
56
57do_faultsim_test 1.2 -faults oom-* -prep {
58  catch {db2 close}
59  catch {db close}
60  faultsim_restore_and_reopen
61} -body {
62  sqlite3session S db main
63  S attach *
64  execsql {
65    INSERT INTO t1 VALUES('a string value', 8, 9);
66    UPDATE t1 SET c = 10 WHERE a = 1;
67    DELETE FROM t1 WHERE a = 4;
68  }
69  set ::changeset [S changeset]
70  set {} {}
71} -test {
72  catch { S delete }
73  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
74  faultsim_integrity_check
75  if {$testrc==0} {
76    proc xConflict {args} { return "OMIT" }
77    sqlite3 db2 test.db2
78    sqlite3changeset_apply db2 $::changeset xConflict
79    compare_db db db2
80  }
81}
82
83#-------------------------------------------------------------------------
84# The following block of tests - 2.* - are designed to check
85# the handling of faults in the sqlite3changeset_apply() function.
86#
87catch {db close}
88catch {db2 close}
89forcedelete test.db2 test.db
90sqlite3 db2 test.db2
91sqlite3 db test.db
92do_common_sql {
93  CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b));
94  INSERT INTO t1 VALUES('apple', 'orange', 'pear');
95
96  CREATE TABLE t2(x PRIMARY KEY, y);
97}
98db2 close
99faultsim_save_and_close
100
101
102foreach {tn conflict_policy sql sql2} {
103  1 OMIT { INSERT INTO t1 VALUES('one text', 'two text', X'00ff00') } {}
104  2 OMIT { DELETE FROM t1 WHERE a = 'apple' }                         {}
105  3 OMIT { UPDATE t1 SET c = 'banana' WHERE b = 'orange' }            {}
106  4 REPLACE { INSERT INTO t2 VALUES('keyvalue', 'value 1') } {
107    INSERT INTO t2 VALUES('keyvalue', 'value 2');
108  }
109} {
110  proc xConflict args [list return $conflict_policy]
111
112  do_faultsim_test 2.$tn -faults oom-transient -prep {
113    catch {db2 close}
114    catch {db close}
115    faultsim_restore_and_reopen
116    set ::changeset [changeset_from_sql $::sql]
117    sqlite3 db2 test.db2
118    sqlite3_db_config_lookaside db2 0 0 0
119    execsql $::sql2 db2
120  } -body {
121    sqlite3changeset_apply db2 $::changeset xConflict
122  } -test {
123    faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
124    faultsim_integrity_check
125    if {$testrc==0} { compare_db db db2 }
126  }
127}
128
129#-------------------------------------------------------------------------
130# This test case is designed so that a malloc() failure occurs while
131# resizing the session object hash-table from 256 to 512 buckets. This
132# is not an error, just a sub-optimal condition.
133#
134do_faultsim_test 3 -faults oom-* -prep {
135  catch {db2 close}
136  catch {db close}
137  faultsim_restore_and_reopen
138  sqlite3 db2 test.db2
139
140  sqlite3session S db main
141  S attach t1
142  execsql { BEGIN }
143  for {set i 0} {$i < 125} {incr i} {
144    execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 10+$i)}
145  }
146} -body {
147  for {set i 125} {$i < 133} {incr i} {
148    execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 1-+$i)}
149  }
150  S changeset
151  set {} {}
152} -test {
153  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
154  if {$testrc==0} {
155    sqlite3changeset_apply db2 [S changeset] xConflict
156    compare_db db db2
157  }
158  catch { S delete }
159  faultsim_integrity_check
160}
161
162catch { db close }
163catch { db2 close }
164forcedelete test.db2 test.db
165sqlite3 db2 test.db2
166sqlite3 db test.db
167
168proc xConflict {op tbl type args} {
169  if { $type=="CONFLICT" || $type=="DATA" } {
170    return "REPLACE"
171  }
172  return "OMIT"
173}
174
175do_test 4.0 {
176  execsql {
177    PRAGMA encoding = 'utf16';
178    CREATE TABLE t1(a PRIMARY KEY, b);
179    INSERT INTO t1 VALUES(5, 32);
180  }
181  execsql {
182    PRAGMA encoding = 'utf16';
183    CREATE TABLE t1(a PRIMARY KEY, b NOT NULL);
184    INSERT INTO t1 VALUES(1, 2);
185    INSERT INTO t1 VALUES(2, 4);
186    INSERT INTO t1 VALUES(4, 16);
187  } db2
188} {}
189
190faultsim_save_and_close
191db2 close
192
193do_faultsim_test 4 -faults oom-* -prep {
194  catch {db2 close}
195  catch {db close}
196  faultsim_restore_and_reopen
197  sqlite3 db2 test.db2
198  sqlite3session S db main
199  S attach t1
200  execsql {
201    INSERT INTO t1 VALUES(1, 45);
202    INSERT INTO t1 VALUES(2, 55);
203    INSERT INTO t1 VALUES(3, 55);
204    UPDATE t1 SET a = 4 WHERE a = 5;
205  }
206} -body {
207  sqlite3changeset_apply db2 [S changeset] xConflict
208} -test {
209  catch { S delete }
210  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
211  if {$testrc==0} { compare_db db db2 }
212}
213
214#-------------------------------------------------------------------------
215# This block of tests verifies that OOM faults in the
216# sqlite3changeset_invert() function are handled correctly.
217#
218catch {db close}
219catch {db2 close}
220forcedelete test.db
221sqlite3 db test.db
222execsql {
223  CREATE TABLE t1(a, b, PRIMARY KEY(b));
224  CREATE TABLE t2(a PRIMARY KEY, b);
225  INSERT INTO t1 VALUES('string', 1);
226  INSERT INTO t1 VALUES(4, 2);
227  INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3);
228}
229set changeset [changeset_from_sql {
230  INSERT INTO t1 VALUES('xxx', 'yyy');
231  DELETE FROM t1 WHERE a = 'string';
232  UPDATE t1 SET a = 20 WHERE b = 2;
233}]
234db close
235
236do_faultsim_test 5.1 -faults oom* -body {
237  set ::inverse [sqlite3changeset_invert $::changeset]
238  set {} {}
239} -test {
240  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
241  if {$testrc==0} {
242    set x [list]
243    sqlite3session_foreach c $::inverse { lappend x $c }
244    foreach c {
245        {DELETE t1 0 .X {t xxx t yyy} {}}
246        {INSERT t1 0 .X {} {t string i 1}}
247        {UPDATE t1 0 .X {i 20 i 2} {i 4 {} {}}}
248    } { lappend y $c }
249    if {$x != $y} { error "changeset no good" }
250  }
251}
252
253catch {db close}
254catch {db2 close}
255forcedelete test.db
256sqlite3 db test.db
257execsql {
258  CREATE TABLE t2(a PRIMARY KEY, b);
259  INSERT INTO t2 VALUES(1, 'abc');
260  INSERT INTO t2 VALUES(2, 'def');
261}
262set changeset [changeset_from_sql {
263  UPDATE t2 SET b = (b || b || b || b);
264  UPDATE t2 SET b = (b || b || b || b);
265  UPDATE t2 SET b = (b || b || b || b);
266  UPDATE t2 SET b = (b || b || b || b);
267}]
268db close
269set abc [string repeat abc 256]
270set def [string repeat def 256]
271
272do_faultsim_test 5.2 -faults oom-tra* -body {
273  set ::inverse [sqlite3changeset_invert $::changeset]
274  set {} {}
275} -test {
276  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
277  if {$testrc==0} {
278    set x [list]
279    sqlite3session_foreach c $::inverse { lappend x $c }
280    foreach c "
281        {UPDATE t2 0 X. {i 1 t $::abc} {{} {} t abc}}
282        {UPDATE t2 0 X. {i 2 t $::def} {{} {} t def}}
283    " { lappend y $c }
284    if {$x != $y} { error "changeset no good" }
285  }
286}
287
288catch {db close}
289catch {db2 close}
290forcedelete test.db
291sqlite3 db test.db
292set abc [string repeat abc 256]
293set def [string repeat def 256]
294execsql "
295  CREATE TABLE t2(a PRIMARY KEY, b);
296  INSERT INTO t2 VALUES(1, '$abc');
297"
298set changeset [changeset_from_sql "
299  INSERT INTO t2 VALUES(2, '$def');
300  DELETE FROM t2 WHERE a = 1;
301"]
302db close
303
304do_faultsim_test 5.3 -faults oom-tra* -body {
305  set ::inverse [sqlite3changeset_invert $::changeset]
306  set {} {}
307} -test {
308  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
309  if {$testrc==0} {
310    set x [list]
311    sqlite3session_foreach c $::inverse { lappend x $c }
312    foreach c "
313        {INSERT t2 0 X. {} {i 1 t $::abc}}
314        {DELETE t2 0 X. {i 2 t $::def} {}}
315    " { lappend y $c }
316    if {$x != $y} { error "changeset no good" }
317  }
318}
319
320#-------------------------------------------------------------------------
321# Test that OOM errors in sqlite3changeset_concat() are handled correctly.
322#
323catch {db close}
324forcedelete test.db
325sqlite3 db test.db
326do_execsql_test 5.prep1 {
327  CREATE TABLE t1(a, b, PRIMARY KEY(b));
328  CREATE TABLE t2(a PRIMARY KEY, b);
329  INSERT INTO t1 VALUES('string', 1);
330  INSERT INTO t1 VALUES(4, 2);
331  INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3);
332}
333
334do_test 6.prep2 {
335  sqlite3session M db main
336  M attach *
337  set ::c2 [changeset_from_sql {
338    INSERT INTO t2 VALUES(randomblob(1000), randomblob(1000));
339    INSERT INTO t2 VALUES('one', 'two');
340    INSERT INTO t2 VALUES(1, NULL);
341    UPDATE t1 SET a = 5 WHERE a = 2;
342  }]
343  set ::c1 [changeset_from_sql {
344    DELETE FROM t2 WHERE a = 1;
345    UPDATE t1 SET a = 4 WHERE a = 2;
346    INSERT INTO t2 VALUES('x', 'y');
347  }]
348  set ::total [changeset_to_list [M changeset]]
349  M delete
350} {}
351
352do_faultsim_test 6 -faults oom-* -body {
353  set ::result [sqlite3changeset_concat $::c1 $::c2]
354  set {} {}
355} -test {
356  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
357  if {$testrc==0} {
358    set v [changeset_to_list $::result]
359    if {$v != $::total} { error "result no good" }
360  }
361}
362
363faultsim_delete_and_reopen
364do_execsql_test 5.prep1 {
365  CREATE TABLE t1(a, b, PRIMARY KEY(a));
366}
367faultsim_save_and_close
368
369set res [list]
370for {set ::i 0} {$::i < 480} {incr ::i 4} {
371  lappend res "INSERT t1 0 X. {} {i $::i i $::i}"
372}
373set res [lsort $res]
374do_faultsim_test 7 -faults oom-transient -prep {
375  faultsim_restore_and_reopen
376  sqlite3session S db main
377  S attach *
378} -body {
379  for {set ::i 0} {$::i < 480} {incr ::i 4} {
380    execsql {INSERT INTO t1 VALUES($::i, $::i)}
381  }
382} -test {
383  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
384  if {$testrc==0} {
385    set cres [list [catch {changeset_to_list [S changeset]} msg] $msg]
386    S delete
387    if {$cres != "1 SQLITE_NOMEM" && $cres != "0 {$::res}"} {
388      error "Expected {0 $::res} Got {$cres}"
389    }
390  } else {
391    S changeset
392    S delete
393  }
394}
395
396faultsim_delete_and_reopen
397do_test 8.prep {
398  sqlite3session S db main
399  S attach *
400  execsql {
401    CREATE TABLE t1(a, b, PRIMARY KEY(a));
402    INSERT INTO t1 VALUES(1, 2);
403    INSERT INTO t1 VALUES(3, 4);
404    INSERT INTO t1 VALUES(5, 6);
405  }
406  set ::changeset [S changeset]
407  S delete
408} {}
409
410set expected [normalize_list {
411  {INSERT t1 0 X. {} {i 1 i 2}}
412  {INSERT t1 0 X. {} {i 3 i 4}}
413  {INSERT t1 0 X. {} {i 5 i 6}}
414}]
415do_faultsim_test 8.1 -faults oom* -body {
416  set ::res [list]
417  sqlite3session_foreach -next v $::changeset { lappend ::res $v }
418  normalize_list $::res
419} -test {
420  faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
421}
422do_faultsim_test 8.2 -faults oom* -body {
423  set ::res [list]
424  sqlite3session_foreach v $::changeset { lappend ::res $v }
425  normalize_list $::res
426} -test {
427  faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
428}
429
430faultsim_delete_and_reopen
431do_test 9.1.prep {
432  execsql {
433    PRAGMA encoding = 'utf16';
434    CREATE TABLE t1(a PRIMARY KEY, b);
435  }
436} {}
437faultsim_save_and_close
438
439set answers [list {0 {}} {1 SQLITE_NOMEM} \
440                  {1 {callback requested query abort}} \
441                  {1 {abort due to ROLLBACK}}]
442do_faultsim_test 9.1 -faults oom-transient -prep {
443  catch { unset ::c }
444  faultsim_restore_and_reopen
445  sqlite3session S db main
446  S attach *
447} -body {
448  execsql {
449    INSERT INTO t1 VALUES('abcdefghijklmnopqrstuv', 'ABCDEFGHIJKLMNOPQRSTUV');
450  }
451  set ::c [S changeset]
452  set {} {}
453} -test {
454  S delete
455  eval faultsim_test_result $::answers
456  if {[info exists ::c]} {
457    set expected [normalize_list {
458      {INSERT t1 0 X. {} {t abcdefghijklmnopqrstuv t ABCDEFGHIJKLMNOPQRSTUV}}
459    }]
460    if { [changeset_to_list $::c] != $expected } {
461      error "changeset mismatch"
462    }
463  }
464}
465
466faultsim_delete_and_reopen
467do_test 9.2.prep {
468  execsql {
469    PRAGMA encoding = 'utf16';
470    CREATE TABLE t1(a PRIMARY KEY, b);
471    INSERT INTO t1 VALUES('abcdefghij', 'ABCDEFGHIJKLMNOPQRSTUV');
472  }
473} {}
474faultsim_save_and_close
475
476set answers [list {0 {}} {1 SQLITE_NOMEM} \
477                  {1 {callback requested query abort}} \
478                  {1 {abort due to ROLLBACK}}]
479do_faultsim_test 9.2 -faults oom-transient -prep {
480  catch { unset ::c }
481  faultsim_restore_and_reopen
482  sqlite3session S db main
483  S attach *
484} -body {
485  execsql {
486    UPDATE t1 SET b = 'xyz';
487  }
488  set ::c [S changeset]
489  set {} {}
490} -test {
491  S delete
492  eval faultsim_test_result $::answers
493  if {[info exists ::c]} {
494    set expected [normalize_list {
495      {UPDATE t1 0 X. {t abcdefghij t ABCDEFGHIJKLMNOPQRSTUV} {{} {} t xyz}}
496    }]
497    if { [changeset_to_list $::c] != $expected } {
498      error "changeset mismatch"
499    }
500  }
501}
502
503#-------------------------------------------------------------------------
504# Test that if a conflict-handler encounters an OOM in
505# sqlite3_value_text() but goes on to return SQLITE_CHANGESET_REPLACE
506# anyway, the OOM is picked up by the sessions module.
507set bigstr [string repeat abcdefghij 100]
508faultsim_delete_and_reopen
509do_test 10.prep.1  {
510  execsql {
511    CREATE TABLE t1(a PRIMARY KEY, b);
512    INSERT INTO t1 VALUES($bigstr, $bigstr);
513  }
514
515  sqlite3session S db main
516  S attach *
517  execsql { UPDATE t1 SET b = b||'x' }
518  set C [S changeset]
519  S delete
520  execsql { UPDATE t1 SET b = b||'xyz' }
521} {}
522faultsim_save_and_close
523
524faultsim_restore_and_reopen
525do_test 10.prep.2  {
526  proc xConflict {args} { return "ABORT" }
527  list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
528} {1 SQLITE_ABORT}
529do_execsql_test 10.prep.3 { SELECT b=$bigstr||'x' FROM t1 } 0
530do_test 10.prep.4  {
531  proc xConflict {args} { return "REPLACE" }
532  list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
533} {0 {}}
534do_execsql_test 10.prep.5 { SELECT b=$bigstr||'x' FROM t1 } 1
535db close
536
537do_faultsim_test 10 -faults oom-tra* -prep {
538  faultsim_restore_and_reopen
539} -body {
540  sqlite3changeset_apply_replace_all db $::C
541} -test {
542  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
543  if {$testrc==0} {
544    if {[db one {SELECT b=$bigstr||'x' FROM t1}]==0} {
545      error "data does not look right"
546    }
547  }
548}
549
550#-------------------------------------------------------------------------
551# Test an OOM with an sqlite3changeset_apply() filter callback.
552#
553reset_db
554do_test 11.prep {
555  execsql {
556    CREATE TABLE t1(a PRIMARY KEY, b);
557    CREATE TABLE t2(x PRIMARY KEY, y);
558    BEGIN;
559  }
560
561  set ::cs [changeset_from_sql {
562    INSERT INTO t1 VALUES(1, 2);
563    INSERT INTO t2 VALUES('x', 'y');
564  }]
565
566  execsql ROLLBACK
567  set {} {}
568} {}
569
570proc filter {x} { return [string equal t1 $x] }
571faultsim_save_and_close
572
573do_faultsim_test 11 -faults oom-tra* -prep {
574  faultsim_restore_and_reopen
575} -body {
576  sqlite3changeset_apply db $::cs {} filter
577} -test {
578  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
579  if {$testrc==0} {
580    if {[db eval {SELECT * FROM t1 UNION ALL SELECT * FROM t2}] != "1 2"} {
581      error "data does not look right"
582    }
583  }
584}
585
586
587finish_test
588