/ Check-in [51444f67]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fix compression of keys stored on internal segment b-tree nodes by fts5.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | fts5
Files: files | file ages | folders
SHA1: 51444f67c0cc58a3023eb1cd78e7cf889da6c80f
User & Date: dan 2015-01-23 17:43:21
Context
2015-01-24
19:57
Have fts5 store rowids in ascending order. Query speed is virtually the same regardless of rowid order, and ascending order makes some insert optimizations easier. check-in: 5206ca60 user: dan tags: fts5
2015-01-23
17:43
Fix compression of keys stored on internal segment b-tree nodes by fts5. check-in: 51444f67 user: dan tags: fts5
06:50
Remove some redundant code from fts5. check-in: 939b7a5d user: dan tags: fts5
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/fts5/fts5.c.

  1017   1017       rc = sqlite3_step(pCsr->pStmt);
  1018   1018       if( rc==SQLITE_ROW ){
  1019   1019         rc = SQLITE_OK;
  1020   1020         CsrFlagClear(pCsr, FTS5CSR_REQUIRE_CONTENT);
  1021   1021       }else{
  1022   1022         rc = sqlite3_reset(pCsr->pStmt);
  1023   1023         if( rc==SQLITE_OK ){
  1024         -        rc = SQLITE_CORRUPT_VTAB;
         1024  +        rc = FTS5_CORRUPT;
  1025   1025         }
  1026   1026       }
  1027   1027     }
  1028   1028     return rc;
  1029   1029   }
  1030   1030   
  1031   1031   static void fts5SetVtabError(Fts5Table *p, const char *zFormat, ...){

Changes to ext/fts5/fts5Int.h.

    28     28   
    29     29   #define FTS5_DEFAULT_NEARDIST 10
    30     30   #define FTS5_DEFAULT_RANK     "bm25"
    31     31   
    32     32   /* Name of rank and rowid columns */
    33     33   #define FTS5_RANK_NAME "rank"
    34     34   #define FTS5_ROWID_NAME "rowid"
           35  +
           36  +#ifdef SQLITE_DEBUG
           37  +# define FTS5_CORRUPT sqlite3Fts5Corrupt()
           38  +int sqlite3Fts5Corrupt(void);
           39  +#else
           40  +# define FTS5_CORRUPT SQLITE_CORRUPT_VTAB
           41  +#endif
    35     42   
    36     43   /**************************************************************************
    37     44   ** Interface to code in fts5.c. 
    38     45   */
    39     46   typedef struct Fts5Global Fts5Global;
    40     47   
    41     48   int sqlite3Fts5GetTokenizer(

Changes to ext/fts5/fts5_index.c.

   252    252   ** The rowid for the doclist index associated with leaf page pgno of segment
   253    253   ** segid in index idx.
   254    254   */
   255    255   #define FTS5_DOCLIST_IDX_ROWID(idx, segid, pgno) \
   256    256           FTS5_SEGMENT_ROWID(idx, segid, FTS5_SEGMENT_MAX_HEIGHT, pgno)
   257    257   
   258    258   #ifdef SQLITE_DEBUG
   259         -static int fts5Corrupt() { return SQLITE_CORRUPT_VTAB; }
   260         -# define FTS5_CORRUPT fts5Corrupt()
   261         -#else
   262         -# define FTS5_CORRUPT SQLITE_CORRUPT_VTAB
          259  +int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; }
   263    260   #endif
   264    261   
   265    262   
   266    263   /*
   267    264   ** Each time a blob is read from the %_data table, it is padded with this
   268    265   ** many zero bytes. This makes it easier to decode the various record formats
   269    266   ** without overreading if the records are corrupt.
................................................................................
   369    366   
   370    367   /*
   371    368   ** An object of type Fts5SegWriter is used to write to segments.
   372    369   */
   373    370   struct Fts5PageWriter {
   374    371     int pgno;                       /* Page number for this page */
   375    372     Fts5Buffer buf;                 /* Buffer containing page data */
   376         -  Fts5Buffer term;                /* Buffer containing previous term on page */
          373  +  Fts5Buffer term;              /* Buffer containing previous term on page */
   377    374   };
   378    375   struct Fts5SegWriter {
   379    376     int iIdx;                       /* Index to write to */
   380    377     int iSegid;                     /* Segid to write to */
   381    378     int nWriter;                    /* Number of entries in aWriter */
   382    379     Fts5PageWriter *aWriter;        /* Array of PageWriter objects */
   383    380     i64 iPrevRowid;                 /* Previous docid written to current leaf */
   384    381     u8 bFirstRowidInDoclist;        /* True if next rowid is first in doclist */
   385    382     u8 bFirstRowidInPage;           /* True if next rowid is first in page */
          383  +  u8 bFirstTermInPage;            /* True if next term will be first in leaf */
   386    384     int nLeafWritten;               /* Number of leaf pages written */
   387    385     int nEmpty;                     /* Number of contiguous term-less nodes */
   388    386     Fts5Buffer dlidx;               /* Doclist index */
   389    387     i64 iDlidxPrev;                 /* Previous rowid appended to dlidx */
   390    388     int bDlidxPrevValid;            /* True if iDlidxPrev is valid */
   391    389   };
   392    390   
................................................................................
  2673   2671   }
  2674   2672   
  2675   2673   static void fts5WriteFlushLeaf(Fts5Index *p, Fts5SegWriter *pWriter){
  2676   2674     static const u8 zero[] = { 0x00, 0x00, 0x00, 0x00 };
  2677   2675     Fts5PageWriter *pPage = &pWriter->aWriter[0];
  2678   2676     i64 iRowid;
  2679   2677   
  2680         -  if( pPage->term.n==0 ){
         2678  +  if( pWriter->bFirstTermInPage ){
  2681   2679       /* No term was written to this page. */
  2682   2680       assert( 0==fts5GetU16(&pPage->buf.p[2]) );
  2683   2681       fts5WriteBtreeNoTerm(p, pWriter);
  2684   2682     }
  2685   2683   
  2686   2684     /* Write the current page to the db. */
  2687   2685     iRowid = FTS5_SEGMENT_ROWID(pWriter->iIdx, pWriter->iSegid, 0, pPage->pgno);
  2688   2686     fts5DataWrite(p, iRowid, pPage->buf.p, pPage->buf.n);
  2689   2687   
  2690   2688     /* Initialize the next page. */
  2691   2689     fts5BufferZero(&pPage->buf);
  2692         -  fts5BufferZero(&pPage->term);
  2693   2690     fts5BufferAppendBlob(&p->rc, &pPage->buf, 4, zero);
  2694   2691     pPage->pgno++;
  2695   2692   
  2696   2693     /* Increase the leaves written counter */
  2697   2694     pWriter->nLeafWritten++;
         2695  +
         2696  +  /* The new leaf holds no terms */
         2697  +  pWriter->bFirstTermInPage = 1;
  2698   2698   }
  2699   2699   
  2700   2700   /*
  2701   2701   ** Append term pTerm/nTerm to the segment being written by the writer passed
  2702   2702   ** as the second argument.
  2703   2703   **
  2704   2704   ** If an error occurs, set the Fts5Index.rc error code. If an error has 
................................................................................
  2713   2713     Fts5PageWriter *pPage = &pWriter->aWriter[0];
  2714   2714   
  2715   2715     assert( pPage==0 || pPage->buf.n==0 || pPage->buf.n>4 );
  2716   2716     if( pPage && pPage->buf.n==0 ){
  2717   2717       /* Zero the first term and first docid fields */
  2718   2718       static const u8 zero[] = { 0x00, 0x00, 0x00, 0x00 };
  2719   2719       fts5BufferAppendBlob(&p->rc, &pPage->buf, 4, zero);
  2720         -    assert( pPage->term.n==0 );
         2720  +    assert( pWriter->bFirstTermInPage );
  2721   2721     }
  2722   2722     if( p->rc ) return;
  2723   2723     
  2724         -  if( pPage->term.n==0 ){
         2724  +  if( pWriter->bFirstTermInPage ){
  2725   2725       /* Update the "first term" field of the page header. */
  2726   2726       assert( pPage->buf.p[2]==0 && pPage->buf.p[3]==0 );
  2727   2727       fts5PutU16(&pPage->buf.p[2], pPage->buf.n);
  2728   2728       nPrefix = 0;
  2729         -    if( pWriter->aWriter[0].pgno!=1 ){
  2730         -      fts5WriteBtreeTerm(p, pWriter, nTerm, pTerm);
         2729  +    if( pPage->pgno!=1 ){
         2730  +      /* This is the first term on a leaf that is not the leftmost leaf in
         2731  +      ** the segment b-tree. In this case it is necessary to add a term to
         2732  +      ** the b-tree hierarchy that is (a) larger than the largest term 
         2733  +      ** already written to the segment and (b) smaller than or equal to
         2734  +      ** this term. In other words, a prefix of (pTerm/nTerm) that is one
         2735  +      ** byte longer than the longest prefix (pTerm/nTerm) shares with the
         2736  +      ** previous term. 
         2737  +      **
         2738  +      ** Usually, the previous term is available in pPage->term. The exception
         2739  +      ** is if this is the first term written in an incremental-merge step.
         2740  +      ** In this case the previous term is not available, so just write a
         2741  +      ** copy of (pTerm/nTerm) into the parent node. This is slightly
         2742  +      ** inefficient, but still correct.  */
         2743  +      int n = nTerm;
         2744  +      if( pPage->term.n ){
         2745  +        n = 1 + fts5PrefixCompress(pPage->term.n, pPage->term.p, nTerm, pTerm);
         2746  +      }
         2747  +      fts5WriteBtreeTerm(p, pWriter, n, pTerm);
  2731   2748         pPage = &pWriter->aWriter[0];
  2732   2749       }
  2733   2750     }else{
  2734         -    nPrefix = fts5PrefixCompress(
  2735         -        pPage->term.n, pPage->term.p, nTerm, pTerm
  2736         -    );
         2751  +    nPrefix = fts5PrefixCompress(pPage->term.n, pPage->term.p, nTerm, pTerm);
  2737   2752       fts5BufferAppendVarint(&p->rc, &pPage->buf, nPrefix);
  2738   2753     }
  2739   2754   
  2740   2755     /* Append the number of bytes of new data, then the term data itself
  2741   2756     ** to the page. */
  2742   2757     fts5BufferAppendVarint(&p->rc, &pPage->buf, nTerm - nPrefix);
  2743   2758     fts5BufferAppendBlob(&p->rc, &pPage->buf, nTerm - nPrefix, &pTerm[nPrefix]);
  2744   2759   
  2745   2760     /* Update the Fts5PageWriter.term field. */
  2746   2761     fts5BufferSet(&p->rc, &pPage->term, nTerm, pTerm);
         2762  +  pWriter->bFirstTermInPage = 0;
  2747   2763   
  2748   2764     pWriter->bFirstRowidInPage = 0;
  2749   2765     pWriter->bFirstRowidInDoclist = 1;
  2750   2766   
  2751   2767     /* If the current leaf page is full, flush it to disk. */
  2752   2768     if( pPage->buf.n>=p->pConfig->pgsz ){
  2753   2769       fts5WriteFlushLeaf(p, pWriter);
................................................................................
  2896   2912     pWriter->iIdx = iIdx;
  2897   2913     pWriter->iSegid = iSegid;
  2898   2914   
  2899   2915     pWriter->aWriter = (Fts5PageWriter*)fts5IdxMalloc(p,sizeof(Fts5PageWriter));
  2900   2916     if( pWriter->aWriter==0 ) return;
  2901   2917     pWriter->nWriter = 1;
  2902   2918     pWriter->aWriter[0].pgno = 1;
         2919  +  pWriter->bFirstTermInPage = 1;
  2903   2920   }
  2904   2921   
  2905   2922   static void fts5WriteInitForAppend(
  2906   2923     Fts5Index *p,                   /* FTS5 backend object */
  2907   2924     Fts5SegWriter *pWriter,         /* Writer to initialize */
  2908   2925     int iIdx,                       /* Index segment is a part of */
  2909   2926     Fts5StructureSegment *pSeg      /* Segment object to append to */
................................................................................
  2933   2950           fts5NodeIterFree(&ss);
  2934   2951         }
  2935   2952       }
  2936   2953       if( pSeg->nHeight==1 ){
  2937   2954         pWriter->nEmpty = pSeg->pgnoLast-1;
  2938   2955       }
  2939   2956       assert( (pgno+pWriter->nEmpty)==pSeg->pgnoLast );
         2957  +    pWriter->bFirstTermInPage = 1;
         2958  +    assert( pWriter->aWriter[0].term.n==0 );
  2940   2959     }
  2941   2960   }
  2942   2961   
  2943   2962   /*
  2944   2963   ** Iterator pIter was used to iterate through the input segments of on an
  2945   2964   ** incremental merge operation. This function is called if the incremental
  2946   2965   ** merge step has finished but the input has not been completely exhausted.
................................................................................
  3913   3932   ** error, or some other SQLite error code if another error (e.g. OOM)
  3914   3933   ** occurs.
  3915   3934   */
  3916   3935   int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
  3917   3936     Fts5Config *pConfig = p->pConfig;
  3918   3937     int iIdx;                       /* Used to iterate through indexes */
  3919   3938     u64 cksum2 = 0;                 /* Checksum based on contents of indexes */
         3939  +
         3940  +  /* Check that the internal nodes of each segment match the leaves */
         3941  +  for(iIdx=0; p->rc==SQLITE_OK && iIdx<=pConfig->nPrefix; iIdx++){
         3942  +    Fts5Structure *pStruct = fts5StructureRead(p, iIdx);
         3943  +    if( pStruct ){
         3944  +      int iLvl, iSeg;
         3945  +      for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
         3946  +        for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
         3947  +          Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg];
         3948  +          fts5IndexIntegrityCheckSegment(p, iIdx, pSeg);
         3949  +        }
         3950  +      }
         3951  +    }
         3952  +    fts5StructureRelease(pStruct);
         3953  +  }
  3920   3954   
  3921   3955     /* Check that the checksum of the index matches the argument checksum */
  3922   3956     for(iIdx=0; iIdx<=pConfig->nPrefix; iIdx++){
  3923   3957       Fts5MultiSegIter *pIter;
  3924   3958       Fts5Structure *pStruct = fts5StructureRead(p, iIdx);
  3925   3959       for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, 0, 0, -1, 0, &pIter);
  3926   3960           fts5MultiIterEof(p, pIter)==0;
................................................................................
  3946   3980         }
  3947   3981       }
  3948   3982       fts5MultiIterFree(p, pIter);
  3949   3983       fts5StructureRelease(pStruct);
  3950   3984     }
  3951   3985     if( p->rc==SQLITE_OK && cksum!=cksum2 ) p->rc = FTS5_CORRUPT;
  3952   3986   
  3953         -  /* Check that the internal nodes of each segment match the leaves */
  3954         -  for(iIdx=0; p->rc==SQLITE_OK && iIdx<=pConfig->nPrefix; iIdx++){
  3955         -    Fts5Structure *pStruct = fts5StructureRead(p, iIdx);
  3956         -    if( pStruct ){
  3957         -      int iLvl, iSeg;
  3958         -      for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
  3959         -        for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
  3960         -          Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg];
  3961         -          fts5IndexIntegrityCheckSegment(p, iIdx, pSeg);
  3962         -        }
  3963         -      }
  3964         -    }
  3965         -    fts5StructureRelease(pStruct);
  3966         -  }
  3967         -
  3968   3987     return fts5IndexReturn(p);
  3969   3988   }
  3970   3989   
  3971   3990   
  3972   3991   /*
  3973   3992   ** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain
  3974   3993   ** to the document with rowid iRowid.
................................................................................
  3986   4005       apNew = (Fts5Hash**)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Hash*)*nHash);
  3987   4006       for(i=0; rc==SQLITE_OK && i<nHash; i++){
  3988   4007         rc = sqlite3Fts5HashNew(&apNew[i], &p->nPendingData);
  3989   4008       }
  3990   4009       if( rc==SQLITE_OK ){
  3991   4010         p->apHash = apNew;
  3992   4011       }else{
  3993         -      for(i=0; i<nHash; i++){
  3994         -        sqlite3Fts5HashFree(apNew[i]);
         4012  +      if( apNew ){
         4013  +        for(i=0; i<nHash; i++){
         4014  +          sqlite3Fts5HashFree(apNew[i]);
         4015  +        }
         4016  +        sqlite3_free(apNew);
  3995   4017         }
  3996         -      sqlite3_free(apNew);
  3997   4018         return rc;
  3998   4019       }
  3999   4020     }
  4000   4021   
  4001   4022     if( iRowid<=p->iWriteRowid || (p->nPendingData > p->nMaxPendingData) ){
  4002   4023       fts5IndexFlush(p);
  4003   4024     }

Changes to ext/fts5/fts5_storage.c.

   785    785           rc = sqlite3Fts5Tokenize(
   786    786               pConfig, 
   787    787               (const char*)sqlite3_column_text(pScan, i+1),
   788    788               sqlite3_column_bytes(pScan, i+1),
   789    789               (void*)&ctx,
   790    790               fts5StorageIntegrityCallback
   791    791           );
   792         -        if( ctx.szCol!=aColSize[i] ) rc = SQLITE_CORRUPT_VTAB;
          792  +        if( ctx.szCol!=aColSize[i] ) rc = FTS5_CORRUPT;
   793    793           aTotalSize[i] += ctx.szCol;
   794    794         }
   795    795         if( rc!=SQLITE_OK ) break;
   796    796       }
   797    797       rc2 = sqlite3_reset(pScan);
   798    798       if( rc==SQLITE_OK ) rc = rc2;
   799    799     }
   800    800   
   801    801     /* Test that the "totals" (sometimes called "averages") record looks Ok */
   802    802     if( rc==SQLITE_OK ){
   803    803       int i;
   804    804       rc = fts5StorageLoadTotals(p, 0);
   805    805       for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
   806         -      if( p->aTotalSize[i]!=aTotalSize[i] ) rc = SQLITE_CORRUPT_VTAB;
          806  +      if( p->aTotalSize[i]!=aTotalSize[i] ) rc = FTS5_CORRUPT;
   807    807       }
   808    808     }
   809    809   
   810    810     /* Check that the %_docsize and %_content tables contain the expected
   811    811     ** number of rows.  */
   812    812     if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
   813    813       i64 nRow;
   814    814       rc = fts5StorageCount(p, "content", &nRow);
   815         -    if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = SQLITE_CORRUPT_VTAB;
          815  +    if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT;
   816    816     }
   817    817     if( rc==SQLITE_OK ){
   818    818       i64 nRow;
   819    819       rc = fts5StorageCount(p, "docsize", &nRow);
   820         -    if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = SQLITE_CORRUPT_VTAB;
          820  +    if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT;
   821    821     }
   822    822   
   823    823     /* Pass the expected checksum down to the FTS index module. It will
   824    824     ** verify, amongst other things, that it matches the checksum generated by
   825    825     ** inspecting the index itself.  */
   826    826     if( rc==SQLITE_OK ){
   827    827       rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum);
................................................................................
   909    909         int nBlob = sqlite3_column_bytes(pLookup, 0);
   910    910         if( 0==fts5StorageDecodeSizeArray(aCol, nCol, aBlob, nBlob) ){
   911    911           bCorrupt = 0;
   912    912         }
   913    913       }
   914    914       rc = sqlite3_reset(pLookup);
   915    915       if( bCorrupt && rc==SQLITE_OK ){
   916         -      rc = SQLITE_CORRUPT_VTAB;
          916  +      rc = FTS5_CORRUPT;
   917    917       }
   918    918     }
   919    919     return rc;
   920    920   }
   921    921   
   922    922   int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnToken){
   923    923     int rc = fts5StorageLoadTotals(p, 0);

Changes to ext/fts5/test/fts5_common.tcl.

   118    118     set sql "SELECT fts5_decode(rowid,block) aS r FROM ${tbl}_data WHERE rowid=10"
   119    119     set ret [list]
   120    120     foreach L [lrange [db one $sql] 1 end] {
   121    121       lappend ret [expr [llength $L] - 2]
   122    122     }
   123    123     set ret
   124    124   } 
          125  +
          126  +proc fts5_level_segids {tbl} {
          127  +  set sql "SELECT fts5_decode(rowid,block) aS r FROM ${tbl}_data WHERE rowid=10"
          128  +  set ret [list]
          129  +  foreach L [lrange [db one $sql] 1 end] {
          130  +    set lvl [list]
          131  +    foreach S [lrange $L 2 end] {
          132  +      regexp {id=([1234567890]*)} $S -> segid
          133  +      lappend lvl $segid
          134  +    }
          135  +    lappend ret $lvl
          136  +  }
          137  +  set ret
          138  +}
   125    139   
   126    140   proc fts5_rnddoc {n} {
   127    141     set map [list 0 a  1 b  2 c  3 d  4 e  5 f  6 g  7 h  8 i  9 j]
   128    142     set doc [list]
   129    143     for {set i 0} {$i < $n} {incr i} {
   130    144       lappend doc "x[string map $map [format %.3d [expr int(rand()*1000)]]]"
   131    145     }
   132    146     set doc
   133    147   }
   134    148   

Added ext/fts5/test/fts5corrupt.test.

            1  +# 2014 Dec 20
            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  +#
           13  +
           14  +source [file join [file dirname [info script]] fts5_common.tcl]
           15  +set testprefix fts5corrupt
           16  +
           17  +do_execsql_test 1.0 {
           18  +  CREATE VIRTUAL TABLE t1 USING fts5(x);
           19  +  INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
           20  +}
           21  +
           22  +do_test 1.1 {
           23  +  db transaction {
           24  +    for {set i 1} {$i < 200} {incr i} {
           25  +      set doc [list [string repeat x $i] [string repeat y $i]]
           26  +      execsql { INSERT INTO t1(rowid, x) VALUES($i, $doc) }
           27  +    }
           28  +  }
           29  +  fts5_level_segs t1
           30  +} {1}
           31  +db_save
           32  +
           33  +do_execsql_test 1.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
           34  +set segid [lindex [fts5_level_segids t1] 0]
           35  +
           36  +do_test 1.3 {
           37  +  execsql {
           38  +    DELETE FROM t1_data WHERE rowid = fts5_rowid('segment', 0, $segid, 0, 4);
           39  +  }
           40  +  catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
           41  +} {1 {SQL logic error or missing database}}
           42  +
           43  +do_test 1.4 {
           44  +  db_restore_and_reopen
           45  +  execsql {
           46  +    UPDATE t1_data set block = X'00000000' || substr(block, 5) WHERE
           47  +    rowid = fts5_rowid('segment', 0, $segid, 0, 4);
           48  +  }
           49  +  catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
           50  +} {1 {database disk image is malformed}}
           51  +
           52  +db_restore_and_reopen
           53  +#db eval {SELECT rowid, fts5_decode(rowid, block) aS r FROM t1_data} {puts $r}
           54  +
           55  +
           56  +
           57  +#--------------------------------------------------------------------
           58  +do_execsql_test 2.0 {
           59  +  CREATE VIRTUAL TABLE t2 USING fts5(x);
           60  +  INSERT INTO t2(t2, rank) VALUES('pgsz', 32);
           61  +}
           62  +do_test 2.1 {
           63  +  db transaction {
           64  +    for {set i 0} {$i < 20} {incr i} {
           65  +      execsql { INSERT INTO t2 VALUES('xxxxxxxxxx') }
           66  +    }
           67  +    for {set i 0} {$i < 20} {incr i} {
           68  +      execsql { INSERT INTO t2 VALUES('xxxxxxxxxzzzz') }
           69  +    }
           70  +  }
           71  +} {}
           72  +db eval {SELECT rowid, fts5_decode(rowid, block) aS r FROM t2_data} {puts $r}
           73  +
           74  +finish_test
           75  +