modernc.org/cc@v1.0.1/v2/testdata/_sqlite/ext/fts5/test/fts5corrupt2.test (about) 1 # 2015 Apr 24 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 # 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. 15 # 16 17 source [file join [file dirname [info script]] fts5_common.tcl] 18 set testprefix fts5corrupt2 19 20 # If SQLITE_ENABLE_FTS5 is defined, omit this file. 21 ifcapable !fts5 { 22 finish_test 23 return 24 } 25 sqlite3_fts5_may_be_corrupt 1 26 27 # Create a simple FTS5 table containing 100 documents. Each document 28 # contains 10 terms, each of which start with the character "x". 29 # 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; 37 } 38 set mask [expr 31 << 31] 39 40 if 0 { 41 42 # Test 1: 43 # 44 # For each page in the t1_data table, open a transaction and DELETE 45 # the t1_data entry. Then run: 46 # 47 # * an integrity-check, and 48 # * unless the deleted block was a b-tree node, a query for "t1 MATCH 'x*'" 49 # 50 # and check that the corruption is detected in both cases. The 51 # rollback the transaction. 52 # 53 # Test 2: 54 # 55 # Same thing, except instead of deleting a row from t1_data, replace its 56 # blob content with integer value 14. 57 # 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 } 85 } 86 87 } 88 89 # Using the same database as the 1.* tests. 90 # 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. 94 # 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. 98 # 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 for {set i [expr $nbyte-2]} {$i>=0} {incr i -1} { 103 do_execsql_test 2.$i.1 { 104 BEGIN; 105 UPDATE t1_data SET block = substr(block, 1, $i) WHERE rowid=$lrowid; 106 } 107 108 do_catchsql_test 2.$i.2 { 109 INSERT INTO t1(t1) VALUES('integrity-check'); 110 } {1 {database disk image is malformed}} 111 112 do_test 2.$i.3 { 113 set res [catchsql {SELECT rowid FROM t1 WHERE t1 MATCH 'x*'}] 114 expr { 115 $res=="1 {database disk image is malformed}" 116 || $res=="0 {$all}" 117 } 118 } 1 119 120 do_execsql_test 2.$i.4 { 121 ROLLBACK; 122 INSERT INTO t1(t1) VALUES('integrity-check'); 123 } {} 124 } 125 126 #------------------------------------------------------------------------- 127 # Test that corruption in leaf page headers is detected by queries that use 128 # doclist-indexes. 129 # 130 set doc "A B C D E F G H I J " 131 do_execsql_test 3.0 { 132 CREATE VIRTUAL TABLE x3 USING fts5(tt); 133 INSERT INTO x3(x3, rank) VALUES('pgsz', 32); 134 WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<1000) 135 INSERT INTO x3 136 SELECT ($doc || CASE WHEN (i%50)==0 THEN 'X' ELSE 'Y' END) FROM ii; 137 } 138 139 foreach {tn hdr} { 140 1 "\x00\x00\x00\x00" 141 2 "\xFF\xFF\xFF\xFF" 142 3 "\x44\x45" 143 } { 144 set tn2 0 145 set nCorrupt 0 146 set nCorrupt2 0 147 foreach rowid [db eval {SELECT rowid FROM x3_data WHERE rowid>10}] { 148 if {$rowid & $mask} continue 149 incr tn2 150 do_test 3.$tn.$tn2.1 { 151 execsql BEGIN 152 153 set fd [db incrblob main x3_data block $rowid] 154 fconfigure $fd -encoding binary -translation binary 155 set existing [read $fd [string length $hdr]] 156 seek $fd 0 157 puts -nonewline $fd $hdr 158 close $fd 159 160 set res [catchsql {SELECT rowid FROM x3 WHERE x3 MATCH 'x AND a'}] 161 if {$res == "1 {database disk image is malformed}"} {incr nCorrupt} 162 set {} 1 163 } {1} 164 165 if {($tn2 % 10)==0 && $existing != $hdr} { 166 do_test 3.$tn.$tn2.2 { 167 catchsql { INSERT INTO x3(x3) VALUES('integrity-check') } 168 } {1 {database disk image is malformed}} 169 } 170 171 execsql ROLLBACK 172 } 173 174 do_test 3.$tn.x { expr $nCorrupt>0 } 1 175 } 176 177 #-------------------------------------------------------------------- 178 # 179 set doc "A B C D E F G H I J " 180 do_execsql_test 4.0 { 181 CREATE VIRTUAL TABLE x4 USING fts5(tt); 182 INSERT INTO x4(x4, rank) VALUES('pgsz', 32); 183 WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<10) 184 INSERT INTO x4 185 SELECT ($doc || CASE WHEN (i%50)==0 THEN 'X' ELSE 'Y' END) FROM ii; 186 } 187 188 foreach {tn nCut} { 189 1 1 190 2 10 191 } { 192 set tn2 0 193 set nCorrupt 0 194 foreach rowid [db eval {SELECT rowid FROM x4_data WHERE rowid>10}] { 195 if {$rowid & $mask} continue 196 incr tn2 197 do_test 4.$tn.$tn2 { 198 execsql { 199 BEGIN; 200 UPDATE x4_data SET block = substr(block, 1, length(block)-$nCut) 201 WHERE id = $rowid; 202 } 203 204 set res [catchsql { 205 SELECT rowid FROM x4 WHERE x4 MATCH 'a' ORDER BY 1 DESC 206 }] 207 if {$res == "1 {database disk image is malformed}"} {incr nCorrupt} 208 set {} 1 209 } {1} 210 211 execsql ROLLBACK 212 } 213 214 # do_test 4.$tn.x { expr $nCorrupt>0 } 1 215 } 216 217 set doc [string repeat "A B C " 1000] 218 do_execsql_test 5.0 { 219 CREATE VIRTUAL TABLE x5 USING fts5(tt); 220 INSERT INTO x5(x5, rank) VALUES('pgsz', 32); 221 WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<10) 222 INSERT INTO x5 SELECT $doc FROM ii; 223 } 224 225 foreach {tn hdr} { 226 1 "\x00\x01" 227 } { 228 set tn2 0 229 set nCorrupt 0 230 foreach rowid [db eval {SELECT rowid FROM x5_data WHERE rowid>10}] { 231 if {$rowid & $mask} continue 232 incr tn2 233 do_test 5.$tn.$tn2 { 234 execsql BEGIN 235 236 set fd [db incrblob main x5_data block $rowid] 237 fconfigure $fd -encoding binary -translation binary 238 puts -nonewline $fd $hdr 239 close $fd 240 241 catchsql { INSERT INTO x5(x5) VALUES('integrity-check') } 242 set {} {} 243 } {} 244 245 execsql ROLLBACK 246 } 247 } 248 249 #-------------------------------------------------------------------- 250 reset_db 251 do_execsql_test 6.1 { 252 CREATE VIRTUAL TABLE x5 USING fts5(tt); 253 INSERT INTO x5 VALUES('a'); 254 INSERT INTO x5 VALUES('a a'); 255 INSERT INTO x5 VALUES('a a a'); 256 INSERT INTO x5 VALUES('a a a a'); 257 258 UPDATE x5_docsize SET sz = X'' WHERE id=3; 259 } 260 proc colsize {cmd i} { 261 $cmd xColumnSize $i 262 } 263 sqlite3_fts5_create_function db colsize colsize 264 265 do_catchsql_test 6.2 { 266 SELECT colsize(x5, 0) FROM x5 WHERE x5 MATCH 'a' 267 } {1 SQLITE_CORRUPT_VTAB} 268 269 270 sqlite3_fts5_may_be_corrupt 0 271 finish_test