Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Merge the latest trunk enhancements into the wal-shm-exceptions branch. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | wal-shm-exceptions |
Files: | files | file ages | folders |
SHA3-256: |
c44fb4d06591ef4872f015aa9e81ce8b |
User & Date: | drh 2023-07-28 19:07:25.753 |
Context
2023-08-10
| ||
17:07 | Merge latest trunk changes into this branch. (Leaf check-in: 3ed89c344f user: dan tags: wal-shm-exceptions) | |
2023-07-28
| ||
19:07 | Merge the latest trunk enhancements into the wal-shm-exceptions branch. (check-in: c44fb4d065 user: drh tags: wal-shm-exceptions) | |
18:37 | Add the sqlite3_stmt_explain() API. (check-in: 0443c0ef85 user: drh tags: trunk) | |
2023-07-26
| ||
18:34 | Add a comment describing the contents of the ExceptionInformation[] array for win32 EXCEPTION_IN_PAGE_ERROR exceptions. No changes to code. (check-in: 8e20354242 user: dan tags: wal-shm-exceptions) | |
Changes
Changes to ext/fts5/fts5Int.h.
︙ | ︙ | |||
150 151 152 153 154 155 156 157 158 159 160 161 162 163 | ** And all information loaded from the %_config table. ** ** nAutomerge: ** The minimum number of segments that an auto-merge operation should ** attempt to merge together. A value of 1 sets the object to use the ** compile time default. Zero disables auto-merge altogether. ** ** zContent: ** ** zContentRowid: ** The value of the content_rowid= option, if one was specified. Or ** the string "rowid" otherwise. This text is not quoted - if it is ** used as part of an SQL statement it needs to be quoted appropriately. ** | > > > > | 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | ** And all information loaded from the %_config table. ** ** nAutomerge: ** The minimum number of segments that an auto-merge operation should ** attempt to merge together. A value of 1 sets the object to use the ** compile time default. Zero disables auto-merge altogether. ** ** bContentlessDelete: ** True if the contentless_delete option was present in the CREATE ** VIRTUAL TABLE statement. ** ** zContent: ** ** zContentRowid: ** The value of the content_rowid= option, if one was specified. Or ** the string "rowid" otherwise. This text is not quoted - if it is ** used as part of an SQL statement it needs to be quoted appropriately. ** |
︙ | ︙ | |||
184 185 186 187 188 189 190 191 192 193 194 195 196 197 | char *zName; /* Name of FTS index */ int nCol; /* Number of columns */ char **azCol; /* Column names */ u8 *abUnindexed; /* True for unindexed columns */ int nPrefix; /* Number of prefix indexes */ int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */ int eContent; /* An FTS5_CONTENT value */ char *zContent; /* content table */ char *zContentRowid; /* "content_rowid=" option value */ int bColumnsize; /* "columnsize=" option value (dflt==1) */ int eDetail; /* FTS5_DETAIL_XXX value */ char *zContentExprlist; Fts5Tokenizer *pTok; fts5_tokenizer *pTokApi; | > | 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | char *zName; /* Name of FTS index */ int nCol; /* Number of columns */ char **azCol; /* Column names */ u8 *abUnindexed; /* True for unindexed columns */ int nPrefix; /* Number of prefix indexes */ int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */ int eContent; /* An FTS5_CONTENT value */ int bContentlessDelete; /* "contentless_delete=" option (dflt==0) */ char *zContent; /* content table */ char *zContentRowid; /* "content_rowid=" option value */ int bColumnsize; /* "columnsize=" option value (dflt==1) */ int eDetail; /* FTS5_DETAIL_XXX value */ char *zContentExprlist; Fts5Tokenizer *pTok; fts5_tokenizer *pTokApi; |
︙ | ︙ | |||
205 206 207 208 209 210 211 212 213 214 215 216 217 218 | 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 | > | 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | 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' */ int nDeleteMerge; /* 'deletemerge' */ /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */ char **pzErrmsg; #ifdef SQLITE_DEBUG int bPrefixIndex; /* True to use prefix-indexes */ #endif |
︙ | ︙ | |||
527 528 529 530 531 532 533 534 535 536 537 538 539 540 | int sqlite3Fts5IndexReinit(Fts5Index *p); int sqlite3Fts5IndexOptimize(Fts5Index *p); int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge); int sqlite3Fts5IndexReset(Fts5Index *p); int sqlite3Fts5IndexLoadConfig(Fts5Index *p); /* ** End of interface to code in fts5_index.c. **************************************************************************/ /************************************************************************** ** Interface to code in fts5_varint.c. */ | > > > | 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 | int sqlite3Fts5IndexReinit(Fts5Index *p); int sqlite3Fts5IndexOptimize(Fts5Index *p); int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge); int sqlite3Fts5IndexReset(Fts5Index *p); int sqlite3Fts5IndexLoadConfig(Fts5Index *p); int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin); int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid); /* ** End of interface to code in fts5_index.c. **************************************************************************/ /************************************************************************** ** Interface to code in fts5_varint.c. */ |
︙ | ︙ | |||
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 | ); /* ** Empty (but do not delete) a hash table. */ void sqlite3Fts5HashClear(Fts5Hash*); int sqlite3Fts5HashQuery( Fts5Hash*, /* Hash table to query */ int nPre, const char *pTerm, int nTerm, /* Query term */ void **ppObj, /* OUT: Pointer to doclist for pTerm */ int *pnDoclist /* OUT: Size of doclist in bytes */ ); int sqlite3Fts5HashScanInit( Fts5Hash*, /* Hash table to query */ const char *pTerm, int nTerm /* Query prefix */ ); void sqlite3Fts5HashScanNext(Fts5Hash*); int sqlite3Fts5HashScanEof(Fts5Hash*); void sqlite3Fts5HashScanEntry(Fts5Hash *, const char **pzTerm, /* OUT: term (nul-terminated) */ const u8 **ppDoclist, /* OUT: pointer to doclist */ int *pnDoclist /* OUT: size of doclist in bytes */ ); /* ** End of interface to code in fts5_hash.c. **************************************************************************/ /************************************************************************** | > > > > > > | 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 657 658 | ); /* ** Empty (but do not delete) a hash table. */ void sqlite3Fts5HashClear(Fts5Hash*); /* ** Return true if the hash is empty, false otherwise. */ int sqlite3Fts5HashIsEmpty(Fts5Hash*); int sqlite3Fts5HashQuery( Fts5Hash*, /* Hash table to query */ int nPre, const char *pTerm, int nTerm, /* Query term */ void **ppObj, /* OUT: Pointer to doclist for pTerm */ int *pnDoclist /* OUT: Size of doclist in bytes */ ); int sqlite3Fts5HashScanInit( Fts5Hash*, /* Hash table to query */ const char *pTerm, int nTerm /* Query prefix */ ); void sqlite3Fts5HashScanNext(Fts5Hash*); int sqlite3Fts5HashScanEof(Fts5Hash*); void sqlite3Fts5HashScanEntry(Fts5Hash *, const char **pzTerm, /* OUT: term (nul-terminated) */ const u8 **ppDoclist, /* OUT: pointer to doclist */ int *pnDoclist /* OUT: size of doclist in bytes */ ); /* ** End of interface to code in fts5_hash.c. **************************************************************************/ /************************************************************************** |
︙ | ︙ |
Changes to ext/fts5/fts5_config.c.
︙ | ︙ | |||
18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #define FTS5_DEFAULT_PAGE_SIZE 4050 #define FTS5_DEFAULT_AUTOMERGE 4 #define FTS5_DEFAULT_USERMERGE 4 #define FTS5_DEFAULT_CRISISMERGE 16 #define FTS5_DEFAULT_HASHSIZE (1024*1024) /* Maximum allowed page size */ #define FTS5_MAX_PAGE_SIZE (64*1024) static int fts5_iswhitespace(char x){ return (x==' '); } | > > | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #define FTS5_DEFAULT_PAGE_SIZE 4050 #define FTS5_DEFAULT_AUTOMERGE 4 #define FTS5_DEFAULT_USERMERGE 4 #define FTS5_DEFAULT_CRISISMERGE 16 #define FTS5_DEFAULT_HASHSIZE (1024*1024) #define FTS5_DEFAULT_DELETE_AUTOMERGE 10 /* default 10% */ /* Maximum allowed page size */ #define FTS5_MAX_PAGE_SIZE (64*1024) static int fts5_iswhitespace(char x){ return (x==' '); } |
︙ | ︙ | |||
347 348 349 350 351 352 353 354 355 356 357 358 359 360 | pConfig->zContent = sqlite3Fts5Mprintf(&rc, "%Q.%Q", pConfig->zDb,zArg); }else{ pConfig->eContent = FTS5_CONTENT_NONE; } } return rc; } if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){ if( pConfig->zContentRowid ){ *pzErr = sqlite3_mprintf("multiple content_rowid=... directives"); rc = SQLITE_ERROR; }else{ pConfig->zContentRowid = sqlite3Fts5Strndup(&rc, zArg, -1); | > > > > > > > > > > | 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 | pConfig->zContent = sqlite3Fts5Mprintf(&rc, "%Q.%Q", pConfig->zDb,zArg); }else{ pConfig->eContent = FTS5_CONTENT_NONE; } } return rc; } if( sqlite3_strnicmp("contentless_delete", zCmd, nCmd)==0 ){ if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ *pzErr = sqlite3_mprintf("malformed contentless_delete=... directive"); rc = SQLITE_ERROR; }else{ pConfig->bContentlessDelete = (zArg[0]=='1'); } return rc; } if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){ if( pConfig->zContentRowid ){ *pzErr = sqlite3_mprintf("multiple content_rowid=... directives"); rc = SQLITE_ERROR; }else{ pConfig->zContentRowid = sqlite3Fts5Strndup(&rc, zArg, -1); |
︙ | ︙ | |||
591 592 593 594 595 596 597 598 599 600 601 602 603 604 | } } } sqlite3_free(zOne); sqlite3_free(zTwo); } /* If a tokenizer= option was successfully parsed, the tokenizer has ** already been allocated. Otherwise, allocate an instance of the default ** tokenizer (unicode61) now. */ if( rc==SQLITE_OK && pRet->pTok==0 ){ rc = fts5ConfigDefaultTokenizer(pGlobal, pRet); } | > > > > > > > > > > > > > > > > > > > > > > | 603 604 605 606 607 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 | } } } sqlite3_free(zOne); sqlite3_free(zTwo); } /* We only allow contentless_delete=1 if the table is indeed contentless. */ if( rc==SQLITE_OK && pRet->bContentlessDelete && pRet->eContent!=FTS5_CONTENT_NONE ){ *pzErr = sqlite3_mprintf( "contentless_delete=1 requires a contentless table" ); rc = SQLITE_ERROR; } /* We only allow contentless_delete=1 if columnsize=0 is not present. ** ** This restriction may be removed at some point. */ if( rc==SQLITE_OK && pRet->bContentlessDelete && pRet->bColumnsize==0 ){ *pzErr = sqlite3_mprintf( "contentless_delete=1 is incompatible with columnsize=0" ); rc = SQLITE_ERROR; } /* If a tokenizer= option was successfully parsed, the tokenizer has ** already been allocated. Otherwise, allocate an instance of the default ** tokenizer (unicode61) now. */ if( rc==SQLITE_OK && pRet->pTok==0 ){ rc = fts5ConfigDefaultTokenizer(pGlobal, pRet); } |
︙ | ︙ | |||
885 886 887 888 889 890 891 892 893 894 895 896 897 898 | *pbBadkey = 1; }else{ if( nCrisisMerge<=1 ) nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; if( nCrisisMerge>=FTS5_MAX_SEGMENT ) nCrisisMerge = FTS5_MAX_SEGMENT-1; pConfig->nCrisisMerge = nCrisisMerge; } } else if( 0==sqlite3_stricmp(zKey, "rank") ){ const char *zIn = (const char*)sqlite3_value_text(pVal); char *zRank; char *zRankArgs; rc = sqlite3Fts5ConfigParseRank(zIn, &zRank, &zRankArgs); if( rc==SQLITE_OK ){ | > > > > > > > > > > > > | 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 | *pbBadkey = 1; }else{ if( nCrisisMerge<=1 ) nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; if( nCrisisMerge>=FTS5_MAX_SEGMENT ) nCrisisMerge = FTS5_MAX_SEGMENT-1; pConfig->nCrisisMerge = nCrisisMerge; } } else if( 0==sqlite3_stricmp(zKey, "deletemerge") ){ int nVal = -1; if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ nVal = sqlite3_value_int(pVal); }else{ *pbBadkey = 1; } if( nVal<0 ) nVal = FTS5_DEFAULT_DELETE_AUTOMERGE; if( nVal>100 ) nVal = 0; pConfig->nDeleteMerge = nVal; } else if( 0==sqlite3_stricmp(zKey, "rank") ){ const char *zIn = (const char*)sqlite3_value_text(pVal); char *zRank; char *zRankArgs; rc = sqlite3Fts5ConfigParseRank(zIn, &zRank, &zRankArgs); if( rc==SQLITE_OK ){ |
︙ | ︙ | |||
934 935 936 937 938 939 940 941 942 943 944 945 946 947 | /* Set default values */ pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE; pConfig->nAutomerge = FTS5_DEFAULT_AUTOMERGE; pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE; pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE; zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName); if( zSql ){ rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p, 0); sqlite3_free(zSql); } | > | 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 | /* Set default values */ pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE; pConfig->nAutomerge = FTS5_DEFAULT_AUTOMERGE; pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE; pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE; pConfig->nDeleteMerge = FTS5_DEFAULT_DELETE_AUTOMERGE; zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName); if( zSql ){ rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p, 0); sqlite3_free(zSql); } |
︙ | ︙ |
Changes to ext/fts5/fts5_expr.c.
︙ | ︙ | |||
2473 2474 2475 2476 2477 2478 2479 | pRet = sqlite3Fts5ParseNode(pParse, FTS5_AND, pLeft, pRight, 0); } } return pRet; } | | | 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 | pRet = sqlite3Fts5ParseNode(pParse, FTS5_AND, pLeft, pRight, 0); } } return pRet; } #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){ sqlite3_int64 nByte = 0; Fts5ExprTerm *p; char *zQuoted; /* Determine the maximum amount of space required. */ for(p=pTerm; p; p=p->pSynonym){ |
︙ | ︙ | |||
2840 2841 2842 2843 2844 2845 2846 | int iCode; int bRemoveDiacritics = 0; iCode = sqlite3_value_int(apVal[0]); if( nArg==2 ) bRemoveDiacritics = sqlite3_value_int(apVal[1]); sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics)); } } | | | | 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 | int iCode; int bRemoveDiacritics = 0; iCode = sqlite3_value_int(apVal[0]); if( nArg==2 ) bRemoveDiacritics = sqlite3_value_int(apVal[1]); sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics)); } } #endif /* if SQLITE_TEST || SQLITE_FTS5_DEBUG */ /* ** This is called during initialization to register the fts5_expr() scalar ** UDF with the SQLite handle passed as the only argument. */ int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) struct Fts5ExprFunc { const char *z; void (*x)(sqlite3_context*,int,sqlite3_value**); } aFunc[] = { { "fts5_expr", fts5ExprFunctionHr }, { "fts5_expr_tcl", fts5ExprFunctionTcl }, { "fts5_isalnum", fts5ExprIsAlnum }, |
︙ | ︙ |
Changes to ext/fts5/fts5_hash.c.
︙ | ︙ | |||
471 472 473 474 475 476 477 | } pList = 0; for(i=0; i<nMergeSlot; i++){ pList = fts5HashEntryMerge(pList, ap[i]); } | < | 471 472 473 474 475 476 477 478 479 480 481 482 483 484 | } pList = 0; for(i=0; i<nMergeSlot; i++){ pList = fts5HashEntryMerge(pList, ap[i]); } sqlite3_free(ap); *ppSorted = pList; return SQLITE_OK; } /* ** Query the hash table for a doclist associated with term pTerm/nTerm. |
︙ | ︙ | |||
524 525 526 527 528 529 530 531 532 533 534 535 536 537 | int sqlite3Fts5HashScanInit( Fts5Hash *p, /* Hash table to query */ const char *pTerm, int nTerm /* Query prefix */ ){ return fts5HashEntrySort(p, pTerm, nTerm, &p->pScan); } void sqlite3Fts5HashScanNext(Fts5Hash *p){ assert( !sqlite3Fts5HashScanEof(p) ); p->pScan = p->pScan->pScanNext; } int sqlite3Fts5HashScanEof(Fts5Hash *p){ | > > > > > > > > > > > > > > > > > > > > > > | 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 | int sqlite3Fts5HashScanInit( Fts5Hash *p, /* Hash table to query */ const char *pTerm, int nTerm /* Query prefix */ ){ return fts5HashEntrySort(p, pTerm, nTerm, &p->pScan); } #ifdef SQLITE_DEBUG static int fts5HashCount(Fts5Hash *pHash){ int nEntry = 0; int ii; for(ii=0; ii<pHash->nSlot; ii++){ Fts5HashEntry *p = 0; for(p=pHash->aSlot[ii]; p; p=p->pHashNext){ nEntry++; } } return nEntry; } #endif /* ** Return true if the hash table is empty, false otherwise. */ int sqlite3Fts5HashIsEmpty(Fts5Hash *pHash){ assert( pHash->nEntry==fts5HashCount(pHash) ); return pHash->nEntry==0; } void sqlite3Fts5HashScanNext(Fts5Hash *p){ assert( !sqlite3Fts5HashScanEof(p) ); p->pScan = p->pScan->pScanNext; } int sqlite3Fts5HashScanEof(Fts5Hash *p){ |
︙ | ︙ |
Changes to ext/fts5/fts5_index.c.
︙ | ︙ | |||
52 53 54 55 56 57 58 59 60 61 62 63 64 65 | #if FTS5_MAX_PREFIX_INDEXES > 31 # error "FTS5_MAX_PREFIX_INDEXES is too large" #endif #define FTS5_MAX_LEVEL 64 /* ** Details: ** ** The %_data table managed by this module, ** ** CREATE TABLE %_data(id INTEGER PRIMARY KEY, block BLOB); ** | > > > > > > > > > > > > > > > > > > | | | < < | > > | > > > > > > | 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 | #if FTS5_MAX_PREFIX_INDEXES > 31 # error "FTS5_MAX_PREFIX_INDEXES is too large" #endif #define FTS5_MAX_LEVEL 64 /* ** There are two versions of the format used for the structure record: ** ** 1. the legacy format, that may be read by all fts5 versions, and ** ** 2. the V2 format, which is used by contentless_delete=1 databases. ** ** Both begin with a 4-byte "configuration cookie" value. Then, a legacy ** format structure record contains a varint - the number of levels in ** the structure. Whereas a V2 structure record contains the constant ** 4 bytes [0xff 0x00 0x00 0x01]. This is unambiguous as the value of a ** varint has to be at least 16256 to begin with "0xFF". And the default ** maximum number of levels is 64. ** ** See below for more on structure record formats. */ #define FTS5_STRUCTURE_V2 "\xFF\x00\x00\x01" /* ** Details: ** ** The %_data table managed by this module, ** ** CREATE TABLE %_data(id INTEGER PRIMARY KEY, block BLOB); ** ** , contains the following 6 types of records. See the comments surrounding ** the FTS5_*_ROWID macros below for a description of how %_data rowids are ** assigned to each fo them. ** ** 1. Structure Records: ** ** The set of segments that make up an index - the index structure - are ** recorded in a single record within the %_data table. The record consists ** of a single 32-bit configuration cookie value followed by a list of ** SQLite varints. ** ** If the structure record is a V2 record, the configuration cookie is ** followed by the following 4 bytes: [0xFF 0x00 0x00 0x01]. ** ** Next, the record continues with three varints: ** ** + number of levels, ** + total number of segments on all levels, ** + value of write counter. ** ** Then, for each level from 0 to nMax: ** ** + number of input segments in ongoing merge. ** + total number of segments in level. ** + for each segment from oldest to newest: ** + segment id (always > 0) ** + first leaf page number (often 1, always greater than 0) ** + final leaf page number ** ** Then, for V2 structures only: ** ** + lower origin counter value, ** + upper origin counter value, ** + the number of tombstone hash pages. ** ** 2. The Averages Record: ** ** A single record within the %_data table. The data is a list of varints. ** The first value is the number of rows in the index. Then, for each column ** from left to right, the total number of tokens in the column for all ** rows of the table. ** |
︙ | ︙ | |||
203 204 205 206 207 208 209 210 211 212 213 214 215 216 | ** * Page number of first child page. As a varint. ** ** * Copy of first rowid on page indicated by previous field. As a varint. ** ** * A list of delta-encoded varints - the first rowid on each subsequent ** child page. ** */ /* ** Rowids for the averages and structure records in the %_data table. */ #define FTS5_AVERAGES_ROWID 1 /* Rowid used for the averages record */ #define FTS5_STRUCTURE_ROWID 10 /* The structure record */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | ** * Page number of first child page. As a varint. ** ** * Copy of first rowid on page indicated by previous field. As a varint. ** ** * A list of delta-encoded varints - the first rowid on each subsequent ** child page. ** ** 6. Tombstone Hash Page ** ** These records are only ever present in contentless_delete=1 tables. ** There are zero or more of these associated with each segment. They ** are used to store the tombstone rowids for rows contained in the ** associated segments. ** ** The set of nHashPg tombstone hash pages associated with a single ** segment together form a single hash table containing tombstone rowids. ** To find the page of the hash on which a key might be stored: ** ** iPg = (rowid % nHashPg) ** ** Then, within page iPg, which has nSlot slots: ** ** iSlot = (rowid / nHashPg) % nSlot ** ** Each tombstone hash page begins with an 8 byte header: ** ** 1-byte: Key-size (the size in bytes of each slot). Either 4 or 8. ** 1-byte: rowid-0-tombstone flag. This flag is only valid on the ** first tombstone hash page for each segment (iPg=0). If set, ** the hash table contains rowid 0. If clear, it does not. ** Rowid 0 is handled specially. ** 2-bytes: unused. ** 4-bytes: Big-endian integer containing number of entries on page. ** ** Following this are nSlot 4 or 8 byte slots (depending on the key-size ** in the first byte of the page header). The number of slots may be ** determined based on the size of the page record and the key-size: ** ** nSlot = (nByte - 8) / key-size */ /* ** Rowids for the averages and structure records in the %_data table. */ #define FTS5_AVERAGES_ROWID 1 /* Rowid used for the averages record */ #define FTS5_STRUCTURE_ROWID 10 /* The structure record */ |
︙ | ︙ | |||
236 237 238 239 240 241 242 243 244 245 246 247 248 249 | ((i64)(dlidx) << (FTS5_DATA_PAGE_B + FTS5_DATA_HEIGHT_B)) + \ ((i64)(height) << (FTS5_DATA_PAGE_B)) + \ ((i64)(pgno)) \ ) #define FTS5_SEGMENT_ROWID(segid, pgno) fts5_dri(segid, 0, 0, pgno) #define FTS5_DLIDX_ROWID(segid, height, pgno) fts5_dri(segid, 1, height, pgno) #ifdef SQLITE_DEBUG int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; } #endif /* | > | 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | ((i64)(dlidx) << (FTS5_DATA_PAGE_B + FTS5_DATA_HEIGHT_B)) + \ ((i64)(height) << (FTS5_DATA_PAGE_B)) + \ ((i64)(pgno)) \ ) #define FTS5_SEGMENT_ROWID(segid, pgno) fts5_dri(segid, 0, 0, pgno) #define FTS5_DLIDX_ROWID(segid, height, pgno) fts5_dri(segid, 1, height, pgno) #define FTS5_TOMBSTONE_ROWID(segid,ipg) fts5_dri(segid+(1<<16), 0, 0, ipg) #ifdef SQLITE_DEBUG int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; } #endif /* |
︙ | ︙ | |||
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 | u8 *p; /* Pointer to buffer containing record */ int nn; /* Size of record in bytes */ int szLeaf; /* Size of leaf without page-index */ }; /* ** One object per %_data table. */ struct Fts5Index { Fts5Config *pConfig; /* Virtual table configuration */ char *zDataTbl; /* Name of %_data table */ int nWorkUnit; /* Leaf pages in a "unit" of work */ /* ** Variables related to the accumulation of tokens and doclists within the ** in-memory hash tables before they are flushed to disk. */ Fts5Hash *pHash; /* Hash table for in-memory data */ int nPendingData; /* Current bytes of pending data */ i64 iWriteRowid; /* Rowid for current doc being written */ int bDelete; /* Current write is a delete */ /* Error state. */ int rc; /* Current error code */ /* State used by the fts5DataXXX() functions. */ sqlite3_blob *pReader; /* RO incr-blob open on %_data table */ sqlite3_stmt *pWriter; /* "INSERT ... %_data VALUES(?,?)" */ | > > > > > > > > | 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 | u8 *p; /* Pointer to buffer containing record */ int nn; /* Size of record in bytes */ int szLeaf; /* Size of leaf without page-index */ }; /* ** One object per %_data table. ** ** nContentlessDelete: ** The number of contentless delete operations since the most recent ** call to fts5IndexFlush() or fts5IndexDiscardData(). This is tracked ** so that extra auto-merge work can be done by fts5IndexFlush() to ** account for the delete operations. */ struct Fts5Index { Fts5Config *pConfig; /* Virtual table configuration */ char *zDataTbl; /* Name of %_data table */ int nWorkUnit; /* Leaf pages in a "unit" of work */ /* ** Variables related to the accumulation of tokens and doclists within the ** in-memory hash tables before they are flushed to disk. */ Fts5Hash *pHash; /* Hash table for in-memory data */ int nPendingData; /* Current bytes of pending data */ i64 iWriteRowid; /* Rowid for current doc being written */ int bDelete; /* Current write is a delete */ int nContentlessDelete; /* Number of contentless delete ops */ int nPendingRow; /* Number of INSERT in hash table */ /* Error state. */ int rc; /* Current error code */ /* State used by the fts5DataXXX() functions. */ sqlite3_blob *pReader; /* RO incr-blob open on %_data table */ sqlite3_stmt *pWriter; /* "INSERT ... %_data VALUES(?,?)" */ |
︙ | ︙ | |||
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 | int nSize; }; /* ** The contents of the "structure" record for each index are represented ** using an Fts5Structure record in memory. Which uses instances of the ** other Fts5StructureXXX types as components. */ struct Fts5StructureSegment { int iSegid; /* Segment id */ int pgnoFirst; /* First leaf page number in segment */ int pgnoLast; /* Last leaf page number in segment */ }; struct Fts5StructureLevel { int nMerge; /* Number of segments in incr-merge */ int nSeg; /* Total number of segments on level */ Fts5StructureSegment *aSeg; /* Array of segments. aSeg[0] is oldest. */ }; struct Fts5Structure { int nRef; /* Object reference count */ u64 nWriteCounter; /* Total leaves written to level 0 */ int nSegment; /* Total segments in this structure */ int nLevel; /* Number of levels in this index */ Fts5StructureLevel aLevel[1]; /* Array of nLevel level objects */ }; /* ** An object of type Fts5SegWriter is used to write to segments. | > > > > > > > > > > > > > | 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 | int nSize; }; /* ** The contents of the "structure" record for each index are represented ** using an Fts5Structure record in memory. Which uses instances of the ** other Fts5StructureXXX types as components. ** ** nOriginCntr: ** This value is set to non-zero for structure records created for ** contentlessdelete=1 tables only. In that case it represents the ** origin value to apply to the next top-level segment created. */ struct Fts5StructureSegment { int iSegid; /* Segment id */ int pgnoFirst; /* First leaf page number in segment */ int pgnoLast; /* Last leaf page number in segment */ /* contentlessdelete=1 tables only: */ u64 iOrigin1; u64 iOrigin2; int nPgTombstone; /* Number of tombstone hash table pages */ u64 nEntryTombstone; /* Number of tombstone entries that "count" */ u64 nEntry; /* Number of rows in this segment */ }; struct Fts5StructureLevel { int nMerge; /* Number of segments in incr-merge */ int nSeg; /* Total number of segments on level */ Fts5StructureSegment *aSeg; /* Array of segments. aSeg[0] is oldest. */ }; struct Fts5Structure { int nRef; /* Object reference count */ u64 nWriteCounter; /* Total leaves written to level 0 */ u64 nOriginCntr; /* Origin value for next top-level segment */ int nSegment; /* Total segments in this structure */ int nLevel; /* Number of levels in this index */ Fts5StructureLevel aLevel[1]; /* Array of nLevel level objects */ }; /* ** An object of type Fts5SegWriter is used to write to segments. |
︙ | ︙ | |||
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 | ** ** For each rowid on the page corresponding to the current term, the ** corresponding aRowidOffset[] entry is set to the byte offset of the ** start of the "position-list-size" field within the page. ** ** iTermIdx: ** Index of current term on iTermLeafPgno. */ struct Fts5SegIter { Fts5StructureSegment *pSeg; /* Segment to iterate through */ int flags; /* Mask of configuration flags */ int iLeafPgno; /* Current leaf page number */ Fts5Data *pLeaf; /* Current leaf data */ Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */ i64 iLeafOffset; /* Byte offset within current leaf */ /* Next method */ void (*xNext)(Fts5Index*, Fts5SegIter*, int*); /* The page and offset from which the current term was read. The offset ** is the offset of the first rowid in the current doclist. */ int iTermLeafPgno; | > > > > > > > > > | 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 | ** ** For each rowid on the page corresponding to the current term, the ** corresponding aRowidOffset[] entry is set to the byte offset of the ** start of the "position-list-size" field within the page. ** ** iTermIdx: ** Index of current term on iTermLeafPgno. ** ** apTombstone/nTombstone: ** These are used for contentless_delete=1 tables only. When the cursor ** is first allocated, the apTombstone[] array is allocated so that it ** is large enough for all tombstones hash pages associated with the ** segment. The pages themselves are loaded lazily from the database as ** they are required. */ struct Fts5SegIter { Fts5StructureSegment *pSeg; /* Segment to iterate through */ int flags; /* Mask of configuration flags */ int iLeafPgno; /* Current leaf page number */ Fts5Data *pLeaf; /* Current leaf data */ Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */ i64 iLeafOffset; /* Byte offset within current leaf */ Fts5Data **apTombstone; /* Array of tombstone pages */ int nTombstone; /* Next method */ void (*xNext)(Fts5Index*, Fts5SegIter*, int*); /* The page and offset from which the current term was read. The offset ** is the offset of the first rowid in the current doclist. */ int iTermLeafPgno; |
︙ | ︙ | |||
557 558 559 560 561 562 563 564 565 566 567 568 569 570 | aOut[0] = (iVal>>8); aOut[1] = (iVal&0xFF); } static u16 fts5GetU16(const u8 *aIn){ return ((u16)aIn[0] << 8) + aIn[1]; } /* ** Allocate and return a buffer at least nByte bytes in size. ** ** If an OOM error is encountered, return NULL and set the error code in ** the Fts5Index handle passed as the first argument. */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 644 645 646 647 648 649 650 651 652 653 654 655 656 657 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 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 | aOut[0] = (iVal>>8); aOut[1] = (iVal&0xFF); } static u16 fts5GetU16(const u8 *aIn){ return ((u16)aIn[0] << 8) + aIn[1]; } /* ** The only argument points to a buffer at least 8 bytes in size. This ** function interprets the first 8 bytes of the buffer as a 64-bit big-endian ** unsigned integer and returns the result. */ static u64 fts5GetU64(u8 *a){ return ((u64)a[0] << 56) + ((u64)a[1] << 48) + ((u64)a[2] << 40) + ((u64)a[3] << 32) + ((u64)a[4] << 24) + ((u64)a[5] << 16) + ((u64)a[6] << 8) + ((u64)a[7] << 0); } /* ** The only argument points to a buffer at least 4 bytes in size. This ** function interprets the first 4 bytes of the buffer as a 32-bit big-endian ** unsigned integer and returns the result. */ static u32 fts5GetU32(const u8 *a){ return ((u32)a[0] << 24) + ((u32)a[1] << 16) + ((u32)a[2] << 8) + ((u32)a[3] << 0); } /* ** Write iVal, formated as a 64-bit big-endian unsigned integer, to the ** buffer indicated by the first argument. */ static void fts5PutU64(u8 *a, u64 iVal){ a[0] = ((iVal >> 56) & 0xFF); a[1] = ((iVal >> 48) & 0xFF); a[2] = ((iVal >> 40) & 0xFF); a[3] = ((iVal >> 32) & 0xFF); a[4] = ((iVal >> 24) & 0xFF); a[5] = ((iVal >> 16) & 0xFF); a[6] = ((iVal >> 8) & 0xFF); a[7] = ((iVal >> 0) & 0xFF); } /* ** Write iVal, formated as a 32-bit big-endian unsigned integer, to the ** buffer indicated by the first argument. */ static void fts5PutU32(u8 *a, u32 iVal){ a[0] = ((iVal >> 24) & 0xFF); a[1] = ((iVal >> 16) & 0xFF); a[2] = ((iVal >> 8) & 0xFF); a[3] = ((iVal >> 0) & 0xFF); } /* ** Allocate and return a buffer at least nByte bytes in size. ** ** If an OOM error is encountered, return NULL and set the error code in ** the Fts5Index handle passed as the first argument. */ |
︙ | ︙ | |||
785 786 787 788 789 790 791 | sqlite3_step(p->pDeleter); p->rc = sqlite3_reset(p->pDeleter); } /* ** Remove all records associated with segment iSegid. */ | | > > > > > > > | 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 | sqlite3_step(p->pDeleter); p->rc = sqlite3_reset(p->pDeleter); } /* ** Remove all records associated with segment iSegid. */ static void fts5DataRemoveSegment(Fts5Index *p, Fts5StructureSegment *pSeg){ int iSegid = pSeg->iSegid; i64 iFirst = FTS5_SEGMENT_ROWID(iSegid, 0); i64 iLast = FTS5_SEGMENT_ROWID(iSegid+1, 0)-1; fts5DataDelete(p, iFirst, iLast); if( pSeg->nPgTombstone ){ i64 iTomb1 = FTS5_TOMBSTONE_ROWID(iSegid, 0); i64 iTomb2 = FTS5_TOMBSTONE_ROWID(iSegid, pSeg->nPgTombstone-1); fts5DataDelete(p, iTomb1, iTomb2); } if( p->pIdxDeleter==0 ){ Fts5Config *pConfig = p->pConfig; fts5IndexPrepareStmt(p, &p->pIdxDeleter, sqlite3_mprintf( "DELETE FROM '%q'.'%q_idx' WHERE segid=?", pConfig->zDb, pConfig->zName )); } |
︙ | ︙ | |||
899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 | int rc = SQLITE_OK; int i = 0; int iLvl; int nLevel = 0; int nSegment = 0; sqlite3_int64 nByte; /* Bytes of space to allocate at pRet */ Fts5Structure *pRet = 0; /* Structure object to return */ /* Grab the cookie value */ if( piCookie ) *piCookie = sqlite3Fts5Get32(pData); i = 4; /* Read the total number of levels and segments from the start of the ** structure record. */ i += fts5GetVarint32(&pData[i], nLevel); i += fts5GetVarint32(&pData[i], nSegment); if( nLevel>FTS5_MAX_SEGMENT || nLevel<0 || nSegment>FTS5_MAX_SEGMENT || nSegment<0 | > > > > > > > > | 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 | int rc = SQLITE_OK; int i = 0; int iLvl; int nLevel = 0; int nSegment = 0; sqlite3_int64 nByte; /* Bytes of space to allocate at pRet */ Fts5Structure *pRet = 0; /* Structure object to return */ int bStructureV2 = 0; /* True for FTS5_STRUCTURE_V2 */ u64 nOriginCntr = 0; /* Largest origin value seen so far */ /* Grab the cookie value */ if( piCookie ) *piCookie = sqlite3Fts5Get32(pData); i = 4; /* Check if this is a V2 structure record. Set bStructureV2 if it is. */ if( 0==memcmp(&pData[i], FTS5_STRUCTURE_V2, 4) ){ i += 4; bStructureV2 = 1; } /* Read the total number of levels and segments from the start of the ** structure record. */ i += fts5GetVarint32(&pData[i], nLevel); i += fts5GetVarint32(&pData[i], nSegment); if( nLevel>FTS5_MAX_SEGMENT || nLevel<0 || nSegment>FTS5_MAX_SEGMENT || nSegment<0 |
︙ | ︙ | |||
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 | rc = FTS5_CORRUPT; break; } assert( pSeg!=0 ); i += fts5GetVarint32(&pData[i], pSeg->iSegid); i += fts5GetVarint32(&pData[i], pSeg->pgnoFirst); i += fts5GetVarint32(&pData[i], pSeg->pgnoLast); if( pSeg->pgnoLast<pSeg->pgnoFirst ){ rc = FTS5_CORRUPT; break; } } if( iLvl>0 && pLvl[-1].nMerge && nTotal==0 ) rc = FTS5_CORRUPT; if( iLvl==nLevel-1 && pLvl->nMerge ) rc = FTS5_CORRUPT; } } if( nSegment!=0 && rc==SQLITE_OK ) rc = FTS5_CORRUPT; if( rc!=SQLITE_OK ){ fts5StructureRelease(pRet); pRet = 0; } } | > > > > > > > > > > > | 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 | rc = FTS5_CORRUPT; break; } assert( pSeg!=0 ); i += fts5GetVarint32(&pData[i], pSeg->iSegid); i += fts5GetVarint32(&pData[i], pSeg->pgnoFirst); i += fts5GetVarint32(&pData[i], pSeg->pgnoLast); if( bStructureV2 ){ i += fts5GetVarint(&pData[i], &pSeg->iOrigin1); i += fts5GetVarint(&pData[i], &pSeg->iOrigin2); i += fts5GetVarint32(&pData[i], pSeg->nPgTombstone); i += fts5GetVarint(&pData[i], &pSeg->nEntryTombstone); i += fts5GetVarint(&pData[i], &pSeg->nEntry); nOriginCntr = MAX(nOriginCntr, pSeg->iOrigin2); } if( pSeg->pgnoLast<pSeg->pgnoFirst ){ rc = FTS5_CORRUPT; break; } } if( iLvl>0 && pLvl[-1].nMerge && nTotal==0 ) rc = FTS5_CORRUPT; if( iLvl==nLevel-1 && pLvl->nMerge ) rc = FTS5_CORRUPT; } } if( nSegment!=0 && rc==SQLITE_OK ) rc = FTS5_CORRUPT; if( bStructureV2 ){ pRet->nOriginCntr = nOriginCntr+1; } if( rc!=SQLITE_OK ){ fts5StructureRelease(pRet); pRet = 0; } } |
︙ | ︙ | |||
1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 | ** error has already occurred, this function is a no-op. */ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ if( p->rc==SQLITE_OK ){ Fts5Buffer buf; /* Buffer to serialize record into */ int iLvl; /* Used to iterate through levels */ int iCookie; /* Cookie value to store */ assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) ); memset(&buf, 0, sizeof(Fts5Buffer)); /* Append the current configuration cookie */ iCookie = p->pConfig->iCookie; if( iCookie<0 ) iCookie = 0; | > | > > > > | | | > > > > > > > | 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 | ** error has already occurred, this function is a no-op. */ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ if( p->rc==SQLITE_OK ){ Fts5Buffer buf; /* Buffer to serialize record into */ int iLvl; /* Used to iterate through levels */ int iCookie; /* Cookie value to store */ int nHdr = (pStruct->nOriginCntr>0 ? (4+4+9+9+9) : (4+9+9)); assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) ); memset(&buf, 0, sizeof(Fts5Buffer)); /* Append the current configuration cookie */ iCookie = p->pConfig->iCookie; if( iCookie<0 ) iCookie = 0; if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, nHdr) ){ sqlite3Fts5Put32(buf.p, iCookie); buf.n = 4; if( pStruct->nOriginCntr>0 ){ fts5BufferSafeAppendBlob(&buf, FTS5_STRUCTURE_V2, 4); } fts5BufferSafeAppendVarint(&buf, pStruct->nLevel); fts5BufferSafeAppendVarint(&buf, pStruct->nSegment); fts5BufferSafeAppendVarint(&buf, (i64)pStruct->nWriteCounter); } for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){ int iSeg; /* Used to iterate through segments */ Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl]; fts5BufferAppendVarint(&p->rc, &buf, pLvl->nMerge); fts5BufferAppendVarint(&p->rc, &buf, pLvl->nSeg); assert( pLvl->nMerge<=pLvl->nSeg ); for(iSeg=0; iSeg<pLvl->nSeg; iSeg++){ Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; fts5BufferAppendVarint(&p->rc, &buf, pSeg->iSegid); fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoFirst); fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoLast); if( pStruct->nOriginCntr>0 ){ fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin1); fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin2); fts5BufferAppendVarint(&p->rc, &buf, pSeg->nPgTombstone); fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntryTombstone); fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntry); } } } fts5DataWrite(p, FTS5_STRUCTURE_ROWID, buf.p, buf.n); fts5BufferFree(&buf); } } |
︙ | ︙ | |||
1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 | pIter->xNext = fts5SegIterNext_Reverse; }else if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ pIter->xNext = fts5SegIterNext_None; }else{ pIter->xNext = fts5SegIterNext; } } /* ** Initialize the iterator object pIter to iterate through the entries in ** segment pSeg. The iterator is left pointing to the first entry when ** this function returns. ** ** If an error occurs, Fts5Index.rc is set to an appropriate error code. If | > > > > > > > > > > > > > > > > > | 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 | pIter->xNext = fts5SegIterNext_Reverse; }else if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ pIter->xNext = fts5SegIterNext_None; }else{ pIter->xNext = fts5SegIterNext; } } /* ** Allocate a tombstone hash page array (pIter->apTombstone) for the ** iterator passed as the second argument. If an OOM error occurs, leave ** an error in the Fts5Index object. */ static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){ const int nTomb = pIter->pSeg->nPgTombstone; if( nTomb>0 ){ Fts5Data **apTomb = 0; apTomb = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data)*nTomb); if( apTomb ){ pIter->apTombstone = apTomb; pIter->nTombstone = nTomb; } } } /* ** Initialize the iterator object pIter to iterate through the entries in ** segment pSeg. The iterator is left pointing to the first entry when ** this function returns. ** ** If an error occurs, Fts5Index.rc is set to an appropriate error code. If |
︙ | ︙ | |||
1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 | 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); } } /* ** This function is only ever called on iterators created by calls to ** Fts5IndexQuery() with the FTS5INDEX_QUERY_DESC flag set. ** | > | 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 | 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); fts5SegIterAllocTombstone(p, pIter); } } /* ** This function is only ever called on iterators created by calls to ** Fts5IndexQuery() with the FTS5INDEX_QUERY_DESC flag set. ** |
︙ | ︙ | |||
2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 | if( flags & FTS5INDEX_QUERY_DESC ){ fts5SegIterReverse(p, pIter); } } } fts5SegIterSetNext(p, pIter); /* Either: ** ** 1) an error has occurred, or ** 2) the iterator points to EOF, or ** 3) the iterator points to an entry with term (pTerm/nTerm), or ** 4) the FTS5INDEX_QUERY_SCAN flag was set and the iterator points | > | 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 | if( flags & FTS5INDEX_QUERY_DESC ){ fts5SegIterReverse(p, pIter); } } } fts5SegIterSetNext(p, pIter); fts5SegIterAllocTombstone(p, pIter); /* Either: ** ** 1) an error has occurred, or ** 2) the iterator points to EOF, or ** 3) the iterator points to an entry with term (pTerm/nTerm), or ** 4) the FTS5INDEX_QUERY_SCAN flag was set and the iterator points |
︙ | ︙ | |||
2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 | }else{ fts5SegIterLoadNPos(p, pIter); } } fts5SegIterSetNext(p, pIter); } /* ** Zero the iterator passed as the only argument. */ static void fts5SegIterClear(Fts5SegIter *pIter){ fts5BufferFree(&pIter->term); fts5DataRelease(pIter->pLeaf); fts5DataRelease(pIter->pNextLeaf); fts5DlidxIterFree(pIter->pDlidx); sqlite3_free(pIter->aRowidOffset); memset(pIter, 0, sizeof(Fts5SegIter)); } #ifdef SQLITE_DEBUG | > > > > > > > > > > > > > > > | 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 | }else{ fts5SegIterLoadNPos(p, pIter); } } fts5SegIterSetNext(p, pIter); } /* ** Array ap[] contains n elements. Release each of these elements using ** fts5DataRelease(). Then free the array itself using sqlite3_free(). */ static void fts5IndexFreeArray(Fts5Data **ap, int n){ if( ap ){ int ii; for(ii=0; ii<n; ii++){ fts5DataRelease(ap[ii]); } sqlite3_free(ap); } } /* ** Zero the iterator passed as the only argument. */ static void fts5SegIterClear(Fts5SegIter *pIter){ fts5BufferFree(&pIter->term); fts5DataRelease(pIter->pLeaf); fts5DataRelease(pIter->pNextLeaf); fts5IndexFreeArray(pIter->apTombstone, pIter->nTombstone); fts5DlidxIterFree(pIter->pDlidx); sqlite3_free(pIter->aRowidOffset); memset(pIter, 0, sizeof(Fts5SegIter)); } #ifdef SQLITE_DEBUG |
︙ | ︙ | |||
2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 | ** Set the pIter->bEof variable based on the state of the sub-iterators. */ static void fts5MultiIterSetEof(Fts5Iter *pIter){ Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; pIter->base.bEof = pSeg->pLeaf==0; pIter->iSwitchRowid = pSeg->iRowid; } /* ** Move the iterator to the next entry. ** ** If an error occurs, an error code is left in Fts5Index.rc. It is not ** considered an error if the iterator reaches EOF, or if it is already at ** EOF when this function is called. | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 | ** Set the pIter->bEof variable based on the state of the sub-iterators. */ static void fts5MultiIterSetEof(Fts5Iter *pIter){ Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ]; pIter->base.bEof = pSeg->pLeaf==0; pIter->iSwitchRowid = pSeg->iRowid; } /* ** The argument to this macro must be an Fts5Data structure containing a ** tombstone hash page. This macro returns the key-size of the hash-page. */ #define TOMBSTONE_KEYSIZE(pPg) (pPg->p[0]==4 ? 4 : 8) #define TOMBSTONE_NSLOT(pPg) \ ((pPg->nn > 16) ? ((pPg->nn-8) / TOMBSTONE_KEYSIZE(pPg)) : 1) /* ** Query a single tombstone hash table for rowid iRowid. Return true if ** it is found or false otherwise. The tombstone hash table is one of ** nHashTable tables. */ static int fts5IndexTombstoneQuery( Fts5Data *pHash, /* Hash table page to query */ int nHashTable, /* Number of pages attached to segment */ u64 iRowid /* Rowid to query hash for */ ){ const int szKey = TOMBSTONE_KEYSIZE(pHash); const int nSlot = TOMBSTONE_NSLOT(pHash); int iSlot = (iRowid / nHashTable) % nSlot; int nCollide = nSlot; if( iRowid==0 ){ return pHash->p[1]; }else if( szKey==4 ){ u32 *aSlot = (u32*)&pHash->p[8]; while( aSlot[iSlot] ){ if( fts5GetU32((u8*)&aSlot[iSlot])==iRowid ) return 1; if( nCollide--==0 ) break; iSlot = (iSlot+1)%nSlot; } }else{ u64 *aSlot = (u64*)&pHash->p[8]; while( aSlot[iSlot] ){ if( fts5GetU64((u8*)&aSlot[iSlot])==iRowid ) return 1; if( nCollide--==0 ) break; iSlot = (iSlot+1)%nSlot; } } return 0; } /* ** Return true if the iterator passed as the only argument points ** to an segment entry for which there is a tombstone. Return false ** if there is no tombstone or if the iterator is already at EOF. */ static int fts5MultiIterIsDeleted(Fts5Iter *pIter){ int iFirst = pIter->aFirst[1].iFirst; Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; if( pSeg->pLeaf && pSeg->nTombstone ){ /* Figure out which page the rowid might be present on. */ int iPg = ((u64)pSeg->iRowid) % pSeg->nTombstone; assert( iPg>=0 ); /* If tombstone hash page iPg has not yet been loaded from the ** database, load it now. */ if( pSeg->apTombstone[iPg]==0 ){ pSeg->apTombstone[iPg] = fts5DataRead(pIter->pIndex, FTS5_TOMBSTONE_ROWID(pSeg->pSeg->iSegid, iPg) ); if( pSeg->apTombstone[iPg]==0 ) return 0; } return fts5IndexTombstoneQuery( pSeg->apTombstone[iPg], pSeg->nTombstone, pSeg->iRowid ); } return 0; } /* ** Move the iterator to the next entry. ** ** If an error occurs, an error code is left in Fts5Index.rc. It is not ** considered an error if the iterator reaches EOF, or if it is already at ** EOF when this function is called. |
︙ | ︙ | |||
2928 2929 2930 2931 2932 2933 2934 | fts5MultiIterSetEof(pIter); pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; if( pSeg->pLeaf==0 ) return; } fts5AssertMultiIterSetup(p, pIter); assert( pSeg==&pIter->aSeg[pIter->aFirst[1].iFirst] && pSeg->pLeaf ); | | > > | 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 | fts5MultiIterSetEof(pIter); pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; if( pSeg->pLeaf==0 ) return; } fts5AssertMultiIterSetup(p, pIter); assert( pSeg==&pIter->aSeg[pIter->aFirst[1].iFirst] && pSeg->pLeaf ); if( (pIter->bSkipEmpty==0 || pSeg->nPos) && 0==fts5MultiIterIsDeleted(pIter) ){ pIter->xSetOutputs(pIter, pSeg); return; } bUseFrom = 0; } } |
︙ | ︙ | |||
2960 2961 2962 2963 2964 2965 2966 | ){ fts5MultiIterAdvanced(p, pIter, iFirst, 1); fts5MultiIterSetEof(pIter); *pbNewTerm = 1; } fts5AssertMultiIterSetup(p, pIter); | | > > | 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 | ){ fts5MultiIterAdvanced(p, pIter, iFirst, 1); fts5MultiIterSetEof(pIter); *pbNewTerm = 1; } fts5AssertMultiIterSetup(p, pIter); }while( (fts5MultiIterIsEmpty(p, pIter) || fts5MultiIterIsDeleted(pIter)) && (p->rc==SQLITE_OK) ); } } static void fts5IterSetOutputs_Noop(Fts5Iter *pUnused1, Fts5SegIter *pUnused2){ UNUSED_PARAM2(pUnused1, pUnused2); } |
︙ | ︙ | |||
3515 3516 3517 3518 3519 3520 3521 | if( p->rc==SQLITE_OK ) pSeg->xNext(p, pSeg, 0); fts5MultiIterAdvanced(p, pNew, iEq, iIter); } } fts5MultiIterSetEof(pNew); fts5AssertMultiIterSetup(p, pNew); | | > > | 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 | if( p->rc==SQLITE_OK ) pSeg->xNext(p, pSeg, 0); fts5MultiIterAdvanced(p, pNew, iEq, iIter); } } fts5MultiIterSetEof(pNew); fts5AssertMultiIterSetup(p, pNew); if( (pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew)) || fts5MultiIterIsDeleted(pNew) ){ fts5MultiIterNext(p, pNew, 0, 0); }else if( pNew->base.bEof==0 ){ Fts5SegIter *pSeg = &pNew->aSeg[pNew->aFirst[1].iFirst]; pNew->xSetOutputs(pNew, pSeg); } }else{ |
︙ | ︙ | |||
3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 | ** Discard all data currently cached in the hash-tables. */ static void fts5IndexDiscardData(Fts5Index *p){ assert( p->pHash || p->nPendingData==0 ); if( p->pHash ){ sqlite3Fts5HashClear(p->pHash); p->nPendingData = 0; } } /* ** Return the size of the prefix, in bytes, that buffer ** (pNew/<length-unknown>) shares with buffer (pOld/nOld). ** ** Buffer (pNew/<length-unknown>) is guaranteed to be greater | > > | 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 | ** Discard all data currently cached in the hash-tables. */ static void fts5IndexDiscardData(Fts5Index *p){ assert( p->pHash || p->nPendingData==0 ); if( p->pHash ){ sqlite3Fts5HashClear(p->pHash); p->nPendingData = 0; p->nPendingRow = 0; } p->nContentlessDelete = 0; } /* ** Return the size of the prefix, in bytes, that buffer ** (pNew/<length-unknown>) shares with buffer (pOld/nOld). ** ** Buffer (pNew/<length-unknown>) is guaranteed to be greater |
︙ | ︙ | |||
4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 | pLvlOut->nSeg++; pSeg->pgnoFirst = 1; pSeg->iSegid = iSegid; pStruct->nSegment++; /* Read input from all segments in the input level */ nInput = pLvl->nSeg; } bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2); assert( iLvl>=0 ); for(fts5MultiIterNew(p, pStruct, flags, 0, 0, 0, iLvl, nInput, &pIter); fts5MultiIterEof(p, pIter)==0; fts5MultiIterNext(p, pIter, 0, 0) | > > > > > > | 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 | pLvlOut->nSeg++; pSeg->pgnoFirst = 1; pSeg->iSegid = iSegid; pStruct->nSegment++; /* Read input from all segments in the input level */ nInput = pLvl->nSeg; /* Set the range of origins that will go into the output segment. */ if( pStruct->nOriginCntr>0 ){ pSeg->iOrigin1 = pLvl->aSeg[0].iOrigin1; pSeg->iOrigin2 = pLvl->aSeg[pLvl->nSeg-1].iOrigin2; } } bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2); assert( iLvl>=0 ); for(fts5MultiIterNew(p, pStruct, flags, 0, 0, 0, iLvl, nInput, &pIter); fts5MultiIterEof(p, pIter)==0; fts5MultiIterNext(p, pIter, 0, 0) |
︙ | ︙ | |||
4389 4390 4391 4392 4393 4394 4395 4396 | fts5WriteFinish(p, &writer, &pSeg->pgnoLast); assert( pIter!=0 || p->rc!=SQLITE_OK ); if( fts5MultiIterEof(p, pIter) ){ int i; /* Remove the redundant segments from the %_data table */ for(i=0; i<nInput; i++){ | > > > | | 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 | fts5WriteFinish(p, &writer, &pSeg->pgnoLast); assert( pIter!=0 || p->rc!=SQLITE_OK ); if( fts5MultiIterEof(p, pIter) ){ int i; /* Remove the redundant segments from the %_data table */ assert( pSeg->nEntry==0 ); for(i=0; i<nInput; i++){ Fts5StructureSegment *pOld = &pLvl->aSeg[i]; pSeg->nEntry += (pOld->nEntry - pOld->nEntryTombstone); fts5DataRemoveSegment(p, pOld); } /* Remove the redundant segments from the input level */ if( pLvl->nSeg!=nInput ){ int nMove = (pLvl->nSeg - nInput) * sizeof(Fts5StructureSegment); memmove(pLvl->aSeg, &pLvl->aSeg[nInput], nMove); } |
︙ | ︙ | |||
4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 | pLvl->nMerge = nInput; } fts5MultiIterFree(pIter); fts5BufferFree(&term); if( pnRem ) *pnRem -= writer.nLeafWritten; } /* ** Do up to nPg pages of automerge work on the index. ** ** Return true if any changes were actually made, or false otherwise. */ static int fts5IndexMerge( Fts5Index *p, /* FTS5 backend object */ Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */ int nPg, /* Pages of work to do */ int nMin /* Minimum number of segments to merge */ ){ int nRem = nPg; int bRet = 0; Fts5Structure *pStruct = *ppStruct; while( nRem>0 && p->rc==SQLITE_OK ){ int iLvl; /* To iterate through levels */ int iBestLvl = 0; /* Level offering the most input segments */ int nBest = 0; /* Number of input segments on best level */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | | < < < | < < | < > > | 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 | pLvl->nMerge = nInput; } fts5MultiIterFree(pIter); fts5BufferFree(&term); if( pnRem ) *pnRem -= writer.nLeafWritten; } /* ** If this is not a contentless_delete=1 table, or if the 'deletemerge' ** configuration option is set to 0, then this function always returns -1. ** Otherwise, it searches the structure object passed as the second argument ** for a level suitable for merging due to having a large number of ** tombstones in the tombstone hash. If one is found, its index is returned. ** Otherwise, if there is no suitable level, -1. */ static int fts5IndexFindDeleteMerge(Fts5Index *p, Fts5Structure *pStruct){ Fts5Config *pConfig = p->pConfig; int iRet = -1; if( pConfig->bContentlessDelete && pConfig->nDeleteMerge>0 ){ int ii; int nBest = 0; for(ii=0; ii<pStruct->nLevel; ii++){ Fts5StructureLevel *pLvl = &pStruct->aLevel[ii]; i64 nEntry = 0; i64 nTomb = 0; int iSeg; for(iSeg=0; iSeg<pLvl->nSeg; iSeg++){ nEntry += pLvl->aSeg[iSeg].nEntry; nTomb += pLvl->aSeg[iSeg].nEntryTombstone; } assert( nEntry>0 || pLvl->nSeg==0 ); if( nEntry>0 ){ int nPercent = (nTomb * 100) / nEntry; if( nPercent>=pConfig->nDeleteMerge && nPercent>nBest ){ iRet = ii; nBest = nPercent; } } } } return iRet; } /* ** Do up to nPg pages of automerge work on the index. ** ** Return true if any changes were actually made, or false otherwise. */ static int fts5IndexMerge( Fts5Index *p, /* FTS5 backend object */ Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */ int nPg, /* Pages of work to do */ int nMin /* Minimum number of segments to merge */ ){ int nRem = nPg; int bRet = 0; Fts5Structure *pStruct = *ppStruct; while( nRem>0 && p->rc==SQLITE_OK ){ int iLvl; /* To iterate through levels */ int iBestLvl = 0; /* Level offering the most input segments */ int nBest = 0; /* Number of input segments on best level */ /* Set iBestLvl to the level to read input segments from. Or to -1 if ** there is no level suitable to merge segments from. */ assert( pStruct->nLevel>0 ); for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){ Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl]; if( pLvl->nMerge ){ if( pLvl->nMerge>nBest ){ iBestLvl = iLvl; nBest = nMin; } break; } if( pLvl->nSeg>nBest ){ nBest = pLvl->nSeg; iBestLvl = iLvl; } } if( nBest<nMin ){ iBestLvl = fts5IndexFindDeleteMerge(p, pStruct); } if( iBestLvl<0 ) break; bRet = 1; fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem); if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){ fts5StructurePromote(p, iBestLvl+1, pStruct); } if( nMin==1 ) nMin = 2; } *ppStruct = pStruct; return bRet; } /* ** A total of nLeaf leaf pages of data has just been flushed to a level-0 |
︙ | ︙ | |||
4979 4980 4981 4982 4983 4984 4985 | Fts5Structure *pStruct; int iSegid; int pgnoLast = 0; /* Last leaf page number in segment */ /* Obtain a reference to the index structure and allocate a new segment-id ** for the new level-0 segment. */ pStruct = fts5StructureRead(p); | < > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > | | | | | | > | > | < > > | | | > | | | | > > > > > | 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 | Fts5Structure *pStruct; int iSegid; int pgnoLast = 0; /* Last leaf page number in segment */ /* Obtain a reference to the index structure and allocate a new segment-id ** for the new level-0 segment. */ pStruct = fts5StructureRead(p); fts5StructureInvalidate(p); if( sqlite3Fts5HashIsEmpty(pHash)==0 ){ iSegid = fts5AllocateSegid(p, pStruct); 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); pBuf = &writer.writer.buf; pPgidx = &writer.writer.pgidx; /* fts5WriteInit() should have initialized the buffers to (most likely) ** the maximum space required. */ assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) ); assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) ); /* 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( p->rc!=SQLITE_OK || 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; iOff++; } } if( (pBuf->n + pPgidx->n)>=pgsz ){ fts5WriteFlushLeaf(p, &writer); } }else{ int bDummy; int nPos; int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy); nCopy += nPos; if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ /* The entire poslist will fit on the current leaf. So copy ** it in one go. */ fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); }else{ /* The entire poslist will not fit on this leaf. So it needs ** to be broken into sections. The only qualification being ** that each varint must be stored contiguously. */ const u8 *pPoslist = &pDoclist[iOff]; int iPos = 0; while( p->rc==SQLITE_OK ){ int nSpace = pgsz - pBuf->n - pPgidx->n; int n = 0; if( (nCopy - iPos)<=nSpace ){ n = nCopy - iPos; }else{ n = fts5PoslistPrefix(&pPoslist[iPos], nSpace); } assert( n>0 ); fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n); iPos += n; if( (pBuf->n + pPgidx->n)>=pgsz ){ fts5WriteFlushLeaf(p, &writer); } if( iPos>=nCopy ) break; } } iOff += nCopy; } } } /* TODO2: Doclist terminator written here. */ /* 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; if( pStruct->nOriginCntr>0 ){ pSeg->iOrigin1 = pStruct->nOriginCntr; pSeg->iOrigin2 = pStruct->nOriginCntr; pSeg->nEntry = p->nPendingRow; pStruct->nOriginCntr++; } pStruct->nSegment++; } fts5StructurePromote(p, 0, pStruct); } } } fts5IndexAutomerge(p, &pStruct, pgnoLast + p->nContentlessDelete); fts5IndexCrisismerge(p, &pStruct); fts5StructureWrite(p, pStruct); fts5StructureRelease(pStruct); p->nContentlessDelete = 0; } /* ** Flush any data stored in the in-memory hash tables to the database. */ static void fts5IndexFlush(Fts5Index *p){ /* Unless it is empty, flush the hash table to disk */ if( p->nPendingData || p->nContentlessDelete ){ assert( p->pHash ); fts5FlushOneHash(p); p->nPendingData = 0; p->nPendingRow = 0; } } static Fts5Structure *fts5IndexOptimizeStruct( Fts5Index *p, Fts5Structure *pStruct ){ Fts5Structure *pNew = 0; sqlite3_int64 nByte = sizeof(Fts5Structure); int nSeg = pStruct->nSegment; int i; /* Figure out if this structure requires optimization. A structure does ** not require optimization if either: ** ** 1. it consists of fewer than two segments, or ** 2. all segments are on the same level, or ** 3. all segments except one are currently inputs to a merge operation. ** ** In the first case, if there are no tombstone hash pages, return NULL. In ** the second, increment the ref-count on *pStruct and return a copy of the ** pointer to it. */ if( nSeg==0 ) return 0; for(i=0; i<pStruct->nLevel; i++){ int nThis = pStruct->aLevel[i].nSeg; int nMerge = pStruct->aLevel[i].nMerge; if( nThis>0 && (nThis==nSeg || (nThis==nSeg-1 && nMerge==nThis)) ){ if( nSeg==1 && nThis==1 && pStruct->aLevel[i].aSeg[0].nPgTombstone==0 ){ return 0; } fts5StructureRef(pStruct); return pStruct; } assert( pStruct->aLevel[i].nMerge<=nThis ); } nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel); pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte); if( pNew ){ Fts5StructureLevel *pLvl; nByte = nSeg * sizeof(Fts5StructureSegment); pNew->nLevel = MIN(pStruct->nLevel+1, FTS5_MAX_LEVEL); pNew->nRef = 1; pNew->nWriteCounter = pStruct->nWriteCounter; pNew->nOriginCntr = pStruct->nOriginCntr; pLvl = &pNew->aLevel[pNew->nLevel-1]; pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte); if( pLvl->aSeg ){ int iLvl, iSeg; int iSegOut = 0; /* Iterate through all segments, from oldest to newest. Add them to ** the new Fts5Level object so that pLvl->aSeg[0] is the oldest |
︙ | ︙ | |||
5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 | int sqlite3Fts5IndexOptimize(Fts5Index *p){ Fts5Structure *pStruct; Fts5Structure *pNew = 0; assert( p->rc==SQLITE_OK ); fts5IndexFlush(p); pStruct = fts5StructureRead(p); fts5StructureInvalidate(p); if( pStruct ){ pNew = fts5IndexOptimizeStruct(p, pStruct); } fts5StructureRelease(pStruct); | > | 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 | int sqlite3Fts5IndexOptimize(Fts5Index *p){ Fts5Structure *pStruct; Fts5Structure *pNew = 0; assert( p->rc==SQLITE_OK ); fts5IndexFlush(p); assert( p->nContentlessDelete==0 ); pStruct = fts5StructureRead(p); fts5StructureInvalidate(p); if( pStruct ){ pNew = fts5IndexOptimizeStruct(p, pStruct); } fts5StructureRelease(pStruct); |
︙ | ︙ | |||
5271 5272 5273 5274 5275 5276 5277 | } /* ** This is called to implement the special "VALUES('merge', $nMerge)" ** INSERT command. */ int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ | | > > > | | 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 | } /* ** This is called to implement the special "VALUES('merge', $nMerge)" ** INSERT command. */ int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ Fts5Structure *pStruct = 0; fts5IndexFlush(p); pStruct = fts5StructureRead(p); if( pStruct ){ int nMin = p->pConfig->nUsermerge; fts5StructureInvalidate(p); if( nMerge<0 ){ Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct); fts5StructureRelease(pStruct); pStruct = pNew; nMin = 1; nMerge = nMerge*-1; } if( pStruct && pStruct->nLevel ){ if( fts5IndexMerge(p, &pStruct, nMerge, nMin) ){ fts5StructureWrite(p, pStruct); } } |
︙ | ︙ | |||
5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 | || (p->nPendingData > p->pConfig->nHashSize) ){ fts5IndexFlush(p); } p->iWriteRowid = iRowid; p->bDelete = bDelete; return fts5IndexReturn(p); } /* ** Commit data to disk. */ int sqlite3Fts5IndexSync(Fts5Index *p){ | > > > | 6155 6156 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 6170 6171 | || (p->nPendingData > p->pConfig->nHashSize) ){ fts5IndexFlush(p); } p->iWriteRowid = iRowid; p->bDelete = bDelete; if( bDelete==0 ){ p->nPendingRow++; } return fts5IndexReturn(p); } /* ** Commit data to disk. */ int sqlite3Fts5IndexSync(Fts5Index *p){ |
︙ | ︙ | |||
5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 | ** and the initial version of the "averages" record (a zero-byte blob). */ int sqlite3Fts5IndexReinit(Fts5Index *p){ Fts5Structure s; fts5StructureInvalidate(p); fts5IndexDiscardData(p); memset(&s, 0, sizeof(Fts5Structure)); fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0); fts5StructureWrite(p, &s); return fts5IndexReturn(p); } /* ** Open a new Fts5Index handle. If the bCreate argument is true, create | > > > | 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 | ** and the initial version of the "averages" record (a zero-byte blob). */ int sqlite3Fts5IndexReinit(Fts5Index *p){ Fts5Structure s; fts5StructureInvalidate(p); fts5IndexDiscardData(p); memset(&s, 0, sizeof(Fts5Structure)); if( p->pConfig->bContentlessDelete ){ s.nOriginCntr = 1; } fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0); fts5StructureWrite(p, &s); return fts5IndexReturn(p); } /* ** Open a new Fts5Index handle. If the bCreate argument is true, create |
︙ | ︙ | |||
6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 | int sqlite3Fts5IndexLoadConfig(Fts5Index *p){ Fts5Structure *pStruct; pStruct = fts5StructureRead(p); fts5StructureRelease(pStruct); return fts5IndexReturn(p); } /************************************************************************* ************************************************************************** ** Below this point is the implementation of the integrity-check ** functionality. */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 6589 6590 6591 6592 6593 6594 6595 6596 6597 6598 6599 6600 6601 6602 6603 6604 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627 6628 6629 6630 6631 6632 6633 6634 6635 6636 6637 6638 6639 6640 6641 6642 6643 6644 6645 6646 6647 6648 6649 6650 6651 6652 6653 6654 6655 6656 6657 6658 6659 6660 6661 6662 6663 6664 6665 6666 6667 6668 6669 6670 6671 6672 6673 6674 6675 6676 6677 6678 6679 6680 6681 6682 6683 6684 6685 6686 6687 6688 6689 6690 6691 6692 6693 6694 6695 6696 6697 6698 6699 6700 6701 6702 6703 6704 6705 6706 6707 6708 6709 6710 6711 6712 6713 6714 6715 6716 6717 6718 6719 6720 6721 6722 6723 6724 6725 6726 6727 6728 6729 6730 6731 6732 6733 6734 6735 6736 6737 6738 6739 6740 6741 6742 6743 6744 6745 6746 6747 6748 6749 6750 6751 6752 6753 6754 6755 6756 6757 6758 6759 6760 6761 6762 6763 6764 6765 6766 6767 6768 6769 6770 6771 6772 6773 6774 6775 6776 6777 6778 6779 6780 6781 6782 6783 6784 6785 6786 6787 6788 6789 6790 6791 6792 6793 6794 6795 6796 6797 6798 6799 6800 6801 6802 6803 6804 6805 6806 6807 6808 6809 6810 6811 6812 6813 6814 6815 6816 6817 6818 6819 6820 6821 6822 6823 6824 6825 6826 6827 6828 6829 6830 6831 6832 6833 6834 6835 6836 6837 6838 6839 6840 6841 6842 6843 6844 6845 6846 6847 6848 6849 6850 6851 6852 6853 6854 6855 6856 6857 6858 6859 6860 6861 6862 6863 6864 6865 6866 6867 6868 6869 6870 6871 6872 6873 6874 6875 6876 6877 6878 6879 6880 6881 6882 6883 6884 6885 6886 6887 6888 6889 6890 6891 6892 6893 6894 6895 6896 6897 6898 6899 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909 6910 6911 6912 6913 6914 6915 6916 6917 6918 6919 6920 6921 6922 6923 6924 6925 6926 6927 6928 6929 6930 6931 6932 6933 6934 6935 6936 6937 6938 6939 6940 6941 6942 6943 | int sqlite3Fts5IndexLoadConfig(Fts5Index *p){ Fts5Structure *pStruct; pStruct = fts5StructureRead(p); fts5StructureRelease(pStruct); return fts5IndexReturn(p); } /* ** Retrieve the origin value that will be used for the segment currently ** being accumulated in the in-memory hash table when it is flushed to ** disk. If successful, SQLITE_OK is returned and (*piOrigin) set to ** the queried value. Or, if an error occurs, an error code is returned ** and the final value of (*piOrigin) is undefined. */ int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin){ Fts5Structure *pStruct; pStruct = fts5StructureRead(p); if( pStruct ){ *piOrigin = pStruct->nOriginCntr; fts5StructureRelease(pStruct); } return fts5IndexReturn(p); } /* ** Buffer pPg contains a page of a tombstone hash table - one of nPg pages ** associated with the same segment. This function adds rowid iRowid to ** the hash table. The caller is required to guarantee that there is at ** least one free slot on the page. ** ** If parameter bForce is false and the hash table is deemed to be full ** (more than half of the slots are occupied), then non-zero is returned ** and iRowid not inserted. Or, if bForce is true or if the hash table page ** is not full, iRowid is inserted and zero returned. */ static int fts5IndexTombstoneAddToPage( Fts5Data *pPg, int bForce, int nPg, u64 iRowid ){ const int szKey = TOMBSTONE_KEYSIZE(pPg); const int nSlot = TOMBSTONE_NSLOT(pPg); const int nElem = fts5GetU32(&pPg->p[4]); int iSlot = (iRowid / nPg) % nSlot; int nCollide = nSlot; if( szKey==4 && iRowid>0xFFFFFFFF ) return 2; if( iRowid==0 ){ pPg->p[1] = 0x01; return 0; } if( bForce==0 && nElem>=(nSlot/2) ){ return 1; } fts5PutU32(&pPg->p[4], nElem+1); if( szKey==4 ){ u32 *aSlot = (u32*)&pPg->p[8]; while( aSlot[iSlot] ){ iSlot = (iSlot + 1) % nSlot; if( nCollide--==0 ) return 0; } fts5PutU32((u8*)&aSlot[iSlot], (u32)iRowid); }else{ u64 *aSlot = (u64*)&pPg->p[8]; while( aSlot[iSlot] ){ iSlot = (iSlot + 1) % nSlot; if( nCollide--==0 ) return 0; } fts5PutU64((u8*)&aSlot[iSlot], iRowid); } return 0; } /* ** This function attempts to build a new hash containing all the keys ** currently in the tombstone hash table for segment pSeg. The new ** hash will be stored in the nOut buffers passed in array apOut[]. ** All pages of the new hash use key-size szKey (4 or 8). ** ** Return 0 if the hash is successfully rebuilt into the nOut pages. ** Or non-zero if it is not (because one page became overfull). In this ** case the caller should retry with a larger nOut parameter. ** ** Parameter pData1 is page iPg1 of the hash table being rebuilt. */ static int fts5IndexTombstoneRehash( Fts5Index *p, Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */ Fts5Data *pData1, /* One page of current hash - or NULL */ int iPg1, /* Which page of the current hash is pData1 */ int szKey, /* 4 or 8, the keysize */ int nOut, /* Number of output pages */ Fts5Data **apOut /* Array of output hash pages */ ){ int ii; int res = 0; /* Initialize the headers of all the output pages */ for(ii=0; ii<nOut; ii++){ apOut[ii]->p[0] = szKey; fts5PutU32(&apOut[ii]->p[4], 0); } /* Loop through the current pages of the hash table. */ for(ii=0; res==0 && ii<pSeg->nPgTombstone; ii++){ Fts5Data *pData = 0; /* Page ii of the current hash table */ Fts5Data *pFree = 0; /* Free this at the end of the loop */ if( iPg1==ii ){ pData = pData1; }else{ pFree = pData = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid, ii)); } if( pData ){ int szKeyIn = TOMBSTONE_KEYSIZE(pData); int nSlotIn = (pData->nn - 8) / szKeyIn; int iIn; for(iIn=0; iIn<nSlotIn; iIn++){ u64 iVal = 0; /* Read the value from slot iIn of the input page into iVal. */ if( szKeyIn==4 ){ u32 *aSlot = (u32*)&pData->p[8]; if( aSlot[iIn] ) iVal = fts5GetU32((u8*)&aSlot[iIn]); }else{ u64 *aSlot = (u64*)&pData->p[8]; if( aSlot[iIn] ) iVal = fts5GetU64((u8*)&aSlot[iIn]); } /* If iVal is not 0 at this point, insert it into the new hash table */ if( iVal ){ Fts5Data *pPg = apOut[(iVal % nOut)]; res = fts5IndexTombstoneAddToPage(pPg, 0, nOut, iVal); if( res ) break; } } /* If this is page 0 of the old hash, copy the rowid-0-flag from the ** old hash to the new. */ if( ii==0 ){ apOut[0]->p[1] = pData->p[1]; } } fts5DataRelease(pFree); } return res; } /* ** This is called to rebuild the hash table belonging to segment pSeg. ** If parameter pData1 is not NULL, then one page of the existing hash table ** has already been loaded - pData1, which is page iPg1. The key-size for ** the new hash table is szKey (4 or 8). ** ** If successful, the new hash table is not written to disk. Instead, ** output parameter (*pnOut) is set to the number of pages in the new ** hash table, and (*papOut) to point to an array of buffers containing ** the new page data. ** ** If an error occurs, an error code is left in the Fts5Index object and ** both output parameters set to 0 before returning. */ static void fts5IndexTombstoneRebuild( Fts5Index *p, Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */ Fts5Data *pData1, /* One page of current hash - or NULL */ int iPg1, /* Which page of the current hash is pData1 */ int szKey, /* 4 or 8, the keysize */ int *pnOut, /* OUT: Number of output pages */ Fts5Data ***papOut /* OUT: Output hash pages */ ){ const int MINSLOT = 32; int nSlotPerPage = MAX(MINSLOT, (p->pConfig->pgsz - 8) / szKey); int nSlot = 0; /* Number of slots in each output page */ int nOut = 0; /* Figure out how many output pages (nOut) and how many slots per ** page (nSlot). There are three possibilities: ** ** 1. The hash table does not yet exist. In this case the new hash ** table will consist of a single page with MINSLOT slots. ** ** 2. The hash table exists but is currently a single page. In this ** case an attempt is made to grow the page to accommodate the new ** entry. The page is allowed to grow up to nSlotPerPage (see above) ** slots. ** ** 3. The hash table already consists of more than one page, or of ** a single page already so large that it cannot be grown. In this ** case the new hash consists of (nPg*2+1) pages of nSlotPerPage ** slots each, where nPg is the current number of pages in the ** hash table. */ if( pSeg->nPgTombstone==0 ){ /* Case 1. */ nOut = 1; nSlot = MINSLOT; }else if( pSeg->nPgTombstone==1 ){ /* Case 2. */ int nElem = (int)fts5GetU32(&pData1->p[4]); assert( pData1 && iPg1==0 ); nOut = 1; nSlot = MAX(nElem*4, MINSLOT); if( nSlot>nSlotPerPage ) nOut = 0; } if( nOut==0 ){ /* Case 3. */ nOut = (pSeg->nPgTombstone * 2 + 1); nSlot = nSlotPerPage; } /* Allocate the required array and output pages */ while( 1 ){ int res = 0; int ii = 0; int szPage = 0; Fts5Data **apOut = 0; /* Allocate space for the new hash table */ assert( nSlot>=MINSLOT ); apOut = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data*) * nOut); szPage = 8 + nSlot*szKey; for(ii=0; ii<nOut; ii++){ Fts5Data *pNew = (Fts5Data*)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data)+szPage ); if( pNew ){ pNew->nn = szPage; pNew->p = (u8*)&pNew[1]; apOut[ii] = pNew; } } /* Rebuild the hash table. */ if( p->rc==SQLITE_OK ){ res = fts5IndexTombstoneRehash(p, pSeg, pData1, iPg1, szKey, nOut, apOut); } if( res==0 ){ if( p->rc ){ fts5IndexFreeArray(apOut, nOut); apOut = 0; nOut = 0; } *pnOut = nOut; *papOut = apOut; break; } /* If control flows to here, it was not possible to rebuild the hash ** table. Free all buffers and then try again with more pages. */ assert( p->rc==SQLITE_OK ); fts5IndexFreeArray(apOut, nOut); nSlot = nSlotPerPage; nOut = nOut*2 + 1; } } /* ** Add a tombstone for rowid iRowid to segment pSeg. */ static void fts5IndexTombstoneAdd( Fts5Index *p, Fts5StructureSegment *pSeg, u64 iRowid ){ Fts5Data *pPg = 0; int iPg = -1; int szKey = 0; int nHash = 0; Fts5Data **apHash = 0; p->nContentlessDelete++; if( pSeg->nPgTombstone>0 ){ iPg = iRowid % pSeg->nPgTombstone; pPg = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg)); if( pPg==0 ){ assert( p->rc!=SQLITE_OK ); return; } if( 0==fts5IndexTombstoneAddToPage(pPg, 0, pSeg->nPgTombstone, iRowid) ){ fts5DataWrite(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg), pPg->p, pPg->nn); fts5DataRelease(pPg); return; } } /* Have to rebuild the hash table. First figure out the key-size (4 or 8). */ szKey = pPg ? TOMBSTONE_KEYSIZE(pPg) : 4; if( iRowid>0xFFFFFFFF ) szKey = 8; /* Rebuild the hash table */ fts5IndexTombstoneRebuild(p, pSeg, pPg, iPg, szKey, &nHash, &apHash); assert( p->rc==SQLITE_OK || (nHash==0 && apHash==0) ); /* If all has succeeded, write the new rowid into one of the new hash ** table pages, then write them all out to disk. */ if( nHash ){ int ii = 0; fts5IndexTombstoneAddToPage(apHash[iRowid % nHash], 1, nHash, iRowid); for(ii=0; ii<nHash; ii++){ i64 iTombstoneRowid = FTS5_TOMBSTONE_ROWID(pSeg->iSegid, ii); fts5DataWrite(p, iTombstoneRowid, apHash[ii]->p, apHash[ii]->nn); } pSeg->nPgTombstone = nHash; fts5StructureWrite(p, p->pStruct); } fts5DataRelease(pPg); fts5IndexFreeArray(apHash, nHash); } /* ** Add iRowid to the tombstone list of the segment or segments that contain ** rows from origin iOrigin. Return SQLITE_OK if successful, or an SQLite ** error code otherwise. */ int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid){ Fts5Structure *pStruct; pStruct = fts5StructureRead(p); if( pStruct ){ int bFound = 0; /* True after pSeg->nEntryTombstone incr. */ int iLvl; for(iLvl=pStruct->nLevel-1; iLvl>=0; iLvl--){ int iSeg; for(iSeg=pStruct->aLevel[iLvl].nSeg-1; iSeg>=0; iSeg--){ Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; if( pSeg->iOrigin1<=(u64)iOrigin && pSeg->iOrigin2>=(u64)iOrigin ){ if( bFound==0 ){ pSeg->nEntryTombstone++; bFound = 1; } fts5IndexTombstoneAdd(p, pSeg, iRowid); } } } fts5StructureRelease(pStruct); } return fts5IndexReturn(p); } /************************************************************************* ************************************************************************** ** Below this point is the implementation of the integrity-check ** functionality. */ |
︙ | ︙ | |||
6772 6773 6774 6775 6776 6777 6778 | /************************************************************************* ************************************************************************** ** Below this point is the implementation of the fts5_decode() scalar ** function only. */ | | > > | > > | | | | | | > > | | | > > > > > > | | | 7481 7482 7483 7484 7485 7486 7487 7488 7489 7490 7491 7492 7493 7494 7495 7496 7497 7498 7499 7500 7501 7502 7503 7504 7505 7506 7507 7508 7509 7510 7511 7512 7513 7514 7515 7516 7517 7518 7519 7520 7521 7522 7523 7524 7525 7526 7527 7528 7529 7530 7531 7532 7533 7534 7535 7536 7537 7538 7539 7540 7541 7542 7543 7544 7545 7546 7547 7548 7549 7550 7551 7552 7553 7554 7555 7556 7557 7558 7559 7560 7561 7562 7563 7564 7565 7566 7567 7568 7569 7570 7571 7572 7573 7574 7575 7576 | /************************************************************************* ************************************************************************** ** Below this point is the implementation of the fts5_decode() scalar ** function only. */ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** Decode a segment-data rowid from the %_data table. This function is ** the opposite of macro FTS5_SEGMENT_ROWID(). */ static void fts5DecodeRowid( i64 iRowid, /* Rowid from %_data table */ int *pbTombstone, /* OUT: Tombstone hash flag */ int *piSegid, /* OUT: Segment id */ int *pbDlidx, /* OUT: Dlidx flag */ int *piHeight, /* OUT: Height */ int *piPgno /* OUT: Page number */ ){ *piPgno = (int)(iRowid & (((i64)1 << FTS5_DATA_PAGE_B) - 1)); iRowid >>= FTS5_DATA_PAGE_B; *piHeight = (int)(iRowid & (((i64)1 << FTS5_DATA_HEIGHT_B) - 1)); iRowid >>= FTS5_DATA_HEIGHT_B; *pbDlidx = (int)(iRowid & 0x0001); iRowid >>= FTS5_DATA_DLI_B; *piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1)); iRowid >>= FTS5_DATA_ID_B; *pbTombstone = (int)(iRowid & 0x0001); } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ int iSegid, iHeight, iPgno, bDlidx, bTomb; /* Rowid compenents */ fts5DecodeRowid(iKey, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno); if( iSegid==0 ){ if( iKey==FTS5_AVERAGES_ROWID ){ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{averages} "); }else{ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{structure}"); } } else{ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%s%ssegid=%d h=%d pgno=%d}", bDlidx ? "dlidx " : "", bTomb ? "tombstone " : "", iSegid, iHeight, iPgno ); } } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) static void fts5DebugStructure( int *pRc, /* IN/OUT: error code */ Fts5Buffer *pBuf, Fts5Structure *p ){ int iLvl, iSeg; /* Iterate through levels, segments */ for(iLvl=0; iLvl<p->nLevel; iLvl++){ Fts5StructureLevel *pLvl = &p->aLevel[iLvl]; sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {lvl=%d nMerge=%d nSeg=%d", iLvl, pLvl->nMerge, pLvl->nSeg ); for(iSeg=0; iSeg<pLvl->nSeg; iSeg++){ Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d", pSeg->iSegid, pSeg->pgnoFirst, pSeg->pgnoLast ); if( pSeg->iOrigin1>0 ){ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " origin=%lld..%lld", pSeg->iOrigin1, pSeg->iOrigin2 ); } sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); } sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); } } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** This is part of the fts5_decode() debugging aid. ** ** Arguments pBlob/nBlob contain a serialized Fts5Structure object. This ** function appends a human-readable representation of the same object ** to the buffer passed as the second argument. */ |
︙ | ︙ | |||
6866 6867 6868 6869 6870 6871 6872 | *pRc = rc; return; } fts5DebugStructure(pRc, pBuf, p); fts5StructureRelease(p); } | | | | 7587 7588 7589 7590 7591 7592 7593 7594 7595 7596 7597 7598 7599 7600 7601 7602 7603 | *pRc = rc; return; } fts5DebugStructure(pRc, pBuf, p); fts5StructureRelease(p); } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** This is part of the fts5_decode() debugging aid. ** ** Arguments pBlob/nBlob contain an "averages" record. This function ** appends a human-readable representation of record to the buffer passed ** as the second argument. */ |
︙ | ︙ | |||
6891 6892 6893 6894 6895 6896 6897 | while( i<nBlob ){ u64 iVal; i += sqlite3Fts5GetVarint(&pBlob[i], &iVal); sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "%s%d", zSpace, (int)iVal); zSpace = " "; } } | | | | | | 7612 7613 7614 7615 7616 7617 7618 7619 7620 7621 7622 7623 7624 7625 7626 7627 7628 7629 7630 7631 7632 7633 7634 7635 7636 7637 7638 7639 7640 7641 7642 7643 7644 7645 7646 7647 | while( i<nBlob ){ u64 iVal; i += sqlite3Fts5GetVarint(&pBlob[i], &iVal); sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "%s%d", zSpace, (int)iVal); zSpace = " "; } } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** Buffer (a/n) is assumed to contain a list of serialized varints. Read ** each varint and append its string representation to buffer pBuf. Return ** after either the input buffer is exhausted or a 0 value is read. ** ** The return value is the number of bytes read from the input buffer. */ static int fts5DecodePoslist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ int iOff = 0; while( iOff<n ){ int iVal; iOff += fts5GetVarint32(&a[iOff], iVal); sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %d", iVal); } return iOff; } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** The start of buffer (a/n) contains the start of a doclist. The doclist ** may or may not finish within the buffer. This function appends a text ** representation of the part of the doclist that is present to buffer ** pBuf. ** ** The return value is the number of bytes read from the input buffer. |
︙ | ︙ | |||
6945 6946 6947 6948 6949 6950 6951 | iDocid += iDelta; sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " id=%lld", iDocid); } } return iOff; } | | | | 7666 7667 7668 7669 7670 7671 7672 7673 7674 7675 7676 7677 7678 7679 7680 7681 7682 | iDocid += iDelta; sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " id=%lld", iDocid); } } return iOff; } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** This function is part of the fts5_decode() debugging function. It is ** only ever used with detail=none tables. ** ** Buffer (pData/nData) contains a doclist in the format used by detail=none ** tables. This function appends a human-readable version of that list to ** buffer pBuf. |
︙ | ︙ | |||
6988 6989 6990 6991 6992 6993 6994 | zApp = "*"; } } sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp); } } | | | > | 7709 7710 7711 7712 7713 7714 7715 7716 7717 7718 7719 7720 7721 7722 7723 7724 7725 7726 7727 7728 7729 7730 7731 7732 7733 7734 7735 7736 | zApp = "*"; } } sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp); } } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** The implementation of user-defined scalar function fts5_decode(). */ static void fts5DecodeFunction( sqlite3_context *pCtx, /* Function call context */ int nArg, /* Number of args (always 2) */ sqlite3_value **apVal /* Function arguments */ ){ i64 iRowid; /* Rowid for record being decoded */ int iSegid,iHeight,iPgno,bDlidx;/* Rowid components */ int bTomb; const u8 *aBlob; int n; /* Record to decode */ u8 *a = 0; Fts5Buffer s; /* Build up text to return here */ int rc = SQLITE_OK; /* Return code */ sqlite3_int64 nSpace = 0; int eDetailNone = (sqlite3_user_data(pCtx)!=0); |
︙ | ︙ | |||
7023 7024 7025 7026 7027 7028 7029 | n = sqlite3_value_bytes(apVal[1]); aBlob = sqlite3_value_blob(apVal[1]); nSpace = n + FTS5_DATA_ZERO_PADDING; a = (u8*)sqlite3Fts5MallocZero(&rc, nSpace); if( a==0 ) goto decode_out; if( n>0 ) memcpy(a, aBlob, n); | | > > > > > > > > > > > > > > > > > > > > > > | 7745 7746 7747 7748 7749 7750 7751 7752 7753 7754 7755 7756 7757 7758 7759 7760 7761 7762 7763 7764 7765 7766 7767 7768 7769 7770 7771 7772 7773 7774 7775 7776 7777 7778 7779 7780 7781 7782 7783 7784 7785 7786 7787 7788 7789 7790 7791 7792 7793 7794 7795 7796 7797 7798 | n = sqlite3_value_bytes(apVal[1]); aBlob = sqlite3_value_blob(apVal[1]); nSpace = n + FTS5_DATA_ZERO_PADDING; a = (u8*)sqlite3Fts5MallocZero(&rc, nSpace); if( a==0 ) goto decode_out; if( n>0 ) memcpy(a, aBlob, n); fts5DecodeRowid(iRowid, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno); fts5DebugRowid(&rc, &s, iRowid); if( bDlidx ){ Fts5Data dlidx; Fts5DlidxLvl lvl; dlidx.p = a; dlidx.nn = n; memset(&lvl, 0, sizeof(Fts5DlidxLvl)); lvl.pData = &dlidx; lvl.iLeafPgno = iPgno; for(fts5DlidxLvlNext(&lvl); lvl.bEof==0; fts5DlidxLvlNext(&lvl)){ sqlite3Fts5BufferAppendPrintf(&rc, &s, " %d(%lld)", lvl.iLeafPgno, lvl.iRowid ); } }else if( bTomb ){ u32 nElem = fts5GetU32(&a[4]); int szKey = (aBlob[0]==4 || aBlob[0]==8) ? aBlob[0] : 8; int nSlot = (n - 8) / szKey; int ii; sqlite3Fts5BufferAppendPrintf(&rc, &s, " nElem=%d", (int)nElem); if( aBlob[1] ){ sqlite3Fts5BufferAppendPrintf(&rc, &s, " 0"); } for(ii=0; ii<nSlot; ii++){ u64 iVal = 0; if( szKey==4 ){ u32 *aSlot = (u32*)&aBlob[8]; if( aSlot[ii] ) iVal = fts5GetU32((u8*)&aSlot[ii]); }else{ u64 *aSlot = (u64*)&aBlob[8]; if( aSlot[ii] ) iVal = fts5GetU64((u8*)&aSlot[ii]); } if( iVal!=0 ){ sqlite3Fts5BufferAppendPrintf(&rc, &s, " %lld", (i64)iVal); } } }else if( iSegid==0 ){ if( iRowid==FTS5_AVERAGES_ROWID ){ fts5DecodeAverages(&rc, &s, a, n); }else{ fts5DecodeStructure(&rc, &s, a, n); } |
︙ | ︙ | |||
7199 7200 7201 7202 7203 7204 7205 | if( rc==SQLITE_OK ){ sqlite3_result_text(pCtx, (const char*)s.p, s.n, SQLITE_TRANSIENT); }else{ sqlite3_result_error_code(pCtx, rc); } fts5BufferFree(&s); } | | | | 7943 7944 7945 7946 7947 7948 7949 7950 7951 7952 7953 7954 7955 7956 7957 7958 7959 | if( rc==SQLITE_OK ){ sqlite3_result_text(pCtx, (const char*)s.p, s.n, SQLITE_TRANSIENT); }else{ sqlite3_result_error_code(pCtx, rc); } fts5BufferFree(&s); } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** The implementation of user-defined scalar function fts5_rowid(). */ static void fts5RowidFunction( sqlite3_context *pCtx, /* Function call context */ int nArg, /* Number of args (always 2) */ sqlite3_value **apVal /* Function arguments */ |
︙ | ︙ | |||
7235 7236 7237 7238 7239 7240 7241 | }else{ sqlite3_result_error(pCtx, "first arg to fts5_rowid() must be 'segment'" , -1 ); } } } | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 7979 7980 7981 7982 7983 7984 7985 7986 7987 7988 7989 7990 7991 7992 7993 7994 7995 7996 7997 7998 7999 8000 8001 8002 8003 8004 8005 8006 8007 8008 8009 8010 8011 8012 8013 8014 8015 8016 8017 8018 8019 8020 8021 8022 8023 8024 8025 8026 8027 8028 8029 8030 8031 8032 8033 8034 8035 8036 8037 8038 8039 8040 8041 8042 8043 8044 8045 8046 8047 8048 8049 8050 8051 8052 8053 8054 8055 8056 8057 8058 8059 8060 8061 8062 8063 8064 8065 8066 8067 8068 8069 8070 8071 8072 8073 8074 8075 8076 8077 8078 8079 8080 8081 8082 8083 8084 8085 8086 8087 8088 8089 8090 8091 8092 8093 8094 8095 8096 8097 8098 8099 8100 8101 8102 8103 8104 8105 8106 8107 8108 8109 8110 8111 8112 8113 8114 8115 8116 8117 8118 8119 8120 8121 8122 8123 8124 8125 8126 8127 8128 8129 8130 8131 8132 8133 8134 8135 8136 8137 8138 8139 8140 8141 8142 8143 8144 8145 8146 8147 8148 8149 8150 8151 8152 8153 8154 8155 8156 8157 8158 8159 8160 8161 8162 8163 8164 8165 8166 8167 8168 8169 8170 8171 8172 8173 8174 8175 8176 8177 8178 8179 8180 8181 8182 8183 8184 8185 8186 8187 8188 8189 8190 8191 8192 8193 8194 8195 8196 8197 8198 8199 8200 8201 8202 8203 8204 8205 8206 8207 8208 8209 8210 8211 8212 8213 8214 8215 8216 8217 8218 8219 8220 8221 8222 8223 8224 8225 8226 8227 8228 8229 8230 8231 8232 8233 8234 8235 8236 8237 8238 8239 8240 8241 8242 8243 8244 8245 8246 8247 8248 8249 8250 8251 8252 8253 8254 8255 8256 8257 8258 8259 8260 8261 8262 8263 8264 8265 8266 8267 8268 8269 8270 8271 8272 8273 8274 8275 8276 8277 | }else{ sqlite3_result_error(pCtx, "first arg to fts5_rowid() must be 'segment'" , -1 ); } } } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) typedef struct Fts5StructVtab Fts5StructVtab; struct Fts5StructVtab { sqlite3_vtab base; }; typedef struct Fts5StructVcsr Fts5StructVcsr; struct Fts5StructVcsr { sqlite3_vtab_cursor base; Fts5Structure *pStruct; int iLevel; int iSeg; int iRowid; }; /* ** Create a new fts5_structure() table-valued function. */ static int fts5structConnectMethod( sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ Fts5StructVtab *pNew = 0; int rc = SQLITE_OK; rc = sqlite3_declare_vtab(db, "CREATE TABLE xyz(" "level, segment, merge, segid, leaf1, leaf2, loc1, loc2, " "npgtombstone, nentrytombstone, nentry, struct HIDDEN);" ); if( rc==SQLITE_OK ){ pNew = sqlite3Fts5MallocZero(&rc, sizeof(*pNew)); } *ppVtab = (sqlite3_vtab*)pNew; return rc; } /* ** We must have a single struct=? constraint that will be passed through ** into the xFilter method. If there is no valid stmt=? constraint, ** then return an SQLITE_CONSTRAINT error. */ static int fts5structBestIndexMethod( sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ int i; int rc = SQLITE_CONSTRAINT; struct sqlite3_index_constraint *p; pIdxInfo->estimatedCost = (double)100; pIdxInfo->estimatedRows = 100; pIdxInfo->idxNum = 0; for(i=0, p=pIdxInfo->aConstraint; i<pIdxInfo->nConstraint; i++, p++){ if( p->usable==0 ) continue; if( p->op==SQLITE_INDEX_CONSTRAINT_EQ && p->iColumn==11 ){ rc = SQLITE_OK; pIdxInfo->aConstraintUsage[i].omit = 1; pIdxInfo->aConstraintUsage[i].argvIndex = 1; break; } } return rc; } /* ** This method is the destructor for bytecodevtab objects. */ static int fts5structDisconnectMethod(sqlite3_vtab *pVtab){ Fts5StructVtab *p = (Fts5StructVtab*)pVtab; sqlite3_free(p); return SQLITE_OK; } /* ** Constructor for a new bytecodevtab_cursor object. */ static int fts5structOpenMethod(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){ int rc = SQLITE_OK; Fts5StructVcsr *pNew = 0; pNew = sqlite3Fts5MallocZero(&rc, sizeof(*pNew)); *ppCsr = (sqlite3_vtab_cursor*)pNew; return SQLITE_OK; } /* ** Destructor for a bytecodevtab_cursor. */ static int fts5structCloseMethod(sqlite3_vtab_cursor *cur){ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; fts5StructureRelease(pCsr->pStruct); sqlite3_free(pCsr); return SQLITE_OK; } /* ** Advance a bytecodevtab_cursor to its next row of output. */ static int fts5structNextMethod(sqlite3_vtab_cursor *cur){ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; assert( pCsr->pStruct ); pCsr->iSeg++; pCsr->iRowid++; while( pCsr->iSeg>=pCsr->pStruct->aLevel[pCsr->iLevel].nSeg ){ pCsr->iLevel++; pCsr->iSeg = 0; } if( pCsr->iLevel>=pCsr->pStruct->nLevel ){ fts5StructureRelease(pCsr->pStruct); pCsr->pStruct = 0; } return SQLITE_OK; } /* ** Return TRUE if the cursor has been moved off of the last ** row of output. */ static int fts5structEofMethod(sqlite3_vtab_cursor *cur){ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; return pCsr->pStruct==0; } static int fts5structRowidMethod( sqlite3_vtab_cursor *cur, sqlite_int64 *piRowid ){ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; *piRowid = pCsr->iRowid; return SQLITE_OK; } /* ** Return values of columns for the row at which the bytecodevtab_cursor ** is currently pointing. */ static int fts5structColumnMethod( sqlite3_vtab_cursor *cur, /* The cursor */ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ int i /* Which column to return */ ){ Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; Fts5Structure *p = pCsr->pStruct; Fts5StructureSegment *pSeg = &p->aLevel[pCsr->iLevel].aSeg[pCsr->iSeg]; switch( i ){ case 0: /* level */ sqlite3_result_int(ctx, pCsr->iLevel); break; case 1: /* segment */ sqlite3_result_int(ctx, pCsr->iSeg); break; case 2: /* merge */ sqlite3_result_int(ctx, pCsr->iSeg < p->aLevel[pCsr->iLevel].nMerge); break; case 3: /* segid */ sqlite3_result_int(ctx, pSeg->iSegid); break; case 4: /* leaf1 */ sqlite3_result_int(ctx, pSeg->pgnoFirst); break; case 5: /* leaf2 */ sqlite3_result_int(ctx, pSeg->pgnoLast); break; case 6: /* origin1 */ sqlite3_result_int64(ctx, pSeg->iOrigin1); break; case 7: /* origin2 */ sqlite3_result_int64(ctx, pSeg->iOrigin2); break; case 8: /* npgtombstone */ sqlite3_result_int(ctx, pSeg->nPgTombstone); break; case 9: /* nentrytombstone */ sqlite3_result_int64(ctx, pSeg->nEntryTombstone); break; case 10: /* nentry */ sqlite3_result_int64(ctx, pSeg->nEntry); break; } return SQLITE_OK; } /* ** Initialize a cursor. ** ** idxNum==0 means show all subprograms ** idxNum==1 means show only the main bytecode and omit subprograms. */ static int fts5structFilterMethod( sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ Fts5StructVcsr *pCsr = (Fts5StructVcsr *)pVtabCursor; int rc = SQLITE_OK; const u8 *aBlob = 0; int nBlob = 0; assert( argc==1 ); fts5StructureRelease(pCsr->pStruct); pCsr->pStruct = 0; nBlob = sqlite3_value_bytes(argv[0]); aBlob = (const u8*)sqlite3_value_blob(argv[0]); rc = fts5StructureDecode(aBlob, nBlob, 0, &pCsr->pStruct); if( rc==SQLITE_OK ){ pCsr->iLevel = 0; pCsr->iRowid = 0; pCsr->iSeg = -1; rc = fts5structNextMethod(pVtabCursor); } return rc; } #endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ /* ** This is called as part of registering the FTS5 module with database ** connection db. It registers several user-defined scalar functions useful ** with FTS5. ** ** If successful, SQLITE_OK is returned. If an error occurs, some other ** SQLite error code is returned instead. */ int sqlite3Fts5IndexInit(sqlite3 *db){ #if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) int rc = sqlite3_create_function( db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0 ); if( rc==SQLITE_OK ){ rc = sqlite3_create_function( db, "fts5_decode_none", 2, SQLITE_UTF8, (void*)db, fts5DecodeFunction, 0, 0 ); } if( rc==SQLITE_OK ){ rc = sqlite3_create_function( db, "fts5_rowid", -1, SQLITE_UTF8, 0, fts5RowidFunction, 0, 0 ); } if( rc==SQLITE_OK ){ static const sqlite3_module fts5structure_module = { 0, /* iVersion */ 0, /* xCreate */ fts5structConnectMethod, /* xConnect */ fts5structBestIndexMethod, /* xBestIndex */ fts5structDisconnectMethod, /* xDisconnect */ 0, /* xDestroy */ fts5structOpenMethod, /* xOpen */ fts5structCloseMethod, /* xClose */ fts5structFilterMethod, /* xFilter */ fts5structNextMethod, /* xNext */ fts5structEofMethod, /* xEof */ fts5structColumnMethod, /* xColumn */ fts5structRowidMethod, /* xRowid */ 0, /* xUpdate */ 0, /* xBegin */ 0, /* xSync */ 0, /* xCommit */ 0, /* xRollback */ 0, /* xFindFunction */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ 0 /* xShadowName */ }; rc = sqlite3_create_module(db, "fts5_structure", &fts5structure_module, 0); } return rc; #else return SQLITE_OK; UNUSED_PARAM(db); #endif } |
︙ | ︙ |
Changes to ext/fts5/fts5_main.c.
︙ | ︙ | |||
1620 1621 1622 1623 1624 1625 1626 | 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; | < | 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 | 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 |
︙ | ︙ | |||
1650 1651 1652 1653 1654 1655 1656 | && sqlite3_value_type(apVal[2+pConfig->nCol])!=SQLITE_NULL ){ /* A "special" INSERT op. These are handled separately. */ const char *z = (const char*)sqlite3_value_text(apVal[2+pConfig->nCol]); if( pConfig->eContent!=FTS5_CONTENT_NORMAL && 0==sqlite3_stricmp("delete", z) ){ | > > > > > > | > | | > | > > > | 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 | && sqlite3_value_type(apVal[2+pConfig->nCol])!=SQLITE_NULL ){ /* A "special" INSERT op. These are handled separately. */ const char *z = (const char*)sqlite3_value_text(apVal[2+pConfig->nCol]); if( pConfig->eContent!=FTS5_CONTENT_NORMAL && 0==sqlite3_stricmp("delete", z) ){ if( pConfig->bContentlessDelete ){ fts5SetVtabError(pTab, "'delete' may not be used with a contentless_delete=1 table" ); rc = SQLITE_ERROR; }else{ rc = fts5SpecialDelete(pTab, apVal); } }else{ rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]); } }else{ /* A regular INSERT, UPDATE or DELETE statement. The trick here is that ** any conflict on the rowid value must be detected before any ** modifications are made to the database file. There are 4 cases: ** ** 1) DELETE ** 2) UPDATE (rowid not modified) ** 3) UPDATE (rowid modified) ** 4) INSERT ** ** Cases 3 and 4 may violate the rowid constraint. */ int eConflict = SQLITE_ABORT; if( pConfig->eContent==FTS5_CONTENT_NORMAL || pConfig->bContentlessDelete ){ eConflict = sqlite3_vtab_on_conflict(pConfig->db); } assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL ); assert( nArg!=1 || eType0==SQLITE_INTEGER ); /* Filter out attempts to run UPDATE or DELETE on contentless tables. ** This is not suported. Except - DELETE is supported if the CREATE ** VIRTUAL TABLE statement contained "contentless_delete=1". */ if( eType0==SQLITE_INTEGER && pConfig->eContent==FTS5_CONTENT_NONE && (nArg>1 || pConfig->bContentlessDelete==0) ){ pTab->p.base.zErrMsg = sqlite3_mprintf( "cannot %s contentless fts5 table: %s", (nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName ); rc = SQLITE_ERROR; } |
︙ | ︙ | |||
1763 1764 1765 1766 1767 1768 1769 | ** Implementation of xSync() method. */ static int fts5SyncMethod(sqlite3_vtab *pVtab){ int rc; Fts5FullTable *pTab = (Fts5FullTable*)pVtab; fts5CheckTransactionState(pTab, FTS5_SYNC, 0); pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg; | < | | 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 | ** Implementation of xSync() method. */ static int fts5SyncMethod(sqlite3_vtab *pVtab){ int rc; Fts5FullTable *pTab = (Fts5FullTable*)pVtab; fts5CheckTransactionState(pTab, FTS5_SYNC, 0); pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg; rc = sqlite3Fts5FlushToDisk(&pTab->p); pTab->p.pConfig->pzErrmsg = 0; return rc; } /* ** Implementation of xBegin() method. */ |
︙ | ︙ |
Changes to ext/fts5/fts5_storage.c.
︙ | ︙ | |||
73 74 75 76 77 78 79 | "SELECT %s FROM %s T WHERE T.%Q >= ? AND T.%Q <= ? ORDER BY T.%Q ASC", "SELECT %s FROM %s T WHERE T.%Q <= ? AND T.%Q >= ? ORDER BY T.%Q DESC", "SELECT %s FROM %s T WHERE T.%Q=?", /* LOOKUP */ "INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */ "REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */ "DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */ | | | | 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | "SELECT %s FROM %s T WHERE T.%Q >= ? AND T.%Q <= ? ORDER BY T.%Q ASC", "SELECT %s FROM %s T WHERE T.%Q <= ? AND T.%Q >= ? ORDER BY T.%Q DESC", "SELECT %s FROM %s T WHERE T.%Q=?", /* LOOKUP */ "INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */ "REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */ "DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */ "REPLACE INTO %Q.'%q_docsize' VALUES(?,?%s)", /* REPLACE_DOCSIZE */ "DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */ "SELECT sz%s FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */ "REPLACE INTO %Q.'%q_config' VALUES(?,?)", /* REPLACE_CONFIG */ "SELECT %s FROM %s AS T", /* SCAN */ }; Fts5Config *pC = p->pConfig; char *zSql = 0; |
︙ | ︙ | |||
123 124 125 126 127 128 129 130 131 132 133 134 135 136 | } zBind[i*2-1] = '\0'; zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, zBind); sqlite3_free(zBind); } break; } default: zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName); break; } if( zSql==0 ){ | > > > > > > > > > > > > > | 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 | } zBind[i*2-1] = '\0'; zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, zBind); sqlite3_free(zBind); } break; } case FTS5_STMT_REPLACE_DOCSIZE: zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, (pC->bContentlessDelete ? ",?" : "") ); break; case FTS5_STMT_LOOKUP_DOCSIZE: zSql = sqlite3_mprintf(azStmt[eStmt], (pC->bContentlessDelete ? ",origin" : ""), pC->zDb, pC->zName ); break; default: zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName); break; } if( zSql==0 ){ |
︙ | ︙ | |||
313 314 315 316 317 318 319 | } rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr); } sqlite3_free(zDefn); } if( rc==SQLITE_OK && pConfig->bColumnsize ){ | > > > > | < < | 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 | } rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr); } sqlite3_free(zDefn); } if( rc==SQLITE_OK && pConfig->bColumnsize ){ const char *zCols = "id INTEGER PRIMARY KEY, sz BLOB"; if( pConfig->bContentlessDelete ){ zCols = "id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER"; } rc = sqlite3Fts5CreateTable(pConfig, "docsize", zCols, 0, pzErr); } if( rc==SQLITE_OK ){ rc = sqlite3Fts5CreateTable( pConfig, "config", "k PRIMARY KEY, v", 1, pzErr ); } if( rc==SQLITE_OK ){ |
︙ | ︙ | |||
392 393 394 395 396 397 398 | static int fts5StorageDeleteFromIndex( Fts5Storage *p, i64 iDel, sqlite3_value **apVal ){ Fts5Config *pConfig = p->pConfig; sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */ | | < | 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 | static int fts5StorageDeleteFromIndex( Fts5Storage *p, i64 iDel, sqlite3_value **apVal ){ 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; if( apVal==0 ){ rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP, &pSeek, 0); if( rc!=SQLITE_OK ) return rc; sqlite3_bind_int64(pSeek, 1, iDel); if( sqlite3_step(pSeek)!=SQLITE_ROW ){ return sqlite3_reset(pSeek); } } ctx.pStorage = p; ctx.iCol = -1; for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ if( pConfig->abUnindexed[iCol-1]==0 ){ const char *zText; int nText; assert( pSeek==0 || apVal==0 ); assert( pSeek!=0 || apVal!=0 ); if( pSeek ){ |
︙ | ︙ | |||
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 | } rc2 = sqlite3_reset(pSeek); if( rc==SQLITE_OK ) rc = rc2; return rc; } /* ** Insert a record into the %_docsize table. Specifically, do: ** ** INSERT OR REPLACE INTO %_docsize(id, sz) VALUES(iRowid, pBuf); ** ** If there is no %_docsize table (as happens if the columnsize=0 option ** is specified when the FTS5 table is created), this function is a no-op. */ static int fts5StorageInsertDocsize( Fts5Storage *p, /* Storage module to write to */ i64 iRowid, /* id value */ Fts5Buffer *pBuf /* sz value */ ){ int rc = SQLITE_OK; if( p->pConfig->bColumnsize ){ sqlite3_stmt *pReplace = 0; rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pReplace, 1, iRowid); | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | > | 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 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 | } rc2 = sqlite3_reset(pSeek); if( rc==SQLITE_OK ) rc = rc2; return rc; } /* ** 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 ** iDel. If successful, SQLITE_OK is returned. Or, if an error occurs, ** an SQLite error code. */ static int fts5StorageContentlessDelete(Fts5Storage *p, i64 iDel){ i64 iOrigin = 0; sqlite3_stmt *pLookup = 0; int rc = SQLITE_OK; assert( p->pConfig->bContentlessDelete ); assert( p->pConfig->eContent==FTS5_CONTENT_NONE ); /* Look up the origin of the document in the %_docsize table. Store ** this in stack variable iOrigin. */ rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pLookup, 1, iDel); if( SQLITE_ROW==sqlite3_step(pLookup) ){ iOrigin = sqlite3_column_int64(pLookup, 1); } rc = sqlite3_reset(pLookup); } if( rc==SQLITE_OK && iOrigin!=0 ){ rc = sqlite3Fts5IndexContentlessDelete(p->pIndex, iOrigin, iDel); } return rc; } /* ** Insert a record into the %_docsize table. Specifically, do: ** ** INSERT OR REPLACE INTO %_docsize(id, sz) VALUES(iRowid, pBuf); ** ** If there is no %_docsize table (as happens if the columnsize=0 option ** is specified when the FTS5 table is created), this function is a no-op. */ static int fts5StorageInsertDocsize( Fts5Storage *p, /* Storage module to write to */ i64 iRowid, /* id value */ Fts5Buffer *pBuf /* sz value */ ){ int rc = SQLITE_OK; if( p->pConfig->bColumnsize ){ sqlite3_stmt *pReplace = 0; rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pReplace, 1, iRowid); if( p->pConfig->bContentlessDelete ){ i64 iOrigin = 0; rc = sqlite3Fts5IndexGetOrigin(p->pIndex, &iOrigin); sqlite3_bind_int64(pReplace, 3, iOrigin); } if( rc==SQLITE_OK ){ sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC); sqlite3_step(pReplace); rc = sqlite3_reset(pReplace); sqlite3_bind_null(pReplace, 2); } } } return rc; } /* ** Load the contents of the "averages" record from disk into the |
︙ | ︙ | |||
532 533 534 535 536 537 538 | sqlite3_stmt *pDel = 0; assert( pConfig->eContent!=FTS5_CONTENT_NORMAL || apVal==0 ); rc = fts5StorageLoadTotals(p, 1); /* Delete the index records */ if( rc==SQLITE_OK ){ | > > > > > > > | > | 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 | sqlite3_stmt *pDel = 0; assert( pConfig->eContent!=FTS5_CONTENT_NORMAL || apVal==0 ); rc = fts5StorageLoadTotals(p, 1); /* Delete the index records */ if( rc==SQLITE_OK ){ rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); } if( rc==SQLITE_OK ){ if( p->pConfig->bContentlessDelete ){ rc = fts5StorageContentlessDelete(p, iDel); }else{ rc = fts5StorageDeleteFromIndex(p, iDel, apVal); } } /* Delete the %_docsize record */ if( rc==SQLITE_OK && pConfig->bColumnsize ){ rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_DOCSIZE, &pDel, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pDel, 1, iDel); |
︙ | ︙ |
Added ext/fts5/test/fts5contentless.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 | # 2014 Dec 20 # # 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 contains tests for the content= and content_rowid= options. # source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5contentless # If SQLITE_ENABLE_FTS5 is defined, omit this file. ifcapable !fts5 { finish_test return } # Check that it is not possible to specify "contentless_delete=1" for # anything other than a contentless table. # set res(0) {0 {}} set res(1) {1 {contentless_delete=1 requires a contentless table}} foreach {tn sql bError} { 1 "(a, b, contentless_delete=1)" 1 2 "(a, b, contentless_delete=1, content=abc)" 1 3 "(a, b, contentless_delete=1, content=)" 0 4 "(content=, contentless_delete=1, a)" 0 5 "(content='', contentless_delete=1, hello)" 0 } { execsql { BEGIN } do_catchsql_test 1.$tn "CREATE VIRTUAL TABLE t1 USING fts5 $sql" $res($bError) execsql { ROLLBACK } } # Check that it is not possible to specify "contentless_delete=1" # along with columnsize=1. # set res(0) {0 {}} set res(1) {1 {contentless_delete=1 is incompatible with columnsize=0}} foreach {tn sql bError} { 2 "(a, b, content='', contentless_delete=1, columnsize=0)" 1 } { execsql { BEGIN } do_catchsql_test 1.$tn "CREATE VIRTUAL TABLE t1 USING fts5 $sql" $res($bError) execsql { ROLLBACK } } # Check that if contentless_delete=1 is specified, then the "origin" # column is added to the %_docsize table. reset_db do_execsql_test 3.0 { CREATE VIRTUAL TABLE x1 USING fts5(c, content=''); CREATE VIRTUAL TABLE x2 USING fts5(c, content='', contentless_delete=1); } do_execsql_test 3.1 { SELECT sql FROM sqlite_schema WHERE name IN ('x1_docsize', 'x2_docsize'); } { {CREATE TABLE 'x1_docsize'(id INTEGER PRIMARY KEY, sz BLOB)} {CREATE TABLE 'x2_docsize'(id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER)} } do_execsql_test 3.2.1 { SELECT hex(block) FROM x1_data WHERE id=10 } {00000000000000} do_execsql_test 3.2.2 { SELECT hex(block) FROM x2_data WHERE id=10 } {00000000FF000001000000} do_execsql_test 3.3 { INSERT INTO x2 VALUES('first text'); INSERT INTO x2 VALUES('second text'); } do_execsql_test 3.4 { SELECT id, origin FROM x2_docsize } {1 1 2 2} do_execsql_test 3.5 { SELECT level, segment, loc1, loc2 FROM fts5_structure( (SELECT block FROM x2_data WHERE id=10) ) } { 0 0 1 1 0 1 2 2 } do_execsql_test 3.6 { INSERT INTO x2(x2) VALUES('optimize'); } do_execsql_test 3.7 { SELECT level, segment, loc1, loc2 FROM fts5_structure( (SELECT block FROM x2_data WHERE id=10) ) } { 1 0 1 2 } do_execsql_test 3.8 { DELETE FROM x2 WHERE rowid=2; } do_execsql_test 3.9 { SELECT rowid FROM x2('text') } {1} #-------------------------------------------------------------------------- reset_db proc document {n} { set vocab [list A B C D E F G H I J K L M N O P Q R S T U V W X Y Z] set ret [list] for {set ii 0} {$ii < $n} {incr ii} { lappend ret [lindex $vocab [expr int(rand()*[llength $vocab])]] } set ret } set nRow 1000 do_execsql_test 4.0 { CREATE TABLE t1(x); CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); INSERT INTO ft(ft, rank) VALUES('pgsz', 100); } do_test 4.1 { for {set ii 0} {$ii < $nRow} {incr ii} { set doc [document 6] execsql { INSERT INTO t1 VALUES($doc); INSERT INTO ft VALUES($doc); } } } {} foreach v {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} { set L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}] set L2 [execsql {SELECT rowid FROM ft($v)}] do_test 4.2.$v { set L1 } $L2 } do_test 4.3 { for {set ii 1} {$ii < $nRow} {incr ii 2} { execsql { DELETE FROM ft WHERE rowid=$ii; DELETE FROM t1 WHERE rowid=$ii; } } } {} foreach v {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} { set L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}] set L2 [execsql {SELECT rowid FROM ft($v)}] do_test 4.4.$v { set L1 } $L2 } do_execsql_test 4.5 { INSERT INTO ft(ft) VALUES('optimize'); } {} foreach v {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} { set L1 [execsql {SELECT rowid FROM t1 WHERE x LIKE '%'||$v||'%'}] set L2 [execsql {SELECT rowid FROM ft($v)}] do_test 4.6.$v { set L1 } $L2 } #execsql_pp { SELECT fts5_decode(id, block) FROM ft_data } #------------------------------------------------------------------------- reset_db do_execsql_test 5.0 { CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); INSERT INTO ft(rowid, x) VALUES(1, 'one two three'); INSERT INTO ft(rowid, x) VALUES(2, 'one two four'); INSERT INTO ft(rowid, x) VALUES(3, 'one two five'); INSERT INTO ft(rowid, x) VALUES(4, 'one two seven'); INSERT INTO ft(rowid, x) VALUES(5, 'one two eight'); } do_execsql_test 5.1 { DELETE FROM ft WHERE rowid=2 } do_execsql_test 5.2 { SELECT rowid FROM ft } {1 3 4 5} do_catchsql_test 5.3 { UPDATE ft SET x='four six' WHERE rowid=3 } {1 {cannot UPDATE contentless fts5 table: ft}} do_execsql_test 5.4 { SELECT rowid FROM ft('one'); } {1 3 4 5} do_execsql_test 5.5 { REPLACE INTO ft(rowid, x) VALUES(3, 'four six'); SELECT rowid FROM ft('one'); } {1 4 5} do_execsql_test 5.6 { REPLACE INTO ft(rowid, x) VALUES(6, 'one two eleven'); SELECT rowid FROM ft('one'); } {1 4 5 6} #------------------------------------------------------------------------- reset_db do_execsql_test 6.0 { CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); INSERT INTO ft(rowid, x) VALUES(1, 'one two three'); INSERT INTO ft(rowid, x) VALUES(2, 'one two four'); } do_test 6.1 { db eval { SELECT rowid FROM ft('one two') } { if {$rowid==1} { db eval { INSERT INTO ft(rowid, x) VALUES(3, 'one two four') } } } } {} #------------------------------------------------------------------------- reset_db do_execsql_test 7.0 { CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); } set lRowid [list -450 0 1 2 42] do_test 7.1 { execsql BEGIN foreach r $lRowid { execsql { INSERT INTO ft(rowid, x) VALUES($r, 'one one one'); } } execsql COMMIT } {} do_test 7.2 { execsql BEGIN foreach r $lRowid { execsql { REPLACE INTO ft(rowid, x) VALUES($r, 'two two two'); } } execsql COMMIT } {} do_execsql_test 7.3 { SELECT rowid FROM ft('one'); } {} do_execsql_test 7.4 { SELECT rowid FROM ft('two'); } $lRowid #------------------------------------------------------------------------- reset_db do_execsql_test 8.0 { CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); INSERT INTO ft VALUES('hello world'); INSERT INTO ft VALUES('one two three'); } do_catchsql_test 8.1 { INSERT INTO ft(ft, rowid, x) VALUES('delete', 1, 'hello world'); } {1 {'delete' may not be used with a contentless_delete=1 table}} do_execsql_test 8.2 { BEGIN; INSERT INTO ft(rowid, x) VALUES(3, 'four four four'); DELETE FROM ft WHERE rowid=3; COMMIT; SELECT rowid FROM ft('four'); } {} finish_test |
Added ext/fts5/test/fts5contentless2.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 | # 2023 July 19 # # 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 contains tests for the content= and content_rowid= options. # source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5contentless2 # If SQLITE_ENABLE_FTS5 is defined, omit this file. ifcapable !fts5 { finish_test return } proc vocab {} { list aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp } proc document {nToken} { set doc [list] set vocab [vocab] for {set ii 0} {$ii < $nToken} {incr ii} { lappend doc [lindex $vocab [expr int(rand()*[llength $vocab])]] } set doc } db func document document proc contains {doc token} { expr {[lsearch $doc $token]>=0} } db func contains contains proc do_compare_tables_test {tn} { uplevel [list do_test $tn { foreach v [vocab] { set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $v) }] set l2 [execsql { SELECT rowid FROM t2($v) }] if {$l1!=$l2} { error "1: query mismatch ($l1) ($l2)" } set w "[string range $v 0 1]*" set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $w) }] set l2 [execsql { SELECT rowid FROM t2($w) }] if {$l1!=$l2} { error "2: query mismatch ($l1) ($l2)" } set w "[string range $v 0 0]*" set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $w) }] set l2 [execsql { SELECT rowid FROM t2($w) }] if {$l1!=$l2} { error "2: query mismatch ($l1) ($l2)" } set l1 [execsql { SELECT rowid FROM t1 WHERE contains(doc, $v) ORDER BY rowid DESC }] set l2 [execsql { SELECT rowid FROM t2($v) ORDER BY rowid DESC }] if {$l1!=$l2} { error "1: query mismatch ($l1) ($l2)" } } set {} {} } {}] } proc lshuffle {in} { set L [list] set ret [list] foreach elem $in { lappend L [list [expr rand()] $elem] } foreach pair [lsort -index 0 $L] { lappend ret [lindex $pair 1] } set ret } expr srand(0) do_execsql_test 1.0 { CREATE VIRTUAL TABLE t2 USING fts5( doc, prefix=2, content=, contentless_delete=1 ); CREATE TABLE t1(doc); CREATE TRIGGER tr1 AFTER DELETE ON t1 BEGIN DELETE FROM t2 WHERE rowid = old.rowid; END; } set SMALLEST64 -9223372036854775808 set LARGEST64 9223372036854775807 foreach {tn r1 r2} { 1 0 50 2 $SMALLEST64 $SMALLEST64+50 3 $LARGEST64-50 $LARGEST64 4 -50 -1 } { set r1 [expr $r1] set r2 [expr $r2] do_test 1.1.$tn { execsql BEGIN for {set ii $r1} {$ii <= $r2} {incr ii} { execsql { INSERT INTO t1(rowid, doc) VALUES ($ii, document(8)); } } execsql COMMIT } {} } do_test 1.2 { db eval { SELECT rowid, doc FROM t1 } { execsql { INSERT INTO t2(rowid, doc) VALUES($rowid, $doc) } } } {} foreach {tn rowid} { 1 $SMALLEST64 2 0 3 -5 4 -30 5 $LARGEST64 6 $LARGEST64-1 } { set rowid [expr $rowid] do_execsql_test 1.3.$tn.1 { DELETE FROM t1 WHERE rowid=$rowid } do_compare_tables_test 1.3.$tn.2 } set iTest 1 foreach r [lshuffle [execsql {SELECT rowid FROM t1}]] { if {($iTest % 50)==0} { execsql { INSERT INTO t2(t2) VALUES('optimize') } } if {($iTest % 5)==0} { execsql { INSERT INTO t2(t2, rank) VALUES('merge', 5) } } do_execsql_test 1.4.$iTest.1($r) { DELETE FROM t1 WHERE rowid=$r } do_compare_tables_test 1.4.$iTest.2 incr iTest } do_execsql_test 1.5 { SELECT * FROM t1 } {} #------------------------------------------------------------------------- reset_db db func document document do_execsql_test 2.0 { CREATE VIRTUAL TABLE t2 USING fts5(doc, content=, contentless_delete=1); WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 ) INSERT INTO t2(rowid, doc) SELECT i, i || ' ' || i FROM s; } do_execsql_test 2.1 { BEGIN; DELETE FROM t2 WHERE rowid=32; DELETE FROM t2 WHERE rowid=64; DELETE FROM t2 WHERE rowid=96; DELETE FROM t2 WHERE rowid=128; DELETE FROM t2 WHERE rowid=160; DELETE FROM t2 WHERE rowid=192; COMMIT; } do_execsql_test 2.2 { SELECT * FROM t2('128'); } {} #------------------------------------------------------------------------- foreach {tn step} { 1 3 2 7 3 15 } { set step [expr $step] reset_db db func document document do_execsql_test 3.$tn.0 { CREATE VIRTUAL TABLE t2 USING fts5(doc, content=, contentless_delete=1); INSERT INTO t2(t2, rank) VALUES('pgsz', 100); WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 ) INSERT INTO t2(rowid, doc) SELECT i, i || ' ' || i FROM s; } do_execsql_test 3.$tn.1 { DELETE FROM t2 WHERE (rowid % $step)==0 } do_execsql_test 3.$tn.2 { SELECT * FROM t2( $step * 5 ) } {} } finish_test |
Added ext/fts5/test/fts5contentless3.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 | # 2023 July 21 # # 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 contains tests for the content= and content_rowid= options. # source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5contentless3 # If SQLITE_ENABLE_FTS5 is defined, omit this file. ifcapable !fts5 { finish_test return } do_execsql_test 1.0 { CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1); BEGIN; INSERT INTO ft VALUES('one one one'); INSERT INTO ft VALUES('two two two'); INSERT INTO ft VALUES('three three three'); INSERT INTO ft VALUES('four four four'); INSERT INTO ft VALUES('five five five'); INSERT INTO ft VALUES('six six six'); INSERT INTO ft VALUES('seven seven seven'); INSERT INTO ft VALUES('eight eight eight'); INSERT INTO ft VALUES('nine nine nine'); COMMIT; DELETE FROM ft WHERE rowid=3; } proc myhex {hex} { binary decode hex $hex } db func myhex myhex do_execsql_test 1.1 { UPDATE ft_data SET block = myhex('04000000 00000001' || '01020304 01020304 01020304 01020304' || '01020304 01020304 01020304 01020304' ) WHERE id = (SELECT max(id) FROM ft_data); } do_execsql_test 1.2 { DELETE FROM ft WHERE rowid=1 } do_execsql_test 1.3 { SELECT rowid FROM ft('two'); } {2} do_execsql_test 1.3 { UPDATE ft_data SET block = myhex('08000000 00000001' || '0000000001020304 0000000001020304 0000000001020304 0000000001020304' || '0000000001020304 0000000001020304 0000000001020304 0000000001020304' ) WHERE id = (SELECT max(id) FROM ft_data); } do_execsql_test 1.4 { SELECT rowid FROM ft('two'); } {2} do_execsql_test 1.5 { DELETE FROM ft WHERE rowid=4 } do_execsql_test 1.6 { UPDATE ft_data SET block = myhex('04000000 00000000') WHERE id = (SELECT max(id) FROM ft_data); } do_execsql_test 1.7 { SELECT rowid FROM ft('two'); } {2} do_execsql_test 1.8 { UPDATE ft_data SET block = myhex('04000000 00000000') WHERE id = (SELECT max(id) FROM ft_data); } do_execsql_test 1.9 { DELETE FROM ft WHERE rowid=8 } {} #------------------------------------------------------------------------- reset_db do_execsql_test 2.0 { CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1); INSERT INTO ft VALUES('one one one'); INSERT INTO ft VALUES('two two two'); INSERT INTO ft VALUES('three three three'); INSERT INTO ft VALUES('four four four'); INSERT INTO ft VALUES('five five five'); INSERT INTO ft VALUES('six six six'); INSERT INTO ft VALUES('seven seven seven'); INSERT INTO ft VALUES('eight eight eight'); INSERT INTO ft VALUES('nine nine nine'); } do_execsql_test 2.1 { INSERT INTO ft(ft) VALUES('optimize'); } do_execsql_test 2.2 { SELECT count(*) FROM ft_data } {3} do_execsql_test 2.3 { DELETE FROM ft WHERE rowid=5 } do_execsql_test 2.4 { SELECT count(*) FROM ft_data } {4} # Check that an 'optimize' works (rewrites the index) if there is a single # segment with one or more tombstone hash pages. do_execsql_test 2.5 { INSERT INTO ft(ft) VALUES('optimize'); } do_execsql_test 2.6 { SELECT count(*) FROM ft_data } {3} # Check that an 'optimize' is a no-op if there is a single segment # and no tombstone hash pages. do_execsql_test 2.7 { INSERT INTO ft(ft) VALUES('optimize'); SELECT rowid FROM ft_data; } [db eval {SELECT rowid FROM ft_data}] #------------------------------------------------------------------------- reset_db do_execsql_test 3.0 { CREATE VIRTUAL TABLE ft USING fts5(x, content=, contentless_delete=1); INSERT INTO ft(ft, rank) VALUES('pgsz', 64); WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 ) INSERT INTO ft(rowid, x) SELECT i, i||' '||i||' '||i||' '||i FROM s; INSERT INTO ft(ft) VALUES('optimize'); } do_execsql_test 3.1 { SELECT count(*) FROM ft_data } {200} do_execsql_test 3.2 { DELETE FROM ft WHERE (rowid % 50)==0; SELECT count(*) FROM ft_data; } {203} do_execsql_test 3.3 { INSERT INTO ft(ft, rank) VALUES('merge', 500); SELECT rowid FROM ft_data; } [db eval {SELECT rowid FROM ft_data}] do_execsql_test 3.4 { INSERT INTO ft(ft, rank) VALUES('merge', -1000); SELECT count(*) FROM ft_data; } {197} do_execsql_test 3.5 { DELETE FROM ft WHERE (rowid % 50)==1; SELECT count(*) FROM ft_data; } {200} do_execsql_test 3.6 { SELECT level, segment, npgtombstone FROM fts5_structure( (SELECT block FROM ft_data WHERE id=10) ) } {1 0 3} do_test 3.6 { while 1 { set nChange [db total_changes] execsql { INSERT INTO ft(ft, rank) VALUES('merge', -5) } if {([db total_changes] - $nChange)<2} break } } {} do_execsql_test 3.7 { SELECT level, segment, npgtombstone FROM fts5_structure( (SELECT block FROM ft_data WHERE id=10) ) } {2 0 0} finish_test |
Added ext/fts5/test/fts5contentless4.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 | # 2023 July 21 # # 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 contains tests for the content= and content_rowid= options. # source [file join [file dirname [info script]] fts5_common.tcl] set testprefix fts5contentless4 # If SQLITE_ENABLE_FTS5 is defined, omit this file. ifcapable !fts5 { finish_test return } proc document {n} { set vocab [list A B C D E F G H I J K L M N O P Q R S T U V W X Y Z] set ret [list] for {set ii 0} {$ii < $n} {incr ii} { lappend ret [lindex $vocab [expr int(rand()*[llength $vocab])]] } set ret } db func document document do_execsql_test 1.0 { CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); INSERT INTO ft(ft, rank) VALUES('pgsz', 240); WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 ) INSERT INTO ft SELECT document(12) FROM s; } do_execsql_test 1.1 { INSERT INTO ft(ft) VALUES('optimize'); } do_execsql_test 1.2 { SELECT level, segment, nentry, nentrytombstone FROM fts5_structure(( SELECT block FROM ft_data WHERE id=10 )) } {0 0 1000 0} do_execsql_test 1.3 { DELETE FROM ft WHERE rowid < 50 } do_execsql_test 1.4 { SELECT level, segment, nentry, nentrytombstone FROM fts5_structure(( SELECT block FROM ft_data WHERE id=10 )) } {0 0 1000 49} do_execsql_test 1.5 { DELETE FROM ft WHERE rowid < 1000 } do_execsql_test 1.6 { SELECT level, segment, nentry, nentrytombstone FROM fts5_structure(( SELECT block FROM ft_data WHERE id=10 )) } {1 0 1 0} #-------------------------------------------------------------------------- reset_db db func document document do_execsql_test 2.0 { CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); } do_test 2.1 { for {set ii 0} {$ii < 5000} {incr ii} { execsql { INSERT INTO ft VALUES( document(12) ) } } } {} do_execsql_test 2.2 { SELECT sum(nentry) - sum(nentrytombstone) FROM fts5_structure(( SELECT block FROM ft_data WHERE id=10 )) } {5000} for {set ii 5000} {$ii >= 0} {incr ii -100} { do_execsql_test 2.3.$ii { DELETE FROM ft WHERE rowid > $ii } do_execsql_test 2.3.$ii.2 { SELECT CAST((total(nentry) - total(nentrytombstone)) AS integer) FROM fts5_structure( (SELECT block FROM ft_data WHERE id=10) ) } $ii } execsql_pp { SELECT * FROM fts5_structure(( SELECT block FROM ft_data WHERE id=10 )) } do_test 2.4 { for {set ii 0} {$ii < 5000} {incr ii} { execsql { INSERT INTO ft VALUES( document(12) ) } } } {} for {set ii 1} {$ii <= 5000} {incr ii 10} { do_execsql_test 2.3.$ii { DELETE FROM ft WHERE rowid = $ii; INSERT INTO ft VALUES( document(12) ); INSERT INTO ft(ft, rank) VALUES('merge', -10); } do_execsql_test 2.3.$ii.2 { SELECT CAST((total(nentry) - total(nentrytombstone)) AS integer) FROM fts5_structure( (SELECT block FROM ft_data WHERE id=10) ) } 5000 } #------------------------------------------------------------------------- reset_db db func document document do_execsql_test 3.0 { CREATE VIRTUAL TABLE ft USING fts5(x, content='', contentless_delete=1); WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100 ) INSERT INTO ft SELECT document(12) FROM s; } do_catchsql_test 3.1 { INSERT INTO ft(ft, rank) VALUES('deletemerge', 'text'); } {1 {SQL logic error}} do_catchsql_test 3.2 { INSERT INTO ft(ft, rank) VALUES('deletemerge', 50); } {0 {}} do_execsql_test 3.3 { SELECT * FROM ft_config WHERE k='deletemerge' } {deletemerge 50} do_catchsql_test 3.4 { INSERT INTO ft(ft, rank) VALUES('deletemerge', 101); } {0 {}} do_execsql_test 3.5 { SELECT * FROM ft_config WHERE k='deletemerge' } {deletemerge 101} do_execsql_test 3.6 { DELETE FROM ft WHERE rowid<95 } do_execsql_test 3.7 { SELECT nentrytombstone, nentry FROM fts5_structure(( SELECT block FROM ft_data WHERE id=10 )) } {94 100} do_execsql_test 3.8 { DELETE FROM ft WHERE rowid=95 } do_execsql_test 3.9 { SELECT nentrytombstone, nentry FROM fts5_structure(( SELECT block FROM ft_data WHERE id=10 )) } {95 100} do_execsql_test 3.10 { DELETE FROM ft; WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100 ) INSERT INTO ft SELECT document(12) FROM s; INSERT INTO ft(ft, rank) VALUES('deletemerge', 50); } do_execsql_test 3.11 { DELETE FROM ft WHERE rowid<95 } do_execsql_test 3.12 { SELECT nentrytombstone, nentry FROM fts5_structure(( SELECT block FROM ft_data WHERE id=10 )) } {0 6} #------------------------------------------------------------------------- reset_db db func document document do_execsql_test 4.0 { CREATE VIRTUAL TABLE x1 USING fts5(x, content='', contentless_delete=1); INSERT INTO x1(x1, rank) VALUES('usermerge', 16); INSERT INTO x1(x1, rank) VALUES('deletemerge', 40); INSERT INTO x1 VALUES('one'); INSERT INTO x1 VALUES('two'); INSERT INTO x1 VALUES('three'); INSERT INTO x1 VALUES('four'); INSERT INTO x1 VALUES('five'); INSERT INTO x1 VALUES('six'); INSERT INTO x1 VALUES('seven'); INSERT INTO x1 VALUES('eight'); INSERT INTO x1 VALUES('nine'); INSERT INTO x1 VALUES('ten'); } do_execsql_test 4.1 { SELECT level, segment FROM fts5_structure(( SELECT block FROM x1_data WHERE id=10 )) } { 0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 } for {set ii 1} {$ii < 4} {incr ii} { do_execsql_test 4.2.$ii { DELETE FROM x1 WHERE rowid = $ii; INSERT INTO x1(x1, rank) VALUES('merge', 5); SELECT level, segment FROM fts5_structure(( SELECT block FROM x1_data WHERE id=10 )) } { 0 0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 } } do_execsql_test 4.3 { DELETE FROM x1 WHERE rowid = $ii; INSERT INTO x1(x1, rank) VALUES('merge', 5); SELECT level, segment, nentry FROM fts5_structure(( SELECT block FROM x1_data WHERE id=10 )) } { 1 0 6 } finish_test |
Added ext/fts5/test/fts5faultF.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 | # 2023 July 20 # # 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 is focused on OOM errors. Particularly those that may occur # when using contentless_delete=1 databases. # source [file join [file dirname [info script]] fts5_common.tcl] source $testdir/malloc_common.tcl set testprefix fts5faultF # If SQLITE_ENABLE_FTS5 is not defined, omit this file. ifcapable !fts5 { finish_test return } faultsim_save_and_close do_faultsim_test 1 -prep { faultsim_restore_and_reopen } -body { execsql { CREATE VIRTUAL TABLE t1 USING fts5(x, y, content=, contentless_delete=1) } } -test { faultsim_test_result {0 {}} {1 {vtable constructor failed: t1}} } reset_db do_execsql_test 2.0 { CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1); BEGIN; INSERT INTO t1(rowid, doc) VALUES(1, 'a b c d'); INSERT INTO t1(rowid, doc) VALUES(2, 'a b c d'); INSERT INTO t1(rowid, doc) VALUES(3, 'a b c d'); INSERT INTO t1(rowid, doc) VALUES(4, 'a b c d'); COMMIT; DELETE FROM t1 WHERE rowid IN (2, 4); } do_faultsim_test 2 -prep { sqlite3 db test.db execsql { SELECT rowid FROM t1 } } -body { execsql { SELECT rowid FROM t1('b c'); } } -test { faultsim_test_result {0 {1 3}} } #------------------------------------------------------------------------- reset_db do_execsql_test 3.0 { CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1); BEGIN; INSERT INTO t1(rowid, doc) VALUES(1, 'a b c d'); INSERT INTO t1(rowid, doc) VALUES(2, 'a b c d'); INSERT INTO t1(rowid, doc) VALUES(3, 'a b c d'); INSERT INTO t1(rowid, doc) VALUES(4, 'a b c d'); COMMIT; } faultsim_save_and_close do_faultsim_test 3 -prep { faultsim_restore_and_reopen execsql { SELECT rowid FROM t1 } } -body { execsql { INSERT INTO t1(rowid, doc) VALUES(5, 'a b c d'); } } -test { faultsim_test_result {0 {}} } #------------------------------------------------------------------------- reset_db do_execsql_test 4.0 { CREATE VIRTUAL TABLE t1 USING fts5(doc, content=, contentless_delete=1); INSERT INTO t1(t1, rank) VALUES('pgsz', 64); WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<1000 ) INSERT INTO t1(rowid, doc) SELECT i, 'a b c d' FROM s; } do_execsql_test 4.1 { DELETE FROM t1 WHERE rowid <= 25 } faultsim_save_and_close do_faultsim_test 4 -faults oom-t* -prep { faultsim_restore_and_reopen execsql { SELECT rowid FROM t1 } } -body { execsql { DELETE FROM t1 WHERE rowid < 100 } } -test { faultsim_test_result {0 {}} } finish_test |
Changes to ext/session/sqlite3session.c.
︙ | ︙ | |||
887 888 889 890 891 892 893 894 895 896 897 898 899 900 | /* assert( db->pPreUpdate->pNewUnpacked || db->pPreUpdate->aNew ); */ rc = pSession->hook.xNew(pSession->hook.pCtx, iCol, &pVal); }else{ /* assert( db->pPreUpdate->pUnpacked ); */ rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal); } assert( rc==SQLITE_OK ); if( sqlite3_value_type(pVal)!=eType ) return 0; /* A SessionChange object never has a NULL value in a PK column */ assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT || eType==SQLITE_BLOB || eType==SQLITE_TEXT ); | > | 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 | /* assert( db->pPreUpdate->pNewUnpacked || db->pPreUpdate->aNew ); */ rc = pSession->hook.xNew(pSession->hook.pCtx, iCol, &pVal); }else{ /* assert( db->pPreUpdate->pUnpacked ); */ rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal); } assert( rc==SQLITE_OK ); (void)rc; /* Suppress warning about unused variable */ if( sqlite3_value_type(pVal)!=eType ) return 0; /* A SessionChange object never has a NULL value in a PK column */ assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT || eType==SQLITE_BLOB || eType==SQLITE_TEXT ); |
︙ | ︙ |
Changes to ext/wasm/GNUmakefile.
︙ | ︙ | |||
447 448 449 450 451 452 453 454 455 456 457 458 459 460 | emcc.jsflags += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.api) emcc.exportedRuntimeMethods := \ -sEXPORTED_RUNTIME_METHODS=wasmMemory # wasmMemory ==> required by our code for use with -sIMPORTED_MEMORY emcc.jsflags += $(emcc.exportedRuntimeMethods) emcc.jsflags += -sUSE_CLOSURE_COMPILER=0 emcc.jsflags += -sIMPORTED_MEMORY emcc.jsflags += -sSTRICT_JS=0 # STRICT_JS disabled due to: # https://github.com/emscripten-core/emscripten/issues/18610 # TL;DR: does not work with MODULARIZE or EXPORT_ES6 as of version 3.1.31. # -sENVIRONMENT values for the various build modes: emcc.environment.vanilla := web,worker | > > > > > > > | 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 | emcc.jsflags += -sEXPORTED_FUNCTIONS=@$(EXPORTED_FUNCTIONS.api) emcc.exportedRuntimeMethods := \ -sEXPORTED_RUNTIME_METHODS=wasmMemory # wasmMemory ==> required by our code for use with -sIMPORTED_MEMORY emcc.jsflags += $(emcc.exportedRuntimeMethods) emcc.jsflags += -sUSE_CLOSURE_COMPILER=0 emcc.jsflags += -sIMPORTED_MEMORY #emcc.jsflags += -sASYNCIFY=2 # ^^^ ASYNCIFY=2 is for experimental JSPI support # (https://v8.dev/blog/jspi), but enabling it causes the lib-level # init code to throw inexplicable complaints about C-level function # signatures not matching what we expect them to be. JSPI requires, as of # this writing, requires an experimental Chrome flag: # chrome://flags/#enable-experimental-webassembly-stack-switching emcc.jsflags += -sSTRICT_JS=0 # STRICT_JS disabled due to: # https://github.com/emscripten-core/emscripten/issues/18610 # TL;DR: does not work with MODULARIZE or EXPORT_ES6 as of version 3.1.31. # -sENVIRONMENT values for the various build modes: emcc.environment.vanilla := web,worker |
︙ | ︙ | |||
673 674 675 676 677 678 679 680 681 682 | # when building *.mjs, which is bad because we need to export an # overwritten version of that function and cannot "export default" # twice. Because of this, we have to sed *.mjs to remove the _first_ # instance (only) of /^export default/. # # Upstream RFE: # https://github.com/emscripten-core/emscripten/issues/18237 define SQLITE3.xJS.ESM-EXPORT-DEFAULT if [ x1 = x$(1) ]; then \ echo "Fragile workaround for emscripten/issues/18237. See SQLITE3.xJS.RECIPE."; \ | > > > > > | | 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 | # when building *.mjs, which is bad because we need to export an # overwritten version of that function and cannot "export default" # twice. Because of this, we have to sed *.mjs to remove the _first_ # instance (only) of /^export default/. # # Upstream RFE: # https://github.com/emscripten-core/emscripten/issues/18237 # # Maintenance reminder: Mac sed works differently than GNU sed, so # don't use sed for this. define SQLITE3.xJS.ESM-EXPORT-DEFAULT if [ x1 = x$(1) ]; then \ echo "Fragile workaround for emscripten/issues/18237. See SQLITE3.xJS.RECIPE."; \ {\ awk '/^export default/ && !f{f=1; next} 1' $@ > $@.tmp && mv $@.tmp $@; \ } || exit $$?; \ if [ x != x$(2) ]; then \ if ! grep -q '^export default' $@; then \ echo "Cannot find export default." 1>&2; \ exit 1; \ fi; \ fi; \ fi |
︙ | ︙ | |||
854 855 856 857 858 859 860 | ls -1 $(dir.sql)/*.sql | grep -v speedtest1.sql | sort > $@ clean-batch: rm -f batch-runner.list $(dir.sql)/speedtest1*.sql # ^^^ we don't do this along with 'clean' because we clean/rebuild on # a regular basis with different -Ox flags and rebuilding the batch # pieces each time is an unnecessary time sink. batch: batch-runner.list | | | 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 | ls -1 $(dir.sql)/*.sql | grep -v speedtest1.sql | sort > $@ clean-batch: rm -f batch-runner.list $(dir.sql)/speedtest1*.sql # ^^^ we don't do this along with 'clean' because we clean/rebuild on # a regular basis with different -Ox flags and rebuilding the batch # pieces each time is an unnecessary time sink. batch: batch-runner.list #all: batch # end batch-runner.js ######################################################################## # Wasmified speedtest1 is our primary benchmarking tool. # # emcc.speedtest1.common = emcc flags used by multiple builds of speedtest1 # emcc.speedtest1 = emcc flags used by main build of speedtest1 emcc.speedtest1.common := $(emcc_opt_full) |
︙ | ︙ |
Changes to ext/wasm/README.md.
︙ | ︙ | |||
86 87 88 89 90 91 92 | * Remote: Install git, emsdk, and althttpd * Use a [version of althttpd][althttpd] from September 26, 2022 or newer. * Remote: Install the SQLite source tree. CD to ext/wasm * Remote: "`make`" to build WASM * Remote: `althttpd --enable-sab --port 8080 --popup` | | | 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | * Remote: Install git, emsdk, and althttpd * Use a [version of althttpd][althttpd] from September 26, 2022 or newer. * Remote: Install the SQLite source tree. CD to ext/wasm * Remote: "`make`" to build WASM * Remote: `althttpd --enable-sab --port 8080 --popup` * Local: `ssh -L 8180:localhost:8080 remote` * Local: Point your web-browser at http://localhost:8180/index.html In order to enable [SharedArrayBuffer][], the web-browser requires that the two extra Cross-Origin lines be present in HTTP reply headers and that the request must come from "localhost" (_or_ over an SSL connection). Since the web-server is on a different machine from the web-broser, the localhost requirement means that the connection must |
︙ | ︙ |
Changes to ext/wasm/api/extern-post-js.c-pp.js.
︙ | ︙ | |||
115 116 117 118 119 120 121 | } /* AMD modules get injected in a way we cannot override, so we can't handle those here. */ //#endif // !target=es6-module return globalThis.sqlite3InitModule /* required for ESM */; })(); //#if target=es6-module | | > | 115 116 117 118 119 120 121 122 123 124 | } /* AMD modules get injected in a way we cannot override, so we can't handle those here. */ //#endif // !target=es6-module return globalThis.sqlite3InitModule /* required for ESM */; })(); //#if target=es6-module sqlite3InitModule = toExportForESM; export default sqlite3InitModule; //#endif |
Changes to ext/wasm/api/sqlite3-api-cleanup.js.
︙ | ︙ | |||
18 19 20 21 22 23 24 | 'use strict'; if('undefined' !== typeof Module){ // presumably an Emscripten build /** Install a suitable default configuration for sqlite3ApiBootstrap(). */ const SABC = Object.assign( Object.create(null), { | > | > | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 'use strict'; if('undefined' !== typeof Module){ // presumably an Emscripten build /** Install a suitable default configuration for sqlite3ApiBootstrap(). */ const SABC = Object.assign( Object.create(null), { exports: ('undefined'===typeof wasmExports) ? Module['asm']/* emscripten <=3.1.43 */ : wasmExports /* emscripten >=3.1.44 */, memory: Module.wasmMemory /* gets set if built with -sIMPORTED_MEMORY */ }, globalThis.sqlite3ApiConfig || {} ); /** For current (2022-08-22) purposes, automatically call |
︙ | ︙ |
Changes to ext/wasm/api/sqlite3-api-glue.js.
︙ | ︙ | |||
724 725 726 727 728 729 730 731 732 733 734 735 736 737 | ('sqlite3_vfs*', __xRcPtr) ('void*', __xRcPtr); /** Populate api object with sqlite3_...() by binding the "raw" wasm exports into type-converting proxies using wasm.xWrap(). */ for(const e of wasm.bindingSignatures){ capi[e[0]] = wasm.xWrap.apply(null, e); } for(const e of wasm.bindingSignatures.wasm){ wasm[e[0]] = wasm.xWrap.apply(null, e); } | > > > > > > > > > | 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 | ('sqlite3_vfs*', __xRcPtr) ('void*', __xRcPtr); /** Populate api object with sqlite3_...() by binding the "raw" wasm exports into type-converting proxies using wasm.xWrap(). */ if(0 === wasm.exports.sqlite3_step.length){ /* This environment wraps exports in nullary functions, which means we must disable the arg-count validation we otherwise perform on the wrappers. */ wasm.xWrap.doArgcCheck = false; sqlite3.config.warn( "Disabling sqlite3.wasm.xWrap.doArgcCheck due to environmental quirks." ); } for(const e of wasm.bindingSignatures){ capi[e[0]] = wasm.xWrap.apply(null, e); } for(const e of wasm.bindingSignatures.wasm){ wasm[e[0]] = wasm.xWrap.apply(null, e); } |
︙ | ︙ |
Changes to ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js.
︙ | ︙ | |||
496 497 498 499 500 501 502 503 504 505 506 507 508 509 | getVfs(){return this.#cVfs} /* Current pool capacity. */ getCapacity(){return this.#mapSAHToName.size} /* Current number of in-use files from pool. */ getFileCount(){return this.#mapFilenameToSAH.size} // #createFileObject(sah,clientName,opaqueName){ // const f = Object.assign(Object.create(null),{ // clientName, opaqueName // }); // this.#mapSAHToMeta.set(sah, f); // return f; | > > > > > > > > > | 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 | getVfs(){return this.#cVfs} /* Current pool capacity. */ getCapacity(){return this.#mapSAHToName.size} /* Current number of in-use files from pool. */ getFileCount(){return this.#mapFilenameToSAH.size} /* Returns an array of the names of all currently-opened client-specified filenames. */ getFileNames(){ const rc = []; const iter = this.#mapFilenameToSAH.keys(); for(const n of iter) rc.push(n); return rc; } // #createFileObject(sah,clientName,opaqueName){ // const f = Object.assign(Object.create(null),{ // clientName, opaqueName // }); // this.#mapSAHToMeta.set(sah, f); // return f; |
︙ | ︙ | |||
897 898 899 900 901 902 903 904 905 906 907 908 909 910 | async addCapacity(n){ return this.#p.addCapacity(n) } async reduceCapacity(n){ return this.#p.reduceCapacity(n) } getCapacity(){ return this.#p.getCapacity(this.#p) } getFileCount(){ return this.#p.getFileCount() } async reserveMinimumCapacity(min){ const c = this.#p.getCapacity(); return (c < min) ? this.#p.addCapacity(min - c) : c; } exportFile(name){ return this.#p.exportFile(name) } | > | 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 | async addCapacity(n){ return this.#p.addCapacity(n) } async reduceCapacity(n){ return this.#p.reduceCapacity(n) } getCapacity(){ return this.#p.getCapacity(this.#p) } getFileCount(){ return this.#p.getFileCount() } getFileNames(){ return this.#p.getFileNames() } async reserveMinimumCapacity(min){ const c = this.#p.getCapacity(); return (c < min) ? this.#p.addCapacity(min - c) : c; } exportFile(name){ return this.#p.exportFile(name) } |
︙ | ︙ | |||
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 | or two databases and their associated temp files. - number getFileCount() Returns the number of files from the pool currently allocated to slots. This is not the same as the files being "opened". - void importDb(name, byteArray) Imports the contents of an SQLite database, provided as a byte array, under the given name, overwriting any existing content. Throws if the pool has no available file slots, on I/O error, or if the input does not appear to be a database. In the latter case, only a cursory examination is made. Note that this | > > > > > | 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 | or two databases and their associated temp files. - number getFileCount() Returns the number of files from the pool currently allocated to slots. This is not the same as the files being "opened". - array getFileNames() Returns an array of the names of the files currently allocated to slots. This list is the same length as getFileCount(). - void importDb(name, byteArray) Imports the contents of an SQLite database, provided as a byte array, under the given name, overwriting any existing content. Throws if the pool has no available file slots, on I/O error, or if the input does not appear to be a database. In the latter case, only a cursory examination is made. Note that this |
︙ | ︙ |
Changes to ext/wasm/common/whwasmutil.js.
︙ | ︙ | |||
107 108 109 110 111 112 113 | time one of the installed APIs is used (as opposed to when this function is called) except where explicitly noted: - `exports` must be a property of the target object OR a property of `target.instance` (a WebAssembly.Module instance) and it must contain the symbols exported by the WASM module associated with this code. In an Enscripten environment it must be set to | > | | 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | time one of the installed APIs is used (as opposed to when this function is called) except where explicitly noted: - `exports` must be a property of the target object OR a property of `target.instance` (a WebAssembly.Module instance) and it must contain the symbols exported by the WASM module associated with this code. In an Enscripten environment it must be set to `Module['asm']` (versions <=3.1.43) or `wasmExports` (versions >=3.1.44). The exports object must contain a minimum of the following symbols: - `memory`: a WebAssembly.Memory object representing the WASM memory. _Alternately_, the `memory` property can be set as `target.memory`, in particular if the WASM heap memory is initialized in JS an _imported_ into WASM, as opposed to being initialized in WASM and exported to JS. |
︙ | ︙ |
Changes to ext/wasm/demo-123.js.
︙ | ︙ | |||
231 232 233 234 235 236 237 | log("That's all, folks!"); /** Some of the features of the OO API not demonstrated above... - get change count (total or statement-local, 32- or 64-bit) - get a DB's file name | | | | 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | log("That's all, folks!"); /** Some of the features of the OO API not demonstrated above... - get change count (total or statement-local, 32- or 64-bit) - get a DB's file name Misc. Stmt features: - Various forms of bind() - clearBindings() - reset() - Various forms of step() - Variants of get() for explicit type treatment/conversion, e.g. getInt(), getFloat(), getBlob(), getJSON() - getColumnName(ndx), getColumnNames() - getParamIndex(name) |
︙ | ︙ |
Changes to ext/wasm/index.html.
︙ | ︙ | |||
103 104 105 106 107 108 109 | </ul> </li> <li>The obligatory "misc." category... <ul> <li><a href='module-symbols.html'>module-symbols</a> gives a high-level overview of the symbols exposed by the JS module.</li> | | > | 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | </ul> </li> <li>The obligatory "misc." category... <ul> <li><a href='module-symbols.html'>module-symbols</a> gives a high-level overview of the symbols exposed by the JS module.</li> <!--li><a href='batch-runner.html'>batch-runner</a>: runs batches of SQL exported from speedtest1.</li--> <li><a href='test-opfs-vfs.html'>test-opfs-vfs</a> (<a href='test-opfs-vfs.html?opfs-sanity-check&opfs-verbose'>same with verbose output and sanity-checking tests</a>) is an sqlite3_vfs OPFS proxy using SharedArrayBuffer and the Atomics APIs to regulate communication between the synchronous sqlite3_vfs interface and the async OPFS impl. |
︙ | ︙ |
Changes to ext/wasm/tester1.c-pp.js.
︙ | ︙ | |||
3068 3069 3070 3071 3072 3073 3074 | T.assert(1 === u1.getFileCount()); T.assert(3 === db.selectValue('select count(*) from t')); db.close(); T.assert(1 === u1.getFileCount()); db = new u2.OpfsSAHPoolDb(dbName); T.assert(1 === u1.getFileCount()); db.close(); | > > > | | > > > > > > > > > > < | 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 | T.assert(1 === u1.getFileCount()); T.assert(3 === db.selectValue('select count(*) from t')); db.close(); T.assert(1 === u1.getFileCount()); db = new u2.OpfsSAHPoolDb(dbName); T.assert(1 === u1.getFileCount()); db.close(); const fileNames = u1.getFileNames(); T.assert(1 === fileNames.length) .assert(dbName === fileNames[0]) .assert(1 === u1.getFileCount()) .assert(true === u1.unlink(dbName)) .assert(false === u1.unlink(dbName)) .assert(0 === u1.getFileCount()) .assert(0 === u1.getFileNames().length); // Demonstrate that two SAH pools can coexist so long as // they have different names. const conf2 = JSON.parse(JSON.stringify(sahPoolConfig)); conf2.name += '-test2'; const POther = await inst(conf2); log("Installed second SAH instance as",conf2.name); T.assert(0 === POther.getFileCount()) .assert(true === await POther.removeVfs()); if(0){ /* Enable this block to inspect vfs's contents via the dev console or OPFS Explorer browser extension. The following bits will remove them. */ return; } T.assert(true === await u2.removeVfs()) .assert(false === await u1.removeVfs()) .assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name)); let cErr, u3; conf2.$testThrowInInit = new Error("Testing throwing during init."); conf2.name = sahPoolConfig.name+'-err'; const P3 = await inst(conf2).then(u=>u3 = u).catch((e)=>cErr=e); T.assert(P3 === conf2.$testThrowInInit) .assert(cErr === P3) .assert(undefined === u3) .assert(!sqlite3.capi.sqlite3_vfs_find(conf2.name)); |
︙ | ︙ |
Changes to src/btree.c.
︙ | ︙ | |||
4822 4823 4824 4825 4826 4827 4828 | pCur->curFlags |= BTCF_Pinned; } void sqlite3BtreeCursorUnpin(BtCursor *pCur){ assert( (pCur->curFlags & BTCF_Pinned)!=0 ); pCur->curFlags &= ~BTCF_Pinned; } | < < | 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 | pCur->curFlags |= BTCF_Pinned; } void sqlite3BtreeCursorUnpin(BtCursor *pCur){ assert( (pCur->curFlags & BTCF_Pinned)!=0 ); pCur->curFlags &= ~BTCF_Pinned; } /* ** Return the offset into the database file for the start of the ** payload to which the cursor is pointing. */ i64 sqlite3BtreeOffset(BtCursor *pCur){ assert( cursorHoldsMutex(pCur) ); assert( pCur->eState==CURSOR_VALID ); getCellInfo(pCur); return (i64)pCur->pBt->pageSize*((i64)pCur->pPage->pgno - 1) + (i64)(pCur->info.pPayload - pCur->pPage->aData); } /* ** Return the number of bytes of payload for the entry that pCur is ** currently pointing to. For table btrees, this will be the amount ** of data. For index btrees, this will be the size of the key. ** ** The caller must guarantee that the cursor is pointing to a non-NULL |
︙ | ︙ |
Changes to src/btree.h.
︙ | ︙ | |||
317 318 319 320 321 322 323 | int sqlite3BtreeLast(BtCursor*, int *pRes); int sqlite3BtreeNext(BtCursor*, int flags); int sqlite3BtreeEof(BtCursor*); int sqlite3BtreePrevious(BtCursor*, int flags); i64 sqlite3BtreeIntegerKey(BtCursor*); void sqlite3BtreeCursorPin(BtCursor*); void sqlite3BtreeCursorUnpin(BtCursor*); | < < | 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 | int sqlite3BtreeLast(BtCursor*, int *pRes); int sqlite3BtreeNext(BtCursor*, int flags); int sqlite3BtreeEof(BtCursor*); int sqlite3BtreePrevious(BtCursor*, int flags); i64 sqlite3BtreeIntegerKey(BtCursor*); void sqlite3BtreeCursorPin(BtCursor*); void sqlite3BtreeCursorUnpin(BtCursor*); i64 sqlite3BtreeOffset(BtCursor*); int sqlite3BtreePayload(BtCursor*, u32 offset, u32 amt, void*); const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt); u32 sqlite3BtreePayloadSize(BtCursor*); sqlite3_int64 sqlite3BtreeMaxRecordSize(BtCursor*); int sqlite3BtreeIntegrityCheck( sqlite3 *db, /* Database connection that is running the check */ |
︙ | ︙ |
Changes to src/json.c.
︙ | ︙ | |||
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | # define VVA(X) X #endif /* Objects */ typedef struct JsonString JsonString; typedef struct JsonNode JsonNode; typedef struct JsonParse JsonParse; /* An instance of this object represents a JSON string ** under construction. Really, this is a generic string accumulator ** that can be and is used to create strings other than JSON. */ struct JsonString { sqlite3_context *pCtx; /* Function context - put error messages here */ char *zBuf; /* Append JSON content here */ u64 nAlloc; /* Bytes of storage available in zBuf[] */ u64 nUsed; /* Bytes of zBuf[] currently used */ u8 bStatic; /* True if zBuf is static space */ u8 bErr; /* True if an error has been encountered */ char zSpace[100]; /* Initial static space */ }; /* JSON type values */ | > > > > > > > > > > > | | | | | | | | > | | | | < | | | | > > > > > | > > | < | > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > | | 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 | # define VVA(X) X #endif /* Objects */ typedef struct JsonString JsonString; typedef struct JsonNode JsonNode; typedef struct JsonParse JsonParse; typedef struct JsonCleanup JsonCleanup; /* An instance of this object represents a JSON string ** under construction. Really, this is a generic string accumulator ** that can be and is used to create strings other than JSON. */ struct JsonString { sqlite3_context *pCtx; /* Function context - put error messages here */ char *zBuf; /* Append JSON content here */ u64 nAlloc; /* Bytes of storage available in zBuf[] */ u64 nUsed; /* Bytes of zBuf[] currently used */ u8 bStatic; /* True if zBuf is static space */ u8 bErr; /* True if an error has been encountered */ char zSpace[100]; /* Initial static space */ }; /* A deferred cleanup task. A list of JsonCleanup objects might be ** run when the JsonParse object is destroyed. */ struct JsonCleanup { JsonCleanup *pJCNext; /* Next in a list */ void (*xOp)(void*); /* Routine to run */ void *pArg; /* Argument to xOp() */ }; /* JSON type values */ #define JSON_SUBST 0 /* Special edit node. Uses u.iPrev */ #define JSON_NULL 1 #define JSON_TRUE 2 #define JSON_FALSE 3 #define JSON_INT 4 #define JSON_REAL 5 #define JSON_STRING 6 #define JSON_ARRAY 7 #define JSON_OBJECT 8 /* The "subtype" set for JSON values */ #define JSON_SUBTYPE 74 /* Ascii for "J" */ /* ** Names of the various JSON types: */ static const char * const jsonType[] = { "subst", "null", "true", "false", "integer", "real", "text", "array", "object" }; /* Bit values for the JsonNode.jnFlag field */ #define JNODE_RAW 0x01 /* Content is raw, not JSON encoded */ #define JNODE_ESCAPE 0x02 /* Content is text with \ escapes */ #define JNODE_REMOVE 0x04 /* Do not output */ #define JNODE_REPLACE 0x08 /* Target of a JSON_SUBST node */ #define JNODE_APPEND 0x10 /* More ARRAY/OBJECT entries at u.iAppend */ #define JNODE_LABEL 0x20 /* Is a label of an object */ #define JNODE_JSON5 0x40 /* Node contains JSON5 enhancements */ /* A single node of parsed JSON. An array of these nodes describes ** a parse of JSON + edits. ** ** Use the json_parse() SQL function (available when compiled with ** -DSQLITE_DEBUG) to see a dump of complete JsonParse objects, including ** a complete listing and decoding of the array of JsonNodes. */ struct JsonNode { u8 eType; /* One of the JSON_ type values */ u8 jnFlags; /* JNODE flags */ u8 eU; /* Which union element to use */ u32 n; /* Bytes of content for INT, REAL or STRING ** Number of sub-nodes for ARRAY and OBJECT ** Node that SUBST applies to */ union { const char *zJContent; /* 1: Content for INT, REAL, and STRING */ u32 iAppend; /* 2: More terms for ARRAY and OBJECT */ u32 iKey; /* 3: Key for ARRAY objects in json_tree() */ u32 iPrev; /* 4: Previous SUBST node, or 0 */ } u; }; /* A parsed and possibly edited JSON string. Lifecycle: ** ** 1. JSON comes in and is parsed into an array aNode[]. The original ** JSON text is stored in zJson. ** ** 2. Zero or more changes are made (via json_remove() or json_replace() ** or similar) to the aNode[] array. ** ** 3. A new, edited and mimified JSON string is generated from aNode ** and stored in zAlt. The JsonParse object always owns zAlt. ** ** Step 1 always happens. Step 2 and 3 may or may not happen, depending ** on the operation. ** ** aNode[].u.zJContent entries typically point into zJson. Hence zJson ** must remain valid for the lifespan of the parse. For edits, ** aNode[].u.zJContent might point to malloced space other than zJson. ** Entries in pClup are responsible for freeing that extra malloced space. ** ** When walking the parse tree in aNode[], edits are ignored if useMod is ** false. */ struct JsonParse { u32 nNode; /* Number of slots of aNode[] used */ u32 nAlloc; /* Number of slots of aNode[] allocated */ JsonNode *aNode; /* Array of nodes containing the parse */ char *zJson; /* Original JSON string (before edits) */ char *zAlt; /* Revised and/or mimified JSON */ u32 *aUp; /* Index of parent of each node */ JsonCleanup *pClup;/* Cleanup operations prior to freeing this object */ u16 iDepth; /* Nesting depth */ u8 nErr; /* Number of errors seen */ u8 oom; /* Set to true if out of memory */ u8 bJsonIsRCStr; /* True if zJson is an RCStr */ u8 hasNonstd; /* True if input uses non-standard features like JSON5 */ u8 useMod; /* Actually use the edits contain inside aNode */ u8 hasMod; /* aNode contains edits from the original zJson */ u32 nJPRef; /* Number of references to this object */ int nJson; /* Length of the zJson string in bytes */ int nAlt; /* Length of alternative JSON string zAlt, in bytes */ u32 iErr; /* Error location in zJson[] */ u32 iSubst; /* Last JSON_SUBST entry in aNode[] */ u32 iHold; /* Age of this entry in the cache for LRU replacement */ }; /* ** Maximum nesting depth of JSON for this implementation. ** ** This limit is needed to avoid a stack overflow in the recursive ** descent parser. A depth of 1000 is far deeper than any sane JSON |
︙ | ︙ | |||
166 167 168 169 170 171 172 | */ static void jsonInit(JsonString *p, sqlite3_context *pCtx){ p->pCtx = pCtx; p->bErr = 0; jsonZero(p); } | < | < | | | | > | < | 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 | */ static void jsonInit(JsonString *p, sqlite3_context *pCtx){ p->pCtx = pCtx; p->bErr = 0; jsonZero(p); } /* Free all allocated memory and reset the JsonString object back to its ** initial state. */ static void jsonReset(JsonString *p){ if( !p->bStatic ) sqlite3RCStrUnref(p->zBuf); jsonZero(p); } /* Report an out-of-memory (OOM) condition */ static void jsonOom(JsonString *p){ p->bErr = 1; sqlite3_result_error_nomem(p->pCtx); jsonReset(p); } /* Enlarge pJson->zBuf so that it can hold at least N more bytes. ** Return zero on success. Return non-zero on an OOM error */ static int jsonGrow(JsonString *p, u32 N){ u64 nTotal = N<p->nAlloc ? p->nAlloc*2 : p->nAlloc+N+10; char *zNew; if( p->bStatic ){ if( p->bErr ) return 1; zNew = sqlite3RCStrNew(nTotal); if( zNew==0 ){ jsonOom(p); return SQLITE_NOMEM; } memcpy(zNew, p->zBuf, (size_t)p->nUsed); p->zBuf = zNew; p->bStatic = 0; }else{ p->zBuf = sqlite3RCStrResize(p->zBuf, nTotal); if( p->zBuf==0 ){ p->bErr = 1; jsonZero(p); return SQLITE_NOMEM; } } p->nAlloc = nTotal; return SQLITE_OK; } /* Append N bytes from zIn onto the end of the JsonString string. */ |
︙ | ︙ | |||
268 269 270 271 272 273 274 275 276 277 278 279 280 281 | static void jsonAppendChar(JsonString *p, char c){ if( p->nUsed>=p->nAlloc ){ jsonAppendCharExpand(p,c); }else{ p->zBuf[p->nUsed++] = c; } } /* Append a comma separator to the output buffer, if the previous ** character is not '[' or '{'. */ static void jsonAppendSeparator(JsonString *p){ char c; if( p->nUsed==0 ) return; | > > > > > > > > > > > > > > > > > > | 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 | static void jsonAppendChar(JsonString *p, char c){ if( p->nUsed>=p->nAlloc ){ jsonAppendCharExpand(p,c); }else{ p->zBuf[p->nUsed++] = c; } } /* Try to force the string to be a zero-terminated RCStr string. ** ** Return true on success. Return false if an OOM prevents this ** from happening. */ static int jsonForceRCStr(JsonString *p){ jsonAppendChar(p, 0); if( p->bErr ) return 0; p->nUsed--; if( p->bStatic==0 ) return 1; p->nAlloc = 0; p->nUsed++; jsonGrow(p, p->nUsed); p->nUsed--; return p->bStatic==0; } /* Append a comma separator to the output buffer, if the previous ** character is not '[' or '{'. */ static void jsonAppendSeparator(JsonString *p){ char c; if( p->nUsed==0 ) return; |
︙ | ︙ | |||
340 341 342 343 344 345 346 | N -= 2; while( N>0 ){ for(i=0; i<N && zIn[i]!='\\'; i++){} if( i>0 ){ jsonAppendRawNZ(p, zIn, i); zIn += i; N -= i; | | | 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 | N -= 2; while( N>0 ){ for(i=0; i<N && zIn[i]!='\\'; i++){} if( i>0 ){ jsonAppendRawNZ(p, zIn, i); zIn += i; N -= i; if( N==0 ) break; } assert( zIn[0]=='\\' ); switch( (u8)zIn[1] ){ case '\'': jsonAppendChar(p, '\''); break; case 'v': |
︙ | ︙ | |||
449 450 451 452 453 454 455 | jsonAppendRawNZ(p, zIn, N); } } /* | | | 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 | jsonAppendRawNZ(p, zIn, N); } } /* ** Append a function parameter value to the JSON string under ** construction. */ static void jsonAppendValue( JsonString *p, /* Append to this JSON string */ sqlite3_value *pValue /* Value to append */ ){ switch( sqlite3_value_type(pValue) ){ |
︙ | ︙ | |||
494 495 496 497 498 499 500 501 502 503 | break; } } } /* Make the JSON in p the result of the SQL function. */ static void jsonResult(JsonString *p){ if( p->bErr==0 ){ | > > | | | > > > > | < | > | > > > | 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 | break; } } } /* Make the JSON in p the result of the SQL function. ** ** The JSON string is reset. */ static void jsonResult(JsonString *p){ if( p->bErr==0 ){ if( p->bStatic ){ sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, SQLITE_TRANSIENT, SQLITE_UTF8); }else if( jsonForceRCStr(p) ){ sqlite3RCStrRef(p->zBuf); sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, (void(*)(void*))sqlite3RCStrUnref, SQLITE_UTF8); } } if( p->bErr==1 ){ sqlite3_result_error_nomem(p->pCtx); } jsonReset(p); } /************************************************************************** ** Utility routines for dealing with JsonNode and JsonParse objects **************************************************************************/ /* |
︙ | ︙ | |||
528 529 530 531 532 533 534 | } /* ** Reclaim all memory allocated by a JsonParse object. But do not ** delete the JsonParse object itself. */ static void jsonParseReset(JsonParse *pParse){ | > > > > > > > > | | > > | | > > > > > > > > > > > > > > > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > | < < | > > > | > > > > | | | | < > | 599 600 601 602 603 604 605 606 607 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 657 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 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 | } /* ** Reclaim all memory allocated by a JsonParse object. But do not ** delete the JsonParse object itself. */ static void jsonParseReset(JsonParse *pParse){ while( pParse->pClup ){ JsonCleanup *pTask = pParse->pClup; pParse->pClup = pTask->pJCNext; pTask->xOp(pTask->pArg); sqlite3_free(pTask); } assert( pParse->nJPRef<=1 ); if( pParse->aNode ){ sqlite3_free(pParse->aNode); pParse->aNode = 0; } pParse->nNode = 0; pParse->nAlloc = 0; if( pParse->aUp ){ sqlite3_free(pParse->aUp); pParse->aUp = 0; } if( pParse->bJsonIsRCStr ){ sqlite3RCStrUnref(pParse->zJson); pParse->zJson = 0; pParse->bJsonIsRCStr = 0; } if( pParse->zAlt ){ sqlite3RCStrUnref(pParse->zAlt); pParse->zAlt = 0; } } /* ** Free a JsonParse object that was obtained from sqlite3_malloc(). ** ** Note that destroying JsonParse might call sqlite3RCStrUnref() to ** destroy the zJson value. The RCStr object might recursively invoke ** JsonParse to destroy this pParse object again. Take care to ensure ** that this recursive destructor sequence terminates harmlessly. */ static void jsonParseFree(JsonParse *pParse){ if( pParse->nJPRef>1 ){ pParse->nJPRef--; }else{ jsonParseReset(pParse); sqlite3_free(pParse); } } /* ** Add a cleanup task to the JsonParse object. ** ** If an OOM occurs, the cleanup operation happens immediately ** and this function returns SQLITE_NOMEM. */ static int jsonParseAddCleanup( JsonParse *pParse, /* Add the cleanup task to this parser */ void(*xOp)(void*), /* The cleanup task */ void *pArg /* Argument to the cleanup */ ){ JsonCleanup *pTask = sqlite3_malloc64( sizeof(*pTask) ); if( pTask==0 ){ pParse->oom = 1; xOp(pArg); return SQLITE_ERROR; } pTask->pJCNext = pParse->pClup; pParse->pClup = pTask; pTask->xOp = xOp; pTask->pArg = pArg; return SQLITE_OK; } /* ** Convert the JsonNode pNode into a pure JSON string and ** append to pOut. Subsubstructure is also included. Return ** the number of JsonNode objects that are encoded. */ static void jsonRenderNode( JsonParse *pParse, /* the complete parse of the JSON */ JsonNode *pNode, /* The node to render */ JsonString *pOut /* Write JSON here */ ){ assert( pNode!=0 ); while( (pNode->jnFlags & JNODE_REPLACE)!=0 && pParse->useMod ){ u32 idx = (u32)(pNode - pParse->aNode); u32 i = pParse->iSubst; while( 1 /*exit-by-break*/ ){ assert( i<pParse->nNode ); assert( pParse->aNode[i].eType==JSON_SUBST ); assert( pParse->aNode[i].eU==4 ); assert( pParse->aNode[i].u.iPrev<i ); if( pParse->aNode[i].n==idx ){ pNode = &pParse->aNode[i+1]; break; } i = pParse->aNode[i].u.iPrev; } } switch( pNode->eType ){ default: { assert( pNode->eType==JSON_NULL ); jsonAppendRawNZ(pOut, "null", 4); break; } |
︙ | ︙ | |||
621 622 623 624 625 626 627 | break; } case JSON_ARRAY: { u32 j = 1; jsonAppendChar(pOut, '['); for(;;){ while( j<=pNode->n ){ | | | > | | | | > | > < > > > > > > | | > > > > | | > | 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 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 | break; } case JSON_ARRAY: { u32 j = 1; jsonAppendChar(pOut, '['); for(;;){ while( j<=pNode->n ){ if( (pNode[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ jsonAppendSeparator(pOut); jsonRenderNode(pParse, &pNode[j], pOut); } j += jsonNodeSize(&pNode[j]); } if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; if( pParse->useMod==0 ) break; assert( pNode->eU==2 ); pNode = &pParse->aNode[pNode->u.iAppend]; j = 1; } jsonAppendChar(pOut, ']'); break; } case JSON_OBJECT: { u32 j = 1; jsonAppendChar(pOut, '{'); for(;;){ while( j<=pNode->n ){ if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ jsonAppendSeparator(pOut); jsonRenderNode(pParse, &pNode[j], pOut); jsonAppendChar(pOut, ':'); jsonRenderNode(pParse, &pNode[j+1], pOut); } j += 1 + jsonNodeSize(&pNode[j+1]); } if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; if( pParse->useMod==0 ) break; assert( pNode->eU==2 ); pNode = &pParse->aNode[pNode->u.iAppend]; j = 1; } jsonAppendChar(pOut, '}'); break; } } } /* ** Return a JsonNode and all its descendants as a JSON string. */ static void jsonReturnJson( JsonParse *pParse, /* The complete JSON */ JsonNode *pNode, /* Node to return */ sqlite3_context *pCtx, /* Return value for this function */ int bGenerateAlt /* Also store the rendered text in zAlt */ ){ JsonString s; if( pParse->oom ){ sqlite3_result_error_nomem(pCtx); return; } if( pParse->nErr==0 ){ jsonInit(&s, pCtx); jsonRenderNode(pParse, pNode, &s); if( bGenerateAlt && pParse->zAlt==0 && jsonForceRCStr(&s) ){ pParse->zAlt = sqlite3RCStrRef(s.zBuf); pParse->nAlt = s.nUsed; } jsonResult(&s); sqlite3_result_subtype(pCtx, JSON_SUBTYPE); } } /* ** Translate a single byte of Hex into an integer. ** This routine only works if h really is a valid hexadecimal ** character: 0..9a..fA..F */ |
︙ | ︙ | |||
709 710 711 712 713 714 715 716 | return v; } /* ** Make the JsonNode the return value of the function. */ static void jsonReturn( JsonNode *pNode, /* Node to return */ | > | < | < | 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 | return v; } /* ** Make the JsonNode the return value of the function. */ static void jsonReturn( JsonParse *pParse, /* Complete JSON parse tree */ JsonNode *pNode, /* Node to return */ sqlite3_context *pCtx /* Return value for this function */ ){ switch( pNode->eType ){ default: { assert( pNode->eType==JSON_NULL ); sqlite3_result_null(pCtx); break; } case JSON_TRUE: { sqlite3_result_int(pCtx, 1); break; } case JSON_FALSE: { sqlite3_result_int(pCtx, 0); break; } case JSON_INT: { sqlite3_int64 i = 0; int rc; int bNeg = 0; const char *z; assert( pNode->eU==1 ); z = pNode->u.zJContent; if( z[0]=='-' ){ z++; bNeg = 1; } else if( z[0]=='+' ){ z++; } rc = sqlite3DecOrHexToI64(z, &i); if( rc<=1 ){ sqlite3_result_int64(pCtx, bNeg ? -i : i); |
︙ | ︙ | |||
858 859 860 861 862 863 864 | zOut[j] = 0; sqlite3_result_text(pCtx, zOut, j, sqlite3_free); } break; } case JSON_ARRAY: case JSON_OBJECT: { | | | 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 | zOut[j] = 0; sqlite3_result_text(pCtx, zOut, j, sqlite3_free); } break; } case JSON_ARRAY: case JSON_OBJECT: { jsonReturnJson(pParse, pNode, pCtx, 0); break; } } } /* Forward reference */ static int jsonParseAddNode(JsonParse*,u32,u32,const char*); |
︙ | ︙ | |||
880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 | #elif defined(_MSC_VER) && _MSC_VER>=1310 # define JSON_NOINLINE __declspec(noinline) #else # define JSON_NOINLINE #endif static JSON_NOINLINE int jsonParseAddNodeExpand( JsonParse *pParse, /* Append the node to this object */ u32 eType, /* Node type */ u32 n, /* Content size or sub-node count */ const char *zContent /* Content */ ){ u32 nNew; JsonNode *pNew; assert( pParse->nNode>=pParse->nAlloc ); if( pParse->oom ) return -1; nNew = pParse->nAlloc*2 + 10; pNew = sqlite3_realloc64(pParse->aNode, sizeof(JsonNode)*nNew); if( pNew==0 ){ pParse->oom = 1; return -1; } | > > > > > > | | 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 | #elif defined(_MSC_VER) && _MSC_VER>=1310 # define JSON_NOINLINE __declspec(noinline) #else # define JSON_NOINLINE #endif /* ** Add a single node to pParse->aNode after first expanding the ** size of the aNode array. Return the index of the new node. ** ** If an OOM error occurs, set pParse->oom and return -1. */ static JSON_NOINLINE int jsonParseAddNodeExpand( JsonParse *pParse, /* Append the node to this object */ u32 eType, /* Node type */ u32 n, /* Content size or sub-node count */ const char *zContent /* Content */ ){ u32 nNew; JsonNode *pNew; assert( pParse->nNode>=pParse->nAlloc ); if( pParse->oom ) return -1; nNew = pParse->nAlloc*2 + 10; pNew = sqlite3_realloc64(pParse->aNode, sizeof(JsonNode)*nNew); if( pNew==0 ){ pParse->oom = 1; return -1; } pParse->nAlloc = sqlite3_msize(pNew)/sizeof(JsonNode); pParse->aNode = pNew; assert( pParse->nNode<pParse->nAlloc ); return jsonParseAddNode(pParse, eType, n, zContent); } /* ** Create a new JsonNode instance based on the arguments and append that |
︙ | ︙ | |||
926 927 928 929 930 931 932 933 934 935 936 937 938 939 | p->eType = (u8)(eType & 0xff); p->jnFlags = (u8)(eType >> 8); VVA( p->eU = zContent ? 1 : 0 ); p->n = n; p->u.zJContent = zContent; return pParse->nNode++; } /* ** Return true if z[] begins with 2 (or more) hexadecimal digits */ static int jsonIs2Hex(const char *z){ return sqlite3Isxdigit(z[0]) && sqlite3Isxdigit(z[1]); } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 | p->eType = (u8)(eType & 0xff); p->jnFlags = (u8)(eType >> 8); VVA( p->eU = zContent ? 1 : 0 ); p->n = n; p->u.zJContent = zContent; return pParse->nNode++; } /* ** Add an array of new nodes to the current pParse->aNode array. ** Return the index of the first node added. ** ** If an OOM error occurs, set pParse->oom. */ static void jsonParseAddNodeArray( JsonParse *pParse, /* Append the node to this object */ JsonNode *aNode, /* Array of nodes to add */ u32 nNode /* Number of elements in aNew */ ){ if( pParse->nNode + nNode > pParse->nAlloc ){ u32 nNew = pParse->nNode + nNode; JsonNode *aNew = sqlite3_realloc64(pParse->aNode, nNew*sizeof(JsonNode)); if( aNew==0 ){ pParse->oom = 1; return; } pParse->nAlloc = sqlite3_msize(aNew)/sizeof(JsonNode); pParse->aNode = aNew; } memcpy(&pParse->aNode[pParse->nNode], aNode, nNode*sizeof(JsonNode)); pParse->nNode += nNode; } /* ** Add a new JSON_SUBST node. The node immediately following ** this new node will be the substitute content for iNode. */ static int jsonParseAddSubstNode( JsonParse *pParse, /* Add the JSON_SUBST here */ u32 iNode /* References this node */ ){ int idx = jsonParseAddNode(pParse, JSON_SUBST, iNode, 0); if( pParse->oom ) return -1; pParse->aNode[iNode].jnFlags |= JNODE_REPLACE; pParse->aNode[idx].eU = 4; pParse->aNode[idx].u.iPrev = pParse->iSubst; pParse->iSubst = idx; pParse->hasMod = 1; pParse->useMod = 1; return idx; } /* ** Return true if z[] begins with 2 (or more) hexadecimal digits */ static int jsonIs2Hex(const char *z){ return sqlite3Isxdigit(z[0]) && sqlite3Isxdigit(z[1]); } |
︙ | ︙ | |||
1093 1094 1095 1096 1097 1098 1099 | /* ** Parse a single JSON value which begins at pParse->zJson[i]. Return the ** index of the first character past the end of the value parsed. ** ** Special return values: ** | | | 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 | /* ** Parse a single JSON value which begins at pParse->zJson[i]. Return the ** index of the first character past the end of the value parsed. ** ** Special return values: ** ** 0 End of input ** -1 Syntax error ** -2 '}' seen ** -3 ']' seen ** -4 ',' seen ** -5 ':' seen */ static int jsonParseValue(JsonParse *pParse, u32 i){ |
︙ | ︙ | |||
1270 1271 1272 1273 1274 1275 1276 | case '"': /* Parse string */ jnFlags = 0; parse_string: cDelim = z[i]; for(j=i+1; 1; j++){ static const char aOk[256] = { | | | | | | | | | | | | | | | | | | 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 1489 1490 1491 1492 | case '"': /* Parse string */ jnFlags = 0; parse_string: cDelim = z[i]; for(j=i+1; 1; j++){ static const char aOk[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }; if( aOk[(unsigned char)z[j]] ) continue; c = z[j]; if( c==cDelim ){ break; }else if( c=='\\' ){ c = z[++j]; |
︙ | ︙ | |||
1552 1553 1554 1555 1556 1557 1558 | return -1; /* Syntax error */ } } /* End switch(z[i]) */ } /* ** Parse a complete JSON string. Return 0 on success or non-zero if there | | | > | | < < < | | 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 | return -1; /* Syntax error */ } } /* End switch(z[i]) */ } /* ** Parse a complete JSON string. Return 0 on success or non-zero if there ** are any errors. If an error occurs, free all memory held by pParse, ** but not pParse itself. ** ** pParse must be initialized to an empty parse object prior to calling ** this routine. */ static int jsonParse( JsonParse *pParse, /* Initialize and fill this JsonParse object */ sqlite3_context *pCtx /* Report errors here */ ){ int i; const char *zJson = pParse->zJson; i = jsonParseValue(pParse, 0); if( pParse->oom ) i = -1; if( i>0 ){ assert( pParse->iDepth==0 ); while( fast_isspace(zJson[i]) ) i++; if( zJson[i] ){ i += json5Whitespace(&zJson[i]); |
︙ | ︙ | |||
1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 | } } jsonParseReset(pParse); return 1; } return 0; } /* Mark node i of pParse as being a child of iParent. Call recursively ** to fill in all the descendants of node i. */ static void jsonParseFillInParentage(JsonParse *pParse, u32 i, u32 iParent){ JsonNode *pNode = &pParse->aNode[i]; u32 j; | > | 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 | } } jsonParseReset(pParse); return 1; } return 0; } /* Mark node i of pParse as being a child of iParent. Call recursively ** to fill in all the descendants of node i. */ static void jsonParseFillInParentage(JsonParse *pParse, u32 i, u32 iParent){ JsonNode *pNode = &pParse->aNode[i]; u32 j; |
︙ | ︙ | |||
1643 1644 1645 1646 1647 1648 1649 | /* ** Magic number used for the JSON parse cache in sqlite3_get_auxdata() */ #define JSON_CACHE_ID (-429938) /* First cache entry */ #define JSON_CACHE_SZ 4 /* Max number of cache entries */ /* | | > | | | | > > > > | | < | | > > > > > > > | | | > | | > > > | > > > > > > > > > > > > > > > > > > > | > > > > | | > > | > | > | 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 | /* ** Magic number used for the JSON parse cache in sqlite3_get_auxdata() */ #define JSON_CACHE_ID (-429938) /* First cache entry */ #define JSON_CACHE_SZ 4 /* Max number of cache entries */ /* ** Obtain a complete parse of the JSON found in the pJson argument ** ** Use the sqlite3_get_auxdata() cache to find a preexisting parse ** if it is available. If the cache is not available or if it ** is no longer valid, parse the JSON again and return the new parse. ** Also register the new parse so that it will be available for ** future sqlite3_get_auxdata() calls. ** ** If an error occurs and pErrCtx!=0 then report the error on pErrCtx ** and return NULL. ** ** The returned pointer (if it is not NULL) is owned by the cache in ** most cases, not the caller. The caller does NOT need to invoke ** jsonParseFree(), in most cases. ** ** Except, if an error occurs and pErrCtx==0 then return the JsonParse ** object with JsonParse.nErr non-zero and the caller will own the JsonParse ** object. In that case, it will be the responsibility of the caller to ** invoke jsonParseFree(). To summarize: ** ** pErrCtx!=0 || p->nErr==0 ==> Return value p is owned by the ** cache. Call does not need to ** free it. ** ** pErrCtx==0 && p->nErr!=0 ==> Return value is owned by the caller ** and so the caller must free it. */ static JsonParse *jsonParseCached( sqlite3_context *pCtx, /* Context to use for cache search */ sqlite3_value *pJson, /* Function param containing JSON text */ sqlite3_context *pErrCtx, /* Write parse errors here if not NULL */ int bUnedited /* No prior edits allowed */ ){ char *zJson = (char*)sqlite3_value_text(pJson); int nJson = sqlite3_value_bytes(pJson); JsonParse *p; JsonParse *pMatch = 0; int iKey; int iMinKey = 0; u32 iMinHold = 0xffffffff; u32 iMaxHold = 0; int bJsonRCStr; if( zJson==0 ) return 0; for(iKey=0; iKey<JSON_CACHE_SZ; iKey++){ p = (JsonParse*)sqlite3_get_auxdata(pCtx, JSON_CACHE_ID+iKey); if( p==0 ){ iMinKey = iKey; break; } if( pMatch==0 && p->nJson==nJson && (p->hasMod==0 || bUnedited==0) && (p->zJson==zJson || memcmp(p->zJson,zJson,nJson)==0) ){ p->nErr = 0; p->useMod = 0; pMatch = p; }else if( pMatch==0 && p->zAlt!=0 && bUnedited==0 && p->nAlt==nJson && memcmp(p->zAlt, zJson, nJson)==0 ){ p->nErr = 0; p->useMod = 1; pMatch = p; }else if( p->iHold<iMinHold ){ iMinHold = p->iHold; iMinKey = iKey; } if( p->iHold>iMaxHold ){ iMaxHold = p->iHold; } } if( pMatch ){ /* The input JSON text was found in the cache. Use the preexisting ** parse of this JSON */ pMatch->nErr = 0; pMatch->iHold = iMaxHold+1; assert( pMatch->nJPRef>0 ); /* pMatch is owned by the cache */ return pMatch; } /* The input JSON was not found anywhere in the cache. We will need ** to parse it ourselves and generate a new JsonParse object. */ bJsonRCStr = sqlite3ValueIsOfClass(pJson,(void(*)(void*))sqlite3RCStrUnref); p = sqlite3_malloc64( sizeof(*p) + (bJsonRCStr ? 0 : nJson+1) ); if( p==0 ){ sqlite3_result_error_nomem(pCtx); return 0; } memset(p, 0, sizeof(*p)); if( bJsonRCStr ){ p->zJson = sqlite3RCStrRef(zJson); p->bJsonIsRCStr = 1; }else{ p->zJson = (char*)&p[1]; memcpy(p->zJson, zJson, nJson+1); } p->nJPRef = 1; if( jsonParse(p, pErrCtx) ){ if( pErrCtx==0 ){ p->nErr = 1; assert( p->nJPRef==1 ); /* Caller will own the new JsonParse object p */ return p; } jsonParseFree(p); return 0; } p->nJson = nJson; p->iHold = iMaxHold+1; /* Transfer ownership of the new JsonParse to the cache */ sqlite3_set_auxdata(pCtx, JSON_CACHE_ID+iMinKey, p, (void(*)(void*))jsonParseFree); return (JsonParse*)sqlite3_get_auxdata(pCtx, JSON_CACHE_ID+iMinKey); } /* ** Compare the OBJECT label at pNode against zKey,nKey. Return true on |
︙ | ︙ | |||
1766 1767 1768 1769 1770 1771 1772 | u32 iRoot, /* Begin the search at this node */ const char *zPath, /* The path to search */ int *pApnd, /* Append nodes to complete path if not NULL */ const char **pzErr /* Make *pzErr point to any syntax error in zPath */ ){ u32 i, j, nKey; const char *zKey; | > > | > > > > > > > > > > > > > > > > < | 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 | u32 iRoot, /* Begin the search at this node */ const char *zPath, /* The path to search */ int *pApnd, /* Append nodes to complete path if not NULL */ const char **pzErr /* Make *pzErr point to any syntax error in zPath */ ){ u32 i, j, nKey; const char *zKey; JsonNode *pRoot; if( pParse->oom ) return 0; pRoot = &pParse->aNode[iRoot]; while( (pRoot->jnFlags & JNODE_REPLACE)!=0 && pParse->useMod ){ u32 idx = (u32)(pRoot - pParse->aNode); i = pParse->iSubst; while( 1 /*exit-by-break*/ ){ assert( i<pParse->nNode ); assert( pParse->aNode[i].eType==JSON_SUBST ); assert( pParse->aNode[i].eU==4 ); assert( pParse->aNode[i].u.iPrev<i ); if( pParse->aNode[i].n==idx ){ pRoot = &pParse->aNode[i+1]; iRoot = i+1; break; } i = pParse->aNode[i].u.iPrev; } } if( zPath[0]==0 ) return pRoot; if( zPath[0]=='.' ){ if( pRoot->eType!=JSON_OBJECT ) return 0; zPath++; if( zPath[0]=='"' ){ zKey = zPath + 1; for(i=1; zPath[i] && zPath[i]!='"'; i++){} nKey = i-1; |
︙ | ︙ | |||
1802 1803 1804 1805 1806 1807 1808 1809 | if( jsonLabelCompare(pRoot+j, zKey, nKey) ){ return jsonLookupStep(pParse, iRoot+j+1, &zPath[i], pApnd, pzErr); } j++; j += jsonNodeSize(&pRoot[j]); } if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; assert( pRoot->eU==2 ); | > | > | | > | | 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 | if( jsonLabelCompare(pRoot+j, zKey, nKey) ){ return jsonLookupStep(pParse, iRoot+j+1, &zPath[i], pApnd, pzErr); } j++; j += jsonNodeSize(&pRoot[j]); } if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; if( pParse->useMod==0 ) break; assert( pRoot->eU==2 ); iRoot = pRoot->u.iAppend; pRoot = &pParse->aNode[iRoot]; j = 1; } if( pApnd ){ u32 iStart, iLabel; JsonNode *pNode; assert( pParse->useMod ); iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0); iLabel = jsonParseAddNode(pParse, JSON_STRING, nKey, zKey); zPath += i; pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr); if( pParse->oom ) return 0; if( pNode ){ pRoot = &pParse->aNode[iRoot]; assert( pRoot->eU==0 ); pRoot->u.iAppend = iStart; pRoot->jnFlags |= JNODE_APPEND; VVA( pRoot->eU = 2 ); pParse->aNode[iLabel].jnFlags |= JNODE_RAW; } return pNode; } }else if( zPath[0]=='[' ){ i = 0; j = 1; while( sqlite3Isdigit(zPath[j]) ){ i = i*10 + zPath[j] - '0'; j++; } if( j<2 || zPath[j]!=']' ){ if( zPath[1]=='#' ){ JsonNode *pBase = pRoot; int iBase = iRoot; if( pRoot->eType!=JSON_ARRAY ) return 0; for(;;){ while( j<=pBase->n ){ if( (pBase[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ) i++; j += jsonNodeSize(&pBase[j]); } if( (pBase->jnFlags & JNODE_APPEND)==0 ) break; if( pParse->useMod==0 ) break; assert( pBase->eU==2 ); iBase = pBase->u.iAppend; pBase = &pParse->aNode[iBase]; j = 1; } j = 2; if( zPath[2]=='-' && sqlite3Isdigit(zPath[3]) ){ unsigned int x = 0; j = 3; |
︙ | ︙ | |||
1872 1873 1874 1875 1876 1877 1878 | return 0; } } if( pRoot->eType!=JSON_ARRAY ) return 0; zPath += j + 1; j = 1; for(;;){ | | > > | > | > | | 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 | return 0; } } if( pRoot->eType!=JSON_ARRAY ) return 0; zPath += j + 1; j = 1; for(;;){ while( j<=pRoot->n && (i>0 || ((pRoot[j].jnFlags & JNODE_REMOVE)!=0 && pParse->useMod)) ){ if( (pRoot[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ) i--; j += jsonNodeSize(&pRoot[j]); } if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; if( pParse->useMod==0 ) break; assert( pRoot->eU==2 ); iRoot = pRoot->u.iAppend; pRoot = &pParse->aNode[iRoot]; j = 1; } if( j<=pRoot->n ){ return jsonLookupStep(pParse, iRoot+j, zPath, pApnd, pzErr); } if( i==0 && pApnd ){ u32 iStart; JsonNode *pNode; assert( pParse->useMod ); iStart = jsonParseAddNode(pParse, JSON_ARRAY, 1, 0); pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr); if( pParse->oom ) return 0; if( pNode ){ pRoot = &pParse->aNode[iRoot]; assert( pRoot->eU==0 ); pRoot->u.iAppend = iStart; pRoot->jnFlags |= JNODE_APPEND; VVA( pRoot->eU = 2 ); } return pNode; } }else{ *pzErr = zPath; |
︙ | ︙ | |||
1994 1995 1996 1997 1998 1999 2000 | static void jsonWrongNumArgs( sqlite3_context *pCtx, const char *zFuncName ){ char *zMsg = sqlite3_mprintf("json_%s() needs an odd number of arguments", zFuncName); sqlite3_result_error(pCtx, zMsg, -1); | | | 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 | static void jsonWrongNumArgs( sqlite3_context *pCtx, const char *zFuncName ){ char *zMsg = sqlite3_mprintf("json_%s() needs an odd number of arguments", zFuncName); sqlite3_result_error(pCtx, zMsg, -1); sqlite3_free(zMsg); } /* ** Mark all NULL entries in the Object passed in as JNODE_REMOVE. */ static void jsonRemoveAllNulls(JsonNode *pNode){ int i, n; |
︙ | ︙ | |||
2020 2021 2022 2023 2024 2025 2026 2027 | } } /**************************************************************************** ** SQL functions used for testing and debugging ****************************************************************************/ | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > > > > > > > > > > > > | > > > > > > > < | < | | < | > > > | | | | | | < > | < < < | < < < < < < < < | | 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 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 2367 2368 2369 2370 2371 2372 2373 2374 2375 | } } /**************************************************************************** ** SQL functions used for testing and debugging ****************************************************************************/ #if SQLITE_DEBUG /* ** Print N node entries. */ static void jsonDebugPrintNodeEntries( JsonNode *aNode, /* First node entry to print */ int N /* Number of node entries to print */ ){ int i; for(i=0; i<N; i++){ const char *zType; if( aNode[i].jnFlags & JNODE_LABEL ){ zType = "label"; }else{ zType = jsonType[aNode[i].eType]; } printf("node %4u: %-7s n=%-5d", i, zType, aNode[i].n); if( (aNode[i].jnFlags & ~JNODE_LABEL)!=0 ){ u8 f = aNode[i].jnFlags; if( f & JNODE_RAW ) printf(" RAW"); if( f & JNODE_ESCAPE ) printf(" ESCAPE"); if( f & JNODE_REMOVE ) printf(" REMOVE"); if( f & JNODE_REPLACE ) printf(" REPLACE"); if( f & JNODE_APPEND ) printf(" APPEND"); if( f & JNODE_JSON5 ) printf(" JSON5"); } switch( aNode[i].eU ){ case 1: printf(" zJContent=[%.*s]\n", aNode[i].n, aNode[i].u.zJContent); break; case 2: printf(" iAppend=%u\n", aNode[i].u.iAppend); break; case 3: printf(" iKey=%u\n", aNode[i].u.iKey); break; case 4: printf(" iPrev=%u\n", aNode[i].u.iPrev); break; default: printf("\n"); } } } #endif /* SQLITE_DEBUG */ #if 0 /* 1 for debugging. 0 normally. Requires -DSQLITE_DEBUG too */ static void jsonDebugPrintParse(JsonParse *p){ jsonDebugPrintNodeEntries(p->aNode, p->nNode); } static void jsonDebugPrintNode(JsonNode *pNode){ jsonDebugPrintNodeEntries(pNode, jsonNodeSize(pNode)); } #else /* The usual case */ # define jsonDebugPrintNode(X) # define jsonDebugPrintParse(X) #endif #ifdef SQLITE_DEBUG /* ** SQL function: json_parse(JSON) ** ** Parse JSON using jsonParseCached(). Then print a dump of that ** parse on standard output. Return the mimified JSON result, just ** like the json() function. */ static void jsonParseFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ JsonParse *p; /* The parse */ assert( argc==1 ); p = jsonParseCached(ctx, argv[0], ctx, 0); if( p==0 ) return; printf("nNode = %u\n", p->nNode); printf("nAlloc = %u\n", p->nAlloc); printf("nJson = %d\n", p->nJson); printf("nAlt = %d\n", p->nAlt); printf("nErr = %u\n", p->nErr); printf("oom = %u\n", p->oom); printf("hasNonstd = %u\n", p->hasNonstd); printf("useMod = %u\n", p->useMod); printf("hasMod = %u\n", p->hasMod); printf("nJPRef = %u\n", p->nJPRef); printf("iSubst = %u\n", p->iSubst); printf("iHold = %u\n", p->iHold); jsonDebugPrintNodeEntries(p->aNode, p->nNode); jsonReturnJson(p, p->aNode, ctx, 1); } /* ** The json_test1(JSON) function return true (1) if the input is JSON ** text generated by another json function. It returns (0) if the input ** is not known to be JSON. */ |
︙ | ︙ | |||
2085 2086 2087 2088 2089 2090 2091 | /**************************************************************************** ** Scalar SQL function implementations ****************************************************************************/ /* ** Implementation of the json_QUOTE(VALUE) function. Return a JSON value | | | 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 | /**************************************************************************** ** Scalar SQL function implementations ****************************************************************************/ /* ** Implementation of the json_QUOTE(VALUE) function. Return a JSON value ** corresponding to the SQL value input. Mostly this means putting ** double-quotes around strings and returning the unquoted string "null" ** when given a NULL input. */ static void jsonQuoteFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv |
︙ | ︙ | |||
2132 2133 2134 2135 2136 2137 2138 | } /* ** json_array_length(JSON) ** json_array_length(JSON, PATH) ** | | | | | | > > > > > | 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 | } /* ** json_array_length(JSON) ** json_array_length(JSON, PATH) ** ** Return the number of elements in the top-level JSON array. ** Return 0 if the input is not a well-formed JSON array. */ static void jsonArrayLengthFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ JsonParse *p; /* The parse */ sqlite3_int64 n = 0; u32 i; JsonNode *pNode; p = jsonParseCached(ctx, argv[0], ctx, 0); if( p==0 ) return; assert( p->nNode ); if( argc==2 ){ const char *zPath = (const char*)sqlite3_value_text(argv[1]); pNode = jsonLookup(p, zPath, 0, ctx); }else{ pNode = p->aNode; } if( pNode==0 ){ return; } if( pNode->eType==JSON_ARRAY ){ while( 1 /*exit-by-break*/ ){ for(i=1; i<=pNode->n; n++){ i += jsonNodeSize(&pNode[i]); } if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; if( p->useMod==0 ) break; assert( pNode->eU==2 ); pNode = &p->aNode[pNode->u.iAppend]; } } sqlite3_result_int64(ctx, n); } /* ** Bit values for the flags passed into jsonExtractFunc() or |
︙ | ︙ | |||
2207 2208 2209 2210 2211 2212 2213 | JsonParse *p; /* The parse */ JsonNode *pNode; const char *zPath; int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); JsonString jx; if( argc<2 ) return; | | | 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 | JsonParse *p; /* The parse */ JsonNode *pNode; const char *zPath; int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); JsonString jx; if( argc<2 ) return; p = jsonParseCached(ctx, argv[0], ctx, 0); if( p==0 ) return; if( argc==2 ){ /* With a single PATH argument */ zPath = (const char*)sqlite3_value_text(argv[1]); if( zPath==0 ) return; if( flags & JSON_ABPATH ){ if( zPath[0]!='$' || (zPath[1]!='.' && zPath[1]!='[' && zPath[1]!=0) ){ |
︙ | ︙ | |||
2240 2241 2242 2243 2244 2245 2246 | pNode = jx.bErr ? 0 : jsonLookup(p, jx.zBuf, 0, ctx); jsonReset(&jx); }else{ pNode = jsonLookup(p, zPath, 0, ctx); } if( pNode ){ if( flags & JSON_JSON ){ | | | | | | 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 | pNode = jx.bErr ? 0 : jsonLookup(p, jx.zBuf, 0, ctx); jsonReset(&jx); }else{ pNode = jsonLookup(p, zPath, 0, ctx); } if( pNode ){ if( flags & JSON_JSON ){ jsonReturnJson(p, pNode, ctx, 0); }else{ jsonReturn(p, pNode, ctx); sqlite3_result_subtype(ctx, 0); } } }else{ pNode = jsonLookup(p, zPath, 0, ctx); if( p->nErr==0 && pNode ) jsonReturn(p, pNode, ctx); } }else{ /* Two or more PATH arguments results in a JSON array with each ** element of the array being the value selected by one of the PATHs */ int i; jsonInit(&jx, ctx); jsonAppendChar(&jx, '['); for(i=1; i<argc; i++){ zPath = (const char*)sqlite3_value_text(argv[i]); pNode = jsonLookup(p, zPath, 0, ctx); if( p->nErr ) break; jsonAppendSeparator(&jx); if( pNode ){ jsonRenderNode(p, pNode, &jx); }else{ jsonAppendRawNZ(&jx, "null", 4); } } if( i==argc ){ jsonAppendChar(&jx, ']'); jsonResult(&jx); |
︙ | ︙ | |||
2309 2310 2311 2312 2313 2314 2315 | assert( pPatch[i].eU==1 ); nKey = pPatch[i].n; zKey = pPatch[i].u.zJContent; for(j=1; j<pTarget->n; j += jsonNodeSize(&pTarget[j+1])+1 ){ assert( pTarget[j].eType==JSON_STRING ); assert( pTarget[j].jnFlags & JNODE_LABEL ); if( jsonSameLabel(&pPatch[i], &pTarget[j]) ){ | | | < < | < < < < | < > | > > | > > > | < | < | | < < | < < | | | | | | < > > > | | | > > | < < | 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 | assert( pPatch[i].eU==1 ); nKey = pPatch[i].n; zKey = pPatch[i].u.zJContent; for(j=1; j<pTarget->n; j += jsonNodeSize(&pTarget[j+1])+1 ){ assert( pTarget[j].eType==JSON_STRING ); assert( pTarget[j].jnFlags & JNODE_LABEL ); if( jsonSameLabel(&pPatch[i], &pTarget[j]) ){ if( pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_REPLACE) ) break; if( pPatch[i+1].eType==JSON_NULL ){ pTarget[j+1].jnFlags |= JNODE_REMOVE; }else{ JsonNode *pNew = jsonMergePatch(pParse, iTarget+j+1, &pPatch[i+1]); if( pNew==0 ) return 0; if( pNew!=&pParse->aNode[iTarget+j+1] ){ jsonParseAddSubstNode(pParse, iTarget+j+1); jsonParseAddNodeArray(pParse, pNew, jsonNodeSize(pNew)); } pTarget = &pParse->aNode[iTarget]; } break; } } if( j>=pTarget->n && pPatch[i+1].eType!=JSON_NULL ){ int iStart; JsonNode *pApnd; u32 nApnd; iStart = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); jsonParseAddNode(pParse, JSON_STRING, nKey, zKey); pApnd = &pPatch[i+1]; if( pApnd->eType==JSON_OBJECT ) jsonRemoveAllNulls(pApnd); nApnd = jsonNodeSize(pApnd); jsonParseAddNodeArray(pParse, pApnd, jsonNodeSize(pApnd)); if( pParse->oom ) return 0; pParse->aNode[iStart].n = 1+nApnd; pParse->aNode[iRoot].jnFlags |= JNODE_APPEND; pParse->aNode[iRoot].u.iAppend = iStart; VVA( pParse->aNode[iRoot].eU = 2 ); iRoot = iStart; pTarget = &pParse->aNode[iTarget]; } } return pTarget; } /* ** Implementation of the json_mergepatch(JSON1,JSON2) function. Return a JSON ** object that is the result of running the RFC 7396 MergePatch() algorithm ** on the two arguments. */ static void jsonPatchFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ JsonParse *pX; /* The JSON that is being patched */ JsonParse *pY; /* The patch */ JsonNode *pResult; /* The result of the merge */ UNUSED_PARAMETER(argc); pX = jsonParseCached(ctx, argv[0], ctx, 1); if( pX==0 ) return; pY = jsonParseCached(ctx, argv[1], ctx, 1); if( pY==0 ) return; pX->useMod = 1; pX->hasMod = 1; pY->useMod = 1; pResult = jsonMergePatch(pX, 0, pY->aNode); assert( pResult!=0 || pX->oom ); if( pResult && pX->oom==0 ){ jsonDebugPrintParse(pX); jsonDebugPrintNode(pResult); jsonReturnJson(pX, pResult, ctx, 0); }else{ sqlite3_result_error_nomem(ctx); } } /* ** Implementation of the json_object(NAME,VALUE,...) function. Return a JSON ** object that contains all name/value given in arguments. Or if any name ** is not a string or if any value is a BLOB, throw an error. |
︙ | ︙ | |||
2437 2438 2439 2440 2441 2442 2443 | ** JSON or PATH arguments result in an error. */ static void jsonRemoveFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ | | | | | | > | > > | > | | > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | > | | < < < < | < < < < | < | | | | > | | | < < < < | < < < | | | < > | | 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 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 | ** JSON or PATH arguments result in an error. */ static void jsonRemoveFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ JsonParse *pParse; /* The parse */ JsonNode *pNode; const char *zPath; u32 i; if( argc<1 ) return; pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); if( pParse==0 ) return; for(i=1; i<(u32)argc; i++){ zPath = (const char*)sqlite3_value_text(argv[i]); if( zPath==0 ) goto remove_done; pNode = jsonLookup(pParse, zPath, 0, ctx); if( pParse->nErr ) goto remove_done; if( pNode ){ pNode->jnFlags |= JNODE_REMOVE; pParse->hasMod = 1; pParse->useMod = 1; } } if( (pParse->aNode[0].jnFlags & JNODE_REMOVE)==0 ){ jsonReturnJson(pParse, pParse->aNode, ctx, 1); } remove_done: jsonDebugPrintParse(p); } /* ** Substitute the value at iNode with the pValue parameter. */ static void jsonReplaceNode( sqlite3_context *pCtx, JsonParse *p, int iNode, sqlite3_value *pValue ){ int idx = jsonParseAddSubstNode(p, iNode); if( idx<=0 ){ assert( p->oom ); return; } switch( sqlite3_value_type(pValue) ){ case SQLITE_NULL: { jsonParseAddNode(p, JSON_NULL, 0, 0); break; } case SQLITE_FLOAT: { char *z = sqlite3_mprintf("%!0.15g", sqlite3_value_double(pValue)); int n; if( z==0 ){ p->oom = 1; break; } n = sqlite3Strlen30(z); jsonParseAddNode(p, JSON_REAL, n, z); jsonParseAddCleanup(p, sqlite3_free, z); break; } case SQLITE_INTEGER: { char *z = sqlite3_mprintf("%lld", sqlite3_value_int64(pValue)); int n; if( z==0 ){ p->oom = 1; break; } n = sqlite3Strlen30(z); jsonParseAddNode(p, JSON_INT, n, z); jsonParseAddCleanup(p, sqlite3_free, z); break; } case SQLITE_TEXT: { const char *z = (const char*)sqlite3_value_text(pValue); u32 n = (u32)sqlite3_value_bytes(pValue); if( z==0 ){ p->oom = 1; break; } if( sqlite3_value_subtype(pValue)!=JSON_SUBTYPE ){ char *zCopy = sqlite3DbStrDup(0, z); int k; if( zCopy ){ jsonParseAddCleanup(p, sqlite3_free, zCopy); }else{ p->oom = 1; sqlite3_result_error_nomem(pCtx); } k = jsonParseAddNode(p, JSON_STRING, n, zCopy); assert( k>0 || p->oom ); if( p->oom==0 ) p->aNode[k].jnFlags |= JNODE_RAW; }else{ JsonParse *pPatch = jsonParseCached(pCtx, pValue, pCtx, 1); if( pPatch==0 ){ p->oom = 1; break; } jsonParseAddNodeArray(p, pPatch->aNode, pPatch->nNode); /* The nodes copied out of pPatch and into p likely contain ** u.zJContent pointers into pPatch->zJson. So preserve the ** content of pPatch until p is destroyed. */ assert( pPatch->nJPRef>=1 ); pPatch->nJPRef++; jsonParseAddCleanup(p, (void(*)(void*))jsonParseFree, pPatch); } break; } default: { jsonParseAddNode(p, JSON_NULL, 0, 0); sqlite3_result_error(pCtx, "JSON cannot hold BLOB values", -1); p->nErr++; break; } } } /* ** json_replace(JSON, PATH, VALUE, ...) ** ** Replace the value at PATH with VALUE. If PATH does not already exist, ** this routine is a no-op. If JSON or PATH is malformed, throw an error. */ static void jsonReplaceFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ JsonParse *pParse; /* The parse */ JsonNode *pNode; const char *zPath; u32 i; if( argc<1 ) return; if( (argc&1)==0 ) { jsonWrongNumArgs(ctx, "replace"); return; } pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); if( pParse==0 ) return; for(i=1; i<(u32)argc; i+=2){ zPath = (const char*)sqlite3_value_text(argv[i]); pParse->useMod = 1; pNode = jsonLookup(pParse, zPath, 0, ctx); if( pParse->nErr ) goto replace_err; if( pNode ){ jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); } } jsonReturnJson(pParse, pParse->aNode, ctx, 1); replace_err: jsonDebugPrintParse(pParse); } /* ** json_set(JSON, PATH, VALUE, ...) ** ** Set the value at PATH to VALUE. Create the PATH if it does not already ** exist. Overwrite existing values that do exist. ** If JSON or PATH is malformed, throw an error. ** ** json_insert(JSON, PATH, VALUE, ...) ** ** Create PATH and initialize it to VALUE. If PATH already exists, this ** routine is a no-op. If JSON or PATH is malformed, throw an error. */ static void jsonSetFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ JsonParse *pParse; /* The parse */ JsonNode *pNode; const char *zPath; u32 i; int bApnd; int bIsSet = sqlite3_user_data(ctx)!=0; if( argc<1 ) return; if( (argc&1)==0 ) { jsonWrongNumArgs(ctx, bIsSet ? "set" : "insert"); return; } pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); if( pParse==0 ) return; for(i=1; i<(u32)argc; i+=2){ zPath = (const char*)sqlite3_value_text(argv[i]); bApnd = 0; pParse->useMod = 1; pNode = jsonLookup(pParse, zPath, &bApnd, ctx); if( pParse->oom ){ sqlite3_result_error_nomem(ctx); goto jsonSetDone; }else if( pParse->nErr ){ goto jsonSetDone; }else if( pNode && (bApnd || bIsSet) ){ jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); } } jsonDebugPrintParse(pParse); jsonReturnJson(pParse, pParse->aNode, ctx, 1); jsonSetDone: /* no cleanup required */; } /* ** json_type(JSON) ** json_type(JSON, PATH) ** ** Return the top-level "type" of a JSON string. json_type() raises an ** error if either the JSON or PATH inputs are not well-formed. */ static void jsonTypeFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ JsonParse *p; /* The parse */ const char *zPath; JsonNode *pNode; p = jsonParseCached(ctx, argv[0], ctx, 0); if( p==0 ) return; if( argc==2 ){ zPath = (const char*)sqlite3_value_text(argv[1]); pNode = jsonLookup(p, zPath, 0, ctx); }else{ pNode = p->aNode; } |
︙ | ︙ | |||
2606 2607 2608 2609 2610 2611 2612 | sqlite3_context *ctx, int argc, sqlite3_value **argv ){ JsonParse *p; /* The parse */ UNUSED_PARAMETER(argc); if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; | | | | 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 | sqlite3_context *ctx, int argc, sqlite3_value **argv ){ JsonParse *p; /* The parse */ UNUSED_PARAMETER(argc); if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; p = jsonParseCached(ctx, argv[0], 0, 0); if( p==0 || p->oom ){ sqlite3_result_error_nomem(ctx); sqlite3_free(p); }else{ sqlite3_result_int(ctx, p->nErr==0 && (p->hasNonstd==0 || p->useMod)); if( p->nErr ) jsonParseFree(p); } } /* ** json_error_position(JSON) ** |
︙ | ︙ | |||
2652 2653 2654 2655 2656 2657 2658 | sqlite3_context *ctx, int argc, sqlite3_value **argv ){ JsonParse *p; /* The parse */ UNUSED_PARAMETER(argc); if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; | | | | 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 | sqlite3_context *ctx, int argc, sqlite3_value **argv ){ JsonParse *p; /* The parse */ UNUSED_PARAMETER(argc); if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; p = jsonParseCached(ctx, argv[0], 0, 0); if( p==0 || p->oom ){ sqlite3_result_error_nomem(ctx); sqlite3_free(p); }else if( p->nErr==0 ){ sqlite3_result_int(ctx, 0); }else{ int n = 1; u32 i; const char *z = (const char*)sqlite3_value_text(argv[0]); for(i=0; i<p->iErr && ALWAYS(z[i]); i++){ if( (z[i]&0xc0)!=0x80 ) n++; } sqlite3_result_int(ctx, n); jsonParseFree(p); } } |
︙ | ︙ | |||
2709 2710 2711 2712 2713 2714 2715 | pStr->pCtx = ctx; jsonAppendChar(pStr, ']'); if( pStr->bErr ){ if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx); assert( pStr->bStatic ); }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, | | > | 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 | pStr->pCtx = ctx; jsonAppendChar(pStr, ']'); if( pStr->bErr ){ if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx); assert( pStr->bStatic ); }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, pStr->bStatic ? SQLITE_TRANSIENT : (void(*)(void*))sqlite3RCStrUnref); pStr->bStatic = 1; }else{ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); pStr->nUsed--; } }else{ sqlite3_result_text(ctx, "[]", 2, SQLITE_STATIC); |
︙ | ︙ | |||
2817 2818 2819 2820 2821 2822 2823 | if( pStr ){ jsonAppendChar(pStr, '}'); if( pStr->bErr ){ if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx); assert( pStr->bStatic ); }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, | | > | 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 | if( pStr ){ jsonAppendChar(pStr, '}'); if( pStr->bErr ){ if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx); assert( pStr->bStatic ); }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, pStr->bStatic ? SQLITE_TRANSIENT : (void(*)(void*))sqlite3RCStrUnref); pStr->bStatic = 1; }else{ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); pStr->nUsed--; } }else{ sqlite3_result_text(ctx, "{}", 2, SQLITE_STATIC); |
︙ | ︙ | |||
2885 2886 2887 2888 2889 2890 2891 | #define JEACH_JSON 8 #define JEACH_ROOT 9 UNUSED_PARAMETER(pzErr); UNUSED_PARAMETER(argv); UNUSED_PARAMETER(argc); UNUSED_PARAMETER(pAux); | | | 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 | #define JEACH_JSON 8 #define JEACH_ROOT 9 UNUSED_PARAMETER(pzErr); UNUSED_PARAMETER(argv); UNUSED_PARAMETER(argc); UNUSED_PARAMETER(pAux); rc = sqlite3_declare_vtab(db, "CREATE TABLE x(key,value,type,atom,id,parent,fullkey,path," "json HIDDEN,root HIDDEN)"); if( rc==SQLITE_OK ){ pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); if( pNew==0 ) return SQLITE_NOMEM; memset(pNew, 0, sizeof(*pNew)); sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); |
︙ | ︙ | |||
2928 2929 2930 2931 2932 2933 2934 | } return rc; } /* Reset a JsonEachCursor back to its original state. Free any memory ** held. */ static void jsonEachCursorReset(JsonEachCursor *p){ | < | 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 | } return rc; } /* Reset a JsonEachCursor back to its original state. Free any memory ** held. */ static void jsonEachCursorReset(JsonEachCursor *p){ sqlite3_free(p->zRoot); jsonParseReset(&p->sParse); p->iRowid = 0; p->i = 0; p->iEnd = 0; p->eType = 0; p->zJson = 0; |
︙ | ︙ | |||
3066 3067 3068 3069 3070 3071 3072 | ){ JsonEachCursor *p = (JsonEachCursor*)cur; JsonNode *pThis = &p->sParse.aNode[p->i]; switch( i ){ case JEACH_KEY: { if( p->i==0 ) break; if( p->eType==JSON_OBJECT ){ | | | | | | 3445 3446 3447 3448 3449 3450 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 3487 3488 3489 3490 | ){ JsonEachCursor *p = (JsonEachCursor*)cur; JsonNode *pThis = &p->sParse.aNode[p->i]; switch( i ){ case JEACH_KEY: { if( p->i==0 ) break; if( p->eType==JSON_OBJECT ){ jsonReturn(&p->sParse, pThis, ctx); }else if( p->eType==JSON_ARRAY ){ u32 iKey; if( p->bRecursive ){ if( p->iRowid==0 ) break; assert( p->sParse.aNode[p->sParse.aUp[p->i]].eU==3 ); iKey = p->sParse.aNode[p->sParse.aUp[p->i]].u.iKey; }else{ iKey = p->iRowid; } sqlite3_result_int64(ctx, (sqlite3_int64)iKey); } break; } case JEACH_VALUE: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; jsonReturn(&p->sParse, pThis, ctx); break; } case JEACH_TYPE: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; sqlite3_result_text(ctx, jsonType[pThis->eType], -1, SQLITE_STATIC); break; } case JEACH_ATOM: { if( pThis->jnFlags & JNODE_LABEL ) pThis++; if( pThis->eType>=JSON_ARRAY ) break; jsonReturn(&p->sParse, pThis, ctx); break; } case JEACH_ID: { sqlite3_result_int64(ctx, (sqlite3_int64)p->i + ((pThis->jnFlags & JNODE_LABEL)!=0)); break; } case JEACH_PARENT: { if( p->i>p->iBegin && p->bRecursive ){ sqlite3_result_int64(ctx, (sqlite3_int64)p->sParse.aUp[p->i]); } |
︙ | ︙ | |||
3197 3198 3199 3200 3201 3202 3203 | if( pConstraint->usable==0 ){ unusableMask |= iMask; }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ aIdx[iCol] = i; idxMask |= iMask; } } | | | | 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 | if( pConstraint->usable==0 ){ unusableMask |= iMask; }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ aIdx[iCol] = i; idxMask |= iMask; } } if( pIdxInfo->nOrderBy>0 && pIdxInfo->aOrderBy[0].iColumn<0 && pIdxInfo->aOrderBy[0].desc==0 ){ pIdxInfo->orderByConsumed = 1; } if( (unusableMask & ~idxMask)!=0 ){ /* If there are any unusable constraints on JSON or ROOT, then reject |
︙ | ︙ | |||
3248 3249 3250 3251 3252 3253 3254 | UNUSED_PARAMETER(idxStr); UNUSED_PARAMETER(argc); jsonEachCursorReset(p); if( idxNum==0 ) return SQLITE_OK; z = (const char*)sqlite3_value_text(argv[0]); if( z==0 ) return SQLITE_OK; | > > > > > | | | | > > > | | 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 | UNUSED_PARAMETER(idxStr); UNUSED_PARAMETER(argc); jsonEachCursorReset(p); if( idxNum==0 ) return SQLITE_OK; z = (const char*)sqlite3_value_text(argv[0]); if( z==0 ) return SQLITE_OK; memset(&p->sParse, 0, sizeof(p->sParse)); p->sParse.nJPRef = 1; if( sqlite3ValueIsOfClass(argv[0], (void(*)(void*))sqlite3RCStrUnref) ){ p->sParse.zJson = sqlite3RCStrRef((char*)z); }else{ n = sqlite3_value_bytes(argv[0]); p->sParse.zJson = sqlite3RCStrNew( n+1 ); if( p->sParse.zJson==0 ) return SQLITE_NOMEM; memcpy(p->sParse.zJson, z, (size_t)n+1); } p->sParse.bJsonIsRCStr = 1; p->zJson = p->sParse.zJson; if( jsonParse(&p->sParse, 0) ){ int rc = SQLITE_NOMEM; if( p->sParse.oom==0 ){ sqlite3_free(cur->pVtab->zErrMsg); cur->pVtab->zErrMsg = sqlite3_mprintf("malformed JSON"); if( cur->pVtab->zErrMsg ) rc = SQLITE_ERROR; } jsonEachCursorReset(p); |
︙ | ︙ | |||
3398 3399 3400 3401 3402 3403 3404 | JFUNCTION(json_type, 1, 0, jsonTypeFunc), JFUNCTION(json_type, 2, 0, jsonTypeFunc), JFUNCTION(json_valid, 1, 0, jsonValidFunc), #if SQLITE_DEBUG JFUNCTION(json_parse, 1, 0, jsonParseFunc), JFUNCTION(json_test1, 1, 0, jsonTest1Func), #endif | | | | 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 | JFUNCTION(json_type, 1, 0, jsonTypeFunc), JFUNCTION(json_type, 2, 0, jsonTypeFunc), JFUNCTION(json_valid, 1, 0, jsonValidFunc), #if SQLITE_DEBUG JFUNCTION(json_parse, 1, 0, jsonParseFunc), JFUNCTION(json_test1, 1, 0, jsonTest1Func), #endif WAGGREGATE(json_group_array, 1, 0, 0, jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse, SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC), WAGGREGATE(json_group_object, 2, 0, 0, jsonObjectStep, jsonObjectFinal, jsonObjectValue, jsonGroupInverse, SQLITE_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC) }; sqlite3InsertBuiltinFuncs(aJsonFunc, ArraySize(aJsonFunc)); #endif } |
︙ | ︙ |
Changes to src/parse.y.
︙ | ︙ | |||
144 145 146 147 148 149 150 | input ::= cmdlist. cmdlist ::= cmdlist ecmd. cmdlist ::= ecmd. ecmd ::= SEMI. ecmd ::= cmdx SEMI. %ifndef SQLITE_OMIT_EXPLAIN ecmd ::= explain cmdx SEMI. {NEVER-REDUCE} | | | | 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | input ::= cmdlist. cmdlist ::= cmdlist ecmd. cmdlist ::= ecmd. ecmd ::= SEMI. ecmd ::= cmdx SEMI. %ifndef SQLITE_OMIT_EXPLAIN ecmd ::= explain cmdx SEMI. {NEVER-REDUCE} explain ::= EXPLAIN. { if( pParse->pReprepare==0 ) pParse->explain = 1; } explain ::= EXPLAIN QUERY PLAN. { if( pParse->pReprepare==0 ) pParse->explain = 2; } %endif SQLITE_OMIT_EXPLAIN cmdx ::= cmd. { sqlite3FinishCoding(pParse); } ///////////////////// Begin and end transactions. //////////////////////////// // cmd ::= BEGIN transtype(Y) trans_opt. {sqlite3BeginTransaction(pParse, Y);} |
︙ | ︙ |
Changes to src/prepare.c.
︙ | ︙ | |||
696 697 698 699 700 701 702 | /* sqlite3ParseObjectInit(&sParse, db); // inlined for performance */ memset(PARSE_HDR(&sParse), 0, PARSE_HDR_SZ); memset(PARSE_TAIL(&sParse), 0, PARSE_TAIL_SZ); sParse.pOuterParse = db->pParse; db->pParse = &sParse; sParse.db = db; | > | > > > > | 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 | /* sqlite3ParseObjectInit(&sParse, db); // inlined for performance */ memset(PARSE_HDR(&sParse), 0, PARSE_HDR_SZ); memset(PARSE_TAIL(&sParse), 0, PARSE_TAIL_SZ); sParse.pOuterParse = db->pParse; db->pParse = &sParse; sParse.db = db; if( pReprepare ){ sParse.pReprepare = pReprepare; sParse.explain = sqlite3_stmt_isexplain((sqlite3_stmt*)pReprepare); }else{ assert( sParse.pReprepare==0 ); } assert( ppStmt && *ppStmt==0 ); if( db->mallocFailed ){ sqlite3ErrorMsg(&sParse, "out of memory"); db->errCode = rc = SQLITE_NOMEM; goto end_prepare; } assert( sqlite3_mutex_held(db->mutex) ); |
︙ | ︙ |
Changes to src/printf.c.
︙ | ︙ | |||
1362 1363 1364 1365 1366 1367 1368 | */ void sqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){ va_list ap; va_start(ap,zFormat); sqlite3_str_vappendf(p, zFormat, ap); va_end(ap); } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 | */ void sqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){ va_list ap; va_start(ap,zFormat); sqlite3_str_vappendf(p, zFormat, ap); va_end(ap); } /***************************************************************************** ** Reference counted string storage *****************************************************************************/ /* ** Increase the reference count of the string by one. ** ** The input parameter is returned. */ char *sqlite3RCStrRef(char *z){ RCStr *p = (RCStr*)z; assert( p!=0 ); p--; p->nRCRef++; return z; } /* ** Decrease the reference count by one. Free the string when the ** reference count reaches zero. */ void sqlite3RCStrUnref(char *z){ RCStr *p = (RCStr*)z; assert( p!=0 ); p--; assert( p->nRCRef>0 ); if( p->nRCRef>=2 ){ p->nRCRef--; }else{ sqlite3_free(p); } } /* ** Create a new string that is capable of holding N bytes of text, not counting ** the zero byte at the end. The string is uninitialized. ** ** The reference count is initially 1. Call sqlite3RCStrUnref() to free the ** newly allocated string. ** ** This routine returns 0 on an OOM. */ char *sqlite3RCStrNew(u64 N){ RCStr *p = sqlite3_malloc64( N + sizeof(*p) + 1 ); if( p==0 ) return 0; p->nRCRef = 1; return (char*)&p[1]; } /* ** Change the size of the string so that it is able to hold N bytes. ** The string might be reallocated, so return the new allocation. */ char *sqlite3RCStrResize(char *z, u64 N){ RCStr *p = (RCStr*)z; RCStr *pNew; assert( p!=0 ); p--; assert( p->nRCRef==1 ); pNew = sqlite3_realloc64(p, N+sizeof(RCStr)+1); if( pNew==0 ){ sqlite3_free(p); return 0; }else{ return (char*)&pNew[1]; } } |
Changes to src/select.c.
︙ | ︙ | |||
2097 2098 2099 2100 2101 2102 2103 | Table *pTab; SrcList *pTabList; ExprList *pEList; sqlite3 *db = pParse->db; int fullName; /* TABLE.COLUMN if no AS clause and is a direct table ref */ int srcName; /* COLUMN or TABLE.COLUMN if no AS clause and is direct */ | < < < < < < < | 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 | Table *pTab; SrcList *pTabList; ExprList *pEList; sqlite3 *db = pParse->db; int fullName; /* TABLE.COLUMN if no AS clause and is a direct table ref */ int srcName; /* COLUMN or TABLE.COLUMN if no AS clause and is direct */ if( pParse->colNamesSet ) return; /* Column names are determined by the left-most term of a compound select */ while( pSelect->pPrior ) pSelect = pSelect->pPrior; TREETRACE(0x80,pParse,pSelect,("generating column names\n")); pTabList = pSelect->pSrc; pEList = pSelect->pEList; assert( v!=0 ); |
︙ | ︙ |
Changes to src/shell.c.in.
︙ | ︙ | |||
1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 | #define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */ #define MODE_Json 13 /* Output JSON */ #define MODE_Markdown 14 /* Markdown formatting */ #define MODE_Table 15 /* MySQL-style table formatting */ #define MODE_Box 16 /* Unicode box-drawing characters */ #define MODE_Count 17 /* Output only a count of the rows of output */ #define MODE_Off 18 /* No query output shown */ static const char *modeDescr[] = { "line", "column", "list", "semi", "html", | > | 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 | #define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */ #define MODE_Json 13 /* Output JSON */ #define MODE_Markdown 14 /* Markdown formatting */ #define MODE_Table 15 /* MySQL-style table formatting */ #define MODE_Box 16 /* Unicode box-drawing characters */ #define MODE_Count 17 /* Output only a count of the rows of output */ #define MODE_Off 18 /* No query output shown */ #define MODE_ScanExp 19 /* Like MODE_Explain, but for ".scanstats vm" */ static const char *modeDescr[] = { "line", "column", "list", "semi", "html", |
︙ | ︙ | |||
2516 2517 2518 2519 2520 2521 2522 2523 | if( p->cnt++>0 ) utf8_printf(p->out, "%s", p->rowSeparator); for(i=0; i<nArg; i++){ utf8_printf(p->out,"%*s = %s%s", w, azCol[i], azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator); } break; } case MODE_Explain: { | > | > > > > | > | > | > > > > > > > > > < | < | > > > > | > | | > | | | | 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 | if( p->cnt++>0 ) utf8_printf(p->out, "%s", p->rowSeparator); for(i=0; i<nArg; i++){ utf8_printf(p->out,"%*s = %s%s", w, azCol[i], azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator); } break; } case MODE_ScanExp: case MODE_Explain: { static const int aExplainWidth[] = {4, 13, 4, 4, 4, 13, 2, 13}; static const int aExplainMap[] = {0, 1, 2, 3, 4, 5, 6, 7 }; static const int aScanExpWidth[] = {4, 6, 6, 13, 4, 4, 4, 13, 2, 13}; static const int aScanExpMap[] = {0, 9, 8, 1, 2, 3, 4, 5, 6, 7 }; const int *aWidth = aExplainWidth; const int *aMap = aExplainMap; int nWidth = ArraySize(aExplainWidth); int iIndent = 1; if( p->cMode==MODE_ScanExp ){ aWidth = aScanExpWidth; aMap = aScanExpMap; nWidth = ArraySize(aScanExpWidth); iIndent = 3; } if( nArg>nWidth ) nArg = nWidth; /* If this is the first row seen, print out the headers */ if( p->cnt++==0 ){ for(i=0; i<nArg; i++){ utf8_width_print(p->out, aWidth[i], azCol[ aMap[i] ]); fputs(i==nArg-1 ? "\n" : " ", p->out); } for(i=0; i<nArg; i++){ print_dashes(p->out, aWidth[i]); fputs(i==nArg-1 ? "\n" : " ", p->out); } } /* If there is no data, exit early. */ if( azArg==0 ) break; for(i=0; i<nArg; i++){ const char *zSep = " "; int w = aWidth[i]; const char *zVal = azArg[ aMap[i] ]; if( i==nArg-1 ) w = 0; if( zVal && strlenChar(zVal)>w ){ w = strlenChar(zVal); zSep = " "; } if( i==iIndent && p->aiIndent && p->pStmt ){ if( p->iIndent<p->nIndent ){ utf8_printf(p->out, "%*.s", p->aiIndent[p->iIndent], ""); } p->iIndent++; } utf8_width_print(p->out, w, zVal ? zVal : p->nullValue); fputs(i==nArg-1 ? "\n" : zSep, p->out); } break; } case MODE_Semi: { /* .schema and .fullschema output */ printSchemaLine(p->out, azArg[0], ";\n"); break; } |
︙ | ︙ | |||
3328 3329 3330 3331 3332 3333 3334 | } ret++; } return ret; } #endif | < < < > | < < < < | 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 | } ret++; } return ret; } #endif #ifdef SQLITE_ENABLE_STMT_SCANSTATUS static void display_explain_scanstats( sqlite3 *db, /* Database to query */ ShellState *pArg /* Pointer to ShellState */ ){ static const int f = SQLITE_SCANSTAT_COMPLEX; sqlite3_stmt *p = pArg->pStmt; int ii = 0; i64 nTotal = 0; int nWidth = 0; eqp_reset(pArg); |
︙ | ︙ | |||
3410 3411 3412 3413 3414 3415 3416 3417 | } eqp_append(pArg, iId, iPid, zText); sqlite3_free(zText); } eqp_render(pArg, nTotal); #endif | > | | 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 | } eqp_append(pArg, iId, iPid, zText); sqlite3_free(zText); } eqp_render(pArg, nTotal); } #endif /* ** Parameter azArray points to a zero-terminated array of strings. zStr ** points to a single nul-terminated string. Return non-zero if zStr ** is equal, according to strcmp(), to any of the strings in the array. ** Otherwise, return zero. */ |
︙ | ︙ | |||
3449 3450 3451 3452 3453 3454 3455 | ** and ends on one of: ** Yield SeekGt SeekLt RowSetRead Rewind ** or if the P1 parameter is one instead of zero, ** then indent all opcodes between the earlier instruction ** and "Goto" by 2 spaces. */ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ | < < > > | < > | < < < | > | < | < < < > > < | | | | < < < < < < < < < < < < < < < > < | < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 | ** and ends on one of: ** Yield SeekGt SeekLt RowSetRead Rewind ** or if the P1 parameter is one instead of zero, ** then indent all opcodes between the earlier instruction ** and "Goto" by 2 spaces. */ static void explain_data_prepare(ShellState *p, sqlite3_stmt *pSql){ int *abYield = 0; /* True if op is an OP_Yield */ int nAlloc = 0; /* Allocated size of p->aiIndent[], abYield */ int iOp; /* Index of operation in p->aiIndent[] */ const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext", "Return", 0 }; const char *azYield[] = { "Yield", "SeekLT", "SeekGT", "RowSetRead", "Rewind", 0 }; const char *azGoto[] = { "Goto", 0 }; /* The caller guarantees that the leftmost 4 columns of the statement ** passed to this function are equivalent to the leftmost 4 columns ** of EXPLAIN statement output. In practice the statement may be ** an EXPLAIN, or it may be a query on the bytecode() virtual table. */ assert( sqlite3_column_count(pSql)>=4 ); assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 0), "addr" ) ); assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 1), "opcode" ) ); assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 2), "p1" ) ); assert( 0==sqlite3_stricmp( sqlite3_column_name(pSql, 3), "p2" ) ); for(iOp=0; SQLITE_ROW==sqlite3_step(pSql); iOp++){ int i; int iAddr = sqlite3_column_int(pSql, 0); const char *zOp = (const char*)sqlite3_column_text(pSql, 1); int p1 = sqlite3_column_int(pSql, 2); int p2 = sqlite3_column_int(pSql, 3); /* Assuming that p2 is an instruction address, set variable p2op to the ** index of that instruction in the aiIndent[] array. p2 and p2op may be ** different if the current instruction is part of a sub-program generated ** by an SQL trigger or foreign key. */ int p2op = (p2 + (iOp-iAddr)); /* Grow the p->aiIndent array as required */ if( iOp>=nAlloc ){ nAlloc += 100; p->aiIndent = (int*)sqlite3_realloc64(p->aiIndent, nAlloc*sizeof(int)); shell_check_oom(p->aiIndent); abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int)); shell_check_oom(abYield); } abYield[iOp] = str_in_array(zOp, azYield); p->aiIndent[iOp] = 0; p->nIndent = iOp+1; if( str_in_array(zOp, azNext) && p2op>0 ){ for(i=p2op; i<iOp; i++) p->aiIndent[i] += 2; } if( str_in_array(zOp, azGoto) && p2op<iOp && (abYield[p2op] || p1) ){ for(i=p2op; i<iOp; i++) p->aiIndent[i] += 2; } } p->iIndent = 0; sqlite3_free(abYield); sqlite3_reset(pSql); } /* ** Free the array allocated by explain_data_prepare(). */ static void explain_data_delete(ShellState *p){ sqlite3_free(p->aiIndent); p->aiIndent = 0; p->nIndent = 0; p->iIndent = 0; } static void exec_prepared_stmt(ShellState*, sqlite3_stmt*); /* ** Display scan stats. */ static void display_scanstats( sqlite3 *db, /* Database to query */ ShellState *pArg /* Pointer to ShellState */ ){ #ifndef SQLITE_ENABLE_STMT_SCANSTATUS UNUSED_PARAMETER(db); UNUSED_PARAMETER(pArg); #else if( pArg->scanstatsOn==3 ){ const char *zSql = " SELECT addr, opcode, p1, p2, p3, p4, p5, comment, nexec," " round(ncycle*100.0 / (sum(ncycle) OVER ()), 2)||'%' AS cycles" " FROM bytecode(?)"; int rc = SQLITE_OK; sqlite3_stmt *pStmt = 0; rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); if( rc==SQLITE_OK ){ sqlite3_stmt *pSave = pArg->pStmt; pArg->pStmt = pStmt; sqlite3_bind_pointer(pStmt, 1, pSave, "stmt-pointer", 0); pArg->cnt = 0; pArg->cMode = MODE_ScanExp; explain_data_prepare(pArg, pStmt); exec_prepared_stmt(pArg, pStmt); explain_data_delete(pArg); sqlite3_finalize(pStmt); pArg->pStmt = pSave; } }else{ display_explain_scanstats(db, pArg); } #endif } /* ** Disable and restore .wheretrace and .treetrace/.selecttrace settings. */ static unsigned int savedSelectTrace; static unsigned int savedWhereTrace; static void disable_debug_trace_modes(void){ |
︙ | ︙ | |||
4328 4329 4330 4331 4332 4333 4334 | pArg->pStmt = pStmt; pArg->cnt = 0; } /* Show the EXPLAIN QUERY PLAN if .eqp is on */ if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){ sqlite3_stmt *pExplain; | < > | < | < < | < | > < < < < < < > > > | | | 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 | pArg->pStmt = pStmt; pArg->cnt = 0; } /* Show the EXPLAIN QUERY PLAN if .eqp is on */ if( pArg && pArg->autoEQP && sqlite3_stmt_isexplain(pStmt)==0 ){ sqlite3_stmt *pExplain; int triggerEQP = 0; disable_debug_trace_modes(); sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP); if( pArg->autoEQP>=AUTOEQP_trigger ){ sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0); } pExplain = pStmt; sqlite3_reset(pExplain); rc = sqlite3_stmt_explain(pExplain, 2); if( rc==SQLITE_OK ){ while( sqlite3_step(pExplain)==SQLITE_ROW ){ const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3); int iEqpId = sqlite3_column_int(pExplain, 0); int iParentId = sqlite3_column_int(pExplain, 1); if( zEQPLine==0 ) zEQPLine = ""; if( zEQPLine[0]=='-' ) eqp_render(pArg, 0); eqp_append(pArg, iEqpId, iParentId, zEQPLine); } eqp_render(pArg, 0); } if( pArg->autoEQP>=AUTOEQP_full ){ /* Also do an EXPLAIN for ".eqp full" mode */ sqlite3_reset(pExplain); rc = sqlite3_stmt_explain(pExplain, 1); if( rc==SQLITE_OK ){ pArg->cMode = MODE_Explain; assert( sqlite3_stmt_isexplain(pExplain)==1 ); explain_data_prepare(pArg, pExplain); exec_prepared_stmt(pArg, pExplain); explain_data_delete(pArg); } } if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){ sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0); } sqlite3_reset(pStmt); sqlite3_stmt_explain(pStmt, 0); restore_debug_trace_modes(); } if( pArg ){ int bIsExplain = (sqlite3_stmt_isexplain(pStmt)==1); pArg->cMode = pArg->mode; if( pArg->autoExplain ){ if( bIsExplain ){ pArg->cMode = MODE_Explain; } if( sqlite3_stmt_isexplain(pStmt)==2 ){ pArg->cMode = MODE_EQP; } } /* If the shell is currently in ".explain" mode, gather the extra ** data required to add indents to the output.*/ if( pArg->cMode==MODE_Explain && bIsExplain ){ explain_data_prepare(pArg, pStmt); } } bind_prepared_stmt(pArg, pStmt); exec_prepared_stmt(pArg, pStmt); explain_data_delete(pArg); |
︙ | ︙ | |||
9929 9930 9931 9932 9933 9934 9935 9936 9937 9938 9939 9940 9941 9942 | } close_db(pSrc); }else #endif /* !defined(SQLITE_SHELL_FIDDLE) */ if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){ if( nArg==2 ){ if( cli_strcmp(azArg[1], "est")==0 ){ p->scanstatsOn = 2; }else{ p->scanstatsOn = (u8)booleanValue(azArg[1]); } open_db(p, 0); sqlite3_db_config( | > > > | 9959 9960 9961 9962 9963 9964 9965 9966 9967 9968 9969 9970 9971 9972 9973 9974 9975 | } close_db(pSrc); }else #endif /* !defined(SQLITE_SHELL_FIDDLE) */ if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){ if( nArg==2 ){ if( cli_strcmp(azArg[1], "vm")==0 ){ p->scanstatsOn = 3; }else if( cli_strcmp(azArg[1], "est")==0 ){ p->scanstatsOn = 2; }else{ p->scanstatsOn = (u8)booleanValue(azArg[1]); } open_db(p, 0); sqlite3_db_config( |
︙ | ︙ |
Changes to src/sqlite.h.in.
︙ | ︙ | |||
4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 | ** prepared statement S is an EXPLAIN statement, or 2 if the ** statement S is an EXPLAIN QUERY PLAN. ** ^The sqlite3_stmt_isexplain(S) interface returns 0 if S is ** an ordinary statement or a NULL pointer. */ int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt); /* ** CAPI3REF: Determine If A Prepared Statement Has Been Reset ** METHOD: sqlite3_stmt ** ** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the ** [prepared statement] S has been stepped at least once using ** [sqlite3_step(S)] but has neither run to completion (returned | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 | ** prepared statement S is an EXPLAIN statement, or 2 if the ** statement S is an EXPLAIN QUERY PLAN. ** ^The sqlite3_stmt_isexplain(S) interface returns 0 if S is ** an ordinary statement or a NULL pointer. */ int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt); /* ** CAPI3REF: Change The EXPLAIN Setting For A Prepared Statement ** METHOD: sqlite3_stmt ** ** The sqlite3_stmt_explain(S,E) interface changes the EXPLAIN ** setting for prepared statement S. If E is zero, then S becomes ** a normal prepared statement. If E is 1, then S behaves as if ** its SQL text began with "EXPLAIN". If E is 2, then S behaves as if ** its SQL text began with "EXPLAIN QUERY PLAN". ** ** Calling sqlite3_stmt_explain(S,E) might cause S to be reprepared. ** SQLite tries to avoid a reprepare, but a reprepare might be necessary ** on the first transition into EXPLAIN or EXPLAIN QUERY PLAN mode. ** ** Because of the potential need to reprepare, a call to ** sqlite3_stmt_explain(S,E) will fail with SQLITE_ERROR if S cannot be ** reprepared because it was created using sqlite3_prepare() instead of ** the newer sqlite_prepare_v2() or sqlite3_prepare_v3() interfaces and ** hence has no saved SQL text with which to reprepare. ** ** Changing the explain setting for a prepared statement does not change ** the original SQL text for the statement. Hence, if the SQL text originally ** began with EXPLAIN or EXPLAIN QUERY PLAN, but sqlite3_stmt_explain(S,0) ** is called to convert the statement into an ordinary statement, the EXPLAIN ** or EXPLAIN QUERY PLAN keywords will still appear in the sqlite3_sql(S) ** output, even though the statement now acts like a normal SQL statement. ** ** This routine returns SQLITE_OK if the explain mode is successfully ** changed, or an error code if the explain mode could not be changed. ** The explain mode cannot be changed while a statement is active. ** Hence, it is good practice to call [sqlite3_reset(S)] ** immediately prior to calling sqlite3_stmt_explain(S,E). */ int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode); /* ** CAPI3REF: Determine If A Prepared Statement Has Been Reset ** METHOD: sqlite3_stmt ** ** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the ** [prepared statement] S has been stepped at least once using ** [sqlite3_step(S)] but has neither run to completion (returned |
︙ | ︙ |
Changes to src/sqliteInt.h.
︙ | ︙ | |||
1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 | typedef struct Module Module; typedef struct NameContext NameContext; typedef struct OnOrUsing OnOrUsing; typedef struct Parse Parse; typedef struct ParseCleanup ParseCleanup; typedef struct PreUpdate PreUpdate; typedef struct PrintfArguments PrintfArguments; typedef struct RenameToken RenameToken; typedef struct Returning Returning; typedef struct RowSet RowSet; typedef struct Savepoint Savepoint; typedef struct Select Select; typedef struct SQLiteThread SQLiteThread; typedef struct SelectDest SelectDest; | > | 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 | typedef struct Module Module; typedef struct NameContext NameContext; typedef struct OnOrUsing OnOrUsing; typedef struct Parse Parse; typedef struct ParseCleanup ParseCleanup; typedef struct PreUpdate PreUpdate; typedef struct PrintfArguments PrintfArguments; typedef struct RCStr RCStr; typedef struct RenameToken RenameToken; typedef struct Returning Returning; typedef struct RowSet RowSet; typedef struct Savepoint Savepoint; typedef struct Select Select; typedef struct SQLiteThread SQLiteThread; typedef struct SelectDest SelectDest; |
︙ | ︙ | |||
4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 | }; #define SQLITE_PRINTF_INTERNAL 0x01 /* Internal-use-only converters allowed */ #define SQLITE_PRINTF_SQLFUNC 0x02 /* SQL function arguments to VXPrintf */ #define SQLITE_PRINTF_MALLOCED 0x04 /* True if xText is allocated space */ #define isMalloced(X) (((X)->printfFlags & SQLITE_PRINTF_MALLOCED)!=0) /* ** A pointer to this structure is used to communicate information ** from sqlite3Init and OP_ParseSchema into the sqlite3InitCallback. */ typedef struct { sqlite3 *db; /* The database being initialized */ | > > > > > > > > > > > > > > > > > > > | 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 | }; #define SQLITE_PRINTF_INTERNAL 0x01 /* Internal-use-only converters allowed */ #define SQLITE_PRINTF_SQLFUNC 0x02 /* SQL function arguments to VXPrintf */ #define SQLITE_PRINTF_MALLOCED 0x04 /* True if xText is allocated space */ #define isMalloced(X) (((X)->printfFlags & SQLITE_PRINTF_MALLOCED)!=0) /* ** The following object is the header for an "RCStr" or "reference-counted ** string". An RCStr is passed around and used like any other char* ** that has been dynamically allocated. The important interface ** differences: ** ** 1. RCStr strings are reference counted. They are deallocated ** when the reference count reaches zero. ** ** 2. Use sqlite3RCStrUnref() to free an RCStr string rather than ** sqlite3_free() ** ** 3. Make a (read-only) copy of a read-only RCStr string using ** sqlite3RCStrRef(). */ struct RCStr { u64 nRCRef; /* Number of references */ /* Total structure size should be a multiple of 8 bytes for alignment */ }; /* ** A pointer to this structure is used to communicate information ** from sqlite3Init and OP_ParseSchema into the sqlite3InitCallback. */ typedef struct { sqlite3 *db; /* The database being initialized */ |
︙ | ︙ | |||
5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 | void sqlite3FileSuffix3(const char*, char*); #else # define sqlite3FileSuffix3(X,Y) #endif u8 sqlite3GetBoolean(const char *z,u8); const void *sqlite3ValueText(sqlite3_value*, u8); int sqlite3ValueBytes(sqlite3_value*, u8); void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, void(*)(void*)); void sqlite3ValueSetNull(sqlite3_value*); void sqlite3ValueFree(sqlite3_value*); #ifndef SQLITE_UNTESTABLE void sqlite3ResultIntReal(sqlite3_context*); | > | 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 | void sqlite3FileSuffix3(const char*, char*); #else # define sqlite3FileSuffix3(X,Y) #endif u8 sqlite3GetBoolean(const char *z,u8); const void *sqlite3ValueText(sqlite3_value*, u8); int sqlite3ValueIsOfClass(const sqlite3_value*, void(*)(void*)); int sqlite3ValueBytes(sqlite3_value*, u8); void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, void(*)(void*)); void sqlite3ValueSetNull(sqlite3_value*); void sqlite3ValueFree(sqlite3_value*); #ifndef SQLITE_UNTESTABLE void sqlite3ResultIntReal(sqlite3_context*); |
︙ | ︙ | |||
5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 | ); void sqlite3NoopDestructor(void*); void *sqlite3OomFault(sqlite3*); void sqlite3OomClear(sqlite3*); int sqlite3ApiExit(sqlite3 *db, int); int sqlite3OpenTempDatabase(Parse *); void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int); int sqlite3StrAccumEnlarge(StrAccum*, i64); char *sqlite3StrAccumFinish(StrAccum*); void sqlite3StrAccumSetError(StrAccum*, u8); void sqlite3ResultStrAccum(sqlite3_context*,StrAccum*); void sqlite3SelectDestInit(SelectDest*,int,int); Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int); | > > > > > | 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 | ); void sqlite3NoopDestructor(void*); void *sqlite3OomFault(sqlite3*); void sqlite3OomClear(sqlite3*); int sqlite3ApiExit(sqlite3 *db, int); int sqlite3OpenTempDatabase(Parse *); char *sqlite3RCStrRef(char*); void sqlite3RCStrUnref(char*); char *sqlite3RCStrNew(u64); char *sqlite3RCStrResize(char*,u64); void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int); int sqlite3StrAccumEnlarge(StrAccum*, i64); char *sqlite3StrAccumFinish(StrAccum*); void sqlite3StrAccumSetError(StrAccum*, u8); void sqlite3ResultStrAccum(sqlite3_context*,StrAccum*); void sqlite3SelectDestInit(SelectDest*,int,int); Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int); |
︙ | ︙ |
Changes to src/test1.c.
︙ | ︙ | |||
2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 | } if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; rc = sqlite3_stmt_isexplain(pStmt); Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); return TCL_OK; } /* ** Usage: sqlite3_stmt_busy STMT ** ** Return true if STMT is a non-NULL pointer to a statement ** that has been stepped but not to completion. */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 | } if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; rc = sqlite3_stmt_isexplain(pStmt); Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); return TCL_OK; } /* ** Usage: sqlite3_stmt_explain STMT INT ** ** Set the explain to normal (0), EXPLAIN (1) or EXPLAIN QUERY PLAN (2). */ static int SQLITE_TCLAPI test_stmt_explain( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ sqlite3_stmt *pStmt; int eMode = 0; int rc; if( objc!=3 ){ Tcl_AppendResult(interp, "wrong # args: should be \"", Tcl_GetStringFromObj(objv[0], 0), " STMT INT", 0); return TCL_ERROR; } if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR; if( Tcl_GetIntFromObj(interp, objv[2], &eMode) ) return TCL_ERROR; rc = sqlite3_stmt_explain(pStmt, eMode); Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); return TCL_OK; } /* ** Usage: sqlite3_stmt_busy STMT ** ** Return true if STMT is a non-NULL pointer to a statement ** that has been stepped but not to completion. */ |
︙ | ︙ | |||
8987 8988 8989 8990 8991 8992 8993 8994 8995 8996 8997 8998 8999 9000 | { "sqlite3_expanded_sql", test_ex_sql ,0 }, #ifdef SQLITE_ENABLE_NORMALIZE { "sqlite3_normalized_sql", test_norm_sql ,0 }, #endif { "sqlite3_next_stmt", test_next_stmt ,0 }, { "sqlite3_stmt_readonly", test_stmt_readonly ,0 }, { "sqlite3_stmt_isexplain", test_stmt_isexplain,0 }, { "sqlite3_stmt_busy", test_stmt_busy ,0 }, { "uses_stmt_journal", uses_stmt_journal ,0 }, { "sqlite3_release_memory", test_release_memory, 0}, { "sqlite3_db_release_memory", test_db_release_memory, 0}, { "sqlite3_db_cacheflush", test_db_cacheflush, 0}, { "sqlite3_system_errno", test_system_errno, 0}, | > | 9015 9016 9017 9018 9019 9020 9021 9022 9023 9024 9025 9026 9027 9028 9029 | { "sqlite3_expanded_sql", test_ex_sql ,0 }, #ifdef SQLITE_ENABLE_NORMALIZE { "sqlite3_normalized_sql", test_norm_sql ,0 }, #endif { "sqlite3_next_stmt", test_next_stmt ,0 }, { "sqlite3_stmt_readonly", test_stmt_readonly ,0 }, { "sqlite3_stmt_isexplain", test_stmt_isexplain,0 }, { "sqlite3_stmt_explain", test_stmt_explain ,0 }, { "sqlite3_stmt_busy", test_stmt_busy ,0 }, { "uses_stmt_journal", uses_stmt_journal ,0 }, { "sqlite3_release_memory", test_release_memory, 0}, { "sqlite3_db_release_memory", test_db_release_memory, 0}, { "sqlite3_db_cacheflush", test_db_cacheflush, 0}, { "sqlite3_system_errno", test_system_errno, 0}, |
︙ | ︙ |
Changes to src/vdbe.c.
︙ | ︙ | |||
690 691 692 693 694 695 696 697 698 699 700 701 702 703 | ** though, at least, those hashes are different from each other and ** from NULL. */ h += 4093 + (p->flags & (MEM_Str|MEM_Blob)); } } return h; } /* ** Return the symbolic name for the data type of a pMem */ static const char *vdbeMemTypeName(Mem *pMem){ static const char *azTypes[] = { /* SQLITE_INTEGER */ "INT", | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 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 741 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 | ** though, at least, those hashes are different from each other and ** from NULL. */ h += 4093 + (p->flags & (MEM_Str|MEM_Blob)); } } return h; } /* ** For OP_Column, factor out the case where content is loaded from ** overflow pages, so that the code to implement this case is separate ** the common case where all content fits on the page. Factoring out ** the code reduces register pressure and helps the common case ** to run faster. */ static SQLITE_NOINLINE int vdbeColumnFromOverflow( VdbeCursor *pC, /* The BTree cursor from which we are reading */ int iCol, /* The column to read */ int t, /* The serial-type code for the column value */ i64 iOffset, /* Offset to the start of the content value */ Mem *pDest /* Store the value into this register. */ ){ int rc; sqlite3 *db = pDest->db; int encoding = pDest->enc; int len = sqlite3VdbeSerialTypeLen(t); assert( pC->eCurType==CURTYPE_BTREE ); if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) return SQLITE_TOOBIG; if( len > 4000 ){ /* Cache large column values that are on overflow pages using ** an RCStr (reference counted string) so that if they are reloaded, ** that do not have to be copied a second time. The overhead of ** creating and managing the cache is such that this is only ** profitable for larger TEXT and BLOB values. */ VdbeTxtBlbCache *pCache; char *pBuf; if( pC->colCache==0 ){ pC->pCache = sqlite3DbMallocZero(db, sizeof(VdbeTxtBlbCache) ); if( pC->pCache==0 ) return SQLITE_NOMEM; pC->colCache = 1; } pCache = pC->pCache; if( pCache->pCValue==0 || pCache->iCol!=iCol || pCache->iOffset!=sqlite3BtreeOffset(pC->uc.pCursor) ){ if( pCache->pCValue ) sqlite3RCStrUnref(pCache->pCValue); pBuf = pCache->pCValue = sqlite3RCStrNew( len+3 ); if( pBuf==0 ) return SQLITE_NOMEM; rc = sqlite3BtreePayload(pC->uc.pCursor, iOffset, len, pBuf); if( rc ) return rc; pBuf[len] = 0; pBuf[len+1] = 0; pBuf[len+2] = 0; pCache->iCol = iCol; pCache->iOffset = sqlite3BtreeOffset(pC->uc.pCursor); }else{ pBuf = pCache->pCValue; } assert( t>=12 ); sqlite3RCStrRef(pBuf); if( t&1 ){ rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, encoding, (void(*)(void*))sqlite3RCStrUnref); pDest->flags |= MEM_Term; }else{ rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, 0, (void(*)(void*))sqlite3RCStrUnref); } }else{ rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, iOffset, len, pDest); if( rc ) return rc; sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); if( (t&1)!=0 && encoding==SQLITE_UTF8 ){ pDest->z[len] = 0; pDest->flags |= MEM_Term; } } pDest->flags &= ~MEM_Ephem; return rc; } /* ** Return the symbolic name for the data type of a pMem */ static const char *vdbeMemTypeName(Mem *pMem){ static const char *azTypes[] = { /* SQLITE_INTEGER */ "INT", |
︙ | ︙ | |||
3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 | pDest->z[len] = 0; pDest->z[len+1] = 0; pDest->flags = aFlag[t&1]; } }else{ u8 p5; pDest->enc = encoding; /* This branch happens only when content is on overflow pages */ if( ((p5 = (pOp->p5 & OPFLAG_BYTELENARG))!=0 && (p5==OPFLAG_TYPEOFARG || (t>=12 && ((t&1)==0 || p5==OPFLAG_BYTELENARG)) ) ) || (len = sqlite3VdbeSerialTypeLen(t))==0 | > | 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 | pDest->z[len] = 0; pDest->z[len+1] = 0; pDest->flags = aFlag[t&1]; } }else{ u8 p5; pDest->enc = encoding; assert( pDest->db==db ); /* This branch happens only when content is on overflow pages */ if( ((p5 = (pOp->p5 & OPFLAG_BYTELENARG))!=0 && (p5==OPFLAG_TYPEOFARG || (t>=12 && ((t&1)==0 || p5==OPFLAG_BYTELENARG)) ) ) || (len = sqlite3VdbeSerialTypeLen(t))==0 |
︙ | ︙ | |||
3080 3081 3082 3083 3084 3085 3086 | ** buffer passed to it, debugging function VdbeMemPrettyPrint() may ** read more. Use the global constant sqlite3CtypeMap[] as the array, ** as that array is 256 bytes long (plenty for VdbeMemPrettyPrint()) ** and it begins with a bunch of zeros. */ sqlite3VdbeSerialGet((u8*)sqlite3CtypeMap, t, pDest); }else{ | > > > | < | < < < < | 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 | ** buffer passed to it, debugging function VdbeMemPrettyPrint() may ** read more. Use the global constant sqlite3CtypeMap[] as the array, ** as that array is 256 bytes long (plenty for VdbeMemPrettyPrint()) ** and it begins with a bunch of zeros. */ sqlite3VdbeSerialGet((u8*)sqlite3CtypeMap, t, pDest); }else{ rc = vdbeColumnFromOverflow(pC, p2, t, aOffset[p2], pDest); if( rc ){ if( rc==SQLITE_NOMEM ) goto no_mem; if( rc==SQLITE_TOOBIG ) goto too_big; goto abort_due_to_error; } } } op_column_out: UPDATE_MAX_BLOBSIZE(pDest); REGISTER_TRACE(pOp->p3, pDest); break; |
︙ | ︙ |
Changes to src/vdbeInt.h.
︙ | ︙ | |||
52 53 54 55 56 57 58 59 60 61 62 63 64 65 | /* Opaque type used by code in vdbesort.c */ typedef struct VdbeSorter VdbeSorter; /* Elements of the linked list at Vdbe.pAuxData */ typedef struct AuxData AuxData; /* Types of VDBE cursors */ #define CURTYPE_BTREE 0 #define CURTYPE_SORTER 1 #define CURTYPE_VTAB 2 #define CURTYPE_PSEUDO 3 /* | > > > | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | /* Opaque type used by code in vdbesort.c */ typedef struct VdbeSorter VdbeSorter; /* Elements of the linked list at Vdbe.pAuxData */ typedef struct AuxData AuxData; /* A cache of large TEXT or BLOB values in a VdbeCursor */ typedef struct VdbeTxtBlbCache VdbeTxtBlbCache; /* Types of VDBE cursors */ #define CURTYPE_BTREE 0 #define CURTYPE_SORTER 1 #define CURTYPE_VTAB 2 #define CURTYPE_PSEUDO 3 /* |
︙ | ︙ | |||
83 84 85 86 87 88 89 90 91 92 93 94 95 96 | u8 seekOp; /* Most recent seek operation on this cursor */ u8 wrFlag; /* The wrFlag argument to sqlite3BtreeCursor() */ #endif Bool isEphemeral:1; /* True for an ephemeral table */ Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */ Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ Bool noReuse:1; /* OpenEphemeral may not reuse this cursor */ u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */ union { /* pBtx for isEphermeral. pAltMap otherwise */ Btree *pBtx; /* Separate file holding temporary table */ u32 *aAltMap; /* Mapping from table to index column numbers */ } ub; i64 seqCount; /* Sequence counter */ | > | 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | u8 seekOp; /* Most recent seek operation on this cursor */ u8 wrFlag; /* The wrFlag argument to sqlite3BtreeCursor() */ #endif Bool isEphemeral:1; /* True for an ephemeral table */ Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */ Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ Bool noReuse:1; /* OpenEphemeral may not reuse this cursor */ Bool colCache:1; /* pCache pointer is initialized and non-NULL */ u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */ union { /* pBtx for isEphermeral. pAltMap otherwise */ Btree *pBtx; /* Separate file holding temporary table */ u32 *aAltMap; /* Mapping from table to index column numbers */ } ub; i64 seqCount; /* Sequence counter */ |
︙ | ︙ | |||
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | u32 *aOffset; /* Pointer to aType[nField] */ const u8 *aRow; /* Data for the current row, if all on one page */ u32 payloadSize; /* Total number of bytes in the record */ u32 szRow; /* Byte available in aRow */ #ifdef SQLITE_ENABLE_COLUMN_USED_MASK u64 maskUsed; /* Mask of columns used by this cursor */ #endif /* 2*nField extra array elements allocated for aType[], beyond the one ** static element declared in the structure. nField total array slots for ** aType[] and nField+1 array slots for aOffset[] */ u32 aType[1]; /* Type values record decode. MUST BE LAST */ }; /* Return true if P is a null-only cursor */ #define IsNullCursor(P) \ ((P)->eCurType==CURTYPE_PSEUDO && (P)->nullRow && (P)->seekResult==0) | > < > > > > > > > > > > > > | 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 | u32 *aOffset; /* Pointer to aType[nField] */ const u8 *aRow; /* Data for the current row, if all on one page */ u32 payloadSize; /* Total number of bytes in the record */ u32 szRow; /* Byte available in aRow */ #ifdef SQLITE_ENABLE_COLUMN_USED_MASK u64 maskUsed; /* Mask of columns used by this cursor */ #endif VdbeTxtBlbCache *pCache; /* Cache of large TEXT or BLOB values */ /* 2*nField extra array elements allocated for aType[], beyond the one ** static element declared in the structure. nField total array slots for ** aType[] and nField+1 array slots for aOffset[] */ u32 aType[1]; /* Type values record decode. MUST BE LAST */ }; /* Return true if P is a null-only cursor */ #define IsNullCursor(P) \ ((P)->eCurType==CURTYPE_PSEUDO && (P)->nullRow && (P)->seekResult==0) /* ** A value for VdbeCursor.cacheStatus that means the cache is always invalid. */ #define CACHE_STALE 0 /* ** Large TEXT or BLOB values can be slow to load, so we want to avoid ** loading them more than once. For that reason, large TEXT and BLOB values ** can be stored in a cache defined by this object, and attached to the ** VdbeCursor using the pCache field. */ struct VdbeTxtBlbCache { char *pCValue; /* A RCStr buffer to hold the value */ i64 iOffset; /* File offset of the row being cached */ int iCol; /* Column for which the cache is valid */ }; /* ** When a sub-program is executed (OP_Program), a structure of this type ** is allocated to store the current value of the program counter, as ** well as the current memory cell array and various other frame specific ** values stored in the Vdbe struct. When the sub-program is finished, ** these values are copied back to the Vdbe from the VdbeFrame structure, |
︙ | ︙ | |||
461 462 463 464 465 466 467 468 469 470 471 472 | i64 startTime; /* Time when query started - used for profiling */ #endif #ifdef SQLITE_DEBUG int rcApp; /* errcode set by sqlite3_result_error_code() */ u32 nWrite; /* Number of write operations that have occurred */ #endif u16 nResColumn; /* Number of columns in one row of the result set */ u8 errorAction; /* Recovery action to do in case of an error */ u8 minWriteFileFormat; /* Minimum file format for writable database files */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 eVdbeState; /* On of the VDBE_*_STATE values */ bft expired:2; /* 1: recompile VM immediately 2: when convenient */ | > | > | 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 | i64 startTime; /* Time when query started - used for profiling */ #endif #ifdef SQLITE_DEBUG int rcApp; /* errcode set by sqlite3_result_error_code() */ u32 nWrite; /* Number of write operations that have occurred */ #endif u16 nResColumn; /* Number of columns in one row of the result set */ u16 nResAlloc; /* Column slots allocated to aColName[] */ u8 errorAction; /* Recovery action to do in case of an error */ u8 minWriteFileFormat; /* Minimum file format for writable database files */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 eVdbeState; /* On of the VDBE_*_STATE values */ bft expired:2; /* 1: recompile VM immediately 2: when convenient */ bft explain:2; /* 0: normal, 1: EXPLAIN, 2: EXPLAIN QUERY PLAN */ bft changeCntOn:1; /* True to update the change-counter */ bft usesStmtJournal:1; /* True if uses a statement journal */ bft readOnly:1; /* True for statements that do not write */ bft bIsReader:1; /* True for statements that read */ bft haveEqpOps:1; /* Bytecode supports EXPLAIN QUERY PLAN */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ u32 aCounter[9]; /* Counters used by sqlite3_stmt_status() */ char *zSql; /* Text of the SQL statement that generated this */ #ifdef SQLITE_ENABLE_NORMALIZE char *zNormSql; /* Normalization of the associated SQL statement */ DblquoteStr *pDblStr; /* List of double-quoted string literals */ |
︙ | ︙ |
Changes to src/vdbeapi.c.
︙ | ︙ | |||
1123 1124 1125 1126 1127 1128 1129 | #endif /* ** Return the number of columns in the result set for the statement pStmt. */ int sqlite3_column_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; | > | | 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 | #endif /* ** Return the number of columns in the result set for the statement pStmt. */ int sqlite3_column_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; if( pVm==0 ) return 0; return pVm->nResColumn; } /* ** Return the number of values available from the current row of the ** currently executing statement pStmt. */ int sqlite3_data_count(sqlite3_stmt *pStmt){ |
︙ | ︙ | |||
1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 | #endif /* SQLITE_OMIT_UTF16 */ int sqlite3_column_type(sqlite3_stmt *pStmt, int i){ int iType = sqlite3_value_type( columnMem(pStmt,i) ); columnMallocFailure(pStmt); return iType; } /* ** Convert the N-th element of pStmt->pColName[] into a string using ** xFunc() then return that string. If N is out of range, return 0. ** ** There are up to 5 names for each column. useType determines which ** name is returned. Here are the names: ** | > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 1330 1331 1332 1333 1334 1335 1336 | #endif /* SQLITE_OMIT_UTF16 */ int sqlite3_column_type(sqlite3_stmt *pStmt, int i){ int iType = sqlite3_value_type( columnMem(pStmt,i) ); columnMallocFailure(pStmt); return iType; } /* ** Column names appropriate for EXPLAIN or EXPLAIN QUERY PLAN. */ static const char * const azExplainColNames8[] = { "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", /* EXPLAIN */ "id", "parent", "notused", "detail" /* EQP */ }; static const u16 azExplainColNames16data[] = { /* 0 */ 'a', 'd', 'd', 'r', 0, /* 5 */ 'o', 'p', 'c', 'o', 'd', 'e', 0, /* 12 */ 'p', '1', 0, /* 15 */ 'p', '2', 0, /* 18 */ 'p', '3', 0, /* 21 */ 'p', '4', 0, /* 24 */ 'p', '5', 0, /* 27 */ 'c', 'o', 'm', 'm', 'e', 'n', 't', 0, /* 35 */ 'i', 'd', 0, /* 38 */ 'p', 'a', 'r', 'e', 'n', 't', 0, /* 45 */ 'n', 'o', 't', 'u', 's', 'e', 'd', 0, /* 53 */ 'd', 'e', 't', 'a', 'i', 'l', 0 }; static const u8 iExplainColNames16[] = { 0, 5, 12, 15, 18, 21, 24, 27, 35, 38, 45, 53 }; /* ** Convert the N-th element of pStmt->pColName[] into a string using ** xFunc() then return that string. If N is out of range, return 0. ** ** There are up to 5 names for each column. useType determines which ** name is returned. Here are the names: ** |
︙ | ︙ | |||
1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 | sqlite3 *db; #ifdef SQLITE_ENABLE_API_ARMOR if( pStmt==0 ){ (void)SQLITE_MISUSE_BKPT; return 0; } #endif ret = 0; p = (Vdbe *)pStmt; db = p->db; assert( db!=0 ); | > > | > > > > > > > > > > > > > | < < > > | 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 1405 1406 1407 1408 1409 1410 | sqlite3 *db; #ifdef SQLITE_ENABLE_API_ARMOR if( pStmt==0 ){ (void)SQLITE_MISUSE_BKPT; return 0; } #endif if( N<0 ) return 0; ret = 0; p = (Vdbe *)pStmt; db = p->db; assert( db!=0 ); sqlite3_mutex_enter(db->mutex); if( p->explain ){ if( useType>0 ) goto columnName_end; n = p->explain==1 ? 8 : 4; if( N>=n ) goto columnName_end; if( useUtf16 ){ int i = iExplainColNames16[N + 8*p->explain - 8]; ret = (void*)&azExplainColNames16data[i]; }else{ ret = (void*)azExplainColNames8[N + 8*p->explain - 8]; } goto columnName_end; } n = p->nResColumn; if( N<n ){ u8 prior_mallocFailed = db->mallocFailed; N += useType*n; #ifndef SQLITE_OMIT_UTF16 if( useUtf16 ){ ret = sqlite3_value_text16((sqlite3_value*)&p->aColName[N]); }else #endif { ret = sqlite3_value_text((sqlite3_value*)&p->aColName[N]); } /* A malloc may have failed inside of the _text() call. If this ** is the case, clear the mallocFailed flag and return NULL. */ assert( db->mallocFailed==0 || db->mallocFailed==1 ); if( db->mallocFailed > prior_mallocFailed ){ sqlite3OomClear(db); ret = 0; } } columnName_end: sqlite3_mutex_leave(db->mutex); return ret; } /* ** Return the name of the Nth column of the result set returned by SQL ** statement pStmt. */ |
︙ | ︙ | |||
1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 | /* ** Return 1 if the statement is an EXPLAIN and return 2 if the ** statement is an EXPLAIN QUERY PLAN */ int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){ return pStmt ? ((Vdbe*)pStmt)->explain : 0; } /* ** Return true if the prepared statement is in need of being reset. */ int sqlite3_stmt_busy(sqlite3_stmt *pStmt){ Vdbe *v = (Vdbe*)pStmt; return v!=0 && v->eVdbeState==VDBE_RUN_STATE; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 | /* ** Return 1 if the statement is an EXPLAIN and return 2 if the ** statement is an EXPLAIN QUERY PLAN */ int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){ return pStmt ? ((Vdbe*)pStmt)->explain : 0; } /* ** Set the explain mode for a statement. */ int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode){ Vdbe *v = (Vdbe*)pStmt; int rc; if( v->eVdbeState!=VDBE_READY_STATE ) return SQLITE_BUSY; if( v->explain==eMode ) return SQLITE_OK; if( v->zSql==0 || eMode<0 || eMode>2 ) return SQLITE_ERROR; sqlite3_mutex_enter(v->db->mutex); if( v->nMem>=10 && (eMode!=2 || v->haveEqpOps) ){ /* No reprepare necessary */ v->explain = eMode; rc = SQLITE_OK; }else{ int haveEqpOps = v->explain==2 || v->haveEqpOps; v->explain = eMode; rc = sqlite3Reprepare(v); v->haveEqpOps = haveEqpOps!=0; } if( v->explain ){ v->nResColumn = 12 - 4*v->explain; }else{ v->nResColumn = v->nResAlloc; } sqlite3_mutex_leave(v->db->mutex); return rc; } /* ** Return true if the prepared statement is in need of being reset. */ int sqlite3_stmt_busy(sqlite3_stmt *pStmt){ Vdbe *v = (Vdbe*)pStmt; return v!=0 && v->eVdbeState==VDBE_RUN_STATE; |
︙ | ︙ |
Changes to src/vdbeaux.c.
︙ | ︙ | |||
1494 1495 1496 1497 1498 1499 1500 | if( N>0 ){ sqlite3VdbeAddOp3(pParse->pVdbe, OP_ReleaseReg, iFirst, N, *(int*)&mask); if( bUndefine ) sqlite3VdbeChangeP5(pParse->pVdbe, 1); } } #endif /* SQLITE_DEBUG */ | < | 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 | if( N>0 ){ sqlite3VdbeAddOp3(pParse->pVdbe, OP_ReleaseReg, iFirst, N, *(int*)&mask); if( bUndefine ) sqlite3VdbeChangeP5(pParse->pVdbe, 1); } } #endif /* SQLITE_DEBUG */ /* ** Change the value of the P4 operand for a specific instruction. ** This routine is useful when a large program is loaded from a ** static array using sqlite3VdbeAddOpList but we want to make a ** few minor changes to the program. ** ** If n>=0 then the P4 operand is dynamic, meaning that a copy of |
︙ | ︙ | |||
2414 2415 2416 2417 2418 2419 2420 | sqlite3VdbeError(p, sqlite3ErrStr(p->rc)); }else{ char *zP4 = sqlite3VdbeDisplayP4(db, pOp); if( p->explain==2 ){ sqlite3VdbeMemSetInt64(pMem, pOp->p1); sqlite3VdbeMemSetInt64(pMem+1, pOp->p2); sqlite3VdbeMemSetInt64(pMem+2, pOp->p3); | | | | | 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 | sqlite3VdbeError(p, sqlite3ErrStr(p->rc)); }else{ char *zP4 = sqlite3VdbeDisplayP4(db, pOp); if( p->explain==2 ){ sqlite3VdbeMemSetInt64(pMem, pOp->p1); sqlite3VdbeMemSetInt64(pMem+1, pOp->p2); sqlite3VdbeMemSetInt64(pMem+2, pOp->p3); sqlite3VdbeMemSetStr(pMem+3, zP4, -1, SQLITE_UTF8, sqlite3_free); assert( p->nResColumn==4 ); }else{ sqlite3VdbeMemSetInt64(pMem+0, i); sqlite3VdbeMemSetStr(pMem+1, (char*)sqlite3OpcodeName(pOp->opcode), -1, SQLITE_UTF8, SQLITE_STATIC); sqlite3VdbeMemSetInt64(pMem+2, pOp->p1); sqlite3VdbeMemSetInt64(pMem+3, pOp->p2); sqlite3VdbeMemSetInt64(pMem+4, pOp->p3); /* pMem+5 for p4 is done last */ sqlite3VdbeMemSetInt64(pMem+6, pOp->p5); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS { char *zCom = sqlite3VdbeDisplayComment(db, pOp, zP4); sqlite3VdbeMemSetStr(pMem+7, zCom, -1, SQLITE_UTF8, sqlite3_free); } #else sqlite3VdbeMemSetNull(pMem+7); #endif sqlite3VdbeMemSetStr(pMem+5, zP4, -1, SQLITE_UTF8, sqlite3_free); assert( p->nResColumn==8 ); } p->pResultRow = pMem; if( db->mallocFailed ){ p->rc = SQLITE_NOMEM; rc = SQLITE_ERROR; }else{ p->rc = SQLITE_OK; |
︙ | ︙ | |||
2648 2649 2650 2651 2652 2653 2654 | x.nFree = ROUNDDOWN8(pParse->szOpAlloc - n); /* Bytes of unused memory */ assert( x.nFree>=0 ); assert( EIGHT_BYTE_ALIGNMENT(&x.pSpace[x.nFree]) ); resolveP2Values(p, &nArg); p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort); if( pParse->explain ){ | < < < < < < < < | < < < < < < < < < | 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 | x.nFree = ROUNDDOWN8(pParse->szOpAlloc - n); /* Bytes of unused memory */ assert( x.nFree>=0 ); assert( EIGHT_BYTE_ALIGNMENT(&x.pSpace[x.nFree]) ); resolveP2Values(p, &nArg); p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort); if( pParse->explain ){ if( nMem<10 ) nMem = 10; p->explain = pParse->explain; p->nResColumn = 12 - 4*p->explain; } p->expired = 0; /* Memory for registers, parameters, cursor, etc, is allocated in one or two ** passes. On the first pass, we try to reuse unused memory at the ** end of the opcode array. If we are unable to satisfy all memory ** requirements by reusing the opcode array tail, then the second |
︙ | ︙ | |||
2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 | /* ** Close a VDBE cursor and release all the resources that cursor ** happens to hold. */ void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ if( pCx ) sqlite3VdbeFreeCursorNN(p,pCx); } void sqlite3VdbeFreeCursorNN(Vdbe *p, VdbeCursor *pCx){ switch( pCx->eCurType ){ case CURTYPE_SORTER: { sqlite3VdbeSorterClose(p->db, pCx); break; } case CURTYPE_BTREE: { assert( pCx->uc.pCursor!=0 ); | > > > > > > > > > > > > > > > > | 2700 2701 2702 2703 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 | /* ** Close a VDBE cursor and release all the resources that cursor ** happens to hold. */ void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ if( pCx ) sqlite3VdbeFreeCursorNN(p,pCx); } static SQLITE_NOINLINE void freeCursorWithCache(Vdbe *p, VdbeCursor *pCx){ VdbeTxtBlbCache *pCache = pCx->pCache; assert( pCx->colCache ); pCx->colCache = 0; pCx->pCache = 0; if( pCache->pCValue ){ sqlite3RCStrUnref(pCache->pCValue); pCache->pCValue = 0; } sqlite3DbFree(p->db, pCache); sqlite3VdbeFreeCursorNN(p, pCx); } void sqlite3VdbeFreeCursorNN(Vdbe *p, VdbeCursor *pCx){ if( pCx->colCache ){ freeCursorWithCache(p, pCx); return; } switch( pCx->eCurType ){ case CURTYPE_SORTER: { sqlite3VdbeSorterClose(p->db, pCx); break; } case CURTYPE_BTREE: { assert( pCx->uc.pCursor!=0 ); |
︙ | ︙ | |||
2820 2821 2822 2823 2824 2825 2826 | ** execution of the vdbe program so that sqlite3_column_count() can ** be called on an SQL statement before sqlite3_step(). */ void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){ int n; sqlite3 *db = p->db; | | | | | 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 | ** execution of the vdbe program so that sqlite3_column_count() can ** be called on an SQL statement before sqlite3_step(). */ void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){ int n; sqlite3 *db = p->db; if( p->nResAlloc ){ releaseMemArray(p->aColName, p->nResAlloc*COLNAME_N); sqlite3DbFree(db, p->aColName); } n = nResColumn*COLNAME_N; p->nResColumn = p->nResAlloc = (u16)nResColumn; p->aColName = (Mem*)sqlite3DbMallocRawNN(db, sizeof(Mem)*n ); if( p->aColName==0 ) return; initMemArray(p->aColName, n, db, MEM_Null); } /* ** Set the name of the idx'th column to be returned by the SQL statement. |
︙ | ︙ | |||
2850 2851 2852 2853 2854 2855 2856 | int idx, /* Index of column zName applies to */ int var, /* One of the COLNAME_* constants */ const char *zName, /* Pointer to buffer containing name */ void (*xDel)(void*) /* Memory management strategy for zName */ ){ int rc; Mem *pColName; | | | | 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 | int idx, /* Index of column zName applies to */ int var, /* One of the COLNAME_* constants */ const char *zName, /* Pointer to buffer containing name */ void (*xDel)(void*) /* Memory management strategy for zName */ ){ int rc; Mem *pColName; assert( idx<p->nResAlloc ); assert( var<COLNAME_N ); if( p->db->mallocFailed ){ assert( !zName || xDel!=SQLITE_DYNAMIC ); return SQLITE_NOMEM_BKPT; } assert( p->aColName!=0 ); pColName = &(p->aColName[idx+var*p->nResAlloc]); rc = sqlite3VdbeMemSetStr(pColName, zName, -1, SQLITE_UTF8, xDel); assert( rc!=0 || !zName || (pColName->flags&MEM_Term)!=0 ); return rc; } /* ** A read or write transaction may or may not be active on database handle |
︙ | ︙ | |||
3682 3683 3684 3685 3686 3687 3688 | ** the database connection and frees the object itself. */ static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ SubProgram *pSub, *pNext; assert( db!=0 ); assert( p->db==0 || p->db==db ); if( p->aColName ){ | | | 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 | ** the database connection and frees the object itself. */ static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ SubProgram *pSub, *pNext; assert( db!=0 ); assert( p->db==0 || p->db==db ); if( p->aColName ){ releaseMemArray(p->aColName, p->nResAlloc*COLNAME_N); sqlite3DbNNFreeNN(db, p->aColName); } for(pSub=p->pProgram; pSub; pSub=pNext){ pNext = pSub->pNext; vdbeFreeOpArray(db, pSub->aOp, pSub->nOp); sqlite3DbFree(db, pSub); } |
︙ | ︙ |
Changes to src/vdbemem.c.
︙ | ︙ | |||
328 329 330 331 332 333 334 335 336 337 338 339 340 341 | if( pMem->flags & MEM_Dyn ){ if( pMem->xDel==sqlite3_free && sqlite3_msize(pMem->z) >= (u64)(pMem->n+1) ){ pMem->z[pMem->n] = 0; pMem->flags |= MEM_Term; return; } }else if( pMem->szMalloc>0 && pMem->szMalloc >= pMem->n+1 ){ pMem->z[pMem->n] = 0; pMem->flags |= MEM_Term; return; } } | > > > > > | 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 | if( pMem->flags & MEM_Dyn ){ if( pMem->xDel==sqlite3_free && sqlite3_msize(pMem->z) >= (u64)(pMem->n+1) ){ pMem->z[pMem->n] = 0; pMem->flags |= MEM_Term; return; } if( pMem->xDel==(void(*)(void*))sqlite3RCStrUnref ){ /* Blindly assume that all RCStr objects are zero-terminated */ pMem->flags |= MEM_Term; return; } }else if( pMem->szMalloc>0 && pMem->szMalloc >= pMem->n+1 ){ pMem->z[pMem->n] = 0; pMem->flags |= MEM_Term; return; } } |
︙ | ︙ | |||
1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 | return pVal->z; } if( pVal->flags&MEM_Null ){ return 0; } return valueToText(pVal, enc); } /* ** Create a new sqlite3_value object. */ sqlite3_value *sqlite3ValueNew(sqlite3 *db){ Mem *p = sqlite3DbMallocZero(db, sizeof(*p)); if( p ){ | > > > > > > > > > > > > > > > > > > | 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 | return pVal->z; } if( pVal->flags&MEM_Null ){ return 0; } return valueToText(pVal, enc); } /* Return true if sqlit3_value object pVal is a string or blob value ** that uses the destructor specified in the second argument. ** ** TODO: Maybe someday promote this interface into a published API so ** that third-party extensions can get access to it? */ int sqlite3ValueIsOfClass(const sqlite3_value *pVal, void(*xFree)(void*)){ if( ALWAYS(pVal!=0) && ALWAYS((pVal->flags & (MEM_Str|MEM_Blob))!=0) && (pVal->flags & MEM_Dyn)!=0 && pVal->xDel==xFree ){ return 1; }else{ return 0; } } /* ** Create a new sqlite3_value object. */ sqlite3_value *sqlite3ValueNew(sqlite3 *db){ Mem *p = sqlite3DbMallocZero(db, sizeof(*p)); if( p ){ |
︙ | ︙ |
Changes to src/vdbevtab.c.
︙ | ︙ | |||
65 66 67 68 69 70 71 72 73 74 75 76 77 78 | "p1 INT," "p2 INT," "p3 INT," "p4 TEXT," "p5 INT," "comment TEXT," "subprog TEXT," "stmt HIDDEN" ");", /* Tables_used() schema */ "CREATE TABLE x(" "type TEXT," "schema TEXT," | > > | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | "p1 INT," "p2 INT," "p3 INT," "p4 TEXT," "p5 INT," "comment TEXT," "subprog TEXT," "nexec INT," "ncycle INT," "stmt HIDDEN" ");", /* Tables_used() schema */ "CREATE TABLE x(" "type TEXT," "schema TEXT," |
︙ | ︙ | |||
227 228 229 230 231 232 233 | if( pIdx->tnum==iRoot ){ pCur->zName = pIdx->zName; pCur->zType = "index"; } } } } | | | 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | if( pIdx->tnum==iRoot ){ pCur->zName = pIdx->zName; pCur->zType = "index"; } } } } i += 20; } } switch( i ){ case 0: /* addr */ sqlite3_result_int(ctx, pCur->iAddr); break; case 1: /* opcode */ |
︙ | ︙ | |||
277 278 279 280 281 282 283 | }else if( aOp[0].p4.z!=0 ){ sqlite3_result_text(ctx, aOp[0].p4.z+3, -1, SQLITE_STATIC); }else{ sqlite3_result_text(ctx, "(FK)", 4, SQLITE_STATIC); } break; } | > > > > > > > > > > > > > > > | | | | | 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | }else if( aOp[0].p4.z!=0 ){ sqlite3_result_text(ctx, aOp[0].p4.z+3, -1, SQLITE_STATIC); }else{ sqlite3_result_text(ctx, "(FK)", 4, SQLITE_STATIC); } break; } #ifdef SQLITE_ENABLE_STMT_SCANSTATUS case 9: /* nexec */ sqlite3_result_int(ctx, pOp->nExec); break; case 10: /* ncycle */ sqlite3_result_int(ctx, pOp->nCycle); break; #else case 9: /* nexec */ case 10: /* ncycle */ sqlite3_result_int(ctx, 0); break; #endif case 20: /* tables_used.type */ sqlite3_result_text(ctx, pCur->zType, -1, SQLITE_STATIC); break; case 21: /* tables_used.schema */ sqlite3_result_text(ctx, pCur->zSchema, -1, SQLITE_STATIC); break; case 22: /* tables_used.name */ sqlite3_result_text(ctx, pCur->zName, -1, SQLITE_STATIC); break; case 23: /* tables_used.wr */ sqlite3_result_int(ctx, pOp->opcode==OP_OpenWrite); break; } return SQLITE_OK; } /* |
︙ | ︙ | |||
360 361 362 363 364 365 366 | sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ int i; int rc = SQLITE_CONSTRAINT; struct sqlite3_index_constraint *p; bytecodevtab *pVTab = (bytecodevtab*)tab; | | | 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 | sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ int i; int rc = SQLITE_CONSTRAINT; struct sqlite3_index_constraint *p; bytecodevtab *pVTab = (bytecodevtab*)tab; int iBaseCol = pVTab->bTablesUsed ? 4 : 10; pIdxInfo->estimatedCost = (double)100; pIdxInfo->estimatedRows = 100; pIdxInfo->idxNum = 0; for(i=0, p=pIdxInfo->aConstraint; i<pIdxInfo->nConstraint; i++, p++){ if( p->usable==0 ) continue; if( p->op==SQLITE_INDEX_CONSTRAINT_EQ && p->iColumn==iBaseCol+1 ){ rc = SQLITE_OK; |
︙ | ︙ |
Changes to test/fuzzdata6.db.
cannot compute difference between binary files
Changes to test/json/README.md.
1 | The files in this subdirectory are used to help measure the performance | | > > > | > | | | | 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 | The files in this subdirectory are used to help measure the performance of the SQLite JSON functions, especially in relation to handling large JSON inputs. # 1.0 Prerequisites 1. Valgrind 2. Fossil 3. tclsh # 2.0 Setup 1. Run: "`tclsh json-generator.tcl | sqlite3 json100mb.db`" to create the 100 megabyte test database. Do this so that the "json100mb.db" file lands in the directory from which you will run tests, not in the test/json subdirectory of the source tree. 2. Build the baseline sqlite3.c file with sqlite3.h and shell.c. ("`CFLAGS='-Os -g' make -e clean sqlite3.c`") 3. Run "`sh json-speed-check.sh trunk`". This creates the baseline profile in "jout-trunk.txt". # 3.0 Testing 1. Build the sqlite3.c (with sqlite3.h and shell.c) to be tested. 2. Run "`sh json-speed-check.sh x1`". The profile output will appear in jout-x1.txt. Substitute any label you want in place of "x1". 3. Run the script shown below in the CLI. Divide 2500 by the real elapse time from this test to get an estimate for number of MB/s that the JSON parser is able to process. |
︙ | ︙ |
Changes to test/json/json-q1.txt.
1 2 3 4 | .mode qbox .timer on .param set $label 'q87' SELECT rowid, x->>$label FROM data1 WHERE x->>$label IS NOT NULL; | > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | .mode qbox .timer on .param set $label 'q87' SELECT rowid, x->>$label FROM data1 WHERE x->>$label IS NOT NULL; CREATE TEMP TABLE t2(x JSON TEXT); WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<25000), array1(y) AS ( SELECT json_group_array( json_object('x',x,'y',random(),'z',hex(randomblob(50))) ) FROM c ), c2(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c2 WHERE n<5) INSERT INTO t2(x) SELECT json_object('a',n,'b',n*2,'c',y,'d',3,'e',5,'f',6) FROM array1, c2; CREATE INDEX t2x1 ON t2(x->>'a'); CREATE INDEX t2x2 ON t2(x->>'b'); CREATE INDEX t2x3 ON t2(x->>'e'); CREATE INDEX t2x4 ON t2(x->>'f'); UPDATE t2 SET x=json_replace(x,'$.f',(x->>'f')+1); UPDATE t2 SET x=json_set(x,'$.e',(x->>'f')-1); UPDATE t2 SET x=json_remove(x,'$.d'); |