mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-27 20:41:58 +03:00
Merge the latest trunk changes into the sessions branch.
FossilOrigin-Name: 6883580e6c8973010a42d1d2c5bde04c6b2f4eb7
This commit is contained in:
152
ext/fts3/fts3.c
152
ext/fts3/fts3.c
@ -419,6 +419,34 @@ static void fts3GetDeltaVarint(char **pp, sqlite3_int64 *pVal){
|
||||
*pVal += iVal;
|
||||
}
|
||||
|
||||
/*
|
||||
** When this function is called, *pp points to the first byte following a
|
||||
** varint that is part of a doclist (or position-list, or any other list
|
||||
** of varints). This function moves *pp to point to the start of that varint,
|
||||
** and decrements the value stored in *pVal by the varint value.
|
||||
**
|
||||
** Argument pStart points to the first byte of the doclist that the
|
||||
** varint is part of.
|
||||
*/
|
||||
static void fts3GetReverseDeltaVarint(
|
||||
char **pp,
|
||||
char *pStart,
|
||||
sqlite3_int64 *pVal
|
||||
){
|
||||
sqlite3_int64 iVal;
|
||||
char *p = *pp;
|
||||
|
||||
/* Pointer p now points at the first byte past the varint we are
|
||||
** interested in. So, unless the doclist is corrupt, the 0x80 bit is
|
||||
** clear on character p[-1]. */
|
||||
for(p = (*pp)-2; p>=pStart && *p&0x80; p--);
|
||||
p++;
|
||||
*pp = p;
|
||||
|
||||
sqlite3Fts3GetVarint(p, &iVal);
|
||||
*pVal -= iVal;
|
||||
}
|
||||
|
||||
/*
|
||||
** As long as *pp has not reached its end (pEnd), then do the same
|
||||
** as fts3GetDeltaVarint(): read a single varint and add it to *pVal.
|
||||
@ -524,6 +552,8 @@ static void fts3DeclareVtab(int *pRc, Fts3Table *p){
|
||||
char *zSql; /* SQL statement passed to declare_vtab() */
|
||||
char *zCols; /* List of user defined columns */
|
||||
|
||||
sqlite3_vtab_config(p->db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
|
||||
|
||||
/* Create a list of user columns for the virtual table */
|
||||
zCols = sqlite3_mprintf("%Q, ", p->azColumn[0]);
|
||||
for(i=1; zCols && i<p->nColumn; i++){
|
||||
@ -1092,6 +1122,22 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
|
||||
pInfo->aConstraintUsage[iCons].argvIndex = 1;
|
||||
pInfo->aConstraintUsage[iCons].omit = 1;
|
||||
}
|
||||
|
||||
/* Regardless of the strategy selected, FTS can deliver rows in rowid (or
|
||||
** docid) order. Both ascending and descending are possible.
|
||||
*/
|
||||
if( pInfo->nOrderBy==1 ){
|
||||
struct sqlite3_index_orderby *pOrder = &pInfo->aOrderBy[0];
|
||||
if( pOrder->iColumn<0 || pOrder->iColumn==p->nColumn+1 ){
|
||||
if( pOrder->desc ){
|
||||
pInfo->idxStr = "DESC";
|
||||
}else{
|
||||
pInfo->idxStr = "ASC";
|
||||
}
|
||||
}
|
||||
pInfo->orderByConsumed = 1;
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
@ -2996,12 +3042,20 @@ static int fts3NextMethod(sqlite3_vtab_cursor *pCursor){
|
||||
}
|
||||
pCsr->iPrevId = sqlite3_column_int64(pCsr->pStmt, 0);
|
||||
}else{
|
||||
if( pCsr->pNextId>=&pCsr->aDoclist[pCsr->nDoclist] ){
|
||||
pCsr->isEof = 1;
|
||||
break;
|
||||
if( pCsr->desc==0 ){
|
||||
if( pCsr->pNextId>=&pCsr->aDoclist[pCsr->nDoclist] ){
|
||||
pCsr->isEof = 1;
|
||||
break;
|
||||
}
|
||||
fts3GetDeltaVarint(&pCsr->pNextId, &pCsr->iPrevId);
|
||||
}else{
|
||||
fts3GetReverseDeltaVarint(&pCsr->pNextId,pCsr->aDoclist,&pCsr->iPrevId);
|
||||
if( pCsr->pNextId<=pCsr->aDoclist ){
|
||||
pCsr->isEof = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
sqlite3_reset(pCsr->pStmt);
|
||||
fts3GetDeltaVarint(&pCsr->pNextId, &pCsr->iPrevId);
|
||||
pCsr->isRequireSeek = 1;
|
||||
pCsr->isMatchinfoNeeded = 1;
|
||||
}
|
||||
@ -3034,8 +3088,8 @@ static int fts3FilterMethod(
|
||||
sqlite3_value **apVal /* Arguments for the indexing scheme */
|
||||
){
|
||||
const char *azSql[] = {
|
||||
"SELECT %s FROM %Q.'%q_content' AS x WHERE docid = ?", /* non-full-scan */
|
||||
"SELECT %s FROM %Q.'%q_content' AS x ", /* full-scan */
|
||||
"SELECT %s FROM %Q.'%q_content' AS x WHERE docid = ?", /* non-full-scan */
|
||||
"SELECT %s FROM %Q.'%q_content' AS x ORDER BY docid %s", /* full-scan */
|
||||
};
|
||||
int rc; /* Return code */
|
||||
char *zSql; /* SQL statement used to access %_content */
|
||||
@ -3091,7 +3145,9 @@ static int fts3FilterMethod(
|
||||
** row by docid.
|
||||
*/
|
||||
zSql = (char *)azSql[idxNum==FTS3_FULLSCAN_SEARCH];
|
||||
zSql = sqlite3_mprintf(zSql, p->zReadExprlist, p->zDb, p->zName);
|
||||
zSql = sqlite3_mprintf(
|
||||
zSql, p->zReadExprlist, p->zDb, p->zName, (idxStr ? idxStr : "ASC")
|
||||
);
|
||||
if( !zSql ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
@ -3103,7 +3159,22 @@ static int fts3FilterMethod(
|
||||
}
|
||||
pCsr->eSearch = (i16)idxNum;
|
||||
|
||||
assert( pCsr->desc==0 );
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
if( rc==SQLITE_OK && pCsr->nDoclist>0 && idxStr && idxStr[0]=='D' ){
|
||||
sqlite3_int64 iDocid = 0;
|
||||
char *csr = pCsr->aDoclist;
|
||||
while( csr<&pCsr->aDoclist[pCsr->nDoclist] ){
|
||||
fts3GetDeltaVarint(&csr, &iDocid);
|
||||
}
|
||||
pCsr->pNextId = csr;
|
||||
pCsr->iPrevId = iDocid;
|
||||
pCsr->desc = 1;
|
||||
pCsr->isRequireSeek = 1;
|
||||
pCsr->isMatchinfoNeeded = 1;
|
||||
pCsr->eEvalmode = FTS3_EVAL_NEXT;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
return fts3NextMethod(pCursor);
|
||||
}
|
||||
|
||||
@ -3256,12 +3327,32 @@ int sqlite3Fts3ExprLoadFtDoclist(
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** When called, *ppPoslist must point to the byte immediately following the
|
||||
** end of a position-list. i.e. ( (*ppPoslist)[-1]==POS_END ). This function
|
||||
** moves *ppPoslist so that it instead points to the first byte of the
|
||||
** same position list.
|
||||
*/
|
||||
static void fts3ReversePoslist(char *pStart, char **ppPoslist){
|
||||
char *p = &(*ppPoslist)[-3];
|
||||
char c = p[1];
|
||||
while( p>pStart && (*p & 0x80) | c ){
|
||||
c = *p--;
|
||||
}
|
||||
if( p>pStart ){ p = &p[2]; }
|
||||
while( *p++&0x80 );
|
||||
*ppPoslist = p;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** After ExprLoadDoclist() (see above) has been called, this function is
|
||||
** used to iterate/search through the position lists that make up the doclist
|
||||
** stored in pExpr->aDoclist.
|
||||
*/
|
||||
char *sqlite3Fts3FindPositions(
|
||||
Fts3Cursor *pCursor, /* Associate FTS3 cursor */
|
||||
Fts3Expr *pExpr, /* Access this expressions doclist */
|
||||
sqlite3_int64 iDocid, /* Docid associated with requested pos-list */
|
||||
int iCol /* Column of requested pos-list */
|
||||
@ -3272,20 +3363,36 @@ char *sqlite3Fts3FindPositions(
|
||||
char *pCsr;
|
||||
|
||||
if( pExpr->pCurrent==0 ){
|
||||
pExpr->pCurrent = pExpr->aDoclist;
|
||||
pExpr->iCurrent = 0;
|
||||
pExpr->pCurrent += sqlite3Fts3GetVarint(pExpr->pCurrent,&pExpr->iCurrent);
|
||||
if( pCursor->desc==0 ){
|
||||
pExpr->pCurrent = pExpr->aDoclist;
|
||||
pExpr->iCurrent = 0;
|
||||
fts3GetDeltaVarint(&pExpr->pCurrent, &pExpr->iCurrent);
|
||||
}else{
|
||||
pCsr = pExpr->aDoclist;
|
||||
while( pCsr<pEnd ){
|
||||
fts3GetDeltaVarint(&pCsr, &pExpr->iCurrent);
|
||||
fts3PoslistCopy(0, &pCsr);
|
||||
}
|
||||
fts3ReversePoslist(pExpr->aDoclist, &pCsr);
|
||||
pExpr->pCurrent = pCsr;
|
||||
}
|
||||
}
|
||||
pCsr = pExpr->pCurrent;
|
||||
assert( pCsr );
|
||||
|
||||
while( pCsr<pEnd ){
|
||||
if( pExpr->iCurrent<iDocid ){
|
||||
while( (pCursor->desc==0 && pCsr<pEnd)
|
||||
|| (pCursor->desc && pCsr>pExpr->aDoclist)
|
||||
){
|
||||
if( pCursor->desc==0 && pExpr->iCurrent<iDocid ){
|
||||
fts3PoslistCopy(0, &pCsr);
|
||||
if( pCsr<pEnd ){
|
||||
fts3GetDeltaVarint(&pCsr, &pExpr->iCurrent);
|
||||
}
|
||||
pExpr->pCurrent = pCsr;
|
||||
}else if( pCursor->desc && pExpr->iCurrent>iDocid ){
|
||||
fts3GetReverseDeltaVarint(&pCsr, pExpr->aDoclist, &pExpr->iCurrent);
|
||||
fts3ReversePoslist(pExpr->aDoclist, &pCsr);
|
||||
pExpr->pCurrent = pCsr;
|
||||
}else{
|
||||
if( pExpr->iCurrent==iDocid ){
|
||||
int iThis = 0;
|
||||
@ -3542,8 +3649,19 @@ static int fts3RenameMethod(
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
|
||||
return sqlite3Fts3PendingTermsFlush((Fts3Table *)pVtab);
|
||||
}
|
||||
static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
|
||||
sqlite3Fts3PendingTermsClear((Fts3Table *)pVtab);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static const sqlite3_module fts3Module = {
|
||||
/* iVersion */ 0,
|
||||
/* iVersion */ 1,
|
||||
/* xCreate */ fts3CreateMethod,
|
||||
/* xConnect */ fts3ConnectMethod,
|
||||
/* xBestIndex */ fts3BestIndexMethod,
|
||||
@ -3563,6 +3681,9 @@ static const sqlite3_module fts3Module = {
|
||||
/* xRollback */ fts3RollbackMethod,
|
||||
/* xFindFunction */ fts3FindFunctionMethod,
|
||||
/* xRename */ fts3RenameMethod,
|
||||
/* xSavepoint */ fts3SavepointMethod,
|
||||
/* xRelease */ fts3ReleaseMethod,
|
||||
/* xRollbackTo */ fts3RollbackToMethod,
|
||||
};
|
||||
|
||||
/*
|
||||
@ -3609,6 +3730,11 @@ int sqlite3Fts3Init(sqlite3 *db){
|
||||
sqlite3Fts3IcuTokenizerModule(&pIcu);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
rc = sqlite3Fts3InitTerm(db);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
#endif
|
||||
|
||||
rc = sqlite3Fts3InitAux(db);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
|
@ -171,6 +171,7 @@ struct Fts3Cursor {
|
||||
char *pNextId; /* Pointer into the body of aDoclist */
|
||||
char *aDoclist; /* List of docids for full-text queries */
|
||||
int nDoclist; /* Size of buffer at aDoclist */
|
||||
int desc; /* True to sort in descending order */
|
||||
int eEvalmode; /* An FTS3_EVAL_XX constant */
|
||||
int nRowAvg; /* Average size of database rows, in pages */
|
||||
|
||||
@ -353,7 +354,7 @@ int sqlite3Fts3GetVarint32(const char *, int *);
|
||||
int sqlite3Fts3VarintLen(sqlite3_uint64);
|
||||
void sqlite3Fts3Dequote(char *);
|
||||
|
||||
char *sqlite3Fts3FindPositions(Fts3Expr *, sqlite3_int64, int);
|
||||
char *sqlite3Fts3FindPositions(Fts3Cursor *, Fts3Expr *, sqlite3_int64, int);
|
||||
int sqlite3Fts3ExprLoadDoclist(Fts3Cursor *, Fts3Expr *);
|
||||
int sqlite3Fts3ExprLoadFtDoclist(Fts3Cursor *, Fts3Expr *, char **, int *);
|
||||
int sqlite3Fts3ExprNearTrim(Fts3Expr *, Fts3Expr *, int);
|
||||
|
@ -415,7 +415,7 @@ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){
|
||||
|
||||
pPhrase->nToken = pExpr->pPhrase->nToken;
|
||||
|
||||
pCsr = sqlite3Fts3FindPositions(pExpr, p->pCsr->iPrevId, p->iCol);
|
||||
pCsr = sqlite3Fts3FindPositions(p->pCsr, pExpr, p->pCsr->iPrevId, p->iCol);
|
||||
if( pCsr ){
|
||||
int iFirst = 0;
|
||||
pPhrase->pList = pCsr;
|
||||
@ -888,7 +888,7 @@ static int fts3ExprLocalHitsCb(
|
||||
if( pExpr->aDoclist ){
|
||||
char *pCsr;
|
||||
|
||||
pCsr = sqlite3Fts3FindPositions(pExpr, p->pCursor->iPrevId, -1);
|
||||
pCsr = sqlite3Fts3FindPositions(p->pCursor, pExpr, p->pCursor->iPrevId, -1);
|
||||
if( pCsr ){
|
||||
fts3LoadColumnlistCounts(&pCsr, &p->aMatchinfo[iStart], 0);
|
||||
}
|
||||
@ -1055,7 +1055,7 @@ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){
|
||||
LcsIterator *pIter = &aIter[i];
|
||||
nToken -= pIter->pExpr->pPhrase->nToken;
|
||||
pIter->iPosOffset = nToken;
|
||||
pIter->pRead = sqlite3Fts3FindPositions(pIter->pExpr, pCsr->iPrevId, -1);
|
||||
pIter->pRead = sqlite3Fts3FindPositions(pCsr,pIter->pExpr,pCsr->iPrevId,-1);
|
||||
if( pIter->pRead ){
|
||||
pIter->iPos = pIter->iPosOffset;
|
||||
fts3LcsIteratorAdvance(&aIter[i]);
|
||||
@ -1408,6 +1408,7 @@ struct TermOffset {
|
||||
};
|
||||
|
||||
struct TermOffsetCtx {
|
||||
Fts3Cursor *pCsr;
|
||||
int iCol; /* Column of table to populate aTerm for */
|
||||
int iTerm;
|
||||
sqlite3_int64 iDocid;
|
||||
@ -1425,7 +1426,7 @@ static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){
|
||||
int iPos = 0; /* First position in position-list */
|
||||
|
||||
UNUSED_PARAMETER(iPhrase);
|
||||
pList = sqlite3Fts3FindPositions(pExpr, p->iDocid, p->iCol);
|
||||
pList = sqlite3Fts3FindPositions(p->pCsr, pExpr, p->iDocid, p->iCol);
|
||||
nTerm = pExpr->pPhrase->nToken;
|
||||
if( pList ){
|
||||
fts3GetDeltaPosition(&pList, &iPos);
|
||||
@ -1478,6 +1479,7 @@ void sqlite3Fts3Offsets(
|
||||
goto offsets_out;
|
||||
}
|
||||
sCtx.iDocid = pCsr->iPrevId;
|
||||
sCtx.pCsr = pCsr;
|
||||
|
||||
/* Loop through the table columns, appending offset information to
|
||||
** string-buffer res for each column.
|
||||
|
360
ext/fts3/fts3_term.c
Normal file
360
ext/fts3/fts3_term.c
Normal file
@ -0,0 +1,360 @@
|
||||
/*
|
||||
** 2011 Jan 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.
|
||||
**
|
||||
******************************************************************************
|
||||
**
|
||||
** This file is not part of the production FTS code. It is only used for
|
||||
** testing. It contains a virtual table implementation that provides direct
|
||||
** access to the full-text index of an FTS table.
|
||||
*/
|
||||
|
||||
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
|
||||
#ifdef SQLITE_TEST
|
||||
|
||||
#include "fts3Int.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
typedef struct Fts3termTable Fts3termTable;
|
||||
typedef struct Fts3termCursor Fts3termCursor;
|
||||
|
||||
struct Fts3termTable {
|
||||
sqlite3_vtab base; /* Base class used by SQLite core */
|
||||
Fts3Table *pFts3Tab;
|
||||
};
|
||||
|
||||
struct Fts3termCursor {
|
||||
sqlite3_vtab_cursor base; /* Base class used by SQLite core */
|
||||
Fts3SegReaderCursor csr; /* Must be right after "base" */
|
||||
Fts3SegFilter filter;
|
||||
|
||||
int isEof; /* True if cursor is at EOF */
|
||||
char *pNext;
|
||||
|
||||
sqlite3_int64 iRowid; /* Current 'rowid' value */
|
||||
sqlite3_int64 iDocid; /* Current 'docid' value */
|
||||
int iCol; /* Current 'col' value */
|
||||
int iPos; /* Current 'pos' value */
|
||||
};
|
||||
|
||||
/*
|
||||
** Schema of the terms table.
|
||||
*/
|
||||
#define FTS3_TERMS_SCHEMA "CREATE TABLE x(term, docid, col, pos)"
|
||||
|
||||
/*
|
||||
** This function does all the work for both the xConnect and xCreate methods.
|
||||
** These tables have no persistent representation of their own, so xConnect
|
||||
** and xCreate are identical operations.
|
||||
*/
|
||||
static int fts3termConnectMethod(
|
||||
sqlite3 *db, /* Database connection */
|
||||
void *pUnused, /* Unused */
|
||||
int argc, /* Number of elements in argv array */
|
||||
const char * const *argv, /* xCreate/xConnect argument array */
|
||||
sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
|
||||
char **pzErr /* OUT: sqlite3_malloc'd error message */
|
||||
){
|
||||
char const *zDb; /* Name of database (e.g. "main") */
|
||||
char const *zFts3; /* Name of fts3 table */
|
||||
int nDb; /* Result of strlen(zDb) */
|
||||
int nFts3; /* Result of strlen(zFts3) */
|
||||
int nByte; /* Bytes of space to allocate here */
|
||||
int rc; /* value returned by declare_vtab() */
|
||||
Fts3termTable *p; /* Virtual table object to return */
|
||||
|
||||
UNUSED_PARAMETER(pUnused);
|
||||
|
||||
/* The user should specify a single argument - the name of an fts3 table. */
|
||||
if( argc!=4 ){
|
||||
*pzErr = sqlite3_mprintf(
|
||||
"wrong number of arguments to fts4term constructor"
|
||||
);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
zDb = argv[1];
|
||||
nDb = strlen(zDb);
|
||||
zFts3 = argv[3];
|
||||
nFts3 = strlen(zFts3);
|
||||
|
||||
rc = sqlite3_declare_vtab(db, FTS3_TERMS_SCHEMA);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
nByte = sizeof(Fts3termTable) + sizeof(Fts3Table) + nDb + nFts3 + 2;
|
||||
p = (Fts3termTable *)sqlite3_malloc(nByte);
|
||||
if( !p ) return SQLITE_NOMEM;
|
||||
memset(p, 0, nByte);
|
||||
|
||||
p->pFts3Tab = (Fts3Table *)&p[1];
|
||||
p->pFts3Tab->zDb = (char *)&p->pFts3Tab[1];
|
||||
p->pFts3Tab->zName = &p->pFts3Tab->zDb[nDb+1];
|
||||
p->pFts3Tab->db = db;
|
||||
|
||||
memcpy((char *)p->pFts3Tab->zDb, zDb, nDb);
|
||||
memcpy((char *)p->pFts3Tab->zName, zFts3, nFts3);
|
||||
sqlite3Fts3Dequote((char *)p->pFts3Tab->zName);
|
||||
|
||||
*ppVtab = (sqlite3_vtab *)p;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function does the work for both the xDisconnect and xDestroy methods.
|
||||
** These tables have no persistent representation of their own, so xDisconnect
|
||||
** and xDestroy are identical operations.
|
||||
*/
|
||||
static int fts3termDisconnectMethod(sqlite3_vtab *pVtab){
|
||||
Fts3termTable *p = (Fts3termTable *)pVtab;
|
||||
Fts3Table *pFts3 = p->pFts3Tab;
|
||||
int i;
|
||||
|
||||
/* Free any prepared statements held */
|
||||
for(i=0; i<SizeofArray(pFts3->aStmt); i++){
|
||||
sqlite3_finalize(pFts3->aStmt[i]);
|
||||
}
|
||||
sqlite3_free(pFts3->zSegmentsTbl);
|
||||
sqlite3_free(p);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
#define FTS4AUX_EQ_CONSTRAINT 1
|
||||
#define FTS4AUX_GE_CONSTRAINT 2
|
||||
#define FTS4AUX_LE_CONSTRAINT 4
|
||||
|
||||
/*
|
||||
** xBestIndex - Analyze a WHERE and ORDER BY clause.
|
||||
*/
|
||||
static int fts3termBestIndexMethod(
|
||||
sqlite3_vtab *pVTab,
|
||||
sqlite3_index_info *pInfo
|
||||
){
|
||||
UNUSED_PARAMETER(pVTab);
|
||||
|
||||
/* This vtab naturally does "ORDER BY term, docid, col, pos". */
|
||||
if( pInfo->nOrderBy ){
|
||||
int i;
|
||||
for(i=0; i<pInfo->nOrderBy; i++){
|
||||
if( pInfo->aOrderBy[i].iColumn!=i || pInfo->aOrderBy[i].desc ) break;
|
||||
}
|
||||
if( i==pInfo->nOrderBy ){
|
||||
pInfo->orderByConsumed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xOpen - Open a cursor.
|
||||
*/
|
||||
static int fts3termOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
|
||||
Fts3termCursor *pCsr; /* Pointer to cursor object to return */
|
||||
|
||||
UNUSED_PARAMETER(pVTab);
|
||||
|
||||
pCsr = (Fts3termCursor *)sqlite3_malloc(sizeof(Fts3termCursor));
|
||||
if( !pCsr ) return SQLITE_NOMEM;
|
||||
memset(pCsr, 0, sizeof(Fts3termCursor));
|
||||
|
||||
*ppCsr = (sqlite3_vtab_cursor *)pCsr;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xClose - Close a cursor.
|
||||
*/
|
||||
static int fts3termCloseMethod(sqlite3_vtab_cursor *pCursor){
|
||||
Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
|
||||
Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
|
||||
|
||||
sqlite3Fts3SegmentsClose(pFts3);
|
||||
sqlite3Fts3SegReaderFinish(&pCsr->csr);
|
||||
sqlite3_free(pCsr);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xNext - Advance the cursor to the next row, if any.
|
||||
*/
|
||||
static int fts3termNextMethod(sqlite3_vtab_cursor *pCursor){
|
||||
Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
|
||||
Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
|
||||
int rc;
|
||||
sqlite3_int64 v;
|
||||
|
||||
/* Increment our pretend rowid value. */
|
||||
pCsr->iRowid++;
|
||||
|
||||
/* Advance to the next term in the full-text index. */
|
||||
if( pCsr->csr.aDoclist==0
|
||||
|| pCsr->pNext>=&pCsr->csr.aDoclist[pCsr->csr.nDoclist-1]
|
||||
){
|
||||
rc = sqlite3Fts3SegReaderStep(pFts3, &pCsr->csr);
|
||||
if( rc!=SQLITE_ROW ){
|
||||
pCsr->isEof = 1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
pCsr->iCol = 0;
|
||||
pCsr->iPos = 0;
|
||||
pCsr->iDocid = 0;
|
||||
pCsr->pNext = pCsr->csr.aDoclist;
|
||||
|
||||
/* Read docid */
|
||||
pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &pCsr->iDocid);
|
||||
}
|
||||
|
||||
pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
|
||||
if( v==0 ){
|
||||
pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
|
||||
pCsr->iDocid += v;
|
||||
pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
|
||||
pCsr->iCol = 0;
|
||||
pCsr->iPos = 0;
|
||||
}
|
||||
|
||||
if( v==1 ){
|
||||
pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
|
||||
pCsr->iCol += v;
|
||||
pCsr->iPos = 0;
|
||||
pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
|
||||
}
|
||||
|
||||
pCsr->iPos += (v - 2);
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xFilter - Initialize a cursor to point at the start of its data.
|
||||
*/
|
||||
static int fts3termFilterMethod(
|
||||
sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
|
||||
int idxNum, /* Strategy index */
|
||||
const char *idxStr, /* Unused */
|
||||
int nVal, /* Number of elements in apVal */
|
||||
sqlite3_value **apVal /* Arguments for the indexing scheme */
|
||||
){
|
||||
Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
|
||||
Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
|
||||
int rc;
|
||||
|
||||
UNUSED_PARAMETER(nVal);
|
||||
UNUSED_PARAMETER(idxNum);
|
||||
UNUSED_PARAMETER(idxStr);
|
||||
UNUSED_PARAMETER(apVal);
|
||||
|
||||
assert( idxStr==0 && idxNum==0 );
|
||||
|
||||
/* In case this cursor is being reused, close and zero it. */
|
||||
testcase(pCsr->filter.zTerm);
|
||||
sqlite3Fts3SegReaderFinish(&pCsr->csr);
|
||||
memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr);
|
||||
|
||||
pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY;
|
||||
pCsr->filter.flags |= FTS3_SEGMENT_SCAN;
|
||||
|
||||
rc = sqlite3Fts3SegReaderCursor(pFts3, FTS3_SEGCURSOR_ALL,
|
||||
pCsr->filter.zTerm, pCsr->filter.nTerm, 0, 1, &pCsr->csr
|
||||
);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = fts3termNextMethod(pCursor);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** xEof - Return true if the cursor is at EOF, or false otherwise.
|
||||
*/
|
||||
static int fts3termEofMethod(sqlite3_vtab_cursor *pCursor){
|
||||
Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
|
||||
return pCsr->isEof;
|
||||
}
|
||||
|
||||
/*
|
||||
** xColumn - Return a column value.
|
||||
*/
|
||||
static int fts3termColumnMethod(
|
||||
sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
|
||||
sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */
|
||||
int iCol /* Index of column to read value from */
|
||||
){
|
||||
Fts3termCursor *p = (Fts3termCursor *)pCursor;
|
||||
|
||||
assert( iCol>=0 && iCol<=3 );
|
||||
switch( iCol ){
|
||||
case 0:
|
||||
sqlite3_result_text(pCtx, p->csr.zTerm, p->csr.nTerm, SQLITE_TRANSIENT);
|
||||
break;
|
||||
case 1:
|
||||
sqlite3_result_int64(pCtx, p->iDocid);
|
||||
break;
|
||||
case 2:
|
||||
sqlite3_result_int64(pCtx, p->iCol);
|
||||
break;
|
||||
default:
|
||||
sqlite3_result_int64(pCtx, p->iPos);
|
||||
break;
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** xRowid - Return the current rowid for the cursor.
|
||||
*/
|
||||
static int fts3termRowidMethod(
|
||||
sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
|
||||
sqlite_int64 *pRowid /* OUT: Rowid value */
|
||||
){
|
||||
Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
|
||||
*pRowid = pCsr->iRowid;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Register the fts3term module with database connection db. Return SQLITE_OK
|
||||
** if successful or an error code if sqlite3_create_module() fails.
|
||||
*/
|
||||
int sqlite3Fts3InitTerm(sqlite3 *db){
|
||||
static const sqlite3_module fts3term_module = {
|
||||
0, /* iVersion */
|
||||
fts3termConnectMethod, /* xCreate */
|
||||
fts3termConnectMethod, /* xConnect */
|
||||
fts3termBestIndexMethod, /* xBestIndex */
|
||||
fts3termDisconnectMethod, /* xDisconnect */
|
||||
fts3termDisconnectMethod, /* xDestroy */
|
||||
fts3termOpenMethod, /* xOpen */
|
||||
fts3termCloseMethod, /* xClose */
|
||||
fts3termFilterMethod, /* xFilter */
|
||||
fts3termNextMethod, /* xNext */
|
||||
fts3termEofMethod, /* xEof */
|
||||
fts3termColumnMethod, /* xColumn */
|
||||
fts3termRowidMethod, /* xRowid */
|
||||
0, /* xUpdate */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindFunction */
|
||||
0 /* xRename */
|
||||
};
|
||||
int rc; /* Return code */
|
||||
|
||||
rc = sqlite3_create_module(db, "fts4term", &fts3term_module, 0);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
|
@ -542,6 +542,14 @@ static int fts3PendingTermsAdd(
|
||||
|
||||
assert( pTokenizer && pModule );
|
||||
|
||||
/* If the user has inserted a NULL value, this function may be called with
|
||||
** zText==0. In this case, add zero token entries to the hash table and
|
||||
** return early. */
|
||||
if( zText==0 ){
|
||||
*pnWord = 0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
rc = pModule->xOpen(pTokenizer, zText, -1, &pCsr);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
@ -632,11 +640,9 @@ static int fts3InsertTerms(Fts3Table *p, sqlite3_value **apVal, u32 *aSz){
|
||||
int i; /* Iterator variable */
|
||||
for(i=2; i<p->nColumn+2; i++){
|
||||
const char *zText = (const char *)sqlite3_value_text(apVal[i]);
|
||||
if( zText ){
|
||||
int rc = fts3PendingTermsAdd(p, zText, i-2, &aSz[i-2]);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
int rc = fts3PendingTermsAdd(p, zText, i-2, &aSz[i-2]);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
aSz[p->nColumn] += sqlite3_value_bytes(apVal[i]);
|
||||
}
|
||||
@ -741,14 +747,14 @@ static int fts3DeleteAll(Fts3Table *p){
|
||||
static void fts3DeleteTerms(
|
||||
int *pRC, /* Result code */
|
||||
Fts3Table *p, /* The FTS table to delete from */
|
||||
sqlite3_value **apVal, /* apVal[] contains the docid to be deleted */
|
||||
sqlite3_value *pRowid, /* The docid to be deleted */
|
||||
u32 *aSz /* Sizes of deleted document written here */
|
||||
){
|
||||
int rc;
|
||||
sqlite3_stmt *pSelect;
|
||||
|
||||
if( *pRC ) return;
|
||||
rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pSelect, apVal);
|
||||
rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pSelect, &pRowid);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( SQLITE_ROW==sqlite3_step(pSelect) ){
|
||||
int i;
|
||||
@ -1888,16 +1894,16 @@ static void fts3SegWriterFree(SegmentWriter *pWriter){
|
||||
** The first value in the apVal[] array is assumed to contain an integer.
|
||||
** This function tests if there exist any documents with docid values that
|
||||
** are different from that integer. i.e. if deleting the document with docid
|
||||
** apVal[0] would mean the FTS3 table were empty.
|
||||
** pRowid would mean the FTS3 table were empty.
|
||||
**
|
||||
** If successful, *pisEmpty is set to true if the table is empty except for
|
||||
** document apVal[0], or false otherwise, and SQLITE_OK is returned. If an
|
||||
** document pRowid, or false otherwise, and SQLITE_OK is returned. If an
|
||||
** error occurs, an SQLite error code is returned.
|
||||
*/
|
||||
static int fts3IsEmpty(Fts3Table *p, sqlite3_value **apVal, int *pisEmpty){
|
||||
static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){
|
||||
sqlite3_stmt *pStmt;
|
||||
int rc;
|
||||
rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, apVal);
|
||||
rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( SQLITE_ROW==sqlite3_step(pStmt) ){
|
||||
*pisEmpty = sqlite3_column_int(pStmt, 0);
|
||||
@ -2621,6 +2627,40 @@ int sqlite3Fts3DeferToken(
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** SQLite value pRowid contains the rowid of a row that may or may not be
|
||||
** present in the FTS3 table. If it is, delete it and adjust the contents
|
||||
** of subsiduary data structures accordingly.
|
||||
*/
|
||||
static int fts3DeleteByRowid(
|
||||
Fts3Table *p,
|
||||
sqlite3_value *pRowid,
|
||||
int *pnDoc,
|
||||
u32 *aSzDel
|
||||
){
|
||||
int isEmpty = 0;
|
||||
int rc = fts3IsEmpty(p, pRowid, &isEmpty);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( isEmpty ){
|
||||
/* Deleting this row means the whole table is empty. In this case
|
||||
** delete the contents of all three tables and throw away any
|
||||
** data in the pendingTerms hash table. */
|
||||
rc = fts3DeleteAll(p);
|
||||
*pnDoc = *pnDoc - 1;
|
||||
}else{
|
||||
sqlite3_int64 iRemove = sqlite3_value_int64(pRowid);
|
||||
rc = fts3PendingTermsDocid(p, iRemove);
|
||||
fts3DeleteTerms(&rc, p, pRowid, aSzDel);
|
||||
fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, &pRowid);
|
||||
if( sqlite3_changes(p->db) ) *pnDoc = *pnDoc - 1;
|
||||
if( p->bHasDocsize ){
|
||||
fts3SqlExec(&rc, p, SQL_DELETE_DOCSIZE, &pRowid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function does the work for the xUpdate method of FTS3 virtual
|
||||
@ -2639,46 +2679,91 @@ int sqlite3Fts3UpdateMethod(
|
||||
u32 *aSzIns; /* Sizes of inserted documents */
|
||||
u32 *aSzDel; /* Sizes of deleted documents */
|
||||
int nChng = 0; /* Net change in number of documents */
|
||||
int bInsertDone = 0;
|
||||
|
||||
assert( p->pSegments==0 );
|
||||
|
||||
/* Check for a "special" INSERT operation. One of the form:
|
||||
**
|
||||
** INSERT INTO xyz(xyz) VALUES('command');
|
||||
*/
|
||||
if( nArg>1
|
||||
&& sqlite3_value_type(apVal[0])==SQLITE_NULL
|
||||
&& sqlite3_value_type(apVal[p->nColumn+2])!=SQLITE_NULL
|
||||
){
|
||||
return fts3SpecialInsert(p, apVal[p->nColumn+2]);
|
||||
}
|
||||
|
||||
/* Allocate space to hold the change in document sizes */
|
||||
aSzIns = sqlite3_malloc( sizeof(aSzIns[0])*(p->nColumn+1)*2 );
|
||||
if( aSzIns==0 ) return SQLITE_NOMEM;
|
||||
aSzDel = &aSzIns[p->nColumn+1];
|
||||
memset(aSzIns, 0, sizeof(aSzIns[0])*(p->nColumn+1)*2);
|
||||
|
||||
/* If this is a DELETE or UPDATE operation, remove the old record. */
|
||||
if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
|
||||
int isEmpty = 0;
|
||||
rc = fts3IsEmpty(p, apVal, &isEmpty);
|
||||
if( rc==SQLITE_OK ){
|
||||
if( isEmpty ){
|
||||
/* Deleting this row means the whole table is empty. In this case
|
||||
** delete the contents of all three tables and throw away any
|
||||
** data in the pendingTerms hash table.
|
||||
*/
|
||||
rc = fts3DeleteAll(p);
|
||||
/* If this is an INSERT operation, or an UPDATE that modifies the rowid
|
||||
** value, then this operation requires constraint handling.
|
||||
**
|
||||
** If the on-conflict mode is REPLACE, this means that the existing row
|
||||
** should be deleted from the database before inserting the new row. Or,
|
||||
** if the on-conflict mode is other than REPLACE, then this method must
|
||||
** detect the conflict and return SQLITE_CONSTRAINT before beginning to
|
||||
** modify the database file.
|
||||
*/
|
||||
if( nArg>1 ){
|
||||
/* Find the value object that holds the new rowid value. */
|
||||
sqlite3_value *pNewRowid = apVal[3+p->nColumn];
|
||||
if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){
|
||||
pNewRowid = apVal[1];
|
||||
}
|
||||
|
||||
if( sqlite3_value_type(pNewRowid)!=SQLITE_NULL && (
|
||||
sqlite3_value_type(apVal[0])==SQLITE_NULL
|
||||
|| sqlite3_value_int64(apVal[0])!=sqlite3_value_int64(pNewRowid)
|
||||
)){
|
||||
/* The new rowid is not NULL (in this case the rowid will be
|
||||
** automatically assigned and there is no chance of a conflict), and
|
||||
** the statement is either an INSERT or an UPDATE that modifies the
|
||||
** rowid column. So if the conflict mode is REPLACE, then delete any
|
||||
** existing row with rowid=pNewRowid.
|
||||
**
|
||||
** Or, if the conflict mode is not REPLACE, insert the new record into
|
||||
** the %_content table. If we hit the duplicate rowid constraint (or any
|
||||
** other error) while doing so, return immediately.
|
||||
**
|
||||
** This branch may also run if pNewRowid contains a value that cannot
|
||||
** be losslessly converted to an integer. In this case, the eventual
|
||||
** call to fts3InsertData() (either just below or further on in this
|
||||
** function) will return SQLITE_MISMATCH. If fts3DeleteByRowid is
|
||||
** invoked, it will delete zero rows (since no row will have
|
||||
** docid=$pNewRowid if $pNewRowid is not an integer value).
|
||||
*/
|
||||
if( sqlite3_vtab_on_conflict(p->db)==SQLITE_REPLACE ){
|
||||
rc = fts3DeleteByRowid(p, pNewRowid, &nChng, aSzDel);
|
||||
}else{
|
||||
isRemove = 1;
|
||||
iRemove = sqlite3_value_int64(apVal[0]);
|
||||
rc = fts3PendingTermsDocid(p, iRemove);
|
||||
fts3DeleteTerms(&rc, p, apVal, aSzDel);
|
||||
fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, apVal);
|
||||
if( p->bHasDocsize ){
|
||||
fts3SqlExec(&rc, p, SQL_DELETE_DOCSIZE, apVal);
|
||||
}
|
||||
nChng--;
|
||||
rc = fts3InsertData(p, apVal, pRowid);
|
||||
bInsertDone = 1;
|
||||
}
|
||||
}
|
||||
}else if( sqlite3_value_type(apVal[p->nColumn+2])!=SQLITE_NULL ){
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3_free(aSzIns);
|
||||
return fts3SpecialInsert(p, apVal[p->nColumn+2]);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* If this is a DELETE or UPDATE operation, remove the old record. */
|
||||
if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
|
||||
assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER );
|
||||
rc = fts3DeleteByRowid(p, apVal[0], &nChng, aSzDel);
|
||||
isRemove = 1;
|
||||
iRemove = sqlite3_value_int64(apVal[0]);
|
||||
}
|
||||
|
||||
/* If this is an INSERT or UPDATE operation, insert the new record. */
|
||||
if( nArg>1 && rc==SQLITE_OK ){
|
||||
rc = fts3InsertData(p, apVal, pRowid);
|
||||
if( bInsertDone==0 ){
|
||||
rc = fts3InsertData(p, apVal, pRowid);
|
||||
if( rc==SQLITE_CONSTRAINT ) rc = SQLITE_CORRUPT;
|
||||
}
|
||||
if( rc==SQLITE_OK && (!isRemove || *pRowid!=iRemove) ){
|
||||
rc = fts3PendingTermsDocid(p, *pRowid);
|
||||
}
|
||||
|
@ -2625,6 +2625,90 @@ static int newRowid(Rtree *pRtree, i64 *piRowid){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove the entry with rowid=iDelete from the r-tree structure.
|
||||
*/
|
||||
static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){
|
||||
int rc; /* Return code */
|
||||
RtreeNode *pLeaf; /* Leaf node containing record iDelete */
|
||||
int iCell; /* Index of iDelete cell in pLeaf */
|
||||
RtreeNode *pRoot; /* Root node of rtree structure */
|
||||
|
||||
|
||||
/* Obtain a reference to the root node to initialise Rtree.iDepth */
|
||||
rc = nodeAcquire(pRtree, 1, 0, &pRoot);
|
||||
|
||||
/* Obtain a reference to the leaf node that contains the entry
|
||||
** about to be deleted.
|
||||
*/
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = findLeafNode(pRtree, iDelete, &pLeaf);
|
||||
}
|
||||
|
||||
/* Delete the cell in question from the leaf node. */
|
||||
if( rc==SQLITE_OK ){
|
||||
int rc2;
|
||||
rc = nodeRowidIndex(pRtree, pLeaf, iDelete, &iCell);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = deleteCell(pRtree, pLeaf, iCell, 0);
|
||||
}
|
||||
rc2 = nodeRelease(pRtree, pLeaf);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = rc2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete the corresponding entry in the <rtree>_rowid table. */
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_bind_int64(pRtree->pDeleteRowid, 1, iDelete);
|
||||
sqlite3_step(pRtree->pDeleteRowid);
|
||||
rc = sqlite3_reset(pRtree->pDeleteRowid);
|
||||
}
|
||||
|
||||
/* Check if the root node now has exactly one child. If so, remove
|
||||
** it, schedule the contents of the child for reinsertion and
|
||||
** reduce the tree height by one.
|
||||
**
|
||||
** This is equivalent to copying the contents of the child into
|
||||
** the root node (the operation that Gutman's paper says to perform
|
||||
** in this scenario).
|
||||
*/
|
||||
if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){
|
||||
int rc2;
|
||||
RtreeNode *pChild;
|
||||
i64 iChild = nodeGetRowid(pRtree, pRoot, 0);
|
||||
rc = nodeAcquire(pRtree, iChild, pRoot, &pChild);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = removeNode(pRtree, pChild, pRtree->iDepth-1);
|
||||
}
|
||||
rc2 = nodeRelease(pRtree, pChild);
|
||||
if( rc==SQLITE_OK ) rc = rc2;
|
||||
if( rc==SQLITE_OK ){
|
||||
pRtree->iDepth--;
|
||||
writeInt16(pRoot->zData, pRtree->iDepth);
|
||||
pRoot->isDirty = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Re-insert the contents of any underfull nodes removed from the tree. */
|
||||
for(pLeaf=pRtree->pDeleted; pLeaf; pLeaf=pRtree->pDeleted){
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = reinsertNodeContent(pRtree, pLeaf);
|
||||
}
|
||||
pRtree->pDeleted = pLeaf->pNext;
|
||||
sqlite3_free(pLeaf);
|
||||
}
|
||||
|
||||
/* Release the reference to the root node. */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = nodeRelease(pRtree, pRoot);
|
||||
}else{
|
||||
nodeRelease(pRtree, pRoot);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** The xUpdate method for rtree module virtual tables.
|
||||
*/
|
||||
@ -2636,103 +2720,25 @@ static int rtreeUpdate(
|
||||
){
|
||||
Rtree *pRtree = (Rtree *)pVtab;
|
||||
int rc = SQLITE_OK;
|
||||
RtreeCell cell; /* New cell to insert if nData>1 */
|
||||
int bHaveRowid = 0; /* Set to 1 after new rowid is determined */
|
||||
|
||||
rtreeReference(pRtree);
|
||||
|
||||
assert(nData>=1);
|
||||
|
||||
/* If azData[0] is not an SQL NULL value, it is the rowid of a
|
||||
** record to delete from the r-tree table. The following block does
|
||||
** just that.
|
||||
/* Constraint handling. A write operation on an r-tree table may return
|
||||
** SQLITE_CONSTRAINT for two reasons:
|
||||
**
|
||||
** 1. A duplicate rowid value, or
|
||||
** 2. The supplied data violates the "x2>=x1" constraint.
|
||||
**
|
||||
** In the first case, if the conflict-handling mode is REPLACE, then
|
||||
** the conflicting row can be removed before proceeding. In the second
|
||||
** case, SQLITE_CONSTRAINT must be returned regardless of the
|
||||
** conflict-handling mode specified by the user.
|
||||
*/
|
||||
if( sqlite3_value_type(azData[0])!=SQLITE_NULL ){
|
||||
i64 iDelete; /* The rowid to delete */
|
||||
RtreeNode *pLeaf; /* Leaf node containing record iDelete */
|
||||
int iCell; /* Index of iDelete cell in pLeaf */
|
||||
RtreeNode *pRoot;
|
||||
|
||||
/* Obtain a reference to the root node to initialise Rtree.iDepth */
|
||||
rc = nodeAcquire(pRtree, 1, 0, &pRoot);
|
||||
|
||||
/* Obtain a reference to the leaf node that contains the entry
|
||||
** about to be deleted.
|
||||
*/
|
||||
if( rc==SQLITE_OK ){
|
||||
iDelete = sqlite3_value_int64(azData[0]);
|
||||
rc = findLeafNode(pRtree, iDelete, &pLeaf);
|
||||
}
|
||||
|
||||
/* Delete the cell in question from the leaf node. */
|
||||
if( rc==SQLITE_OK ){
|
||||
int rc2;
|
||||
rc = nodeRowidIndex(pRtree, pLeaf, iDelete, &iCell);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = deleteCell(pRtree, pLeaf, iCell, 0);
|
||||
}
|
||||
rc2 = nodeRelease(pRtree, pLeaf);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = rc2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete the corresponding entry in the <rtree>_rowid table. */
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_bind_int64(pRtree->pDeleteRowid, 1, iDelete);
|
||||
sqlite3_step(pRtree->pDeleteRowid);
|
||||
rc = sqlite3_reset(pRtree->pDeleteRowid);
|
||||
}
|
||||
|
||||
/* Check if the root node now has exactly one child. If so, remove
|
||||
** it, schedule the contents of the child for reinsertion and
|
||||
** reduce the tree height by one.
|
||||
**
|
||||
** This is equivalent to copying the contents of the child into
|
||||
** the root node (the operation that Gutman's paper says to perform
|
||||
** in this scenario).
|
||||
*/
|
||||
if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){
|
||||
int rc2;
|
||||
RtreeNode *pChild;
|
||||
i64 iChild = nodeGetRowid(pRtree, pRoot, 0);
|
||||
rc = nodeAcquire(pRtree, iChild, pRoot, &pChild);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = removeNode(pRtree, pChild, pRtree->iDepth-1);
|
||||
}
|
||||
rc2 = nodeRelease(pRtree, pChild);
|
||||
if( rc==SQLITE_OK ) rc = rc2;
|
||||
if( rc==SQLITE_OK ){
|
||||
pRtree->iDepth--;
|
||||
writeInt16(pRoot->zData, pRtree->iDepth);
|
||||
pRoot->isDirty = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Re-insert the contents of any underfull nodes removed from the tree. */
|
||||
for(pLeaf=pRtree->pDeleted; pLeaf; pLeaf=pRtree->pDeleted){
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = reinsertNodeContent(pRtree, pLeaf);
|
||||
}
|
||||
pRtree->pDeleted = pLeaf->pNext;
|
||||
sqlite3_free(pLeaf);
|
||||
}
|
||||
|
||||
/* Release the reference to the root node. */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = nodeRelease(pRtree, pRoot);
|
||||
}else{
|
||||
nodeRelease(pRtree, pRoot);
|
||||
}
|
||||
}
|
||||
|
||||
/* If the azData[] array contains more than one element, elements
|
||||
** (azData[2]..azData[argc-1]) contain a new record to insert into
|
||||
** the r-tree structure.
|
||||
*/
|
||||
if( rc==SQLITE_OK && nData>1 ){
|
||||
/* Insert a new record into the r-tree */
|
||||
RtreeCell cell;
|
||||
if( nData>1 ){
|
||||
int ii;
|
||||
RtreeNode *pLeaf;
|
||||
|
||||
/* Populate the cell.aCoord[] array. The first coordinate is azData[3]. */
|
||||
assert( nData==(pRtree->nDim*2 + 3) );
|
||||
@ -2756,18 +2762,49 @@ static int rtreeUpdate(
|
||||
}
|
||||
}
|
||||
|
||||
/* Figure out the rowid of the new row. */
|
||||
if( sqlite3_value_type(azData[2])==SQLITE_NULL ){
|
||||
rc = newRowid(pRtree, &cell.iRowid);
|
||||
}else{
|
||||
/* If a rowid value was supplied, check if it is already present in
|
||||
** the table. If so, the constraint has failed. */
|
||||
if( sqlite3_value_type(azData[2])!=SQLITE_NULL ){
|
||||
cell.iRowid = sqlite3_value_int64(azData[2]);
|
||||
sqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid);
|
||||
if( SQLITE_ROW==sqlite3_step(pRtree->pReadRowid) ){
|
||||
sqlite3_reset(pRtree->pReadRowid);
|
||||
rc = SQLITE_CONSTRAINT;
|
||||
goto constraint;
|
||||
if( sqlite3_value_type(azData[0])==SQLITE_NULL
|
||||
|| sqlite3_value_int64(azData[0])!=cell.iRowid
|
||||
){
|
||||
int steprc;
|
||||
sqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid);
|
||||
steprc = sqlite3_step(pRtree->pReadRowid);
|
||||
rc = sqlite3_reset(pRtree->pReadRowid);
|
||||
if( SQLITE_ROW==steprc ){
|
||||
if( sqlite3_vtab_on_conflict(pRtree->db)==SQLITE_REPLACE ){
|
||||
rc = rtreeDeleteRowid(pRtree, cell.iRowid);
|
||||
}else{
|
||||
rc = SQLITE_CONSTRAINT;
|
||||
goto constraint;
|
||||
}
|
||||
}
|
||||
}
|
||||
rc = sqlite3_reset(pRtree->pReadRowid);
|
||||
bHaveRowid = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* If azData[0] is not an SQL NULL value, it is the rowid of a
|
||||
** record to delete from the r-tree table. The following block does
|
||||
** just that.
|
||||
*/
|
||||
if( sqlite3_value_type(azData[0])!=SQLITE_NULL ){
|
||||
rc = rtreeDeleteRowid(pRtree, sqlite3_value_int64(azData[0]));
|
||||
}
|
||||
|
||||
/* If the azData[] array contains more than one element, elements
|
||||
** (azData[2]..azData[argc-1]) contain a new record to insert into
|
||||
** the r-tree structure.
|
||||
*/
|
||||
if( rc==SQLITE_OK && nData>1 ){
|
||||
/* Insert the new record into the r-tree */
|
||||
RtreeNode *pLeaf;
|
||||
|
||||
/* Figure out the rowid of the new row. */
|
||||
if( bHaveRowid==0 ){
|
||||
rc = newRowid(pRtree, &cell.iRowid);
|
||||
}
|
||||
*pRowid = cell.iRowid;
|
||||
|
||||
@ -3008,6 +3045,8 @@ static int rtreeInit(
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
|
||||
|
||||
/* Allocate the sqlite3_vtab structure */
|
||||
nDb = strlen(argv[1]);
|
||||
nName = strlen(argv[2]);
|
||||
|
@ -31,6 +31,8 @@ source $testdir/tester.tcl
|
||||
# rtree-7.*: Test renaming an r-tree table.
|
||||
# rtree-8.*: Test constrained scans of r-tree data.
|
||||
#
|
||||
# rtree-12.*: Test that on-conflict clauses are supported.
|
||||
#
|
||||
|
||||
ifcapable !rtree {
|
||||
finish_test
|
||||
@ -416,4 +418,83 @@ do_test rtree-11.2 {
|
||||
}
|
||||
} {2}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test on-conflict clause handling.
|
||||
#
|
||||
db_delete_and_reopen
|
||||
do_execsql_test 12.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING rtree_i32(idx, x1, x2, y1, y2);
|
||||
INSERT INTO t1 VALUES(1, 1, 2, 3, 4);
|
||||
INSERT INTO t1 VALUES(2, 2, 3, 4, 5);
|
||||
INSERT INTO t1 VALUES(3, 3, 4, 5, 6);
|
||||
|
||||
CREATE TABLE source(idx, x1, x2, y1, y2);
|
||||
INSERT INTO source VALUES(5, 8, 8, 8, 8);
|
||||
INSERT INTO source VALUES(2, 7, 7, 7, 7);
|
||||
|
||||
}
|
||||
db_save_and_close
|
||||
foreach {tn sql_template testdata} {
|
||||
1 "INSERT %CONF% INTO t1 VALUES(2, 7, 7, 7, 7)" {
|
||||
ROLLBACK 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
|
||||
ABORT 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
IGNORE 0 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
FAIL 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
REPLACE 0 0 {1 1 2 3 4 2 7 7 7 7 3 3 4 5 6 4 4 5 6 7}
|
||||
}
|
||||
|
||||
2 "INSERT %CONF% INTO t1 SELECT * FROM source" {
|
||||
ROLLBACK 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
|
||||
ABORT 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
IGNORE 1 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7 5 8 8 8 8}
|
||||
FAIL 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7 5 8 8 8 8}
|
||||
REPLACE 1 0 {1 1 2 3 4 2 7 7 7 7 3 3 4 5 6 4 4 5 6 7 5 8 8 8 8}
|
||||
}
|
||||
|
||||
3 "UPDATE %CONF% t1 SET idx = 2 WHERE idx = 4" {
|
||||
ROLLBACK 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
|
||||
ABORT 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
IGNORE 1 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
FAIL 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
REPLACE 1 0 {1 1 2 3 4 2 4 5 6 7 3 3 4 5 6}
|
||||
}
|
||||
|
||||
3 "UPDATE %CONF% t1 SET idx = ((idx+1)%5)+1 WHERE idx > 2" {
|
||||
ROLLBACK 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
|
||||
ABORT 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
IGNORE 1 0 {1 1 2 3 4 2 2 3 4 5 4 4 5 6 7 5 3 4 5 6}
|
||||
FAIL 1 1 {1 1 2 3 4 2 2 3 4 5 4 4 5 6 7 5 3 4 5 6}
|
||||
REPLACE 1 0 {1 4 5 6 7 2 2 3 4 5 5 3 4 5 6}
|
||||
}
|
||||
|
||||
4 "INSERT %CONF% INTO t1 VALUES(2, 7, 6, 7, 7)" {
|
||||
ROLLBACK 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
|
||||
ABORT 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
IGNORE 0 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
FAIL 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
REPLACE 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
|
||||
}
|
||||
|
||||
} {
|
||||
foreach {mode uses error data} $testdata {
|
||||
db_restore_and_reopen
|
||||
|
||||
set sql [string map [list %CONF% "OR $mode"] $sql_template]
|
||||
set testname "12.$tn.[string tolower $mode]"
|
||||
|
||||
execsql {
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(4, 4, 5, 6, 7);
|
||||
}
|
||||
|
||||
set res(0) {0 {}}
|
||||
set res(1) {1 {constraint failed}}
|
||||
do_catchsql_test $testname.1 $sql $res($error)
|
||||
do_test $testname.2 [list sql_uses_stmt db $sql] $uses
|
||||
do_execsql_test $testname.3 { SELECT * FROM t1 ORDER BY idx } $data
|
||||
|
||||
do_test $testname.4 { rtree_check db t1 } 0
|
||||
db close
|
||||
}
|
||||
}
|
||||
finish_test
|
||||
|
Reference in New Issue
Block a user