Snapshot of upstream SQLite 3.42.0
[sqlcipher.git] / ext / fts5 / test / fts5corrupt2.test
bloba815320b763fb8eb3d218aeff0d83181808e15a2
1 # 2015 Apr 24
3 # The author disclaims copyright to this source code.  In place of
4 # a legal notice, here is a blessing:
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.
10 #***********************************************************************
12 # This file tests that FTS5 handles corrupt databases (i.e. internal
13 # inconsistencies in the backing tables) correctly. In this case 
14 # "correctly" means without crashing.
17 source [file join [file dirname [info script]] fts5_common.tcl]
18 set testprefix fts5corrupt2
20 # If SQLITE_ENABLE_FTS5 is defined, omit this file.
21 ifcapable !fts5 {
22   finish_test
23   return
25 sqlite3_fts5_may_be_corrupt 1
27 # Create a simple FTS5 table containing 100 documents. Each document 
28 # contains 10 terms, each of which start with the character "x".
30 expr srand(0)
31 db func rnddoc fts5_rnddoc
32 do_execsql_test 1.0 {
33   CREATE VIRTUAL TABLE t1 USING fts5(x);
34   INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
35   WITH ii(i) AS (SELECT 1 UNION SELECT i+1 FROM ii WHERE i<100)
36   INSERT INTO t1 SELECT rnddoc(10) FROM ii;
38 set mask [expr 31 << 31]
40 if 0 {
42 # Test 1:
44 #   For each page in the t1_data table, open a transaction and DELETE
45 #   the t1_data entry. Then run:
47 #     * an integrity-check, and
48 #     * unless the deleted block was a b-tree node, a query for "t1 MATCH 'x*'"
50 #   and check that the corruption is detected in both cases. The 
51 #   rollback the transaction.
53 # Test 2:
55 #   Same thing, except instead of deleting a row from t1_data, replace its
56 #   blob content with integer value 14.
58 foreach {tno stmt} {
59   1 { DELETE FROM t1_data WHERE rowid=$rowid }
60   2 { UPDATE t1_data SET block=14 WHERE rowid=$rowid }
61 } {
62   set tn 0
63   foreach rowid [db eval {SELECT rowid FROM t1_data WHERE rowid>10}] {
64     incr tn
65     #if {$tn!=224} continue
66   
67     do_test 1.$tno.$tn.1.$rowid {
68       execsql { BEGIN }
69       execsql $stmt
70       catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
71     } {1 {database disk image is malformed}}
72   
73     if {($rowid & $mask)==0} {
74       # Node is a leaf node, not a b-tree node.
75       do_catchsql_test 1.$tno.$tn.2.$rowid {
76         SELECT rowid FROM t1 WHERE t1 MATCH 'x*'
77       } {1 {database disk image is malformed}}
78     }
79   
80     do_execsql_test 1.$tno.$tn.3.$rowid {
81       ROLLBACK;
82       INSERT INTO t1(t1) VALUES('integrity-check');
83     } {}
84   }
89 # Using the same database as the 1.* tests.
91 # Run N-1 tests, where N is the number of bytes in the rightmost leaf page
92 # of the fts index. For test $i, truncate the rightmost leafpage to $i
93 # bytes. Then test both the integrity-check detects the corruption.
95 # Also tested is that "MATCH 'x*'" does not crash and sometimes reports
96 # corruption. It may not report the db as corrupt because truncating the
97 # final leaf to some sizes may create a valid leaf page.
99 set lrowid [db one {SELECT max(rowid) FROM t1_data WHERE (rowid & $mask)=0}] 
100 set nbyte [db one {SELECT length(block) FROM t1_data WHERE rowid=$lrowid}]
101 set all [db eval {SELECT rowid FROM t1}]
102 sqlite3_db_config db DEFENSIVE 0
103 for {set i [expr $nbyte-2]} {$i>=0} {incr i -1} {
104   do_execsql_test 2.$i.1 {
105     BEGIN;
106       UPDATE t1_data SET block = substr(block, 1, $i) WHERE rowid=$lrowid;
107   }
109   do_catchsql_test 2.$i.2 {
110     INSERT INTO t1(t1) VALUES('integrity-check');
111   } {1 {database disk image is malformed}}
113   do_test 2.$i.3 {
114     set res [catchsql {SELECT rowid FROM t1 WHERE t1 MATCH 'x*'}]
115     expr {
116         $res=="1 {database disk image is malformed}" 
117      || $res=="0 {$all}" 
118     }
119   } 1
121   do_execsql_test 2.$i.4 {
122     ROLLBACK;
123     INSERT INTO t1(t1) VALUES('integrity-check');
124   } {}
127 #-------------------------------------------------------------------------
128 # Test that corruption in leaf page headers is detected by queries that use
129 # doclist-indexes.
131 set doc "A B C D E F G H I J "
132 do_execsql_test 3.0 {
133   CREATE VIRTUAL TABLE x3 USING fts5(tt);
134   INSERT INTO x3(x3, rank) VALUES('pgsz', 32);
135   WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<1000) 
136   INSERT INTO x3 
137   SELECT ($doc || CASE WHEN (i%50)==0 THEN 'X' ELSE 'Y' END) FROM ii;
140 foreach {tn hdr} {
141   1 "\x00\x00\x00\x00"
142   2 "\xFF\xFF\xFF\xFF"
143   3 "\x44\x45"
144 } {
145   set tn2 0
146   set nCorrupt 0
147   set nCorrupt2 0
148   foreach rowid [db eval {SELECT rowid FROM x3_data WHERE rowid>10}] {
149     if {$rowid & $mask} continue
150     incr tn2
151     do_test 3.$tn.$tn2.1 {
152       execsql BEGIN
154       set fd [db incrblob main x3_data block $rowid]
155       fconfigure $fd -encoding binary -translation binary
156       set existing [read $fd [string length $hdr]]
157       seek $fd 0
158       puts -nonewline $fd $hdr
159       close $fd
161       set res [catchsql {SELECT rowid FROM x3 WHERE x3 MATCH 'x AND a'}]
162       if {$res == "1 {database disk image is malformed}"} {incr nCorrupt}
163       set {} 1
164     } {1}
166     if {($tn2 % 10)==0 && $existing != $hdr} {
167       do_test 3.$tn.$tn2.2 {
168         catchsql { INSERT INTO x3(x3) VALUES('integrity-check') }
169       } {1 {database disk image is malformed}}
170     }
172     execsql ROLLBACK
173   }
175   do_test 3.$tn.x { expr $nCorrupt>0 } 1
178 #--------------------------------------------------------------------
180 set doc "A B C D E F G H I J "
181 do_execsql_test 4.0 {
182   CREATE VIRTUAL TABLE x4 USING fts5(tt);
183   INSERT INTO x4(x4, rank) VALUES('pgsz', 32);
184   WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<10) 
185   INSERT INTO x4 
186   SELECT ($doc || CASE WHEN (i%50)==0 THEN 'X' ELSE 'Y' END) FROM ii;
189 foreach {tn nCut} {
190   1 1
191   2 10
192 } {
193   set tn2 0
194   set nCorrupt 0
195   foreach rowid [db eval {SELECT rowid FROM x4_data WHERE rowid>10}] {
196     if {$rowid & $mask} continue
197     incr tn2
198     do_test 4.$tn.$tn2 {
199       execsql {
200         BEGIN;
201           UPDATE x4_data SET block = substr(block, 1, length(block)-$nCut) 
202           WHERE id = $rowid;
203       }
205       set res [catchsql {
206         SELECT rowid FROM x4 WHERE x4 MATCH 'a' ORDER BY 1 DESC
207       }]
208       if {$res == "1 {database disk image is malformed}"} {incr nCorrupt}
209       set {} 1
210     } {1}
212     execsql ROLLBACK
213   }
215   # do_test 4.$tn.x { expr $nCorrupt>0 } 1
218 set doc [string repeat "A B C " 1000]
219 do_execsql_test 5.0 {
220   CREATE VIRTUAL TABLE x5 USING fts5(tt);
221   INSERT INTO x5(x5, rank) VALUES('pgsz', 32);
222   WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<10) 
223   INSERT INTO x5 SELECT $doc FROM ii;
226 foreach {tn hdr} {
227   1 "\x00\x01"
228 } {
229   set tn2 0
230   set nCorrupt 0
231   foreach rowid [db eval {SELECT rowid FROM x5_data WHERE rowid>10}] {
232     if {$rowid & $mask} continue
233     incr tn2
234     do_test 5.$tn.$tn2 {
235       execsql BEGIN
237       set fd [db incrblob main x5_data block $rowid]
238       fconfigure $fd -encoding binary -translation binary
239       puts -nonewline $fd $hdr
240       close $fd
242       catchsql { INSERT INTO x5(x5) VALUES('integrity-check') }
243       set {} {}
244     } {}
246     execsql ROLLBACK
247   }
250 #--------------------------------------------------------------------
251 reset_db
252 sqlite3_db_config db DEFENSIVE 0
253 do_execsql_test 6.1 {
254   CREATE VIRTUAL TABLE x5 USING fts5(tt);
255   INSERT INTO x5 VALUES('a');
256   INSERT INTO x5 VALUES('a a');
257   INSERT INTO x5 VALUES('a a a');
258   INSERT INTO x5 VALUES('a a a a');
260   UPDATE x5_docsize SET sz = X'' WHERE id=3;
262 proc colsize {cmd i} { 
263   $cmd xColumnSize $i
265 sqlite3_fts5_create_function db colsize colsize
267 do_catchsql_test 6.2 {
268   SELECT colsize(x5, 0) FROM x5 WHERE x5 MATCH 'a'
269 } {1 SQLITE_CORRUPT_VTAB}
272 sqlite3_fts5_may_be_corrupt 0
273 finish_test