use std::num::NonZeroUsize;
use crate::advisory::Advisory;
use crate::dialect::Dialect;
use crate::form::KeyPart;
use crate::insert::{Placeholder, Spelling, quote_into};
use crate::lease::Lease;
use crate::opening::{Isolation, Opening, Opens, level};
use crate::outbox::OutboxDialect;
use crate::row_lock::RowLock;
use crate::spec::TableSpec;
use crate::statement::{ClaimShape, NameLimit, Param, Statement, StatementError};
use crate::table_name::TableName;
use crate::writer::{BuiltIn, OutboxWriter, Probe, SqlWriter};
const DATABASE_NOW: &str = "statement_timestamp()";
const LOCK: &str = " FOR UPDATE SKIP LOCKED";
const TRY_LOCK: &str = "SELECT pg_try_advisory_lock(hashtextextended($1, 0))::int::bigint";
const UNLOCK: &str = "SELECT pg_advisory_unlock(hashtextextended($1, 0))::int::bigint";
pub(crate) const SPELLING: Spelling = Spelling {
dialect: "postgres",
quote: b'"',
placeholder: Placeholder::Dollar,
default_row: " DEFAULT VALUES",
name_limit: Some(NameLimit::Bytes(63)),
};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Postgres;
impl BuiltIn for Postgres {
const SPELLING: Spelling = SPELLING;
const ROW_LOCKS: bool = true;
const BACKSLASH_ESCAPES: bool = false;
const UPDATE_RETURNS: bool = true;
const PROBE: Probe = Probe::Lock(
"pg_try_advisory_xact_lock_shared(hashtextextended(",
", 0))",
);
const UNCOUNT: &'static str = " - 1::smallint";
fn database_now(&self) -> &'static str {
DATABASE_NOW
}
fn database_later(&self, sql: &mut SqlWriter<'_, Self>) {
sql.push(DATABASE_NOW)
.push(" + ")
.param(Param::Delay)
.push("::bigint * interval '1 microsecond'");
}
fn render_lock_key(&self, sql: &mut SqlWriter<'_, Self>, _: Option<&str>, key: &[KeyPart<'_>]) {
sql.push("concat(")
.key_parts(key, ", ", |sql, column| {
sql.ident(column);
})
.push(")");
}
}
impl Dialect for Postgres {
fn name(&self) -> &'static str {
"postgres"
}
fn quote_into(&self, ident: &str, out: &mut String) {
quote_into(SPELLING.quote, ident, out);
}
fn placeholder_into(&self, index: NonZeroUsize, out: &mut String) {
out.push('$');
out.push_str(&index.to_string());
}
fn fetch(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.spec_fits(spec)?;
let id = spec.id().name();
let mut sql = SqlWriter::new(self);
sql.push("SELECT ")
.columns(spec)
.push(" FROM ")
.table(spec)
.push(" WHERE ")
.ident(id)
.push(" = ANY(")
.param(Param::Ids)
.push(")");
Ok(sql.finish())
}
fn ack(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.finish_statement(spec)
}
fn retry(&self, spec: &TableSpec<'_>) -> Result<Option<Statement>, StatementError> {
self.retry_statement(spec)
}
fn retry_after(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.retry_after_statement(spec)
}
fn discard(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.finish_statement(spec)
}
fn dead_letter_group(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.dead_letter_group_statement(spec)
}
fn dead_letter_table(
&self,
spec: &TableSpec<'_>,
target: TableName<'_>,
) -> Result<Vec<Statement>, StatementError> {
self.movable(spec, target)?;
let mut sql = SqlWriter::new(self);
sql.push("WITH moved AS (DELETE FROM ")
.table(spec)
.settled_row(spec)
.push(" RETURNING ")
.columns(spec)
.push(") INSERT INTO ")
.table_name(target);
if !spec.selects_all() {
sql.push(" (").columns(spec).push(")");
}
sql.push(" SELECT ").moved_columns(spec).push(" FROM moved");
Ok(vec![sql.finish()])
}
fn insert(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.insert_statement(spec)
}
fn begin(&self, opening: Opening) -> Result<Option<&'static str>, StatementError> {
match opening {
Opening::Default => Ok(None),
Opening::Isolation(Isolation::ReadCommitted) => {
Ok(Some("BEGIN ISOLATION LEVEL READ COMMITTED"))
}
Opening::Isolation(Isolation::RepeatableRead) => {
Ok(Some("BEGIN ISOLATION LEVEL REPEATABLE READ"))
}
Opening::Isolation(Isolation::Serializable) => {
Ok(Some("BEGIN ISOLATION LEVEL SERIALIZABLE"))
}
Opening::Isolation(Isolation::ReadUncommitted) | Opening::Mode(_) => {
Err(opening.refused(self.name()))
}
}
}
fn fifo_guard(&self, spec: &TableSpec<'_>) -> Result<Option<Statement>, StatementError> {
if !self.guards_group(spec)? {
return Ok(None);
}
let mut table = spec
.schema()
.map(|schema| format!("{schema}."))
.unwrap_or_default();
table.push_str(spec.table());
table.push(':');
let mut sql = SqlWriter::new(self);
sql.push("SELECT pg_try_advisory_xact_lock(hashtextextended(")
.literal(&table)
.push(" || ")
.param(Param::Group)
.push(", 0))::int::bigint");
Ok(Some(sql.finish()))
}
}
impl OutboxDialect for Postgres {
fn outbox_fetch(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.outbox_fetch_statement(spec)
}
fn outbox_mark(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.outbox_mark_statement(spec)
}
fn outbox_recover(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.outbox_recover_statement(spec)
}
}
impl Opens<level::ReadCommitted> for Postgres {}
impl Opens<level::RepeatableRead> for Postgres {}
impl Opens<level::Serializable> for Postgres {}
impl RowLock for Postgres {
fn lock_claim(
&self,
spec: &TableSpec<'_>,
shape: ClaimShape,
) -> Result<Statement, StatementError> {
self.locked(spec, "lock_claim")?;
let mut sql = SqlWriter::new(self);
sql.claim(spec, shape, LOCK);
Ok(sql.finish())
}
}
impl Advisory for Postgres {
fn advisory_claim(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.advisory_claim_statement(spec)
}
fn lock(&self) -> Option<Statement> {
Some(Statement::new(TRY_LOCK, [Param::Key]))
}
fn unlock(&self) -> Option<Statement> {
Some(Statement::new(UNLOCK, [Param::Key]))
}
fn take(
&self,
spec: &TableSpec<'_>,
shape: ClaimShape,
) -> Result<Vec<Statement>, StatementError> {
self.take_statements(spec, shape)
}
}
impl Lease for Postgres {
fn lease_claim(
&self,
spec: &TableSpec<'_>,
shape: ClaimShape,
) -> Result<Statement, StatementError> {
let expiry = self.leased(spec, "lease_claim")?;
let mut sql = SqlWriter::new(self);
sql.lease_claim(spec, shape, expiry.name(), LOCK);
Ok(sql.finish())
}
fn extend(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.extend_statement(spec)
}
fn stamp(&self, spec: &TableSpec<'_>) -> Result<Statement, StatementError> {
self.stamp_statement(spec)
}
}