use std::fmt::Debug;
use ruststream::{AckError, IncomingMessage};
use sqlx_core::transaction::TransactionManager;
use super::{Lender, Returned, Transactional};
use crate::inbox::database::QueueDatabase;
use crate::inbox::delivery::settle::{Step, run_step};
use crate::inbox::delivery::{Hold, InboxDelivery};
use crate::inbox::engine::{Events, Settled, Settling};
use crate::inbox::error::SqlxBrokerError;
use crate::inbox::form::advisory::session::Session;
use crate::inbox::form::advisory::settle::run_advised;
use crate::inbox::form::lease::settle::{run_leased, settle_leased};
use crate::inbox::form::lease::{LeaseBook, Slot};
use crate::inbox::keys::{TransactionalDelivery, TxContext};
use crate::inbox::queue::Queue;
use crate::inbox::tx::PoolTx;
impl<DB, Row> TransactionalDelivery<DB> for InboxDelivery<DB, Row, Transactional>
where
DB: QueueDatabase,
Row: Events<DB>,
{
fn tx_context(&self) -> TxContext<DB> {
let lender = match &self.hold {
Some(Hold::Lent(hold) | Hold::Lease { tx: Some(hold), .. }) => hold.lender(),
Some(Hold::Advisory(hold)) => Lender::Lock(hold.book(), hold.slot()),
Some(Hold::Own(_) | Hold::Batch(_) | Hold::Lease { tx: None, .. }) | None => {
unreachable!(
"a transactional delivery holds a transaction it lends until it settles"
)
}
};
TxContext::new(lender, self.pool, self.redelivery_count())
}
}
pub(crate) async fn settle_lent<DB, Row>(
returned: Returned<DB>,
cx: &Settling,
id: &Row::Id,
step: Step,
) -> Result<Settled, sqlx::Error>
where
DB: QueueDatabase,
Row: Events<DB>,
{
let Returned { mut tx, panicked } = returned;
let discard = step != Step::Ack || panicked;
let settled = match run_lent::<DB, Row>(&mut tx, cx, id, step, discard).await {
Ok(settled) => settled,
Err(error) => {
let _ = tx.rollback().await;
return Err(error);
}
};
match settled {
Settled::Written => tx.commit().await?,
Settled::Untouched if !discard => tx.commit().await?,
Settled::Untouched | Settled::Lost => tx.rollback().await?,
}
Ok(settled)
}
async fn run_lent<DB, Row>(
tx: &mut PoolTx<DB>,
cx: &Settling,
id: &Row::Id,
step: Step,
discard: bool,
) -> Result<Settled, sqlx::Error>
where
DB: QueueDatabase,
Row: Events<DB>,
{
if discard && let Some(savepoint) = cx.queue.prepared.savepoint {
DB::execute_text(tx, savepoint.rollback_to).await?;
}
run_step::<DB, Row>(tx, cx, id, None, step).await
}
pub(crate) async fn settle_leased_lent<DB, Row>(
book: &'static LeaseBook<DB, Row>,
slot: Slot,
returned: Returned<DB>,
cx: &Settling,
id: &Row::Id,
step: Step,
) -> Result<Settled, sqlx::Error>
where
DB: QueueDatabase,
Row: Events<DB>,
{
let Returned { mut tx, panicked } = returned;
if step != Step::Ack || panicked {
let _ = tx.rollback().await;
return settle_leased::<DB, Row>(book, slot, cx, id, step).await;
}
let Ok(held) = book.take_ahead(slot) else {
tx.rollback().await?;
return Ok(Settled::Lost);
};
let acknowledged = match ack_by_lease::<DB, Row>(&mut tx, cx, id, &held).await {
Ok(Settled::Lost) => return tx.rollback().await.map(|()| Settled::Lost),
Ok(settled) => tx.commit().await.map(|()| settled),
Err(error) => {
let _ = tx.rollback().await;
Err(error)
}
};
if acknowledged.is_err() {
let _ =
run_leased::<DB, Row>(book.pool(), book.runtime(), cx, id, &held, Step::Retry).await;
}
acknowledged
}
async fn ack_by_lease<DB, Row>(
tx: &mut PoolTx<DB>,
cx: &Settling,
id: &Row::Id,
held: &Row::Token,
) -> Result<Settled, sqlx::Error>
where
DB: QueueDatabase,
Row: Events<DB>,
{
if Step::Ack.overridden(Row::SHAPE)
&& Row::extend(tx, cx, id, held, held).await? == Settled::Lost
{
return Ok(Settled::Lost);
}
run_step::<DB, Row>(tx, cx, id, Some(held), Step::Ack).await
}
pub(crate) async fn settle_in_session<DB, Row>(
session: &mut Session<DB>,
cx: &Settling,
id: &Row::Id,
step: Step,
panicked: bool,
) -> Result<Settled, sqlx::Error>
where
DB: QueueDatabase,
Row: Events<DB>,
{
if step != Step::Ack || panicked {
DB::TransactionManager::rollback(session.conn()).await?;
session.set_open(false);
return run_advised::<DB, Row>(session, cx, id, step).await;
}
match run_step::<DB, Row>(session.conn(), cx, id, None, step).await {
Ok(settled) => {
DB::TransactionManager::commit(session.conn()).await?;
session.set_open(false);
Ok(settled)
}
Err(error) => {
if DB::TransactionManager::rollback(session.conn())
.await
.is_ok()
{
session.set_open(false);
}
Err(error)
}
}
}
pub(crate) fn transaction_held<Id: Debug>(queue: &Queue, id: &Id) -> AckError {
AckError::Broker(Box::new(SqlxBrokerError::TransactionHeld {
subscription: queue.name.to_owned(),
table: queue.table.to_owned(),
row: queue.row,
id: format!("{id:?}"),
}))
}