SQLite

Check-in [cc1d4296d7]
Login

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

Overview
Comment:When CLI -utf8 active, do no translation to MBCS for output to Windows console.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | cli-utf8
Files: files | file ages | folders
SHA3-256: cc1d4296d71ee6e2ba133edc581385e201c89657911c2ed39108843c776633a4
User & Date: larrybr 2023-04-17 21:18:53.516
Context
2023-04-17
22:28
Minor coding style changes. (Leaf check-in: 481aa3ccf6 user: mistachkin tags: cli-utf8)
21:18
When CLI -utf8 active, do no translation to MBCS for output to Windows console. (check-in: cc1d4296d7 user: larrybr tags: cli-utf8)
18:32
Fix some problems with using fts5 options 'secure-delete' and detail=none together. (check-in: 4d3f27ba90 user: dan tags: trunk)
2023-04-15
17:47
Add the 'secure-delete' option to the fts5 extension. For configuring fts5 to delete old entries directly from the full-text index instead of using delete keys. (check-in: 394980e4fe user: dan tags: trunk)
16:12
For CLI -utf8, set output codepage too. Adjust PP vars so that the code can be entirely omitted for targets pretending to be "WIN32" but not quite doing so. (check-in: 543594a727 user: larrybr tags: cli-utf8)
Changes
Unified Diff Ignore Whitespace Patch
Changes to ext/fts5/fts5Int.h.
195
196
197
198
199
200
201

202
203
204
205
206
207
208
209

210
211
212
213
214
215
216
217
218
219


220

221
222
223
224
225
226
227
  char *zContentExprlist;
  Fts5Tokenizer *pTok;
  fts5_tokenizer *pTokApi;
  int bLock;                      /* True when table is preparing statement */
  int ePattern;                   /* FTS_PATTERN_XXX constant */

  /* Values loaded from the %_config table */

  int iCookie;                    /* Incremented when %_config is modified */
  int pgsz;                       /* Approximate page size used in %_data */
  int nAutomerge;                 /* 'automerge' setting */
  int nCrisisMerge;               /* Maximum allowed segments per level */
  int nUsermerge;                 /* 'usermerge' setting */
  int nHashSize;                  /* Bytes of memory for in-memory hash */
  char *zRank;                    /* Name of rank function */
  char *zRankArgs;                /* Arguments to rank function */


  /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */
  char **pzErrmsg;

#ifdef SQLITE_DEBUG
  int bPrefixIndex;               /* True to use prefix-indexes */
#endif
};

/* Current expected value of %_config table 'version' field */


#define FTS5_CURRENT_VERSION  4


#define FTS5_CONTENT_NORMAL   0
#define FTS5_CONTENT_NONE     1
#define FTS5_CONTENT_EXTERNAL 2

#define FTS5_DETAIL_FULL      0
#define FTS5_DETAIL_NONE      1







>








>









|
>
>
|
>







195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
  char *zContentExprlist;
  Fts5Tokenizer *pTok;
  fts5_tokenizer *pTokApi;
  int bLock;                      /* True when table is preparing statement */
  int ePattern;                   /* FTS_PATTERN_XXX constant */

  /* Values loaded from the %_config table */
  int iVersion;                   /* fts5 file format 'version' */
  int iCookie;                    /* Incremented when %_config is modified */
  int pgsz;                       /* Approximate page size used in %_data */
  int nAutomerge;                 /* 'automerge' setting */
  int nCrisisMerge;               /* Maximum allowed segments per level */
  int nUsermerge;                 /* 'usermerge' setting */
  int nHashSize;                  /* Bytes of memory for in-memory hash */
  char *zRank;                    /* Name of rank function */
  char *zRankArgs;                /* Arguments to rank function */
  int bSecureDelete;              /* 'secure-delete' */

  /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */
  char **pzErrmsg;

#ifdef SQLITE_DEBUG
  int bPrefixIndex;               /* True to use prefix-indexes */
#endif
};

/* Current expected value of %_config table 'version' field. And
** the expected version if the 'secure-delete' option has ever been
** set on the table.  */
#define FTS5_CURRENT_VERSION               4
#define FTS5_CURRENT_VERSION_SECUREDELETE  5

#define FTS5_CONTENT_NORMAL   0
#define FTS5_CONTENT_NONE     1
#define FTS5_CONTENT_EXTERNAL 2

#define FTS5_DETAIL_FULL      0
#define FTS5_DETAIL_NONE      1
379
380
381
382
383
384
385

386
387
388
389
390
391
392
#define FTS5INDEX_QUERY_SCAN       0x0008   /* Scan query (fts5vocab) */

/* The following are used internally by the fts5_index.c module. They are
** defined here only to make it easier to avoid clashes with the flags
** above. */
#define FTS5INDEX_QUERY_SKIPEMPTY  0x0010
#define FTS5INDEX_QUERY_NOOUTPUT   0x0020


/*
** Create/destroy an Fts5Index object.
*/
int sqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, char**);
int sqlite3Fts5IndexClose(Fts5Index *p);








>







384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
#define FTS5INDEX_QUERY_SCAN       0x0008   /* Scan query (fts5vocab) */

/* The following are used internally by the fts5_index.c module. They are
** defined here only to make it easier to avoid clashes with the flags
** above. */
#define FTS5INDEX_QUERY_SKIPEMPTY  0x0010
#define FTS5INDEX_QUERY_NOOUTPUT   0x0020
#define FTS5INDEX_QUERY_SKIPHASH   0x0040

/*
** Create/destroy an Fts5Index object.
*/
int sqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, char**);
int sqlite3Fts5IndexClose(Fts5Index *p);

Changes to ext/fts5/fts5_config.c.
899
900
901
902
903
904
905












906
907
908
909
910
911
912
      sqlite3_free(pConfig->zRankArgs);
      pConfig->zRank = zRank;
      pConfig->zRankArgs = zRankArgs;
    }else if( rc==SQLITE_ERROR ){
      rc = SQLITE_OK;
      *pbBadkey = 1;
    }












  }else{
    *pbBadkey = 1;
  }
  return rc;
}

/*







>
>
>
>
>
>
>
>
>
>
>
>







899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
      sqlite3_free(pConfig->zRankArgs);
      pConfig->zRank = zRank;
      pConfig->zRankArgs = zRankArgs;
    }else if( rc==SQLITE_ERROR ){
      rc = SQLITE_OK;
      *pbBadkey = 1;
    }
  }

  else if( 0==sqlite3_stricmp(zKey, "secure-delete") ){
    int bVal = -1;
    if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
      bVal = sqlite3_value_int(pVal);
    }
    if( bVal<0 ){
      *pbBadkey = 1;
    }else{
      pConfig->bSecureDelete = (bVal ? 1 : 0);
    }
  }else{
    *pbBadkey = 1;
  }
  return rc;
}

/*
943
944
945
946
947
948
949

950


951
952
953
954
955
956
957
958


959
960
961
962
963
964
965
        int bDummy = 0;
        sqlite3Fts5ConfigSetValue(pConfig, zK, pVal, &bDummy);
      }
    }
    rc = sqlite3_finalize(p);
  }
  

  if( rc==SQLITE_OK && iVersion!=FTS5_CURRENT_VERSION ){


    rc = SQLITE_ERROR;
    if( pConfig->pzErrmsg ){
      assert( 0==*pConfig->pzErrmsg );
      *pConfig->pzErrmsg = sqlite3_mprintf(
          "invalid fts5 file format (found %d, expected %d) - run 'rebuild'",
          iVersion, FTS5_CURRENT_VERSION
      );
    }


  }

  if( rc==SQLITE_OK ){
    pConfig->iCookie = iCookie;
  }
  return rc;
}







>
|
>
>



|
|
|


>
>







955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
        int bDummy = 0;
        sqlite3Fts5ConfigSetValue(pConfig, zK, pVal, &bDummy);
      }
    }
    rc = sqlite3_finalize(p);
  }
  
  if( rc==SQLITE_OK 
   && iVersion!=FTS5_CURRENT_VERSION
   && iVersion!=FTS5_CURRENT_VERSION_SECUREDELETE
  ){
    rc = SQLITE_ERROR;
    if( pConfig->pzErrmsg ){
      assert( 0==*pConfig->pzErrmsg );
      *pConfig->pzErrmsg = sqlite3_mprintf("invalid fts5 file format "
          "(found %d, expected %d or %d) - run 'rebuild'",
          iVersion, FTS5_CURRENT_VERSION, FTS5_CURRENT_VERSION_SECUREDELETE
      );
    }
  }else{
    pConfig->iVersion = iVersion;
  }

  if( rc==SQLITE_OK ){
    pConfig->iCookie = iCookie;
  }
  return rc;
}
Changes to ext/fts5/fts5_index.c.
298
299
300
301
302
303
304


305
306
307
308
309
310
311
  sqlite3_stmt *pWriter;          /* "INSERT ... %_data VALUES(?,?)" */
  sqlite3_stmt *pDeleter;         /* "DELETE FROM %_data ... id>=? AND id<=?" */
  sqlite3_stmt *pIdxWriter;       /* "INSERT ... %_idx VALUES(?,?,?,?)" */
  sqlite3_stmt *pIdxDeleter;      /* "DELETE FROM %_idx WHERE segid=?" */
  sqlite3_stmt *pIdxSelect;
  int nRead;                      /* Total number of blocks read */



  sqlite3_stmt *pDataVersion;
  i64 iStructVersion;             /* data_version when pStruct read */
  Fts5Structure *pStruct;         /* Current db structure (or NULL) */
};

struct Fts5DoclistIter {
  u8 *aEof;                       /* Pointer to 1 byte past end of doclist */







>
>







298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
  sqlite3_stmt *pWriter;          /* "INSERT ... %_data VALUES(?,?)" */
  sqlite3_stmt *pDeleter;         /* "DELETE FROM %_data ... id>=? AND id<=?" */
  sqlite3_stmt *pIdxWriter;       /* "INSERT ... %_idx VALUES(?,?,?,?)" */
  sqlite3_stmt *pIdxDeleter;      /* "DELETE FROM %_idx WHERE segid=?" */
  sqlite3_stmt *pIdxSelect;
  int nRead;                      /* Total number of blocks read */

  sqlite3_stmt *pDeleteFromIdx;

  sqlite3_stmt *pDataVersion;
  i64 iStructVersion;             /* data_version when pStruct read */
  Fts5Structure *pStruct;         /* Current db structure (or NULL) */
};

struct Fts5DoclistIter {
  u8 *aEof;                       /* Pointer to 1 byte past end of doclist */
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
**
** iLeafPgno:
**   Current leaf page number within segment.
**
** iLeafOffset:
**   Byte offset within the current leaf that is the first byte of the 
**   position list data (one byte passed the position-list size field).
**   rowid field of the current entry. Usually this is the size field of the
**   position list data. The exception is if the rowid for the current entry 
**   is the last thing on the leaf page.
**
** pLeaf:
**   Buffer containing current leaf page data. Set to NULL at EOF.
**
** iTermLeafPgno, iTermLeafOffset:
**   Leaf page number containing the last term read from the segment. And
**   the offset immediately following the term data.







<
<
<







392
393
394
395
396
397
398



399
400
401
402
403
404
405
**
** iLeafPgno:
**   Current leaf page number within segment.
**
** iLeafOffset:
**   Byte offset within the current leaf that is the first byte of the 
**   position list data (one byte passed the position-list size field).



**
** pLeaf:
**   Buffer containing current leaf page data. Set to NULL at EOF.
**
** iTermLeafPgno, iTermLeafOffset:
**   Leaf page number containing the last term read from the segment. And
**   the offset immediately following the term data.
1439
1440
1441
1442
1443
1444
1445
1446
1447

1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466

1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480

1481

1482
1483
1484
1485
1486
1487
1488
  int iOff = pLvl->iOff;

  assert( pLvl->bEof==0 );
  if( iOff<=pLvl->iFirstOff ){
    pLvl->bEof = 1;
  }else{
    u8 *a = pLvl->pData->p;
    i64 iVal;
    int iLimit;

    int ii;
    int nZero = 0;

    /* Currently iOff points to the first byte of a varint. This block 
    ** decrements iOff until it points to the first byte of the previous 
    ** varint. Taking care not to read any memory locations that occur
    ** before the buffer in memory.  */
    iLimit = (iOff>9 ? iOff-9 : 0);
    for(iOff--; iOff>iLimit; iOff--){
      if( (a[iOff-1] & 0x80)==0 ) break;
    }

    fts5GetVarint(&a[iOff], (u64*)&iVal);
    pLvl->iRowid -= iVal;
    pLvl->iLeafPgno--;

    /* Skip backwards past any 0x00 varints. */
    for(ii=iOff-1; ii>=pLvl->iFirstOff && a[ii]==0x00; ii--){
      nZero++;

    }
    if( ii>=pLvl->iFirstOff && (a[ii] & 0x80) ){
      /* The byte immediately before the last 0x00 byte has the 0x80 bit
      ** set. So the last 0x00 is only a varint 0 if there are 8 more 0x80
      ** bytes before a[ii]. */
      int bZero = 0;              /* True if last 0x00 counts */
      if( (ii-8)>=pLvl->iFirstOff ){
        int j;
        for(j=1; j<=8 && (a[ii-j] & 0x80); j++);
        bZero = (j>8);
      }
      if( bZero==0 ) nZero--;
    }
    pLvl->iLeafPgno -= nZero;

    pLvl->iOff = iOff - nZero;

  }

  return pLvl->bEof;
}

static int fts5DlidxIterPrevR(Fts5Index *p, Fts5DlidxIter *pIter, int iLvl){
  Fts5DlidxLvl *pLvl = &pIter->aLvl[iLvl];







|
|
>
|
|
|
<
<
<
<
<
<
<
<
|
<
<
<

<
|
|
>
|
<
<
<
|
<
<
<
<
<
|
|
<
|
>
|
>







1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450








1451



1452

1453
1454
1455
1456



1457





1458
1459

1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
  int iOff = pLvl->iOff;

  assert( pLvl->bEof==0 );
  if( iOff<=pLvl->iFirstOff ){
    pLvl->bEof = 1;
  }else{
    u8 *a = pLvl->pData->p;

    pLvl->iOff = 0;
    fts5DlidxLvlNext(pLvl);
    while( 1 ){
      int nZero = 0;
      int ii = pLvl->iOff;








      u64 delta = 0;





      while( a[ii]==0 ){
        nZero++;
        ii++;
      }



      ii += sqlite3Fts5GetVarint(&a[ii], &delta);






      if( ii>=iOff ) break;

      pLvl->iLeafPgno += nZero+1;
      pLvl->iRowid += delta;
      pLvl->iOff = ii;
    }
  }

  return pLvl->bEof;
}

static int fts5DlidxIterPrevR(Fts5Index *p, Fts5DlidxIter *pIter, int iLvl){
  Fts5DlidxLvl *pLvl = &pIter->aLvl[iLvl];
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
}

static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){
  u8 *a = pIter->pLeaf->p;        /* Buffer to read data from */
  i64 iOff = pIter->iLeafOffset;

  ASSERT_SZLEAF_OK(pIter->pLeaf);
  if( iOff>=pIter->pLeaf->szLeaf ){
    fts5SegIterNextPage(p, pIter);
    if( pIter->pLeaf==0 ){
      if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
      return;
    }
    iOff = 4;
    a = pIter->pLeaf->p;







|







1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
}

static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){
  u8 *a = pIter->pLeaf->p;        /* Buffer to read data from */
  i64 iOff = pIter->iLeafOffset;

  ASSERT_SZLEAF_OK(pIter->pLeaf);
  while( iOff>=pIter->pLeaf->szLeaf ){
    fts5SegIterNextPage(p, pIter);
    if( pIter->pLeaf==0 ){
      if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
      return;
    }
    iOff = 4;
    a = pIter->pLeaf->p;
1769
1770
1771
1772
1773
1774
1775

1776

1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
  }

  if( p->rc==SQLITE_OK ){
    memset(pIter, 0, sizeof(*pIter));
    fts5SegIterSetNext(p, pIter);
    pIter->pSeg = pSeg;
    pIter->iLeafPgno = pSeg->pgnoFirst-1;

    fts5SegIterNextPage(p, pIter);

  }

  if( p->rc==SQLITE_OK ){
    pIter->iLeafOffset = 4;
    assert( pIter->pLeaf!=0 );
    assert_nc( pIter->pLeaf->nn>4 );
    assert_nc( fts5LeafFirstTermOff(pIter->pLeaf)==4 );
    pIter->iPgidxOff = pIter->pLeaf->szLeaf+1;
    fts5SegIterLoadTerm(p, pIter, 0);
    fts5SegIterLoadNPos(p, pIter);







>
|
>


|







1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
  }

  if( p->rc==SQLITE_OK ){
    memset(pIter, 0, sizeof(*pIter));
    fts5SegIterSetNext(p, pIter);
    pIter->pSeg = pSeg;
    pIter->iLeafPgno = pSeg->pgnoFirst-1;
    do {
      fts5SegIterNextPage(p, pIter);
    }while( p->rc==SQLITE_OK && pIter->pLeaf && pIter->pLeaf->nn==4 );
  }

  if( p->rc==SQLITE_OK && pIter->pLeaf ){
    pIter->iLeafOffset = 4;
    assert( pIter->pLeaf!=0 );
    assert_nc( pIter->pLeaf->nn>4 );
    assert_nc( fts5LeafFirstTermOff(pIter->pLeaf)==4 );
    pIter->iPgidxOff = pIter->pLeaf->szLeaf+1;
    fts5SegIterLoadTerm(p, pIter, 0);
    fts5SegIterLoadNPos(p, pIter);
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
  assert( (pIter->flags & FTS5_SEGITER_REVERSE)==0 );
  assert( p->pConfig->eDetail==FTS5_DETAIL_NONE );

  ASSERT_SZLEAF_OK(pIter->pLeaf);
  iOff = pIter->iLeafOffset;

  /* Next entry is on the next page */
  if( pIter->pSeg && iOff>=pIter->pLeaf->szLeaf ){
    fts5SegIterNextPage(p, pIter);
    if( p->rc || pIter->pLeaf==0 ) return;
    pIter->iRowid = 0;
    iOff = 4;
  }

  if( iOff<pIter->iEndofDoclist ){







|







1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
  assert( (pIter->flags & FTS5_SEGITER_REVERSE)==0 );
  assert( p->pConfig->eDetail==FTS5_DETAIL_NONE );

  ASSERT_SZLEAF_OK(pIter->pLeaf);
  iOff = pIter->iLeafOffset;

  /* Next entry is on the next page */
  while( pIter->pSeg && iOff>=pIter->pLeaf->szLeaf ){
    fts5SegIterNextPage(p, pIter);
    if( p->rc || pIter->pLeaf==0 ) return;
    pIter->iRowid = 0;
    iOff = 4;
  }

  if( iOff<pIter->iEndofDoclist ){
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
** the doclist.
*/
static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){
  Fts5DlidxIter *pDlidx = pIter->pDlidx;
  Fts5Data *pLast = 0;
  int pgnoLast = 0;

  if( pDlidx ){
    int iSegid = pIter->pSeg->iSegid;
    pgnoLast = fts5DlidxIterPgno(pDlidx);
    pLast = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast));
  }else{
    Fts5Data *pLeaf = pIter->pLeaf;         /* Current leaf data */

    /* Currently, Fts5SegIter.iLeafOffset points to the first byte of







|







2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
** the doclist.
*/
static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){
  Fts5DlidxIter *pDlidx = pIter->pDlidx;
  Fts5Data *pLast = 0;
  int pgnoLast = 0;

  if( pDlidx && p->pConfig->iVersion==FTS5_CURRENT_VERSION ){
    int iSegid = pIter->pSeg->iSegid;
    pgnoLast = fts5DlidxIterPgno(pDlidx);
    pLast = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast));
  }else{
    Fts5Data *pLeaf = pIter->pLeaf;         /* Current leaf data */

    /* Currently, Fts5SegIter.iLeafOffset points to the first byte of
2720
2721
2722
2723
2724
2725
2726
2727

2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746




2747
2748
2749
2750
2751
2752
2753
2754
2755
2756


2757
2758
2759
2760
2761
2762
2763

  pRes->iFirst = (u16)iRes;
  return 0;
}

/*
** Move the seg-iter so that it points to the first rowid on page iLeafPgno.
** It is an error if leaf iLeafPgno does not exist or contains no rowids.

*/
static void fts5SegIterGotoPage(
  Fts5Index *p,                   /* FTS5 backend object */
  Fts5SegIter *pIter,             /* Iterator to advance */
  int iLeafPgno
){
  assert( iLeafPgno>pIter->iLeafPgno );

  if( iLeafPgno>pIter->pSeg->pgnoLast ){
    p->rc = FTS5_CORRUPT;
  }else{
    fts5DataRelease(pIter->pNextLeaf);
    pIter->pNextLeaf = 0;
    pIter->iLeafPgno = iLeafPgno-1;
    fts5SegIterNextPage(p, pIter);
    assert( p->rc!=SQLITE_OK || pIter->iLeafPgno==iLeafPgno );

    if( p->rc==SQLITE_OK && ALWAYS(pIter->pLeaf!=0) ){
      int iOff;




      u8 *a = pIter->pLeaf->p;
      int n = pIter->pLeaf->szLeaf;

      iOff = fts5LeafFirstRowidOff(pIter->pLeaf);
      if( iOff<4 || iOff>=n ){
        p->rc = FTS5_CORRUPT;
      }else{
        iOff += fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid);
        pIter->iLeafOffset = iOff;
        fts5SegIterLoadNPos(p, pIter);


      }
    }
  }
}

/*
** Advance the iterator passed as the second argument until it is at or 







|
>














<
<

|

>
>
>
>
|
|
<
<
|
|
|
|
|
|
>
>







2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726


2727
2728
2729
2730
2731
2732
2733
2734
2735


2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750

  pRes->iFirst = (u16)iRes;
  return 0;
}

/*
** Move the seg-iter so that it points to the first rowid on page iLeafPgno.
** It is an error if leaf iLeafPgno does not exist. Unless the db is
** a 'secure-delete' db, if it contains no rowids then this is also an error.
*/
static void fts5SegIterGotoPage(
  Fts5Index *p,                   /* FTS5 backend object */
  Fts5SegIter *pIter,             /* Iterator to advance */
  int iLeafPgno
){
  assert( iLeafPgno>pIter->iLeafPgno );

  if( iLeafPgno>pIter->pSeg->pgnoLast ){
    p->rc = FTS5_CORRUPT;
  }else{
    fts5DataRelease(pIter->pNextLeaf);
    pIter->pNextLeaf = 0;
    pIter->iLeafPgno = iLeafPgno-1;



    while( p->rc==SQLITE_OK ){
      int iOff;
      fts5SegIterNextPage(p, pIter);
      if( pIter->pLeaf==0 ) break;
      iOff = fts5LeafFirstRowidOff(pIter->pLeaf);
      if( iOff>0 ){
        u8 *a = pIter->pLeaf->p;
        int n = pIter->pLeaf->szLeaf;


        if( iOff<4 || iOff>=n ){
          p->rc = FTS5_CORRUPT;
        }else{
          iOff += fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid);
          pIter->iLeafOffset = iOff;
          fts5SegIterLoadNPos(p, pIter);
        }
        break;
      }
    }
  }
}

/*
** Advance the iterator passed as the second argument until it is at or 
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
  assert( (pTerm==0 && nTerm==0) || iLevel<0 );

  /* Allocate space for the new multi-seg-iterator. */
  if( p->rc==SQLITE_OK ){
    if( iLevel<0 ){
      assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) );
      nSeg = pStruct->nSegment;
      nSeg += (p->pHash ? 1 : 0);
    }else{
      nSeg = MIN(pStruct->aLevel[iLevel].nSeg, nSegment);
    }
  }
  *ppOut = pNew = fts5MultiIterAlloc(p, nSeg);
  if( pNew==0 ){
    assert( p->rc!=SQLITE_OK );
    goto fts5MultiIterNew_post_check;
  }
  pNew->bRev = (0!=(flags & FTS5INDEX_QUERY_DESC));
  pNew->bSkipEmpty = (0!=(flags & FTS5INDEX_QUERY_SKIPEMPTY));
  pNew->pColset = pColset;
  if( (flags & FTS5INDEX_QUERY_NOOUTPUT)==0 ){
    fts5IterSetOutputCb(&p->rc, pNew);
  }

  /* Initialize each of the component segment iterators. */
  if( p->rc==SQLITE_OK ){
    if( iLevel<0 ){
      Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel];
      if( p->pHash ){
        /* Add a segment iterator for the current contents of the hash table. */
        Fts5SegIter *pIter = &pNew->aSeg[iIter++];
        fts5SegIterHashInit(p, pTerm, nTerm, flags, pIter);
      }
      for(pLvl=&pStruct->aLevel[0]; pLvl<pEnd; pLvl++){
        for(iSeg=pLvl->nSeg-1; iSeg>=0; iSeg--){
          Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];







|




















|







3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
  assert( (pTerm==0 && nTerm==0) || iLevel<0 );

  /* Allocate space for the new multi-seg-iterator. */
  if( p->rc==SQLITE_OK ){
    if( iLevel<0 ){
      assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) );
      nSeg = pStruct->nSegment;
      nSeg += (p->pHash && 0==(flags & FTS5INDEX_QUERY_SKIPHASH));
    }else{
      nSeg = MIN(pStruct->aLevel[iLevel].nSeg, nSegment);
    }
  }
  *ppOut = pNew = fts5MultiIterAlloc(p, nSeg);
  if( pNew==0 ){
    assert( p->rc!=SQLITE_OK );
    goto fts5MultiIterNew_post_check;
  }
  pNew->bRev = (0!=(flags & FTS5INDEX_QUERY_DESC));
  pNew->bSkipEmpty = (0!=(flags & FTS5INDEX_QUERY_SKIPEMPTY));
  pNew->pColset = pColset;
  if( (flags & FTS5INDEX_QUERY_NOOUTPUT)==0 ){
    fts5IterSetOutputCb(&p->rc, pNew);
  }

  /* Initialize each of the component segment iterators. */
  if( p->rc==SQLITE_OK ){
    if( iLevel<0 ){
      Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel];
      if( p->pHash && 0==(flags & FTS5INDEX_QUERY_SKIPHASH) ){
        /* Add a segment iterator for the current contents of the hash table. */
        Fts5SegIter *pIter = &pNew->aSeg[iIter++];
        fts5SegIterHashInit(p, pTerm, nTerm, flags, pIter);
      }
      for(pLvl=&pStruct->aLevel[0]; pLvl<pEnd; pLvl++){
        for(iSeg=pLvl->nSeg-1; iSeg>=0; iSeg--){
          Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
          p->rc = FTS5_CORRUPT;
        }else{
          fts5BufferZero(&buf);
          fts5BufferGrow(&p->rc, &buf, pData->nn);
          fts5BufferAppendBlob(&p->rc, &buf, sizeof(aHdr), aHdr);
          fts5BufferAppendVarint(&p->rc, &buf, pSeg->term.n);
          fts5BufferAppendBlob(&p->rc, &buf, pSeg->term.n, pSeg->term.p);
          fts5BufferAppendBlob(&p->rc, &buf, pData->szLeaf-iOff,&pData->p[iOff]);
          if( p->rc==SQLITE_OK ){
            /* Set the szLeaf field */
            fts5PutU16(&buf.p[2], (u16)buf.n);
          }

          /* Set up the new page-index array */
          fts5BufferAppendVarint(&p->rc, &buf, 4);







|







4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
          p->rc = FTS5_CORRUPT;
        }else{
          fts5BufferZero(&buf);
          fts5BufferGrow(&p->rc, &buf, pData->nn);
          fts5BufferAppendBlob(&p->rc, &buf, sizeof(aHdr), aHdr);
          fts5BufferAppendVarint(&p->rc, &buf, pSeg->term.n);
          fts5BufferAppendBlob(&p->rc, &buf, pSeg->term.n, pSeg->term.p);
          fts5BufferAppendBlob(&p->rc, &buf,pData->szLeaf-iOff,&pData->p[iOff]);
          if( p->rc==SQLITE_OK ){
            /* Set the szLeaf field */
            fts5PutU16(&buf.p[2], (u16)buf.n);
          }

          /* Set up the new page-index array */
          fts5BufferAppendVarint(&p->rc, &buf, 4);
4518
4519
4520
4521
4522
4523
4524

4525
4526
4527
4528
4529
4530
4531
4532
4533
4534

4535
4536
4537
4538
4539
4540
4541

static void fts5IndexCrisismerge(
  Fts5Index *p,                   /* FTS5 backend object */
  Fts5Structure **ppStruct        /* IN/OUT: Current structure of index */
){
  const int nCrisis = p->pConfig->nCrisisMerge;
  Fts5Structure *pStruct = *ppStruct;

  int iLvl = 0;

  assert( p->rc!=SQLITE_OK || pStruct->nLevel>0 );
  while( p->rc==SQLITE_OK && pStruct->aLevel[iLvl].nSeg>=nCrisis ){
    fts5IndexMergeLevel(p, &pStruct, iLvl, 0);
    assert( p->rc!=SQLITE_OK || pStruct->nLevel>(iLvl+1) );
    fts5StructurePromote(p, iLvl+1, pStruct);
    iLvl++;
  }
  *ppStruct = pStruct;

}

static int fts5IndexReturn(Fts5Index *p){
  int rc = p->rc;
  p->rc = SQLITE_OK;
  return rc;
}







>
|
<
<
|
|
|
|
|
|
|
>







4505
4506
4507
4508
4509
4510
4511
4512
4513


4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528

static void fts5IndexCrisismerge(
  Fts5Index *p,                   /* FTS5 backend object */
  Fts5Structure **ppStruct        /* IN/OUT: Current structure of index */
){
  const int nCrisis = p->pConfig->nCrisisMerge;
  Fts5Structure *pStruct = *ppStruct;
  if( pStruct && pStruct->nLevel>0 ){
    int iLvl = 0;


    while( p->rc==SQLITE_OK && pStruct->aLevel[iLvl].nSeg>=nCrisis ){
      fts5IndexMergeLevel(p, &pStruct, iLvl, 0);
      assert( p->rc!=SQLITE_OK || pStruct->nLevel>(iLvl+1) );
      fts5StructurePromote(p, iLvl+1, pStruct);
      iLvl++;
    }
    *ppStruct = pStruct;
  }
}

static int fts5IndexReturn(Fts5Index *p){
  int rc = p->rc;
  p->rc = SQLITE_OK;
  return rc;
}
4560
4561
4562
4563
4564
4565
4566





















































































































































































































































































































































































































4567
4568
4569
4570
4571
4572
4573
      int i = fts5GetVarint32(&aBuf[ret], dummy);
      if( (ret + i) > nMax ) break;
      ret += i;
    }
  }
  return ret;
}






















































































































































































































































































































































































































/*
** Flush the contents of in-memory hash table iHash to a new level-0 
** segment on disk. Also update the corresponding structure record.
**
** If an error occurs, set the Fts5Index.rc error code. If an error has 
** already occurred, this function is a no-op.







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
4647
4648
4649
4650
4651
4652
4653
4654
4655
4656
4657
4658
4659
4660
4661
4662
4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
4732
4733
4734
4735
4736
4737
4738
4739
4740
4741
4742
4743
4744
4745
4746
4747
4748
4749
4750
4751
4752
4753
4754
4755
4756
4757
4758
4759
4760
4761
4762
4763
4764
4765
4766
4767
4768
4769
4770
4771
4772
4773
4774
4775
4776
4777
4778
4779
4780
4781
4782
4783
4784
4785
4786
4787
4788
4789
4790
4791
4792
4793
4794
4795
4796
4797
4798
4799
4800
4801
4802
4803
4804
4805
4806
4807
4808
4809
4810
4811
4812
4813
4814
4815
4816
4817
4818
4819
4820
4821
4822
4823
4824
4825
4826
4827
4828
4829
4830
4831
4832
4833
4834
4835
4836
4837
4838
4839
4840
4841
4842
4843
4844
4845
4846
4847
4848
4849
4850
4851
4852
4853
4854
4855
4856
4857
4858
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
4872
4873
4874
4875
4876
4877
4878
4879
4880
4881
4882
4883
4884
4885
4886
4887
4888
4889
4890
4891
4892
4893
4894
4895
4896
4897
4898
4899
4900
4901
4902
4903
4904
4905
4906
4907
4908
4909
4910
4911
4912
4913
4914
4915
4916
4917
4918
4919
4920
4921
4922
4923
4924
4925
4926
4927
4928
4929
4930
4931
4932
4933
4934
4935
4936
4937
4938
4939
4940
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
      int i = fts5GetVarint32(&aBuf[ret], dummy);
      if( (ret + i) > nMax ) break;
      ret += i;
    }
  }
  return ret;
}

/*
** Execute the SQL statement:
**
**    DELETE FROM %_idx WHERE (segid, (pgno/2)) = ($iSegid, $iPgno);
**
** This is used when a secure-delete operation removes the last term
** from a segment leaf page. In that case the %_idx entry is removed 
** too. This is done to ensure that if all instances of a token are
** removed from an fts5 database in secure-delete mode, no trace of
** the token itself remains in the database.
*/
static void fts5SecureDeleteIdxEntry(
  Fts5Index *p,                   /* FTS5 backend object */
  int iSegid,                     /* Id of segment to delete entry for */
  int iPgno                       /* Page number within segment */
){
  if( iPgno!=1 ){
    assert( p->pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE );
    if( p->pDeleteFromIdx==0 ){
      fts5IndexPrepareStmt(p, &p->pDeleteFromIdx, sqlite3_mprintf(
          "DELETE FROM '%q'.'%q_idx' WHERE (segid, (pgno/2)) = (?1, ?2)",
          p->pConfig->zDb, p->pConfig->zName
      ));
    }
    if( p->rc==SQLITE_OK ){
      sqlite3_bind_int(p->pDeleteFromIdx, 1, iSegid);
      sqlite3_bind_int(p->pDeleteFromIdx, 2, iPgno);
      sqlite3_step(p->pDeleteFromIdx);
      p->rc = sqlite3_reset(p->pDeleteFromIdx);
    }
  }
}

/*
** This is called when a secure-delete operation removes a position-list
** that overflows onto segment page iPgno of segment pSeg. This function
** rewrites node iPgno, and possibly one or more of its right-hand peers,
** to remove this portion of the position list.
**
** Output variable (*pbLastInDoclist) is set to true if the position-list
** removed is followed by a new term or the end-of-segment, or false if
** it is followed by another rowid/position list.
*/
static void fts5SecureDeleteOverflow(
  Fts5Index *p,
  Fts5StructureSegment *pSeg,
  int iPgno,
  int *pbLastInDoclist
){
  const int bDetailNone = (p->pConfig->eDetail==FTS5_DETAIL_NONE);
  int pgno;
  Fts5Data *pLeaf = 0;
  assert( iPgno!=1 );

  *pbLastInDoclist = 1;
  for(pgno=iPgno; p->rc==SQLITE_OK && pgno<=pSeg->pgnoLast; pgno++){
    i64 iRowid = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno);
    int iNext = 0;
    u8 *aPg = 0;

    pLeaf = fts5DataRead(p, iRowid);
    if( pLeaf==0 ) break;
    aPg = pLeaf->p;

    iNext = fts5GetU16(&aPg[0]);
    if( iNext!=0 ){
      *pbLastInDoclist = 0;
    }
    if( iNext==0 && pLeaf->szLeaf!=pLeaf->nn ){
      fts5GetVarint32(&aPg[pLeaf->szLeaf], iNext);
    }

    if( iNext==0 ){
      /* The page contains no terms or rowids. Replace it with an empty
      ** page and move on to the right-hand peer.  */
      const u8 aEmpty[] = {0x00, 0x00, 0x00, 0x04}; 
      assert_nc( bDetailNone==0 || pLeaf->nn==4 );
      if( bDetailNone==0 ) fts5DataWrite(p, iRowid, aEmpty, sizeof(aEmpty));
      fts5DataRelease(pLeaf);
      pLeaf = 0;
    }else if( bDetailNone ){
      break;
    }else{
      int nShift = iNext - 4;
      int nPg;

      int nIdx = 0;
      u8 *aIdx = 0;

      /* Unless the current page footer is 0 bytes in size (in which case
      ** the new page footer will be as well), allocate and populate a 
      ** buffer containing the new page footer. Set stack variables aIdx 
      ** and nIdx accordingly.  */
      if( pLeaf->nn>pLeaf->szLeaf ){
        int iFirst = 0;
        int i1 = pLeaf->szLeaf;
        int i2 = 0;

        aIdx = sqlite3Fts5MallocZero(&p->rc, (pLeaf->nn-pLeaf->szLeaf)+2);
        if( aIdx==0 ) break;
        i1 += fts5GetVarint32(&aPg[i1], iFirst);
        i2 = sqlite3Fts5PutVarint(aIdx, iFirst-nShift);
        if( i1<pLeaf->nn ){
          memcpy(&aIdx[i2], &aPg[i1], pLeaf->nn-i1);
          i2 += (pLeaf->nn-i1);
        }
        nIdx = i2;
      }

      /* Modify the contents of buffer aPg[]. Set nPg to the new size 
      ** in bytes. The new page is always smaller than the old.  */
      nPg = pLeaf->szLeaf - nShift;
      memmove(&aPg[4], &aPg[4+nShift], nPg-4);
      fts5PutU16(&aPg[2], nPg);
      if( fts5GetU16(&aPg[0]) ) fts5PutU16(&aPg[0], 4);
      if( nIdx>0 ){
        memcpy(&aPg[nPg], aIdx, nIdx);
        nPg += nIdx;
      }
      sqlite3_free(aIdx);

      /* Write the new page to disk and exit the loop */
      assert( nPg>4 || fts5GetU16(aPg)==0 );
      fts5DataWrite(p, iRowid, aPg, nPg);
      break;
    }
  }
  fts5DataRelease(pLeaf);
}

/*
** Completely remove the entry that pSeg currently points to from 
** the database.
*/
static void fts5DoSecureDelete(
  Fts5Index *p,
  Fts5Structure *pStruct,
  Fts5SegIter *pSeg
){
  const int bDetailNone = (p->pConfig->eDetail==FTS5_DETAIL_NONE);
  int iSegid = pSeg->pSeg->iSegid;
  u8 *aPg = pSeg->pLeaf->p;
  int nPg = pSeg->pLeaf->nn;
  int iPgIdx = pSeg->pLeaf->szLeaf;

  u64 iDelta = 0;
  u64 iNextDelta = 0;
  int iNextOff = 0;
  int iOff = 0;
  int nIdx = 0;
  u8 *aIdx = 0;
  int bLastInDoclist = 0;
  int iIdx = 0;
  int iStart = 0;
  int iKeyOff = 0;
  int iPrevKeyOff = 0;
  int iDelKeyOff = 0;       /* Offset of deleted key, if any */

  nIdx = nPg-iPgIdx;
  aIdx = sqlite3Fts5MallocZero(&p->rc, nIdx+16);
  if( p->rc ) return;
  memcpy(aIdx, &aPg[iPgIdx], nIdx);

  /* At this point segment iterator pSeg points to the entry
  ** this function should remove from the b-tree segment. 
  **
  ** In detail=full or detail=column mode, pSeg->iLeafOffset is the 
  ** offset of the first byte in the position-list for the entry to 
  ** remove. Immediately before this comes two varints that will also
  ** need to be removed:
  **
  **     + the rowid or delta rowid value for the entry, and
  **     + the size of the position list in bytes.
  **
  ** Or, in detail=none mode, there is a single varint prior to 
  ** pSeg->iLeafOffset - the rowid or delta rowid value.
  **
  ** This block sets the following variables:
  **
  **   iStart:
  **   iDelta:
  */
  {
    int iSOP;
    if( pSeg->iLeafPgno==pSeg->iTermLeafPgno ){
      iStart = pSeg->iTermLeafOffset;
    }else{
      iStart = fts5GetU16(&aPg[0]);
    }

    iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta);
    assert_nc( iSOP<=pSeg->iLeafOffset );

    if( bDetailNone ){
      while( iSOP<pSeg->iLeafOffset ){
        if( aPg[iSOP]==0x00 ) iSOP++;
        if( aPg[iSOP]==0x00 ) iSOP++;
        iStart = iSOP;
        iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta);
      }

      iNextOff = iSOP;
      if( iNextOff<pSeg->iEndofDoclist && aPg[iNextOff]==0x00 ) iNextOff++;
      if( iNextOff<pSeg->iEndofDoclist && aPg[iNextOff]==0x00 ) iNextOff++;

    }else{
      int nPos = 0;
      iSOP += fts5GetVarint32(&aPg[iSOP], nPos);
      while( iSOP<pSeg->iLeafOffset ){
        iStart = iSOP + (nPos/2);
        iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta);
        iSOP += fts5GetVarint32(&aPg[iSOP], nPos);
      }
      assert_nc( iSOP==pSeg->iLeafOffset );
      iNextOff = pSeg->iLeafOffset + pSeg->nPos;
    }
  }

  iOff = iStart;
  if( iNextOff>=iPgIdx ){
    int pgno = pSeg->iLeafPgno+1;
    fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist);
    iNextOff = iPgIdx;
  }else{
    /* Set bLastInDoclist to true if the entry being removed is the last
    ** in its doclist.  */
    for(iIdx=0, iKeyOff=0; iIdx<nIdx; /* no-op */){
      u32 iVal = 0;
      iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
      iKeyOff += iVal;
      if( iKeyOff==iNextOff ){
        bLastInDoclist = 1;
      }
    }
  }

  if( fts5GetU16(&aPg[0])==iStart && (bLastInDoclist||iNextOff==iPgIdx) ){
    fts5PutU16(&aPg[0], 0);
  }

  if( bLastInDoclist==0 ){
    if( iNextOff!=iPgIdx ){
      iNextOff += fts5GetVarint(&aPg[iNextOff], &iNextDelta);
      iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta + iNextDelta);
    }
  }else if( 
      iStart==pSeg->iTermLeafOffset && pSeg->iLeafPgno==pSeg->iTermLeafPgno 
  ){
    /* The entry being removed was the only position list in its
    ** doclist. Therefore the term needs to be removed as well. */
    int iKey = 0;
    for(iIdx=0, iKeyOff=0; iIdx<nIdx; iKey++){
      u32 iVal = 0;
      iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
      if( (iKeyOff+iVal)>iStart ) break;
      iKeyOff += iVal;
    }

    iDelKeyOff = iOff = iKeyOff;
    if( iNextOff!=iPgIdx ){
      int nPrefix = 0;
      int nSuffix = 0;
      int nPrefix2 = 0;
      int nSuffix2 = 0;

      iDelKeyOff = iNextOff;
      iNextOff += fts5GetVarint32(&aPg[iNextOff], nPrefix2);
      iNextOff += fts5GetVarint32(&aPg[iNextOff], nSuffix2);

      if( iKey!=1 ){
        iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nPrefix);
      }
      iKeyOff += fts5GetVarint32(&aPg[iKeyOff], nSuffix);

      nPrefix = MIN(nPrefix, nPrefix2);
      nSuffix = (nPrefix2 + nSuffix2) - nPrefix;

      if( (iKeyOff+nSuffix)>iPgIdx || (iNextOff+nSuffix2)>iPgIdx ){
        p->rc = FTS5_CORRUPT;
      }else{
        if( iKey!=1 ){
          iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix);
        }
        iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix);
        if( nPrefix2>nPrefix ){
          memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix);
          iOff += (nPrefix2-nPrefix);
        }
        memmove(&aPg[iOff], &aPg[iNextOff], nSuffix2);
        iOff += nSuffix2;
        iNextOff += nSuffix2;
      }
    }
  }else if( iStart==4 ){
      assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno );
      /* The entry being removed may be the only position list in
      ** its doclist. */
      int iPgno = pSeg->iLeafPgno-1;

      for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){
        Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno));
        int bEmpty = (pPg && pPg->nn==4);
        fts5DataRelease(pPg);
        if( bEmpty==0 ) break;
      }

      if( iPgno==pSeg->iTermLeafPgno ){
        i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno);
        Fts5Data *pTerm = fts5DataRead(p, iId);
        if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){
          u8 *aTermIdx = &pTerm->p[pTerm->szLeaf];
          int nTermIdx = pTerm->nn - pTerm->szLeaf;
          int iTermIdx = 0;
          int iTermOff = 0;

          while( 1 ){
            u32 iVal = 0;
            int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal);
            iTermOff += iVal;
            if( (iTermIdx+nByte)>=nTermIdx ) break;
            iTermIdx += nByte;
          }
          nTermIdx = iTermIdx;

          memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx);
          fts5PutU16(&pTerm->p[2], iTermOff);

          fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx);
          if( nTermIdx==0 ){
            fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno);
          }
        }
        fts5DataRelease(pTerm);
      }
    }

    if( p->rc==SQLITE_OK ){
      const int nMove = nPg - iNextOff;
      int nShift = 0;

      memmove(&aPg[iOff], &aPg[iNextOff], nMove);
      iPgIdx -= (iNextOff - iOff);
      nPg = iPgIdx;
      fts5PutU16(&aPg[2], iPgIdx);

      nShift = iNextOff - iOff;
      for(iIdx=0, iKeyOff=0, iPrevKeyOff=0; iIdx<nIdx; /* no-op */){
        u32 iVal = 0;
        iIdx += fts5GetVarint32(&aIdx[iIdx], iVal);
        iKeyOff += iVal;
        if( iKeyOff!=iDelKeyOff ){
          if( iKeyOff>iOff ){
            iKeyOff -= nShift;
            nShift = 0;
          }
          nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOff - iPrevKeyOff);
          iPrevKeyOff = iKeyOff;
        }
      }

      if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){
        fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno);
      }

      assert_nc( nPg>4 || fts5GetU16(aPg)==0 );
      fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg,nPg);
    }
    sqlite3_free(aIdx);
}

/*
** This is called as part of flushing a delete to disk in 'secure-delete'
** mode. It edits the segments within the database described by argument
** pStruct to remove the entries for term zTerm, rowid iRowid.
*/
static void fts5FlushSecureDelete(
  Fts5Index *p,
  Fts5Structure *pStruct,
  const char *zTerm,
  i64 iRowid
){
  const int f = FTS5INDEX_QUERY_SKIPHASH;
  int nTerm = strlen(zTerm);
  Fts5Iter *pIter = 0;            /* Used to find term instance */

  fts5MultiIterNew(p, pStruct, f, 0, (const u8*)zTerm, nTerm, -1, 0, &pIter);
  if( fts5MultiIterEof(p, pIter)==0 ){
    i64 iThis = fts5MultiIterRowid(pIter);
    if( iThis<iRowid ){
      fts5MultiIterNextFrom(p, pIter, iRowid);
    }

    if( p->rc==SQLITE_OK 
     && fts5MultiIterEof(p, pIter)==0 
     && iRowid==fts5MultiIterRowid(pIter)
    ){
      Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst];
      fts5DoSecureDelete(p, pStruct, pSeg);
    }
  }

  fts5MultiIterFree(pIter);
}


/*
** Flush the contents of in-memory hash table iHash to a new level-0 
** segment on disk. Also update the corresponding structure record.
**
** If an error occurs, set the Fts5Index.rc error code. If an error has 
** already occurred, this function is a no-op.
4583
4584
4585
4586
4587
4588
4589

4590
4591
4592
4593
4594
4595
4596
  pStruct = fts5StructureRead(p);
  iSegid = fts5AllocateSegid(p, pStruct);
  fts5StructureInvalidate(p);

  if( iSegid ){
    const int pgsz = p->pConfig->pgsz;
    int eDetail = p->pConfig->eDetail;

    Fts5StructureSegment *pSeg;   /* New segment within pStruct */
    Fts5Buffer *pBuf;             /* Buffer in which to assemble leaf page */
    Fts5Buffer *pPgidx;           /* Buffer in which to assemble pgidx */

    Fts5SegWriter writer;
    fts5WriteInit(p, &writer, iSegid);








>







4975
4976
4977
4978
4979
4980
4981
4982
4983
4984
4985
4986
4987
4988
4989
  pStruct = fts5StructureRead(p);
  iSegid = fts5AllocateSegid(p, pStruct);
  fts5StructureInvalidate(p);

  if( iSegid ){
    const int pgsz = p->pConfig->pgsz;
    int eDetail = p->pConfig->eDetail;
    int bSecureDelete = p->pConfig->bSecureDelete;
    Fts5StructureSegment *pSeg;   /* New segment within pStruct */
    Fts5Buffer *pBuf;             /* Buffer in which to assemble leaf page */
    Fts5Buffer *pPgidx;           /* Buffer in which to assemble pgidx */

    Fts5SegWriter writer;
    fts5WriteInit(p, &writer, iSegid);

4605
4606
4607
4608
4609
4610
4611

4612
4613
4614
4615
4616


4617
4618

4619
4620
4621
4622
4623
4624

4625
4626
4627
4628
4629
4630
4631
4632

4633
4634






























4635
4636
4637
4638
4639
4640
4641
4642
4643
4644

4645

4646
4647
4648
4649
4650
4651
4652
    /* Begin scanning through hash table entries. This loop runs once for each
    ** term/doclist currently stored within the hash table. */
    if( p->rc==SQLITE_OK ){
      p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0);
    }
    while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){
      const char *zTerm;          /* Buffer containing term */

      const u8 *pDoclist;         /* Pointer to doclist for this term */
      int nDoclist;               /* Size of doclist in bytes */

      /* Write the term for this entry to disk. */
      sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist);


      fts5WriteAppendTerm(p, &writer, (int)strlen(zTerm), (const u8*)zTerm);
      if( p->rc!=SQLITE_OK ) break;


      assert( writer.bFirstRowidInPage==0 );
      if( pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){
        /* The entire doclist will fit on the current leaf. */
        fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist);
      }else{

        i64 iRowid = 0;
        u64 iDelta = 0;
        int iOff = 0;

        /* The entire doclist will not fit on this leaf. The following 
        ** loop iterates through the poslists that make up the current 
        ** doclist.  */
        while( p->rc==SQLITE_OK && iOff<nDoclist ){

          iOff += fts5GetVarint(&pDoclist[iOff], &iDelta);
          iRowid += iDelta;






























          
          if( writer.bFirstRowidInPage ){
            fts5PutU16(&pBuf->p[0], (u16)pBuf->n);   /* first rowid on page */
            pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid);
            writer.bFirstRowidInPage = 0;
            fts5WriteDlidxAppend(p, &writer, iRowid);
            if( p->rc!=SQLITE_OK ) break;
          }else{
            pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iDelta);
          }

          assert( pBuf->n<=pBuf->nSpace );


          if( eDetail==FTS5_DETAIL_NONE ){
            if( iOff<nDoclist && pDoclist[iOff]==0 ){
              pBuf->p[pBuf->n++] = 0;
              iOff++;
              if( iOff<nDoclist && pDoclist[iOff]==0 ){
                pBuf->p[pBuf->n++] = 0;







>



|

>
>
|
|
>
|
|
|



>

|






>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>






<

|

>

>







4998
4999
5000
5001
5002
5003
5004
5005
5006
5007
5008
5009
5010
5011
5012
5013
5014
5015
5016
5017
5018
5019
5020
5021
5022
5023
5024
5025
5026
5027
5028
5029
5030
5031
5032
5033
5034
5035
5036
5037
5038
5039
5040
5041
5042
5043
5044
5045
5046
5047
5048
5049
5050
5051
5052
5053
5054
5055
5056
5057
5058
5059
5060
5061
5062
5063
5064
5065
5066
5067
5068
5069

5070
5071
5072
5073
5074
5075
5076
5077
5078
5079
5080
5081
5082
    /* Begin scanning through hash table entries. This loop runs once for each
    ** term/doclist currently stored within the hash table. */
    if( p->rc==SQLITE_OK ){
      p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0);
    }
    while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){
      const char *zTerm;          /* Buffer containing term */
      int nTerm;                  /* Size of zTerm in bytes */
      const u8 *pDoclist;         /* Pointer to doclist for this term */
      int nDoclist;               /* Size of doclist in bytes */

      /* Get the term and doclist for this entry. */
      sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist);
      nTerm = (int)strlen(zTerm);
      if( bSecureDelete==0 ){
        fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm);
        if( p->rc!=SQLITE_OK ) break;
        assert( writer.bFirstRowidInPage==0 );
      }

      if( !bSecureDelete && pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){
        /* The entire doclist will fit on the current leaf. */
        fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist);
      }else{
        int bTermWritten = !bSecureDelete;
        i64 iRowid = 0;
        i64 iPrev = 0;
        int iOff = 0;

        /* The entire doclist will not fit on this leaf. The following 
        ** loop iterates through the poslists that make up the current 
        ** doclist.  */
        while( p->rc==SQLITE_OK && iOff<nDoclist ){
          u64 iDelta = 0;
          iOff += fts5GetVarint(&pDoclist[iOff], &iDelta);
          iRowid += iDelta;

          /* If in secure delete mode, and if this entry in the poslist is
          ** in fact a delete, then edit the existing segments directly
          ** using fts5FlushSecureDelete().  */
          if( bSecureDelete ){
            if( eDetail==FTS5_DETAIL_NONE ){
              if( iOff<nDoclist && pDoclist[iOff]==0x00 ){
                fts5FlushSecureDelete(p, pStruct, zTerm, iRowid);
                iOff++;
                if( iOff<nDoclist && pDoclist[iOff]==0x00 ){
                  iOff++;
                  nDoclist = 0;
                }else{
                  continue;
                }
              }
            }else if( (pDoclist[iOff] & 0x01) ){
              fts5FlushSecureDelete(p, pStruct, zTerm, iRowid);
              if( p->rc!=SQLITE_OK || pDoclist[iOff]==0x01 ){
                iOff++;
                continue;
              }
            }
          }

          if( p->rc==SQLITE_OK && bTermWritten==0 ){
            fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm);
            bTermWritten = 1;
            assert( writer.bFirstRowidInPage==0 );
          }
          
          if( writer.bFirstRowidInPage ){
            fts5PutU16(&pBuf->p[0], (u16)pBuf->n);   /* first rowid on page */
            pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid);
            writer.bFirstRowidInPage = 0;
            fts5WriteDlidxAppend(p, &writer, iRowid);

          }else{
            pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid-iPrev);
          }
          if( p->rc!=SQLITE_OK ) break;
          assert( pBuf->n<=pBuf->nSpace );
          iPrev = iRowid;

          if( eDetail==FTS5_DETAIL_NONE ){
            if( iOff<nDoclist && pDoclist[iOff]==0 ){
              pBuf->p[pBuf->n++] = 0;
              iOff++;
              if( iOff<nDoclist && pDoclist[iOff]==0 ){
                pBuf->p[pBuf->n++] = 0;
4697
4698
4699
4700
4701
4702
4703


4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717

4718
4719
4720
4721
4722
4723
4724
      /* pBuf->p[pBuf->n++] = '\0'; */
      assert( pBuf->n<=pBuf->nSpace );
      if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash);
    }
    sqlite3Fts5HashClear(pHash);
    fts5WriteFinish(p, &writer, &pgnoLast);



    /* Update the Fts5Structure. It is written back to the database by the
    ** fts5StructureRelease() call below.  */
    if( pStruct->nLevel==0 ){
      fts5StructureAddLevel(&p->rc, &pStruct);
    }
    fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0);
    if( p->rc==SQLITE_OK ){
      pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ];
      pSeg->iSegid = iSegid;
      pSeg->pgnoFirst = 1;
      pSeg->pgnoLast = pgnoLast;
      pStruct->nSegment++;
    }
    fts5StructurePromote(p, 0, pStruct);

  }

  fts5IndexAutomerge(p, &pStruct, pgnoLast);
  fts5IndexCrisismerge(p, &pStruct);
  fts5StructureWrite(p, pStruct);
  fts5StructureRelease(pStruct);
}







>
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>







5127
5128
5129
5130
5131
5132
5133
5134
5135
5136
5137
5138
5139
5140
5141
5142
5143
5144
5145
5146
5147
5148
5149
5150
5151
5152
5153
5154
5155
5156
5157
      /* pBuf->p[pBuf->n++] = '\0'; */
      assert( pBuf->n<=pBuf->nSpace );
      if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash);
    }
    sqlite3Fts5HashClear(pHash);
    fts5WriteFinish(p, &writer, &pgnoLast);

    assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 );
    if( pgnoLast>0 ){
      /* Update the Fts5Structure. It is written back to the database by the
      ** fts5StructureRelease() call below.  */
      if( pStruct->nLevel==0 ){
        fts5StructureAddLevel(&p->rc, &pStruct);
      }
      fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0);
      if( p->rc==SQLITE_OK ){
        pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ];
        pSeg->iSegid = iSegid;
        pSeg->pgnoFirst = 1;
        pSeg->pgnoLast = pgnoLast;
        pStruct->nSegment++;
      }
      fts5StructurePromote(p, 0, pStruct);
    }
  }

  fts5IndexAutomerge(p, &pStruct, pgnoLast);
  fts5IndexCrisismerge(p, &pStruct);
  fts5StructureWrite(p, pStruct);
  fts5StructureRelease(pStruct);
}
5451
5452
5453
5454
5455
5456
5457

5458
5459
5460
5461
5462
5463
5464
    fts5StructureInvalidate(p);
    sqlite3_finalize(p->pWriter);
    sqlite3_finalize(p->pDeleter);
    sqlite3_finalize(p->pIdxWriter);
    sqlite3_finalize(p->pIdxDeleter);
    sqlite3_finalize(p->pIdxSelect);
    sqlite3_finalize(p->pDataVersion);

    sqlite3Fts5HashFree(p->pHash);
    sqlite3_free(p->zDataTbl);
    sqlite3_free(p);
  }
  return rc;
}








>







5884
5885
5886
5887
5888
5889
5890
5891
5892
5893
5894
5895
5896
5897
5898
    fts5StructureInvalidate(p);
    sqlite3_finalize(p->pWriter);
    sqlite3_finalize(p->pDeleter);
    sqlite3_finalize(p->pIdxWriter);
    sqlite3_finalize(p->pIdxDeleter);
    sqlite3_finalize(p->pIdxSelect);
    sqlite3_finalize(p->pDataVersion);
    sqlite3_finalize(p->pDeleteFromIdx);
    sqlite3Fts5HashFree(p->pHash);
    sqlite3_free(p->zDataTbl);
    sqlite3_free(p);
  }
  return rc;
}

6081
6082
6083
6084
6085
6086
6087

6088
6089
6090
6091
6092
6093
6094
}

static void fts5IndexIntegrityCheckSegment(
  Fts5Index *p,                   /* FTS5 backend object */
  Fts5StructureSegment *pSeg      /* Segment to check internal consistency */
){
  Fts5Config *pConfig = p->pConfig;

  sqlite3_stmt *pStmt = 0;
  int rc2;
  int iIdxPrevLeaf = pSeg->pgnoFirst-1;
  int iDlidxPrevLeaf = pSeg->pgnoLast;

  if( pSeg->pgnoFirst==0 ) return;








>







6515
6516
6517
6518
6519
6520
6521
6522
6523
6524
6525
6526
6527
6528
6529
}

static void fts5IndexIntegrityCheckSegment(
  Fts5Index *p,                   /* FTS5 backend object */
  Fts5StructureSegment *pSeg      /* Segment to check internal consistency */
){
  Fts5Config *pConfig = p->pConfig;
  int bSecureDelete = (pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE);
  sqlite3_stmt *pStmt = 0;
  int rc2;
  int iIdxPrevLeaf = pSeg->pgnoFirst-1;
  int iDlidxPrevLeaf = pSeg->pgnoLast;

  if( pSeg->pgnoFirst==0 ) return;

6116
6117
6118
6119
6120
6121
6122










6123


6124
6125
6126
6127
6128
6129
6130
    if( pLeaf==0 ) break;

    /* Check that the leaf contains at least one term, and that it is equal
    ** to or larger than the split-key in zIdxTerm.  Also check that if there
    ** is also a rowid pointer within the leaf page header, it points to a
    ** location before the term.  */
    if( pLeaf->nn<=pLeaf->szLeaf ){










      p->rc = FTS5_CORRUPT;


    }else{
      int iOff;                   /* Offset of first term on leaf */
      int iRowidOff;              /* Offset of first rowid on leaf */
      int nTerm;                  /* Size of term on leaf in bytes */
      int res;                    /* Comparison of term and split-key */

      iOff = fts5LeafFirstTermOff(pLeaf);







>
>
>
>
>
>
>
>
>
>
|
>
>







6551
6552
6553
6554
6555
6556
6557
6558
6559
6560
6561
6562
6563
6564
6565
6566
6567
6568
6569
6570
6571
6572
6573
6574
6575
6576
6577
    if( pLeaf==0 ) break;

    /* Check that the leaf contains at least one term, and that it is equal
    ** to or larger than the split-key in zIdxTerm.  Also check that if there
    ** is also a rowid pointer within the leaf page header, it points to a
    ** location before the term.  */
    if( pLeaf->nn<=pLeaf->szLeaf ){

      if( nIdxTerm==0 
       && pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE
       && pLeaf->nn==pLeaf->szLeaf
       && pLeaf->nn==4
      ){
        /* special case - the very first page in a segment keeps its %_idx
        ** entry even if all the terms are removed from it by secure-delete 
        ** operations. */
      }else{
        p->rc = FTS5_CORRUPT;
      }

    }else{
      int iOff;                   /* Offset of first term on leaf */
      int iRowidOff;              /* Offset of first rowid on leaf */
      int nTerm;                  /* Size of term on leaf in bytes */
      int res;                    /* Comparison of term and split-key */

      iOff = fts5LeafFirstTermOff(pLeaf);
6180
6181
6182
6183
6184
6185
6186
6187

6188

6189

6190
6191
6192
6193
6194
6195
6196
        pLeaf = fts5DataRead(p, iKey);
        if( pLeaf ){
          i64 iRowid;
          int iRowidOff = fts5LeafFirstRowidOff(pLeaf);
          ASSERT_SZLEAF_OK(pLeaf);
          if( iRowidOff>=pLeaf->szLeaf ){
            p->rc = FTS5_CORRUPT;
          }else{

            fts5GetVarint(&pLeaf->p[iRowidOff], (u64*)&iRowid);

            if( iRowid!=fts5DlidxIterRowid(pDlidx) ) p->rc = FTS5_CORRUPT;

          }
          fts5DataRelease(pLeaf);
        }
      }

      iDlidxPrevLeaf = iPg;
      fts5DlidxIterFree(pDlidx);







|
>

>
|
>







6627
6628
6629
6630
6631
6632
6633
6634
6635
6636
6637
6638
6639
6640
6641
6642
6643
6644
6645
6646
        pLeaf = fts5DataRead(p, iKey);
        if( pLeaf ){
          i64 iRowid;
          int iRowidOff = fts5LeafFirstRowidOff(pLeaf);
          ASSERT_SZLEAF_OK(pLeaf);
          if( iRowidOff>=pLeaf->szLeaf ){
            p->rc = FTS5_CORRUPT;
          }else if( bSecureDelete==0 || iRowidOff>0 ){
            i64 iDlRowid = fts5DlidxIterRowid(pDlidx);
            fts5GetVarint(&pLeaf->p[iRowidOff], (u64*)&iRowid);
            if( iRowid<iDlRowid || (bSecureDelete==0 && iRowid!=iDlRowid) ){
              p->rc = FTS5_CORRUPT;
            }
          }
          fts5DataRelease(pLeaf);
        }
      }

      iDlidxPrevLeaf = iPg;
      fts5DlidxIterFree(pDlidx);
Changes to ext/fts5/fts5_main.c.
1619
1620
1621
1622
1623
1624
1625


1626
1627
1628
1629
1630
1631
1632
1633
1634
1635





1636
1637
1638
1639
1640
1641
1642
  sqlite3_value **apVal,          /* Array of arguments */
  sqlite_int64 *pRowid            /* OUT: The affected (or effected) rowid */
){
  Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
  Fts5Config *pConfig = pTab->p.pConfig;
  int eType0;                     /* value_type() of apVal[0] */
  int rc = SQLITE_OK;             /* Return code */



  /* A transaction must be open when this is called. */
  assert( pTab->ts.eState==1 || pTab->ts.eState==2 );

  assert( pVtab->zErrMsg==0 );
  assert( nArg==1 || nArg==(2+pConfig->nCol+2) );
  assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER 
       || sqlite3_value_type(apVal[0])==SQLITE_NULL 
  );
  assert( pTab->p.pConfig->pzErrmsg==0 );





  pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg;

  /* Put any active cursors into REQUIRE_SEEK state. */
  fts5TripCursors(pTab);

  eType0 = sqlite3_value_type(apVal[0]);
  if( eType0==SQLITE_NULL 







>
>










>
>
>
>
>







1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
  sqlite3_value **apVal,          /* Array of arguments */
  sqlite_int64 *pRowid            /* OUT: The affected (or effected) rowid */
){
  Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
  Fts5Config *pConfig = pTab->p.pConfig;
  int eType0;                     /* value_type() of apVal[0] */
  int rc = SQLITE_OK;             /* Return code */
  int bUpdateOrDelete = 0;
  

  /* A transaction must be open when this is called. */
  assert( pTab->ts.eState==1 || pTab->ts.eState==2 );

  assert( pVtab->zErrMsg==0 );
  assert( nArg==1 || nArg==(2+pConfig->nCol+2) );
  assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER 
       || sqlite3_value_type(apVal[0])==SQLITE_NULL 
  );
  assert( pTab->p.pConfig->pzErrmsg==0 );
  if( pConfig->pgsz==0 ){
    rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
    if( rc!=SQLITE_OK ) return rc;
  }

  pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg;

  /* Put any active cursors into REQUIRE_SEEK state. */
  fts5TripCursors(pTab);

  eType0 = sqlite3_value_type(apVal[0]);
  if( eType0==SQLITE_NULL 
1681
1682
1683
1684
1685
1686
1687

1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702

1703
1704
1705
1706
1707
1708
1709
      rc = SQLITE_ERROR;
    }

    /* DELETE */
    else if( nArg==1 ){
      i64 iDel = sqlite3_value_int64(apVal[0]);  /* Rowid to delete */
      rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0);

    }

    /* INSERT or UPDATE */
    else{
      int eType1 = sqlite3_value_numeric_type(apVal[1]);

      if( eType1!=SQLITE_INTEGER && eType1!=SQLITE_NULL ){
        rc = SQLITE_MISMATCH;
      }

      else if( eType0!=SQLITE_INTEGER ){     
        /* If this is a REPLACE, first remove the current entry (if any) */
        if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){
          i64 iNew = sqlite3_value_int64(apVal[1]);  /* Rowid to delete */
          rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);

        }
        fts5StorageInsert(&rc, pTab, apVal, pRowid);
      }

      /* UPDATE */
      else{
        i64 iOld = sqlite3_value_int64(apVal[0]);  /* Old rowid */







>















>







1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
      rc = SQLITE_ERROR;
    }

    /* DELETE */
    else if( nArg==1 ){
      i64 iDel = sqlite3_value_int64(apVal[0]);  /* Rowid to delete */
      rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0);
      bUpdateOrDelete = 1;
    }

    /* INSERT or UPDATE */
    else{
      int eType1 = sqlite3_value_numeric_type(apVal[1]);

      if( eType1!=SQLITE_INTEGER && eType1!=SQLITE_NULL ){
        rc = SQLITE_MISMATCH;
      }

      else if( eType0!=SQLITE_INTEGER ){     
        /* If this is a REPLACE, first remove the current entry (if any) */
        if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){
          i64 iNew = sqlite3_value_int64(apVal[1]);  /* Rowid to delete */
          rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);
          bUpdateOrDelete = 1;
        }
        fts5StorageInsert(&rc, pTab, apVal, pRowid);
      }

      /* UPDATE */
      else{
        i64 iOld = sqlite3_value_int64(apVal[0]);  /* Old rowid */
1724
1725
1726
1727
1728
1729
1730

1731
1732
1733













1734
1735
1736
1737
1738
1739
1740
              rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal,*pRowid);
            }
          }
        }else{
          rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0);
          fts5StorageInsert(&rc, pTab, apVal, pRowid);
        }

      }
    }
  }














  pTab->p.pConfig->pzErrmsg = 0;
  return rc;
}

/*
** Implementation of xSync() method. 







>



>
>
>
>
>
>
>
>
>
>
>
>
>







1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
              rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal,*pRowid);
            }
          }
        }else{
          rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0);
          fts5StorageInsert(&rc, pTab, apVal, pRowid);
        }
        bUpdateOrDelete = 1;
      }
    }
  }

  if( rc==SQLITE_OK 
   && bUpdateOrDelete 
   && pConfig->bSecureDelete 
   && pConfig->iVersion==FTS5_CURRENT_VERSION 
  ){
    rc = sqlite3Fts5StorageConfigValue(
        pTab->pStorage, "version", 0, FTS5_CURRENT_VERSION_SECUREDELETE
    );
    if( rc==SQLITE_OK ){
      pConfig->iVersion = FTS5_CURRENT_VERSION_SECUREDELETE;
    }
  }

  pTab->p.pConfig->pzErrmsg = 0;
  return rc;
}

/*
** Implementation of xSync() method. 
2587
2588
2589
2590
2591
2592
2593

2594
2595
2596
2597
2598
2599
2600
** Discard the contents of the pending terms table.
*/
static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
  Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
  UNUSED_PARAM(iSavepoint);  /* Call below is a no-op for NDEBUG builds */
  fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint);
  fts5TripCursors(pTab);

  return sqlite3Fts5StorageRollback(pTab->pStorage);
}

/*
** Register a new auxiliary function with global context pGlobal.
*/
static int fts5CreateAux(







>







2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
** Discard the contents of the pending terms table.
*/
static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
  Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
  UNUSED_PARAM(iSavepoint);  /* Call below is a no-op for NDEBUG builds */
  fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint);
  fts5TripCursors(pTab);
  pTab->p.pConfig->pgsz = 0;
  return sqlite3Fts5StorageRollback(pTab->pStorage);
}

/*
** Register a new auxiliary function with global context pGlobal.
*/
static int fts5CreateAux(
Changes to ext/fts5/test/fts5_common.tcl.
590
591
592
593
594
595
596




597
598
599
600
601
602
603
  nearset_rf $aCol {*}$args
  if {[lsearch $args -col]>=0} { 
    set ::expr_not_ok 1
  }
  list
}






#-------------------------------------------------------------------------
# Code for a simple Tcl tokenizer that supports synonyms at query time.
#
proc tclnum_tokenize {mode tflags text} {
  foreach {w iStart iEnd} [fts5_tokenize_split $text] {
    sqlite3_fts5_token $w $iStart $iEnd







>
>
>
>







590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
  nearset_rf $aCol {*}$args
  if {[lsearch $args -col]>=0} { 
    set ::expr_not_ok 1
  }
  list
}

proc dump {tname} {
  execsql_pp "SELECT * FROM ${tname}_idx"
  execsql_pp "SELECT id, quote(block), fts5_decode(id,block) FROM ${tname}_data"
}

#-------------------------------------------------------------------------
# Code for a simple Tcl tokenizer that supports synonyms at query time.
#
proc tclnum_tokenize {mode tflags text} {
  foreach {w iStart iEnd} [fts5_tokenize_split $text] {
    sqlite3_fts5_token $w $iStart $iEnd
Added ext/fts5/test/fts5secure.test.












































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# 2023 Feb 17
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#*************************************************************************
#

source [file join [file dirname [info script]] fts5_common.tcl]
ifcapable !fts5 { finish_test ; return }
set ::testprefix fts5secure

proc dump {tname} {
  execsql_pp "SELECT * FROM ${tname}_idx"
  execsql_pp "SELECT id, quote(block), fts5_decode(id,block) FROM ${tname}_data"
}


do_execsql_test 0.0 {
  CREATE VIRTUAL TABLE t1 USING fts5(ab);
  CREATE VIRTUAL TABLE v1 USING fts5vocab('t1', 'instance');
  INSERT INTO t1(rowid, ab) VALUES
      (0,'abc'), (1,'abc'), (2,'abc'), (3,'abc'), (4,'def');
}

do_execsql_test 0.1 {
  INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
}

do_execsql_test 0.2 {
  DELETE FROM t1 WHERE rowid=2;
}

do_execsql_test 0.3 {
  SELECT count(*) FROM t1_data
} 3

do_execsql_test 0.4 {
  INSERT INTO t1(t1) VALUES('integrity-check');
} 

do_execsql_test 0.5 {
  DELETE FROM t1 WHERE rowid=3;
}

do_execsql_test 0.6 {
  INSERT INTO t1(t1) VALUES('integrity-check');
} 

do_execsql_test 0.7 {
  DELETE FROM t1 WHERE rowid=0;
}

do_execsql_test 0.8 {
  INSERT INTO t1(t1) VALUES('integrity-check');
} 

#----------------------------------

do_execsql_test 1.0 {
  CREATE VIRTUAL TABLE t2 USING fts5(ab);
  INSERT INTO t2(rowid, ab) VALUES (5, 'key'), (6, 'value');
  INSERT INTO t2(t2, rank) VALUES('secure-delete', 1);
}

#execsql_pp { SELECT id, quote(block) FROM t1_data }
#execsql_pp { SELECT segid, quote(term), pgno FROM t1_idx }

do_execsql_test 1.1 {
  DELETE FROM t2 WHERE rowid = 5;
}

do_execsql_test 1.2 {
  INSERT INTO t2(t2) VALUES('integrity-check');
} 

do_execsql_test 1.3 {
  DELETE FROM t2 WHERE rowid = 6;
}

do_execsql_test 1.4 {
  INSERT INTO t2(t2) VALUES('integrity-check');
} 

do_execsql_test 1.5 {
  SELECT * FROM t2('value');
  SELECT * FROM t2('v*');
} 

do_execsql_test 1.6 {
  SELECT * FROM t2('value') ORDER BY rowid DESC;
  SELECT * FROM t2('v*') ORDER BY rowid DESC;
} 
execsql_pp {
  SELECT id, quote(block) FROM t2_data;
}

#----------------------------------

do_execsql_test 2.0 {
  CREATE VIRTUAL TABLE ft USING fts5(ab);
  CREATE VIRTUAL TABLE vocab USING fts5vocab('ft', 'instance');
  INSERT INTO ft(rowid, ab) VALUES
    (1, 'one'), 
    (2, 'two'), 
    (3, 'three'), 
    (4, 'four'), 
    (5, 'one one'), 
    (6, 'one two'), 
    (7, 'one three'), 
    (8, 'one four'), 
    (9,  'two one'),
    (10, 'two two'),
    (11, 'two three'),
    (12, 'two four'),
    (13, 'three one'), 
    (14, 'three two'), 
    (15, 'three three'), 
    (16, 'three four');
}

do_execsql_test 2.1 {
  SELECT count(*) FROM ft_data;
} {3}

do_execsql_test 2.2 {
  INSERT INTO ft(ft, rank) VALUES('secure-delete', 1);
}

do_execsql_test 2.3 {
  DELETE FROM ft WHERE rowid=9;
}

do_execsql_test 2.4 {
  INSERT INTO ft(ft) VALUES('integrity-check');
}

do_execsql_test 2.5 {
  DELETE FROM ft WHERE ab LIKE '%two%'
}

do_execsql_test 2.6 {
  INSERT INTO ft(ft) VALUES('integrity-check');
}

do_execsql_test 2.7 {
  SELECT count(*) FROM ft_data;
} {3}

#----------------------------------
reset_db

set ::vocab {
  one two three four five six seven eight nine ten
  eleven twelve thirteen fourteen fifteen sixteen 
  seventeen eighteen nineteen twenty
}
proc rnddoc {} {
  set nVocab [llength $::vocab]
  set ret [list]
  for {set ii 0} {$ii < 8} {incr ii} {
    lappend ret [lindex $::vocab [expr int(abs(rand()) * $nVocab)]]
  }
  set ret
}

proc contains {list val} {
  expr {[lsearch $list $val]>=0}
}

foreach {tn pgsz} {
  2 64
  1 1000
} {
  reset_db
  db function rnddoc rnddoc
  db function contains contains
  
  expr srand(1)

  do_execsql_test 3.$tn.0 {
    CREATE VIRTUAL TABLE t1 USING fts5(x);
    INSERT INTO t1(t1, rank) VALUES('pgsz', $pgsz);
    WITH s(i) AS (
      VALUES(1) UNION SELECT i+1 FROM s WHERE i<20
    )
    INSERT INTO t1 SELECT rnddoc() FROM s;
  }

  do_execsql_test 3.$tn.1 {
    INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
  }
  
  foreach {rowid} {
    6 16 3 4 9 14 13 7 20 15 19 10 11 2 5 18 17 1 12 8
  } {

    do_execsql_test 3.$tn.2.$rowid {
      DELETE FROM t1 WHERE rowid=$rowid;
    }
    do_execsql_test 3.$tn.2.$rowid.ic {
      INSERT INTO t1(t1) VALUES('integrity-check');
    }

    foreach v $::vocab {
      do_execsql_test 3.$tn.2.$rowid.q.$v {
        SELECT rowid FROM t1($v)
      } [db eval {SELECT rowid FROM t1 WHERE contains(x, $v)}]

      do_execsql_test 3.$tn.2.$rowid.q.$v.DESC {
        SELECT rowid FROM t1($v) ORDER BY 1 DESC
      } [db eval {SELECT rowid FROM t1 WHERE contains(x, $v) ORDER BY 1 DESC}]
    }
  }
}

do_execsql_test 3.3 {
  INSERT INTO t1(x) VALUES('optimize');
  INSERT INTO t1(t1) VALUES('optimize');
  SELECT count(*) FROM t1_data;
} {3}

#----------------------------------
reset_db
do_execsql_test 4.0 {
  CREATE VIRTUAL TABLE t1 USING fts5(x);
  INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
  INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
}

set L1 [string repeat abcdefghij 10]
set L2 [string repeat 1234567890 10]

do_execsql_test 4.1 {
  INSERT INTO t1 VALUES('aa' || $L1 || ' ' || $L2);
}
do_execsql_test 4.2 {
  DELETE FROM t1 WHERE rowid=1
}
do_execsql_test 4.3 {
  INSERT INTO t1(t1) VALUES('integrity-check');
}

#----------------------------------
reset_db
do_execsql_test 5.0 {
  CREATE VIRTUAL TABLE t1 USING fts5(x);
  INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
  INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
}

set doc "aa [string repeat {abc } 60]"

do_execsql_test 5.1 {
  BEGIN;
    INSERT INTO t1 VALUES($doc);
    INSERT INTO t1 VALUES('aa abc');
  COMMIT;
}

do_execsql_test 5.2 {
  DELETE FROM t1 WHERE rowid = 1;
}

do_execsql_test 5.3 {
  INSERT INTO t1(t1) VALUES('integrity-check');
}

do_execsql_test 5.4 { SELECT rowid FROM t1('abc'); } 2
do_execsql_test 5.5 { SELECT rowid FROM t1('aa'); } 2


finish_test

Added ext/fts5/test/fts5secure2.test.














































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# 2023 Feb 17
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#*************************************************************************
#

source [file join [file dirname [info script]] fts5_common.tcl]
ifcapable !fts5 { finish_test ; return }
set ::testprefix fts5secure2

do_execsql_test 1.0 {
  CREATE VIRTUAL TABLE ft USING fts5(col);
  INSERT INTO ft VALUES('data for the table');
  INSERT INTO ft VALUES('more of the same');
  INSERT INTO ft VALUES('and extra data');
}

do_execsql_test 1.1 {
  SELECT * FROM ft_config
} {version 4}

do_execsql_test 1.2 {
  INSERT INTO ft(ft, rank) VALUES('secure-delete', 1);
  SELECT * FROM ft_config;
} {secure-delete 1 version 4}

do_execsql_test 1.3 {
  INSERT INTO ft(ft, rank) VALUES('secure-delete', 1);
  SELECT * FROM ft_config;
} {secure-delete 1 version 4}

do_execsql_test 1.4 {
  DELETE FROM ft WHERE rowid=2;
  SELECT * FROM ft_config;
} {secure-delete 1 version 5}

do_execsql_test 1.5 {
  SELECT rowid, col FROM ft('data');
} {1 {data for the table} 3 {and extra data}}

db close
sqlite3 db test.db

do_execsql_test 1.6 {
  SELECT rowid, col FROM ft('data');
} {1 {data for the table} 3 {and extra data}}

#------------------------------------------------------------------------

reset_db
do_execsql_test 2.0 {
  CREATE VIRTUAL TABLE ft USING fts5(col);
  INSERT INTO ft VALUES('one zero one one zero');
  INSERT INTO ft(ft, rank) VALUES('secure-delete', 1);
}

do_execsql_test 2.1 {
  SELECT count(*) FROM ft_data WHERE block=X'00000004';
} {0}

do_execsql_test 2.2 {
  UPDATE ft SET col = 'zero one zero zero one' WHERE rowid=1;
}

do_execsql_test 2.3 {
  SELECT count(*) FROM ft_data WHERE block=X'00000004';
} {1}

do_execsql_test 2.4 {
  INSERT INTO ft VALUES('one zero zero one');
  DELETE FROM ft WHERE rowid=1;
}

do_execsql_test 2.5 {
  SELECT count(*) FROM ft_data WHERE block=X'00000004';
} {2}


finish_test


Added ext/fts5/test/fts5secure3.test.












































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# 2023 Feb 17
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#*************************************************************************
#
# TESTRUNNER: slow
#

source [file join [file dirname [info script]] fts5_common.tcl]
ifcapable !fts5 { finish_test ; return }
set ::testprefix fts5secure3

do_execsql_test 1.0 {
  CREATE VIRTUAL TABLE ft USING fts5(col);
  INSERT INTO ft VALUES('data for the table');
  INSERT INTO ft VALUES('more of the same');
  INSERT INTO ft VALUES('and extra data');

  INSERT INTO ft(ft, rank) VALUES('secure-delete', 1);
}

do_execsql_test 1.1 {
  BEGIN;
    INSERT INTO ft(rowid, col) VALUES(0, 'the next data');
    DELETE FROM ft WHERE rowid=1;
    DELETE FROM ft WHERE rowid=2;
    INSERT INTO ft(rowid, col) VALUES(6, 'with some more of the same data');
  COMMIT;
}

do_execsql_test 1.2 {
  INSERT INTO ft(ft) VALUES('integrity-check');
}

#-------------------------------------------------------------------------

reset_db
do_execsql_test 2.0 {
  CREATE VIRTUAL TABLE t1 USING fts5(x);
  INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
  INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
  BEGIN;
    INSERT INTO t1 VALUES('the start');
}
do_test 2.1 {
  for {set i 0} {$i < 1000} {incr i} {
    execsql { INSERT INTO t1 VALUES('the ' || hex(randomblob(3))) }
  }
  execsql  {
      INSERT INTO t1 VALUES('the end');
    COMMIT;
  }
} {}

do_execsql_test 2.2 {
  DELETE FROM t1 WHERE rowid BETWEEN 2 AND 1000;
}

do_execsql_test 2.3 {
  INSERT INTO t1(t1) VALUES('integrity-check');
}

do_execsql_test 2.6 {
  INSERT INTO t1(rowid, x) VALUES(500, 'middle');
  INSERT INTO t1(rowid, x) VALUES(501, 'value');
  SELECT * FROM t1('the middle');
}

do_execsql_test 2.7 {
  INSERT INTO t1(t1) VALUES('optimize');
}

do_execsql_test 2.8 {
  SELECT count(*) FROM t1_data
} 4

#execsql_pp { SELECT id, quote(block), fts5_decode(id, block) FROM t1_data; }

#-------------------------------------------------------------------------
# Tests with large/small rowid values.
#

reset_db

expr srand(0)

set vocab {
  Popper Poppins Popsicle Porfirio Porrima Porsche
  Porter Portia Portland Portsmouth Portugal Portuguese
  Poseidon Post PostgreSQL Potemkin Potomac Potsdam
  Pottawatomie Potter Potts Pound Poussin Powell
  PowerPC PowerPoint Powers Powhatan Poznan Prada
  Prado Praetorian Prague Praia Prakrit Pratchett
  Pratt Pravda Praxiteles Preakness Precambrian Preminger
  Premyslid Prensa Prentice Pres Presbyterian Presbyterianism
}
proc newdoc {} {
  for {set i 0} {$i<8} {incr i} {
    lappend ret [lindex $::vocab [expr int(abs(rand()) * [llength $::vocab])]]
  }
  set ret
}
db func newdoc newdoc

do_execsql_test 3.0 {
  CREATE VIRTUAL TABLE fff USING fts5(y);
  INSERT INTO fff(fff, rank) VALUES('pgsz', 64);

  WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 )
  INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s;

  WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 )
  INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s;

  WITH s(x) AS ( VALUES(1) UNION ALL SELECT x+1 FROM s WHERE x<1000 )
  INSERT INTO fff(rowid, y) SELECT random() , newdoc() FROM s;

  INSERT INTO fff(fff, rank) VALUES('secure-delete', 1);
}

proc lshuffle {in} {
  set out [list]
  while {[llength $in]>0} {
    set idx [expr int(abs(rand()) * [llength $in])]
    lappend out [lindex $in $idx]
    set in [lreplace $in $idx $idx]
  }
  set out
}

#dump fff

set iTest 1
foreach ii [lshuffle [db eval {SELECT rowid FROM fff}]] {
  #if {$iTest==1} { dump fff }
  #if {$iTest==1} { breakpoint }
  do_execsql_test 3.1.$iTest.$ii {
    DELETE FROM fff WHERE rowid=$ii;
  }
  #if {$iTest==1} { dump fff }
  if {($iTest % 20)==0} {
    do_execsql_test 3.1.$iTest.$ii.ic {
      INSERT INTO fff(fff) VALUES('integrity-check');
    }
  }
  #if {$iTest==1} { break }
  incr iTest
}

#execsql_pp { SELECT rowid FROM fff('post') ORDER BY rowid ASC }
#breakpoint
#execsql_pp { 
#  SELECT rowid FROM fff('post') ORDER BY rowid DESC 
#}
#
#dump fff


finish_test

Added ext/fts5/test/fts5secure4.test.




















































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# 2023 April 14
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#*************************************************************************
#

source [file join [file dirname [info script]] fts5_common.tcl]
return_if_no_fts5
set ::testprefix fts5secure4

#-------------------------------------------------------------------------
# Test using the 'delete' command to attempt to delete a token that 
# is not present in the index in secure-delete mode.
#
do_execsql_test 1.0 {
  CREATE VIRTUAL TABLE t1 USING fts5(a, b, content=x1);

  CREATE TABLE x1(rowid INTEGER PRIMARY KEY, a, b);
  INSERT INTO x1 VALUES
    (1, 'hello world', 'today xyz'),
    (2, 'not the day', 'crunch crumble and chomp'),
    (3, 'one', 'two');
  INSERT INTO t1(t1) VALUES('rebuild');
}

do_execsql_test 1.1 {
  INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
}

do_execsql_test 1.2 {
  INSERT INTO t1(t1, rowid, a, b) VALUES('delete', 4, 'nosuchtoken', '');
}

do_execsql_test 1.3 {
  INSERT INTO t1(t1) VALUES('integrity-check');
}

do_execsql_test 1.4 {
  INSERT INTO t1(t1, rowid, a, b) VALUES('delete', 1, 'crunch', '');
}

do_execsql_test 1.5 {
  INSERT INTO t1(t1, rowid, a, b) VALUES('delete', 3, 'crunch', '');
}

do_execsql_test 1.6 {
  INSERT INTO t1(t1) VALUES('integrity-check');
}

do_execsql_test 1.7 {
CREATE VIRTUAL TABLE y1 USING fts5(xx, prefix='1,2');
INSERT INTO y1(y1, rank) VALUES('pgsz', 64);
INSERT INTO y1(y1, rank) VALUES('secure-delete', 1);
}
do_execsql_test 1.8 {
  BEGIN;
  INSERT INTO y1(rowid, xx) VALUES(1, 'abc def');
  INSERT INTO y1(rowid, xx) VALUES(2, 'reallyreallylongtoken');
  COMMIT;
}
do_execsql_test 1.9 {
  DELETE FROM y1 WHERE rowid=1;
  INSERT INTO y1(y1) VALUES('integrity-check');
}

do_execsql_test 1.10 {
  CREATE VIRTUAL TABLE w1 USING fts5(ww, content="");
  INSERT INTO w1(rowid, ww) VALUES(123, '');
}
do_catchsql_test 1.11 {
  INSERT INTO w1(w1, rowid, ww) VALUES('delete', 123, 'xyz');
} {1 {database disk image is malformed}}
do_catchsql_test 1.12 {
  DROP TABLE w1;
  CREATE VIRTUAL TABLE w1 USING fts5(ww, content="");
  INSERT INTO w1(rowid, ww) VALUES(123, '');
  DELETE FROM w1_data WHERE id>10;
  INSERT INTO w1(w1, rowid, ww) VALUES('delete', 123, 'xyz');
} {1 {database disk image is malformed}}

#-------------------------------------------------------------------------
# Test using secure-delete with detail=none or detail=col.
#
foreach {tn d} {1 full 2 none 3 column} {
  reset_db
  do_execsql_test 2.$tn.1 "
    CREATE VIRTUAL TABLE x1 USING fts5(xx, yy, zz, detail=$d, prefix='10,20');
    INSERT INTO x1(x1, rank) VALUES('pgsz', 64);
    INSERT INTO x1(x1, rank) VALUES('secure-delete', 1);
  "

  do_execsql_test 2.$tn.2 {
    BEGIN;
      INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c');
      INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c');
      INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c');
      INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c');
      INSERT INTO x1(xx, yy, zz) VALUES('a b c', 'd e f', 'a b c');
    COMMIT;
    INSERT INTO x1(x1) VALUES('integrity-check');
  }

  do_execsql_test 2.$tn.3 {
    DELETE FROM x1 WHERE rowid IN (2, 4, 6);
    INSERT INTO x1(x1) VALUES('integrity-check');
  }

  do_execsql_test 2.$tn.4 {
    DELETE FROM x1 WHERE rowid IN (1, 3, 5);
    INSERT INTO x1(x1) VALUES('integrity-check');
  }

  do_execsql_test 2.$tn.5 {
    WITH s(i) AS (
      SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
    )
    INSERT INTO x1 
      SELECT 'seems to be', 'used brew to', 'everything is working' FROM s
        UNION ALL
      SELECT 'used brew to', 'everything is working', 'seems to be' FROM s
        UNION ALL
      SELECT 'everything is working', 'seems to be', 'used brew to' FROM s
        UNION ALL
      SELECT 'abc', 'zzz', 'a b c d'
        UNION ALL
      SELECT 'z', 'z', 'z' FROM s
  }

  do_test 2.$tn.6 {
    for {set i 300} {$i > 200} {incr i -1} {
      execsql {
        DELETE FROM x1 WHERE rowid=$i;
        INSERT INTO x1(x1) VALUES('integrity-check');
      }
    }
  } {}

  do_test 2.$tn.7 {
    for {set i 1} {$i < 100} {incr i} {
      execsql {
        DELETE FROM x1 WHERE rowid=$i;
        INSERT INTO x1(x1) VALUES('integrity-check');
      }
    }
  } {}

  do_test 2.$tn.8 {
    foreach i [db eval {SELECT rowid FROM x1}] {
      execsql {
        DELETE FROM x1 WHERE rowid=$i;
        INSERT INTO x1(x1) VALUES('integrity-check');
      }
    }
  } {}

  do_execsql_test 2.$tn.9 {
    SELECT * FROM x1
  } {}
}



finish_test

Added ext/fts5/test/fts5secure5.test.


































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# 2023 April 14
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#*************************************************************************
#

source [file join [file dirname [info script]] fts5_common.tcl]
return_if_no_fts5
set ::testprefix fts5secure5
return_if_no_fts5

proc dump {} {
  execsql_pp {
    SELECT id, quote(block), fts5_decode_none(id, block) FROM ft1_data
  }
}

do_execsql_test 1.0 {
  CREATE VIRTUAL TABLE ft1 USING fts5(a, detail=none);
  INSERT INTO ft1(ft1, rank) VALUES('secure-delete', 1);
}

do_execsql_test 1.1 {
  BEGIN;
    INSERT INTO ft1(rowid, a) VALUES(1, 'abcd');
    INSERT INTO ft1(rowid, a) VALUES(2, 'abcd');
    INSERT INTO ft1(rowid, a) VALUES(3, 'abcd');
  COMMIT;
}
do_execsql_test 1.2 {
  DELETE FROM ft1 WHERE rowid=1;
}
do_execsql_test 1.3 {
  INSERT INTO ft1(ft1) VALUES('integrity-check');
}
do_execsql_test 1.4 {
  DELETE FROM ft1 WHERE rowid=3;
}
do_execsql_test 1.5 {
  INSERT INTO ft1(ft1) VALUES('integrity-check');
}
do_execsql_test 1.6 {
  DELETE FROM ft1 WHERE rowid=3;
}
do_execsql_test 1.7 {
  INSERT INTO ft1(ft1) VALUES('integrity-check');
}

#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
  CREATE VIRTUAL TABLE ft1 USING fts5(a, detail=none);
  INSERT INTO ft1(ft1, rank) VALUES('secure-delete', 1);
}

do_execsql_test 2.1 {
  BEGIN;
    INSERT INTO ft1(rowid, a) VALUES(1, 'abcd one');
    INSERT INTO ft1(rowid, a) VALUES(2, 'abcd two');
    INSERT INTO ft1(rowid, a) VALUES(3, 'abcd two');
    INSERT INTO ft1(rowid, a) VALUES(4, 'abcd two');
    INSERT INTO ft1(rowid, a) VALUES(5, 'abcd three');
  COMMIT;
}

do_execsql_test 2.2a {
  DELETE FROM ft1 WHERE rowid=3;
}
do_execsql_test 2.2b {
  INSERT INTO ft1(ft1) VALUES('integrity-check');
}
do_execsql_test 2.3a {
  DELETE FROM ft1 WHERE rowid=2;
}
do_execsql_test 2.3b {
  INSERT INTO ft1(ft1) VALUES('integrity-check');
}
do_execsql_test 2.4a {
  DELETE FROM ft1 WHERE rowid=4;
}
do_execsql_test 2.4b {
  INSERT INTO ft1(ft1) VALUES('integrity-check');
}

#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
  CREATE VIRTUAL TABLE ft1 USING fts5(a, detail=none, prefix=1);
  INSERT INTO ft1(ft1, rank) VALUES('secure-delete', 1);
  INSERT INTO ft1(ft1, rank) VALUES('pgsz', 64);
}
do_execsql_test 3.1 {
  BEGIN;
    INSERT INTO ft1(a) VALUES('c');
  COMMIT;
}
do_execsql_test 3.2 {
  DELETE FROM ft1 WHERE rowid IN (1);
  INSERT INTO ft1(ft1) VALUES('integrity-check');
}

#-------------------------------------------------------------------------
reset_db
do_execsql_test 4.0 {
  CREATE VIRTUAL TABLE ft1 USING fts5(a, detail=none);
  INSERT INTO ft1(ft1, rank) VALUES('secure-delete', 1);
  INSERT INTO ft1(ft1, rank) VALUES('pgsz', 64);

  WITH s(i) AS (
    SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<500
  )
  INSERT INTO ft1 SELECT 'abcdefg' FROM s;
}

do_test 4.1 {
  for {set i 500} {$i > 0} {incr i -1} {
    execsql { DELETE FROM ft1 WHERE rowid=$i }
    execsql { INSERT INTO ft1(ft1) VALUES('integrity-check') }
  }
} {}

finish_test

Added ext/fts5/test/fts5securefault.test.
















































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# 2023 April 14
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#*************************************************************************
# This file implements regression tests for SQLite library.  The
# focus of this script is testing the FTS5 module.
#

source [file join [file dirname [info script]] fts5_common.tcl]
source $testdir/malloc_common.tcl
set testprefix fts5securefault

# If SQLITE_ENABLE_FTS5 is defined, omit this file.
return_if_no_fts5

do_execsql_test 1.0 {
  CREATE VIRTUAL TABLE t1 USING fts5(ab);
  INSERT INTO t1(rowid, ab) VALUES
      (0, 'abc'), (1, 'abc'), (2, 'abc'), (3, 'abc'), (4, 'def');
}
faultsim_save_and_close

do_faultsim_test 1.1 -faults oom* -prep {
  faultsim_restore_and_reopen
  execsql {
    INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
  }
} -body {
  execsql { DELETE FROM t1 WHERE rowid=2 }
} -test {
  faultsim_test_result {0 {}}
}
do_faultsim_test 1.2 -faults oom* -prep {
  faultsim_restore_and_reopen
  execsql {
    INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
  }
} -body {
  execsql { DELETE FROM t1 WHERE rowid IN(0, 1, 2, 3, 4) }
} -test {
  faultsim_test_result {0 {}}
}

#-------------------------------------------------------------------------
#
reset_db
set big  [string repeat abcdefghij 5]
set big2 [string repeat klmnopqrst 5]
set doc "$big $big2"

do_execsql_test 2.0 {
  CREATE VIRTUAL TABLE t1 USING fts5(ab);
  INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
  WITH s(i) AS (
    SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<4
  )
  INSERT INTO t1(rowid, ab) SELECT i, $doc FROM s;
}
faultsim_save_and_close

do_faultsim_test 2.1 -faults oom* -prep {
  faultsim_restore_and_reopen
  execsql {
    INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
  }
} -body {
  execsql { DELETE FROM t1 WHERE rowid = 3 }
  execsql { DELETE FROM t1 WHERE rowid = 4 }
} -test {
  faultsim_test_result {0 {}}
}

#-------------------------------------------------------------------------
#
reset_db
set big  [string repeat abcdefghij 5]
set big2 [string repeat klmnopqrst 5]
set doc "$big $big2"

do_execsql_test 3.0 {
  CREATE VIRTUAL TABLE t1 USING fts5(ab);
  INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
  WITH s(i) AS (
    SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<25
  )
  INSERT INTO t1(rowid, ab) SELECT i, $doc FROM s;

  INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
  DELETE FROM t1 WHERE rowid BETWEEN 3 AND 23;
}
faultsim_save_and_close

do_faultsim_test 3.1 -faults oom* -prep {
  faultsim_restore_and_reopen
  execsql {
    INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
  }
} -body {
  execsql { DELETE FROM t1 WHERE rowid = 24 }
  execsql { DELETE FROM t1 WHERE rowid = 25 }
} -test {
  faultsim_test_result {0 {}}
}

#-------------------------------------------------------------------------
#
reset_db
set doc [string repeat "tok " 400]

do_execsql_test 4.0 {
  CREATE VIRTUAL TABLE t1 USING fts5(ab);
  INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
  INSERT INTO t1(rowid, ab) VALUES(1, $doc), (2, $doc), (3, $doc);
}
faultsim_save_and_close

do_faultsim_test 4.1 -faults oom* -prep {
  faultsim_restore_and_reopen
  execsql {
    INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
  }
} -body {
  execsql { DELETE FROM t1 WHERE rowid = 2 }
} -test {
  faultsim_test_result {0 {}}
}

#-------------------------------------------------------------------------
#
reset_db

set doc1 [string repeat "abc " 10]
set doc2 [string repeat "def " 10]

do_test 5.0 {
  execsql {
    CREATE VIRTUAL TABLE t1 USING fts5(ab);
    INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
    BEGIN;
  }
  for {set i 0} {$i < 50} {incr i} {
    execsql {
      INSERT INTO t1(rowid, ab) VALUES($i, 'abcdefg');
    }
  }
  execsql {
    INSERT INTO t1(rowid, ab) VALUES(105, 'def');
    COMMIT;
  }
} {}
faultsim_save_and_close

do_faultsim_test 5.1 -faults oom* -prep {
  faultsim_restore_and_reopen
  execsql {
    INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
  }
} -body {
  execsql { DELETE FROM t1 WHERE rowid = 105 }
} -test {
  faultsim_test_result {0 {}}
}

#-------------------------------------------------------------------------
#
reset_db
do_test 6.0 {
  execsql {
    CREATE VIRTUAL TABLE t1 USING fts5(ab);
    INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
    BEGIN;
      INSERT INTO t1(rowid, ab) VALUES(1, 'abcdefg');
      INSERT INTO t1(rowid, ab) VALUES(2, 'abcdefg');
      INSERT INTO t1(rowid, ab) VALUES(3, 'abcdefg');
    COMMIT;
  }
} {}
faultsim_save_and_close

do_faultsim_test 6.1 -faults oom* -prep {
  faultsim_restore_and_reopen
  execsql {
    INSERT INTO t1(t1, rank) VALUES('secure-delete', 1);
  }
} -body {
  execsql { 
    UPDATE t1 SET ab='abcdefg' WHERE rowid=2;
  }
} -test {
  faultsim_test_result {0 {}}
}


finish_test
Changes to ext/fts5/test/fts5version.test.
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63





64






























































65


do_execsql_test 1.3 {
  SELECT rowid FROM t1 WHERE t1 MATCH 'a';
} {1}

sqlite3_db_config db DEFENSIVE 0
do_execsql_test 1.4 {
  UPDATE t1_config set v=5 WHERE k='version';
}

do_test 1.5 {
  db close
  sqlite3 db test.db
  catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' }
} {1 {invalid fts5 file format (found 5, expected 4) - run 'rebuild'}}

do_test 1.6 {
  db close
  sqlite3 db test.db
  catchsql { INSERT INTO t1 VALUES('x y z') }
} {1 {invalid fts5 file format (found 5, expected 4) - run 'rebuild'}}

do_test 1.7 {
  sqlite3_db_config db DEFENSIVE 0
  execsql { DELETE FROM t1_config WHERE k='version' }
  db close
  sqlite3 db test.db
  catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' }
} {1 {invalid fts5 file format (found 0, expected 4) - run 'rebuild'}}





































































finish_test








|






|





|







|

>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

do_execsql_test 1.3 {
  SELECT rowid FROM t1 WHERE t1 MATCH 'a';
} {1}

sqlite3_db_config db DEFENSIVE 0
do_execsql_test 1.4 {
  UPDATE t1_config set v=6 WHERE k='version';
}

do_test 1.5 {
  db close
  sqlite3 db test.db
  catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' }
} {1 {invalid fts5 file format (found 6, expected 4 or 5) - run 'rebuild'}}

do_test 1.6 {
  db close
  sqlite3 db test.db
  catchsql { INSERT INTO t1 VALUES('x y z') }
} {1 {invalid fts5 file format (found 6, expected 4 or 5) - run 'rebuild'}}

do_test 1.7 {
  sqlite3_db_config db DEFENSIVE 0
  execsql { DELETE FROM t1_config WHERE k='version' }
  db close
  sqlite3 db test.db
  catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' }
} {1 {invalid fts5 file format (found 0, expected 4 or 5) - run 'rebuild'}}

do_test 1.8 {
  sqlite3_db_config db DEFENSIVE 0
  execsql { INSERT INTO t1_config VALUES('version', 4) }
  execsql { INSERT INTO t1(t1, rank) VALUES('secure-delete', 1) }
} {}

do_execsql_test 1.10 {
  SELECT * FROM t1_config
} {secure-delete 1 version 4}

do_execsql_test 1.11 {
  INSERT INTO t1(rowid, one) VALUES(123, 'one two three');
  DELETE FROM t1 WHERE rowid=123;
  SELECT * FROM t1_config
} {secure-delete 1 version 5}

do_execsql_test 1.11 {
  INSERT INTO t1(t1) VALUES('rebuild');
  SELECT * FROM t1_config
} {secure-delete 1 version 4}

do_execsql_test 1.12 {
  SELECT * FROM t1_config
} {secure-delete 1 version 4}

#-------------------------------------------------------------------------
reset_db

do_execsql_test 2.0 {
  CREATE VIRTUAL TABLE xyz USING fts5(x);
  INSERT INTO xyz(rowid, x) VALUES
      (1, 'one document'),
      (2, 'two document'),
      (3, 'three document'),
      (4, 'four document'),
      (5, 'five document'),
      (6, 'six document');

  INSERT INTO xyz(xyz, rank) VALUES('secure-delete', 1);
  SELECT v FROM xyz_config WHERE k='version';
} {4}

do_execsql_test 2.1 {
  BEGIN;
    INSERT INTO xyz(rowid, x) VALUES(7, 'seven document');
    SAVEPOINT one;
      DELETE FROM xyz WHERE rowid = 4;
}

do_execsql_test 2.2 {
      SELECT v FROM xyz_config WHERE k='version';
} {5}

do_execsql_test 2.3 {
    ROLLBACK TO one;
      SELECT v FROM xyz_config WHERE k='version';
} {4}


do_execsql_test 2.4 {
      DELETE FROM xyz WHERE rowid = 3;
  COMMIT;
  SELECT v FROM xyz_config WHERE k='version';
} {5}




finish_test

Changes to ext/rbu/rbuexlock.test.
283
284
285
286
287
288
289

290
291
do_test 4.21 { rbu step ; rbu state } {done}
rbu close

do_test 4.22 {
  catchsql { SELECT * FROM t1 } cons
} {0 {1 one 2 two}}



finish_test







>


283
284
285
286
287
288
289
290
291
292
do_test 4.21 { rbu step ; rbu state } {done}
rbu close

do_test 4.22 {
  catchsql { SELECT * FROM t1 } cons
} {0 {1 one 2 two}}

cons close

finish_test
Changes to ext/recover/recoverbuild.test.
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
  INSERT INTO t1 VALUES(123, 'one hundred and twenty three');
}

forcedelete test.db2
do_test 1.1 {
  set R [sqlite3_recover_init db main test.db2]
} {sqlite_recover1}

do_test 1.2 {
  $R run
} {1}

do_test 1.3 {
  list [catch { $R finish } msg] $msg







|







24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
  INSERT INTO t1 VALUES(123, 'one hundred and twenty three');
}

forcedelete test.db2
do_test 1.1 {
  set R [sqlite3_recover_init db main test.db2]
} {/sqlite_recover.*/}

do_test 1.2 {
  $R run
} {1}

do_test 1.3 {
  list [catch { $R finish } msg] $msg
Changes to src/alter.c.
1274
1275
1276
1277
1278
1279
1280













1281
1282
1283
1284
1285
1286
1287
  }else{
    rc = SQLITE_NOMEM;
  }

  sqlite3_free(zQuot);
  return rc;
}














/*
** Resolve all symbols in the trigger at pParse->pNewTrigger, assuming
** it was read from the schema of database zDb. Return SQLITE_OK if 
** successful. Otherwise, return an SQLite error code and leave an error
** message in the Parse object.
*/







>
>
>
>
>
>
>
>
>
>
>
>
>







1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
  }else{
    rc = SQLITE_NOMEM;
  }

  sqlite3_free(zQuot);
  return rc;
}

/*
** Set all pEList->a[].fg.eEName fields in the expression-list to val.
*/
static void renameSetENames(ExprList *pEList, int val){
  if( pEList ){
    int i;
    for(i=0; i<pEList->nExpr; i++){
      assert( val==ENAME_NAME || pEList->a[i].fg.eEName==ENAME_NAME );
      pEList->a[i].fg.eEName = val;
    }
  }
}

/*
** Resolve all symbols in the trigger at pParse->pNewTrigger, assuming
** it was read from the schema of database zDb. Return SQLITE_OK if 
** successful. Otherwise, return an SQLite error code and leave an error
** message in the Parse object.
*/
1322
1323
1324
1325
1326
1327
1328









1329

1330
1331
1332
1333
1334
1335
1336
            pParse, pStep->pExprList, pSrc, 0, 0, 0, 0, 0, 0
        );
        if( pSel==0 ){
          pStep->pExprList = 0;
          pSrc = 0;
          rc = SQLITE_NOMEM;
        }else{









          sqlite3SelectPrep(pParse, pSel, 0);

          rc = pParse->nErr ? SQLITE_ERROR : SQLITE_OK;
          assert( pStep->pExprList==0 || pStep->pExprList==pSel->pEList );
          assert( pSrc==pSel->pSrc );
          if( pStep->pExprList ) pSel->pEList = 0;
          pSel->pSrc = 0;
          sqlite3SelectDelete(db, pSel);
        }







>
>
>
>
>
>
>
>
>

>







1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
            pParse, pStep->pExprList, pSrc, 0, 0, 0, 0, 0, 0
        );
        if( pSel==0 ){
          pStep->pExprList = 0;
          pSrc = 0;
          rc = SQLITE_NOMEM;
        }else{
          /* pStep->pExprList contains an expression-list used for an UPDATE
          ** statement. So the a[].zEName values are the RHS of the
          ** "<col> = <expr>" clauses of the UPDATE statement. So, before
          ** running SelectPrep(), change all the eEName values in
          ** pStep->pExprList to ENAME_SPAN (from their current value of
          ** ENAME_NAME). This is to prevent any ids in ON() clauses that are
          ** part of pSrc from being incorrectly resolved against the
          ** a[].zEName values as if they were column aliases.  */
          renameSetENames(pStep->pExprList, ENAME_SPAN);
          sqlite3SelectPrep(pParse, pSel, 0);
          renameSetENames(pStep->pExprList, ENAME_NAME);
          rc = pParse->nErr ? SQLITE_ERROR : SQLITE_OK;
          assert( pStep->pExprList==0 || pStep->pExprList==pSel->pEList );
          assert( pSrc==pSel->pSrc );
          if( pStep->pExprList ) pSel->pEList = 0;
          pSel->pSrc = 0;
          sqlite3SelectDelete(db, pSel);
        }
Changes to src/expr.c.
6489
6490
6491
6492
6493
6494
6495
6496
6497
6498
6499
6500
6501
6502
6503
          assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) );
          if( pExpr->iTable==pItem->iCursor ){
            findOrCreateAggInfoColumn(pParse, pAggInfo, pExpr);
            break;
          } /* endif pExpr->iTable==pItem->iCursor */
        } /* end loop over pSrcList */
      }
      return WRC_Prune;
    }
    case TK_AGG_FUNCTION: {
      if( (pNC->ncFlags & NC_InAggFunc)==0
       && pWalker->walkerDepth==pExpr->op2
      ){
        /* Check to see if pExpr is a duplicate of another aggregate 
        ** function that is already in the pAggInfo structure







|







6489
6490
6491
6492
6493
6494
6495
6496
6497
6498
6499
6500
6501
6502
6503
          assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) );
          if( pExpr->iTable==pItem->iCursor ){
            findOrCreateAggInfoColumn(pParse, pAggInfo, pExpr);
            break;
          } /* endif pExpr->iTable==pItem->iCursor */
        } /* end loop over pSrcList */
      }
      return WRC_Continue;
    }
    case TK_AGG_FUNCTION: {
      if( (pNC->ncFlags & NC_InAggFunc)==0
       && pWalker->walkerDepth==pExpr->op2
      ){
        /* Check to see if pExpr is a duplicate of another aggregate 
        ** function that is already in the pAggInfo structure
Changes to src/fkey.c.
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332






1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
    sqlite3DbFree(db, aiCol);

    zFrom = pFKey->pFrom->zName;
    nFrom = sqlite3Strlen30(zFrom);

    if( action==OE_Restrict ){
      int iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
      Token tFrom;
      Token tDb;
      Expr *pRaise; 

      tFrom.z = zFrom;
      tFrom.n = nFrom;
      tDb.z = db->aDb[iDb].zDbSName;
      tDb.n = sqlite3Strlen30(tDb.z);

      pRaise = sqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed");
      if( pRaise ){
        pRaise->affExpr = OE_Abort;
      }






      pSelect = sqlite3SelectNew(pParse, 
          sqlite3ExprListAppend(pParse, 0, pRaise),
          sqlite3SrcListAppend(pParse, 0, &tDb, &tFrom),
          pWhere,
          0, 0, 0, 0, 0
      );
      pWhere = 0;
    }

    /* Disable lookaside memory allocation */







|
<


<
<
<
<
<




>
>
>
>
>
>


|







1313
1314
1315
1316
1317
1318
1319
1320

1321
1322





1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
    sqlite3DbFree(db, aiCol);

    zFrom = pFKey->pFrom->zName;
    nFrom = sqlite3Strlen30(zFrom);

    if( action==OE_Restrict ){
      int iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
      SrcList *pSrc;

      Expr *pRaise; 






      pRaise = sqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed");
      if( pRaise ){
        pRaise->affExpr = OE_Abort;
      }
      pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0);
      if( pSrc ){
        assert( pSrc->nSrc==1 );
        pSrc->a[0].zName = sqlite3DbStrDup(db, zFrom);
        pSrc->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName);
      }
      pSelect = sqlite3SelectNew(pParse, 
          sqlite3ExprListAppend(pParse, 0, pRaise),
          pSrc,
          pWhere,
          0, 0, 0, 0, 0
      );
      pWhere = 0;
    }

    /* Disable lookaside memory allocation */
Changes to src/os_unix.c.
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#    define SQLITE_ENABLE_LOCKING_STYLE 1
#  else
#    define SQLITE_ENABLE_LOCKING_STYLE 0
#  endif
#endif

/* Use pread() and pwrite() if they are available */
#if defined(__APPLE__)
# define HAVE_PREAD 1
# define HAVE_PWRITE 1
#endif
#if defined(HAVE_PREAD64) && defined(HAVE_PWRITE64)
# undef USE_PREAD
# define USE_PREAD64 1
#elif defined(HAVE_PREAD) && defined(HAVE_PWRITE)







|







68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#    define SQLITE_ENABLE_LOCKING_STYLE 1
#  else
#    define SQLITE_ENABLE_LOCKING_STYLE 0
#  endif
#endif

/* Use pread() and pwrite() if they are available */
#if defined(__APPLE__) || defined(__linux__)
# define HAVE_PREAD 1
# define HAVE_PWRITE 1
#endif
#if defined(HAVE_PREAD64) && defined(HAVE_PWRITE64)
# undef USE_PREAD
# define USE_PREAD64 1
#elif defined(HAVE_PREAD) && defined(HAVE_PWRITE)
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
** are gather together into this division.
*/

/*
** Seek to the offset passed as the second argument, then read cnt 
** bytes into pBuf. Return the number of bytes actually read.
**
** NB:  If you define USE_PREAD or USE_PREAD64, then it might also
** be necessary to define _XOPEN_SOURCE to be 500.  This varies from
** one system to another.  Since SQLite does not define USE_PREAD
** in any form by default, we will not attempt to define _XOPEN_SOURCE.
** See tickets #2741 and #2681.
**
** To avoid stomping the errno value on a failed read the lastErrno value
** is set before returning.
*/
static int seekAndRead(unixFile *id, sqlite3_int64 offset, void *pBuf, int cnt){
  int got;
  int prior = 0;
#if (!defined(USE_PREAD) && !defined(USE_PREAD64))







<
<
<
<
<
<







3318
3319
3320
3321
3322
3323
3324






3325
3326
3327
3328
3329
3330
3331
** are gather together into this division.
*/

/*
** Seek to the offset passed as the second argument, then read cnt 
** bytes into pBuf. Return the number of bytes actually read.
**






** To avoid stomping the errno value on a failed read the lastErrno value
** is set before returning.
*/
static int seekAndRead(unixFile *id, sqlite3_int64 offset, void *pBuf, int cnt){
  int got;
  int prior = 0;
#if (!defined(USE_PREAD) && !defined(USE_PREAD64))
Changes to src/select.c.
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
  struct ExprList_item *a;
  NameContext sNC;

  assert( pSelect!=0 );
  assert( (pSelect->selFlags & SF_Resolved)!=0 );
  assert( pTab->nCol==pSelect->pEList->nExpr || pParse->nErr>0 );
  assert( aff==SQLITE_AFF_NONE || aff==SQLITE_AFF_BLOB );
  if( db->mallocFailed ) return;
  while( pSelect->pPrior ) pSelect = pSelect->pPrior;
  a = pSelect->pEList->a;
  memset(&sNC, 0, sizeof(sNC));
  sNC.pSrcList = pSelect->pSrc;
  for(i=0, pCol=pTab->aCol; i<pTab->nCol; i++, pCol++){
    const char *zType;
    i64 n;







|







2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
  struct ExprList_item *a;
  NameContext sNC;

  assert( pSelect!=0 );
  assert( (pSelect->selFlags & SF_Resolved)!=0 );
  assert( pTab->nCol==pSelect->pEList->nExpr || pParse->nErr>0 );
  assert( aff==SQLITE_AFF_NONE || aff==SQLITE_AFF_BLOB );
  if( db->mallocFailed || IN_RENAME_OBJECT ) return;
  while( pSelect->pPrior ) pSelect = pSelect->pPrior;
  a = pSelect->pEList->a;
  memset(&sNC, 0, sizeof(sNC));
  sNC.pSrcList = pSelect->pSrc;
  for(i=0, pCol=pTab->aCol; i<pTab->nCol; i++, pCol++){
    const char *zType;
    i64 n;
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371

2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
        zType = 0;
        for(j=1; j<SQLITE_N_STDTYPE; j++){
          if( sqlite3StdTypeAffinity[j]==pCol->affinity ){
            zType = sqlite3StdType[j];
            break;
          }
        }
       }
     }
     if( zType ){
       i64 m = sqlite3Strlen30(zType);
       n = sqlite3Strlen30(pCol->zCnName);
       pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2);

       if( pCol->zCnName ){
         memcpy(&pCol->zCnName[n+1], zType, m+1);
         pCol->colFlags |= COLFLAG_HASTYPE;
       }else{
         testcase( pCol->colFlags & COLFLAG_HASTYPE );
        pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL);
      }
    }
    pColl = sqlite3ExprCollSeq(pParse, p);
    if( pColl ){
      assert( pTab->pIndex==0 );
      sqlite3ColumnSetColl(db, pCol, pColl->zName);
    }







|
|
|
|
|
|
>
|
|
|
|
|
<







2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377

2378
2379
2380
2381
2382
2383
2384
        zType = 0;
        for(j=1; j<SQLITE_N_STDTYPE; j++){
          if( sqlite3StdTypeAffinity[j]==pCol->affinity ){
            zType = sqlite3StdType[j];
            break;
          }
        }
      }
    }
    if( zType ){
      i64 m = sqlite3Strlen30(zType);
      n = sqlite3Strlen30(pCol->zCnName);
      pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2);
      pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL);
      if( pCol->zCnName ){
        memcpy(&pCol->zCnName[n+1], zType, m+1);
        pCol->colFlags |= COLFLAG_HASTYPE;
      }else{
        testcase( pCol->colFlags & COLFLAG_HASTYPE );

      }
    }
    pColl = sqlite3ExprCollSeq(pParse, p);
    if( pColl ){
      assert( pTab->pIndex==0 );
      sqlite3ColumnSetColl(db, pCol, pColl->zName);
    }
Changes to src/shell.c.in.
608
609
610
611
612
613
614
615
616

617
618
619
620
621

622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645


646
647
648
649
650
651
652
653
/*
** Prepare console, (if known to be a WIN32 console), for UTF-8
** input (from either typing or suitable paste operations) and for
** UTF-8 rendering. This may "fail" with a message to stderr, where
** the preparation is not done and common "code page" issues occur.
*/
static void console_prepare(void){
  conState.hConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
  if( isatty(0) && GetFileType(conState.hConsoleIn)==FILE_TYPE_CHAR ){

    if( !IsValidCodePage(CP_UTF8) ){
      fprintf(stderr, "Cannot use UTF-8 code page.\n");
      console_utf8 = 0;
      return;
    }

    conState.inCodePage = GetConsoleCP();
    conState.outCodePage = GetConsoleOutputCP();
    SetConsoleCP(CP_UTF8);
    SetConsoleOutputCP(CP_UTF8);
    GetConsoleMode( conState.hConsoleIn, &conState.consoleMode);
    SetConsoleMode( conState.hConsoleIn,
                    conState.consoleMode | ENABLE_LINE_INPUT );
    conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT);
    console_utf8 = 1;
  }else{
    console_utf8 = 0;
  }
}

/*
** Undo the effects of console_prepare(), if any.
*/
static void SQLITE_CDECL console_restore(void){
  if( console_utf8 && conState.inCodePage!=0
      && conState.hConsoleIn != INVALID_HANDLE_VALUE ){
    _setmode(_fileno(stdin), conState.infsMode);
    SetConsoleCP(conState.inCodePage);
    SetConsoleOutputCP(conState.outCodePage);
    SetConsoleMode( conState.hConsoleIn, conState.consoleMode );


    console_utf8 = 0; /* Avoid multiple calls. */
  }
}

/*
** Collect input like fgets(...) with special provisions for input
** from the Windows console to get around its strange coding issues.
** Defers to plain fgets() when input is not interactive or when the







|
|
>





>




<



















>
>
|







608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627

628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
/*
** Prepare console, (if known to be a WIN32 console), for UTF-8
** input (from either typing or suitable paste operations) and for
** UTF-8 rendering. This may "fail" with a message to stderr, where
** the preparation is not done and common "code page" issues occur.
*/
static void console_prepare(void){
  HANDLE hCI = GetStdHandle(STD_INPUT_HANDLE);
  if( isatty(0) && GetFileType(hCI)==FILE_TYPE_CHAR
      && GetConsoleMode( hCI, &conState.consoleMode) ){
    if( !IsValidCodePage(CP_UTF8) ){
      fprintf(stderr, "Cannot use UTF-8 code page.\n");
      console_utf8 = 0;
      return;
    }
    conState.hConsoleIn = hCI;
    conState.inCodePage = GetConsoleCP();
    conState.outCodePage = GetConsoleOutputCP();
    SetConsoleCP(CP_UTF8);
    SetConsoleOutputCP(CP_UTF8);

    SetConsoleMode( conState.hConsoleIn,
                    conState.consoleMode | ENABLE_LINE_INPUT );
    conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT);
    console_utf8 = 1;
  }else{
    console_utf8 = 0;
  }
}

/*
** Undo the effects of console_prepare(), if any.
*/
static void SQLITE_CDECL console_restore(void){
  if( console_utf8 && conState.inCodePage!=0
      && conState.hConsoleIn != INVALID_HANDLE_VALUE ){
    _setmode(_fileno(stdin), conState.infsMode);
    SetConsoleCP(conState.inCodePage);
    SetConsoleOutputCP(conState.outCodePage);
    SetConsoleMode( conState.hConsoleIn, conState.consoleMode );
    /* Avoid multiple calls. */
    conState.hConsoleIn = INVALID_HANDLE_VALUE;
    console_utf8 = 0;
  }
}

/*
** Collect input like fgets(...) with special provisions for input
** from the Windows console to get around its strange coding issues.
** Defers to plain fgets() when input is not interactive or when the
710
711
712
713
714
715
716
717

718

719
720
721
722
723
724




725
726
727
728
729
730
731
}

# define fgets(b,n,f) utf8_fgets(b,n,f)
#endif /* SHELL_WIN_UTF8_OPT */

/*
** Render output like fprintf().  Except, if the output is going to the
** console and if this is running on a Windows machine, translate the

** output from UTF-8 into MBCS.

*/
#if defined(_WIN32) || defined(WIN32)
void utf8_printf(FILE *out, const char *zFormat, ...){
  va_list ap;
  va_start(ap, zFormat);
  if( stdout_is_console && (out==stdout || out==stderr) ){




    char *z1 = sqlite3_vmprintf(zFormat, ap);
    char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0);
    sqlite3_free(z1);
    fputs(z2, out);
    sqlite3_free(z2);
  }else{
    vfprintf(out, zFormat, ap);







|
>
|
>





|
>
>
>
>







713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
}

# define fgets(b,n,f) utf8_fgets(b,n,f)
#endif /* SHELL_WIN_UTF8_OPT */

/*
** Render output like fprintf().  Except, if the output is going to the
** console and if this is running on a Windows machine, and if the -utf8
** option is unavailable or (available and inactive), translate the
** output from UTF-8 into MBCS for output through 8-bit stdout stream.
** (With -utf8 active, no translation is needed and must not be done.)
*/
#if defined(_WIN32) || defined(WIN32)
void utf8_printf(FILE *out, const char *zFormat, ...){
  va_list ap;
  va_start(ap, zFormat);
  if( stdout_is_console && (out==stdout || out==stderr)
# if SHELL_WIN_UTF8_OPT
      && !console_utf8
# endif
      ){
    char *z1 = sqlite3_vmprintf(zFormat, ap);
    char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0);
    sqlite3_free(z1);
    fputs(z2, out);
    sqlite3_free(z2);
  }else{
    vfprintf(out, zFormat, ap);
Changes to src/test1.c.
2387
2388
2389
2390
2391
2392
2393

2394
2395
2396
2397
2398
2399
2400
    return TCL_ERROR;
  }
  pVfs->xCurrentTimeInt64(pVfs, &t);
  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(t));
  return TCL_OK;
}


/*
** Usage: create_null_module DB NAME
*/
static int SQLITE_TCLAPI test_create_null_module(
  void * clientData,
  Tcl_Interp *interp,
  int objc,







>







2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
    return TCL_ERROR;
  }
  pVfs->xCurrentTimeInt64(pVfs, &t);
  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(t));
  return TCL_OK;
}

#ifndef SQLITE_OMIT_VIRTUALTABLE
/*
** Usage: create_null_module DB NAME
*/
static int SQLITE_TCLAPI test_create_null_module(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
2409
2410
2411
2412
2413
2414
2415

2416
2417
2418
2419
2420
2421
2422
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  zName = Tcl_GetString(objv[2]);

  sqlite3_create_module(db, zName, 0, 0);
  return TCL_OK;
}


#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Usage: sqlite3_snapshot_get DB DBNAME
*/
static int SQLITE_TCLAPI test_snapshot_get(
  void * clientData,







>







2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  zName = Tcl_GetString(objv[2]);

  sqlite3_create_module(db, zName, 0, 0);
  return TCL_OK;
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */

#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Usage: sqlite3_snapshot_get DB DBNAME
*/
static int SQLITE_TCLAPI test_snapshot_get(
  void * clientData,
9001
9002
9003
9004
9005
9006
9007

9008

9009
9010
9011
9012
9013
9014
9015
     { "sqlite3_config_sorterref", test_config_sorterref,   0 },
     { "sqlite3_autovacuum_pages", test_autovacuum_pages,   0 },
     { "decode_hexdb",             test_decode_hexdb,       0 },
     { "test_write_db",            test_write_db,           0 },
     { "sqlite3_register_cksumvfs", test_register_cksumvfs,  0 },
     { "sqlite3_unregister_cksumvfs", test_unregister_cksumvfs,  0 },
     { "number_of_cores",             guess_number_of_cores,     0 },

     { "create_null_module",       test_create_null_module,     0 },

  };
  static int bitmask_size = sizeof(Bitmask)*8;
  static int longdouble_size = sizeof(LONGDOUBLE_TYPE);
  int i;
  extern int sqlite3_sync_count, sqlite3_fullsync_count;
  extern int sqlite3_opentemp_count;
  extern int sqlite3_like_count;







>

>







9003
9004
9005
9006
9007
9008
9009
9010
9011
9012
9013
9014
9015
9016
9017
9018
9019
     { "sqlite3_config_sorterref", test_config_sorterref,   0 },
     { "sqlite3_autovacuum_pages", test_autovacuum_pages,   0 },
     { "decode_hexdb",             test_decode_hexdb,       0 },
     { "test_write_db",            test_write_db,           0 },
     { "sqlite3_register_cksumvfs", test_register_cksumvfs,  0 },
     { "sqlite3_unregister_cksumvfs", test_unregister_cksumvfs,  0 },
     { "number_of_cores",             guess_number_of_cores,     0 },
#ifndef SQLITE_OMIT_VIRTUALTABLE
     { "create_null_module",       test_create_null_module,     0 },
#endif
  };
  static int bitmask_size = sizeof(Bitmask)*8;
  static int longdouble_size = sizeof(LONGDOUBLE_TYPE);
  int i;
  extern int sqlite3_sync_count, sqlite3_fullsync_count;
  extern int sqlite3_opentemp_count;
  extern int sqlite3_like_count;
Changes to src/util.c.
666
667
668
669
670
671
672
673

674
675
676


677
678
679
680
681
682
683
684
685
686
  if( v<0 ){
    x = (v==SMALLEST_INT64) ? ((u64)1)<<63 : (u64)-v;
  }else{
    x = v;
  }
  i = sizeof(zTemp)-2;
  zTemp[sizeof(zTemp)-1] = 0;
  do{

    zTemp[i--] = (x%10) + '0';
    x = x/10;
  }while( x );


  if( v<0 ) zTemp[i--] = '-';
  memcpy(zOut, &zTemp[i+1], sizeof(zTemp)-1-i);
  return sizeof(zTemp)-2-i;
}

/*
** Compare the 19-character string zNum against the text representation
** value 2^63:  9223372036854775808.  Return negative, zero, or positive
** if zNum is less than, equal to, or greater than the string.
** Note that zNum must contain exactly 19 characters.







<
>
|

|
>
>
|
|
|







666
667
668
669
670
671
672

673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
  if( v<0 ){
    x = (v==SMALLEST_INT64) ? ((u64)1)<<63 : (u64)-v;
  }else{
    x = v;
  }
  i = sizeof(zTemp)-2;
  zTemp[sizeof(zTemp)-1] = 0;

  while( 1 /*exit-by-break*/ ){
    zTemp[i] = (x%10) + '0';
    x = x/10;
    if( x==0 ) break;
    i--;
  };
  if( v<0 ) zTemp[--i] = '-';
  memcpy(zOut, &zTemp[i], sizeof(zTemp)-i);
  return sizeof(zTemp)-1-i;
}

/*
** Compare the 19-character string zNum against the text representation
** value 2^63:  9223372036854775808.  Return negative, zero, or positive
** if zNum is less than, equal to, or greater than the string.
** Note that zNum must contain exactly 19 characters.
Changes to src/wal.c.
742
743
744
745
746
747
748

749
750
751
752
753
754
755
756
757
758
759
760
























761

762
763
764
765
766
767
768
  }else{
    s1 = s2 = 0;
  }

  assert( nByte>=8 );
  assert( (nByte&0x00000007)==0 );
  assert( nByte<=65536 );


  if( nativeCksum ){
    do {
      s1 += *aData++ + s2;
      s2 += *aData++ + s1;
    }while( aData<aEnd );
  }else{
    do {
      s1 += BYTESWAP32(aData[0]) + s2;
      s2 += BYTESWAP32(aData[1]) + s1;
      aData += 2;
    }while( aData<aEnd );
























  }


  aOut[0] = s1;
  aOut[1] = s2;
}

/*
** If there is the possibility of concurrent access to the SHM file







>

|
<
<
<
<
<





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>







742
743
744
745
746
747
748
749
750
751





752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
  }else{
    s1 = s2 = 0;
  }

  assert( nByte>=8 );
  assert( (nByte&0x00000007)==0 );
  assert( nByte<=65536 );
  assert( nByte%4==0 );

  if( !nativeCksum ){





    do {
      s1 += BYTESWAP32(aData[0]) + s2;
      s2 += BYTESWAP32(aData[1]) + s1;
      aData += 2;
    }while( aData<aEnd );
  }else if( nByte%64==0 ){
    do {
      s1 += *aData++ + s2;
      s2 += *aData++ + s1;
      s1 += *aData++ + s2;
      s2 += *aData++ + s1;
      s1 += *aData++ + s2;
      s2 += *aData++ + s1;
      s1 += *aData++ + s2;
      s2 += *aData++ + s1;
      s1 += *aData++ + s2;
      s2 += *aData++ + s1;
      s1 += *aData++ + s2;
      s2 += *aData++ + s1;
      s1 += *aData++ + s2;
      s2 += *aData++ + s1;
      s1 += *aData++ + s2;
      s2 += *aData++ + s1;
    }while( aData<aEnd );
  }else{
    do {
      s1 += *aData++ + s2;
      s2 += *aData++ + s1;
    }while( aData<aEnd );
  }
  assert( aData==aEnd );

  aOut[0] = s1;
  aOut[1] = s2;
}

/*
** If there is the possibility of concurrent access to the SHM file
Changes to src/window.c.
781
782
783
784
785
786
787

788
789
790
791
792
793
794
            assert( pWin->pOwner==pExpr );
            return WRC_Prune;
          }
        }
      }
      /* no break */ deliberate_fall_through


    case TK_AGG_FUNCTION:
    case TK_COLUMN: {
      int iCol = -1;
      if( pParse->db->mallocFailed ) return WRC_Abort;
      if( p->pSub ){
        int i;
        for(i=0; i<p->pSub->nExpr; i++){







>







781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
            assert( pWin->pOwner==pExpr );
            return WRC_Prune;
          }
        }
      }
      /* no break */ deliberate_fall_through

    case TK_IF_NULL_ROW:
    case TK_AGG_FUNCTION:
    case TK_COLUMN: {
      int iCol = -1;
      if( pParse->db->mallocFailed ) return WRC_Abort;
      if( p->pSub ){
        int i;
        for(i=0; i<p->pSub->nExpr; i++){
Changes to test/altertab.test.
976
977
978
979
980
981
982


















983
984
do_catchsql_test 32.0 {
  CREATE TABLE t1(x);
  CREATE TRIGGER r1 BEFORE INSERT ON t1 BEGIN 
    UPDATE t1 SET x=x FROM (SELECT*);
  END;
  ALTER TABLE t1 RENAME TO x;
} {1 {error in trigger r1: no tables specified}}



















finish_test







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
do_catchsql_test 32.0 {
  CREATE TABLE t1(x);
  CREATE TRIGGER r1 BEFORE INSERT ON t1 BEGIN 
    UPDATE t1 SET x=x FROM (SELECT*);
  END;
  ALTER TABLE t1 RENAME TO x;
} {1 {error in trigger r1: no tables specified}}

# 2023-04-13 https://sqlite.org/forum/forumpost/ff3840145a
#
reset_db
do_execsql_test 33.0 {
  CREATE TABLE t1(a TEXT);
  INSERT INTO t1(a) VALUES('abc'),('def'),(NULL);
  CREATE TABLE t2(b TEXT);
  CREATE TRIGGER r3 AFTER INSERT ON t1 BEGIN
   UPDATE t2 SET (b,a)=(SELECT 1) FROM t1 JOIN t2 ON (SELECT * FROM (SELECT a));
  END;
}
do_catchsql_test 33.1 {
  ALTER TABLE t1 RENAME COLUMN a TO b;
} {1 {error in trigger r3 after rename: no such column: a}}
do_execsql_test 33.2 {
  SELECT quote(a) FROM t1 ORDER BY +a;
} {NULL 'abc' 'def'}

finish_test
Changes to test/fkey1.test.
267
268
269
270
271
272
273











274
275
  CREATE TABLE sqlite_stat1(tbl INTEGER PRIMARY KEY DESC, idx UNIQUE DEFAULT NULL) WITHOUT ROWID;
  UPDATE sqlite_schema SET name='sqlite_autoindex_sqlite_stat1_1' WHERE name='sqlite_autoindex_sqlite_stat1_2';
  PRAGMA writable_schema=RESET;
} {}
do_catchsql_test 8.3 {
  REINDEX;
} {1 {database disk image is malformed}}












finish_test







>
>
>
>
>
>
>
>
>
>
>


267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
  CREATE TABLE sqlite_stat1(tbl INTEGER PRIMARY KEY DESC, idx UNIQUE DEFAULT NULL) WITHOUT ROWID;
  UPDATE sqlite_schema SET name='sqlite_autoindex_sqlite_stat1_1' WHERE name='sqlite_autoindex_sqlite_stat1_2';
  PRAGMA writable_schema=RESET;
} {}
do_catchsql_test 8.3 {
  REINDEX;
} {1 {database disk image is malformed}}

# 2023-04-13 https://bugs.chromium.org/p/chromium/issues/detail?id=1405220
# Avoid double-de-quoting of table names when processing foreign keys.
#
reset_db
do_execsql_test 9.1 {
  PRAGMA foreign_keys = ON;
  CREATE TABLE """1"("""2", """3" PRIMARY KEY);
  CREATE TABLE """4"("""5" REFERENCES """1" ON DELETE RESTRICT);
  DELETE FROM """1";
}

finish_test
Changes to test/window1.test.
2325
2326
2327
2328
2329
2330
2331

































2332
2333
  CREATE INDEX t1x ON t1(a+b);
}
do_catchsql_test 75.1 {
  SELECT count((SELECT count(a0.a+a0.b) ORDER BY sum(0) OVER (PARTITION BY 0)))
    FROM t1 AS a0 JOIN t1 AS a1
   GROUP BY a1.a;
} {1 {misuse of aggregate: count()}}


































finish_test







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
  CREATE INDEX t1x ON t1(a+b);
}
do_catchsql_test 75.1 {
  SELECT count((SELECT count(a0.a+a0.b) ORDER BY sum(0) OVER (PARTITION BY 0)))
    FROM t1 AS a0 JOIN t1 AS a1
   GROUP BY a1.a;
} {1 {misuse of aggregate: count()}}

# 2023-04-13 https://sqlite.org/forum/forumpost/0d48347967
reset_db
do_execsql_test 76.0 {
  CREATE TABLE t1(a INT, b INT);
  INSERT INTO t1(a,b) VALUES (111,222),(111,223),(118,229);
  CREATE INDEX t1a ON t1(a);
  CREATE TABLE t2(x INT);
  INSERT INTO t2 VALUES (333),(444),(555);
}
do_execsql_test 76.1 {
  SELECT c, (SELECT c + sum(1) OVER ()) AS "res"
    FROM t2 LEFT JOIN (SELECT +a AS c FROM t1) AS v1 ON true
   GROUP BY c
   ORDER by c;
} {111 112 118 119}
# ^^^^^^^^^^^^^^^^^-- results verified against PG 14.2

do_execsql_test 76.2 {
  CREATE TABLE t3(x);
  CREATE TABLE t4(y);
  INSERT INTO t3 VALUES(100), (200), (400);
  INSERT INTO t4 VALUES(100), (300), (400);
}
do_execsql_test 76.3 {
  SELECT (SELECT y+sum(0) OVER ()) FROM t3 LEFT JOIN t4 ON x=y;
} {100 {} 400}
do_execsql_test 76.4 {
  SELECT (SELECT y+sum(0) OVER ()) FROM t3 LEFT JOIN t4 ON x=y GROUP BY x;
} {100 {} 400}
do_execsql_test 76.5 {
  SELECT (SELECT max(y)+sum(0) OVER ()) FROM t3 LEFT JOIN t4 ON x=y GROUP BY x;
} {100 {} 400}

finish_test