use std::fmt::{self, Debug, Formatter};
use std::marker::PhantomData;
use ruststream_sqlx_dialect::{Column, Form, KeyPart, Opening, Role, TableSpec};
use super::{
Advisory, Attempt, AttemptFrom, Clock, Declaration, Fifo, HeaderFields, Headers, Key, Lease,
OpeningLevel, Opens, OwnEvent, Payload, ProcessedAt, Push, RetryAfter, Valid,
};
use crate::TimeSource;
pub trait InboxTable: Sized + Send + Sync + 'static {
type Id: Clone + Debug + Send + Sync + 'static;
type Table: Valid;
const TABLE: Self::Table;
fn id(&self) -> &Self::Id;
}
pub struct InboxSpec<Settings = ()> {
spec: TableSpec<'static>,
settings: PhantomData<fn() -> Settings>,
}
impl<Settings> Debug for InboxSpec<Settings> {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("InboxSpec")
.field("spec", &self.spec)
.finish_non_exhaustive()
}
}
impl<Settings> Clone for InboxSpec<Settings> {
fn clone(&self) -> Self {
*self
}
}
impl<Settings> Copy for InboxSpec<Settings> {}
impl<Settings> PartialEq for InboxSpec<Settings> {
fn eq(&self, other: &Self) -> bool {
self.spec == other.spec
}
}
impl<Settings> Eq for InboxSpec<Settings> {}
impl InboxSpec {
#[must_use]
pub const fn new(table: &'static str, id: Column<'static>) -> Self {
Self {
spec: TableSpec::new(table, id, Form::RowLock),
settings: PhantomData,
}
}
}
impl<Settings> InboxSpec<Settings> {
#[must_use]
pub const fn spec(&self) -> TableSpec<'static> {
self.spec
}
#[must_use]
pub const fn within(self, schema: &'static str) -> Self {
Self {
spec: self.spec.within(schema),
..self
}
}
#[must_use]
pub const fn group(self, column: Column<'static>) -> Self {
Self {
spec: self.spec.group(column),
..self
}
}
#[must_use]
pub const fn priority(self, column: Column<'static>) -> Self {
Self {
spec: self.spec.priority(column),
..self
}
}
#[must_use]
pub const fn data(self, columns: &'static [Column<'static>]) -> Self {
Self {
spec: self.spec.data(columns),
..self
}
}
#[must_use]
pub const fn fetching(self, columns: &'static [Column<'static>]) -> Self {
Self {
spec: self.spec.fetching(columns),
..self
}
}
#[must_use]
pub const fn selecting_all(self) -> Self {
Self {
spec: self.spec.selecting_all(),
..self
}
}
#[must_use]
pub const fn lease<Time>(
self,
column: Column<'static>,
) -> InboxSpec<<Settings as Push<Lease<Time>>>::Out>
where
Settings: Push<Lease<Time>>,
<Settings as Push<Lease<Time>>>::Out: Declaration,
{
Self::with(&self.reformed(Form::Lease(column)))
}
#[must_use]
pub const fn advisory(
self,
key: &'static [KeyPart<'static>],
) -> InboxSpec<<Settings as Push<Advisory>>::Out>
where
Settings: Push<Advisory>,
<Settings as Push<Advisory>>::Out: Declaration,
{
Self::with(&self.reformed(Form::Advisory(key)))
}
#[must_use]
pub const fn payload(
self,
column: Column<'static>,
) -> InboxSpec<<Settings as Push<Payload>>::Out>
where
Settings: Push<Payload>,
<Settings as Push<Payload>>::Out: Declaration,
{
Self::with(&self.spec.payload(column))
}
#[must_use]
pub const fn partition_key(
self,
column: Column<'static>,
) -> InboxSpec<<Settings as Push<Key>>::Out>
where
Settings: Push<Key>,
<Settings as Push<Key>>::Out: Declaration,
{
Self::with(&self.spec.partition_key(column))
}
#[must_use]
pub const fn attempt(
self,
column: Column<'static>,
) -> InboxSpec<<Settings as Push<Attempt>>::Out>
where
Settings: Push<Attempt>,
<Settings as Push<Attempt>>::Out: Declaration,
{
Self::with(&self.spec.attempt(column))
}
#[doc(hidden)]
#[must_use]
pub const fn attempt_from<Decoded>(
self,
column: Column<'static>,
) -> InboxSpec<<Settings as Push<AttemptFrom<Decoded>>>::Out>
where
Settings: Push<AttemptFrom<Decoded>>,
<Settings as Push<AttemptFrom<Decoded>>>::Out: Declaration,
{
Self::with(&self.spec.attempt(column))
}
#[must_use]
pub const fn headers(
self,
column: Column<'static>,
) -> InboxSpec<<Settings as Push<Headers>>::Out>
where
Settings: Push<Headers>,
<Settings as Push<Headers>>::Out: Declaration,
{
Self::with(&self.spec.headers(column))
}
#[must_use]
pub const fn header_fields(self) -> InboxSpec<<Settings as Push<HeaderFields>>::Out>
where
Settings: Push<HeaderFields>,
<Settings as Push<HeaderFields>>::Out: Declaration,
{
Self::with(&self.spec)
}
#[must_use]
pub const fn retry_after<Time>(
self,
column: Column<'static>,
) -> InboxSpec<<Settings as Push<RetryAfter<Time>>>::Out>
where
Settings: Push<RetryAfter<Time>>,
<Settings as Push<RetryAfter<Time>>>::Out: Declaration,
{
Self::with(&self.spec.retry_after(column))
}
#[must_use]
pub const fn processed_at<Time>(
self,
column: Column<'static>,
) -> InboxSpec<<Settings as Push<ProcessedAt<Time>>>::Out>
where
Settings: Push<ProcessedAt<Time>>,
<Settings as Push<ProcessedAt<Time>>>::Out: Declaration,
{
Self::with(&self.spec.processed_at(column))
}
#[must_use]
pub const fn fifo_group(
self,
column: Column<'static>,
) -> InboxSpec<<Settings as Push<Fifo>>::Out>
where
Settings: Push<Fifo>,
<Settings as Push<Fifo>>::Out: Declaration,
{
Self::with(&self.spec.fifo_group(column))
}
#[must_use]
pub const fn clock<Source: TimeSource>(
self,
) -> InboxSpec<<Settings as Push<Clock<Source>>>::Out>
where
Settings: Push<Clock<Source>>,
<Settings as Push<Clock<Source>>>::Out: Declaration,
{
if Source::DATABASE {
Self::with(&self.spec.database_clock())
} else {
Self::with(&self.spec)
}
}
#[must_use]
pub const fn opens<Level: OpeningLevel>(
self,
) -> InboxSpec<<Settings as Push<Opens<Level>>>::Out>
where
Settings: Push<Opens<Level>>,
<Settings as Push<Opens<Level>>>::Out: Declaration,
{
Self::with(&opened(&self.spec, Level::OPENING))
}
#[must_use]
pub const fn own<Event: OwnEvent>(self) -> InboxSpec<<Settings as Push<Event>>::Out>
where
Settings: Push<Event>,
<Settings as Push<Event>>::Out: Declaration,
{
Self::with(&self.spec)
}
const fn with<Next>(spec: &TableSpec<'static>) -> InboxSpec<Next> {
InboxSpec {
spec: *spec,
settings: PhantomData,
}
}
const fn reformed(self, form: Form<'static>) -> TableSpec<'static> {
let old = self.spec;
let mut spec = TableSpec::new(old.table(), old.id(), form);
if let Some(schema) = old.schema() {
spec = spec.within(schema);
}
if let Some(column) = old.column(Role::Group) {
spec = if old.is_fifo() {
spec.fifo_group(column)
} else {
spec.group(column)
};
}
if let Some(column) = old.column(Role::PartitionKey) {
spec = spec.partition_key(column);
}
if let Some(column) = old.column(Role::Priority) {
spec = spec.priority(column);
}
if let Some(column) = old.column(Role::RetryAfter) {
spec = spec.retry_after(column);
}
if let Some(column) = old.column(Role::Attempt) {
spec = spec.attempt(column);
}
if let Some(column) = old.column(Role::ProcessedAt) {
spec = spec.processed_at(column);
}
if let Some(column) = old.column(Role::Headers) {
spec = spec.headers(column);
}
if let Some(column) = old.column(Role::Payload) {
spec = spec.payload(column);
}
if !old.data_columns().is_empty() {
spec = spec.data(old.data_columns());
}
if !old.fetched_columns().is_empty() {
spec = spec.fetching(old.fetched_columns());
}
if old.selects_all() {
spec = spec.selecting_all();
}
if old.uses_database_clock() {
spec = spec.database_clock();
}
opened(&spec, old.opening())
}
}
const fn opened(spec: &TableSpec<'static>, opening: Opening) -> TableSpec<'static> {
let spec = *spec;
match opening {
Opening::Isolation(isolation) => spec.isolation(isolation),
Opening::Mode(mode) => spec.mode(mode),
_ => spec,
}
}