chore: sync current workspace changes
Desktop CI Build / Build macOS ARM64 DMG (push) Has been cancelled
Desktop CI Build / Build Windows x64 (push) Has been cancelled

This commit is contained in:
boos
2026-07-14 03:21:10 +08:00
parent 1512fa23ce
commit 557914fea8
745 changed files with 124917 additions and 11222 deletions
+371
View File
@@ -0,0 +1,371 @@
use std::ffi::c_void;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::ptr;
use std::ptr::NonNull;
use std::slice::from_raw_parts;
use std::str::{from_utf8, from_utf8_unchecked};
use libsqlite3_sys::{
sqlite3, sqlite3_bind_blob64, sqlite3_bind_double, sqlite3_bind_int, sqlite3_bind_int64,
sqlite3_bind_null, sqlite3_bind_parameter_count, sqlite3_bind_parameter_name,
sqlite3_bind_text64, sqlite3_changes, sqlite3_clear_bindings, sqlite3_column_blob,
sqlite3_column_bytes, sqlite3_column_count, sqlite3_column_database_name,
sqlite3_column_decltype, sqlite3_column_double, sqlite3_column_int, sqlite3_column_int64,
sqlite3_column_name, sqlite3_column_origin_name, sqlite3_column_table_name,
sqlite3_column_type, sqlite3_column_value, sqlite3_db_handle, sqlite3_finalize, sqlite3_reset,
sqlite3_sql, sqlite3_step, sqlite3_stmt, sqlite3_stmt_readonly, sqlite3_table_column_metadata,
sqlite3_value, SQLITE_DONE, SQLITE_LOCKED_SHAREDCACHE, SQLITE_MISUSE, SQLITE_OK, SQLITE_ROW,
SQLITE_TRANSIENT, SQLITE_UTF8,
};
use crate::error::{BoxDynError, Error};
use crate::type_info::DataType;
use crate::{SqliteError, SqliteTypeInfo};
use super::unlock_notify;
#[derive(Debug)]
pub(crate) struct StatementHandle(NonNull<sqlite3_stmt>);
// access to SQLite3 statement handles are safe to send and share between threads
// as long as the `sqlite3_step` call is serialized.
unsafe impl Send for StatementHandle {}
macro_rules! expect_ret_valid {
($fn_name:ident($($args:tt)*)) => {{
let val = $fn_name($($args)*);
TryFrom::try_from(val)
// This likely means UB in SQLite itself or our usage of it;
// signed integer overflow is UB in the C standard.
.unwrap_or_else(|_| panic!("{}() returned invalid value: {val:?}", stringify!($fn_name)))
}}
}
macro_rules! check_col_idx {
($idx:ident) => {
c_int::try_from($idx).unwrap_or_else(|_| panic!("invalid column index: {}", $idx))
};
}
// might use some of this later
#[allow(dead_code)]
impl StatementHandle {
pub(super) fn new(ptr: NonNull<sqlite3_stmt>) -> Self {
Self(ptr)
}
#[inline]
pub(super) unsafe fn db_handle(&self) -> *mut sqlite3 {
// O(c) access to the connection handle for this statement handle
// https://sqlite.org/c3ref/db_handle.html
sqlite3_db_handle(self.0.as_ptr())
}
pub(crate) fn read_only(&self) -> bool {
// https://sqlite.org/c3ref/stmt_readonly.html
unsafe { sqlite3_stmt_readonly(self.0.as_ptr()) != 0 }
}
pub(crate) fn sql(&self) -> &str {
// https://sqlite.org/c3ref/expanded_sql.html
unsafe {
let raw = sqlite3_sql(self.0.as_ptr());
debug_assert!(!raw.is_null());
from_utf8_unchecked(CStr::from_ptr(raw).to_bytes())
}
}
#[inline]
pub(crate) fn last_error(&mut self) -> SqliteError {
unsafe { SqliteError::new(self.db_handle()) }
}
#[inline]
pub(crate) fn column_count(&self) -> usize {
// https://sqlite.org/c3ref/column_count.html
unsafe { expect_ret_valid!(sqlite3_column_count(self.0.as_ptr())) }
}
#[inline]
pub(crate) fn changes(&self) -> u64 {
// returns the number of changes of the *last* statement; not
// necessarily this statement.
// https://sqlite.org/c3ref/changes.html
unsafe { expect_ret_valid!(sqlite3_changes(self.db_handle())) }
}
#[inline]
pub(crate) fn column_name(&self, index: usize) -> &str {
// https://sqlite.org/c3ref/column_name.html
unsafe {
let name = sqlite3_column_name(self.0.as_ptr(), check_col_idx!(index));
debug_assert!(!name.is_null());
from_utf8_unchecked(CStr::from_ptr(name).to_bytes())
}
}
pub(crate) fn column_type_info(&self, index: usize) -> SqliteTypeInfo {
SqliteTypeInfo(DataType::from_code(self.column_type(index)))
}
pub(crate) fn column_type_info_opt(&self, index: usize) -> Option<SqliteTypeInfo> {
match DataType::from_code(self.column_type(index)) {
DataType::Null => None,
dt => Some(SqliteTypeInfo(dt)),
}
}
#[inline]
pub(crate) fn column_decltype(&self, index: usize) -> Option<SqliteTypeInfo> {
unsafe {
let decl = sqlite3_column_decltype(self.0.as_ptr(), check_col_idx!(index));
if decl.is_null() {
// If the Nth column of the result set is an expression or subquery,
// then a NULL pointer is returned.
return None;
}
let decl = from_utf8_unchecked(CStr::from_ptr(decl).to_bytes());
let ty: DataType = decl.parse().ok()?;
Some(SqliteTypeInfo(ty))
}
}
pub(crate) fn column_nullable(&self, index: usize) -> Result<Option<bool>, Error> {
unsafe {
let index = check_col_idx!(index);
// https://sqlite.org/c3ref/column_database_name.html
//
// ### Note
// The returned string is valid until the prepared statement is destroyed using
// sqlite3_finalize() or until the statement is automatically reprepared by the
// first call to sqlite3_step() for a particular run or until the same information
// is requested again in a different encoding.
let db_name = sqlite3_column_database_name(self.0.as_ptr(), index);
let table_name = sqlite3_column_table_name(self.0.as_ptr(), index);
let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), index);
if db_name.is_null() || table_name.is_null() || origin_name.is_null() {
return Ok(None);
}
let mut not_null: c_int = 0;
// https://sqlite.org/c3ref/table_column_metadata.html
let status = sqlite3_table_column_metadata(
self.db_handle(),
db_name,
table_name,
origin_name,
// function docs state to provide NULL for return values you don't care about
ptr::null_mut(),
ptr::null_mut(),
&mut not_null,
ptr::null_mut(),
ptr::null_mut(),
);
if status != SQLITE_OK {
// implementation note: the docs for sqlite3_table_column_metadata() specify
// that an error can be returned if the column came from a view; however,
// experimentally we found that the above functions give us the true origin
// for columns in views that came from real tables and so we should never hit this
// error; for view columns that are expressions we are given NULL for their origins
// so we don't need special handling for that case either.
//
// this is confirmed in the `tests/sqlite-macros.rs` integration test
return Err(SqliteError::new(self.db_handle()).into());
}
Ok(Some(not_null == 0))
}
}
// Number Of SQL Parameters
#[inline]
pub(crate) fn bind_parameter_count(&self) -> usize {
// https://www.sqlite.org/c3ref/bind_parameter_count.html
unsafe { expect_ret_valid!(sqlite3_bind_parameter_count(self.0.as_ptr())) }
}
// Name Of A Host Parameter
// NOTE: The first host parameter has an index of 1, not 0.
#[inline]
pub(crate) fn bind_parameter_name(&self, index: usize) -> Option<&str> {
unsafe {
// https://www.sqlite.org/c3ref/bind_parameter_name.html
let name = sqlite3_bind_parameter_name(self.0.as_ptr(), check_col_idx!(index));
if name.is_null() {
return None;
}
Some(from_utf8_unchecked(CStr::from_ptr(name).to_bytes()))
}
}
// Binding Values To Prepared Statements
// https://www.sqlite.org/c3ref/bind_blob.html
#[inline]
pub(crate) fn bind_blob(&self, index: usize, v: &[u8]) -> c_int {
unsafe {
sqlite3_bind_blob64(
self.0.as_ptr(),
check_col_idx!(index),
v.as_ptr() as *const c_void,
v.len() as u64,
SQLITE_TRANSIENT(),
)
}
}
#[inline]
pub(crate) fn bind_text(&self, index: usize, v: &str) -> c_int {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let encoding = SQLITE_UTF8 as u8;
unsafe {
sqlite3_bind_text64(
self.0.as_ptr(),
check_col_idx!(index),
v.as_ptr() as *const c_char,
v.len() as u64,
SQLITE_TRANSIENT(),
encoding,
)
}
}
#[inline]
pub(crate) fn bind_int(&self, index: usize, v: i32) -> c_int {
unsafe { sqlite3_bind_int(self.0.as_ptr(), check_col_idx!(index), v as c_int) }
}
#[inline]
pub(crate) fn bind_int64(&self, index: usize, v: i64) -> c_int {
unsafe { sqlite3_bind_int64(self.0.as_ptr(), check_col_idx!(index), v) }
}
#[inline]
pub(crate) fn bind_double(&self, index: usize, v: f64) -> c_int {
unsafe { sqlite3_bind_double(self.0.as_ptr(), check_col_idx!(index), v) }
}
#[inline]
pub(crate) fn bind_null(&self, index: usize) -> c_int {
unsafe { sqlite3_bind_null(self.0.as_ptr(), check_col_idx!(index)) }
}
// result values from the query
// https://www.sqlite.org/c3ref/column_blob.html
#[inline]
pub(crate) fn column_type(&self, index: usize) -> c_int {
unsafe { sqlite3_column_type(self.0.as_ptr(), check_col_idx!(index)) }
}
#[inline]
pub(crate) fn column_int(&self, index: usize) -> i32 {
unsafe { sqlite3_column_int(self.0.as_ptr(), check_col_idx!(index)) as i32 }
}
#[inline]
pub(crate) fn column_int64(&self, index: usize) -> i64 {
unsafe { sqlite3_column_int64(self.0.as_ptr(), check_col_idx!(index)) as i64 }
}
#[inline]
pub(crate) fn column_double(&self, index: usize) -> f64 {
unsafe { sqlite3_column_double(self.0.as_ptr(), check_col_idx!(index)) }
}
#[inline]
pub(crate) fn column_value(&self, index: usize) -> *mut sqlite3_value {
unsafe { sqlite3_column_value(self.0.as_ptr(), check_col_idx!(index)) }
}
pub(crate) fn column_blob(&self, index: usize) -> &[u8] {
let len = unsafe {
expect_ret_valid!(sqlite3_column_bytes(self.0.as_ptr(), check_col_idx!(index)))
};
if len == 0 {
// empty blobs are NULL so just return an empty slice
return &[];
}
let ptr =
unsafe { sqlite3_column_blob(self.0.as_ptr(), check_col_idx!(index)) } as *const u8;
debug_assert!(!ptr.is_null());
unsafe { from_raw_parts(ptr, len) }
}
pub(crate) fn column_text(&self, index: usize) -> Result<&str, BoxDynError> {
Ok(from_utf8(self.column_blob(index))?)
}
pub(crate) fn clear_bindings(&self) {
unsafe { sqlite3_clear_bindings(self.0.as_ptr()) };
}
pub(crate) fn reset(&mut self) -> Result<(), SqliteError> {
// SAFETY: we have exclusive access to the handle
unsafe {
if sqlite3_reset(self.0.as_ptr()) != SQLITE_OK {
return Err(SqliteError::new(self.db_handle()));
}
}
Ok(())
}
pub(crate) fn step(&mut self) -> Result<bool, SqliteError> {
// SAFETY: we have exclusive access to the handle
unsafe {
loop {
match sqlite3_step(self.0.as_ptr()) {
SQLITE_ROW => return Ok(true),
SQLITE_DONE => return Ok(false),
SQLITE_MISUSE => panic!("misuse!"),
SQLITE_LOCKED_SHAREDCACHE => {
// The shared cache is locked by another connection. Wait for unlock
// notification and try again.
unlock_notify::wait(self.db_handle())?;
// Need to reset the handle after the unlock
// (https://www.sqlite.org/unlock_notify.html)
sqlite3_reset(self.0.as_ptr());
}
_ => return Err(SqliteError::new(self.db_handle())),
}
}
}
}
}
impl Drop for StatementHandle {
fn drop(&mut self) {
// SAFETY: we have exclusive access to the `StatementHandle` here
unsafe {
// https://sqlite.org/c3ref/finalize.html
let status = sqlite3_finalize(self.0.as_ptr());
if status == SQLITE_MISUSE {
// Panic in case of detected misuse of SQLite API.
//
// sqlite3_finalize returns it at least in the
// case of detected double free, i.e. calling
// sqlite3_finalize on already finalized
// statement.
panic!("Detected sqlite3_finalize misuse.");
}
}
}
}
+79
View File
@@ -0,0 +1,79 @@
use crate::column::ColumnIndex;
use crate::error::Error;
use crate::ext::ustr::UStr;
use crate::{Sqlite, SqliteArguments, SqliteColumn, SqliteTypeInfo};
use sqlx_core::{Either, HashMap};
use std::borrow::Cow;
use std::sync::Arc;
pub(crate) use sqlx_core::statement::*;
mod handle;
pub(super) mod unlock_notify;
mod r#virtual;
pub(crate) use handle::StatementHandle;
pub(crate) use r#virtual::VirtualStatement;
#[derive(Debug, Clone)]
#[allow(clippy::rc_buffer)]
pub struct SqliteStatement<'q> {
pub(crate) sql: Cow<'q, str>,
pub(crate) parameters: usize,
pub(crate) columns: Arc<Vec<SqliteColumn>>,
pub(crate) column_names: Arc<HashMap<UStr, usize>>,
}
impl<'q> Statement<'q> for SqliteStatement<'q> {
type Database = Sqlite;
fn to_owned(&self) -> SqliteStatement<'static> {
SqliteStatement::<'static> {
sql: Cow::Owned(self.sql.clone().into_owned()),
parameters: self.parameters,
columns: Arc::clone(&self.columns),
column_names: Arc::clone(&self.column_names),
}
}
fn sql(&self) -> &str {
&self.sql
}
fn parameters(&self) -> Option<Either<&[SqliteTypeInfo], usize>> {
Some(Either::Right(self.parameters))
}
fn columns(&self) -> &[SqliteColumn] {
&self.columns
}
impl_statement_query!(SqliteArguments<'_>);
}
impl ColumnIndex<SqliteStatement<'_>> for &'_ str {
fn index(&self, statement: &SqliteStatement<'_>) -> Result<usize, Error> {
statement
.column_names
.get(*self)
.ok_or_else(|| Error::ColumnNotFound((*self).into()))
.copied()
}
}
// #[cfg(feature = "any")]
// impl<'q> From<SqliteStatement<'q>> for crate::any::AnyStatement<'q> {
// #[inline]
// fn from(statement: SqliteStatement<'q>) -> Self {
// crate::any::AnyStatement::<'q> {
// columns: statement
// .columns
// .iter()
// .map(|col| col.clone().into())
// .collect(),
// column_names: statement.column_names,
// parameters: Some(Either::Right(statement.parameters)),
// sql: statement.sql,
// }
// }
// }
+65
View File
@@ -0,0 +1,65 @@
use std::ffi::c_void;
use std::os::raw::c_int;
use std::slice;
use std::sync::{Condvar, Mutex};
use libsqlite3_sys::{sqlite3, sqlite3_unlock_notify, SQLITE_OK};
use crate::SqliteError;
// Wait for unlock notification (https://www.sqlite.org/unlock_notify.html)
pub unsafe fn wait(conn: *mut sqlite3) -> Result<(), SqliteError> {
let notify = Notify::new();
if sqlite3_unlock_notify(
conn,
Some(unlock_notify_cb),
&notify as *const Notify as *mut Notify as *mut _,
) != SQLITE_OK
{
return Err(SqliteError::new(conn));
}
notify.wait();
Ok(())
}
unsafe extern "C" fn unlock_notify_cb(ptr: *mut *mut c_void, len: c_int) {
let ptr = ptr as *mut &Notify;
// We don't have a choice; we can't panic and unwind into FFI here.
let slice = slice::from_raw_parts(ptr, usize::try_from(len).unwrap_or(0));
for notify in slice {
notify.fire();
}
}
struct Notify {
mutex: Mutex<bool>,
condvar: Condvar,
}
impl Notify {
fn new() -> Self {
Self {
mutex: Mutex::new(false),
condvar: Condvar::new(),
}
}
fn wait(&self) {
// We only want to wait until the lock is available again.
#[allow(let_underscore_lock)]
let _ = self
.condvar
.wait_while(self.mutex.lock().unwrap(), |fired| !*fired)
.unwrap();
}
fn fire(&self) {
let mut lock = self.mutex.lock().unwrap();
*lock = true;
self.condvar.notify_one();
}
}
+203
View File
@@ -0,0 +1,203 @@
#![allow(clippy::rc_buffer)]
use std::cmp;
use std::os::raw::c_char;
use std::ptr::{null, null_mut, NonNull};
use std::sync::Arc;
use libsqlite3_sys::{
sqlite3, sqlite3_prepare_v3, sqlite3_stmt, SQLITE_OK, SQLITE_PREPARE_PERSISTENT,
};
use sqlx_core::bytes::{Buf, Bytes};
use sqlx_core::error::Error;
use sqlx_core::ext::ustr::UStr;
use sqlx_core::{HashMap, SmallVec};
use crate::connection::ConnectionHandle;
use crate::statement::StatementHandle;
use crate::{SqliteColumn, SqliteError};
// A virtual statement consists of *zero* or more raw SQLite3 statements. We chop up a SQL statement
// on `;` to support multiple statements in one query.
#[derive(Debug)]
pub struct VirtualStatement {
persistent: bool,
/// the current index of the actual statement that is executing
/// if `None`, no statement is executing and `prepare()` must be called;
/// if `Some(self.handles.len())` and `self.tail.is_empty()`,
/// there are no more statements to execute and `reset()` must be called
index: Option<usize>,
/// tail of the most recently prepared SQL statement within this container
tail: Bytes,
/// underlying sqlite handles for each inner statement
/// a SQL query string in SQLite is broken up into N statements
/// we use a [`SmallVec`] to optimize for the most likely case of a single statement
pub(crate) handles: SmallVec<[StatementHandle; 1]>,
// each set of columns
pub(crate) columns: SmallVec<[Arc<Vec<SqliteColumn>>; 1]>,
// each set of column names
pub(crate) column_names: SmallVec<[Arc<HashMap<UStr, usize>>; 1]>,
}
pub struct PreparedStatement<'a> {
pub(crate) handle: &'a mut StatementHandle,
pub(crate) columns: &'a Arc<Vec<SqliteColumn>>,
pub(crate) column_names: &'a Arc<HashMap<UStr, usize>>,
}
impl VirtualStatement {
pub(crate) fn new(mut query: &str, persistent: bool) -> Result<Self, Error> {
query = query.trim();
if query.len() > i32::MAX as usize {
return Err(err_protocol!(
"query string must be smaller than {} bytes",
i32::MAX
));
}
Ok(Self {
persistent,
tail: Bytes::from(String::from(query)),
handles: SmallVec::with_capacity(1),
index: None,
columns: SmallVec::with_capacity(1),
column_names: SmallVec::with_capacity(1),
})
}
pub(crate) fn prepare_next(
&mut self,
conn: &mut ConnectionHandle,
) -> Result<Option<PreparedStatement<'_>>, Error> {
// increment `self.index` up to `self.handles.len()`
self.index = self
.index
.map(|idx| cmp::min(idx + 1, self.handles.len()))
.or(Some(0));
while self.handles.len() <= self.index.unwrap_or(0) {
if self.tail.is_empty() {
return Ok(None);
}
if let Some(statement) = prepare(conn.as_ptr(), &mut self.tail, self.persistent)? {
let num = statement.column_count();
let mut columns = Vec::with_capacity(num);
let mut column_names = HashMap::with_capacity(num);
for i in 0..num {
let name: UStr = statement.column_name(i).to_owned().into();
let type_info = statement
.column_decltype(i)
.unwrap_or_else(|| statement.column_type_info(i));
columns.push(SqliteColumn {
ordinal: i,
name: name.clone(),
type_info,
});
column_names.insert(name, i);
}
self.handles.push(statement);
self.columns.push(Arc::new(columns));
self.column_names.push(Arc::new(column_names));
}
}
Ok(self.current())
}
pub fn current(&mut self) -> Option<PreparedStatement<'_>> {
self.index
.filter(|&idx| idx < self.handles.len())
.map(move |idx| PreparedStatement {
handle: &mut self.handles[idx],
columns: &self.columns[idx],
column_names: &self.column_names[idx],
})
}
pub fn reset(&mut self) -> Result<(), Error> {
self.index = None;
for handle in self.handles.iter_mut() {
handle.reset()?;
handle.clear_bindings();
}
Ok(())
}
}
fn prepare(
conn: *mut sqlite3,
query: &mut Bytes,
persistent: bool,
) -> Result<Option<StatementHandle>, Error> {
let mut flags = 0;
// For some reason, when building with the `sqlcipher` feature enabled
// `SQLITE_PREPARE_PERSISTENT` ends up being `i32` instead of `u32`. Crazy, right?
#[allow(trivial_casts, clippy::unnecessary_cast)]
if persistent {
// SQLITE_PREPARE_PERSISTENT
// The SQLITE_PREPARE_PERSISTENT flag is a hint to the query
// planner that the prepared statement will be retained for a long time
// and probably reused many times.
flags |= SQLITE_PREPARE_PERSISTENT as u32;
}
while !query.is_empty() {
let mut statement_handle: *mut sqlite3_stmt = null_mut();
let mut tail: *const c_char = null();
let query_ptr = query.as_ptr() as *const c_char;
let query_len = i32::try_from(query.len()).map_err(|_| {
err_protocol!(
"query string too large for SQLite3 API ({} bytes); \
try breaking it into smaller chunks (< 2 GiB), executed separately",
query.len()
)
})?;
// <https://www.sqlite.org/c3ref/prepare.html>
let status = unsafe {
sqlite3_prepare_v3(
conn,
query_ptr,
query_len,
flags,
&mut statement_handle,
&mut tail,
)
};
if status != SQLITE_OK {
return Err(unsafe { SqliteError::new(conn).into() });
}
// tail should point to the first byte past the end of the first SQL
// statement in zSql. these routines only compile the first statement,
// so tail is left pointing to what remains un-compiled.
let n = (tail as usize) - (query_ptr as usize);
query.advance(n);
if let Some(handle) = NonNull::new(statement_handle) {
return Ok(Some(StatementHandle::new(handle)));
}
}
Ok(None)
}