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