0e90ad6a37
FossilOrigin-Name: 31932a9eb8dbb33d5535715ae8bbfdc55ce66b1a1a0abd57cefe720eeb31e231
928 lines
24 KiB
C
928 lines
24 KiB
C
/*
|
|
** 2017 October 27
|
|
**
|
|
** 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.
|
|
**
|
|
*************************************************************************
|
|
*/
|
|
|
|
#include "sqlite3ext.h"
|
|
SQLITE_EXTENSION_INIT1
|
|
|
|
/*
|
|
** Stuff that is available inside the amalgamation, but which we need to
|
|
** declare ourselves if this module is compiled separately.
|
|
*/
|
|
#ifndef SQLITE_AMALGAMATION
|
|
# include <string.h>
|
|
# include <stdio.h>
|
|
# include <stdlib.h>
|
|
# include <assert.h>
|
|
typedef unsigned char u8;
|
|
typedef unsigned short u16;
|
|
typedef unsigned int u32;
|
|
#define get4byte(x) ( \
|
|
((u32)((x)[0])<<24) + \
|
|
((u32)((x)[1])<<16) + \
|
|
((u32)((x)[2])<<8) + \
|
|
((u32)((x)[3])) \
|
|
)
|
|
#endif
|
|
|
|
typedef struct CidxTable CidxTable;
|
|
typedef struct CidxCursor CidxCursor;
|
|
|
|
struct CidxTable {
|
|
sqlite3_vtab base; /* Base class. Must be first */
|
|
sqlite3 *db;
|
|
};
|
|
|
|
struct CidxCursor {
|
|
sqlite3_vtab_cursor base; /* Base class. Must be first */
|
|
sqlite3_int64 iRowid; /* Row number of the output */
|
|
char *zIdxName; /* Copy of the index_name parameter */
|
|
char *zAfterKey; /* Copy of the after_key parameter */
|
|
sqlite3_stmt *pStmt; /* SQL statement that generates the output */
|
|
};
|
|
|
|
typedef struct CidxColumn CidxColumn;
|
|
struct CidxColumn {
|
|
char *zExpr; /* Text for indexed expression */
|
|
int bDesc; /* True for DESC columns, otherwise false */
|
|
int bKey; /* Part of index, not PK */
|
|
};
|
|
|
|
typedef struct CidxIndex CidxIndex;
|
|
struct CidxIndex {
|
|
char *zWhere; /* WHERE clause, if any */
|
|
int nCol; /* Elements in aCol[] array */
|
|
CidxColumn aCol[1]; /* Array of indexed columns */
|
|
};
|
|
|
|
static void *cidxMalloc(int *pRc, int n){
|
|
void *pRet = 0;
|
|
assert( n!=0 );
|
|
if( *pRc==SQLITE_OK ){
|
|
pRet = sqlite3_malloc(n);
|
|
if( pRet ){
|
|
memset(pRet, 0, n);
|
|
}else{
|
|
*pRc = SQLITE_NOMEM;
|
|
}
|
|
}
|
|
return pRet;
|
|
}
|
|
|
|
static void cidxCursorError(CidxCursor *pCsr, const char *zFmt, ...){
|
|
va_list ap;
|
|
va_start(ap, zFmt);
|
|
assert( pCsr->base.pVtab->zErrMsg==0 );
|
|
pCsr->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
/*
|
|
** Connect to the incremental_index_check virtual table.
|
|
*/
|
|
static int cidxConnect(
|
|
sqlite3 *db,
|
|
void *pAux,
|
|
int argc, const char *const*argv,
|
|
sqlite3_vtab **ppVtab,
|
|
char **pzErr
|
|
){
|
|
int rc = SQLITE_OK;
|
|
CidxTable *pRet;
|
|
|
|
#define IIC_ERRMSG 0
|
|
#define IIC_CURRENT_KEY 1
|
|
#define IIC_INDEX_NAME 2
|
|
#define IIC_AFTER_KEY 3
|
|
#define IIC_SCANNER_SQL 4
|
|
rc = sqlite3_declare_vtab(db,
|
|
"CREATE TABLE xyz("
|
|
" errmsg TEXT," /* Error message or NULL if everything is ok */
|
|
" current_key TEXT," /* SQLite quote() text of key values */
|
|
" index_name HIDDEN," /* IN: name of the index being scanned */
|
|
" after_key HIDDEN," /* IN: Start scanning after this key */
|
|
" scanner_sql HIDDEN" /* debuggingn info: SQL used for scanner */
|
|
")"
|
|
);
|
|
pRet = cidxMalloc(&rc, sizeof(CidxTable));
|
|
if( pRet ){
|
|
pRet->db = db;
|
|
}
|
|
|
|
*ppVtab = (sqlite3_vtab*)pRet;
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** Disconnect from or destroy an incremental_index_check virtual table.
|
|
*/
|
|
static int cidxDisconnect(sqlite3_vtab *pVtab){
|
|
CidxTable *pTab = (CidxTable*)pVtab;
|
|
sqlite3_free(pTab);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** idxNum and idxStr are not used. There are only three possible plans,
|
|
** which are all distinguished by the number of parameters.
|
|
**
|
|
** No parameters: A degenerate plan. The result is zero rows.
|
|
** 1 Parameter: Scan all of the index starting with first entry
|
|
** 2 parameters: Scan the index starting after the "after_key".
|
|
**
|
|
** Provide successively smaller costs for each of these plans to encourage
|
|
** the query planner to select the one with the most parameters.
|
|
*/
|
|
static int cidxBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pInfo){
|
|
int iIdxName = -1;
|
|
int iAfterKey = -1;
|
|
int i;
|
|
|
|
for(i=0; i<pInfo->nConstraint; i++){
|
|
struct sqlite3_index_constraint *p = &pInfo->aConstraint[i];
|
|
if( p->usable==0 ) continue;
|
|
if( p->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
|
|
|
|
if( p->iColumn==IIC_INDEX_NAME ){
|
|
iIdxName = i;
|
|
}
|
|
if( p->iColumn==IIC_AFTER_KEY ){
|
|
iAfterKey = i;
|
|
}
|
|
}
|
|
|
|
if( iIdxName<0 ){
|
|
pInfo->estimatedCost = 1000000000.0;
|
|
}else{
|
|
pInfo->aConstraintUsage[iIdxName].argvIndex = 1;
|
|
pInfo->aConstraintUsage[iIdxName].omit = 1;
|
|
if( iAfterKey<0 ){
|
|
pInfo->estimatedCost = 1000000.0;
|
|
}else{
|
|
pInfo->aConstraintUsage[iAfterKey].argvIndex = 2;
|
|
pInfo->aConstraintUsage[iAfterKey].omit = 1;
|
|
pInfo->estimatedCost = 1000.0;
|
|
}
|
|
}
|
|
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Open a new btreeinfo cursor.
|
|
*/
|
|
static int cidxOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
|
|
CidxCursor *pRet;
|
|
int rc = SQLITE_OK;
|
|
|
|
pRet = cidxMalloc(&rc, sizeof(CidxCursor));
|
|
|
|
*ppCursor = (sqlite3_vtab_cursor*)pRet;
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** Close a btreeinfo cursor.
|
|
*/
|
|
static int cidxClose(sqlite3_vtab_cursor *pCursor){
|
|
CidxCursor *pCsr = (CidxCursor*)pCursor;
|
|
sqlite3_finalize(pCsr->pStmt);
|
|
sqlite3_free(pCsr->zIdxName);
|
|
sqlite3_free(pCsr->zAfterKey);
|
|
sqlite3_free(pCsr);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Move a btreeinfo cursor to the next entry in the file.
|
|
*/
|
|
static int cidxNext(sqlite3_vtab_cursor *pCursor){
|
|
CidxCursor *pCsr = (CidxCursor*)pCursor;
|
|
int rc = sqlite3_step(pCsr->pStmt);
|
|
if( rc!=SQLITE_ROW ){
|
|
rc = sqlite3_finalize(pCsr->pStmt);
|
|
pCsr->pStmt = 0;
|
|
if( rc!=SQLITE_OK ){
|
|
sqlite3 *db = ((CidxTable*)pCsr->base.pVtab)->db;
|
|
cidxCursorError(pCsr, "Cursor error: %s", sqlite3_errmsg(db));
|
|
}
|
|
}else{
|
|
pCsr->iRowid++;
|
|
rc = SQLITE_OK;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/* We have reached EOF if previous sqlite3_step() returned
|
|
** anything other than SQLITE_ROW;
|
|
*/
|
|
static int cidxEof(sqlite3_vtab_cursor *pCursor){
|
|
CidxCursor *pCsr = (CidxCursor*)pCursor;
|
|
return pCsr->pStmt==0;
|
|
}
|
|
|
|
static char *cidxMprintf(int *pRc, const char *zFmt, ...){
|
|
char *zRet = 0;
|
|
va_list ap;
|
|
va_start(ap, zFmt);
|
|
zRet = sqlite3_vmprintf(zFmt, ap);
|
|
if( *pRc==SQLITE_OK ){
|
|
if( zRet==0 ){
|
|
*pRc = SQLITE_NOMEM;
|
|
}
|
|
}else{
|
|
sqlite3_free(zRet);
|
|
zRet = 0;
|
|
}
|
|
va_end(ap);
|
|
return zRet;
|
|
}
|
|
|
|
static sqlite3_stmt *cidxPrepare(
|
|
int *pRc, CidxCursor *pCsr, const char *zFmt, ...
|
|
){
|
|
sqlite3_stmt *pRet = 0;
|
|
char *zSql;
|
|
va_list ap; /* ... printf arguments */
|
|
va_start(ap, zFmt);
|
|
|
|
zSql = sqlite3_vmprintf(zFmt, ap);
|
|
if( *pRc==SQLITE_OK ){
|
|
if( zSql==0 ){
|
|
*pRc = SQLITE_NOMEM;
|
|
}else{
|
|
sqlite3 *db = ((CidxTable*)pCsr->base.pVtab)->db;
|
|
*pRc = sqlite3_prepare_v2(db, zSql, -1, &pRet, 0);
|
|
if( *pRc!=SQLITE_OK ){
|
|
cidxCursorError(pCsr, "SQL error: %s", sqlite3_errmsg(db));
|
|
}
|
|
}
|
|
}
|
|
sqlite3_free(zSql);
|
|
va_end(ap);
|
|
|
|
return pRet;
|
|
}
|
|
|
|
static void cidxFinalize(int *pRc, sqlite3_stmt *pStmt){
|
|
int rc = sqlite3_finalize(pStmt);
|
|
if( *pRc==SQLITE_OK ) *pRc = rc;
|
|
}
|
|
|
|
char *cidxStrdup(int *pRc, const char *zStr){
|
|
char *zRet = 0;
|
|
if( *pRc==SQLITE_OK ){
|
|
int n = (int)strlen(zStr);
|
|
zRet = cidxMalloc(pRc, n+1);
|
|
if( zRet ) memcpy(zRet, zStr, n+1);
|
|
}
|
|
return zRet;
|
|
}
|
|
|
|
static void cidxFreeIndex(CidxIndex *pIdx){
|
|
if( pIdx ){
|
|
int i;
|
|
for(i=0; i<pIdx->nCol; i++){
|
|
sqlite3_free(pIdx->aCol[i].zExpr);
|
|
}
|
|
sqlite3_free(pIdx->zWhere);
|
|
sqlite3_free(pIdx);
|
|
}
|
|
}
|
|
|
|
static int cidx_isspace(char c){
|
|
return c==' ' || c=='\t' || c=='\r' || c=='\n';
|
|
}
|
|
|
|
static int cidx_isident(char c){
|
|
return c<0
|
|
|| (c>='0' && c<='9') || (c>='a' && c<='z')
|
|
|| (c>='A' && c<='Z') || c=='_';
|
|
}
|
|
|
|
#define CIDX_PARSE_EOF 0
|
|
#define CIDX_PARSE_COMMA 1 /* "," */
|
|
#define CIDX_PARSE_OPEN 2 /* "(" */
|
|
#define CIDX_PARSE_CLOSE 3 /* ")" */
|
|
|
|
/*
|
|
** Argument zIn points into the start, middle or end of a CREATE INDEX
|
|
** statement. If argument pbDoNotTrim is non-NULL, then this function
|
|
** scans the input until it finds EOF, a comma (",") or an open or
|
|
** close parenthesis character. It then sets (*pzOut) to point to said
|
|
** character and returns a CIDX_PARSE_XXX constant as appropriate. The
|
|
** parser is smart enough that special characters inside SQL strings
|
|
** or comments are not returned for.
|
|
**
|
|
** Or, if argument pbDoNotTrim is NULL, then this function sets *pzOut
|
|
** to point to the first character of the string that is not whitespace
|
|
** or part of an SQL comment and returns CIDX_PARSE_EOF.
|
|
**
|
|
** Additionally, if pbDoNotTrim is not NULL and the element immediately
|
|
** before (*pzOut) is an SQL comment of the form "-- comment", then
|
|
** (*pbDoNotTrim) is set before returning. In all other cases it is
|
|
** cleared.
|
|
*/
|
|
static int cidxFindNext(
|
|
const char *zIn,
|
|
const char **pzOut,
|
|
int *pbDoNotTrim /* OUT: True if prev is -- comment */
|
|
){
|
|
const char *z = zIn;
|
|
|
|
while( 1 ){
|
|
while( cidx_isspace(*z) ) z++;
|
|
if( z[0]=='-' && z[1]=='-' ){
|
|
z += 2;
|
|
while( z[0]!='\n' ){
|
|
if( z[0]=='\0' ) return CIDX_PARSE_EOF;
|
|
z++;
|
|
}
|
|
while( cidx_isspace(*z) ) z++;
|
|
if( pbDoNotTrim ) *pbDoNotTrim = 1;
|
|
}else
|
|
if( z[0]=='/' && z[1]=='*' ){
|
|
z += 2;
|
|
while( z[0]!='*' || z[1]!='/' ){
|
|
if( z[1]=='\0' ) return CIDX_PARSE_EOF;
|
|
z++;
|
|
}
|
|
z += 2;
|
|
}else{
|
|
*pzOut = z;
|
|
if( pbDoNotTrim==0 ) return CIDX_PARSE_EOF;
|
|
switch( *z ){
|
|
case '\0':
|
|
return CIDX_PARSE_EOF;
|
|
case '(':
|
|
return CIDX_PARSE_OPEN;
|
|
case ')':
|
|
return CIDX_PARSE_CLOSE;
|
|
case ',':
|
|
return CIDX_PARSE_COMMA;
|
|
|
|
case '"':
|
|
case '\'':
|
|
case '`': {
|
|
char q = *z;
|
|
z++;
|
|
while( *z ){
|
|
if( *z==q ){
|
|
z++;
|
|
if( *z!=q ) break;
|
|
}
|
|
z++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case '[':
|
|
while( *z++!=']' );
|
|
break;
|
|
|
|
default:
|
|
z++;
|
|
break;
|
|
}
|
|
*pbDoNotTrim = 0;
|
|
}
|
|
}
|
|
|
|
assert( 0 );
|
|
return -1;
|
|
}
|
|
|
|
static int cidxParseSQL(CidxCursor *pCsr, CidxIndex *pIdx, const char *zSql){
|
|
const char *z = zSql;
|
|
const char *z1;
|
|
int e;
|
|
int rc = SQLITE_OK;
|
|
int nParen = 1;
|
|
int bDoNotTrim = 0;
|
|
CidxColumn *pCol = pIdx->aCol;
|
|
|
|
e = cidxFindNext(z, &z, &bDoNotTrim);
|
|
if( e!=CIDX_PARSE_OPEN ) goto parse_error;
|
|
z1 = z+1;
|
|
z++;
|
|
while( nParen>0 ){
|
|
e = cidxFindNext(z, &z, &bDoNotTrim);
|
|
if( e==CIDX_PARSE_EOF ) goto parse_error;
|
|
if( (e==CIDX_PARSE_COMMA || e==CIDX_PARSE_CLOSE) && nParen==1 ){
|
|
const char *z2 = z;
|
|
if( pCol->zExpr ) goto parse_error;
|
|
|
|
if( bDoNotTrim==0 ){
|
|
while( cidx_isspace(z[-1]) ) z--;
|
|
if( !sqlite3_strnicmp(&z[-3], "asc", 3) && 0==cidx_isident(z[-4]) ){
|
|
z -= 3;
|
|
while( cidx_isspace(z[-1]) ) z--;
|
|
}else
|
|
if( !sqlite3_strnicmp(&z[-4], "desc", 4) && 0==cidx_isident(z[-5]) ){
|
|
z -= 4;
|
|
while( cidx_isspace(z[-1]) ) z--;
|
|
}
|
|
while( cidx_isspace(z1[0]) ) z1++;
|
|
}
|
|
|
|
pCol->zExpr = cidxMprintf(&rc, "%.*s", z-z1, z1);
|
|
pCol++;
|
|
z = z1 = z2+1;
|
|
}
|
|
if( e==CIDX_PARSE_OPEN ) nParen++;
|
|
if( e==CIDX_PARSE_CLOSE ) nParen--;
|
|
z++;
|
|
}
|
|
|
|
/* Search for a WHERE clause */
|
|
cidxFindNext(z, &z, 0);
|
|
if( 0==sqlite3_strnicmp(z, "where", 5) ){
|
|
pIdx->zWhere = cidxMprintf(&rc, "%s\n", &z[5]);
|
|
}else if( z[0]!='\0' ){
|
|
goto parse_error;
|
|
}
|
|
|
|
return rc;
|
|
|
|
parse_error:
|
|
cidxCursorError(pCsr, "Parse error in: %s", zSql);
|
|
return SQLITE_ERROR;
|
|
}
|
|
|
|
static int cidxLookupIndex(
|
|
CidxCursor *pCsr, /* Cursor object */
|
|
const char *zIdx, /* Name of index to look up */
|
|
CidxIndex **ppIdx, /* OUT: Description of columns */
|
|
char **pzTab /* OUT: Table name */
|
|
){
|
|
int rc = SQLITE_OK;
|
|
char *zTab = 0;
|
|
CidxIndex *pIdx = 0;
|
|
|
|
sqlite3_stmt *pFindTab = 0;
|
|
sqlite3_stmt *pInfo = 0;
|
|
|
|
/* Find the table for this index. */
|
|
pFindTab = cidxPrepare(&rc, pCsr,
|
|
"SELECT tbl_name, sql FROM sqlite_master WHERE name=%Q AND type='index'",
|
|
zIdx
|
|
);
|
|
if( rc==SQLITE_OK && sqlite3_step(pFindTab)==SQLITE_ROW ){
|
|
const char *zSql = (const char*)sqlite3_column_text(pFindTab, 1);
|
|
zTab = cidxStrdup(&rc, (const char*)sqlite3_column_text(pFindTab, 0));
|
|
|
|
pInfo = cidxPrepare(&rc, pCsr, "PRAGMA index_xinfo(%Q)", zIdx);
|
|
if( rc==SQLITE_OK ){
|
|
int nAlloc = 0;
|
|
int iCol = 0;
|
|
|
|
while( sqlite3_step(pInfo)==SQLITE_ROW ){
|
|
const char *zName = (const char*)sqlite3_column_text(pInfo, 2);
|
|
const char *zColl = (const char*)sqlite3_column_text(pInfo, 4);
|
|
CidxColumn *p;
|
|
if( zName==0 ) zName = "rowid";
|
|
if( iCol==nAlloc ){
|
|
int nByte = sizeof(CidxIndex) + sizeof(CidxColumn)*(nAlloc+8);
|
|
pIdx = (CidxIndex*)sqlite3_realloc(pIdx, nByte);
|
|
nAlloc += 8;
|
|
}
|
|
p = &pIdx->aCol[iCol++];
|
|
p->bDesc = sqlite3_column_int(pInfo, 3);
|
|
p->bKey = sqlite3_column_int(pInfo, 5);
|
|
if( zSql==0 || p->bKey==0 ){
|
|
p->zExpr = cidxMprintf(&rc, "\"%w\" COLLATE %s",zName,zColl);
|
|
}else{
|
|
p->zExpr = 0;
|
|
}
|
|
pIdx->nCol = iCol;
|
|
pIdx->zWhere = 0;
|
|
}
|
|
cidxFinalize(&rc, pInfo);
|
|
}
|
|
|
|
if( rc==SQLITE_OK && zSql ){
|
|
rc = cidxParseSQL(pCsr, pIdx, zSql);
|
|
}
|
|
}
|
|
|
|
cidxFinalize(&rc, pFindTab);
|
|
if( rc==SQLITE_OK && zTab==0 ){
|
|
rc = SQLITE_ERROR;
|
|
}
|
|
|
|
if( rc!=SQLITE_OK ){
|
|
sqlite3_free(zTab);
|
|
cidxFreeIndex(pIdx);
|
|
}else{
|
|
*pzTab = zTab;
|
|
*ppIdx = pIdx;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int cidxDecodeAfter(
|
|
CidxCursor *pCsr,
|
|
int nCol,
|
|
const char *zAfterKey,
|
|
char ***pazAfter
|
|
){
|
|
char **azAfter;
|
|
int rc = SQLITE_OK;
|
|
int nAfterKey = (int)strlen(zAfterKey);
|
|
|
|
azAfter = cidxMalloc(&rc, sizeof(char*)*nCol + nAfterKey+1);
|
|
if( rc==SQLITE_OK ){
|
|
int i;
|
|
char *zCopy = (char*)&azAfter[nCol];
|
|
char *p = zCopy;
|
|
memcpy(zCopy, zAfterKey, nAfterKey+1);
|
|
for(i=0; i<nCol; i++){
|
|
while( *p==' ' ) p++;
|
|
|
|
/* Check NULL values */
|
|
if( *p=='N' ){
|
|
if( memcmp(p, "NULL", 4) ) goto parse_error;
|
|
p += 4;
|
|
}
|
|
|
|
/* Check strings and blob literals */
|
|
else if( *p=='X' || *p=='\'' ){
|
|
azAfter[i] = p;
|
|
if( *p=='X' ) p++;
|
|
if( *p!='\'' ) goto parse_error;
|
|
p++;
|
|
while( 1 ){
|
|
if( *p=='\0' ) goto parse_error;
|
|
if( *p=='\'' ){
|
|
p++;
|
|
if( *p!='\'' ) break;
|
|
}
|
|
p++;
|
|
}
|
|
}
|
|
|
|
/* Check numbers */
|
|
else{
|
|
azAfter[i] = p;
|
|
while( (*p>='0' && *p<='9')
|
|
|| *p=='.' || *p=='+' || *p=='-' || *p=='e' || *p=='E'
|
|
){
|
|
p++;
|
|
}
|
|
}
|
|
|
|
while( *p==' ' ) p++;
|
|
if( *p!=(i==(nCol-1) ? '\0' : ',') ){
|
|
goto parse_error;
|
|
}
|
|
*p++ = '\0';
|
|
}
|
|
}
|
|
|
|
*pazAfter = azAfter;
|
|
return rc;
|
|
|
|
parse_error:
|
|
sqlite3_free(azAfter);
|
|
*pazAfter = 0;
|
|
cidxCursorError(pCsr, "%s", "error parsing after value");
|
|
return SQLITE_ERROR;
|
|
}
|
|
|
|
static char *cidxWhere(
|
|
int *pRc, CidxColumn *aCol, char **azAfter, int iGt, int bLastIsNull
|
|
){
|
|
char *zRet = 0;
|
|
const char *zSep = "";
|
|
int i;
|
|
|
|
for(i=0; i<iGt; i++){
|
|
zRet = cidxMprintf(pRc, "%z%s(%s) IS %s", zRet,
|
|
zSep, aCol[i].zExpr, (azAfter[i] ? azAfter[i] : "NULL")
|
|
);
|
|
zSep = " AND ";
|
|
}
|
|
|
|
if( bLastIsNull ){
|
|
zRet = cidxMprintf(pRc, "%z%s(%s) IS NULL", zRet, zSep, aCol[iGt].zExpr);
|
|
}
|
|
else if( azAfter[iGt] ){
|
|
zRet = cidxMprintf(pRc, "%z%s(%s) %s %s", zRet,
|
|
zSep, aCol[iGt].zExpr, (aCol[iGt].bDesc ? "<" : ">"),
|
|
azAfter[iGt]
|
|
);
|
|
}else{
|
|
zRet = cidxMprintf(pRc, "%z%s(%s) IS NOT NULL", zRet, zSep,aCol[iGt].zExpr);
|
|
}
|
|
|
|
return zRet;
|
|
}
|
|
|
|
#define CIDX_CLIST_ALL 0
|
|
#define CIDX_CLIST_ORDERBY 1
|
|
#define CIDX_CLIST_CURRENT_KEY 2
|
|
#define CIDX_CLIST_SUBWHERE 3
|
|
#define CIDX_CLIST_SUBEXPR 4
|
|
|
|
/*
|
|
** This function returns various strings based on the contents of the
|
|
** CidxIndex structure and the eType parameter.
|
|
*/
|
|
static char *cidxColumnList(
|
|
int *pRc, /* IN/OUT: Error code */
|
|
const char *zIdx,
|
|
CidxIndex *pIdx, /* Indexed columns */
|
|
int eType /* True to include ASC/DESC */
|
|
){
|
|
char *zRet = 0;
|
|
if( *pRc==SQLITE_OK ){
|
|
const char *aDir[2] = {"", " DESC"};
|
|
int i;
|
|
const char *zSep = "";
|
|
|
|
for(i=0; i<pIdx->nCol; i++){
|
|
CidxColumn *p = &pIdx->aCol[i];
|
|
assert( pIdx->aCol[i].bDesc==0 || pIdx->aCol[i].bDesc==1 );
|
|
switch( eType ){
|
|
|
|
case CIDX_CLIST_ORDERBY:
|
|
zRet = cidxMprintf(pRc, "%z%s%d%s", zRet, zSep, i+1, aDir[p->bDesc]);
|
|
zSep = ",";
|
|
break;
|
|
|
|
case CIDX_CLIST_CURRENT_KEY:
|
|
zRet = cidxMprintf(pRc, "%z%squote(i%d)", zRet, zSep, i);
|
|
zSep = "||','||";
|
|
break;
|
|
|
|
case CIDX_CLIST_SUBWHERE:
|
|
if( p->bKey==0 ){
|
|
zRet = cidxMprintf(pRc, "%z%s%s IS i.i%d", zRet,
|
|
zSep, p->zExpr, i
|
|
);
|
|
zSep = " AND ";
|
|
}
|
|
break;
|
|
|
|
case CIDX_CLIST_SUBEXPR:
|
|
if( p->bKey==1 ){
|
|
zRet = cidxMprintf(pRc, "%z%s%s IS i.i%d", zRet,
|
|
zSep, p->zExpr, i
|
|
);
|
|
zSep = " AND ";
|
|
}
|
|
break;
|
|
|
|
default:
|
|
assert( eType==CIDX_CLIST_ALL );
|
|
zRet = cidxMprintf(pRc, "%z%s(%s) AS i%d", zRet, zSep, p->zExpr, i);
|
|
zSep = ", ";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return zRet;
|
|
}
|
|
|
|
/*
|
|
** Generate SQL (in memory obtained from sqlite3_malloc()) that will
|
|
** continue the index scan for zIdxName starting after zAfterKey.
|
|
*/
|
|
int cidxGenerateScanSql(
|
|
CidxCursor *pCsr, /* The cursor which needs the new statement */
|
|
const char *zIdxName, /* index to be scanned */
|
|
const char *zAfterKey, /* start after this key, if not NULL */
|
|
char **pzSqlOut /* OUT: Write the generated SQL here */
|
|
){
|
|
int rc;
|
|
char *zTab = 0;
|
|
char *zCurrentKey = 0;
|
|
char *zOrderBy = 0;
|
|
char *zSubWhere = 0;
|
|
char *zSubExpr = 0;
|
|
char *zSrcList = 0;
|
|
char **azAfter = 0;
|
|
CidxIndex *pIdx = 0;
|
|
|
|
*pzSqlOut = 0;
|
|
rc = cidxLookupIndex(pCsr, zIdxName, &pIdx, &zTab);
|
|
|
|
zOrderBy = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_ORDERBY);
|
|
zCurrentKey = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_CURRENT_KEY);
|
|
zSubWhere = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_SUBWHERE);
|
|
zSubExpr = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_SUBEXPR);
|
|
zSrcList = cidxColumnList(&rc, zIdxName, pIdx, CIDX_CLIST_ALL);
|
|
|
|
if( rc==SQLITE_OK && zAfterKey ){
|
|
rc = cidxDecodeAfter(pCsr, pIdx->nCol, zAfterKey, &azAfter);
|
|
}
|
|
|
|
if( rc==SQLITE_OK ){
|
|
if( zAfterKey==0 ){
|
|
*pzSqlOut = cidxMprintf(&rc,
|
|
"SELECT (SELECT %s FROM %Q AS t WHERE %s), %s "
|
|
"FROM (SELECT %s FROM %Q INDEXED BY %Q %s%sORDER BY %s) AS i",
|
|
zSubExpr, zTab, zSubWhere, zCurrentKey,
|
|
zSrcList, zTab, zIdxName,
|
|
(pIdx->zWhere ? "WHERE " : ""), (pIdx->zWhere ? pIdx->zWhere : ""),
|
|
zOrderBy
|
|
);
|
|
}else{
|
|
const char *zSep = "";
|
|
char *zSql;
|
|
int i;
|
|
|
|
zSql = cidxMprintf(&rc,
|
|
"SELECT (SELECT %s FROM %Q WHERE %s), %s FROM (",
|
|
zSubExpr, zTab, zSubWhere, zCurrentKey
|
|
);
|
|
for(i=pIdx->nCol-1; i>=0; i--){
|
|
int j;
|
|
if( pIdx->aCol[i].bDesc && azAfter[i]==0 ) continue;
|
|
for(j=0; j<2; j++){
|
|
char *zWhere = cidxWhere(&rc, pIdx->aCol, azAfter, i, j);
|
|
zSql = cidxMprintf(&rc, "%z"
|
|
"%sSELECT * FROM ("
|
|
"SELECT %s FROM %Q INDEXED BY %Q WHERE %s%s%z ORDER BY %s"
|
|
")",
|
|
zSql, zSep, zSrcList, zTab, zIdxName,
|
|
pIdx->zWhere ? pIdx->zWhere : "",
|
|
pIdx->zWhere ? " AND " : "",
|
|
zWhere, zOrderBy
|
|
);
|
|
zSep = " UNION ALL ";
|
|
if( pIdx->aCol[i].bDesc==0 ) break;
|
|
}
|
|
}
|
|
*pzSqlOut = cidxMprintf(&rc, "%z) AS i", zSql);
|
|
}
|
|
}
|
|
|
|
sqlite3_free(zTab);
|
|
sqlite3_free(zCurrentKey);
|
|
sqlite3_free(zOrderBy);
|
|
sqlite3_free(zSubWhere);
|
|
sqlite3_free(zSubExpr);
|
|
sqlite3_free(zSrcList);
|
|
cidxFreeIndex(pIdx);
|
|
sqlite3_free(azAfter);
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*
|
|
** Position a cursor back to the beginning.
|
|
*/
|
|
static int cidxFilter(
|
|
sqlite3_vtab_cursor *pCursor,
|
|
int idxNum, const char *idxStr,
|
|
int argc, sqlite3_value **argv
|
|
){
|
|
int rc = SQLITE_OK;
|
|
CidxCursor *pCsr = (CidxCursor*)pCursor;
|
|
const char *zIdxName = 0;
|
|
const char *zAfterKey = 0;
|
|
|
|
sqlite3_free(pCsr->zIdxName);
|
|
pCsr->zIdxName = 0;
|
|
sqlite3_free(pCsr->zAfterKey);
|
|
pCsr->zAfterKey = 0;
|
|
sqlite3_finalize(pCsr->pStmt);
|
|
pCsr->pStmt = 0;
|
|
|
|
if( argc>0 ){
|
|
zIdxName = (const char*)sqlite3_value_text(argv[0]);
|
|
if( argc>1 ){
|
|
zAfterKey = (const char*)sqlite3_value_text(argv[1]);
|
|
}
|
|
}
|
|
|
|
if( zIdxName ){
|
|
char *zSql = 0;
|
|
pCsr->zIdxName = sqlite3_mprintf("%s", zIdxName);
|
|
pCsr->zAfterKey = zAfterKey ? sqlite3_mprintf("%s", zAfterKey) : 0;
|
|
rc = cidxGenerateScanSql(pCsr, zIdxName, zAfterKey, &zSql);
|
|
if( zSql ){
|
|
pCsr->pStmt = cidxPrepare(&rc, pCsr, "%z", zSql);
|
|
}
|
|
}
|
|
|
|
if( pCsr->pStmt ){
|
|
assert( rc==SQLITE_OK );
|
|
rc = cidxNext(pCursor);
|
|
}
|
|
pCsr->iRowid = 1;
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** Return a column value.
|
|
*/
|
|
static int cidxColumn(
|
|
sqlite3_vtab_cursor *pCursor,
|
|
sqlite3_context *ctx,
|
|
int iCol
|
|
){
|
|
CidxCursor *pCsr = (CidxCursor*)pCursor;
|
|
assert( iCol>=IIC_ERRMSG && iCol<=IIC_SCANNER_SQL );
|
|
switch( iCol ){
|
|
case IIC_ERRMSG: {
|
|
const char *zVal = 0;
|
|
if( sqlite3_column_type(pCsr->pStmt, 0)==SQLITE_INTEGER ){
|
|
if( sqlite3_column_int(pCsr->pStmt, 0)==0 ){
|
|
zVal = "row data mismatch";
|
|
}
|
|
}else{
|
|
zVal = "row missing";
|
|
}
|
|
sqlite3_result_text(ctx, zVal, -1, SQLITE_STATIC);
|
|
break;
|
|
}
|
|
case IIC_CURRENT_KEY: {
|
|
sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pStmt, 1));
|
|
break;
|
|
}
|
|
case IIC_INDEX_NAME: {
|
|
sqlite3_result_text(ctx, pCsr->zIdxName, -1, SQLITE_TRANSIENT);
|
|
break;
|
|
}
|
|
case IIC_AFTER_KEY: {
|
|
sqlite3_result_text(ctx, pCsr->zAfterKey, -1, SQLITE_TRANSIENT);
|
|
break;
|
|
}
|
|
case IIC_SCANNER_SQL: {
|
|
char *zSql = 0;
|
|
cidxGenerateScanSql(pCsr, pCsr->zIdxName, pCsr->zAfterKey, &zSql);
|
|
sqlite3_result_text(ctx, zSql, -1, sqlite3_free);
|
|
break;
|
|
}
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/* Return the ROWID for the sqlite_btreeinfo table */
|
|
static int cidxRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
|
|
CidxCursor *pCsr = (CidxCursor*)pCursor;
|
|
*pRowid = pCsr->iRowid;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Register the virtual table modules with the database handle passed
|
|
** as the only argument.
|
|
*/
|
|
static int ciInit(sqlite3 *db){
|
|
static sqlite3_module cidx_module = {
|
|
0, /* iVersion */
|
|
0, /* xCreate */
|
|
cidxConnect, /* xConnect */
|
|
cidxBestIndex, /* xBestIndex */
|
|
cidxDisconnect, /* xDisconnect */
|
|
0, /* xDestroy */
|
|
cidxOpen, /* xOpen - open a cursor */
|
|
cidxClose, /* xClose - close a cursor */
|
|
cidxFilter, /* xFilter - configure scan constraints */
|
|
cidxNext, /* xNext - advance a cursor */
|
|
cidxEof, /* xEof - check for end of scan */
|
|
cidxColumn, /* xColumn - read data */
|
|
cidxRowid, /* xRowid - read data */
|
|
0, /* xUpdate */
|
|
0, /* xBegin */
|
|
0, /* xSync */
|
|
0, /* xCommit */
|
|
0, /* xRollback */
|
|
0, /* xFindMethod */
|
|
0, /* xRename */
|
|
0, /* xSavepoint */
|
|
0, /* xRelease */
|
|
0, /* xRollbackTo */
|
|
};
|
|
return sqlite3_create_module(db, "incremental_index_check", &cidx_module, 0);
|
|
}
|
|
|
|
/*
|
|
** Extension load function.
|
|
*/
|
|
#ifdef _WIN32
|
|
__declspec(dllexport)
|
|
#endif
|
|
int sqlite3_checkindex_init(
|
|
sqlite3 *db,
|
|
char **pzErrMsg,
|
|
const sqlite3_api_routines *pApi
|
|
){
|
|
SQLITE_EXTENSION_INIT2(pApi);
|
|
return ciInit(db);
|
|
}
|