Skip to main content

Crate reliar_store_postgres

Crate reliar_store_postgres 

Source
Expand description

reliar-store-postgres is Reliar’s PostgreSQL provider: the schema, the explicit migrate API, and PostgresOutboxStore — the only crate where an sqlx/Postgres type appears (ADR 0002).

§Quickstart

PostgresOutboxStore::new is the default-type-param constructor, gated on the default json feature; without it this block still shows the shape but is not compiled.

use reliar_store_postgres::{PostgresOutboxStore, migrate};
use reliar_outbox::OutboxEnqueue;
use reliar_core::Message;
use sqlx::postgres::PgPoolOptions;

#[derive(serde::Serialize, serde::Deserialize)]
struct OrderPlaced {
    order_id: String,
}

impl Message for OrderPlaced {
    const TYPE: &'static str = "orders.placed";
    const VERSION: u16 = 1;
}

let database_url = std::env::var("DATABASE_URL")?;
let pool = PgPoolOptions::new().connect(&database_url).await?;

// Run once, out of band — never implicitly at startup.
migrate(&pool, Default::default()).await?;

let store = PostgresOutboxStore::new(pool.clone());

let mut tx = pool.begin().await?;
store.enqueue(&mut tx, OrderPlaced { order_id: "ord_1".into() }).await?;
tx.commit().await?;

// Hand `store` to an `OutboxDispatcher` to publish what was just enqueued.

§MSRV

This crate declares rust-version = "1.94", six releases above the workspace floor (1.88): sqlx 0.9 requires it. Pure crates (reliar-core, reliar-outbox) stay reachable on 1.88 for hosts bringing their own store (ADR 0025).

§Features

§search_path setup

Every Reliar object lives in one configurable schema, reliar by default, with unprefixed table names (outbox). sqlx::query! checks SQL at compile time, so every identifier in every statement is a static, unqualified literal — the schema is resolved at connection time through search_path, never compiled in (ADR 0017).

  • The host puts reliar first on the connection URL: ?options=-c%20search_path%3Dreliar,public.
  • Behind a transaction-mode pooler that drops startup options (some reject the parameter outright with 08P01), use a server-side default instead: ALTER ROLE <app> SET search_path = reliar, public. This is the portable mechanism — verify it against your own pooler build/version rather than assuming: PgDog (ghcr.io/pgdogdev/pgdog:v0.1.46, the pooler this crate’s suite runs behind) was found to pass the options parameter through to the upstream server instead of dropping it, so the URL-options path above works unmodified behind it too, with no ALTER ROLE required — but a different pooler, or a different PgDog configuration, could behave either way.
  • Reliar does not verify this at startup (ADR 0047). Constructing a store issues no query; a search_path that does not resolve outbox/inbox surfaces at the first store call as PostgresOutboxError::NotMigrated/PostgresInboxError::NotMigrated, whose message names both the migrate() and the ALTER ROLE remedy. Reliar never sets search_path on a pool it does not own — not at construction, not per call.
  • migrate does not depend on the caller’s search_path: it creates the schema itself and qualifies its own bookkeeping table name (ADR 0018).

§PostgreSQL version floor

Requirements: PostgreSQL 18 or later. Reliar does not check the server version; behaviour on older servers is undefined. Neither a store constructor nor migrate issues a version probe: a server below the floor is unsupported and fails at whichever statement first needs a PostgreSQL 18 feature (uuidv7(), in practice) — there is no conditional DDL, no substitute, and no degraded mode (ADR 0015, ADR 0047 Amendment B).

§Guarantees

  • Migrations never run implicitly. migrate is the only entry point, and it is idempotent and safe under concurrent callers.
  • A store constructor performs no I/O (ADR 0047) — no query, no connection, no schema or version check; it can run inside a OnceLock, a Default impl, or a synchronous main().
  • The claim is one statement. PostgresOutboxStore’s acquire (via reliar_outbox::OutboxStore) uses a FOR UPDATE SKIP LOCKED claim that commits before the call returns; no network I/O ever happens while a Reliar transaction is open (ADR 0006).
  • Enqueuing joins the caller’s own transactionPostgresOutboxStore implements reliar_outbox::OutboxEnqueue directly, no facade type in between, and atomicity is visible in the signature — and performs no I/O beyond the one INSERT.

Structs§

JsonSerializerjson
The default Serializer: JSON via serde_json. Ships behind the default json feature; disable it to supply a different wire format (ADR 0010).
MigrateOptions
Where migrate creates Reliar’s schema and its bookkeeping table.
PostgresInboxSettings
What is provider-specific about the inbox (inbox contract §3). A separate settings type from PostgresOutboxSettings — the inbox has no lease/ordering/retention knobs. It does share the outbox’s Self::statement_timeout knob, applied to the same kind of call: every statement the inbox issues on its own pool (fail, find, purge) rather than the caller’s transaction (claim/complete, which stay the caller’s to bound).
PostgresInboxStore
Reliar’s PostgreSQL inbox provider (inbox contract §3). A separate type from crate::PostgresOutboxStore: the inbox stores no payload, so it needs no Serializer type parameter and none of the outbox’s lease/ordering/retention settings. Same crate, same schema, same crate::migrate. Cheap to clone — wraps a sqlx::PgPool; no outer Arc required.
PostgresOutboxSettings
What is provider-specific about the outbox. Everything portable lives in reliar_outbox::OutboxSettings.
PostgresOutboxStore
Reliar’s PostgreSQL outbox provider. Cheap to clone into an AppState — it wraps a PgPool; no outer Arc required. The connection pool stays the host’s: Reliar never owns or reads a DATABASE_URL.

Enums§

EnqueueError
crate::PostgresOutboxStore’s reliar_outbox::OutboxEnqueue::enqueue_envelope failures. Enqueuing runs on the host’s write path, where the host decides whether to retry its own transaction, so this implements Classify on the same rules as PostgresOutboxError rather than making the host re-derive which SQLSTATEs are worth retrying.
MigrateError
migrate’s failure. Provider-owned, not a re-export of sqlx::migrate::MigrateError: a rejected schema identifier has no variant in sqlx’s own type to report it as, since that check happens before any sqlx::migrate code runs at all.
PostgresInboxError
A failure of a crate::PostgresInboxStore call (inbox contract §3). Deliberately smaller than crate::PostgresOutboxError: the inbox has no enqueue/acquire analog, so it needs no Decode/UnknownMetadataVersion/DuplicateMessage variant.
PostgresOutboxError
A failure of a crate::PostgresOutboxStore OutboxStore/OutboxDeadLetters call — never a property of one row’s content. Row-content problems surface as reliar_outbox::PoisonedRows instead (ADR 0008).
SettingsError
Why a *Settings::from_env call failed. OutboxSettings::from_env (reliar-outbox) was the first caller; every provider’s own from_env returns this same type, so a host wiring several from_env calls handles one error type for the whole family.

Functions§

migrate
Applies Reliar’s migrations. Never invoked implicitly. pool must reach a PostgreSQL 18 or later server — a hard requirement, with no older-version fallback. This is a stated requirement, not a checked one: migrate() issues no version probe, and a server below the floor fails later, at whichever migration file or query first needs a PostgreSQL 18 feature (uuidv7(), in practice).