blob: cedf752e6adef404b6f7d537f521045cb8342b5a [file]
use crate::connection::handle::ConnectionHandle;
use crate::connection::LogSettings;
use crate::connection::{ConnectionState, Statements};
use crate::error::Error;
use crate::SqliteConnectOptions;
use libsqlite3_sys::{
sqlite3_busy_timeout, SQLITE_OPEN_CREATE, SQLITE_OPEN_FULLMUTEX, SQLITE_OPEN_MEMORY,
SQLITE_OPEN_NOMUTEX, SQLITE_OPEN_PRIVATECACHE, SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE,
SQLITE_OPEN_SHAREDCACHE, SQLITE_OPEN_URI,
};
use percent_encoding::NON_ALPHANUMERIC;
use std::collections::BTreeMap;
use std::ffi::CString;
use std::io;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
#[cfg(feature = "load-extension")]
use sqlx_core::IndexMap;
// This was originally `AtomicU64` but that's not supported on MIPS (or PowerPC):
// https://github.com/launchbadge/sqlx/issues/2859
// https://doc.rust-lang.org/stable/std/sync/atomic/index.html#portability
static THREAD_ID: AtomicUsize = AtomicUsize::new(0);
pub struct EstablishParams {
filename: CString,
open_flags: i32,
busy_timeout: Duration,
statement_cache_capacity: usize,
log_settings: LogSettings,
#[cfg(feature = "load-extension")]
extensions: IndexMap<CString, Option<CString>>,
pub(crate) thread_name: String,
pub(crate) command_channel_size: usize,
#[cfg(feature = "regexp")]
register_regexp_function: bool,
}
impl EstablishParams {
pub fn from_options(options: &SqliteConnectOptions) -> Result<Self, Error> {
let mut filename = options
.filename
.to_str()
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"filename passed to SQLite must be valid UTF-8",
)
})?
.to_owned();
// Set common flags we expect to have in sqlite
let mut flags = SQLITE_OPEN_URI;
// By default, we connect to an in-memory database.
// [SQLITE_OPEN_NOMUTEX] will instruct [sqlite3_open_v2] to return an error if it
// cannot satisfy our wish for a thread-safe, lock-free connection object
flags |= if options.serialized {
SQLITE_OPEN_FULLMUTEX
} else {
SQLITE_OPEN_NOMUTEX
};
flags |= if options.read_only {
SQLITE_OPEN_READONLY
} else if options.create_if_missing {
SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE
} else {
SQLITE_OPEN_READWRITE
};
if options.in_memory {
flags |= SQLITE_OPEN_MEMORY;
}
flags |= if options.shared_cache {
SQLITE_OPEN_SHAREDCACHE
} else {
SQLITE_OPEN_PRIVATECACHE
};
let mut query_params = BTreeMap::new();
if options.immutable {
query_params.insert("immutable", "true");
}
if let Some(vfs) = options.vfs.as_deref() {
query_params.insert("vfs", vfs);
}
if !query_params.is_empty() {
filename = format!(
"file:{}?",
percent_encoding::percent_encode(filename.as_bytes(), NON_ALPHANUMERIC),
);
// Suffix serializer automatically handles `&` separators for us.
let filename_len = filename.len();
filename = form_urlencoded::Serializer::for_suffix(filename, filename_len)
.extend_pairs(query_params)
.finish();
}
let filename = CString::new(filename).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"filename passed to SQLite must not contain nul bytes",
)
})?;
#[cfg(feature = "load-extension")]
let extensions = options
.extensions
.iter()
.map(|(name, entry)| {
let entry = entry
.as_ref()
.map(|e| {
CString::new(e.as_bytes()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"extension entrypoint names passed to SQLite must not contain nul bytes"
)
})
})
.transpose()?;
Ok((
CString::new(name.as_bytes()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"extension names passed to SQLite must not contain nul bytes",
)
})?,
entry,
))
})
.collect::<Result<IndexMap<CString, Option<CString>>, io::Error>>()?;
let thread_id = THREAD_ID.fetch_add(1, Ordering::AcqRel);
Ok(Self {
filename,
open_flags: flags,
busy_timeout: options.busy_timeout,
statement_cache_capacity: options.statement_cache_capacity,
log_settings: options.log_settings.clone(),
#[cfg(feature = "load-extension")]
extensions,
thread_name: (options.thread_name)(thread_id as u64),
command_channel_size: options.command_channel_size,
#[cfg(feature = "regexp")]
register_regexp_function: options.register_regexp_function,
})
}
pub(crate) fn establish(&self) -> Result<ConnectionState, Error> {
let mut handle = ConnectionHandle::open(&self.filename, self.open_flags)?;
#[cfg(feature = "load-extension")]
unsafe {
self.apply_extensions(&mut handle)?;
}
#[cfg(feature = "regexp")]
if self.register_regexp_function {
// configure a `regexp` function for sqlite, it does not come with one by default
let status = crate::regexp::register(handle.as_ptr());
if status != libsqlite3_sys::SQLITE_OK {
return Err(Error::Database(Box::new(handle.expect_error())));
}
}
// Configure a busy timeout
// This causes SQLite to automatically sleep in increasing intervals until the time
// when there is something locked during [sqlite3_step].
//
// We also need to convert the u128 value to i32, checking we're not overflowing.
let ms = i32::try_from(self.busy_timeout.as_millis())
.expect("Given busy timeout value is too big.");
handle.call_with_result(|db| unsafe { sqlite3_busy_timeout(db, ms) })?;
Ok(ConnectionState {
handle,
statements: Statements::new(self.statement_cache_capacity),
log_settings: self.log_settings.clone(),
progress_handler_callback: None,
update_hook_callback: None,
#[cfg(feature = "preupdate-hook")]
preupdate_hook_callback: None,
commit_hook_callback: None,
rollback_hook_callback: None,
})
}
#[cfg(feature = "load-extension")]
unsafe fn apply_extensions(&self, handle: &mut ConnectionHandle) -> Result<(), Error> {
use libsqlite3_sys::{sqlite3_free, sqlite3_load_extension};
use std::ffi::{c_int, CStr};
use std::ptr;
/// `true` enables *just* `sqlite3_load_extension`, false disables *all* extension loading.
fn enable_load_extension(
handle: &mut ConnectionHandle,
enabled: bool,
) -> Result<(), Error> {
use libsqlite3_sys::{sqlite3_db_config, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION};
// SAFETY: we have exclusive access and this matches the expected signature
// <https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension>
handle.call_with_result(|db| unsafe {
// https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.bool-char-as-int
sqlite3_db_config(
db,
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,
enabled as c_int,
ptr::null_mut::<c_int>(),
)
})?;
Ok(())
}
if self.extensions.is_empty() {
return Ok(());
}
// We enable extension loading only so long as *we're* doing it.
enable_load_extension(handle, true)?;
for (name, entrypoint) in &self.extensions {
let name_ptr = name.as_ptr();
let entrypoint_ptr = entrypoint.as_ref().map_or_else(ptr::null, |s| s.as_ptr());
let mut err_msg_ptr = ptr::null_mut();
// SAFETY:
// * we have exclusive access
// * all pointers are initialized
// * we warn the user about loading extensions in documentation
handle
.call_with_result(|db| unsafe {
sqlite3_load_extension(db, name_ptr, entrypoint_ptr, &mut err_msg_ptr)
})
.map_err(|e| {
if !err_msg_ptr.is_null() {
// SAFETY: pointer is not-null,
// and we copy the error message to an allocation we own.
let err_msg = unsafe { CStr::from_ptr(err_msg_ptr) }
// In practice, the string *should* be UTF-8.
.to_string_lossy()
.into_owned();
// SAFETY: we're expected to free the error message afterward.
unsafe {
sqlite3_free(err_msg_ptr.cast());
}
e.with_message(err_msg)
} else {
e
}
})?;
}
// We then disable extension loading immediately afterward.
enable_load_extension(handle, false)
}
}