use std::{
ffi::CString,
fmt::{self, Debug, Formatter, Write},
result::Result as StdResult,
sync::atomic::Ordering,
};
use either::Either;
use futures_core::{future::BoxFuture, stream::BoxStream};
use futures_util::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future};
pub use handle::ConnectionHandle;
use crate::{
QueryResult, Result, Row,
error::Error,
executor::Execute,
logger::LogSettings,
musq::{Musq, OptimizeOnClose},
query::{query_as, query_scalar},
sqlite::{
connection::{establish::EstablishParams, worker::ConnectionWorker},
ffi,
statement::{Prepared, Statement},
},
statement_cache::StatementCache,
transaction::Transaction,
};
pub use control::{DbStatus, DbStatusKind, SqliteRuntimeInfo, WalCheckpoint, WalCheckpointMode};
mod control;
pub mod establish;
pub mod execute;
mod handle;
mod worker;
pub struct Connection {
optimize_on_close: OptimizeOnClose,
pub(crate) worker: ConnectionWorker,
pub(crate) row_channel_size: usize,
}
unsafe impl Sync for Connection {}
pub struct ConnectionState {
pub(crate) handle: ConnectionHandle,
pub(crate) transaction_depth: usize,
pub(crate) statements: StatementCache,
log_settings: LogSettings,
}
impl Debug for Connection {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("SqliteConnection")
.field("row_channel_size", &self.row_channel_size)
.field("cached_statements_size", &self.cached_statements_size())
.finish()
}
}
impl Connection {
pub(crate) async fn establish(options: &Musq) -> Result<Self> {
let params = EstablishParams::from_options(options)?;
let worker = ConnectionWorker::establish(params).await?;
Ok(Self {
optimize_on_close: options.optimize_on_close.clone(),
worker,
row_channel_size: options.row_channel_size,
})
}
#[must_use = "futures returned by `Connection::close` must be awaited"]
pub async fn close(&self) -> Result<()> {
if let OptimizeOnClose::Enabled { analysis_limit } = self.optimize_on_close {
let mut pragma_string = String::new();
if let Some(limit) = analysis_limit {
write!(pragma_string, "PRAGMA analysis_limit = {limit}; ").ok();
}
pragma_string.push_str("PRAGMA optimize;");
self.execute(crate::query(&pragma_string)).await?;
}
self.worker.shutdown().await
}
pub fn begin(&mut self) -> BoxFuture<'_, Result<Transaction<&mut Self>>>
where
Self: Sized,
{
Transaction::begin(self)
}
pub(crate) fn cached_statements_size(&self) -> usize {
self.worker
.shared
.cached_statements_size
.load(Ordering::Acquire)
}
pub async fn runtime_info(&self) -> Result<SqliteRuntimeInfo> {
let version: String = query_scalar("SELECT sqlite_version()")
.fetch_one(self)
.await?;
let source_id: String = query_scalar("SELECT sqlite_source_id()")
.fetch_one(self)
.await?;
let compile_option_rows: Vec<(String,)> =
query_as("PRAGMA compile_options").fetch_all(self).await?;
let mut compile_options = compile_option_rows
.into_iter()
.map(|(option,)| option)
.collect::<Vec<_>>();
compile_options.sort();
Ok(SqliteRuntimeInfo {
version,
version_number: ffi::libversion_number(),
source_id,
compile_options,
})
}
pub async fn db_status(&self, kind: DbStatusKind, reset_highwater: bool) -> Result<DbStatus> {
self.worker.db_status(kind, reset_highwater).await
}
pub async fn wal_checkpoint(
&self,
schema: Option<&str>,
mode: WalCheckpointMode,
) -> Result<WalCheckpoint> {
let schema = schema
.map(CString::new)
.transpose()
.map_err(|_| Error::Protocol("WAL checkpoint schema contains nul bytes".into()))?;
self.worker.wal_checkpoint(schema, mode).await
}
pub async fn parser_depth_limit(&self) -> Result<u32> {
self.worker.parser_depth_limit().await
}
#[cfg(test)]
pub(crate) async fn clear_cached_statements(&self) -> Result<()> {
self.worker.clear_cache().await?;
Ok(())
}
pub async fn transaction<'a, F, R, E>(&'a mut self, callback: F) -> StdResult<R, E>
where
for<'c> F: FnOnce(&'c mut Transaction<&'a mut Self>) -> BoxFuture<'c, StdResult<R, E>>
+ Send
+ Sync,
Self: Sized,
R: Send,
E: From<Error> + Send,
{
let mut transaction = self.begin().await?;
let ret = {
let fut = callback(&mut transaction);
fut.await
};
match ret {
Ok(ret) => {
transaction.commit().await?;
Ok(ret)
}
Err(err) => {
transaction.rollback().await?;
Err(err)
}
}
}
pub async fn connect_with(options: &Musq) -> Result<Self>
where
Self: Sized,
{
options.connect().await
}
pub(crate) fn fetch_many<'c, 'q: 'c, E>(
&'c self,
query: E,
) -> BoxStream<'c, Result<Either<QueryResult, Row>>>
where
E: Execute + 'q,
{
let mut query = query;
let arguments = query.arguments();
let sql = query.sql().into();
drop(query);
Box::pin(
self.worker
.execute(sql, arguments, self.row_channel_size)
.map_ok(flume::Receiver::into_stream)
.try_flatten_stream(),
)
}
pub(crate) fn fetch_optional<'c, 'q: 'c, E>(
&'c self,
query: E,
) -> BoxFuture<'c, Result<Option<Row>>>
where
E: Execute + 'q,
{
let mut query = query;
let arguments = query.arguments();
let sql = query.sql().to_string();
drop(query);
Box::pin(async move {
let stream = self
.worker
.execute(sql, arguments, self.row_channel_size)
.map_ok(flume::Receiver::into_stream)
.try_flatten_stream();
futures_util::pin_mut!(stream);
while let Some(res) = stream.try_next().await? {
if let Either::Right(row) = res {
return Ok(Some(row));
}
}
Ok(None)
})
}
#[allow(dead_code)]
pub(crate) fn prepare_with<'c, 'q: 'c>(
&'c self,
sql: &'q str,
) -> BoxFuture<'c, Result<Prepared>> {
Box::pin(async move {
self.worker.prepare(sql).await?;
Ok(Prepared {
statement: Statement { sql: sql.into() },
})
})
}
pub fn prepare<'c, 'q: 'c>(&'c self, sql: &'q str) -> BoxFuture<'c, Result<Prepared>> {
self.prepare_with(sql)
}
pub(crate) fn fetch<'c, 'q: 'c, E>(&'c self, query: E) -> BoxStream<'c, Result<Row>>
where
E: Execute + 'q,
{
self.fetch_many(query)
.try_filter_map(|step| async move {
Ok(match step {
Either::Left(_) => None,
Either::Right(row) => Some(row),
})
})
.boxed()
}
pub(crate) fn execute_many<'c, 'q: 'c, E>(
&'c self,
query: E,
) -> BoxStream<'c, Result<QueryResult>>
where
E: Execute + 'q,
{
self.fetch_many(query)
.try_filter_map(|step| async move {
Ok(match step {
Either::Left(rows) => Some(rows),
Either::Right(_) => None,
})
})
.boxed()
}
pub(crate) fn execute<'c, 'q: 'c, E>(&'c self, query: E) -> BoxFuture<'c, Result<QueryResult>>
where
E: Execute + 'q,
{
self.execute_many(query)
.try_fold(QueryResult::default(), |mut acc, qr| async move {
acc.changes += qr.changes;
acc.last_insert_rowid = qr.last_insert_rowid;
Ok(acc)
})
.boxed()
}
pub(crate) fn fetch_all<'c, 'q: 'c, E>(&'c self, query: E) -> BoxFuture<'c, Result<Vec<Row>>>
where
E: Execute + 'q,
{
self.fetch(query).try_collect().boxed()
}
pub(crate) fn fetch_one<'c, 'q: 'c, E>(&'c self, query: E) -> BoxFuture<'c, Result<Row>>
where
E: Execute + 'q,
{
self.fetch_optional(query)
.and_then(|row| match row {
Some(row) => future::ok(row),
None => future::err(Error::RowNotFound),
})
.boxed()
}
}
impl Drop for Connection {
fn drop(&mut self) {
}
}
impl Drop for ConnectionState {
fn drop(&mut self) {
self.statements.clear();
}
}