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
json(default) —PostgresOutboxStore<JsonSerializer>’s default type parameter and thePostgresOutboxStore::new/PostgresOutboxStore::with_settingsconvenience constructors (forwardsreliar-core/json). Not hard-enabled: a deployment supplying its ownreliar_core::Serializershould not have to pull inserde_json. Under--no-default-features,PostgresOutboxStore::with_serializeris the only constructor.serde(off by default) —Serialize/DeserializeonPostgresOutboxSettings,#[serde(default, deny_unknown_fields)]so a typo’d config key is a hard error, durations as integer milliseconds.serdeitself is always a dependency regardless of this feature — it also drives the crate’s privateMetadataRestJSONB contract (ADR 0012), which is not feature-gated.
§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
reliarfirst on the connection URL:?options=-c%20search_path%3Dreliar,public. - Behind a transaction-mode pooler that drops startup
options(some reject the parameter outright with08P01), 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 theoptionsparameter through to the upstream server instead of dropping it, so the URL-optionspath above works unmodified behind it too, with noALTER ROLErequired — but a different pooler, or a differentPgDogconfiguration, could behave either way. - Reliar does not verify this at startup (ADR 0047). Constructing a store issues no
query; a
search_paththat does not resolveoutbox/inboxsurfaces at the first store call asPostgresOutboxError::NotMigrated/PostgresInboxError::NotMigrated, whose message names both themigrate()and theALTER ROLEremedy. Reliar never setssearch_pathon a pool it does not own — not at construction, not per call. migratedoes not depend on the caller’ssearch_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.
migrateis 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, aDefaultimpl, or a synchronousmain(). - The claim is one statement.
PostgresOutboxStore’sacquire(viareliar_outbox::OutboxStore) uses aFOR UPDATE SKIP LOCKEDclaim 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 transaction —
PostgresOutboxStoreimplementsreliar_outbox::OutboxEnqueuedirectly, no facade type in between, and atomicity is visible in the signature — and performs no I/O beyond the oneINSERT.
Structs§
- Json
Serializer json - The default
Serializer: JSON viaserde_json. Ships behind the defaultjsonfeature; disable it to supply a different wire format (ADR 0010). - Migrate
Options - Where
migratecreates Reliar’s schema and its bookkeeping table. - Postgres
Inbox Settings - 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’sSelf::statement_timeoutknob, 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). - Postgres
Inbox Store - Reliar’s PostgreSQL inbox provider (inbox contract §3). A separate type from
crate::PostgresOutboxStore: the inbox stores no payload, so it needs noSerializertype parameter and none of the outbox’s lease/ordering/retention settings. Same crate, same schema, samecrate::migrate. Cheap to clone — wraps asqlx::PgPool; no outerArcrequired. - Postgres
Outbox Settings - What is provider-specific about the outbox. Everything portable lives in
reliar_outbox::OutboxSettings. - Postgres
Outbox Store - Reliar’s PostgreSQL outbox provider. Cheap to clone into an
AppState— it wraps aPgPool; no outerArcrequired. The connection pool stays the host’s: Reliar never owns or reads aDATABASE_URL.
Enums§
- Enqueue
Error crate::PostgresOutboxStore’sreliar_outbox::OutboxEnqueue::enqueue_envelopefailures. Enqueuing runs on the host’s write path, where the host decides whether to retry its own transaction, so this implementsClassifyon the same rules asPostgresOutboxErrorrather than making the host re-derive which SQLSTATEs are worth retrying.- Migrate
Error migrate’s failure. Provider-owned, not a re-export ofsqlx::migrate::MigrateError: a rejected schema identifier has no variant insqlx’s own type to report it as, since that check happens before anysqlx::migratecode runs at all.- Postgres
Inbox Error - A failure of a
crate::PostgresInboxStorecall (inbox contract §3). Deliberately smaller thancrate::PostgresOutboxError: the inbox has noenqueue/acquireanalog, so it needs noDecode/UnknownMetadataVersion/DuplicateMessagevariant. - Postgres
Outbox Error - A failure of a
crate::PostgresOutboxStoreOutboxStore/OutboxDeadLetterscall — never a property of one row’s content. Row-content problems surface asreliar_outbox::PoisonedRows instead (ADR 0008). - Settings
Error - Why a
*Settings::from_envcall failed.OutboxSettings::from_env(reliar-outbox) was the first caller; every provider’s ownfrom_envreturns this same type, so a host wiring severalfrom_envcalls handles one error type for the whole family.
Functions§
- migrate
- Applies Reliar’s migrations. Never invoked implicitly.
poolmust 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).