From 7be541467dcab2bb4299f568498ff117296aba76 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 30 Sep 2026 17:16:21 +0200 Subject: [PATCH] Schema check: a key is a unique index on its columns, primary or not The store's ON CONFLICT clauses and the foreign keys need a unique, non-partial index on exactly the key's columns; whether it is the primary key changes nothing a request does. Requiring the primary flag added a refusal no failing request motivated, so it is dropped. Co-Authored-By: Claude Opus 5.5 --- src/store/postgres.ts | 8 +++----- src/store/schema.ts | 14 ++++++-------- tests/store.test.ts | 12 ++++++------ 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/store/postgres.ts b/src/store/postgres.ts index 8051446..ab0765a 100644 --- a/src/store/postgres.ts +++ b/src/store/postgres.ts @@ -319,13 +319,12 @@ export class PostgresStore implements HeimdallStore { AND NOT a.attisdropped ) UNION ALL - SELECT 'missing' FROM unnest($4::text[], $5::text[], $6::boolean[]) AS required(table_name, columns, is_primary) + SELECT 'missing' FROM unnest($4::text[], $5::text[]) AS required(table_name, columns) WHERE NOT EXISTS ( SELECT 1 FROM pg_index i WHERE i.indrelid = to_regclass(required.table_name) AND i.indisunique AND i.indpred IS NULL - AND (i.indisprimary OR NOT required.is_primary) AND i.indnatts = cardinality(string_to_array(required.columns, ',')) AND ( SELECT string_agg(a.attname, ',' ORDER BY a.attname) @@ -334,8 +333,8 @@ export class PostgresStore implements HeimdallStore { ) = required.columns ) UNION ALL - SELECT 'privilege' FROM unnest($7::text[]) AS required(table_name) - CROSS JOIN unnest($8::text[]) AS wanted(privilege) + SELECT 'privilege' FROM unnest($6::text[]) AS required(table_name) + CROSS JOIN unnest($7::text[]) AS wanted(privilege) WHERE to_regclass(required.table_name) IS NOT NULL AND NOT has_table_privilege(to_regclass(required.table_name), wanted.privilege) `, @@ -345,7 +344,6 @@ export class PostgresStore implements HeimdallStore { REQUIRED_COLUMNS.map((required) => required.column), REQUIRED_KEYS.map((required) => required.table), REQUIRED_KEYS.map((required) => required.columns), - REQUIRED_KEYS.map((required) => required.primary), REQUIRED_TABLES, REQUIRED_TABLE_PRIVILEGES, ] diff --git a/src/store/schema.ts b/src/store/schema.ts index cb80eb0..de39316 100644 --- a/src/store/schema.ts +++ b/src/store/schema.ts @@ -207,16 +207,14 @@ export const REQUIRED_COLUMNS = [ /** * Every primary key and unique constraint a created table declares, by its - * sorted columns. Foreign keys reference the primary keys, and each of the - * store's ON CONFLICT clauses needs a unique index on exactly its columns: - * without one, every such write fails. + * sorted columns. Each must stand as a unique, non-partial index on exactly + * those columns: the store's ON CONFLICT clauses and the foreign keys need + * nothing more, and without it every such write fails. */ export const REQUIRED_KEYS = createdTables.flatMap(({ table, lines }) => lines.flatMap((line) => { - const inline = /^(\w+) .*\bPRIMARY KEY\b/.exec(line); - const columns = inline?.[1] ?? keyColumns(line, "PRIMARY KEY"); - if (columns) return [{ table, columns, primary: true }]; - const unique = keyColumns(line, "UNIQUE"); - return unique ? [{ table, columns: unique, primary: false }] : []; + const columns = + /^(\w+) .*\bPRIMARY KEY\b/.exec(line)?.[1] ?? keyColumns(line, "PRIMARY KEY") ?? keyColumns(line, "UNIQUE"); + return columns ? [{ table, columns }] : []; }) ); diff --git a/tests/store.test.ts b/tests/store.test.ts index 5f9f5a5..0729ba0 100644 --- a/tests/store.test.ts +++ b/tests/store.test.ts @@ -250,12 +250,12 @@ describe("PostgresStore.checkSchema", () => { // The keys the store's ON CONFLICT clauses and the foreign keys rely on. expect(REQUIRED_KEYS).toEqual( expect.arrayContaining([ - { table: "linked_identities", columns: "provider,provider_user_id", primary: false }, - { table: "sessions", columns: "id", primary: true }, - { table: "private_command_receipts", columns: "app_slug,idempotency_key", primary: true }, - { table: "entitlement_snapshots", columns: "account_id,provider,scope", primary: false }, - { table: "accounts", columns: "id", primary: true }, - { table: "auth_completions", columns: "code", primary: true }, + { table: "linked_identities", columns: "provider,provider_user_id" }, + { table: "sessions", columns: "id" }, + { table: "private_command_receipts", columns: "app_slug,idempotency_key" }, + { table: "entitlement_snapshots", columns: "account_id,provider,scope" }, + { table: "accounts", columns: "id" }, + { table: "auth_completions", columns: "code" }, ]) ); expect(REQUIRED_KEYS).toHaveLength(11);