Skip to content

feat(postgres): support explicitly configured storage schemas - #517

Merged
arun-pathiban-ddog merged 2 commits into
mainfrom
codex/postgres-schema
Oct 2, 2026
Merged

arun-pathiban-ddog merged 2 commits into
mainfrom
codex/postgres-schema

Conversation

@arun-pathiban-ddog

@arun-pathiban-ddog arun-pathiban-ddog commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Add optional PostgreSQL schema selection to the kernel. PostgresEventStore::new(pool) keeps the existing search-path behavior. PostgresEventStore::with_schema(pool, PostgresSchema::new(name)?) explicitly qualifies the store's table references, including events, snapshots, policies, specs, WASM, secrets, catalog queries and indexes.

The standard server retains the configured store through its metadata helpers. A new restore_registry_from_postgres_store entry point preserves the schema when restoring specs; the existing pool-based entry point remains compatible.

run_migrations_in_schema runs the original SQLx migrations and migration history in the chosen schema within one transaction, with a transaction-scoped advisory lock and transaction-local search path. Existing migration files and checksums are unchanged. Runtime queries require no session settings. Schema names are quoted as one identifier and validated against PostgreSQL's identifier length/NUL restrictions.

Motivation

The control plane's tables exist in its own schema, but OrgStore uses transaction pooling and does not retain application session settings between requests. Explicit table qualification lets the application choose its storage schema without changing shared database connection defaults.

Verification

  • Reproduced the missing-table failure with the schema excluded from search_path.
  • Real PostgreSQL: selected-schema access passes without changing the connection default.
  • Real PostgreSQL: three stores share one pool and isolate events, snapshots, specs, policies, WASM, secrets, blobs and filtered/paginated catalog queries; legacy constructor and migration behavior are exercised too.
  • cargo test --locked -p temper-store-postgres: 57 unit tests and the API documentation example passed (live tests run separately).
  • cargo test --locked -p temper-store-postgres --test schema_selection -- --ignored: both real PostgreSQL regressions passed.
  • cargo test --locked -p temper-server --test postgres_schema -- --ignored: standard-server secret, WASM and specification persistence/restoration passed with the runtime schema excluded from search_path.
  • Control-plane adoption: both real PostgreSQL tests passed, including a separate-process restart, preserved administration tables, and startup without SQLx migration history.
  • cargo check -p temper-server: passed. GitHub compile/lint and integrity checks passed on the implementation commit; CI reruns for the test-fixture follow-up.

Staging deployment has not yet validated this change against OrgStore. No OrgStore connection defaults are changed by this PR.

Control-plane adoption: https://github.com/ddoghq/temper-cloud/pull/9

RetriggerConfidence Score: 4/5

The PR should not merge until migrating a second schema reliably creates its published_artifacts tenant policy.

Fix All in Claude CodeFindings

  1. P1 Second schema misses tenant policy ▶
  2. P2 Default queries allocate SQL strings ▶
Fix with agent prompt
### Issue 1
crates/temper-store-postgres/src/migration.rs:60-66
When `published_artifacts` already has a `tenant_isolation` policy in another schema, this migration sees that policy through a check that does not filter by schema. It then skips creating the policy in the selected schema, even though row-level security is enabled there. Reads and writes through an RLS-enforcing role are denied.

### Issue 2
crates/temper-store-postgres/src/namespace.rs:45-49
`qualify_sql` calls `String::replace` for every template containing `{schema}`, even when `PostgresEventStore::new` has no schema prefix. Routine queries, including event appends and segment updates, therefore allocate a SQL string that the previous default path did not need. This adds avoidable work to the persistence hot path.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Summary

The PR adds an explicitly selected PostgreSQL schema to the event store, qualifies its runtime SQL, preserves the selected store in server metadata paths, and introduces schema-scoped migrations and integration tests.

  • Schema-scoped migrations need a schema-aware policy check before multiple schemas can be migrated safely in one database.
  • Default-schema queries now incur an avoidable SQL-string allocation.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Pool[Shared PostgreSQL pool] --> Store[PostgresEventStore]
  Store -->|explicit schema| SQL[Qualified runtime queries]
  Store --> Metadata[Server metadata helpers]
  Schema[PostgresSchema] --> Store
  Schema --> Migration[Schema-scoped migration transaction]
  Migration --> Tables[Tables and migration history]
  SQL --> Tables
  Metadata --> Tables
Loading

Reviews (1) · Last reviewed commit: "test(postgres): configure artifact stora..."

arun-pathiban-ddog and others added 2 commits October 1, 2026 19:19
Qualify runtime table access, retain schema selection through server metadata and registry restoration, and scope SQLx migrations to a transaction without changing default search-path behavior.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
Provide a temporary data directory for the standard server test.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
@arun-pathiban-ddog
arun-pathiban-ddog marked this pull request as ready for review October 1, 2026 23:34
Comment on lines +60 to +66
sqlx::query(&format!(
"SET LOCAL search_path TO {identifier}, pg_catalog"
))
.execute(&mut *tx)
.await
.map_err(migration_error)?;
migrator.run(&mut *tx).await.map_err(migration_error)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Second schema misses tenant policy When published_artifacts already has a tenant_isolation policy in another schema, this migration sees that policy through a check that does not filter by schema. It then skips creating the policy in the selected schema, even though row-level security is enabled there. Reads and writes through an RLS-enforcing role are denied.

Knowledge Base Used: PostgreSQL platform store

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/temper-store-postgres/src/migration.rs
Line: 60-66

Comment:
**Second schema misses tenant policy** When `published_artifacts` already has a `tenant_isolation` policy in another schema, this migration sees that policy through a check that does not filter by schema. It then skips creating the policy in the selected schema, even though row-level security is enabled there. Reads and writes through an RLS-enforcing role are denied.

**Knowledge Base Used:** [PostgreSQL platform store](https://app.greptile.com/arni-labs/-/custom-context/knowledge-base/nerdsane/temper/-/docs/postgres-platform-store.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment on lines +45 to +49
if template.contains("{schema}") {
Cow::Owned(template.replace("{schema}", &self.prefix))
} else {
Cow::Borrowed(template)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Default queries allocate SQL strings qualify_sql calls String::replace for every template containing {schema}, even when PostgresEventStore::new has no schema prefix. Routine queries, including event appends and segment updates, therefore allocate a SQL string that the previous default path did not need. This adds avoidable work to the persistence hot path.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/temper-store-postgres/src/namespace.rs
Line: 45-49

Comment:
**Default queries allocate SQL strings** `qualify_sql` calls `String::replace` for every template containing `{schema}`, even when `PostgresEventStore::new` has no schema prefix. Routine queries, including event appends and segment updates, therefore allocate a SQL string that the previous default path did not need. This adds avoidable work to the persistence hot path.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex Fix in Cursor

@arun-pathiban-ddog
arun-pathiban-ddog merged commit f66287d into main Oct 2, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant