SQLite

Check-in [70e42f941c]
Login

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

Overview
Comment:Fix a problem with fts5 locale=1 tables and UPDATE statements that may affect more than one row.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 70e42f941c0778a04b82655409c7caf4c1039589f7e43a8ec1e736ea8f931b26
User & Date: dan 2024-09-07 16:22:22.943
Context
2024-09-09
15:12
Fix harmless compiler warnings in FTS5. (check-in: aa75e701de user: drh tags: trunk)
14:50
Move the vfstrace extension out of src/ over into ext/misc/ where it belongs. Make it part of the standard build for the CLI. Bring some of the vfstrace output up-to-date. (check-in: 055b97de8d user: drh tags: vfstrace)
2024-09-07
16:22
Fix a problem with fts5 locale=1 tables and UPDATE statements that may affect more than one row. (check-in: 70e42f941c user: dan tags: trunk)
16:04
Fix an off-by-one error in the routines that bind the special $test_TTT and $int_NNN parameters for fuzz testing. Fix to testing logic only - no changes to the SQLite core. (check-in: 6206b90a4e user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to ext/fts5/fts5Int.h.
642
643
644
645
646
647
648


649
650
651
652
653
654
655
  int bContent,                   /* Loaded from content table */
  int *pbResetTokenizer,          /* OUT: True if ClearLocale() required */
  const char **ppText,            /* OUT: Pointer to text buffer */
  int *pnText                     /* OUT: Size of (*ppText) in bytes */
);

void sqlite3Fts5ClearLocale(Fts5Config *pConfig);



/*
** End of interface to code in fts5.c.
**************************************************************************/

/**************************************************************************
** Interface to code in fts5_hash.c. 







>
>







642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
  int bContent,                   /* Loaded from content table */
  int *pbResetTokenizer,          /* OUT: True if ClearLocale() required */
  const char **ppText,            /* OUT: Pointer to text buffer */
  int *pnText                     /* OUT: Size of (*ppText) in bytes */
);

void sqlite3Fts5ClearLocale(Fts5Config *pConfig);

int sqlite3Fts5IsLocaleValue(Fts5Config *pConfig, sqlite3_value *pVal);

/*
** End of interface to code in fts5.c.
**************************************************************************/

/**************************************************************************
** Interface to code in fts5_hash.c. 
Changes to ext/fts5/fts5_main.c.
79
80
81
82
83
84
85

86








87
88
89
90
91
92
93
  fts5_api api;                   /* User visible part of object (see fts5.h) */
  sqlite3 *db;                    /* Associated database connection */ 
  i64 iNextId;                    /* Used to allocate unique cursor ids */
  Fts5Auxiliary *pAux;            /* First in list of all aux. functions */
  Fts5TokenizerModule *pTok;      /* First in list of all tokenizer modules */
  Fts5TokenizerModule *pDfltTok;  /* Default tokenizer module */
  Fts5Cursor *pCsr;               /* First in list of all open cursors */

};









/*
** Each auxiliary function registered with the FTS5 module is represented
** by an object of the following type. All such objects are stored as part
** of the Fts5Global.pAux list.
*/
struct Fts5Auxiliary {







>

>
>
>
>
>
>
>
>







79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
  fts5_api api;                   /* User visible part of object (see fts5.h) */
  sqlite3 *db;                    /* Associated database connection */ 
  i64 iNextId;                    /* Used to allocate unique cursor ids */
  Fts5Auxiliary *pAux;            /* First in list of all aux. functions */
  Fts5TokenizerModule *pTok;      /* First in list of all tokenizer modules */
  Fts5TokenizerModule *pDfltTok;  /* Default tokenizer module */
  Fts5Cursor *pCsr;               /* First in list of all open cursors */
  u32 aLocaleHdr[4];
};

/*
** Size of header on fts5_locale() values. And macro to access a buffer
** containing a copy of the header from an Fts5Config pointer.
*/
#define FTS5_LOCALE_HDR_SIZE sizeof( ((Fts5Global*)0)->aLocaleHdr )
#define FTS5_LOCALE_HDR(pConfig) ((const u8*)(pConfig->pGlobal->aLocaleHdr))


/*
** Each auxiliary function registered with the FTS5 module is represented
** by an object of the following type. All such objects are stored as part
** of the Fts5Global.pAux list.
*/
struct Fts5Auxiliary {
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#define FTS5CSR_FREE_ZRANK        0x10
#define FTS5CSR_REQUIRE_RESEEK    0x20
#define FTS5CSR_REQUIRE_POSLIST   0x40

#define BitFlagAllTest(x,y) (((x) & (y))==(y))
#define BitFlagTest(x,y)    (((x) & (y))!=0)

/*
** The subtype value and header bytes used by fts5_locale().
*/
#define FTS5_LOCALE_SUBTYPE ((unsigned int)'L')
#define FTS5_LOCALE_HEADER  "\x00\xE0\xB2\xEB"


/*
** Macros to Set(), Clear() and Test() cursor flags.
*/
#define CsrFlagSet(pCsr, flag)   ((pCsr)->csrflags |= (flag))
#define CsrFlagClear(pCsr, flag) ((pCsr)->csrflags &= ~(flag))
#define CsrFlagTest(pCsr, flag)  ((pCsr)->csrflags & (flag))







<
<
<
<
<
<







252
253
254
255
256
257
258






259
260
261
262
263
264
265
#define FTS5CSR_FREE_ZRANK        0x10
#define FTS5CSR_REQUIRE_RESEEK    0x20
#define FTS5CSR_REQUIRE_POSLIST   0x40

#define BitFlagAllTest(x,y) (((x) & (y))==(y))
#define BitFlagTest(x,y)    (((x) & (y))!=0)








/*
** Macros to Set(), Clear() and Test() cursor flags.
*/
#define CsrFlagSet(pCsr, flag)   ((pCsr)->csrflags |= (flag))
#define CsrFlagClear(pCsr, flag) ((pCsr)->csrflags &= ~(flag))
#define CsrFlagTest(pCsr, flag)  ((pCsr)->csrflags & (flag))
1269
1270
1271
1272
1273
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
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
/*
** Clear any locale configured by an earlier call to fts5SetLocale() or
** sqlite3Fts5ExtractText().
*/
void sqlite3Fts5ClearLocale(Fts5Config *pConfig){
  fts5SetLocale(pConfig, 0, 0);
}

















/*
** This function is used to extract utf-8 text from an sqlite3_value. This
** is usually done in order to tokenize it. For example, when:
**
**     * a value is written to an fts5 table,
**     * a value is deleted from an FTS5_CONTENT_NORMAL table,
**     * a value containing a query expression is passed to xFilter()
**
** and so on.
**
** This function handles 2 cases:
**
**   1) Ordinary values. The text can be extracted from these using
**      sqlite3_value_text().
**
**   2) Combination text/locale blobs created by fts5_locale(). There
**      are several cases for these:
**
**        * Blobs tagged with FTS5_LOCALE_SUBTYPE.
**        * Blobs read from the content table of a locale=1 external-content
**          table, and 
**        * Blobs read from the content table of a locale=1 regular 
**          content table.
**
**      The first two cases above should have the 4 byte FTS5_LOCALE_HEADER
**      header. It is an error if a blob with the subtype or a blob read
**      from the content table of an external content table does not have
**      the required header. A blob read from the content table of a regular
**      locale=1 table does not have the header. This is to save space.
**
** If successful, SQLITE_OK is returned and output parameters (*ppText)
** and (*pnText) are set to point to a buffer containing the extracted utf-8 
** text and its length in bytes, respectively. The buffer is not 
** nul-terminated. It has the same lifetime as the sqlite3_value object
** from which it is extracted.
**







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



















|
<
<



|
|
|
|
|







1272
1273
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
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314


1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
/*
** Clear any locale configured by an earlier call to fts5SetLocale() or
** sqlite3Fts5ExtractText().
*/
void sqlite3Fts5ClearLocale(Fts5Config *pConfig){
  fts5SetLocale(pConfig, 0, 0);
}

/*
** Return true if the value passed as the only argument is an 
** fts5_locale() value.  
*/
int sqlite3Fts5IsLocaleValue(Fts5Config *pConfig, sqlite3_value *pVal){
  int ret = 0;
  if( sqlite3_value_type(pVal)==SQLITE_BLOB ){
    if( sqlite3_value_bytes(pVal)>FTS5_LOCALE_HDR_SIZE
     && 0==memcmp(sqlite3_value_blob(pVal), FTS5_LOCALE_HDR(pConfig), 4)
    ){
      ret = 1;
    }
  }
  return ret;
}

/*
** This function is used to extract utf-8 text from an sqlite3_value. This
** is usually done in order to tokenize it. For example, when:
**
**     * a value is written to an fts5 table,
**     * a value is deleted from an FTS5_CONTENT_NORMAL table,
**     * a value containing a query expression is passed to xFilter()
**
** and so on.
**
** This function handles 2 cases:
**
**   1) Ordinary values. The text can be extracted from these using
**      sqlite3_value_text().
**
**   2) Combination text/locale blobs created by fts5_locale(). There
**      are several cases for these:
**
**        * Blobs that have the 16-byte header, and


**        * Blobs read from the content table of a locale=1 regular 
**          content table.
**
**      The first case above has the 16 byte FTS5_LOCALE_HDR(pConfig)
**      header. It is an error if a blob read from the content table of 
**      an external content table does not have the required header. A blob 
**      read from the content table of a regular locale=1 table does not 
**      have the header. This is to save space.
**
** If successful, SQLITE_OK is returned and output parameters (*ppText)
** and (*pnText) are set to point to a buffer containing the extracted utf-8 
** text and its length in bytes, respectively. The buffer is not 
** nul-terminated. It has the same lifetime as the sqlite3_value object
** from which it is extracted.
**
1326
1327
1328
1329
1330
1331
1332





1333
1334
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
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
  int *pbResetTokenizer,          /* OUT: True if xSetLocale(NULL) required */
  const char **ppText,            /* OUT: Pointer to text buffer */
  int *pnText                     /* OUT: Size of (*ppText) in bytes */
){
  const char *pText = 0;
  int nText = 0;
  int rc = SQLITE_OK;





  int bDecodeBlob = 0;

  assert( pbResetTokenizer==0 || *pbResetTokenizer==0 );
  assert( bContent==0 || pConfig->eContent!=FTS5_CONTENT_NONE );
  assert( bContent==0 || sqlite3_value_subtype(pVal)==0 );

  if( sqlite3_value_type(pVal)==SQLITE_BLOB ){
    if( sqlite3_value_subtype(pVal)==FTS5_LOCALE_SUBTYPE 
     || (bContent && pConfig->bLocale)

    ){


      bDecodeBlob = 1;


    }
  }

  if( bDecodeBlob ){
    const int SZHDR = sizeof(FTS5_LOCALE_HEADER)-1;
    const u8 *pBlob = sqlite3_value_blob(pVal);
    int nBlob = sqlite3_value_bytes(pVal);


    /* Unless this blob was read from the %_content table of an 
    ** FTS5_CONTENT_NORMAL table, it should have the 4 byte fts5_locale() 
    ** header. Check for this. If it is not found, return an error.  */
    if( (!bContent || pConfig->eContent!=FTS5_CONTENT_NORMAL) ){
      if( nBlob<SZHDR || memcmp(FTS5_LOCALE_HEADER, pBlob, SZHDR) ){
        rc = SQLITE_ERROR;
      }else{
        pBlob += 4;
        nBlob -= 4;
      }
    }

    if( rc==SQLITE_OK ){
      int nLocale = 0;

      for(nLocale=0; nLocale<nBlob; nLocale++){
        if( pBlob[nLocale]==0x00 ) break;
      }
      if( nLocale==nBlob || nLocale==0 ){
        rc = SQLITE_ERROR;
      }else{
        pText = (const char*)&pBlob[nLocale+1];
        nText = nBlob-nLocale-1;

        if( pbResetTokenizer ){
          fts5SetLocale(pConfig, (const char*)pBlob, nLocale);
          *pbResetTokenizer = 1;
        }
      }
    }

  }else{
    pText = (const char*)sqlite3_value_text(pVal);
    nText = sqlite3_value_bytes(pVal);
  }







>
>
>
>
>
|



<


|
|
>

>
>
|
>
>



|
<


>




|
<
<
<
|
|
|
|
<
<
<
<
|
|
|
|
|
|
|
|

|
|
|
<







1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358

1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373

1374
1375
1376
1377
1378
1379
1380
1381



1382
1383
1384
1385




1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397

1398
1399
1400
1401
1402
1403
1404
  int *pbResetTokenizer,          /* OUT: True if xSetLocale(NULL) required */
  const char **ppText,            /* OUT: Pointer to text buffer */
  int *pnText                     /* OUT: Size of (*ppText) in bytes */
){
  const char *pText = 0;
  int nText = 0;
  int rc = SQLITE_OK;

  /* 0: Do not decode blob 
  ** 1: Decode blob, expect fts5_locale() header
  ** 2: Decode blob, expect no fts5_locale() header
  */
  int eDecodeBlob = 0;

  assert( pbResetTokenizer==0 || *pbResetTokenizer==0 );
  assert( bContent==0 || pConfig->eContent!=FTS5_CONTENT_NONE );


  if( sqlite3_value_type(pVal)==SQLITE_BLOB ){
    if( bContent 
     && pConfig->bLocale 
     && pConfig->eContent==FTS5_CONTENT_NORMAL 
    ){
      eDecodeBlob = 2;
    }else if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){
      eDecodeBlob = 1;
    }else if( bContent && pConfig->bLocale ){
      return SQLITE_ERROR;
    }
  }

  if( eDecodeBlob ){

    const u8 *pBlob = sqlite3_value_blob(pVal);
    int nBlob = sqlite3_value_bytes(pVal);
    int nLocale = 0;

    /* Unless this blob was read from the %_content table of an 
    ** FTS5_CONTENT_NORMAL table, it should have the 4 byte fts5_locale() 
    ** header. Check for this. If it is not found, return an error.  */
    if( eDecodeBlob==1 ){



      pBlob += FTS5_LOCALE_HDR_SIZE;
      nBlob -= FTS5_LOCALE_HDR_SIZE;
    }





    for(nLocale=0; nLocale<nBlob; nLocale++){
      if( pBlob[nLocale]==0x00 ) break;
    }
    if( nLocale==nBlob || nLocale==0 ){
      rc = SQLITE_ERROR;
    }else{
      pText = (const char*)&pBlob[nLocale+1];
      nText = nBlob-nLocale-1;

      if( pbResetTokenizer ){
        fts5SetLocale(pConfig, (const char*)pBlob, nLocale);
        *pbResetTokenizer = 1;

      }
    }

  }else{
    pText = (const char*)sqlite3_value_text(pVal);
    nText = sqlite3_value_bytes(pVal);
  }
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973

1974
1975
1976
1977
1978
1979
1980


1981



1982
1983

1984
1985
1986
1987
1988
1989
1990
      bUpdateOrDelete = 1;
    }

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

      /* Ensure that no fts5_locale() values are written to locale=0 tables.
      ** And that no blobs except fts5_locale() blobs are written to indexed
      ** (i.e. not UNINDEXED) columns of locale=1 tables. */
      int ii;
      for(ii=0; ii<pConfig->nCol; ii++){

        if( sqlite3_value_type(apVal[ii+2])==SQLITE_BLOB ){
          int bSub = (sqlite3_value_subtype(apVal[ii+2])==FTS5_LOCALE_SUBTYPE);
          if( (pConfig->bLocale && !bSub && pConfig->abUnindexed[ii]==0) 
           || (pConfig->bLocale==0 && bSub)
          ){
            if( pConfig->bLocale==0 ){
              fts5SetVtabError(pTab, "fts5_locale() requires locale=1");


            }



            rc = SQLITE_MISMATCH;
            goto update_out;

          }
        }
      }

      if( eType0!=SQLITE_INTEGER ){
        /* An INSERT statement. If the conflict-mode is REPLACE, first remove
        ** the current entry (if any). */







<
<
<


>
|
<
<
|
<
|
|
>
>

>
>
>
|
|
>







1980
1981
1982
1983
1984
1985
1986



1987
1988
1989
1990


1991

1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
      bUpdateOrDelete = 1;
    }

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




      int ii;
      for(ii=0; ii<pConfig->nCol; ii++){
        sqlite3_value *pVal = apVal[ii+2];
        if( sqlite3_value_type(pVal)==SQLITE_BLOB ){


          int isLocale = sqlite3Fts5IsLocaleValue(pConfig, pVal);

          if( pConfig->bLocale ){
            if( isLocale==0 && pConfig->abUnindexed[ii]==0 ){
              rc = SQLITE_MISMATCH;
              goto update_out;
            }
          }else{
            if( isLocale ){
              fts5SetVtabError(pTab, "fts5_locale() requires locale=1");
              rc = SQLITE_MISMATCH;
              goto update_out;
            }
          }
        }
      }

      if( eType0!=SQLITE_INTEGER ){
        /* An INSERT statement. If the conflict-mode is REPLACE, first remove
        ** the current entry (if any). */
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
   && pConfig->bLocale
  ){
    rc = fts5SeekCursor(pCsr, 0);
    if( rc==SQLITE_OK ){
      /* Load the value into pVal. pVal is a locale/text pair iff:
      **
      **   1) It is an SQLITE_BLOB, and
      **   2) Either the subtype is FTS5_LOCALE_SUBTYPE, or else the
      **      value was loaded from an FTS5_CONTENT_NORMAL table, and
      **   3) It does not begin with an 0x00 byte.

      */ 
      sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, iCol+1);
      if( sqlite3_value_type(pVal)==SQLITE_BLOB ){
        const u8 *pBlob = (const u8*)sqlite3_value_blob(pVal);
        int nBlob = sqlite3_value_bytes(pVal);
        if( pConfig->eContent==FTS5_CONTENT_EXTERNAL ){
          const int SZHDR = sizeof(FTS5_LOCALE_HEADER)-1;
          if( nBlob<SZHDR || memcmp(FTS5_LOCALE_HEADER, pBlob, SZHDR) ){
            rc = SQLITE_ERROR;
          }
          pBlob += 4;
          nBlob -= 4;
        }
        if( rc==SQLITE_OK ){
          int nLocale = 0;
          for(nLocale=0; nLocale<nBlob && pBlob[nLocale]!=0x00; nLocale++);
          if( nLocale==nBlob || nLocale==0 ){
            rc = SQLITE_ERROR;
          }else{







|
|
|
>






|
<


|
|







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
   && pConfig->bLocale
  ){
    rc = fts5SeekCursor(pCsr, 0);
    if( rc==SQLITE_OK ){
      /* Load the value into pVal. pVal is a locale/text pair iff:
      **
      **   1) It is an SQLITE_BLOB, and
      **   2) Either the FTS5_LOCALE_HDR header is present, or else the
      **      value was loaded from an FTS5_CONTENT_NORMAL table.
      **
      ** If condition (1) is met but condition (2) is not, it is an error.
      */ 
      sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, iCol+1);
      if( sqlite3_value_type(pVal)==SQLITE_BLOB ){
        const u8 *pBlob = (const u8*)sqlite3_value_blob(pVal);
        int nBlob = sqlite3_value_bytes(pVal);
        if( pConfig->eContent==FTS5_CONTENT_EXTERNAL ){
          if( sqlite3Fts5IsLocaleValue(pConfig, pVal)==0 ){

            rc = SQLITE_ERROR;
          }
          pBlob += FTS5_LOCALE_HDR_SIZE;
          nBlob -= FTS5_LOCALE_HDR_SIZE;
        }
        if( rc==SQLITE_OK ){
          int nLocale = 0;
          for(nLocale=0; nLocale<nBlob && pBlob[nLocale]!=0x00; nLocale++);
          if( nLocale==nBlob || nLocale==0 ){
            rc = SQLITE_ERROR;
          }else{
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995

2996

2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
){
  assert( pConfig->eContent!=FTS5_CONTENT_NONE );

  if( pConfig->bLocale 
   && sqlite3_value_type(pVal)==SQLITE_BLOB 
   && pConfig->abUnindexed[iCol]==0
  ){
    const int SZHDR = sizeof(FTS5_LOCALE_HEADER)-1;
    const u8 *pBlob = sqlite3_value_blob(pVal);
    int nBlob = sqlite3_value_bytes(pVal);
    int ii;

    if( pConfig->eContent==FTS5_CONTENT_EXTERNAL ){

      if( nBlob<SZHDR || memcmp(pBlob, FTS5_LOCALE_HEADER, SZHDR) ){

        sqlite3_result_error_code(pCtx, SQLITE_ERROR);
        return;
      }else{
        pBlob += 4;
        nBlob -= 4;
      }
    }

    for(ii=0; ii<nBlob && pBlob[ii]; ii++);
    if( ii==0 || ii==nBlob ){
      sqlite3_result_error_code(pCtx, SQLITE_ERROR);
    }else{







<





>
|
>



|
|







3002
3003
3004
3005
3006
3007
3008

3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
){
  assert( pConfig->eContent!=FTS5_CONTENT_NONE );

  if( pConfig->bLocale 
   && sqlite3_value_type(pVal)==SQLITE_BLOB 
   && pConfig->abUnindexed[iCol]==0
  ){

    const u8 *pBlob = sqlite3_value_blob(pVal);
    int nBlob = sqlite3_value_bytes(pVal);
    int ii;

    if( pConfig->eContent==FTS5_CONTENT_EXTERNAL ){
      if( nBlob<FTS5_LOCALE_HDR_SIZE 
       || memcmp(pBlob, FTS5_LOCALE_HDR(pConfig), FTS5_LOCALE_HDR_SIZE) 
      ){
        sqlite3_result_error_code(pCtx, SQLITE_ERROR);
        return;
      }else{
        pBlob += FTS5_LOCALE_HDR_SIZE;
        nBlob -= FTS5_LOCALE_HDR_SIZE;
      }
    }

    for(ii=0; ii<nBlob && pBlob[ii]; ii++);
    if( ii==0 || ii==nBlob ){
      sqlite3_result_error_code(pCtx, SQLITE_ERROR);
    }else{
3630
3631
3632
3633
3634
3635
3636

3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667

  zText = (const char*)sqlite3_value_text(apArg[1]);
  nText = sqlite3_value_bytes(apArg[1]);

  if( zLocale==0 || zLocale[0]=='\0' ){
    sqlite3_result_text(pCtx, zText, nText, SQLITE_TRANSIENT);
  }else{

    u8 *pBlob = 0;
    u8 *pCsr = 0;
    int nBlob = 0;
    const int nHdr = 4;
    assert( sizeof(FTS5_LOCALE_HEADER)==nHdr+1 );

    nBlob = nHdr + nLocale + 1 + nText;
    pBlob = (u8*)sqlite3_malloc(nBlob);
    if( pBlob==0 ){
      sqlite3_result_error_nomem(pCtx);
      return;
    }

    pCsr = pBlob;
    memcpy(pCsr, FTS5_LOCALE_HEADER, nHdr);
    pCsr += nHdr;
    memcpy(pCsr, zLocale, nLocale);
    pCsr += nLocale;
    (*pCsr++) = 0x00;
    if( zText ) memcpy(pCsr, zText, nText);
    assert( &pCsr[nText]==&pBlob[nBlob] );

    sqlite3_result_blob(pCtx, pBlob, nBlob, sqlite3_free);
    sqlite3_result_subtype(pCtx, FTS5_LOCALE_SUBTYPE);
  }
}

/*
** Return true if zName is the extension on one of the shadow tables used
** by this module.
*/







>



<
<

|







|
|







<







3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660


3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678

3679
3680
3681
3682
3683
3684
3685

  zText = (const char*)sqlite3_value_text(apArg[1]);
  nText = sqlite3_value_bytes(apArg[1]);

  if( zLocale==0 || zLocale[0]=='\0' ){
    sqlite3_result_text(pCtx, zText, nText, SQLITE_TRANSIENT);
  }else{
    Fts5Global *p = (Fts5Global*)sqlite3_user_data(pCtx);
    u8 *pBlob = 0;
    u8 *pCsr = 0;
    int nBlob = 0;



    nBlob = FTS5_LOCALE_HDR_SIZE + nLocale + 1 + nText;
    pBlob = (u8*)sqlite3_malloc(nBlob);
    if( pBlob==0 ){
      sqlite3_result_error_nomem(pCtx);
      return;
    }

    pCsr = pBlob;
    memcpy(pCsr, (const u8*)p->aLocaleHdr, FTS5_LOCALE_HDR_SIZE);
    pCsr += FTS5_LOCALE_HDR_SIZE;
    memcpy(pCsr, zLocale, nLocale);
    pCsr += nLocale;
    (*pCsr++) = 0x00;
    if( zText ) memcpy(pCsr, zText, nText);
    assert( &pCsr[nText]==&pBlob[nBlob] );

    sqlite3_result_blob(pCtx, pBlob, nBlob, sqlite3_free);

  }
}

/*
** Return true if zName is the extension on one of the shadow tables used
** by this module.
*/
3755
3756
3757
3758
3759
3760
3761










3762
3763
3764
3765
3766
3767
3768
    pGlobal->db = db;
    pGlobal->api.iVersion = 3;
    pGlobal->api.xCreateFunction = fts5CreateAux;
    pGlobal->api.xCreateTokenizer = fts5CreateTokenizer;
    pGlobal->api.xFindTokenizer = fts5FindTokenizer;
    pGlobal->api.xCreateTokenizer_v2 = fts5CreateTokenizer_v2;
    pGlobal->api.xFindTokenizer_v2 = fts5FindTokenizer_v2;










    rc = sqlite3_create_module_v2(db, "fts5", &fts5Mod, p, fts5ModuleDestroy);
    if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db);
    if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(pGlobal, db);
    if( rc==SQLITE_OK ) rc = sqlite3Fts5AuxInit(&pGlobal->api);
    if( rc==SQLITE_OK ) rc = sqlite3Fts5TokenizerInit(&pGlobal->api);
    if( rc==SQLITE_OK ) rc = sqlite3Fts5VocabInit(pGlobal, db);
    if( rc==SQLITE_OK ){







>
>
>
>
>
>
>
>
>
>







3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
    pGlobal->db = db;
    pGlobal->api.iVersion = 3;
    pGlobal->api.xCreateFunction = fts5CreateAux;
    pGlobal->api.xCreateTokenizer = fts5CreateTokenizer;
    pGlobal->api.xFindTokenizer = fts5FindTokenizer;
    pGlobal->api.xCreateTokenizer_v2 = fts5CreateTokenizer_v2;
    pGlobal->api.xFindTokenizer_v2 = fts5FindTokenizer_v2;

    /* Initialize pGlobal->aLocaleHdr[] to a 128-bit pseudo-random vector.
    ** The constants below were generated randomly.  */
    sqlite3_randomness(sizeof(pGlobal->aLocaleHdr), pGlobal->aLocaleHdr);
    pGlobal->aLocaleHdr[0] ^= 0xF924976D;
    pGlobal->aLocaleHdr[1] ^= 0x16596E13;
    pGlobal->aLocaleHdr[2] ^= 0x7C80BEAA;
    pGlobal->aLocaleHdr[3] ^= 0x9B03A67F;
    assert( sizeof(pGlobal->aLocaleHdr)==16 );

    rc = sqlite3_create_module_v2(db, "fts5", &fts5Mod, p, fts5ModuleDestroy);
    if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db);
    if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(pGlobal, db);
    if( rc==SQLITE_OK ) rc = sqlite3Fts5AuxInit(&pGlobal->api);
    if( rc==SQLITE_OK ) rc = sqlite3Fts5TokenizerInit(&pGlobal->api);
    if( rc==SQLITE_OK ) rc = sqlite3Fts5VocabInit(pGlobal, db);
    if( rc==SQLITE_OK ){
Changes to ext/fts5/fts5_storage.c.
887
888
889
890
891
892
893
894
895
896
897

898
899
900
901
902
903


904
905
906
907
908
909
910

911
912
913
914
915
916
917
    rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0);
    for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){
      sqlite3_value *pVal = apVal[i];
      if( sqlite3_value_nochange(pVal) && p->pSavedRow ){
        /* This is an UPDATE statement, and column (i-2) was not modified.
        ** Retrieve the value from Fts5Storage.pSavedRow instead. */
        pVal = sqlite3_column_value(p->pSavedRow, i-1);
      }else if( sqlite3_value_subtype(pVal)==FTS5_LOCALE_SUBTYPE ){
        assert( pConfig->bLocale );
        assert( i>1 );
        if( pConfig->abUnindexed[i-2] ){

          /* At attempt to insert an fts5_locale() value into an UNINDEXED
          ** column. Strip the locale away and just bind the text.  */
          const char *pText = 0;
          int nText = 0;
          rc = sqlite3Fts5ExtractText(pConfig, pVal, 0, 0, &pText, &nText);
          sqlite3_bind_text(pInsert, i, pText, nText, SQLITE_TRANSIENT);


        }else{
          const u8 *pBlob = (const u8*)sqlite3_value_blob(pVal);
          int nBlob = sqlite3_value_bytes(pVal);
          assert( nBlob>4 );
          sqlite3_bind_blob(pInsert, i, pBlob+4, nBlob-4, SQLITE_TRANSIENT);
        }
        continue;

      }

      rc = sqlite3_bind_value(pInsert, i, pVal);
    }
    if( rc==SQLITE_OK ){
      sqlite3_step(pInsert);
      rc = sqlite3_reset(pInsert);







|



>
|
|
|
|
|
|
>
>




|
<
|
>







887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911

912
913
914
915
916
917
918
919
920
    rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0);
    for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){
      sqlite3_value *pVal = apVal[i];
      if( sqlite3_value_nochange(pVal) && p->pSavedRow ){
        /* This is an UPDATE statement, and column (i-2) was not modified.
        ** Retrieve the value from Fts5Storage.pSavedRow instead. */
        pVal = sqlite3_column_value(p->pSavedRow, i-1);
      }else if( sqlite3_value_type(pVal)==SQLITE_BLOB && pConfig->bLocale ){
        assert( pConfig->bLocale );
        assert( i>1 );
        if( pConfig->abUnindexed[i-2] ){
          if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){
            /* At attempt to insert an fts5_locale() value into an UNINDEXED
            ** column. Strip the locale away and just bind the text.  */
            const char *pText = 0;
            int nText = 0;
            rc = sqlite3Fts5ExtractText(pConfig, pVal, 0, 0, &pText, &nText);
            sqlite3_bind_text(pInsert, i, pText, nText, SQLITE_TRANSIENT);
            continue;
          }
        }else{
          const u8 *pBlob = (const u8*)sqlite3_value_blob(pVal);
          int nBlob = sqlite3_value_bytes(pVal);
          assert( nBlob>4 );
          sqlite3_bind_blob(pInsert, i, pBlob+16, nBlob-16, SQLITE_TRANSIENT);

          continue;
        }
      }

      rc = sqlite3_bind_value(pInsert, i, pVal);
    }
    if( rc==SQLITE_OK ){
      sqlite3_step(pInsert);
      rc = sqlite3_reset(pInsert);
Changes to ext/fts5/test/fts5locale.test.
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
  
    do_catchsql_test 10.2.$tn.3 {
      INSERT INTO ft(ft) VALUES('rebuild');
    } {1 {SQL logic error}}
  
    do_catchsql_test 10.2.$tn.4 "
      SELECT * FROM ft( test_setsubtype($v, 76) );
    " {1 {SQL logic error}}
  
    do_execsql_test 10.2.$tn.5 {
      INSERT INTO ft(rowid, x) VALUES(1, 'hello world');
    }
  
    if {"%DETAIL%"!="full"} {
      do_catchsql_test 10.2.$tn.6 {







|







484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
  
    do_catchsql_test 10.2.$tn.3 {
      INSERT INTO ft(ft) VALUES('rebuild');
    } {1 {SQL logic error}}
  
    do_catchsql_test 10.2.$tn.4 "
      SELECT * FROM ft( test_setsubtype($v, 76) );
    " {1 {fts5: syntax error near ""}}
  
    do_execsql_test 10.2.$tn.5 {
      INSERT INTO ft(rowid, x) VALUES(1, 'hello world');
    }
  
    if {"%DETAIL%"!="full"} {
      do_catchsql_test 10.2.$tn.6 {
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
    do_execsql_test 10.2.$tn.10 {
      DELETE FROM x1;
      INSERT INTO x1(ii, x) VALUES(1, 'hello world');
    }

    do_catchsql_test 10.2.$tn.11 "
      INSERT INTO ft(ft, rowid, x) VALUES('delete', 1, test_setsubtype($v,76) )
    " {1 {SQL logic error}}

    do_catchsql_test 10.2.$tn.12 "
      INSERT INTO ft(rowid, x) VALUES(2, test_setsubtype($v,76) )
    " {1 {SQL logic error}}

    do_execsql_test 10.2.$tn.13 {
      INSERT INTO ft2(rowid, x) VALUES(1, 'hello world');
    }
    do_execsql_test 10.2.$tn.14 "UPDATE ft2_content SET c0=$v"

    do_catchsql_test 10.2.$tn.15 {







|



|







519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
    do_execsql_test 10.2.$tn.10 {
      DELETE FROM x1;
      INSERT INTO x1(ii, x) VALUES(1, 'hello world');
    }

    do_catchsql_test 10.2.$tn.11 "
      INSERT INTO ft(ft, rowid, x) VALUES('delete', 1, test_setsubtype($v,76) )
    " {0 {}}

    do_catchsql_test 10.2.$tn.12 "
      INSERT INTO ft(rowid, x) VALUES(2, test_setsubtype($v,76) )
    " {1 {datatype mismatch}}

    do_execsql_test 10.2.$tn.13 {
      INSERT INTO ft2(rowid, x) VALUES(1, 'hello world');
    }
    do_execsql_test 10.2.$tn.14 "UPDATE ft2_content SET c0=$v"

    do_catchsql_test 10.2.$tn.15 {
658
659
660
661
662
663
664
665
























666
667
  FROM ft('one AND three') ORDER BY rowid
} {1 {non-integer argument passed to function fts5_get_locale()}}
do_catchsql_test 13.2.7 {
  SELECT 
    quote(fts5_get_locale(ft, 0.0)), quote(fts5_get_locale(ft, 1)) 
  FROM ft('one AND three') ORDER BY rowid
} {1 {non-integer argument passed to function fts5_get_locale()}}

























finish_test









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


658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
  FROM ft('one AND three') ORDER BY rowid
} {1 {non-integer argument passed to function fts5_get_locale()}}
do_catchsql_test 13.2.7 {
  SELECT 
    quote(fts5_get_locale(ft, 0.0)), quote(fts5_get_locale(ft, 1)) 
  FROM ft('one AND three') ORDER BY rowid
} {1 {non-integer argument passed to function fts5_get_locale()}}

#-------------------------------------------------------------------------
# Check that UPDATE statements that may affect more than one row work.
#
reset_db
do_execsql_test 14.1 {
  CREATE VIRTUAL TABLE ft USING fts5(a, b, locale=1);
}

do_execsql_test 14.2 {
  INSERT INTO ft VALUES('hello', 'world');
}

do_execsql_test 14.3 {
  UPDATE ft SET b = fts5_locale('en_AU', 'world');
}

do_catchsql_test 14.4 {
  INSERT INTO ft VALUES(X'abcd', X'1234');
} {1 {datatype mismatch}}

do_execsql_test 14.4 {
  SELECT * FROM ft
} {hello world}

finish_test

Changes to test/fts3corrupt4.test.
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
  WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x%1 FROM c WHERE x<599237)
    INSERT INTO t1( a ) SELECT randomblob(3000) FROM t2 ;
} {0 {}}

do_catchsql_test 25.6 {
  INSERT INTO t1(t1) SELECT x FROM t2;
  INSERT INTO t1(t1) SELECT x FROM t2;
} {1 {database disk image is malformed}}

#-------------------------------------------------------------------------
reset_db
do_test 26.0 {
  sqlite3 db {}
  db deserialize [decode_hexdb {
.open --hexdb







|







4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
  WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x%1 FROM c WHERE x<599237)
    INSERT INTO t1( a ) SELECT randomblob(3000) FROM t2 ;
} {0 {}}

do_catchsql_test 25.6 {
  INSERT INTO t1(t1) SELECT x FROM t2;
  INSERT INTO t1(t1) SELECT x FROM t2;
} {0 {}}

#-------------------------------------------------------------------------
reset_db
do_test 26.0 {
  sqlite3 db {}
  db deserialize [decode_hexdb {
.open --hexdb