mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-22 15:30:34 +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"]
|
||||
column_decltype = []
|
||||
wasm32-wasi-vfs = ["libsqlite3-sys/wasm32-wasi-vfs"]
|
||||
# Note: doesn't support 32-bit.
|
||||
winsqlite3 = ["libsqlite3-sys/winsqlite3"]
|
||||
|
||||
# Helper feature for enabling most non-build-related optional features
|
||||
@ -109,7 +110,7 @@ bundled-full = ["modern-full", "bundled"]
|
||||
[dependencies]
|
||||
time = { version = "0.3.0", features = ["formatting", "macros", "parsing"], optional = true }
|
||||
bitflags = "1.2"
|
||||
hashlink = "0.7"
|
||||
hashlink = "0.8"
|
||||
chrono = { version = "0.4", optional = true, default-features = false, features = ["clock"] }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
csv = { version = "1.1", optional = true }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.24.1"
|
||||
version = "0.24.2"
|
||||
authors = ["The rusqlite developers"]
|
||||
edition = "2018"
|
||||
repository = "https://github.com/rusqlite/rusqlite"
|
||||
@ -35,8 +35,13 @@ session = ["preupdate_hook", "buildtime_bindgen"]
|
||||
in_gecko = []
|
||||
with-asan = []
|
||||
wasm32-wasi-vfs = []
|
||||
|
||||
# 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]
|
||||
openssl-sys = { version = "0.9", optional = true }
|
||||
|
@ -1,9 +1,9 @@
|
||||
/* automatically generated by rust-bindgen 0.59.2 */
|
||||
|
||||
pub const SQLITE_VERSION: &[u8; 7usize] = b"3.38.1\0";
|
||||
pub const SQLITE_VERSION_NUMBER: i32 = 3038001;
|
||||
pub const SQLITE_VERSION: &[u8; 7usize] = b"3.38.2\0";
|
||||
pub const SQLITE_VERSION_NUMBER: i32 = 3038002;
|
||||
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_ERROR: i32 = 1;
|
||||
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
|
||||
** 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
|
||||
** unit. This allows many compilers to do optimizations that would not be
|
||||
** possible if the files were compiled separately. Performance improvements
|
||||
@ -452,9 +452,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.38.1"
|
||||
#define SQLITE_VERSION_NUMBER 3038001
|
||||
#define SQLITE_SOURCE_ID "2022-03-12 13:37:29 38c210fdd258658321c85ec9c01a072fda3ada94540e3239d29b34dc547a8cbc"
|
||||
#define SQLITE_VERSION "3.38.2"
|
||||
#define SQLITE_VERSION_NUMBER 3038002
|
||||
#define SQLITE_SOURCE_ID "2022-03-26 13:51:10 d33c709cc0af66bc5b6dc6216eba9f1f0b40960b9ae83694c986fbf4c1d6f08f"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
@ -39691,11 +39691,17 @@ static int unixShmLock(
|
||||
int flags /* What to do with the lock */
|
||||
){
|
||||
unixFile *pDbFd = (unixFile*)fd; /* Connection holding shared memory */
|
||||
unixShm *p = pDbFd->pShm; /* The shared memory being locked */
|
||||
unixShmNode *pShmNode = p->pShmNode; /* The underlying file iNode */
|
||||
unixShm *p; /* The shared memory being locked */
|
||||
unixShmNode *pShmNode; /* The underlying file iNode */
|
||||
int rc = SQLITE_OK; /* Result code */
|
||||
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->pInode==pDbFd->pInode );
|
||||
@ -46983,10 +46989,14 @@ static int winShmLock(
|
||||
winFile *pDbFd = (winFile*)fd; /* Connection holding shared memory */
|
||||
winShm *p = pDbFd->pShm; /* The shared memory being locked */
|
||||
winShm *pX; /* For looping over all siblings */
|
||||
winShmNode *pShmNode = p->pShmNode;
|
||||
winShmNode *pShmNode;
|
||||
int rc = SQLITE_OK; /* Result code */
|
||||
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( n>=1 );
|
||||
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_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.
|
||||
**
|
||||
** 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 ){
|
||||
assert( pX->pKey==0 );
|
||||
/* 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
|
||||
|| (pCur->eState==CURSOR_INVALID && loc)
|
||||
|| CORRUPT_DB );
|
||||
|| (pCur->eState==CURSOR_INVALID && loc) );
|
||||
|
||||
pPage = pCur->pPage;
|
||||
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( !hasReadConflicts(p, pCur->pgnoRoot) );
|
||||
assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 );
|
||||
if( pCur->eState==CURSOR_REQUIRESEEK ){
|
||||
rc = btreeRestoreCursorPosition(pCur);
|
||||
assert( rc!=SQLITE_OK || CORRUPT_DB || pCur->eState==CURSOR_VALID );
|
||||
if( rc || pCur->eState!=CURSOR_VALID ) return rc;
|
||||
if( pCur->eState!=CURSOR_VALID ){
|
||||
if( pCur->eState>=CURSOR_REQUIRESEEK ){
|
||||
rc = btreeRestoreCursorPosition(pCur);
|
||||
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;
|
||||
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++){
|
||||
assert( pTab->aCol[i].affinity!=0 );
|
||||
assert( pTab->aCol[i].affinity!=0 || sqlite3VdbeParser(v)->nErr>0 );
|
||||
if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){
|
||||
zColAff[j++] = pTab->aCol[i].affinity;
|
||||
}
|
||||
@ -133539,7 +133552,7 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl
|
||||
sqlite3ResetAllSchemasOfConnection(db);
|
||||
pDb = &db->aDb[iDb];
|
||||
}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
|
||||
** the schema loaded, even if errors (other than OOM) occurred. In
|
||||
** 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);
|
||||
sParse.eParseMode = PARSE_MODE_DECLARE_VTAB;
|
||||
sParse.disableTriggers = 1;
|
||||
/* We should never be able to reach this point while loading the
|
||||
** schema. Nevertheless, defend against that (turn off db->init.busy)
|
||||
** 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
|
||||
** that depend only on the table being scanned, and that will tend to
|
||||
** cause many rows to be omitted, then mark that table as
|
||||
** "self-culling". */
|
||||
pLoop->wsFlags |= WHERE_SELFCULL;
|
||||
** "self-culling".
|
||||
**
|
||||
** 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 a truth probability is specified using the likelihood() hints,
|
||||
@ -157882,6 +157905,26 @@ whereBeginError:
|
||||
}
|
||||
#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
|
||||
** sqlite3WhereBegin() for additional information.
|
||||
@ -158134,14 +158177,15 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
|
||||
){
|
||||
int x = pOp->p2;
|
||||
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) ){
|
||||
Index *pPk = sqlite3PrimaryKeyIndex(pTab);
|
||||
x = pPk->aiColumn[x];
|
||||
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{
|
||||
testcase( x!=sqlite3StorageColumnToTable(pTab,x) );
|
||||
x = sqlite3StorageColumnToTable(pTab,x);
|
||||
@ -158151,9 +158195,22 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
|
||||
pOp->p2 = x;
|
||||
pOp->p1 = pLevel->iIdxCur;
|
||||
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 ){
|
||||
pOp->p1 = pLevel->iIdxCur;
|
||||
pOp->opcode = OP_IdxRowid;
|
||||
@ -234376,7 +234433,7 @@ static void fts5SourceIdFunc(
|
||||
){
|
||||
assert( nArg==0 );
|
||||
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()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.38.1"
|
||||
#define SQLITE_VERSION_NUMBER 3038001
|
||||
#define SQLITE_SOURCE_ID "2022-03-12 13:37:29 38c210fdd258658321c85ec9c01a072fda3ada94540e3239d29b34dc547a8cbc"
|
||||
#define SQLITE_VERSION "3.38.2"
|
||||
#define SQLITE_VERSION_NUMBER 3038002
|
||||
#define SQLITE_SOURCE_ID "2022-03-26 13:51:10 d33c709cc0af66bc5b6dc6216eba9f1f0b40960b9ae83694c986fbf4c1d6f08f"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
|
@ -5,6 +5,9 @@
|
||||
#[cfg(feature = "bundled-sqlcipher-vendored-openssl")]
|
||||
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::*;
|
||||
|
||||
use std::default::Default;
|
||||
|
@ -9,7 +9,7 @@ export SQLITE3_LIB_DIR="$SCRIPT_DIR/sqlite3"
|
||||
export SQLITE3_INCLUDE_DIR="$SQLITE3_LIB_DIR"
|
||||
|
||||
# Download and extract amalgamation
|
||||
SQLITE=sqlite-amalgamation-3380100
|
||||
SQLITE=sqlite-amalgamation-3380200
|
||||
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.h" > "$SQLITE3_LIB_DIR/sqlite3.h"
|
||||
|
@ -33,7 +33,7 @@ pub enum DbConfig {
|
||||
SQLITE_DBCONFIG_TRIGGER_EQP = 1008, // 3.22.0
|
||||
/// Activates or deactivates the "reset" flag for a database connection.
|
||||
/// 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.
|
||||
SQLITE_DBCONFIG_DEFENSIVE = 1010, // 3.26.0
|
||||
/// 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")]
|
||||
ToSqlOutput::ZeroBlob(len) => {
|
||||
// TODO sqlite3_result_zeroblob64 // 3.8.11
|
||||
return ffi::sqlite3_result_zeroblob(ctx, len);
|
||||
}
|
||||
#[cfg(feature = "array")]
|
||||
@ -50,6 +51,7 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
|
||||
// TODO sqlite3_result_error
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -60,6 +62,7 @@ pub(super) unsafe fn set_result(ctx: *mut sqlite3_context, result: &ToSqlOutput<
|
||||
} else if length == 0 {
|
||||
ffi::sqlite3_result_zeroblob(ctx, 0);
|
||||
} else {
|
||||
// TODO sqlite3_result_blob64 // 3.8.7
|
||||
ffi::sqlite3_result_blob(
|
||||
ctx,
|
||||
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.
|
||||
|
||||
#[cold]
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -162,6 +162,19 @@ impl Context<'_> {
|
||||
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
|
||||
/// parameter. This is intended to be an easier-to-use way of fetching it
|
||||
/// compared to calling [`get_aux`](Context::get_aux) and
|
||||
@ -234,6 +247,13 @@ impl Context<'_> {
|
||||
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.
|
||||
@ -319,7 +339,7 @@ bitflags::bitflags! {
|
||||
/// Specifies UTF-16 using native byte order as the text encoding this SQL function prefers for its parameters.
|
||||
const SQLITE_UTF16 = ffi::SQLITE_UTF16;
|
||||
/// 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.
|
||||
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.
|
||||
|
@ -285,7 +285,7 @@ impl<'c> AuthAction<'c> {
|
||||
operation: TransactionOperation::from_str(operation_str),
|
||||
savepoint_name,
|
||||
},
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
#[cfg(feature = "modern_sqlite")] // 3.8.3
|
||||
(ffi::SQLITE_RECURSIVE, ..) => Self::Recursive,
|
||||
(code, arg1, arg2) => Self::Unknown { code, arg1, arg2 },
|
||||
}
|
||||
|
@ -222,6 +222,7 @@ impl InnerConnection {
|
||||
let mut c_stmt = ptr::null_mut();
|
||||
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
|
||||
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"))]
|
||||
let r = unsafe {
|
||||
ffi::sqlite3_prepare_v2(
|
||||
@ -278,8 +279,15 @@ impl InnerConnection {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn changes(&self) -> usize {
|
||||
unsafe { ffi::sqlite3_changes(self.db()) as usize }
|
||||
pub fn changes(&self) -> u64 {
|
||||
#[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]
|
||||
@ -310,6 +318,50 @@ impl InnerConnection {
|
||||
#[cfg(not(feature = "hooks"))]
|
||||
#[inline]
|
||||
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 {
|
||||
|
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
|
||||
//! expose an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres).
|
||||
//! Rusqlite is an ergonomic wrapper for using SQLite from Rust.
|
||||
//!
|
||||
//! 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
|
||||
//! use rusqlite::{params, Connection, Result};
|
||||
@ -346,27 +350,59 @@ impl Drop for Connection {
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
/// Open a new connection to a SQLite database.
|
||||
///
|
||||
/// `Connection::open(path)` is equivalent to
|
||||
/// `Connection::open_with_flags(path,
|
||||
/// OpenFlags::SQLITE_OPEN_READ_WRITE |
|
||||
/// OpenFlags::SQLITE_OPEN_CREATE)`.
|
||||
/// Open a new connection to a SQLite database. If a database does not exist
|
||||
/// at the path, one is created.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use rusqlite::{Connection, Result};
|
||||
/// fn open_my_db() -> Result<()> {
|
||||
/// let path = "./my_db.db3";
|
||||
/// let db = Connection::open(&path)?;
|
||||
/// let db = Connection::open(path)?;
|
||||
/// // Use the database somehow...
|
||||
/// println!("{}", db.is_autocommit());
|
||||
/// 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
|
||||
///
|
||||
/// Will return `Err` if `path` cannot be converted to a C-compatible
|
||||
/// string or if the underlying SQLite open call fails.
|
||||
/// Will return `Err` if `path` cannot be converted to a C-compatible string
|
||||
/// or if the underlying SQLite open call fails.
|
||||
#[inline]
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Connection> {
|
||||
let flags = OpenFlags::default();
|
||||
@ -901,8 +937,10 @@ impl Connection {
|
||||
/// Return the number of rows modified, inserted or deleted by the most
|
||||
/// recently completed INSERT, UPDATE or DELETE statement on the database
|
||||
/// connection.
|
||||
///
|
||||
/// See <https://www.sqlite.org/c3ref/changes.html>
|
||||
#[inline]
|
||||
fn changes(&self) -> usize {
|
||||
pub fn changes(&self) -> u64 {
|
||||
self.db.borrow().changes()
|
||||
}
|
||||
|
||||
@ -927,6 +965,13 @@ impl Connection {
|
||||
pub fn cache_flush(&self) -> Result<()> {
|
||||
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 {
|
||||
@ -999,40 +1044,81 @@ impl<'conn> Iterator for Batch<'conn, '_> {
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Flags for opening SQLite database connections.
|
||||
/// See [sqlite3_open_v2](http://www.sqlite.org/c3ref/open.html) for details.
|
||||
/// Flags for opening SQLite database connections. See
|
||||
/// [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)]
|
||||
pub struct OpenFlags: ::std::os::raw::c_int {
|
||||
/// The database is opened in read-only mode.
|
||||
/// 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,
|
||||
/// 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.
|
||||
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
|
||||
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.
|
||||
const SQLITE_OPEN_URI = 0x0000_0040;
|
||||
const SQLITE_OPEN_URI = 0x0000_0040;
|
||||
/// The database will be opened as an in-memory database.
|
||||
const SQLITE_OPEN_MEMORY = 0x0000_0080;
|
||||
/// The new database connection will use the "multi-thread" threading mode.
|
||||
const SQLITE_OPEN_NO_MUTEX = ffi::SQLITE_OPEN_NOMUTEX;
|
||||
/// The new database connection will use the "serialized" threading mode.
|
||||
const SQLITE_OPEN_FULL_MUTEX = ffi::SQLITE_OPEN_FULLMUTEX;
|
||||
/// The database is opened shared cache enabled.
|
||||
const SQLITE_OPEN_SHARED_CACHE = 0x0002_0000;
|
||||
const SQLITE_OPEN_MEMORY = 0x0000_0080;
|
||||
/// The new database connection will not use a per-connection mutex (the
|
||||
/// connection will use the "multi-thread" threading mode, in SQLite
|
||||
/// parlance).
|
||||
///
|
||||
/// This is used by default, as proper `Send`/`Sync` usage (in
|
||||
/// 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.
|
||||
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;
|
||||
/// Extended result codes.
|
||||
/// Extended result codes. (3.37.0)
|
||||
const SQLITE_OPEN_EXRESCODE = 0x0200_0000;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OpenFlags {
|
||||
#[inline]
|
||||
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_CREATE
|
||||
| OpenFlags::SQLITE_OPEN_NO_MUTEX
|
||||
@ -1508,15 +1594,28 @@ mod test {
|
||||
#[test]
|
||||
fn test_pragma_query_row() -> Result<()> {
|
||||
let db = Connection::open_in_memory()?;
|
||||
|
||||
assert_eq!(
|
||||
"memory",
|
||||
db.query_row::<String, _, _>("PRAGMA journal_mode", [], |r| r.get(0))?
|
||||
);
|
||||
assert_eq!(
|
||||
"off",
|
||||
db.query_row::<String, _, _>("PRAGMA journal_mode=off", [], |r| r.get(0))?
|
||||
);
|
||||
let mode = db.query_row::<String, _, _>("PRAGMA journal_mode=off", [], |r| r.get(0))?;
|
||||
if cfg!(features = "bundled") {
|
||||
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(())
|
||||
}
|
||||
|
||||
@ -2012,4 +2111,12 @@ mod test {
|
||||
let db = Connection::open_in_memory()?;
|
||||
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 journal_mode: String =
|
||||
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
|
||||
assert_eq!(
|
||||
"off",
|
||||
db.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row
|
||||
.get::<_, String>(0))?,
|
||||
);
|
||||
let mode = db
|
||||
.pragma_update_and_check(None, "journal_mode", &"OFF", |row| row.get::<_, String>(0))?;
|
||||
assert!(mode == "off" || mode == "memory", "mode: {:?}", mode);
|
||||
|
||||
let param: &dyn crate::ToSql = &"OFF";
|
||||
assert_eq!(
|
||||
"off",
|
||||
db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?,
|
||||
);
|
||||
let mode =
|
||||
db.pragma_update_and_check(None, "journal_mode", param, |row| row.get::<_, String>(0))?;
|
||||
assert!(mode == "off" || mode == "memory", "mode: {:?}", mode);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -224,6 +224,14 @@ impl RawStatement {
|
||||
pub fn tail(&self) -> usize {
|
||||
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 {
|
||||
|
@ -132,6 +132,7 @@ impl Statement<'_> {
|
||||
/// Will return `Err` if binding parameters fails, the executed statement
|
||||
/// returns rows (in which case `query` should be used instead), or the
|
||||
/// underlying SQLite call fails.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "You can use `execute` with named params now."]
|
||||
#[inline]
|
||||
pub fn execute_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<usize> {
|
||||
@ -267,6 +268,7 @@ impl Statement<'_> {
|
||||
/// # Failure
|
||||
///
|
||||
/// Will return `Err` if binding parameters fails.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "You can use `query` with named params now."]
|
||||
pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<Rows<'_>> {
|
||||
self.query(params)
|
||||
@ -344,6 +346,7 @@ impl Statement<'_> {
|
||||
/// ## Failure
|
||||
///
|
||||
/// Will return `Err` if binding parameters fails.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "You can use `query_map` with named params now."]
|
||||
pub fn query_map_named<T, F>(
|
||||
&mut self,
|
||||
@ -436,6 +439,7 @@ impl Statement<'_> {
|
||||
/// ## Failure
|
||||
///
|
||||
/// Will return `Err` if binding parameters fails.
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "You can use `query_and_then` with named params now."]
|
||||
pub fn query_and_then_named<T, E, F>(
|
||||
&mut self,
|
||||
@ -503,6 +507,7 @@ impl Statement<'_> {
|
||||
///
|
||||
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
|
||||
/// or if the underlying SQLite call fails.
|
||||
#[doc(hidden)]
|
||||
#[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>
|
||||
where
|
||||
@ -727,6 +732,7 @@ impl Statement<'_> {
|
||||
|
||||
#[cfg(feature = "blob")]
|
||||
ToSqlOutput::ZeroBlob(len) => {
|
||||
// TODO sqlite3_bind_zeroblob64 // 3.8.11
|
||||
return self
|
||||
.conn
|
||||
.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::Text(s) => unsafe {
|
||||
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)
|
||||
},
|
||||
ValueRef::Blob(b) => unsafe {
|
||||
@ -757,6 +764,7 @@ impl Statement<'_> {
|
||||
if length == 0 {
|
||||
ffi::sqlite3_bind_zeroblob(ptr, col as c_int, 0)
|
||||
} else {
|
||||
// TODO sqlite3_bind_blob64 // 3.8.7
|
||||
ffi::sqlite3_bind_blob(
|
||||
ptr,
|
||||
col as c_int,
|
||||
@ -775,7 +783,7 @@ impl Statement<'_> {
|
||||
let r = self.stmt.step();
|
||||
self.stmt.reset();
|
||||
match r {
|
||||
ffi::SQLITE_DONE => Ok(self.conn.changes()),
|
||||
ffi::SQLITE_DONE => Ok(self.conn.changes() as usize),
|
||||
ffi::SQLITE_ROW => Err(Error::ExecuteReturnedResults),
|
||||
_ => Err(self.conn.decode_result(r).unwrap_err()),
|
||||
}
|
||||
@ -838,6 +846,16 @@ impl Statement<'_> {
|
||||
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")]
|
||||
#[inline]
|
||||
pub(crate) fn check_no_tail(&self) -> Result<()> {
|
||||
@ -985,15 +1003,15 @@ pub enum StatementStatus {
|
||||
AutoIndex = 3,
|
||||
/// Equivalent to SQLITE_STMTSTATUS_VM_STEP
|
||||
VmStep = 4,
|
||||
/// Equivalent to SQLITE_STMTSTATUS_REPREPARE
|
||||
/// Equivalent to SQLITE_STMTSTATUS_REPREPARE (3.20.0)
|
||||
RePrepare = 5,
|
||||
/// Equivalent to SQLITE_STMTSTATUS_RUN
|
||||
/// Equivalent to SQLITE_STMTSTATUS_RUN (3.20.0)
|
||||
Run = 6,
|
||||
/// Equivalent to SQLITE_STMTSTATUS_FILTER_MISS
|
||||
FilterMiss = 7,
|
||||
/// Equivalent to SQLITE_STMTSTATUS_FILTER_HIT
|
||||
FilterHit = 8,
|
||||
/// Equivalent to SQLITE_STMTSTATUS_MEMUSED
|
||||
/// Equivalent to SQLITE_STMTSTATUS_MEMUSED (3.20.0)
|
||||
MemUsed = 99,
|
||||
}
|
||||
|
||||
@ -1509,6 +1527,15 @@ mod test {
|
||||
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]
|
||||
#[cfg(feature = "modern_sqlite")]
|
||||
fn test_error_offset() -> Result<()> {
|
||||
|
@ -119,6 +119,8 @@ impl Connection {
|
||||
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)]
|
||||
|
@ -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 {
|
||||
/// 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<'_>> {
|
||||
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)]
|
||||
@ -710,4 +734,25 @@ mod test {
|
||||
assert_eq!(x, i);
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
||||
fn open(&self) -> Result<ArrayTabCursor<'_>> {
|
||||
fn open(&mut self) -> Result<ArrayTabCursor<'_>> {
|
||||
Ok(ArrayTabCursor::new())
|
||||
}
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ use std::str;
|
||||
use crate::ffi;
|
||||
use crate::types::Null;
|
||||
use crate::vtab::{
|
||||
dequote, escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo,
|
||||
VTab, VTabConnection, VTabCursor, Values,
|
||||
escape_double_quote, parse_boolean, read_only_module, Context, CreateVTab, IndexInfo, VTab,
|
||||
VTabConfig, VTabConnection, VTabCursor, VTabKind, Values,
|
||||
};
|
||||
use crate::{Connection, Error, Result};
|
||||
|
||||
@ -74,19 +74,6 @@ impl CsvTab {
|
||||
.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> {
|
||||
if arg.len() == 1 {
|
||||
arg.bytes().next()
|
||||
@ -101,7 +88,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
||||
type Cursor = CsvTabCursor<'vtab>;
|
||||
|
||||
fn connect(
|
||||
_: &mut VTabConnection,
|
||||
db: &mut VTabConnection,
|
||||
_aux: Option<&()>,
|
||||
args: &[&[u8]],
|
||||
) -> Result<(String, CsvTab)> {
|
||||
@ -122,7 +109,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
||||
|
||||
let args = &args[3..];
|
||||
for c_slice in args {
|
||||
let (param, value) = CsvTab::parameter(c_slice)?;
|
||||
let (param, value) = super::parameter(c_slice)?;
|
||||
match param {
|
||||
"filename" => {
|
||||
if !Path::new(value).exists() {
|
||||
@ -249,7 +236,7 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
||||
}
|
||||
schema = Some(sql);
|
||||
}
|
||||
|
||||
db.config(VTabConfig::DirectOnly)?;
|
||||
Ok((schema.unwrap(), vtab))
|
||||
}
|
||||
|
||||
@ -259,12 +246,14 @@ unsafe impl<'vtab> VTab<'vtab> for CsvTab {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open(&self) -> Result<CsvTabCursor<'_>> {
|
||||
fn open(&mut self) -> Result<CsvTabCursor<'_>> {
|
||||
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
|
||||
#[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_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
|
||||
///
|
||||
/// (See [SQLite doc](https://sqlite.org/c3ref/module.html))
|
||||
@ -84,31 +101,26 @@ const ZERO_MODULE: ffi::sqlite3_module = unsafe {
|
||||
.module
|
||||
};
|
||||
|
||||
/// 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> {
|
||||
// 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.
|
||||
macro_rules! module {
|
||||
($lt:lifetime, $vt:ty, $ct:ty, $xc:expr, $xd:expr, $xu:expr) => {
|
||||
#[allow(clippy::needless_update)]
|
||||
&Module {
|
||||
base: ffi::sqlite3_module {
|
||||
// We don't use V3
|
||||
iVersion: 2, // We don't use V2 or V3 features in read_only_module types
|
||||
xCreate: Some(rust_create::<T>),
|
||||
xConnect: Some(rust_connect::<T>),
|
||||
xBestIndex: Some(rust_best_index::<T>),
|
||||
xDisconnect: Some(rust_disconnect::<T>),
|
||||
xDestroy: Some(rust_destroy::<T>),
|
||||
xOpen: Some(rust_open::<T>),
|
||||
xClose: Some(rust_close::<T::Cursor>),
|
||||
xFilter: Some(rust_filter::<T::Cursor>),
|
||||
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,
|
||||
iVersion: 2,
|
||||
xCreate: $xc,
|
||||
xConnect: Some(rust_connect::<$vt>),
|
||||
xBestIndex: Some(rust_best_index::<$vt>),
|
||||
xDisconnect: Some(rust_disconnect::<$vt>),
|
||||
xDestroy: $xd,
|
||||
xOpen: Some(rust_open::<$vt>),
|
||||
xClose: Some(rust_close::<$ct>),
|
||||
xFilter: Some(rust_filter::<$ct>),
|
||||
xNext: Some(rust_next::<$ct>),
|
||||
xEof: Some(rust_eof::<$ct>),
|
||||
xColumn: Some(rust_column::<$ct>),
|
||||
xRowid: Some(rust_rowid::<$ct>), // FIXME optional
|
||||
xUpdate: $xu,
|
||||
xBegin: None,
|
||||
xSync: None,
|
||||
xCommit: None,
|
||||
@ -120,7 +132,46 @@ pub fn read_only_module<'vtab, T: CreateVTab<'vtab>>() -> &'static Module<'vtab,
|
||||
xRollbackTo: None,
|
||||
..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).
|
||||
#[must_use]
|
||||
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
|
||||
// as the xConnect method For eponymous-only virtual tables, the xCreate
|
||||
// method is NULL
|
||||
#[allow(clippy::needless_update)]
|
||||
&Module {
|
||||
base: ffi::sqlite3_module {
|
||||
// We don't use V3
|
||||
iVersion: 2,
|
||||
xCreate: None,
|
||||
xConnect: Some(rust_connect::<T>),
|
||||
xBestIndex: Some(rust_best_index::<T>),
|
||||
xDisconnect: Some(rust_disconnect::<T>),
|
||||
xDestroy: None,
|
||||
xOpen: Some(rust_open::<T>),
|
||||
xClose: Some(rust_close::<T::Cursor>),
|
||||
xFilter: Some(rust_filter::<T::Cursor>),
|
||||
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>,
|
||||
}
|
||||
// For eponymous-only virtual tables, the xCreate method is NULL
|
||||
module!('vtab, T, T::Cursor, None, None, None)
|
||||
}
|
||||
|
||||
/// Virtual table configuration options
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
#[cfg(feature = "modern_sqlite")] // 3.7.7
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum VTabConfig {
|
||||
/// Equivalent to SQLITE_VTAB_CONSTRAINT_SUPPORT
|
||||
ConstraintSupport = 1,
|
||||
/// Equivalent to SQLITE_VTAB_INNOCUOUS
|
||||
Innocuous = 2,
|
||||
/// Equivalent to SQLITE_VTAB_DIRECTONLY
|
||||
DirectOnly = 3,
|
||||
}
|
||||
|
||||
/// `feature = "vtab"`
|
||||
pub struct VTabConnection(*mut ffi::sqlite3);
|
||||
|
||||
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.
|
||||
///
|
||||
@ -191,7 +229,7 @@ impl VTabConnection {
|
||||
}
|
||||
}
|
||||
|
||||
/// Virtual table instance trait.
|
||||
/// Eponymous-only virtual table instance trait.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
@ -229,13 +267,17 @@ pub unsafe trait VTab<'vtab>: Sized {
|
||||
|
||||
/// Create a new cursor used for accessing a virtual table.
|
||||
/// (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))
|
||||
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
|
||||
/// TABLE statement. The `db` parameter is a pointer to the SQLite
|
||||
/// 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.
|
||||
/// See [Virtual Table Constraint Operator Codes](https://sqlite.org/c3ref/c_index_constraint_eq.html) for details.
|
||||
#[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
|
||||
/// [`VTab::best_index`] method.
|
||||
///
|
||||
/// (See [SQLite doc](http://sqlite.org/c3ref/index_info.html))
|
||||
#[derive(Debug)]
|
||||
pub struct IndexInfo(*mut ffi::sqlite3_index_info);
|
||||
|
||||
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
|
||||
#[inline]
|
||||
pub fn set_order_by_consumed(&mut self, order_by_consumed: bool) {
|
||||
@ -402,10 +483,60 @@ impl IndexInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO idxFlags
|
||||
// TODO colUsed
|
||||
/// Mask of SQLITE_INDEX_SCAN_* flags.
|
||||
#[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.
|
||||
@ -583,7 +714,7 @@ impl Context {
|
||||
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
|
||||
@ -651,6 +782,7 @@ impl Values<'_> {
|
||||
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> {
|
||||
@ -707,6 +839,13 @@ impl InnerConnection {
|
||||
module: &'static Module<'vtab, T>,
|
||||
aux: Option<T::Aux>,
|
||||
) -> 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 r = match aux {
|
||||
Some(aux) => {
|
||||
@ -754,7 +893,7 @@ pub fn dequote(s: &str) -> &str {
|
||||
}
|
||||
match s.bytes().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,
|
||||
@ -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
|
||||
unsafe extern "C" fn free_boxed_value<T>(p: *mut c_void) {
|
||||
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
|
||||
/// `zErrMsg`.
|
||||
#[cold]
|
||||
@ -1138,6 +1334,8 @@ pub mod csvtab;
|
||||
#[cfg(feature = "series")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "series")))]
|
||||
pub mod series; // SQLite >= 3.9.0
|
||||
#[cfg(test)]
|
||||
mod vtablog;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
@ -10,8 +10,8 @@ use std::os::raw::c_int;
|
||||
use crate::ffi;
|
||||
use crate::types::Type;
|
||||
use crate::vtab::{
|
||||
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConnection, VTabCursor,
|
||||
Values,
|
||||
eponymous_only_module, Context, IndexConstraintOp, IndexInfo, VTab, VTabConfig, VTabConnection,
|
||||
VTabCursor, Values,
|
||||
};
|
||||
use crate::{Connection, Error, Result};
|
||||
|
||||
@ -57,13 +57,14 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
|
||||
type Cursor = SeriesTabCursor<'vtab>;
|
||||
|
||||
fn connect(
|
||||
_: &mut VTabConnection,
|
||||
db: &mut VTabConnection,
|
||||
_aux: Option<&()>,
|
||||
_args: &[&[u8]],
|
||||
) -> Result<(String, SeriesTab)> {
|
||||
let vtab = SeriesTab {
|
||||
base: ffi::sqlite3_vtab::default(),
|
||||
};
|
||||
db.config(VTabConfig::Innocuous)?;
|
||||
Ok((
|
||||
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)".to_owned(),
|
||||
vtab,
|
||||
@ -103,6 +104,8 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
|
||||
let mut constraint_usage = info.constraint_usage(*j);
|
||||
constraint_usage.set_argv_index(n_arg);
|
||||
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() {
|
||||
return Err(Error::SqliteFailure(
|
||||
@ -150,7 +153,7 @@ unsafe impl<'vtab> VTab<'vtab> for SeriesTab {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open(&self) -> Result<SeriesTabCursor<'_>> {
|
||||
fn open(&mut self) -> Result<SeriesTabCursor<'_>> {
|
||||
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;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_error_when_singlethread_mode() {
|
||||
// put SQLite into single-threaded mode
|
||||
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 {
|
||||
return;
|
||||
}
|
||||
if ffi::sqlite3_initialize() != ffi::SQLITE_OK {
|
||||
return;
|
||||
}
|
||||
assert_eq!(ffi::sqlite3_initialize(), ffi::SQLITE_OK);
|
||||
}
|
||||
|
||||
let _ = Connection::open_in_memory().unwrap();
|
||||
let res = Connection::open_in_memory();
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ fn test_dummy_module() -> rusqlite::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open(&'vtab self) -> Result<DummyTabCursor<'vtab>> {
|
||||
fn open(&'vtab mut self) -> Result<DummyTabCursor<'vtab>> {
|
||||
Ok(DummyTabCursor::default())
|
||||
}
|
||||
}
|
||||
@ -88,7 +88,7 @@ fn test_dummy_module() -> rusqlite::Result<()> {
|
||||
db.create_module::<DummyTab>("dummy", module, None)?;
|
||||
|
||||
let version = version_number();
|
||||
if version < 3_008_012 {
|
||||
if version < 3_009_000 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user