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