mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-23 11:22:09 +03:00
Merge the latest trunk changes into the sessions branch.
FossilOrigin-Name: 6883580e6c8973010a42d1d2c5bde04c6b2f4eb7
This commit is contained in:
18
configure
vendored
18
configure
vendored
@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.62 for sqlite 3.7.6.1.
|
||||
# Generated by GNU Autoconf 2.62 for sqlite 3.7.7.
|
||||
#
|
||||
# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
|
||||
# 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
|
||||
@ -743,8 +743,8 @@ SHELL=${CONFIG_SHELL-/bin/sh}
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='sqlite'
|
||||
PACKAGE_TARNAME='sqlite'
|
||||
PACKAGE_VERSION='3.7.6.1'
|
||||
PACKAGE_STRING='sqlite 3.7.6.1'
|
||||
PACKAGE_VERSION='3.7.7'
|
||||
PACKAGE_STRING='sqlite 3.7.7'
|
||||
PACKAGE_BUGREPORT=''
|
||||
|
||||
# Factoring default headers for most tests.
|
||||
@ -1485,7 +1485,7 @@ if test "$ac_init_help" = "long"; then
|
||||
# Omit some internal or obsolete options to make the list less imposing.
|
||||
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||
cat <<_ACEOF
|
||||
\`configure' configures sqlite 3.7.6.1 to adapt to many kinds of systems.
|
||||
\`configure' configures sqlite 3.7.7 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@ -1550,7 +1550,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of sqlite 3.7.6.1:";;
|
||||
short | recursive ) echo "Configuration of sqlite 3.7.7:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@ -1666,7 +1666,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
sqlite configure 3.7.6.1
|
||||
sqlite configure 3.7.7
|
||||
generated by GNU Autoconf 2.62
|
||||
|
||||
Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
|
||||
@ -1680,7 +1680,7 @@ cat >config.log <<_ACEOF
|
||||
This file contains any messages produced by compilers while
|
||||
running configure, to aid debugging if configure makes a mistake.
|
||||
|
||||
It was created by sqlite $as_me 3.7.6.1, which was
|
||||
It was created by sqlite $as_me 3.7.7, which was
|
||||
generated by GNU Autoconf 2.62. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
@ -13942,7 +13942,7 @@ exec 6>&1
|
||||
# report actual input values of CONFIG_FILES etc. instead of their
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by sqlite $as_me 3.7.6.1, which was
|
||||
This file was extended by sqlite $as_me 3.7.7, which was
|
||||
generated by GNU Autoconf 2.62. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@ -13995,7 +13995,7 @@ Report bugs to <bug-autoconf@gnu.org>."
|
||||
_ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_version="\\
|
||||
sqlite config.status 3.7.6.1
|
||||
sqlite config.status 3.7.7
|
||||
configured by $0, generated by GNU Autoconf 2.62,
|
||||
with options \\"`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
|
||||
|
||||
|
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
|
||||
|
1
main.mk
1
main.mk
@ -300,6 +300,7 @@ TESTSRC2 = \
|
||||
$(TOP)/ext/fts3/fts3.c \
|
||||
$(TOP)/ext/fts3/fts3_aux.c \
|
||||
$(TOP)/ext/fts3/fts3_expr.c \
|
||||
$(TOP)/ext/fts3/fts3_term.c \
|
||||
$(TOP)/ext/fts3/fts3_tokenizer.c \
|
||||
$(TOP)/ext/fts3/fts3_write.c \
|
||||
$(TOP)/ext/async/sqlite3async.c \
|
||||
|
70
manifest
70
manifest
@ -1,11 +1,11 @@
|
||||
C Merge\strunk\schanges\sinto\ssessions\sbranch.
|
||||
D 2011-04-18T17:30:56.521
|
||||
C Merge\sthe\slatest\strunk\schanges\sinto\sthe\ssessions\sbranch.
|
||||
D 2011-05-05T15:46:16.843
|
||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||
F Makefile.in 7a4d9524721d40ef9ee26f93f9bd6a51dba106f2
|
||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||
F Makefile.vxworks c85ec1d8597fe2f7bc225af12ac1666e21379151
|
||||
F README cd04a36fbc7ea56932a4052d7d0b7f09f27c33d6
|
||||
F VERSION c97e5dcdea2407f4a94f9740294cdf39ce9e88c4
|
||||
F VERSION 3fcdd7fbe3eb282df3978fe77288544543767961
|
||||
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
|
||||
F addopcodes.awk 17dc593f791f874d2c23a0f9360850ded0286531
|
||||
F art/2005osaward.gif 0d1851b2a7c1c9d0ccce545f3e14bca42d7fd248
|
||||
@ -22,7 +22,7 @@ F art/src_logo.gif 9341ef09f0e53cd44c0c9b6fc3c16f7f3d6c2ad9
|
||||
F config.guess 226d9a188c6196f3033ffc651cbc9dcee1a42977
|
||||
F config.h.in 868fdb48c028421a203470e15c69ada15b9ba673
|
||||
F config.sub 9ebe4c3b3dab6431ece34f16828b594fb420da55
|
||||
F configure 61dbf78cdc4d6a871333dc599c130be6cce865c5 x
|
||||
F configure 1c31f231ba59b71ff81dbf5c7c7594fdc83803d5 x
|
||||
F configure.ac 87a3c71bbe9c925381c154413eea7f3cdc397244
|
||||
F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
|
||||
F doc/lemon.html f0f682f50210928c07e562621c3b7e8ab912a538
|
||||
@ -61,29 +61,30 @@ F ext/fts2/mkfts2amal.tcl 974d5d438cb3f7c4a652639262f82418c1e4cff0
|
||||
F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
|
||||
F ext/fts3/README.tokenizers 998756696647400de63d5ba60e9655036cb966e9
|
||||
F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
|
||||
F ext/fts3/fts3.c 5653c5654ac9b65bf3646af7e1d695c7e9b991a0
|
||||
F ext/fts3/fts3.c 73d6718dba1e4ef621ff8a4ac1c22019460c84cc
|
||||
F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
|
||||
F ext/fts3/fts3Int.h 945926ea4b6a686c3e9834640a252d9870b7191e
|
||||
F ext/fts3/fts3Int.h 8c2ac39ee17362571c58ab2c4f0667324c31f738
|
||||
F ext/fts3/fts3_aux.c 9e931f55eed8498dafe7bc1160f10cbb1a652fdf
|
||||
F ext/fts3/fts3_expr.c 5f49e0deaf723724b08100bb3ff40aab02ad0c93
|
||||
F ext/fts3/fts3_hash.c 3c8f6387a4a7f5305588b203fa7c887d753e1f1c
|
||||
F ext/fts3/fts3_hash.h 8331fb2206c609f9fc4c4735b9ab5ad6137c88ec
|
||||
F ext/fts3/fts3_icu.c ac494aed69835008185299315403044664bda295
|
||||
F ext/fts3/fts3_porter.c d61cfd81fb0fd8fbcb25adcaee0ba671aefaa5c2
|
||||
F ext/fts3/fts3_snippet.c e857c6a89d81d3b89df59f3b44b35c68d8ed5c62
|
||||
F ext/fts3/fts3_snippet.c a4a3c7d2ab15ca9188e2d9b51a5e3927bf76580d
|
||||
F ext/fts3/fts3_term.c f115f5a5f4298303d3b22fc6c524b8d565c7b950
|
||||
F ext/fts3/fts3_tokenizer.c 055f3dc7369585350b28db1ee0f3b214dca6724d
|
||||
F ext/fts3/fts3_tokenizer.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3
|
||||
F ext/fts3/fts3_tokenizer1.c 6e5cbaa588924ac578263a598e4fb9f5c9bb179d
|
||||
F ext/fts3/fts3_write.c 813495ed106eb9461044e3c0374f4db69b37eb09
|
||||
F ext/fts3/fts3_write.c 7d6d904b89333448eb968fc82470a74985d0b61e
|
||||
F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
|
||||
F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
|
||||
F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
|
||||
F ext/icu/icu.c eb9ae1d79046bd7871aa97ee6da51eb770134b5a
|
||||
F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
|
||||
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
|
||||
F ext/rtree/rtree.c f5fa951eba03c41d292958064604a033021acdee
|
||||
F ext/rtree/rtree.c 829c6901a2b065ff93a68d431f9eaba8de7128e0
|
||||
F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e
|
||||
F ext/rtree/rtree1.test dbd4250ac0ad367a262eb9676f7e3080b0368206
|
||||
F ext/rtree/rtree1.test 28e1b8da4da98093ce3210187434dd760a8d89d8
|
||||
F ext/rtree/rtree2.test acbb3a4ce0f4fbc2c304d2b4b784cfa161856bba
|
||||
F ext/rtree/rtree3.test a494da55c30ee0bc9b01a91c80c81b387b22d2dc
|
||||
F ext/rtree/rtree4.test 0061e6f464fd3dc6a79f82454c5a1c3dadbe42af
|
||||
@ -111,7 +112,7 @@ F ext/session/sqlite3session.h 665f5591562e3c71eb3d0da26f1a1efae26f7bcf
|
||||
F ext/session/test_session.c 311e5b9228374d0b5780448f289847ff1cf7d388
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F main.mk f942406cb7df55d1aec40a88a7ae399b730cd94f
|
||||
F main.mk 352002cedf9e754ec96cfe0a8d1688cdea343fea
|
||||
F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a
|
||||
F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f
|
||||
F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac
|
||||
@ -143,12 +144,12 @@ F src/delete.c ad9fa1cbf91a83ec6990d0aecb7e21cd5ff07e71
|
||||
F src/expr.c e3cf0957c6b8faaaf7386a3bc69e53c0dc9705be
|
||||
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
|
||||
F src/fkey.c a43ba8a005fb5efd1deeee06853e3a6120d46a91
|
||||
F src/func.c 3a8cb2fb2de3e3aed7f39106daf4878d9d17fcce
|
||||
F src/func.c b9117e40975245b8504cf3625d7e321d8d4b63dc
|
||||
F src/global.c 02335177cf6946fe5525c6f0755cf181140debf3
|
||||
F src/hash.c 458488dcc159c301b8e7686280ab209f1fb915af
|
||||
F src/hash.h 2894c932d84d9f892d4b4023a75e501f83050970
|
||||
F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08
|
||||
F src/insert.c 8796ca3f9209b699cb8120fc44290fc97ac26abe
|
||||
F src/insert.c 1f1688a9da8b8e27114ba1909a6e74100791139b
|
||||
F src/journal.c 552839e54d1bf76fb8f7abe51868b66acacf6a0e
|
||||
F src/legacy.c a199d7683d60cef73089e892409113e69c23a99f
|
||||
F src/lempar.c 7f026423f4d71d989e719a743f98a1cbd4e6d99e
|
||||
@ -172,8 +173,8 @@ F src/os.c 22ac61d06e72a0dac900400147333b07b13d8e1d
|
||||
F src/os.h 9dbed8c2b9c1f2f2ebabc09e49829d4777c26bf9
|
||||
F src/os_common.h a8f95b81eca8a1ab8593d23e94f8a35f35d4078f
|
||||
F src/os_os2.c 4a75888ba3dfc820ad5e8177025972d74d7f2440
|
||||
F src/os_unix.c d7889a0f9389c8c2e1d3b380f5aa1256c22a90e8
|
||||
F src/os_win.c d149b9a7dfdd38de09afc054f8168cd3cd80630b
|
||||
F src/os_unix.c 2c67d126874b78eb427371db4793f0e8fbc7448b
|
||||
F src/os_win.c 4271f0bf733c0b45635ddcfb41c935573de8284c
|
||||
F src/pager.c 055239dcdfe12b3f5d97f6f01f85da01e2d6d912
|
||||
F src/pager.h 3f8c783de1d4706b40b1ac15b64f5f896bcc78d1
|
||||
F src/parse.y 12b7ebd61ea54f0e1b1083ff69cc2c8ce9353d58
|
||||
@ -188,14 +189,14 @@ F src/resolve.c 1c0f32b64f8e3f555fe1f732f9d6f501a7f05706
|
||||
F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697
|
||||
F src/select.c d9d440809025a58547e39f4f268c2a296bfb56ff
|
||||
F src/shell.c 72e7e176bf46d5c6518d15ac4ad6847c4bb5df79
|
||||
F src/sqlite.h.in 0cf61c41c48e1e6b863ff8cf9ecd69620932330e
|
||||
F src/sqlite.h.in 628de30f6063695288eadf34c167e49bc34c9828
|
||||
F src/sqlite3ext.h c90bd5507099f62043832d73f6425d8d5c5da754
|
||||
F src/sqliteInt.h 9a29e5bb82f3abef6b4af91e18d637050fa3c883
|
||||
F src/sqliteInt.h 1577ac69cb67a1dc0c07974a4e5129b1cde039a3
|
||||
F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
|
||||
F src/status.c 7ac64842c86cec2fc1a1d0e5c16d3beb8ad332bf
|
||||
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
|
||||
F src/tclsqlite.c fe0da0eb0ebd8d21eec90683b779456e64351de6
|
||||
F src/test1.c 9ca440e80e16e53920904a0a5ac7feffb9b2c9a1
|
||||
F src/test1.c e0e4af306b678da05334c2ccaf0377ae8f06e911
|
||||
F src/test2.c 80d323d11e909cf0eb1b6fbb4ac22276483bcf31
|
||||
F src/test3.c 056093cfef69ff4227a6bdb9108564dc7f45e4bc
|
||||
F src/test4.c d1e5a5e904d4b444cf572391fdcb017638e36ff7
|
||||
@ -209,7 +210,7 @@ F src/test_autoext.c 30e7bd98ab6d70a62bb9ba572e4c7df347fe645e
|
||||
F src/test_backup.c c129c91127e9b46e335715ae2e75756e25ba27de
|
||||
F src/test_btree.c 47cd771250f09cdc6e12dda5bc71bc0b3abc96e2
|
||||
F src/test_config.c 25a4128c2dc9e1dbebafcb7e8c61d45f09f7fbc3
|
||||
F src/test_demovfs.c 31050680fa6925b4f677cfd4fa965b5f19195e50
|
||||
F src/test_demovfs.c 938d0f595f8bd310076e1c06cf7885a01ce7ce01
|
||||
F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc
|
||||
F src/test_func.c cbdec5cededa0761daedde5baf06004a9bf416b5
|
||||
F src/test_fuzzer.c f884f6f32e8513d34248d6e1ac8a32047fead254
|
||||
@ -241,19 +242,19 @@ F src/test_wholenumber.c 6129adfbe7c7444f2e60cc785927f3aa74e12290
|
||||
F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
|
||||
F src/tokenize.c 604607d6813e9551cf5189d899e0a25c12681080
|
||||
F src/trigger.c 144cc18bb701f3286484aae4292a9531f09278c8
|
||||
F src/update.c 3f3f3bb734a0da1dffd0ed33e504642b35ed3605
|
||||
F src/update.c f66b651c15e42875f36501ec39a968e836ee5586
|
||||
F src/utf.c d83650c3ea08f7407bd9d0839d9885241c209c60
|
||||
F src/util.c 465fe10aabf0ca7d7826a156dab919b0b65c525a
|
||||
F src/vacuum.c 05513dca036a1e7848fe18d5ed1265ac0b32365e
|
||||
F src/vdbe.c dd53dda1cf786397e72643a497b5c2f368ff11ba
|
||||
F src/vdbe.c fa5bfc0c820706a1f78042b134a0f7a388ca9a3e
|
||||
F src/vdbe.h 44fd57aeed86da0cd31206626c13cdde0e72cc0e
|
||||
F src/vdbeInt.h b95de01246c15499c700ae00cfda0de25c01358a
|
||||
F src/vdbeapi.c 8051038f7674c708f4515ab189fc3ea929e09a4c
|
||||
F src/vdbeaux.c b0a2a184a25380f7eb9d07e9336034ec38d1b213
|
||||
F src/vdbeaux.c cc95d80b899b75829cb93d260d3f14125a5c26ad
|
||||
F src/vdbeblob.c c3ccb7c8732858c680f442932e66ad06bb036562
|
||||
F src/vdbemem.c 0498796b6ffbe45e32960d6a1f5adfb6e419883b
|
||||
F src/vdbetrace.c 5d0dc3d5fd54878cc8d6d28eb41deb8d5885b114
|
||||
F src/vtab.c b0abc931f95af94c9ffdf9f747eb191cda953123
|
||||
F src/vtab.c 1491acb3e0a67eafe69134fb65bfa0b7b7e82342
|
||||
F src/wal.c 7334009b396285b658a95a3b6bc6d2b016a1f794
|
||||
F src/wal.h 7a5fbb00114b7f2cd40c7e1003d4c41ce9d26840
|
||||
F src/walker.c 3112bb3afe1d85dc52317cb1d752055e9a781f8f
|
||||
@ -375,7 +376,7 @@ F test/descidx2.test 9f1a0c83fd57f8667c82310ca21b30a350888b5d
|
||||
F test/descidx3.test fe720e8b37d59f4cef808b0bf4e1b391c2e56b6f
|
||||
F test/diskfull.test 0cede7ef9d8f415d9d3944005c76be7589bb5ebb
|
||||
F test/distinctagg.test 1a6ef9c87a58669438fc771450d7a72577417376
|
||||
F test/e_createtable.test b40fc61bc4f1ad2a3c84590bd1d711507263d921
|
||||
F test/e_createtable.test 4771686a586b6ae414f927c389b2c101cc05c028
|
||||
F test/e_delete.test 55d868b647acc091c261a10b9b0cb0ab660a6acb
|
||||
F test/e_droptrigger.test ddd4b28ed8a3d81bd5153fa0ab7559529a2ca03a
|
||||
F test/e_dropview.test b347bab30fc8de67b131594b3cd6f3d3bdaa753d
|
||||
@ -460,11 +461,12 @@ F test/fts3al.test 07d64326e79bbdbab20ee87fc3328fbf01641c9f
|
||||
F test/fts3am.test 218aa6ba0dfc50c7c16b2022aac5c6be593d08d8
|
||||
F test/fts3an.test a49ccadc07a2f7d646ec1b81bc09da2d85a85b18
|
||||
F test/fts3ao.test b83f99f70e9eec85f27d75801a974b3f820e01f9
|
||||
F test/fts3atoken.test 25c2070e1e8755d414bf9c8200427b277a9f99fa
|
||||
F test/fts3aux1.test 719c35cbbcc04dde8e5a54a6f69851a0af9ed1f2
|
||||
F test/fts3atoken.test 402ef2f7c2fb4b3d4fa0587df6441c1447e799b3
|
||||
F test/fts3aux1.test 0b02743955d56fc0d4d66236a26177bd1b726de0
|
||||
F test/fts3b.test e93bbb653e52afde110ad53bbd793f14fe7a8984
|
||||
F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958
|
||||
F test/fts3comp1.test a0f5b16a2df44dd0b15751787130af2183167c0c
|
||||
F test/fts3conf.test 8e65ea56f88ced6cdd2252bdddb1a8327ae5af7e
|
||||
F test/fts3corrupt.test 7890cc202406858386ddf390a879dcf80bc10abf
|
||||
F test/fts3corrupt2.test 6d96efae2f8a6af3eeaf283aba437e6d0e5447ba
|
||||
F test/fts3cov.test e0fb00d8b715ddae4a94c305992dfc3ef70353d7
|
||||
@ -483,6 +485,7 @@ F test/fts3query.test ef79d31fdb355d094baec1c1b24b60439a1fb8a2
|
||||
F test/fts3rnd.test 2b1a579be557ab8ac54a51b39caa4aa8043cc4ad
|
||||
F test/fts3shared.test 8bb266521d7c5495c0ae522bb4d376ad5387d4a2
|
||||
F test/fts3snippet.test a12f22a3ba4dd59751a57c79b031d07ab5f51ddd
|
||||
F test/fts3sort.test e6f24e9cffc46484bcc9fe63d3c2ce41afcaa6c9
|
||||
F test/fts4aa.test eadf85621c0a113d4c7ad3ccbf8441130e007b8f
|
||||
F test/func.test 6c5ce11e3a0021ca3c0649234e2d4454c89110ca
|
||||
F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f
|
||||
@ -515,7 +518,7 @@ F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7
|
||||
F test/insert.test aef273dd1cee84cc92407469e6bd1b3cdcb76908
|
||||
F test/insert2.test 4f3a04d168c728ed5ec2c88842e772606c7ce435
|
||||
F test/insert3.test 1b7db95a03ad9c5013fdf7d6722b6cd66ee55e30
|
||||
F test/insert4.test c1469999a58e86a85b74df645a820f4cc7a8273b
|
||||
F test/insert4.test b3e02648a5fc3075c29e13c369b5127bf859b5a2
|
||||
F test/insert5.test 1f93cbe9742110119133d7e8e3ccfe6d7c249766
|
||||
F test/intarray.test 066b7d7ac38d25bf96f87f1b017bfc687551cdd4
|
||||
F test/interrupt.test 42e7cf98646fd9cb4a3b131a93ed3c50b9e149f1
|
||||
@ -697,7 +700,7 @@ F test/tclsqlite.test 1ce9b6340d6d412420634e129a2e3722c651056a
|
||||
F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c
|
||||
F test/temptable.test f42121a0d29a62f00f93274464164177ab1cc24a
|
||||
F test/temptrigger.test b0273db072ce5f37cf19140ceb1f0d524bbe9f05
|
||||
F test/tester.tcl fc2e27b3a782c5c5b03f7cded15cf498cad9bfef
|
||||
F test/tester.tcl cf301cdb35f20378b086849acd26a4574187183f
|
||||
F test/thread001.test a3e6a7254d1cb057836cb3145b60c10bf5b7e60f
|
||||
F test/thread002.test afd20095e6e845b405df4f2c920cb93301ca69db
|
||||
F test/thread003.test b824d4f52b870ae39fc5bae4d8070eca73085dca
|
||||
@ -910,6 +913,7 @@ F tool/diffdb.c 7524b1b5df217c20cd0431f6789851a4e0cb191b
|
||||
F tool/fragck.tcl 5265a95126abcf6ab357f7efa544787e5963f439
|
||||
F tool/genfkey.README cf68fddd4643bbe3ff8e31b8b6d8b0a1b85e20f4
|
||||
F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5
|
||||
F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce
|
||||
F tool/lemon.c dfd81a51b6e27e469ba21d01a75ddf092d429027
|
||||
F tool/lempar.c 01ca97f87610d1dac6d8cd96ab109ab1130e76dc
|
||||
F tool/mkkeywordhash.c d2e6b4a5965e23afb80fbe74bb54648cd371f309
|
||||
@ -922,7 +926,7 @@ F tool/omittest.tcl b1dd290c1596e0f31fd335160a74ec5dfea3df4a
|
||||
F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c
|
||||
F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a
|
||||
F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5
|
||||
F tool/shell1.test fee04fce1bf55e6e081523545fd4e0e83490ff8e
|
||||
F tool/shell1.test 5542ecdc952f91121a835ed817e6feaf8988b333
|
||||
F tool/shell2.test 5dc76b8005b465f420fed8241621da7513060ff3
|
||||
F tool/shell3.test 4fad469e8003938426355afdf34155f08c587836
|
||||
F tool/shell4.test 35f9c3d452b4e76d5013c63e1fd07478a62f14ce
|
||||
@ -940,7 +944,7 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
|
||||
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
|
||||
F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
|
||||
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
|
||||
P 99f0f35092b0b78b7016b21c242da263ab64b77b 3e135748f1efacb52b414b3ac3f4ae2c08bcd8fb
|
||||
R b2692e50ee7820b096983b9aa33c1efd
|
||||
U dan
|
||||
Z c7fa020efb14904762c294d2eedf7456
|
||||
P b91b4c31fe311b292044c9c747feba294ffce25c 930be6a1bdec8c150caafd790973f7a401fc1970
|
||||
R 13217a0634732c3fc1250bf1a2936d3d
|
||||
U drh
|
||||
Z f29377c78641e183f6acae5b288b38b3
|
||||
|
@ -1 +1 @@
|
||||
b91b4c31fe311b292044c9c747feba294ffce25c
|
||||
6883580e6c8973010a42d1d2c5bde04c6b2f4eb7
|
16
src/func.c
16
src/func.c
@ -774,6 +774,21 @@ static void sourceidFunc(
|
||||
sqlite3_result_text(context, sqlite3_sourceid(), -1, SQLITE_STATIC);
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of the sqlite_log() function. This is a wrapper around
|
||||
** sqlite3_log(). The return value is NULL. The function exists purely for
|
||||
** its side-effects.
|
||||
*/
|
||||
static void errlogFunc(
|
||||
sqlite3_context *context,
|
||||
int argc,
|
||||
sqlite3_value **argv
|
||||
){
|
||||
UNUSED_PARAMETER(argc);
|
||||
UNUSED_PARAMETER(context);
|
||||
sqlite3_log(sqlite3_value_int(argv[0]), "%s", sqlite3_value_text(argv[1]));
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of the sqlite_compileoption_used() function.
|
||||
** The result is an integer that identifies if the compiler option
|
||||
@ -1541,6 +1556,7 @@ void sqlite3RegisterGlobalFunctions(void){
|
||||
FUNCTION(nullif, 2, 0, 1, nullifFunc ),
|
||||
FUNCTION(sqlite_version, 0, 0, 0, versionFunc ),
|
||||
FUNCTION(sqlite_source_id, 0, 0, 0, sourceidFunc ),
|
||||
FUNCTION(sqlite_log, 2, 0, 0, errlogFunc ),
|
||||
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
|
||||
FUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ),
|
||||
FUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ),
|
||||
|
13
src/insert.c
13
src/insert.c
@ -969,6 +969,7 @@ void sqlite3Insert(
|
||||
const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
|
||||
sqlite3VtabMakeWritable(pParse, pTab);
|
||||
sqlite3VdbeAddOp4(v, OP_VUpdate, 1, pTab->nCol+2, regIns, pVTab, P4_VTAB);
|
||||
sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError);
|
||||
sqlite3MayAbort(pParse);
|
||||
}else
|
||||
#endif
|
||||
@ -1744,6 +1745,18 @@ static int xferOptimization(
|
||||
return 0; /* Tables have different CHECK constraints. Ticket #2252 */
|
||||
}
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_FOREIGN_KEY
|
||||
/* Disallow the transfer optimization if the destination table constains
|
||||
** any foreign key constraints. This is more restrictive than necessary.
|
||||
** But the main beneficiary of the transfer optimization is the VACUUM
|
||||
** command, and the VACUUM command disables foreign key constraints. So
|
||||
** the extra complication to make this rule less restrictive is probably
|
||||
** not worth the effort. Ticket [6284df89debdfa61db8073e062908af0c9b6118e]
|
||||
*/
|
||||
if( (pParse->db->flags & SQLITE_ForeignKeys)!=0 && pDest->pFKey!=0 ){
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* If we get this far, it means either:
|
||||
**
|
||||
|
@ -281,6 +281,18 @@ struct unixFile {
|
||||
#define threadid 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Different Unix systems declare open() in different ways. Same use
|
||||
** open(const char*,int,mode_t). Others use open(const char*,int,...).
|
||||
** The difference is important when using a pointer to the function.
|
||||
**
|
||||
** The safest way to deal with the problem is to always use this wrapper
|
||||
** which always has the same well-defined interface.
|
||||
*/
|
||||
static int posixOpen(const char *zFile, int flags, int mode){
|
||||
return open(zFile, flags, mode);
|
||||
}
|
||||
|
||||
/*
|
||||
** Many system calls are accessed through pointer-to-functions so that
|
||||
** they may be overridden at runtime to facilitate fault injection during
|
||||
@ -292,8 +304,8 @@ static struct unix_syscall {
|
||||
sqlite3_syscall_ptr pCurrent; /* Current value of the system call */
|
||||
sqlite3_syscall_ptr pDefault; /* Default value */
|
||||
} aSyscall[] = {
|
||||
{ "open", (sqlite3_syscall_ptr)open, 0 },
|
||||
#define osOpen ((int(*)(const char*,int,...))aSyscall[0].pCurrent)
|
||||
{ "open", (sqlite3_syscall_ptr)posixOpen, 0 },
|
||||
#define osOpen ((int(*)(const char*,int,int))aSyscall[0].pCurrent)
|
||||
|
||||
{ "close", (sqlite3_syscall_ptr)close, 0 },
|
||||
#define osClose ((int(*)(int))aSyscall[1].pCurrent)
|
||||
|
@ -286,7 +286,7 @@ char *sqlite3_win32_mbcs_to_utf8(const char *zFilename){
|
||||
** Convert UTF-8 to multibyte character string. Space to hold the
|
||||
** returned string is obtained from malloc().
|
||||
*/
|
||||
static char *utf8ToMbcs(const char *zFilename){
|
||||
char *sqlite3_win32_utf8_to_mbcs(const char *zFilename){
|
||||
char *zFilenameMbcs;
|
||||
WCHAR *zTmpWide;
|
||||
|
||||
@ -1052,7 +1052,7 @@ static int unlockReadLock(winFile *pFile){
|
||||
res = UnlockFile(pFile->h, SHARED_FIRST + pFile->sharedLockByte, 0, 1, 0);
|
||||
#endif
|
||||
}
|
||||
if( res == 0 ){
|
||||
if( res==0 && GetLastError()!=ERROR_NOT_LOCKED ){
|
||||
pFile->lastErrno = GetLastError();
|
||||
winLogError(SQLITE_IOERR_UNLOCK, "unlockReadLock", pFile->zPath);
|
||||
}
|
||||
@ -2004,7 +2004,7 @@ static void *convertUtf8Filename(const char *zFilename){
|
||||
*/
|
||||
#if SQLITE_OS_WINCE==0
|
||||
}else{
|
||||
zConverted = utf8ToMbcs(zFilename);
|
||||
zConverted = sqlite3_win32_utf8_to_mbcs(zFilename);
|
||||
#endif
|
||||
}
|
||||
/* caller will handle out of memory */
|
||||
|
102
src/sqlite.h.in
102
src/sqlite.h.in
@ -370,7 +370,8 @@ int sqlite3_exec(
|
||||
**
|
||||
** New error codes may be added in future versions of SQLite.
|
||||
**
|
||||
** See also: [SQLITE_IOERR_READ | extended result codes]
|
||||
** See also: [SQLITE_IOERR_READ | extended result codes],
|
||||
** [sqlite3_vtab_on_conflict()] [SQLITE_ROLLBACK | result codes].
|
||||
*/
|
||||
#define SQLITE_OK 0 /* Successful result */
|
||||
/* beginning-of-error-codes */
|
||||
@ -2198,6 +2199,9 @@ int sqlite3_set_authorizer(
|
||||
** to signal SQLite whether or not the action is permitted. See the
|
||||
** [sqlite3_set_authorizer | authorizer documentation] for additional
|
||||
** information.
|
||||
**
|
||||
** Note that SQLITE_IGNORE is also used as a [SQLITE_ROLLBACK | return code]
|
||||
** from the [sqlite3_vtab_on_conflict()] interface.
|
||||
*/
|
||||
#define SQLITE_DENY 1 /* Abort the SQL statement with an error */
|
||||
#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */
|
||||
@ -4607,6 +4611,11 @@ struct sqlite3_module {
|
||||
void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
|
||||
void **ppArg);
|
||||
int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
|
||||
/* The methods above are in version 0 of the sqlite_module object. Those
|
||||
** below are for version 1 and greater. */
|
||||
int (*xSavepoint)(sqlite3_vtab *pVTab, int);
|
||||
int (*xRelease)(sqlite3_vtab *pVTab, int);
|
||||
int (*xRollbackTo)(sqlite3_vtab *pVTab, int);
|
||||
};
|
||||
|
||||
/*
|
||||
@ -6384,11 +6393,102 @@ int sqlite3_wal_checkpoint_v2(
|
||||
** [sqlite3_wal_checkpoint_v2()]. See the [sqlite3_wal_checkpoint_v2()]
|
||||
** documentation for additional information about the meaning and use of
|
||||
** each of these values.
|
||||
**
|
||||
** <dt>SQLITE_CONFIG_GETMUTEX</dt>
|
||||
** <dd> ^(This option takes a single argument which is a pointer to an
|
||||
*/
|
||||
#define SQLITE_CHECKPOINT_PASSIVE 0
|
||||
#define SQLITE_CHECKPOINT_FULL 1
|
||||
#define SQLITE_CHECKPOINT_RESTART 2
|
||||
|
||||
/*
|
||||
** CAPI3REF: Virtual Table Interface Configuration
|
||||
**
|
||||
** This function may be called by either the [xConnect] or [xCreate] method
|
||||
** of a [virtual table] implementation to configure
|
||||
** various facets of the virtual table interface.
|
||||
**
|
||||
** If this interface is invoked outside the context of an xConnect or
|
||||
** xCreate virtual table method then the behavior is undefined.
|
||||
**
|
||||
** At present, there is only one option that may be configured using
|
||||
** this function. (See [SQLITE_VTAB_CONSTRAINT_SUPPORT].) Further options
|
||||
** may be added in the future.
|
||||
*/
|
||||
int sqlite3_vtab_config(sqlite3*, int op, ...);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Virtual Table Configuration Options
|
||||
**
|
||||
** These macros define the various options to the
|
||||
** [sqlite3_vtab_config()] interface that [virtual table] implementations
|
||||
** can use to customize and optimize their behavior.
|
||||
**
|
||||
** <dl>
|
||||
** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT
|
||||
** <dd>If the second argument to [sqlite3_vtab_config()] is
|
||||
** SQLITE_VTAB_CONSTRAINT_SUPPORT, then SQLite expects this function to
|
||||
** have been called with three arguments, the third of which being of
|
||||
** type 'int'. If the third argument is zero, then the virtual table
|
||||
** is indicating that it does not support constraints. In this case if
|
||||
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], the entire
|
||||
** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
|
||||
** specified as part of the users SQL statement, regardless of the actual
|
||||
** ON CONFLICT mode specified.
|
||||
**
|
||||
** If the third argument passed is non-zero, then the virtual table
|
||||
** implementation must guarantee that if [xUpdate] returns
|
||||
** [SQLITE_CONSTRAINT], it does so before any modifications to internal
|
||||
** or persistent data structures have been made. If the [ON CONFLICT]
|
||||
** mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite is able to roll back
|
||||
** a statement or database transaction, and abandon or continue processing
|
||||
** the current SQL statement as appropriate. If the ON CONFLICT mode is
|
||||
** REPLACE and the [xUpdate] method returns [SQLITE_CONSTRAINT], SQLite
|
||||
** handles this as if the ON CONFLICT mode had been ABORT.
|
||||
**
|
||||
** Virtual table implementations that are required to handle OR REPLACE
|
||||
** must do so within the xUpdate method. If a call to the
|
||||
** [sqlite3_vtab_on_conflict()] function indicates that the current ON
|
||||
** CONFLICT policy is REPLACE, the virtual table implementation should
|
||||
** silently replace the appropriate rows within the xUpdate callback and
|
||||
** return SQLITE_OK. Or, if this is not possible, it may return
|
||||
** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT
|
||||
** constraint handling.
|
||||
** </dl>
|
||||
**
|
||||
*/
|
||||
#define SQLITE_VTAB_CONSTRAINT_SUPPORT 1
|
||||
|
||||
/*
|
||||
** CAPI3REF: Determine The Virtual Table Conflict Policy
|
||||
**
|
||||
** This function may only be called from within a call to the [xUpdate] method
|
||||
** of a [virtual table] implementation for an INSERT or UPDATE operation. ^The
|
||||
** value returned is one of [SQLITE_ROLLBACK], [SQLITE_IGNORE], [SQLITE_FAIL],
|
||||
** [SQLITE_ABORT], or [SQLITE_REPLACE], according to the [ON CONFLICT] mode
|
||||
** of the SQL statement that triggered the call to the [xUpdate] method of the
|
||||
** [virtual table].
|
||||
*/
|
||||
int sqlite3_vtab_on_conflict(sqlite3 *);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Conflict resolution modes
|
||||
**
|
||||
** These constants are returned by [sqlite3_vtab_on_conflict()] to
|
||||
** inform a [virtual table] implementation what the [ON CONFLICT] mode
|
||||
** is for the SQL statement being evaluated.
|
||||
**
|
||||
** Note that the [SQLITE_IGNORE] constant is also used as a potential
|
||||
** return value from the [sqlite3_set_authorizer()] callback and that
|
||||
** [SQLITE_ABORT] is also a [result code].
|
||||
*/
|
||||
#define SQLITE_ROLLBACK 1
|
||||
/* #define SQLITE_IGNORE 2 // Also used by sqlite3_authorizer() callback */
|
||||
#define SQLITE_FAIL 3
|
||||
/* #define SQLITE_ABORT 4 // Also an error code */
|
||||
#define SQLITE_REPLACE 5
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** CAPI3REF: The pre-update hook.
|
||||
|
@ -633,6 +633,7 @@ typedef struct TriggerPrg TriggerPrg;
|
||||
typedef struct TriggerStep TriggerStep;
|
||||
typedef struct UnpackedRecord UnpackedRecord;
|
||||
typedef struct VTable VTable;
|
||||
typedef struct VtabCtx VtabCtx;
|
||||
typedef struct Walker Walker;
|
||||
typedef struct WherePlan WherePlan;
|
||||
typedef struct WhereInfo WhereInfo;
|
||||
@ -812,6 +813,7 @@ struct sqlite3 {
|
||||
u8 dfltLockMode; /* Default locking-mode for attached dbs */
|
||||
signed char nextAutovac; /* Autovac setting after VACUUM if >=0 */
|
||||
u8 suppressErr; /* Do not issue error messages if true */
|
||||
u8 vtabOnConflict; /* Value to return for s3_vtab_on_conflict() */
|
||||
int nextPagesize; /* Pagesize after VACUUM if >0 */
|
||||
int nTable; /* Number of tables in the database */
|
||||
CollSeq *pDfltColl; /* The default collating sequence (BINARY) */
|
||||
@ -877,7 +879,7 @@ struct sqlite3 {
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
Hash aModule; /* populated by sqlite3_create_module() */
|
||||
Table *pVTab; /* vtab with active Connect/Create method */
|
||||
VtabCtx *pVtabCtx; /* Context for active vtab connect/create */
|
||||
VTable **aVTrans; /* Virtual tables with open transactions */
|
||||
int nVTrans; /* Allocated size of aVTrans */
|
||||
VTable *pDisconnect; /* Disconnect these in next sqlite3_prepare() */
|
||||
@ -1240,6 +1242,7 @@ struct VTable {
|
||||
Module *pMod; /* Pointer to module implementation */
|
||||
sqlite3_vtab *pVtab; /* Pointer to vtab instance */
|
||||
int nRef; /* Number of pointers to this structure */
|
||||
u8 bConstraint; /* True if constraints are supported */
|
||||
VTable *pNext; /* Next in linked list (see above) */
|
||||
};
|
||||
|
||||
@ -3052,6 +3055,7 @@ void sqlite3AutoLoadExtensions(sqlite3*);
|
||||
# define sqlite3VtabLock(X)
|
||||
# define sqlite3VtabUnlock(X)
|
||||
# define sqlite3VtabUnlockList(X)
|
||||
# define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK
|
||||
#else
|
||||
void sqlite3VtabClear(sqlite3 *db, Table*);
|
||||
int sqlite3VtabSync(sqlite3 *db, char **);
|
||||
@ -3060,6 +3064,7 @@ void sqlite3AutoLoadExtensions(sqlite3*);
|
||||
void sqlite3VtabLock(VTable *);
|
||||
void sqlite3VtabUnlock(VTable *);
|
||||
void sqlite3VtabUnlockList(sqlite3*);
|
||||
int sqlite3VtabSavepoint(sqlite3 *, int, int);
|
||||
# define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0)
|
||||
#endif
|
||||
void sqlite3VtabMakeWritable(Parse*,Table*);
|
||||
|
28
src/test1.c
28
src/test1.c
@ -14,6 +14,7 @@
|
||||
** testing of the SQLite library.
|
||||
*/
|
||||
#include "sqliteInt.h"
|
||||
#include "vdbeInt.h"
|
||||
#include "tcl.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -2326,6 +2327,32 @@ static int test_stmt_readonly(
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Usage: uses_stmt_journal STMT
|
||||
**
|
||||
** Return true if STMT uses a statement journal.
|
||||
*/
|
||||
static int uses_stmt_journal(
|
||||
void * clientData,
|
||||
Tcl_Interp *interp,
|
||||
int objc,
|
||||
Tcl_Obj *CONST objv[]
|
||||
){
|
||||
sqlite3_stmt *pStmt;
|
||||
int rc;
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_AppendResult(interp, "wrong # args: should be \"",
|
||||
Tcl_GetStringFromObj(objv[0], 0), " STMT", 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
|
||||
rc = sqlite3_stmt_readonly(pStmt);
|
||||
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(((Vdbe *)pStmt)->usesStmtJournal));
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Usage: sqlite3_reset STMT
|
||||
@ -5583,6 +5610,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
|
||||
{ "sqlite3_sql", test_sql ,0 },
|
||||
{ "sqlite3_next_stmt", test_next_stmt ,0 },
|
||||
{ "sqlite3_stmt_readonly", test_stmt_readonly ,0 },
|
||||
{ "uses_stmt_journal", uses_stmt_journal ,0 },
|
||||
|
||||
{ "sqlite3_release_memory", test_release_memory, 0},
|
||||
{ "sqlite3_soft_heap_limit", test_soft_heap_limit, 0},
|
||||
|
@ -128,6 +128,7 @@
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/*
|
||||
** Size of the write buffer used by journal files in bytes.
|
||||
|
@ -23,7 +23,8 @@ static void updateVirtualTable(
|
||||
ExprList *pChanges, /* The columns to change in the UPDATE statement */
|
||||
Expr *pRowidExpr, /* Expression used to recompute the rowid */
|
||||
int *aXRef, /* Mapping from columns of pTab to entries in pChanges */
|
||||
Expr *pWhere /* WHERE clause of the UPDATE statement */
|
||||
Expr *pWhere, /* WHERE clause of the UPDATE statement */
|
||||
int onError /* ON CONFLICT strategy */
|
||||
);
|
||||
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
||||
|
||||
@ -267,7 +268,7 @@ void sqlite3Update(
|
||||
/* Virtual tables must be handled separately */
|
||||
if( IsVirtual(pTab) ){
|
||||
updateVirtualTable(pParse, pTabList, pTab, pChanges, pRowidExpr, aXRef,
|
||||
pWhere);
|
||||
pWhere, onError);
|
||||
pWhere = 0;
|
||||
pTabList = 0;
|
||||
goto update_cleanup;
|
||||
@ -610,7 +611,8 @@ static void updateVirtualTable(
|
||||
ExprList *pChanges, /* The columns to change in the UPDATE statement */
|
||||
Expr *pRowid, /* Expression used to recompute the rowid */
|
||||
int *aXRef, /* Mapping from columns of pTab to entries in pChanges */
|
||||
Expr *pWhere /* WHERE clause of the UPDATE statement */
|
||||
Expr *pWhere, /* WHERE clause of the UPDATE statement */
|
||||
int onError /* ON CONFLICT strategy */
|
||||
){
|
||||
Vdbe *v = pParse->pVdbe; /* Virtual machine under construction */
|
||||
ExprList *pEList = 0; /* The result set of the SELECT statement */
|
||||
@ -667,6 +669,7 @@ static void updateVirtualTable(
|
||||
}
|
||||
sqlite3VtabMakeWritable(pParse, pTab);
|
||||
sqlite3VdbeAddOp4(v, OP_VUpdate, 0, pTab->nCol+2, iReg, pVTab, P4_VTAB);
|
||||
sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError);
|
||||
sqlite3MayAbort(pParse);
|
||||
sqlite3VdbeAddOp2(v, OP_Next, ephemTab, addr+1);
|
||||
sqlite3VdbeJumpHere(v, addr);
|
||||
|
35
src/vdbe.c
35
src/vdbe.c
@ -2590,6 +2590,14 @@ case OP_Savepoint: {
|
||||
}else{
|
||||
nName = sqlite3Strlen30(zName);
|
||||
|
||||
/* This call is Ok even if this savepoint is actually a transaction
|
||||
** savepoint (and therefore should not prompt xSavepoint()) callbacks.
|
||||
** If this is a transaction savepoint being opened, it is guaranteed
|
||||
** that the db->aVTrans[] array is empty. */
|
||||
assert( db->autoCommit==0 || db->nVTrans==0 );
|
||||
rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, p->iStatement);
|
||||
if( rc!=SQLITE_OK ) goto abort_due_to_error;
|
||||
|
||||
/* Create a new savepoint structure. */
|
||||
pNew = sqlite3DbMallocRaw(db, sizeof(Savepoint)+nName+1);
|
||||
if( pNew ){
|
||||
@ -2696,6 +2704,11 @@ case OP_Savepoint: {
|
||||
}else{
|
||||
db->nDeferredCons = pSavepoint->nDeferredCons;
|
||||
}
|
||||
|
||||
if( !isTransaction ){
|
||||
rc = sqlite3VtabSavepoint(db, p1, iSavepoint);
|
||||
if( rc!=SQLITE_OK ) goto abort_due_to_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2831,7 +2844,11 @@ case OP_Transaction: {
|
||||
db->nStatement++;
|
||||
p->iStatement = db->nSavepoint + db->nStatement;
|
||||
}
|
||||
rc = sqlite3BtreeBeginStmt(pBt, p->iStatement);
|
||||
|
||||
rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, p->iStatement);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3BtreeBeginStmt(pBt, p->iStatement);
|
||||
}
|
||||
|
||||
/* Store the current value of the database handles deferred constraint
|
||||
** counter. If the statement transaction needs to be rolled back,
|
||||
@ -5825,11 +5842,15 @@ case OP_VUpdate: {
|
||||
Mem **apArg;
|
||||
Mem *pX;
|
||||
|
||||
assert( pOp->p2==1 || pOp->p5==OE_Fail || pOp->p5==OE_Rollback
|
||||
|| pOp->p5==OE_Abort || pOp->p5==OE_Ignore || pOp->p5==OE_Replace
|
||||
);
|
||||
pVtab = pOp->p4.pVtab->pVtab;
|
||||
pModule = (sqlite3_module *)pVtab->pModule;
|
||||
nArg = pOp->p2;
|
||||
assert( pOp->p4type==P4_VTAB );
|
||||
if( ALWAYS(pModule->xUpdate) ){
|
||||
u8 vtabOnConflict = db->vtabOnConflict;
|
||||
apArg = p->apArg;
|
||||
pX = &aMem[pOp->p3];
|
||||
for(i=0; i<nArg; i++){
|
||||
@ -5839,13 +5860,23 @@ case OP_VUpdate: {
|
||||
apArg[i] = pX;
|
||||
pX++;
|
||||
}
|
||||
db->vtabOnConflict = pOp->p5;
|
||||
rc = pModule->xUpdate(pVtab, nArg, apArg, &rowid);
|
||||
db->vtabOnConflict = vtabOnConflict;
|
||||
importVtabErrMsg(p, pVtab);
|
||||
if( rc==SQLITE_OK && pOp->p1 ){
|
||||
assert( nArg>1 && apArg[0] && (apArg[0]->flags&MEM_Null) );
|
||||
db->lastRowid = rowid;
|
||||
}
|
||||
p->nChange++;
|
||||
if( rc==SQLITE_CONSTRAINT && pOp->p4.pVtab->bConstraint ){
|
||||
if( pOp->p5==OE_Ignore ){
|
||||
rc = SQLITE_OK;
|
||||
}else{
|
||||
p->errorAction = ((pOp->p5==OE_Replace) ? OE_Abort : pOp->p5);
|
||||
}
|
||||
}else{
|
||||
p->nChange++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -2014,6 +2014,15 @@ int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){
|
||||
db->nStatement--;
|
||||
p->iStatement = 0;
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
if( eOp==SAVEPOINT_ROLLBACK ){
|
||||
rc = sqlite3VtabSavepoint(db, SAVEPOINT_ROLLBACK, iSavepoint);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3VtabSavepoint(db, SAVEPOINT_RELEASE, iSavepoint);
|
||||
}
|
||||
}
|
||||
|
||||
/* If the statement transaction is being rolled back, also restore the
|
||||
** database handles deferred constraint counter to the value it had when
|
||||
** the statement transaction was opened. */
|
||||
@ -2931,7 +2940,7 @@ int sqlite3VdbeRecordCompare(
|
||||
|
||||
/* Compilers may complain that mem1.u.i is potentially uninitialized.
|
||||
** We could initialize it, as shown here, to silence those complaints.
|
||||
** But in fact, mem1.u.i will never actually be used initialized, and doing
|
||||
** But in fact, mem1.u.i will never actually be used uninitialized, and doing
|
||||
** the unnecessary initialization has a measurable negative performance
|
||||
** impact, since this routine is a very high runner. And so, we choose
|
||||
** to ignore the compiler warnings and leave this variable uninitialized.
|
||||
|
128
src/vtab.c
128
src/vtab.c
@ -14,6 +14,18 @@
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
#include "sqliteInt.h"
|
||||
|
||||
/*
|
||||
** Before a virtual table xCreate() or xConnect() method is invoked, the
|
||||
** sqlite3.pVtabCtx member variable is set to point to an instance of
|
||||
** this struct allocated on the stack. It is used by the implementation of
|
||||
** the sqlite3_declare_vtab() and sqlite3_vtab_config() APIs, both of which
|
||||
** are invoked only from within xCreate and xConnect methods.
|
||||
*/
|
||||
struct VtabCtx {
|
||||
Table *pTab;
|
||||
VTable *pVTable;
|
||||
};
|
||||
|
||||
/*
|
||||
** The actual function that does the work of creating a new module.
|
||||
** This function implements the sqlite3_create_module() and
|
||||
@ -42,13 +54,13 @@ static int createModule(
|
||||
pMod->xDestroy = xDestroy;
|
||||
pDel = (Module *)sqlite3HashInsert(&db->aModule, zCopy, nName, (void*)pMod);
|
||||
if( pDel && pDel->xDestroy ){
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
pDel->xDestroy(pDel->pAux);
|
||||
}
|
||||
sqlite3DbFree(db, pDel);
|
||||
if( pDel==pMod ){
|
||||
db->mallocFailed = 1;
|
||||
}
|
||||
sqlite3ResetInternalSchema(db, -1);
|
||||
}else if( xDestroy ){
|
||||
xDestroy(pAux);
|
||||
}
|
||||
@ -434,6 +446,7 @@ static int vtabCallConstructor(
|
||||
int (*xConstruct)(sqlite3*,void*,int,const char*const*,sqlite3_vtab**,char**),
|
||||
char **pzErr
|
||||
){
|
||||
VtabCtx sCtx;
|
||||
VTable *pVTable;
|
||||
int rc;
|
||||
const char *const*azArg = (const char *const*)pTab->azModuleArg;
|
||||
@ -453,12 +466,14 @@ static int vtabCallConstructor(
|
||||
pVTable->db = db;
|
||||
pVTable->pMod = pMod;
|
||||
|
||||
assert( !db->pVTab );
|
||||
assert( xConstruct );
|
||||
db->pVTab = pTab;
|
||||
|
||||
/* Invoke the virtual table constructor */
|
||||
assert( &db->pVtabCtx );
|
||||
assert( xConstruct );
|
||||
sCtx.pTab = pTab;
|
||||
sCtx.pVTable = pVTable;
|
||||
db->pVtabCtx = &sCtx;
|
||||
rc = xConstruct(db, pMod->pAux, nArg, azArg, &pVTable->pVtab, &zErr);
|
||||
db->pVtabCtx = 0;
|
||||
if( rc==SQLITE_NOMEM ) db->mallocFailed = 1;
|
||||
|
||||
if( SQLITE_OK!=rc ){
|
||||
@ -474,7 +489,7 @@ static int vtabCallConstructor(
|
||||
** the sqlite3_vtab object if successful. */
|
||||
pVTable->pVtab->pModule = pMod->pModule;
|
||||
pVTable->nRef = 1;
|
||||
if( db->pVTab ){
|
||||
if( sCtx.pTab ){
|
||||
const char *zFormat = "vtable constructor did not declare schema: %s";
|
||||
*pzErr = sqlite3MPrintf(db, zFormat, pTab->zName);
|
||||
sqlite3VtabUnlock(pVTable);
|
||||
@ -522,7 +537,6 @@ static int vtabCallConstructor(
|
||||
}
|
||||
|
||||
sqlite3DbFree(db, zModuleName);
|
||||
db->pVTab = 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -642,8 +656,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
|
||||
char *zErr = 0;
|
||||
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
pTab = db->pVTab;
|
||||
if( !pTab ){
|
||||
if( !db->pVtabCtx || !(pTab = db->pVtabCtx->pTab) ){
|
||||
sqlite3Error(db, SQLITE_MISUSE, 0);
|
||||
sqlite3_mutex_leave(db->mutex);
|
||||
return SQLITE_MISUSE_BKPT;
|
||||
@ -670,7 +683,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
|
||||
pParse->pNewTable->nCol = 0;
|
||||
pParse->pNewTable->aCol = 0;
|
||||
}
|
||||
db->pVTab = 0;
|
||||
db->pVtabCtx->pTab = 0;
|
||||
}else{
|
||||
sqlite3Error(db, SQLITE_ERROR, (zErr ? "%s" : 0), zErr);
|
||||
sqlite3DbFree(db, zErr);
|
||||
@ -822,7 +835,6 @@ int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){
|
||||
if( pModule->xBegin ){
|
||||
int i;
|
||||
|
||||
|
||||
/* If pVtab is already in the aVTrans array, return early */
|
||||
for(i=0; i<db->nVTrans; i++){
|
||||
if( db->aVTrans[i]==pVTab ){
|
||||
@ -839,6 +851,49 @@ int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Invoke either the xSavepoint, xRollbackTo or xRelease method of all
|
||||
** virtual tables that currently have an open transaction. Pass iSavepoint
|
||||
** as the second argument to the virtual table method invoked.
|
||||
**
|
||||
** If op is SAVEPOINT_BEGIN, the xSavepoint method is invoked. If it is
|
||||
** SAVEPOINT_ROLLBACK, the xRollbackTo method. Otherwise, if op is
|
||||
** SAVEPOINT_RELEASE, then the xRelease method of each virtual table with
|
||||
** an open transaction is invoked.
|
||||
**
|
||||
** If any virtual table method returns an error code other than SQLITE_OK,
|
||||
** processing is abandoned and the error returned to the caller of this
|
||||
** function immediately. If all calls to virtual table methods are successful,
|
||||
** SQLITE_OK is returned.
|
||||
*/
|
||||
int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
assert( op==SAVEPOINT_RELEASE||op==SAVEPOINT_ROLLBACK||op==SAVEPOINT_BEGIN );
|
||||
if( db->aVTrans ){
|
||||
int i;
|
||||
for(i=0; rc==SQLITE_OK && i<db->nVTrans; i++){
|
||||
const sqlite3_module *pMod = db->aVTrans[i]->pMod->pModule;
|
||||
if( pMod->iVersion>=1 ){
|
||||
int (*xMethod)(sqlite3_vtab *, int);
|
||||
switch( op ){
|
||||
case SAVEPOINT_BEGIN:
|
||||
xMethod = pMod->xSavepoint;
|
||||
break;
|
||||
case SAVEPOINT_ROLLBACK:
|
||||
xMethod = pMod->xRollbackTo;
|
||||
break;
|
||||
default:
|
||||
xMethod = pMod->xRelease;
|
||||
break;
|
||||
}
|
||||
if( xMethod ) rc = xMethod(db->aVTrans[i]->pVtab, iSavepoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** The first parameter (pDef) is a function implementation. The
|
||||
** second parameter (pExpr) is the first argument to this function.
|
||||
@ -937,4 +992,55 @@ void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the ON CONFLICT resolution mode in effect for the virtual
|
||||
** table update operation currently in progress.
|
||||
**
|
||||
** The results of this routine are undefined unless it is called from
|
||||
** within an xUpdate method.
|
||||
*/
|
||||
int sqlite3_vtab_on_conflict(sqlite3 *db){
|
||||
static const unsigned char aMap[] = {
|
||||
SQLITE_ROLLBACK, SQLITE_IGNORE, SQLITE_ABORT, SQLITE_FAIL, SQLITE_REPLACE
|
||||
};
|
||||
assert( OE_Rollback==1 && OE_Abort==2 && OE_Fail==3 );
|
||||
assert( OE_Ignore==4 && OE_Replace==5 );
|
||||
assert( db->vtabOnConflict>=1 && db->vtabOnConflict<=5 );
|
||||
return (int)aMap[db->vtabOnConflict-1];
|
||||
}
|
||||
|
||||
/*
|
||||
** Call from within the xCreate() or xConnect() methods to provide
|
||||
** the SQLite core with additional information about the behavior
|
||||
** of the virtual table being implemented.
|
||||
*/
|
||||
int sqlite3_vtab_config(sqlite3 *db, int op, ...){
|
||||
va_list ap;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
|
||||
va_start(ap, op);
|
||||
switch( op ){
|
||||
case SQLITE_VTAB_CONSTRAINT_SUPPORT: {
|
||||
VtabCtx *p = db->pVtabCtx;
|
||||
if( !p ){
|
||||
rc = SQLITE_MISUSE_BKPT;
|
||||
}else{
|
||||
assert( (p->pTab->tabFlags & TF_Virtual)!=0 );
|
||||
p->pVTable->bConstraint = (u8)va_arg(ap, int);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
rc = SQLITE_MISUSE_BKPT;
|
||||
break;
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
if( rc!=SQLITE_OK ) sqlite3Error(db, rc, 0);
|
||||
sqlite3_mutex_leave(db->mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
||||
|
@ -989,9 +989,9 @@ do_execsql_test e_createtable-3.7.4 {
|
||||
SELECT quote(a), quote(b) FROM t6;
|
||||
} {1 2 'X' 3 1 4 'X' 5}
|
||||
|
||||
# EVIDENCE-OF: R-18683-56219 If the default value of a column is
|
||||
# CURRENT_TIME, CURRENT_DATE or CURRENT_DATETIME, then the value used in
|
||||
# the new row is a text representation of the current UTC date and/or
|
||||
# EVIDENCE-OF: R-15363-55230 If the default value of a column is
|
||||
# CURRENT_TIME, CURRENT_DATE or CURRENT_TIMESTAMP, then the value used
|
||||
# in the new row is a text representation of the current UTC date and/or
|
||||
# time.
|
||||
#
|
||||
# This is difficult to test literally without knowing what time the
|
||||
|
@ -24,6 +24,8 @@ ifcapable !fts3 {
|
||||
return
|
||||
}
|
||||
|
||||
set ::testprefix fts3token
|
||||
|
||||
proc escape_string {str} {
|
||||
set out ""
|
||||
foreach char [split $str ""] {
|
||||
@ -165,10 +167,21 @@ ifcapable icu {
|
||||
do_icu_test fts3token-4.6 MiddleOfTheOcean $input $output
|
||||
do_icu_test fts3token-4.7 th_TH $input $output
|
||||
do_icu_test fts3token-4.8 en_US $input $output
|
||||
|
||||
do_execsql_test 5.1 {
|
||||
CREATE VIRTUAL TABLE x1 USING fts3(name,TOKENIZE icu en_US);
|
||||
insert into x1 (name) values (NULL);
|
||||
insert into x1 (name) values (NULL);
|
||||
delete from x1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
do_test fts3token-internal {
|
||||
execsql { SELECT fts3_tokenizer_internal_test() }
|
||||
} {ok}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
|
@ -38,10 +38,10 @@ do_execsql_test 1.2 {
|
||||
six 1 1 three 4 6 two 1 1
|
||||
}
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
DELETE FROM t1;
|
||||
do_execsql_test 1.3.1 { DELETE FROM t1; }
|
||||
do_execsql_test 1.3.2 {
|
||||
SELECT term, documents, occurrences FROM terms WHERE col = '*';
|
||||
} {}
|
||||
}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
INSERT INTO t1 VALUES('a b a b a b a');
|
||||
|
139
test/fts3conf.test
Normal file
139
test/fts3conf.test
Normal file
@ -0,0 +1,139 @@
|
||||
# 2011 April 25
|
||||
#
|
||||
# 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 testing the FTS3 module.
|
||||
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix fts3conf
|
||||
|
||||
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
|
||||
ifcapable !fts3 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
proc fts3_integrity {tn db tbl} {
|
||||
|
||||
if {[sqlite3_get_autocommit $db]==0} {
|
||||
error "fts3_integrity does not work with an open transaction"
|
||||
}
|
||||
|
||||
set sql [db one {SELECT sql FROM sqlite_master WHERE name = $tbl}]
|
||||
regexp -nocase {[^(]* using (.*)} $sql -> tail
|
||||
set cols [list]
|
||||
$db eval "PRAGMA table_info($tbl)" {
|
||||
lappend cols $name
|
||||
}
|
||||
set cols [join [concat docid $cols] ,]
|
||||
|
||||
$db eval [subst {
|
||||
CREATE VIRTUAL TABLE fts3check USING fts4term($tbl);
|
||||
CREATE VIRTUAL TABLE temp.fts3check2 USING $tail;
|
||||
INSERT INTO temp.fts3check2($cols) SELECT docid, * FROM $tbl;
|
||||
CREATE VIRTUAL TABLE temp.fts3check3 USING fts4term(fts3check2);
|
||||
}]
|
||||
|
||||
set m1 [$db one {SELECT md5sum(term, docid, col, pos) FROM fts3check}]
|
||||
set m2 [$db one {SELECT md5sum(term, docid, col, pos) FROM fts3check3}]
|
||||
|
||||
$db eval {
|
||||
DROP TABLE fts3check;
|
||||
DROP TABLE temp.fts3check2;
|
||||
DROP TABLE temp.fts3check3;
|
||||
}
|
||||
|
||||
uplevel [list do_test $tn [list set {} $m1] $m2]
|
||||
}
|
||||
|
||||
do_execsql_test 1.0.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts3(x);
|
||||
INSERT INTO t1(rowid, x) VALUES(1, 'a b c d');
|
||||
INSERT INTO t1(rowid, x) VALUES(2, 'e f g h');
|
||||
|
||||
CREATE TABLE source(a, b);
|
||||
INSERT INTO source VALUES(4, 'z');
|
||||
INSERT INTO source VALUES(2, 'y');
|
||||
}
|
||||
db_save_and_close
|
||||
|
||||
set T1 "INTO t1(rowid, x) VALUES(1, 'x')"
|
||||
set T2 "INTO t1(rowid, x) SELECT * FROM source"
|
||||
|
||||
set T3 "t1 SET docid = 2 WHERE docid = 1"
|
||||
set T4 "t1 SET docid = CASE WHEN docid = 1 THEN 4 ELSE 3 END WHERE docid <=2"
|
||||
|
||||
foreach {tn sql uses constraint data} [subst {
|
||||
1 "INSERT OR ROLLBACK $T1" 0 1 {{a b c d} {e f g h}}
|
||||
2 "INSERT OR ABORT $T1" 0 1 {{a b c d} {e f g h} {i j k l}}
|
||||
3 "INSERT OR FAIL $T1" 0 1 {{a b c d} {e f g h} {i j k l}}
|
||||
4 "INSERT OR IGNORE $T1" 0 0 {{a b c d} {e f g h} {i j k l}}
|
||||
5 "INSERT OR REPLACE $T1" 0 0 {x {e f g h} {i j k l}}
|
||||
|
||||
6 "INSERT OR ROLLBACK $T2" 1 1 {{a b c d} {e f g h}}
|
||||
7 "INSERT OR ABORT $T2" 1 1 {{a b c d} {e f g h} {i j k l}}
|
||||
8 "INSERT OR FAIL $T2" 1 1 {{a b c d} {e f g h} {i j k l} z}
|
||||
9 "INSERT OR IGNORE $T2" 1 0 {{a b c d} {e f g h} {i j k l} z}
|
||||
10 "INSERT OR REPLACE $T2" 1 0 {{a b c d} y {i j k l} z}
|
||||
|
||||
11 "UPDATE OR ROLLBACK $T3" 1 1 {{a b c d} {e f g h}}
|
||||
12 "UPDATE OR ABORT $T3" 1 1 {{a b c d} {e f g h} {i j k l}}
|
||||
13 "UPDATE OR FAIL $T3" 1 1 {{a b c d} {e f g h} {i j k l}}
|
||||
14 "UPDATE OR IGNORE $T3" 1 0 {{a b c d} {e f g h} {i j k l}}
|
||||
15 "UPDATE OR REPLACE $T3" 1 0 {{a b c d} {i j k l}}
|
||||
|
||||
16 "UPDATE OR ROLLBACK $T4" 1 1 {{a b c d} {e f g h}}
|
||||
17 "UPDATE OR ABORT $T4" 1 1 {{a b c d} {e f g h} {i j k l}}
|
||||
18 "UPDATE OR FAIL $T4" 1 1 {{e f g h} {i j k l} {a b c d}}
|
||||
19 "UPDATE OR IGNORE $T4" 1 0 {{e f g h} {i j k l} {a b c d}}
|
||||
20 "UPDATE OR REPLACE $T4" 1 0 {{e f g h} {a b c d}}
|
||||
}] {
|
||||
db_restore_and_reopen
|
||||
execsql {
|
||||
BEGIN;
|
||||
INSERT INTO t1(rowid, x) VALUES(3, 'i j k l');
|
||||
}
|
||||
set R(0) {0 {}}
|
||||
set R(1) {1 {constraint failed}}
|
||||
do_catchsql_test 1.$tn.1 $sql $R($constraint)
|
||||
do_catchsql_test 1.$tn.2 { SELECT * FROM t1 } [list 0 $data]
|
||||
catchsql COMMIT
|
||||
|
||||
fts3_integrity 1.$tn.3 db t1
|
||||
|
||||
do_test 1.$tn.4 [list sql_uses_stmt db $sql] $uses
|
||||
}
|
||||
|
||||
do_execsql_test 2.1.1 {
|
||||
DELETE FROM t1;
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES('a b c');
|
||||
SAVEPOINT a;
|
||||
INSERT INTO t1 VALUES('x y z');
|
||||
ROLLBACK TO a;
|
||||
COMMIT;
|
||||
}
|
||||
fts3_integrity 2.1.2 db t1
|
||||
|
||||
do_catchsql_test 2.2.1 {
|
||||
DELETE FROM t1;
|
||||
BEGIN;
|
||||
INSERT INTO t1(docid, x) VALUES(0, 'a b c');
|
||||
INSERT INTO t1(docid, x) VALUES(1, 'a b c');
|
||||
REPLACE INTO t1(docid, x) VALUES('zero', 'd e f');
|
||||
} {1 {datatype mismatch}}
|
||||
do_execsql_test 2.2.2 { COMMIT }
|
||||
do_execsql_test 2.2.3 { SELECT * FROM t1 } {{a b c} {a b c}}
|
||||
fts3_integrity 2.2.4 db t1
|
||||
|
||||
finish_test
|
108
test/fts3sort.test
Normal file
108
test/fts3sort.test
Normal file
@ -0,0 +1,108 @@
|
||||
# 2011 May 04
|
||||
#
|
||||
# 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 testing the FTS3 module.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
|
||||
ifcapable !fts3 {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
set testprefix fts3sort
|
||||
|
||||
proc build_database {nRow} {
|
||||
db close
|
||||
forcedelete test.db
|
||||
sqlite3 db test.db
|
||||
|
||||
set vocab [list aa ab ac ba bb bc ca cb cc da]
|
||||
expr srand(0)
|
||||
|
||||
execsql { CREATE VIRTUAL TABLE t1 USING fts4 }
|
||||
for {set i 0} {$i < $nRow} {incr i} {
|
||||
set v [expr int(rand()*1000000)]
|
||||
set doc [list]
|
||||
for {set div 1} {$div < 1000000} {set div [expr $div*10]} {
|
||||
lappend doc [lindex $vocab [expr ($v/$div) % 10]]
|
||||
}
|
||||
execsql { INSERT INTO t1 VALUES($doc) }
|
||||
}
|
||||
}
|
||||
|
||||
set nRow 1000
|
||||
do_test 1.0 {
|
||||
build_database $nRow
|
||||
execsql { SELECT count(*) FROM t1 }
|
||||
} $nRow
|
||||
|
||||
foreach {tn query} {
|
||||
1 "SELECT docid, * FROM t1"
|
||||
2 "SELECT docid, * FROM t1 WHERE t1 MATCH 'aa'"
|
||||
3 "SELECT docid, * FROM t1 WHERE t1 MATCH 'a*'"
|
||||
4 "SELECT docid, quote(matchinfo(t1)) FROM t1 WHERE t1 MATCH 'a*'"
|
||||
5 "SELECT docid, quote(matchinfo(t1,'pcnxals')) FROM t1 WHERE t1 MATCH 'b*'"
|
||||
6 "SELECT docid, * FROM t1 WHERE t1 MATCH 'a* b* c*'"
|
||||
7 "SELECT docid, * FROM t1 WHERE t1 MATCH 'aa OR da'"
|
||||
8 "SELECT docid, * FROM t1 WHERE t1 MATCH 'nosuchtoken'"
|
||||
9 "SELECT docid, snippet(t1) FROM t1 WHERE t1 MATCH 'aa OR da'"
|
||||
10 "SELECT docid, snippet(t1) FROM t1 WHERE t1 MATCH 'aa OR nosuchtoken'"
|
||||
} {
|
||||
|
||||
unset -nocomplain A B C D
|
||||
set A_list [list]
|
||||
set B_list [list]
|
||||
set C_list [list]
|
||||
set D_list [list]
|
||||
|
||||
unset -nocomplain X
|
||||
db eval "$query ORDER BY rowid ASC" X {
|
||||
set A($X(docid)) [array get X]
|
||||
lappend A_list $X(docid)
|
||||
}
|
||||
unset -nocomplain X
|
||||
db eval "$query ORDER BY rowid DESC" X {
|
||||
set B($X(docid)) [array get X]
|
||||
lappend B_list $X(docid)
|
||||
}
|
||||
unset -nocomplain X
|
||||
db eval "$query ORDER BY docid ASC" X {
|
||||
set C($X(docid)) [array get X]
|
||||
lappend C_list $X(docid)
|
||||
}
|
||||
unset -nocomplain X
|
||||
db eval "$query ORDER BY docid DESC" X {
|
||||
set D($X(docid)) [array get X]
|
||||
lappend D_list $X(docid)
|
||||
}
|
||||
|
||||
do_test 1.$tn.1 { set A_list } [lsort -integer -increasing $A_list]
|
||||
do_test 1.$tn.2 { set B_list } [lsort -integer -decreasing $B_list]
|
||||
do_test 1.$tn.3 { set C_list } [lsort -integer -increasing $C_list]
|
||||
do_test 1.$tn.4 { set D_list } [lsort -integer -decreasing $D_list]
|
||||
|
||||
unset -nocomplain DATA
|
||||
unset -nocomplain X
|
||||
db eval "$query" X {
|
||||
set DATA($X(docid)) [array get X]
|
||||
}
|
||||
|
||||
do_test 1.$tn.5 { lsort [array get A] } [lsort [array get DATA]]
|
||||
do_test 1.$tn.6 { lsort [array get B] } [lsort [array get DATA]]
|
||||
do_test 1.$tn.7 { lsort [array get C] } [lsort [array get DATA]]
|
||||
do_test 1.$tn.8 { lsort [array get D] } [lsort [array get DATA]]
|
||||
}
|
||||
|
||||
finish_test
|
@ -326,4 +326,64 @@ do_test insert4-6.7 {
|
||||
}
|
||||
} {1 {constraint failed}}
|
||||
|
||||
# Ticket [6284df89debdfa61db8073e062908af0c9b6118e]
|
||||
# Disable the xfer optimization if the destination table contains
|
||||
# a foreign key constraint
|
||||
#
|
||||
ifcapable foreignkey {
|
||||
do_test insert4-7.1 {
|
||||
set ::sqlite3_xferopt_count 0
|
||||
execsql {
|
||||
CREATE TABLE t7a(x INTEGER PRIMARY KEY); INSERT INTO t7a VALUES(123);
|
||||
CREATE TABLE t7b(y INTEGER REFERENCES t7a);
|
||||
CREATE TABLE t7c(z INT); INSERT INTO t7c VALUES(234);
|
||||
INSERT INTO t7b SELECT * FROM t7c;
|
||||
SELECT * FROM t7b;
|
||||
}
|
||||
} {234}
|
||||
do_test insert4-7.2 {
|
||||
set ::sqlite3_xferopt_count
|
||||
} {1}
|
||||
do_test insert4-7.3 {
|
||||
set ::sqlite3_xferopt_count 0
|
||||
execsql {
|
||||
DELETE FROM t7b;
|
||||
PRAGMA foreign_keys=ON;
|
||||
}
|
||||
catchsql {
|
||||
INSERT INTO t7b SELECT * FROM t7c;
|
||||
}
|
||||
} {1 {foreign key constraint failed}}
|
||||
do_test insert4-7.4 {
|
||||
execsql {SELECT * FROM t7b}
|
||||
} {}
|
||||
do_test insert4-7.5 {
|
||||
set ::sqlite3_xferopt_count
|
||||
} {0}
|
||||
do_test insert4-7.6 {
|
||||
set ::sqlite3_xferopt_count 0
|
||||
execsql {
|
||||
DELETE FROM t7b; DELETE FROM t7c;
|
||||
INSERT INTO t7c VALUES(123);
|
||||
INSERT INTO t7b SELECT * FROM t7c;
|
||||
SELECT * FROM t7b;
|
||||
}
|
||||
} {123}
|
||||
do_test insert4-7.7 {
|
||||
set ::sqlite3_xferopt_count
|
||||
} {0}
|
||||
do_test insert4-7.7 {
|
||||
set ::sqlite3_xferopt_count 0
|
||||
execsql {
|
||||
PRAGMA foreign_keys=OFF;
|
||||
DELETE FROM t7b;
|
||||
INSERT INTO t7b SELECT * FROM t7c;
|
||||
SELECT * FROM t7b;
|
||||
}
|
||||
} {123}
|
||||
do_test insert4-7.8 {
|
||||
set ::sqlite3_xferopt_count
|
||||
} {1}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
@ -753,6 +753,17 @@ proc integrity_check {name {db db}} {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Return true if the SQL statement passed as the second argument uses a
|
||||
# statement transaction.
|
||||
#
|
||||
proc sql_uses_stmt {db sql} {
|
||||
set stmt [sqlite3_prepare $db $sql -1 dummy]
|
||||
set uses [uses_stmt_journal $stmt]
|
||||
sqlite3_finalize $stmt
|
||||
return $uses
|
||||
}
|
||||
|
||||
proc fix_ifcapable_expr {expr} {
|
||||
set ret ""
|
||||
set state 0
|
||||
|
134
tool/getlock.c
Normal file
134
tool/getlock.c
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
** This utility program looks at an SQLite database and determines whether
|
||||
** or not it is locked, the kind of lock, and who is holding this lock.
|
||||
**
|
||||
** This only works on unix when the posix advisory locking method is used
|
||||
** (which is the default on unix) and when the PENDING_BYTE is in its
|
||||
** usual place.
|
||||
*/
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
static void usage(const char *argv0){
|
||||
fprintf(stderr, "Usage: %s database\n", argv0);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Check for a conflicting lock. If one is found, print an this
|
||||
** on standard output using the format string given and return 1.
|
||||
** If there are no conflicting locks, return 0.
|
||||
*/
|
||||
static int isLocked(
|
||||
int h, /* File descriptor to check */
|
||||
int type, /* F_RDLCK or F_WRLCK */
|
||||
unsigned int iOfst, /* First byte of the lock */
|
||||
unsigned int iCnt, /* Number of bytes in the lock range */
|
||||
const char *zType /* Type of lock */
|
||||
){
|
||||
struct flock lk;
|
||||
|
||||
memset(&lk, 0, sizeof(lk));
|
||||
lk.l_type = type;
|
||||
lk.l_whence = SEEK_SET;
|
||||
lk.l_start = iOfst;
|
||||
lk.l_len = iCnt;
|
||||
if( fcntl(h, F_GETLK, &lk)==(-1) ){
|
||||
fprintf(stderr, "fcntl(%d) failed: errno=%d\n", h, errno);
|
||||
exit(1);
|
||||
}
|
||||
if( lk.l_type==F_UNLCK ) return 0;
|
||||
printf("%s lock held by %d\n", zType, (int)lk.l_pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
** Location of locking bytes in the database file
|
||||
*/
|
||||
#define PENDING_BYTE (0x40000000)
|
||||
#define RESERVED_BYTE (PENDING_BYTE+1)
|
||||
#define SHARED_FIRST (PENDING_BYTE+2)
|
||||
#define SHARED_SIZE 510
|
||||
|
||||
/*
|
||||
** Lock locations for shared-memory locks used by WAL mode.
|
||||
*/
|
||||
#define SHM_BASE 120
|
||||
#define SHM_WRITE SHM_BASE
|
||||
#define SHM_CHECKPOINT (SHM_BASE+1)
|
||||
#define SHM_RECOVER (SHM_BASE+2)
|
||||
#define SHM_READ_FIRST (SHM_BASE+3)
|
||||
#define SHM_READ_SIZE 5
|
||||
|
||||
|
||||
int main(int argc, char **argv){
|
||||
int hDb; /* File descriptor for the open database file */
|
||||
int hShm; /* File descriptor for WAL shared-memory file */
|
||||
char *zShm; /* Name of the shared-memory file for WAL mode */
|
||||
ssize_t got; /* Bytes read from header */
|
||||
int isWal; /* True if in WAL mode */
|
||||
int nName; /* Length of filename */
|
||||
unsigned char aHdr[100]; /* Database header */
|
||||
int nLock = 0; /* Number of locks held */
|
||||
int i; /* Loop counter */
|
||||
|
||||
if( argc!=2 ) usage(argv[0]);
|
||||
hDb = open(argv[1], O_RDONLY, 0);
|
||||
if( hDb<0 ){
|
||||
fprintf(stderr, "cannot open %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Make sure we are dealing with an database file */
|
||||
got = read(hDb, aHdr, 100);
|
||||
if( got!=100 || memcmp(aHdr, "SQLite format 3",16)!=0 ){
|
||||
fprintf(stderr, "not an SQLite database: %s\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* First check for an exclusive lock */
|
||||
if( isLocked(hDb, F_RDLCK, SHARED_FIRST, SHARED_SIZE, "EXCLUSIVE") ){
|
||||
return 0;
|
||||
}
|
||||
isWal = aHdr[18]==2;
|
||||
if( isWal==0 ){
|
||||
/* Rollback mode */
|
||||
if( isLocked(hDb, F_RDLCK, PENDING_BYTE, 1, "PENDING") ) return 0;
|
||||
if( isLocked(hDb, F_RDLCK, RESERVED_BYTE, 1, "RESERVED") ) return 0;
|
||||
if( isLocked(hDb, F_WRLCK, SHARED_FIRST, SHARED_SIZE, "SHARED") ){
|
||||
return 0;
|
||||
}
|
||||
}else{
|
||||
/* WAL mode */
|
||||
nName = (int)strlen(argv[1]);
|
||||
zShm = malloc( nName + 100 );
|
||||
if( zShm==0 ){
|
||||
fprintf(stderr, "out of memory\n");
|
||||
exit(1);
|
||||
}
|
||||
memcpy(zShm, argv[1], nName);
|
||||
memcpy(&zShm[nName], "-shm", 5);
|
||||
hShm = open(zShm, O_RDONLY, 0);
|
||||
if( hShm<0 ){
|
||||
fprintf(stderr, "cannot open %s\n", zShm);
|
||||
return 1;
|
||||
}
|
||||
if( isLocked(hShm, F_RDLCK, SHM_RECOVER, 1, "WAL-RECOVERY") ){
|
||||
return 0;
|
||||
}
|
||||
nLock += isLocked(hShm, F_RDLCK, SHM_CHECKPOINT, 1, "WAL-CHECKPOINT");
|
||||
nLock += isLocked(hShm, F_RDLCK, SHM_WRITE, 1, "WAL-WRITE");
|
||||
for(i=0; i<SHM_READ_SIZE; i++){
|
||||
nLock += isLocked(hShm, F_WRLCK, SHM_READ_FIRST+i, 1, "WAL-READ");
|
||||
}
|
||||
}
|
||||
if( nLock==0 ){
|
||||
printf("file is not locked\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -200,7 +200,7 @@ do_test shell1-1.15.3 {
|
||||
# -version show SQLite version
|
||||
do_test shell1-1.16.1 {
|
||||
catchcmd "-version test.db" ""
|
||||
} {0 3.7.6.1}
|
||||
} {0 3.7.7}
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# Test cases shell1-2.*: Basic "dot" command token parsing.
|
||||
@ -711,4 +711,9 @@ do_test shell1-3.27.4 {
|
||||
catchcmd "test.db" ".timer OFF BAD"
|
||||
} {1 {Error: unknown command or invalid arguments: "timer". Enter ".help" for help}}
|
||||
|
||||
do_test shell1-3-28.1 {
|
||||
catchcmd test.db \
|
||||
".log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');"
|
||||
} "0 {(123) hello\n456}"
|
||||
|
||||
puts "CLI tests completed successfully"
|
||||
|
Reference in New Issue
Block a user