Merge pull request #1339 from gwenn/prepare_with_flags

Use SQLITE_PREPARE_PERSISTENT for CachedStatement
This commit is contained in:
gwenn 2023-06-03 11:38:28 +02:00 committed by GitHub
commit 371e60ab6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 27 deletions

View File

@ -506,6 +506,7 @@ mod bindings {
None
}
}
fn item_name(&self, original_item_name: &str) -> Option<String> {
original_item_name
.strip_prefix("sqlite3_index_info_")

View File

@ -1,7 +1,7 @@
//! Prepared statements cache for faster execution.
use crate::raw_statement::RawStatement;
use crate::{Connection, Result, Statement};
use crate::{Connection, PrepFlags, Result, Statement};
use hashlink::LruCache;
use std::cell::RefCell;
use std::ops::{Deref, DerefMut};
@ -144,7 +144,7 @@ impl StatementCache {
let mut cache = self.0.borrow_mut();
let stmt = match cache.remove(trimmed) {
Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)),
None => conn.prepare(trimmed),
None => conn.prepare_with_flags(trimmed, PrepFlags::SQLITE_PREPARE_PERSISTENT),
};
stmt.map(|mut stmt| {
stmt.stmt.set_statement_cache_key(trimmed);

View File

@ -61,7 +61,8 @@ pub enum DbConfig {
/// sqlite_master tables) are untainted by malicious content.
#[cfg(feature = "modern_sqlite")]
SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017, // 3.31.0
/// Sets or clears a flag that enables collection of the sqlite3_stmt_scanstatus_v2() statistics
/// Sets or clears a flag that enables collection of the
/// sqlite3_stmt_scanstatus_v2() statistics
#[cfg(feature = "modern_sqlite")]
SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018, // 3.42.0
/// Changes the default order in which tables and indexes are scanned

View File

@ -9,7 +9,7 @@ use std::sync::{Arc, Mutex};
use super::ffi;
use super::str_for_sqlite;
use super::{Connection, InterruptHandle, OpenFlags, Result};
use super::{Connection, InterruptHandle, OpenFlags, PrepFlags, Result};
use crate::error::{error_from_handle, error_from_sqlite_code, error_with_offset, Error};
use crate::raw_statement::RawStatement;
use crate::statement::Statement;
@ -218,33 +218,24 @@ impl InnerConnection {
unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
}
pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
let mut c_stmt = ptr::null_mut();
pub fn prepare<'a>(
&mut self,
conn: &'a Connection,
sql: &str,
flags: PrepFlags,
) -> Result<Statement<'a>> {
let mut c_stmt: *mut ffi::sqlite3_stmt = ptr::null_mut();
let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
let mut c_tail = ptr::null();
let mut c_tail: *const c_char = 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(
self.db(),
c_sql,
len,
&mut c_stmt as *mut *mut ffi::sqlite3_stmt,
&mut c_tail as *mut *const c_char,
)
};
let r = unsafe { self.prepare_(c_sql, len, flags, &mut c_stmt, &mut c_tail) };
#[cfg(feature = "unlock_notify")]
let r = unsafe {
use crate::unlock_notify;
let mut rc;
loop {
rc = ffi::sqlite3_prepare_v2(
self.db(),
c_sql,
len,
&mut c_stmt as *mut *mut ffi::sqlite3_stmt,
&mut c_tail as *mut *const c_char,
);
rc = self.prepare_(c_sql, len, flags, &mut c_stmt, &mut c_tail);
if !unlock_notify::is_locked(self.db, rc) {
break;
}
@ -261,8 +252,6 @@ impl InnerConnection {
}
// If the input text contains no SQL (if the input is an empty string or a
// comment) then *ppStmt is set to NULL.
let c_stmt: *mut ffi::sqlite3_stmt = c_stmt;
let c_tail: *const c_char = c_tail;
let tail = if c_tail.is_null() {
0
} else {
@ -278,6 +267,32 @@ impl InnerConnection {
}))
}
#[inline]
#[cfg(not(feature = "modern_sqlite"))]
unsafe fn prepare_(
&self,
z_sql: *const c_char,
n_byte: c_int,
_: PrepFlags,
pp_stmt: *mut *mut ffi::sqlite3_stmt,
pz_tail: *mut *const c_char,
) -> c_int {
ffi::sqlite3_prepare_v2(self.db(), z_sql, n_byte, pp_stmt, pz_tail)
}
#[inline]
#[cfg(feature = "modern_sqlite")]
unsafe fn prepare_(
&self,
z_sql: *const c_char,
n_byte: c_int,
flags: PrepFlags,
pp_stmt: *mut *mut ffi::sqlite3_stmt,
pz_tail: *mut *const c_char,
) -> c_int {
ffi::sqlite3_prepare_v3(self.db(), z_sql, n_byte, flags.bits(), pp_stmt, pz_tail)
}
#[inline]
pub fn changes(&self) -> u64 {
#[cfg(not(feature = "modern_sqlite"))]

View File

@ -711,7 +711,18 @@ impl Connection {
/// or if the underlying SQLite call fails.
#[inline]
pub fn prepare(&self, sql: &str) -> Result<Statement<'_>> {
self.db.borrow_mut().prepare(self, sql)
self.prepare_with_flags(sql, PrepFlags::default())
}
/// Prepare a SQL statement for execution.
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
/// or if the underlying SQLite call fails.
#[inline]
pub fn prepare_with_flags(&self, sql: &str, flags: PrepFlags) -> Result<Statement<'_>> {
self.db.borrow_mut().prepare(self, sql, flags)
}
/// Close the SQLite connection.
@ -897,7 +908,8 @@ impl Connection {
///
/// This function is unsafe because improper use may impact the Connection.
/// In particular, it should only be called on connections created
/// and owned by the caller, e.g. as a result of calling ffi::sqlite3_open().
/// and owned by the caller, e.g. as a result of calling
/// ffi::sqlite3_open().
#[inline]
pub unsafe fn from_handle_owned(db: *mut ffi::sqlite3) -> Result<Connection> {
let db = InnerConnection::new(db, true);
@ -1106,6 +1118,19 @@ impl Default for OpenFlags {
}
}
bitflags::bitflags! {
/// Prepare flags. See
/// [sqlite3_prepare_v3](https://sqlite.org/c3ref/c_prepare_normalize.html) for details.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[repr(C)]
pub struct PrepFlags: ::std::os::raw::c_uint {
/// A hint to the query planner that the prepared statement will be retained for a long time and probably reused many times.
const SQLITE_PREPARE_PERSISTENT = 0x01;
/// Causes the SQL compiler to return an error (error code SQLITE_ERROR) if the statement uses any virtual tables.
const SQLITE_PREPARE_NO_VTAB = 0x04;
}
}
/// rusqlite's check for a safe SQLite threading mode requires SQLite 3.7.0 or
/// later. If you are running against a SQLite older than that, rusqlite
/// attempts to ensure safety by performing configuration and initialization of