modernc.org/cc@v1.0.1/v2/testdata/_sqlite/ext/fts5/test/fts5corrupt3.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 fts5corrupt3
    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  proc create_t1 {} {
    28    expr srand(0)
    29    db func rnddoc fts5_rnddoc
    30    db eval {
    31      CREATE VIRTUAL TABLE t1 USING fts5(x);
    32      INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
    33      WITH ii(i) AS (SELECT 1 UNION SELECT i+1 FROM ii WHERE i<100)
    34        INSERT INTO t1 SELECT rnddoc(10) FROM ii;
    35    }
    36  }
    37  
    38  if 1 {
    39  
    40  # Create a simple FTS5 table containing 100 documents. Each document 
    41  # contains 10 terms, each of which start with the character "x".
    42  #
    43  do_test 1.0 { create_t1 } {}
    44  
    45  do_test 1.1 {
    46    # Pick out the rowid of the right-most b-tree leaf in the new segment.
    47    set rowid [db one {
    48      SELECT max(rowid) FROM t1_data WHERE ((rowid>>31) & 0x0F)==1
    49    }]
    50    set L [db one {SELECT length(block) FROM t1_data WHERE rowid = $rowid}]
    51    set {} {}
    52  } {} 
    53  
    54  for {set i 0} {$i < $L} {incr i} {
    55    do_test 1.2.$i {
    56      catchsql {
    57        BEGIN;
    58        UPDATE t1_data SET block = substr(block, 1, $i) WHERE id = $rowid;
    59        INSERT INTO t1(t1) VALUES('integrity-check');
    60      }
    61    } {1 {database disk image is malformed}}
    62    catchsql ROLLBACK
    63  }
    64   
    65  #-------------------------------------------------------------------------
    66  # Test that trailing bytes appended to the averages record are ignored.
    67  #
    68  do_execsql_test 2.1 {
    69    CREATE VIRTUAL TABLE t2 USING fts5(x);
    70    INSERT INTO t2 VALUES(rnddoc(10));
    71    INSERT INTO t2 VALUES(rnddoc(10));
    72    SELECT length(block) FROM t2_data WHERE id=1;
    73  } {2}
    74  do_execsql_test 2.2 {
    75    UPDATE t2_data SET block = block || 'abcd' WHERE id=1;
    76    SELECT length(block) FROM t2_data WHERE id=1;
    77  } {6}
    78  do_execsql_test 2.2 {
    79    INSERT INTO t2 VALUES(rnddoc(10));
    80    SELECT length(block) FROM t2_data WHERE id=1;
    81  } {2}
    82  
    83  
    84  #-------------------------------------------------------------------------
    85  # Test that missing leaf pages are recognized as corruption.
    86  #
    87  reset_db
    88  do_test 3.0 { create_t1 } {}
    89  
    90  do_execsql_test 3.1 {
    91    SELECT count(*) FROM t1_data;
    92  } {105}
    93  
    94  proc do_3_test {tn} {
    95    set i 0
    96    foreach ::rowid [db eval "SELECT rowid FROM t1_data WHERE rowid>100"] {
    97      incr i
    98      do_test $tn.$i {
    99        db eval BEGIN
   100        db eval {DELETE FROM t1_data WHERE rowid = $::rowid}
   101        list [
   102          catch { db eval {SELECT rowid FROM t1 WHERE t1 MATCH 'x*'} } msg
   103        ] $msg
   104      } {1 {database disk image is malformed}}
   105      catch { db eval ROLLBACK }
   106    }
   107  }
   108  
   109  do_3_test 3.2
   110  
   111  do_execsql_test 3.3 {
   112    INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
   113    INSERT INTO t1 SELECT x FROM t1;
   114    INSERT INTO t1(t1) VALUES('optimize');
   115  } {}
   116  
   117  do_3_test 3.4
   118  
   119  do_test 3.5 {
   120    execsql { 
   121      DELETE FROM t1;
   122      INSERT INTO t1(t1, rank) VALUES('pgsz', 40);
   123    }
   124    for {set i 0} {$i < 1000} {incr i} {
   125      set rnd [expr int(rand() * 1000)]
   126      set doc [string repeat "x$rnd " [expr int(rand() * 3) + 1]]
   127      execsql { INSERT INTO t1(rowid, x) VALUES($i, $doc) }
   128    }
   129  } {}
   130  
   131  do_3_test 3.6
   132  
   133  do_test 3.7 {
   134    execsql {
   135      INSERT INTO t1(t1, rank) VALUES('pgsz', 40);
   136      INSERT INTO t1 SELECT x FROM t1;
   137      INSERT INTO t1(t1) VALUES('optimize');
   138    }
   139  } {}
   140  
   141  do_3_test 3.8
   142  
   143  do_test 3.9 {
   144    execsql { 
   145      DELETE FROM t1;
   146      INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
   147    }
   148    for {set i 0} {$i < 100} {incr i} {
   149      set rnd [expr int(rand() * 100)]
   150      set doc "x[string repeat $rnd 20]"
   151      execsql { INSERT INTO t1(rowid, x) VALUES($i, $doc) }
   152    }
   153  } {}
   154  
   155  do_3_test 3.10
   156  
   157  #-------------------------------------------------------------------------
   158  # Test that segments that end unexpectedly are identified as corruption.
   159  #
   160  reset_db
   161  do_test 4.0 {
   162    execsql { 
   163      CREATE VIRTUAL TABLE t1 USING fts5(x);
   164      INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
   165    }
   166    for {set i 0} {$i < 100} {incr i} {
   167      set rnd [expr int(rand() * 100)]
   168      set doc "x[string repeat $rnd 20]"
   169      execsql { INSERT INTO t1(rowid, x) VALUES($i, $doc) }
   170    }
   171    execsql { INSERT INTO t1(t1) VALUES('optimize') }
   172  } {}
   173  
   174  set nErr 0
   175  for {set i 1} {1} {incr i} {
   176    set struct [db one {SELECT block FROM t1_data WHERE id=10}]
   177    binary scan $struct c* var
   178    set end [lindex $var end]
   179    if {$end<=$i} break
   180    lset var end [expr $end - $i]
   181    set struct [binary format c* $var]
   182  
   183    db close
   184    sqlite3 db test.db
   185  
   186    db eval {
   187      BEGIN;
   188      UPDATE t1_data SET block = $struct WHERE id=10;
   189    }
   190    do_test 4.1.$i {
   191      incr nErr [catch { db eval { SELECT rowid FROM t1 WHERE t1 MATCH 'x*' } }]
   192      set {} {}
   193    } {}
   194    catch { db eval ROLLBACK }
   195  }
   196  do_test 4.1.x { expr $nErr>45 } 1
   197  
   198  #-------------------------------------------------------------------------
   199  #
   200  
   201  # The first argument passed to this command must be a binary blob 
   202  # containing an FTS5 leaf page. This command returns a copy of this
   203  # blob, with the pgidx of the leaf page replaced by a single varint
   204  # containing value $iVal.
   205  #
   206  proc rewrite_pgidx {blob iVal} {
   207    binary scan $blob SS off1 szLeaf
   208    if {$iVal<0 || $iVal>=128} {
   209      error "$iVal out of range!"
   210    } else {
   211      set pgidx [binary format c $iVal]
   212    }
   213  
   214    binary format a${szLeaf}a* $blob $pgidx
   215  }
   216  
   217  reset_db
   218  do_execsql_test 5.1 {
   219    CREATE VIRTUAL TABLE x1 USING fts5(x);
   220    INSERT INTO x1(x1, rank) VALUES('pgsz', 40);
   221    BEGIN;
   222    INSERT INTO x1 VALUES('xaaa xabb xccc xcdd xeee xeff xggg xghh xiii xijj');
   223    INSERT INTO x1 SELECT x FROM x1;
   224    INSERT INTO x1 SELECT x FROM x1;
   225    INSERT INTO x1 SELECT x FROM x1;
   226    INSERT INTO x1 SELECT x FROM x1;
   227    INSERT INTO x1(x1) VALUES('optimize');
   228    COMMIT;
   229  }
   230  
   231  #db eval { SELECT fts5_decode(id, block) b from x1_data } { puts $b }
   232  #
   233  db func rewrite_pgidx rewrite_pgidx  
   234  set i 0
   235  foreach rowid [db eval {SELECT rowid FROM x1_data WHERE rowid>100}] {
   236    foreach val {2 100} {
   237      do_test 5.2.$val.[incr i] {
   238        catchsql {
   239          BEGIN;
   240          UPDATE x1_data SET block=rewrite_pgidx(block, $val) WHERE id=$rowid;
   241          SELECT rowid FROM x1 WHERE x1 MATCH 'xa*';
   242          SELECT rowid FROM x1 WHERE x1 MATCH 'xb*';
   243          SELECT rowid FROM x1 WHERE x1 MATCH 'xc*';
   244          SELECT rowid FROM x1 WHERE x1 MATCH 'xd*';
   245          SELECT rowid FROM x1 WHERE x1 MATCH 'xe*';
   246          SELECT rowid FROM x1 WHERE x1 MATCH 'xf*';
   247          SELECT rowid FROM x1 WHERE x1 MATCH 'xg*';
   248          SELECT rowid FROM x1 WHERE x1 MATCH 'xh*';
   249          SELECT rowid FROM x1 WHERE x1 MATCH 'xi*';
   250        }
   251        set {} {}
   252      } {}
   253      catch { db eval ROLLBACK }
   254    }
   255  }
   256  
   257  #------------------------------------------------------------------------
   258  #
   259  reset_db
   260  do_execsql_test 6.1.0 {
   261    CREATE VIRTUAL TABLE t1 USING fts5(a);
   262    INSERT INTO t1 VALUES('bbbbb ccccc');
   263    SELECT quote(block) FROM t1_data WHERE rowid>100;
   264  } {X'000000180630626262626201020201056363636363010203040A'}
   265  do_execsql_test 6.1.1 {
   266    UPDATE t1_data SET block = 
   267    X'000000180630626262626201020201056161616161010203040A'
   268    WHERE rowid>100;
   269  }
   270  do_catchsql_test 6.1.2 {
   271    INSERT INTO t1(t1) VALUES('integrity-check');
   272  } {1 {database disk image is malformed}}
   273  
   274  #-------
   275  reset_db
   276  do_execsql_test 6.2.0 {
   277    CREATE VIRTUAL TABLE t1 USING fts5(a);
   278    INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
   279    INSERT INTO t1 VALUES('aa bb cc dd ee');
   280    SELECT pgno, quote(term) FROM t1_idx;
   281  } {2 X'' 4 X'3064'}
   282  do_execsql_test 6.2.1 {
   283    UPDATE t1_idx SET term = X'3065' WHERE pgno=4;
   284  }
   285  do_catchsql_test 6.2.2 {
   286    INSERT INTO t1(t1) VALUES('integrity-check');
   287  } {1 {database disk image is malformed}}
   288  
   289  #-------
   290  reset_db
   291  do_execsql_test 6.3.0 {
   292    CREATE VIRTUAL TABLE t1 USING fts5(a);
   293    INSERT INTO t1 VALUES('abc abcdef abcdefghi');
   294    SELECT quote(block) FROM t1_data WHERE id>100;
   295  }    {X'0000001C043061626301020204036465660102030703676869010204040808'}
   296  do_execsql_test 6.3.1 {
   297    BEGIN;
   298      UPDATE t1_data SET block = 
   299        X'0000001C043061626301020204036465660102035003676869010204040808'
   300        ------------------------------------------^^---------------------
   301      WHERE id>100;
   302  }
   303  do_catchsql_test 6.3.2 {
   304    INSERT INTO t1(t1) VALUES('integrity-check');
   305  } {1 {database disk image is malformed}}
   306  do_execsql_test 6.3.3 {
   307    ROLLBACK;
   308    BEGIN;
   309      UPDATE t1_data SET block = 
   310        X'0000001C043061626301020204036465660102030750676869010204040808'
   311        --------------------------------------------^^-------------------
   312      WHERE id>100;
   313  }
   314  do_catchsql_test 6.3.3 {
   315    INSERT INTO t1(t1) VALUES('integrity-check');
   316  } {1 {database disk image is malformed}}
   317  do_execsql_test 6.3.4 {
   318    ROLLBACK;
   319    BEGIN;
   320      UPDATE t1_data SET block = 
   321        X'0000001C043061626301020204036465660102030707676869010204040850'
   322        --------------------------------------------------------------^^-
   323      WHERE id>100;
   324  }
   325  do_catchsql_test 6.3.5 {
   326    INSERT INTO t1(t1) VALUES('integrity-check');
   327  } {1 {database disk image is malformed}}
   328  do_execsql_test 6.3.6 {
   329    ROLLBACK;
   330    BEGIN;
   331      UPDATE t1_data SET block = 
   332        X'0000001C503061626301020204036465660102030707676869010204040808'
   333        ----------^^-----------------------------------------------------
   334      WHERE id>100;
   335  }
   336  do_catchsql_test 6.3.5 {
   337    INSERT INTO t1(t1) VALUES('integrity-check');
   338  } {1 {database disk image is malformed}}
   339  
   340  
   341  #------------------------------------------------------------------------
   342  #
   343  reset_db
   344  proc rnddoc {n} {
   345    set map [list a b c d]
   346    set doc [list]
   347    for {set i 0} {$i < $n} {incr i} {
   348      lappend doc "x[lindex $map [expr int(rand()*4)]]"
   349    }
   350    set doc
   351  }
   352  
   353  db func rnddoc rnddoc
   354  do_test 7.0 {
   355    execsql {
   356      CREATE VIRTUAL TABLE t5 USING fts5(x);
   357      INSERT INTO t5 VALUES( rnddoc(10000) );
   358      INSERT INTO t5 VALUES( rnddoc(10000) );
   359      INSERT INTO t5 VALUES( rnddoc(10000) );
   360      INSERT INTO t5 VALUES( rnddoc(10000) );
   361      INSERT INTO t5(t5) VALUES('optimize');
   362    }
   363  } {}
   364  
   365  do_test 7.1 {
   366    foreach i [db eval { SELECT rowid FROM t5_data WHERE rowid>100 }] {
   367      db eval BEGIN  
   368      db eval {DELETE FROM t5_data WHERE rowid = $i}
   369      set r [catchsql { INSERT INTO t5(t5) VALUES('integrity-check')} ]
   370      if {$r != "1 {database disk image is malformed}"} { error $r }
   371      db eval ROLLBACK  
   372    }
   373  } {}
   374  
   375  }
   376  
   377  #------------------------------------------------------------------------
   378  # Corruption within the structure record.
   379  #
   380  reset_db
   381  do_execsql_test 8.1 {
   382    CREATE VIRTUAL TABLE t1 USING fts5(x, y);
   383    INSERT INTO t1 VALUES('one', 'two');
   384  }
   385  
   386  do_test 9.1.1 {
   387    set    blob "12345678"    ;# cookie
   388    append blob "0105"        ;# 1 level, total of 5 segments
   389    append blob "06"          ;# write counter
   390    append blob "0002"        ;# first level has 0 segments merging, 2 other.
   391    append blob "450108"      ;# first segment
   392    execsql "REPLACE INTO t1_data VALUES(10, X'$blob')"
   393  } {}
   394  do_catchsql_test 9.1.2 {
   395    SELECT * FROM t1('one AND two');
   396  } {1 {database disk image is malformed}}
   397  
   398  do_test 9.2.1 {
   399    set    blob "12345678"    ;# cookie
   400    append blob "0205"        ;# 2 levels, total of 5 segments
   401    append blob "06"          ;# write counter
   402    append blob "0001"        ;# first level has 0 segments merging, 1 other.
   403    append blob "450108"      ;# first segment
   404    execsql "REPLACE INTO t1_data VALUES(10, X'$blob')"
   405  } {}
   406  do_catchsql_test 9.2.2 {
   407    SELECT * FROM t1('one AND two');
   408  } {1 {database disk image is malformed}}
   409  
   410  sqlite3_fts5_may_be_corrupt 0
   411  finish_test