1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! `turnframe-store-postgres`: the PostgreSQL implementation of the Turnframe
//! persistence contract.
//!
//! # This crate is optional
//!
//! The contract is [`turnframe-store`](turnframe_store): seven object-safe
//! traits and an executable conformance suite that proves an implementation
//! right. This crate is *one* implementation of them. An adopter with an
//! existing schema, another database, or a different operational story can
//! implement the traits themselves and never depend on this crate — the runtime
//! cannot tell the difference, and the conformance suite will say so either way.
//!
//! What you get by using it is a schema that has already been argued about: the
//! constraints below are where the rules of the contract live, so a race that
//! would break an invariant loses on an index rather than on a lucky
//! interleaving.
//!
//! # Getting started
//!
//! ```rust,no_run
//! use turnframe_core::ids::{AccountId, ConversationId};
//! use turnframe_store::prelude::*;
//! use turnframe_store_postgres::PgStores;
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! let store = PgStores::connect("postgres://turnframe@localhost/turnframe").await?;
//! store.migrate().await?;
//!
//! let stores: Stores = store.stores()?;
//! let account = AccountId::from("aurora");
//! let conversation = ConversationId::new();
//!
//! stores
//! .conversations()
//! .create_conversation(ConversationRecord::new(
//! conversation,
//! account.clone(),
//! chrono::Utc::now(),
//! ))
//! .await?;
//!
//! // Another tenant cannot tell it apart from a conversation that never existed.
//! assert_eq!(
//! stores
//! .conversations()
//! .load_conversation(&AccountId::from("other"), &conversation)
//! .await,
//! Err(StoreError::NotFound)
//! );
//! # Ok(())
//! # }
//! ```
//!
//! # Where each rule lives
//!
//! | Rule | How it is enforced |
//! |---|---|
//! | at most one open blocking card per case (I5) | the partial unique index `tf_one_open_blocking_interaction_per_case` |
//! | one admission per idempotency key (I14) | `UNIQUE (account_id, idempotency_key)` plus `INSERT … ON CONFLICT DO NOTHING`, then a re-read that replays the winner |
//! | one external action per destination | `UNIQUE (destination, idempotency_key)` |
//! | a claimed outbox row is one dispatcher's | `SELECT … FOR UPDATE SKIP LOCKED` inside the claiming statement |
//! | compare-and-swap, never blind overwrite | the expected state is the `WHERE` clause of the write; zero affected rows means the precondition failed |
//! | a bundle is all or nothing | one transaction, committed only after the last item succeeded |
//! | tenant isolation | `account_id` leads every primary key and every index, so a query that forgets it cannot use one |
//!
//! # Runtime-checked queries, on purpose
//!
//! Every statement in this crate goes through `sqlx::query` and reads its
//! columns by name. None of them uses the `sqlx::query!` family.
//!
//! Those macros check SQL against a live database *at compile time*, which is a
//! real benefit and the wrong trade for a published library: it makes the crate
//! unbuildable in a clean checkout unless a database is reachable or a
//! `.sqlx` cache is committed and kept in step with every edit. A contributor
//! with no PostgreSQL, a `cargo install`, a `docs.rs` build and a downstream
//! `cargo vendor` would all fail on something that has nothing to do with their
//! change.
//!
//! The check that macros would have given is bought back by the conformance
//! suite instead: it runs the whole persistence contract against a real
//! PostgreSQL 16, so a column renamed on one side and not the other fails a test
//! rather than a build — later, but against behaviour rather than shape.
//! [`sqlx::migrate!`] is still a macro and still used: it reads `migrations/`
//! while compiling and needs no database.
//!
//! # Assumptions this adapter makes
//!
//! * **`READ COMMITTED`.** PostgreSQL's default. The idempotency admission and
//! every compare-and-swap rely on a statement re-reading a row another
//! transaction has just committed. At `REPEATABLE READ` those become
//! serialization failures, reported as `Conflict` — correct, but it turns
//! routine contention into caller-visible refusals.
//! * **Microsecond timestamps.** `timestamptz` keeps microseconds; instants this
//! adapter stamps are truncated to match, so a value written and read back
//! compares equal.
//! * **One schema.** Set [`PgStoreConfig::schema`] to keep the tables out of
//! `public`; [`PgStores::migrate`] creates it.
//!
//! See the [README](https://github.com/turnframe-rs/turnframe/blob/main/crates/turnframe-store-postgres/README.md)
//! for the schema table by table and for running the conformance suite.
/// The crate README, compiled as a doc-test so its examples cannot rot.
pub use crate;
pub use crate;
/// Applies every migration this crate carries to a pool the caller owns.
///
/// Equivalent to [`PgStores::migrate`] without the store, for a deployment that
/// migrates from a separate binary. It does not create a schema: point the
/// pool's `search_path` at one that exists, or use [`PgStores::migrate`], which
/// creates the configured schema first.
///
/// # Errors
/// * [`MigrateError`](sqlx::migrate::MigrateError) when a migration fails or an
/// already-applied migration no longer matches its recorded checksum.
pub async