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 7.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  catch { S delete }
376  faultsim_restore_and_reopen
377  sqlite3session S db main
378  S attach *
379} -body {
380  for {set ::i 0} {$::i < 480} {incr ::i 4} {
381    execsql {INSERT INTO t1 VALUES($::i, $::i)}
382  }
383} -test {
384  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
385  if {$testrc==0} {
386    set cres [list [catch {changeset_to_list [S changeset]} msg] $msg]
387    S delete
388    if {$cres != "1 SQLITE_NOMEM" && $cres != "0 {$::res}"} {
389      error "Expected {0 $::res} Got {$cres}"
390    }
391  } else {
392    catch { S changeset }
393    catch { S delete }
394  }
395}
396
397faultsim_delete_and_reopen
398do_test 8.prep {
399  sqlite3session S db main
400  S attach *
401  execsql {
402    CREATE TABLE t1(a, b, PRIMARY KEY(a));
403    INSERT INTO t1 VALUES(1, 2);
404    INSERT INTO t1 VALUES(3, 4);
405    INSERT INTO t1 VALUES(5, 6);
406  }
407  set ::changeset [S changeset]
408  S delete
409} {}
410
411set expected [normalize_list {
412  {INSERT t1 0 X. {} {i 1 i 2}}
413  {INSERT t1 0 X. {} {i 3 i 4}}
414  {INSERT t1 0 X. {} {i 5 i 6}}
415}]
416do_faultsim_test 8.1 -faults oom* -body {
417  set ::res [list]
418  sqlite3session_foreach -next v $::changeset { lappend ::res $v }
419  normalize_list $::res
420} -test {
421  faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
422}
423do_faultsim_test 8.2 -faults oom* -body {
424  set ::res [list]
425  sqlite3session_foreach v $::changeset { lappend ::res $v }
426  normalize_list $::res
427} -test {
428  faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
429}
430
431faultsim_delete_and_reopen
432do_test 9.1.prep {
433  execsql {
434    PRAGMA encoding = 'utf16';
435    CREATE TABLE t1(a PRIMARY KEY, b);
436  }
437} {}
438faultsim_save_and_close
439
440set answers [list {0 {}} {1 SQLITE_NOMEM} \
441                  {1 {callback requested query abort}} \
442                  {1 {abort due to ROLLBACK}}]
443do_faultsim_test 9.1 -faults oom-transient -prep {
444  catch { unset ::c }
445  faultsim_restore_and_reopen
446  sqlite3session S db main
447  S attach *
448} -body {
449  execsql {
450    INSERT INTO t1 VALUES('abcdefghijklmnopqrstuv', 'ABCDEFGHIJKLMNOPQRSTUV');
451  }
452  set ::c [S changeset]
453  set {} {}
454} -test {
455  S delete
456  eval faultsim_test_result $::answers
457  if {[info exists ::c]} {
458    set expected [normalize_list {
459      {INSERT t1 0 X. {} {t abcdefghijklmnopqrstuv t ABCDEFGHIJKLMNOPQRSTUV}}
460    }]
461    if { [changeset_to_list $::c] != $expected } {
462      error "changeset mismatch"
463    }
464  }
465}
466
467faultsim_delete_and_reopen
468do_test 9.2.prep {
469  execsql {
470    PRAGMA encoding = 'utf16';
471    CREATE TABLE t1(a PRIMARY KEY, b);
472    INSERT INTO t1 VALUES('abcdefghij', 'ABCDEFGHIJKLMNOPQRSTUV');
473  }
474} {}
475faultsim_save_and_close
476
477set answers [list {0 {}} {1 SQLITE_NOMEM} \
478                  {1 {callback requested query abort}} \
479                  {1 {abort due to ROLLBACK}}]
480do_faultsim_test 9.2 -faults oom-transient -prep {
481  catch { unset ::c }
482  faultsim_restore_and_reopen
483  sqlite3session S db main
484  S attach *
485} -body {
486  execsql {
487    UPDATE t1 SET b = 'xyz';
488  }
489  set ::c [S changeset]
490  set {} {}
491} -test {
492  S delete
493  eval faultsim_test_result $::answers
494  if {[info exists ::c]} {
495    set expected [normalize_list {
496      {UPDATE t1 0 X. {t abcdefghij t ABCDEFGHIJKLMNOPQRSTUV} {{} {} t xyz}}
497    }]
498    if { [changeset_to_list $::c] != $expected } {
499      error "changeset mismatch"
500    }
501  }
502}
503
504#-------------------------------------------------------------------------
505# Test that if a conflict-handler encounters an OOM in
506# sqlite3_value_text() but goes on to return SQLITE_CHANGESET_REPLACE
507# anyway, the OOM is picked up by the sessions module.
508set bigstr [string repeat abcdefghij 100]
509faultsim_delete_and_reopen
510do_test 10.prep.1  {
511  execsql {
512    CREATE TABLE t1(a PRIMARY KEY, b);
513    INSERT INTO t1 VALUES($bigstr, $bigstr);
514  }
515
516  sqlite3session S db main
517  S attach *
518  execsql { UPDATE t1 SET b = b||'x' }
519  set C [S changeset]
520  S delete
521  execsql { UPDATE t1 SET b = b||'xyz' }
522} {}
523faultsim_save_and_close
524
525faultsim_restore_and_reopen
526do_test 10.prep.2  {
527  proc xConflict {args} { return "ABORT" }
528  list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
529} {1 SQLITE_ABORT}
530do_execsql_test 10.prep.3 { SELECT b=$bigstr||'x' FROM t1 } 0
531do_test 10.prep.4  {
532  proc xConflict {args} { return "REPLACE" }
533  list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
534} {0 {}}
535do_execsql_test 10.prep.5 { SELECT b=$bigstr||'x' FROM t1 } 1
536db close
537
538do_faultsim_test 10 -faults oom-tra* -prep {
539  faultsim_restore_and_reopen
540} -body {
541  sqlite3changeset_apply_replace_all db $::C
542} -test {
543  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
544  if {$testrc==0} {
545    if {[db one {SELECT b=$bigstr||'x' FROM t1}]==0} {
546      error "data does not look right"
547    }
548  }
549}
550
551#-------------------------------------------------------------------------
552# Test an OOM with an sqlite3changeset_apply() filter callback.
553#
554reset_db
555do_test 11.prep {
556  execsql {
557    CREATE TABLE t1(a PRIMARY KEY, b);
558    CREATE TABLE t2(x PRIMARY KEY, y);
559    BEGIN;
560  }
561
562  set ::cs [changeset_from_sql {
563    INSERT INTO t1 VALUES(1, 2);
564    INSERT INTO t2 VALUES('x', 'y');
565  }]
566
567  execsql ROLLBACK
568  set {} {}
569} {}
570
571proc filter {x} { return [string equal t1 $x] }
572faultsim_save_and_close
573
574do_faultsim_test 11 -faults oom-tra* -prep {
575  faultsim_restore_and_reopen
576} -body {
577  sqlite3changeset_apply db $::cs {} filter
578} -test {
579  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
580  if {$testrc==0} {
581    if {[db eval {SELECT * FROM t1 UNION ALL SELECT * FROM t2}] != "1 2"} {
582      error "data does not look right"
583    }
584  }
585}
586
587
588finish_test
589