use std::fmt;
use std::ops::{Deref, DerefMut};
use std::thread;
use sqlx::Database;
use tokio::task::Id;
use super::book::{TxBook, TxSlot};
use super::writers::{self, Ran, Watch};
use crate::inbox::database::QueueDatabase;
use crate::inbox::form::advisory::{LockBook, LockSlot, Locked, database_of};
use crate::inbox::pools::ServicePool;
use crate::inbox::tx::PoolTx;
pub struct Tx<DB: QueueDatabase> {
lent: Option<Lent<DB>>,
first_statement: Option<Ran>,
}
impl<DB: QueueDatabase> Tx<DB> {
pub(crate) fn lent_by(lender: Lender<DB>, pool: &'static ServicePool<DB>) -> Option<Self> {
let (lent, ran) = lender.lend(writers::noted_task::<DB>())?;
let first_statement = ran.filter(|_| {
let watch = match lender {
Lender::Tx(book, _) => book.watch(|| {
writers::on_sqlite::<DB>(lent.conn()).then(|| Watch {
database: database_of(pool),
at_begin: writers::locks_at_begin(lent.opened_with()),
})
}),
Lender::Lock(book, _) => book.watch(|| writers::on_sqlite::<DB>(lent.conn())),
};
watch.is_some_and(|watch| !watch.at_begin)
});
Some(Self {
lent: Some(lent),
first_statement,
})
}
fn lent(&self) -> &Lent<DB> {
self.lent
.as_ref()
.expect("a `Tx` holds its transaction until it drops")
}
fn lent_mut(&mut self) -> &mut Lent<DB> {
self.lent
.as_mut()
.expect("a `Tx` holds its transaction until it drops")
}
}
impl<DB: QueueDatabase> Deref for Tx<DB> {
type Target = DB::Connection;
fn deref(&self) -> &DB::Connection {
self.lent().conn()
}
}
impl<DB: QueueDatabase> DerefMut for Tx<DB> {
fn deref_mut(&mut self) -> &mut DB::Connection {
if let Some(ran) = self.first_statement.take() {
ran.set();
}
self.lent_mut().conn_mut()
}
}
impl<DB: QueueDatabase> AsMut<DB::Connection> for Tx<DB> {
fn as_mut(&mut self) -> &mut DB::Connection {
self
}
}
impl<DB: QueueDatabase> Drop for Tx<DB> {
fn drop(&mut self) {
if let Some(lent) = self.lent.take() {
lent.give_back(thread::panicking());
}
}
}
impl<DB: QueueDatabase> fmt::Debug for Tx<DB> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Tx").finish_non_exhaustive()
}
}
pub(crate) enum Lent<DB: Database> {
Tx {
book: &'static TxBook<DB>,
slot: TxSlot,
tx: PoolTx<DB>,
},
Session {
book: &'static LockBook<DB>,
slot: LockSlot,
locked: Locked<'static, DB>,
},
}
impl<DB: Database> Lent<DB> {
fn opened_with(&self) -> Option<&'static str> {
match self {
Self::Tx { tx, .. } => tx.opened_with(),
Self::Session { .. } => None,
}
}
fn conn(&self) -> &DB::Connection {
match self {
Self::Tx { tx, .. } => tx,
Self::Session { locked, .. } => locked.conn_ref(),
}
}
fn conn_mut(&mut self) -> &mut DB::Connection {
match self {
Self::Tx { tx, .. } => tx,
Self::Session { locked, .. } => locked.conn(),
}
}
pub(super) fn give_back(self, panicked: bool) {
match self {
Self::Tx { book, slot, tx } => book.give_back(slot, tx, panicked),
Self::Session { book, slot, locked } => book.give_back(slot, locked, panicked),
}
}
}
pub(crate) enum Lender<DB: Database> {
Tx(&'static TxBook<DB>, TxSlot),
Lock(&'static LockBook<DB>, LockSlot),
}
impl<DB: Database> Clone for Lender<DB> {
fn clone(&self) -> Self {
*self
}
}
impl<DB: Database> Copy for Lender<DB> {}
impl<DB: Database> fmt::Debug for Lender<DB> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Tx(_, slot) => f.debug_tuple("Tx").field(slot).finish(),
Self::Lock(_, slot) => f.debug_tuple("Lock").field(slot).finish(),
}
}
}
impl<DB: Database> Lender<DB> {
pub(crate) fn lend(self, task: Option<Id>) -> Option<(Lent<DB>, Option<Ran>)> {
match self {
Self::Tx(book, slot) => book
.lend(slot, task)
.map(|(tx, ran)| (Lent::Tx { book, slot, tx }, ran)),
Self::Lock(book, slot) => book
.lend_to(slot, task)
.map(|(locked, ran)| (Lent::Session { book, slot, locked }, ran)),
}
}
}