use super::{Probe, SqlWriter};
use crate::column::Column;
use crate::dialect::Dialect;
use crate::form::{Form, KeyPart};
use crate::insert::{self, Spelling, fits, too_long};
use crate::role::Role;
use crate::spec::TableSpec;
use crate::statement::{ClaimShape, Param, Statement, StatementError};
use crate::table_name::TableName;
fn required<'a>(
spec: &TableSpec<'a>,
role: Role,
statement: &'static str,
) -> Result<&'a str, StatementError> {
spec.column(role)
.map(|column| column.name())
.ok_or(StatementError::MissingRole { statement, role })
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Built<'a> {
RowLock,
Lease(Column<'a>),
Advisory,
}
pub(crate) trait BuiltIn: Dialect {
const SPELLING: Spelling;
const ROW_LOCKS: bool;
const BACKSLASH_ESCAPES: bool;
const UPDATE_RETURNS: bool;
const PROBE: Probe;
const UNCOUNT: &'static str = " - 1";
fn database_now(&self) -> &'static str;
fn database_later(&self, sql: &mut SqlWriter<'_, Self>);
fn render_lock_key(
&self,
sql: &mut SqlWriter<'_, Self>,
schema: Option<&str>,
key: &[KeyPart<'_>],
);
fn names_fit<'a>(
&self,
names: impl IntoIterator<Item = &'a str>,
) -> Result<(), StatementError> {
let Some(limit) = Self::SPELLING.name_limit else {
return Ok(());
};
names
.into_iter()
.find(|name| !fits(name, limit))
.map_or(Ok(()), |name| {
Err(StatementError::IdentifierTooLong {
dialect: self.name(),
identifier: name.to_owned(),
limit,
})
})
}
fn spec_fits(&self, spec: &TableSpec<'_>) -> Result<(), StatementError> {
self.names_fit(
Self::SPELLING
.name_limit
.and_then(|limit| too_long(spec, limit)),
)
}
fn form<'a>(&self, spec: &TableSpec<'a>) -> Result<Built<'a>, StatementError> {
self.spec_fits(spec)?;
match spec.form() {
Form::RowLock if Self::ROW_LOCKS => Ok(Built::RowLock),
Form::Lease(_) if spec.uses_database_clock() => {
Err(StatementError::LeaseOnDatabaseClock {
dialect: self.name(),
})
}
Form::Lease(expiry) => Ok(Built::Lease(expiry)),
Form::Advisory(_) => Ok(Built::Advisory),
other => Err(StatementError::UnsupportedForm {
dialect: self.name(),
form: other.name(),
}),
}
}
fn advised<'a>(
&self,
spec: &TableSpec<'a>,
statement: &'static str,
) -> Result<&'a [KeyPart<'a>], StatementError> {
self.spec_fits(spec)?;
match spec.form() {
Form::Advisory(_) if spec.is_fifo() => Err(StatementError::AdvisoryFifo {
dialect: self.name(),
}),
Form::Advisory(key) => {
self.names_fit(key.iter().filter_map(|part| match part {
KeyPart::Column(column) => Some(*column),
KeyPart::Literal(_) => None,
}))?;
Ok(key)
}
other => Err(StatementError::FormMismatch {
statement,
form: other.name(),
}),
}
}
#[cfg(any(feature = "postgres", feature = "mysql"))]
fn guards_group(&self, spec: &TableSpec<'_>) -> Result<bool, StatementError> {
if !spec.is_fifo() {
return Ok(false);
}
match self.form(spec)? {
Built::Advisory => Err(StatementError::AdvisoryFifo {
dialect: self.name(),
}),
Built::RowLock | Built::Lease(_) => Ok(true),
}
}
#[cfg(any(feature = "postgres", feature = "mysql"))]
fn locked(&self, spec: &TableSpec<'_>, statement: &'static str) -> Result<(), StatementError> {
self.spec_fits(spec)?;
match spec.form() {
Form::RowLock => Ok(()),
other => Err(StatementError::FormMismatch {
statement,
form: other.name(),
}),
}
}
fn leased<'a>(
&self,
spec: &TableSpec<'a>,
statement: &'static str,
) -> Result<Column<'a>, StatementError> {
self.spec_fits(spec)?;
match spec.form() {
Form::Lease(_) if spec.uses_database_clock() => {
Err(StatementError::LeaseOnDatabaseClock {
dialect: self.name(),
})
}
Form::Lease(expiry) => Ok(expiry),
other => Err(StatementError::FormMismatch {
statement,
form: other.name(),
}),
}
}
fn movable(&self, spec: &TableSpec<'_>, target: TableName<'_>) -> Result<(), StatementError> {
if matches!(self.form(spec)?, Built::Lease(_)) && spec.selects_all() {
return Err(StatementError::Flattened {
statement: "dead_letter_table",
});
}
self.names_fit(target.schema().into_iter().chain([target.table()]))
}
fn finish_statement(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.form(spec)?;
let mut sql = SqlWriter::new(self);
match spec.column(Role::ProcessedAt) {
Some(processed_at) => sql
.push("UPDATE ")
.table(spec)
.push(" SET ")
.ident(processed_at.name())
.push(" = ")
.now(spec)
.release(spec),
None => sql.push("DELETE FROM ").table(spec),
};
sql.settled_row(spec);
Ok(sql.finish())
}
fn retry_statement(&self, spec: &TableSpec<'_>) -> Result<Option<Statement>, StatementError> {
let form = self.form(spec)?;
let mut sql = SqlWriter::new(self);
sql.push("UPDATE ").table(spec).push(" SET ");
match form {
Built::Lease(expiry) => sql.ident(expiry.name()).push(" = NULL"),
Built::RowLock => match spec.column(Role::Attempt) {
Some(attempt) => sql.increment(attempt.name()),
None => return Ok(None),
},
Built::Advisory => return Ok(None),
};
sql.settled_row(spec);
Ok(Some(sql.finish()))
}
fn retry_after_statement(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
let form = self.form(spec)?;
let retry_after = required(spec, Role::RetryAfter, "retry_after")?;
let mut sql = SqlWriter::new(self);
sql.push("UPDATE ")
.table(spec)
.push(" SET ")
.ident(retry_after)
.push(" = ")
.later(spec);
match form {
Built::RowLock => {
if let Some(attempt) = spec.column(Role::Attempt) {
sql.push(", ").increment(attempt.name());
}
}
Built::Lease(_) => {
sql.release(spec);
}
Built::Advisory => {}
}
sql.settled_row(spec);
Ok(sql.finish())
}
fn dead_letter_group_statement(
&self,
spec: &TableSpec<'_>,
) -> Result<Statement, StatementError> {
self.form(spec)?;
let group = required(spec, Role::Group, "dead_letter_group")?;
let mut sql = SqlWriter::new(self);
sql.push("UPDATE ")
.table(spec)
.push(" SET ")
.ident(group)
.push(" = ")
.param(Param::Destination)
.release(spec)
.settled_row(spec);
Ok(sql.finish())
}
fn extend_statement(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
let expiry = self.leased(spec, "extend")?;
let mut sql = SqlWriter::new(self);
sql.push("UPDATE ")
.table(spec)
.push(" SET ")
.ident(expiry.name())
.push(" = ")
.param(Param::Lease)
.settled_row(spec);
Ok(sql.finish())
}
fn stamp_statement(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
let expiry = self.leased(spec, "stamp")?;
let id = spec.id().name();
let mut sql = SqlWriter::new(self);
sql.push("UPDATE ")
.table(spec)
.push(" SET ")
.ident(expiry.name())
.push(" = ")
.param(Param::Lease);
if let Some(attempt) = spec.column(Role::Attempt) {
sql.push(", ").increment(attempt.name());
}
sql.push(" WHERE ")
.ident(id)
.push(" = ")
.param(Param::Id)
.push(" AND ")
.lease_free(expiry.name());
Ok(sql.finish())
}
fn advisory_claim_statement(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
let key = self.advised(spec, "advisory_claim")?;
let mut sql = SqlWriter::new(self);
sql.candidates(spec, key);
Ok(sql.finish())
}
fn take_statements(
&self,
spec: &TableSpec<'_>,
shape: ClaimShape,
) -> Result<Vec<Statement>, StatementError> {
self.advised(spec, "take")?;
let Some(attempt) = spec.column(Role::Attempt) else {
let mut read = SqlWriter::new(self);
read.push("SELECT ")
.claimed_columns(spec, shape)
.push(" FROM ")
.table(spec)
.taken_row(spec);
return Ok(vec![read.finish()]);
};
let mut count = SqlWriter::new(self);
count
.push("UPDATE ")
.table(spec)
.push(" SET ")
.increment(attempt.name())
.taken_row(spec);
if Self::UPDATE_RETURNS {
count.push(" RETURNING ").counted_columns(spec, shape);
return Ok(vec![count.finish()]);
}
let mut read = SqlWriter::new(self);
read.push("SELECT ")
.counted_columns(spec, shape)
.push(" FROM ")
.table(spec)
.push(" WHERE ")
.ident(spec.id().name())
.push(" = ")
.param(Param::Id);
Ok(vec![count.finish(), read.finish()])
}
#[cfg(any(feature = "mysql", feature = "sqlite"))]
fn copy_then_delete(
&self,
spec: &TableSpec<'_>,
target: TableName<'_>,
) -> Result<Vec<Statement>, StatementError> {
self.movable(spec, target)?;
let mut copy = SqlWriter::new(self);
copy.push("INSERT INTO ").table_name(target);
if !spec.selects_all() {
copy.push(" (").columns(spec).push(")");
}
copy.push(" SELECT ")
.moved_columns(spec)
.push(" FROM ")
.table(spec)
.settled_row(spec);
let mut delete = SqlWriter::new(self);
delete.push("DELETE FROM ").table(spec).settled_row(spec);
Ok(vec![copy.finish(), delete.finish()])
}
fn insert_statement(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
insert::statement(spec, Self::SPELLING)
}
}