SQLite

Check-in [e8a61d5c]
Login

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

Overview
Comment:Clarify the role of Fts5Storage.pSavedRow in the new feature on this branch.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | fts5-locale
Files: files | file ages | folders
SHA3-256: e8a61d5c48073fdd4d99d0b6fc70469b37af009f281336a44e3789e7eeed820d
User & Date: dan 2024-08-01 17:15:17
Context
2024-08-02
21:06
Change things so that locale=1 is required to write fts5_locale() values to an fts5 table, and so that blobs may not be stored in indexed (i.e. not UNINDEXED) columns of these tables. (check-in: c98ccc12 user: dan tags: fts5-locale)
2024-08-01
17:15
Clarify the role of Fts5Storage.pSavedRow in the new feature on this branch. (check-in: e8a61d5c user: dan tags: fts5-locale)
2024-07-31
20:49
Fix various problems with the code on this branch. (check-in: 8bd4ae7e user: dan tags: fts5-locale)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/fts5/fts5_storage.c.

12
13
14
15
16
17
18


























19
20
21
22
23
24
25
**
*/



#include "fts5Int.h"



























struct Fts5Storage {
  Fts5Config *pConfig;
  Fts5Index *pIndex;
  int bTotalsValid;               /* True if nTotalRow/aTotalSize[] are valid */
  i64 nTotalRow;                  /* Total number of rows in FTS table */
  i64 *aTotalSize;                /* Total sizes of each column */ 
  sqlite3_stmt *pSavedRow;







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







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
**
*/



#include "fts5Int.h"

/*
** pSavedRow:
**   SQL statement FTS5_STMT_LOOKUP2 is a copy of FTS5_STMT_LOOKUP, it 
**   does a by-rowid lookup to retrieve a single row from the %_content 
**   table or equivalent external-content table/view.
**
**   However, FTS5_STMT_LOOKUP2 is only used when retrieving the original
**   values for a row being UPDATEd. In that case, the SQL statement is
**   not reset and pSavedRow is set to point at it. This is so that the
**   insert operation that follows the delete may access the original 
**   row values for any new values for which sqlite3_value_nochange() returns
**   true. i.e. if the user executes:
**
**        CREATE VIRTUAL TABLE ft USING fts5(a, b, c, locale=1);
**        ...
**        UPDATE fts SET a=?, b=? WHERE rowid=?;
**
**   then the value passed to the xUpdate() method of this table as the 
**   new.c value is an sqlite3_value_nochange() value. So in this case it
**   must be read from the saved row stored in Fts5Storage.pSavedRow.
**
**   This is necessary - using sqlite3_value_nochange() instead of just having
**   SQLite pass the original value back via xUpdate() - so as not to discard
**   any locale information associated with such values.
**
*/
struct Fts5Storage {
  Fts5Config *pConfig;
  Fts5Index *pIndex;
  int bTotalsValid;               /* True if nTotalRow/aTotalSize[] are valid */
  i64 nTotalRow;                  /* Total number of rows in FTS table */
  i64 *aTotalSize;                /* Total sizes of each column */ 
  sqlite3_stmt *pSavedRow;
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#if FTS5_STMT_SCAN_DESC!=1 
# error "FTS5_STMT_SCAN_DESC mismatch" 
#endif
#if FTS5_STMT_LOOKUP!=2
# error "FTS5_STMT_LOOKUP mismatch" 
#endif

#define FTS5_STMT_LOOKUP2  3
#define FTS5_STMT_INSERT_CONTENT  4
#define FTS5_STMT_REPLACE_CONTENT 5
#define FTS5_STMT_DELETE_CONTENT  6
#define FTS5_STMT_REPLACE_DOCSIZE  7
#define FTS5_STMT_DELETE_DOCSIZE  8
#define FTS5_STMT_LOOKUP_DOCSIZE  9
#define FTS5_STMT_REPLACE_CONFIG 10
#define FTS5_STMT_SCAN 11

/*
** Prepare the two insert statements - Fts5Storage.pInsertContent and
** Fts5Storage.pInsertDocsize - if they have not already been prepared.
** Return SQLITE_OK if successful, or an SQLite error code if an error
** occurs.
*/







|



|



|







59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#if FTS5_STMT_SCAN_DESC!=1 
# error "FTS5_STMT_SCAN_DESC mismatch" 
#endif
#if FTS5_STMT_LOOKUP!=2
# error "FTS5_STMT_LOOKUP mismatch" 
#endif

#define FTS5_STMT_LOOKUP2         3
#define FTS5_STMT_INSERT_CONTENT  4
#define FTS5_STMT_REPLACE_CONTENT 5
#define FTS5_STMT_DELETE_CONTENT  6
#define FTS5_STMT_REPLACE_DOCSIZE 7
#define FTS5_STMT_DELETE_DOCSIZE  8
#define FTS5_STMT_LOOKUP_DOCSIZE  9
#define FTS5_STMT_REPLACE_CONFIG 10
#define FTS5_STMT_SCAN           11

/*
** Prepare the two insert statements - Fts5Storage.pInsertContent and
** Fts5Storage.pInsertDocsize - if they have not already been prepared.
** Return SQLITE_OK if successful, or an SQLite error code if an error
** occurs.
*/
401
402
403
404
405
406
407










408
409
410
411
412
413
414
  if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
  if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){
    pCtx->szCol++;
  }
  return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken);
}











int sqlite3Fts5StorageFindDeleteRow(Fts5Storage *p, i64 iDel){
  int rc = SQLITE_OK;
  sqlite3_stmt *pSeek = 0;

  assert( p->pSavedRow==0 );
  rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP+1, &pSeek, 0);
  if( rc==SQLITE_OK ){







>
>
>
>
>
>
>
>
>
>







427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
  if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
  if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){
    pCtx->szCol++;
  }
  return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken);
}

/*
** This function is used as part of an UPDATE statement that modifies the
** rowid of a row. In that case, this function is called first to set
** Fts5Storage.pSavedRow to point to a statement that may be used to 
** access the original values of the row being deleted - iDel.
**
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
** It is not considered an error if row iDel does not exist. In this case
** pSavedRow is not set and SQLITE_OK returned.
*/
int sqlite3Fts5StorageFindDeleteRow(Fts5Storage *p, i64 iDel){
  int rc = SQLITE_OK;
  sqlite3_stmt *pSeek = 0;

  assert( p->pSavedRow==0 );
  rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP+1, &pSeek, 0);
  if( rc==SQLITE_OK ){
423
424
425
426
427
428
429





430
431
432
433
434
435
436
437
438
439
440
441
442
  return rc;
}

/*
** If a row with rowid iDel is present in the %_content table, add the
** delete-markers to the FTS index necessary to delete it. Do not actually
** remove the %_content row at this time though.





*/
static int fts5StorageDeleteFromIndex(
  Fts5Storage *p, 
  i64 iDel, 
  sqlite3_value **apVal,
  int bSaveRow
){
  Fts5Config *pConfig = p->pConfig;
  sqlite3_stmt *pSeek = 0;        /* SELECT to read row iDel from %_data */
  int rc = SQLITE_OK;             /* Return code */
  int rc2;                        /* sqlite3_reset() return code */
  int iCol;
  Fts5InsertCtx ctx;







>
>
>
>
>





|







459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
  return rc;
}

/*
** If a row with rowid iDel is present in the %_content table, add the
** delete-markers to the FTS index necessary to delete it. Do not actually
** remove the %_content row at this time though.
**
** If parameter bSaveRow is true, then Fts5Storage.pSavedRow is left
** pointing to a statement (FTS5_STMT_LOOKUP2) that may be used to access
** the original values of the row being deleted. This is used by UPDATE
** statements.
*/
static int fts5StorageDeleteFromIndex(
  Fts5Storage *p, 
  i64 iDel, 
  sqlite3_value **apVal,
  int bSaveRow                    /* True to set pSavedRow */
){
  Fts5Config *pConfig = p->pConfig;
  sqlite3_stmt *pSeek = 0;        /* SELECT to read row iDel from %_data */
  int rc = SQLITE_OK;             /* Return code */
  int rc2;                        /* sqlite3_reset() return code */
  int iCol;
  Fts5InsertCtx ctx;
502
503
504
505
506
507
508





509



510
511
512
513
514
515
516
  }else{
    rc2 = sqlite3_reset(pSeek);
    if( rc==SQLITE_OK ) rc = rc2;
  }
  return rc;
}






void sqlite3Fts5StorageReleaseDeleteRow(Fts5Storage *pStorage){



  sqlite3_reset(pStorage->pSavedRow);
  pStorage->pSavedRow = 0;
}

/*
** This function is called to process a DELETE on a contentless_delete=1
** table. It adds the tombstone required to delete the entry with rowid 







>
>
>
>
>

>
>
>







543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
  }else{
    rc2 = sqlite3_reset(pSeek);
    if( rc==SQLITE_OK ) rc = rc2;
  }
  return rc;
}

/*
** Reset any saved statement pSavedRow. Zero pSavedRow as well. This
** should be called by the xUpdate() method of the fts5 table before 
** returning from any operation that may have set Fts5Storage.pSavedRow.
*/
void sqlite3Fts5StorageReleaseDeleteRow(Fts5Storage *pStorage){
  assert( pStorage->pSavedRow==0 
       || pStorage->pSavedRow==pStorage->aStmt[FTS5_STMT_LOOKUP2] 
  );
  sqlite3_reset(pStorage->pSavedRow);
  pStorage->pSavedRow = 0;
}

/*
** This function is called to process a DELETE on a contentless_delete=1
** table. It adds the tombstone required to delete the entry with rowid 
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
/*
** Remove a row from the FTS table.
*/
int sqlite3Fts5StorageDelete(
  Fts5Storage *p,                 /* Storage object */
  i64 iDel,                       /* Rowid to delete from table */
  sqlite3_value **apVal,          /* Optional - values to remove from index */
  int bSaveRow
){
  Fts5Config *pConfig = p->pConfig;
  int rc;
  sqlite3_stmt *pDel = 0;

  assert( pConfig->eContent!=FTS5_CONTENT_NORMAL || apVal==0 );
  rc = fts5StorageLoadTotals(p, 1);







|







675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
/*
** Remove a row from the FTS table.
*/
int sqlite3Fts5StorageDelete(
  Fts5Storage *p,                 /* Storage object */
  i64 iDel,                       /* Rowid to delete from table */
  sqlite3_value **apVal,          /* Optional - values to remove from index */
  int bSaveRow                    /* If true, set pSavedRow for deleted row */
){
  Fts5Config *pConfig = p->pConfig;
  int rc;
  sqlite3_stmt *pDel = 0;

  assert( pConfig->eContent!=FTS5_CONTENT_NORMAL || apVal==0 );
  rc = fts5StorageLoadTotals(p, 1);
733
734
735
736
737
738
739
740
741
742
743
744
745
746

747
748
749
750
751
752
753
    i64 iRowid = sqlite3_column_int64(pScan, 0);

    sqlite3Fts5BufferZero(&buf);
    rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid);
    for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
      ctx.szCol = 0;
      if( pConfig->abUnindexed[ctx.iCol]==0 ){
        int bReset = 0;
        int nText = 0;
        const char *pText = 0;
        rc = sqlite3Fts5ExtractText(pConfig, 
            sqlite3_column_value(pScan, ctx.iCol+1), 1, &bReset, &pText, &nText
        );


        if( rc==SQLITE_OK ){
          rc = sqlite3Fts5Tokenize(pConfig, 
              FTS5_TOKENIZE_DOCUMENT,
              pText, nText,
              (void*)&ctx,
              fts5StorageInsertCallback
          );







|
|
|
<
|
<

>







782
783
784
785
786
787
788
789
790
791

792

793
794
795
796
797
798
799
800
801
    i64 iRowid = sqlite3_column_int64(pScan, 0);

    sqlite3Fts5BufferZero(&buf);
    rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid);
    for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
      ctx.szCol = 0;
      if( pConfig->abUnindexed[ctx.iCol]==0 ){
        int bReset = 0;           /* True if tokenizer locale must be reset */
        int nText = 0;            /* Size of pText in bytes */
        const char *pText = 0;    /* Pointer to buffer containing text value */

        sqlite3_value *pVal = sqlite3_column_value(pScan, ctx.iCol+1);


        rc = sqlite3Fts5ExtractText(pConfig, pVal, 1, &bReset, &pText, &nText);
        if( rc==SQLITE_OK ){
          rc = sqlite3Fts5Tokenize(pConfig, 
              FTS5_TOKENIZE_DOCUMENT,
              pText, nText,
              (void*)&ctx,
              fts5StorageInsertCallback
          );
834
835
836
837
838
839
840


841
842
843
844
845
846
847
  }else{
    sqlite3_stmt *pInsert = 0;    /* Statement to write %_content table */
    int i;                        /* Counter variable */
    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 ){


        pVal = sqlite3_column_value(p->pSavedRow, i-1);
      }else if( sqlite3_value_subtype(pVal)==FTS5_LOCALE_SUBTYPE ){
        if( pConfig->bLocale==0 ){
          sqlite3Fts5ConfigErrmsg(pConfig, 
              "fts5_locale() may not be used without locale=1"
          );
          rc = SQLITE_ERROR;







>
>







882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
  }else{
    sqlite3_stmt *pInsert = 0;    /* Statement to write %_content table */
    int i;                        /* Counter variable */
    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 ){
        if( pConfig->bLocale==0 ){
          sqlite3Fts5ConfigErrmsg(pConfig, 
              "fts5_locale() may not be used without locale=1"
          );
          rc = SQLITE_ERROR;
855
856
857
858
859
860
861
862

863
864
865
866
867
868
869
          sqlite3_bind_text(pInsert, i, pText, nText, SQLITE_TRANSIENT);
          continue;
        }
      }else if( pConfig->bLocale 
       && sqlite3_value_type(pVal)==SQLITE_BLOB 
       && i>=2 && pConfig->abUnindexed[i-2]==0
      ){
        /* Inserting a blob into a normal content table with locale=1. */

        int n = sqlite3_value_bytes(pVal);
        u8 *pBlob = sqlite3Fts5MallocZero(&rc, n+4);
        if( pBlob ){
          memcpy(&pBlob[4], sqlite3_value_blob(pVal), n);
          rc = sqlite3_bind_blob(pInsert, i, pBlob, n+4, SQLITE_TRANSIENT);
          sqlite3_free(pBlob);
        }







|
>







905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
          sqlite3_bind_text(pInsert, i, pText, nText, SQLITE_TRANSIENT);
          continue;
        }
      }else if( pConfig->bLocale 
       && sqlite3_value_type(pVal)==SQLITE_BLOB 
       && i>=2 && pConfig->abUnindexed[i-2]==0
      ){
        /* Inserting a blob into a normal content table with locale=1. 
        ** Add the 4 0x00 byte header.  */
        int n = sqlite3_value_bytes(pVal);
        u8 *pBlob = sqlite3Fts5MallocZero(&rc, n+4);
        if( pBlob ){
          memcpy(&pBlob[4], sqlite3_value_blob(pVal), n);
          rc = sqlite3_bind_blob(pInsert, i, pBlob, n+4, SQLITE_TRANSIENT);
          sqlite3_free(pBlob);
        }
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936

  if( rc==SQLITE_OK ){
    rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid);
  }
  for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
    ctx.szCol = 0;
    if( pConfig->abUnindexed[ctx.iCol]==0 ){
      int bReset = 0;
      int nText = 0;
      const char *pText = 0;
      sqlite3_value *pVal = apVal[ctx.iCol+2];
      int bDisk = 0;
      if( p->pSavedRow && sqlite3_value_nochange(pVal) ){
        pVal = sqlite3_column_value(p->pSavedRow, ctx.iCol+1);
        bDisk = 1;
      }
      rc = sqlite3Fts5ExtractText(pConfig, pVal, bDisk, &bReset,&pText,&nText);
      if( rc==SQLITE_OK ){
        if( bReset && pConfig->bLocale==0 ){
          rc = SQLITE_ERROR;
          sqlite3Fts5ConfigErrmsg(pConfig, 
              "fts5_locale() may not be used without locale=1"
          );
        }else{
          rc = sqlite3Fts5Tokenize(pConfig, 
              FTS5_TOKENIZE_DOCUMENT, pText, nText, (void*)&ctx,
              fts5StorageInsertCallback
          );
        }
        if( bReset ) sqlite3Fts5ClearLocale(pConfig);
      }
    }
    sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
    p->aTotalSize[ctx.iCol] += (i64)ctx.szCol;
  }
  p->nTotalRow++;







|
|
|






|

|
<
<
<
<
<
|
|
|
|
<







952
953
954
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

  if( rc==SQLITE_OK ){
    rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid);
  }
  for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
    ctx.szCol = 0;
    if( pConfig->abUnindexed[ctx.iCol]==0 ){
      int bReset = 0;             /* True if tokenizer locale must be reset */
      int nText = 0;              /* Size of pText in bytes */
      const char *pText = 0;      /* Pointer to buffer containing text value */
      sqlite3_value *pVal = apVal[ctx.iCol+2];
      int bDisk = 0;
      if( p->pSavedRow && sqlite3_value_nochange(pVal) ){
        pVal = sqlite3_column_value(p->pSavedRow, ctx.iCol+1);
        bDisk = 1;
      }
      rc = sqlite3Fts5ExtractText(pConfig, pVal, bDisk, &bReset, &pText,&nText);
      if( rc==SQLITE_OK ){
        assert( bReset==0 || pConfig->bLocale );





        rc = sqlite3Fts5Tokenize(pConfig, 
            FTS5_TOKENIZE_DOCUMENT, pText, nText, (void*)&ctx,
            fts5StorageInsertCallback
        );

        if( bReset ) sqlite3Fts5ClearLocale(pConfig);
      }
    }
    sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
    p->aTotalSize[ctx.iCol] += (i64)ctx.szCol;
  }
  p->nTotalRow++;
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
          if( pConfig->abUnindexed[i] ) continue;
          ctx.iCol = i;
          ctx.szCol = 0;
          if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
            rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
          }
          if( rc==SQLITE_OK ){
            const char *pText = 0;
            int nText = 0;
            int bReset = 0;

            rc = sqlite3Fts5ExtractText(pConfig, 
                sqlite3_column_value(pScan, i+1), 1, &bReset, &pText, &nText
            );

            if( rc==SQLITE_OK ){
              rc = sqlite3Fts5Tokenize(pConfig, 
                  FTS5_TOKENIZE_DOCUMENT,
                  pText, nText,
                  (void*)&ctx,
                  fts5StorageIntegrityCallback
              );







|
|
|




<







1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153

1154
1155
1156
1157
1158
1159
1160
          if( pConfig->abUnindexed[i] ) continue;
          ctx.iCol = i;
          ctx.szCol = 0;
          if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
            rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
          }
          if( rc==SQLITE_OK ){
            int bReset = 0;        /* True if tokenizer locale must be reset */
            int nText = 0;         /* Size of pText in bytes */
            const char *pText = 0; /* Pointer to buffer containing text value */

            rc = sqlite3Fts5ExtractText(pConfig, 
                sqlite3_column_value(pScan, i+1), 1, &bReset, &pText, &nText
            );

            if( rc==SQLITE_OK ){
              rc = sqlite3Fts5Tokenize(pConfig, 
                  FTS5_TOKENIZE_DOCUMENT,
                  pText, nText,
                  (void*)&ctx,
                  fts5StorageIntegrityCallback
              );