Postgres engine: schema bootstrap — engine-owned schema, table family, idempotent DDL, schema-prefixed indexes (task pg-engine-schema)

This commit is contained in:
glm-5.3-flash committed 2026-10-09 05:11:48 +00:00
1 parent 4caea21098
commit 2f1353bd41
6 files changed
+951 -9

No files matched your search

+82 -9
View File
@@ -1,7 +1,7 @@
---
id: pg-engine-schema
name: Postgres engine — schema bootstrap (engine-owned schema, table family)
status: pending
status: completed
depends_on: []
scope: narrow
risk: low
@@ -79,17 +79,18 @@ index exists.
## Acceptance Criteria
- [ ] `schema.rs` with an idempotent `bootstrap(conn, schema_name)`
- [x] `schema.rs` with an idempotent `bootstrap(conn, schema_name)`
creating the full table family + indexes in the one schema
- [ ] Column sets match the contract's value shapes (the constructors'
- [x] Column sets match the contract's value shapes (the constructors'
field lists; stamp columns per ADR-010 §3a; `claimed_at` per
ADR-021 §2)
- [ ] Payload columns are `bytea` (exact-bytes posture, JSONB
- [x] Payload columns are `bytea` (exact-bytes posture, JSONB
rejected — documented in the module docs with the reason)
- [ ] Schema name is a parameter (default `alkstore` decided at the
- [x] Schema name is a parameter (default `alkstore` decided at the
`PgOpts` layer, next task)
- [ ] Idempotence + table-shape tests green against the harness server
- [ ] `cargo test -p alkstore-postgres`, clippy `-D warnings`, fmt clean
- [x] Indexes: idempotence + table-shape tests green against the
harness server
- [x] `cargo test -p alkstore-postgres`, clippy `-D warnings`, fmt clean
## References
@@ -103,8 +104,80 @@ index exists.
## Notes
> To be filled by implementation agent
Decisions of record made while implementing (the description didn't
pin them):
- **Table names**: `job`, `dead`, `stream_events`, `stream_offsets`,
`schedule`, `locks` — fixed constants exported as
`alkstore_postgres::tables` for the later mechanism tasks' SQL.
Table names do not ride configuration; only the schema name does.
- **Index-name qualification**: Postgres rejects schema-qualified
index names at `CREATE INDEX`, but `IF NOT EXISTS` on a *bare* name
checks existence across all schemas — a co-tenant's unrelated
`public.job_claim_idx` would silently skip our index creation. So
every engine index is named `{schema}_{table}_{suffix}`
(`job_claim_idx`, `job_pending_idx`, `job_deadline_idx`,
`dead_retention_idx`, `events_read_idx`, `schedule_fire_idx`,
`locks_expiry_idx`, exported as `indexes::`) — schema-prefixed and
quoted, tested against a co-tenant carrying the bare name.
Sub-HOT-path indexes (`job_pending`, `job_deadline`, the substrate's
trio) carried over from the reference shapes — the claim/heartbeat
paths the queue task re-derives ride them.
- **Reserved-word columns**: the offsets checkpoint column is
`"offset"` and the events key column `"key"` — both quoted here and
flagged in the module docs so every later task's SQL keeps the
quotes. The offsets column carries a non-negative CHECK (cursors
start at 0); monotonicity itself stays in the streams task's
GREATEST-guarded upsert.
- **Identifier quoting**: `quote_identifier` (doubles embedded `"`)
is the engine's injection boundary for the consumer-supplied schema
name, exported for later tasks; tested hostile (SQL-breakout schema
name stays inert single-schema DDL).
- **Schedule columns** anchor to the substrate's
`__alkstore_scheduler_tasks` concrete-stamp shape, `enabled` dropped
(no pause/resume in the collapse surface): `name` PK, `spec`,
`queue`, `payload`, `priority`, `expires_s`, `next_fire_at` (the
tick's boundary column), `max_attempts` (default 3),
`visibility_timeout_s` (default 300), `backoff_base_s` (default 5),
`dead_letter_retention_s`. No `last_fire_at` (`next_fire_at` is the
only state the tick math needs).
- **Dead table** mirrors the job table's live columns + the diagnosis
fields (`last_error`, `died_at NOT NULL`), `id` a plain BIGINT PK
(carried from the move, not a sequence); retention index
`(queue, died_at)`.
- **Job table** = the `Job::from_row` field list of record verbatim +
the five stamp columns; `state TEXT DEFAULT 'pending'` (SQLite
parity — the claim predicates read the text values); three indexes
(claim-ordering partial `WHERE state IN ('pending','processing')`
per ADR-010 §1's ordering row, pending `(queue, run_at)`, processing
deadline `(queue, claim_expires_at)`).
- **`thiserror` added to the crate** (v2, same as core/SQLite) for
`BootstrapError`; the `open` task maps it onto `Error::Database`
next task.
- **Harness convention** (ride env, never hardcoded, skip-clean
server-less): `ALKSTORE_PG_HOST/PORT/USER/PASSWORD/DB` — host
127.0.0.1, port 15432, user/password `postgres`/`poc`, db `blobs`
(the `pglo-poc` container's). The next tasks' tests should reuse
these variable names. `bootstrap` itself takes any
`&tokio_postgres::Client` (NoTls in tests; TLS is the `open` task's
concern via deadpool config, not this module's).
## Summary
> To be filled on completion
Landed: `alkstore-postgres/src/schema.rs` — the engine-owned schema
module (`bootstrap(conn, schema_name)` idempotent DDL over
`batch_execute`; `DEFAULT_SCHEMA = "alkstore"`; `quote_identifier`;
`tables`/`indexes` name constants; `BootstrapError`), wired into
`lib.rs` re-exports, plus `tests/schema_tests.rs` (9 tests) driven
against the harness server via env-carried DSN, skipping cleanly
server-less. Table family per the acceptance criteria (job/dead/
stream_events/stream_offsets/schedule/locks, bytea payloads, bigserial
offsets, claim-ordering partial index with the pinned ordering row).
Verified: 9/9 green against the harness server (idempotence ×3 runs,
per-table column sets, bytea types, bigserial monotone + gap-on-delete,
claim index def fragments incl. `priority DESC` + partiality, schema
name as parameter incl. reserved-word name `select`, co-tenancy
re-bootstrap, co-tenant bare-index-name collision exclusion, hostile
schema-name quoting); workspace `cargo test` green server-less (236
tests, pg tests skipped per convention); clippy `-D warnings` and fmt
clean workspace-wide.