pub(crate) use crate::sys::Step;
use crate::{
api::{self, stmt},
catalogs, config, sys,
};
use api::{exec, work};
pub(crate) use controls::Controls;
use guard::open;
pub(crate) use progress::Progress;
use std::{marker, mem, ptr, result};
use stmt::access::{Access, Authority};
use work::guard;
pub(crate) mod access;
mod binder;
mod bound;
mod control;
mod controls;
mod progress;
mod storage;
pub(crate) use binder::Binder;
pub(crate) use bound::Bound;
pub(crate) use control::Control;
pub(crate) use storage::Storage;
pub(crate) mod target;
pub(crate) enum Inventory {
Empty,
Populated,
}
pub(crate) struct Engine<'connection, I: catalogs::Catalog> {
slots: &'connection mut [Slot<'connection>],
connection: &'connection crate::sys::Connection,
marker: marker::PhantomData<I>,
}
impl<'connection, I: catalogs::Catalog> Engine<'connection, I> {
pub(crate) fn transaction(&self) -> sys::Tx {
self.connection.transaction()
}
pub(crate) fn controls(&mut self) -> Controls<'_, 'connection> {
Controls::new(self)
}
pub(crate) fn retire(mut self) -> Retirement<'connection> {
Retirement {
slots: mem::take(&mut self.slots),
}
}
fn lease<Q: catalogs::TypedQuery>(
&mut self,
key: catalogs::Statement<I, Q>,
) -> &mut Prepared<'connection>
where
I: catalogs::HasQuery<Q>,
{
let slot = key.slot();
let index = config::CONTROL_STATEMENT_ITEMS + slot;
unsafe {
self.slots
.get_unchecked_mut(index)
.as_mut_slice()
.get_unchecked_mut(0)
}
}
}
pub(crate) struct Prepare<'connection, I: catalogs::Catalog> {
slots: &'connection mut [Slot<'connection>],
live: usize,
connection: &'connection crate::sys::Connection,
capacity: config::Capacity,
used: usize,
marker: marker::PhantomData<I>,
}
impl<'connection, I: catalogs::Catalog> Prepare<'connection, I> {
pub(crate) unsafe fn new(
db: &'connection crate::sys::Connection,
storage: &'connection mut Storage<I>,
bytes: config::Capacity,
) -> Self {
Self {
slots: storage.slots(),
live: 0,
connection: db,
capacity: bytes,
used: 0,
marker: marker::PhantomData,
}
}
pub(crate) fn advance(
mut self,
guard: &guard::Guard<'connection, open::Open>,
) -> result::Result<Progress<Self, Engine<'connection, I>>, (Self, crate::Error)> {
match self.insert(guard) {
Ok(true) => {
let slots = mem::take(&mut self.slots);
self.live = 0;
Ok(Progress::Ready(Engine {
slots,
connection: self.connection,
marker: marker::PhantomData,
}))
}
Ok(false) => Ok(Progress::Pending(self)),
Err(error) => Err((self, error)),
}
}
fn insert(
&mut self,
guard: &guard::Guard<'connection, open::Open>,
) -> result::Result<bool, crate::Error> {
let index = self.live;
let call = guard.call(self.connection);
let prepare = |sql, params, columns| {
call.check(Prepared::new(
call.inner(),
guard.work(),
sql,
params,
columns,
))
};
let stmt = if let Some(control) = Control::ALL.get(index) {
guard
.work()
.control()
.run(|| prepare(control.sql(), 0, 0))?
} else {
let meta = unsafe { I::STATEMENTS.get_unchecked(index - Control::ALL.len()) };
prepare(meta.sql(), meta.parameters(), meta.columns())?
};
let bytes = stmt.bytes();
let required = self.used + bytes;
let limit = self.capacity.get();
if required > limit {
return Err(crate::Error::StatementBytes {
required,
available: limit,
});
}
let slot = unsafe { self.slots.get_unchecked_mut(index) };
*slot = Some(stmt);
self.used = required;
self.live = index + 1;
Ok(self.live == self.slots.len())
}
pub(crate) fn retire_one(&mut self) -> Inventory {
let live = unsafe { self.slots.get_unchecked_mut(..self.live) };
let Some((slot, rest)) = live.split_last_mut() else {
return Inventory::Empty;
};
self.live = rest.len();
*slot = None;
match self.live {
0 => Inventory::Empty,
_ => Inventory::Populated,
}
}
}
pub(crate) struct Retirement<'connection> {
slots: &'connection mut [Slot<'connection>],
}
pub(super) type Slot<'connection> = Option<Prepared<'connection>>;
impl<I: catalogs::Catalog> Drop for Engine<'_, I> {
fn drop(&mut self) {
Retirement {
slots: mem::take(&mut self.slots),
}
.retire_all();
}
}
impl<I: catalogs::Catalog> Drop for Prepare<'_, I> {
fn drop(&mut self) {
let slots = mem::take(&mut self.slots);
let slots = unsafe { slots.get_unchecked_mut(..self.live) };
Retirement { slots }.retire_all();
}
}
impl Retirement<'_> {
pub(crate) fn retire_one(&mut self) -> Inventory {
if let Some((slot, rest)) = mem::take(&mut self.slots).split_last_mut() {
self.slots = rest;
*slot = None;
}
self.inventory()
}
pub(crate) fn retire_all(&mut self) {
for slot in mem::take(&mut self.slots).iter_mut().rev() {
*slot = None;
}
}
pub(crate) const fn inventory(&self) -> Inventory {
if self.slots.is_empty() {
Inventory::Empty
} else {
Inventory::Populated
}
}
}
impl Drop for Retirement<'_> {
fn drop(&mut self) {
self.retire_all();
}
}
pub(crate) fn bytes(items: usize) -> Option<usize> {
let bytes = items.checked_mul(mem::size_of::<Slot<'_>>())?;
let rounded = bytes.checked_add(storage::ALIGNMENT - 1)?;
Some((rounded / storage::ALIGNMENT) * storage::ALIGNMENT)
}
const _: () = {
let word = mem::size_of::<usize>();
assert!(mem::size_of::<Engine<'static, catalogs::Empty>>() == 3 * word);
assert!(mem::size_of::<Prepare<'static, catalogs::Empty>>() == 6 * word);
assert!(mem::size_of::<Retirement<'static>>() == 2 * word);
assert!(mem::size_of::<Slot<'static>>() == 3 * word);
assert!(mem::size_of::<Prepared<'static>>() == 3 * word);
};
pub(crate) struct Prepared<'connection> {
statement: ptr::NonNull<libsqlite3_sys::sqlite3_stmt>,
work: &'connection work::Work,
parameters: u32,
columns: u32,
connection: marker::PhantomData<&'connection crate::sys::Connection>,
}
impl<'connection> Prepared<'connection> {
pub(crate) const fn as_non_null(&self) -> ptr::NonNull<libsqlite3_sys::sqlite3_stmt> {
self.statement
}
pub(in crate::engine::api) fn transaction(&self) -> sys::Tx {
unsafe { sys::statement_tx(self.statement) }
}
pub(in crate::engine::api) const fn work(&self) -> &'connection work::Work {
self.work
}
pub(crate) fn new(
db: &'connection crate::sys::Connection,
work: &'connection work::Work,
sql: &str,
params: usize,
columns: usize,
) -> result::Result<Self, crate::Error> {
let sql = crate::sys::Sql::new(sql)?;
sql.prepare(
db,
libsqlite3_sys::SQLITE_PREPARE_PERSISTENT,
Err,
|stmt, tail| {
let mut prepared = Self {
statement: stmt,
work,
parameters: 0,
columns: 0,
connection: marker::PhantomData,
};
tail.check()?;
prepared.parameters = unsafe { sys::statement_parameters(stmt, params) }?;
prepared.columns = unsafe { sys::statement_columns(stmt) };
if columns != usize::MAX && prepared.columns as usize != columns {
return Err(crate::Error::ColumnCount {
expected: columns,
actual: prepared.columns as usize,
});
}
Ok(prepared)
},
)
}
fn bytes(&self) -> usize {
let bytes = unsafe {
libsqlite3_sys::sqlite3_stmt_status(
self.statement.as_ptr(),
libsqlite3_sys::SQLITE_STMTSTATUS_MEMUSED,
0,
)
};
bytes as usize
}
fn params(&self) -> usize {
self.parameters as usize
}
fn columns<const CURRENT: bool>(&self) -> usize {
if !CURRENT {
return self.columns as usize;
}
unsafe { sys::statement_columns(self.statement) as usize }
}
pub(crate) fn step<A: Access>(
&mut self,
access: &A,
txn: exec::Tx<'_>,
) -> result::Result<Step, crate::Error> {
let code = unsafe { libsqlite3_sys::sqlite3_step(self.statement.as_ptr()) };
match code {
libsqlite3_sys::SQLITE_ROW => Ok(Step::Row),
libsqlite3_sys::SQLITE_DONE => Ok(Step::Done),
code => {
unsafe { libsqlite3_sys::sqlite3_reset(self.statement.as_ptr()) };
Err(txn.error(self, access, sys::Action::Step, code))
}
}
}
fn reset<A: Access>(
&mut self,
access: &A,
txn: exec::Tx<'_>,
) -> result::Result<(), crate::Error> {
let code = unsafe { libsqlite3_sys::sqlite3_reset(self.statement.as_ptr()) };
if matches!(A::AUTHORIZATION, Authority::Plain) {
self.clear();
}
if code == libsqlite3_sys::SQLITE_OK {
Ok(())
} else {
Err(txn.error(self, access, sys::Action::Reset, code))
}
}
fn clear(&mut self) {
unsafe { libsqlite3_sys::sqlite3_clear_bindings(self.statement.as_ptr()) };
}
}
impl Drop for Prepared<'_> {
fn drop(&mut self) {
if let Some(fault) = sys::finalize(self.statement) {
self.work.finalized(fault);
}
}
}