Merge remote-tracking branch 'origin/master' into error_offset

# Conflicts:
#	src/statement.rs
This commit is contained in:
gwenn 2022-04-20 07:27:05 +02:00
commit cfcbb56fce
27 changed files with 1047 additions and 204 deletions

View File

@ -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 }

View File

@ -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 }

View File

@ -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;

View File

@ -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 ){
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,9 +154596,18 @@ 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". */
** "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,
** then use the probability provided by the application. */
@ -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);
}
/*

View File

@ -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

View File

@ -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;

View File

@ -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"

View File

@ -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.

View File

@ -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>(),

View File

@ -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)
}

View File

@ -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.

View File

@ -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 },
}

View File

@ -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 {

View File

@ -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,8 +1044,12 @@ 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.
@ -1016,23 +1065,60 @@ bitflags::bitflags! {
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.
/// 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 the "serialized" threading mode.
/// 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 shared cache enabled.
/// 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(())
}
}

View File

@ -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(())
}

View File

@ -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 {

View File

@ -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<()> {

View File

@ -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)]

View File

@ -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(())
}
}

View File

@ -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
}

View File

@ -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())
}
}

View File

@ -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)]

View File

@ -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 {

View File

@ -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
View 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(())
}
}

View File

@ -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());
}

View File

@ -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(());
}