mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-26 11:31:37 +08:00
Merge remote-tracking branch 'origin/master' into error_offset
# Conflicts: # src/statement.rs
This commit is contained in:
commit
cfcbb56fce
@ -72,6 +72,7 @@ bundled-windows = ["libsqlite3-sys/bundled-windows"]
|
|||||||
with-asan = ["libsqlite3-sys/with-asan"]
|
with-asan = ["libsqlite3-sys/with-asan"]
|
||||||
column_decltype = []
|
column_decltype = []
|
||||||
wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
|
wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
|
||||||
|
# Note: doesn't support 32-bit.
|
||||||
winsqlite3 = ["libsqlite3-sys/winsqlite3"]
|
winsqlite3 = ["libsqlite3-sys/winsqlite3"]
|
||||||
|
|
||||||
# Helper feature for enabling most non-build-related optional features
|
# Helper feature for enabling most non-build-related optional features
|
||||||
@ -109,7 +110,7 @@ bundled-full = ["modern-full", "bundled"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
time = { version = "0.3.0", features = ["formatting", "macros", "parsing"], optional = true }
|
time = { version = "0.3.0", features = ["formatting", "macros", "parsing"], optional = true }
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
hashlink = "0.7"
|
hashlink = "0.8"
|
||||||
chrono = { version = "0.4", optional = true, default-features = false, features = ["clock"] }
|
chrono = { version = "0.4", optional = true, default-features = false, features = ["clock"] }
|
||||||
serde_json = { version = "1.0", optional = true }
|
serde_json = { version = "1.0", optional = true }
|
||||||
csv = { version = "1.1", optional = true }
|
csv = { version = "1.1", optional = true }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.24.1"
|
version = "0.24.2"
|
||||||
authors = ["The rusqlite developers"]
|
authors = ["The rusqlite developers"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
repository = "https://github.com/rusqlite/rusqlite"
|
repository = "https://github.com/rusqlite/rusqlite"
|
||||||
@ -35,8 +35,13 @@ session = ["preupdate_hook", "buildtime_bindgen"]
|
|||||||
in_gecko = []
|
in_gecko = []
|
||||||
with-asan = []
|
with-asan = []
|
||||||
wasm32-wasi-vfs = []
|
wasm32-wasi-vfs = []
|
||||||
|
|
||||||
# lowest version shipped with Windows 10.0.10586 was 3.8.8.3
|
# lowest version shipped with Windows 10.0.10586 was 3.8.8.3
|
||||||
winsqlite3 = ["min_sqlite_version_3_7_16", "buildtime_bindgen"]
|
#
|
||||||
|
# Note that because `winsqlite3.dll` exports SQLite functions using a atypical
|
||||||
|
# ABI on 32-bit systems, this is currently unsupported on these. This may change
|
||||||
|
# in the future.
|
||||||
|
winsqlite3 = ["min_sqlite_version_3_7_16"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
openssl-sys = { version = "0.9", optional = true }
|
openssl-sys = { version = "0.9", optional = true }
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
/* automatically generated by rust-bindgen 0.59.2 */
|
/* automatically generated by rust-bindgen 0.59.2 */
|
||||||
|
|
||||||
pub const SQLITE_VERSION: &[u8; 7usize] = b"3.38.1\0";
|
pub const SQLITE_VERSION: &[u8; 7usize] = b"3.38.2\0";
|
||||||
pub const SQLITE_VERSION_NUMBER: i32 = 3038001;
|
pub const SQLITE_VERSION_NUMBER: i32 = 3038002;
|
||||||
pub const SQLITE_SOURCE_ID: &[u8; 85usize] =
|
pub const SQLITE_SOURCE_ID: &[u8; 85usize] =
|
||||||
b"2022-03-12 13:37:29 38c210fdd258658321c85ec9c01a072fda3ada94540e3239d29b34dc547a8cbc\0";
|
b"2022-03-26 13:51:10 d33c709cc0af66bc5b6dc6216eba9f1f0b40960b9ae83694c986fbf4c1d6f08f\0";
|
||||||
pub const SQLITE_OK: i32 = 0;
|
pub const SQLITE_OK: i32 = 0;
|
||||||
pub const SQLITE_ERROR: i32 = 1;
|
pub const SQLITE_ERROR: i32 = 1;
|
||||||
pub const SQLITE_INTERNAL: i32 = 2;
|
pub const SQLITE_INTERNAL: i32 = 2;
|
||||||
|
145
libsqlite3-sys/sqlite3/sqlite3.c
vendored
145
libsqlite3-sys/sqlite3/sqlite3.c
vendored
@ -1,6 +1,6 @@
|
|||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
** This file is an amalgamation of many separate C source files from SQLite
|
** This file is an amalgamation of many separate C source files from SQLite
|
||||||
** version 3.38.1. By combining all the individual C code files into this
|
** version 3.38.2. By combining all the individual C code files into this
|
||||||
** single large file, the entire code can be compiled as a single translation
|
** single large file, the entire code can be compiled as a single translation
|
||||||
** unit. This allows many compilers to do optimizations that would not be
|
** unit. This allows many compilers to do optimizations that would not be
|
||||||
** possible if the files were compiled separately. Performance improvements
|
** possible if the files were compiled separately. Performance improvements
|
||||||
@ -452,9 +452,9 @@ extern "C" {
|
|||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.38.1"
|
#define SQLITE_VERSION "3.38.2"
|
||||||
#define SQLITE_VERSION_NUMBER 3038001
|
#define SQLITE_VERSION_NUMBER 3038002
|
||||||
#define SQLITE_SOURCE_ID "2022-03-12 13:37:29 38c210fdd258658321c85ec9c01a072fda3ada94540e3239d29b34dc547a8cbc"
|
#define SQLITE_SOURCE_ID "2022-03-26 13:51:10 d33c709cc0af66bc5b6dc6216eba9f1f0b40960b9ae83694c986fbf4c1d6f08f"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
@ -39691,11 +39691,17 @@ static int unixShmLock(
|
|||||||
int flags /* What to do with the lock */
|
int flags /* What to do with the lock */
|
||||||
){
|
){
|
||||||
unixFile *pDbFd = (unixFile*)fd; /* Connection holding shared memory */
|
unixFile *pDbFd = (unixFile*)fd; /* Connection holding shared memory */
|
||||||
unixShm *p = pDbFd->pShm; /* The shared memory being locked */
|
unixShm *p; /* The shared memory being locked */
|
||||||
unixShmNode *pShmNode = p->pShmNode; /* The underlying file iNode */
|
unixShmNode *pShmNode; /* The underlying file iNode */
|
||||||
int rc = SQLITE_OK; /* Result code */
|
int rc = SQLITE_OK; /* Result code */
|
||||||
u16 mask; /* Mask of locks to take or release */
|
u16 mask; /* Mask of locks to take or release */
|
||||||
int *aLock = pShmNode->aLock;
|
int *aLock;
|
||||||
|
|
||||||
|
p = pDbFd->pShm;
|
||||||
|
if( p==0 ) return SQLITE_IOERR_SHMLOCK;
|
||||||
|
pShmNode = p->pShmNode;
|
||||||
|
if( NEVER(pShmNode==0) ) return SQLITE_IOERR_SHMLOCK;
|
||||||
|
aLock = pShmNode->aLock;
|
||||||
|
|
||||||
assert( pShmNode==pDbFd->pInode->pShmNode );
|
assert( pShmNode==pDbFd->pInode->pShmNode );
|
||||||
assert( pShmNode->pInode==pDbFd->pInode );
|
assert( pShmNode->pInode==pDbFd->pInode );
|
||||||
@ -46983,10 +46989,14 @@ static int winShmLock(
|
|||||||
winFile *pDbFd = (winFile*)fd; /* Connection holding shared memory */
|
winFile *pDbFd = (winFile*)fd; /* Connection holding shared memory */
|
||||||
winShm *p = pDbFd->pShm; /* The shared memory being locked */
|
winShm *p = pDbFd->pShm; /* The shared memory being locked */
|
||||||
winShm *pX; /* For looping over all siblings */
|
winShm *pX; /* For looping over all siblings */
|
||||||
winShmNode *pShmNode = p->pShmNode;
|
winShmNode *pShmNode;
|
||||||
int rc = SQLITE_OK; /* Result code */
|
int rc = SQLITE_OK; /* Result code */
|
||||||
u16 mask; /* Mask of locks to take or release */
|
u16 mask; /* Mask of locks to take or release */
|
||||||
|
|
||||||
|
if( p==0 ) return SQLITE_IOERR_SHMLOCK;
|
||||||
|
pShmNode = p->pShmNode;
|
||||||
|
if( NEVER(pShmNode==0) ) return SQLITE_IOERR_SHMLOCK;
|
||||||
|
|
||||||
assert( ofst>=0 && ofst+n<=SQLITE_SHM_NLOCK );
|
assert( ofst>=0 && ofst+n<=SQLITE_SHM_NLOCK );
|
||||||
assert( n>=1 );
|
assert( n>=1 );
|
||||||
assert( flags==(SQLITE_SHM_LOCK | SQLITE_SHM_SHARED)
|
assert( flags==(SQLITE_SHM_LOCK | SQLITE_SHM_SHARED)
|
||||||
@ -74993,24 +75003,6 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
|
|||||||
assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND|BTREE_PREFORMAT))==flags );
|
assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND|BTREE_PREFORMAT))==flags );
|
||||||
assert( (flags & BTREE_PREFORMAT)==0 || seekResult || pCur->pKeyInfo==0 );
|
assert( (flags & BTREE_PREFORMAT)==0 || seekResult || pCur->pKeyInfo==0 );
|
||||||
|
|
||||||
if( pCur->eState==CURSOR_FAULT ){
|
|
||||||
assert( pCur->skipNext!=SQLITE_OK );
|
|
||||||
return pCur->skipNext;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert( cursorOwnsBtShared(pCur) );
|
|
||||||
assert( (pCur->curFlags & BTCF_WriteFlag)!=0
|
|
||||||
&& pBt->inTransaction==TRANS_WRITE
|
|
||||||
&& (pBt->btsFlags & BTS_READ_ONLY)==0 );
|
|
||||||
assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) );
|
|
||||||
|
|
||||||
/* Assert that the caller has been consistent. If this cursor was opened
|
|
||||||
** expecting an index b-tree, then the caller should be inserting blob
|
|
||||||
** keys with no associated data. If the cursor was opened expecting an
|
|
||||||
** intkey table, the caller should be inserting integer keys with a
|
|
||||||
** blob of associated data. */
|
|
||||||
assert( (flags & BTREE_PREFORMAT) || (pX->pKey==0)==(pCur->pKeyInfo==0) );
|
|
||||||
|
|
||||||
/* Save the positions of any other cursors open on this table.
|
/* Save the positions of any other cursors open on this table.
|
||||||
**
|
**
|
||||||
** In some cases, the call to btreeMoveto() below is a no-op. For
|
** In some cases, the call to btreeMoveto() below is a no-op. For
|
||||||
@ -75035,6 +75027,24 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( pCur->eState>=CURSOR_REQUIRESEEK ){
|
||||||
|
rc = moveToRoot(pCur);
|
||||||
|
if( rc && rc!=SQLITE_EMPTY ) return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert( cursorOwnsBtShared(pCur) );
|
||||||
|
assert( (pCur->curFlags & BTCF_WriteFlag)!=0
|
||||||
|
&& pBt->inTransaction==TRANS_WRITE
|
||||||
|
&& (pBt->btsFlags & BTS_READ_ONLY)==0 );
|
||||||
|
assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) );
|
||||||
|
|
||||||
|
/* Assert that the caller has been consistent. If this cursor was opened
|
||||||
|
** expecting an index b-tree, then the caller should be inserting blob
|
||||||
|
** keys with no associated data. If the cursor was opened expecting an
|
||||||
|
** intkey table, the caller should be inserting integer keys with a
|
||||||
|
** blob of associated data. */
|
||||||
|
assert( (flags & BTREE_PREFORMAT) || (pX->pKey==0)==(pCur->pKeyInfo==0) );
|
||||||
|
|
||||||
if( pCur->pKeyInfo==0 ){
|
if( pCur->pKeyInfo==0 ){
|
||||||
assert( pX->pKey==0 );
|
assert( pX->pKey==0 );
|
||||||
/* If this is an insert into a table b-tree, invalidate any incrblob
|
/* If this is an insert into a table b-tree, invalidate any incrblob
|
||||||
@ -75123,8 +75133,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert( pCur->eState==CURSOR_VALID
|
assert( pCur->eState==CURSOR_VALID
|
||||||
|| (pCur->eState==CURSOR_INVALID && loc)
|
|| (pCur->eState==CURSOR_INVALID && loc) );
|
||||||
|| CORRUPT_DB );
|
|
||||||
|
|
||||||
pPage = pCur->pPage;
|
pPage = pCur->pPage;
|
||||||
assert( pPage->intKey || pX->nKey>=0 || (flags & BTREE_PREFORMAT) );
|
assert( pPage->intKey || pX->nKey>=0 || (flags & BTREE_PREFORMAT) );
|
||||||
@ -75411,12 +75420,16 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
|
|||||||
assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) );
|
assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) );
|
||||||
assert( !hasReadConflicts(p, pCur->pgnoRoot) );
|
assert( !hasReadConflicts(p, pCur->pgnoRoot) );
|
||||||
assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 );
|
assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 );
|
||||||
if( pCur->eState==CURSOR_REQUIRESEEK ){
|
if( pCur->eState!=CURSOR_VALID ){
|
||||||
rc = btreeRestoreCursorPosition(pCur);
|
if( pCur->eState>=CURSOR_REQUIRESEEK ){
|
||||||
assert( rc!=SQLITE_OK || CORRUPT_DB || pCur->eState==CURSOR_VALID );
|
rc = btreeRestoreCursorPosition(pCur);
|
||||||
if( rc || pCur->eState!=CURSOR_VALID ) return rc;
|
assert( rc!=SQLITE_OK || CORRUPT_DB || pCur->eState==CURSOR_VALID );
|
||||||
|
if( rc || pCur->eState!=CURSOR_VALID ) return rc;
|
||||||
|
}else{
|
||||||
|
return SQLITE_CORRUPT_BKPT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assert( CORRUPT_DB || pCur->eState==CURSOR_VALID );
|
assert( pCur->eState==CURSOR_VALID );
|
||||||
|
|
||||||
iCellDepth = pCur->iPage;
|
iCellDepth = pCur->iPage;
|
||||||
iCellIdx = pCur->ix;
|
iCellIdx = pCur->ix;
|
||||||
@ -125098,7 +125111,7 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(i=j=0; i<pTab->nCol; i++){
|
for(i=j=0; i<pTab->nCol; i++){
|
||||||
assert( pTab->aCol[i].affinity!=0 );
|
assert( pTab->aCol[i].affinity!=0 || sqlite3VdbeParser(v)->nErr>0 );
|
||||||
if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){
|
if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){
|
||||||
zColAff[j++] = pTab->aCol[i].affinity;
|
zColAff[j++] = pTab->aCol[i].affinity;
|
||||||
}
|
}
|
||||||
@ -133539,7 +133552,7 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl
|
|||||||
sqlite3ResetAllSchemasOfConnection(db);
|
sqlite3ResetAllSchemasOfConnection(db);
|
||||||
pDb = &db->aDb[iDb];
|
pDb = &db->aDb[iDb];
|
||||||
}else
|
}else
|
||||||
if( rc==SQLITE_OK || (db->flags&SQLITE_NoSchemaError)){
|
if( rc==SQLITE_OK || ((db->flags&SQLITE_NoSchemaError) && rc!=SQLITE_NOMEM)){
|
||||||
/* Hack: If the SQLITE_NoSchemaError flag is set, then consider
|
/* Hack: If the SQLITE_NoSchemaError flag is set, then consider
|
||||||
** the schema loaded, even if errors (other than OOM) occurred. In
|
** the schema loaded, even if errors (other than OOM) occurred. In
|
||||||
** this situation the current sqlite3_prepare() operation will fail,
|
** this situation the current sqlite3_prepare() operation will fail,
|
||||||
@ -146285,6 +146298,7 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
|
|||||||
|
|
||||||
sqlite3ParseObjectInit(&sParse, db);
|
sqlite3ParseObjectInit(&sParse, db);
|
||||||
sParse.eParseMode = PARSE_MODE_DECLARE_VTAB;
|
sParse.eParseMode = PARSE_MODE_DECLARE_VTAB;
|
||||||
|
sParse.disableTriggers = 1;
|
||||||
/* We should never be able to reach this point while loading the
|
/* We should never be able to reach this point while loading the
|
||||||
** schema. Nevertheless, defend against that (turn off db->init.busy)
|
** schema. Nevertheless, defend against that (turn off db->init.busy)
|
||||||
** in case a bug arises. */
|
** in case a bug arises. */
|
||||||
@ -154582,8 +154596,17 @@ static void whereLoopOutputAdjust(
|
|||||||
/* If there are extra terms in the WHERE clause not used by an index
|
/* If there are extra terms in the WHERE clause not used by an index
|
||||||
** that depend only on the table being scanned, and that will tend to
|
** that depend only on the table being scanned, and that will tend to
|
||||||
** cause many rows to be omitted, then mark that table as
|
** cause many rows to be omitted, then mark that table as
|
||||||
** "self-culling". */
|
** "self-culling".
|
||||||
pLoop->wsFlags |= WHERE_SELFCULL;
|
**
|
||||||
|
** 2022-03-24: Self-culling only applies if either the extra terms
|
||||||
|
** are straight comparison operators that are non-true with NULL
|
||||||
|
** operand, or if the loop is not a LEFT JOIN.
|
||||||
|
*/
|
||||||
|
if( (pTerm->eOperator & 0x3f)!=0
|
||||||
|
|| (pWC->pWInfo->pTabList->a[pLoop->iTab].fg.jointype & JT_LEFT)==0
|
||||||
|
){
|
||||||
|
pLoop->wsFlags |= WHERE_SELFCULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if( pTerm->truthProb<=0 ){
|
if( pTerm->truthProb<=0 ){
|
||||||
/* If a truth probability is specified using the likelihood() hints,
|
/* If a truth probability is specified using the likelihood() hints,
|
||||||
@ -157882,6 +157905,26 @@ whereBeginError:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SQLITE_DEBUG
|
||||||
|
/*
|
||||||
|
** Return true if cursor iCur is opened by instruction k of the
|
||||||
|
** bytecode. Used inside of assert() only.
|
||||||
|
*/
|
||||||
|
static int cursorIsOpen(Vdbe *v, int iCur, int k){
|
||||||
|
while( k>=0 ){
|
||||||
|
VdbeOp *pOp = sqlite3VdbeGetOp(v,k--);
|
||||||
|
if( pOp->p1!=iCur ) continue;
|
||||||
|
if( pOp->opcode==OP_Close ) return 0;
|
||||||
|
if( pOp->opcode==OP_OpenRead ) return 1;
|
||||||
|
if( pOp->opcode==OP_OpenWrite ) return 1;
|
||||||
|
if( pOp->opcode==OP_OpenDup ) return 1;
|
||||||
|
if( pOp->opcode==OP_OpenAutoindex ) return 1;
|
||||||
|
if( pOp->opcode==OP_OpenEphemeral ) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* SQLITE_DEBUG */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Generate the end of the WHERE loop. See comments on
|
** Generate the end of the WHERE loop. See comments on
|
||||||
** sqlite3WhereBegin() for additional information.
|
** sqlite3WhereBegin() for additional information.
|
||||||
@ -158134,14 +158177,15 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
|
|||||||
){
|
){
|
||||||
int x = pOp->p2;
|
int x = pOp->p2;
|
||||||
assert( pIdx->pTable==pTab );
|
assert( pIdx->pTable==pTab );
|
||||||
|
#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
|
||||||
|
if( pOp->opcode==OP_Offset ){
|
||||||
|
/* Do not need to translate the column number */
|
||||||
|
}else
|
||||||
|
#endif
|
||||||
if( !HasRowid(pTab) ){
|
if( !HasRowid(pTab) ){
|
||||||
Index *pPk = sqlite3PrimaryKeyIndex(pTab);
|
Index *pPk = sqlite3PrimaryKeyIndex(pTab);
|
||||||
x = pPk->aiColumn[x];
|
x = pPk->aiColumn[x];
|
||||||
assert( x>=0 );
|
assert( x>=0 );
|
||||||
#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
|
|
||||||
}else if( pOp->opcode==OP_Offset ){
|
|
||||||
/* Do not need to translate the column number */
|
|
||||||
#endif
|
|
||||||
}else{
|
}else{
|
||||||
testcase( x!=sqlite3StorageColumnToTable(pTab,x) );
|
testcase( x!=sqlite3StorageColumnToTable(pTab,x) );
|
||||||
x = sqlite3StorageColumnToTable(pTab,x);
|
x = sqlite3StorageColumnToTable(pTab,x);
|
||||||
@ -158151,9 +158195,22 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
|
|||||||
pOp->p2 = x;
|
pOp->p2 = x;
|
||||||
pOp->p1 = pLevel->iIdxCur;
|
pOp->p1 = pLevel->iIdxCur;
|
||||||
OpcodeRewriteTrace(db, k, pOp);
|
OpcodeRewriteTrace(db, k, pOp);
|
||||||
|
}else{
|
||||||
|
/* Unable to translate the table reference into an index
|
||||||
|
** reference. Verify that this is harmless - that the
|
||||||
|
** table being referenced really is open.
|
||||||
|
*/
|
||||||
|
#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
|
||||||
|
assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0
|
||||||
|
|| cursorIsOpen(v,pOp->p1,k)
|
||||||
|
|| pOp->opcode==OP_Offset
|
||||||
|
);
|
||||||
|
#else
|
||||||
|
assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0
|
||||||
|
|| cursorIsOpen(v,pOp->p1,k)
|
||||||
|
);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || x>=0
|
|
||||||
|| pWInfo->eOnePass );
|
|
||||||
}else if( pOp->opcode==OP_Rowid ){
|
}else if( pOp->opcode==OP_Rowid ){
|
||||||
pOp->p1 = pLevel->iIdxCur;
|
pOp->p1 = pLevel->iIdxCur;
|
||||||
pOp->opcode = OP_IdxRowid;
|
pOp->opcode = OP_IdxRowid;
|
||||||
@ -234376,7 +234433,7 @@ static void fts5SourceIdFunc(
|
|||||||
){
|
){
|
||||||
assert( nArg==0 );
|
assert( nArg==0 );
|
||||||
UNUSED_PARAM2(nArg, apUnused);
|
UNUSED_PARAM2(nArg, apUnused);
|
||||||
sqlite3_result_text(pCtx, "fts5: 2022-03-12 13:37:29 38c210fdd258658321c85ec9c01a072fda3ada94540e3239d29b34dc547a8cbc", -1, SQLITE_TRANSIENT);
|
sqlite3_result_text(pCtx, "fts5: 2022-03-26 13:51:10 d33c709cc0af66bc5b6dc6216eba9f1f0b40960b9ae83694c986fbf4c1d6f08f", -1, SQLITE_TRANSIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
6
libsqlite3-sys/sqlite3/sqlite3.h
vendored
6
libsqlite3-sys/sqlite3/sqlite3.h
vendored
@ -146,9 +146,9 @@ extern "C" {
|
|||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.38.1"
|
#define SQLITE_VERSION "3.38.2"
|
||||||
#define SQLITE_VERSION_NUMBER 3038001
|
#define SQLITE_VERSION_NUMBER 3038002
|
||||||
#define SQLITE_SOURCE_ID "2022-03-12 13:37:29 38c210fdd258658321c85ec9c01a072fda3ada94540e3239d29b34dc547a8cbc"
|
#define SQLITE_SOURCE_ID "2022-03-26 13:51:10 d33c709cc0af66bc5b6dc6216eba9f1f0b40960b9ae83694c986fbf4c1d6f08f"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
|
@ -5,6 +5,9 @@
|
|||||||
#[cfg(feature = "bundled-sqlcipher-vendored-openssl")]
|
#[cfg(feature = "bundled-sqlcipher-vendored-openssl")]
|
||||||
extern crate openssl_sys;
|
extern crate openssl_sys;
|
||||||
|
|
||||||
|
#[cfg(all(windows, feature = "winsqlite3", target_pointer_width = "32"))]
|
||||||
|
compile_error!("The `libsqlite3-sys/winsqlite3` feature is not supported on 32 bit targets.");
|
||||||
|
|
||||||
pub use self::error::*;
|
pub use self::error::*;
|
||||||
|
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
@ -9,7 +9,7 @@ export SQLITE3_LIB_DIR="$SCRIPT_DIR/sqlite3"
|
|||||||
export SQLITE3_INCLUDE_DIR="$SQLITE3_LIB_DIR"
|
export SQLITE3_INCLUDE_DIR="$SQLITE3_LIB_DIR"
|
||||||
|
|
||||||
# Download and extract amalgamation
|
# Download and extract amalgamation
|
||||||
SQLITE=sqlite-amalgamation-3380100
|
SQLITE=sqlite-amalgamation-3380200
|
||||||
curl -O https://sqlite.org/2022/$SQLITE.zip
|
curl -O https://sqlite.org/2022/$SQLITE.zip
|
||||||
unzip -p "$SQLITE.zip" "$SQLITE/sqlite3.c" > "$SQLITE3_LIB_DIR/sqlite3.c"
|
unzip -p "$SQLITE.zip" "$SQLITE/sqlite3.c" > "$SQLITE3_LIB_DIR/sqlite3.c"
|
||||||
unzip -p "$SQLITE.zip" "$SQLITE/sqlite3.h" > "$SQLITE3_LIB_DIR/sqlite3.h"
|
unzip -p "$SQLITE.zip" "$SQLITE/sqlite3.h" > "$SQLITE3_LIB_DIR/sqlite3.h"
|
||||||
|
@ -33,7 +33,7 @@ pub enum DbConfig {
|
|||||||
SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0
|
SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0
|
||||||
/// Activates or deactivates the "reset" flag for a database connection.
|
/// Activates or deactivates the "reset" flag for a database connection.
|
||||||
/// Run VACUUM with this flag set to reset the database.
|
/// Run VACUUM with this flag set to reset the database.
|
||||||
SQLITE_DBCONFIG_RESET_DATABASE = 1009,
|
SQLITE_DBCONFIG_RESET_DATABASE = 1009, // 3.24.0
|
||||||
/// Activates or deactivates the "defensive" flag for a database connection.
|
/// Activates or deactivates the "defensive" flag for a database connection.
|
||||||
SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0
|
SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0
|
||||||
/// Activates or deactivates the "writable_schema" flag.
|
/// Activates or deactivates the "writable_schema" flag.
|
||||||
|
@ -23,6 +23,7 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
|
|||||||
|
|
||||||
#[cfg(feature = "blob")]
|
#[cfg(feature = "blob")]
|
||||||
ToSqlOutput::ZeroBlob(len) => {
|
ToSqlOutput::ZeroBlob(len) => {
|
||||||
|
// TODO sqlite3_result_zeroblob64 // 3.8.11
|
||||||
return ffi::sqlite3_result_zeroblob(ctx, len);
|
return ffi::sqlite3_result_zeroblob(ctx, len);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "array")]
|
#[cfg(feature = "array")]
|
||||||
@ -50,6 +51,7 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
|
|||||||
// TODO sqlite3_result_error
|
// TODO sqlite3_result_error
|
||||||
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
|
Err(_) => return ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_MISUSE),
|
||||||
};
|
};
|
||||||
|
// TODO sqlite3_result_text64 // 3.8.7
|
||||||
ffi::sqlite3_result_text(ctx, c_str, len, destructor);
|
ffi::sqlite3_result_text(ctx, c_str, len, destructor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,6 +62,7 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
|
|||||||
} else if length == 0 {
|
} else if length == 0 {
|
||||||
ffi::sqlite3_result_zeroblob(ctx, 0);
|
ffi::sqlite3_result_zeroblob(ctx, 0);
|
||||||
} else {
|
} else {
|
||||||
|
// TODO sqlite3_result_blob64 // 3.8.7
|
||||||
ffi::sqlite3_result_blob(
|
ffi::sqlite3_result_blob(
|
||||||
ctx,
|
ctx,
|
||||||
b.as_ptr().cast::<c_void>(),
|
b.as_ptr().cast::<c_void>(),
|
||||||
|
19
src/error.rs
19
src/error.rs
@ -364,10 +364,29 @@ impl error::Error for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
/// Returns the underlying SQLite error if this is [`Error::SqliteFailure`].
|
||||||
|
#[inline]
|
||||||
|
pub fn sqlite_error(&self) -> Option<&ffi::Error> {
|
||||||
|
match self {
|
||||||
|
Self::SqliteFailure(error, _) => Some(error),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the underlying SQLite error code if this is
|
||||||
|
/// [`Error::SqliteFailure`].
|
||||||
|
#[inline]
|
||||||
|
pub fn sqlite_error_code(&self) -> Option<ffi::ErrorCode> {
|
||||||
|
self.sqlite_error().map(|error| error.code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// These are public but not re-exported by lib.rs, so only visible within crate.
|
// These are public but not re-exported by lib.rs, so only visible within crate.
|
||||||
|
|
||||||
#[cold]
|
#[cold]
|
||||||
pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error {
|
pub fn error_from_sqlite_code(code: c_int, message: Option<String>) -> Error {
|
||||||
|
// TODO sqlite3_error_offset // 3.38.0, #1130
|
||||||
Error::SqliteFailure(ffi::Error::new(code), message)
|
Error::SqliteFailure(ffi::Error::new(code), message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +162,19 @@ impl Context<'_> {
|
|||||||
unsafe { ValueRef::from_value(arg) }
|
unsafe { ValueRef::from_value(arg) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the subtype of `idx`th argument.
|
||||||
|
///
|
||||||
|
/// # Failure
|
||||||
|
///
|
||||||
|
/// Will panic if `idx` is greater than or equal to
|
||||||
|
/// [`self.len()`](Context::len).
|
||||||
|
#[cfg(feature = "modern_sqlite")] // 3.9.0
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
pub fn get_subtype(&self, idx: usize) -> std::os::raw::c_uint {
|
||||||
|
let arg = self.args[idx];
|
||||||
|
unsafe { ffi::sqlite3_value_subtype(arg) }
|
||||||
|
}
|
||||||
|
|
||||||
/// Fetch or insert the auxiliary data associated with a particular
|
/// Fetch or insert the auxiliary data associated with a particular
|
||||||
/// parameter. This is intended to be an easier-to-use way of fetching it
|
/// parameter. This is intended to be an easier-to-use way of fetching it
|
||||||
/// compared to calling [`get_aux`](Context::get_aux) and
|
/// compared to calling [`get_aux`](Context::get_aux) and
|
||||||
@ -234,6 +247,13 @@ impl Context<'_> {
|
|||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the Subtype of an SQL function
|
||||||
|
#[cfg(feature = "modern_sqlite")] // 3.9.0
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
pub fn set_result_subtype(&self, sub_type: std::os::raw::c_uint) {
|
||||||
|
unsafe { ffi::sqlite3_result_subtype(self.ctx, sub_type) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A reference to a connection handle with a lifetime bound to something.
|
/// A reference to a connection handle with a lifetime bound to something.
|
||||||
@ -319,7 +339,7 @@ bitflags::bitflags! {
|
|||||||
/// Specifies UTF-16 using native byte order as the text encoding this SQL function prefers for its parameters.
|
/// Specifies UTF-16 using native byte order as the text encoding this SQL function prefers for its parameters.
|
||||||
const SQLITE_UTF16 = ffi::SQLITE_UTF16;
|
const SQLITE_UTF16 = ffi::SQLITE_UTF16;
|
||||||
/// Means that the function always gives the same output when the input parameters are the same.
|
/// Means that the function always gives the same output when the input parameters are the same.
|
||||||
const SQLITE_DETERMINISTIC = ffi::SQLITE_DETERMINISTIC;
|
const SQLITE_DETERMINISTIC = ffi::SQLITE_DETERMINISTIC; // 3.8.3
|
||||||
/// Means that the function may only be invoked from top-level SQL.
|
/// Means that the function may only be invoked from top-level SQL.
|
||||||
const SQLITE_DIRECTONLY = 0x0000_0008_0000; // 3.30.0
|
const SQLITE_DIRECTONLY = 0x0000_0008_0000; // 3.30.0
|
||||||
/// Indicates to SQLite that a function may call `sqlite3_value_subtype()` to inspect the sub-types of its arguments.
|
/// Indicates to SQLite that a function may call `sqlite3_value_subtype()` to inspect the sub-types of its arguments.
|
||||||
|
@ -285,7 +285,7 @@ impl<'c> AuthAction<'c> {
|
|||||||
operation: TransactionOperation::from_str(operation_str),
|
operation: TransactionOperation::from_str(operation_str),
|
||||||
savepoint_name,
|
savepoint_name,
|
||||||
},
|
},
|
||||||
#[cfg(feature = "modern_sqlite")]
|
#[cfg(feature = "modern_sqlite")] // 3.8.3
|
||||||
(ffi::SQLITE_RECURSIVE, ..) => Self::Recursive,
|
(ffi::SQLITE_RECURSIVE, ..) => Self::Recursive,
|
||||||
(code, arg1, arg2) => Self::Unknown { code, arg1, arg2 },
|
(code, arg1, arg2) => Self::Unknown { code, arg1, arg2 },
|
||||||
}
|
}
|
||||||
|
@ -222,6 +222,7 @@ impl InnerConnection {
|
|||||||
let mut c_stmt = ptr::null_mut();
|
let mut c_stmt = ptr::null_mut();
|
||||||
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
|
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
|
||||||
let mut c_tail = ptr::null();
|
let mut c_tail = ptr::null();
|
||||||
|
// TODO sqlite3_prepare_v3 (https://sqlite.org/c3ref/c_prepare_normalize.html) // 3.20.0, #728
|
||||||
#[cfg(not(feature = "unlock_notify"))]
|
#[cfg(not(feature = "unlock_notify"))]
|
||||||
let r = unsafe {
|
let r = unsafe {
|
||||||
ffi::sqlite3_prepare_v2(
|
ffi::sqlite3_prepare_v2(
|
||||||
@ -278,8 +279,15 @@ impl InnerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn changes(&self) -> usize {
|
pub fn changes(&self) -> u64 {
|
||||||
unsafe { ffi::sqlite3_changes(self.db()) as usize }
|
#[cfg(not(feature = "modern_sqlite"))]
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_changes(self.db()) as u64
|
||||||
|
}
|
||||||
|
#[cfg(feature = "modern_sqlite")] // 3.37.0
|
||||||
|
unsafe {
|
||||||
|
ffi::sqlite3_changes64(self.db()) as u64
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -310,6 +318,50 @@ impl InnerConnection {
|
|||||||
#[cfg(not(feature = "hooks"))]
|
#[cfg(not(feature = "hooks"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn remove_hooks(&mut self) {}
|
fn remove_hooks(&mut self) {}
|
||||||
|
|
||||||
|
#[cfg(feature = "modern_sqlite")] // 3.7.11
|
||||||
|
pub fn db_readonly(&self, db_name: super::DatabaseName<'_>) -> Result<bool> {
|
||||||
|
let name = db_name.as_cstring()?;
|
||||||
|
let r = unsafe { ffi::sqlite3_db_readonly(self.db, name.as_ptr()) };
|
||||||
|
match r {
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
-1 => Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||||
|
Some(format!("{:?} is not the name of a database", db_name)),
|
||||||
|
)),
|
||||||
|
_ => Err(error_from_sqlite_code(
|
||||||
|
r,
|
||||||
|
Some("Unexpected result".to_owned()),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "modern_sqlite")] // 3.37.0
|
||||||
|
pub fn txn_state(
|
||||||
|
&self,
|
||||||
|
db_name: Option<super::DatabaseName<'_>>,
|
||||||
|
) -> Result<super::transaction::TransactionState> {
|
||||||
|
let r = if let Some(ref name) = db_name {
|
||||||
|
let name = name.as_cstring()?;
|
||||||
|
unsafe { ffi::sqlite3_txn_state(self.db, name.as_ptr()) }
|
||||||
|
} else {
|
||||||
|
unsafe { ffi::sqlite3_txn_state(self.db, ptr::null()) }
|
||||||
|
};
|
||||||
|
match r {
|
||||||
|
0 => Ok(super::transaction::TransactionState::None),
|
||||||
|
1 => Ok(super::transaction::TransactionState::Read),
|
||||||
|
2 => Ok(super::transaction::TransactionState::Write),
|
||||||
|
-1 => Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||||
|
Some(format!("{:?} is not the name of a valid schema", db_name)),
|
||||||
|
)),
|
||||||
|
_ => Err(error_from_sqlite_code(
|
||||||
|
r,
|
||||||
|
Some("Unexpected result".to_owned()),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for InnerConnection {
|
impl Drop for InnerConnection {
|
||||||
|
171
src/lib.rs
171
src/lib.rs
@ -1,5 +1,9 @@
|
|||||||
//! Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to
|
//! Rusqlite is an ergonomic wrapper for using SQLite from Rust.
|
||||||
//! expose an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
|
//!
|
||||||
|
//! Historically, the API was based on the one from
|
||||||
|
//! [`rust-postgres`](https://github.com/sfackler/rust-postgres). However, the
|
||||||
|
//! two have diverged in many ways, and no compatibility between the two is
|
||||||
|
//! intended.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use rusqlite::{params, Connection, Result};
|
//! use rusqlite::{params, Connection, Result};
|
||||||
@ -346,27 +350,59 @@ impl Drop for Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Open a new connection to a SQLite database.
|
/// Open a new connection to a SQLite database. If a database does not exist
|
||||||
///
|
/// at the path, one is created.
|
||||||
/// `Connection::open(path)` is equivalent to
|
|
||||||
/// `Connection::open_with_flags(path,
|
|
||||||
/// OpenFlags::SQLITE_OPEN_READ_WRITE |
|
|
||||||
/// OpenFlags::SQLITE_OPEN_CREATE)`.
|
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # use rusqlite::{Connection, Result};
|
/// # use rusqlite::{Connection, Result};
|
||||||
/// fn open_my_db() -> Result<()> {
|
/// fn open_my_db() -> Result<()> {
|
||||||
/// let path = "./my_db.db3";
|
/// let path = "./my_db.db3";
|
||||||
/// let db = Connection::open(&path)?;
|
/// let db = Connection::open(path)?;
|
||||||
|
/// // Use the database somehow...
|
||||||
/// println!("{}", db.is_autocommit());
|
/// println!("{}", db.is_autocommit());
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// # Flags
|
||||||
|
///
|
||||||
|
/// `Connection::open(path)` is equivalent to using
|
||||||
|
/// [`Connection::open_with_flags`] with the default [`OpenFlags`]. That is,
|
||||||
|
/// it's equivalent to:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// Connection::open_with_flags(
|
||||||
|
/// path,
|
||||||
|
/// OpenFlags::SQLITE_OPEN_READ_WRITE
|
||||||
|
/// | OpenFlags::SQLITE_OPEN_CREATE
|
||||||
|
/// | OpenFlags::SQLITE_OPEN_URI
|
||||||
|
/// | OpenFlags::SQLITE_OPEN_NO_MUTEX,
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// These flags have the following effects:
|
||||||
|
///
|
||||||
|
/// - Open the database for both reading or writing.
|
||||||
|
/// - Create the database if one does not exist at the path.
|
||||||
|
/// - Allow the filename to be interpreted as a URI (see
|
||||||
|
/// <https://www.sqlite.org/uri.html#uri_filenames_in_sqlite> for
|
||||||
|
/// details).
|
||||||
|
/// - Disables the use of a per-connection mutex.
|
||||||
|
///
|
||||||
|
/// Rusqlite enforces thread-safety at compile time, so additional
|
||||||
|
/// locking is not needed and provides no benefit. (See the
|
||||||
|
/// documentation on [`OpenFlags::SQLITE_OPEN_FULL_MUTEX`] for some
|
||||||
|
/// additional discussion about this).
|
||||||
|
///
|
||||||
|
/// Most of these are also the default settings for the C API, although
|
||||||
|
/// technically the default locking behavior is controlled by the flags used
|
||||||
|
/// when compiling SQLite -- rather than let it vary, we choose `NO_MUTEX`
|
||||||
|
/// because it's a fairly clearly the best choice for users of this library.
|
||||||
|
///
|
||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if `path` cannot be converted to a C-compatible
|
/// Will return `Err` if `path` cannot be converted to a C-compatible string
|
||||||
/// string or if the underlying SQLite open call fails.
|
/// or if the underlying SQLite open call fails.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Connection> {
|
pub fn open<P: AsRef<Path>>(path: P) -> Result<Connection> {
|
||||||
let flags = OpenFlags::default();
|
let flags = OpenFlags::default();
|
||||||
@ -901,8 +937,10 @@ impl Connection {
|
|||||||
/// Return the number of rows modified, inserted or deleted by the most
|
/// Return the number of rows modified, inserted or deleted by the most
|
||||||
/// recently completed INSERT, UPDATE or DELETE statement on the database
|
/// recently completed INSERT, UPDATE or DELETE statement on the database
|
||||||
/// connection.
|
/// connection.
|
||||||
|
///
|
||||||
|
/// See <https://www.sqlite.org/c3ref/changes.html>
|
||||||
#[inline]
|
#[inline]
|
||||||
fn changes(&self) -> usize {
|
pub fn changes(&self) -> u64 {
|
||||||
self.db.borrow().changes()
|
self.db.borrow().changes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -927,6 +965,13 @@ impl Connection {
|
|||||||
pub fn cache_flush(&self) -> Result<()> {
|
pub fn cache_flush(&self) -> Result<()> {
|
||||||
self.db.borrow_mut().cache_flush()
|
self.db.borrow_mut().cache_flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determine if a database is read-only
|
||||||
|
#[cfg(feature = "modern_sqlite")] // 3.7.11
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
pub fn is_readonly(&self, db_name: DatabaseName<'_>) -> Result<bool> {
|
||||||
|
self.db.borrow().db_readonly(db_name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Connection {
|
impl fmt::Debug for Connection {
|
||||||
@ -999,40 +1044,81 @@ impl<'conn> Iterator for Batch<'conn, '_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
/// Flags for opening SQLite database connections.
|
/// Flags for opening SQLite database connections. See
|
||||||
/// See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details.
|
/// [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details.
|
||||||
|
///
|
||||||
|
/// The default open flags are `SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE
|
||||||
|
/// | SQLITE_OPEN_URI | SQLITE_OPEN_NO_MUTEX`. See [`Connection::open`] for
|
||||||
|
/// some discussion about these flags.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct OpenFlags: ::std::os::raw::c_int {
|
pub struct OpenFlags: ::std::os::raw::c_int {
|
||||||
/// The database is opened in read-only mode.
|
/// The database is opened in read-only mode.
|
||||||
/// If the database does not already exist, an error is returned.
|
/// If the database does not already exist, an error is returned.
|
||||||
const SQLITE_OPEN_READ_ONLY = ffi::SQLITE_OPEN_READONLY;
|
const SQLITE_OPEN_READ_ONLY = ffi::SQLITE_OPEN_READONLY;
|
||||||
/// The database is opened for reading and writing if possible,
|
/// The database is opened for reading and writing if possible,
|
||||||
/// or reading only if the file is write protected by the operating system.
|
/// or reading only if the file is write protected by the operating system.
|
||||||
/// In either case the database must already exist, otherwise an error is returned.
|
/// In either case the database must already exist, otherwise an error is returned.
|
||||||
const SQLITE_OPEN_READ_WRITE = ffi::SQLITE_OPEN_READWRITE;
|
const SQLITE_OPEN_READ_WRITE = ffi::SQLITE_OPEN_READWRITE;
|
||||||
/// The database is created if it does not already exist
|
/// The database is created if it does not already exist
|
||||||
const SQLITE_OPEN_CREATE = ffi::SQLITE_OPEN_CREATE;
|
const SQLITE_OPEN_CREATE = ffi::SQLITE_OPEN_CREATE;
|
||||||
/// The filename can be interpreted as a URI if this flag is set.
|
/// The filename can be interpreted as a URI if this flag is set.
|
||||||
const SQLITE_OPEN_URI = 0x0000_0040;
|
const SQLITE_OPEN_URI = 0x0000_0040;
|
||||||
/// The database will be opened as an in-memory database.
|
/// The database will be opened as an in-memory database.
|
||||||
const SQLITE_OPEN_MEMORY = 0x0000_0080;
|
const SQLITE_OPEN_MEMORY = 0x0000_0080;
|
||||||
/// The new database connection will use the "multi-thread" threading mode.
|
/// The new database connection will not use a per-connection mutex (the
|
||||||
const SQLITE_OPEN_NO_MUTEX = ffi::SQLITE_OPEN_NOMUTEX;
|
/// connection will use the "multi-thread" threading mode, in SQLite
|
||||||
/// The new database connection will use the "serialized" threading mode.
|
/// parlance).
|
||||||
const SQLITE_OPEN_FULL_MUTEX = ffi::SQLITE_OPEN_FULLMUTEX;
|
///
|
||||||
/// The database is opened shared cache enabled.
|
/// This is used by default, as proper `Send`/`Sync` usage (in
|
||||||
const SQLITE_OPEN_SHARED_CACHE = 0x0002_0000;
|
/// particular, the fact that [`Connection`] does not implement `Sync`)
|
||||||
|
/// ensures thread-safety without the need to perform locking around all
|
||||||
|
/// calls.
|
||||||
|
const SQLITE_OPEN_NO_MUTEX = ffi::SQLITE_OPEN_NOMUTEX;
|
||||||
|
/// The new database connection will use a per-connection mutex -- the
|
||||||
|
/// "serialized" threading mode, in SQLite parlance.
|
||||||
|
///
|
||||||
|
/// # Caveats
|
||||||
|
///
|
||||||
|
/// This flag should probably never be used with `rusqlite`, as we
|
||||||
|
/// ensure thread-safety statically (we implement [`Send`] and not
|
||||||
|
/// [`Sync`]). That said
|
||||||
|
///
|
||||||
|
/// Critically, even if this flag is used, the [`Connection`] is not
|
||||||
|
/// safe to use across multiple threads simultaneously. To access a
|
||||||
|
/// database from multiple threads, you should either create multiple
|
||||||
|
/// connections, one for each thread (if you have very many threads,
|
||||||
|
/// wrapping the `rusqlite::Connection` in a mutex is also reasonable).
|
||||||
|
///
|
||||||
|
/// This is both because of the additional per-connection state stored
|
||||||
|
/// by `rusqlite` (for example, the prepared statement cache), and
|
||||||
|
/// because not all of SQLites functions are fully thread safe, even in
|
||||||
|
/// serialized/`SQLITE_OPEN_FULLMUTEX` mode.
|
||||||
|
///
|
||||||
|
/// All that said, it's fairly harmless to enable this flag with
|
||||||
|
/// `rusqlite`, it will just slow things down while providing no
|
||||||
|
/// benefit.
|
||||||
|
const SQLITE_OPEN_FULL_MUTEX = ffi::SQLITE_OPEN_FULLMUTEX;
|
||||||
|
/// The database is opened with shared cache enabled.
|
||||||
|
///
|
||||||
|
/// This is frequently useful for in-memory connections, but note that
|
||||||
|
/// broadly speaking it's discouraged by SQLite itself, which states
|
||||||
|
/// "Any use of shared cache is discouraged" in the official
|
||||||
|
/// [documentation](https://www.sqlite.org/c3ref/enable_shared_cache.html).
|
||||||
|
const SQLITE_OPEN_SHARED_CACHE = 0x0002_0000;
|
||||||
/// The database is opened shared cache disabled.
|
/// The database is opened shared cache disabled.
|
||||||
const SQLITE_OPEN_PRIVATE_CACHE = 0x0004_0000;
|
const SQLITE_OPEN_PRIVATE_CACHE = 0x0004_0000;
|
||||||
/// The database filename is not allowed to be a symbolic link.
|
/// The database filename is not allowed to be a symbolic link. (3.31.0)
|
||||||
const SQLITE_OPEN_NOFOLLOW = 0x0100_0000;
|
const SQLITE_OPEN_NOFOLLOW = 0x0100_0000;
|
||||||
/// Extended result codes.
|
/// Extended result codes. (3.37.0)
|
||||||
const SQLITE_OPEN_EXRESCODE = 0x0200_0000;
|
const SQLITE_OPEN_EXRESCODE = 0x0200_0000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for OpenFlags {
|
impl Default for OpenFlags {
|
||||||
|
#[inline]
|
||||||
fn default() -> OpenFlags {
|
fn default() -> OpenFlags {
|
||||||
|
// Note: update the `Connection::open` and top-level `OpenFlags` docs if
|
||||||
|
// you change these.
|
||||||
OpenFlags::SQLITE_OPEN_READ_WRITE
|
OpenFlags::SQLITE_OPEN_READ_WRITE
|
||||||
| OpenFlags::SQLITE_OPEN_CREATE
|
| OpenFlags::SQLITE_OPEN_CREATE
|
||||||
| OpenFlags::SQLITE_OPEN_NO_MUTEX
|
| OpenFlags::SQLITE_OPEN_NO_MUTEX
|
||||||
@ -1508,15 +1594,28 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_pragma_query_row() -> Result<()> {
|
fn test_pragma_query_row() -> Result<()> {
|
||||||
let db = Connection::open_in_memory()?;
|
let db = Connection::open_in_memory()?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"memory",
|
"memory",
|
||||||
db.query_row::<String, _, _>("PRAGMA journal_mode", [], |r| r.get(0))?
|
db.query_row::<String, _, _>("PRAGMA journal_mode", [], |r| r.get(0))?
|
||||||
);
|
);
|
||||||
assert_eq!(
|
let mode = db.query_row::<String, _, _>("PRAGMA journal_mode=off", [], |r| r.get(0))?;
|
||||||
"off",
|
if cfg!(features = "bundled") {
|
||||||
db.query_row::<String, _, _>("PRAGMA journal_mode=off", [], |r| r.get(0))?
|
assert_eq!(mode, "off");
|
||||||
);
|
} else {
|
||||||
|
// Note: system SQLite on macOS defaults to "off" rather than
|
||||||
|
// "memory" for the journal mode (which cannot be changed for
|
||||||
|
// in-memory connections). This seems like it's *probably* legal
|
||||||
|
// according to the docs below, so we relax this test when not
|
||||||
|
// bundling:
|
||||||
|
//
|
||||||
|
// From https://www.sqlite.org/pragma.html#pragma_journal_mode
|
||||||
|
// > Note that the journal_mode for an in-memory database is either
|
||||||
|
// > MEMORY or OFF and can not be changed to a different value. An
|
||||||
|
// > attempt to change the journal_mode of an in-memory database to
|
||||||
|
// > any setting other than MEMORY or OFF is ignored.
|
||||||
|
assert!(mode == "memory" || mode == "off", "Got mode {:?}", mode);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2012,4 +2111,12 @@ mod test {
|
|||||||
let db = Connection::open_in_memory()?;
|
let db = Connection::open_in_memory()?;
|
||||||
db.cache_flush()
|
db.cache_flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "modern_sqlite")]
|
||||||
|
pub fn db_readonly() -> Result<()> {
|
||||||
|
let db = Connection::open_in_memory()?;
|
||||||
|
assert!(!db.is_readonly(super::MAIN_DB)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -406,18 +406,20 @@ mod test {
|
|||||||
let db = Connection::open_in_memory()?;
|
let db = Connection::open_in_memory()?;
|
||||||
let journal_mode: String =
|
let journal_mode: String =
|
||||||
db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get(0))?;
|
db.pragma_update_and_check(None, "journal_mode", "OFF", |row| row.get(0))?;
|
||||||
assert_eq!("off", &journal_mode);
|
assert!(
|
||||||
|
journal_mode == "off" || journal_mode == "memory",
|
||||||
|
"mode: {:?}",
|
||||||
|
journal_mode,
|
||||||
|
);
|
||||||
// Sanity checks to ensure the move to a generic `ToSql` wasn't breaking
|
// Sanity checks to ensure the move to a generic `ToSql` wasn't breaking
|
||||||
assert_eq!(
|
let mode = db
|
||||||
"off",
|
.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row.get::<_, String>(0))?;
|
||||||
db.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row
|
assert!(mode == "off" || mode == "memory", "mode: {:?}", mode);
|
||||||
.get::<_, String>(0))?,
|
|
||||||
);
|
|
||||||
let param: &dyn crate::ToSql = &"OFF";
|
let param: &dyn crate::ToSql = &"OFF";
|
||||||
assert_eq!(
|
let mode =
|
||||||
"off",
|
db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?;
|
||||||
db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?,
|
assert!(mode == "off" || mode == "memory", "mode: {:?}", mode);
|
||||||
);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,6 +224,14 @@ impl RawStatement {
|
|||||||
pub fn tail(&self) -> usize {
|
pub fn tail(&self) -> usize {
|
||||||
self.tail
|
self.tail
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[cfg(feature = "modern_sqlite")] // 3.28.0
|
||||||
|
pub fn is_explain(&self) -> i32 {
|
||||||
|
unsafe { ffi::sqlite3_stmt_isexplain(self.ptr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO sqlite3_normalized_sql (https://sqlite.org/c3ref/expanded_sql.html) // 3.27.0 + SQLITE_ENABLE_NORMALIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for RawStatement {
|
impl Drop for RawStatement {
|
||||||
|
@ -132,6 +132,7 @@ impl Statement<'_> {
|
|||||||
/// Will return `Err` if binding parameters fails, the executed statement
|
/// Will return `Err` if binding parameters fails, the executed statement
|
||||||
/// returns rows (in which case `query` should be used instead), or the
|
/// returns rows (in which case `query` should be used instead), or the
|
||||||
/// underlying SQLite call fails.
|
/// underlying SQLite call fails.
|
||||||
|
#[doc(hidden)]
|
||||||
#[deprecated = "You can use `execute` with named params now."]
|
#[deprecated = "You can use `execute` with named params now."]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
|
pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
|
||||||
@ -267,6 +268,7 @@ impl Statement<'_> {
|
|||||||
/// # Failure
|
/// # Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails.
|
/// Will return `Err` if binding parameters fails.
|
||||||
|
#[doc(hidden)]
|
||||||
#[deprecated = "You can use `query` with named params now."]
|
#[deprecated = "You can use `query` with named params now."]
|
||||||
pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'_>> {
|
pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'_>> {
|
||||||
self.query(params)
|
self.query(params)
|
||||||
@ -344,6 +346,7 @@ impl Statement<'_> {
|
|||||||
/// ## Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails.
|
/// Will return `Err` if binding parameters fails.
|
||||||
|
#[doc(hidden)]
|
||||||
#[deprecated = "You can use `query_map` with named params now."]
|
#[deprecated = "You can use `query_map` with named params now."]
|
||||||
pub fn query_map_named<T, F>(
|
pub fn query_map_named<T, F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -436,6 +439,7 @@ impl Statement<'_> {
|
|||||||
/// ## Failure
|
/// ## Failure
|
||||||
///
|
///
|
||||||
/// Will return `Err` if binding parameters fails.
|
/// Will return `Err` if binding parameters fails.
|
||||||
|
#[doc(hidden)]
|
||||||
#[deprecated = "You can use `query_and_then` with named params now."]
|
#[deprecated = "You can use `query_and_then` with named params now."]
|
||||||
pub fn query_and_then_named<T, E, F>(
|
pub fn query_and_then_named<T, E, F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -503,6 +507,7 @@ impl Statement<'_> {
|
|||||||
///
|
///
|
||||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
||||||
/// or if the underlying SQLite call fails.
|
/// or if the underlying SQLite call fails.
|
||||||
|
#[doc(hidden)]
|
||||||
#[deprecated = "You can use `query_row` with named params now."]
|
#[deprecated = "You can use `query_row` with named params now."]
|
||||||
pub fn query_row_named<T, F>(&mut self, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
|
pub fn query_row_named<T, F>(&mut self, params: &[(&str, &dyn ToSql)], f: F) -> Result<T>
|
||||||
where
|
where
|
||||||
@ -727,6 +732,7 @@ impl Statement<'_> {
|
|||||||
|
|
||||||
#[cfg(feature = "blob")]
|
#[cfg(feature = "blob")]
|
||||||
ToSqlOutput::ZeroBlob(len) => {
|
ToSqlOutput::ZeroBlob(len) => {
|
||||||
|
// TODO sqlite3_bind_zeroblob64 // 3.8.11
|
||||||
return self
|
return self
|
||||||
.conn
|
.conn
|
||||||
.decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col as c_int, len) });
|
.decode_result(unsafe { ffi::sqlite3_bind_zeroblob(ptr, col as c_int, len) });
|
||||||
@ -750,6 +756,7 @@ impl Statement<'_> {
|
|||||||
ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) },
|
ValueRef::Real(r) => unsafe { ffi::sqlite3_bind_double(ptr, col as c_int, r) },
|
||||||
ValueRef::Text(s) => unsafe {
|
ValueRef::Text(s) => unsafe {
|
||||||
let (c_str, len, destructor) = str_for_sqlite(s)?;
|
let (c_str, len, destructor) = str_for_sqlite(s)?;
|
||||||
|
// TODO sqlite3_bind_text64 // 3.8.7
|
||||||
ffi::sqlite3_bind_text(ptr, col as c_int, c_str, len, destructor)
|
ffi::sqlite3_bind_text(ptr, col as c_int, c_str, len, destructor)
|
||||||
},
|
},
|
||||||
ValueRef::Blob(b) => unsafe {
|
ValueRef::Blob(b) => unsafe {
|
||||||
@ -757,6 +764,7 @@ impl Statement<'_> {
|
|||||||
if length == 0 {
|
if length == 0 {
|
||||||
ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0)
|
ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0)
|
||||||
} else {
|
} else {
|
||||||
|
// TODO sqlite3_bind_blob64 // 3.8.7
|
||||||
ffi::sqlite3_bind_blob(
|
ffi::sqlite3_bind_blob(
|
||||||
ptr,
|
ptr,
|
||||||
col as c_int,
|
col as c_int,
|
||||||
@ -775,7 +783,7 @@ impl Statement<'_> {
|
|||||||
let r = self.stmt.step();
|
let r = self.stmt.step();
|
||||||
self.stmt.reset();
|
self.stmt.reset();
|
||||||
match r {
|
match r {
|
||||||
ffi::SQLITE_DONE => Ok(self.conn.changes()),
|
ffi::SQLITE_DONE => Ok(self.conn.changes() as usize),
|
||||||
ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults),
|
ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults),
|
||||||
_ => Err(self.conn.decode_result(r).unwrap_err()),
|
_ => Err(self.conn.decode_result(r).unwrap_err()),
|
||||||
}
|
}
|
||||||
@ -838,6 +846,16 @@ impl Statement<'_> {
|
|||||||
self.stmt.get_status(status, true)
|
self.stmt.get_status(status, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns 1 if the prepared statement is an EXPLAIN statement,
|
||||||
|
/// or 2 if the statement is an EXPLAIN QUERY PLAN,
|
||||||
|
/// or 0 if it is an ordinary statement or a NULL pointer.
|
||||||
|
#[inline]
|
||||||
|
#[cfg(feature = "modern_sqlite")] // 3.28.0
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
pub fn is_explain(&self) -> i32 {
|
||||||
|
self.stmt.is_explain()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "extra_check")]
|
#[cfg(feature = "extra_check")]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn check_no_tail(&self) -> Result<()> {
|
pub(crate) fn check_no_tail(&self) -> Result<()> {
|
||||||
@ -985,15 +1003,15 @@ pub enum StatementStatus {
|
|||||||
AutoIndex = 3,
|
AutoIndex = 3,
|
||||||
/// Equivalent to SQLITE_STMTSTATUS_VM_STEP
|
/// Equivalent to SQLITE_STMTSTATUS_VM_STEP
|
||||||
VmStep = 4,
|
VmStep = 4,
|
||||||
/// Equivalent to SQLITE_STMTSTATUS_REPREPARE
|
/// Equivalent to SQLITE_STMTSTATUS_REPREPARE (3.20.0)
|
||||||
RePrepare = 5,
|
RePrepare = 5,
|
||||||
/// Equivalent to SQLITE_STMTSTATUS_RUN
|
/// Equivalent to SQLITE_STMTSTATUS_RUN (3.20.0)
|
||||||
Run = 6,
|
Run = 6,
|
||||||
/// Equivalent to SQLITE_STMTSTATUS_FILTER_MISS
|
/// Equivalent to SQLITE_STMTSTATUS_FILTER_MISS
|
||||||
FilterMiss = 7,
|
FilterMiss = 7,
|
||||||
/// Equivalent to SQLITE_STMTSTATUS_FILTER_HIT
|
/// Equivalent to SQLITE_STMTSTATUS_FILTER_HIT
|
||||||
FilterHit = 8,
|
FilterHit = 8,
|
||||||
/// Equivalent to SQLITE_STMTSTATUS_MEMUSED
|
/// Equivalent to SQLITE_STMTSTATUS_MEMUSED (3.20.0)
|
||||||
MemUsed = 99,
|
MemUsed = 99,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1509,6 +1527,15 @@ mod test {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "modern_sqlite")]
|
||||||
|
fn is_explain() -> Result<()> {
|
||||||
|
let db = Connection::open_in_memory()?;
|
||||||
|
let stmt = db.prepare("SELECT 1;")?;
|
||||||
|
assert_eq!(0, stmt.is_explain());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "modern_sqlite")]
|
#[cfg(feature = "modern_sqlite")]
|
||||||
fn test_error_offset() -> Result<()> {
|
fn test_error_offset() -> Result<()> {
|
||||||
|
@ -119,6 +119,8 @@ impl Connection {
|
|||||||
None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) },
|
None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO sqlite3_trace_v2 (https://sqlite.org/c3ref/trace_v2.html) // 3.14.0, #977
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -375,6 +375,20 @@ impl Drop for Savepoint<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transaction state of a database
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[cfg(feature = "modern_sqlite")] // 3.37.0
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
pub enum TransactionState {
|
||||||
|
/// Equivalent to SQLITE_TXN_NONE
|
||||||
|
None,
|
||||||
|
/// Equivalent to SQLITE_TXN_READ
|
||||||
|
Read,
|
||||||
|
/// Equivalent to SQLITE_TXN_WRITE
|
||||||
|
Write,
|
||||||
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
/// Begin a new transaction with the default behavior (DEFERRED).
|
/// Begin a new transaction with the default behavior (DEFERRED).
|
||||||
///
|
///
|
||||||
@ -499,6 +513,16 @@ impl Connection {
|
|||||||
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
pub fn savepoint_with_name<T: Into<String>>(&mut self, name: T) -> Result<Savepoint<'_>> {
|
||||||
Savepoint::with_name(self, name)
|
Savepoint::with_name(self, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determine the transaction state of a database
|
||||||
|
#[cfg(feature = "modern_sqlite")] // 3.37.0
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
pub fn transaction_state(
|
||||||
|
&self,
|
||||||
|
db_name: Option<crate::DatabaseName<'_>>,
|
||||||
|
) -> Result<TransactionState> {
|
||||||
|
self.db.borrow().txn_state(db_name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -710,4 +734,25 @@ mod test {
|
|||||||
assert_eq!(x, i);
|
assert_eq!(x, i);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "modern_sqlite")]
|
||||||
|
fn txn_state() -> Result<()> {
|
||||||
|
use super::TransactionState;
|
||||||
|
use crate::DatabaseName;
|
||||||
|
let db = Connection::open_in_memory()?;
|
||||||
|
assert_eq!(
|
||||||
|
TransactionState::None,
|
||||||
|
db.transaction_state(Some(DatabaseName::Main))?
|
||||||
|
);
|
||||||
|
assert_eq!(TransactionState::None, db.transaction_state(None)?);
|
||||||
|
db.execute_batch("BEGIN")?;
|
||||||
|
assert_eq!(TransactionState::None, db.transaction_state(None)?);
|
||||||
|
let _: i32 = db.pragma_query_value(None, "user_version", |row| row.get(0))?;
|
||||||
|
assert_eq!(TransactionState::Read, db.transaction_state(None)?);
|
||||||
|
db.pragma_update(None, "user_version", 1)?;
|
||||||
|
assert_eq!(TransactionState::Write, db.transaction_state(None)?);
|
||||||
|
db.execute_batch("ROLLBACK")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,4 +257,7 @@ impl<'a> ValueRef<'a> {
|
|||||||
_ => unreachable!("sqlite3_value_type returned invalid value"),
|
_ => unreachable!("sqlite3_value_type returned invalid value"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO sqlite3_value_nochange // 3.22.0 & VTab xUpdate
|
||||||
|
// TODO sqlite3_value_frombind // 3.28.0
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ unsafe impl<'vtab> VTab<'vtab> for ArrayTab {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(&self) -> Result<ArrayTabCursor<'_>> {
|
fn open(&mut self) -> Result<ArrayTabCursor<'_>> {
|
||||||
Ok(ArrayTabCursor::new())
|
Ok(ArrayTabCursor::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,8 @@ use std::str;
|
|||||||
use crate::ffi;
|
use crate::ffi;
|
||||||
use crate::types::Null;
|
use crate::types::Null;
|
||||||
use crate::vtab::{
|
use crate::vtab::{
|
||||||
dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo,
|
escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo, VTab,
|
||||||
VTab, VTabConnection, VTabCursor, Values,
|
VTabConfig, VTabConnection, VTabCursor, VTabKind, Values,
|
||||||
};
|
};
|
||||||
use crate::{Connection, Error, Result};
|
use crate::{Connection, Error, Result};
|
||||||
|
|
||||||
@ -74,19 +74,6 @@ impl CsvTab {
|
|||||||
.from_path(&self.filename)
|
.from_path(&self.filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> {
|
|
||||||
let arg = str::from_utf8(c_slice)?.trim();
|
|
||||||
let mut split = arg.split('=');
|
|
||||||
if let Some(key) = split.next() {
|
|
||||||
if let Some(value) = split.next() {
|
|
||||||
let param = key.trim();
|
|
||||||
let value = dequote(value);
|
|
||||||
return Ok((param, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Error::ModuleError(format!("illegal argument: '{}'", arg)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_byte(arg: &str) -> Option<u8> {
|
fn parse_byte(arg: &str) -> Option<u8> {
|
||||||
if arg.len() == 1 {
|
if arg.len() == 1 {
|
||||||
arg.bytes().next()
|
arg.bytes().next()
|
||||||
@ -101,7 +88,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
|||||||
type Cursor = CsvTabCursor<'vtab>;
|
type Cursor = CsvTabCursor<'vtab>;
|
||||||
|
|
||||||
fn connect(
|
fn connect(
|
||||||
_: &mut VTabConnection,
|
db: &mut VTabConnection,
|
||||||
_aux: Option<&()>,
|
_aux: Option<&()>,
|
||||||
args: &[&[u8]],
|
args: &[&[u8]],
|
||||||
) -> Result<(String, CsvTab)> {
|
) -> Result<(String, CsvTab)> {
|
||||||
@ -122,7 +109,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
|||||||
|
|
||||||
let args = &args[3..];
|
let args = &args[3..];
|
||||||
for c_slice in args {
|
for c_slice in args {
|
||||||
let (param, value) = CsvTab::parameter(c_slice)?;
|
let (param, value) = super::parameter(c_slice)?;
|
||||||
match param {
|
match param {
|
||||||
"filename" => {
|
"filename" => {
|
||||||
if !Path::new(value).exists() {
|
if !Path::new(value).exists() {
|
||||||
@ -249,7 +236,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
|||||||
}
|
}
|
||||||
schema = Some(sql);
|
schema = Some(sql);
|
||||||
}
|
}
|
||||||
|
db.config(VTabConfig::DirectOnly)?;
|
||||||
Ok((schema.unwrap(), vtab))
|
Ok((schema.unwrap(), vtab))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,12 +246,14 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(&self) -> Result<CsvTabCursor<'_>> {
|
fn open(&mut self) -> Result<CsvTabCursor<'_>> {
|
||||||
Ok(CsvTabCursor::new(self.reader()?))
|
Ok(CsvTabCursor::new(self.reader()?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateVTab<'_> for CsvTab {}
|
impl CreateVTab<'_> for CsvTab {
|
||||||
|
const KIND: VTabKind = VTabKind::Default;
|
||||||
|
}
|
||||||
|
|
||||||
/// A cursor for the CSV virtual table
|
/// A cursor for the CSV virtual table
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
330
src/vtab/mod.rs
330
src/vtab/mod.rs
@ -57,6 +57,23 @@ use crate::{str_to_cstring, Connection, Error, InnerConnection, Result};
|
|||||||
// ffi::sqlite3_vtab => VTab
|
// ffi::sqlite3_vtab => VTab
|
||||||
// ffi::sqlite3_vtab_cursor => VTabCursor
|
// ffi::sqlite3_vtab_cursor => VTabCursor
|
||||||
|
|
||||||
|
/// Virtual table kind
|
||||||
|
pub enum VTabKind {
|
||||||
|
/// Non-eponymous
|
||||||
|
Default,
|
||||||
|
/// [`create`](CreateVTab::create) == [`connect`](VTab::connect)
|
||||||
|
///
|
||||||
|
/// See [SQLite doc](https://sqlite.org/vtab.html#eponymous_virtual_tables)
|
||||||
|
Eponymous,
|
||||||
|
/// No [`create`](CreateVTab::create) / [`destroy`](CreateVTab::destroy) or
|
||||||
|
/// not used
|
||||||
|
///
|
||||||
|
/// SQLite >= 3.9.0
|
||||||
|
///
|
||||||
|
/// See [SQLite doc](https://sqlite.org/vtab.html#eponymous_only_virtual_tables)
|
||||||
|
EponymousOnly,
|
||||||
|
}
|
||||||
|
|
||||||
/// Virtual table module
|
/// Virtual table module
|
||||||
///
|
///
|
||||||
/// (See [SQLite doc](https://sqlite.org/c3ref/module.html))
|
/// (See [SQLite doc](https://sqlite.org/c3ref/module.html))
|
||||||
@ -84,31 +101,26 @@ const ZERO_MODULE: ffi::sqlite3_module = unsafe {
|
|||||||
.module
|
.module
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Create a read-only virtual table implementation.
|
macro_rules! module {
|
||||||
///
|
($lt:lifetime, $vt:ty, $ct:ty, $xc:expr, $xd:expr, $xu:expr) => {
|
||||||
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
|
|
||||||
#[must_use]
|
|
||||||
pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab, T> {
|
|
||||||
// The xConnect and xCreate methods do the same thing, but they must be
|
|
||||||
// different so that the virtual table is not an eponymous virtual table.
|
|
||||||
#[allow(clippy::needless_update)]
|
#[allow(clippy::needless_update)]
|
||||||
&Module {
|
&Module {
|
||||||
base: ffi::sqlite3_module {
|
base: ffi::sqlite3_module {
|
||||||
// We don't use V3
|
// We don't use V3
|
||||||
iVersion: 2, // We don't use V2 or V3 features in read_only_module types
|
iVersion: 2,
|
||||||
xCreate: Some(rust_create::<T>),
|
xCreate: $xc,
|
||||||
xConnect: Some(rust_connect::<T>),
|
xConnect: Some(rust_connect::<$vt>),
|
||||||
xBestIndex: Some(rust_best_index::<T>),
|
xBestIndex: Some(rust_best_index::<$vt>),
|
||||||
xDisconnect: Some(rust_disconnect::<T>),
|
xDisconnect: Some(rust_disconnect::<$vt>),
|
||||||
xDestroy: Some(rust_destroy::<T>),
|
xDestroy: $xd,
|
||||||
xOpen: Some(rust_open::<T>),
|
xOpen: Some(rust_open::<$vt>),
|
||||||
xClose: Some(rust_close::<T::Cursor>),
|
xClose: Some(rust_close::<$ct>),
|
||||||
xFilter: Some(rust_filter::<T::Cursor>),
|
xFilter: Some(rust_filter::<$ct>),
|
||||||
xNext: Some(rust_next::<T::Cursor>),
|
xNext: Some(rust_next::<$ct>),
|
||||||
xEof: Some(rust_eof::<T::Cursor>),
|
xEof: Some(rust_eof::<$ct>),
|
||||||
xColumn: Some(rust_column::<T::Cursor>),
|
xColumn: Some(rust_column::<$ct>),
|
||||||
xRowid: Some(rust_rowid::<T::Cursor>),
|
xRowid: Some(rust_rowid::<$ct>), // FIXME optional
|
||||||
xUpdate: None,
|
xUpdate: $xu,
|
||||||
xBegin: None,
|
xBegin: None,
|
||||||
xSync: None,
|
xSync: None,
|
||||||
xCommit: None,
|
xCommit: None,
|
||||||
@ -120,7 +132,46 @@ pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab,
|
|||||||
xRollbackTo: None,
|
xRollbackTo: None,
|
||||||
..ZERO_MODULE
|
..ZERO_MODULE
|
||||||
},
|
},
|
||||||
phantom: PhantomData::<&'vtab T>,
|
phantom: PhantomData::<&$lt $vt>,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an modifiable virtual table implementation.
|
||||||
|
///
|
||||||
|
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
|
||||||
|
#[must_use]
|
||||||
|
pub fn update_module<'vtab, T: UpdateVTab<'vtab>>() -> &'static Module<'vtab, T> {
|
||||||
|
match T::KIND {
|
||||||
|
VTabKind::EponymousOnly => {
|
||||||
|
module!('vtab, T, T::Cursor, None, None, Some(rust_update::<T>))
|
||||||
|
}
|
||||||
|
VTabKind::Eponymous => {
|
||||||
|
module!('vtab, T, T::Cursor, Some(rust_connect::<T>), Some(rust_disconnect::<T>), Some(rust_update::<T>))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
module!('vtab, T, T::Cursor, Some(rust_create::<T>), Some(rust_destroy::<T>), Some(rust_update::<T>))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a read-only virtual table implementation.
|
||||||
|
///
|
||||||
|
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
|
||||||
|
#[must_use]
|
||||||
|
pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab, T> {
|
||||||
|
match T::KIND {
|
||||||
|
VTabKind::EponymousOnly => eponymous_only_module(),
|
||||||
|
VTabKind::Eponymous => {
|
||||||
|
// A virtual table is eponymous if its xCreate method is the exact same function
|
||||||
|
// as the xConnect method
|
||||||
|
module!('vtab, T, T::Cursor, Some(rust_connect::<T>), Some(rust_disconnect::<T>), None)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// The xConnect and xCreate methods may do the same thing, but they must be
|
||||||
|
// different so that the virtual table is not an eponymous virtual table.
|
||||||
|
module!('vtab, T, T::Cursor, Some(rust_create::<T>), Some(rust_destroy::<T>), None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,49 +180,36 @@ pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab,
|
|||||||
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
|
/// Step 2 of [Creating New Virtual Table Implementations](https://sqlite.org/vtab.html#creating_new_virtual_table_implementations).
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn eponymous_only_module<'vtab, T: VTab<'vtab>>() -> &'static Module<'vtab, T> {
|
pub fn eponymous_only_module<'vtab, T: VTab<'vtab>>() -> &'static Module<'vtab, T> {
|
||||||
// A virtual table is eponymous if its xCreate method is the exact same function
|
// For eponymous-only virtual tables, the xCreate method is NULL
|
||||||
// as the xConnect method For eponymous-only virtual tables, the xCreate
|
module!('vtab, T, T::Cursor, None, None, None)
|
||||||
// method is NULL
|
}
|
||||||
#[allow(clippy::needless_update)]
|
|
||||||
&Module {
|
/// Virtual table configuration options
|
||||||
base: ffi::sqlite3_module {
|
#[repr(i32)]
|
||||||
// We don't use V3
|
#[non_exhaustive]
|
||||||
iVersion: 2,
|
#[cfg(feature = "modern_sqlite")] // 3.7.7
|
||||||
xCreate: None,
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
xConnect: Some(rust_connect::<T>),
|
pub enum VTabConfig {
|
||||||
xBestIndex: Some(rust_best_index::<T>),
|
/// Equivalent to SQLITE_VTAB_CONSTRAINT_SUPPORT
|
||||||
xDisconnect: Some(rust_disconnect::<T>),
|
ConstraintSupport = 1,
|
||||||
xDestroy: None,
|
/// Equivalent to SQLITE_VTAB_INNOCUOUS
|
||||||
xOpen: Some(rust_open::<T>),
|
Innocuous = 2,
|
||||||
xClose: Some(rust_close::<T::Cursor>),
|
/// Equivalent to SQLITE_VTAB_DIRECTONLY
|
||||||
xFilter: Some(rust_filter::<T::Cursor>),
|
DirectOnly = 3,
|
||||||
xNext: Some(rust_next::<T::Cursor>),
|
|
||||||
xEof: Some(rust_eof::<T::Cursor>),
|
|
||||||
xColumn: Some(rust_column::<T::Cursor>),
|
|
||||||
xRowid: Some(rust_rowid::<T::Cursor>),
|
|
||||||
xUpdate: None,
|
|
||||||
xBegin: None,
|
|
||||||
xSync: None,
|
|
||||||
xCommit: None,
|
|
||||||
xRollback: None,
|
|
||||||
xFindFunction: None,
|
|
||||||
xRename: None,
|
|
||||||
xSavepoint: None,
|
|
||||||
xRelease: None,
|
|
||||||
xRollbackTo: None,
|
|
||||||
..ZERO_MODULE
|
|
||||||
},
|
|
||||||
phantom: PhantomData::<&'vtab T>,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `feature = "vtab"`
|
/// `feature = "vtab"`
|
||||||
pub struct VTabConnection(*mut ffi::sqlite3);
|
pub struct VTabConnection(*mut ffi::sqlite3);
|
||||||
|
|
||||||
impl VTabConnection {
|
impl VTabConnection {
|
||||||
// TODO sqlite3_vtab_config (http://sqlite.org/c3ref/vtab_config.html)
|
/// Configure various facets of the virtual table interface
|
||||||
|
#[cfg(feature = "modern_sqlite")] // 3.7.7
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
pub fn config(&mut self, config: VTabConfig) -> Result<()> {
|
||||||
|
crate::error::check(unsafe { ffi::sqlite3_vtab_config(self.0, config as c_int) })
|
||||||
|
}
|
||||||
|
|
||||||
// TODO sqlite3_vtab_on_conflict (http://sqlite.org/c3ref/vtab_on_conflict.html)
|
// TODO sqlite3_vtab_on_conflict (http://sqlite.org/c3ref/vtab_on_conflict.html) & xUpdate
|
||||||
|
|
||||||
/// Get access to the underlying SQLite database connection handle.
|
/// Get access to the underlying SQLite database connection handle.
|
||||||
///
|
///
|
||||||
@ -191,7 +229,7 @@ impl VTabConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Virtual table instance trait.
|
/// Eponymous-only virtual table instance trait.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
@ -229,13 +267,17 @@ pub unsafe trait VTab<'vtab>: Sized {
|
|||||||
|
|
||||||
/// Create a new cursor used for accessing a virtual table.
|
/// Create a new cursor used for accessing a virtual table.
|
||||||
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xopen_method))
|
/// (See [SQLite doc](https://sqlite.org/vtab.html#the_xopen_method))
|
||||||
fn open(&'vtab self) -> Result<Self::Cursor>;
|
fn open(&'vtab mut self) -> Result<Self::Cursor>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Non-eponymous virtual table instance trait.
|
/// Read-only virtual table instance trait.
|
||||||
///
|
///
|
||||||
/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
|
/// (See [SQLite doc](https://sqlite.org/c3ref/vtab.html))
|
||||||
pub trait CreateVTab<'vtab>: VTab<'vtab> {
|
pub trait CreateVTab<'vtab>: VTab<'vtab> {
|
||||||
|
/// For [`EponymousOnly`](VTabKind::EponymousOnly),
|
||||||
|
/// [`create`](CreateVTab::create) and [`destroy`](CreateVTab::destroy) are
|
||||||
|
/// not called
|
||||||
|
const KIND: VTabKind;
|
||||||
/// Create a new instance of a virtual table in response to a CREATE VIRTUAL
|
/// Create a new instance of a virtual table in response to a CREATE VIRTUAL
|
||||||
/// TABLE statement. The `db` parameter is a pointer to the SQLite
|
/// TABLE statement. The `db` parameter is a pointer to the SQLite
|
||||||
/// database connection that is executing the CREATE VIRTUAL TABLE
|
/// database connection that is executing the CREATE VIRTUAL TABLE
|
||||||
@ -261,6 +303,23 @@ pub trait CreateVTab<'vtab>: VTab<'vtab> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writable virtual table instance trait.
|
||||||
|
///
|
||||||
|
/// (See [SQLite doc](https://sqlite.org/vtab.html#xupdate))
|
||||||
|
pub trait UpdateVTab<'vtab>: CreateVTab<'vtab> {
|
||||||
|
/// Delete rowid or PK
|
||||||
|
fn delete(&mut self, arg: ValueRef<'_>) -> Result<()>;
|
||||||
|
/// Insert: `args[0] == NULL: old rowid or PK, args[1]: new rowid or PK,
|
||||||
|
/// args[2]: ...`
|
||||||
|
///
|
||||||
|
/// Return the new rowid.
|
||||||
|
// TODO Make the distinction between argv[1] == NULL and argv[1] != NULL ?
|
||||||
|
fn insert(&mut self, args: &Values<'_>) -> Result<i64>;
|
||||||
|
/// Update: `args[0] != NULL: old rowid or PK, args[1]: new row id or PK,
|
||||||
|
/// args[2]: ...`
|
||||||
|
fn update(&mut self, args: &Values<'_>) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Index constraint operator.
|
/// Index constraint operator.
|
||||||
/// See [Virtual Table Constraint Operator Codes](https://sqlite.org/c3ref/c_index_constraint_eq.html) for details.
|
/// See [Virtual Table Constraint Operator Codes](https://sqlite.org/c3ref/c_index_constraint_eq.html) for details.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -310,10 +369,24 @@ impl From<u8> for IndexConstraintOp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "modern_sqlite")] // 3.9.0
|
||||||
|
bitflags::bitflags! {
|
||||||
|
/// Virtual table scan flags
|
||||||
|
/// See [Function Flags](https://sqlite.org/c3ref/c_index_scan_unique.html) for details.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct IndexFlags: ::std::os::raw::c_int {
|
||||||
|
/// Default
|
||||||
|
const NONE = 0;
|
||||||
|
/// Scan visits at most 1 row.
|
||||||
|
const SQLITE_INDEX_SCAN_UNIQUE = ffi::SQLITE_INDEX_SCAN_UNIQUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Pass information into and receive the reply from the
|
/// Pass information into and receive the reply from the
|
||||||
/// [`VTab::best_index`] method.
|
/// [`VTab::best_index`] method.
|
||||||
///
|
///
|
||||||
/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html))
|
/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html))
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct IndexInfo(*mut ffi::sqlite3_index_info);
|
pub struct IndexInfo(*mut ffi::sqlite3_index_info);
|
||||||
|
|
||||||
impl IndexInfo {
|
impl IndexInfo {
|
||||||
@ -376,6 +449,14 @@ impl IndexInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// String used to identify the index
|
||||||
|
pub fn set_idx_str(&mut self, idx_str: &str) {
|
||||||
|
unsafe {
|
||||||
|
(*self.0).idxStr = alloc(idx_str);
|
||||||
|
(*self.0).needToFreeIdxStr = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// True if output is already ordered
|
/// True if output is already ordered
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
|
pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
|
||||||
@ -402,10 +483,60 @@ impl IndexInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO idxFlags
|
/// Mask of SQLITE_INDEX_SCAN_* flags.
|
||||||
// TODO colUsed
|
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.9.0
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
#[inline]
|
||||||
|
pub fn set_idx_flags(&mut self, flags: IndexFlags) {
|
||||||
|
unsafe { (*self.0).idxFlags = flags.bits() };
|
||||||
|
}
|
||||||
|
|
||||||
// TODO sqlite3_vtab_collation (http://sqlite.org/c3ref/vtab_collation.html)
|
/// Mask of columns used by statement
|
||||||
|
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.10.0
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
#[inline]
|
||||||
|
pub fn col_used(&self) -> u64 {
|
||||||
|
unsafe { (*self.0).colUsed }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine the collation for a virtual table constraint
|
||||||
|
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.22.0
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
pub fn collation(&self, constraint_idx: usize) -> Result<&str> {
|
||||||
|
use std::ffi::CStr;
|
||||||
|
let idx = constraint_idx as c_int;
|
||||||
|
let collation = unsafe { ffi::sqlite3_vtab_collation(self.0, idx) };
|
||||||
|
if collation.is_null() {
|
||||||
|
return Err(Error::SqliteFailure(
|
||||||
|
ffi::Error::new(ffi::SQLITE_MISUSE),
|
||||||
|
Some(format!("{} is out of range", constraint_idx)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(unsafe { CStr::from_ptr(collation) }.to_str()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// Determine if a virtual table query is DISTINCT
|
||||||
|
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
pub fn distinct(&self) -> c_int {
|
||||||
|
unsafe { ffi::sqlite3_vtab_distinct(self.0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constraint values
|
||||||
|
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
pub fn set_rhs_value(&mut self, constraint_idx: c_int, value: ValueRef) -> Result<()> {
|
||||||
|
// TODO ValueRef to sqlite3_value
|
||||||
|
crate::error::check(unsafe { ffi::sqlite3_vtab_rhs_value(self.O, constraint_idx, value) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Identify and handle IN constraints
|
||||||
|
#[cfg(feature = "modern_sqlite")] // SQLite >= 3.38.0
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "modern_sqlite")))]
|
||||||
|
pub fn set_in_constraint(&mut self, constraint_idx: c_int, b_handle: c_int) -> bool {
|
||||||
|
unsafe { ffi::sqlite3_vtab_in(self.0, constraint_idx, b_handle) != 0 }
|
||||||
|
} // TODO sqlite3_vtab_in_first / sqlite3_vtab_in_next https://sqlite.org/c3ref/vtab_in_first.html
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate on index constraint and its associated usage.
|
/// Iterate on index constraint and its associated usage.
|
||||||
@ -583,7 +714,7 @@ impl Context {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html)
|
// TODO sqlite3_vtab_nochange (http://sqlite.org/c3ref/vtab_nochange.html) // 3.22.0 & xColumn
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper to [`VTabCursor::filter`] arguments, the values
|
/// Wrapper to [`VTabCursor::filter`] arguments, the values
|
||||||
@ -651,6 +782,7 @@ impl Values<'_> {
|
|||||||
iter: self.args.iter(),
|
iter: self.args.iter(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO sqlite3_vtab_in_first / sqlite3_vtab_in_next https://sqlite.org/c3ref/vtab_in_first.html & 3.38.0
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a Values<'a> {
|
impl<'a> IntoIterator for &'a Values<'a> {
|
||||||
@ -707,6 +839,13 @@ impl InnerConnection {
|
|||||||
module: &'static Module<'vtab, T>,
|
module: &'static Module<'vtab, T>,
|
||||||
aux: Option<T::Aux>,
|
aux: Option<T::Aux>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
use crate::version;
|
||||||
|
if version::version_number() < 3_009_000 && module.base.xCreate.is_none() {
|
||||||
|
return Err(Error::ModuleError(format!(
|
||||||
|
"Eponymous-only virtual table not supported by SQLite version {}",
|
||||||
|
version::version()
|
||||||
|
)));
|
||||||
|
}
|
||||||
let c_name = str_to_cstring(module_name)?;
|
let c_name = str_to_cstring(module_name)?;
|
||||||
let r = match aux {
|
let r = match aux {
|
||||||
Some(aux) => {
|
Some(aux) => {
|
||||||
@ -754,7 +893,7 @@ pub fn dequote(s: &str) -> &str {
|
|||||||
}
|
}
|
||||||
match s.bytes().next() {
|
match s.bytes().next() {
|
||||||
Some(b) if b == b'"' || b == b'\'' => match s.bytes().rev().next() {
|
Some(b) if b == b'"' || b == b'\'' => match s.bytes().rev().next() {
|
||||||
Some(e) if e == b => &s[1..s.len() - 1],
|
Some(e) if e == b => &s[1..s.len() - 1], // FIXME handle inner escaped quote(s)
|
||||||
_ => s,
|
_ => s,
|
||||||
},
|
},
|
||||||
_ => s,
|
_ => s,
|
||||||
@ -784,6 +923,20 @@ pub fn parse_boolean(s: &str) -> Option<bool> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `<param_name>=['"]?<param_value>['"]?` => `(<param_name>, <param_value>)`
|
||||||
|
pub fn parameter(c_slice: &[u8]) -> Result<(&str, &str)> {
|
||||||
|
let arg = std::str::from_utf8(c_slice)?.trim();
|
||||||
|
let mut split = arg.split('=');
|
||||||
|
if let Some(key) = split.next() {
|
||||||
|
if let Some(value) = split.next() {
|
||||||
|
let param = key.trim();
|
||||||
|
let value = dequote(value);
|
||||||
|
return Ok((param, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::ModuleError(format!("illegal argument: '{}'", arg)))
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME copy/paste from function.rs
|
// FIXME copy/paste from function.rs
|
||||||
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
|
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
|
||||||
drop(Box::from_raw(p.cast::<T>()));
|
drop(Box::from_raw(p.cast::<T>()));
|
||||||
@ -1061,6 +1214,49 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn rust_update<'vtab, T: 'vtab>(
|
||||||
|
vtab: *mut ffi::sqlite3_vtab,
|
||||||
|
argc: c_int,
|
||||||
|
argv: *mut *mut ffi::sqlite3_value,
|
||||||
|
p_rowid: *mut ffi::sqlite3_int64,
|
||||||
|
) -> c_int
|
||||||
|
where
|
||||||
|
T: UpdateVTab<'vtab>,
|
||||||
|
{
|
||||||
|
assert!(argc >= 1);
|
||||||
|
let args = slice::from_raw_parts_mut(argv, argc as usize);
|
||||||
|
let vt = vtab.cast::<T>();
|
||||||
|
let r = if args.len() == 1 {
|
||||||
|
(*vt).delete(ValueRef::from_value(args[0]))
|
||||||
|
} else if ffi::sqlite3_value_type(args[0]) == ffi::SQLITE_NULL {
|
||||||
|
// TODO Make the distinction between argv[1] == NULL and argv[1] != NULL ?
|
||||||
|
let values = Values { args };
|
||||||
|
match (*vt).insert(&values) {
|
||||||
|
Ok(rowid) => {
|
||||||
|
*p_rowid = rowid;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let values = Values { args };
|
||||||
|
(*vt).update(&values)
|
||||||
|
};
|
||||||
|
match r {
|
||||||
|
Ok(_) => ffi::SQLITE_OK,
|
||||||
|
Err(Error::SqliteFailure(err, s)) => {
|
||||||
|
if let Some(err_msg) = s {
|
||||||
|
set_err_msg(vtab, &err_msg);
|
||||||
|
}
|
||||||
|
err.extended_code
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
set_err_msg(vtab, &err.to_string());
|
||||||
|
ffi::SQLITE_ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Virtual table cursors can set an error message by assigning a string to
|
/// Virtual table cursors can set an error message by assigning a string to
|
||||||
/// `zErrMsg`.
|
/// `zErrMsg`.
|
||||||
#[cold]
|
#[cold]
|
||||||
@ -1138,6 +1334,8 @@ pub mod csvtab;
|
|||||||
#[cfg(feature = "series")]
|
#[cfg(feature = "series")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "series")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "series")))]
|
||||||
pub mod series; // SQLite >= 3.9.0
|
pub mod series; // SQLite >= 3.9.0
|
||||||
|
#[cfg(test)]
|
||||||
|
mod vtablog;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
@ -10,8 +10,8 @@ use std::os::raw::c_int;
|
|||||||
use crate::ffi;
|
use crate::ffi;
|
||||||
use crate::types::Type;
|
use crate::types::Type;
|
||||||
use crate::vtab::{
|
use crate::vtab::{
|
||||||
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConnection, VTabCursor,
|
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConfig, VTabConnection,
|
||||||
Values,
|
VTabCursor, Values,
|
||||||
};
|
};
|
||||||
use crate::{Connection, Error, Result};
|
use crate::{Connection, Error, Result};
|
||||||
|
|
||||||
@ -57,13 +57,14 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
|
|||||||
type Cursor = SeriesTabCursor<'vtab>;
|
type Cursor = SeriesTabCursor<'vtab>;
|
||||||
|
|
||||||
fn connect(
|
fn connect(
|
||||||
_: &mut VTabConnection,
|
db: &mut VTabConnection,
|
||||||
_aux: Option<&()>,
|
_aux: Option<&()>,
|
||||||
_args: &[&[u8]],
|
_args: &[&[u8]],
|
||||||
) -> Result<(String, SeriesTab)> {
|
) -> Result<(String, SeriesTab)> {
|
||||||
let vtab = SeriesTab {
|
let vtab = SeriesTab {
|
||||||
base: ffi::sqlite3_vtab::default(),
|
base: ffi::sqlite3_vtab::default(),
|
||||||
};
|
};
|
||||||
|
db.config(VTabConfig::Innocuous)?;
|
||||||
Ok((
|
Ok((
|
||||||
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(),
|
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(),
|
||||||
vtab,
|
vtab,
|
||||||
@ -103,6 +104,8 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
|
|||||||
let mut constraint_usage = info.constraint_usage(*j);
|
let mut constraint_usage = info.constraint_usage(*j);
|
||||||
constraint_usage.set_argv_index(n_arg);
|
constraint_usage.set_argv_index(n_arg);
|
||||||
constraint_usage.set_omit(true);
|
constraint_usage.set_omit(true);
|
||||||
|
#[cfg(all(test, feature = "modern_sqlite"))]
|
||||||
|
debug_assert_eq!(Ok("BINARY"), info.collation(*j));
|
||||||
}
|
}
|
||||||
if !(unusable_mask & !idx_num).is_empty() {
|
if !(unusable_mask & !idx_num).is_empty() {
|
||||||
return Err(Error::SqliteFailure(
|
return Err(Error::SqliteFailure(
|
||||||
@ -150,7 +153,7 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(&self) -> Result<SeriesTabCursor<'_>> {
|
fn open(&mut self) -> Result<SeriesTabCursor<'_>> {
|
||||||
Ok(SeriesTabCursor::new())
|
Ok(SeriesTabCursor::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
300
src/vtab/vtablog.rs
Normal file
300
src/vtab/vtablog.rs
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
///! Port of C [vtablog](http://www.sqlite.org/cgi/src/finfo?name=ext/misc/vtablog.c)
|
||||||
|
use std::default::Default;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use crate::vtab::{
|
||||||
|
update_module, Context, CreateVTab, IndexInfo, UpdateVTab, VTab, VTabConnection, VTabCursor,
|
||||||
|
VTabKind, Values,
|
||||||
|
};
|
||||||
|
use crate::{ffi, ValueRef};
|
||||||
|
use crate::{Connection, Error, Result};
|
||||||
|
|
||||||
|
/// Register the "vtablog" module.
|
||||||
|
pub fn load_module(conn: &Connection) -> Result<()> {
|
||||||
|
let aux: Option<()> = None;
|
||||||
|
conn.create_module("vtablog", update_module::<VTabLog>(), aux)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An instance of the vtablog virtual table
|
||||||
|
#[repr(C)]
|
||||||
|
struct VTabLog {
|
||||||
|
/// Base class. Must be first
|
||||||
|
base: ffi::sqlite3_vtab,
|
||||||
|
/// Number of rows in the table
|
||||||
|
n_row: i64,
|
||||||
|
/// Instance number for this vtablog table
|
||||||
|
i_inst: usize,
|
||||||
|
/// Number of cursors created
|
||||||
|
n_cursor: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VTabLog {
|
||||||
|
fn connect_create(
|
||||||
|
_: &mut VTabConnection,
|
||||||
|
_: Option<&()>,
|
||||||
|
args: &[&[u8]],
|
||||||
|
is_create: bool,
|
||||||
|
) -> Result<(String, VTabLog)> {
|
||||||
|
static N_INST: AtomicUsize = AtomicUsize::new(1);
|
||||||
|
let i_inst = N_INST.fetch_add(1, Ordering::SeqCst);
|
||||||
|
println!(
|
||||||
|
"VTabLog::{}(tab={}, args={:?}):",
|
||||||
|
if is_create { "create" } else { "connect" },
|
||||||
|
i_inst,
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
let mut schema = None;
|
||||||
|
let mut n_row = None;
|
||||||
|
|
||||||
|
let args = &args[3..];
|
||||||
|
for c_slice in args {
|
||||||
|
let (param, value) = super::parameter(c_slice)?;
|
||||||
|
match param {
|
||||||
|
"schema" => {
|
||||||
|
if schema.is_some() {
|
||||||
|
return Err(Error::ModuleError(format!(
|
||||||
|
"more than one '{}' parameter",
|
||||||
|
param
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
schema = Some(value.to_owned())
|
||||||
|
}
|
||||||
|
"rows" => {
|
||||||
|
if n_row.is_some() {
|
||||||
|
return Err(Error::ModuleError(format!(
|
||||||
|
"more than one '{}' parameter",
|
||||||
|
param
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if let Ok(n) = i64::from_str(value) {
|
||||||
|
n_row = Some(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Error::ModuleError(format!(
|
||||||
|
"unrecognized parameter '{}'",
|
||||||
|
param
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if schema.is_none() {
|
||||||
|
return Err(Error::ModuleError("no schema defined".to_owned()));
|
||||||
|
}
|
||||||
|
let vtab = VTabLog {
|
||||||
|
base: ffi::sqlite3_vtab::default(),
|
||||||
|
n_row: n_row.unwrap_or(10),
|
||||||
|
i_inst,
|
||||||
|
n_cursor: 0,
|
||||||
|
};
|
||||||
|
Ok((schema.unwrap(), vtab))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VTabLog {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
println!("VTabLog::drop({})", self.i_inst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<'vtab> VTab<'vtab> for VTabLog {
|
||||||
|
type Aux = ();
|
||||||
|
type Cursor = VTabLogCursor<'vtab>;
|
||||||
|
|
||||||
|
fn connect(
|
||||||
|
db: &mut VTabConnection,
|
||||||
|
aux: Option<&Self::Aux>,
|
||||||
|
args: &[&[u8]],
|
||||||
|
) -> Result<(String, Self)> {
|
||||||
|
VTabLog::connect_create(db, aux, args, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
|
||||||
|
println!("VTabLog::best_index({})", self.i_inst);
|
||||||
|
info.set_estimated_cost(500.);
|
||||||
|
info.set_estimated_rows(500);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open(&'vtab mut self) -> Result<Self::Cursor> {
|
||||||
|
self.n_cursor += 1;
|
||||||
|
println!(
|
||||||
|
"VTabLog::open(tab={}, cursor={})",
|
||||||
|
self.i_inst, self.n_cursor
|
||||||
|
);
|
||||||
|
Ok(VTabLogCursor {
|
||||||
|
base: ffi::sqlite3_vtab_cursor::default(),
|
||||||
|
i_cursor: self.n_cursor,
|
||||||
|
row_id: 0,
|
||||||
|
phantom: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'vtab> CreateVTab<'vtab> for VTabLog {
|
||||||
|
const KIND: VTabKind = VTabKind::Default;
|
||||||
|
|
||||||
|
fn create(
|
||||||
|
db: &mut VTabConnection,
|
||||||
|
aux: Option<&Self::Aux>,
|
||||||
|
args: &[&[u8]],
|
||||||
|
) -> Result<(String, Self)> {
|
||||||
|
VTabLog::connect_create(db, aux, args, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&self) -> Result<()> {
|
||||||
|
println!("VTabLog::destroy({})", self.i_inst);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'vtab> UpdateVTab<'vtab> for VTabLog {
|
||||||
|
fn delete(&mut self, arg: ValueRef<'_>) -> Result<()> {
|
||||||
|
println!("VTabLog::delete({}, {:?})", self.i_inst, arg);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&mut self, args: &Values<'_>) -> Result<i64> {
|
||||||
|
println!(
|
||||||
|
"VTabLog::insert({}, {:?})",
|
||||||
|
self.i_inst,
|
||||||
|
args.iter().collect::<Vec<ValueRef<'_>>>()
|
||||||
|
);
|
||||||
|
Ok(self.n_row as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, args: &Values<'_>) -> Result<()> {
|
||||||
|
println!(
|
||||||
|
"VTabLog::update({}, {:?})",
|
||||||
|
self.i_inst,
|
||||||
|
args.iter().collect::<Vec<ValueRef<'_>>>()
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A cursor for the Series virtual table
|
||||||
|
#[repr(C)]
|
||||||
|
struct VTabLogCursor<'vtab> {
|
||||||
|
/// Base class. Must be first
|
||||||
|
base: ffi::sqlite3_vtab_cursor,
|
||||||
|
/// Cursor number
|
||||||
|
i_cursor: usize,
|
||||||
|
/// The rowid
|
||||||
|
row_id: i64,
|
||||||
|
phantom: PhantomData<&'vtab VTabLog>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VTabLogCursor<'_> {
|
||||||
|
fn vtab(&self) -> &VTabLog {
|
||||||
|
unsafe { &*(self.base.pVtab as *const VTabLog) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VTabLogCursor<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
println!(
|
||||||
|
"VTabLogCursor::drop(tab={}, cursor={})",
|
||||||
|
self.vtab().i_inst,
|
||||||
|
self.i_cursor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl VTabCursor for VTabLogCursor<'_> {
|
||||||
|
fn filter(&mut self, _: c_int, _: Option<&str>, _: &Values<'_>) -> Result<()> {
|
||||||
|
println!(
|
||||||
|
"VTabLogCursor::filter(tab={}, cursor={})",
|
||||||
|
self.vtab().i_inst,
|
||||||
|
self.i_cursor
|
||||||
|
);
|
||||||
|
self.row_id = 0;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Result<()> {
|
||||||
|
println!(
|
||||||
|
"VTabLogCursor::next(tab={}, cursor={}): rowid {} -> {}",
|
||||||
|
self.vtab().i_inst,
|
||||||
|
self.i_cursor,
|
||||||
|
self.row_id,
|
||||||
|
self.row_id + 1
|
||||||
|
);
|
||||||
|
self.row_id += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eof(&self) -> bool {
|
||||||
|
let eof = self.row_id >= self.vtab().n_row;
|
||||||
|
println!(
|
||||||
|
"VTabLogCursor::eof(tab={}, cursor={}): {}",
|
||||||
|
self.vtab().i_inst,
|
||||||
|
self.i_cursor,
|
||||||
|
eof,
|
||||||
|
);
|
||||||
|
eof
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column(&self, ctx: &mut Context, i: c_int) -> Result<()> {
|
||||||
|
let value = if i < 26 {
|
||||||
|
format!(
|
||||||
|
"{}{}",
|
||||||
|
"abcdefghijklmnopqrstuvwyz".chars().nth(i as usize).unwrap(),
|
||||||
|
self.row_id
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("{}{}", i, self.row_id)
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
"VTabLogCursor::column(tab={}, cursor={}, i={}): {}",
|
||||||
|
self.vtab().i_inst,
|
||||||
|
self.i_cursor,
|
||||||
|
i,
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
ctx.set_result(&value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rowid(&self) -> Result<i64> {
|
||||||
|
println!(
|
||||||
|
"VTabLogCursor::rowid(tab={}, cursor={}): {}",
|
||||||
|
self.vtab().i_inst,
|
||||||
|
self.i_cursor,
|
||||||
|
self.row_id,
|
||||||
|
);
|
||||||
|
Ok(self.row_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{Connection, Result};
|
||||||
|
#[test]
|
||||||
|
fn test_module() -> Result<()> {
|
||||||
|
let db = Connection::open_in_memory()?;
|
||||||
|
super::load_module(&db)?;
|
||||||
|
|
||||||
|
db.execute_batch(
|
||||||
|
"CREATE VIRTUAL TABLE temp.log USING vtablog(
|
||||||
|
schema='CREATE TABLE x(a,b,c)',
|
||||||
|
rows=25
|
||||||
|
);",
|
||||||
|
)?;
|
||||||
|
let mut stmt = db.prepare("SELECT * FROM log;")?;
|
||||||
|
let mut rows = stmt.query([])?;
|
||||||
|
while rows.next()?.is_some() {}
|
||||||
|
db.execute("DELETE FROM log WHERE a = ?", ["a1"])?;
|
||||||
|
db.execute(
|
||||||
|
"INSERT INTO log (a, b, c) VALUES (?, ?, ?)",
|
||||||
|
["a", "b", "c"],
|
||||||
|
)?;
|
||||||
|
db.execute(
|
||||||
|
"UPDATE log SET b = ?, c = ? WHERE a = ?",
|
||||||
|
["bn", "cn", "a1"],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -5,17 +5,16 @@ use rusqlite::ffi;
|
|||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
|
||||||
fn test_error_when_singlethread_mode() {
|
fn test_error_when_singlethread_mode() {
|
||||||
// put SQLite into single-threaded mode
|
// put SQLite into single-threaded mode
|
||||||
unsafe {
|
unsafe {
|
||||||
|
// Note: macOS system SQLite seems to return an error if you attempt to
|
||||||
|
// reconfigure to single-threaded mode.
|
||||||
if ffi::sqlite3_config(ffi::SQLITE_CONFIG_SINGLETHREAD) != ffi::SQLITE_OK {
|
if ffi::sqlite3_config(ffi::SQLITE_CONFIG_SINGLETHREAD) != ffi::SQLITE_OK {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ffi::sqlite3_initialize() != ffi::SQLITE_OK {
|
assert_eq!(ffi::sqlite3_initialize(), ffi::SQLITE_OK);
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let res = Connection::open_in_memory();
|
||||||
let _ = Connection::open_in_memory().unwrap();
|
assert!(res.is_err());
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ fn test_dummy_module() -> rusqlite::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(&'vtab self) -> Result<DummyTabCursor<'vtab>> {
|
fn open(&'vtab mut self) -> Result<DummyTabCursor<'vtab>> {
|
||||||
Ok(DummyTabCursor::default())
|
Ok(DummyTabCursor::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +88,7 @@ fn test_dummy_module() -> rusqlite::Result<()> {
|
|||||||
db.create_module::<DummyTab>("dummy", module, None)?;
|
db.create_module::<DummyTab>("dummy", module, None)?;
|
||||||
|
|
||||||
let version = version_number();
|
let version = version_number();
|
||||||
if version < 3_008_012 {
|
if version < 3_009_000 {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user