Centralize durable schema migration ownership
This commit is contained in:
parent
cc328237f4
commit
069f3813ae
5 changed files with 354 additions and 350 deletions
|
|
@ -18,4 +18,5 @@ time.workspace = true
|
|||
tokio.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
remora-schema.workspace = true
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync", "time"] }
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
-- DurableStore substrate tables owned by remora-durable.
|
||||
-- Feature packages add their own migrations; they do not alter these tables.
|
||||
|
||||
CREATE TABLE mutation_records (
|
||||
scope_subject TEXT NOT NULL,
|
||||
scope_operation TEXT NOT NULL,
|
||||
operation_key TEXT NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
-- Opaque RemoraApiMutation eligibility / authority binding (attempt, fence,
|
||||
-- worker incarnation, or other claim material). Compared for exact replay.
|
||||
eligibility_claim BYTEA NOT NULL,
|
||||
request_fingerprint BYTEA NOT NULL CHECK (octet_length(request_fingerprint) = 32),
|
||||
response_status TEXT NOT NULL CHECK (response_status IN ('accepted', 'rejected')),
|
||||
response_body JSONB NOT NULL,
|
||||
effect_applied BOOLEAN NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (scope_subject, scope_operation, operation_key),
|
||||
CHECK (effect_applied = (response_status = 'accepted'))
|
||||
);
|
||||
|
||||
CREATE TABLE outbox_messages (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
topic TEXT NOT NULL,
|
||||
payload JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
available_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
claimed_by TEXT,
|
||||
claimed_until TIMESTAMPTZ,
|
||||
-- Monotonic per-row claim epoch; with claimed_by forms the ownership token.
|
||||
claim_generation BIGINT NOT NULL DEFAULT 0,
|
||||
projected_at TIMESTAMPTZ,
|
||||
attempt_count INTEGER NOT NULL DEFAULT 0,
|
||||
last_error TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX outbox_messages_claimable_idx
|
||||
ON outbox_messages (available_at, id)
|
||||
WHERE projected_at IS NULL;
|
||||
|
|
@ -1,15 +1,13 @@
|
|||
//! PostgreSQL DurableStore substrate.
|
||||
//!
|
||||
//! Provides the pool, migration runner, transaction helpers, generic mutation
|
||||
//! ledger refining `RemoraApiMutation`, durable outbox claim/projection, and
|
||||
//! integrity-protected keyset cursor primitives. Feature packages own their
|
||||
//! domain schema additions.
|
||||
//! Provides the pool, transaction helpers, generic mutation ledger refining
|
||||
//! `RemoraApiMutation`, durable outbox claim/projection, and integrity-protected
|
||||
//! keyset cursor primitives.
|
||||
|
||||
mod cas;
|
||||
mod cursor;
|
||||
mod error;
|
||||
mod ledger;
|
||||
mod migrate;
|
||||
mod outbox;
|
||||
mod pool;
|
||||
mod tx;
|
||||
|
|
@ -21,7 +19,6 @@ pub use ledger::{
|
|||
MutationDisposition, MutationOutcome, MutationStatus, execute_mutation, execute_mutation_in_tx,
|
||||
observe_mutation,
|
||||
};
|
||||
pub use migrate::{migrate, migrator};
|
||||
pub use outbox::{
|
||||
OutboxClaim, OutboxClaimFilter, OutboxMessage, claim_batch, claim_batch_in_tx, enqueue,
|
||||
enqueue_after, mark_projected, mark_projected_pool, release_with_error, unprojected_count,
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
use sqlx::PgPool;
|
||||
|
||||
use crate::error::DurableError;
|
||||
|
||||
static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("./migrations");
|
||||
|
||||
pub async fn migrate(pool: &PgPool) -> Result<(), DurableError> {
|
||||
MIGRATOR.run(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn migrator() -> &'static sqlx::migrate::Migrator {
|
||||
&MIGRATOR
|
||||
}
|
||||
|
|
@ -7,9 +7,10 @@ use remora_durable::{
|
|||
ADVISORY_CLASS_APP, CursorCodec, DurableError, KeysetPosition, MutationDisposition,
|
||||
MutationStatus, OutboxClaim, OutboxClaimFilter, PoolConfig, advisory_key, advisory_xact_lock,
|
||||
cas_set_text, claim_batch, claim_batch_in_tx, connect, enqueue, enqueue_after,
|
||||
execute_mutation, first_page_cursor, mark_projected, mark_projected_pool, migrate,
|
||||
next_page_cursor, release_with_error, unprojected_count, with_transaction,
|
||||
execute_mutation, first_page_cursor, mark_projected, mark_projected_pool, next_page_cursor,
|
||||
release_with_error, unprojected_count, with_transaction,
|
||||
};
|
||||
use remora_schema::migrate as migrate_schema;
|
||||
use serde_json::json;
|
||||
use sqlx::PgPool;
|
||||
use time::{Duration as TimeDuration, OffsetDateTime};
|
||||
|
|
@ -39,7 +40,7 @@ async fn test_pool() -> Option<PgPool> {
|
|||
)
|
||||
.await
|
||||
.expect("connect to postgres");
|
||||
migrate(&pool).await.expect("run substrate migrations");
|
||||
migrate_schema(&pool).await.expect("run schema migrations");
|
||||
Some(pool)
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +86,7 @@ async fn migration_round_trip_is_idempotent() {
|
|||
let Some(pool) = test_pool().await else {
|
||||
return;
|
||||
};
|
||||
migrate(&pool).await.expect("second migrate");
|
||||
migrate_schema(&pool).await.expect("second migrate");
|
||||
let (table_count,): (i64,) = sqlx::query_as(
|
||||
"SELECT COUNT(*)::bigint
|
||||
FROM information_schema.tables
|
||||
|
|
@ -97,8 +98,6 @@ async fn migration_round_trip_is_idempotent() {
|
|||
.expect("count substrate tables");
|
||||
assert_eq!(table_count, 2);
|
||||
|
||||
let applied = remora_durable::migrator().iter().count();
|
||||
assert_eq!(applied, 1);
|
||||
let (version_count,): (i64,) = sqlx::query_as("SELECT COUNT(*)::bigint FROM _sqlx_migrations")
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
|
|
|
|||
Loading…
Reference in a new issue