Merge enhancements from trunk.
FossilOrigin-Name: 66fe06832614010d3156d7b21a760af9957018cc
This commit is contained in:
commit
81966bea22
@ -108,13 +108,15 @@ proc get_tokenizer_docs {data} {
|
||||
append res "<dt><b>$line</b></dt><dd><p style=margin-top:0>\n"
|
||||
continue
|
||||
}
|
||||
if {[regexp {SYNONYM SUPPORT} $line]} {
|
||||
set line "</dl><h3>Synonym Support</h3>"
|
||||
}
|
||||
if {[string trim $line] == ""} {
|
||||
append res "<p>\n"
|
||||
} else {
|
||||
append res "$line\n"
|
||||
}
|
||||
}
|
||||
append res "</dl>\n"
|
||||
|
||||
set res
|
||||
}
|
||||
@ -208,6 +210,10 @@ proc main {data} {
|
||||
|
||||
fts5_tokenizer {
|
||||
output [get_fts5_struct $data "typedef struct Fts5Tokenizer" "^\};"]
|
||||
output [get_fts5_struct $data \
|
||||
"Flags that may be passed as the third argument to xTokenize()" \
|
||||
"#define FTS5_TOKEN_COLOCATED"
|
||||
]
|
||||
}
|
||||
|
||||
fts5_extension {
|
||||
|
164
ext/fts5/fts5.h
164
ext/fts5/fts5.h
@ -217,7 +217,7 @@ struct Fts5ExtensionApi {
|
||||
int (*xTokenize)(Fts5Context*,
|
||||
const char *pText, int nText, /* Text to tokenize */
|
||||
void *pCtx, /* Context passed to xToken() */
|
||||
int (*xToken)(void*, const char*, int, int, int) /* Callback */
|
||||
int (*xToken)(void*, int, const char*, int, int, int) /* Callback */
|
||||
);
|
||||
|
||||
int (*xPhraseCount)(Fts5Context*);
|
||||
@ -278,18 +278,46 @@ struct Fts5ExtensionApi {
|
||||
**
|
||||
** xTokenize:
|
||||
** This function is expected to tokenize the nText byte string indicated
|
||||
** by argument pText. pText may not be nul-terminated. The first argument
|
||||
** passed to this function is a pointer to an Fts5Tokenizer object returned
|
||||
** by an earlier call to xCreate().
|
||||
** by argument pText. pText may or may not be nul-terminated. The first
|
||||
** argument passed to this function is a pointer to an Fts5Tokenizer object
|
||||
** returned by an earlier call to xCreate().
|
||||
**
|
||||
** The second argument indicates the reason that FTS5 is requesting
|
||||
** tokenization of the supplied text. This is always one of the following
|
||||
** four values:
|
||||
**
|
||||
** <ul><li> <b>FTS5_TOKENIZE_DOCUMENT</b> - A document is being inserted into
|
||||
** or removed from the FTS table. The tokenizer is being invoked to
|
||||
** determine the set of tokens to add to (or delete from) the
|
||||
** FTS index.
|
||||
**
|
||||
** <li> <b>FTS5_TOKENIZE_QUERY</b> - A MATCH query is being executed
|
||||
** against the FTS index. The tokenizer is being called to tokenize
|
||||
** a bareword or quoted string specified as part of the query.
|
||||
**
|
||||
** <li> <b>(FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX)</b> - Same as
|
||||
** FTS5_TOKENIZE_QUERY, except that the bareword or quoted string is
|
||||
** followed by a "*" character, indicating that the last token
|
||||
** returned by the tokenizer will be treated as a token prefix.
|
||||
**
|
||||
** <li> <b>FTS5_TOKENIZE_AUX</b> - The tokenizer is being invoked to
|
||||
** satisfy an fts5_api.xTokenize() request made by an auxiliary
|
||||
** function. Or an fts5_api.xColumnSize() request made by the same
|
||||
** on a columnsize=0 database.
|
||||
** </ul>
|
||||
**
|
||||
** For each token in the input string, the supplied callback xToken() must
|
||||
** be invoked. The first argument to it should be a copy of the pointer
|
||||
** passed as the second argument to xTokenize(). The next two arguments
|
||||
** are a pointer to a buffer containing the token text, and the size of
|
||||
** the token in bytes. The 4th and 5th arguments are the byte offsets of
|
||||
** the first byte of and first byte immediately following the text from
|
||||
** passed as the second argument to xTokenize(). The third and fourth
|
||||
** arguments are a pointer to a buffer containing the token text, and the
|
||||
** size of the token in bytes. The 4th and 5th arguments are the byte offsets
|
||||
** of the first byte of and first byte immediately following the text from
|
||||
** which the token is derived within the input.
|
||||
**
|
||||
** The second argument passed to the xToken() callback ("tflags") should
|
||||
** normally be set to 0. The exception is if the tokenizer supports
|
||||
** synonyms. In this case see the discussion below for details.
|
||||
**
|
||||
** FTS5 assumes the xToken() callback is invoked for each token in the
|
||||
** order that they occur within the input text.
|
||||
**
|
||||
@ -301,6 +329,112 @@ struct Fts5ExtensionApi {
|
||||
** may abandon the tokenization and return any error code other than
|
||||
** SQLITE_OK or SQLITE_DONE.
|
||||
**
|
||||
** SYNONYM SUPPORT
|
||||
**
|
||||
** Custom tokenizers may also support synonyms. Consider a case in which a
|
||||
** user wishes to query for a phrase such as "first place". Using the
|
||||
** built-in tokenizers, the FTS5 query 'first + place' will match instances
|
||||
** of "first place" within the document set, but not alternative forms
|
||||
** such as "1st place". In some applications, it would be better to match
|
||||
** all instances of "first place" or "1st place" regardless of which form
|
||||
** the user specified in the MATCH query text.
|
||||
**
|
||||
** There are several ways to approach this in FTS5:
|
||||
**
|
||||
** <ol><li> By mapping all synonyms to a single token. In this case, the
|
||||
** In the above example, this means that the tokenizer returns the
|
||||
** same token for inputs "first" and "1st". Say that token is in
|
||||
** fact "first", so that when the user inserts the document "I won
|
||||
** 1st place" entries are added to the index for tokens "i", "won",
|
||||
** "first" and "place". If the user then queries for '1st + place',
|
||||
** the tokenizer substitutes "first" for "1st" and the query works
|
||||
** as expected.
|
||||
**
|
||||
** <li> By adding multiple synonyms for a single term to the FTS index.
|
||||
** In this case, when tokenizing query text, the tokenizer may
|
||||
** provide multiple synonyms for a single term within the document.
|
||||
** FTS5 then queries the index for each synonym individually. For
|
||||
** example, faced with the query:
|
||||
**
|
||||
** <codeblock>
|
||||
** ... MATCH 'first place'</codeblock>
|
||||
**
|
||||
** the tokenizer offers both "1st" and "first" as synonyms for the
|
||||
** first token in the MATCH query and FTS5 effectively runs a query
|
||||
** similar to:
|
||||
**
|
||||
** <codeblock>
|
||||
** ... MATCH '(first OR 1st) place'</codeblock>
|
||||
**
|
||||
** except that, for the purposes of auxiliary functions, the query
|
||||
** still appears to contain just two phrases - "(first OR 1st)"
|
||||
** being treated as a single phrase.
|
||||
**
|
||||
** <li> By adding multiple synonyms for a single term to the FTS index.
|
||||
** Using this method, when tokenizing document text, the tokenizer
|
||||
** provides multiple synonyms for each token. So that when a
|
||||
** document such as "I won first place" is tokenized, entries are
|
||||
** added to the FTS index for "i", "won", "first", "1st" and
|
||||
** "place".
|
||||
**
|
||||
** This way, even if the tokenizer does not provide synonyms
|
||||
** when tokenizing query text (it should not - to do would be
|
||||
** inefficient), it doesn't matter if the user queries for
|
||||
** 'first + place' or '1st + place', as there are entires in the
|
||||
** FTS index corresponding to both forms of the first token.
|
||||
** </ol>
|
||||
**
|
||||
** Whether is is parsing document or query text, any call to xToken that
|
||||
** specifies a <i>tflags</i> argument with the FTS5_TOKEN_COLOCATED bit
|
||||
** is considered to supply a synonym for the previous token. For example,
|
||||
** when parsing the document "I won first place", a tokenizer that supports
|
||||
** synonyms would call xToken() 5 times, as follows:
|
||||
**
|
||||
** <codeblock>
|
||||
** xToken(pCtx, 0, "i", 1, 0, 1);
|
||||
** xToken(pCtx, 0, "won", 3, 2, 5);
|
||||
** xToken(pCtx, 0, "first", 5, 6, 11);
|
||||
** xToken(pCtx, FTS5_TOKEN_COLOCATED, "1st", 3, 6, 11);
|
||||
** xToken(pCtx, 0, "place", 5, 12, 17);
|
||||
**</codeblock>
|
||||
**
|
||||
** It is an error to specify the FTS5_TOKEN_COLOCATED flag the first time
|
||||
** xToken() is called. Multiple synonyms may be specified for a single token
|
||||
** by making multiple calls to xToken(FTS5_TOKEN_COLOCATED) in sequence.
|
||||
** There is no limit to the number of synonyms that may be provided for a
|
||||
** single token.
|
||||
**
|
||||
** In many cases, method (1) above is the best approach. It does not add
|
||||
** extra data to the FTS index or require FTS5 to query for multiple terms,
|
||||
** so it is efficient in terms of disk space and query speed. However, it
|
||||
** does not support prefix queries very well. If, as suggested above, the
|
||||
** token "first" is subsituted for "1st" by the tokenizer, then the query:
|
||||
**
|
||||
** <codeblock>
|
||||
** ... MATCH '1s*'</codeblock>
|
||||
**
|
||||
** will not match documents that contain the token "1st" (as the tokenizer
|
||||
** will probably not map "1s" to any prefix of "first").
|
||||
**
|
||||
** For full prefix support, method (3) may be preferred. In this case,
|
||||
** because the index contains entries for both "first" and "1st", prefix
|
||||
** queries such as 'fi*' or '1s*' will match correctly. However, because
|
||||
** extra entries are added to the FTS index, this method uses more space
|
||||
** within the database.
|
||||
**
|
||||
** Method (2) offers a midpoint between (1) and (3). Using this method,
|
||||
** a query such as '1s*' will match documents that contain the literal
|
||||
** token "1st", but not "first" (assuming the tokenizer is not able to
|
||||
** provide synonyms for prefixes). However, a non-prefix query like '1st'
|
||||
** will match against "1st" and "first". This method does not require
|
||||
** extra disk space, as no extra entries are added to the FTS index.
|
||||
** On the other hand, it may require more CPU cycles to run MATCH queries,
|
||||
** as separate queries of the FTS index are required for each synonym.
|
||||
**
|
||||
** When using methods (2) or (3), it is important that the tokenizer only
|
||||
** provide synonyms when tokenizing document text (method (2)) or query
|
||||
** text (method (3)), not both. Doing so will not cause any errors, but is
|
||||
** inefficient.
|
||||
*/
|
||||
typedef struct Fts5Tokenizer Fts5Tokenizer;
|
||||
typedef struct fts5_tokenizer fts5_tokenizer;
|
||||
@ -309,9 +443,11 @@ struct fts5_tokenizer {
|
||||
void (*xDelete)(Fts5Tokenizer*);
|
||||
int (*xTokenize)(Fts5Tokenizer*,
|
||||
void *pCtx,
|
||||
int flags, /* Mask of FTS5_TOKENIZE_* flags */
|
||||
const char *pText, int nText,
|
||||
int (*xToken)(
|
||||
void *pCtx, /* Copy of 2nd argument to xTokenize() */
|
||||
int tflags, /* Mask of FTS5_TOKEN_* flags */
|
||||
const char *pToken, /* Pointer to buffer containing token */
|
||||
int nToken, /* Size of token in bytes */
|
||||
int iStart, /* Byte offset of token within input text */
|
||||
@ -320,6 +456,16 @@ struct fts5_tokenizer {
|
||||
);
|
||||
};
|
||||
|
||||
/* Flags that may be passed as the third argument to xTokenize() */
|
||||
#define FTS5_TOKENIZE_QUERY 0x0001
|
||||
#define FTS5_TOKENIZE_PREFIX 0x0002
|
||||
#define FTS5_TOKENIZE_DOCUMENT 0x0004
|
||||
#define FTS5_TOKENIZE_AUX 0x0008
|
||||
|
||||
/* Flags that may be passed by the tokenizer implementation back to FTS5
|
||||
** as the third argument to the supplied xToken callback. */
|
||||
#define FTS5_TOKEN_COLOCATED 0x0001 /* Same position as prev. token */
|
||||
|
||||
/*
|
||||
** END OF CUSTOM TOKENIZERS
|
||||
*************************************************************************/
|
||||
@ -329,7 +475,7 @@ struct fts5_tokenizer {
|
||||
*/
|
||||
typedef struct fts5_api fts5_api;
|
||||
struct fts5_api {
|
||||
int iVersion; /* Currently always set to 1 */
|
||||
int iVersion; /* Currently always set to 2 */
|
||||
|
||||
/* Create a new tokenizer */
|
||||
int (*xCreateTokenizer)(
|
||||
|
@ -117,6 +117,12 @@ typedef struct Fts5Config Fts5Config;
|
||||
** bColumnsize:
|
||||
** True if the %_docsize table is created.
|
||||
**
|
||||
** bPrefixIndex:
|
||||
** This is only used for debugging. If set to false, any prefix indexes
|
||||
** are ignored. This value is configured using:
|
||||
**
|
||||
** INSERT INTO tbl(tbl, rank) VALUES('prefix-index', $bPrefixIndex);
|
||||
**
|
||||
*/
|
||||
struct Fts5Config {
|
||||
sqlite3 *db; /* Database handle */
|
||||
@ -145,10 +151,14 @@ struct Fts5Config {
|
||||
|
||||
/* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */
|
||||
char **pzErrmsg;
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
int bPrefixIndex; /* True to use prefix-indexes */
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Current expected value of %_config table 'version' field */
|
||||
#define FTS5_CURRENT_VERSION 3
|
||||
#define FTS5_CURRENT_VERSION 4
|
||||
|
||||
#define FTS5_CONTENT_NORMAL 0
|
||||
#define FTS5_CONTENT_NONE 1
|
||||
@ -166,9 +176,10 @@ int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig);
|
||||
|
||||
int sqlite3Fts5Tokenize(
|
||||
Fts5Config *pConfig, /* FTS5 Configuration object */
|
||||
int flags, /* FTS5_TOKENIZE_* flags */
|
||||
const char *pText, int nText, /* Text to tokenize */
|
||||
void *pCtx, /* Context passed to xToken() */
|
||||
int (*xToken)(void*, const char*, int, int, int) /* Callback */
|
||||
int (*xToken)(void*, int, const char*, int, int, int) /* Callback */
|
||||
);
|
||||
|
||||
void sqlite3Fts5Dequote(char *z);
|
||||
@ -234,8 +245,10 @@ struct Fts5PoslistReader {
|
||||
int n; /* Size of buffer at a[] in bytes */
|
||||
int i; /* Current offset in a[] */
|
||||
|
||||
u8 bFlag; /* For client use (any custom purpose) */
|
||||
|
||||
/* Output variables */
|
||||
int bEof; /* Set to true at EOF */
|
||||
u8 bEof; /* Set to true at EOF */
|
||||
i64 iPos; /* (iCol<<32) + iPos */
|
||||
};
|
||||
int sqlite3Fts5PoslistReaderInit(
|
||||
@ -375,15 +388,9 @@ int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit);
|
||||
int sqlite3Fts5IndexRollback(Fts5Index *p);
|
||||
|
||||
/*
|
||||
** Retrieve and clear the current error code, respectively.
|
||||
** Get or set the "averages" values.
|
||||
*/
|
||||
int sqlite3Fts5IndexErrcode(Fts5Index*);
|
||||
void sqlite3Fts5IndexReset(Fts5Index*);
|
||||
|
||||
/*
|
||||
** Get or set the "averages" record.
|
||||
*/
|
||||
int sqlite3Fts5IndexGetAverages(Fts5Index *p, Fts5Buffer *pBuf);
|
||||
int sqlite3Fts5IndexGetAverages(Fts5Index *p, i64 *pnRow, i64 *anSize);
|
||||
int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8*, int);
|
||||
|
||||
/*
|
||||
@ -596,7 +603,7 @@ int sqlite3Fts5ExprPhraseCount(Fts5Expr*);
|
||||
int sqlite3Fts5ExprPhraseSize(Fts5Expr*, int iPhrase);
|
||||
int sqlite3Fts5ExprPoslist(Fts5Expr*, int, const u8 **);
|
||||
|
||||
int sqlite3Fts5ExprPhraseExpr(Fts5Config*, Fts5Expr*, int, Fts5Expr**);
|
||||
int sqlite3Fts5ExprClonePhrase(Fts5Config*, Fts5Expr*, int, Fts5Expr**);
|
||||
|
||||
/*******************************************
|
||||
** The fts5_expr.c API above this point is used by the other hand-written
|
||||
@ -665,17 +672,6 @@ int sqlite3Fts5TokenizerInit(fts5_api*);
|
||||
** End of interface to code in fts5_tokenizer.c.
|
||||
**************************************************************************/
|
||||
|
||||
/**************************************************************************
|
||||
** Interface to code in fts5_sorter.c.
|
||||
*/
|
||||
typedef struct Fts5Sorter Fts5Sorter;
|
||||
|
||||
int sqlite3Fts5SorterNew(Fts5Expr *pExpr, Fts5Sorter **pp);
|
||||
|
||||
/*
|
||||
** End of interface to code in fts5_sorter.c.
|
||||
**************************************************************************/
|
||||
|
||||
/**************************************************************************
|
||||
** Interface to code in fts5_vocab.c.
|
||||
*/
|
||||
|
@ -148,6 +148,7 @@ static void fts5HighlightAppend(
|
||||
*/
|
||||
static int fts5HighlightCb(
|
||||
void *pContext, /* Pointer to HighlightContext object */
|
||||
int tflags, /* Mask of FTS5_TOKEN_* flags */
|
||||
const char *pToken, /* Buffer containing token */
|
||||
int nToken, /* Size of token in bytes */
|
||||
int iStartOff, /* Start offset of token */
|
||||
@ -155,7 +156,10 @@ static int fts5HighlightCb(
|
||||
){
|
||||
HighlightContext *p = (HighlightContext*)pContext;
|
||||
int rc = SQLITE_OK;
|
||||
int iPos = p->iPos++;
|
||||
int iPos;
|
||||
|
||||
if( tflags & FTS5_TOKEN_COLOCATED ) return SQLITE_OK;
|
||||
iPos = p->iPos++;
|
||||
|
||||
if( p->iRangeEnd>0 ){
|
||||
if( iPos<p->iRangeStart || iPos>p->iRangeEnd ) return SQLITE_OK;
|
||||
|
@ -16,12 +16,14 @@
|
||||
#include "fts5Int.h"
|
||||
|
||||
int sqlite3Fts5BufferGrow(int *pRc, Fts5Buffer *pBuf, int nByte){
|
||||
/* A no-op if an error has already occurred */
|
||||
if( *pRc ) return 1;
|
||||
|
||||
if( (pBuf->n + nByte) > pBuf->nSpace ){
|
||||
u8 *pNew;
|
||||
int nNew = pBuf->nSpace ? pBuf->nSpace*2 : 64;
|
||||
|
||||
/* A no-op if an error has already occurred */
|
||||
if( *pRc ) return 1;
|
||||
|
||||
while( nNew<(pBuf->n + nByte) ){
|
||||
nNew = nNew * 2;
|
||||
}
|
||||
|
@ -480,6 +480,9 @@ int sqlite3Fts5ConfigParse(
|
||||
pRet->zDb = sqlite3Fts5Strndup(&rc, azArg[1], -1);
|
||||
pRet->zName = sqlite3Fts5Strndup(&rc, azArg[2], -1);
|
||||
pRet->bColumnsize = 1;
|
||||
#ifdef SQLITE_DEBUG
|
||||
pRet->bPrefixIndex = 1;
|
||||
#endif
|
||||
if( rc==SQLITE_OK && sqlite3_stricmp(pRet->zName, FTS5_RANK_NAME)==0 ){
|
||||
*pzErr = sqlite3_mprintf("reserved fts5 table name: %s", pRet->zName);
|
||||
rc = SQLITE_ERROR;
|
||||
@ -645,12 +648,15 @@ int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){
|
||||
*/
|
||||
int sqlite3Fts5Tokenize(
|
||||
Fts5Config *pConfig, /* FTS5 Configuration object */
|
||||
int flags, /* FTS5_TOKENIZE_* flags */
|
||||
const char *pText, int nText, /* Text to tokenize */
|
||||
void *pCtx, /* Context passed to xToken() */
|
||||
int (*xToken)(void*, const char*, int, int, int) /* Callback */
|
||||
int (*xToken)(void*, int, const char*, int, int, int) /* Callback */
|
||||
){
|
||||
if( pText==0 ) return SQLITE_OK;
|
||||
return pConfig->pTokApi->xTokenize(pConfig->pTok, pCtx, pText, nText, xToken);
|
||||
return pConfig->pTokApi->xTokenize(
|
||||
pConfig->pTok, pCtx, flags, pText, nText, xToken
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -22,6 +22,8 @@
|
||||
*/
|
||||
#define FTS5_EOF 0
|
||||
|
||||
#define FTS5_LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32))
|
||||
|
||||
typedef struct Fts5ExprTerm Fts5ExprTerm;
|
||||
|
||||
/*
|
||||
@ -73,6 +75,7 @@ struct Fts5ExprTerm {
|
||||
int bPrefix; /* True for a prefix term */
|
||||
char *zTerm; /* nul-terminated term */
|
||||
Fts5IndexIter *pIter; /* Iterator for this term */
|
||||
Fts5ExprTerm *pSynonym; /* Pointer to first in list of synonyms */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -181,6 +184,10 @@ static int fts5ExprGetToken(
|
||||
|
||||
default: {
|
||||
const char *z2;
|
||||
if( sqlite3Fts5IsBareword(z[0])==0 ){
|
||||
sqlite3Fts5ParseError(pParse, "fts5: syntax error near \"%.1s\"", z);
|
||||
return FTS5_EOF;
|
||||
}
|
||||
tok = FTS5_STRING;
|
||||
for(z2=&z[1]; sqlite3Fts5IsBareword(*z2); z2++);
|
||||
pToken->n = (z2 - z);
|
||||
@ -244,79 +251,6 @@ int sqlite3Fts5ExprNew(
|
||||
return sParse.rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Create a new FTS5 expression by cloning phrase iPhrase of the
|
||||
** expression passed as the second argument.
|
||||
*/
|
||||
int sqlite3Fts5ExprPhraseExpr(
|
||||
Fts5Config *pConfig,
|
||||
Fts5Expr *pExpr,
|
||||
int iPhrase,
|
||||
Fts5Expr **ppNew
|
||||
){
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
Fts5ExprPhrase *pOrig; /* The phrase extracted from pExpr */
|
||||
Fts5ExprPhrase *pCopy; /* Copy of pOrig */
|
||||
Fts5Expr *pNew = 0; /* Expression to return via *ppNew */
|
||||
|
||||
pOrig = pExpr->apExprPhrase[iPhrase];
|
||||
pCopy = (Fts5ExprPhrase*)sqlite3Fts5MallocZero(&rc,
|
||||
sizeof(Fts5ExprPhrase) + sizeof(Fts5ExprTerm) * pOrig->nTerm
|
||||
);
|
||||
if( pCopy ){
|
||||
int i; /* Used to iterate through phrase terms */
|
||||
Fts5ExprPhrase **apPhrase;
|
||||
Fts5ExprNode *pNode;
|
||||
Fts5ExprNearset *pNear;
|
||||
|
||||
pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr));
|
||||
apPhrase = (Fts5ExprPhrase**)sqlite3Fts5MallocZero(&rc,
|
||||
sizeof(Fts5ExprPhrase*)
|
||||
);
|
||||
pNode = (Fts5ExprNode*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprNode));
|
||||
pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc,
|
||||
sizeof(Fts5ExprNearset) + sizeof(Fts5ExprPhrase*)
|
||||
);
|
||||
|
||||
for(i=0; i<pOrig->nTerm; i++){
|
||||
pCopy->aTerm[i].zTerm = sqlite3Fts5Strndup(&rc, pOrig->aTerm[i].zTerm,-1);
|
||||
pCopy->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
/* All the allocations succeeded. Put the expression object together. */
|
||||
pNew->pIndex = pExpr->pIndex;
|
||||
pNew->pRoot = pNode;
|
||||
pNew->nPhrase = 1;
|
||||
pNew->apExprPhrase = apPhrase;
|
||||
pNew->apExprPhrase[0] = pCopy;
|
||||
|
||||
pNode->eType = (pOrig->nTerm==1 ? FTS5_TERM : FTS5_STRING);
|
||||
pNode->pNear = pNear;
|
||||
|
||||
pNear->nPhrase = 1;
|
||||
pNear->apPhrase[0] = pCopy;
|
||||
|
||||
pCopy->nTerm = pOrig->nTerm;
|
||||
pCopy->pNode = pNode;
|
||||
}else{
|
||||
/* At least one allocation failed. Free them all. */
|
||||
for(i=0; i<pOrig->nTerm; i++){
|
||||
sqlite3_free(pCopy->aTerm[i].zTerm);
|
||||
}
|
||||
sqlite3_free(pCopy);
|
||||
sqlite3_free(pNear);
|
||||
sqlite3_free(pNode);
|
||||
sqlite3_free(apPhrase);
|
||||
sqlite3_free(pNew);
|
||||
pNew = 0;
|
||||
}
|
||||
}
|
||||
|
||||
*ppNew = pNew;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Free the expression node object passed as the only argument.
|
||||
*/
|
||||
@ -350,6 +284,114 @@ static int fts5ExprColsetTest(Fts5ExprColset *pColset, int iCol){
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Argument pTerm must be a synonym iterator. Return the current rowid
|
||||
** that it points to.
|
||||
*/
|
||||
static i64 fts5ExprSynonymRowid(Fts5ExprTerm *pTerm, int bDesc, int *pbEof){
|
||||
i64 iRet = 0;
|
||||
int bRetValid = 0;
|
||||
Fts5ExprTerm *p;
|
||||
|
||||
assert( pTerm->pSynonym );
|
||||
assert( bDesc==0 || bDesc==1 );
|
||||
for(p=pTerm; p; p=p->pSynonym){
|
||||
if( 0==sqlite3Fts5IterEof(p->pIter) ){
|
||||
i64 iRowid = sqlite3Fts5IterRowid(p->pIter);
|
||||
if( bRetValid==0 || (bDesc!=(iRowid<iRet)) ){
|
||||
iRet = iRowid;
|
||||
bRetValid = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( pbEof && bRetValid==0 ) *pbEof = 1;
|
||||
return iRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** Argument pTerm must be a synonym iterator.
|
||||
*/
|
||||
static int fts5ExprSynonymPoslist(
|
||||
Fts5ExprTerm *pTerm,
|
||||
i64 iRowid,
|
||||
int *pbDel, /* OUT: Caller should sqlite3_free(*pa) */
|
||||
u8 **pa, int *pn
|
||||
){
|
||||
Fts5PoslistReader aStatic[4];
|
||||
Fts5PoslistReader *aIter = aStatic;
|
||||
int nIter = 0;
|
||||
int nAlloc = 4;
|
||||
int rc = SQLITE_OK;
|
||||
Fts5ExprTerm *p;
|
||||
|
||||
assert( pTerm->pSynonym );
|
||||
for(p=pTerm; p; p=p->pSynonym){
|
||||
Fts5IndexIter *pIter = p->pIter;
|
||||
if( sqlite3Fts5IterEof(pIter)==0 && sqlite3Fts5IterRowid(pIter)==iRowid ){
|
||||
const u8 *a;
|
||||
int n;
|
||||
i64 dummy;
|
||||
rc = sqlite3Fts5IterPoslist(pIter, &a, &n, &dummy);
|
||||
if( rc!=SQLITE_OK ) goto synonym_poslist_out;
|
||||
if( nIter==nAlloc ){
|
||||
int nByte = sizeof(Fts5PoslistReader) * nAlloc * 2;
|
||||
Fts5PoslistReader *aNew = (Fts5PoslistReader*)sqlite3_malloc(nByte);
|
||||
if( aNew==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
goto synonym_poslist_out;
|
||||
}
|
||||
memcpy(aNew, aIter, sizeof(Fts5PoslistReader) * nIter);
|
||||
nAlloc = nAlloc*2;
|
||||
if( aIter!=aStatic ) sqlite3_free(aIter);
|
||||
aIter = aNew;
|
||||
}
|
||||
sqlite3Fts5PoslistReaderInit(-1, a, n, &aIter[nIter]);
|
||||
assert( aIter[nIter].bEof==0 );
|
||||
nIter++;
|
||||
}
|
||||
}
|
||||
|
||||
assert( *pbDel==0 );
|
||||
if( nIter==1 ){
|
||||
*pa = (u8*)aIter[0].a;
|
||||
*pn = aIter[0].n;
|
||||
}else{
|
||||
Fts5PoslistWriter writer = {0};
|
||||
Fts5Buffer buf = {0,0,0};
|
||||
i64 iPrev = -1;
|
||||
while( 1 ){
|
||||
int i;
|
||||
i64 iMin = FTS5_LARGEST_INT64;
|
||||
for(i=0; i<nIter; i++){
|
||||
if( aIter[i].bEof==0 ){
|
||||
if( aIter[i].iPos==iPrev ){
|
||||
if( sqlite3Fts5PoslistReaderNext(&aIter[i]) ) continue;
|
||||
}
|
||||
if( aIter[i].iPos<iMin ){
|
||||
iMin = aIter[i].iPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( iMin==FTS5_LARGEST_INT64 || rc!=SQLITE_OK ) break;
|
||||
rc = sqlite3Fts5PoslistWriterAppend(&buf, &writer, iMin);
|
||||
iPrev = iMin;
|
||||
}
|
||||
if( rc ){
|
||||
sqlite3_free(buf.p);
|
||||
}else{
|
||||
*pa = buf.p;
|
||||
*pn = buf.n;
|
||||
*pbDel = 1;
|
||||
}
|
||||
}
|
||||
|
||||
synonym_poslist_out:
|
||||
if( aIter!=aStatic ) sqlite3_free(aIter);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** All individual term iterators in pPhrase are guaranteed to be valid and
|
||||
** pointing to the same rowid when this function is called. This function
|
||||
@ -362,7 +404,7 @@ static int fts5ExprColsetTest(Fts5ExprColset *pColset, int iCol){
|
||||
** not a match.
|
||||
*/
|
||||
static int fts5ExprPhraseIsMatch(
|
||||
Fts5Expr *pExpr, /* Expression pPhrase belongs to */
|
||||
Fts5ExprNode *pNode, /* Node pPhrase belongs to */
|
||||
Fts5ExprColset *pColset, /* Restrict matches to these columns */
|
||||
Fts5ExprPhrase *pPhrase, /* Phrase object to initialize */
|
||||
int *pbMatch /* OUT: Set to true if really a match */
|
||||
@ -388,16 +430,24 @@ static int fts5ExprPhraseIsMatch(
|
||||
aIter = (Fts5PoslistReader*)sqlite3_malloc(nByte);
|
||||
if( !aIter ) return SQLITE_NOMEM;
|
||||
}
|
||||
memset(aIter, 0, sizeof(Fts5PoslistReader) * pPhrase->nTerm);
|
||||
|
||||
/* Initialize a term iterator for each term in the phrase */
|
||||
for(i=0; i<pPhrase->nTerm; i++){
|
||||
Fts5ExprTerm *pTerm = &pPhrase->aTerm[i];
|
||||
i64 dummy;
|
||||
int n;
|
||||
const u8 *a;
|
||||
rc = sqlite3Fts5IterPoslist(pPhrase->aTerm[i].pIter, &a, &n, &dummy);
|
||||
if( rc || sqlite3Fts5PoslistReaderInit(iCol, a, n, &aIter[i]) ){
|
||||
goto ismatch_out;
|
||||
int n = 0;
|
||||
int bFlag = 0;
|
||||
const u8 *a = 0;
|
||||
if( pTerm->pSynonym ){
|
||||
rc = fts5ExprSynonymPoslist(pTerm, pNode->iRowid, &bFlag, (u8**)&a, &n);
|
||||
}else{
|
||||
rc = sqlite3Fts5IterPoslist(pTerm->pIter, &a, &n, &dummy);
|
||||
}
|
||||
if( rc!=SQLITE_OK ) goto ismatch_out;
|
||||
sqlite3Fts5PoslistReaderInit(iCol, a, n, &aIter[i]);
|
||||
aIter[i].bFlag = bFlag;
|
||||
if( aIter[i].bEof ) goto ismatch_out;
|
||||
}
|
||||
|
||||
while( 1 ){
|
||||
@ -431,6 +481,9 @@ static int fts5ExprPhraseIsMatch(
|
||||
|
||||
ismatch_out:
|
||||
*pbMatch = (pPhrase->poslist.n>0);
|
||||
for(i=0; i<pPhrase->nTerm; i++){
|
||||
if( aIter[i].bFlag ) sqlite3_free((u8*)aIter[i].a);
|
||||
}
|
||||
if( aIter!=aStatic ) sqlite3_free(aIter);
|
||||
return rc;
|
||||
}
|
||||
@ -598,17 +651,55 @@ static int fts5ExprNearAdvanceFirst(
|
||||
int bFromValid,
|
||||
i64 iFrom
|
||||
){
|
||||
Fts5IndexIter *pIter = pNode->pNear->apPhrase[0]->aTerm[0].pIter;
|
||||
int rc;
|
||||
Fts5ExprTerm *pTerm = &pNode->pNear->apPhrase[0]->aTerm[0];
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
assert( Fts5NodeIsString(pNode) );
|
||||
if( bFromValid ){
|
||||
rc = sqlite3Fts5IterNextFrom(pIter, iFrom);
|
||||
if( pTerm->pSynonym ){
|
||||
int bEof = 1;
|
||||
Fts5ExprTerm *p;
|
||||
|
||||
/* Find the firstest rowid any synonym points to. */
|
||||
i64 iRowid = fts5ExprSynonymRowid(pTerm, pExpr->bDesc, 0);
|
||||
|
||||
/* Advance each iterator that currently points to iRowid. Or, if iFrom
|
||||
** is valid - each iterator that points to a rowid before iFrom. */
|
||||
for(p=pTerm; p; p=p->pSynonym){
|
||||
if( sqlite3Fts5IterEof(p->pIter)==0 ){
|
||||
i64 ii = sqlite3Fts5IterRowid(p->pIter);
|
||||
if( ii==iRowid
|
||||
|| (bFromValid && ii!=iFrom && (ii>iFrom)==pExpr->bDesc)
|
||||
){
|
||||
if( bFromValid ){
|
||||
rc = sqlite3Fts5IterNextFrom(p->pIter, iFrom);
|
||||
}else{
|
||||
rc = sqlite3Fts5IterNext(p->pIter);
|
||||
}
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
if( sqlite3Fts5IterEof(p->pIter)==0 ){
|
||||
bEof = 0;
|
||||
}
|
||||
}else{
|
||||
bEof = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the EOF flag if either all synonym iterators are at EOF or an
|
||||
** error has occurred. */
|
||||
pNode->bEof = (rc || bEof);
|
||||
}else{
|
||||
rc = sqlite3Fts5IterNext(pIter);
|
||||
Fts5IndexIter *pIter = pTerm->pIter;
|
||||
|
||||
assert( Fts5NodeIsString(pNode) );
|
||||
if( bFromValid ){
|
||||
rc = sqlite3Fts5IterNextFrom(pIter, iFrom);
|
||||
}else{
|
||||
rc = sqlite3Fts5IterNext(pIter);
|
||||
}
|
||||
|
||||
pNode->bEof = (rc || sqlite3Fts5IterEof(pIter));
|
||||
}
|
||||
|
||||
pNode->bEof = (rc || sqlite3Fts5IterEof(pIter));
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -647,6 +738,35 @@ static int fts5ExprAdvanceto(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fts5ExprSynonymAdvanceto(
|
||||
Fts5ExprTerm *pTerm, /* Term iterator to advance */
|
||||
int bDesc, /* True if iterator is "rowid DESC" */
|
||||
i64 *piLast, /* IN/OUT: Lastest rowid seen so far */
|
||||
int *pRc /* OUT: Error code */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
i64 iLast = *piLast;
|
||||
Fts5ExprTerm *p;
|
||||
int bEof = 0;
|
||||
|
||||
for(p=pTerm; rc==SQLITE_OK && p; p=p->pSynonym){
|
||||
if( sqlite3Fts5IterEof(p->pIter)==0 ){
|
||||
i64 iRowid = sqlite3Fts5IterRowid(p->pIter);
|
||||
if( (bDesc==0 && iLast>iRowid) || (bDesc && iLast<iRowid) ){
|
||||
rc = sqlite3Fts5IterNextFrom(p->pIter, iLast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
*pRc = rc;
|
||||
bEof = 1;
|
||||
}else{
|
||||
*piLast = fts5ExprSynonymRowid(pTerm, bDesc, &bEof);
|
||||
}
|
||||
return bEof;
|
||||
}
|
||||
|
||||
/*
|
||||
** IN/OUT parameter (*pa) points to a position list n bytes in size. If
|
||||
** the position list contains entries for column iCol, then (*pa) is set
|
||||
@ -717,9 +837,9 @@ static int fts5ExprNearTest(
|
||||
** phrase is not a match, break out of the loop early. */
|
||||
for(i=0; rc==SQLITE_OK && i<pNear->nPhrase; i++){
|
||||
Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
|
||||
if( pPhrase->nTerm>1 || pNear->pColset ){
|
||||
if( pPhrase->nTerm>1 || pPhrase->aTerm[0].pSynonym || pNear->pColset ){
|
||||
int bMatch = 0;
|
||||
rc = fts5ExprPhraseIsMatch(pExpr, pNear->pColset, pPhrase, &bMatch);
|
||||
rc = fts5ExprPhraseIsMatch(pNode, pNear->pColset, pPhrase, &bMatch);
|
||||
if( bMatch==0 ) break;
|
||||
}else{
|
||||
rc = sqlite3Fts5IterPoslistBuffer(
|
||||
@ -755,6 +875,7 @@ static int fts5ExprTokenTest(
|
||||
|
||||
assert( pNode->eType==FTS5_TERM );
|
||||
assert( pNear->nPhrase==1 && pPhrase->nTerm==1 );
|
||||
assert( pPhrase->aTerm[0].pSynonym==0 );
|
||||
|
||||
rc = sqlite3Fts5IterPoslist(pIter, &pPos, &nPos, &pNode->iRowid);
|
||||
|
||||
@ -801,69 +922,97 @@ static int fts5ExprNearNextMatch(
|
||||
i64 iLast; /* Lastest rowid any iterator points to */
|
||||
int i, j; /* Phrase and token index, respectively */
|
||||
int bMatch; /* True if all terms are at the same rowid */
|
||||
const int bDesc = pExpr->bDesc;
|
||||
|
||||
assert( pNear->nPhrase>1 || pNear->apPhrase[0]->nTerm>1 );
|
||||
/* Check that this node should not be FTS5_TERM */
|
||||
assert( pNear->nPhrase>1
|
||||
|| pNear->apPhrase[0]->nTerm>1
|
||||
|| pNear->apPhrase[0]->aTerm[0].pSynonym
|
||||
);
|
||||
|
||||
/* Initialize iLast, the "lastest" rowid any iterator points to. If the
|
||||
** iterator skips through rowids in the default ascending order, this means
|
||||
** the maximum rowid. Or, if the iterator is "ORDER BY rowid DESC", then it
|
||||
** means the minimum rowid. */
|
||||
iLast = sqlite3Fts5IterRowid(pLeft->aTerm[0].pIter);
|
||||
if( pLeft->aTerm[0].pSynonym ){
|
||||
iLast = fts5ExprSynonymRowid(&pLeft->aTerm[0], bDesc, 0);
|
||||
}else{
|
||||
iLast = sqlite3Fts5IterRowid(pLeft->aTerm[0].pIter);
|
||||
}
|
||||
|
||||
do {
|
||||
bMatch = 1;
|
||||
for(i=0; i<pNear->nPhrase; i++){
|
||||
Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
|
||||
for(j=0; j<pPhrase->nTerm; j++){
|
||||
Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter;
|
||||
i64 iRowid = sqlite3Fts5IterRowid(pIter);
|
||||
if( iRowid!=iLast ) bMatch = 0;
|
||||
if( fts5ExprAdvanceto(pIter, pExpr->bDesc, &iLast,&rc,&pNode->bEof) ){
|
||||
return rc;
|
||||
Fts5ExprTerm *pTerm = &pPhrase->aTerm[j];
|
||||
if( pTerm->pSynonym ){
|
||||
i64 iRowid = fts5ExprSynonymRowid(pTerm, bDesc, 0);
|
||||
if( iRowid==iLast ) continue;
|
||||
bMatch = 0;
|
||||
if( fts5ExprSynonymAdvanceto(pTerm, bDesc, &iLast, &rc) ){
|
||||
pNode->bEof = 1;
|
||||
return rc;
|
||||
}
|
||||
}else{
|
||||
Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter;
|
||||
i64 iRowid = sqlite3Fts5IterRowid(pIter);
|
||||
if( iRowid==iLast ) continue;
|
||||
bMatch = 0;
|
||||
if( fts5ExprAdvanceto(pIter, bDesc, &iLast, &rc, &pNode->bEof) ){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}while( bMatch==0 );
|
||||
|
||||
pNode->bNomatch = (0==fts5ExprNearTest(&rc, pExpr, pNode));
|
||||
pNode->iRowid = iLast;
|
||||
pNode->bNomatch = (0==fts5ExprNearTest(&rc, pExpr, pNode));
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Initialize all term iterators in the pNear object. If any term is found
|
||||
** to match no documents at all, set *pbEof to true and return immediately,
|
||||
** without initializing any further iterators.
|
||||
** to match no documents at all, return immediately without initializing any
|
||||
** further iterators.
|
||||
*/
|
||||
static int fts5ExprNearInitAll(
|
||||
Fts5Expr *pExpr,
|
||||
Fts5ExprNode *pNode
|
||||
){
|
||||
Fts5ExprNearset *pNear = pNode->pNear;
|
||||
Fts5ExprTerm *pTerm;
|
||||
Fts5ExprPhrase *pPhrase;
|
||||
int i, j;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
for(i=0; rc==SQLITE_OK && i<pNear->nPhrase; i++){
|
||||
pPhrase = pNear->apPhrase[i];
|
||||
Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
|
||||
for(j=0; j<pPhrase->nTerm; j++){
|
||||
pTerm = &pPhrase->aTerm[j];
|
||||
if( pTerm->pIter ){
|
||||
sqlite3Fts5IterClose(pTerm->pIter);
|
||||
pTerm->pIter = 0;
|
||||
Fts5ExprTerm *pTerm = &pPhrase->aTerm[j];
|
||||
Fts5ExprTerm *p;
|
||||
int bEof = 1;
|
||||
|
||||
for(p=pTerm; p && rc==SQLITE_OK; p=p->pSynonym){
|
||||
if( p->pIter ){
|
||||
sqlite3Fts5IterClose(p->pIter);
|
||||
p->pIter = 0;
|
||||
}
|
||||
rc = sqlite3Fts5IndexQuery(
|
||||
pExpr->pIndex, p->zTerm, strlen(p->zTerm),
|
||||
(pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) |
|
||||
(pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0),
|
||||
&p->pIter
|
||||
);
|
||||
assert( rc==SQLITE_OK || p->pIter==0 );
|
||||
if( p->pIter && 0==sqlite3Fts5IterEof(p->pIter) ){
|
||||
bEof = 0;
|
||||
}
|
||||
}
|
||||
rc = sqlite3Fts5IndexQuery(
|
||||
pExpr->pIndex, pTerm->zTerm, strlen(pTerm->zTerm),
|
||||
(pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) |
|
||||
(pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0),
|
||||
&pTerm->pIter
|
||||
);
|
||||
assert( rc==SQLITE_OK || pTerm->pIter==0 );
|
||||
if( pTerm->pIter==0 || sqlite3Fts5IterEof(pTerm->pIter) ){
|
||||
|
||||
if( bEof ){
|
||||
pNode->bEof = 1;
|
||||
break;
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1029,10 +1178,17 @@ static int fts5ExprNodeNext(
|
||||
};
|
||||
|
||||
case FTS5_TERM: {
|
||||
rc = fts5ExprNearAdvanceFirst(pExpr, pNode, bFromValid, iFrom);
|
||||
if( pNode->bEof==0 ){
|
||||
Fts5IndexIter *pIter = pNode->pNear->apPhrase[0]->aTerm[0].pIter;
|
||||
if( bFromValid ){
|
||||
rc = sqlite3Fts5IterNextFrom(pIter, iFrom);
|
||||
}else{
|
||||
rc = sqlite3Fts5IterNext(pIter);
|
||||
}
|
||||
if( rc==SQLITE_OK && sqlite3Fts5IterEof(pIter)==0 ){
|
||||
assert( rc==SQLITE_OK );
|
||||
rc = fts5ExprTokenTest(pExpr, pNode);
|
||||
}else{
|
||||
pNode->bEof = 1;
|
||||
}
|
||||
return rc;
|
||||
};
|
||||
@ -1266,10 +1422,16 @@ static void fts5ExprPhraseFree(Fts5ExprPhrase *pPhrase){
|
||||
if( pPhrase ){
|
||||
int i;
|
||||
for(i=0; i<pPhrase->nTerm; i++){
|
||||
Fts5ExprTerm *pSyn;
|
||||
Fts5ExprTerm *pNext;
|
||||
Fts5ExprTerm *pTerm = &pPhrase->aTerm[i];
|
||||
sqlite3_free(pTerm->zTerm);
|
||||
if( pTerm->pIter ){
|
||||
sqlite3Fts5IterClose(pTerm->pIter);
|
||||
sqlite3Fts5IterClose(pTerm->pIter);
|
||||
|
||||
for(pSyn=pTerm->pSynonym; pSyn; pSyn=pNext){
|
||||
pNext = pSyn->pSynonym;
|
||||
sqlite3Fts5IterClose(pSyn->pIter);
|
||||
sqlite3_free(pSyn);
|
||||
}
|
||||
}
|
||||
if( pPhrase->poslist.nSpace>0 ) fts5BufferFree(&pPhrase->poslist);
|
||||
@ -1331,6 +1493,7 @@ Fts5ExprNearset *sqlite3Fts5ParseNearset(
|
||||
typedef struct TokenCtx TokenCtx;
|
||||
struct TokenCtx {
|
||||
Fts5ExprPhrase *pPhrase;
|
||||
int rc;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -1338,34 +1501,60 @@ struct TokenCtx {
|
||||
*/
|
||||
static int fts5ParseTokenize(
|
||||
void *pContext, /* Pointer to Fts5InsertCtx object */
|
||||
int tflags, /* Mask of FTS5_TOKEN_* flags */
|
||||
const char *pToken, /* Buffer containing token */
|
||||
int nToken, /* Size of token in bytes */
|
||||
int iStart, /* Start offset of token */
|
||||
int iEnd /* End offset of token */
|
||||
int iUnused1, /* Start offset of token */
|
||||
int iUnused2 /* End offset of token */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
const int SZALLOC = 8;
|
||||
TokenCtx *pCtx = (TokenCtx*)pContext;
|
||||
Fts5ExprPhrase *pPhrase = pCtx->pPhrase;
|
||||
Fts5ExprTerm *pTerm;
|
||||
|
||||
if( pPhrase==0 || (pPhrase->nTerm % SZALLOC)==0 ){
|
||||
Fts5ExprPhrase *pNew;
|
||||
int nNew = SZALLOC + (pPhrase ? pPhrase->nTerm : 0);
|
||||
/* If an error has already occurred, this is a no-op */
|
||||
if( pCtx->rc!=SQLITE_OK ) return pCtx->rc;
|
||||
|
||||
pNew = (Fts5ExprPhrase*)sqlite3_realloc(pPhrase,
|
||||
sizeof(Fts5ExprPhrase) + sizeof(Fts5ExprTerm) * nNew
|
||||
);
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
if( pPhrase==0 ) memset(pNew, 0, sizeof(Fts5ExprPhrase));
|
||||
pCtx->pPhrase = pPhrase = pNew;
|
||||
pNew->nTerm = nNew - SZALLOC;
|
||||
assert( pPhrase==0 || pPhrase->nTerm>0 );
|
||||
if( pPhrase && (tflags & FTS5_TOKEN_COLOCATED) ){
|
||||
Fts5ExprTerm *pSyn;
|
||||
int nByte = sizeof(Fts5ExprTerm) + nToken+1;
|
||||
pSyn = (Fts5ExprTerm*)sqlite3_malloc(nByte);
|
||||
if( pSyn==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
memset(pSyn, 0, nByte);
|
||||
pSyn->zTerm = (char*)&pSyn[1];
|
||||
memcpy(pSyn->zTerm, pToken, nToken);
|
||||
pSyn->pSynonym = pPhrase->aTerm[pPhrase->nTerm-1].pSynonym;
|
||||
pPhrase->aTerm[pPhrase->nTerm-1].pSynonym = pSyn;
|
||||
}
|
||||
}else{
|
||||
Fts5ExprTerm *pTerm;
|
||||
if( pPhrase==0 || (pPhrase->nTerm % SZALLOC)==0 ){
|
||||
Fts5ExprPhrase *pNew;
|
||||
int nNew = SZALLOC + (pPhrase ? pPhrase->nTerm : 0);
|
||||
|
||||
pNew = (Fts5ExprPhrase*)sqlite3_realloc(pPhrase,
|
||||
sizeof(Fts5ExprPhrase) + sizeof(Fts5ExprTerm) * nNew
|
||||
);
|
||||
if( pNew==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
if( pPhrase==0 ) memset(pNew, 0, sizeof(Fts5ExprPhrase));
|
||||
pCtx->pPhrase = pPhrase = pNew;
|
||||
pNew->nTerm = nNew - SZALLOC;
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
pTerm = &pPhrase->aTerm[pPhrase->nTerm++];
|
||||
memset(pTerm, 0, sizeof(Fts5ExprTerm));
|
||||
pTerm->zTerm = sqlite3Fts5Strndup(&rc, pToken, nToken);
|
||||
}
|
||||
}
|
||||
|
||||
pTerm = &pPhrase->aTerm[pPhrase->nTerm++];
|
||||
memset(pTerm, 0, sizeof(Fts5ExprTerm));
|
||||
pTerm->zTerm = sqlite3Fts5Strndup(&rc, pToken, nToken);
|
||||
|
||||
pCtx->rc = rc;
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -1417,11 +1606,14 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm(
|
||||
|
||||
rc = fts5ParseStringFromToken(pToken, &z);
|
||||
if( rc==SQLITE_OK ){
|
||||
int flags = FTS5_TOKENIZE_QUERY | (bPrefix ? FTS5_TOKENIZE_QUERY : 0);
|
||||
int n;
|
||||
sqlite3Fts5Dequote(z);
|
||||
rc = sqlite3Fts5Tokenize(pConfig, z, strlen(z), &sCtx, fts5ParseTokenize);
|
||||
n = strlen(z);
|
||||
rc = sqlite3Fts5Tokenize(pConfig, flags, z, n, &sCtx, fts5ParseTokenize);
|
||||
}
|
||||
sqlite3_free(z);
|
||||
if( rc ){
|
||||
if( rc || (rc = sCtx.rc) ){
|
||||
pParse->rc = rc;
|
||||
fts5ExprPhraseFree(sCtx.pPhrase);
|
||||
sCtx.pPhrase = 0;
|
||||
@ -1450,6 +1642,79 @@ Fts5ExprPhrase *sqlite3Fts5ParseTerm(
|
||||
return sCtx.pPhrase;
|
||||
}
|
||||
|
||||
/*
|
||||
** Create a new FTS5 expression by cloning phrase iPhrase of the
|
||||
** expression passed as the second argument.
|
||||
*/
|
||||
int sqlite3Fts5ExprClonePhrase(
|
||||
Fts5Config *pConfig,
|
||||
Fts5Expr *pExpr,
|
||||
int iPhrase,
|
||||
Fts5Expr **ppNew
|
||||
){
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
Fts5ExprPhrase *pOrig; /* The phrase extracted from pExpr */
|
||||
int i; /* Used to iterate through phrase terms */
|
||||
|
||||
Fts5Expr *pNew = 0; /* Expression to return via *ppNew */
|
||||
|
||||
TokenCtx sCtx = {0,0}; /* Context object for fts5ParseTokenize */
|
||||
|
||||
|
||||
pOrig = pExpr->apExprPhrase[iPhrase];
|
||||
|
||||
pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr));
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew->apExprPhrase = (Fts5ExprPhrase**)sqlite3Fts5MallocZero(&rc,
|
||||
sizeof(Fts5ExprPhrase*));
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew->pRoot = (Fts5ExprNode*)sqlite3Fts5MallocZero(&rc,
|
||||
sizeof(Fts5ExprNode));
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew->pRoot->pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc,
|
||||
sizeof(Fts5ExprNearset) + sizeof(Fts5ExprPhrase*));
|
||||
}
|
||||
|
||||
for(i=0; rc==SQLITE_OK && i<pOrig->nTerm; i++){
|
||||
int tflags = 0;
|
||||
Fts5ExprTerm *p;
|
||||
for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){
|
||||
const char *zTerm = p->zTerm;
|
||||
rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, strlen(zTerm), 0, 0);
|
||||
tflags = FTS5_TOKEN_COLOCATED;
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
/* All the allocations succeeded. Put the expression object together. */
|
||||
pNew->pIndex = pExpr->pIndex;
|
||||
pNew->nPhrase = 1;
|
||||
pNew->apExprPhrase[0] = sCtx.pPhrase;
|
||||
pNew->pRoot->pNear->apPhrase[0] = sCtx.pPhrase;
|
||||
pNew->pRoot->pNear->nPhrase = 1;
|
||||
sCtx.pPhrase->pNode = pNew->pRoot;
|
||||
|
||||
if( pOrig->nTerm==1 && pOrig->aTerm[0].pSynonym==0 ){
|
||||
pNew->pRoot->eType = FTS5_TERM;
|
||||
}else{
|
||||
pNew->pRoot->eType = FTS5_STRING;
|
||||
}
|
||||
}else{
|
||||
sqlite3Fts5ExprFree(pNew);
|
||||
fts5ExprPhraseFree(sCtx.pPhrase);
|
||||
pNew = 0;
|
||||
}
|
||||
|
||||
*ppNew = pNew;
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Token pTok has appeared in a MATCH expression where the NEAR operator
|
||||
** is expected. If token pTok does not contain "NEAR", store an error
|
||||
@ -1630,7 +1895,10 @@ Fts5ExprNode *sqlite3Fts5ParseNode(
|
||||
for(iPhrase=0; iPhrase<pNear->nPhrase; iPhrase++){
|
||||
pNear->apPhrase[iPhrase]->pNode = pRet;
|
||||
}
|
||||
if( pNear->nPhrase==1 && pNear->apPhrase[0]->nTerm==1 ){
|
||||
if( pNear->nPhrase==1
|
||||
&& pNear->apPhrase[0]->nTerm==1
|
||||
&& pNear->apPhrase[0]->aTerm[0].pSynonym==0
|
||||
){
|
||||
pRet->eType = FTS5_TERM;
|
||||
}
|
||||
}else{
|
||||
@ -1650,16 +1918,28 @@ Fts5ExprNode *sqlite3Fts5ParseNode(
|
||||
}
|
||||
|
||||
static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
|
||||
char *zQuoted = sqlite3_malloc(strlen(pTerm->zTerm) * 2 + 3 + 2);
|
||||
int nByte = 0;
|
||||
Fts5ExprTerm *p;
|
||||
char *zQuoted;
|
||||
|
||||
/* Determine the maximum amount of space required. */
|
||||
for(p=pTerm; p; p=p->pSynonym){
|
||||
nByte += strlen(pTerm->zTerm) * 2 + 3 + 2;
|
||||
}
|
||||
zQuoted = sqlite3_malloc(nByte);
|
||||
|
||||
if( zQuoted ){
|
||||
int i = 0;
|
||||
char *zIn = pTerm->zTerm;
|
||||
zQuoted[i++] = '"';
|
||||
while( *zIn ){
|
||||
if( *zIn=='"' ) zQuoted[i++] = '"';
|
||||
zQuoted[i++] = *zIn++;
|
||||
for(p=pTerm; p; p=p->pSynonym){
|
||||
char *zIn = p->zTerm;
|
||||
zQuoted[i++] = '"';
|
||||
while( *zIn ){
|
||||
if( *zIn=='"' ) zQuoted[i++] = '"';
|
||||
zQuoted[i++] = *zIn++;
|
||||
}
|
||||
zQuoted[i++] = '"';
|
||||
if( p->pSynonym ) zQuoted[i++] = '|';
|
||||
}
|
||||
zQuoted[i++] = '"';
|
||||
if( pTerm->bPrefix ){
|
||||
zQuoted[i++] = ' ';
|
||||
zQuoted[i++] = '*';
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -25,11 +25,11 @@
|
||||
int sqlite3_fts5_may_be_corrupt = 1;
|
||||
|
||||
|
||||
typedef struct Fts5Table Fts5Table;
|
||||
typedef struct Fts5Cursor Fts5Cursor;
|
||||
typedef struct Fts5Auxiliary Fts5Auxiliary;
|
||||
typedef struct Fts5Auxdata Fts5Auxdata;
|
||||
|
||||
typedef struct Fts5Auxiliary Fts5Auxiliary;
|
||||
typedef struct Fts5Cursor Fts5Cursor;
|
||||
typedef struct Fts5Sorter Fts5Sorter;
|
||||
typedef struct Fts5Table Fts5Table;
|
||||
typedef struct Fts5TokenizerModule Fts5TokenizerModule;
|
||||
|
||||
/*
|
||||
@ -1317,6 +1317,10 @@ static int fts5SpecialInsert(
|
||||
rc = sqlite3Fts5StorageMerge(pTab->pStorage, nMerge);
|
||||
}else if( 0==sqlite3_stricmp("integrity-check", z) ){
|
||||
rc = sqlite3Fts5StorageIntegrity(pTab->pStorage);
|
||||
#ifdef SQLITE_DEBUG
|
||||
}else if( 0==sqlite3_stricmp("prefix-index", z) ){
|
||||
pConfig->bPrefixIndex = sqlite3_value_int(pVal);
|
||||
#endif
|
||||
}else{
|
||||
rc = sqlite3Fts5IndexLoadConfig(pTab->pIndex);
|
||||
if( rc==SQLITE_OK ){
|
||||
@ -1498,11 +1502,13 @@ static int fts5ApiTokenize(
|
||||
Fts5Context *pCtx,
|
||||
const char *pText, int nText,
|
||||
void *pUserData,
|
||||
int (*xToken)(void*, const char*, int, int, int)
|
||||
int (*xToken)(void*, int, const char*, int, int, int)
|
||||
){
|
||||
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
|
||||
Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
|
||||
return sqlite3Fts5Tokenize(pTab->pConfig, pText, nText, pUserData, xToken);
|
||||
return sqlite3Fts5Tokenize(
|
||||
pTab->pConfig, FTS5_TOKENIZE_AUX, pText, nText, pUserData, xToken
|
||||
);
|
||||
}
|
||||
|
||||
static int fts5ApiPhraseCount(Fts5Context *pCtx){
|
||||
@ -1655,13 +1661,16 @@ static int fts5ApiColumnText(
|
||||
|
||||
static int fts5ColumnSizeCb(
|
||||
void *pContext, /* Pointer to int */
|
||||
int tflags,
|
||||
const char *pToken, /* Buffer containing token */
|
||||
int nToken, /* Size of token in bytes */
|
||||
int iStart, /* Start offset of token */
|
||||
int iEnd /* End offset of token */
|
||||
){
|
||||
int *pCnt = (int*)pContext;
|
||||
*pCnt = *pCnt + 1;
|
||||
if( (tflags & FTS5_TOKEN_COLOCATED)==0 ){
|
||||
(*pCnt)++;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@ -1691,7 +1700,9 @@ static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){
|
||||
pCsr->aColumnSize[i] = 0;
|
||||
rc = fts5ApiColumnText(pCtx, i, &z, &n);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3Fts5Tokenize(pConfig, z, n, p, fts5ColumnSizeCb);
|
||||
rc = sqlite3Fts5Tokenize(
|
||||
pConfig, FTS5_TOKENIZE_AUX, z, n, p, fts5ColumnSizeCb
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1853,7 +1864,7 @@ static int fts5ApiQueryPhrase(
|
||||
pNew->iFirstRowid = SMALLEST_INT64;
|
||||
pNew->iLastRowid = LARGEST_INT64;
|
||||
pNew->base.pVtab = (sqlite3_vtab*)pTab;
|
||||
rc = sqlite3Fts5ExprPhraseExpr(pConf, pCsr->pExpr, iPhrase, &pNew->pExpr);
|
||||
rc = sqlite3Fts5ExprClonePhrase(pConf, pCsr->pExpr, iPhrase, &pNew->pExpr);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
@ -2344,7 +2355,7 @@ int sqlite3_fts5_init(
|
||||
void *p = (void*)pGlobal;
|
||||
memset(pGlobal, 0, sizeof(Fts5Global));
|
||||
pGlobal->db = db;
|
||||
pGlobal->api.iVersion = 1;
|
||||
pGlobal->api.iVersion = 2;
|
||||
pGlobal->api.xCreateFunction = fts5CreateAux;
|
||||
pGlobal->api.xCreateTokenizer = fts5CreateTokenizer;
|
||||
pGlobal->api.xFindTokenizer = fts5FindTokenizer;
|
||||
|
@ -359,6 +359,7 @@ struct Fts5InsertCtx {
|
||||
*/
|
||||
static int fts5StorageInsertCallback(
|
||||
void *pContext, /* Pointer to Fts5InsertCtx object */
|
||||
int tflags,
|
||||
const char *pToken, /* Buffer containing token */
|
||||
int nToken, /* Size of token in bytes */
|
||||
int iStart, /* Start offset of token */
|
||||
@ -366,8 +367,10 @@ static int fts5StorageInsertCallback(
|
||||
){
|
||||
Fts5InsertCtx *pCtx = (Fts5InsertCtx*)pContext;
|
||||
Fts5Index *pIdx = pCtx->pStorage->pIndex;
|
||||
int iPos = pCtx->szCol++;
|
||||
return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, iPos, pToken, nToken);
|
||||
if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){
|
||||
pCtx->szCol++;
|
||||
}
|
||||
return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -394,6 +397,7 @@ static int fts5StorageDeleteFromIndex(Fts5Storage *p, i64 iDel){
|
||||
if( pConfig->abUnindexed[iCol-1] ) continue;
|
||||
ctx.szCol = 0;
|
||||
rc = sqlite3Fts5Tokenize(pConfig,
|
||||
FTS5_TOKENIZE_DOCUMENT,
|
||||
(const char*)sqlite3_column_text(pSeek, iCol),
|
||||
sqlite3_column_bytes(pSeek, iCol),
|
||||
(void*)&ctx,
|
||||
@ -451,22 +455,7 @@ static int fts5StorageInsertDocsize(
|
||||
static int fts5StorageLoadTotals(Fts5Storage *p, int bCache){
|
||||
int rc = SQLITE_OK;
|
||||
if( p->bTotalsValid==0 ){
|
||||
int nCol = p->pConfig->nCol;
|
||||
Fts5Buffer buf;
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
|
||||
memset(p->aTotalSize, 0, sizeof(i64) * nCol);
|
||||
p->nTotalRow = 0;
|
||||
rc = sqlite3Fts5IndexGetAverages(p->pIndex, &buf);
|
||||
if( rc==SQLITE_OK && buf.n ){
|
||||
int i = 0;
|
||||
int iCol;
|
||||
i += fts5GetVarint(&buf.p[i], (u64*)&p->nTotalRow);
|
||||
for(iCol=0; i<buf.n && iCol<nCol; iCol++){
|
||||
i += fts5GetVarint(&buf.p[i], (u64*)&p->aTotalSize[iCol]);
|
||||
}
|
||||
}
|
||||
sqlite3_free(buf.p);
|
||||
rc = sqlite3Fts5IndexGetAverages(p->pIndex, &p->nTotalRow, p->aTotalSize);
|
||||
p->bTotalsValid = bCache;
|
||||
}
|
||||
return rc;
|
||||
@ -565,6 +554,7 @@ int sqlite3Fts5StorageSpecialDelete(
|
||||
if( pConfig->abUnindexed[iCol] ) continue;
|
||||
ctx.szCol = 0;
|
||||
rc = sqlite3Fts5Tokenize(pConfig,
|
||||
FTS5_TOKENIZE_DOCUMENT,
|
||||
(const char*)sqlite3_value_text(apVal[iCol]),
|
||||
sqlite3_value_bytes(apVal[iCol]),
|
||||
(void*)&ctx,
|
||||
@ -654,6 +644,7 @@ int sqlite3Fts5StorageRebuild(Fts5Storage *p){
|
||||
ctx.szCol = 0;
|
||||
if( pConfig->abUnindexed[ctx.iCol]==0 ){
|
||||
rc = sqlite3Fts5Tokenize(pConfig,
|
||||
FTS5_TOKENIZE_DOCUMENT,
|
||||
(const char*)sqlite3_column_text(pScan, ctx.iCol+1),
|
||||
sqlite3_column_bytes(pScan, ctx.iCol+1),
|
||||
(void*)&ctx,
|
||||
@ -771,6 +762,7 @@ int sqlite3Fts5StorageInsert(
|
||||
ctx.szCol = 0;
|
||||
if( pConfig->abUnindexed[ctx.iCol]==0 ){
|
||||
rc = sqlite3Fts5Tokenize(pConfig,
|
||||
FTS5_TOKENIZE_DOCUMENT,
|
||||
(const char*)sqlite3_value_text(apVal[ctx.iCol+2]),
|
||||
sqlite3_value_bytes(apVal[ctx.iCol+2]),
|
||||
(void*)&ctx,
|
||||
@ -838,15 +830,18 @@ struct Fts5IntegrityCtx {
|
||||
*/
|
||||
static int fts5StorageIntegrityCallback(
|
||||
void *pContext, /* Pointer to Fts5InsertCtx object */
|
||||
int tflags,
|
||||
const char *pToken, /* Buffer containing token */
|
||||
int nToken, /* Size of token in bytes */
|
||||
int iStart, /* Start offset of token */
|
||||
int iEnd /* End offset of token */
|
||||
){
|
||||
Fts5IntegrityCtx *pCtx = (Fts5IntegrityCtx*)pContext;
|
||||
int iPos = pCtx->szCol++;
|
||||
if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){
|
||||
pCtx->szCol++;
|
||||
}
|
||||
pCtx->cksum ^= sqlite3Fts5IndexCksum(
|
||||
pCtx->pConfig, pCtx->iRowid, pCtx->iCol, iPos, pToken, nToken
|
||||
pCtx->pConfig, pCtx->iRowid, pCtx->iCol, pCtx->szCol-1, pToken, nToken
|
||||
);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@ -881,19 +876,23 @@ int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
|
||||
int i;
|
||||
ctx.iRowid = sqlite3_column_int64(pScan, 0);
|
||||
ctx.szCol = 0;
|
||||
rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize);
|
||||
if( pConfig->bColumnsize ){
|
||||
rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize);
|
||||
}
|
||||
for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
|
||||
if( pConfig->abUnindexed[i] ) continue;
|
||||
ctx.iCol = i;
|
||||
ctx.szCol = 0;
|
||||
rc = sqlite3Fts5Tokenize(
|
||||
pConfig,
|
||||
rc = sqlite3Fts5Tokenize(pConfig,
|
||||
FTS5_TOKENIZE_DOCUMENT,
|
||||
(const char*)sqlite3_column_text(pScan, i+1),
|
||||
sqlite3_column_bytes(pScan, i+1),
|
||||
(void*)&ctx,
|
||||
fts5StorageIntegrityCallback
|
||||
);
|
||||
if( ctx.szCol!=aColSize[i] ) rc = FTS5_CORRUPT;
|
||||
if( pConfig->bColumnsize && ctx.szCol!=aColSize[i] ){
|
||||
rc = FTS5_CORRUPT;
|
||||
}
|
||||
aTotalSize[i] += ctx.szCol;
|
||||
}
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
@ -918,7 +917,7 @@ int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
|
||||
rc = fts5StorageCount(p, "content", &nRow);
|
||||
if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT;
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
if( rc==SQLITE_OK && pConfig->bColumnsize ){
|
||||
i64 nRow;
|
||||
rc = fts5StorageCount(p, "docsize", &nRow);
|
||||
if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT;
|
||||
@ -1002,9 +1001,12 @@ static int fts5StorageDecodeSizeArray(
|
||||
** otherwise.
|
||||
*/
|
||||
int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){
|
||||
int nCol = p->pConfig->nCol;
|
||||
sqlite3_stmt *pLookup = 0;
|
||||
int rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0);
|
||||
int nCol = p->pConfig->nCol; /* Number of user columns in table */
|
||||
sqlite3_stmt *pLookup = 0; /* Statement to query %_docsize */
|
||||
int rc; /* Return Code */
|
||||
|
||||
assert( p->pConfig->bColumnsize );
|
||||
rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
int bCorrupt = 1;
|
||||
sqlite3_bind_int64(pLookup, 1, iRowid);
|
||||
|
@ -141,6 +141,7 @@ struct F5tAuxData {
|
||||
|
||||
static int xTokenizeCb(
|
||||
void *pCtx,
|
||||
int tflags,
|
||||
const char *zToken, int nToken,
|
||||
int iStart, int iEnd
|
||||
){
|
||||
@ -584,6 +585,7 @@ struct F5tTokenizeCtx {
|
||||
|
||||
static int xTokenizeCb2(
|
||||
void *pCtx,
|
||||
int tflags,
|
||||
const char *zToken, int nToken,
|
||||
int iStart, int iEnd
|
||||
){
|
||||
@ -666,7 +668,9 @@ static int f5tTokenize(
|
||||
ctx.bSubst = (objc==5);
|
||||
ctx.pRet = pRet;
|
||||
ctx.zInput = zText;
|
||||
rc = tokenizer.xTokenize(pTok, (void*)&ctx, zText, nText, xTokenizeCb2);
|
||||
rc = tokenizer.xTokenize(
|
||||
pTok, (void*)&ctx, FTS5_TOKENIZE_DOCUMENT, zText, nText, xTokenizeCb2
|
||||
);
|
||||
tokenizer.xDelete(pTok);
|
||||
if( rc!=SQLITE_OK ){
|
||||
Tcl_AppendResult(interp, "error in tokenizer.xTokenize()", 0);
|
||||
@ -688,11 +692,11 @@ static int f5tTokenize(
|
||||
typedef struct F5tTokenizerContext F5tTokenizerContext;
|
||||
typedef struct F5tTokenizerCb F5tTokenizerCb;
|
||||
typedef struct F5tTokenizerModule F5tTokenizerModule;
|
||||
typedef struct F5tTokenizerModule F5tTokenizerInstance;
|
||||
typedef struct F5tTokenizerInstance F5tTokenizerInstance;
|
||||
|
||||
struct F5tTokenizerContext {
|
||||
void *pCtx;
|
||||
int (*xToken)(void*, const char*, int, int, int);
|
||||
int (*xToken)(void*, int, const char*, int, int, int);
|
||||
};
|
||||
|
||||
struct F5tTokenizerModule {
|
||||
@ -701,6 +705,12 @@ struct F5tTokenizerModule {
|
||||
F5tTokenizerContext *pContext;
|
||||
};
|
||||
|
||||
struct F5tTokenizerInstance {
|
||||
Tcl_Interp *interp;
|
||||
Tcl_Obj *pScript;
|
||||
F5tTokenizerContext *pContext;
|
||||
};
|
||||
|
||||
static int f5tTokenizerCreate(
|
||||
void *pCtx,
|
||||
const char **azArg,
|
||||
@ -748,26 +758,53 @@ static void f5tTokenizerDelete(Fts5Tokenizer *p){
|
||||
static int f5tTokenizerTokenize(
|
||||
Fts5Tokenizer *p,
|
||||
void *pCtx,
|
||||
int flags,
|
||||
const char *pText, int nText,
|
||||
int (*xToken)(void*, const char*, int, int, int)
|
||||
int (*xToken)(void*, int, const char*, int, int, int)
|
||||
){
|
||||
F5tTokenizerInstance *pInst = (F5tTokenizerInstance*)p;
|
||||
void *pOldCtx;
|
||||
int (*xOldToken)(void*, const char*, int, int, int);
|
||||
int (*xOldToken)(void*, int, const char*, int, int, int);
|
||||
Tcl_Obj *pEval;
|
||||
int rc;
|
||||
const char *zFlags;
|
||||
|
||||
pOldCtx = pInst->pContext->pCtx;
|
||||
xOldToken = pInst->pContext->xToken;
|
||||
|
||||
pInst->pContext->pCtx = pCtx;
|
||||
pInst->pContext->xToken = xToken;
|
||||
|
||||
assert(
|
||||
flags==FTS5_TOKENIZE_DOCUMENT
|
||||
|| flags==FTS5_TOKENIZE_AUX
|
||||
|| flags==FTS5_TOKENIZE_QUERY
|
||||
|| flags==(FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX)
|
||||
);
|
||||
pEval = Tcl_DuplicateObj(pInst->pScript);
|
||||
Tcl_IncrRefCount(pEval);
|
||||
rc = Tcl_ListObjAppendElement(
|
||||
pInst->interp, pEval, Tcl_NewStringObj(pText, nText)
|
||||
);
|
||||
if( rc==TCL_OK ){
|
||||
rc = Tcl_EvalObjEx(pInst->interp, pEval, TCL_GLOBAL_ONLY);
|
||||
switch( flags ){
|
||||
case FTS5_TOKENIZE_DOCUMENT:
|
||||
zFlags = "document";
|
||||
break;
|
||||
case FTS5_TOKENIZE_AUX:
|
||||
zFlags = "aux";
|
||||
break;
|
||||
case FTS5_TOKENIZE_QUERY:
|
||||
zFlags = "query";
|
||||
break;
|
||||
case (FTS5_TOKENIZE_PREFIX | FTS5_TOKENIZE_QUERY):
|
||||
zFlags = "prefixquery";
|
||||
break;
|
||||
default:
|
||||
assert( 0 );
|
||||
zFlags = "invalid";
|
||||
break;
|
||||
}
|
||||
|
||||
Tcl_ListObjAppendElement(pInst->interp, pEval, Tcl_NewStringObj(zFlags, -1));
|
||||
Tcl_ListObjAppendElement(pInst->interp, pEval, Tcl_NewStringObj(pText,nText));
|
||||
rc = Tcl_EvalObjEx(pInst->interp, pEval, TCL_GLOBAL_ONLY);
|
||||
Tcl_DecrRefCount(pEval);
|
||||
|
||||
pInst->pContext->pCtx = pOldCtx;
|
||||
@ -776,7 +813,7 @@ static int f5tTokenizerTokenize(
|
||||
}
|
||||
|
||||
/*
|
||||
** sqlite3_fts5_token TEXT START END POS
|
||||
** sqlite3_fts5_token ?-colocated? TEXT START END
|
||||
*/
|
||||
static int f5tTokenizerReturn(
|
||||
void * clientData,
|
||||
@ -788,14 +825,29 @@ static int f5tTokenizerReturn(
|
||||
int iStart;
|
||||
int iEnd;
|
||||
int nToken;
|
||||
int tflags = 0;
|
||||
char *zToken;
|
||||
int rc;
|
||||
|
||||
assert( p );
|
||||
if( objc!=4 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "TEXT START END");
|
||||
if( objc==5 ){
|
||||
int nArg;
|
||||
char *zArg = Tcl_GetStringFromObj(objv[1], &nArg);
|
||||
if( nArg<=10 && nArg>=2 && memcmp("-colocated", zArg, nArg)==0 ){
|
||||
tflags |= FTS5_TOKEN_COLOCATED;
|
||||
}else{
|
||||
goto usage;
|
||||
}
|
||||
}else if( objc!=4 ){
|
||||
goto usage;
|
||||
}
|
||||
|
||||
zToken = Tcl_GetStringFromObj(objv[objc-3], &nToken);
|
||||
if( Tcl_GetIntFromObj(interp, objv[objc-2], &iStart)
|
||||
|| Tcl_GetIntFromObj(interp, objv[objc-1], &iEnd)
|
||||
){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( p->xToken==0 ){
|
||||
Tcl_AppendResult(interp,
|
||||
"sqlite3_fts5_token may only be used by tokenizer callback", 0
|
||||
@ -803,16 +855,13 @@ static int f5tTokenizerReturn(
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
zToken = Tcl_GetStringFromObj(objv[1], &nToken);
|
||||
if( Tcl_GetIntFromObj(interp, objv[2], &iStart)
|
||||
|| Tcl_GetIntFromObj(interp, objv[3], &iEnd)
|
||||
){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
rc = p->xToken(p->pCtx, zToken, nToken, iStart, iEnd);
|
||||
rc = p->xToken(p->pCtx, tflags, zToken, nToken, iStart, iEnd);
|
||||
Tcl_SetResult(interp, (char*)sqlite3ErrName(rc), TCL_VOLATILE);
|
||||
return TCL_OK;
|
||||
|
||||
usage:
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "?-colocated? TEXT START END");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
static void f5tDelTokenizer(void *pCtx){
|
||||
|
@ -352,7 +352,7 @@ static void fts5MatchinfoFunc(
|
||||
){
|
||||
const char *zArg;
|
||||
Fts5MatchinfoCtx *p;
|
||||
int rc;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
if( nVal>0 ){
|
||||
zArg = (const char*)sqlite3_value_text(apVal[0]);
|
||||
@ -363,11 +363,16 @@ static void fts5MatchinfoFunc(
|
||||
p = (Fts5MatchinfoCtx*)pApi->xGetAuxdata(pFts, 0);
|
||||
if( p==0 || sqlite3_stricmp(zArg, p->zArg) ){
|
||||
p = fts5MatchinfoNew(pApi, pFts, pCtx, zArg);
|
||||
pApi->xSetAuxdata(pFts, p, sqlite3_free);
|
||||
if( p==0 ) return;
|
||||
if( p==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
rc = pApi->xSetAuxdata(pFts, p, sqlite3_free);
|
||||
}
|
||||
}
|
||||
|
||||
rc = fts5MatchinfoIter(pApi, pFts, p, fts5MatchinfoLocalCb);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts5MatchinfoIter(pApi, pFts, p, fts5MatchinfoLocalCb);
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_result_error_code(pCtx, rc);
|
||||
}else{
|
||||
|
@ -116,8 +116,9 @@ static void asciiFold(char *aOut, const char *aIn, int nByte){
|
||||
static int fts5AsciiTokenize(
|
||||
Fts5Tokenizer *pTokenizer,
|
||||
void *pCtx,
|
||||
int flags,
|
||||
const char *pText, int nText,
|
||||
int (*xToken)(void*, const char*, int nToken, int iStart, int iEnd)
|
||||
int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd)
|
||||
){
|
||||
AsciiTokenizer *p = (AsciiTokenizer*)pTokenizer;
|
||||
int rc = SQLITE_OK;
|
||||
@ -158,7 +159,7 @@ static int fts5AsciiTokenize(
|
||||
asciiFold(pFold, &pText[is], nByte);
|
||||
|
||||
/* Invoke the token callback */
|
||||
rc = xToken(pCtx, pFold, nByte, is, ie);
|
||||
rc = xToken(pCtx, 0, pFold, nByte, is, ie);
|
||||
is = ie+1;
|
||||
}
|
||||
|
||||
@ -385,8 +386,9 @@ static int fts5UnicodeIsAlnum(Unicode61Tokenizer *p, int iCode){
|
||||
static int fts5UnicodeTokenize(
|
||||
Fts5Tokenizer *pTokenizer,
|
||||
void *pCtx,
|
||||
int flags,
|
||||
const char *pText, int nText,
|
||||
int (*xToken)(void*, const char*, int nToken, int iStart, int iEnd)
|
||||
int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd)
|
||||
){
|
||||
Unicode61Tokenizer *p = (Unicode61Tokenizer*)pTokenizer;
|
||||
int rc = SQLITE_OK;
|
||||
@ -475,7 +477,7 @@ static int fts5UnicodeTokenize(
|
||||
}
|
||||
|
||||
/* Invoke the token callback */
|
||||
rc = xToken(pCtx, aFold, zOut-aFold, is, ie);
|
||||
rc = xToken(pCtx, 0, aFold, zOut-aFold, is, ie);
|
||||
}
|
||||
|
||||
tokenize_done:
|
||||
@ -553,7 +555,7 @@ static int fts5PorterCreate(
|
||||
typedef struct PorterContext PorterContext;
|
||||
struct PorterContext {
|
||||
void *pCtx;
|
||||
int (*xToken)(void*, const char*, int, int, int);
|
||||
int (*xToken)(void*, int, const char*, int, int, int);
|
||||
char *aBuf;
|
||||
};
|
||||
|
||||
@ -1118,6 +1120,7 @@ static void fts5PorterStep1A(char *aBuf, int *pnBuf){
|
||||
|
||||
static int fts5PorterCb(
|
||||
void *pCtx,
|
||||
int tflags,
|
||||
const char *pToken,
|
||||
int nToken,
|
||||
int iStart,
|
||||
@ -1175,10 +1178,10 @@ static int fts5PorterCb(
|
||||
nBuf--;
|
||||
}
|
||||
|
||||
return p->xToken(p->pCtx, aBuf, nBuf, iStart, iEnd);
|
||||
return p->xToken(p->pCtx, tflags, aBuf, nBuf, iStart, iEnd);
|
||||
|
||||
pass_through:
|
||||
return p->xToken(p->pCtx, pToken, nToken, iStart, iEnd);
|
||||
return p->xToken(p->pCtx, tflags, pToken, nToken, iStart, iEnd);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1187,8 +1190,9 @@ static int fts5PorterCb(
|
||||
static int fts5PorterTokenize(
|
||||
Fts5Tokenizer *pTokenizer,
|
||||
void *pCtx,
|
||||
int flags,
|
||||
const char *pText, int nText,
|
||||
int (*xToken)(void*, const char*, int nToken, int iStart, int iEnd)
|
||||
int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd)
|
||||
){
|
||||
PorterTokenizer *p = (PorterTokenizer*)pTokenizer;
|
||||
PorterContext sCtx;
|
||||
@ -1196,7 +1200,7 @@ static int fts5PorterTokenize(
|
||||
sCtx.pCtx = pCtx;
|
||||
sCtx.aBuf = p->aBuf;
|
||||
return p->tokenizer.xTokenize(
|
||||
p->pTokenizer, (void*)&sCtx, pText, nText, fts5PorterCb
|
||||
p->pTokenizer, (void*)&sCtx, flags, pText, nText, fts5PorterCb
|
||||
);
|
||||
}
|
||||
|
||||
@ -1225,7 +1229,7 @@ int sqlite3Fts5TokenizerInit(fts5_api *pApi){
|
||||
);
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
@ -295,3 +295,36 @@ proc NOT {a b} {
|
||||
return $a
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# This command is similar to [split], except that it also provides the
|
||||
# start and end offsets of each token. For example:
|
||||
#
|
||||
# [fts5_tokenize_split "abc d ef"] -> {abc 0 3 d 4 5 ef 6 8}
|
||||
#
|
||||
|
||||
proc gobble_whitespace {textvar} {
|
||||
upvar $textvar t
|
||||
regexp {([ ]*)(.*)} $t -> space t
|
||||
return [string length $space]
|
||||
}
|
||||
|
||||
proc gobble_text {textvar wordvar} {
|
||||
upvar $textvar t
|
||||
upvar $wordvar w
|
||||
regexp {([^ ]*)(.*)} $t -> w t
|
||||
return [string length $w]
|
||||
}
|
||||
|
||||
proc fts5_tokenize_split {text} {
|
||||
set token ""
|
||||
set ret [list]
|
||||
set iOff [gobble_whitespace text]
|
||||
while {[set nToken [gobble_text text word]]} {
|
||||
lappend ret $word $iOff [expr $iOff+$nToken]
|
||||
incr iOff $nToken
|
||||
incr iOff [gobble_whitespace text]
|
||||
}
|
||||
|
||||
set ret
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ do_execsql_test 2.1 {
|
||||
|
||||
do_test 2.2 {
|
||||
execsql { SELECT fts5_decode(id, block) FROM t1_data WHERE id==10 }
|
||||
} {/{\(structure\) {lvl=0 nMerge=0 nSeg=1 {id=[0123456789]* h=0 leaves=1..1}}}/}
|
||||
} {/{{structure} {lvl=0 nMerge=0 nSeg=1 {id=[0123456789]* leaves=1..1}}}/}
|
||||
|
||||
foreach w {a b c d e f} {
|
||||
do_execsql_test 2.3.$w.asc {
|
||||
@ -139,7 +139,6 @@ foreach {i x y} {
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
breakpoint
|
||||
reset_db
|
||||
do_execsql_test 6.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x,y);
|
||||
@ -201,6 +200,7 @@ for {set i 1} {$i <= 10} {incr i} {
|
||||
}
|
||||
execsql { INSERT INTO t1(t1) VALUES('integrity-check'); }
|
||||
} {}
|
||||
if {[set_test_counter errors]} break
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@ -343,7 +343,7 @@ do_execsql_test 13.5 {
|
||||
} {1}
|
||||
|
||||
do_execsql_test 13.6 {
|
||||
SELECT rowid FROM t1 WHERE t1 MATCH '.';
|
||||
SELECT rowid FROM t1 WHERE t1 MATCH '""';
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@ -506,6 +506,36 @@ do_execsql_test 18.3 {
|
||||
SELECT t1.rowid, t2.rowid FROM t2, t1 WHERE t2 MATCH t1.a AND t1.rowid = t2.c
|
||||
} {1 1}
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# fts5 table in the temp schema.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 19.0 {
|
||||
CREATE VIRTUAL TABLE temp.t1 USING fts5(x);
|
||||
INSERT INTO t1 VALUES('x y z');
|
||||
INSERT INTO t1 VALUES('w x 1');
|
||||
SELECT rowid FROM t1 WHERE t1 MATCH 'x';
|
||||
} {1 2}
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Test that 6 and 7 byte varints can be read.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 20.0 {
|
||||
CREATE VIRTUAL TABLE temp.tmp USING fts5(x);
|
||||
}
|
||||
set ::ids [list \
|
||||
0 [expr 1<<36] [expr 2<<36] [expr 1<<43] [expr 2<<43]
|
||||
]
|
||||
do_test 20.1 {
|
||||
foreach id $::ids {
|
||||
execsql { INSERT INTO tmp(rowid, x) VALUES($id, 'x y z') }
|
||||
}
|
||||
execsql { SELECT rowid FROM tmp WHERE tmp MATCH 'y' }
|
||||
} $::ids
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
|
@ -205,6 +205,9 @@ foreach {T create} {
|
||||
return $ret
|
||||
}
|
||||
|
||||
do_execsql_test $T.integrity {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
foreach {bAsc sql} {
|
||||
1 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix}
|
||||
|
@ -90,13 +90,13 @@ foreach {tn q res} "
|
||||
|
||||
do_test 1.6.$tn.1 {
|
||||
set n [execsql_reads $q]
|
||||
puts -nonewline "(n=$n nReadX=$nReadX)"
|
||||
#puts -nonewline "(n=$n nReadX=$nReadX)"
|
||||
expr {$n < ($nReadX / 8)}
|
||||
} {1}
|
||||
|
||||
do_test 1.6.$tn.2 {
|
||||
set n [execsql_reads "$q ORDER BY rowid DESC"]
|
||||
puts -nonewline "(n=$n nReadX=$nReadX)"
|
||||
#puts -nonewline "(n=$n nReadX=$nReadX)"
|
||||
expr {$n < ($nReadX / 8)}
|
||||
} {1}
|
||||
|
||||
|
@ -26,17 +26,17 @@ ifcapable !fts5 {
|
||||
do_execsql_test 1.1 {
|
||||
CREATE VIRTUAL TABLE ft1 USING fts5(x);
|
||||
SELECT * FROM ft1_config;
|
||||
} {version 3}
|
||||
} {version 4}
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
INSERT INTO ft1(ft1, rank) VALUES('pgsz', 32);
|
||||
SELECT * FROM ft1_config;
|
||||
} {pgsz 32 version 3}
|
||||
} {pgsz 32 version 4}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
INSERT INTO ft1(ft1, rank) VALUES('pgsz', 64);
|
||||
SELECT * FROM ft1_config;
|
||||
} {pgsz 64 version 3}
|
||||
} {pgsz 64 version 4}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Test the logic for parsing the rank() function definition.
|
||||
|
@ -134,5 +134,18 @@ do_execsql_test 3.2.1 {
|
||||
1 {-1 0 -1} 2 {-1 0 -1}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the integrity-check
|
||||
#
|
||||
do_execsql_test 4.1.1 {
|
||||
CREATE VIRTUAL TABLE t5 USING fts5(x, columnsize=0);
|
||||
INSERT INTO t5 VALUES('1 2 3 4');
|
||||
INSERT INTO t5 VALUES('2 4 6 8');
|
||||
}
|
||||
|
||||
breakpoint
|
||||
do_execsql_test 4.1.2 {
|
||||
INSERT INTO t5(t5) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
@ -43,7 +43,7 @@ set segid [lindex [fts5_level_segids t1] 0]
|
||||
|
||||
do_test 1.3 {
|
||||
execsql {
|
||||
DELETE FROM t1_data WHERE rowid = fts5_rowid('segment', $segid, 0, 4);
|
||||
DELETE FROM t1_data WHERE rowid = fts5_rowid('segment', $segid, 4);
|
||||
}
|
||||
catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
|
||||
} {1 {database disk image is malformed}}
|
||||
@ -52,7 +52,7 @@ do_test 1.4 {
|
||||
db_restore_and_reopen
|
||||
execsql {
|
||||
UPDATE t1_data set block = X'00000000' || substr(block, 5) WHERE
|
||||
rowid = fts5_rowid('segment', $segid, 0, 4);
|
||||
rowid = fts5_rowid('segment', $segid, 4);
|
||||
}
|
||||
catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
|
||||
} {1 {database disk image is malformed}}
|
||||
|
@ -209,13 +209,13 @@ foreach {tn nCut} {
|
||||
execsql ROLLBACK
|
||||
}
|
||||
|
||||
do_test 4.$tn.x { expr $nCorrupt>0 } 1
|
||||
# do_test 4.$tn.x { expr $nCorrupt>0 } 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
set doc [string repeat "A B C " 1000]
|
||||
do_execsql_test 4.0 {
|
||||
do_execsql_test 5.0 {
|
||||
CREATE VIRTUAL TABLE x5 USING fts5(tt);
|
||||
INSERT INTO x5(x5, rank) VALUES('pgsz', 32);
|
||||
WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<10)
|
||||
@ -230,7 +230,7 @@ foreach {tn hdr} {
|
||||
foreach rowid [db eval {SELECT rowid FROM x5_data WHERE rowid>10}] {
|
||||
if {$rowid & $mask} continue
|
||||
incr tn2
|
||||
do_test 4.$tn.$tn2 {
|
||||
do_test 5.$tn.$tn2 {
|
||||
execsql BEGIN
|
||||
|
||||
set fd [db incrblob main x5_data block $rowid]
|
||||
@ -248,7 +248,7 @@ foreach {tn hdr} {
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 5.1 {
|
||||
do_execsql_test 6.1 {
|
||||
CREATE VIRTUAL TABLE x5 USING fts5(tt);
|
||||
INSERT INTO x5 VALUES('a');
|
||||
INSERT INTO x5 VALUES('a a');
|
||||
@ -262,7 +262,7 @@ proc colsize {cmd i} {
|
||||
}
|
||||
sqlite3_fts5_create_function db colsize colsize
|
||||
|
||||
do_catchsql_test 5.2 {
|
||||
do_catchsql_test 6.2 {
|
||||
SELECT colsize(x5, 0) FROM x5 WHERE x5 MATCH 'a'
|
||||
} {1 SQLITE_CORRUPT_VTAB}
|
||||
|
||||
|
@ -24,18 +24,23 @@ ifcapable !fts5 {
|
||||
}
|
||||
sqlite3_fts5_may_be_corrupt 1
|
||||
|
||||
proc create_t1 {} {
|
||||
expr srand(0)
|
||||
db func rnddoc fts5_rnddoc
|
||||
db eval {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
|
||||
WITH ii(i) AS (SELECT 1 UNION SELECT i+1 FROM ii WHERE i<100)
|
||||
INSERT INTO t1 SELECT rnddoc(10) FROM ii;
|
||||
}
|
||||
}
|
||||
|
||||
if 1 {
|
||||
|
||||
# Create a simple FTS5 table containing 100 documents. Each document
|
||||
# contains 10 terms, each of which start with the character "x".
|
||||
#
|
||||
expr srand(0)
|
||||
db func rnddoc fts5_rnddoc
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
|
||||
WITH ii(i) AS (SELECT 1 UNION SELECT i+1 FROM ii WHERE i<100)
|
||||
INSERT INTO t1 SELECT rnddoc(10) FROM ii;
|
||||
}
|
||||
set mask [expr 31 << 31]
|
||||
do_test 1.0 { create_t1 } {}
|
||||
|
||||
do_test 1.1 {
|
||||
# Pick out the rowid of the right-most b-tree leaf in the new segment.
|
||||
@ -75,6 +80,261 @@ do_execsql_test 2.2 {
|
||||
SELECT length(block) FROM t2_data WHERE id=1;
|
||||
} {2}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that missing leaf pages are recognized as corruption.
|
||||
#
|
||||
reset_db
|
||||
do_test 3.0 { create_t1 } {}
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
SELECT count(*) FROM t1_data;
|
||||
} {105}
|
||||
|
||||
proc do_3_test {tn} {
|
||||
set i 0
|
||||
foreach ::rowid [db eval "SELECT rowid FROM t1_data WHERE rowid>100"] {
|
||||
incr i
|
||||
do_test $tn.$i {
|
||||
db eval BEGIN
|
||||
db eval {DELETE FROM t1_data WHERE rowid = $::rowid}
|
||||
list [
|
||||
catch { db eval {SELECT rowid FROM t1 WHERE t1 MATCH 'x*'} } msg
|
||||
] $msg
|
||||
} {1 {database disk image is malformed}}
|
||||
catch { db eval ROLLBACK }
|
||||
}
|
||||
}
|
||||
|
||||
do_3_test 3.2
|
||||
|
||||
do_execsql_test 3.3 {
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
INSERT INTO t1 SELECT x FROM t1;
|
||||
INSERT INTO t1(t1) VALUES('optimize');
|
||||
} {}
|
||||
|
||||
do_3_test 3.4
|
||||
|
||||
do_test 3.5 {
|
||||
execsql {
|
||||
DELETE FROM t1;
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 40);
|
||||
}
|
||||
for {set i 0} {$i < 1000} {incr i} {
|
||||
set rnd [expr int(rand() * 1000)]
|
||||
set doc [string repeat "x$rnd " [expr int(rand() * 3) + 1]]
|
||||
execsql { INSERT INTO t1(rowid, x) VALUES($i, $doc) }
|
||||
}
|
||||
} {}
|
||||
|
||||
do_3_test 3.6
|
||||
|
||||
do_test 3.7 {
|
||||
execsql {
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 40);
|
||||
INSERT INTO t1 SELECT x FROM t1;
|
||||
INSERT INTO t1(t1) VALUES('optimize');
|
||||
}
|
||||
} {}
|
||||
|
||||
do_3_test 3.8
|
||||
|
||||
do_test 3.9 {
|
||||
execsql {
|
||||
DELETE FROM t1;
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
}
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
set rnd [expr int(rand() * 100)]
|
||||
set doc "x[string repeat $rnd 20]"
|
||||
execsql { INSERT INTO t1(rowid, x) VALUES($i, $doc) }
|
||||
}
|
||||
} {}
|
||||
|
||||
do_3_test 3.10
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that segments that end unexpectedly are identified as corruption.
|
||||
#
|
||||
reset_db
|
||||
do_test 4.0 {
|
||||
execsql {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
}
|
||||
for {set i 0} {$i < 100} {incr i} {
|
||||
set rnd [expr int(rand() * 100)]
|
||||
set doc "x[string repeat $rnd 20]"
|
||||
execsql { INSERT INTO t1(rowid, x) VALUES($i, $doc) }
|
||||
}
|
||||
execsql { INSERT INTO t1(t1) VALUES('optimize') }
|
||||
} {}
|
||||
|
||||
set nErr 0
|
||||
for {set i 1} {1} {incr i} {
|
||||
set struct [db one {SELECT block FROM t1_data WHERE id=10}]
|
||||
binary scan $struct c* var
|
||||
set end [lindex $var end]
|
||||
if {$end<=$i} break
|
||||
lset var end [expr $end - $i]
|
||||
set struct [binary format c* $var]
|
||||
db eval {
|
||||
BEGIN;
|
||||
UPDATE t1_data SET block = $struct WHERE id=10;
|
||||
}
|
||||
do_test 4.1.$i {
|
||||
incr nErr [catch { db eval { SELECT rowid FROM t1 WHERE t1 MATCH 'x*' } }]
|
||||
set {} {}
|
||||
} {}
|
||||
catch { db eval ROLLBACK }
|
||||
}
|
||||
do_test 4.1.x { expr $nErr>45 } 1
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
|
||||
# The first argument passed to this command must be a binary blob
|
||||
# containing an FTS5 leaf page. This command returns a copy of this
|
||||
# blob, with the pgidx of the leaf page replaced by a single varint
|
||||
# containing value $iVal.
|
||||
#
|
||||
proc rewrite_pgidx {blob iVal} {
|
||||
binary scan $blob SS off1 szLeaf
|
||||
if {$iVal<0 || $iVal>=128} {
|
||||
error "$iVal out of range!"
|
||||
} else {
|
||||
set pgidx [binary format c $iVal]
|
||||
}
|
||||
|
||||
binary format a${szLeaf}a* $blob $pgidx
|
||||
}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 5.1 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts5(x);
|
||||
INSERT INTO x1(x1, rank) VALUES('pgsz', 40);
|
||||
BEGIN;
|
||||
INSERT INTO x1 VALUES('xaaa xabb xccc xcdd xeee xeff xggg xghh xiii xijj');
|
||||
INSERT INTO x1 SELECT x FROM x1;
|
||||
INSERT INTO x1 SELECT x FROM x1;
|
||||
INSERT INTO x1 SELECT x FROM x1;
|
||||
INSERT INTO x1 SELECT x FROM x1;
|
||||
INSERT INTO x1(x1) VALUES('optimize');
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
#db eval { SELECT fts5_decode(id, block) b from x1_data } { puts $b }
|
||||
#
|
||||
db func rewrite_pgidx rewrite_pgidx
|
||||
set i 0
|
||||
foreach rowid [db eval {SELECT rowid FROM x1_data WHERE rowid>100}] {
|
||||
foreach val {2 100} {
|
||||
do_test 5.2.$val.[incr i] {
|
||||
catchsql {
|
||||
BEGIN;
|
||||
UPDATE x1_data SET block=rewrite_pgidx(block, $val) WHERE id=$rowid;
|
||||
SELECT rowid FROM x1 WHERE x1 MATCH 'xa*';
|
||||
SELECT rowid FROM x1 WHERE x1 MATCH 'xb*';
|
||||
SELECT rowid FROM x1 WHERE x1 MATCH 'xc*';
|
||||
SELECT rowid FROM x1 WHERE x1 MATCH 'xd*';
|
||||
SELECT rowid FROM x1 WHERE x1 MATCH 'xe*';
|
||||
SELECT rowid FROM x1 WHERE x1 MATCH 'xf*';
|
||||
SELECT rowid FROM x1 WHERE x1 MATCH 'xg*';
|
||||
SELECT rowid FROM x1 WHERE x1 MATCH 'xh*';
|
||||
SELECT rowid FROM x1 WHERE x1 MATCH 'xi*';
|
||||
}
|
||||
set {} {}
|
||||
} {}
|
||||
catch { db eval ROLLBACK }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 6.1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a);
|
||||
INSERT INTO t1 VALUES('bbbbb ccccc');
|
||||
SELECT quote(block) FROM t1_data WHERE rowid>100;
|
||||
} {X'000000180630626262626201020201056363636363010203040A'}
|
||||
do_execsql_test 6.1.1 {
|
||||
UPDATE t1_data SET block =
|
||||
X'000000180630626262626201020201056161616161010203040A'
|
||||
WHERE rowid>100;
|
||||
}
|
||||
do_catchsql_test 6.1.2 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
} {1 {database disk image is malformed}}
|
||||
|
||||
#-------
|
||||
reset_db
|
||||
do_execsql_test 6.2.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
INSERT INTO t1 VALUES('aa bb cc dd ee');
|
||||
SELECT pgno, quote(term) FROM t1_idx;
|
||||
} {2 X'' 4 X'3064'}
|
||||
do_execsql_test 6.2.1 {
|
||||
UPDATE t1_idx SET term = X'3065' WHERE pgno=4;
|
||||
}
|
||||
do_catchsql_test 6.2.2 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
} {1 {database disk image is malformed}}
|
||||
|
||||
#-------
|
||||
reset_db
|
||||
do_execsql_test 6.3.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a);
|
||||
INSERT INTO t1 VALUES('abc abcdef abcdefghi');
|
||||
SELECT quote(block) FROM t1_data WHERE id>100;
|
||||
} {X'0000001C043061626301020204036465660102030703676869010204040808'}
|
||||
do_execsql_test 6.3.1 {
|
||||
BEGIN;
|
||||
UPDATE t1_data SET block =
|
||||
X'0000001C043061626301020204036465660102035003676869010204040808'
|
||||
------------------------------------------^^---------------------
|
||||
WHERE id>100;
|
||||
}
|
||||
do_catchsql_test 6.3.2 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
} {1 {database disk image is malformed}}
|
||||
do_execsql_test 6.3.3 {
|
||||
ROLLBACK;
|
||||
BEGIN;
|
||||
UPDATE t1_data SET block =
|
||||
X'0000001C043061626301020204036465660102030750676869010204040808'
|
||||
--------------------------------------------^^-------------------
|
||||
WHERE id>100;
|
||||
}
|
||||
do_catchsql_test 6.3.3 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
} {1 {database disk image is malformed}}
|
||||
do_execsql_test 6.3.4 {
|
||||
ROLLBACK;
|
||||
BEGIN;
|
||||
UPDATE t1_data SET block =
|
||||
X'0000001C043061626301020204036465660102030707676869010204040850'
|
||||
--------------------------------------------------------------^^-
|
||||
WHERE id>100;
|
||||
}
|
||||
do_catchsql_test 6.3.5 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
} {1 {database disk image is malformed}}
|
||||
do_execsql_test 6.3.6 {
|
||||
ROLLBACK;
|
||||
BEGIN;
|
||||
UPDATE t1_data SET block =
|
||||
X'0000001C503061626301020204036465660102030707676869010204040808'
|
||||
----------^^-----------------------------------------------------
|
||||
WHERE id>100;
|
||||
}
|
||||
do_catchsql_test 6.3.5 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
} {1 {database disk image is malformed}}
|
||||
|
||||
sqlite3_fts5_may_be_corrupt 0
|
||||
finish_test
|
||||
|
||||
|
@ -26,6 +26,8 @@ if { $tcl_platform(wordSize)<8 } {
|
||||
return
|
||||
}
|
||||
|
||||
if 1 {
|
||||
|
||||
proc do_fb_test {tn sql res} {
|
||||
set res2 [lsort -integer -decr $res]
|
||||
uplevel [list do_execsql_test $tn.1 $sql $res]
|
||||
@ -128,5 +130,68 @@ proc do_dlidx_test2 {tn nEntry iFirst nStep} {
|
||||
|
||||
do_dlidx_test2 2.1 [expr 20] [expr 1<<57] [expr (1<<57) + 128]
|
||||
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
|
||||
set ::vocab [list \
|
||||
IteratorpItercurrentlypointstothefirstrowidofadoclist \
|
||||
Thereisadoclistindexassociatedwiththefinaltermonthecurrent \
|
||||
pageIfthecurrenttermisthelasttermonthepageloadthe \
|
||||
doclistindexfromdiskandinitializeaniteratoratpIterpDlidx \
|
||||
IteratorpItercurrentlypointstothefirstrowidofadoclist \
|
||||
Thereisadoclistindexassociatedwiththefinaltermonthecurrent \
|
||||
pageIfthecurrenttermisthelasttermonthepageloadthe \
|
||||
doclistindexfromdiskandinitializeaniteratoratpIterpDlidx \
|
||||
]
|
||||
proc rnddoc {} {
|
||||
global vocab
|
||||
set nVocab [llength $vocab]
|
||||
set ret [list]
|
||||
for {set i 0} {$i < 64} {incr i} {
|
||||
lappend ret [lindex $vocab [expr $i % $nVocab]]
|
||||
}
|
||||
set ret
|
||||
}
|
||||
db func rnddoc rnddoc
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
CREATE VIRTUAL TABLE abc USING fts5(a);
|
||||
INSERT INTO abc(abc, rank) VALUES('pgsz', 32);
|
||||
|
||||
INSERT INTO abc VALUES ( rnddoc() );
|
||||
INSERT INTO abc VALUES ( rnddoc() );
|
||||
INSERT INTO abc VALUES ( rnddoc() );
|
||||
INSERT INTO abc VALUES ( rnddoc() );
|
||||
|
||||
INSERT INTO abc SELECT rnddoc() FROM abc;
|
||||
INSERT INTO abc SELECT rnddoc() FROM abc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
SELECT rowid FROM abc WHERE abc
|
||||
MATCH 'IteratorpItercurrentlypointstothefirstrowidofadoclist'
|
||||
ORDER BY rowid DESC;
|
||||
} {16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
INSERT INTO abc(abc) VALUES('integrity-check');
|
||||
INSERT INTO abc(abc) VALUES('optimize');
|
||||
INSERT INTO abc(abc) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
set v [lindex $vocab 0]
|
||||
set i 0
|
||||
foreach v $vocab {
|
||||
do_execsql_test 3.3.[incr i] {
|
||||
SELECT rowid FROM abc WHERE abc MATCH $v
|
||||
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
@ -87,6 +87,12 @@ do_execsql_test 4.0 {
|
||||
SELECT fts5_expr('a AND """"', 'x', 'tokenize="unicode61 tokenchars ''""''"');
|
||||
} {{"a" AND """"}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Experiment with a tokenizer that considers " to be a token character.
|
||||
#
|
||||
do_catchsql_test 5.0 {
|
||||
SELECT fts5_expr('abc | def');
|
||||
} {1 {fts5: syntax error near "|"}}
|
||||
|
||||
|
||||
|
||||
|
@ -30,18 +30,18 @@ proc do_syntax_test {tn expr res} {
|
||||
}
|
||||
|
||||
foreach {tn expr res} {
|
||||
1 {abc} {"abc"}
|
||||
2 {abc .} {"abc"}
|
||||
3 {.} {}
|
||||
4 {abc OR .} {"abc"}
|
||||
5 {abc NOT .} {"abc"}
|
||||
6 {abc AND .} {"abc"}
|
||||
7 {. OR abc} {"abc"}
|
||||
8 {. NOT abc} {"abc"}
|
||||
9 {. AND abc} {"abc"}
|
||||
10 {abc + . + def} {"abc" + "def"}
|
||||
11 {abc . def} {"abc" AND "def"}
|
||||
12 {r+e OR w} {"r" + "e" OR "w"}
|
||||
1 {abc} {"abc"}
|
||||
2 {abc ""} {"abc"}
|
||||
3 {""} {}
|
||||
4 {abc OR ""} {"abc"}
|
||||
5 {abc NOT ""} {"abc"}
|
||||
6 {abc AND ""} {"abc"}
|
||||
7 {"" OR abc} {"abc"}
|
||||
8 {"" NOT abc} {"abc"}
|
||||
9 {"" AND abc} {"abc"}
|
||||
10 {abc + "" + def} {"abc" + "def"}
|
||||
11 {abc "" def} {"abc" AND "def"}
|
||||
12 {r+e OR w} {"r" + "e" OR "w"}
|
||||
} {
|
||||
do_execsql_test 1.$tn {SELECT fts5_expr($expr)} [list $res]
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ ifcapable !fts5 {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# OOM while rebuilding an FTS5 table.
|
||||
#
|
||||
@ -148,5 +149,149 @@ do_faultsim_test 4.1 -faults oom-t* -prep {
|
||||
faultsim_test_result {0 {}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# 5.2.* OOM while running a query that includes synonyms and matchinfo().
|
||||
#
|
||||
# 5.3.* OOM while running a query that returns a row containing instances
|
||||
# of more than 4 synonyms for a single term.
|
||||
#
|
||||
proc mit {blob} {
|
||||
set scan(littleEndian) i*
|
||||
set scan(bigEndian) I*
|
||||
binary scan $blob $scan($::tcl_platform(byteOrder)) r
|
||||
return $r
|
||||
}
|
||||
proc tcl_tokenize {tflags text} {
|
||||
foreach {w iStart iEnd} [fts5_tokenize_split $text] {
|
||||
sqlite3_fts5_token $w $iStart $iEnd
|
||||
if {$tflags=="query" && [string length $w]==1} {
|
||||
for {set i 2} {$i < 7} {incr i} {
|
||||
sqlite3_fts5_token -colo [string repeat $w $i] $iStart $iEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
proc tcl_create {args} { return "tcl_tokenize" }
|
||||
reset_db
|
||||
sqlite3_fts5_create_tokenizer db tcl tcl_create
|
||||
db func mit mit
|
||||
sqlite3_fts5_register_matchinfo db
|
||||
do_test 5.0 {
|
||||
execsql { CREATE VIRTUAL TABLE t1 USING fts5(a, tokenize=tcl) }
|
||||
execsql { INSERT INTO t1(t1, rank) VALUES('pgsz', 32) }
|
||||
foreach {rowid text} {
|
||||
1 {aaaa cc b aaaaa cc aa}
|
||||
2 {aa aa bb a bbb}
|
||||
3 {bb aaaaa aaaaa b aaaa aaaaa}
|
||||
4 {aa a b aaaa aa}
|
||||
5 {aa b ccc aaaaa cc}
|
||||
6 {aa aaaaa bbbb cc aaa}
|
||||
7 {aaaaa aa aa ccccc bb}
|
||||
8 {ccc bbbbb ccccc bbb c}
|
||||
9 {cccccc bbbb a aaa cccc c}
|
||||
|
||||
20 {ddd f ddd eeeee fff ffff eeee ddd fff eeeee dddddd eeee}
|
||||
21 {fffff eee dddd fffff dd ee ee eeeee eee eeeeee ee dd e}
|
||||
22 {fffff d eeee dddd fffff dddddd ffff ddddd eeeee ee eee dddd ddddd}
|
||||
23 {ddddd fff ddd eeeee ffff eeee ddd ff ff ffffff eeeeee dddd ffffff}
|
||||
24 {eee dd ee dddd dddd eeeeee e eee fff ffff}
|
||||
25 {ddddd ffffff dddddd fff ddd ddddd ddd f eeee fff dddd f}
|
||||
26 {f ffff fff fff eeeeee dddd d dddddd ddddd eee ff eeeee}
|
||||
27 {eee fff dddddd eeeee eeeee dddd ddddd ffff f eeeee eee dddddd ddddd d}
|
||||
28 {dd ddddd d ddd d fff d dddd ee dddd ee ddd dddddd dddddd}
|
||||
29 {eeee dddd ee dddd eeee dddd dd fffff f ddd eeeee ddd ee}
|
||||
30 {ff ffffff eeeeee eeeee eee ffffff ff ffff f fffff eeeee}
|
||||
31 {fffff eeeeee dddd eeee eeee eeeeee eee fffff d ddddd ffffff ffff dddddd}
|
||||
32 {dddddd fffff ee eeeeee eeee ee fff dddd fff eeee ffffff eeeeee ffffff}
|
||||
33 {ddddd eeee dd ffff dddddd fff eeee ddddd ffff eeee ddd}
|
||||
34 {ee dddd ddddd dddddd eeee eeeeee f dd ee dddddd ffffff}
|
||||
35 {ee dddd dd eeeeee ddddd eee d eeeeee dddddd eee dddd fffff}
|
||||
36 {eee ffffff ffffff e fffff eeeee ff dddddd dddddd fff}
|
||||
37 {eeeee fffff dddddd dddd ffffff fff f dd ee dd dd eeeee}
|
||||
38 {eeeeee ee d ff eeeeee eeeeee eee eeeee ee ffffff dddd eeee dddddd ee}
|
||||
39 {eeeeee ddd fffff e dddd ee eee eee ffffff ee f d dddd}
|
||||
40 {ffffff dddddd eee ee ffffff eee eeee ddddd ee eeeeee f}
|
||||
41 {ddd ddd fff fffff ee fffff f fff ddddd fffff}
|
||||
42 {dddd ee ff d f ffffff fff ffffff ff dd dddddd f eeee}
|
||||
43 {d dd fff fffff d f fff e dddd ee ee}
|
||||
44 {ff ffff eee ddd d dd ffff dddd d eeee d eeeeee}
|
||||
45 {eeee f eeeee ee e ffff f ddd e fff}
|
||||
46 {ffff d ffff eeee ffff eeeee f ffff ddddd eee}
|
||||
47 {dd dd dddddd ddddd fffff dddddd ddd ddddd eeeeee ffff eeee eee ee}
|
||||
48 {ffff ffff e dddd ffffff dd dd dddd f fffff}
|
||||
49 {ffffff d dddddd ffff eeeee f ffff ffff d dd fffff eeeee}
|
||||
|
||||
50 {x e}
|
||||
} {
|
||||
execsql { INSERT INTO t1(rowid, a) VALUES($rowid, $text) }
|
||||
}
|
||||
} {}
|
||||
|
||||
set res [list {*}{
|
||||
1 {3 24 8 2 12 6}
|
||||
5 {2 24 8 2 12 6}
|
||||
6 {3 24 8 1 12 6}
|
||||
7 {3 24 8 1 12 6}
|
||||
9 {2 24 8 3 12 6}
|
||||
}]
|
||||
do_execsql_test 5.1.1 {
|
||||
SELECT rowid, mit(matchinfo(t1, 'x')) FROM t1 WHERE t1 MATCH 'a AND c'
|
||||
} $res
|
||||
do_execsql_test 5.1.2 {
|
||||
SELECT count(*) FROM t1 WHERE t1 MATCH 'd e f'
|
||||
} 29
|
||||
|
||||
faultsim_save_and_close
|
||||
do_faultsim_test 5.2 -faults oom* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
sqlite3_fts5_create_tokenizer db tcl tcl_create
|
||||
sqlite3_fts5_register_matchinfo db
|
||||
db func mit mit
|
||||
} -body {
|
||||
db eval {
|
||||
SELECT rowid, mit(matchinfo(t1, 'x')) FROM t1 WHERE t1 MATCH 'a AND c'
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result [list 0 $::res]
|
||||
}
|
||||
|
||||
do_faultsim_test 5.3 -faults oom* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
sqlite3_fts5_create_tokenizer db tcl tcl_create
|
||||
} -body {
|
||||
db eval {
|
||||
SELECT count(*) FROM t1 WHERE t1 MATCH 'd AND e AND f'
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 29}
|
||||
}
|
||||
|
||||
do_faultsim_test 5.4 -faults oom* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
sqlite3_fts5_create_tokenizer db tcl tcl_create
|
||||
} -body {
|
||||
db eval {
|
||||
SELECT count(*) FROM t1 WHERE t1 MATCH 'x + e'
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 1}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
catch { db close }
|
||||
breakpoint
|
||||
do_faultsim_test 6 -faults oom* -prep {
|
||||
sqlite_orig db test.db
|
||||
sqlite3_db_config_lookaside db 0 0 0
|
||||
} -body {
|
||||
load_static_extension db fts5
|
||||
} -test {
|
||||
faultsim_test_result {0 {}} {1 {initialization of fts5 failed: }}
|
||||
if {$testrc==0} {
|
||||
db eval { CREATE VIRTUAL TABLE temp.t1 USING fts5(x) }
|
||||
}
|
||||
db close
|
||||
}
|
||||
finish_test
|
||||
|
||||
|
91
ext/fts5/test/fts5fault7.test
Normal file
91
ext/fts5/test/fts5fault7.test
Normal file
@ -0,0 +1,91 @@
|
||||
# 2015 September 3
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
source $testdir/malloc_common.tcl
|
||||
set testprefix fts5fault7
|
||||
|
||||
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
if 1 {
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test fault-injection on a query that uses xColumnSize() on columnsize=0
|
||||
# table.
|
||||
#
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, columnsize=0);
|
||||
INSERT INTO t1 VALUES('a b c d e f g');
|
||||
INSERT INTO t1 VALUES('a b c d');
|
||||
INSERT INTO t1 VALUES('a b c d e f g h i j');
|
||||
}
|
||||
|
||||
|
||||
fts5_aux_test_functions db
|
||||
do_faultsim_test 1 -faults oom* -body {
|
||||
execsql { SELECT fts5_test_columnsize(t1) FROM t1 WHERE t1 MATCH 'b' }
|
||||
} -test {
|
||||
faultsim_test_result {0 {7 4 10}} {1 SQLITE_NOMEM}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test fault-injection when a segment is promoted.
|
||||
#
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(a);
|
||||
INSERT INTO t2(t2, rank) VALUES('automerge', 0);
|
||||
INSERT INTO t2(t2, rank) VALUES('crisismerge', 4);
|
||||
INSERT INTO t2(t2, rank) VALUES('pgsz', 40);
|
||||
|
||||
INSERT INTO t2 VALUES('a b c');
|
||||
INSERT INTO t2 VALUES('d e f');
|
||||
INSERT INTO t2 VALUES('f e d');
|
||||
INSERT INTO t2 VALUES('c b a');
|
||||
|
||||
INSERT INTO t2 VALUES('a b c');
|
||||
INSERT INTO t2 VALUES('d e f');
|
||||
INSERT INTO t2 VALUES('f e d');
|
||||
INSERT INTO t2 VALUES('c b a');
|
||||
} {}
|
||||
|
||||
faultsim_save_and_close
|
||||
do_faultsim_test 1 -faults oom-t* -prep {
|
||||
faultsim_restore_and_reopen
|
||||
db eval {
|
||||
BEGIN;
|
||||
INSERT INTO t2 VALUES('c d c g g f');
|
||||
INSERT INTO t2 VALUES('c d g b f d');
|
||||
INSERT INTO t2 VALUES('c c f d e d');
|
||||
INSERT INTO t2 VALUES('e a f c e f');
|
||||
INSERT INTO t2 VALUES('c g f b b d');
|
||||
INSERT INTO t2 VALUES('d a g a b b');
|
||||
INSERT INTO t2 VALUES('e f a b c e');
|
||||
INSERT INTO t2 VALUES('e c a g c d');
|
||||
INSERT INTO t2 VALUES('g b d d e b');
|
||||
INSERT INTO t2 VALUES('e a d a e d');
|
||||
}
|
||||
} -body {
|
||||
db eval COMMIT
|
||||
} -test {
|
||||
faultsim_test_result {0 {}}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
@ -355,10 +355,10 @@ do_execsql_test 10.1 {
|
||||
#---------------------------------------------------------------------------
|
||||
# Test the 'y' matchinfo flag
|
||||
#
|
||||
set sqlite_fts3_enable_parentheses 1
|
||||
reset_db
|
||||
sqlite3_fts5_register_matchinfo db
|
||||
do_execsql_test 11.0 {
|
||||
CREATE VIRTUAL TABLE tt USING fts3(x, y);
|
||||
CREATE VIRTUAL TABLE tt USING fts5(x, y);
|
||||
INSERT INTO tt VALUES('c d a c d d', 'e a g b d a'); -- 1
|
||||
INSERT INTO tt VALUES('c c g a e b', 'c g d g e c'); -- 2
|
||||
INSERT INTO tt VALUES('b e f d e g', 'b a c b c g'); -- 3
|
||||
@ -432,19 +432,18 @@ foreach {tn expr res} {
|
||||
SELECT rowid, mit(matchinfo(tt, 'b')) FROM tt WHERE tt MATCH $expr
|
||||
} $r2
|
||||
}
|
||||
set sqlite_fts3_enable_parentheses 0
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Test the 'b' matchinfo flag
|
||||
#
|
||||
set sqlite_fts3_enable_parentheses 1
|
||||
reset_db
|
||||
sqlite3_fts5_register_matchinfo db
|
||||
db func mit mit
|
||||
|
||||
do_test 12.0 {
|
||||
set cols [list]
|
||||
for {set i 0} {$i < 50} {incr i} { lappend cols "c$i" }
|
||||
execsql "CREATE VIRTUAL TABLE tt USING fts3([join $cols ,])"
|
||||
execsql "CREATE VIRTUAL TABLE tt USING fts5([join $cols ,])"
|
||||
} {}
|
||||
|
||||
do_execsql_test 12.1 {
|
||||
@ -452,6 +451,5 @@ do_execsql_test 12.1 {
|
||||
SELECT mit(matchinfo(tt, 'b')) FROM tt WHERE tt MATCH 'abc';
|
||||
} [list [list [expr 1<<4] [expr 1<<(45-32)]]]
|
||||
|
||||
set sqlite_fts3_enable_parentheses 0
|
||||
finish_test
|
||||
|
||||
|
@ -27,15 +27,15 @@ do_catchsql_test 1.1 {
|
||||
|
||||
do_catchsql_test 1.2 {
|
||||
SELECT fts5_rowid('segment')
|
||||
} {1 {should be: fts5_rowid('segment', segid, height, pgno))}}
|
||||
} {1 {should be: fts5_rowid('segment', segid, pgno))}}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
SELECT fts5_rowid('segment', 1, 1, 1)
|
||||
} {139586437121}
|
||||
SELECT fts5_rowid('segment', 1, 1)
|
||||
} {137438953473}
|
||||
|
||||
do_catchsql_test 1.4 {
|
||||
SELECT fts5_rowid('nosucharg');
|
||||
} {1 {first arg to fts5_rowid() must be 'segment' or 'start-of-index'}}
|
||||
} {1 {first arg to fts5_rowid() must be 'segment'}}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@ -91,6 +91,10 @@ do_execsql_test 2.6 {
|
||||
# SELECT count(fts5_decode(rowid, block)) FROM x1_data;
|
||||
#} $res
|
||||
|
||||
do_execsql_test 2.8 {
|
||||
SELECT fts5_decode(fts5_rowid('segment', 1000, 1), X'AB')
|
||||
} {corrupt}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Tests with very large tokens.
|
||||
#
|
||||
|
173
ext/fts5/test/fts5simple.test
Normal file
173
ext/fts5/test/fts5simple.test
Normal file
@ -0,0 +1,173 @@
|
||||
# 2015 September 05
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#*************************************************************************
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5simple
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
if 1 {
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
set doc "x x [string repeat {y } 50]z z"
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES($doc);
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x);
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
INSERT INTO t1 VALUES('d e f');
|
||||
INSERT INTO t1(t1) VALUES('optimize');
|
||||
}
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
} {}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, prefix='1,2');
|
||||
INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES('one');
|
||||
SELECT * FROM t1 WHERE t1 MATCH 'o*';
|
||||
} {one}
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 4.1 {
|
||||
CREATE VIRTUAL TABLE t11 USING fts5(content);
|
||||
INSERT INTO t11(t11, rank) VALUES('pgsz', 32);
|
||||
INSERT INTO t11 VALUES('another');
|
||||
INSERT INTO t11 VALUES('string');
|
||||
INSERT INTO t11 VALUES('of');
|
||||
INSERT INTO t11 VALUES('text');
|
||||
}
|
||||
do_test 4.2 {
|
||||
execsql { INSERT INTO t11(t11) VALUES('optimize') }
|
||||
} {}
|
||||
do_execsql_test 4.3 {
|
||||
INSERT INTO t11(t11) VALUES('integrity-check');
|
||||
} {}
|
||||
|
||||
#db eval { SELECT fts5_decode(rowid, block) as x FROM t11_data } { puts $x }
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
set doc [string repeat "x y " 5]
|
||||
do_execsql_test 5.1 {
|
||||
CREATE VIRTUAL TABLE yy USING fts5(content);
|
||||
INSERT INTO yy(yy, rank) VALUES('pgsz', 32);
|
||||
BEGIN;
|
||||
INSERT INTO yy VALUES($doc);
|
||||
INSERT INTO yy VALUES($doc);
|
||||
INSERT INTO yy VALUES($doc);
|
||||
INSERT INTO yy VALUES($doc);
|
||||
INSERT INTO yy VALUES($doc);
|
||||
INSERT INTO yy VALUES($doc);
|
||||
INSERT INTO yy VALUES($doc);
|
||||
INSERT INTO yy VALUES($doc);
|
||||
COMMIT;
|
||||
}
|
||||
|
||||
do_execsql_test 5.2 {
|
||||
SELECT rowid FROM yy WHERE yy MATCH 'y' ORDER BY rowid ASC
|
||||
} {1 2 3 4 5 6 7 8}
|
||||
|
||||
do_execsql_test 5.3 {
|
||||
SELECT rowid FROM yy WHERE yy MATCH 'y' ORDER BY rowid DESC
|
||||
} {8 7 6 5 4 3 2 1}
|
||||
|
||||
#db eval { SELECT fts5_decode(rowid, block) as x FROM yy_data } { puts $x }
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
reset_db
|
||||
do_execsql_test 5.1 {
|
||||
CREATE VIRTUAL TABLE tt USING fts5(content);
|
||||
INSERT INTO tt(tt, rank) VALUES('pgsz', 32);
|
||||
INSERT INTO tt VALUES('aa');
|
||||
}
|
||||
|
||||
do_execsql_test 5.2 {
|
||||
SELECT rowid FROM tt WHERE tt MATCH 'a*';
|
||||
} {1}
|
||||
|
||||
do_execsql_test 5.3 {
|
||||
DELETE FROM tt;
|
||||
BEGIN;
|
||||
INSERT INTO tt VALUES('aa');
|
||||
INSERT INTO tt VALUES('ab');
|
||||
COMMIT;
|
||||
} {}
|
||||
|
||||
do_execsql_test 5.4 {
|
||||
SELECT rowid FROM tt WHERE tt MATCH 'a*';
|
||||
} {1 2}
|
||||
|
||||
}
|
||||
|
||||
do_execsql_test 5.5 {
|
||||
DELETE FROM tt;
|
||||
BEGIN;
|
||||
INSERT INTO tt VALUES('aa');
|
||||
INSERT INTO tt VALUES('ab');
|
||||
INSERT INTO tt VALUES('aa');
|
||||
INSERT INTO tt VALUES('ab');
|
||||
INSERT INTO tt VALUES('aa');
|
||||
INSERT INTO tt VALUES('ab');
|
||||
INSERT INTO tt VALUES('aa');
|
||||
INSERT INTO tt VALUES('ab');
|
||||
COMMIT;
|
||||
SELECT rowid FROM tt WHERE tt MATCH 'a*';
|
||||
} {1 2 3 4 5 6 7 8}
|
||||
|
||||
do_execsql_test 5.6 {
|
||||
INSERT INTO tt(tt) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 5.7 {
|
||||
CREATE VIRTUAL TABLE tt USING fts5(content);
|
||||
INSERT INTO tt(tt, rank) VALUES('pgsz', 32);
|
||||
INSERT INTO tt VALUES('aa ab ac ad ae af');
|
||||
}
|
||||
|
||||
do_execsql_test 5.8 {
|
||||
SELECT rowid FROM tt WHERE tt MATCH 'a*';
|
||||
} {1}
|
||||
|
||||
finish_test
|
||||
|
460
ext/fts5/test/fts5synonym.test
Normal file
460
ext/fts5/test/fts5synonym.test
Normal file
@ -0,0 +1,460 @@
|
||||
# 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Tests focusing on custom tokenizers that support synonyms.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5synonym
|
||||
|
||||
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
|
||||
ifcapable !fts5 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
foreach S {
|
||||
{zero 0}
|
||||
{one 1 i}
|
||||
{two 2 ii}
|
||||
{three 3 iii}
|
||||
{four 4 iv}
|
||||
{five 5 v}
|
||||
{six 6 vi}
|
||||
{seven 7 vii}
|
||||
{eight 8 viii}
|
||||
{nine 9 ix}
|
||||
} {
|
||||
foreach s $S {
|
||||
set o [list]
|
||||
foreach x $S {if {$x!=$s} {lappend o $x}}
|
||||
set ::syn($s) $o
|
||||
}
|
||||
}
|
||||
|
||||
proc tcl_tokenize {tflags text} {
|
||||
foreach {w iStart iEnd} [fts5_tokenize_split $text] {
|
||||
sqlite3_fts5_token $w $iStart $iEnd
|
||||
}
|
||||
}
|
||||
|
||||
proc tcl_create {args} {
|
||||
return "tcl_tokenize"
|
||||
}
|
||||
|
||||
sqlite3_fts5_create_tokenizer db tcl tcl_create
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Warm body test for the code in fts5_tcl.c.
|
||||
#
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = tcl);
|
||||
INSERT INTO ft VALUES('abc def ghi');
|
||||
INSERT INTO ft VALUES('jkl mno pqr');
|
||||
SELECT rowid, x FROM ft WHERE ft MATCH 'def';
|
||||
SELECT x, rowid FROM ft WHERE ft MATCH 'pqr';
|
||||
} {1 {abc def ghi} {jkl mno pqr} 2}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test a tokenizer that supports synonyms by adding extra entries to the
|
||||
# FTS index.
|
||||
#
|
||||
|
||||
proc tcl_tokenize {tflags text} {
|
||||
foreach {w iStart iEnd} [fts5_tokenize_split $text] {
|
||||
sqlite3_fts5_token $w $iStart $iEnd
|
||||
if {$tflags=="document" && [info exists ::syn($w)]} {
|
||||
foreach s $::syn($w) {
|
||||
sqlite3_fts5_token -colo $s $iStart $iEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
reset_db
|
||||
sqlite3_fts5_create_tokenizer db tcl tcl_create
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = tcl);
|
||||
INSERT INTO ft VALUES('one two three');
|
||||
INSERT INTO ft VALUES('four five six');
|
||||
INSERT INTO ft VALUES('eight nine ten');
|
||||
} {}
|
||||
|
||||
foreach {tn expr res} {
|
||||
1 "3" 1
|
||||
2 "eight OR 8 OR 5" {2 3}
|
||||
3 "10" {}
|
||||
4 "1*" {1}
|
||||
5 "1 + 2" {1}
|
||||
} {
|
||||
do_execsql_test 2.1.$tn {
|
||||
SELECT rowid FROM ft WHERE ft MATCH $expr
|
||||
} $res
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test some broken tokenizers:
|
||||
#
|
||||
# 3.1.*: A tokenizer that declares the very first token to be colocated.
|
||||
#
|
||||
# 3.2.*: A tokenizer that reports two identical tokens at the same position.
|
||||
# This is allowed.
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_create_tokenizer db tcl tcl_create
|
||||
proc tcl_tokenize {tflags text} {
|
||||
set bColo 1
|
||||
foreach {w iStart iEnd} [fts5_tokenize_split $text] {
|
||||
if {$bColo} {
|
||||
sqlite3_fts5_token -colo $w $iStart $iEnd
|
||||
set bColo 0
|
||||
} {
|
||||
sqlite3_fts5_token $w $iStart $iEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
do_execsql_test 3.1.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = tcl);
|
||||
INSERT INTO ft VALUES('one two three');
|
||||
CREATE VIRTUAL TABLE vv USING fts5vocab(ft, row);
|
||||
SELECT * FROM vv;
|
||||
} {
|
||||
one 1 1 three 1 1 two 1 1
|
||||
}
|
||||
|
||||
do_execsql_test 3.1.1 {
|
||||
INSERT INTO ft(ft) VALUES('integrity-check');
|
||||
} {}
|
||||
|
||||
proc tcl_tokenize {tflags text} {
|
||||
foreach {w iStart iEnd} [fts5_tokenize_split $text] {
|
||||
sqlite3_fts5_token $w $iStart $iEnd
|
||||
}
|
||||
}
|
||||
|
||||
do_execsql_test 3.1.2 {
|
||||
SELECT rowid FROM ft WHERE ft MATCH 'one two three'
|
||||
} {1}
|
||||
|
||||
reset_db
|
||||
sqlite3_fts5_create_tokenizer db tcl tcl_create
|
||||
proc tcl_tokenize {tflags text} {
|
||||
foreach {w iStart iEnd} [fts5_tokenize_split $text] {
|
||||
sqlite3_fts5_token $w $iStart $iEnd
|
||||
sqlite3_fts5_token -colo $w $iStart $iEnd
|
||||
}
|
||||
}
|
||||
do_execsql_test 3.2.0 {
|
||||
CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = tcl);
|
||||
INSERT INTO ft VALUES('one one two three');
|
||||
CREATE VIRTUAL TABLE vv USING fts5vocab(ft, row);
|
||||
SELECT * FROM vv;
|
||||
} {
|
||||
one 1 4 three 1 2 two 1 2
|
||||
}
|
||||
do_execsql_test 3.2.1 {
|
||||
SELECT rowid FROM ft WHERE ft MATCH 'one';
|
||||
} {1}
|
||||
do_execsql_test 3.2.2 {
|
||||
SELECT rowid FROM ft WHERE ft MATCH 'one two three';
|
||||
} {1}
|
||||
do_execsql_test 3.2.3 {
|
||||
SELECT rowid FROM ft WHERE ft MATCH 'one + one + two + three';
|
||||
} {1}
|
||||
do_execsql_test 3.2.4 {
|
||||
SELECT rowid FROM ft WHERE ft MATCH 'one two two three';
|
||||
} {1}
|
||||
do_execsql_test 3.2.5 {
|
||||
SELECT rowid FROM ft WHERE ft MATCH 'one + two + two + three';
|
||||
} {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Check that expressions with synonyms can be parsed and executed.
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_create_tokenizer db tcl tcl_create
|
||||
proc tcl_tokenize {tflags text} {
|
||||
foreach {w iStart iEnd} [fts5_tokenize_split $text] {
|
||||
sqlite3_fts5_token $w $iStart $iEnd
|
||||
if {$tflags=="query" && [info exists ::syn($w)]} {
|
||||
foreach s $::syn($w) {
|
||||
sqlite3_fts5_token -colo $s $iStart $iEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach {tn expr res} {
|
||||
1 {abc} {"abc"}
|
||||
2 {one} {"one"|"i"|"1"}
|
||||
3 {3} {"3"|"iii"|"three"}
|
||||
4 {3*} {"3"|"iii"|"three" *}
|
||||
} {
|
||||
do_execsql_test 4.1.$tn {SELECT fts5_expr($expr, 'tokenize=tcl')} [list $res]
|
||||
}
|
||||
|
||||
do_execsql_test 4.2.1 {
|
||||
CREATE VIRTUAL TABLE xx USING fts5(x, tokenize=tcl);
|
||||
INSERT INTO xx VALUES('one two');
|
||||
INSERT INTO xx VALUES('three four');
|
||||
}
|
||||
|
||||
do_execsql_test 4.2.2 {
|
||||
SELECT rowid FROM xx WHERE xx MATCH '2'
|
||||
} {1}
|
||||
|
||||
do_execsql_test 4.2.3 {
|
||||
SELECT rowid FROM xx WHERE xx MATCH '3'
|
||||
} {2}
|
||||
|
||||
do_test 5.0 {
|
||||
execsql {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b, tokenize=tcl)
|
||||
}
|
||||
foreach {rowid a b} {
|
||||
1 {four v 4 i three} {1 3 five five 4 one}
|
||||
2 {5 1 3 4 i} {2 2 v two 4}
|
||||
3 {5 i 5 2 four 4 1} {iii ii five two 1}
|
||||
4 {ii four 4 one 5 three five} {one 5 1 iii 4 3}
|
||||
5 {three i v i four 4 1} {ii five five five iii}
|
||||
6 {4 2 ii two 2 iii} {three 1 four 4 iv 1 iv}
|
||||
7 {ii ii two three 2 5} {iii i ii iii iii one one}
|
||||
8 {2 ii i two 3 three 2} {two iv v iii 3 five}
|
||||
9 {i 2 iv 3 five four v} {iii 4 three i three ii 1}
|
||||
} {
|
||||
execsql { INSERT INTO t1(rowid, a, b) VALUES($rowid, $a, $b) }
|
||||
}
|
||||
} {}
|
||||
|
||||
|
||||
foreach {tn q res} {
|
||||
1 {one} {
|
||||
1 {four v 4 [i] three} {[1] 3 five five 4 [one]}
|
||||
2 {5 [1] 3 4 [i]} {2 2 v two 4}
|
||||
3 {5 [i] 5 2 four 4 [1]} {iii ii five two [1]}
|
||||
4 {ii four 4 [one] 5 three five} {[one] 5 [1] iii 4 3}
|
||||
5 {three [i] v [i] four 4 [1]} {ii five five five iii}
|
||||
6 {4 2 ii two 2 iii} {three [1] four 4 iv [1] iv}
|
||||
7 {ii ii two three 2 5} {iii [i] ii iii iii [one] [one]}
|
||||
8 {2 ii [i] two 3 three 2} {two iv v iii 3 five}
|
||||
9 {[i] 2 iv 3 five four v} {iii 4 three [i] three ii [1]}
|
||||
}
|
||||
2 {five four} {
|
||||
1 {[four] [v] [4] i three} {1 3 [five] [five] [4] one}
|
||||
2 {[5] 1 3 [4] i} {2 2 [v] two [4]}
|
||||
3 {[5] i [5] 2 [four] [4] 1} {iii ii [five] two 1}
|
||||
4 {ii [four] [4] one [5] three [five]} {one [5] 1 iii [4] 3}
|
||||
5 {three i [v] i [four] [4] 1} {ii [five] [five] [five] iii}
|
||||
8 {2 ii i two 3 three 2} {two [iv] [v] iii 3 [five]}
|
||||
9 {i 2 [iv] 3 [five] [four] [v]} {iii [4] three i three ii 1}
|
||||
}
|
||||
3 {one OR two OR iii OR 4 OR v} {
|
||||
1 {[four] [v] [4] [i] [three]} {[1] [3] [five] [five] [4] [one]}
|
||||
2 {[5] [1] [3] [4] [i]} {[2] [2] [v] [two] [4]}
|
||||
3 {[5] [i] [5] [2] [four] [4] [1]} {[iii] [ii] [five] [two] [1]}
|
||||
4 {[ii] [four] [4] [one] [5] [three] [five]} {[one] [5] [1] [iii] [4] [3]}
|
||||
5 {[three] [i] [v] [i] [four] [4] [1]} {[ii] [five] [five] [five] [iii]}
|
||||
6 {[4] [2] [ii] [two] [2] [iii]} {[three] [1] [four] [4] [iv] [1] [iv]}
|
||||
7 {[ii] [ii] [two] [three] [2] [5]} {[iii] [i] [ii] [iii] [iii] [one] [one]}
|
||||
8 {[2] [ii] [i] [two] [3] [three] [2]} {[two] [iv] [v] [iii] [3] [five]}
|
||||
9 {[i] [2] [iv] [3] [five] [four] [v]} {[iii] [4] [three] [i] [three] [ii] [1]}
|
||||
}
|
||||
|
||||
4 {5 + 1} {
|
||||
2 {[5 1] 3 4 i} {2 2 v two 4}
|
||||
3 {[5 i] 5 2 four 4 1} {iii ii five two 1}
|
||||
4 {ii four 4 one 5 three five} {one [5 1] iii 4 3}
|
||||
5 {three i [v i] four 4 1} {ii five five five iii}
|
||||
}
|
||||
|
||||
5 {one + two + three} {
|
||||
7 {ii ii two three 2 5} {iii [i ii iii] iii one one}
|
||||
8 {2 ii [i two 3] three 2} {two iv v iii 3 five}
|
||||
}
|
||||
|
||||
6 {"v v"} {
|
||||
1 {four v 4 i three} {1 3 [five five] 4 one}
|
||||
5 {three i v i four 4 1} {ii [five five five] iii}
|
||||
}
|
||||
} {
|
||||
do_execsql_test 5.1.$tn {
|
||||
SELECT rowid, highlight(t1, 0, '[', ']'), highlight(t1, 1, '[', ']')
|
||||
FROM t1 WHERE t1 MATCH $q
|
||||
} $res
|
||||
}
|
||||
|
||||
# Test that the xQueryPhrase() API works with synonyms.
|
||||
#
|
||||
proc mit {blob} {
|
||||
set scan(littleEndian) i*
|
||||
set scan(bigEndian) I*
|
||||
binary scan $blob $scan($::tcl_platform(byteOrder)) r
|
||||
return $r
|
||||
}
|
||||
db func mit mit
|
||||
sqlite3_fts5_register_matchinfo db
|
||||
|
||||
foreach {tn q res} {
|
||||
1 {one} {
|
||||
1 {1 11 7 2 12 6} 2 {2 11 7 0 12 6}
|
||||
3 {2 11 7 1 12 6} 4 {1 11 7 2 12 6}
|
||||
5 {3 11 7 0 12 6} 6 {0 11 7 2 12 6}
|
||||
7 {0 11 7 3 12 6} 8 {1 11 7 0 12 6}
|
||||
9 {1 11 7 2 12 6}
|
||||
}
|
||||
} {
|
||||
do_execsql_test 5.2.$tn {
|
||||
SELECT rowid, mit(matchinfo(t1, 'x')) FROM t1 WHERE t1 MATCH $q
|
||||
} $res
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test terms with more than 4 synonyms.
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_create_tokenizer db tcl tcl_create
|
||||
proc tcl_tokenize {tflags text} {
|
||||
foreach {w iStart iEnd} [fts5_tokenize_split $text] {
|
||||
sqlite3_fts5_token $w $iStart $iEnd
|
||||
if {$tflags=="query" && [string length $w]==1} {
|
||||
for {set i 2} {$i<=10} {incr i} {
|
||||
sqlite3_fts5_token -colo [string repeat $w $i] $iStart $iEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do_execsql_test 6.0.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize=tcl);
|
||||
INSERT INTO t1 VALUES('yy xx qq');
|
||||
INSERT INTO t1 VALUES('yy xx xx');
|
||||
}
|
||||
do_execsql_test 6.0.2 {
|
||||
SELECT * FROM t1 WHERE t1 MATCH 'NEAR(y q)';
|
||||
} {{yy xx qq}}
|
||||
|
||||
do_test 6.0.3 {
|
||||
execsql {
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(a, b, tokenize=tcl)
|
||||
}
|
||||
foreach {rowid a b} {
|
||||
1 {yyyy vvvvv qq oo yyyyyy vvvv eee} {ffff uu r qq aaaa}
|
||||
2 {ww oooooo bbbbb ssssss mm} {ffffff yy iiii rr s ccc qqqqq}
|
||||
3 {zzzz llll gggggg cccc uu} {hhhhhh aaaa ppppp rr ee jjjj}
|
||||
4 {r f i rrrrrr ww hhh} {aa yyy t x aaaaa ii}
|
||||
5 {fffff mm vvvv ooo ffffff kkkk tttt} {cccccc bb e zzz d n}
|
||||
6 {iii dddd hh qqqq ddd ooo} {ttt d c b aaaaaa qqqq}
|
||||
7 {jjjj rrrr v zzzzz u tt t} {ppppp pp dddd mm hhh uuu}
|
||||
8 {gggg rrrrrr kkkk vvvv gggg jjjjjj b} {dddddd jj r w cccc wwwwww ss}
|
||||
9 {kkkkk qqq oooo e tttttt mmm} {e ss qqqqqq hhhh llllll gg}
|
||||
} {
|
||||
execsql { INSERT INTO t2(rowid, a, b) VALUES($rowid, $a, $b) }
|
||||
}
|
||||
} {}
|
||||
|
||||
foreach {tn q res} {
|
||||
1 {a} {
|
||||
1 {yyyy vvvvv qq oo yyyyyy vvvv eee} {ffff uu r qq [aaaa]}
|
||||
3 {zzzz llll gggggg cccc uu} {hhhhhh [aaaa] ppppp rr ee jjjj}
|
||||
4 {r f i rrrrrr ww hhh} {[aa] yyy t x [aaaaa] ii}
|
||||
6 {iii dddd hh qqqq ddd ooo} {ttt d c b [aaaaaa] qqqq}
|
||||
}
|
||||
|
||||
2 {a AND q} {
|
||||
1 {yyyy vvvvv [qq] oo yyyyyy vvvv eee} {ffff uu r [qq] [aaaa]}
|
||||
6 {iii dddd hh [qqqq] ddd ooo} {ttt d c b [aaaaaa] [qqqq]}
|
||||
}
|
||||
|
||||
3 {o OR (q AND a)} {
|
||||
1 {yyyy vvvvv [qq] [oo] yyyyyy vvvv eee} {ffff uu r [qq] [aaaa]}
|
||||
2 {ww [oooooo] bbbbb ssssss mm} {ffffff yy iiii rr s ccc qqqqq}
|
||||
5 {fffff mm vvvv [ooo] ffffff kkkk tttt} {cccccc bb e zzz d n}
|
||||
6 {iii dddd hh [qqqq] ddd [ooo]} {ttt d c b [aaaaaa] [qqqq]}
|
||||
9 {kkkkk qqq [oooo] e tttttt mmm} {e ss qqqqqq hhhh llllll gg}
|
||||
}
|
||||
|
||||
4 {NEAR(q y, 20)} {
|
||||
1 {[yyyy] vvvvv [qq] oo [yyyyyy] vvvv eee} {ffff uu r qq aaaa}
|
||||
2 {ww oooooo bbbbb ssssss mm} {ffffff [yy] iiii rr s ccc [qqqqq]}
|
||||
}
|
||||
} {
|
||||
do_execsql_test 6.1.$tn.asc {
|
||||
SELECT rowid, highlight(t2, 0, '[', ']'), highlight(t2, 1, '[', ']')
|
||||
FROM t2 WHERE t2 MATCH $q
|
||||
} $res
|
||||
|
||||
set res2 [list]
|
||||
foreach {rowid a b} $res {
|
||||
set res2 [concat [list $rowid $a $b] $res2]
|
||||
}
|
||||
|
||||
do_execsql_test 6.1.$tn.desc {
|
||||
SELECT rowid, highlight(t2, 0, '[', ']'), highlight(t2, 1, '[', ']')
|
||||
FROM t2 WHERE t2 MATCH $q ORDER BY rowid DESC
|
||||
} $res2
|
||||
}
|
||||
|
||||
do_execsql_test 6.2.1 {
|
||||
INSERT INTO t2(rowid, a, b) VALUES(13,
|
||||
'x xx xxx xxxx xxxxx xxxxxx xxxxxxx', 'y yy yyy yyyy yyyyy yyyyyy yyyyyyy'
|
||||
);
|
||||
SELECT rowid, highlight(t2, 0, '<', '>'), highlight(t2, 1, '(', ')')
|
||||
FROM t2 WHERE t2 MATCH 'x OR y'
|
||||
} {
|
||||
1 {<yyyy> vvvvv qq oo <yyyyyy> vvvv eee} {ffff uu r qq aaaa}
|
||||
2 {ww oooooo bbbbb ssssss mm} {ffffff (yy) iiii rr s ccc qqqqq}
|
||||
4 {r f i rrrrrr ww hhh} {aa (yyy) t (x) aaaaa ii}
|
||||
13 {<x> <xx> <xxx> <xxxx> <xxxxx> <xxxxxx> <xxxxxxx>}
|
||||
{(y) (yy) (yyy) (yyyy) (yyyyy) (yyyyyy) (yyyyyyy)}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that the xColumnSize() API is not confused by colocated tokens.
|
||||
#
|
||||
reset_db
|
||||
sqlite3_fts5_create_tokenizer db tcl tcl_create
|
||||
fts5_aux_test_functions db
|
||||
proc tcl_tokenize {tflags text} {
|
||||
foreach {w iStart iEnd} [fts5_tokenize_split $text] {
|
||||
sqlite3_fts5_token $w $iStart $iEnd
|
||||
if {[string length $w]==1} {
|
||||
for {set i 2} {$i<=10} {incr i} {
|
||||
sqlite3_fts5_token -colo [string repeat $w $i] $iStart $iEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do_execsql_test 7.0.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b, columnsize=1, tokenize=tcl);
|
||||
INSERT INTO t1 VALUES('0 2 3', '4 5 6 7');
|
||||
INSERT INTO t1 VALUES('8 9', '0 0 0 0 0 0 0 0 0 0');
|
||||
SELECT fts5_test_columnsize(t1) FROM t1 WHERE t1 MATCH '000 AND 00 AND 0';
|
||||
} {{3 4} {2 10}}
|
||||
|
||||
do_execsql_test 7.0.2 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
do_execsql_test 7.1.1 {
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(a, b, columnsize=0, tokenize=tcl);
|
||||
INSERT INTO t2 VALUES('0 2 3', '4 5 6 7');
|
||||
INSERT INTO t2 VALUES('8 9', '0 0 0 0 0 0 0 0 0 0');
|
||||
SELECT fts5_test_columnsize(t2) FROM t2 WHERE t2 MATCH '000 AND 00 AND 0';
|
||||
} {{3 4} {2 10}}
|
||||
|
||||
do_execsql_test 7.1.2 {
|
||||
INSERT INTO t2(t2) VALUES('integrity-check');
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
@ -30,34 +30,34 @@ do_execsql_test 1.1 {
|
||||
|
||||
do_execsql_test 1.2 {
|
||||
SELECT * FROM t1_config WHERE k='version'
|
||||
} {version 3}
|
||||
} {version 4}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
SELECT rowid FROM t1 WHERE t1 MATCH 'a';
|
||||
} {1}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
UPDATE t1_config set v=4 WHERE k='version';
|
||||
UPDATE t1_config set v=5 WHERE k='version';
|
||||
}
|
||||
|
||||
do_test 1.5 {
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' }
|
||||
} {1 {invalid fts5 file format (found 4, expected 3) - run 'rebuild'}}
|
||||
} {1 {invalid fts5 file format (found 5, expected 4) - run 'rebuild'}}
|
||||
|
||||
do_test 1.6 {
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
catchsql { INSERT INTO t1 VALUES('x y z') }
|
||||
} {1 {invalid fts5 file format (found 4, expected 3) - run 'rebuild'}}
|
||||
} {1 {invalid fts5 file format (found 5, expected 4) - run 'rebuild'}}
|
||||
|
||||
do_test 1.7 {
|
||||
execsql { DELETE FROM t1_config WHERE k='version' }
|
||||
db close
|
||||
sqlite3 db test.db
|
||||
catchsql { SELECT * FROM t1 WHERE t1 MATCH 'a' }
|
||||
} {1 {invalid fts5 file format (found 0, expected 3) - run 'rebuild'}}
|
||||
} {1 {invalid fts5 file format (found 0, expected 4) - run 'rebuild'}}
|
||||
|
||||
|
||||
finish_test
|
||||
|
@ -19,6 +19,12 @@ proc load_hierachy {dir} {
|
||||
db eval { INSERT INTO t1 VALUES($f, loadfile($f)) }
|
||||
incr ::nRow
|
||||
|
||||
if {$::O(trans) && ($::nRow % $::O(trans))==0} {
|
||||
db eval { COMMIT }
|
||||
db eval { INSERT INTO t1(t1) VALUES('integrity-check') }
|
||||
db eval { BEGIN }
|
||||
}
|
||||
|
||||
if {($::nRow % $::nRowPerDot)==0} {
|
||||
puts -nonewline .
|
||||
if {($::nRow % (65*$::nRowPerDot))==0} { puts "" }
|
||||
@ -41,6 +47,7 @@ proc usage {} {
|
||||
puts stderr " -automerge N (set the automerge parameter to N)"
|
||||
puts stderr " -crisismerge N (set the crisismerge parameter to N)"
|
||||
puts stderr " -prefix PREFIX (comma separated prefix= argument)"
|
||||
puts stderr " -trans N (commit after N inserts - 0 == never)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
@ -51,6 +58,7 @@ set O(delete) 0
|
||||
set O(automerge) -1
|
||||
set O(crisismerge) -1
|
||||
set O(prefix) ""
|
||||
set O(trans) 0
|
||||
|
||||
if {[llength $argv]<2} usage
|
||||
set nOpt [expr {[llength $argv]-2}]
|
||||
@ -77,6 +85,11 @@ for {set i 0} {$i < $nOpt} {incr i} {
|
||||
if { [incr i]>=$nOpt } usage
|
||||
set O(limit) [lindex $argv $i]
|
||||
}
|
||||
|
||||
-trans {
|
||||
if { [incr i]>=$nOpt } usage
|
||||
set O(trans) [lindex $argv $i]
|
||||
}
|
||||
|
||||
-automerge {
|
||||
if { [incr i]>=$nOpt } usage
|
||||
@ -104,8 +117,9 @@ if {$O(delete)} { file delete -force $dbfile }
|
||||
sqlite3 db $dbfile
|
||||
catch { load_static_extension db fts5 }
|
||||
db func loadfile loadfile
|
||||
db eval "PRAGMA page_size=4096"
|
||||
|
||||
db transaction {
|
||||
db eval BEGIN
|
||||
set pref ""
|
||||
if {$O(prefix)!=""} { set pref ", prefix='$O(prefix)'" }
|
||||
catch {
|
||||
@ -126,7 +140,7 @@ db transaction {
|
||||
}
|
||||
}
|
||||
load_hierachy [lindex $argv end]
|
||||
}
|
||||
db eval COMMIT
|
||||
|
||||
|
||||
|
||||
|
@ -5,14 +5,52 @@
|
||||
# Process command line arguments.
|
||||
#
|
||||
proc usage {} {
|
||||
puts stderr "usage: $::argv0 database table"
|
||||
puts stderr "usage: $::argv0 ?OPTIONS? database table"
|
||||
puts stderr ""
|
||||
puts stderr " -nterm (count number of terms in each segment)"
|
||||
puts stderr ""
|
||||
exit 1
|
||||
}
|
||||
if {[llength $argv]!=2} usage
|
||||
set database [lindex $argv 0]
|
||||
set tbl [lindex $argv 1]
|
||||
|
||||
set O(nterm) 0
|
||||
|
||||
if {[llength $argv]<2} usage
|
||||
foreach a [lrange $argv 0 end-2] {
|
||||
switch -- $a {
|
||||
-nterm {
|
||||
set O(nterm) 1
|
||||
}
|
||||
|
||||
default {
|
||||
usage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set database [lindex $argv end-1]
|
||||
set tbl [lindex $argv end]
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Count the number of terms in each segment of fts5 table $tbl. Store the
|
||||
# counts in the array variable in the parent context named by parameter
|
||||
# $arrayname, indexed by segment-id. Example:
|
||||
#
|
||||
# count_terms fts_tbl A
|
||||
# foreach {k v} [array get A] { puts "segid=$k nTerm=$v" }
|
||||
#
|
||||
proc count_terms {tbl arrayname} {
|
||||
upvar A $arrayname
|
||||
array unset A
|
||||
db eval "SELECT fts5_decode(rowid, block) AS d FROM ${tbl}_data" {
|
||||
set desc [lindex $d 0]
|
||||
if {[regexp {^segid=([0-9]*)} $desc -> id]} {
|
||||
foreach i [lrange $d 1 end] {
|
||||
if {[string match {term=*} $i]} { incr A($id) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@ -21,11 +59,21 @@ set tbl [lindex $argv 1]
|
||||
sqlite3 db $database
|
||||
catch { load_static_extension db fts5 }
|
||||
|
||||
if {$O(nterm)} { count_terms $tbl A }
|
||||
|
||||
db eval "SELECT fts5_decode(rowid, block) AS d FROM ${tbl}_data WHERE id=10" {
|
||||
foreach lvl [lrange $d 1 end] {
|
||||
puts [lrange $lvl 0 2]
|
||||
|
||||
foreach seg [lrange $lvl 3 end] {
|
||||
puts " $seg"
|
||||
if {$::O(nterm)} {
|
||||
regexp {^id=([0-9]*)} $seg -> id
|
||||
set nTerm 0
|
||||
catch { set nTerm $A($id) }
|
||||
puts [format " % -28s nTerm=%d" $seg $nTerm]
|
||||
} else {
|
||||
puts [format " % -28s" $seg]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
235
ext/misc/json1.c
235
ext/misc/json1.c
@ -68,6 +68,9 @@ struct JsonString {
|
||||
#define JSON_ARRAY 6
|
||||
#define JSON_OBJECT 7
|
||||
|
||||
/* The "subtype" set for JSON values */
|
||||
#define JSON_SUBTYPE 74 /* Ascii for "J" */
|
||||
|
||||
/*
|
||||
** Names of the various JSON types:
|
||||
*/
|
||||
@ -82,7 +85,7 @@ static const char * const jsonType[] = {
|
||||
#define JNODE_REMOVE 0x04 /* Do not output */
|
||||
#define JNODE_REPLACE 0x08 /* Replace with JsonNode.iVal */
|
||||
#define JNODE_APPEND 0x10 /* More ARRAY/OBJECT entries at u.iAppend */
|
||||
#define JNODE_JSON 0x20 /* Treat REPLACE as JSON text */
|
||||
#define JNODE_LABEL 0x20 /* Is a label of an object */
|
||||
|
||||
|
||||
/* A single node of parsed JSON
|
||||
@ -242,8 +245,7 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){
|
||||
*/
|
||||
static void jsonAppendValue(
|
||||
JsonString *p, /* Append to this JSON string */
|
||||
sqlite3_value *pValue, /* Value to append */
|
||||
u8 textIsJson /* Try to treat text values as JSON */
|
||||
sqlite3_value *pValue /* Value to append */
|
||||
){
|
||||
switch( sqlite3_value_type(pValue) ){
|
||||
case SQLITE_NULL: {
|
||||
@ -260,7 +262,7 @@ static void jsonAppendValue(
|
||||
case SQLITE_TEXT: {
|
||||
const char *z = (const char*)sqlite3_value_text(pValue);
|
||||
u32 n = (u32)sqlite3_value_bytes(pValue);
|
||||
if( textIsJson ){
|
||||
if( sqlite3_value_subtype(pValue)==JSON_SUBTYPE ){
|
||||
jsonAppendRaw(p, z, n);
|
||||
}else{
|
||||
jsonAppendString(p, z, n);
|
||||
@ -364,8 +366,7 @@ static void jsonRenderNode(
|
||||
if( pNode[j].jnFlags & (JNODE_REMOVE|JNODE_REPLACE) ){
|
||||
if( pNode[j].jnFlags & JNODE_REPLACE ){
|
||||
jsonAppendSeparator(pOut);
|
||||
jsonAppendValue(pOut, aReplace[pNode[j].iVal],
|
||||
(pNode[j].jnFlags & JNODE_JSON)!=0);
|
||||
jsonAppendValue(pOut, aReplace[pNode[j].iVal]);
|
||||
}
|
||||
}else{
|
||||
jsonAppendSeparator(pOut);
|
||||
@ -390,8 +391,7 @@ static void jsonRenderNode(
|
||||
jsonRenderNode(&pNode[j], pOut, aReplace);
|
||||
jsonAppendChar(pOut, ':');
|
||||
if( pNode[j+1].jnFlags & JNODE_REPLACE ){
|
||||
jsonAppendValue(pOut, aReplace[pNode[j+1].iVal],
|
||||
(pNode[j+1].jnFlags & JNODE_JSON)!=0);
|
||||
jsonAppendValue(pOut, aReplace[pNode[j+1].iVal]);
|
||||
}else{
|
||||
jsonRenderNode(&pNode[j+1], pOut, aReplace);
|
||||
}
|
||||
@ -420,6 +420,7 @@ static void jsonReturnJson(
|
||||
jsonInit(&s, pCtx);
|
||||
jsonRenderNode(pNode, &s, aReplace);
|
||||
jsonResult(&s);
|
||||
sqlite3_result_subtype(pCtx, JSON_SUBTYPE);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -494,12 +495,12 @@ static void jsonReturn(
|
||||
}
|
||||
if( v==0 ) break;
|
||||
if( v<=0x7f ){
|
||||
zOut[j++] = v;
|
||||
zOut[j++] = (char)v;
|
||||
}else if( v<=0x7ff ){
|
||||
zOut[j++] = 0xc0 | (v>>6);
|
||||
zOut[j++] = (char)(0xc0 | (v>>6));
|
||||
zOut[j++] = 0x80 | (v&0x3f);
|
||||
}else{
|
||||
zOut[j++] = 0xe0 | (v>>12);
|
||||
zOut[j++] = (char)(0xe0 | (v>>12));
|
||||
zOut[j++] = 0x80 | ((v>>6)&0x3f);
|
||||
zOut[j++] = 0x80 | (v&0x3f);
|
||||
}
|
||||
@ -583,6 +584,7 @@ static int jsonParseValue(JsonParse *pParse, u32 i){
|
||||
u32 j;
|
||||
int iThis;
|
||||
int x;
|
||||
JsonNode *pNode;
|
||||
while( isspace(pParse->zJson[i]) ){ i++; }
|
||||
if( (c = pParse->zJson[i])==0 ) return 0;
|
||||
if( c=='{' ){
|
||||
@ -597,7 +599,9 @@ static int jsonParseValue(JsonParse *pParse, u32 i){
|
||||
return -1;
|
||||
}
|
||||
if( pParse->oom ) return -1;
|
||||
if( pParse->aNode[pParse->nNode-1].eType!=JSON_STRING ) return -1;
|
||||
pNode = &pParse->aNode[pParse->nNode-1];
|
||||
if( pNode->eType!=JSON_STRING ) return -1;
|
||||
pNode->jnFlags |= JNODE_LABEL;
|
||||
j = x;
|
||||
while( isspace(pParse->zJson[j]) ){ j++; }
|
||||
if( pParse->zJson[j]!=':' ) return -1;
|
||||
@ -947,21 +951,15 @@ static char *jsonPathSyntaxError(const char *zErr){
|
||||
**
|
||||
** If pApnd!=NULL then try to append missing nodes and set *pApnd = 1 if
|
||||
** nodes are appended.
|
||||
**
|
||||
** If the path starts with $$ then set *pFlags to JNODE_REPLACE|JNODE_JSON
|
||||
** as a single to the caller that the input text to be inserted should be
|
||||
** interpreted as JSON rather than as ordinary text.
|
||||
*/
|
||||
static JsonNode *jsonLookup(
|
||||
JsonParse *pParse, /* The JSON to search */
|
||||
const char *zPath, /* The path to search */
|
||||
int *pApnd, /* Append nodes to complete path if not NULL */
|
||||
sqlite3_context *pCtx, /* Report errors here, if not NULL */
|
||||
u8 *pFlags /* Write JNODE_REPLACE or _REPLACE|_JSON here */
|
||||
sqlite3_context *pCtx /* Report errors here, if not NULL */
|
||||
){
|
||||
const char *zErr = 0;
|
||||
JsonNode *pNode = 0;
|
||||
u8 fg = JNODE_REPLACE;
|
||||
|
||||
if( zPath==0 ) return 0;
|
||||
if( zPath[0]!='$' ){
|
||||
@ -969,15 +967,6 @@ static JsonNode *jsonLookup(
|
||||
goto lookup_err;
|
||||
}
|
||||
zPath++;
|
||||
if( zPath[0]=='$' ){
|
||||
if( pFlags==0 ){
|
||||
zErr = zPath;
|
||||
goto lookup_err;
|
||||
}
|
||||
zPath++;
|
||||
fg = JNODE_REPLACE|JNODE_JSON;
|
||||
}
|
||||
if( pFlags ) *pFlags = fg;
|
||||
pNode = jsonLookupStep(pParse, 0, zPath, pApnd, &zErr);
|
||||
return pNode;
|
||||
|
||||
@ -992,7 +981,6 @@ lookup_err:
|
||||
sqlite3_result_error_nomem(pCtx);
|
||||
}
|
||||
}
|
||||
if( pFlags ) *pFlags = fg;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1036,45 +1024,37 @@ static void jsonParseFunc(
|
||||
jsonParseFindParents(&x);
|
||||
jsonInit(&s, ctx);
|
||||
for(i=0; i<x.nNode; i++){
|
||||
jsonPrintf(100, &s,"node %3u: %7s n=%-4d up=%d\n",
|
||||
i, jsonType[x.aNode[i].eType], x.aNode[i].n, x.aUp[i]);
|
||||
if( x.aNode[i].u.zJContent!=0 ){
|
||||
jsonAppendRaw(&s, " text: ", 10);
|
||||
jsonAppendRaw(&s, x.aNode[i].u.zJContent, x.aNode[i].n);
|
||||
jsonAppendRaw(&s, "\n", 1);
|
||||
const char *zType;
|
||||
if( x.aNode[i].jnFlags & JNODE_LABEL ){
|
||||
assert( x.aNode[i].eType==JSON_STRING );
|
||||
zType = "label";
|
||||
}else{
|
||||
zType = jsonType[x.aNode[i].eType];
|
||||
}
|
||||
jsonPrintf(100, &s,"node %3u: %7s n=%-4d up=%-4d",
|
||||
i, zType, x.aNode[i].n, x.aUp[i]);
|
||||
if( x.aNode[i].u.zJContent!=0 ){
|
||||
jsonAppendRaw(&s, " ", 1);
|
||||
jsonAppendRaw(&s, x.aNode[i].u.zJContent, x.aNode[i].n);
|
||||
}
|
||||
jsonAppendRaw(&s, "\n", 1);
|
||||
}
|
||||
jsonParseReset(&x);
|
||||
jsonResult(&s);
|
||||
}
|
||||
|
||||
/*
|
||||
** The json_test1(JSON) function parses and rebuilds the JSON string.
|
||||
** 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.
|
||||
*/
|
||||
static void jsonTest1Func(
|
||||
sqlite3_context *ctx,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
JsonParse x; /* The parse */
|
||||
if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
|
||||
jsonReturnJson(x.aNode, ctx, 0);
|
||||
jsonParseReset(&x);
|
||||
}
|
||||
|
||||
/*
|
||||
** The json_nodecount(JSON) function returns the number of nodes in the
|
||||
** input JSON string.
|
||||
*/
|
||||
static void jsonNodeCountFunc(
|
||||
sqlite3_context *ctx,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
JsonParse x; /* The parse */
|
||||
if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
|
||||
sqlite3_result_int64(ctx, (sqlite3_int64)x.nNode);
|
||||
jsonParseReset(&x);
|
||||
UNUSED_PARAM(argc);
|
||||
sqlite3_result_int(ctx, sqlite3_value_subtype(argv[0])==JSON_SUBTYPE);
|
||||
}
|
||||
#endif /* SQLITE_DEBUG */
|
||||
|
||||
@ -1099,10 +1079,11 @@ static void jsonArrayFunc(
|
||||
jsonAppendChar(&jx, '[');
|
||||
for(i=0; i<argc; i++){
|
||||
jsonAppendSeparator(&jx);
|
||||
jsonAppendValue(&jx, argv[i], 0);
|
||||
jsonAppendValue(&jx, argv[i]);
|
||||
}
|
||||
jsonAppendChar(&jx, ']');
|
||||
jsonResult(&jx);
|
||||
sqlite3_result_subtype(ctx, JSON_SUBTYPE);
|
||||
}
|
||||
|
||||
|
||||
@ -1127,7 +1108,7 @@ static void jsonArrayLengthFunc(
|
||||
JsonNode *pNode;
|
||||
if( argc==2 ){
|
||||
const char *zPath = (const char*)sqlite3_value_text(argv[1]);
|
||||
pNode = jsonLookup(&x, zPath, 0, ctx, 0);
|
||||
pNode = jsonLookup(&x, zPath, 0, ctx);
|
||||
}else{
|
||||
pNode = x.aNode;
|
||||
}
|
||||
@ -1169,7 +1150,7 @@ static void jsonExtractFunc(
|
||||
jsonAppendChar(&jx, '[');
|
||||
for(i=1; i<argc; i++){
|
||||
zPath = (const char*)sqlite3_value_text(argv[i]);
|
||||
pNode = jsonLookup(&x, zPath, 0, ctx, 0);
|
||||
pNode = jsonLookup(&x, zPath, 0, ctx);
|
||||
if( x.nErr ) break;
|
||||
if( argc>2 ){
|
||||
jsonAppendSeparator(&jx);
|
||||
@ -1185,6 +1166,7 @@ static void jsonExtractFunc(
|
||||
if( argc>2 && i==argc ){
|
||||
jsonAppendChar(&jx, ']');
|
||||
jsonResult(&jx);
|
||||
sqlite3_result_subtype(ctx, JSON_SUBTYPE);
|
||||
}
|
||||
jsonReset(&jx);
|
||||
jsonParseReset(&x);
|
||||
@ -1223,10 +1205,11 @@ static void jsonObjectFunc(
|
||||
n = (u32)sqlite3_value_bytes(argv[i]);
|
||||
jsonAppendString(&jx, z, n);
|
||||
jsonAppendChar(&jx, ':');
|
||||
jsonAppendValue(&jx, argv[i+1], 0);
|
||||
jsonAppendValue(&jx, argv[i+1]);
|
||||
}
|
||||
jsonAppendChar(&jx, '}');
|
||||
jsonResult(&jx);
|
||||
sqlite3_result_subtype(ctx, JSON_SUBTYPE);
|
||||
}
|
||||
|
||||
|
||||
@ -1252,7 +1235,7 @@ static void jsonRemoveFunc(
|
||||
for(i=1; i<(u32)argc; i++){
|
||||
zPath = (const char*)sqlite3_value_text(argv[i]);
|
||||
if( zPath==0 ) goto remove_done;
|
||||
pNode = jsonLookup(&x, zPath, 0, ctx, 0);
|
||||
pNode = jsonLookup(&x, zPath, 0, ctx);
|
||||
if( x.nErr ) goto remove_done;
|
||||
if( pNode ) pNode->jnFlags |= JNODE_REMOVE;
|
||||
}
|
||||
@ -1288,14 +1271,12 @@ static void jsonReplaceFunc(
|
||||
if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
|
||||
if( x.nNode ){
|
||||
for(i=1; i<(u32)argc; i+=2){
|
||||
u8 jnFlags = JNODE_REPLACE;
|
||||
zPath = (const char*)sqlite3_value_text(argv[i]);
|
||||
pNode = jsonLookup(&x, zPath, 0, ctx, &jnFlags);
|
||||
pNode = jsonLookup(&x, zPath, 0, ctx);
|
||||
if( x.nErr ) goto replace_err;
|
||||
if( pNode ){
|
||||
pNode->jnFlags &= ~JNODE_JSON;
|
||||
pNode->jnFlags |= jnFlags;
|
||||
pNode->iVal = i+1;
|
||||
pNode->jnFlags |= (u8)JNODE_REPLACE;
|
||||
pNode->iVal = (u8)(i+1);
|
||||
}
|
||||
}
|
||||
if( x.aNode[0].jnFlags & JNODE_REPLACE ){
|
||||
@ -1340,19 +1321,17 @@ static void jsonSetFunc(
|
||||
if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
|
||||
if( x.nNode ){
|
||||
for(i=1; i<(u32)argc; i+=2){
|
||||
u8 jnFlags = JNODE_REPLACE;
|
||||
zPath = (const char*)sqlite3_value_text(argv[i]);
|
||||
bApnd = 0;
|
||||
pNode = jsonLookup(&x, zPath, &bApnd, ctx, &jnFlags);
|
||||
pNode = jsonLookup(&x, zPath, &bApnd, ctx);
|
||||
if( x.oom ){
|
||||
sqlite3_result_error_nomem(ctx);
|
||||
goto jsonSetDone;
|
||||
}else if( x.nErr ){
|
||||
goto jsonSetDone;
|
||||
}else if( pNode && (bApnd || bIsSet) ){
|
||||
pNode->jnFlags &= ~JNODE_JSON;
|
||||
pNode->jnFlags |= jnFlags;
|
||||
pNode->iVal = i+1;
|
||||
pNode->jnFlags |= (u8)JNODE_REPLACE;
|
||||
pNode->iVal = (u8)(i+1);
|
||||
}
|
||||
}
|
||||
if( x.aNode[0].jnFlags & JNODE_REPLACE ){
|
||||
@ -1385,7 +1364,7 @@ static void jsonTypeFunc(
|
||||
JsonNode *pNode;
|
||||
if( argc==2 ){
|
||||
zPath = (const char*)sqlite3_value_text(argv[1]);
|
||||
pNode = jsonLookup(&x, zPath, 0, ctx, 0);
|
||||
pNode = jsonLookup(&x, zPath, 0, ctx);
|
||||
}else{
|
||||
pNode = x.aNode;
|
||||
}
|
||||
@ -1410,6 +1389,7 @@ static void jsonValidFunc(
|
||||
JsonParse x; /* The parse */
|
||||
int rc = 0;
|
||||
|
||||
UNUSED_PARAM(argc);
|
||||
if( jsonParse(&x, 0, (const char*)sqlite3_value_text(argv[0]))==0
|
||||
&& x.nNode>0
|
||||
){
|
||||
@ -1427,12 +1407,13 @@ typedef struct JsonEachCursor JsonEachCursor;
|
||||
struct JsonEachCursor {
|
||||
sqlite3_vtab_cursor base; /* Base class - must be first */
|
||||
u32 iRowid; /* The rowid */
|
||||
u32 iBegin; /* The first node of the scan */
|
||||
u32 i; /* Index in sParse.aNode[] of current row */
|
||||
u32 iEnd; /* EOF when i equals or exceeds this value */
|
||||
u8 eType; /* Type of top-level element */
|
||||
u8 bRecursive; /* True for json_tree(). False for json_each() */
|
||||
char *zJson; /* Input JSON */
|
||||
char *zPath; /* Path by which to filter zJson */
|
||||
char *zRoot; /* Path by which to filter zJson */
|
||||
JsonParse sParse; /* Parse of the input JSON */
|
||||
};
|
||||
|
||||
@ -1455,16 +1436,17 @@ static int jsonEachConnect(
|
||||
#define JEACH_ID 4
|
||||
#define JEACH_PARENT 5
|
||||
#define JEACH_FULLKEY 6
|
||||
#define JEACH_JSON 7
|
||||
#define JEACH_PATH 8
|
||||
#define JEACH_PATH 7
|
||||
#define JEACH_JSON 8
|
||||
#define JEACH_ROOT 9
|
||||
|
||||
UNUSED_PARAM(pzErr);
|
||||
UNUSED_PARAM(argv);
|
||||
UNUSED_PARAM(argc);
|
||||
UNUSED_PARAM(pAux);
|
||||
rc = sqlite3_declare_vtab(db,
|
||||
"CREATE TABLE x(key,value,type,atom,id,parent,fullkey,"
|
||||
"json HIDDEN,path HIDDEN)");
|
||||
"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;
|
||||
@ -1505,14 +1487,14 @@ static int jsonEachOpenTree(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
** held. */
|
||||
static void jsonEachCursorReset(JsonEachCursor *p){
|
||||
sqlite3_free(p->zJson);
|
||||
sqlite3_free(p->zPath);
|
||||
sqlite3_free(p->zRoot);
|
||||
jsonParseReset(&p->sParse);
|
||||
p->iRowid = 0;
|
||||
p->i = 0;
|
||||
p->iEnd = 0;
|
||||
p->eType = 0;
|
||||
p->zJson = 0;
|
||||
p->zPath = 0;
|
||||
p->zRoot = 0;
|
||||
}
|
||||
|
||||
/* Destructor for a jsonEachCursor object */
|
||||
@ -1534,16 +1516,10 @@ static int jsonEachEof(sqlite3_vtab_cursor *cur){
|
||||
static int jsonEachNext(sqlite3_vtab_cursor *cur){
|
||||
JsonEachCursor *p = (JsonEachCursor*)cur;
|
||||
if( p->bRecursive ){
|
||||
if( p->i==0 ){
|
||||
p->i = 1;
|
||||
}else{
|
||||
u32 iUp = p->sParse.aUp[p->i];
|
||||
JsonNode *pUp = &p->sParse.aNode[iUp];
|
||||
p->i++;
|
||||
if( pUp->eType==JSON_OBJECT && (pUp->n + iUp >= p->i) ) p->i++;
|
||||
}
|
||||
if( p->sParse.aNode[p->i].jnFlags & JNODE_LABEL ) p->i++;
|
||||
p->i++;
|
||||
p->iRowid++;
|
||||
if( p->i<p->sParse.nNode ){
|
||||
if( p->i<p->iEnd ){
|
||||
u32 iUp = p->sParse.aUp[p->i];
|
||||
JsonNode *pUp = &p->sParse.aNode[iUp];
|
||||
p->eType = pUp->eType;
|
||||
@ -1597,8 +1573,9 @@ static void jsonEachComputePath(
|
||||
jsonPrintf(30, pStr, "[%d]", pUp->u.iKey);
|
||||
}else{
|
||||
assert( pUp->eType==JSON_OBJECT );
|
||||
if( pNode->eType>=JSON_ARRAY ) pNode--;
|
||||
if( (pNode->jnFlags & JNODE_LABEL)==0 ) pNode--;
|
||||
assert( pNode->eType==JSON_STRING );
|
||||
assert( pNode->jnFlags & JNODE_LABEL );
|
||||
jsonPrintf(pNode->n+1, pStr, ".%.*s", pNode->n-2, pNode->u.zJContent+1);
|
||||
}
|
||||
}
|
||||
@ -1629,27 +1606,28 @@ static int jsonEachColumn(
|
||||
break;
|
||||
}
|
||||
case JEACH_VALUE: {
|
||||
if( p->eType==JSON_OBJECT && p->i>0 ) pThis++;
|
||||
if( pThis->jnFlags & JNODE_LABEL ) pThis++;
|
||||
jsonReturn(pThis, ctx, 0);
|
||||
break;
|
||||
}
|
||||
case JEACH_TYPE: {
|
||||
if( p->eType==JSON_OBJECT && p->i>0 ) pThis++;
|
||||
if( pThis->jnFlags & JNODE_LABEL ) pThis++;
|
||||
sqlite3_result_text(ctx, jsonType[pThis->eType], -1, SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
case JEACH_ATOM: {
|
||||
if( p->eType==JSON_OBJECT && p->i>0 ) pThis++;
|
||||
if( pThis->jnFlags & JNODE_LABEL ) pThis++;
|
||||
if( pThis->eType>=JSON_ARRAY ) break;
|
||||
jsonReturn(pThis, ctx, 0);
|
||||
break;
|
||||
}
|
||||
case JEACH_ID: {
|
||||
sqlite3_result_int64(ctx, (sqlite3_int64)p->i + (p->eType==JSON_OBJECT));
|
||||
sqlite3_result_int64(ctx,
|
||||
(sqlite3_int64)p->i + ((pThis->jnFlags & JNODE_LABEL)!=0));
|
||||
break;
|
||||
}
|
||||
case JEACH_PARENT: {
|
||||
if( p->i>0 && p->bRecursive ){
|
||||
if( p->i>p->iBegin && p->bRecursive ){
|
||||
sqlite3_result_int64(ctx, (sqlite3_int64)p->sParse.aUp[p->i]);
|
||||
}
|
||||
break;
|
||||
@ -1660,8 +1638,8 @@ static int jsonEachColumn(
|
||||
if( p->bRecursive ){
|
||||
jsonEachComputePath(p, &x, p->i);
|
||||
}else{
|
||||
if( p->zPath ){
|
||||
jsonAppendRaw(&x, p->zPath, (int)strlen(p->zPath));
|
||||
if( p->zRoot ){
|
||||
jsonAppendRaw(&x, p->zRoot, (int)strlen(p->zRoot));
|
||||
}else{
|
||||
jsonAppendChar(&x, '$');
|
||||
}
|
||||
@ -1675,18 +1653,20 @@ static int jsonEachColumn(
|
||||
break;
|
||||
}
|
||||
case JEACH_PATH: {
|
||||
const char *zPath = p->zPath;
|
||||
if( zPath==0 ){
|
||||
if( p->bRecursive ){
|
||||
JsonString x;
|
||||
jsonInit(&x, ctx);
|
||||
jsonEachComputePath(p, &x, p->sParse.aUp[p->i]);
|
||||
jsonResult(&x);
|
||||
break;
|
||||
}
|
||||
zPath = "$";
|
||||
if( p->bRecursive ){
|
||||
JsonString x;
|
||||
jsonInit(&x, ctx);
|
||||
jsonEachComputePath(p, &x, p->sParse.aUp[p->i]);
|
||||
jsonResult(&x);
|
||||
break;
|
||||
}
|
||||
sqlite3_result_text(ctx, zPath, -1, SQLITE_STATIC);
|
||||
/* For json_each() path and root are the same so fall through
|
||||
** into the root case */
|
||||
}
|
||||
case JEACH_ROOT: {
|
||||
const char *zRoot = p->zRoot;
|
||||
if( zRoot==0 ) zRoot = "$";
|
||||
sqlite3_result_text(ctx, zRoot, -1, SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@ -1707,7 +1687,7 @@ static int jsonEachRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
||||
|
||||
/* The query strategy is to look for an equality constraint on the json
|
||||
** column. Without such a constraint, the table cannot operate. idxNum is
|
||||
** 1 if the constraint is found, 3 if the constraint and zPath are found,
|
||||
** 1 if the constraint is found, 3 if the constraint and zRoot are found,
|
||||
** and 0 otherwise.
|
||||
*/
|
||||
static int jsonEachBestIndex(
|
||||
@ -1716,7 +1696,7 @@ static int jsonEachBestIndex(
|
||||
){
|
||||
int i;
|
||||
int jsonIdx = -1;
|
||||
int pathIdx = -1;
|
||||
int rootIdx = -1;
|
||||
const struct sqlite3_index_constraint *pConstraint;
|
||||
|
||||
UNUSED_PARAM(tab);
|
||||
@ -1726,7 +1706,7 @@ static int jsonEachBestIndex(
|
||||
if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
|
||||
switch( pConstraint->iColumn ){
|
||||
case JEACH_JSON: jsonIdx = i; break;
|
||||
case JEACH_PATH: pathIdx = i; break;
|
||||
case JEACH_ROOT: rootIdx = i; break;
|
||||
default: /* no-op */ break;
|
||||
}
|
||||
}
|
||||
@ -1737,11 +1717,11 @@ static int jsonEachBestIndex(
|
||||
pIdxInfo->estimatedCost = 1.0;
|
||||
pIdxInfo->aConstraintUsage[jsonIdx].argvIndex = 1;
|
||||
pIdxInfo->aConstraintUsage[jsonIdx].omit = 1;
|
||||
if( pathIdx<0 ){
|
||||
if( rootIdx<0 ){
|
||||
pIdxInfo->idxNum = 1;
|
||||
}else{
|
||||
pIdxInfo->aConstraintUsage[pathIdx].argvIndex = 2;
|
||||
pIdxInfo->aConstraintUsage[pathIdx].omit = 1;
|
||||
pIdxInfo->aConstraintUsage[rootIdx].argvIndex = 2;
|
||||
pIdxInfo->aConstraintUsage[rootIdx].omit = 1;
|
||||
pIdxInfo->idxNum = 3;
|
||||
}
|
||||
}
|
||||
@ -1756,7 +1736,7 @@ static int jsonEachFilter(
|
||||
){
|
||||
JsonEachCursor *p = (JsonEachCursor*)cur;
|
||||
const char *z;
|
||||
const char *zPath;
|
||||
const char *zRoot = 0;
|
||||
sqlite3_int64 n;
|
||||
|
||||
UNUSED_PARAM(idxStr);
|
||||
@ -1766,11 +1746,11 @@ static int jsonEachFilter(
|
||||
z = (const char*)sqlite3_value_text(argv[0]);
|
||||
if( z==0 ) return SQLITE_OK;
|
||||
if( idxNum&2 ){
|
||||
zPath = (const char*)sqlite3_value_text(argv[1]);
|
||||
if( zPath==0 ) return SQLITE_OK;
|
||||
if( zPath[0]!='$' ){
|
||||
zRoot = (const char*)sqlite3_value_text(argv[1]);
|
||||
if( zRoot==0 ) return SQLITE_OK;
|
||||
if( zRoot[0]!='$' ){
|
||||
sqlite3_free(cur->pVtab->zErrMsg);
|
||||
cur->pVtab->zErrMsg = jsonPathSyntaxError(zPath);
|
||||
cur->pVtab->zErrMsg = jsonPathSyntaxError(zRoot);
|
||||
return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
@ -1794,12 +1774,11 @@ static int jsonEachFilter(
|
||||
JsonNode *pNode;
|
||||
if( idxNum==3 ){
|
||||
const char *zErr = 0;
|
||||
p->bRecursive = 0;
|
||||
n = sqlite3_value_bytes(argv[1]);
|
||||
p->zPath = sqlite3_malloc64( n+1 );
|
||||
if( p->zPath==0 ) return SQLITE_NOMEM;
|
||||
memcpy(p->zPath, zPath, (size_t)n+1);
|
||||
pNode = jsonLookupStep(&p->sParse, 0, p->zPath+1, 0, &zErr);
|
||||
p->zRoot = sqlite3_malloc64( n+1 );
|
||||
if( p->zRoot==0 ) return SQLITE_NOMEM;
|
||||
memcpy(p->zRoot, zRoot, (size_t)n+1);
|
||||
pNode = jsonLookupStep(&p->sParse, 0, p->zRoot+1, 0, &zErr);
|
||||
if( p->sParse.nErr ){
|
||||
sqlite3_free(cur->pVtab->zErrMsg);
|
||||
cur->pVtab->zErrMsg = jsonPathSyntaxError(zErr);
|
||||
@ -1811,12 +1790,18 @@ static int jsonEachFilter(
|
||||
}else{
|
||||
pNode = p->sParse.aNode;
|
||||
}
|
||||
p->i = (int)(pNode - p->sParse.aNode);
|
||||
p->iBegin = p->i = (int)(pNode - p->sParse.aNode);
|
||||
p->eType = pNode->eType;
|
||||
if( p->eType>=JSON_ARRAY ){
|
||||
pNode->u.iKey = 0;
|
||||
p->iEnd = p->i + pNode->n + 1;
|
||||
if( !p->bRecursive ) p->i++;
|
||||
if( p->bRecursive ){
|
||||
if( p->i>0 && (p->sParse.aNode[p->i-1].jnFlags & JNODE_LABEL)!=0 ){
|
||||
p->i--;
|
||||
}
|
||||
}else{
|
||||
p->i++;
|
||||
}
|
||||
}else{
|
||||
p->iEnd = p->i+1;
|
||||
}
|
||||
@ -1901,6 +1886,7 @@ int sqlite3_json_init(
|
||||
int flag;
|
||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
|
||||
} aFunc[] = {
|
||||
{ "json", 1, 0, jsonRemoveFunc },
|
||||
{ "json_array", -1, 0, jsonArrayFunc },
|
||||
{ "json_array_length", 1, 0, jsonArrayLengthFunc },
|
||||
{ "json_array_length", 2, 0, jsonArrayLengthFunc },
|
||||
@ -1918,7 +1904,6 @@ int sqlite3_json_init(
|
||||
/* DEBUG and TESTING functions */
|
||||
{ "json_parse", 1, 0, jsonParseFunc },
|
||||
{ "json_test1", 1, 0, jsonTest1Func },
|
||||
{ "json_nodecount", 1, 0, jsonNodeCountFunc },
|
||||
#endif
|
||||
};
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
|
48
main.mk
48
main.mk
@ -47,6 +47,7 @@
|
||||
TCCX = $(TCC) $(OPTS) -I. -I$(TOP)/src -I$(TOP)
|
||||
TCCX += -I$(TOP)/ext/rtree -I$(TOP)/ext/icu -I$(TOP)/ext/fts3
|
||||
TCCX += -I$(TOP)/ext/async -I$(TOP)/ext/userauth
|
||||
TCCX += -I$(TOP)/ext/fts5
|
||||
|
||||
# Object files for the SQLite library.
|
||||
#
|
||||
@ -230,6 +231,29 @@ SRC += \
|
||||
$(TOP)/ext/rbu/sqlite3rbu.h
|
||||
|
||||
|
||||
# FTS5 things
|
||||
#
|
||||
FTS5_HDR = \
|
||||
$(TOP)/ext/fts5/fts5.h \
|
||||
$(TOP)/ext/fts5/fts5Int.h \
|
||||
fts5parse.h
|
||||
|
||||
FTS5_SRC = \
|
||||
$(TOP)/ext/fts5/fts5_aux.c \
|
||||
$(TOP)/ext/fts5/fts5_buffer.c \
|
||||
$(TOP)/ext/fts5/fts5_main.c \
|
||||
$(TOP)/ext/fts5/fts5_config.c \
|
||||
$(TOP)/ext/fts5/fts5_expr.c \
|
||||
$(TOP)/ext/fts5/fts5_hash.c \
|
||||
$(TOP)/ext/fts5/fts5_index.c \
|
||||
fts5parse.c \
|
||||
$(TOP)/ext/fts5/fts5_storage.c \
|
||||
$(TOP)/ext/fts5/fts5_tokenize.c \
|
||||
$(TOP)/ext/fts5/fts5_unicode2.c \
|
||||
$(TOP)/ext/fts5/fts5_varint.c \
|
||||
$(TOP)/ext/fts5/fts5_vocab.c \
|
||||
|
||||
|
||||
# Generated source code files
|
||||
#
|
||||
SRC += \
|
||||
@ -558,7 +582,7 @@ parse.h: parse.c
|
||||
parse.c: $(TOP)/src/parse.y lemon $(TOP)/addopcodes.awk
|
||||
cp $(TOP)/src/parse.y .
|
||||
rm -f parse.h
|
||||
./lemon $(OPTS) parse.y
|
||||
./lemon -s $(OPTS) parse.y
|
||||
mv parse.h parse.h.temp
|
||||
$(NAWK) -f $(TOP)/addopcodes.awk parse.h.temp >parse.h
|
||||
|
||||
@ -636,25 +660,6 @@ fts3_write.o: $(TOP)/ext/fts3/fts3_write.c $(HDR) $(EXTHDR)
|
||||
rtree.o: $(TOP)/ext/rtree/rtree.c $(HDR) $(EXTHDR)
|
||||
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/rtree/rtree.c
|
||||
|
||||
# FTS5 things
|
||||
#
|
||||
FTS5_SRC = \
|
||||
$(TOP)/ext/fts5/fts5.h \
|
||||
$(TOP)/ext/fts5/fts5Int.h \
|
||||
$(TOP)/ext/fts5/fts5_aux.c \
|
||||
$(TOP)/ext/fts5/fts5_buffer.c \
|
||||
$(TOP)/ext/fts5/fts5_main.c \
|
||||
$(TOP)/ext/fts5/fts5_config.c \
|
||||
$(TOP)/ext/fts5/fts5_expr.c \
|
||||
$(TOP)/ext/fts5/fts5_hash.c \
|
||||
$(TOP)/ext/fts5/fts5_index.c \
|
||||
fts5parse.c fts5parse.h \
|
||||
$(TOP)/ext/fts5/fts5_storage.c \
|
||||
$(TOP)/ext/fts5/fts5_tokenize.c \
|
||||
$(TOP)/ext/fts5/fts5_unicode2.c \
|
||||
$(TOP)/ext/fts5/fts5_varint.c \
|
||||
$(TOP)/ext/fts5/fts5_vocab.c \
|
||||
|
||||
fts5parse.c: $(TOP)/ext/fts5/fts5parse.y lemon
|
||||
cp $(TOP)/ext/fts5/fts5parse.y .
|
||||
rm -f fts5parse.h
|
||||
@ -662,11 +667,10 @@ fts5parse.c: $(TOP)/ext/fts5/fts5parse.y lemon
|
||||
|
||||
fts5parse.h: fts5parse.c
|
||||
|
||||
fts5.c: $(FTS5_SRC)
|
||||
fts5.c: $(FTS5_SRC) $(FTS5_HDR)
|
||||
tclsh $(TOP)/ext/fts5/tool/mkfts5c.tcl
|
||||
cp $(TOP)/ext/fts5/fts5.h .
|
||||
|
||||
|
||||
userauth.o: $(TOP)/ext/userauth/userauth.c $(HDR) $(EXTHDR)
|
||||
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/userauth/userauth.c
|
||||
|
||||
|
207
manifest
207
manifest
@ -1,5 +1,5 @@
|
||||
C Merge\senhancements\sfrom\strunk.
|
||||
D 2015-09-03T14:39:33.829
|
||||
D 2015-09-15T17:31:47.812
|
||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||
F Makefile.in f85066ce844a28b671aaeeff320921cd0ce36239
|
||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||
@ -104,63 +104,64 @@ F ext/fts3/unicode/CaseFolding.txt 8c678ca52ecc95e16bc7afc2dbf6fc9ffa05db8c
|
||||
F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7
|
||||
F ext/fts3/unicode/mkunicode.tcl 95cf7ec186e48d4985e433ff8a1c89090a774252
|
||||
F ext/fts3/unicode/parseunicode.tcl da577d1384810fb4e2b209bf3313074353193e95
|
||||
F ext/fts5/extract_api_docs.tcl 06583c935f89075ea0b32f85efa5dd7619fcbd03
|
||||
F ext/fts5/fts5.h 1950ec0544de667a24c1d8af9b2fde5db7db3bc9
|
||||
F ext/fts5/fts5Int.h 45f2ceb3c030f70e2cc4c199e9f700c2f2367f77
|
||||
F ext/fts5/fts5_aux.c 044cb176a815f4388308738437f6e130aa384fb0
|
||||
F ext/fts5/fts5_buffer.c 80f9ba4431848cb857e3d2158f5280093dcd8015
|
||||
F ext/fts5/fts5_config.c fdfa63ae8e527ecfaa50f94063c610429cc887cf
|
||||
F ext/fts5/fts5_expr.c d075d36c84975a1cfcf070442d28e28027b61c25
|
||||
F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0
|
||||
F ext/fts5/fts5.h f04659e0df5af83731b102189a32280f74f4a6bc
|
||||
F ext/fts5/fts5Int.h 666aba8432940a8449a3bd4636e898fe906ed95d
|
||||
F ext/fts5/fts5_aux.c 7a307760a9c57c750d043188ec0bad59f5b5ec7e
|
||||
F ext/fts5/fts5_buffer.c 64dcaf36a3ebda9e84b7c3b8788887ec325e12a4
|
||||
F ext/fts5/fts5_config.c 57ee5fe71578cb494574fc0e6e51acb9a22a8695
|
||||
F ext/fts5/fts5_expr.c 667faaf14a69a5683ac383acdc8d942cf32c3f93
|
||||
F ext/fts5/fts5_hash.c 4bf4b99708848357b8a2b5819e509eb6d3df9246
|
||||
F ext/fts5/fts5_index.c 076c4995bf06a6d1559a6e31f9a86b90f2105374
|
||||
F ext/fts5/fts5_main.c fc47ad734dfb55765b7542a345cee981170e7caa
|
||||
F ext/fts5/fts5_storage.c 22ec9b5d35a39e2b5b65daf4ba7cd47fbb2d0df5
|
||||
F ext/fts5/fts5_tcl.c 96a3b9e982c4a64a242eefd752fa6669cd405a67
|
||||
F ext/fts5/fts5_test_mi.c 80a9e86fb4c5b6b58f8fefac05e9b96d1a6574e1
|
||||
F ext/fts5/fts5_tokenize.c 2836f6728bd74c7efac7487f5d9c27ca3e1b509c
|
||||
F ext/fts5/fts5_index.c 4fdbc0a321e3a1d73741a623d7aea4db78d6a86d
|
||||
F ext/fts5/fts5_main.c 3fa906f6c0177caf8f82862bc70f37b28bb3305c
|
||||
F ext/fts5/fts5_storage.c 120f7b143688b5b7710dacbd48cff211609b8059
|
||||
F ext/fts5/fts5_tcl.c 6da58d6e8f42a93c4486b5ba9b187a7f995dee37
|
||||
F ext/fts5/fts5_test_mi.c e96be827aa8f571031e65e481251dc1981d608bf
|
||||
F ext/fts5/fts5_tokenize.c f380f46f341af9c9a9908e1aade685ba1eaa157a
|
||||
F ext/fts5/fts5_unicode2.c 78273fbd588d1d9bd0a7e4e0ccc9207348bae33c
|
||||
F ext/fts5/fts5_varint.c 3f86ce09cab152e3d45490d7586b7ed2e40c13f1
|
||||
F ext/fts5/fts5_vocab.c 4622e0b7d84a488a1585aaa56eb214ee67a988bc
|
||||
F ext/fts5/fts5parse.y 833db1101b78c0c47686ab1b84918e38c36e9452
|
||||
F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba
|
||||
F ext/fts5/test/fts5_common.tcl 3338968de1880ca12b0451ae8f9b8b12d14e0ba7
|
||||
F ext/fts5/test/fts5aa.test c6e680a0d1b6c2616a382f1006d5d91eca697bd0
|
||||
F ext/fts5/test/fts5_common.tcl b6e6a40ef5d069c8e86ca4fbad491e1195485dbc
|
||||
F ext/fts5/test/fts5aa.test 4804f237005bb4ba8ea4a76120d8011ebcb5d611
|
||||
F ext/fts5/test/fts5ab.test 6fe3a56731d15978afbb74ae51b355fc9310f2ad
|
||||
F ext/fts5/test/fts5ac.test 9737992d08c56bfd4803e933744d2d764e23795c
|
||||
F ext/fts5/test/fts5ad.test b2edee8b7de0c21d2c88f8a18c195034aad6952d
|
||||
F ext/fts5/test/fts5ad.test e3dfb150fce971b4fd832498c29f56924d451b63
|
||||
F ext/fts5/test/fts5ae.test 0a9984fc3479f89f8c63d9848d6ed0c465dfcebe
|
||||
F ext/fts5/test/fts5af.test c2501ec2b61d6b179c305f5d2b8782ab3d4f832a
|
||||
F ext/fts5/test/fts5ag.test ec3e119b728196620a31507ef503c455a7a73505
|
||||
F ext/fts5/test/fts5ah.test b9e78fa986a7bd564ebadfb244de02c84d7ac3ae
|
||||
F ext/fts5/test/fts5ah.test e592c4978622dbc4de552cd0f9395df60ac5d54c
|
||||
F ext/fts5/test/fts5ai.test f20e53bbf0c55bc596f1fd47f2740dae028b8f37
|
||||
F ext/fts5/test/fts5aj.test 05b569f5c16ea3098fb1984eec5cf50dbdaae5d8
|
||||
F ext/fts5/test/fts5ak.test 7b8c5df96df599293f920b7e5521ebc79f647592
|
||||
F ext/fts5/test/fts5al.test 440d77c0b39ba73bad2ceb8986c2bb1093570735
|
||||
F ext/fts5/test/fts5al.test 5c79525671862861906fa0a848da462a8473eafb
|
||||
F ext/fts5/test/fts5alter.test 6022c61467a82aa11c70822ccad22b328dcf0d04
|
||||
F ext/fts5/test/fts5auto.test caa5bcf917db11944655a2a9bd38c67c520376ca
|
||||
F ext/fts5/test/fts5aux.test 8c687c948cc98e9a94be014df7d518acc1b3b74f
|
||||
F ext/fts5/test/fts5auxdata.test 141a7cbffcceb1bd2799b4b29c183ff8780d586e
|
||||
F ext/fts5/test/fts5bigpl.test 04ee0d7eebbebf17c31f5a0b5c5f9494eac3a0cb
|
||||
F ext/fts5/test/fts5columnsize.test 97dc6bd66c91009d00407aa078dd5e9e8eb22f99
|
||||
F ext/fts5/test/fts5columnsize.test a8cfef21ffa1c264b9f670a7d94eeaccb5341c07
|
||||
F ext/fts5/test/fts5config.test ad2ff42ddc856aed2d05bf89dc1c578c8a39ea3b
|
||||
F ext/fts5/test/fts5content.test 9a952c95518a14182dc3b59e3c8fa71cda82a4e1
|
||||
F ext/fts5/test/fts5corrupt.test 928c9c91d40690d301f943a7ed0ffc19e0d0e7b6
|
||||
F ext/fts5/test/fts5corrupt2.test 1a830ccd6dbe1b601c7e3f5bbc1cf77bd8c8803b
|
||||
F ext/fts5/test/fts5corrupt3.test 1ccf575f5126e79f9fec7979fd02a1f40a076be3
|
||||
F ext/fts5/test/fts5dlidx.test 59b80bbe34169a082c575d9c26f0a7019a7b79c1
|
||||
F ext/fts5/test/fts5corrupt.test c2ad090192708150d50d961278df10ae7a4b8b62
|
||||
F ext/fts5/test/fts5corrupt2.test 26c0a39dd9ff73207e6229f83b50b21d37c7658c
|
||||
F ext/fts5/test/fts5corrupt3.test a1429635c30f922079189e35b2b631eb7463a2fb
|
||||
F ext/fts5/test/fts5dlidx.test ecba5e62ea8b26c33829961602069c546228046d
|
||||
F ext/fts5/test/fts5doclist.test 8edb5b57e5f144030ed74ec00ef6fa4294fed79b
|
||||
F ext/fts5/test/fts5ea.test 451bb37310ee6df8ef72e4354fda5621b3b51448
|
||||
F ext/fts5/test/fts5eb.test 46f49497edc25ef3b2bff9fb6d75b6d201e2b39e
|
||||
F ext/fts5/test/fts5ea.test b01e3a18cdfabbff8104a96a5242a06a68a998a0
|
||||
F ext/fts5/test/fts5eb.test 3e5869af2008cbc4ad03a175a0b6f6e58134cd43
|
||||
F ext/fts5/test/fts5fault1.test 7a562367cb4a735b57b410dbdb62dcc8d971faec
|
||||
F ext/fts5/test/fts5fault2.test 28c36c843bb39ae855ba79827417ecc37f114341
|
||||
F ext/fts5/test/fts5fault3.test d6e9577d4312e331a913c72931bf131704efc8f3
|
||||
F ext/fts5/test/fts5fault4.test 762991d526ee67c2b374351a17248097ea38bee7
|
||||
F ext/fts5/test/fts5fault5.test 54da9fd4c3434a1d4f6abdcb6469299d91cf5875
|
||||
F ext/fts5/test/fts5fault6.test 234dc6355f8d3f8b5be2763f30699d770247c215
|
||||
F ext/fts5/test/fts5fault6.test 97bce1a36b7a64e3203fea504ae8e5cfd5ada423
|
||||
F ext/fts5/test/fts5fault7.test f62ed4d98f137eb03f1db94d1fa41b17a771d971
|
||||
F ext/fts5/test/fts5full.test 6f6143af0c6700501d9fd597189dfab1555bb741
|
||||
F ext/fts5/test/fts5hash.test 42eb066f667e9a389a63437cb7038c51974d4fc6
|
||||
F ext/fts5/test/fts5integrity.test 29f41d2c7126c6122fbb5d54e556506456876145
|
||||
F ext/fts5/test/fts5matchinfo.test ee6e7b130096c708c12049fa9c1ceb628954c4f9
|
||||
F ext/fts5/test/fts5matchinfo.test 2163b0013e824bba65499da9e34ea4da41349cc2
|
||||
F ext/fts5/test/fts5merge.test 8f3cdba2ec9c5e7e568246e81b700ad37f764367
|
||||
F ext/fts5/test/fts5near.test b214cddb1c1f1bddf45c75af768f20145f7e71cc
|
||||
F ext/fts5/test/fts5optimize.test 42741e7c085ee0a1276140a752d4407d97c2c9f5
|
||||
@ -171,17 +172,19 @@ F ext/fts5/test/fts5prefix.test 552a462f0e8595676611f41643de217fb4ac2808
|
||||
F ext/fts5/test/fts5rank.test 11dcebba31d822f7e99685b4ea2c2ae3ec0b16f1
|
||||
F ext/fts5/test/fts5rebuild.test 03935f617ace91ed23a6099c7c74d905227ff29b
|
||||
F ext/fts5/test/fts5restart.test c17728fdea26e7d0f617d22ad5b4b2862b994c17
|
||||
F ext/fts5/test/fts5rowid.test 6f9833b23b176dc4aa15b7fc02afeb2b220fd460
|
||||
F ext/fts5/test/fts5rowid.test 400384798349d658eaf06aefa1e364957d5d4821
|
||||
F ext/fts5/test/fts5simple.test f629e24a35a9f31cfb16c9920e8c2316e3d93e94
|
||||
F ext/fts5/test/fts5synonym.test cf88c0a56d5ea9591e3939ef1f6e294f7f2d0671
|
||||
F ext/fts5/test/fts5tokenizer.test ea4df698b35cc427ebf2ba22829d0e28386d8c89
|
||||
F ext/fts5/test/fts5unicode.test fbef8d8a3b4b88470536cc57604a82ca52e51841
|
||||
F ext/fts5/test/fts5unicode2.test c1dd890ba32b7609adba78e420faa847abe43b59
|
||||
F ext/fts5/test/fts5unicode3.test 35c3d02aa7acf7d43d8de3bfe32c15ba96e8928e
|
||||
F ext/fts5/test/fts5unindexed.test e9539d5b78c677315e7ed8ea911d4fd25437c680
|
||||
F ext/fts5/test/fts5version.test 205beb2a67d9496af64df959e6a19238f69b83e8
|
||||
F ext/fts5/test/fts5version.test 978f59541d8cef7e8591f8be2115ec5ccb863e2e
|
||||
F ext/fts5/test/fts5vocab.test cdf97b9678484e9bad5062edf9c9106e5c3b0c5c
|
||||
F ext/fts5/tool/loadfts5.tcl 95edf0b6b92a09f9ed85595038b1108127987556
|
||||
F ext/fts5/tool/loadfts5.tcl 58e90407cc5c2b1770460119488fd7c0090d4dd3
|
||||
F ext/fts5/tool/mkfts5c.tcl 5745072c7de346e18c7f491e4c3281fe8a1cfe51
|
||||
F ext/fts5/tool/showfts5.tcl fb62e8eae6d862afdd22f367e286fb886d5e1ab6
|
||||
F ext/fts5/tool/showfts5.tcl 9eaf6c3df352f98a2ab5ce1921dd94128ab1381d
|
||||
F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
|
||||
F ext/icu/icu.c b2732aef0b076e4276d9b39b5a33cec7a05e1413
|
||||
F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
|
||||
@ -192,7 +195,7 @@ F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2
|
||||
F ext/misc/fileio.c d4171c815d6543a9edef8308aab2951413cd8d0f
|
||||
F ext/misc/fuzzer.c 4c84635c71c26cfa7c2e5848cf49fe2d2cfcd767
|
||||
F ext/misc/ieee754.c b0362167289170627659e84173f5d2e8fee8566e
|
||||
F ext/misc/json1.c bd51e8c1e8ce580e6f21493bd8e94ed5aca3d777
|
||||
F ext/misc/json1.c f35d00fbd79a7e23af18d7630a2fcf22dce3692b
|
||||
F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342
|
||||
F ext/misc/percentile.c bcbee3c061b884eccb80e21651daaae8e1e43c63
|
||||
F ext/misc/regexp.c af92cdaa5058fcec1451e49becc7ba44dba023dc
|
||||
@ -258,7 +261,7 @@ F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
|
||||
F main.mk 8da13ed011a7ae19450b7554910ff4afa3bd22b7
|
||||
F main.mk 58eb74de702467c3b71cdf06f213cefe7f5ce544
|
||||
F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
|
||||
F mkopcodeh.awk 0e7f04a8eb90f92259e47d80110e4e98d7ce337a
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
@ -273,37 +276,37 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
|
||||
F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786
|
||||
F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
|
||||
F src/alter.c 4911e1f18fc11b60edbc6410643e938762969a6a
|
||||
F src/analyze.c e4ad1495090d6b7bf58b927e1267fc211ad5e7da
|
||||
F src/analyze.c 4c308880cf53c558070cb8513bdff4ffb1a38a77
|
||||
F src/attach.c e944d0052b577703b9b83aac1638452ff42a8395
|
||||
F src/auth.c b56c78ebe40a2110fd361379f7e8162d23f92240
|
||||
F src/backup.c 4d9134dc988a87838c06056c89c0e8c4700a0452
|
||||
F src/bitvec.c d1f21d7d91690747881f03940584f4cc548c9d3d
|
||||
F src/btmutex.c 45a968cc85afed9b5e6cf55bf1f42f8d18107f79
|
||||
F src/btree.c 3d759d9943ee2ce422a2bb1f7810e7447a907e97
|
||||
F src/btree.h 6310645beddbd3eef89fa9b6d1062065976a93c8
|
||||
F src/btree.c 9d19d3f8dcddd1b644b72f1c6613d44692a7a846
|
||||
F src/btree.h 9cc758f0f1e0002049d9eb0232f9783f21f25ac5
|
||||
F src/btreeInt.h 8177c9ab90d772d6d2c6c517e05bed774b7c92c0
|
||||
F src/build.c 6c3a8a9b21402a6be98126f7d86b76527e68ca67
|
||||
F src/build.c 8a86f4203ac8a9ac0734f242a96f043edffb6018
|
||||
F src/callback.c 7b44ce59674338ad48b0e84e7b72f935ea4f68b0
|
||||
F src/complete.c addcd8160b081131005d5bc2d34adf20c1c5c92f
|
||||
F src/ctime.c 5a0b735dc95604766f5dac73973658eef782ee8b
|
||||
F src/date.c 8ec787fed4929d8ccdf6b1bc360fccc3e1d2ca58
|
||||
F src/dbstat.c f402e77e25089c6003d0c60b3233b9b3947d599a
|
||||
F src/delete.c 5b4835982c31f12b256dc4d814363a11e7ef34cf
|
||||
F src/expr.c 3783aa19150be01a2ae92e1eb429c84a2313c8fc
|
||||
F src/date.c fb1c99172017dcc8e237339132c91a21a0788584
|
||||
F src/dbstat.c e637e7a7ff40ef32132a418c6fdf1cfb63aa27c7
|
||||
F src/delete.c 5ab483f15aaf202290227d5dd1f10a7ecce3d6b1
|
||||
F src/expr.c 572ebfeb5c848e8d32096c823f87baa1eb988391
|
||||
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
|
||||
F src/fkey.c 83e1baba999bed3144ea5a2143fc922edf51135f
|
||||
F src/func.c 824bea430d3a2b7dbc62806ad54da8fdb8ed9e3f
|
||||
F src/func.c ecdd69ec6a1e406f04cc73324be2ebbf6354197f
|
||||
F src/global.c 508e4087f7b41d688e4762dcf4d4fe28cfbc87f9
|
||||
F src/hash.c 4263fbc955f26c2e8cdc0cf214bc42435aa4e4f5
|
||||
F src/hash.h c8f3c31722cf3277d03713909761e152a5b81094
|
||||
F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08
|
||||
F src/insert.c bcff4a416177ed90faa8dba65f0266ddad2aeb76
|
||||
F src/insert.c 9748a37e058256eb2ead69f028ab85ebf203ad15
|
||||
F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d
|
||||
F src/legacy.c ba1863ea58c4c840335a84ec276fc2b25e22bc4e
|
||||
F src/lempar.c 92bafa308607dd985ca389a788cd9e0a2b608712
|
||||
F src/loadext.c dfcee8c7c032cd0fd55af3e0fc1fcfb01e426df2
|
||||
F src/lempar.c d344a95d60c24e2f490ee59db9784b1b17439012
|
||||
F src/loadext.c f0b66d28e377fd6c6d36cc9d92df1ff251ebee44
|
||||
F src/main.c e17fcffae4306a9b8334faf3bac80d7396850b54
|
||||
F src/malloc.c 021012e28a81ffdabf4c30ec3df6ce1f6cc93f1d
|
||||
F src/malloc.c 3a37ce6979a40f499d8cea9e9ab4e8517854d35d
|
||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||
F src/mem1.c abe6ee469b6c5a35c7f22bfeb9c9bac664a1c987
|
||||
F src/mem2.c f1940d9e91948dd6a908fbb9ce3835c36b5d83c3
|
||||
@ -311,11 +314,11 @@ F src/mem3.c 61c9d47b792908c532ca3a62b999cf21795c6534
|
||||
F src/mem5.c 61eeb90134f9a5be6c2e68d8daae7628b25953fb
|
||||
F src/memjournal.c 3eb2c0b51adbd869cb6a44780323f05fa904dc85
|
||||
F src/msvc.h d9ba56c6851227ab44b3f228a35f3f5772296495
|
||||
F src/mutex.c 529e95739f815300a33c73fd8a7d6bdf0c24bd18
|
||||
F src/mutex.c 8e45800ee78e0cd1f1f3fe8e398853307f4a085c
|
||||
F src/mutex.h 779d588e3b7756ec3ecf7d78cde1d84aba414f85
|
||||
F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4
|
||||
F src/mutex_unix.c b0d280089df0f49545f1318f45d61d07d2f674a8
|
||||
F src/mutex_w32.c b601f9e3073f7bd2c1f42a8c0ce59e42d6a08f85
|
||||
F src/mutex_unix.c 8cfa6e83c618d2fcae0fe63f4d2b5bb16b11a97a
|
||||
F src/mutex_w32.c 2e025e6642eaf27597403690980f560d1a91f62c
|
||||
F src/notify.c 9711a7575036f0d3040ba61bc6e217f13a9888e7
|
||||
F src/os.c 8fd25588eeba74068d41102d26810e216999b6c8
|
||||
F src/os.h 3e57a24e2794a94d3cf2342c6d9a884888cd96bf
|
||||
@ -324,25 +327,25 @@ F src/os_setup.h c9d4553b5aaa6f73391448b265b89bed0b890faa
|
||||
F src/os_unix.c 76f493ed71c4154338049dee1bf6e47f69c74a55
|
||||
F src/os_win.c 40b3af7a47eb1107d0d69e592bec345a3b7b798a
|
||||
F src/os_win.h eb7a47aa17b26b77eb97e4823f20a00b8bda12ca
|
||||
F src/pager.c aa916ca28606ccf4b6877dfc2b643ccbca86589f
|
||||
F src/pager.c 4784012f80b2197c61ff6eaf4f5c7026d93253fd
|
||||
F src/pager.h 6d435f563b3f7fcae4b84433b76a6ac2730036e2
|
||||
F src/parse.y f599aa5e871a493330d567ced93de696f61f48f7
|
||||
F src/pcache.c cde06aa50962595e412d497e22fd2e07878ba1f0
|
||||
F src/pcache.c 24be750c79272e0ca7b6e007bc94999700f3e5ef
|
||||
F src/pcache.h 9968603796240cdf83da7e7bef76edf90619cea9
|
||||
F src/pcache1.c b31af9dbc83b9c68e87d681b8453a9605f28e734
|
||||
F src/pcache1.c a0c0bb29f7bd720743a16a95eb5dedba3ade15bc
|
||||
F src/pragma.c d71b813e67bf03f3116b9dd5164fbfd81ec673a2
|
||||
F src/pragma.h 631a91c8b0e6ca8f051a1d8a4a0da4150e04620a
|
||||
F src/prepare.c 82e5db1013846a819f198336fed72c44c974e7b1
|
||||
F src/printf.c 0c4bcdd1c2e2521024f0a69cb5eb334f86b3652a
|
||||
F src/random.c ba2679f80ec82c4190062d756f22d0c358180696
|
||||
F src/resolve.c f2ef256786a6435efddd64a632fea89c8be62215
|
||||
F src/resolve.c 3126f7694b8ce0f97282d7dd3a5198b8fa18dce9
|
||||
F src/rowset.c eccf6af6d620aaa4579bd3b72c1b6395d9e9fa1e
|
||||
F src/select.c bb99c37242673405683992085a0fcff380b70575
|
||||
F src/shell.c bbe2bab590b7dd04dd8f9119d4473cb8c52906e3
|
||||
F src/sqlite.h.in 378bebc8fe6a88bade25e5f23b7e6123fdc64b00
|
||||
F src/shell.c 6332ef06db1390ef812cfdff1fc97b4fd76cdd42
|
||||
F src/sqlite.h.in 50f83145c6543000b7d27525ecaec59a23d8280b
|
||||
F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad
|
||||
F src/sqlite3ext.h f700e6a9dd1fdcccc9951ab022b366fb66b9e413
|
||||
F src/sqliteInt.h c452759fafe7d6ed9bbdfec19d327a2b77e85d2e
|
||||
F src/sqlite3ext.h 64350bf36833a56ad675e27392a913f417c5c308
|
||||
F src/sqliteInt.h b108aac991a28e4208f526df54f82995316c56ee
|
||||
F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46
|
||||
F src/status.c f266ad8a2892d659b74f0f50cb6a88b6e7c12179
|
||||
F src/table.c 51b46b2a62d1b3a959633d593b89bab5e2c9155e
|
||||
@ -365,7 +368,7 @@ F src/test_config.c fb2e5d354d9a077f5fbb261652eff4787deb104f
|
||||
F src/test_demovfs.c 0de72c2c89551629f58486fde5734b7d90758852
|
||||
F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc
|
||||
F src/test_fs.c ced436e3d4b8e4681328409b8081051ce614e28f
|
||||
F src/test_func.c f1ac201465472e76a73e2f3695c3553c63e7322a
|
||||
F src/test_func.c 0d9c25956152adefee8881c6fadc8354793764d0
|
||||
F src/test_hexio.c abfdecb6fa58c354623978efceb088ca18e379cd
|
||||
F src/test_init.c 66b33120ffe9cd853b5a905ec850d51151337b32
|
||||
F src/test_intarray.c 870124b95ec4c645d4eb84f15efb7133528fb1a5
|
||||
@ -393,31 +396,31 @@ F src/test_vfs.c 3b65d42e18b262805716bd96178c81da8f2d9283
|
||||
F src/test_vfstrace.c bab9594adc976cbe696ff3970728830b4c5ed698
|
||||
F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
|
||||
F src/threads.c 6bbcc9fe50c917864d48287b4792d46d6e873481
|
||||
F src/tokenize.c 57cb3720f53f84d811def2069c2b169b6be539a5
|
||||
F src/treeview.c 46036cbbceada0836833531b2d963edbca3d9cfa
|
||||
F src/tokenize.c 83c6ed569423a3af83a83973b444cf7123be33a6
|
||||
F src/treeview.c 154f0acc622fa3514de8777dcedf4c8a8802b4ce
|
||||
F src/trigger.c 322f23aad694e8f31d384dcfa386d52a48d3c52f
|
||||
F src/update.c 795fba8ebadeb194285b0a73a07f7ad5ae4d0410
|
||||
F src/update.c eb7ab3ff2928628692a4f14be397c95f4a681d97
|
||||
F src/utf.c fc6b889ba0779b7722634cdeaa25f1930d93820c
|
||||
F src/util.c fc612367108b74573c5fd13a85d0a23027f438bd
|
||||
F src/vacuum.c 2ddd5cad2a7b9cef7f9e431b8c7771634c6b1701
|
||||
F src/vdbe.c 9d85fa72a73f7f07789d76602f724be63e998ddc
|
||||
F src/vdbe.c 1704a79d60f9c316adac0cbce09cae1ffc4b6af8
|
||||
F src/vdbe.h efb7a8c1459e31f3ea4377824c6a7e4cb5068637
|
||||
F src/vdbeInt.h 7258d75fc2dad0bccdef14d7d8d2fd50fd1bf2d2
|
||||
F src/vdbeapi.c bda74ef4b5103d7b4a4be36f936d3cf2b56a7d6f
|
||||
F src/vdbeInt.h c1508eb4ee04d4db7bdfca5620cfa8ad632fdd62
|
||||
F src/vdbeapi.c 0d890f57caf143b114a95ce699e59af51359c508
|
||||
F src/vdbeaux.c 7dccd114c9ee5f801709daecee36464fe6ce71af
|
||||
F src/vdbeblob.c 4f2e8e075d238392df98c5e03a64342465b03f90
|
||||
F src/vdbemem.c ae38a0d35ae71cf604381a887c170466ba518090
|
||||
F src/vdbeblob.c 1d7b97115e7bbac4c318db416d2ca83fc779544a
|
||||
F src/vdbemem.c 19b3036aa4d676e7103b0fb5efd6327da455f915
|
||||
F src/vdbesort.c f5009e7a35e3065635d8918b9a31f498a499976b
|
||||
F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0
|
||||
F src/vtab.c 2ecfe020c10e0a0c7b078203fdba2fae844744bc
|
||||
F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb
|
||||
F src/wal.c 8cd07f1f99e1a81346db1c9da879bef6c6f97cf6
|
||||
F src/wal.c 18b0ed49830cf04fe2d68224b41838a73ac6cd24
|
||||
F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4
|
||||
F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba
|
||||
F src/where.c 6055bee2f4aff2934ea1965c25c4d1f6368dc0eb
|
||||
F src/whereInt.h 901c17c1e3c82745ad9b85b4471543fa59c980e9
|
||||
F src/wherecode.c 93fa794fcd1aba23454cc15bca2b617bca21ec0a
|
||||
F src/whereexpr.c 1a308d1ee5144890d21ea9cf70d49bc96a83432b
|
||||
F src/where.c c03895cdb2624a07e75c95c6889a32a15929a27a
|
||||
F src/whereInt.h 7892bb54cf9ca0ae5c7e6094491b94c9286dc647
|
||||
F src/wherecode.c c80f4c7866f78b298b81fdda60006d31125b6f68
|
||||
F src/whereexpr.c 2473e4350e30f9b55d1c6a8f66ca23c689f23f1d
|
||||
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
|
||||
F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd
|
||||
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
|
||||
@ -567,9 +570,10 @@ F test/date.test 42973251b9429f2c41b77eb98a7b0b0ba2d3b2c0
|
||||
F test/dbstatus.test 8de104bb5606f19537d23cd553b41349b5ab1204
|
||||
F test/dbstatus2.test 10418e62b3db5dca070f0c3eef3ea13946f339c2
|
||||
F test/default.test 0cb49b1c315a0d81c81d775e407f66906a2a604d
|
||||
F test/delete.test a065b05d2ebf60fd16639c579a4adfb7c381c701
|
||||
F test/delete.test e1bcdf8926234e27aac24b346ad83d3329ec8b6f
|
||||
F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa
|
||||
F test/delete3.test 555e84a00a99230b7d049d477a324a631126a6ab
|
||||
F test/delete4.test d9e7d553a939597b27d205b022d769469f361c1f
|
||||
F test/descidx1.test 6d03b44c8538fe0eb4924e19fba10cdd8f3c9240
|
||||
F test/descidx2.test 9f1a0c83fd57f8667c82310ca21b30a350888b5d
|
||||
F test/descidx3.test 09ddbe3f5295f482d2f8b687cf6db8bad7acd9a2
|
||||
@ -581,20 +585,20 @@ F test/e_blobclose.test df756753f571bc30e42e3a6cba2807576e49e716
|
||||
F test/e_blobopen.test 234f960d90235a9b51ec3ca1e062e8541dd558d8
|
||||
F test/e_blobwrite.test 615b405f29feb2cfb5a1f03dab7933258294fa26
|
||||
F test/e_changes.test fd66105385153dbf21fdb35eb8ef6c3e1eade579
|
||||
F test/e_createtable.test c7e67b49e6cf92473c8fb30ab26143e9e2128cf7
|
||||
F test/e_delete.test d5186e2f5478b659f16a2c8b66c09892823e542a
|
||||
F test/e_createtable.test d4c6059d44dcd4b636de9aae322766062b471844
|
||||
F test/e_delete.test ab39084f26ae1f033c940b70ebdbbd523dc4962e
|
||||
F test/e_droptrigger.test 3cd080807622c13e5bbb61fc9a57bd7754da2412
|
||||
F test/e_dropview.test 0c9f7f60989164a70a67a9d9c26d1083bc808306
|
||||
F test/e_expr.test 8f5fdd7261e2d746813b0c6a1c0e34824ad3c5ad
|
||||
F test/e_expr.test 03a84a6fa9bd3472112d6bd4599f5269f5f74803
|
||||
F test/e_fkey.test a1783fe1f759e1990e6a11adfcf0702dac4d0707
|
||||
F test/e_fts3.test 5c02288842e4f941896fd44afdef564dd5fc1459
|
||||
F test/e_insert.test 0e63edc037afe738bb81a626a676811ed7862c90
|
||||
F test/e_reindex.test 57d439f6c644befc8274ac93cf2f5449cf2736c1
|
||||
F test/e_resolve.test dcce9308fb13b934ce29591105d031d3e14fbba6
|
||||
F test/e_insert.test 3de217e95094d3d165992a6de1164bbc4bd92dc7
|
||||
F test/e_reindex.test 2bebf7b393e519198b7c654407221cf171a439b8
|
||||
F test/e_resolve.test a61751c368b109db73df0f20fc75fb47e166b1d8
|
||||
F test/e_select.test 52692ff3849541e828ad4661fe3773a9b8711763
|
||||
F test/e_select2.test aceb80ab927d46fba5ce7586ebabf23e2bb0604f
|
||||
F test/e_totalchanges.test b12ee5809d3e63aeb83238dd501a7bca7fd72c10
|
||||
F test/e_update.test 312cb8f5ccfe41515a6bb092f8ea562a9bd54d52
|
||||
F test/e_update.test f46c2554d915c9197548681e8d8c33a267e84528
|
||||
F test/e_uri.test 5ae33760fb2039c61aa2d90886f1664664173585
|
||||
F test/e_vacuum.test 5bfbdc21b65c0abf24398d0ba31dc88d93ca77a9
|
||||
F test/e_wal.test ae9a593207a77d711443ee69ffe081fda9243625
|
||||
@ -622,7 +626,7 @@ F test/fkey1.test de5b287f6a480b36bd51e8debcf48168e26e4ed2
|
||||
F test/fkey2.test f3d27ecba480a348c328965d154214719bb158a9
|
||||
F test/fkey3.test 76d475c80b84ee7a5d062e56ccb6ea68882e2b49
|
||||
F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d
|
||||
F test/fkey5.test 56bcb5a6e8b725b17febc267fb041a6695e86853
|
||||
F test/fkey5.test 5a373303f201ac03c22ba1ef17a733d3f56e611a
|
||||
F test/fkey6.test abb59f866c1b44926fd02d1fdd217d831fe04f48
|
||||
F test/fkey7.test 72e915890ee4a005daaf3002cb208e8fe973ac13
|
||||
F test/fkey8.test 8f08203458321e6c19a263829de4cfc936274ab0
|
||||
@ -773,14 +777,15 @@ F test/incrvacuum.test d2a6ddf5e429720b5fe502766af747915ccf6c32
|
||||
F test/incrvacuum2.test 676c41428765d58f1da7dbe659ef27726d3d30ac
|
||||
F test/incrvacuum3.test 75256fb1377e7c39ef2de62bfc42bbff67be295a
|
||||
F test/incrvacuum_ioerr.test 6ae2f783424e47a0033304808fe27789cf93e635
|
||||
F test/index.test 4d990005a67a36984e4f1a5f1bdccea8d08da4ee
|
||||
F test/index2.test ee83c6b5e3173a3d7137140d945d9a5d4fdfb9d6
|
||||
F test/index3.test b6ec456cf3b81d9a32123fe7e449bde434db338b
|
||||
F test/index.test fe3c7a1aad82af92623747e9c3f3aa94ccd51238
|
||||
F test/index2.test f835d5e13ca163bd78c4459ca15fd2e4ed487407
|
||||
F test/index3.test fa3e49bbaa4f38091c9c742e36a1abe67c4ef1fc
|
||||
F test/index4.test ab92e736d5946840236cd61ac3191f91a7856bf6
|
||||
F test/index5.test 8621491915800ec274609e42e02a97d67e9b13e7
|
||||
F test/index6.test 7102ec371414c42dfb1d5ca37eb4519aa9edc23a
|
||||
F test/index7.test 9c6765a74fc3fcde7aebc5b3bd40d98df14a527c
|
||||
F test/indexedby.test 5f527a78bae74c61b8046ae3037f9dfb0bf0c353
|
||||
F test/indexedby.test 9c4cd331224e57f79fbf411ae245e6272d415985
|
||||
F test/indexexpr1.test 4feec154aadacb033b41acc1760a18edc4c60470
|
||||
F test/indexfault.test 31d4ab9a7d2f6e9616933eb079722362a883eb1d
|
||||
F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7
|
||||
F test/insert.test 38742b5e9601c8f8d76e9b7555f7270288c2d371
|
||||
@ -811,7 +816,8 @@ F test/journal3.test ff8af941f9e06161d3db1b46bb9f965ff0e7f307
|
||||
F test/jrnlmode.test 7864d59cf7f6e552b9b99ba0f38acd167edc10fa
|
||||
F test/jrnlmode2.test 81610545a4e6ed239ea8fa661891893385e23a1d
|
||||
F test/jrnlmode3.test 556b447a05be0e0963f4311e95ab1632b11c9eaa
|
||||
F test/json101.test 11535d8986184500f4c30cc2f0b154b4ab05cc4e
|
||||
F test/json101.test e20d2421c531db32fad59c5e06e80af0b1b002c8
|
||||
F test/json102.test 4e1403cb06481ab160cf471c3c139820498e0563
|
||||
F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff
|
||||
F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63
|
||||
F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200
|
||||
@ -917,8 +923,8 @@ F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
|
||||
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
|
||||
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
|
||||
F test/permutations.test ac3b00c299250cc087d4a527b5c75a0f8aef4e54
|
||||
F test/pragma.test be7195f0aa72bdb8a512133e9640ac40f15b57a2
|
||||
F test/pragma2.test 8e72df3a16c0fda748ad52abf79cb8256b04a6fe
|
||||
F test/pragma.test a44253f911e7d50127d4a08f927f47c861a4c772
|
||||
F test/pragma2.test b5e2ce4c892afceb308c6ae6163a9099b2a0d8d7
|
||||
F test/pragma3.test 6f849ccffeee7e496d2f2b5e74152306c0b8757c
|
||||
F test/printf.test b3ff34e73d59124140eaf89f7672e21bc2ca5fcc
|
||||
F test/printf2.test 0b61566dd1c0f0b802f59dffa228c5dc5aa6b054
|
||||
@ -936,14 +942,14 @@ F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736
|
||||
F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8
|
||||
F test/regexp1.test 497ea812f264d12b6198d6e50a76be4a1973a9d8
|
||||
F test/reindex.test 44edd3966b474468b823d481eafef0c305022254
|
||||
F test/releasetest.tcl cd2de2749aab7f45b2fe91b4a05431fc08e1692a
|
||||
F test/releasetest.tcl 5af0ca3d6a12471ffd9993c3efc00a553dfb2d41
|
||||
F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb
|
||||
F test/rollback.test 458fe73eb3ffdfdf9f6ba3e9b7350a6220414dea
|
||||
F test/rollback2.test fc14cf6d1a2b250d2735ef16124b971bce152f14
|
||||
F test/rollbackfault.test 6a004f71087cc399296cffbb5429ea6da655ae65
|
||||
F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc
|
||||
F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81
|
||||
F test/rowid.test 742b5741584a8a44fd83e856cc2896688401d645
|
||||
F test/rowid.test 5b7509f384f4f6fae1af3c8c104c8ca299fea18d
|
||||
F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798
|
||||
F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09
|
||||
F test/savepoint.test c671fdbd34cd3bfe1518a777526ada595180cf8d
|
||||
@ -1032,12 +1038,13 @@ F test/subquery.test d7268d193dd33d5505df965399d3a594e76ae13f
|
||||
F test/subquery2.test 438f8a7da1457277b22e4176510f7659b286995f
|
||||
F test/subselect.test d24fd8757daf97dafd2e889c73ea4c4272dcf4e4
|
||||
F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a
|
||||
F test/subtype1.test 7fe09496352f97053af1437150751be2d0a0cae8
|
||||
F test/superlock.test 1cde669f68d2dd37d6c9bd35eee1d95491ae3fc2
|
||||
F test/sync.test a34cd43e98b7fb84eabbf38f7ed8f7349b3f3d85
|
||||
F test/syscall.test d2fdaad713f103ac611fe7ef9b724c7b69f8149c
|
||||
F test/sysfault.test fa776e60bf46bdd3ae69f0b73e46ee3977a58ae6
|
||||
F test/tabfunc01.test a12eba3f48a03a6626f985734ecc28132381fa9b
|
||||
F test/table.test 33bf0d1fd07f304582695184b8e6feb017303816
|
||||
F test/tabfunc01.test fa9d8dfc75747019e0be98d3b6ac68d18632d328
|
||||
F test/table.test b708f3e5fa2542fa51dfab21fc07b36ea445cb2f
|
||||
F test/tableapi.test 2674633fa95d80da917571ebdd759a14d9819126
|
||||
F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930
|
||||
F test/tclsqlite.test 7fb866443c7deceed22b63948ccd6f76b52ad054
|
||||
@ -1287,10 +1294,10 @@ F test/walro.test 34422d1d95aaff0388f0791ec20edb34e2a3ed57
|
||||
F test/walshared.test 0befc811dcf0b287efae21612304d15576e35417
|
||||
F test/walslow.test e7be6d9888f83aa5d3d3c7c08aa9b5c28b93609a
|
||||
F test/walthread.test de8dbaf6d9e41481c460ba31ca61e163d7348f8e
|
||||
F test/where.test 1ff3d9f8da0a6c0dc5ccfd38d9225b2cdb5b6afb
|
||||
F test/where.test 66d4c107e82dfe86c01a96277b77e7a8809aff0b
|
||||
F test/where2.test af78c55589cbc82d793449493adba0dc3d659f23
|
||||
F test/where3.test 1ad55ba900bd7747f98b6082e65bd3e442c5004e
|
||||
F test/where4.test 68aa5ad796e33816db2078bc0f6de719c7a0e21f
|
||||
F test/where4.test 44f506bf1737cf0fa4fc795e340208250f1fcd89
|
||||
F test/where5.test fdf66f96d29a064b63eb543e28da4dfdccd81ad2
|
||||
F test/where6.test 5da5a98cec820d488e82708301b96cb8c18a258b
|
||||
F test/where7.test 5a4b0abc207d71da4deecd734ad8579e8dd40aa8
|
||||
@ -1336,8 +1343,8 @@ F tool/fuzzershell.c f2fc86dd22df654b28851b85019d3bd007361751
|
||||
F tool/genfkey.README cf68fddd4643bbe3ff8e31b8b6d8b0a1b85e20f4
|
||||
F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5
|
||||
F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce
|
||||
F tool/lemon.c b9109f59b57e7b6f101c4fe644c8361ba6dee969
|
||||
F tool/lempar.c 01ca97f87610d1dac6d8cd96ab109ab1130e76dc
|
||||
F tool/lemon.c 039f813b520b9395740c52f9cbf36c90b5d8df03
|
||||
F tool/lempar.c 3617143ddb9b176c3605defe6a9c798793280120
|
||||
F tool/loadfts.c c3c64e4d5e90e8ba41159232c2189dba4be7b862
|
||||
F tool/logest.c eef612f8adf4d0993dafed0416064cf50d5d33c6
|
||||
F tool/mkautoconfamal.sh d1a2da0e15b2ed33d60af35c7e9d483f13a8eb9f
|
||||
@ -1363,7 +1370,7 @@ F tool/showstat4.c 9515faa8ec176599d4a8288293ba8ec61f7b728a
|
||||
F tool/showwal.c 85cb36d4fe3e93e2fbd63e786e0d1ce42d0c4fad
|
||||
F tool/soak1.tcl 8d407956e1a45b485a8e072470a3e629a27037fe
|
||||
F tool/space_used.tcl f714c41a59e326b8b9042f415b628b561bafa06b
|
||||
F tool/spaceanal.tcl 63a415385a66fdbf736bfd204a31c6d851ed8da6
|
||||
F tool/spaceanal.tcl 93c1fdc9733c525b17a2024c7df193daa002e037
|
||||
F tool/speedtest.tcl 06c76698485ccf597b9e7dbb1ac70706eb873355
|
||||
F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81
|
||||
F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
|
||||
@ -1381,7 +1388,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
|
||||
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
|
||||
F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b
|
||||
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
|
||||
P 29570a604806e2a60a8eef7eaf1dce022d2a7004 847387ec8e6fef283899578fb232b2c23b00ee5b
|
||||
R af53d4591486f700f0b5e9bcc7196ecf
|
||||
P 25ee3000e94d60d8c1d7b980f416dcc33eb11105 1d018c35b9e81982df036f5e62a4a42219b54e02
|
||||
R 094d10cbcbe8f2ec763e09bd3b0c23d3
|
||||
U drh
|
||||
Z 6458a4b1081b59ccab00b014bcb486e3
|
||||
Z 515bf273b300e1c6b0d004e464776ff6
|
||||
|
@ -1 +1 @@
|
||||
25ee3000e94d60d8c1d7b980f416dcc33eb11105
|
||||
66fe06832614010d3156d7b21a760af9957018cc
|
@ -1186,6 +1186,7 @@ static void analyzeOneTable(
|
||||
regKey = sqlite3GetTempRange(pParse, pPk->nKeyCol);
|
||||
for(j=0; j<pPk->nKeyCol; j++){
|
||||
k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[j]);
|
||||
assert( k>=0 && k<pTab->nCol );
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, regKey+j);
|
||||
VdbeComment((v, "%s", pTab->aCol[pPk->aiColumn[j]].zName));
|
||||
}
|
||||
@ -1235,12 +1236,10 @@ static void analyzeOneTable(
|
||||
** be taken */
|
||||
VdbeCoverageNeverTaken(v);
|
||||
#ifdef SQLITE_ENABLE_STAT3
|
||||
sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur,
|
||||
pIdx->aiColumn[0], regSample);
|
||||
sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iTabCur, 0, regSample);
|
||||
#else
|
||||
for(i=0; i<nCol; i++){
|
||||
i16 iCol = pIdx->aiColumn[i];
|
||||
sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, iCol, regCol+i);
|
||||
sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iTabCur, i, regCol+i);
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, nCol, regSample);
|
||||
#endif
|
||||
|
126
src/btree.c
126
src/btree.c
@ -591,6 +591,49 @@ static void btreeReleaseAllCursorPages(BtCursor *pCur){
|
||||
pCur->iPage = -1;
|
||||
}
|
||||
|
||||
/*
|
||||
** The cursor passed as the only argument must point to a valid entry
|
||||
** when this function is called (i.e. have eState==CURSOR_VALID). This
|
||||
** function saves the current cursor key in variables pCur->nKey and
|
||||
** pCur->pKey. SQLITE_OK is returned if successful or an SQLite error
|
||||
** code otherwise.
|
||||
**
|
||||
** If the cursor is open on an intkey table, then the integer key
|
||||
** (the rowid) is stored in pCur->nKey and pCur->pKey is left set to
|
||||
** NULL. If the cursor is open on a non-intkey table, then pCur->pKey is
|
||||
** set to point to a malloced buffer pCur->nKey bytes in size containing
|
||||
** the key.
|
||||
*/
|
||||
static int saveCursorKey(BtCursor *pCur){
|
||||
int rc;
|
||||
assert( CURSOR_VALID==pCur->eState );
|
||||
assert( 0==pCur->pKey );
|
||||
assert( cursorHoldsMutex(pCur) );
|
||||
|
||||
rc = sqlite3BtreeKeySize(pCur, &pCur->nKey);
|
||||
assert( rc==SQLITE_OK ); /* KeySize() cannot fail */
|
||||
|
||||
/* If this is an intKey table, then the above call to BtreeKeySize()
|
||||
** stores the integer key in pCur->nKey. In this case this value is
|
||||
** all that is required. Otherwise, if pCur is not open on an intKey
|
||||
** table, then malloc space for and store the pCur->nKey bytes of key
|
||||
** data. */
|
||||
if( 0==pCur->curIntKey ){
|
||||
void *pKey = sqlite3Malloc( pCur->nKey );
|
||||
if( pKey ){
|
||||
rc = sqlite3BtreeKey(pCur, 0, (int)pCur->nKey, pKey);
|
||||
if( rc==SQLITE_OK ){
|
||||
pCur->pKey = pKey;
|
||||
}else{
|
||||
sqlite3_free(pKey);
|
||||
}
|
||||
}else{
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
assert( !pCur->curIntKey || !pCur->pKey );
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Save the current cursor position in the variables BtCursor.nKey
|
||||
@ -611,30 +654,8 @@ static int saveCursorPosition(BtCursor *pCur){
|
||||
}else{
|
||||
pCur->skipNext = 0;
|
||||
}
|
||||
rc = sqlite3BtreeKeySize(pCur, &pCur->nKey);
|
||||
assert( rc==SQLITE_OK ); /* KeySize() cannot fail */
|
||||
|
||||
/* If this is an intKey table, then the above call to BtreeKeySize()
|
||||
** stores the integer key in pCur->nKey. In this case this value is
|
||||
** all that is required. Otherwise, if pCur is not open on an intKey
|
||||
** table, then malloc space for and store the pCur->nKey bytes of key
|
||||
** data.
|
||||
*/
|
||||
if( 0==pCur->curIntKey ){
|
||||
void *pKey = sqlite3Malloc( pCur->nKey );
|
||||
if( pKey ){
|
||||
rc = sqlite3BtreeKey(pCur, 0, (int)pCur->nKey, pKey);
|
||||
if( rc==SQLITE_OK ){
|
||||
pCur->pKey = pKey;
|
||||
}else{
|
||||
sqlite3_free(pKey);
|
||||
}
|
||||
}else{
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
assert( !pCur->curIntKey || !pCur->pKey );
|
||||
|
||||
rc = saveCursorKey(pCur);
|
||||
if( rc==SQLITE_OK ){
|
||||
btreeReleaseAllCursorPages(pCur);
|
||||
pCur->eState = CURSOR_REQUIRESEEK;
|
||||
@ -8047,10 +8068,15 @@ end_insert:
|
||||
}
|
||||
|
||||
/*
|
||||
** Delete the entry that the cursor is pointing to. The cursor
|
||||
** is left pointing at an arbitrary location.
|
||||
** Delete the entry that the cursor is pointing to.
|
||||
**
|
||||
** If the second parameter is zero, then the cursor is left pointing at an
|
||||
** arbitrary location after the delete. If it is non-zero, then the cursor
|
||||
** is left in a state such that the next call to BtreeNext() or BtreePrev()
|
||||
** moves it to the same row as it would if the call to BtreeDelete() had
|
||||
** been omitted.
|
||||
*/
|
||||
int sqlite3BtreeDelete(BtCursor *pCur){
|
||||
int sqlite3BtreeDelete(BtCursor *pCur, int bPreserve){
|
||||
Btree *p = pCur->pBtree;
|
||||
BtShared *pBt = p->pBt;
|
||||
int rc; /* Return code */
|
||||
@ -8059,6 +8085,7 @@ int sqlite3BtreeDelete(BtCursor *pCur){
|
||||
int iCellIdx; /* Index of cell to delete */
|
||||
int iCellDepth; /* Depth of node containing pCell */
|
||||
u16 szCell; /* Size of the cell being deleted */
|
||||
int bSkipnext = 0; /* Leaf cursor in SKIPNEXT state */
|
||||
|
||||
assert( cursorHoldsMutex(pCur) );
|
||||
assert( pBt->inTransaction==TRANS_WRITE );
|
||||
@ -8088,10 +8115,7 @@ int sqlite3BtreeDelete(BtCursor *pCur){
|
||||
}
|
||||
|
||||
/* Save the positions of any other cursors open on this table before
|
||||
** making any modifications. Make the page containing the entry to be
|
||||
** deleted writable. Then free any overflow pages associated with the
|
||||
** entry and finally remove the cell itself from within the page.
|
||||
*/
|
||||
** making any modifications. */
|
||||
if( pCur->curFlags & BTCF_Multiple ){
|
||||
rc = saveAllCursors(pBt, pCur->pgnoRoot, pCur);
|
||||
if( rc ) return rc;
|
||||
@ -8103,6 +8127,31 @@ int sqlite3BtreeDelete(BtCursor *pCur){
|
||||
invalidateIncrblobCursors(p, pCur->info.nKey, 0);
|
||||
}
|
||||
|
||||
/* If the bPreserve flag is set to true, then the cursor position must
|
||||
** be preserved following this delete operation. If the current delete
|
||||
** will cause a b-tree rebalance, then this is done by saving the cursor
|
||||
** key and leaving the cursor in CURSOR_REQUIRESEEK state before
|
||||
** returning.
|
||||
**
|
||||
** Or, if the current delete will not cause a rebalance, then the cursor
|
||||
** will be left in CURSOR_SKIPNEXT state pointing to the entry immediately
|
||||
** before or after the deleted entry. In this case set bSkipnext to true. */
|
||||
if( bPreserve ){
|
||||
if( !pPage->leaf
|
||||
|| (pPage->nFree+cellSizePtr(pPage,pCell)+2)>(int)(pBt->usableSize*2/3)
|
||||
){
|
||||
/* A b-tree rebalance will be required after deleting this entry.
|
||||
** Save the cursor key. */
|
||||
rc = saveCursorKey(pCur);
|
||||
if( rc ) return rc;
|
||||
}else{
|
||||
bSkipnext = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make the page containing the entry to be deleted writable. Then free any
|
||||
** overflow pages associated with the entry and finally remove the cell
|
||||
** itself from within the page. */
|
||||
rc = sqlite3PagerWrite(pPage->pDbPage);
|
||||
if( rc ) return rc;
|
||||
rc = clearCell(pPage, pCell, &szCell);
|
||||
@ -8156,7 +8205,22 @@ int sqlite3BtreeDelete(BtCursor *pCur){
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
moveToRoot(pCur);
|
||||
if( bSkipnext ){
|
||||
assert( bPreserve && pCur->iPage==iCellDepth );
|
||||
assert( pPage->nCell>0 && iCellIdx<=pPage->nCell );
|
||||
pCur->eState = CURSOR_SKIPNEXT;
|
||||
if( iCellIdx>=pPage->nCell ){
|
||||
pCur->skipNext = -1;
|
||||
pCur->aiIdx[iCellDepth] = pPage->nCell-1;
|
||||
}else{
|
||||
pCur->skipNext = 1;
|
||||
}
|
||||
}else{
|
||||
rc = moveToRoot(pCur);
|
||||
if( bPreserve ){
|
||||
pCur->eState = CURSOR_REQUIRESEEK;
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
@ -222,7 +222,7 @@ int sqlite3BtreeMovetoUnpacked(
|
||||
);
|
||||
int sqlite3BtreeCursorHasMoved(BtCursor*);
|
||||
int sqlite3BtreeCursorRestore(BtCursor*, int*);
|
||||
int sqlite3BtreeDelete(BtCursor*);
|
||||
int sqlite3BtreeDelete(BtCursor*, int);
|
||||
int sqlite3BtreeInsert(BtCursor*, const void *pKey, i64 nKey,
|
||||
const void *pData, int nData,
|
||||
int nZero, int bias, int seekResult);
|
||||
|
148
src/build.c
148
src/build.c
@ -357,12 +357,14 @@ Table *sqlite3LocateTable(
|
||||
if( p==0 ){
|
||||
const char *zMsg = isView ? "no such view" : "no such table";
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
/* If zName is the not the name of a table in the schema created using
|
||||
** CREATE, then check to see if it is the name of an virtual table that
|
||||
** can be an eponymous virtual table. */
|
||||
Module *pMod = (Module*)sqlite3HashFind(&pParse->db->aModule, zName);
|
||||
if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){
|
||||
return pMod->pEpoTab;
|
||||
if( sqlite3FindDbName(pParse->db, zDbase)<1 ){
|
||||
/* If zName is the not the name of a table in the schema created using
|
||||
** CREATE, then check to see if it is the name of an virtual table that
|
||||
** can be an eponymous virtual table. */
|
||||
Module *pMod = (Module*)sqlite3HashFind(&pParse->db->aModule, zName);
|
||||
if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){
|
||||
return pMod->pEpoTab;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if( zDbase ){
|
||||
@ -372,7 +374,7 @@ Table *sqlite3LocateTable(
|
||||
}
|
||||
pParse->checkSchema = 1;
|
||||
}
|
||||
#if SQLITE_USER_AUTHENICATION
|
||||
#if SQLITE_USER_AUTHENTICATION
|
||||
else if( pParse->db->auth.authLevel<UAUTH_User ){
|
||||
sqlite3ErrorMsg(pParse, "user not authenticated");
|
||||
p = 0;
|
||||
@ -443,6 +445,7 @@ static void freeIndex(sqlite3 *db, Index *p){
|
||||
sqlite3DeleteIndexSamples(db, p);
|
||||
#endif
|
||||
sqlite3ExprDelete(db, p->pPartIdxWhere);
|
||||
sqlite3ExprListDelete(db, p->aColExpr);
|
||||
sqlite3DbFree(db, p->zColAff);
|
||||
if( p->isResized ) sqlite3DbFree(db, p->azColl);
|
||||
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
|
||||
@ -983,6 +986,8 @@ void sqlite3StartTable(
|
||||
int j1;
|
||||
int fileFormat;
|
||||
int reg1, reg2, reg3;
|
||||
/* nullRow[] is an OP_Record encoding of a row containing 5 NULLs */
|
||||
static const char nullRow[] = { 6, 0, 0, 0, 0, 0 };
|
||||
sqlite3BeginWriteOperation(pParse, 1, iDb);
|
||||
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
@ -1027,7 +1032,7 @@ void sqlite3StartTable(
|
||||
}
|
||||
sqlite3OpenMasterTable(pParse, iDb);
|
||||
sqlite3VdbeAddOp2(v, OP_NewRowid, 0, reg1);
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, reg3);
|
||||
sqlite3VdbeAddOp4(v, OP_Blob, 6, reg3, 0, nullRow, P4_STATIC);
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1);
|
||||
sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
|
||||
sqlite3VdbeAddOp0(v, OP_Close);
|
||||
@ -1309,7 +1314,8 @@ void sqlite3AddPrimaryKey(
|
||||
nTerm = pList->nExpr;
|
||||
for(i=0; i<nTerm; i++){
|
||||
Expr *pCExpr = sqlite3ExprSkipCollate(pList->a[i].pExpr);
|
||||
if( pCExpr && pCExpr->op==TK_ID ){
|
||||
assert( pCExpr!=0 );
|
||||
if( pCExpr->op==TK_ID ){
|
||||
const char *zCName = pCExpr->u.zToken;
|
||||
for(iCol=0; iCol<pTab->nCol; iCol++){
|
||||
if( sqlite3StrICmp(zCName, pTab->aCol[iCol].zName)==0 ){
|
||||
@ -2848,6 +2854,30 @@ Index *sqlite3AllocateIndexObject(
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
** Backwards Compatibility Hack:
|
||||
**
|
||||
** Historical versions of SQLite accepted strings as column names in
|
||||
** indexes and PRIMARY KEY constraints and in UNIQUE constraints. Example:
|
||||
**
|
||||
** CREATE TABLE xyz(a,b,c,d,e,PRIMARY KEY('a'),UNIQUE('b','c' COLLATE trim)
|
||||
** CREATE INDEX abc ON xyz('c','d' DESC,'e' COLLATE nocase DESC);
|
||||
**
|
||||
** This is goofy. But to preserve backwards compatibility we continue to
|
||||
** accept it. This routine does the necessary conversion. It converts
|
||||
** the expression given in its argument from a TK_STRING into a TK_ID
|
||||
** if the expression is just a TK_STRING with an optional COLLATE clause.
|
||||
** If the epxression is anything other than TK_STRING, the expression is
|
||||
** unchanged.
|
||||
*/
|
||||
static void sqlite3StringToId(Expr *p){
|
||||
if( p->op==TK_STRING ){
|
||||
p->op = TK_ID;
|
||||
}else if( p->op==TK_COLLATE && p->pLeft->op==TK_STRING ){
|
||||
p->pLeft->op = TK_ID;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Create a new index for an SQL table. pName1.pName2 is the name of the index
|
||||
** and pTblList is the name of the table that is to be indexed. Both will
|
||||
@ -2889,7 +2919,6 @@ Index *sqlite3CreateIndex(
|
||||
int iDb; /* Index of the database that is being written */
|
||||
Token *pName = 0; /* Unqualified name of the index to create */
|
||||
struct ExprList_item *pListItem; /* For looping over pList */
|
||||
const Column *pTabCol; /* A column in the table */
|
||||
int nExtra = 0; /* Space allocated for zExtra[] */
|
||||
int nExtraCol; /* Number of extra columns needed */
|
||||
char *zExtra = 0; /* Extra space after the Index object */
|
||||
@ -3061,7 +3090,8 @@ Index *sqlite3CreateIndex(
|
||||
*/
|
||||
for(i=0; i<pList->nExpr; i++){
|
||||
Expr *pExpr = pList->a[i].pExpr;
|
||||
if( pExpr && pExpr->op==TK_COLLATE ){
|
||||
assert( pExpr!=0 );
|
||||
if( pExpr->op==TK_COLLATE ){
|
||||
nExtra += (1 + sqlite3Strlen30(pExpr->u.zToken));
|
||||
}
|
||||
}
|
||||
@ -3102,39 +3132,52 @@ Index *sqlite3CreateIndex(
|
||||
sortOrderMask = 0; /* Ignore DESC */
|
||||
}
|
||||
|
||||
/* Scan the names of the columns of the table to be indexed and
|
||||
** load the column indices into the Index structure. Report an error
|
||||
** if any column is not found.
|
||||
/* Analyze the list of expressions that form the terms of the index and
|
||||
** report any errors. In the common case where the expression is exactly
|
||||
** a table column, store that column in aiColumn[]. For general expressions,
|
||||
** populate pIndex->aColExpr and store -2 in aiColumn[].
|
||||
**
|
||||
** TODO: Add a test to make sure that the same column is not named
|
||||
** more than once within the same index. Only the first instance of
|
||||
** the column will ever be used by the optimizer. Note that using the
|
||||
** same column more than once cannot be an error because that would
|
||||
** break backwards compatibility - it needs to be a warning.
|
||||
** TODO: Issue a warning if two or more columns of the index are identical.
|
||||
** TODO: Issue a warning if the table primary key is used as part of the
|
||||
** index key.
|
||||
*/
|
||||
for(i=0, pListItem=pList->a; i<pList->nExpr; i++, pListItem++){
|
||||
const char *zColName;
|
||||
Expr *pCExpr;
|
||||
int requestedSortOrder;
|
||||
Expr *pCExpr; /* The i-th index expression */
|
||||
int requestedSortOrder; /* ASC or DESC on the i-th expression */
|
||||
char *zColl; /* Collation sequence name */
|
||||
|
||||
sqlite3StringToId(pListItem->pExpr);
|
||||
sqlite3ResolveSelfReference(pParse, pTab, NC_IdxExpr, pListItem->pExpr, 0);
|
||||
if( pParse->nErr ) goto exit_create_index;
|
||||
pCExpr = sqlite3ExprSkipCollate(pListItem->pExpr);
|
||||
if( pCExpr->op!=TK_ID ){
|
||||
sqlite3ErrorMsg(pParse, "indexes on expressions not yet supported");
|
||||
continue;
|
||||
if( pCExpr->op!=TK_COLUMN ){
|
||||
if( pTab==pParse->pNewTable ){
|
||||
sqlite3ErrorMsg(pParse, "expressions prohibited in PRIMARY KEY and "
|
||||
"UNIQUE constraints");
|
||||
goto exit_create_index;
|
||||
}
|
||||
if( pIndex->aColExpr==0 ){
|
||||
ExprList *pCopy = sqlite3ExprListDup(db, pList, 0);
|
||||
pIndex->aColExpr = pCopy;
|
||||
if( !db->mallocFailed ){
|
||||
assert( pCopy!=0 );
|
||||
pListItem = &pCopy->a[i];
|
||||
}
|
||||
}
|
||||
j = -2;
|
||||
pIndex->aiColumn[i] = -2;
|
||||
pIndex->uniqNotNull = 0;
|
||||
}else{
|
||||
j = pCExpr->iColumn;
|
||||
assert( j<=0x7fff );
|
||||
if( j<0 ){
|
||||
j = pTab->iPKey;
|
||||
}else if( pTab->aCol[j].notNull==0 ){
|
||||
pIndex->uniqNotNull = 0;
|
||||
}
|
||||
pIndex->aiColumn[i] = (i16)j;
|
||||
}
|
||||
zColName = pCExpr->u.zToken;
|
||||
for(j=0, pTabCol=pTab->aCol; j<pTab->nCol; j++, pTabCol++){
|
||||
if( sqlite3StrICmp(zColName, pTabCol->zName)==0 ) break;
|
||||
}
|
||||
if( j>=pTab->nCol ){
|
||||
sqlite3ErrorMsg(pParse, "table %s has no column named %s",
|
||||
pTab->zName, zColName);
|
||||
pParse->checkSchema = 1;
|
||||
goto exit_create_index;
|
||||
}
|
||||
assert( j<=0x7fff );
|
||||
pIndex->aiColumn[i] = (i16)j;
|
||||
zColl = 0;
|
||||
if( pListItem->pExpr->op==TK_COLLATE ){
|
||||
int nColl;
|
||||
zColl = pListItem->pExpr->u.zToken;
|
||||
@ -3144,21 +3187,26 @@ Index *sqlite3CreateIndex(
|
||||
zColl = zExtra;
|
||||
zExtra += nColl;
|
||||
nExtra -= nColl;
|
||||
}else{
|
||||
}else if( j>=0 ){
|
||||
zColl = pTab->aCol[j].zColl;
|
||||
if( !zColl ) zColl = "BINARY";
|
||||
}
|
||||
if( !zColl ) zColl = "BINARY";
|
||||
if( !db->init.busy && !sqlite3LocateCollSeq(pParse, zColl) ){
|
||||
goto exit_create_index;
|
||||
}
|
||||
pIndex->azColl[i] = zColl;
|
||||
requestedSortOrder = pListItem->sortOrder & sortOrderMask;
|
||||
pIndex->aSortOrder[i] = (u8)requestedSortOrder;
|
||||
if( pTab->aCol[j].notNull==0 ) pIndex->uniqNotNull = 0;
|
||||
}
|
||||
|
||||
/* Append the table key to the end of the index. For WITHOUT ROWID
|
||||
** tables (when pPk!=0) this will be the declared PRIMARY KEY. For
|
||||
** normal tables (when pPk==0) this will be the rowid.
|
||||
*/
|
||||
if( pPk ){
|
||||
for(j=0; j<pPk->nKeyCol; j++){
|
||||
int x = pPk->aiColumn[j];
|
||||
assert( x>=0 );
|
||||
if( hasColumn(pIndex->aiColumn, pIndex->nKeyCol, x) ){
|
||||
pIndex->nColumn--;
|
||||
}else{
|
||||
@ -3209,6 +3257,7 @@ Index *sqlite3CreateIndex(
|
||||
for(k=0; k<pIdx->nKeyCol; k++){
|
||||
const char *z1;
|
||||
const char *z2;
|
||||
assert( pIdx->aiColumn[k]>=0 );
|
||||
if( pIdx->aiColumn[k]!=pIndex->aiColumn[k] ) break;
|
||||
z1 = pIdx->azColl[k];
|
||||
z2 = pIndex->azColl[k];
|
||||
@ -3240,6 +3289,7 @@ Index *sqlite3CreateIndex(
|
||||
/* Link the new Index structure to its table and to the other
|
||||
** in-memory database structures.
|
||||
*/
|
||||
assert( pParse->nErr==0 );
|
||||
if( db->init.busy ){
|
||||
Index *p;
|
||||
assert( sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) );
|
||||
@ -3269,7 +3319,7 @@ Index *sqlite3CreateIndex(
|
||||
** has just been created, it contains no data and the index initialization
|
||||
** step can be skipped.
|
||||
*/
|
||||
else if( pParse->nErr==0 && (HasRowid(pTab) || pTblName!=0) ){
|
||||
else if( HasRowid(pTab) || pTblName!=0 ){
|
||||
Vdbe *v;
|
||||
char *zStmt;
|
||||
int iMem = ++pParse->nMem;
|
||||
@ -4099,12 +4149,16 @@ void sqlite3UniqueConstraint(
|
||||
Table *pTab = pIdx->pTable;
|
||||
|
||||
sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, 200);
|
||||
for(j=0; j<pIdx->nKeyCol; j++){
|
||||
char *zCol = pTab->aCol[pIdx->aiColumn[j]].zName;
|
||||
if( j ) sqlite3StrAccumAppend(&errMsg, ", ", 2);
|
||||
sqlite3StrAccumAppendAll(&errMsg, pTab->zName);
|
||||
sqlite3StrAccumAppend(&errMsg, ".", 1);
|
||||
sqlite3StrAccumAppendAll(&errMsg, zCol);
|
||||
if( pIdx->aColExpr ){
|
||||
sqlite3XPrintf(&errMsg, 0, "index '%q'", pIdx->zName);
|
||||
}else{
|
||||
for(j=0; j<pIdx->nKeyCol; j++){
|
||||
char *zCol;
|
||||
assert( pIdx->aiColumn[j]>=0 );
|
||||
zCol = pTab->aCol[pIdx->aiColumn[j]].zName;
|
||||
if( j ) sqlite3StrAccumAppend(&errMsg, ", ", 2);
|
||||
sqlite3XPrintf(&errMsg, 0, "%s.%s", pTab->zName, zCol);
|
||||
}
|
||||
}
|
||||
zErr = sqlite3StrAccumFinish(&errMsg);
|
||||
sqlite3HaltConstraint(pParse,
|
||||
|
16
src/date.c
16
src/date.c
@ -1115,14 +1115,14 @@ static void currentTimeFunc(
|
||||
void sqlite3RegisterDateTimeFunctions(void){
|
||||
static SQLITE_WSD FuncDef aDateTimeFuncs[] = {
|
||||
#ifndef SQLITE_OMIT_DATETIME_FUNCS
|
||||
FUNCTION(julianday, -1, 0, 0, juliandayFunc ),
|
||||
FUNCTION(date, -1, 0, 0, dateFunc ),
|
||||
FUNCTION(time, -1, 0, 0, timeFunc ),
|
||||
FUNCTION(datetime, -1, 0, 0, datetimeFunc ),
|
||||
FUNCTION(strftime, -1, 0, 0, strftimeFunc ),
|
||||
FUNCTION(current_time, 0, 0, 0, ctimeFunc ),
|
||||
FUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc),
|
||||
FUNCTION(current_date, 0, 0, 0, cdateFunc ),
|
||||
DFUNCTION(julianday, -1, 0, 0, juliandayFunc ),
|
||||
DFUNCTION(date, -1, 0, 0, dateFunc ),
|
||||
DFUNCTION(time, -1, 0, 0, timeFunc ),
|
||||
DFUNCTION(datetime, -1, 0, 0, datetimeFunc ),
|
||||
DFUNCTION(strftime, -1, 0, 0, strftimeFunc ),
|
||||
DFUNCTION(current_time, 0, 0, 0, ctimeFunc ),
|
||||
DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc),
|
||||
DFUNCTION(current_date, 0, 0, 0, cdateFunc ),
|
||||
#else
|
||||
STR_FUNCTION(current_time, 0, "%H:%M:%S", 0, currentTimeFunc),
|
||||
STR_FUNCTION(current_date, 0, "%Y-%m-%d", 0, currentTimeFunc),
|
||||
|
105
src/dbstat.c
105
src/dbstat.c
@ -16,6 +16,9 @@
|
||||
** information from an SQLite database in order to implement the
|
||||
** "sqlite3_analyzer" utility. See the ../tool/spaceanal.tcl script
|
||||
** for an example implementation.
|
||||
**
|
||||
** Additional information is available on the "dbstat.html" page of the
|
||||
** official SQLite documentation.
|
||||
*/
|
||||
|
||||
#include "sqliteInt.h" /* Requires access to internal data structures */
|
||||
@ -64,7 +67,8 @@
|
||||
" unused INTEGER, /* Bytes of unused space on this page */" \
|
||||
" mx_payload INTEGER, /* Largest payload size of all cells */" \
|
||||
" pgoffset INTEGER, /* Offset of page in file */" \
|
||||
" pgsize INTEGER /* Size of the page */" \
|
||||
" pgsize INTEGER, /* Size of the page */" \
|
||||
" schema TEXT HIDDEN /* Database schema being analyzed */" \
|
||||
");"
|
||||
|
||||
|
||||
@ -102,6 +106,7 @@ struct StatCursor {
|
||||
sqlite3_vtab_cursor base;
|
||||
sqlite3_stmt *pStmt; /* Iterates through set of root pages */
|
||||
int isEof; /* After pStmt has returned SQLITE_DONE */
|
||||
int iDb; /* Schema used for this query */
|
||||
|
||||
StatPage aPage[32];
|
||||
int iPage; /* Current entry in aPage[] */
|
||||
@ -179,9 +184,32 @@ static int statDisconnect(sqlite3_vtab *pVtab){
|
||||
|
||||
/*
|
||||
** There is no "best-index". This virtual table always does a linear
|
||||
** scan of the binary VFS log file.
|
||||
** scan. However, a schema=? constraint should cause this table to
|
||||
** operate on a different database schema, so check for it.
|
||||
**
|
||||
** idxNum is normally 0, but will be 1 if a schema=? constraint exists.
|
||||
*/
|
||||
static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
int i;
|
||||
|
||||
pIdxInfo->estimatedCost = 1.0e6; /* Initial cost estimate */
|
||||
|
||||
/* Look for a valid schema=? constraint. If found, change the idxNum to
|
||||
** 1 and request the value of that constraint be sent to xFilter. And
|
||||
** lower the cost estimate to encourage the constrained version to be
|
||||
** used.
|
||||
*/
|
||||
for(i=0; i<pIdxInfo->nConstraint; i++){
|
||||
if( pIdxInfo->aConstraint[i].usable==0 ) continue;
|
||||
if( pIdxInfo->aConstraint[i].op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
|
||||
if( pIdxInfo->aConstraint[i].iColumn!=10 ) continue;
|
||||
pIdxInfo->idxNum = 1;
|
||||
pIdxInfo->estimatedCost = 1.0;
|
||||
pIdxInfo->aConstraintUsage[i].argvIndex = 1;
|
||||
pIdxInfo->aConstraintUsage[i].omit = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/* Records are always returned in ascending order of (name, path).
|
||||
** If this will satisfy the client, set the orderByConsumed flag so that
|
||||
@ -201,7 +229,6 @@ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
pIdxInfo->orderByConsumed = 1;
|
||||
}
|
||||
|
||||
pIdxInfo->estimatedCost = 10.0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@ -211,36 +238,18 @@ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
|
||||
static int statOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
|
||||
StatTable *pTab = (StatTable *)pVTab;
|
||||
StatCursor *pCsr;
|
||||
int rc;
|
||||
|
||||
pCsr = (StatCursor *)sqlite3_malloc64(sizeof(StatCursor));
|
||||
if( pCsr==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
return SQLITE_NOMEM;
|
||||
}else{
|
||||
char *zSql;
|
||||
memset(pCsr, 0, sizeof(StatCursor));
|
||||
pCsr->base.pVtab = pVTab;
|
||||
|
||||
zSql = sqlite3_mprintf(
|
||||
"SELECT 'sqlite_master' AS name, 1 AS rootpage, 'table' AS type"
|
||||
" UNION ALL "
|
||||
"SELECT name, rootpage, type"
|
||||
" FROM \"%w\".sqlite_master WHERE rootpage!=0"
|
||||
" ORDER BY name", pTab->db->aDb[pTab->iDb].zName);
|
||||
if( zSql==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0);
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_free(pCsr);
|
||||
pCsr = 0;
|
||||
}
|
||||
pCsr->iDb = pTab->iDb;
|
||||
}
|
||||
|
||||
*ppCursor = (sqlite3_vtab_cursor *)pCsr;
|
||||
return rc;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static void statClearPage(StatPage *p){
|
||||
@ -265,6 +274,7 @@ static void statResetCsr(StatCursor *pCsr){
|
||||
pCsr->iPage = 0;
|
||||
sqlite3_free(pCsr->zPath);
|
||||
pCsr->zPath = 0;
|
||||
pCsr->isEof = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -427,7 +437,7 @@ static int statNext(sqlite3_vtab_cursor *pCursor){
|
||||
char *z;
|
||||
StatCursor *pCsr = (StatCursor *)pCursor;
|
||||
StatTable *pTab = (StatTable *)pCursor->pVtab;
|
||||
Btree *pBt = pTab->db->aDb[pTab->iDb].pBt;
|
||||
Btree *pBt = pTab->db->aDb[pCsr->iDb].pBt;
|
||||
Pager *pPager = sqlite3BtreePager(pBt);
|
||||
|
||||
sqlite3_free(pCsr->zPath);
|
||||
@ -565,9 +575,43 @@ static int statFilter(
|
||||
int argc, sqlite3_value **argv
|
||||
){
|
||||
StatCursor *pCsr = (StatCursor *)pCursor;
|
||||
StatTable *pTab = (StatTable*)(pCursor->pVtab);
|
||||
char *zSql;
|
||||
int rc = SQLITE_OK;
|
||||
char *zMaster;
|
||||
|
||||
if( idxNum==1 ){
|
||||
const char *zDbase = (const char*)sqlite3_value_text(argv[0]);
|
||||
pCsr->iDb = sqlite3FindDbName(pTab->db, zDbase);
|
||||
if( pCsr->iDb<0 ){
|
||||
sqlite3_free(pCursor->pVtab->zErrMsg);
|
||||
pCursor->pVtab->zErrMsg = sqlite3_mprintf("no such schema: %s", zDbase);
|
||||
return pCursor->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM;
|
||||
}
|
||||
}else{
|
||||
pCsr->iDb = pTab->iDb;
|
||||
}
|
||||
statResetCsr(pCsr);
|
||||
return statNext(pCursor);
|
||||
sqlite3_finalize(pCsr->pStmt);
|
||||
pCsr->pStmt = 0;
|
||||
zMaster = pCsr->iDb==1 ? "sqlite_temp_master" : "sqlite_master";
|
||||
zSql = sqlite3_mprintf(
|
||||
"SELECT 'sqlite_master' AS name, 1 AS rootpage, 'table' AS type"
|
||||
" UNION ALL "
|
||||
"SELECT name, rootpage, type"
|
||||
" FROM \"%w\".%s WHERE rootpage!=0"
|
||||
" ORDER BY name", pTab->db->aDb[pCsr->iDb].zName, zMaster);
|
||||
if( zSql==0 ){
|
||||
return SQLITE_NOMEM;
|
||||
}else{
|
||||
rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0);
|
||||
sqlite3_free(zSql);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = statNext(pCursor);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int statColumn(
|
||||
@ -604,10 +648,15 @@ static int statColumn(
|
||||
case 8: /* pgoffset */
|
||||
sqlite3_result_int64(ctx, pCsr->iOffset);
|
||||
break;
|
||||
default: /* pgsize */
|
||||
assert( i==9 );
|
||||
case 9: /* pgsize */
|
||||
sqlite3_result_int(ctx, pCsr->szPage);
|
||||
break;
|
||||
default: { /* schema */
|
||||
sqlite3 *db = sqlite3_context_db_handle(ctx);
|
||||
int iDb = pCsr->iDb;
|
||||
sqlite3_result_text(ctx, db->aDb[iDb].zName, -1, SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
140
src/delete.c
140
src/delete.c
@ -235,7 +235,7 @@ void sqlite3DeleteFrom(
|
||||
int iDb; /* Database number */
|
||||
int memCnt = -1; /* Memory cell used for change counting */
|
||||
int rcauth; /* Value returned by authorization callback */
|
||||
int okOnePass; /* True for one-pass algorithm without the FIFO */
|
||||
int eOnePass; /* ONEPASS_OFF or _SINGLE or _MULTI */
|
||||
int aiCurOnePass[2]; /* The write cursors opened by WHERE_ONEPASS */
|
||||
u8 *aToOpen = 0; /* Open cursor iTabCur+j if aToOpen[j] is true */
|
||||
Index *pPk; /* The PRIMARY KEY index on the table */
|
||||
@ -247,12 +247,12 @@ void sqlite3DeleteFrom(
|
||||
int iRowSet = 0; /* Register for rowset of rows to delete */
|
||||
int addrBypass = 0; /* Address of jump over the delete logic */
|
||||
int addrLoop = 0; /* Top of the delete loop */
|
||||
int addrDelete = 0; /* Jump directly to the delete logic */
|
||||
int addrEphOpen = 0; /* Instruction to open the Ephemeral table */
|
||||
|
||||
#ifndef SQLITE_OMIT_TRIGGER
|
||||
int isView; /* True if attempting to delete from a view */
|
||||
Trigger *pTrigger; /* List of table triggers, if required */
|
||||
int bComplex; /* True if there are either triggers or FKs */
|
||||
#endif
|
||||
|
||||
memset(&sContext, 0, sizeof(sContext));
|
||||
@ -276,9 +276,11 @@ void sqlite3DeleteFrom(
|
||||
#ifndef SQLITE_OMIT_TRIGGER
|
||||
pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0);
|
||||
isView = pTab->pSelect!=0;
|
||||
bComplex = pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0);
|
||||
#else
|
||||
# define pTrigger 0
|
||||
# define isView 0
|
||||
# define bComplex 0
|
||||
#endif
|
||||
#ifdef SQLITE_OMIT_VIEW
|
||||
# undef isView
|
||||
@ -359,8 +361,10 @@ void sqlite3DeleteFrom(
|
||||
** It is easier just to erase the whole table. Prior to version 3.6.5,
|
||||
** this optimization caused the row change count (the value returned by
|
||||
** API function sqlite3_count_changes) to be set incorrectly. */
|
||||
if( rcauth==SQLITE_OK && pWhere==0 && !pTrigger && !IsVirtual(pTab)
|
||||
&& 0==sqlite3FkRequired(pParse, pTab, 0, 0)
|
||||
if( rcauth==SQLITE_OK
|
||||
&& pWhere==0
|
||||
&& !bComplex
|
||||
&& !IsVirtual(pTab)
|
||||
){
|
||||
assert( !isView );
|
||||
sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName);
|
||||
@ -375,6 +379,8 @@ void sqlite3DeleteFrom(
|
||||
}else
|
||||
#endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
|
||||
{
|
||||
u16 wcf = WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK;
|
||||
wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW);
|
||||
if( HasRowid(pTab) ){
|
||||
/* For a rowid table, initialize the RowSet to an empty set */
|
||||
pPk = 0;
|
||||
@ -395,13 +401,17 @@ void sqlite3DeleteFrom(
|
||||
}
|
||||
|
||||
/* Construct a query to find the rowid or primary key for every row
|
||||
** to be deleted, based on the WHERE clause.
|
||||
** to be deleted, based on the WHERE clause. Set variable eOnePass
|
||||
** to indicate the strategy used to implement this delete:
|
||||
**
|
||||
** ONEPASS_OFF: Two-pass approach - use a FIFO for rowids/PK values.
|
||||
** ONEPASS_SINGLE: One-pass approach - at most one row deleted.
|
||||
** ONEPASS_MULTI: One-pass approach - any number of rows may be deleted.
|
||||
*/
|
||||
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0,
|
||||
WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK,
|
||||
iTabCur+1);
|
||||
pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf, iTabCur+1);
|
||||
if( pWInfo==0 ) goto delete_from_cleanup;
|
||||
okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
|
||||
eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
|
||||
assert( IsVirtual(pTab)==0 || eOnePass==ONEPASS_OFF );
|
||||
|
||||
/* Keep track of the number of rows to be deleted */
|
||||
if( db->flags & SQLITE_CountRows ){
|
||||
@ -411,6 +421,7 @@ void sqlite3DeleteFrom(
|
||||
/* Extract the rowid or primary key for the current row */
|
||||
if( pPk ){
|
||||
for(i=0; i<nPk; i++){
|
||||
assert( pPk->aiColumn[i]>=(-1) );
|
||||
sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur,
|
||||
pPk->aiColumn[i], iPk+i);
|
||||
}
|
||||
@ -421,11 +432,10 @@ void sqlite3DeleteFrom(
|
||||
if( iKey>pParse->nMem ) pParse->nMem = iKey;
|
||||
}
|
||||
|
||||
if( okOnePass ){
|
||||
/* For ONEPASS, no need to store the rowid/primary-key. There is only
|
||||
if( eOnePass!=ONEPASS_OFF ){
|
||||
/* For ONEPASS, no need to store the rowid/primary-key. There is only
|
||||
** one, so just keep it in its register(s) and fall through to the
|
||||
** delete code.
|
||||
*/
|
||||
** delete code. */
|
||||
nKey = nPk; /* OP_Found will use an unpacked key */
|
||||
aToOpen = sqlite3DbMallocRaw(db, nIdx+2);
|
||||
if( aToOpen==0 ){
|
||||
@ -437,27 +447,27 @@ void sqlite3DeleteFrom(
|
||||
if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iTabCur] = 0;
|
||||
if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iTabCur] = 0;
|
||||
if( addrEphOpen ) sqlite3VdbeChangeToNoop(v, addrEphOpen);
|
||||
addrDelete = sqlite3VdbeAddOp0(v, OP_Goto); /* Jump to DELETE logic */
|
||||
}else if( pPk ){
|
||||
/* Construct a composite key for the row to be deleted and remember it */
|
||||
iKey = ++pParse->nMem;
|
||||
nKey = 0; /* Zero tells OP_Found to use a composite key */
|
||||
sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey,
|
||||
sqlite3IndexAffinityStr(pParse->db, pPk), nPk);
|
||||
sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey);
|
||||
}else{
|
||||
/* Get the rowid of the row to be deleted and remember it in the RowSet */
|
||||
nKey = 1; /* OP_Seek always uses a single rowid */
|
||||
sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, iKey);
|
||||
if( pPk ){
|
||||
/* Add the PK key for this row to the temporary table */
|
||||
iKey = ++pParse->nMem;
|
||||
nKey = 0; /* Zero tells OP_Found to use a composite key */
|
||||
sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey,
|
||||
sqlite3IndexAffinityStr(pParse->db, pPk), nPk);
|
||||
sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey);
|
||||
}else{
|
||||
/* Add the rowid of the row to be deleted to the RowSet */
|
||||
nKey = 1; /* OP_Seek always uses a single rowid */
|
||||
sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, iKey);
|
||||
}
|
||||
}
|
||||
|
||||
/* End of the WHERE loop */
|
||||
sqlite3WhereEnd(pWInfo);
|
||||
if( okOnePass ){
|
||||
/* Bypass the delete logic below if the WHERE loop found zero rows */
|
||||
/* If this DELETE cannot use the ONEPASS strategy, this is the
|
||||
** end of the WHERE loop */
|
||||
if( eOnePass!=ONEPASS_OFF ){
|
||||
addrBypass = sqlite3VdbeMakeLabel(v);
|
||||
sqlite3VdbeGoto(v, addrBypass);
|
||||
sqlite3VdbeJumpHere(v, addrDelete);
|
||||
}else{
|
||||
sqlite3WhereEnd(pWInfo);
|
||||
}
|
||||
|
||||
/* Unless this is a view, open cursors for the table we are
|
||||
@ -466,20 +476,23 @@ void sqlite3DeleteFrom(
|
||||
** triggers.
|
||||
*/
|
||||
if( !isView ){
|
||||
int iAddrOnce = 0;
|
||||
if( eOnePass==ONEPASS_MULTI ){
|
||||
iAddrOnce = sqlite3CodeOnce(pParse); VdbeCoverage(v);
|
||||
}
|
||||
testcase( IsVirtual(pTab) );
|
||||
sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, iTabCur, aToOpen,
|
||||
&iDataCur, &iIdxCur);
|
||||
assert( pPk || IsVirtual(pTab) || iDataCur==iTabCur );
|
||||
assert( pPk || IsVirtual(pTab) || iIdxCur==iDataCur+1 );
|
||||
if( eOnePass==ONEPASS_MULTI ) sqlite3VdbeJumpHere(v, iAddrOnce);
|
||||
}
|
||||
|
||||
/* Set up a loop over the rowids/primary-keys that were found in the
|
||||
** where-clause loop above.
|
||||
*/
|
||||
if( okOnePass ){
|
||||
/* Just one row. Hence the top-of-loop is a no-op */
|
||||
if( eOnePass!=ONEPASS_OFF ){
|
||||
assert( nKey==nPk ); /* OP_Found will use an unpacked key */
|
||||
assert( !IsVirtual(pTab) );
|
||||
if( aToOpen[iDataCur-iTabCur] ){
|
||||
assert( pPk!=0 || pTab->pSelect!=0 );
|
||||
sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, addrBypass, iKey, nKey);
|
||||
@ -507,13 +520,18 @@ void sqlite3DeleteFrom(
|
||||
#endif
|
||||
{
|
||||
int count = (pParse->nested==0); /* True to count changes */
|
||||
int iIdxNoSeek = -1;
|
||||
if( bComplex==0 && aiCurOnePass[1]!=iDataCur ){
|
||||
iIdxNoSeek = aiCurOnePass[1];
|
||||
}
|
||||
sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
|
||||
iKey, nKey, count, OE_Default, okOnePass);
|
||||
iKey, nKey, count, OE_Default, eOnePass, iIdxNoSeek);
|
||||
}
|
||||
|
||||
/* End of the loop over all rowids/primary-keys. */
|
||||
if( okOnePass ){
|
||||
if( eOnePass!=ONEPASS_OFF ){
|
||||
sqlite3VdbeResolveLabel(v, addrBypass);
|
||||
sqlite3WhereEnd(pWInfo);
|
||||
}else if( pPk ){
|
||||
sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop+1); VdbeCoverage(v);
|
||||
sqlite3VdbeJumpHere(v, addrLoop);
|
||||
@ -585,6 +603,25 @@ delete_from_cleanup:
|
||||
** sequence of nPk memory cells starting at iPk. If nPk==0 that means
|
||||
** that a search record formed from OP_MakeRecord is contained in the
|
||||
** single memory location iPk.
|
||||
**
|
||||
** eMode:
|
||||
** Parameter eMode may be passed either ONEPASS_OFF (0), ONEPASS_SINGLE, or
|
||||
** ONEPASS_MULTI. If eMode is not ONEPASS_OFF, then the cursor
|
||||
** iDataCur already points to the row to delete. If eMode is ONEPASS_OFF
|
||||
** then this function must seek iDataCur to the entry identified by iPk
|
||||
** and nPk before reading from it.
|
||||
**
|
||||
** If eMode is ONEPASS_MULTI, then this call is being made as part
|
||||
** of a ONEPASS delete that affects multiple rows. In this case, if
|
||||
** iIdxNoSeek is a valid cursor number (>=0), then its position should
|
||||
** be preserved following the delete operation. Or, if iIdxNoSeek is not
|
||||
** a valid cursor number, the position of iDataCur should be preserved
|
||||
** instead.
|
||||
**
|
||||
** iIdxNoSeek:
|
||||
** If iIdxNoSeek is a valid cursor number (>=0), then it identifies an
|
||||
** index cursor (from within array of cursors starting at iIdxCur) that
|
||||
** already points to the index entry to be deleted.
|
||||
*/
|
||||
void sqlite3GenerateRowDelete(
|
||||
Parse *pParse, /* Parsing context */
|
||||
@ -596,7 +633,8 @@ void sqlite3GenerateRowDelete(
|
||||
i16 nPk, /* Number of PRIMARY KEY memory cells */
|
||||
u8 count, /* If non-zero, increment the row change counter */
|
||||
u8 onconf, /* Default ON CONFLICT policy for triggers */
|
||||
u8 bNoSeek /* iDataCur is already pointing to the row to delete */
|
||||
u8 eMode, /* ONEPASS_OFF, _SINGLE, or _MULTI. See above */
|
||||
int iIdxNoSeek /* Cursor number of cursor that does not need seeking */
|
||||
){
|
||||
Vdbe *v = pParse->pVdbe; /* Vdbe */
|
||||
int iOld = 0; /* First register in OLD.* array */
|
||||
@ -613,7 +651,7 @@ void sqlite3GenerateRowDelete(
|
||||
** not attempt to delete it or fire any DELETE triggers. */
|
||||
iLabel = sqlite3VdbeMakeLabel(v);
|
||||
opSeek = HasRowid(pTab) ? OP_NotExists : OP_NotFound;
|
||||
if( !bNoSeek ){
|
||||
if( eMode==ONEPASS_OFF ){
|
||||
sqlite3VdbeAddOp4Int(v, opSeek, iDataCur, iLabel, iPk, nPk);
|
||||
VdbeCoverageIf(v, opSeek==OP_NotExists);
|
||||
VdbeCoverageIf(v, opSeek==OP_NotFound);
|
||||
@ -673,11 +711,15 @@ void sqlite3GenerateRowDelete(
|
||||
** a view (in which case the only effect of the DELETE statement is to
|
||||
** fire the INSTEAD OF triggers). */
|
||||
if( pTab->pSelect==0 ){
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, 0);
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek);
|
||||
sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0));
|
||||
if( count ){
|
||||
sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT);
|
||||
}
|
||||
if( iIdxNoSeek>=0 ){
|
||||
sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek);
|
||||
}
|
||||
sqlite3VdbeChangeP5(v, eMode==ONEPASS_MULTI);
|
||||
}
|
||||
|
||||
/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
|
||||
@ -720,7 +762,8 @@ void sqlite3GenerateRowIndexDelete(
|
||||
Table *pTab, /* Table containing the row to be deleted */
|
||||
int iDataCur, /* Cursor of table holding data. */
|
||||
int iIdxCur, /* First index cursor */
|
||||
int *aRegIdx /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */
|
||||
int *aRegIdx, /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */
|
||||
int iIdxNoSeek /* Do not delete from this cursor */
|
||||
){
|
||||
int i; /* Index loop counter */
|
||||
int r1 = -1; /* Register holding an index key */
|
||||
@ -736,11 +779,12 @@ void sqlite3GenerateRowIndexDelete(
|
||||
assert( iIdxCur+i!=iDataCur || pPk==pIdx );
|
||||
if( aRegIdx!=0 && aRegIdx[i]==0 ) continue;
|
||||
if( pIdx==pPk ) continue;
|
||||
if( iIdxCur+i==iIdxNoSeek ) continue;
|
||||
VdbeModuleComment((v, "GenRowIdxDel for %s", pIdx->zName));
|
||||
r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 1,
|
||||
&iPartIdxLabel, pPrior, r1);
|
||||
&iPartIdxLabel, pPrior, r1);
|
||||
sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1,
|
||||
pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn);
|
||||
pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn);
|
||||
sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
|
||||
pPrior = pIdx;
|
||||
}
|
||||
@ -789,14 +833,13 @@ int sqlite3GenerateIndexKey(
|
||||
){
|
||||
Vdbe *v = pParse->pVdbe;
|
||||
int j;
|
||||
Table *pTab = pIdx->pTable;
|
||||
int regBase;
|
||||
int nCol;
|
||||
|
||||
if( piPartIdxLabel ){
|
||||
if( pIdx->pPartIdxWhere ){
|
||||
*piPartIdxLabel = sqlite3VdbeMakeLabel(v);
|
||||
pParse->iPartIdxTab = iDataCur;
|
||||
pParse->iSelfTab = iDataCur;
|
||||
sqlite3ExprCachePush(pParse);
|
||||
sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, *piPartIdxLabel,
|
||||
SQLITE_JUMPIFNULL);
|
||||
@ -808,9 +851,14 @@ int sqlite3GenerateIndexKey(
|
||||
regBase = sqlite3GetTempRange(pParse, nCol);
|
||||
if( pPrior && (regBase!=regPrior || pPrior->pPartIdxWhere) ) pPrior = 0;
|
||||
for(j=0; j<nCol; j++){
|
||||
if( pPrior && pPrior->aiColumn[j]==pIdx->aiColumn[j] ) continue;
|
||||
sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, pIdx->aiColumn[j],
|
||||
regBase+j);
|
||||
if( pPrior
|
||||
&& pPrior->aiColumn[j]==pIdx->aiColumn[j]
|
||||
&& pPrior->aiColumn[j]>=(-1)
|
||||
){
|
||||
/* This column was already computed by the previous index */
|
||||
continue;
|
||||
}
|
||||
sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j, regBase+j);
|
||||
/* If the column affinity is REAL but the number is an integer, then it
|
||||
** might be stored in the table as an integer (using a compact
|
||||
** representation) then converted to REAL by an OP_RealAffinity opcode.
|
||||
|
39
src/expr.c
39
src/expr.c
@ -91,7 +91,7 @@ Expr *sqlite3ExprAddCollateString(Parse *pParse, Expr *pExpr, const char *zC){
|
||||
}
|
||||
|
||||
/*
|
||||
** Skip over any TK_COLLATE or TK_AS operators and any unlikely()
|
||||
** Skip over any TK_COLLATE operators and any unlikely()
|
||||
** or likelihood() function at the root of an expression.
|
||||
*/
|
||||
Expr *sqlite3ExprSkipCollate(Expr *pExpr){
|
||||
@ -102,7 +102,7 @@ Expr *sqlite3ExprSkipCollate(Expr *pExpr){
|
||||
assert( pExpr->op==TK_FUNCTION );
|
||||
pExpr = pExpr->x.pList->a[0].pExpr;
|
||||
}else{
|
||||
assert( pExpr->op==TK_COLLATE || pExpr->op==TK_AS );
|
||||
assert( pExpr->op==TK_COLLATE );
|
||||
pExpr = pExpr->pLeft;
|
||||
}
|
||||
}
|
||||
@ -2448,6 +2448,28 @@ static void sqlite3ExprCachePinRegister(Parse *pParse, int iReg){
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate code that will load into register regOut a value that is
|
||||
** appropriate for the iIdxCol-th column of index pIdx.
|
||||
*/
|
||||
void sqlite3ExprCodeLoadIndexColumn(
|
||||
Parse *pParse, /* The parsing context */
|
||||
Index *pIdx, /* The index whose column is to be loaded */
|
||||
int iTabCur, /* Cursor pointing to a table row */
|
||||
int iIdxCol, /* The column of the index to be loaded */
|
||||
int regOut /* Store the index column value in this register */
|
||||
){
|
||||
i16 iTabCol = pIdx->aiColumn[iIdxCol];
|
||||
if( iTabCol>=(-1) ){
|
||||
sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable, iTabCur,
|
||||
iTabCol, regOut);
|
||||
return;
|
||||
}
|
||||
assert( pIdx->aColExpr );
|
||||
assert( pIdx->aColExpr->nExpr>iIdxCol );
|
||||
pParse->iSelfTab = iTabCur;
|
||||
sqlite3ExprCode(pParse, pIdx->aColExpr->a[iIdxCol].pExpr, regOut);
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate code to extract the value of the iCol-th column of a table.
|
||||
*/
|
||||
@ -2633,8 +2655,9 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
|
||||
inReg = pExpr->iColumn + pParse->ckBase;
|
||||
break;
|
||||
}else{
|
||||
/* Deleting from a partial index */
|
||||
iTab = pParse->iPartIdxTab;
|
||||
/* Coding an expression that is part of an index where column names
|
||||
** in the index refer to the table to which the index belongs */
|
||||
iTab = pParse->iSelfTab;
|
||||
}
|
||||
}
|
||||
inReg = sqlite3ExprCodeGetColumn(pParse, pExpr->pTab,
|
||||
@ -2694,10 +2717,6 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
|
||||
inReg = pExpr->iTable;
|
||||
break;
|
||||
}
|
||||
case TK_AS: {
|
||||
inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
|
||||
break;
|
||||
}
|
||||
#ifndef SQLITE_OMIT_CAST
|
||||
case TK_CAST: {
|
||||
/* Expressions of the form: CAST(pLeft AS token) */
|
||||
@ -3781,7 +3800,9 @@ int sqlite3ExprCompare(Expr *pA, Expr *pB, int iTab){
|
||||
return 2;
|
||||
}
|
||||
if( pA->op!=TK_COLUMN && ALWAYS(pA->op!=TK_AGG_COLUMN) && pA->u.zToken ){
|
||||
if( strcmp(pA->u.zToken,pB->u.zToken)!=0 ){
|
||||
if( pA->op==TK_FUNCTION ){
|
||||
if( sqlite3StrICmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2;
|
||||
}else if( strcmp(pA->u.zToken,pB->u.zToken)!=0 ){
|
||||
return pA->op==TK_COLLATE ? 1 : 2;
|
||||
}
|
||||
}
|
||||
|
12
src/func.c
12
src/func.c
@ -1737,15 +1737,15 @@ void sqlite3RegisterGlobalFunctions(void){
|
||||
VFUNCTION(random, 0, 0, 0, randomFunc ),
|
||||
VFUNCTION(randomblob, 1, 0, 0, randomBlob ),
|
||||
FUNCTION(nullif, 2, 0, 1, nullifFunc ),
|
||||
FUNCTION(sqlite_version, 0, 0, 0, versionFunc ),
|
||||
FUNCTION(sqlite_source_id, 0, 0, 0, sourceidFunc ),
|
||||
DFUNCTION(sqlite_version, 0, 0, 0, versionFunc ),
|
||||
DFUNCTION(sqlite_source_id, 0, 0, 0, sourceidFunc ),
|
||||
FUNCTION(sqlite_log, 2, 0, 0, errlogFunc ),
|
||||
#if SQLITE_USER_AUTHENTICATION
|
||||
FUNCTION(sqlite_crypt, 2, 0, 0, sqlite3CryptFunc ),
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
|
||||
FUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ),
|
||||
FUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ),
|
||||
DFUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ),
|
||||
DFUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ),
|
||||
#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */
|
||||
FUNCTION(quote, 1, 0, 0, quoteFunc ),
|
||||
VFUNCTION(last_insert_rowid, 0, 0, 0, last_insert_rowid),
|
||||
@ -1757,8 +1757,8 @@ void sqlite3RegisterGlobalFunctions(void){
|
||||
FUNCTION(soundex, 1, 0, 0, soundexFunc ),
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_LOAD_EXTENSION
|
||||
FUNCTION(load_extension, 1, 0, 0, loadExt ),
|
||||
FUNCTION(load_extension, 2, 0, 0, loadExt ),
|
||||
VFUNCTION(load_extension, 1, 0, 0, loadExt ),
|
||||
VFUNCTION(load_extension, 2, 0, 0, loadExt ),
|
||||
#endif
|
||||
AGGREGATE(sum, 1, 0, 0, sumStep, sumFinalize ),
|
||||
AGGREGATE(total, 1, 0, 0, sumStep, totalFinalize ),
|
||||
|
55
src/insert.c
55
src/insert.c
@ -88,7 +88,18 @@ const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){
|
||||
}
|
||||
for(n=0; n<pIdx->nColumn; n++){
|
||||
i16 x = pIdx->aiColumn[n];
|
||||
pIdx->zColAff[n] = x<0 ? SQLITE_AFF_INTEGER : pTab->aCol[x].affinity;
|
||||
if( x>=0 ){
|
||||
pIdx->zColAff[n] = pTab->aCol[x].affinity;
|
||||
}else if( x==(-1) ){
|
||||
pIdx->zColAff[n] = SQLITE_AFF_INTEGER;
|
||||
}else{
|
||||
char aff;
|
||||
assert( x==(-2) );
|
||||
assert( pIdx->aColExpr!=0 );
|
||||
aff = sqlite3ExprAffinity(pIdx->aColExpr->a[n].pExpr);
|
||||
if( aff==0 ) aff = SQLITE_AFF_BLOB;
|
||||
pIdx->zColAff[n] = aff;
|
||||
}
|
||||
}
|
||||
pIdx->zColAff[n] = 0;
|
||||
}
|
||||
@ -1336,10 +1347,13 @@ void sqlite3GenerateConstraintChecks(
|
||||
if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){
|
||||
sqlite3MultiWrite(pParse);
|
||||
sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
|
||||
regNewData, 1, 0, OE_Replace, 1);
|
||||
}else if( pTab->pIndex ){
|
||||
sqlite3MultiWrite(pParse);
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, 0);
|
||||
regNewData, 1, 0, OE_Replace,
|
||||
ONEPASS_SINGLE, -1);
|
||||
}else{
|
||||
if( pTab->pIndex ){
|
||||
sqlite3MultiWrite(pParse);
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,-1);
|
||||
}
|
||||
}
|
||||
seenReplace = 1;
|
||||
break;
|
||||
@ -1394,15 +1408,22 @@ void sqlite3GenerateConstraintChecks(
|
||||
for(i=0; i<pIdx->nColumn; i++){
|
||||
int iField = pIdx->aiColumn[i];
|
||||
int x;
|
||||
if( iField<0 || iField==pTab->iPKey ){
|
||||
if( regRowid==regIdx+i ) continue; /* ROWID already in regIdx+i */
|
||||
x = regNewData;
|
||||
regRowid = pIdx->pPartIdxWhere ? -1 : regIdx+i;
|
||||
if( iField==(-2) ){
|
||||
pParse->ckBase = regNewData+1;
|
||||
sqlite3ExprCode(pParse, pIdx->aColExpr->a[i].pExpr, regIdx+i);
|
||||
pParse->ckBase = 0;
|
||||
VdbeComment((v, "%s column %d", pIdx->zName, i));
|
||||
}else{
|
||||
x = iField + regNewData + 1;
|
||||
if( iField==(-1) || iField==pTab->iPKey ){
|
||||
if( regRowid==regIdx+i ) continue; /* ROWID already in regIdx+i */
|
||||
x = regNewData;
|
||||
regRowid = pIdx->pPartIdxWhere ? -1 : regIdx+i;
|
||||
}else{
|
||||
x = iField + regNewData + 1;
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_SCopy, x, regIdx+i);
|
||||
VdbeComment((v, "%s", iField<0 ? "rowid" : pTab->aCol[iField].zName));
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_SCopy, x, regIdx+i);
|
||||
VdbeComment((v, "%s", iField<0 ? "rowid" : pTab->aCol[iField].zName));
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn, aRegIdx[ix]);
|
||||
VdbeComment((v, "for %s", pIdx->zName));
|
||||
@ -1510,7 +1531,8 @@ void sqlite3GenerateConstraintChecks(
|
||||
pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0);
|
||||
}
|
||||
sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
|
||||
regR, nPkField, 0, OE_Replace, pIdx==pPk);
|
||||
regR, nPkField, 0, OE_Replace,
|
||||
(pIdx==pPk ? ONEPASS_SINGLE : ONEPASS_OFF), -1);
|
||||
seenReplace = 1;
|
||||
break;
|
||||
}
|
||||
@ -1723,6 +1745,13 @@ static int xferCompatibleIndex(Index *pDest, Index *pSrc){
|
||||
if( pSrc->aiColumn[i]!=pDest->aiColumn[i] ){
|
||||
return 0; /* Different columns indexed */
|
||||
}
|
||||
if( pSrc->aiColumn[i]==(-2) ){
|
||||
assert( pSrc->aColExpr!=0 && pDest->aColExpr!=0 );
|
||||
if( sqlite3ExprCompare(pSrc->aColExpr->a[i].pExpr,
|
||||
pDest->aColExpr->a[i].pExpr, -1)!=0 ){
|
||||
return 0; /* Different expressions in the index */
|
||||
}
|
||||
}
|
||||
if( pSrc->aSortOrder[i]!=pDest->aSortOrder[i] ){
|
||||
return 0; /* Different sort orders */
|
||||
}
|
||||
|
109
src/lempar.c
109
src/lempar.c
@ -56,15 +56,19 @@
|
||||
** ParseARG_PDECL A parameter declaration for the %extra_argument
|
||||
** ParseARG_STORE Code to store %extra_argument into yypParser
|
||||
** ParseARG_FETCH Code to extract %extra_argument from yypParser
|
||||
** YYNSTATE the combined number of states.
|
||||
** YYNRULE the number of rules in the grammar
|
||||
** YYERRORSYMBOL is the code number of the error symbol. If not
|
||||
** defined, then do no error processing.
|
||||
** YYNSTATE the combined number of states.
|
||||
** YYNRULE the number of rules in the grammar
|
||||
** YY_MAX_SHIFT Maximum value for shift actions
|
||||
** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions
|
||||
** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions
|
||||
** YY_MIN_REDUCE Maximum value for reduce actions
|
||||
** YY_ERROR_ACTION The yy_action[] code for syntax error
|
||||
** YY_ACCEPT_ACTION The yy_action[] code for accept
|
||||
** YY_NO_ACTION The yy_action[] code for no-op
|
||||
*/
|
||||
%%
|
||||
#define YY_NO_ACTION (YYNSTATE+YYNRULE+2)
|
||||
#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1)
|
||||
#define YY_ERROR_ACTION (YYNSTATE+YYNRULE)
|
||||
|
||||
/* The yyzerominor constant is used to initialize instances of
|
||||
** YYMINORTYPE objects to zero. */
|
||||
@ -91,16 +95,20 @@ static const YYMINORTYPE yyzerominor = { 0 };
|
||||
** Suppose the action integer is N. Then the action is determined as
|
||||
** follows
|
||||
**
|
||||
** 0 <= N < YYNSTATE Shift N. That is, push the lookahead
|
||||
** 0 <= N <= YY_MAX_SHIFT Shift N. That is, push the lookahead
|
||||
** token onto the stack and goto state N.
|
||||
**
|
||||
** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE.
|
||||
** N between YY_MIN_SHIFTREDUCE Shift to an arbitrary state then
|
||||
** and YY_MAX_SHIFTREDUCE reduce by rule N-YY_MIN_SHIFTREDUCE.
|
||||
**
|
||||
** N == YYNSTATE+YYNRULE A syntax error has occurred.
|
||||
** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE
|
||||
** and YY_MAX_REDUCE
|
||||
|
||||
** N == YY_ERROR_ACTION A syntax error has occurred.
|
||||
**
|
||||
** N == YYNSTATE+YYNRULE+1 The parser accepts its input.
|
||||
** N == YY_ACCEPT_ACTION The parser accepts its input.
|
||||
**
|
||||
** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused
|
||||
** N == YY_NO_ACTION No such action. Denotes unused
|
||||
** slots in the yy_action[] table.
|
||||
**
|
||||
** The action table is constructed as a single large table named yy_action[].
|
||||
@ -159,9 +167,13 @@ static const YYCODETYPE yyFallback[] = {
|
||||
** + The semantic value stored at this level of the stack. This is
|
||||
** the information used by the action routines in the grammar.
|
||||
** It is sometimes called the "minor" token.
|
||||
**
|
||||
** After the "shift" half of a SHIFTREDUCE action, the stateno field
|
||||
** actually contains the reduce action for the second half of the
|
||||
** SHIFTREDUCE.
|
||||
*/
|
||||
struct yyStackEntry {
|
||||
YYACTIONTYPE stateno; /* The state-number */
|
||||
YYACTIONTYPE stateno; /* The state-number, or reduce action in SHIFTREDUCE */
|
||||
YYCODETYPE major; /* The major token value. This is the code
|
||||
** number for the token at this stack level */
|
||||
YYMINORTYPE minor; /* The user-supplied minor token value. This
|
||||
@ -395,10 +407,10 @@ static int yy_find_shift_action(
|
||||
int i;
|
||||
int stateno = pParser->yystack[pParser->yyidx].stateno;
|
||||
|
||||
if( stateno>YY_SHIFT_COUNT
|
||||
|| (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){
|
||||
return yy_default[stateno];
|
||||
}
|
||||
if( stateno>=YY_MIN_REDUCE ) return stateno;
|
||||
assert( stateno <= YY_SHIFT_COUNT );
|
||||
i = yy_shift_ofst[stateno];
|
||||
if( i==YY_SHIFT_USE_DFLT ) return yy_default[stateno];
|
||||
assert( iLookAhead!=YYNOCODE );
|
||||
i += iLookAhead;
|
||||
if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){
|
||||
@ -499,7 +511,29 @@ static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){
|
||||
}
|
||||
|
||||
/*
|
||||
** Perform a shift action.
|
||||
** Print tracing information for a SHIFT action
|
||||
*/
|
||||
#ifndef NDEBUG
|
||||
static void yyTraceShift(yyParser *yypParser, int yyNewState){
|
||||
if( yyTraceFILE ){
|
||||
int i;
|
||||
if( yyNewState<YYNSTATE ){
|
||||
fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState);
|
||||
fprintf(yyTraceFILE,"%sStack:",yyTracePrompt);
|
||||
for(i=1; i<=yypParser->yyidx; i++)
|
||||
fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]);
|
||||
fprintf(yyTraceFILE,"\n");
|
||||
}else{
|
||||
fprintf(yyTraceFILE,"%sShift *\n",yyTracePrompt);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
# define yyTraceShift(X,Y)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Perform a shift action. Return the number of errors.
|
||||
*/
|
||||
static void yy_shift(
|
||||
yyParser *yypParser, /* The parser to be shifted */
|
||||
@ -532,16 +566,7 @@ static void yy_shift(
|
||||
yytos->stateno = (YYACTIONTYPE)yyNewState;
|
||||
yytos->major = (YYCODETYPE)yyMajor;
|
||||
yytos->minor = *yypMinor;
|
||||
#ifndef NDEBUG
|
||||
if( yyTraceFILE && yypParser->yyidx>0 ){
|
||||
int i;
|
||||
fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState);
|
||||
fprintf(yyTraceFILE,"%sStack:",yyTracePrompt);
|
||||
for(i=1; i<=yypParser->yyidx; i++)
|
||||
fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]);
|
||||
fprintf(yyTraceFILE,"\n");
|
||||
}
|
||||
#endif
|
||||
yyTraceShift(yypParser, yyNewState);
|
||||
}
|
||||
|
||||
/* The following table contains information about every rule that
|
||||
@ -574,8 +599,9 @@ static void yy_reduce(
|
||||
#ifndef NDEBUG
|
||||
if( yyTraceFILE && yyruleno>=0
|
||||
&& yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){
|
||||
fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt,
|
||||
yyRuleName[yyruleno]);
|
||||
yysize = yyRuleInfo[yyruleno].nrhs;
|
||||
fprintf(yyTraceFILE, "%sReduce [%s] -> state %d.\n", yyTracePrompt,
|
||||
yyRuleName[yyruleno], yymsp[-yysize].stateno);
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
@ -613,9 +639,9 @@ static void yy_reduce(
|
||||
yysize = yyRuleInfo[yyruleno].nrhs;
|
||||
yypParser->yyidx -= yysize;
|
||||
yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto);
|
||||
if( yyact < YYNSTATE ){
|
||||
#ifdef NDEBUG
|
||||
/* If we are not debugging and the reduce action popped at least
|
||||
if( yyact <= YY_MAX_SHIFTREDUCE ){
|
||||
if( yyact>YY_MAX_SHIFT ) yyact += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE;
|
||||
/* If the reduce action popped at least
|
||||
** one element off the stack, then we can push the new element back
|
||||
** onto the stack here, and skip the stack overflow test in yy_shift().
|
||||
** That gives a significant speed improvement. */
|
||||
@ -625,13 +651,12 @@ static void yy_reduce(
|
||||
yymsp->stateno = (YYACTIONTYPE)yyact;
|
||||
yymsp->major = (YYCODETYPE)yygoto;
|
||||
yymsp->minor = yygotominor;
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
yyTraceShift(yypParser, yyact);
|
||||
}else{
|
||||
yy_shift(yypParser,yyact,yygoto,&yygotominor);
|
||||
}
|
||||
}else{
|
||||
assert( yyact == YYNSTATE + YYNRULE + 1 );
|
||||
assert( yyact == YY_ACCEPT_ACTION );
|
||||
yy_accept(yypParser);
|
||||
}
|
||||
}
|
||||
@ -755,12 +780,13 @@ void Parse(
|
||||
|
||||
do{
|
||||
yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor);
|
||||
if( yyact<YYNSTATE ){
|
||||
if( yyact <= YY_MAX_SHIFTREDUCE ){
|
||||
if( yyact > YY_MAX_SHIFT ) yyact += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE;
|
||||
yy_shift(yypParser,yyact,yymajor,&yyminorunion);
|
||||
yypParser->yyerrcnt--;
|
||||
yymajor = YYNOCODE;
|
||||
}else if( yyact < YYNSTATE + YYNRULE ){
|
||||
yy_reduce(yypParser,yyact-YYNSTATE);
|
||||
}else if( yyact <= YY_MAX_REDUCE ){
|
||||
yy_reduce(yypParser,yyact-YY_MIN_REDUCE);
|
||||
}else{
|
||||
assert( yyact == YY_ERROR_ACTION );
|
||||
#ifdef YYERRORSYMBOL
|
||||
@ -810,7 +836,7 @@ void Parse(
|
||||
yymx != YYERRORSYMBOL &&
|
||||
(yyact = yy_find_reduce_action(
|
||||
yypParser->yystack[yypParser->yyidx].stateno,
|
||||
YYERRORSYMBOL)) >= YYNSTATE
|
||||
YYERRORSYMBOL)) >= YY_MIN_REDUCE
|
||||
){
|
||||
yy_pop_parser_stack(yypParser);
|
||||
}
|
||||
@ -860,5 +886,10 @@ void Parse(
|
||||
#endif
|
||||
}
|
||||
}while( yymajor!=YYNOCODE && yypParser->yyidx>=0 );
|
||||
#ifndef NDEBUG
|
||||
if( yyTraceFILE ){
|
||||
fprintf(yyTraceFILE,"%sReturn\n",yyTracePrompt);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
@ -407,7 +407,10 @@ static const sqlite3_api_routines sqlite3Apis = {
|
||||
(sqlite3_value*(*)(const sqlite3_value*))sqlite3_value_dup,
|
||||
sqlite3_value_free,
|
||||
sqlite3_result_zeroblob64,
|
||||
sqlite3_bind_zeroblob64
|
||||
sqlite3_bind_zeroblob64,
|
||||
/* Version 3.8.12 and later */
|
||||
sqlite3_value_subtype,
|
||||
sqlite3_result_subtype
|
||||
};
|
||||
|
||||
/*
|
||||
|
104
src/malloc.c
104
src/malloc.c
@ -45,7 +45,7 @@ typedef struct ScratchFreeslot {
|
||||
*/
|
||||
static SQLITE_WSD struct Mem0Global {
|
||||
sqlite3_mutex *mutex; /* Mutex to serialize access */
|
||||
sqlite3_int64 alarmThreshold; /* The soft heap limit */
|
||||
sqlite3_int64 alarmThreshold; /* The soft heap limit */
|
||||
|
||||
/*
|
||||
** Pointers to the end of sqlite3GlobalConfig.pScratch memory
|
||||
@ -73,59 +73,20 @@ sqlite3_mutex *sqlite3MallocMutex(void){
|
||||
return mem0.mutex;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the amount of memory currently in use.
|
||||
*/
|
||||
static sqlite3_int64 memInUse(void){
|
||||
assert( sqlite3_mutex_held(mem0.mutex) );
|
||||
return sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED);
|
||||
}
|
||||
|
||||
/*
|
||||
** Called when the soft heap limit is exceeded for an allocation
|
||||
** of nBytes.
|
||||
*/
|
||||
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
|
||||
static void sqlite3HeapLimitExceeded(int nByte){
|
||||
sqlite3_int64 excess = memInUse() + nByte - mem0.alarmThreshold;
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
sqlite3_release_memory((int)(excess & 0x7fffffff));
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
}
|
||||
#else
|
||||
# define sqlite3HeapLimitExceeded(X) /* no-op */
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Check to see if increasing the total memory usage by nNew bytes
|
||||
** will exceed the soft heap limit.
|
||||
**
|
||||
** If the soft heap limit is exceeded, set the mem0.nearlyFull flag
|
||||
** and invoke sqlite3HeapLimitExceeded() to try to free up some
|
||||
** memory.
|
||||
*/
|
||||
static void sqlite3CheckSoftHeapLimit(int nNew){
|
||||
assert( sqlite3_mutex_held(mem0.mutex) );
|
||||
if( mem0.alarmThreshold>0 ){
|
||||
if( mem0.alarmThreshold-nNew >= memInUse() ){
|
||||
mem0.nearlyFull = 1;
|
||||
sqlite3HeapLimitExceeded(nNew);
|
||||
}else{
|
||||
mem0.nearlyFull = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
/*
|
||||
** Deprecated external interface. First deprecated 2007-11-05. Changed
|
||||
** into a no-op on 2015-09-02.
|
||||
** Deprecated external interface. It used to set an alarm callback
|
||||
** that was invoked when memory usage grew too large. Now it is a
|
||||
** no-op.
|
||||
*/
|
||||
int sqlite3_memory_alarm(
|
||||
void(*xCallback)(void *pArg, sqlite3_int64 used,int N),
|
||||
void *pArg,
|
||||
sqlite3_int64 iThreshold
|
||||
){
|
||||
(void)xCallback;
|
||||
(void)pArg;
|
||||
(void)iThreshold;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
#endif
|
||||
@ -136,20 +97,24 @@ int sqlite3_memory_alarm(
|
||||
*/
|
||||
sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 n){
|
||||
sqlite3_int64 priorLimit;
|
||||
sqlite3_int64 excess;
|
||||
sqlite3_int64 nUsed;
|
||||
#ifndef SQLITE_OMIT_AUTOINIT
|
||||
int rc = sqlite3_initialize();
|
||||
if( rc ) return -1;
|
||||
#endif
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
priorLimit = mem0.alarmThreshold;
|
||||
if( n>0 ){
|
||||
mem0.alarmThreshold = n;
|
||||
sqlite3CheckSoftHeapLimit(0);
|
||||
}else if( n==0 ){
|
||||
mem0.alarmThreshold = 0;
|
||||
mem0.nearlyFull = 0;
|
||||
if( n<0 ){
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
return priorLimit;
|
||||
}
|
||||
mem0.alarmThreshold = n;
|
||||
nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED);
|
||||
mem0.nearlyFull = (n>0 && n<=nUsed);
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
excess = sqlite3_memory_used() - n;
|
||||
if( excess>0 ) sqlite3_release_memory((int)(excess & 0x7fffffff));
|
||||
return priorLimit;
|
||||
}
|
||||
void sqlite3_soft_heap_limit(int n){
|
||||
@ -240,6 +205,16 @@ sqlite3_int64 sqlite3_memory_highwater(int resetFlag){
|
||||
return mx;
|
||||
}
|
||||
|
||||
/*
|
||||
** Trigger the alarm
|
||||
*/
|
||||
static void sqlite3MallocAlarm(int nByte){
|
||||
if( mem0.alarmThreshold<=0 ) return;
|
||||
sqlite3_mutex_leave(mem0.mutex);
|
||||
sqlite3_release_memory(nByte);
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
** Do a memory allocation with statistics and alarms. Assume the
|
||||
** lock is already held.
|
||||
@ -250,11 +225,19 @@ static int mallocWithAlarm(int n, void **pp){
|
||||
assert( sqlite3_mutex_held(mem0.mutex) );
|
||||
nFull = sqlite3GlobalConfig.m.xRoundup(n);
|
||||
sqlite3StatusSet(SQLITE_STATUS_MALLOC_SIZE, n);
|
||||
sqlite3CheckSoftHeapLimit(nFull);
|
||||
if( mem0.alarmThreshold>0 ){
|
||||
sqlite3_int64 nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED);
|
||||
if( nUsed >= mem0.alarmThreshold - nFull ){
|
||||
mem0.nearlyFull = 1;
|
||||
sqlite3MallocAlarm(nFull);
|
||||
}else{
|
||||
mem0.nearlyFull = 0;
|
||||
}
|
||||
}
|
||||
p = sqlite3GlobalConfig.m.xMalloc(nFull);
|
||||
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
|
||||
if( p==0 && mem0.alarmThreshold ){
|
||||
sqlite3HeapLimitExceeded(nFull);
|
||||
if( p==0 && mem0.alarmThreshold>0 ){
|
||||
sqlite3MallocAlarm(nFull);
|
||||
p = sqlite3GlobalConfig.m.xMalloc(nFull);
|
||||
}
|
||||
#endif
|
||||
@ -537,14 +520,15 @@ void *sqlite3Realloc(void *pOld, u64 nBytes){
|
||||
sqlite3_mutex_enter(mem0.mutex);
|
||||
sqlite3StatusSet(SQLITE_STATUS_MALLOC_SIZE, (int)nBytes);
|
||||
nDiff = nNew - nOld;
|
||||
sqlite3CheckSoftHeapLimit(nDiff);
|
||||
if( sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >=
|
||||
mem0.alarmThreshold-nDiff ){
|
||||
sqlite3MallocAlarm(nDiff);
|
||||
}
|
||||
pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew);
|
||||
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
|
||||
if( pNew==0 && mem0.alarmThreshold ){
|
||||
sqlite3HeapLimitExceeded((int)nBytes);
|
||||
if( pNew==0 && mem0.alarmThreshold>0 ){
|
||||
sqlite3MallocAlarm((int)nBytes);
|
||||
pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew);
|
||||
}
|
||||
#endif
|
||||
if( pNew ){
|
||||
nNew = sqlite3MallocSize(pNew);
|
||||
sqlite3StatusUp(SQLITE_STATUS_MEMORY_USED, nNew-nOld);
|
||||
|
12
src/mutex.c
12
src/mutex.c
@ -22,7 +22,7 @@
|
||||
** allocate a mutex while the system is uninitialized.
|
||||
*/
|
||||
static SQLITE_WSD int mutexIsInit = 0;
|
||||
#endif /* SQLITE_DEBUG */
|
||||
#endif /* SQLITE_DEBUG && !defined(SQLITE_MUTEX_OMIT) */
|
||||
|
||||
|
||||
#ifndef SQLITE_MUTEX_OMIT
|
||||
@ -53,8 +53,10 @@ int sqlite3MutexInit(void){
|
||||
pTo->xMutexLeave = pFrom->xMutexLeave;
|
||||
pTo->xMutexHeld = pFrom->xMutexHeld;
|
||||
pTo->xMutexNotheld = pFrom->xMutexNotheld;
|
||||
sqlite3MemoryBarrier();
|
||||
pTo->xMutexAlloc = pFrom->xMutexAlloc;
|
||||
}
|
||||
assert( sqlite3GlobalConfig.mutex.xMutexInit );
|
||||
rc = sqlite3GlobalConfig.mutex.xMutexInit();
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
@ -89,6 +91,7 @@ sqlite3_mutex *sqlite3_mutex_alloc(int id){
|
||||
if( id<=SQLITE_MUTEX_RECURSIVE && sqlite3_initialize() ) return 0;
|
||||
if( id>SQLITE_MUTEX_RECURSIVE && sqlite3MutexInit() ) return 0;
|
||||
#endif
|
||||
assert( sqlite3GlobalConfig.mutex.xMutexAlloc );
|
||||
return sqlite3GlobalConfig.mutex.xMutexAlloc(id);
|
||||
}
|
||||
|
||||
@ -97,6 +100,7 @@ sqlite3_mutex *sqlite3MutexAlloc(int id){
|
||||
return 0;
|
||||
}
|
||||
assert( GLOBAL(int, mutexIsInit) );
|
||||
assert( sqlite3GlobalConfig.mutex.xMutexAlloc );
|
||||
return sqlite3GlobalConfig.mutex.xMutexAlloc(id);
|
||||
}
|
||||
|
||||
@ -105,6 +109,7 @@ sqlite3_mutex *sqlite3MutexAlloc(int id){
|
||||
*/
|
||||
void sqlite3_mutex_free(sqlite3_mutex *p){
|
||||
if( p ){
|
||||
assert( sqlite3GlobalConfig.mutex.xMutexFree );
|
||||
sqlite3GlobalConfig.mutex.xMutexFree(p);
|
||||
}
|
||||
}
|
||||
@ -115,6 +120,7 @@ void sqlite3_mutex_free(sqlite3_mutex *p){
|
||||
*/
|
||||
void sqlite3_mutex_enter(sqlite3_mutex *p){
|
||||
if( p ){
|
||||
assert( sqlite3GlobalConfig.mutex.xMutexEnter );
|
||||
sqlite3GlobalConfig.mutex.xMutexEnter(p);
|
||||
}
|
||||
}
|
||||
@ -126,6 +132,7 @@ void sqlite3_mutex_enter(sqlite3_mutex *p){
|
||||
int sqlite3_mutex_try(sqlite3_mutex *p){
|
||||
int rc = SQLITE_OK;
|
||||
if( p ){
|
||||
assert( sqlite3GlobalConfig.mutex.xMutexTry );
|
||||
return sqlite3GlobalConfig.mutex.xMutexTry(p);
|
||||
}
|
||||
return rc;
|
||||
@ -139,6 +146,7 @@ int sqlite3_mutex_try(sqlite3_mutex *p){
|
||||
*/
|
||||
void sqlite3_mutex_leave(sqlite3_mutex *p){
|
||||
if( p ){
|
||||
assert( sqlite3GlobalConfig.mutex.xMutexLeave );
|
||||
sqlite3GlobalConfig.mutex.xMutexLeave(p);
|
||||
}
|
||||
}
|
||||
@ -149,9 +157,11 @@ void sqlite3_mutex_leave(sqlite3_mutex *p){
|
||||
** intended for use inside assert() statements.
|
||||
*/
|
||||
int sqlite3_mutex_held(sqlite3_mutex *p){
|
||||
assert( p==0 || sqlite3GlobalConfig.mutex.xMutexHeld );
|
||||
return p==0 || sqlite3GlobalConfig.mutex.xMutexHeld(p);
|
||||
}
|
||||
int sqlite3_mutex_notheld(sqlite3_mutex *p){
|
||||
assert( p==0 || sqlite3GlobalConfig.mutex.xMutexNotheld );
|
||||
return p==0 || sqlite3GlobalConfig.mutex.xMutexNotheld(p);
|
||||
}
|
||||
#endif
|
||||
|
@ -80,6 +80,17 @@ static int pthreadMutexNotheld(sqlite3_mutex *p){
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Try to provide a memory barrier operation, needed for initialization only.
|
||||
*/
|
||||
void sqlite3MemoryBarrier(void){
|
||||
#if defined(SQLITE_MEMORY_BARRIER)
|
||||
SQLITE_MEMORY_BARRIER;
|
||||
#elif defined(__GNUC__) && GCC_VERSION>=4001000
|
||||
__sync_synchronize();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
** Initialize and deinitialize the mutex subsystem.
|
||||
*/
|
||||
|
@ -77,6 +77,19 @@ static int winMutexNotheld(sqlite3_mutex *p){
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Try to provide a memory barrier operation, needed for initialization only.
|
||||
*/
|
||||
void sqlite3MemoryBarrier(void){
|
||||
#if defined(SQLITE_MEMORY_BARRIER)
|
||||
SQLITE_MEMORY_BARRIER;
|
||||
#elif defined(__GNUC__)
|
||||
__sync_synchronize();
|
||||
#else
|
||||
MemoryBarrier();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
** Initialize and deinitialize the mutex subsystem.
|
||||
*/
|
||||
|
25
src/pager.c
25
src/pager.c
@ -647,7 +647,7 @@ struct Pager {
|
||||
u8 doNotSpill; /* Do not spill the cache when non-zero */
|
||||
u8 subjInMemory; /* True to use in-memory sub-journals */
|
||||
u8 bUseFetch; /* True to use xFetch() */
|
||||
u8 hasBeenUsed; /* True if any content previously read */
|
||||
u8 hasHeldSharedLock; /* True if a shared lock has ever been held */
|
||||
Pgno dbSize; /* Number of pages in the database */
|
||||
Pgno dbOrigSize; /* dbSize before the current transaction */
|
||||
Pgno dbFileSize; /* Number of pages in the database file */
|
||||
@ -5097,10 +5097,10 @@ int sqlite3PagerSharedLock(Pager *pPager){
|
||||
);
|
||||
}
|
||||
|
||||
if( !pPager->tempFile && pPager->hasBeenUsed ){
|
||||
if( !pPager->tempFile && pPager->hasHeldSharedLock ){
|
||||
/* The shared-lock has just been acquired then check to
|
||||
** see if the database has been modified. If the database has changed,
|
||||
** flush the cache. The pPager->hasBeenUsed flag prevents this from
|
||||
** flush the cache. The hasHeldSharedLock flag prevents this from
|
||||
** occurring on the very first access to a file, in order to save a
|
||||
** single unnecessary sqlite3OsRead() call at the start-up.
|
||||
**
|
||||
@ -5170,6 +5170,7 @@ int sqlite3PagerSharedLock(Pager *pPager){
|
||||
assert( pPager->eState==PAGER_OPEN );
|
||||
}else{
|
||||
pPager->eState = PAGER_READER;
|
||||
pPager->hasHeldSharedLock = 1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@ -5253,21 +5254,25 @@ int sqlite3PagerAcquire(
|
||||
** page 1 if there is no write-transaction open or the ACQUIRE_READONLY
|
||||
** flag was specified by the caller. And so long as the db is not a
|
||||
** temporary or in-memory database. */
|
||||
const int bMmapOk = (pgno!=1 && USEFETCH(pPager)
|
||||
const int bMmapOk = (pgno>1 && USEFETCH(pPager)
|
||||
&& (pPager->eState==PAGER_READER || (flags & PAGER_GET_READONLY))
|
||||
#ifdef SQLITE_HAS_CODEC
|
||||
&& pPager->xCodec==0
|
||||
#endif
|
||||
);
|
||||
|
||||
/* Optimization note: Adding the "pgno<=1" term before "pgno==0" here
|
||||
** allows the compiler optimizer to reuse the results of the "pgno>1"
|
||||
** test in the previous statement, and avoid testing pgno==0 in the
|
||||
** common case where pgno is large. */
|
||||
if( pgno<=1 && pgno==0 ){
|
||||
return SQLITE_CORRUPT_BKPT;
|
||||
}
|
||||
assert( pPager->eState>=PAGER_READER );
|
||||
assert( assert_pager_state(pPager) );
|
||||
assert( noContent==0 || bMmapOk==0 );
|
||||
|
||||
if( pgno==0 ){
|
||||
return SQLITE_CORRUPT_BKPT;
|
||||
}
|
||||
pPager->hasBeenUsed = 1;
|
||||
assert( pPager->hasHeldSharedLock==1 );
|
||||
|
||||
/* If the pager is in the error state, return an error immediately.
|
||||
** Otherwise, request the page from the PCache layer. */
|
||||
@ -5422,7 +5427,7 @@ DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){
|
||||
assert( pgno!=0 );
|
||||
assert( pPager->pPCache!=0 );
|
||||
pPage = sqlite3PcacheFetch(pPager->pPCache, pgno, 0);
|
||||
assert( pPage==0 || pPager->hasBeenUsed );
|
||||
assert( pPage==0 || pPager->hasHeldSharedLock );
|
||||
if( pPage==0 ) return 0;
|
||||
return sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pPage);
|
||||
}
|
||||
@ -6389,7 +6394,7 @@ u8 sqlite3PagerIsreadonly(Pager *pPager){
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
/*
|
||||
** Return the number of references to the pager.
|
||||
** Return the sum of the reference counts for all pages held by pPager.
|
||||
*/
|
||||
int sqlite3PagerRefcount(Pager *pPager){
|
||||
return sqlite3PcacheRefCount(pPager->pPCache);
|
||||
|
27
src/pcache.c
27
src/pcache.c
@ -19,7 +19,7 @@
|
||||
struct PCache {
|
||||
PgHdr *pDirty, *pDirtyTail; /* List of dirty pages in LRU order */
|
||||
PgHdr *pSynced; /* Last synced page in dirty page list */
|
||||
int nRef; /* Number of referenced pages */
|
||||
int nRefSum; /* Sum of ref counts over all pages */
|
||||
int szCache; /* Configured cache size */
|
||||
int szPage; /* Size of every page in this cache */
|
||||
int szExtra; /* Size of extra space for each page */
|
||||
@ -184,7 +184,7 @@ int sqlite3PcacheOpen(
|
||||
** are no outstanding page references when this function is called.
|
||||
*/
|
||||
int sqlite3PcacheSetPageSize(PCache *pCache, int szPage){
|
||||
assert( pCache->nRef==0 && pCache->pDirty==0 );
|
||||
assert( pCache->nRefSum==0 && pCache->pDirty==0 );
|
||||
if( pCache->szPage ){
|
||||
sqlite3_pcache *pNew;
|
||||
pNew = sqlite3GlobalConfig.pcache2.xCreate(
|
||||
@ -351,9 +351,7 @@ PgHdr *sqlite3PcacheFetchFinish(
|
||||
if( !pPgHdr->pPage ){
|
||||
return pcacheFetchFinishWithInit(pCache, pgno, pPage);
|
||||
}
|
||||
if( 0==pPgHdr->nRef ){
|
||||
pCache->nRef++;
|
||||
}
|
||||
pCache->nRefSum++;
|
||||
pPgHdr->nRef++;
|
||||
return pPgHdr;
|
||||
}
|
||||
@ -364,9 +362,8 @@ PgHdr *sqlite3PcacheFetchFinish(
|
||||
*/
|
||||
void SQLITE_NOINLINE sqlite3PcacheRelease(PgHdr *p){
|
||||
assert( p->nRef>0 );
|
||||
p->nRef--;
|
||||
if( p->nRef==0 ){
|
||||
p->pCache->nRef--;
|
||||
p->pCache->nRefSum--;
|
||||
if( (--p->nRef)==0 ){
|
||||
if( p->flags&PGHDR_CLEAN ){
|
||||
pcacheUnpin(p);
|
||||
}else if( p->pDirtyPrev!=0 ){
|
||||
@ -382,6 +379,7 @@ void SQLITE_NOINLINE sqlite3PcacheRelease(PgHdr *p){
|
||||
void sqlite3PcacheRef(PgHdr *p){
|
||||
assert(p->nRef>0);
|
||||
p->nRef++;
|
||||
p->pCache->nRefSum++;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -394,7 +392,7 @@ void sqlite3PcacheDrop(PgHdr *p){
|
||||
if( p->flags&PGHDR_DIRTY ){
|
||||
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_REMOVE);
|
||||
}
|
||||
p->pCache->nRef--;
|
||||
p->pCache->nRefSum--;
|
||||
sqlite3GlobalConfig.pcache2.xUnpin(p->pCache->pCache, p->pPage, 1);
|
||||
}
|
||||
|
||||
@ -490,11 +488,11 @@ void sqlite3PcacheTruncate(PCache *pCache, Pgno pgno){
|
||||
sqlite3PcacheMakeClean(p);
|
||||
}
|
||||
}
|
||||
if( pgno==0 && pCache->nRef ){
|
||||
if( pgno==0 && pCache->nRefSum ){
|
||||
sqlite3_pcache_page *pPage1;
|
||||
pPage1 = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache,1,0);
|
||||
if( ALWAYS(pPage1) ){ /* Page 1 is always available in cache, because
|
||||
** pCache->nRef>0 */
|
||||
** pCache->nRefSum>0 */
|
||||
memset(pPage1->pBuf, 0, pCache->szPage);
|
||||
pgno = 1;
|
||||
}
|
||||
@ -600,10 +598,13 @@ PgHdr *sqlite3PcacheDirtyList(PCache *pCache){
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the total number of referenced pages held by the cache.
|
||||
** Return the total number of references to all pages held by the cache.
|
||||
**
|
||||
** This is not the total number of pages referenced, but the sum of the
|
||||
** reference count for all pages.
|
||||
*/
|
||||
int sqlite3PcacheRefCount(PCache *pCache){
|
||||
return pCache->nRef;
|
||||
return pCache->nRefSum;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -87,6 +87,24 @@ typedef struct PgHdr1 PgHdr1;
|
||||
typedef struct PgFreeslot PgFreeslot;
|
||||
typedef struct PGroup PGroup;
|
||||
|
||||
/*
|
||||
** Each cache entry is represented by an instance of the following
|
||||
** structure. Unless SQLITE_PCACHE_SEPARATE_HEADER is defined, a buffer of
|
||||
** PgHdr1.pCache->szPage bytes is allocated directly before this structure
|
||||
** in memory.
|
||||
*/
|
||||
struct PgHdr1 {
|
||||
sqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */
|
||||
unsigned int iKey; /* Key value (page number) */
|
||||
u8 isPinned; /* Page in use, not on the LRU list */
|
||||
u8 isBulkLocal; /* This page from bulk local storage */
|
||||
u8 isAnchor; /* This is the PGroup.lru element */
|
||||
PgHdr1 *pNext; /* Next in hash table chain */
|
||||
PCache1 *pCache; /* Cache that currently owns this page */
|
||||
PgHdr1 *pLruNext; /* Next in LRU list of unpinned pages */
|
||||
PgHdr1 *pLruPrev; /* Previous in LRU list of unpinned pages */
|
||||
};
|
||||
|
||||
/* Each page cache (or PCache) belongs to a PGroup. A PGroup is a set
|
||||
** of one or more PCaches that are able to recycle each other's unpinned
|
||||
** pages when they are under memory pressure. A PGroup is an instance of
|
||||
@ -115,7 +133,7 @@ struct PGroup {
|
||||
unsigned int nMinPage; /* Sum of nMin for purgeable caches */
|
||||
unsigned int mxPinned; /* nMaxpage + 10 - nMinPage */
|
||||
unsigned int nCurrentPage; /* Number of purgeable pages allocated */
|
||||
PgHdr1 *pLruHead, *pLruTail; /* LRU list of unpinned pages */
|
||||
PgHdr1 lru; /* The beginning and end of the LRU list */
|
||||
};
|
||||
|
||||
/* Each page cache is an instance of the following object. Every
|
||||
@ -153,23 +171,6 @@ struct PCache1 {
|
||||
void *pBulk; /* Bulk memory used by pcache-local */
|
||||
};
|
||||
|
||||
/*
|
||||
** Each cache entry is represented by an instance of the following
|
||||
** structure. Unless SQLITE_PCACHE_SEPARATE_HEADER is defined, a buffer of
|
||||
** PgHdr1.pCache->szPage bytes is allocated directly before this structure
|
||||
** in memory.
|
||||
*/
|
||||
struct PgHdr1 {
|
||||
sqlite3_pcache_page page;
|
||||
unsigned int iKey; /* Key value (page number) */
|
||||
u8 isPinned; /* Page in use, not on the LRU list */
|
||||
u8 isBulkLocal; /* This page from bulk local storage */
|
||||
PgHdr1 *pNext; /* Next in hash table chain */
|
||||
PCache1 *pCache; /* Cache that currently owns this page */
|
||||
PgHdr1 *pLruNext; /* Next in LRU list of unpinned pages */
|
||||
PgHdr1 *pLruPrev; /* Previous in LRU list of unpinned pages */
|
||||
};
|
||||
|
||||
/*
|
||||
** Free slots in the allocator used to divide up the global page cache
|
||||
** buffer provided using the SQLITE_CONFIG_PAGECACHE mechanism.
|
||||
@ -230,6 +231,7 @@ static SQLITE_WSD struct PCacheGlobal {
|
||||
/******************************************************************************/
|
||||
/******** Page Allocation/SQLITE_CONFIG_PCACHE Related Functions **************/
|
||||
|
||||
|
||||
/*
|
||||
** This function is called during initialization if a static buffer is
|
||||
** supplied to use for the page-cache by passing the SQLITE_CONFIG_PAGECACHE
|
||||
@ -289,6 +291,7 @@ static int pcache1InitBulk(PCache1 *pCache){
|
||||
pX->page.pBuf = zBulk;
|
||||
pX->page.pExtra = &pX[1];
|
||||
pX->isBulkLocal = 1;
|
||||
pX->isAnchor = 0;
|
||||
pX->pNext = pCache->pFree;
|
||||
pCache->pFree = pX;
|
||||
zBulk += pCache->szAlloc;
|
||||
@ -431,6 +434,7 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache, int benignMalloc){
|
||||
p->page.pBuf = pPg;
|
||||
p->page.pExtra = &p[1];
|
||||
p->isBulkLocal = 0;
|
||||
p->isAnchor = 0;
|
||||
}
|
||||
if( pCache->bPurgeable ){
|
||||
pCache->pGroup->nCurrentPage++;
|
||||
@ -557,22 +561,16 @@ static PgHdr1 *pcache1PinPage(PgHdr1 *pPage){
|
||||
assert( pPage!=0 );
|
||||
assert( pPage->isPinned==0 );
|
||||
pCache = pPage->pCache;
|
||||
assert( pPage->pLruNext || pPage==pCache->pGroup->pLruTail );
|
||||
assert( pPage->pLruPrev || pPage==pCache->pGroup->pLruHead );
|
||||
assert( pPage->pLruNext );
|
||||
assert( pPage->pLruPrev );
|
||||
assert( sqlite3_mutex_held(pCache->pGroup->mutex) );
|
||||
if( pPage->pLruPrev ){
|
||||
pPage->pLruPrev->pLruNext = pPage->pLruNext;
|
||||
}else{
|
||||
pCache->pGroup->pLruHead = pPage->pLruNext;
|
||||
}
|
||||
if( pPage->pLruNext ){
|
||||
pPage->pLruNext->pLruPrev = pPage->pLruPrev;
|
||||
}else{
|
||||
pCache->pGroup->pLruTail = pPage->pLruPrev;
|
||||
}
|
||||
pPage->pLruPrev->pLruNext = pPage->pLruNext;
|
||||
pPage->pLruNext->pLruPrev = pPage->pLruPrev;
|
||||
pPage->pLruNext = 0;
|
||||
pPage->pLruPrev = 0;
|
||||
pPage->isPinned = 1;
|
||||
assert( pPage->isAnchor==0 );
|
||||
assert( pCache->pGroup->lru.isAnchor==1 );
|
||||
pCache->nRecyclable--;
|
||||
return pPage;
|
||||
}
|
||||
@ -605,9 +603,11 @@ static void pcache1RemoveFromHash(PgHdr1 *pPage, int freeFlag){
|
||||
*/
|
||||
static void pcache1EnforceMaxPage(PCache1 *pCache){
|
||||
PGroup *pGroup = pCache->pGroup;
|
||||
PgHdr1 *p;
|
||||
assert( sqlite3_mutex_held(pGroup->mutex) );
|
||||
while( pGroup->nCurrentPage>pGroup->nMaxPage && pGroup->pLruTail ){
|
||||
PgHdr1 *p = pGroup->pLruTail;
|
||||
while( pGroup->nCurrentPage>pGroup->nMaxPage
|
||||
&& (p=pGroup->lru.pLruPrev)->isAnchor==0
|
||||
){
|
||||
assert( p->pCache->pGroup==pGroup );
|
||||
assert( p->isPinned==0 );
|
||||
pcache1PinPage(p);
|
||||
@ -741,6 +741,10 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){
|
||||
}else{
|
||||
pGroup = &pcache1.grp;
|
||||
}
|
||||
if( pGroup->lru.isAnchor==0 ){
|
||||
pGroup->lru.isAnchor = 1;
|
||||
pGroup->lru.pLruPrev = pGroup->lru.pLruNext = &pGroup->lru;
|
||||
}
|
||||
pCache->pGroup = pGroup;
|
||||
pCache->szPage = szPage;
|
||||
pCache->szExtra = szExtra;
|
||||
@ -848,11 +852,11 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2(
|
||||
|
||||
/* Step 4. Try to recycle a page. */
|
||||
if( pCache->bPurgeable
|
||||
&& pGroup->pLruTail
|
||||
&& !pGroup->lru.pLruPrev->isAnchor
|
||||
&& ((pCache->nPage+1>=pCache->nMax) || pcache1UnderMemoryPressure(pCache))
|
||||
){
|
||||
PCache1 *pOther;
|
||||
pPage = pGroup->pLruTail;
|
||||
pPage = pGroup->lru.pLruPrev;
|
||||
assert( pPage->isPinned==0 );
|
||||
pcache1RemoveFromHash(pPage, 0);
|
||||
pcache1PinPage(pPage);
|
||||
@ -961,7 +965,10 @@ static PgHdr1 *pcache1FetchNoMutex(
|
||||
pPage = pCache->apHash[iKey % pCache->nHash];
|
||||
while( pPage && pPage->iKey!=iKey ){ pPage = pPage->pNext; }
|
||||
|
||||
/* Step 2: Abort if no existing page is found and createFlag is 0 */
|
||||
/* Step 2: If the page was found in the hash table, then return it.
|
||||
** If the page was not in the hash table and createFlag is 0, abort.
|
||||
** Otherwise (page not in hash and createFlag!=0) continue with
|
||||
** subsequent steps to try to create the page. */
|
||||
if( pPage ){
|
||||
if( !pPage->isPinned ){
|
||||
return pcache1PinPage(pPage);
|
||||
@ -1038,21 +1045,16 @@ static void pcache1Unpin(
|
||||
** part of the PGroup LRU list.
|
||||
*/
|
||||
assert( pPage->pLruPrev==0 && pPage->pLruNext==0 );
|
||||
assert( pGroup->pLruHead!=pPage && pGroup->pLruTail!=pPage );
|
||||
assert( pPage->isPinned==1 );
|
||||
|
||||
if( reuseUnlikely || pGroup->nCurrentPage>pGroup->nMaxPage ){
|
||||
pcache1RemoveFromHash(pPage, 1);
|
||||
}else{
|
||||
/* Add the page to the PGroup LRU list. */
|
||||
if( pGroup->pLruHead ){
|
||||
pGroup->pLruHead->pLruPrev = pPage;
|
||||
pPage->pLruNext = pGroup->pLruHead;
|
||||
pGroup->pLruHead = pPage;
|
||||
}else{
|
||||
pGroup->pLruTail = pPage;
|
||||
pGroup->pLruHead = pPage;
|
||||
}
|
||||
PgHdr1 **ppFirst = &pGroup->lru.pLruNext;
|
||||
pPage->pLruPrev = &pGroup->lru;
|
||||
(pPage->pLruNext = *ppFirst)->pLruPrev = pPage;
|
||||
*ppFirst = pPage;
|
||||
pCache->nRecyclable++;
|
||||
pPage->isPinned = 0;
|
||||
}
|
||||
@ -1190,7 +1192,10 @@ int sqlite3PcacheReleaseMemory(int nReq){
|
||||
if( sqlite3GlobalConfig.nPage==0 ){
|
||||
PgHdr1 *p;
|
||||
pcache1EnterMutex(&pcache1.grp);
|
||||
while( (nReq<0 || nFree<nReq) && ((p=pcache1.grp.pLruTail)!=0) ){
|
||||
while( (nReq<0 || nFree<nReq)
|
||||
&& (p=pcache1.grp.lru.pLruPrev)!=0
|
||||
&& p->isAnchor==0
|
||||
){
|
||||
nFree += pcache1MemSize(p->page.pBuf);
|
||||
#ifdef SQLITE_PCACHE_SEPARATE_HEADER
|
||||
nFree += sqlite3MemSize(p);
|
||||
@ -1218,7 +1223,7 @@ void sqlite3PcacheStats(
|
||||
){
|
||||
PgHdr1 *p;
|
||||
int nRecyclable = 0;
|
||||
for(p=pcache1.grp.pLruHead; p; p=p->pLruNext){
|
||||
for(p=pcache1.grp.lru.pLruNext; p && !p->isAnchor; p=p->pLruNext){
|
||||
assert( p->isPinned==0 );
|
||||
nRecyclable++;
|
||||
}
|
||||
|
@ -45,30 +45,6 @@ static void incrAggFunctionDepth(Expr *pExpr, int N){
|
||||
** Turn the pExpr expression into an alias for the iCol-th column of the
|
||||
** result set in pEList.
|
||||
**
|
||||
** If the result set column is a simple column reference, then this routine
|
||||
** makes an exact copy. But for any other kind of expression, this
|
||||
** routine make a copy of the result set column as the argument to the
|
||||
** TK_AS operator. The TK_AS operator causes the expression to be
|
||||
** evaluated just once and then reused for each alias.
|
||||
**
|
||||
** The reason for suppressing the TK_AS term when the expression is a simple
|
||||
** column reference is so that the column reference will be recognized as
|
||||
** usable by indices within the WHERE clause processing logic.
|
||||
**
|
||||
** The TK_AS operator is inhibited if zType[0]=='G'. This means
|
||||
** that in a GROUP BY clause, the expression is evaluated twice. Hence:
|
||||
**
|
||||
** SELECT random()%5 AS x, count(*) FROM tab GROUP BY x
|
||||
**
|
||||
** Is equivalent to:
|
||||
**
|
||||
** SELECT random()%5 AS x, count(*) FROM tab GROUP BY random()%5
|
||||
**
|
||||
** The result of random()%5 in the GROUP BY clause is probably different
|
||||
** from the result in the result-set. On the other hand Standard SQL does
|
||||
** not allow the GROUP BY clause to contain references to result-set columns.
|
||||
** So this should never come up in well-formed queries.
|
||||
**
|
||||
** If the reference is followed by a COLLATE operator, then make sure
|
||||
** the COLLATE operator is preserved. For example:
|
||||
**
|
||||
@ -102,19 +78,11 @@ static void resolveAlias(
|
||||
db = pParse->db;
|
||||
pDup = sqlite3ExprDup(db, pOrig, 0);
|
||||
if( pDup==0 ) return;
|
||||
if( pOrig->op!=TK_COLUMN && zType[0]!='G' ){
|
||||
incrAggFunctionDepth(pDup, nSubquery);
|
||||
pDup = sqlite3PExpr(pParse, TK_AS, pDup, 0, 0);
|
||||
if( pDup==0 ) return;
|
||||
ExprSetProperty(pDup, EP_Skip);
|
||||
if( pEList->a[iCol].u.x.iAlias==0 ){
|
||||
pEList->a[iCol].u.x.iAlias = (u16)(++pParse->nAlias);
|
||||
}
|
||||
pDup->iTable = pEList->a[iCol].u.x.iAlias;
|
||||
}
|
||||
if( zType[0]!='G' ) incrAggFunctionDepth(pDup, nSubquery);
|
||||
if( pExpr->op==TK_COLLATE ){
|
||||
pDup = sqlite3ExprAddCollateString(pParse, pDup, pExpr->u.zToken);
|
||||
}
|
||||
ExprSetProperty(pDup, EP_Alias);
|
||||
|
||||
/* Before calling sqlite3ExprDelete(), set the EP_Static flag. This
|
||||
** prevents ExprDelete() from deleting the Expr structure itself,
|
||||
@ -506,7 +474,7 @@ static int lookupName(
|
||||
lookupname_end:
|
||||
if( cnt==1 ){
|
||||
assert( pNC!=0 );
|
||||
if( pExpr->op!=TK_AS ){
|
||||
if( !ExprHasProperty(pExpr, EP_Alias) ){
|
||||
sqlite3AuthRead(pParse, pExpr, pSchema, pNC->pSrcList);
|
||||
}
|
||||
/* Increment the nRef value on all name contexts from TopNC up to
|
||||
@ -547,36 +515,25 @@ Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSrc, int iCol){
|
||||
}
|
||||
|
||||
/*
|
||||
** Report an error that an expression is not valid for a partial index WHERE
|
||||
** clause.
|
||||
** Report an error that an expression is not valid for some set of
|
||||
** pNC->ncFlags values determined by validMask.
|
||||
*/
|
||||
static void notValidPartIdxWhere(
|
||||
static void notValid(
|
||||
Parse *pParse, /* Leave error message here */
|
||||
NameContext *pNC, /* The name context */
|
||||
const char *zMsg /* Type of error */
|
||||
const char *zMsg, /* Type of error */
|
||||
int validMask /* Set of contexts for which prohibited */
|
||||
){
|
||||
if( (pNC->ncFlags & NC_PartIdx)!=0 ){
|
||||
sqlite3ErrorMsg(pParse, "%s prohibited in partial index WHERE clauses",
|
||||
zMsg);
|
||||
}
|
||||
}
|
||||
|
||||
assert( (validMask&~(NC_IsCheck|NC_PartIdx|NC_IdxExpr))==0 );
|
||||
if( (pNC->ncFlags & validMask)!=0 ){
|
||||
const char *zIn = "partial index WHERE clauses";
|
||||
if( pNC->ncFlags & NC_IdxExpr ) zIn = "index expressions";
|
||||
#ifndef SQLITE_OMIT_CHECK
|
||||
/*
|
||||
** Report an error that an expression is not valid for a CHECK constraint.
|
||||
*/
|
||||
static void notValidCheckConstraint(
|
||||
Parse *pParse, /* Leave error message here */
|
||||
NameContext *pNC, /* The name context */
|
||||
const char *zMsg /* Type of error */
|
||||
){
|
||||
if( (pNC->ncFlags & NC_IsCheck)!=0 ){
|
||||
sqlite3ErrorMsg(pParse,"%s prohibited in CHECK constraints", zMsg);
|
||||
else if( pNC->ncFlags & NC_IsCheck ) zIn = "CHECK constraints";
|
||||
#endif
|
||||
sqlite3ErrorMsg(pParse, "%s prohibited in %s", zMsg, zIn);
|
||||
}
|
||||
}
|
||||
#else
|
||||
# define notValidCheckConstraint(P,N,M)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Expression p should encode a floating point value between 1.0 and 0.0.
|
||||
@ -661,6 +618,8 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
|
||||
Expr *pRight;
|
||||
|
||||
/* if( pSrcList==0 ) break; */
|
||||
notValid(pParse, pNC, "the \".\" operator", NC_IdxExpr);
|
||||
/*notValid(pParse, pNC, "the \".\" operator", NC_PartIdx|NC_IsCheck, 1);*/
|
||||
pRight = pExpr->pRight;
|
||||
if( pRight->op==TK_ID ){
|
||||
zDb = 0;
|
||||
@ -690,7 +649,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
|
||||
u8 enc = ENC(pParse->db); /* The database encoding */
|
||||
|
||||
assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
|
||||
notValidPartIdxWhere(pParse, pNC, "functions");
|
||||
notValid(pParse, pNC, "functions", NC_PartIdx);
|
||||
zId = pExpr->u.zToken;
|
||||
nId = sqlite3Strlen30(zId);
|
||||
pDef = sqlite3FindFunction(pParse->db, zId, nId, n, enc, 0);
|
||||
@ -738,9 +697,18 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
|
||||
return WRC_Prune;
|
||||
}
|
||||
#endif
|
||||
if( pDef->funcFlags & SQLITE_FUNC_CONSTANT ){
|
||||
if( pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG) ){
|
||||
/* For the purposes of the EP_ConstFunc flag, date and time
|
||||
** functions and other functions that change slowly are considered
|
||||
** constant because they are constant for the duration of one query */
|
||||
ExprSetProperty(pExpr,EP_ConstFunc);
|
||||
}
|
||||
if( (pDef->funcFlags & SQLITE_FUNC_CONSTANT)==0 ){
|
||||
/* Date/time functions that use 'now', and other functions like
|
||||
** sqlite_version() that might change over time cannot be used
|
||||
** in an index. */
|
||||
notValid(pParse, pNC, "non-deterministic functions", NC_IdxExpr);
|
||||
}
|
||||
}
|
||||
if( is_agg && (pNC->ncFlags & NC_AllowAgg)==0 ){
|
||||
sqlite3ErrorMsg(pParse, "misuse of aggregate function %.*s()", nId,zId);
|
||||
@ -786,8 +754,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
|
||||
testcase( pExpr->op==TK_IN );
|
||||
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
|
||||
int nRef = pNC->nRef;
|
||||
notValidCheckConstraint(pParse, pNC, "subqueries");
|
||||
notValidPartIdxWhere(pParse, pNC, "subqueries");
|
||||
notValid(pParse, pNC, "subqueries", NC_IsCheck|NC_PartIdx|NC_IdxExpr);
|
||||
sqlite3WalkSelect(pWalker, pExpr->x.pSelect);
|
||||
assert( pNC->nRef>=nRef );
|
||||
if( nRef!=pNC->nRef ){
|
||||
@ -797,8 +764,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
|
||||
break;
|
||||
}
|
||||
case TK_VARIABLE: {
|
||||
notValidCheckConstraint(pParse, pNC, "parameters");
|
||||
notValidPartIdxWhere(pParse, pNC, "parameters");
|
||||
notValid(pParse, pNC, "parameters", NC_IsCheck|NC_PartIdx|NC_IdxExpr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1501,14 +1467,14 @@ void sqlite3ResolveSelectNames(
|
||||
void sqlite3ResolveSelfReference(
|
||||
Parse *pParse, /* Parsing context */
|
||||
Table *pTab, /* The table being referenced */
|
||||
int type, /* NC_IsCheck or NC_PartIdx */
|
||||
int type, /* NC_IsCheck or NC_PartIdx or NC_IdxExpr */
|
||||
Expr *pExpr, /* Expression to resolve. May be NULL. */
|
||||
ExprList *pList /* Expression list to resolve. May be NUL. */
|
||||
){
|
||||
SrcList sSrc; /* Fake SrcList for pParse->pNewTable */
|
||||
NameContext sNC; /* Name context for pParse->pNewTable */
|
||||
|
||||
assert( type==NC_IsCheck || type==NC_PartIdx );
|
||||
assert( type==NC_IsCheck || type==NC_PartIdx || type==NC_IdxExpr );
|
||||
memset(&sNC, 0, sizeof(sNC));
|
||||
memset(&sSrc, 0, sizeof(sSrc));
|
||||
sSrc.nSrc = 1;
|
||||
|
@ -4252,8 +4252,8 @@ static int process_input(ShellState *p, FILE *in){
|
||||
fprintf(stderr, "Error: incomplete SQL: %s\n", zSql);
|
||||
errCnt++;
|
||||
}
|
||||
free(zSql);
|
||||
}
|
||||
free(zSql);
|
||||
free(zLine);
|
||||
return errCnt>0;
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ extern "C" {
|
||||
** but are associated with the library instead of the header file. ^(Cautious
|
||||
** programmers might include assert() statements in their application to
|
||||
** verify that values returned by these interfaces match the macros in
|
||||
** the header, and thus insure that the application is
|
||||
** the header, and thus ensure that the application is
|
||||
** compiled with matching library and header files.
|
||||
**
|
||||
** <blockquote><pre>
|
||||
@ -374,7 +374,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
|
||||
** Restrictions:
|
||||
**
|
||||
** <ul>
|
||||
** <li> The application must insure that the 1st parameter to sqlite3_exec()
|
||||
** <li> The application must ensure that the 1st parameter to sqlite3_exec()
|
||||
** is a valid and open [database connection].
|
||||
** <li> The application must not close the [database connection] specified by
|
||||
** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running.
|
||||
@ -1367,9 +1367,11 @@ int sqlite3_os_end(void);
|
||||
** applications and so this routine is usually not necessary. It is
|
||||
** provided to support rare applications with unusual needs.
|
||||
**
|
||||
** The sqlite3_config() interface is not threadsafe. The application
|
||||
** must insure that no other SQLite interfaces are invoked by other
|
||||
** threads while sqlite3_config() is running. Furthermore, sqlite3_config()
|
||||
** <b>The sqlite3_config() interface is not threadsafe. The application
|
||||
** must ensure that no other SQLite interfaces are invoked by other
|
||||
** threads while sqlite3_config() is running.</b>
|
||||
**
|
||||
** The sqlite3_config() interface
|
||||
** may only be invoked prior to library initialization using
|
||||
** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()].
|
||||
** ^If sqlite3_config() is called after [sqlite3_initialize()] and before
|
||||
@ -4357,6 +4359,22 @@ const void *sqlite3_value_text16be(sqlite3_value*);
|
||||
int sqlite3_value_type(sqlite3_value*);
|
||||
int sqlite3_value_numeric_type(sqlite3_value*);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Obtaining SQL Values
|
||||
** METHOD: sqlite3_value
|
||||
**
|
||||
** The sqlite3_value_subtype(V) function returns the subtype for
|
||||
** an [application-defined SQL function] argument V. The subtype
|
||||
** information can be used to pass a limited amount of context from
|
||||
** one SQL function to another. Use the [sqlite3_result_subtype()]
|
||||
** routine to set the subtype for the return value of an SQL function.
|
||||
**
|
||||
** SQLite makes no use of subtype itself. It merely passes the subtype
|
||||
** from the result of one [application-defined SQL function] into the
|
||||
** input of another.
|
||||
*/
|
||||
unsigned int sqlite3_value_subtype(sqlite3_value*);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Copy And Free SQL Values
|
||||
** METHOD: sqlite3_value
|
||||
@ -4656,6 +4674,21 @@ void sqlite3_result_value(sqlite3_context*, sqlite3_value*);
|
||||
void sqlite3_result_zeroblob(sqlite3_context*, int n);
|
||||
int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n);
|
||||
|
||||
|
||||
/*
|
||||
** CAPI3REF: Setting The Subtype Of An SQL Function
|
||||
** METHOD: sqlite3_context
|
||||
**
|
||||
** The sqlite3_result_subtype(C,T) function causes the subtype of
|
||||
** the result from the [application-defined SQL function] with
|
||||
** [sqlite3_context] C to be the value T. Only the lower 8 bits
|
||||
** of the subtype T are preserved in current versions of SQLite;
|
||||
** higher order bits are discarded.
|
||||
** The number of subtype bytes preserved by SQLite might increase
|
||||
** in future releases of SQLite.
|
||||
*/
|
||||
void sqlite3_result_subtype(sqlite3_context*,unsigned int);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Define New Collating Sequences
|
||||
** METHOD: sqlite3
|
||||
@ -6094,6 +6127,9 @@ int sqlite3_vfs_unregister(sqlite3_vfs*);
|
||||
** <li> SQLITE_MUTEX_STATIC_APP1
|
||||
** <li> SQLITE_MUTEX_STATIC_APP2
|
||||
** <li> SQLITE_MUTEX_STATIC_APP3
|
||||
** <li> SQLITE_MUTEX_STATIC_VFS1
|
||||
** <li> SQLITE_MUTEX_STATIC_VFS2
|
||||
** <li> SQLITE_MUTEX_STATIC_VFS3
|
||||
** </ul>
|
||||
**
|
||||
** ^The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE)
|
||||
|
@ -272,6 +272,9 @@ struct sqlite3_api_routines {
|
||||
void (*value_free)(sqlite3_value*);
|
||||
int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64);
|
||||
int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64);
|
||||
/* Version 3.8.12 and later */
|
||||
unsigned int (*value_subtype)(sqlite3_value*);
|
||||
void (*result_subtype)(sqlite3_context*,unsigned int);
|
||||
};
|
||||
|
||||
/*
|
||||
@ -285,7 +288,7 @@ struct sqlite3_api_routines {
|
||||
** the API. So the redefinition macros are only valid if the
|
||||
** SQLITE_CORE macros is undefined.
|
||||
*/
|
||||
#ifndef SQLITE_CORE
|
||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
||||
#define sqlite3_aggregate_context sqlite3_api->aggregate_context
|
||||
#ifndef SQLITE_OMIT_DEPRECATED
|
||||
#define sqlite3_aggregate_count sqlite3_api->aggregate_count
|
||||
@ -508,9 +511,12 @@ struct sqlite3_api_routines {
|
||||
#define sqlite3_value_free sqlite3_api->value_free
|
||||
#define sqlite3_result_zeroblob64 sqlite3_api->result_zeroblob64
|
||||
#define sqlite3_bind_zeroblob64 sqlite3_api->bind_zeroblob64
|
||||
#endif /* SQLITE_CORE */
|
||||
/* Version 3.8.12 and later */
|
||||
#define sqlite3_value_subtype sqlite3_api->value_subtype
|
||||
#define sqlite3_result_subtype sqlite3_api->result_subtype
|
||||
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
||||
|
||||
#ifndef SQLITE_CORE
|
||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
||||
/* This case when the file really is being compiled as a loadable
|
||||
** extension */
|
||||
# define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api=0;
|
||||
|
@ -1385,18 +1385,20 @@ struct FuncDestructor {
|
||||
** values must correspond to OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG. There
|
||||
** are assert() statements in the code to verify this.
|
||||
*/
|
||||
#define SQLITE_FUNC_ENCMASK 0x003 /* SQLITE_UTF8, SQLITE_UTF16BE or UTF16LE */
|
||||
#define SQLITE_FUNC_LIKE 0x004 /* Candidate for the LIKE optimization */
|
||||
#define SQLITE_FUNC_CASE 0x008 /* Case-sensitive LIKE-type function */
|
||||
#define SQLITE_FUNC_EPHEM 0x010 /* Ephemeral. Delete with VDBE */
|
||||
#define SQLITE_FUNC_NEEDCOLL 0x020 /* sqlite3GetFuncCollSeq() might be called */
|
||||
#define SQLITE_FUNC_LENGTH 0x040 /* Built-in length() function */
|
||||
#define SQLITE_FUNC_TYPEOF 0x080 /* Built-in typeof() function */
|
||||
#define SQLITE_FUNC_COUNT 0x100 /* Built-in count(*) aggregate */
|
||||
#define SQLITE_FUNC_COALESCE 0x200 /* Built-in coalesce() or ifnull() */
|
||||
#define SQLITE_FUNC_UNLIKELY 0x400 /* Built-in unlikely() function */
|
||||
#define SQLITE_FUNC_CONSTANT 0x800 /* Constant inputs give a constant output */
|
||||
#define SQLITE_FUNC_MINMAX 0x1000 /* True for min() and max() aggregates */
|
||||
#define SQLITE_FUNC_ENCMASK 0x0003 /* SQLITE_UTF8, SQLITE_UTF16BE or UTF16LE */
|
||||
#define SQLITE_FUNC_LIKE 0x0004 /* Candidate for the LIKE optimization */
|
||||
#define SQLITE_FUNC_CASE 0x0008 /* Case-sensitive LIKE-type function */
|
||||
#define SQLITE_FUNC_EPHEM 0x0010 /* Ephemeral. Delete with VDBE */
|
||||
#define SQLITE_FUNC_NEEDCOLL 0x0020 /* sqlite3GetFuncCollSeq() might be called*/
|
||||
#define SQLITE_FUNC_LENGTH 0x0040 /* Built-in length() function */
|
||||
#define SQLITE_FUNC_TYPEOF 0x0080 /* Built-in typeof() function */
|
||||
#define SQLITE_FUNC_COUNT 0x0100 /* Built-in count(*) aggregate */
|
||||
#define SQLITE_FUNC_COALESCE 0x0200 /* Built-in coalesce() or ifnull() */
|
||||
#define SQLITE_FUNC_UNLIKELY 0x0400 /* Built-in unlikely() function */
|
||||
#define SQLITE_FUNC_CONSTANT 0x0800 /* Constant inputs give a constant output */
|
||||
#define SQLITE_FUNC_MINMAX 0x1000 /* True for min() and max() aggregates */
|
||||
#define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a
|
||||
** single query - might change over time */
|
||||
|
||||
/*
|
||||
** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are
|
||||
@ -1412,6 +1414,12 @@ struct FuncDestructor {
|
||||
** VFUNCTION(zName, nArg, iArg, bNC, xFunc)
|
||||
** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag.
|
||||
**
|
||||
** DFUNCTION(zName, nArg, iArg, bNC, xFunc)
|
||||
** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag and
|
||||
** adds the SQLITE_FUNC_SLOCHNG flag. Used for date & time functions
|
||||
** and functions like sqlite_version() that can change, but not during
|
||||
** a single query.
|
||||
**
|
||||
** AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal)
|
||||
** Used to create an aggregate function definition implemented by
|
||||
** the C functions xStep and xFinal. The first four parameters
|
||||
@ -1432,11 +1440,14 @@ struct FuncDestructor {
|
||||
#define VFUNCTION(zName, nArg, iArg, bNC, xFunc) \
|
||||
{nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
|
||||
SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0}
|
||||
#define DFUNCTION(zName, nArg, iArg, bNC, xFunc) \
|
||||
{nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
|
||||
SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0}
|
||||
#define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags) \
|
||||
{nArg,SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags,\
|
||||
SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, #zName, 0, 0}
|
||||
#define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \
|
||||
{nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
|
||||
{nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
|
||||
pArg, 0, xFunc, 0, 0, #zName, 0, 0}
|
||||
#define LIKEFUNC(zName, nArg, arg, flags) \
|
||||
{nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \
|
||||
@ -1872,6 +1883,7 @@ struct Index {
|
||||
u8 *aSortOrder; /* for each column: True==DESC, False==ASC */
|
||||
char **azColl; /* Array of collation sequence names for index */
|
||||
Expr *pPartIdxWhere; /* WHERE clause for partial indices */
|
||||
ExprList *aColExpr; /* Column expressions */
|
||||
int tnum; /* DB Page containing root of this index */
|
||||
LogEst szIdxRow; /* Estimated average row size in bytes */
|
||||
u16 nKeyCol; /* Number of columns forming the key */
|
||||
@ -2121,9 +2133,10 @@ struct Expr {
|
||||
#define EP_MemToken 0x010000 /* Need to sqlite3DbFree() Expr.zToken */
|
||||
#define EP_NoReduce 0x020000 /* Cannot EXPRDUP_REDUCE this Expr */
|
||||
#define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */
|
||||
#define EP_ConstFunc 0x080000 /* Node is a SQLITE_FUNC_CONSTANT function */
|
||||
#define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */
|
||||
#define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */
|
||||
#define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */
|
||||
#define EP_Alias 0x400000 /* Is an alias for a result set column */
|
||||
|
||||
/*
|
||||
** Combinations of two or more EP_* flags
|
||||
@ -2340,6 +2353,7 @@ struct SrcList {
|
||||
#define WHERE_WANT_DISTINCT 0x0400 /* All output needs to be distinct */
|
||||
#define WHERE_SORTBYGROUP 0x0800 /* Support sqlite3WhereIsSorted() */
|
||||
#define WHERE_REOPEN_IDX 0x1000 /* Try to use OP_ReopenIdx */
|
||||
#define WHERE_ONEPASS_MULTIROW 0x2000 /* ONEPASS is ok with multiple rows */
|
||||
|
||||
/* Allowed return values from sqlite3WhereIsDistinct()
|
||||
*/
|
||||
@ -2392,6 +2406,7 @@ struct NameContext {
|
||||
#define NC_IsCheck 0x0004 /* True if resolving names in a CHECK constraint */
|
||||
#define NC_InAggFunc 0x0008 /* True if analyzing arguments to an agg func */
|
||||
#define NC_PartIdx 0x0010 /* True if resolving a partial index WHERE */
|
||||
#define NC_IdxExpr 0x0020 /* True if resolving columns of CREATE INDEX */
|
||||
#define NC_MinMaxAgg 0x1000 /* min/max aggregates seen. See note above */
|
||||
|
||||
/*
|
||||
@ -2661,7 +2676,7 @@ struct Parse {
|
||||
int nOpAlloc; /* Number of slots allocated for Vdbe.aOp[] */
|
||||
int iFixedOp; /* Never back out opcodes iFixedOp-1 or earlier */
|
||||
int ckBase; /* Base register of data during check constraints */
|
||||
int iPartIdxTab; /* Table corresponding to a partial index */
|
||||
int iSelfTab; /* Table of an index whose exprs are being coded */
|
||||
int iCacheLevel; /* ColCache valid when aColCache[].iLevel<=iCacheLevel */
|
||||
int iCacheCnt; /* Counter used to generate aColCache[].lru values */
|
||||
int nLabel; /* Number of labels used */
|
||||
@ -3181,6 +3196,11 @@ const sqlite3_mem_methods *sqlite3MemGetMemsys5(void);
|
||||
int sqlite3MutexInit(void);
|
||||
int sqlite3MutexEnd(void);
|
||||
#endif
|
||||
#if !defined(SQLITE_MUTEX_OMIT) && !defined(SQLITE_MUTEX_NOOP)
|
||||
void sqlite3MemoryBarrier(void);
|
||||
#else
|
||||
# define sqlite3MemoryBarrier()
|
||||
#endif
|
||||
|
||||
sqlite3_int64 sqlite3StatusValue(int);
|
||||
void sqlite3StatusUp(int, int);
|
||||
@ -3364,6 +3384,10 @@ int sqlite3WhereIsSorted(WhereInfo*);
|
||||
int sqlite3WhereContinueLabel(WhereInfo*);
|
||||
int sqlite3WhereBreakLabel(WhereInfo*);
|
||||
int sqlite3WhereOkOnePass(WhereInfo*, int*);
|
||||
#define ONEPASS_OFF 0 /* Use of ONEPASS not allowed */
|
||||
#define ONEPASS_SINGLE 1 /* ONEPASS valid for a single row update */
|
||||
#define ONEPASS_MULTI 2 /* ONEPASS is valid for multiple rows */
|
||||
void sqlite3ExprCodeLoadIndexColumn(Parse*, Index*, int, int, int);
|
||||
int sqlite3ExprCodeGetColumn(Parse*, Table*, int, int, int, u8);
|
||||
void sqlite3ExprCodeGetColumnOfTable(Vdbe*, Table*, int, int, int);
|
||||
void sqlite3ExprCodeMove(Parse*, int, int, int);
|
||||
@ -3426,8 +3450,9 @@ int sqlite3ExprIsInteger(Expr*, int*);
|
||||
int sqlite3ExprCanBeNull(const Expr*);
|
||||
int sqlite3ExprNeedsNoAffinityChange(const Expr*, char);
|
||||
int sqlite3IsRowid(const char*);
|
||||
void sqlite3GenerateRowDelete(Parse*,Table*,Trigger*,int,int,int,i16,u8,u8,u8);
|
||||
void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*);
|
||||
void sqlite3GenerateRowDelete(
|
||||
Parse*,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int);
|
||||
void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*, int);
|
||||
int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int, int*,Index*,int);
|
||||
void sqlite3ResolvePartIdxLabel(Parse*,int);
|
||||
void sqlite3GenerateConstraintChecks(Parse*,Table*,int*,int,int,int,int,
|
||||
|
@ -462,7 +462,7 @@ static void real2hex(
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: test_extract(record, field)
|
||||
** test_extract(record, field)
|
||||
**
|
||||
** This function implements an SQL user-function that accepts a blob
|
||||
** containing a formatted database record as the first argument. The
|
||||
@ -509,7 +509,7 @@ static void test_extract(
|
||||
}
|
||||
|
||||
/*
|
||||
** tclcmd: test_decode(record)
|
||||
** test_decode(record)
|
||||
**
|
||||
** This function implements an SQL user-function that accepts a blob
|
||||
** containing a formatted database record as its only argument. It returns
|
||||
@ -601,6 +601,8 @@ static void test_decode(
|
||||
}
|
||||
|
||||
/*
|
||||
** test_zeroblob(N)
|
||||
**
|
||||
** The implementation of scalar SQL function "test_zeroblob()". This is
|
||||
** similar to the built-in zeroblob() function, except that it does not
|
||||
** check that the integer parameter is within range before passing it
|
||||
@ -615,6 +617,31 @@ static void test_zeroblob(
|
||||
sqlite3_result_zeroblob(context, nZero);
|
||||
}
|
||||
|
||||
/* test_getsubtype(V)
|
||||
**
|
||||
** Return the subtype for value V.
|
||||
*/
|
||||
static void test_getsubtype(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
sqlite3_result_int(context, (int)sqlite3_value_subtype(argv[0]));
|
||||
}
|
||||
|
||||
/* test_setsubtype(V, T)
|
||||
**
|
||||
** Return the value V with its subtype changed to T
|
||||
*/
|
||||
static void test_setsubtype(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
sqlite3_result_value(context, argv[0]);
|
||||
sqlite3_result_subtype(context, (unsigned int)sqlite3_value_int(argv[1]));
|
||||
}
|
||||
|
||||
static int registerTestFunctions(sqlite3 *db){
|
||||
static const struct {
|
||||
char *zName;
|
||||
@ -641,6 +668,8 @@ static int registerTestFunctions(sqlite3 *db){
|
||||
{ "test_decode", 1, SQLITE_UTF8, test_decode},
|
||||
{ "test_extract", 2, SQLITE_UTF8, test_extract},
|
||||
{ "test_zeroblob", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC, test_zeroblob},
|
||||
{ "test_getsubtype", 1, SQLITE_UTF8, test_getsubtype},
|
||||
{ "test_setsubtype", 2, SQLITE_UTF8, test_setsubtype},
|
||||
};
|
||||
int i;
|
||||
|
||||
|
@ -403,6 +403,7 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){
|
||||
pParse->zTail = zSql;
|
||||
i = 0;
|
||||
assert( pzErrMsg!=0 );
|
||||
/* sqlite3ParserTrace(stdout, "parser: "); */
|
||||
pEngine = sqlite3ParserAlloc(sqlite3Malloc);
|
||||
if( pEngine==0 ){
|
||||
db->mallocFailed = 1;
|
||||
|
@ -253,11 +253,6 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
|
||||
sqlite3TreeViewLine(pView,"REGISTER(%d)", pExpr->iTable);
|
||||
break;
|
||||
}
|
||||
case TK_AS: {
|
||||
sqlite3TreeViewLine(pView,"AS %Q", pExpr->u.zToken);
|
||||
sqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
|
||||
break;
|
||||
}
|
||||
case TK_ID: {
|
||||
sqlite3TreeViewLine(pView,"ID \"%w\"", pExpr->u.zToken);
|
||||
break;
|
||||
|
10
src/update.c
10
src/update.c
@ -272,7 +272,9 @@ void sqlite3Update(
|
||||
|
||||
/* There is one entry in the aRegIdx[] array for each index on the table
|
||||
** being updated. Fill in aRegIdx[] with a register number that will hold
|
||||
** the key for accessing each index.
|
||||
** the key for accessing each index.
|
||||
**
|
||||
** FIXME: Be smarter about omitting indexes that use expressions.
|
||||
*/
|
||||
for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
|
||||
int reg;
|
||||
@ -281,7 +283,8 @@ void sqlite3Update(
|
||||
}else{
|
||||
reg = 0;
|
||||
for(i=0; i<pIdx->nKeyCol; i++){
|
||||
if( aXRef[pIdx->aiColumn[i]]>=0 ){
|
||||
i16 iIdxCol = pIdx->aiColumn[i];
|
||||
if( iIdxCol<0 || aXRef[iIdxCol]>=0 ){
|
||||
reg = ++pParse->nMem;
|
||||
break;
|
||||
}
|
||||
@ -381,6 +384,7 @@ void sqlite3Update(
|
||||
if( pWInfo==0 ) goto update_cleanup;
|
||||
okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
|
||||
for(i=0; i<nPk; i++){
|
||||
assert( pPk->aiColumn[i]>=(-1) );
|
||||
sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, pPk->aiColumn[i],
|
||||
iPk+i);
|
||||
}
|
||||
@ -583,7 +587,7 @@ void sqlite3Update(
|
||||
}
|
||||
VdbeCoverageNeverTaken(v);
|
||||
}
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, aRegIdx);
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, aRegIdx, -1);
|
||||
|
||||
/* If changing the record number, delete the old record. */
|
||||
if( hasFK || chngKey || pPk!=0 ){
|
||||
|
42
src/vdbe.c
42
src/vdbe.c
@ -3972,9 +3972,10 @@ case OP_Found: { /* jump, in3 */
|
||||
**
|
||||
** P1 is the index of a cursor open on an SQL table btree (with integer
|
||||
** keys). P3 is an integer rowid. If P1 does not contain a record with
|
||||
** rowid P3 then jump immediately to P2. If P1 does contain a record
|
||||
** with rowid P3 then leave the cursor pointing at that record and fall
|
||||
** through to the next instruction.
|
||||
** rowid P3 then jump immediately to P2. Or, if P2 is 0, raise an
|
||||
** SQLITE_CORRUPT error. If P1 does contain a record with rowid P3 then
|
||||
** leave the cursor pointing at that record and fall through to the next
|
||||
** instruction.
|
||||
**
|
||||
** The OP_NotFound opcode performs the same operation on index btrees
|
||||
** (with arbitrary multi-value keys).
|
||||
@ -4006,13 +4007,21 @@ case OP_NotExists: { /* jump, in3 */
|
||||
res = 0;
|
||||
iKey = pIn3->u.i;
|
||||
rc = sqlite3BtreeMovetoUnpacked(pCrsr, 0, iKey, 0, &res);
|
||||
assert( rc==SQLITE_OK || res==0 );
|
||||
pC->movetoTarget = iKey; /* Used by OP_Delete */
|
||||
pC->nullRow = 0;
|
||||
pC->cacheStatus = CACHE_STALE;
|
||||
pC->deferredMoveto = 0;
|
||||
VdbeBranchTaken(res!=0,2);
|
||||
pC->seekResult = res;
|
||||
if( res!=0 ) goto jump_to_p2;
|
||||
if( res!=0 ){
|
||||
assert( rc==SQLITE_OK );
|
||||
if( pOp->p2==0 ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
}else{
|
||||
goto jump_to_p2;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -4278,14 +4287,15 @@ case OP_InsertInt: {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: Delete P1 P2 * P4 *
|
||||
/* Opcode: Delete P1 P2 * P4 P5
|
||||
**
|
||||
** Delete the record at which the P1 cursor is currently pointing.
|
||||
**
|
||||
** The cursor will be left pointing at either the next or the previous
|
||||
** record in the table. If it is left pointing at the next record, then
|
||||
** the next Next instruction will be a no-op. Hence it is OK to delete
|
||||
** a record from within a Next loop.
|
||||
** If the P5 parameter is non-zero, the cursor will be left pointing at
|
||||
** either the next or the previous record in the table. If it is left
|
||||
** pointing at the next record, then the next Next instruction will be a
|
||||
** no-op. As a result, in this case it is OK to delete a record from within a
|
||||
** Next loop. If P5 is zero, then the cursor is left in an undefined state.
|
||||
**
|
||||
** If the OPFLAG_NCHANGE flag of P2 is set, then the row change count is
|
||||
** incremented (otherwise not).
|
||||
@ -4300,6 +4310,7 @@ case OP_InsertInt: {
|
||||
*/
|
||||
case OP_Delete: {
|
||||
VdbeCursor *pC;
|
||||
u8 hasUpdateCallback;
|
||||
|
||||
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
|
||||
pC = p->apCsr[pOp->p1];
|
||||
@ -4307,22 +4318,27 @@ case OP_Delete: {
|
||||
assert( pC->pCursor!=0 ); /* Only valid for real tables, no pseudotables */
|
||||
assert( pC->deferredMoveto==0 );
|
||||
|
||||
hasUpdateCallback = db->xUpdateCallback && pOp->p4.z && pC->isTable;
|
||||
if( pOp->p5 && hasUpdateCallback ){
|
||||
sqlite3BtreeKeySize(pC->pCursor, &pC->movetoTarget);
|
||||
}
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
/* The seek operation that positioned the cursor prior to OP_Delete will
|
||||
** have also set the pC->movetoTarget field to the rowid of the row that
|
||||
** is being deleted */
|
||||
if( pOp->p4.z && pC->isTable ){
|
||||
if( pOp->p4.z && pC->isTable && pOp->p5==0 ){
|
||||
i64 iKey = 0;
|
||||
sqlite3BtreeKeySize(pC->pCursor, &iKey);
|
||||
assert( pC->movetoTarget==iKey );
|
||||
}
|
||||
#endif
|
||||
|
||||
rc = sqlite3BtreeDelete(pC->pCursor);
|
||||
rc = sqlite3BtreeDelete(pC->pCursor, pOp->p5);
|
||||
pC->cacheStatus = CACHE_STALE;
|
||||
|
||||
/* Invoke the update-hook if required. */
|
||||
if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p4.z && pC->isTable ){
|
||||
if( rc==SQLITE_OK && hasUpdateCallback ){
|
||||
db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE,
|
||||
db->aDb[pC->iDb].zName, pOp->p4.z, pC->movetoTarget);
|
||||
assert( pC->iDb>=0 );
|
||||
@ -4861,7 +4877,7 @@ case OP_IdxDelete: {
|
||||
#endif
|
||||
rc = sqlite3BtreeMovetoUnpacked(pCrsr, &r, 0, 0, &res);
|
||||
if( rc==SQLITE_OK && res==0 ){
|
||||
rc = sqlite3BtreeDelete(pCrsr);
|
||||
rc = sqlite3BtreeDelete(pCrsr, 0);
|
||||
}
|
||||
assert( pC->deferredMoveto==0 );
|
||||
pC->cacheStatus = CACHE_STALE;
|
||||
|
@ -186,6 +186,7 @@ struct Mem {
|
||||
} u;
|
||||
u16 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
|
||||
u8 enc; /* SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */
|
||||
u8 eSubtype; /* Subtype for this value */
|
||||
int n; /* Number of characters in string value, excluding '\0' */
|
||||
char *z; /* String or BLOB value */
|
||||
/* ShallowCopy only needs to copy the information above */
|
||||
|
@ -187,6 +187,9 @@ int sqlite3_value_int(sqlite3_value *pVal){
|
||||
sqlite_int64 sqlite3_value_int64(sqlite3_value *pVal){
|
||||
return sqlite3VdbeIntValue((Mem*)pVal);
|
||||
}
|
||||
unsigned int sqlite3_value_subtype(sqlite3_value *pVal){
|
||||
return ((Mem*)pVal)->eSubtype;
|
||||
}
|
||||
const unsigned char *sqlite3_value_text(sqlite3_value *pVal){
|
||||
return (const unsigned char *)sqlite3ValueText(pVal, SQLITE_UTF8);
|
||||
}
|
||||
@ -365,6 +368,10 @@ void sqlite3_result_null(sqlite3_context *pCtx){
|
||||
assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
|
||||
sqlite3VdbeMemSetNull(pCtx->pOut);
|
||||
}
|
||||
void sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubtype){
|
||||
assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
|
||||
pCtx->pOut->eSubtype = eSubtype & 0xff;
|
||||
}
|
||||
void sqlite3_result_text(
|
||||
sqlite3_context *pCtx,
|
||||
const char *z,
|
||||
@ -696,7 +703,7 @@ void *sqlite3_user_data(sqlite3_context *p){
|
||||
** application defined function.
|
||||
*/
|
||||
sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){
|
||||
assert( p && p->pFunc );
|
||||
assert( p && p->pOut );
|
||||
return p->pOut->db;
|
||||
}
|
||||
|
||||
|
@ -247,7 +247,8 @@ int sqlite3_blob_open(
|
||||
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
|
||||
int j;
|
||||
for(j=0; j<pIdx->nKeyCol; j++){
|
||||
if( pIdx->aiColumn[j]==iCol ){
|
||||
/* FIXME: Be smarter about indexes that use expressions */
|
||||
if( pIdx->aiColumn[j]==iCol || pIdx->aiColumn[j]==(-2) ){
|
||||
zFault = "indexed";
|
||||
}
|
||||
}
|
||||
|
@ -1155,7 +1155,7 @@ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){
|
||||
** to be a scalar SQL function. If
|
||||
**
|
||||
** * all function arguments are SQL literals,
|
||||
** * the SQLITE_FUNC_CONSTANT function flag is set, and
|
||||
** * one of the SQLITE_FUNC_CONSTANT or _SLOCHNG function flags is set, and
|
||||
** * the SQLITE_FUNC_NEEDCOLL function flag is not set,
|
||||
**
|
||||
** then this routine attempts to invoke the SQL function. Assuming no
|
||||
@ -1196,7 +1196,7 @@ static int valueFromFunction(
|
||||
nName = sqlite3Strlen30(p->u.zToken);
|
||||
pFunc = sqlite3FindFunction(db, p->u.zToken, nName, nVal, enc, 0);
|
||||
assert( pFunc );
|
||||
if( (pFunc->funcFlags & SQLITE_FUNC_CONSTANT)==0
|
||||
if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0
|
||||
|| (pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL)
|
||||
){
|
||||
return SQLITE_OK;
|
||||
|
@ -2462,7 +2462,8 @@ int sqlite3WalFindFrame(
|
||||
{
|
||||
u32 iRead2 = 0;
|
||||
u32 iTest;
|
||||
for(iTest=iLast; iTest>0; iTest--){
|
||||
assert( pWal->minFrame>0 );
|
||||
for(iTest=iLast; iTest>=pWal->minFrame; iTest--){
|
||||
if( walFramePgno(pWal, iTest)==pgno ){
|
||||
iRead2 = iTest;
|
||||
break;
|
||||
|
131
src/where.c
131
src/where.c
@ -69,9 +69,11 @@ int sqlite3WhereBreakLabel(WhereInfo *pWInfo){
|
||||
}
|
||||
|
||||
/*
|
||||
** Return TRUE if an UPDATE or DELETE statement can operate directly on
|
||||
** the rowids returned by a WHERE clause. Return FALSE if doing an
|
||||
** UPDATE or DELETE might change subsequent WHERE clause results.
|
||||
** Return ONEPASS_OFF (0) if an UPDATE or DELETE statement is unable to
|
||||
** operate directly on the rowis returned by a WHERE clause. Return
|
||||
** ONEPASS_SINGLE (1) if the statement can operation directly because only
|
||||
** a single row is to be changed. Return ONEPASS_MULTI (2) if the one-pass
|
||||
** optimization can be used on multiple
|
||||
**
|
||||
** If the ONEPASS optimization is used (if this routine returns true)
|
||||
** then also write the indices of open cursors used by ONEPASS
|
||||
@ -85,7 +87,7 @@ int sqlite3WhereBreakLabel(WhereInfo *pWInfo){
|
||||
*/
|
||||
int sqlite3WhereOkOnePass(WhereInfo *pWInfo, int *aiCur){
|
||||
memcpy(aiCur, pWInfo->aiCurOnePass, sizeof(int)*2);
|
||||
return pWInfo->okOnePass;
|
||||
return pWInfo->eOnePass;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -180,10 +182,13 @@ static WhereTerm *whereScanNext(WhereScan *pScan){
|
||||
while( pScan->iEquiv<=pScan->nEquiv ){
|
||||
iCur = pScan->aiCur[pScan->iEquiv-1];
|
||||
iColumn = pScan->aiColumn[pScan->iEquiv-1];
|
||||
assert( iColumn!=(-2) || pScan->pIdxExpr!=0 );
|
||||
while( (pWC = pScan->pWC)!=0 ){
|
||||
for(pTerm=pWC->a+k; k<pWC->nTerm; k++, pTerm++){
|
||||
if( pTerm->leftCursor==iCur
|
||||
&& pTerm->u.leftColumn==iColumn
|
||||
&& (iColumn!=(-2)
|
||||
|| sqlite3ExprCompare(pTerm->pExpr->pLeft,pScan->pIdxExpr,iCur)==0)
|
||||
&& (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_FromJoin))
|
||||
){
|
||||
if( (pTerm->eOperator & WO_EQUIV)!=0
|
||||
@ -268,16 +273,19 @@ static WhereTerm *whereScanInit(
|
||||
u32 opMask, /* Operator(s) to scan for */
|
||||
Index *pIdx /* Must be compatible with this index */
|
||||
){
|
||||
int j;
|
||||
int j = 0;
|
||||
|
||||
/* memset(pScan, 0, sizeof(*pScan)); */
|
||||
pScan->pOrigWC = pWC;
|
||||
pScan->pWC = pWC;
|
||||
pScan->pIdxExpr = 0;
|
||||
if( pIdx ){
|
||||
j = iColumn;
|
||||
iColumn = pIdx->aiColumn[j];
|
||||
if( iColumn==(-2) ) pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr;
|
||||
}
|
||||
if( pIdx && iColumn>=0 ){
|
||||
pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity;
|
||||
for(j=0; pIdx->aiColumn[j]!=iColumn; j++){
|
||||
if( NEVER(j>pIdx->nColumn) ) return 0;
|
||||
}
|
||||
pScan->zCollName = pIdx->azColl[j];
|
||||
}else{
|
||||
pScan->idxaff = 0;
|
||||
@ -298,6 +306,9 @@ static WhereTerm *whereScanInit(
|
||||
** the WO_xx operator codes specified by the op parameter.
|
||||
** Return a pointer to the term. Return 0 if not found.
|
||||
**
|
||||
** If pIdx!=0 then search for terms matching the iColumn-th column of pIdx
|
||||
** rather than the iColumn-th column of table iCur.
|
||||
**
|
||||
** The term returned might by Y=<expr> if there is another constraint in
|
||||
** the WHERE clause that specifies that X=Y. Any such constraints will be
|
||||
** identified by the WO_EQUIV bit in the pTerm->eOperator field. The
|
||||
@ -373,6 +384,25 @@ static int findIndexCol(
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return TRUE if the iCol-th column of index pIdx is NOT NULL
|
||||
*/
|
||||
static int indexColumnNotNull(Index *pIdx, int iCol){
|
||||
int j;
|
||||
assert( pIdx!=0 );
|
||||
assert( iCol>=0 && iCol<pIdx->nColumn );
|
||||
j = pIdx->aiColumn[iCol];
|
||||
if( j>=0 ){
|
||||
return pIdx->pTable->aCol[j].notNull;
|
||||
}else if( j==(-1) ){
|
||||
return 1;
|
||||
}else{
|
||||
assert( j==(-2) );
|
||||
return 0; /* Assume an indexed expression can always yield a NULL */
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Return true if the DISTINCT expression-list passed as the third argument
|
||||
** is redundant.
|
||||
@ -423,12 +453,9 @@ static int isDistinctRedundant(
|
||||
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
|
||||
if( !IsUniqueIndex(pIdx) ) continue;
|
||||
for(i=0; i<pIdx->nKeyCol; i++){
|
||||
i16 iCol = pIdx->aiColumn[i];
|
||||
if( 0==sqlite3WhereFindTerm(pWC, iBase, iCol, ~(Bitmask)0, WO_EQ, pIdx) ){
|
||||
int iIdxCol = findIndexCol(pParse, pDistinct, iBase, pIdx, i);
|
||||
if( iIdxCol<0 || pTab->aCol[iCol].notNull==0 ){
|
||||
break;
|
||||
}
|
||||
if( 0==sqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask)0, WO_EQ, pIdx) ){
|
||||
if( findIndexCol(pParse, pDistinct, iBase, pIdx, i)<0 ) break;
|
||||
if( indexColumnNotNull(pIdx, i)==0 ) break;
|
||||
}
|
||||
}
|
||||
if( i==pIdx->nKeyCol ){
|
||||
@ -780,6 +807,7 @@ static sqlite3_index_info *allocateIndexInfo(
|
||||
testcase( pTerm->eOperator & WO_ALL );
|
||||
if( (pTerm->eOperator & ~(WO_ISNULL|WO_EQUIV|WO_IS))==0 ) continue;
|
||||
if( pTerm->wtFlags & TERM_VNULL ) continue;
|
||||
assert( pTerm->u.leftColumn>=(-1) );
|
||||
nTerm++;
|
||||
}
|
||||
|
||||
@ -835,6 +863,7 @@ static sqlite3_index_info *allocateIndexInfo(
|
||||
testcase( pTerm->eOperator & WO_ALL );
|
||||
if( (pTerm->eOperator & ~(WO_ISNULL|WO_EQUIV|WO_IS))==0 ) continue;
|
||||
if( pTerm->wtFlags & TERM_VNULL ) continue;
|
||||
assert( pTerm->u.leftColumn>=(-1) );
|
||||
pIdxCons[j].iColumn = pTerm->u.leftColumn;
|
||||
pIdxCons[j].iTermOffset = i;
|
||||
op = (u8)pTerm->eOperator & WO_ALL;
|
||||
@ -2126,7 +2155,6 @@ static int whereLoopAddBtreeIndex(
|
||||
u16 saved_nSkip; /* Original value of pNew->nSkip */
|
||||
u32 saved_wsFlags; /* Original value of pNew->wsFlags */
|
||||
LogEst saved_nOut; /* Original value of pNew->nOut */
|
||||
int iCol; /* Index of the column in the table */
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
LogEst rSize; /* Number of rows in the table */
|
||||
LogEst rLogSize; /* Logarithm of table size */
|
||||
@ -2147,16 +2175,15 @@ static int whereLoopAddBtreeIndex(
|
||||
if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE);
|
||||
|
||||
assert( pNew->u.btree.nEq<pProbe->nColumn );
|
||||
iCol = pProbe->aiColumn[pNew->u.btree.nEq];
|
||||
|
||||
pTerm = whereScanInit(&scan, pBuilder->pWC, pSrc->iCursor, iCol,
|
||||
opMask, pProbe);
|
||||
saved_nEq = pNew->u.btree.nEq;
|
||||
saved_nSkip = pNew->nSkip;
|
||||
saved_nLTerm = pNew->nLTerm;
|
||||
saved_wsFlags = pNew->wsFlags;
|
||||
saved_prereq = pNew->prereq;
|
||||
saved_nOut = pNew->nOut;
|
||||
pTerm = whereScanInit(&scan, pBuilder->pWC, pSrc->iCursor, saved_nEq,
|
||||
opMask, pProbe);
|
||||
pNew->rSetup = 0;
|
||||
rSize = pProbe->aiRowLogEst[0];
|
||||
rLogSize = estLog(rSize);
|
||||
@ -2169,7 +2196,7 @@ static int whereLoopAddBtreeIndex(
|
||||
int nRecValid = pBuilder->nRecValid;
|
||||
#endif
|
||||
if( (eOp==WO_ISNULL || (pTerm->wtFlags&TERM_VNULL)!=0)
|
||||
&& (iCol<0 || pSrc->pTab->aCol[iCol].notNull)
|
||||
&& indexColumnNotNull(pProbe, saved_nEq)
|
||||
){
|
||||
continue; /* ignore IS [NOT] NULL constraints on NOT NULL columns */
|
||||
}
|
||||
@ -2206,8 +2233,10 @@ static int whereLoopAddBtreeIndex(
|
||||
** changes "x IN (?)" into "x=?". */
|
||||
|
||||
}else if( eOp & (WO_EQ|WO_IS) ){
|
||||
int iCol = pProbe->aiColumn[saved_nEq];
|
||||
pNew->wsFlags |= WHERE_COLUMN_EQ;
|
||||
if( iCol<0 || (nInMul==0 && pNew->u.btree.nEq==pProbe->nKeyCol-1) ){
|
||||
assert( saved_nEq==pNew->u.btree.nEq );
|
||||
if( iCol==(-1) || (iCol>0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1) ){
|
||||
if( iCol>=0 && pProbe->uniqNotNull==0 ){
|
||||
pNew->wsFlags |= WHERE_UNQ_WANTED;
|
||||
}else{
|
||||
@ -2258,7 +2287,7 @@ static int whereLoopAddBtreeIndex(
|
||||
assert( eOp & (WO_ISNULL|WO_EQ|WO_IN|WO_IS) );
|
||||
|
||||
assert( pNew->nOut==saved_nOut );
|
||||
if( pTerm->truthProb<=0 && iCol>=0 ){
|
||||
if( pTerm->truthProb<=0 && pProbe->aiColumn[saved_nEq]>=0 ){
|
||||
assert( (eOp & WO_IN) || nIn==0 );
|
||||
testcase( eOp & WO_IN );
|
||||
pNew->nOut += pTerm->truthProb;
|
||||
@ -3785,7 +3814,7 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){
|
||||
) continue;
|
||||
opMask = pIdx->uniqNotNull ? (WO_EQ|WO_IS) : WO_EQ;
|
||||
for(j=0; j<pIdx->nKeyCol; j++){
|
||||
pTerm = sqlite3WhereFindTerm(pWC, iCur, pIdx->aiColumn[j], 0, opMask, pIdx);
|
||||
pTerm = sqlite3WhereFindTerm(pWC, iCur, j, 0, opMask, pIdx);
|
||||
if( pTerm==0 ) break;
|
||||
testcase( pTerm->eOperator & WO_IS );
|
||||
pLoop->aLTerm[j] = pTerm;
|
||||
@ -3931,6 +3960,10 @@ WhereInfo *sqlite3WhereBegin(
|
||||
sqlite3 *db; /* Database connection */
|
||||
int rc; /* Return code */
|
||||
|
||||
assert( (wctrlFlags & WHERE_ONEPASS_MULTIROW)==0 || (
|
||||
(wctrlFlags & WHERE_ONEPASS_DESIRED)!=0
|
||||
&& (wctrlFlags & WHERE_OMIT_OPEN_CLOSE)==0
|
||||
));
|
||||
|
||||
/* Variable initialization */
|
||||
db = pParse->db;
|
||||
@ -3986,6 +4019,7 @@ WhereInfo *sqlite3WhereBegin(
|
||||
pWInfo->iBreak = pWInfo->iContinue = sqlite3VdbeMakeLabel(v);
|
||||
pWInfo->wctrlFlags = wctrlFlags;
|
||||
pWInfo->savedNQueryLoop = pParse->nQueryLoop;
|
||||
assert( pWInfo->eOnePass==ONEPASS_OFF ); /* ONEPASS defaults to OFF */
|
||||
pMaskSet = &pWInfo->sMaskSet;
|
||||
sWLB.pWInfo = pWInfo;
|
||||
sWLB.pWC = &pWInfo->sWC;
|
||||
@ -4025,14 +4059,12 @@ WhereInfo *sqlite3WhereBegin(
|
||||
|
||||
/* Assign a bit from the bitmask to every term in the FROM clause.
|
||||
**
|
||||
** When assigning bitmask values to FROM clause cursors, it must be
|
||||
** the case that if X is the bitmask for the N-th FROM clause term then
|
||||
** the bitmask for all FROM clause terms to the left of the N-th term
|
||||
** is (X-1). An expression from the ON clause of a LEFT JOIN can use
|
||||
** its Expr.iRightJoinTable value to find the bitmask of the right table
|
||||
** of the join. Subtracting one from the right table bitmask gives a
|
||||
** bitmask for all tables to the left of the join. Knowing the bitmask
|
||||
** for all tables to the left of a left join is important. Ticket #3015.
|
||||
** The N-th term of the FROM clause is assigned a bitmask of 1<<N.
|
||||
**
|
||||
** The rule of the previous sentence ensures thta if X is the bitmask for
|
||||
** a table T, then X-1 is the bitmask for all other tables to the left of T.
|
||||
** Knowing the bitmask for all tables to the left of a left join is
|
||||
** important. Ticket #3015.
|
||||
**
|
||||
** Note that bitmasks are created for all pTabList->nSrc tables in
|
||||
** pTabList, not just the first nTabList tables. nTabList is normally
|
||||
@ -4043,14 +4075,10 @@ WhereInfo *sqlite3WhereBegin(
|
||||
createMask(pMaskSet, pTabList->a[ii].iCursor);
|
||||
sqlite3WhereTabFuncArgs(pParse, &pTabList->a[ii], &pWInfo->sWC);
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
Bitmask toTheLeft = 0;
|
||||
for(ii=0; ii<pTabList->nSrc; ii++){
|
||||
Bitmask m = sqlite3WhereGetMask(pMaskSet, pTabList->a[ii].iCursor);
|
||||
assert( (m-1)==toTheLeft );
|
||||
toTheLeft |= m;
|
||||
}
|
||||
#ifdef SQLITE_DEBUG
|
||||
for(ii=0; ii<pTabList->nSrc; ii++){
|
||||
Bitmask m = sqlite3WhereGetMask(pMaskSet, pTabList->a[ii].iCursor);
|
||||
assert( m==MASKBIT(ii) );
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -4178,11 +4206,16 @@ WhereInfo *sqlite3WhereBegin(
|
||||
** the statement to update or delete a single row.
|
||||
*/
|
||||
assert( (wctrlFlags & WHERE_ONEPASS_DESIRED)==0 || pWInfo->nLevel==1 );
|
||||
if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0
|
||||
&& (pWInfo->a[0].pWLoop->wsFlags & WHERE_ONEROW)!=0 ){
|
||||
pWInfo->okOnePass = 1;
|
||||
if( HasRowid(pTabList->a[0].pTab) ){
|
||||
pWInfo->a[0].pWLoop->wsFlags &= ~WHERE_IDX_ONLY;
|
||||
if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 ){
|
||||
int wsFlags = pWInfo->a[0].pWLoop->wsFlags;
|
||||
int bOnerow = (wsFlags & WHERE_ONEROW)!=0;
|
||||
if( bOnerow || ( (wctrlFlags & WHERE_ONEPASS_MULTIROW)
|
||||
&& 0==(wsFlags & WHERE_VIRTUALTABLE)
|
||||
)){
|
||||
pWInfo->eOnePass = bOnerow ? ONEPASS_SINGLE : ONEPASS_MULTI;
|
||||
if( HasRowid(pTabList->a[0].pTab) ){
|
||||
pWInfo->a[0].pWLoop->wsFlags &= ~WHERE_IDX_ONLY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4213,15 +4246,15 @@ WhereInfo *sqlite3WhereBegin(
|
||||
if( (pLoop->wsFlags & WHERE_IDX_ONLY)==0
|
||||
&& (wctrlFlags & WHERE_OMIT_OPEN_CLOSE)==0 ){
|
||||
int op = OP_OpenRead;
|
||||
if( pWInfo->okOnePass ){
|
||||
if( pWInfo->eOnePass!=ONEPASS_OFF ){
|
||||
op = OP_OpenWrite;
|
||||
pWInfo->aiCurOnePass[0] = pTabItem->iCursor;
|
||||
};
|
||||
sqlite3OpenTable(pParse, pTabItem->iCursor, iDb, pTab, op);
|
||||
assert( pTabItem->iCursor==pLevel->iTabCur );
|
||||
testcase( !pWInfo->okOnePass && pTab->nCol==BMS-1 );
|
||||
testcase( !pWInfo->okOnePass && pTab->nCol==BMS );
|
||||
if( !pWInfo->okOnePass && pTab->nCol<BMS && HasRowid(pTab) ){
|
||||
testcase( pWInfo->eOnePass==ONEPASS_OFF && pTab->nCol==BMS-1 );
|
||||
testcase( pWInfo->eOnePass==ONEPASS_OFF && pTab->nCol==BMS );
|
||||
if( pWInfo->eOnePass==ONEPASS_OFF && pTab->nCol<BMS && HasRowid(pTab) ){
|
||||
Bitmask b = pTabItem->colUsed;
|
||||
int n = 0;
|
||||
for(; b; b=b>>1, n++){}
|
||||
@ -4252,7 +4285,7 @@ WhereInfo *sqlite3WhereBegin(
|
||||
** WITHOUT ROWID table. No need for a separate index */
|
||||
iIndexCur = pLevel->iTabCur;
|
||||
op = 0;
|
||||
}else if( pWInfo->okOnePass ){
|
||||
}else if( pWInfo->eOnePass!=ONEPASS_OFF ){
|
||||
Index *pJ = pTabItem->pTab->pIndex;
|
||||
iIndexCur = iIdxCur;
|
||||
assert( wctrlFlags & WHERE_ONEPASS_DESIRED );
|
||||
@ -4460,7 +4493,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
|
||||
&& (pWInfo->wctrlFlags & WHERE_OMIT_OPEN_CLOSE)==0
|
||||
){
|
||||
int ws = pLoop->wsFlags;
|
||||
if( !pWInfo->okOnePass && (ws & WHERE_IDX_ONLY)==0 ){
|
||||
if( pWInfo->eOnePass==ONEPASS_OFF && (ws & WHERE_IDX_ONLY)==0 ){
|
||||
sqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor);
|
||||
}
|
||||
if( (ws & WHERE_INDEXED)!=0
|
||||
|
@ -286,6 +286,7 @@ struct WhereScan {
|
||||
WhereClause *pOrigWC; /* Original, innermost WhereClause */
|
||||
WhereClause *pWC; /* WhereClause currently being scanned */
|
||||
char *zCollName; /* Required collating sequence, if not NULL */
|
||||
Expr *pIdxExpr; /* Search for this index expression */
|
||||
char idxaff; /* Must match this affinity, if zCollName!=NULL */
|
||||
unsigned char nEquiv; /* Number of entries in aEquiv[] */
|
||||
unsigned char iEquiv; /* Next unused slot in aEquiv[] */
|
||||
@ -411,7 +412,7 @@ struct WhereInfo {
|
||||
u16 wctrlFlags; /* Flags originally passed to sqlite3WhereBegin() */
|
||||
i8 nOBSat; /* Number of ORDER BY terms satisfied by indices */
|
||||
u8 sorted; /* True if really sorted (not just grouped) */
|
||||
u8 okOnePass; /* Ok to use one-pass algorithm for UPDATE/DELETE */
|
||||
u8 eOnePass; /* ONEPASS_OFF, or _SINGLE, or _MULTI */
|
||||
u8 untestedTerms; /* Not all WHERE terms resolved by outer loop */
|
||||
u8 eDistinct; /* One of the WHERE_DISTINCT_* values below */
|
||||
u8 nLevel; /* Number of nested loop */
|
||||
|
@ -41,6 +41,16 @@ static void explainAppendTerm(
|
||||
sqlite3StrAccumAppend(pStr, "?", 1);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the name of the i-th column of the pIdx index.
|
||||
*/
|
||||
static const char *explainIndexColumnName(Index *pIdx, int i){
|
||||
i = pIdx->aiColumn[i];
|
||||
if( i==(-2) ) return "<expr>";
|
||||
if( i==(-1) ) return "rowid";
|
||||
return pIdx->pTable->aCol[i].zName;
|
||||
}
|
||||
|
||||
/*
|
||||
** Argument pLevel describes a strategy for scanning table pTab. This
|
||||
** function appends text to pStr that describes the subset of table
|
||||
@ -60,24 +70,22 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop, Table *pTab){
|
||||
u16 nEq = pLoop->u.btree.nEq;
|
||||
u16 nSkip = pLoop->nSkip;
|
||||
int i, j;
|
||||
Column *aCol = pTab->aCol;
|
||||
i16 *aiColumn = pIndex->aiColumn;
|
||||
|
||||
if( nEq==0 && (pLoop->wsFlags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))==0 ) return;
|
||||
sqlite3StrAccumAppend(pStr, " (", 2);
|
||||
for(i=0; i<nEq; i++){
|
||||
char *z = aiColumn[i] < 0 ? "rowid" : aCol[aiColumn[i]].zName;
|
||||
const char *z = explainIndexColumnName(pIndex, i);
|
||||
if( i ) sqlite3StrAccumAppend(pStr, " AND ", 5);
|
||||
sqlite3XPrintf(pStr, 0, i>=nSkip ? "%s=?" : "ANY(%s)", z);
|
||||
}
|
||||
|
||||
j = i;
|
||||
if( pLoop->wsFlags&WHERE_BTM_LIMIT ){
|
||||
char *z = aiColumn[j] < 0 ? "rowid" : aCol[aiColumn[j]].zName;
|
||||
const char *z = explainIndexColumnName(pIndex, i);
|
||||
explainAppendTerm(pStr, i++, z, ">");
|
||||
}
|
||||
if( pLoop->wsFlags&WHERE_TOP_LIMIT ){
|
||||
char *z = aiColumn[j] < 0 ? "rowid" : aCol[aiColumn[j]].zName;
|
||||
const char *z = explainIndexColumnName(pIndex, j);
|
||||
explainAppendTerm(pStr, i, z, "<");
|
||||
}
|
||||
sqlite3StrAccumAppend(pStr, ")", 1);
|
||||
@ -1211,7 +1219,12 @@ Bitmask sqlite3WhereCodeOneLoopStart(
|
||||
iRowidReg = ++pParse->nMem;
|
||||
sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, iRowidReg);
|
||||
sqlite3ExprCacheStore(pParse, iCur, -1, iRowidReg);
|
||||
sqlite3VdbeAddOp2(v, OP_Seek, iCur, iRowidReg); /* Deferred seek */
|
||||
if( pWInfo->eOnePass!=ONEPASS_OFF ){
|
||||
sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, iRowidReg);
|
||||
VdbeCoverage(v);
|
||||
}else{
|
||||
sqlite3VdbeAddOp2(v, OP_Seek, iCur, iRowidReg); /* Deferred seek */
|
||||
}
|
||||
}else if( iCur!=iIdxCur ){
|
||||
Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable);
|
||||
iRowidReg = sqlite3GetTempRange(pParse, pPk->nKeyCol);
|
||||
|
@ -795,6 +795,51 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){
|
||||
return mask;
|
||||
}
|
||||
|
||||
/*
|
||||
** Expression pExpr is one operand of a comparison operator that might
|
||||
** be useful for indexing. This routine checks to see if pExpr appears
|
||||
** in any index. Return TRUE (1) if pExpr is an indexed term and return
|
||||
** FALSE (0) if not. If TRUE is returned, also set *piCur to the cursor
|
||||
** number of the table that is indexed and *piColumn to the column number
|
||||
** of the column that is indexed, or -2 if an expression is being indexed.
|
||||
**
|
||||
** If pExpr is a TK_COLUMN column reference, then this routine always returns
|
||||
** true even if that particular column is not indexed, because the column
|
||||
** might be added to an automatic index later.
|
||||
*/
|
||||
static int exprMightBeIndexed(
|
||||
SrcList *pFrom, /* The FROM clause */
|
||||
Bitmask mPrereq, /* Bitmask of FROM clause terms referenced by pExpr */
|
||||
Expr *pExpr, /* An operand of a comparison operator */
|
||||
int *piCur, /* Write the referenced table cursor number here */
|
||||
int *piColumn /* Write the referenced table column number here */
|
||||
){
|
||||
Index *pIdx;
|
||||
int i;
|
||||
int iCur;
|
||||
if( pExpr->op==TK_COLUMN ){
|
||||
*piCur = pExpr->iTable;
|
||||
*piColumn = pExpr->iColumn;
|
||||
return 1;
|
||||
}
|
||||
if( mPrereq==0 ) return 0; /* No table references */
|
||||
if( (mPrereq&(mPrereq-1))!=0 ) return 0; /* Refs more than one table */
|
||||
for(i=0; mPrereq>1; i++, mPrereq>>=1){}
|
||||
iCur = pFrom->a[i].iCursor;
|
||||
for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){
|
||||
if( pIdx->aColExpr==0 ) continue;
|
||||
for(i=0; i<pIdx->nKeyCol; i++){
|
||||
if( pIdx->aiColumn[i]!=(-2) ) continue;
|
||||
if( sqlite3ExprCompare(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){
|
||||
*piCur = iCur;
|
||||
*piColumn = -2;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** The input to this routine is an WhereTerm structure with only the
|
||||
** "pExpr" field filled in. The job of this routine is to analyze the
|
||||
@ -865,16 +910,19 @@ static void exprAnalyze(
|
||||
pTerm->iParent = -1;
|
||||
pTerm->eOperator = 0;
|
||||
if( allowedOp(op) ){
|
||||
int iCur, iColumn;
|
||||
Expr *pLeft = sqlite3ExprSkipCollate(pExpr->pLeft);
|
||||
Expr *pRight = sqlite3ExprSkipCollate(pExpr->pRight);
|
||||
u16 opMask = (pTerm->prereqRight & prereqLeft)==0 ? WO_ALL : WO_EQUIV;
|
||||
if( pLeft->op==TK_COLUMN ){
|
||||
pTerm->leftCursor = pLeft->iTable;
|
||||
pTerm->u.leftColumn = pLeft->iColumn;
|
||||
if( exprMightBeIndexed(pSrc, prereqLeft, pLeft, &iCur, &iColumn) ){
|
||||
pTerm->leftCursor = iCur;
|
||||
pTerm->u.leftColumn = iColumn;
|
||||
pTerm->eOperator = operatorMask(op) & opMask;
|
||||
}
|
||||
if( op==TK_IS ) pTerm->wtFlags |= TERM_IS;
|
||||
if( pRight && pRight->op==TK_COLUMN ){
|
||||
if( pRight
|
||||
&& exprMightBeIndexed(pSrc, pTerm->prereqRight, pRight, &iCur, &iColumn)
|
||||
){
|
||||
WhereTerm *pNew;
|
||||
Expr *pDup;
|
||||
u16 eExtraOp = 0; /* Extra bits for pNew->eOperator */
|
||||
@ -903,8 +951,8 @@ static void exprAnalyze(
|
||||
}
|
||||
exprCommute(pParse, pDup);
|
||||
pLeft = sqlite3ExprSkipCollate(pDup->pLeft);
|
||||
pNew->leftCursor = pLeft->iTable;
|
||||
pNew->u.leftColumn = pLeft->iColumn;
|
||||
pNew->leftCursor = iCur;
|
||||
pNew->u.leftColumn = iColumn;
|
||||
testcase( (prereqLeft | extraRight) != prereqLeft );
|
||||
pNew->prereqRight = prereqLeft | extraRight;
|
||||
pNew->prereqAll = prereqAll;
|
||||
|
@ -68,7 +68,6 @@ do_test delete-3.1.7 {
|
||||
} {1 2 4 16}
|
||||
integrity_check delete-3.2
|
||||
|
||||
|
||||
# Semantic errors in the WHERE clause
|
||||
#
|
||||
do_test delete-4.1 {
|
||||
|
102
test/delete4.test
Normal file
102
test/delete4.test
Normal file
@ -0,0 +1,102 @@
|
||||
# 2005 August 24
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this script is a test of the DELETE command.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix delete4
|
||||
|
||||
do_execsql_test 1.1 {
|
||||
CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
|
||||
INSERT INTO t1 VALUES(1, 0);
|
||||
INSERT INTO t1 VALUES(2, 1);
|
||||
INSERT INTO t1 VALUES(3, 0);
|
||||
INSERT INTO t1 VALUES(4, 1);
|
||||
INSERT INTO t1 VALUES(5, 0);
|
||||
INSERT INTO t1 VALUES(6, 1);
|
||||
INSERT INTO t1 VALUES(7, 0);
|
||||
INSERT INTO t1 VALUES(8, 1);
|
||||
}
|
||||
do_execsql_test 1.2 {
|
||||
DELETE FROM t1 WHERE y=1;
|
||||
}
|
||||
do_execsql_test 1.3 {
|
||||
SELECT x FROM t1;
|
||||
} {1 3 5 7}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 2.1 {
|
||||
CREATE TABLE t1(x INTEGER PRIMARY KEY, y, z);
|
||||
INSERT INTO t1 VALUES(1, 0, randomblob(200));
|
||||
INSERT INTO t1 VALUES(2, 1, randomblob(200));
|
||||
INSERT INTO t1 VALUES(3, 0, randomblob(200));
|
||||
INSERT INTO t1 VALUES(4, 1, randomblob(200));
|
||||
INSERT INTO t1 VALUES(5, 0, randomblob(200));
|
||||
INSERT INTO t1 VALUES(6, 1, randomblob(200));
|
||||
INSERT INTO t1 VALUES(7, 0, randomblob(200));
|
||||
INSERT INTO t1 VALUES(8, 1, randomblob(200));
|
||||
}
|
||||
do_execsql_test 2.2 {
|
||||
DELETE FROM t1 WHERE y=1;
|
||||
}
|
||||
do_execsql_test 2.3 {
|
||||
SELECT x FROM t1;
|
||||
} {1 3 5 7}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.1 {
|
||||
CREATE TABLE t1(a, b, PRIMARY KEY(a, b)) WITHOUT ROWID;
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES(2, 4);
|
||||
INSERT INTO t1 VALUES(1, 5);
|
||||
DELETE FROM t1 WHERE a=1;
|
||||
SELECT * FROM t1;
|
||||
} {2 4}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# DELETE statement that uses the OR optimization
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.1 {
|
||||
CREATE TABLE t1(i INTEGER PRIMARY KEY, a, b);
|
||||
CREATE INDEX i1a ON t1(a);
|
||||
CREATE INDEX i1b ON t1(b);
|
||||
INSERT INTO t1 VALUES(1, 'one', 'i');
|
||||
INSERT INTO t1 VALUES(2, 'two', 'ii');
|
||||
INSERT INTO t1 VALUES(3, 'three', 'iii');
|
||||
INSERT INTO t1 VALUES(4, 'four', 'iv');
|
||||
INSERT INTO t1 VALUES(5, 'one', 'i');
|
||||
INSERT INTO t1 VALUES(6, 'two', 'ii');
|
||||
INSERT INTO t1 VALUES(7, 'three', 'iii');
|
||||
INSERT INTO t1 VALUES(8, 'four', 'iv');
|
||||
} {}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
DELETE FROM t1 WHERE a='two' OR b='iv';
|
||||
}
|
||||
|
||||
do_execsql_test 3.3 {
|
||||
SELECT i FROM t1 ORDER BY i;
|
||||
} {1 3 5 7}
|
||||
|
||||
do_execsql_test 3.4 {
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
|
||||
|
||||
finish_test
|
@ -373,8 +373,8 @@ do_createtable_tests 1.1.2 {
|
||||
}
|
||||
|
||||
|
||||
# EVIDENCE-OF: R-10195-31023 If a <database-name> is specified, it
|
||||
# must be either "main", "temp", or the name of an attached database.
|
||||
# EVIDENCE-OF: R-18448-33677 If a schema-name is specified, it must be
|
||||
# either "main", "temp", or the name of an attached database.
|
||||
#
|
||||
# EVIDENCE-OF: R-39822-07822 In this case the new table is created in
|
||||
# the named database.
|
||||
@ -422,9 +422,9 @@ do_createtable_tests 1.4 -tclquery {
|
||||
2 "CREATE TEMPORARY TABLE t2(a, b)" {{} {t1 t2} {} {}}
|
||||
}
|
||||
|
||||
# EVIDENCE-OF: R-49439-47561 It is an error to specify both a
|
||||
# <database-name> and the TEMP or TEMPORARY keyword, unless the
|
||||
# <database-name> is "temp".
|
||||
# EVIDENCE-OF: R-23976-43329 It is an error to specify both a
|
||||
# schema-name and the TEMP or TEMPORARY keyword, unless the schema-name
|
||||
# is "temp".
|
||||
#
|
||||
drop_all_tables
|
||||
do_createtable_tests 1.5.1 -error {
|
||||
@ -447,9 +447,8 @@ do_createtable_tests 1.5.2 -tclquery {
|
||||
4 "CREATE TEMPORARY TABLE TEMP.xxx(x)" {{} {t1 t2 t3 xxx} {} {}}
|
||||
}
|
||||
|
||||
# EVIDENCE-OF: R-00917-09393 If no database name is specified and the
|
||||
# TEMP keyword is not present then the table is created in the main
|
||||
# database.
|
||||
# EVIDENCE-OF: R-31997-24564 If no schema name is specified and the TEMP
|
||||
# keyword is not present then the table is created in the main database.
|
||||
#
|
||||
drop_all_tables
|
||||
do_createtable_tests 1.6 -tclquery {
|
||||
|
@ -70,9 +70,12 @@ do_delete_tests e_delete-1.1 {
|
||||
2 "DELETE FROM main.t2 ; SELECT * FROM t2" {}
|
||||
}
|
||||
|
||||
# EVIDENCE-OF: R-30203-16177 If a WHERE clause is supplied, then only
|
||||
# those rows for which the result of evaluating the WHERE clause as a
|
||||
# boolean expression is true are deleted.
|
||||
# EVIDENCE-OF: R-26300-50198 If a WHERE clause is supplied, then only
|
||||
# those rows for which the WHERE clause boolean expression is true are
|
||||
# deleted.
|
||||
#
|
||||
# EVIDENCE-OF: R-23360-48280 Rows for which the expression is false or
|
||||
# NULL are retained.
|
||||
#
|
||||
do_delete_tests e_delete-1.2 {
|
||||
1 "DELETE FROM t3 WHERE 1 ; SELECT x FROM t3" {}
|
||||
@ -117,8 +120,8 @@ do_execsql_test e_delete-2.0 {
|
||||
# EVIDENCE-OF: R-09681-58560 The table-name specified as part of a
|
||||
# DELETE statement within a trigger body must be unqualified.
|
||||
#
|
||||
# EVIDENCE-OF: R-36771-43788 In other words, the database-name. prefix
|
||||
# on the table name is not allowed within triggers.
|
||||
# EVIDENCE-OF: R-12275-20298 In other words, the schema-name. prefix on
|
||||
# the table name is not allowed within triggers.
|
||||
#
|
||||
do_delete_tests e_delete-2.1 -error {
|
||||
qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers
|
||||
|
@ -1426,16 +1426,16 @@ do_execsql_test e_expr-27.1.2 {
|
||||
typeof(CAST(4.5 as INTEGER)), CAST(4.5 as INTEGER)
|
||||
} {text UVU real 1.23 integer 4}
|
||||
|
||||
# EVIDENCE-OF: R-27225-65050 If the value of <expr> is NULL, then
|
||||
# the result of the CAST expression is also NULL.
|
||||
# EVIDENCE-OF: R-32434-09092 If the value of expr is NULL, then the
|
||||
# result of the CAST expression is also NULL.
|
||||
#
|
||||
do_expr_test e_expr-27.2.1 { CAST(NULL AS integer) } null {}
|
||||
do_expr_test e_expr-27.2.2 { CAST(NULL AS text) } null {}
|
||||
do_expr_test e_expr-27.2.3 { CAST(NULL AS blob) } null {}
|
||||
do_expr_test e_expr-27.2.4 { CAST(NULL AS number) } null {}
|
||||
|
||||
# EVIDENCE-OF: R-31076-23575 Casting a value to a <type-name> with
|
||||
# no affinity causes the value to be converted into a BLOB.
|
||||
# EVIDENCE-OF: R-43522-35548 Casting a value to a type-name with no
|
||||
# affinity causes the value to be converted into a BLOB.
|
||||
#
|
||||
do_expr_test e_expr-27.3.1 { CAST('abc' AS blob) } blob abc
|
||||
do_expr_test e_expr-27.3.2 { CAST('def' AS shobblob_x) } blob def
|
||||
|
@ -157,9 +157,9 @@ do_insert_tests e_insert-1.1 {
|
||||
3b "SELECT count(*) FROM a2" {4}
|
||||
}
|
||||
|
||||
# EVIDENCE-OF: R-53616-44976 If no column-list is specified then the
|
||||
# number of values inserted into each row must be the same as the number
|
||||
# of columns in the table.
|
||||
# EVIDENCE-OF: R-19218-01018 If the column-name list after table-name is
|
||||
# omitted then the number of values inserted into each row must be the
|
||||
# same as the number of columns in the table.
|
||||
#
|
||||
# A test in the block above verifies that if the VALUES list has the
|
||||
# correct number of columns (for table a2, 3 columns) works. So these
|
||||
@ -191,9 +191,9 @@ do_insert_tests e_insert-1.3 {
|
||||
3b "SELECT * FROM a2 WHERE oid=last_insert_rowid()" {2 x y}
|
||||
}
|
||||
|
||||
# EVIDENCE-OF: R-09234-17933 If a column-list is specified, then the
|
||||
# number of values in each term of the VALUE list must match the number
|
||||
# of specified columns.
|
||||
# EVIDENCE-OF: R-21115-58321 If a column-name list is specified, then
|
||||
# the number of values in each term of the VALUE list must match the
|
||||
# number of specified columns.
|
||||
#
|
||||
do_insert_tests e_insert-1.4 -error {
|
||||
%d values for %d columns
|
||||
@ -394,8 +394,8 @@ foreach {tn sql error ac data } {
|
||||
do_test e_insert-4.1.$tn.3 {sqlite3_get_autocommit db} $ac
|
||||
}
|
||||
|
||||
# EVIDENCE-OF: R-64196-02418 The optional "database-name." prefix on the
|
||||
# table-name is support for top-level INSERT statements only.
|
||||
# EVIDENCE-OF: R-59829-49719 The optional "schema-name." prefix on the
|
||||
# table-name is supported for top-level INSERT statements only.
|
||||
#
|
||||
# EVIDENCE-OF: R-05731-00924 The table name must be unqualified for
|
||||
# INSERT statements that occur within CREATE TRIGGER statements.
|
||||
|
@ -265,10 +265,9 @@ test_index 5.40 t2 collB value
|
||||
test_index 5.41 aux.t1 collA length
|
||||
test_index 5.42 aux.t1 collB value
|
||||
|
||||
# EVIDENCE-OF: R-15639-02023 If no database-name is specified and there
|
||||
# exists both a table or index and a collation sequence of the specified
|
||||
# name, SQLite interprets this as a request to rebuild the indices that
|
||||
# use the named collation sequence.
|
||||
# EVIDENCE-OF: R-35892-30289 For a command of the form "REINDEX name", a
|
||||
# match against collation-name takes precedence over a match against
|
||||
# index-name or table-name.
|
||||
#
|
||||
set_collations value length
|
||||
do_execsql_test e_reindex-2.6.0 {
|
||||
|
@ -65,9 +65,9 @@ do_execsql_test 1.2 { SELECT * FROM n2 } {main n2}
|
||||
do_execsql_test 1.3 { SELECT * FROM n3 } {at1 n3}
|
||||
do_execsql_test 1.4 { SELECT * FROM n4 } {at2 n4}
|
||||
|
||||
# EVIDENCE-OF: R-54577-28142 If a database name is specified as part of
|
||||
# an object reference, it must be either "main", or "temp" or the name
|
||||
# of an attached database.
|
||||
# EVIDENCE-OF: R-00634-08585 If a schema name is specified as part of an
|
||||
# object reference, it must be either "main", or "temp" or the
|
||||
# schema-name of an attached database.
|
||||
#
|
||||
# Or else it is a "no such table: xxx" error.
|
||||
#
|
||||
@ -79,7 +79,7 @@ do_execsql_test 2.1.4 { SELECT * FROM at2.n1 } {at2 n1}
|
||||
|
||||
do_catchsql_test 2.2 { SELECT * FROM xxx.n1 } {1 {no such table: xxx.n1}}
|
||||
|
||||
# EVIDENCE-OF: R-26223-47623 Like other SQL identifiers, database names
|
||||
# EVIDENCE-OF: R-17446-42210 Like other SQL identifiers, schema names
|
||||
# are case-insensitive.
|
||||
#
|
||||
resolve_reopen_db
|
||||
@ -88,8 +88,8 @@ do_execsql_test 3.2 { SELECT * FROM tEmP.n1 } {temp n1}
|
||||
do_execsql_test 3.3 { SELECT * FROM aT1.n1 } {at1 n1}
|
||||
do_execsql_test 3.4 { SELECT * FROM At2.n1 } {at2 n1}
|
||||
|
||||
# EVIDENCE-OF: R-15639-28392 If a database name is specified, then only
|
||||
# the named database is searched for the named object.
|
||||
# EVIDENCE-OF: R-14755-58619 If a schema name is specified, then only
|
||||
# that one schema is searched for the named object.
|
||||
#
|
||||
do_catchsql_test 4.1 { SELECT * FROM temp.n2 } {1 {no such table: temp.n2}}
|
||||
do_catchsql_test 4.2 { SELECT * FROM main.n2 } {0 {main n2}}
|
||||
|
@ -146,9 +146,8 @@ do_update_tests e_update-1.2 {
|
||||
{greek roman greek roman greek roman}
|
||||
}
|
||||
|
||||
# EVIDENCE-OF: R-42117-40023 Otherwise, the UPDATE affects only those
|
||||
# rows for which the result of evaluating the WHERE clause expression as
|
||||
# a boolean expression is true.
|
||||
# EVIDENCE-OF: R-58095-46013 Otherwise, the UPDATE affects only those
|
||||
# rows for which the WHERE clause boolean expression is true.
|
||||
#
|
||||
do_execsql_test e_update-1.3.0 {
|
||||
DELETE FROM main.t1;
|
||||
@ -265,9 +264,10 @@ do_update_tests e_update-1.7 -query {
|
||||
3 "UPDATE t2 SET a=c||c, c=NULL" {44 5 {} 99 14 {} 55 11 {}}
|
||||
}
|
||||
|
||||
# EVIDENCE-OF: R-12619-24112 The optional conflict-clause allows the
|
||||
# user to nominate a specific constraint conflict resolution algorithm
|
||||
# to use during this one UPDATE command.
|
||||
# EVIDENCE-OF: R-28518-13457 The optional "OR action" conflict clause
|
||||
# that follows the UPDATE keyword allows the user to nominate a specific
|
||||
# constraint conflict resolution algorithm to use during this one UPDATE
|
||||
# command.
|
||||
#
|
||||
do_execsql_test e_update-1.8.0 {
|
||||
DELETE FROM t3;
|
||||
@ -322,8 +322,8 @@ foreach {tn sql error ac data } {
|
||||
# EVIDENCE-OF: R-12123-54095 The table-name specified as part of an
|
||||
# UPDATE statement within a trigger body must be unqualified.
|
||||
#
|
||||
# EVIDENCE-OF: R-09690-36749 In other words, the database-name. prefix
|
||||
# on the table name of the UPDATE is not allowed within triggers.
|
||||
# EVIDENCE-OF: R-43190-62442 In other words, the schema-name. prefix on
|
||||
# the table name of the UPDATE is not allowed within triggers.
|
||||
#
|
||||
do_update_tests e_update-2.1 -error {
|
||||
qualified table names are not allowed on INSERT, UPDATE, and DELETE statements within triggers
|
||||
|
@ -12,8 +12,8 @@
|
||||
#
|
||||
# This file tests the PRAGMA foreign_key_check command.
|
||||
#
|
||||
# EVIDENCE-OF: R-01427-50262 PRAGMA database.foreign_key_check; PRAGMA
|
||||
# database.foreign_key_check(table-name);
|
||||
# EVIDENCE-OF: R-15402-03103 PRAGMA schema.foreign_key_check; PRAGMA
|
||||
# schema.foreign_key_check(table-name);
|
||||
#
|
||||
# EVIDENCE-OF: R-23918-17301 The foreign_key_check pragma checks the
|
||||
# database, or the table called "table-name", for foreign key
|
||||
|
@ -56,11 +56,11 @@ do_test index-2.1 {
|
||||
# Try adding an index on a column of a table where the table
|
||||
# exists but the column does not.
|
||||
#
|
||||
do_test index-2.1 {
|
||||
do_test index-2.1b {
|
||||
execsql {CREATE TABLE test1(f1 int, f2 int, f3 int)}
|
||||
set v [catch {execsql {CREATE INDEX index1 ON test1(f4)}} msg]
|
||||
lappend v $msg
|
||||
} {1 {table test1 has no column named f4}}
|
||||
} {1 {no such column: f4}}
|
||||
|
||||
# Try an index with some columns that match and others that do now.
|
||||
#
|
||||
@ -68,7 +68,7 @@ do_test index-2.2 {
|
||||
set v [catch {execsql {CREATE INDEX index1 ON test1(f1, f2, f4, f3)}} msg]
|
||||
execsql {DROP TABLE test1}
|
||||
lappend v $msg
|
||||
} {1 {table test1 has no column named f4}}
|
||||
} {1 {no such column: f4}}
|
||||
|
||||
# Try creating a bunch of indices on the same table
|
||||
#
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 2005 January 11
|
||||
# 2005-01-11
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
@ -11,7 +11,6 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the CREATE INDEX statement.
|
||||
#
|
||||
# $Id: index2.test,v 1.3 2006/03/03 19:12:30 drh Exp $
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 2005 February 14
|
||||
# 2005-02-14
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
@ -11,7 +11,6 @@
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing the CREATE INDEX statement.
|
||||
#
|
||||
# $Id: index3.test,v 1.3 2008/03/19 13:03:34 drh Exp $
|
||||
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
@ -40,17 +39,42 @@ do_test index3-1.3 {
|
||||
} {0 {}}
|
||||
integrity_check index3-1.4
|
||||
|
||||
# Backwards compatibility test:
|
||||
#
|
||||
# Verify that CREATE INDEX statements that use strings instead of
|
||||
# identifiers for the the column names continue to work correctly.
|
||||
# This is undocumented behavior retained for backwards compatiblity.
|
||||
#
|
||||
do_execsql_test index3-2.1 {
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1(a, b, c, d, e,
|
||||
PRIMARY KEY('a'), UNIQUE('b' COLLATE nocase DESC));
|
||||
CREATE INDEX t1c ON t1('c');
|
||||
CREATE INDEX t1d ON t1('d' COLLATE binary ASC);
|
||||
WITH RECURSIVE c(x) AS (VALUES(1) UNION SELECT x+1 FROM c WHERE x<30)
|
||||
INSERT INTO t1(a,b,c,d,e)
|
||||
SELECT x, printf('ab%03xxy',x), x, x, x FROM c;
|
||||
} {}
|
||||
do_execsql_test index3-2.2 {
|
||||
SELECT a FROM t1 WHERE b='ab005xy' COLLATE nocase;
|
||||
} {5}
|
||||
do_execsql_test index3-2.2eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a FROM t1 WHERE b='ab005xy' COLLATE nocase;
|
||||
} {/USING INDEX/}
|
||||
|
||||
|
||||
# This test corrupts the database file so it must be the last test
|
||||
# in the series.
|
||||
#
|
||||
do_test index3-99.1 {
|
||||
execsql {
|
||||
PRAGMA writable_schema=on;
|
||||
UPDATE sqlite_master SET sql='nonsense';
|
||||
UPDATE sqlite_master SET sql='nonsense' WHERE name='t1d'
|
||||
}
|
||||
db close
|
||||
catch { sqlite3 db test.db }
|
||||
catchsql { DROP INDEX i1 }
|
||||
} {1 {malformed database schema (t1)}}
|
||||
catchsql { DROP INDEX t1c }
|
||||
} {1 {malformed database schema (t1d)}}
|
||||
|
||||
finish_test
|
||||
|
@ -58,7 +58,7 @@ do_execsql_test indexedby-1.4 {
|
||||
# SQL view. Also test that specifying an index that does not exist or
|
||||
# is attached to a different table is detected as an error.
|
||||
#
|
||||
# EVIDENCE-OF: R-63761-48810 -- syntax diagram qualified-table-name
|
||||
# EVIDENCE-OF: R-07004-11522 -- syntax diagram qualified-table-name
|
||||
#
|
||||
# EVIDENCE-OF: R-58230-57098 The "INDEXED BY index-name" phrase
|
||||
# specifies that the named index must be used in order to look up values
|
||||
@ -231,13 +231,13 @@ do_execsql_test indexedby-6.2 {
|
||||
#
|
||||
do_execsql_test indexedby-7.1 {
|
||||
EXPLAIN QUERY PLAN DELETE FROM t1 WHERE a = 5
|
||||
} {0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?)}}
|
||||
} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
|
||||
do_execsql_test indexedby-7.2 {
|
||||
EXPLAIN QUERY PLAN DELETE FROM t1 NOT INDEXED WHERE a = 5
|
||||
} {0 0 0 {SCAN TABLE t1}}
|
||||
do_execsql_test indexedby-7.3 {
|
||||
EXPLAIN QUERY PLAN DELETE FROM t1 INDEXED BY i1 WHERE a = 5
|
||||
} {0 0 0 {SEARCH TABLE t1 USING COVERING INDEX i1 (a=?)}}
|
||||
} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
|
||||
do_execsql_test indexedby-7.4 {
|
||||
EXPLAIN QUERY PLAN DELETE FROM t1 INDEXED BY i1 WHERE a = 5 AND b = 10
|
||||
} {0 0 0 {SEARCH TABLE t1 USING INDEX i1 (a=?)}}
|
||||
|
222
test/indexexpr1.test
Normal file
222
test/indexexpr1.test
Normal file
@ -0,0 +1,222 @@
|
||||
# 2015-08-31
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is testing indexes on expressions.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
do_execsql_test indexexpr1-100 {
|
||||
CREATE TABLE t1(a,b,c);
|
||||
INSERT INTO t1(a,b,c)
|
||||
/* 123456789 123456789 123456789 123456789 123456789 123456789 */
|
||||
VALUES('In_the_beginning_was_the_Word',1,1),
|
||||
('and_the_Word_was_with_God',1,2),
|
||||
('and_the_Word_was_God',1,3),
|
||||
('The_same_was_in_the_beginning_with_God',2,1),
|
||||
('All_things_were_made_by_him',3,1),
|
||||
('and_without_him_was_not_any_thing_made_that_was_made',3,2);
|
||||
CREATE INDEX t1a1 ON t1(substr(a,1,12));
|
||||
} {}
|
||||
do_execsql_test indexexpr1-110 {
|
||||
SELECT b, c, '|' FROM t1 WHERE substr(a,1,12)=='and_the_Word' ORDER BY b, c;
|
||||
} {1 2 | 1 3 |}
|
||||
do_execsql_test indexexpr1-110eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT b, c, '|' FROM t1 WHERE substr(a,1,12)=='and_the_Word' ORDER BY b, c;
|
||||
} {/USING INDEX t1a1/}
|
||||
do_execsql_test indexexpr1-120 {
|
||||
SELECT b, c, '|' FROM t1 WHERE 'and_the_Word'==substr(a,1,12) ORDER BY b, c;
|
||||
} {1 2 | 1 3 |}
|
||||
do_execsql_test indexexpr1-120eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT b, c, '|' FROM t1 WHERE 'and_the_Word'==substr(a,1,12) ORDER BY b, c;
|
||||
} {/USING INDEX t1a1/}
|
||||
|
||||
do_execsql_test indexexpr1-130 {
|
||||
CREATE INDEX t1ba ON t1(b,substr(a,2,3),c);
|
||||
SELECT c FROM t1 WHERE b=1 AND substr(a,2,3)='nd_' ORDER BY c;
|
||||
} {2 3}
|
||||
do_execsql_test indexexpr1-130eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT c FROM t1 WHERE b=1 AND substr(a,2,3)='nd_' ORDER BY c;
|
||||
} {/USING INDEX t1ba/}
|
||||
|
||||
do_execsql_test indexexpr1-140 {
|
||||
SELECT rowid, substr(a,b,3), '|' FROM t1 ORDER BY 2;
|
||||
} {1 In_ | 2 and | 3 and | 6 d_w | 4 he_ | 5 l_t |}
|
||||
do_execsql_test indexexpr1-141 {
|
||||
CREATE INDEX t1abx ON t1(substr(a,b,3));
|
||||
SELECT rowid FROM t1 WHERE substr(a,b,3)<='and' ORDER BY +rowid;
|
||||
} {1 2 3}
|
||||
do_execsql_test indexexpr1-141eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT rowid FROM t1 WHERE substr(a,b,3)<='and' ORDER BY +rowid;
|
||||
} {/USING INDEX t1abx/}
|
||||
do_execsql_test indexexpr1-142 {
|
||||
SELECT rowid FROM t1 WHERE +substr(a,b,3)<='and' ORDER BY +rowid;
|
||||
} {1 2 3}
|
||||
do_execsql_test indexexpr1-150 {
|
||||
SELECT rowid FROM t1 WHERE substr(a,b,3) IN ('and','l_t','xyz')
|
||||
ORDER BY +rowid;
|
||||
} {2 3 5}
|
||||
do_execsql_test indexexpr1-150eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT rowid FROM t1 WHERE substr(a,b,3) IN ('and','l_t','xyz')
|
||||
ORDER BY +rowid;
|
||||
} {/USING INDEX t1abx/}
|
||||
|
||||
do_execsql_test indexexpr1-160 {
|
||||
ALTER TABLE t1 ADD COLUMN d;
|
||||
UPDATE t1 SET d=length(a);
|
||||
CREATE INDEX t1a2 ON t1(SUBSTR(a, 27, 3)) WHERE d>=29;
|
||||
SELECT rowid, b, c FROM t1
|
||||
WHERE substr(a,27,3)=='ord' AND d>=29;
|
||||
} {1 1 1}
|
||||
do_execsql_test indexexpr1-160eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT rowid, b, c FROM t1
|
||||
WHERE substr(a,27,3)=='ord' AND d>=29;
|
||||
} {/USING INDEX t1a2/}
|
||||
|
||||
|
||||
do_execsql_test indexexpr1-200 {
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1(id ANY PRIMARY KEY, a,b,c) WITHOUT ROWID;
|
||||
INSERT INTO t1(id,a,b,c)
|
||||
VALUES(1,'In_the_beginning_was_the_Word',1,1),
|
||||
(2,'and_the_Word_was_with_God',1,2),
|
||||
(3,'and_the_Word_was_God',1,3),
|
||||
(4,'The_same_was_in_the_beginning_with_God',2,1),
|
||||
(5,'All_things_were_made_by_him',3,1),
|
||||
(6,'and_without_him_was_not_any_thing_made_that_was_made',3,2);
|
||||
CREATE INDEX t1a1 ON t1(substr(a,1,12));
|
||||
} {}
|
||||
do_execsql_test indexexpr1-210 {
|
||||
SELECT b, c, '|' FROM t1 WHERE substr(a,1,12)=='and_the_Word' ORDER BY b, c;
|
||||
} {1 2 | 1 3 |}
|
||||
do_execsql_test indexexpr1-210eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT b, c, '|' FROM t1 WHERE substr(a,1,12)=='and_the_Word' ORDER BY b, c;
|
||||
} {/USING INDEX t1a1/}
|
||||
do_execsql_test indexexpr1-220 {
|
||||
SELECT b, c, '|' FROM t1 WHERE 'and_the_Word'==substr(a,1,12) ORDER BY b, c;
|
||||
} {1 2 | 1 3 |}
|
||||
do_execsql_test indexexpr1-220eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT b, c, '|' FROM t1 WHERE 'and_the_Word'==substr(a,1,12) ORDER BY b, c;
|
||||
} {/USING INDEX t1a1/}
|
||||
|
||||
do_execsql_test indexexpr1-230 {
|
||||
CREATE INDEX t1ba ON t1(b,substr(a,2,3),c);
|
||||
SELECT c FROM t1 WHERE b=1 AND substr(a,2,3)='nd_' ORDER BY c;
|
||||
} {2 3}
|
||||
do_execsql_test indexexpr1-230eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT c FROM t1 WHERE b=1 AND substr(a,2,3)='nd_' ORDER BY c;
|
||||
} {/USING INDEX t1ba/}
|
||||
|
||||
do_execsql_test indexexpr1-240 {
|
||||
SELECT id, substr(a,b,3), '|' FROM t1 ORDER BY 2;
|
||||
} {1 In_ | 2 and | 3 and | 6 d_w | 4 he_ | 5 l_t |}
|
||||
do_execsql_test indexexpr1-241 {
|
||||
CREATE INDEX t1abx ON t1(substr(a,b,3));
|
||||
SELECT id FROM t1 WHERE substr(a,b,3)<='and' ORDER BY +id;
|
||||
} {1 2 3}
|
||||
do_execsql_test indexexpr1-241eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT id FROM t1 WHERE substr(a,b,3)<='and' ORDER BY +id;
|
||||
} {/USING INDEX t1abx/}
|
||||
do_execsql_test indexexpr1-242 {
|
||||
SELECT id FROM t1 WHERE +substr(a,b,3)<='and' ORDER BY +id;
|
||||
} {1 2 3}
|
||||
do_execsql_test indexexpr1-250 {
|
||||
SELECT id FROM t1 WHERE substr(a,b,3) IN ('and','l_t','xyz')
|
||||
ORDER BY +id;
|
||||
} {2 3 5}
|
||||
do_execsql_test indexexpr1-250eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT id FROM t1 WHERE substr(a,b,3) IN ('and','l_t','xyz')
|
||||
ORDER BY +id;
|
||||
} {/USING INDEX t1abx/}
|
||||
|
||||
do_execsql_test indexexpr1-260 {
|
||||
ALTER TABLE t1 ADD COLUMN d;
|
||||
UPDATE t1 SET d=length(a);
|
||||
CREATE INDEX t1a2 ON t1(SUBSTR(a, 27, 3)) WHERE d>=29;
|
||||
SELECT id, b, c FROM t1
|
||||
WHERE substr(a,27,3)=='ord' AND d>=29;
|
||||
} {1 1 1}
|
||||
do_execsql_test indexexpr1-260eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT id, b, c FROM t1
|
||||
WHERE substr(a,27,3)=='ord' AND d>=29;
|
||||
} {/USING INDEX t1a2/}
|
||||
|
||||
|
||||
do_catchsql_test indexexpr1-300 {
|
||||
CREATE TABLE t2(a,b,c);
|
||||
CREATE INDEX t2x1 ON t2(a,b+random());
|
||||
} {1 {non-deterministic functions prohibited in index expressions}}
|
||||
do_catchsql_test indexexpr1-301 {
|
||||
CREATE INDEX t2x1 ON t2(a+julianday('now'));
|
||||
} {1 {non-deterministic functions prohibited in index expressions}}
|
||||
do_catchsql_test indexexpr1-310 {
|
||||
CREATE INDEX t2x2 ON t2(a,b+(SELECT 15));
|
||||
} {1 {subqueries prohibited in index expressions}}
|
||||
do_catchsql_test indexexpr1-320 {
|
||||
CREATE TABLE e1(x,y,UNIQUE(y,substr(x,1,5)));
|
||||
} {1 {expressions prohibited in PRIMARY KEY and UNIQUE constraints}}
|
||||
do_catchsql_test indexexpr1-330 {
|
||||
CREATE TABLE e1(x,y,PRIMARY KEY(y,substr(x,1,5)));
|
||||
} {1 {expressions prohibited in PRIMARY KEY and UNIQUE constraints}}
|
||||
do_catchsql_test indexexpr1-331 {
|
||||
CREATE TABLE e1(x,y,PRIMARY KEY(y,substr(x,1,5))) WITHOUT ROWID;
|
||||
} {1 {expressions prohibited in PRIMARY KEY and UNIQUE constraints}}
|
||||
do_catchsql_test indexexpr1-340 {
|
||||
CREATE TABLE e1(x,y,FOREIGN KEY(substr(y,1,5)) REFERENCES t1);
|
||||
} {1 {near "(": syntax error}}
|
||||
|
||||
do_execsql_test indexexpr1-400 {
|
||||
CREATE TABLE t3(a,b,c);
|
||||
WITH RECURSIVE c(x) AS (VALUES(1) UNION SELECT x+1 FROM c WHERE x<30)
|
||||
INSERT INTO t3(a,b,c)
|
||||
SELECT x, printf('ab%04xyz',x), random() FROM c;
|
||||
CREATE UNIQUE INDEX t3abc ON t3(CAST(a AS text), b, substr(c,1,3));
|
||||
SELECT a FROM t3 WHERE CAST(a AS text)<='10' ORDER BY +a;
|
||||
} {1 10}
|
||||
do_catchsql_test indexexpr1-410 {
|
||||
INSERT INTO t3 SELECT * FROM t3 WHERE rowid=10;
|
||||
} {1 {UNIQUE constraint failed: index 't3abc'}}
|
||||
|
||||
do_execsql_test indexexpr1-500 {
|
||||
CREATE TABLE t5(a);
|
||||
CREATE TABLE cnt(x);
|
||||
WITH RECURSIVE
|
||||
c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<5)
|
||||
INSERT INTO cnt(x) SELECT x FROM c;
|
||||
INSERT INTO t5(a) SELECT printf('abc%03dxyz',x) FROM cnt;
|
||||
CREATE INDEX t5ax ON t5( substr(a,4,3) );
|
||||
} {}
|
||||
do_execsql_test indexexpr1-510 {
|
||||
-- The use of the "k" alias in the WHERE clause is technically
|
||||
-- illegal, but SQLite allows it for historical reasons. In this
|
||||
-- test and the next, verify that "k" can be used by the t5ax index
|
||||
SELECT substr(a,4,3) AS k FROM cnt, t5 WHERE k=printf('%03d',x);
|
||||
} {001 002 003 004 005}
|
||||
do_execsql_test indexexpr1-510eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT substr(a,4,3) AS k FROM cnt, t5 WHERE k=printf('%03d',x);
|
||||
} {/USING INDEX t5ax/}
|
||||
|
||||
|
||||
finish_test
|
@ -16,9 +16,21 @@ set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
load_static_extension db json
|
||||
do_execsql_test json1-1.1 {
|
||||
do_execsql_test json1-1.1.00 {
|
||||
SELECT json_array(1,2.5,null,'hello');
|
||||
} {[1,2.5,null,"hello"]}
|
||||
do_execsql_test json1-1.1.01 {
|
||||
SELECT json_array(1,'{"abc":2.5,"def":null,"ghi":hello}',99);
|
||||
-- the second term goes in as a string:
|
||||
} {[1,"{\\"abc\\":2.5,\\"def\\":null,\\"ghi\\":hello}",99]}
|
||||
do_execsql_test json1-1.1.02 {
|
||||
SELECT json_array(1,json('{"abc":2.5,"def":null,"ghi":"hello"}'),99);
|
||||
-- the second term goes in as JSON
|
||||
} {[1,{"abc":2.5,"def":null,"ghi":"hello"},99]}
|
||||
do_execsql_test json1-1.1.03 {
|
||||
SELECT json_array(1,json_object('abc',2.5,'def',null,'ghi','hello'),99);
|
||||
-- the second term goes in as JSON
|
||||
} {[1,{"abc":2.5,"def":null,"ghi":"hello"},99]}
|
||||
do_execsql_test json1-1.2 {
|
||||
SELECT hex(json_array('String "\ Test'));
|
||||
} {5B22537472696E67205C225C5C2054657374225D}
|
||||
@ -54,13 +66,13 @@ do_execsql_test json1-3.1 {
|
||||
SELECT json_replace('{"a":1,"b":2}','$.a','[3,4,5]');
|
||||
} {{{"a":"[3,4,5]","b":2}}}
|
||||
do_execsql_test json1-3.2 {
|
||||
SELECT json_replace('{"a":1,"b":2}','$$.a','[3,4,5]');
|
||||
SELECT json_replace('{"a":1,"b":2}','$.a',json('[3,4,5]'));
|
||||
} {{{"a":[3,4,5],"b":2}}}
|
||||
do_execsql_test json1-3.3 {
|
||||
SELECT json_type(json_set('{"a":1,"b":2}','$.b','{"x":3,"y":4}'),'$.b');
|
||||
} {text}
|
||||
do_execsql_test json1-3.4 {
|
||||
SELECT json_type(json_set('{"a":1,"b":2}','$$.b','{"x":3,"y":4}'),'$.b');
|
||||
SELECT json_type(json_set('{"a":1,"b":2}','$.b',json('{"x":3,"y":4}')),'$.b');
|
||||
} {object}
|
||||
|
||||
# Per rfc7159, any JSON value is allowed at the top level, and whitespace
|
||||
|
281
test/json102.test
Normal file
281
test/json102.test
Normal file
@ -0,0 +1,281 @@
|
||||
# 2015-08-12
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements tests for JSON SQL functions extension to the
|
||||
# SQLite library.
|
||||
#
|
||||
# This file contains tests automatically generated from the json1
|
||||
# documentation.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
load_static_extension db json
|
||||
do_execsql_test json102-100 {
|
||||
SELECT json_object('ex','[52,3.14159]');
|
||||
} {{{"ex":"[52,3.14159]"}}}
|
||||
do_execsql_test json102-110 {
|
||||
SELECT json_object('ex',json('[52,3.14159]'));
|
||||
} {{{"ex":[52,3.14159]}}}
|
||||
do_execsql_test json102-120 {
|
||||
SELECT json_object('ex',json_array(52,3.14159));
|
||||
} {{{"ex":[52,3.14159]}}}
|
||||
do_execsql_test json102-130 {
|
||||
SELECT json(' { "this" : "is", "a": [ "test" ] } ');
|
||||
} {{{"this":"is","a":["test"]}}}
|
||||
do_execsql_test json102-140 {
|
||||
SELECT json_array(1,2,'3',4);
|
||||
} {{[1,2,"3",4]}}
|
||||
do_execsql_test json102-150 {
|
||||
SELECT json_array('[1,2]');
|
||||
} {{["[1,2]"]}}
|
||||
do_execsql_test json102-160 {
|
||||
SELECT json_array(json_array(1,2));
|
||||
} {{[[1,2]]}}
|
||||
do_execsql_test json102-170 {
|
||||
SELECT json_array(1,null,'3','[4,5]','{"six":7.7}');
|
||||
} {{[1,null,"3","[4,5]","{\"six\":7.7}"]}}
|
||||
do_execsql_test json102-180 {
|
||||
SELECT json_array(1,null,'3',json('[4,5]'),json('{"six":7.7}'));
|
||||
} {{[1,null,"3",[4,5],{"six":7.7}]}}
|
||||
do_execsql_test json102-190 {
|
||||
SELECT json_array_length('[1,2,3,4]');
|
||||
} {{4}}
|
||||
do_execsql_test json102-200 {
|
||||
SELECT json_array_length('[1,2,3,4]', '$');
|
||||
} {{4}}
|
||||
do_execsql_test json102-210 {
|
||||
SELECT json_array_length('[1,2,3,4]', '$[2]');
|
||||
} {{0}}
|
||||
do_execsql_test json102-220 {
|
||||
SELECT json_array_length('{"one":[1,2,3]}');
|
||||
} {{0}}
|
||||
do_execsql_test json102-230 {
|
||||
SELECT json_array_length('{"one":[1,2,3]}', '$.one');
|
||||
} {{3}}
|
||||
do_execsql_test json102-240 {
|
||||
SELECT json_array_length('{"one":[1,2,3]}', '$.two');
|
||||
} {{}}
|
||||
do_execsql_test json102-250 {
|
||||
SELECT json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$');
|
||||
} {{{"a":2,"c":[4,5,{"f":7}]}}}
|
||||
do_execsql_test json102-260 {
|
||||
SELECT json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.c');
|
||||
} {{[4,5,{"f":7}]}}
|
||||
do_execsql_test json102-270 {
|
||||
SELECT json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.c[2]');
|
||||
} {{{"f":7}}}
|
||||
do_execsql_test json102-280 {
|
||||
SELECT json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.c[2].f');
|
||||
} {{7}}
|
||||
do_execsql_test json102-290 {
|
||||
SELECT json_extract('{"a":2,"c":[4,5],"f":7}','$.c','$.a');
|
||||
} {{[[4,5],2]}}
|
||||
do_execsql_test json102-300 {
|
||||
SELECT json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.x');
|
||||
} {{}}
|
||||
do_execsql_test json102-310 {
|
||||
SELECT json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.x', '$.a');
|
||||
} {{[null,2]}}
|
||||
do_execsql_test json102-320 {
|
||||
SELECT json_insert('{"a":2,"c":4}', '$.a', 99);
|
||||
} {{{"a":2,"c":4}}}
|
||||
do_execsql_test json102-330 {
|
||||
SELECT json_insert('{"a":2,"c":4}', '$.e', 99);
|
||||
} {{{"a":2,"c":4,"e":99}}}
|
||||
do_execsql_test json102-340 {
|
||||
SELECT json_replace('{"a":2,"c":4}', '$.a', 99);
|
||||
} {{{"a":99,"c":4}}}
|
||||
do_execsql_test json102-350 {
|
||||
SELECT json_replace('{"a":2,"c":4}', '$.e', 99);
|
||||
} {{{"a":2,"c":4}}}
|
||||
do_execsql_test json102-360 {
|
||||
SELECT json_set('{"a":2,"c":4}', '$.a', 99);
|
||||
} {{{"a":99,"c":4}}}
|
||||
do_execsql_test json102-370 {
|
||||
SELECT json_set('{"a":2,"c":4}', '$.e', 99);
|
||||
} {{{"a":2,"c":4,"e":99}}}
|
||||
do_execsql_test json102-380 {
|
||||
SELECT json_set('{"a":2,"c":4}', '$.c', '[97,96]');
|
||||
} {{{"a":2,"c":"[97,96]"}}}
|
||||
do_execsql_test json102-390 {
|
||||
SELECT json_set('{"a":2,"c":4}', '$.c', json('[97,96]'));
|
||||
} {{{"a":2,"c":[97,96]}}}
|
||||
do_execsql_test json102-400 {
|
||||
SELECT json_set('{"a":2,"c":4}', '$.c', json_array(97,96));
|
||||
} {{{"a":2,"c":[97,96]}}}
|
||||
do_execsql_test json102-410 {
|
||||
SELECT json_object('a',2,'c',4);
|
||||
} {{{"a":2,"c":4}}}
|
||||
do_execsql_test json102-420 {
|
||||
SELECT json_object('a',2,'c','{e:5}');
|
||||
} {{{"a":2,"c":"{e:5}"}}}
|
||||
do_execsql_test json102-430 {
|
||||
SELECT json_object('a',2,'c',json_object('e',5));
|
||||
} {{{"a":2,"c":{"e":5}}}}
|
||||
do_execsql_test json102-440 {
|
||||
SELECT json_remove('[0,1,2,3,4]','$[2]');
|
||||
} {{[0,1,3,4]}}
|
||||
do_execsql_test json102-450 {
|
||||
SELECT json_remove('[0,1,2,3,4]','$[2]','$[0]');
|
||||
} {{[1,3,4]}}
|
||||
do_execsql_test json102-460 {
|
||||
SELECT json_remove('[0,1,2,3,4]','$[0]','$[2]');
|
||||
} {{[1,2,4]}}
|
||||
do_execsql_test json102-470 {
|
||||
SELECT json_remove('{"x":25,"y":42}');
|
||||
} {{{"x":25,"y":42}}}
|
||||
do_execsql_test json102-480 {
|
||||
SELECT json_remove('{"x":25,"y":42}','$.z');
|
||||
} {{{"x":25,"y":42}}}
|
||||
do_execsql_test json102-490 {
|
||||
SELECT json_remove('{"x":25,"y":42}','$.y');
|
||||
} {{{"x":25}}}
|
||||
do_execsql_test json102-500 {
|
||||
SELECT json_remove('{"x":25,"y":42}','$');
|
||||
} {{}}
|
||||
do_execsql_test json102-510 {
|
||||
SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}');
|
||||
} {{object}}
|
||||
do_execsql_test json102-520 {
|
||||
SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$');
|
||||
} {{object}}
|
||||
do_execsql_test json102-530 {
|
||||
SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a');
|
||||
} {{array}}
|
||||
do_execsql_test json102-540 {
|
||||
SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[0]');
|
||||
} {{integer}}
|
||||
do_execsql_test json102-550 {
|
||||
SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[1]');
|
||||
} {{real}}
|
||||
do_execsql_test json102-560 {
|
||||
SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[2]');
|
||||
} {{true}}
|
||||
do_execsql_test json102-570 {
|
||||
SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[3]');
|
||||
} {{false}}
|
||||
do_execsql_test json102-580 {
|
||||
SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[4]');
|
||||
} {{null}}
|
||||
do_execsql_test json102-590 {
|
||||
SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[5]');
|
||||
} {{text}}
|
||||
do_execsql_test json102-600 {
|
||||
SELECT json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[6]');
|
||||
} {{}}
|
||||
do_execsql_test json102-610 {
|
||||
SELECT json_valid(char(123)||'"x":35'||char(125));
|
||||
} {{1}}
|
||||
do_execsql_test json102-620 {
|
||||
SELECT json_valid(char(123)||'"x":35');
|
||||
} {{0}}
|
||||
|
||||
ifcapable vtab {
|
||||
do_execsql_test json102-1000 {
|
||||
CREATE TABLE user(name,phone);
|
||||
INSERT INTO user(name,phone) VALUES
|
||||
('Alice','["919-555-2345","804-555-3621"]'),
|
||||
('Bob','["201-555-8872"]'),
|
||||
('Cindy','["704-555-9983"]'),
|
||||
('Dave','["336-555-8421","704-555-4321","803-911-4421"]');
|
||||
SELECT DISTINCT user.name
|
||||
FROM user, json_each(user.phone)
|
||||
WHERE json_each.value LIKE '704-%'
|
||||
ORDER BY 1;
|
||||
} {Cindy Dave}
|
||||
|
||||
do_execsql_test json102-1010 {
|
||||
UPDATE user
|
||||
SET phone=json_extract(phone,'$[0]')
|
||||
WHERE json_array_length(phone)<2;
|
||||
SELECT name, substr(phone,1,5) FROM user ORDER BY name;
|
||||
} {Alice {["919} Bob 201-5 Cindy 704-5 Dave {["336}}
|
||||
do_execsql_test json102-1011 {
|
||||
SELECT name FROM user WHERE phone LIKE '704-%'
|
||||
UNION
|
||||
SELECT user.name
|
||||
FROM user, json_each(user.phone)
|
||||
WHERE json_valid(user.phone)
|
||||
AND json_each.value LIKE '704-%';
|
||||
} {Cindy Dave}
|
||||
|
||||
do_execsql_test json102-1100 {
|
||||
CREATE TABLE big(json JSON);
|
||||
INSERT INTO big(json) VALUES('{
|
||||
"id":123,
|
||||
"stuff":[1,2,3,4],
|
||||
"partlist":[
|
||||
{"uuid":"bb108722-572e-11e5-9320-7f3b63a4ca74"},
|
||||
{"uuid":"c690dc14-572e-11e5-95f9-dfc8861fd535"},
|
||||
{"subassembly":[
|
||||
{"uuid":"6fa5181e-5721-11e5-a04e-57f3d7b32808"}
|
||||
]}
|
||||
]
|
||||
}');
|
||||
INSERT INTO big(json) VALUES('{
|
||||
"id":456,
|
||||
"stuff":["hello","world","xyzzy"],
|
||||
"partlist":[
|
||||
{"uuid":false},
|
||||
{"uuid":"c690dc14-572e-11e5-95f9-dfc8861fd535"}
|
||||
]
|
||||
}');
|
||||
} {}
|
||||
set correct_answer [list \
|
||||
1 {$.id} 123 \
|
||||
1 {$.stuff[0]} 1 \
|
||||
1 {$.stuff[1]} 2 \
|
||||
1 {$.stuff[2]} 3 \
|
||||
1 {$.stuff[3]} 4 \
|
||||
1 {$.partlist[0].uuid} bb108722-572e-11e5-9320-7f3b63a4ca74 \
|
||||
1 {$.partlist[1].uuid} c690dc14-572e-11e5-95f9-dfc8861fd535 \
|
||||
1 {$.partlist[2].subassembly[0].uuid} 6fa5181e-5721-11e5-a04e-57f3d7b32808 \
|
||||
2 {$.id} 456 \
|
||||
2 {$.stuff[0]} hello \
|
||||
2 {$.stuff[1]} world \
|
||||
2 {$.stuff[2]} xyzzy \
|
||||
2 {$.partlist[0].uuid} 0 \
|
||||
2 {$.partlist[1].uuid} c690dc14-572e-11e5-95f9-dfc8861fd535]
|
||||
do_execsql_test json102-1110 {
|
||||
SELECT big.rowid, fullkey, value
|
||||
FROM big, json_tree(big.json)
|
||||
WHERE json_tree.type NOT IN ('object','array')
|
||||
ORDER BY +big.rowid, +json_tree.id
|
||||
} $correct_answer
|
||||
do_execsql_test json102-1120 {
|
||||
SELECT big.rowid, fullkey, atom
|
||||
FROM big, json_tree(big.json)
|
||||
WHERE atom IS NOT NULL
|
||||
ORDER BY +big.rowid, +json_tree.id
|
||||
} $correct_answer
|
||||
|
||||
do_execsql_test json102-1130 {
|
||||
SELECT DISTINCT json_extract(big.json,'$.id')
|
||||
FROM big, json_tree(big.json,'$.partlist')
|
||||
WHERE json_tree.key='uuid'
|
||||
AND json_tree.value='6fa5181e-5721-11e5-a04e-57f3d7b32808';
|
||||
} {123}
|
||||
do_execsql_test json102-1131 {
|
||||
SELECT DISTINCT json_extract(big.json,'$.id')
|
||||
FROM big, json_tree(big.json,'$')
|
||||
WHERE json_tree.key='uuid'
|
||||
AND json_tree.value='6fa5181e-5721-11e5-a04e-57f3d7b32808';
|
||||
} {123}
|
||||
do_execsql_test json102-1132 {
|
||||
SELECT DISTINCT json_extract(big.json,'$.id')
|
||||
FROM big, json_tree(big.json)
|
||||
WHERE json_tree.key='uuid'
|
||||
AND json_tree.value='6fa5181e-5721-11e5-a04e-57f3d7b32808';
|
||||
} {123}
|
||||
} ;# end ifcapable vtab
|
||||
|
||||
finish_test
|
@ -83,8 +83,8 @@ delete_file test.db test.db-journal
|
||||
delete_file test3.db test3.db-journal
|
||||
sqlite3 db test.db; set DB [sqlite3_connection_pointer db]
|
||||
|
||||
# EVIDENCE-OF: R-24197-42751 PRAGMA database.cache_size; PRAGMA
|
||||
# database.cache_size = pages; PRAGMA database.cache_size = -kibibytes;
|
||||
# EVIDENCE-OF: R-13861-56665 PRAGMA schema.cache_size; PRAGMA
|
||||
# schema.cache_size = pages; PRAGMA schema.cache_size = -kibibytes;
|
||||
# Query or change the suggested maximum number of database disk pages
|
||||
# that SQLite will hold in memory at once per open database file.
|
||||
#
|
||||
@ -697,8 +697,8 @@ do_test pragma-6.5.1b {
|
||||
} {0 0 a 1 1 b 2 -1 {}}
|
||||
|
||||
|
||||
# EVIDENCE-OF: R-62725-03366 PRAGMA database.index_info(index-name);
|
||||
# This pragma returns one row for each key column in the named index.
|
||||
# EVIDENCE-OF: R-29448-60346 PRAGMA schema.index_info(index-name); This
|
||||
# pragma returns one row for each key column in the named index.
|
||||
#
|
||||
# (The first column of output from PRAGMA index_info is...)
|
||||
# EVIDENCE-OF: R-34186-52914 The rank of the column within the index. (0
|
||||
@ -784,9 +784,9 @@ do_test pragma-6.8 {
|
||||
# Miscellaneous tests
|
||||
#
|
||||
ifcapable schema_pragmas {
|
||||
# EVIDENCE-OF: R-63500-32024 PRAGMA database.index_list(table-name);
|
||||
# This pragma returns one row for each index associated with the given
|
||||
# table.
|
||||
# EVIDENCE-OF: R-64103-17776 PRAGMA schema.index_list(table-name); This
|
||||
# pragma returns one row for each index associated with the given table.
|
||||
#
|
||||
do_test pragma-7.1.1 {
|
||||
# Make sure a pragma knows to read the schema if it needs to
|
||||
db close
|
||||
@ -1379,8 +1379,8 @@ ifcapable pager_pragmas {
|
||||
forcedelete test.db
|
||||
sqlite3 db test.db
|
||||
|
||||
# EVIDENCE-OF: R-13905-26312 PRAGMA database.page_count; Return the
|
||||
# total number of pages in the database file.
|
||||
# EVIDENCE-OF: R-15672-33611 PRAGMA schema.page_count; Return the total
|
||||
# number of pages in the database file.
|
||||
#
|
||||
do_test pragma-14.1 {
|
||||
execsql { pragma auto_vacuum = 0 }
|
||||
@ -1817,8 +1817,8 @@ do_test 23.2a {
|
||||
db2 eval {SELECT cid, name, '|' FROM out ORDER BY seqno}
|
||||
} {2 c | 3 d | 1 b |}
|
||||
|
||||
# EVIDENCE-OF: R-44874-46325 PRAGMA database.index_xinfo(index-name);
|
||||
# This pragma returns information about every column in an index.
|
||||
# EVIDENCE-OF: R-56143-29319 PRAGMA schema.index_xinfo(index-name); This
|
||||
# pragma returns information about every column in an index.
|
||||
#
|
||||
# EVIDENCE-OF: R-45970-35618 Unlike this index_info pragma, this pragma
|
||||
# returns information about every column in the index, not just the key
|
||||
@ -1861,9 +1861,8 @@ do_test 23.2d {
|
||||
db2 eval {PRAGMA index_xinfo(i2x)}
|
||||
} {0 3 d 0 nocase 1 1 2 c 1 BINARY 1 2 -1 {} 0 BINARY 0}
|
||||
|
||||
# EVIDENCE-OF: R-63500-32024 PRAGMA database.index_list(table-name);
|
||||
# This pragma returns one row for each index associated with the given
|
||||
# table.
|
||||
# EVIDENCE-OF: R-64103-17776 PRAGMA schema.index_list(table-name); This
|
||||
# pragma returns one row for each index associated with the given table.
|
||||
#
|
||||
# (The first column of output from PRAGMA index_list is...)
|
||||
# EVIDENCE-OF: R-02753-24748 A sequence number assigned to each index
|
||||
|
@ -42,7 +42,7 @@ sqlite3 db test.db; set DB [sqlite3_connection_pointer db]
|
||||
db eval {PRAGMA auto_vacuum=0}
|
||||
|
||||
|
||||
# EVIDENCE-OF: R-17887-14874 PRAGMA database.freelist_count; Return the
|
||||
# EVIDENCE-OF: R-11211-21323 PRAGMA schema.freelist_count; Return the
|
||||
# number of unused pages in the database file.
|
||||
#
|
||||
do_test pragma2-1.1 {
|
||||
|
@ -292,7 +292,7 @@ set LOG [open releasetest-out.txt w]
|
||||
proc PUTS {args} {
|
||||
if {[llength $args]==2} {
|
||||
puts [lindex $args 0] [lindex $args 1]
|
||||
puts [lindex $args 0] $::LOG [lindex $args 1]
|
||||
puts $::LOG [lindex $args 1]
|
||||
} else {
|
||||
puts [lindex $args 0]
|
||||
puts $::LOG [lindex $args 0]
|
||||
@ -710,6 +710,7 @@ proc main {argv} {
|
||||
set sec [expr {$elapsetime%60}]
|
||||
set etime [format (%02d:%02d:%02d) $hr $min $sec]
|
||||
PUTS [string repeat * 79]
|
||||
incr ::NERRCASE $::NERR
|
||||
PUTS "$::NERRCASE failures out of $::NTESTCASE tests in $etime"
|
||||
if {$::SQLITE_VERSION ne ""} {
|
||||
PUTS "SQLite $::SQLITE_VERSION"
|
||||
|
@ -144,6 +144,8 @@ do_test rowid-2.8 {
|
||||
execsql {SELECT x FROM t1 ORDER BY x}
|
||||
} {1 3 5 7 9}
|
||||
|
||||
if 0 { # With the index-on-expressions enhancement, creating
|
||||
# an index on ROWID has become possible.
|
||||
# We cannot index by ROWID
|
||||
#
|
||||
do_test rowid-2.9 {
|
||||
@ -162,6 +164,7 @@ do_test rowid-2.12 {
|
||||
set v [catch {execsql {CREATE INDEX idxt1 ON t1(x, rowid)}} msg]
|
||||
lappend v $msg
|
||||
} {1 {table t1 has no column named rowid}}
|
||||
}
|
||||
|
||||
# Columns defined in the CREATE statement override the buildin ROWID
|
||||
# column names.
|
||||
|
31
test/subtype1.test
Normal file
31
test/subtype1.test
Normal file
@ -0,0 +1,31 @@
|
||||
# 2015-09-10
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements tests for sqlite3_value_subtype() and
|
||||
# sqlite3_result_subtype() interfaces.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
do_execsql_test subtype1-100 {
|
||||
SELECT test_getsubtype('hello');
|
||||
} {0}
|
||||
do_execsql_test subtype1-110 {
|
||||
SELECT test_getsubtype(test_setsubtype('hello',123));
|
||||
} {123}
|
||||
do_execsql_test subtype1-120 {
|
||||
SELECT typeof(test_setsubtype('hello',123));
|
||||
} {text}
|
||||
do_execsql_test subtype1-130 {
|
||||
SELECT test_setsubtype('hello',123);
|
||||
} {hello}
|
||||
|
||||
finish_test
|
@ -69,5 +69,18 @@ do_execsql_test tabfunc01-3.1 {
|
||||
SELECT DISTINCT value FROM generate_series(1,x), t1 ORDER BY 1;
|
||||
} {1 2 3}
|
||||
|
||||
# Eponymous virtual table exists in the "main" schema only
|
||||
#
|
||||
do_execsql_test tabfunc01-4.1 {
|
||||
SELECT * FROM main.generate_series(1,4)
|
||||
} {1 2 3 4}
|
||||
do_catchsql_test tabfunc01-4.2 {
|
||||
SELECT * FROM temp.generate_series(1,4)
|
||||
} {1 {no such table: temp.generate_series}}
|
||||
do_catchsql_test tabfunc01-4.3 {
|
||||
ATTACH ':memory:' AS aux1;
|
||||
CREATE TABLE aux1.t1(a,b,c);
|
||||
SELECT * FROM aux1.generate_series(1,4)
|
||||
} {1 {no such table: aux1.generate_series}}
|
||||
|
||||
finish_test
|
||||
|
@ -823,5 +823,17 @@ do_execsql_test table-18.2 {
|
||||
PRAGMA integrity_check;
|
||||
} {ok}
|
||||
|
||||
# 2015-09-09
|
||||
# Ticket [https://www.sqlite.org/src/info/acd12990885d9276]
|
||||
# "CREATE TABLE ... AS SELECT ... FROM sqlite_master" fails because the row
|
||||
# in the sqlite_master table for the next table is initially populated
|
||||
# with a NULL instead of a record created by OP_Record.
|
||||
#
|
||||
do_execsql_test table-19.1 {
|
||||
CREATE TABLE t19 AS SELECT * FROM sqlite_master;
|
||||
SELECT name FROM t19 ORDER BY name;
|
||||
} {{} savepoint t10 t11 t12 t13 t16 t2 t3 t3\"xyz t4\"abc t7 t8 t9 tablet8 test1 weird}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user