/
germanubis
/
sqlx
Обзор
Документация
Войти
/
germanubis
/
sqlx
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
sqlx-sqlite/src/connection/establish.rs
266 строк
10 KB
Austin Bonander
breaking(sqlite): `libsqlite3-sys` versioning, feature flags, safety changes (#3928)
17 июл 2025, 11:13
Не верифицирован
17 июл 2025, 11:13
21598cf
Код
Авторство
О чём код?
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), serde_urlencoded::to_string(&query_params).unwrap() ); } 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) } }