Mattia Righetti c5ea6c4435
feat: sqlx sqlite expose de/serialize (#3745)
* feat: implement serialze no copy on lockedsqlitehandle

* feat: implement serialize on sqliteconnection

* feat: implement deserialize on sqliteconnection and add sqlitebuf wrapper type

* refactor: misc sqlite type and deserialize refactoring

* chore: misc clippy refactoring

* fix: misc refactoring and fixes

- pass non-owned byte slice to deserialize
- `SqliteBufError` and better error handling
- more impl for `SqliteOnwedBuf` so it can be used as a slice
- default serialize for `SqliteConnection`

* refactor: move serialize and deserialize on worker thread

This implements `Command::Serialize` and `Command::Deserialize` and moves the
serialize and deserialize logic to the worker thread.

`Serialize` will need some more iterations as it's not clear whether it would
need to wait for other write transactions before running.

* refactor: misc refactoring and changes

- Merged deserialize module with serialize module
- Moved `SqliteOwnedBuf` into serialize module
- Fixed rustdocs

* chore: API tweaks, better docs, tests

* fix: unused import

* fix: export `SqliteOwnedBuf`, docs and safety tweaks

---------

Co-authored-by: Austin Bonander <austin.bonander@gmail.com>
2025-03-02 14:29:29 -08:00

204 lines
6.4 KiB
Rust

#![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)
}