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#-------------------------------------------------------------------------
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 -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
254#-------------------------------------------------------------------------
255# Test that OOM errors in sqlite3changeset_concat() are handled correctly.
256#
257catch {db close}
258forcedelete test.db
259sqlite3 db test.db
260do_execsql_test 5.prep1 {
261  CREATE TABLE t1(a, b, PRIMARY KEY(b));
262  CREATE TABLE t2(a PRIMARY KEY, b);
263  INSERT INTO t1 VALUES('string', 1);
264  INSERT INTO t1 VALUES(4, 2);
265  INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3);
266}
267
268do_test 6.prep2 {
269  sqlite3session M db main
270  M attach *
271  set ::c2 [changeset_from_sql {
272    INSERT INTO t2 VALUES(randomblob(1000), randomblob(1000));
273    INSERT INTO t2 VALUES('one', 'two');
274    INSERT INTO t2 VALUES(1, NULL);
275    UPDATE t1 SET a = 5 WHERE a = 2;
276  }]
277  set ::c1 [changeset_from_sql {
278    DELETE FROM t2 WHERE a = 1;
279    UPDATE t1 SET a = 4 WHERE a = 2;
280    INSERT INTO t2 VALUES('x', 'y');
281  }]
282  set ::total [changeset_to_list [M changeset]]
283  M delete
284} {}
285
286do_faultsim_test 6 -faults oom-* -body {
287  set ::result [sqlite3changeset_concat $::c1 $::c2]
288  set {} {}
289} -test {
290  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
291  if {$testrc==0} {
292    set v [changeset_to_list $::result]
293    if {$v != $::total} { error "result no good" }
294  }
295}
296
297faultsim_delete_and_reopen
298do_execsql_test 5.prep1 {
299  CREATE TABLE t1(a, b, PRIMARY KEY(a));
300}
301faultsim_save_and_close
302
303set res [list]
304for {set ::i 0} {$::i < 480} {incr ::i 4} {
305  lappend res "INSERT t1 0 X. {} {i $::i i $::i}"
306}
307set res [lsort $res]
308do_faultsim_test 7 -faults oom-transient -prep {
309  faultsim_restore_and_reopen
310  sqlite3session S db main
311  S attach *
312} -body {
313  for {set ::i 0} {$::i < 480} {incr ::i 4} {
314    execsql {INSERT INTO t1 VALUES($::i, $::i)}
315  }
316} -test {
317  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
318  if {$testrc==0} {
319    set cres [list [catch {changeset_to_list [S changeset]} msg] $msg]
320    S delete
321    if {$cres != "1 SQLITE_NOMEM" && $cres != "0 {$::res}"} {
322      error "Expected {0 $::res} Got {$cres}"
323    }
324  } else {
325    S changeset
326    S delete
327  }
328}
329
330faultsim_delete_and_reopen
331do_test 8.prep {
332  sqlite3session S db main
333  S attach *
334  execsql {
335    CREATE TABLE t1(a, b, PRIMARY KEY(a));
336    INSERT INTO t1 VALUES(1, 2);
337    INSERT INTO t1 VALUES(3, 4);
338    INSERT INTO t1 VALUES(5, 6);
339  }
340  set ::changeset [S changeset]
341  S delete
342} {}
343
344set expected [normalize_list {
345  {INSERT t1 0 X. {} {i 1 i 2}}
346  {INSERT t1 0 X. {} {i 3 i 4}}
347  {INSERT t1 0 X. {} {i 5 i 6}}
348}]
349do_faultsim_test 8.1 -faults oom* -body {
350  set ::res [list]
351  sqlite3session_foreach -next v $::changeset { lappend ::res $v }
352  normalize_list $::res
353} -test {
354  faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
355}
356do_faultsim_test 8.2 -faults oom* -body {
357  set ::res [list]
358  sqlite3session_foreach v $::changeset { lappend ::res $v }
359  normalize_list $::res
360} -test {
361  faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
362}
363
364faultsim_delete_and_reopen
365do_test 9.1.prep {
366  execsql {
367    PRAGMA encoding = 'utf16';
368    CREATE TABLE t1(a PRIMARY KEY, b);
369  }
370} {}
371faultsim_save_and_close
372
373set answers [list {0 {}} {1 SQLITE_NOMEM} \
374                  {1 {callback requested query abort}} \
375                  {1 {abort due to ROLLBACK}}]
376do_faultsim_test 9.1 -faults oom-transient -prep {
377  catch { unset ::c }
378  faultsim_restore_and_reopen
379  sqlite3session S db main
380  S attach *
381} -body {
382  execsql {
383    INSERT INTO t1 VALUES('abcdefghijklmnopqrstuv', 'ABCDEFGHIJKLMNOPQRSTUV');
384  }
385  set ::c [S changeset]
386  set {} {}
387} -test {
388  S delete
389  eval faultsim_test_result $::answers
390  if {[info exists ::c]} {
391    set expected [normalize_list {
392      {INSERT t1 0 X. {} {t abcdefghijklmnopqrstuv t ABCDEFGHIJKLMNOPQRSTUV}}
393    }]
394    if { [changeset_to_list $::c] != $expected } {
395      error "changeset mismatch"
396    }
397  }
398}
399
400faultsim_delete_and_reopen
401do_test 9.2.prep {
402  execsql {
403    PRAGMA encoding = 'utf16';
404    CREATE TABLE t1(a PRIMARY KEY, b);
405    INSERT INTO t1 VALUES('abcdefghij', 'ABCDEFGHIJKLMNOPQRSTUV');
406  }
407} {}
408faultsim_save_and_close
409
410set answers [list {0 {}} {1 SQLITE_NOMEM} \
411                  {1 {callback requested query abort}} \
412                  {1 {abort due to ROLLBACK}}]
413do_faultsim_test 9.2 -faults oom-transient -prep {
414  catch { unset ::c }
415  faultsim_restore_and_reopen
416  sqlite3session S db main
417  S attach *
418} -body {
419  execsql {
420    UPDATE t1 SET b = 'xyz';
421  }
422  set ::c [S changeset]
423  set {} {}
424} -test {
425  S delete
426  eval faultsim_test_result $::answers
427  if {[info exists ::c]} {
428    set expected [normalize_list {
429      {UPDATE t1 0 X. {t abcdefghij t ABCDEFGHIJKLMNOPQRSTUV} {{} {} t xyz}}
430    }]
431    if { [changeset_to_list $::c] != $expected } {
432      error "changeset mismatch"
433    }
434  }
435}
436
437#-------------------------------------------------------------------------
438# Test that if a conflict-handler encounters an OOM in
439# sqlite3_value_text() but goes on to return SQLITE_CHANGESET_REPLACE
440# anyway, the OOM is picked up by the sessions module.
441set bigstr [string repeat abcdefghij 100]
442faultsim_delete_and_reopen
443do_test 10.prep.1  {
444  execsql {
445    CREATE TABLE t1(a PRIMARY KEY, b);
446    INSERT INTO t1 VALUES($bigstr, $bigstr);
447  }
448
449  sqlite3session S db main
450  S attach *
451  execsql { UPDATE t1 SET b = b||'x' }
452  set C [S changeset]
453  S delete
454  execsql { UPDATE t1 SET b = b||'xyz' }
455} {}
456faultsim_save_and_close
457
458faultsim_restore_and_reopen
459do_test 10.prep.2  {
460  proc xConflict {args} { return "ABORT" }
461  list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
462} {1 SQLITE_ABORT}
463do_execsql_test 10.prep.3 { SELECT b=$bigstr||'x' FROM t1 } 0
464do_test 10.prep.4  {
465  proc xConflict {args} { return "REPLACE" }
466  list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
467} {0 {}}
468do_execsql_test 10.prep.5 { SELECT b=$bigstr||'x' FROM t1 } 1
469db close
470
471do_faultsim_test 10 -faults oom-tra* -prep {
472  faultsim_restore_and_reopen
473} -body {
474  sqlite3changeset_apply_replace_all db $::C
475} -test {
476  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
477  if {$testrc==0} {
478    if {[db one {SELECT b=$bigstr||'x' FROM t1}]==0} {
479      error "data does not look right"
480    }
481  }
482}
483
484
485
486
487finish_test
488