use crate::database::DatabaseInner;
use crate::error::{Error, Result};
use crate::row::FromRow;
use rusqlite::types::FromSql;
use rusqlite::{Connection, Params};
use std::sync::Arc;
mod on_conn {
use super::*;
pub(super) fn execute<P: Params>(conn: &Connection, sql: &str, params: P) -> Result<u64> {
let n = conn.execute(sql, params)?;
Ok(n as u64)
}
pub(super) fn insert<P: Params>(conn: &Connection, sql: &str, params: P) -> Result<i64> {
if conn.execute(sql, params)? == 0 {
return Err(Error::NotFound);
}
Ok(conn.last_insert_rowid())
}
pub(super) fn query_all<T: FromRow, P: Params>(
conn: &Connection,
sql: &str,
params: P,
) -> Result<Vec<T>> {
let mut stmt = conn.prepare(sql)?;
let rows = stmt.query_map(params, |r| T::from_row(r))?;
let mut out = Vec::new();
for row in rows {
out.push(row?);
}
Ok(out)
}
pub(super) fn query_optional<T: FromRow, P: Params>(
conn: &Connection,
sql: &str,
params: P,
) -> Result<Option<T>> {
let mut stmt = conn.prepare(sql)?;
let mut rows = stmt.query(params)?;
match rows.next()? {
Some(row) => Ok(Some(T::from_row(row)?)),
None => Ok(None),
}
}
pub(super) fn query_scalar<T: FromSql, P: Params>(
conn: &Connection,
sql: &str,
params: P,
) -> Result<T> {
let mut stmt = conn.prepare(sql)?;
let mut rows = stmt.query(params)?;
match rows.next()? {
Some(row) => Ok(row.get(0)?),
None => Err(Error::NotFound),
}
}
}
pub trait SqlContext {
fn ctx_execute<P: Params>(&self, sql: &str, params: P) -> Result<u64>;
fn ctx_insert<P: Params>(&self, sql: &str, params: P) -> Result<i64>;
fn ctx_query_all<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Vec<T>>;
fn ctx_query_optional<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Option<T>>;
fn ctx_query_one<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<T> {
self.ctx_query_optional(sql, params)?.ok_or(Error::NotFound)
}
fn ctx_query_scalar<T: FromSql, P: Params>(&self, sql: &str, params: P) -> Result<T>;
fn ctx_transaction<R>(&self, f: impl FnOnce(&Tx<'_>) -> Result<R>) -> Result<R>;
}
impl<C: SqlContext> SqlContext for &C {
fn ctx_execute<P: Params>(&self, sql: &str, params: P) -> Result<u64> {
(**self).ctx_execute(sql, params)
}
fn ctx_insert<P: Params>(&self, sql: &str, params: P) -> Result<i64> {
(**self).ctx_insert(sql, params)
}
fn ctx_query_all<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Vec<T>> {
(**self).ctx_query_all(sql, params)
}
fn ctx_query_optional<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Option<T>> {
(**self).ctx_query_optional(sql, params)
}
fn ctx_query_scalar<T: FromSql, P: Params>(&self, sql: &str, params: P) -> Result<T> {
(**self).ctx_query_scalar(sql, params)
}
fn ctx_transaction<R>(&self, f: impl FnOnce(&Tx<'_>) -> Result<R>) -> Result<R> {
(**self).ctx_transaction(f)
}
}
#[derive(Clone, Copy)]
pub struct SyncHandle<'db> {
pub(crate) inner: &'db Arc<DatabaseInner>,
}
impl<'db> SyncHandle<'db> {
pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<u64> {
self.ctx_execute(sql, params)
}
pub fn query_one<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<T> {
self.ctx_query_one(sql, params)
}
pub fn query_optional<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Option<T>> {
self.ctx_query_optional(sql, params)
}
pub fn query_scalar<T: FromSql, P: Params>(&self, sql: &str, params: P) -> Result<T> {
self.ctx_query_scalar(sql, params)
}
pub fn query_all<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Vec<T>> {
self.ctx_query_all(sql, params)
}
pub fn transaction<R>(&self, f: impl FnOnce(&mut Tx<'_>) -> Result<R>) -> Result<R> {
let mut tx = self.begin()?;
match f(&mut tx) {
Ok(v) => {
tx.commit()?;
Ok(v)
}
Err(e) => {
let _ = tx.rollback();
Err(e)
}
}
}
pub fn begin(&self) -> Result<Tx<'db>> {
let guard = self.inner.pool.connections.acquire()?;
guard.conn().execute_batch("BEGIN IMMEDIATE")?;
log::debug!("transaction begin");
Ok(Tx {
inner: self.inner,
guard,
open: true,
sp_depth: std::cell::Cell::new(0),
#[cfg(feature = "live")]
pending: std::sync::Mutex::new(vec![TxPending::default()]),
})
}
pub fn with_connection<R>(&self, f: impl FnOnce(&Connection) -> Result<R>) -> Result<R> {
let guard = self.inner.pool.connections.acquire()?;
#[cfg(feature = "live")]
let _hook_capture = self.inner.begin_hook_capture();
let out = f(guard.conn());
#[cfg(feature = "live")]
{
let hooks = self.inner.take_hook_tables();
if !hooks.is_empty() {
self.inner.tracker.invalidate(Some(hooks));
}
}
out
}
#[cfg(feature = "live")]
pub fn rearm_hooks(&self) -> Result<()> {
self.inner.pool.connections.for_each_connection(|conn| {
let _previous =
conn.update_hook(Some(move |_action, _db: &str, table: &str, _rowid: i64| {
crate::database::record_hook_table(table);
}));
Ok(())
})
}
}
#[cfg(feature = "live")]
pub(crate) mod watch_impl {
use super::*;
use crate::live::{LiveQuery, OwnedParams, extract_tables};
use rusqlite::ToSql;
use std::collections::HashSet;
fn make<T: Clone + Send + 'static>(
inner: &Arc<DatabaseInner>,
sql: String,
params: Result<OwnedParams>,
tables: Option<HashSet<String>>,
run: impl Fn(&Connection, &str, &OwnedParams) -> Result<T> + Send + Sync + 'static,
) -> LiveQuery<T> {
#[cfg(feature = "multi-instance")]
if let (Some(mi), Some(ts)) = (inner.mi.get(), &tables) {
for t in ts {
if !mi.tables.contains(t) {
log::warn!(
"table \"{t}\" is not multi_instance opt-in — \
cross-process writes will not be observed (spec §9.5)"
);
inner.log_warn(&format!(
"[roomrs 경고] 테이블 \"{t}\"은 multi_instance 옵트인이 아닙니다 — \
교차 프로세스 write 알림을 받지 못합니다 (명세 §9.5)"
));
}
}
}
let tracker = Arc::clone(&inner.tracker);
match params {
Ok(p) => LiveQuery::new(tracker, sql, p, tables, run),
Err(e) => {
let msg = e.to_string();
LiveQuery::new(
tracker,
sql,
OwnedParams::None,
Some(HashSet::new()),
move |_, _, _| Err(Error::Config(msg.clone())),
)
}
}
}
pub(crate) fn watch_all<T: FromRow + Clone + Send + 'static>(
inner: &Arc<DatabaseInner>,
sql: &str,
params: Result<OwnedParams>,
tables: Option<HashSet<String>>,
) -> LiveQuery<Vec<T>> {
let tables = tables.or_else(|| extract_tables(sql));
make(
inner,
sql.to_string(),
params,
tables,
crate::live::query_all_owned::<T>,
)
}
pub(crate) fn watch_optional<T: FromRow + Clone + Send + 'static>(
inner: &Arc<DatabaseInner>,
sql: &str,
params: Result<OwnedParams>,
tables: Option<HashSet<String>>,
) -> LiveQuery<Option<T>> {
let tables = tables.or_else(|| extract_tables(sql));
make(
inner,
sql.to_string(),
params,
tables,
crate::live::query_optional_owned::<T>,
)
}
pub(crate) fn watch_scalar<T: FromSql + Clone + Send + 'static>(
inner: &Arc<DatabaseInner>,
sql: &str,
params: Result<OwnedParams>,
tables: Option<HashSet<String>>,
) -> LiveQuery<T> {
let tables = tables.or_else(|| extract_tables(sql));
make(
inner,
sql.to_string(),
params,
tables,
crate::live::query_scalar_owned::<T>,
)
}
pub(crate) fn tables_from_hint(hint: &[&str]) -> Option<HashSet<String>> {
if hint.is_empty() {
None
} else {
Some(hint.iter().map(|s| s.to_string()).collect())
}
}
pub(crate) fn params_from_dyn(params: &[&dyn ToSql]) -> Result<OwnedParams> {
OwnedParams::from_dyn(params)
}
pub(crate) fn params_named(
pairs: Result<Vec<(String, rusqlite::types::Value)>>,
) -> Result<OwnedParams> {
pairs.map(OwnedParams::Named)
}
}
#[cfg(feature = "live")]
impl SyncHandle<'_> {
pub fn watch_all<T: FromRow + Clone + Send + 'static>(
&self,
sql: &str,
params: &[&dyn rusqlite::ToSql],
) -> crate::live::LiveQuery<Vec<T>> {
watch_impl::watch_all(self.inner, sql, watch_impl::params_from_dyn(params), None)
}
pub fn watch_optional<T: FromRow + Clone + Send + 'static>(
&self,
sql: &str,
params: &[&dyn rusqlite::ToSql],
) -> crate::live::LiveQuery<Option<T>> {
watch_impl::watch_optional(self.inner, sql, watch_impl::params_from_dyn(params), None)
}
pub fn watch_scalar<T: FromSql + Clone + Send + 'static>(
&self,
sql: &str,
params: &[&dyn rusqlite::ToSql],
) -> crate::live::LiveQuery<T> {
watch_impl::watch_scalar(self.inner, sql, watch_impl::params_from_dyn(params), None)
}
}
#[cfg(feature = "live")]
#[doc(hidden)]
impl DatabaseInner {
pub fn __watch_all_dyn<T: FromRow + Clone + Send + 'static>(
self: &Arc<Self>,
sql: &str,
params: &[&dyn rusqlite::ToSql],
) -> crate::live::LiveQuery<Vec<T>> {
watch_impl::watch_all(self, sql, watch_impl::params_from_dyn(params), None)
}
pub fn __watch_optional_dyn<T: FromRow + Clone + Send + 'static>(
self: &Arc<Self>,
sql: &str,
params: &[&dyn rusqlite::ToSql],
) -> crate::live::LiveQuery<Option<T>> {
watch_impl::watch_optional(self, sql, watch_impl::params_from_dyn(params), None)
}
pub fn __watch_scalar_dyn<T: FromSql + Clone + Send + 'static>(
self: &Arc<Self>,
sql: &str,
params: &[&dyn rusqlite::ToSql],
) -> crate::live::LiveQuery<T> {
watch_impl::watch_scalar(self, sql, watch_impl::params_from_dyn(params), None)
}
pub fn __watch_all_named<T: FromRow + Clone + Send + 'static>(
self: &Arc<Self>,
sql: &'static str,
params: Result<Vec<(String, rusqlite::types::Value)>>,
tables: &[&str],
) -> crate::live::LiveQuery<Vec<T>> {
watch_impl::watch_all(
self,
sql,
watch_impl::params_named(params),
watch_impl::tables_from_hint(tables),
)
}
pub fn __watch_optional_named<T: FromRow + Clone + Send + 'static>(
self: &Arc<Self>,
sql: &'static str,
params: Result<Vec<(String, rusqlite::types::Value)>>,
tables: &[&str],
) -> crate::live::LiveQuery<Option<T>> {
watch_impl::watch_optional(
self,
sql,
watch_impl::params_named(params),
watch_impl::tables_from_hint(tables),
)
}
pub fn __watch_scalar_named<T: FromSql + Clone + Send + 'static>(
self: &Arc<Self>,
sql: &'static str,
params: Result<Vec<(String, rusqlite::types::Value)>>,
tables: &[&str],
) -> crate::live::LiveQuery<T> {
watch_impl::watch_scalar(
self,
sql,
watch_impl::params_named(params),
watch_impl::tables_from_hint(tables),
)
}
}
#[cfg(feature = "live")]
pub trait WatchContext {
#[doc(hidden)]
fn ctx_watch_all_named<T: FromRow + Clone + Send + 'static>(
&self,
sql: &'static str,
params: Result<Vec<(String, rusqlite::types::Value)>>,
tables: &[&str],
) -> crate::live::LiveQuery<Vec<T>>;
#[doc(hidden)]
fn ctx_watch_optional_named<T: FromRow + Clone + Send + 'static>(
&self,
sql: &'static str,
params: Result<Vec<(String, rusqlite::types::Value)>>,
tables: &[&str],
) -> crate::live::LiveQuery<Option<T>>;
#[doc(hidden)]
fn ctx_watch_scalar_named<T: FromSql + Clone + Send + 'static>(
&self,
sql: &'static str,
params: Result<Vec<(String, rusqlite::types::Value)>>,
tables: &[&str],
) -> crate::live::LiveQuery<T>;
}
#[cfg(feature = "live")]
impl<C: WatchContext> WatchContext for &C {
fn ctx_watch_all_named<T: FromRow + Clone + Send + 'static>(
&self,
sql: &'static str,
params: Result<Vec<(String, rusqlite::types::Value)>>,
tables: &[&str],
) -> crate::live::LiveQuery<Vec<T>> {
(**self).ctx_watch_all_named(sql, params, tables)
}
fn ctx_watch_optional_named<T: FromRow + Clone + Send + 'static>(
&self,
sql: &'static str,
params: Result<Vec<(String, rusqlite::types::Value)>>,
tables: &[&str],
) -> crate::live::LiveQuery<Option<T>> {
(**self).ctx_watch_optional_named(sql, params, tables)
}
fn ctx_watch_scalar_named<T: FromSql + Clone + Send + 'static>(
&self,
sql: &'static str,
params: Result<Vec<(String, rusqlite::types::Value)>>,
tables: &[&str],
) -> crate::live::LiveQuery<T> {
(**self).ctx_watch_scalar_named(sql, params, tables)
}
}
#[cfg(feature = "live")]
impl WatchContext for SyncHandle<'_> {
fn ctx_watch_all_named<T: FromRow + Clone + Send + 'static>(
&self,
sql: &'static str,
params: Result<Vec<(String, rusqlite::types::Value)>>,
tables: &[&str],
) -> crate::live::LiveQuery<Vec<T>> {
watch_impl::watch_all(
self.inner,
sql,
watch_impl::params_named(params),
watch_impl::tables_from_hint(tables),
)
}
fn ctx_watch_optional_named<T: FromRow + Clone + Send + 'static>(
&self,
sql: &'static str,
params: Result<Vec<(String, rusqlite::types::Value)>>,
tables: &[&str],
) -> crate::live::LiveQuery<Option<T>> {
watch_impl::watch_optional(
self.inner,
sql,
watch_impl::params_named(params),
watch_impl::tables_from_hint(tables),
)
}
fn ctx_watch_scalar_named<T: FromSql + Clone + Send + 'static>(
&self,
sql: &'static str,
params: Result<Vec<(String, rusqlite::types::Value)>>,
tables: &[&str],
) -> crate::live::LiveQuery<T> {
watch_impl::watch_scalar(
self.inner,
sql,
watch_impl::params_named(params),
watch_impl::tables_from_hint(tables),
)
}
}
#[cfg(feature = "live")]
impl WatchContext for Tx<'_> {
fn ctx_watch_all_named<T: FromRow + Clone + Send + 'static>(
&self,
sql: &'static str,
_params: Result<Vec<(String, rusqlite::types::Value)>>,
_tables: &[&str],
) -> crate::live::LiveQuery<Vec<T>> {
watch_impl::watch_all(
self.inner,
sql,
Err(Error::Config(
"트랜잭션 컨텍스트에서는 라이브 쿼리를 만들 수 없습니다".into(),
)),
Some(std::collections::HashSet::new()),
)
}
fn ctx_watch_optional_named<T: FromRow + Clone + Send + 'static>(
&self,
sql: &'static str,
_params: Result<Vec<(String, rusqlite::types::Value)>>,
_tables: &[&str],
) -> crate::live::LiveQuery<Option<T>> {
watch_impl::watch_optional(
self.inner,
sql,
Err(Error::Config(
"트랜잭션 컨텍스트에서는 라이브 쿼리를 만들 수 없습니다".into(),
)),
Some(std::collections::HashSet::new()),
)
}
fn ctx_watch_scalar_named<T: FromSql + Clone + Send + 'static>(
&self,
sql: &'static str,
_params: Result<Vec<(String, rusqlite::types::Value)>>,
_tables: &[&str],
) -> crate::live::LiveQuery<T> {
watch_impl::watch_scalar(
self.inner,
sql,
Err(Error::Config(
"트랜잭션 컨텍스트에서는 라이브 쿼리를 만들 수 없습니다".into(),
)),
Some(std::collections::HashSet::new()),
)
}
}
impl SqlContext for SyncHandle<'_> {
fn ctx_execute<P: Params>(&self, sql: &str, params: P) -> Result<u64> {
let guard = self.inner.pool.connections.acquire()?;
#[cfg(feature = "live")]
let _hook_capture = self.inner.begin_hook_capture();
let out = self
.inner
.log_query(sql, || on_conn::execute(guard.conn(), sql, params));
#[cfg(feature = "live")]
self.inner.emit_after_write(sql);
out
}
fn ctx_insert<P: Params>(&self, sql: &str, params: P) -> Result<i64> {
let guard = self.inner.pool.connections.acquire()?;
#[cfg(feature = "live")]
let _hook_capture = self.inner.begin_hook_capture();
let out = self
.inner
.log_query(sql, || on_conn::insert(guard.conn(), sql, params));
#[cfg(feature = "live")]
self.inner.emit_after_write(sql);
out
}
fn ctx_query_all<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Vec<T>> {
let guard = self.inner.pool.connections.acquire()?;
#[cfg(feature = "live")]
let _hook_capture = self.inner.begin_hook_capture();
let out = self
.inner
.log_query(sql, || on_conn::query_all(guard.conn(), sql, params));
#[cfg(feature = "live")]
self.inner.emit_after_write(sql);
out
}
fn ctx_query_optional<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Option<T>> {
let guard = self.inner.pool.connections.acquire()?;
#[cfg(feature = "live")]
let _hook_capture = self.inner.begin_hook_capture();
let out = self
.inner
.log_query(sql, || on_conn::query_optional(guard.conn(), sql, params));
#[cfg(feature = "live")]
self.inner.emit_after_write(sql);
out
}
fn ctx_query_scalar<T: FromSql, P: Params>(&self, sql: &str, params: P) -> Result<T> {
let guard = self.inner.pool.connections.acquire()?;
#[cfg(feature = "live")]
let _hook_capture = self.inner.begin_hook_capture();
let out = self
.inner
.log_query(sql, || on_conn::query_scalar(guard.conn(), sql, params));
#[cfg(feature = "live")]
self.inner.emit_after_write(sql);
out
}
fn ctx_transaction<R>(&self, f: impl FnOnce(&Tx<'_>) -> Result<R>) -> Result<R> {
let tx = self.begin()?;
match f(&tx) {
Ok(v) => {
tx.commit()?;
Ok(v)
}
Err(e) => {
let _ = tx.rollback();
Err(e)
}
}
}
}
pub struct Tx<'db> {
inner: &'db Arc<DatabaseInner>,
guard: crate::pool::ConnectionGuard<'db>,
open: bool,
sp_depth: std::cell::Cell<u32>,
#[cfg(feature = "live")]
pending: std::sync::Mutex<Vec<TxPending>>,
}
#[cfg(feature = "live")]
#[derive(Default)]
struct TxPending {
all: bool,
tables: std::collections::HashSet<String>,
}
#[cfg(feature = "live")]
impl Tx<'_> {
fn collect_write(&self, sql: &str) {
let hooks = self.inner.take_hook_tables();
let mut levels = self
.pending
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let Some(p) = levels.last_mut() else { return };
p.tables.extend(hooks);
match crate::live::extract_write_tables(sql) {
crate::live::WriteTables::ReadOnly => {}
crate::live::WriteTables::Tables(t) => p.tables.extend(t),
crate::live::WriteTables::Unknown => p.all = true,
}
}
fn collect_write_all(&self) {
let hooks = self.inner.take_hook_tables();
let mut levels = self
.pending
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let Some(p) = levels.last_mut() else { return };
p.tables.extend(hooks);
p.all = true;
}
fn emit_pending(&self) {
let levels = std::mem::take(
&mut *self
.pending
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
let mut all = false;
let mut tables = std::collections::HashSet::new();
for l in levels {
all |= l.all;
tables.extend(l.tables);
}
if all {
self.inner.tracker.invalidate(None);
} else if !tables.is_empty() {
self.inner.tracker.invalidate(Some(tables));
}
}
}
impl Tx<'_> {
pub(crate) fn raw_conn(&self) -> &Connection {
self.guard.conn()
}
pub fn commit(mut self) -> Result<()> {
self.guard.conn().execute_batch("COMMIT")?;
log::debug!("transaction commit");
self.open = false;
#[cfg(feature = "live")]
self.emit_pending();
Ok(())
}
pub fn rollback(mut self) -> Result<()> {
self.guard.conn().execute_batch("ROLLBACK")?;
log::debug!("transaction rollback");
self.open = false;
Ok(())
}
pub fn execute<P: Params>(&self, sql: &str, params: P) -> Result<u64> {
self.ctx_execute(sql, params)
}
pub fn query_one<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<T> {
self.ctx_query_one(sql, params)
}
pub fn query_optional<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Option<T>> {
self.ctx_query_optional(sql, params)
}
pub fn query_scalar<T: FromSql, P: Params>(&self, sql: &str, params: P) -> Result<T> {
self.ctx_query_scalar(sql, params)
}
pub fn query_all<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Vec<T>> {
self.ctx_query_all(sql, params)
}
pub fn execute_batch(&self, sql: &str) -> Result<()> {
#[cfg(feature = "live")]
let _hook_capture = self.inner.begin_hook_capture();
let r = self.raw_conn().execute_batch(sql);
#[cfg(feature = "live")]
match &r {
Ok(()) => self.collect_write(sql),
Err(_) => self.collect_write_all(),
}
r?;
Ok(())
}
pub fn savepoint<R>(&self, f: impl FnOnce(&Tx<'_>) -> Result<R>) -> Result<R> {
let depth = self.sp_depth.get();
let name = format!("roomrs_sp_{depth}");
self.guard
.conn()
.execute_batch(&format!("SAVEPOINT {name}"))?;
self.sp_depth.set(depth + 1);
#[cfg(feature = "live")]
self.pending
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(TxPending::default());
let out = f(self);
self.sp_depth.set(depth);
#[cfg(feature = "live")]
let level = self
.pending
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.pop()
.unwrap_or_default();
match out {
Ok(v) => {
#[cfg(feature = "live")]
{
let mut levels = self
.pending
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(parent) = levels.last_mut() {
parent.all |= level.all;
parent.tables.extend(level.tables);
}
}
self.guard
.conn()
.execute_batch(&format!("RELEASE {name}"))?;
Ok(v)
}
Err(e) => {
if let Err(re) = self
.guard
.conn()
.execute_batch(&format!("ROLLBACK TO {name}; RELEASE {name}"))
{
log::error!("savepoint rollback failed: {re}");
}
Err(e)
}
}
}
}
impl SqlContext for Tx<'_> {
fn ctx_execute<P: Params>(&self, sql: &str, params: P) -> Result<u64> {
#[cfg(feature = "live")]
let _hook_capture = self.inner.begin_hook_capture();
let out = self
.inner
.log_query(sql, || on_conn::execute(self.guard.conn(), sql, params));
#[cfg(feature = "live")]
self.collect_write(sql);
out
}
fn ctx_insert<P: Params>(&self, sql: &str, params: P) -> Result<i64> {
#[cfg(feature = "live")]
let _hook_capture = self.inner.begin_hook_capture();
let out = self
.inner
.log_query(sql, || on_conn::insert(self.guard.conn(), sql, params));
#[cfg(feature = "live")]
self.collect_write(sql);
out
}
fn ctx_query_all<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Vec<T>> {
#[cfg(feature = "live")]
let _hook_capture = self.inner.begin_hook_capture();
let out = self
.inner
.log_query(sql, || on_conn::query_all(self.guard.conn(), sql, params));
#[cfg(feature = "live")]
self.collect_write(sql);
out
}
fn ctx_query_optional<T: FromRow, P: Params>(&self, sql: &str, params: P) -> Result<Option<T>> {
#[cfg(feature = "live")]
let _hook_capture = self.inner.begin_hook_capture();
let out = self.inner.log_query(sql, || {
on_conn::query_optional(self.guard.conn(), sql, params)
});
#[cfg(feature = "live")]
self.collect_write(sql);
out
}
fn ctx_query_scalar<T: FromSql, P: Params>(&self, sql: &str, params: P) -> Result<T> {
#[cfg(feature = "live")]
let _hook_capture = self.inner.begin_hook_capture();
let out = self.inner.log_query(sql, || {
on_conn::query_scalar(self.guard.conn(), sql, params)
});
#[cfg(feature = "live")]
self.collect_write(sql);
out
}
fn ctx_transaction<R>(&self, f: impl FnOnce(&Tx<'_>) -> Result<R>) -> Result<R> {
self.savepoint(f)
}
}
impl Drop for Tx<'_> {
fn drop(&mut self) {
if self.open {
log::debug!("transaction rollback (uncommitted drop)");
let _ = self.guard.conn().execute_batch("ROLLBACK");
}
}
}