fts5corrupt2.test 7.5 KB

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