Skip to content

fix(pg_introspect): read FKs from pg_constraint so read-only roles get 'references' edges (#1746)#1761

Open
rithyKabir wants to merge 1 commit into
Graphify-Labs:v8from
rithyKabir:fix/1746-pg-fk-references-edges
Open

fix(pg_introspect): read FKs from pg_constraint so read-only roles get 'references' edges (#1746)#1761
rithyKabir wants to merge 1 commit into
Graphify-Labs:v8from
rithyKabir:fix/1746-pg-fk-references-edges

Conversation

@rithyKabir

Copy link
Copy Markdown
Contributor

Fixes #1746.

Root cause

introspect_postgres reads foreign keys from information_schema.referential_constraints — but per the PostgreSQL docs:

Only those constraints are shown for which the current user has write access to the referencing table (by way of being the owner or having some privilege other than SELECT).

A read-only introspection role — exactly what hosted providers like Supabase hand out, and what pg_introspect's own READ ONLY transaction encourages — therefore gets zero FK rows, while tables/views/routines all still appear (SELECT is enough for those views). The graph then builds "successfully" with contains/reads_from edges but not a single references edge, matching the reported counts (459 contains + 183 reads_from, 0 references). This is also why file-based extraction of the same schema's migrations does produce references edges — the gap is specific to the live-introspection path.

Reproduced live on PostgreSQL 16 with a SELECT-only role: referential_constraints → 0 rows; pg_catalog.pg_constraint (contype='f') → all 3.

Fix

Query pg_catalog.pg_constraint instead — system catalogs are not privilege-filtered. The query returns the same 7-column shape, so DDL generation is untouched.

This also fixes a latent second bug: PostgreSQL constraint names are only unique per table, and the old name-based key_column_usage joins cross-matched same-named constraints on sibling tables. Verified live: two tables each with a constraint named fk_owner produced columns = {user_id,user_id,user_id,user_id} (malformed FK DDL) even for a privileged user. pg_constraint keys by oid, and unnest(conkey) WITH ORDINALITY preserves composite-FK column order.

Verification

  • Live PostgreSQL 16 (Docker), schema with a single-column FK, a composite FK, and same-named fk_owner constraints on two sibling tables, introspected as a SELECT-only role: all 3 references edges emitted end-to-end, composite FK produces exactly one edge, both fk_owner FKs attributed to the correct tables.
  • New regression test asserts the FK query no longer touches referential_constraints and still yields the references edge; the test mock now records executed queries.
  • tests/test_pg_introspect.py 7/7, plus test_extract.py + test_multilang.py (172 total) green; ruff clean.

🤖 Generated with Claude Code

…t 'references' edges (Graphify-Labs#1746)

information_schema.referential_constraints only shows constraints where
the current user has WRITE access to the referencing table (owner or a
privilege other than SELECT — per the PostgreSQL docs). A read-only
introspection role therefore gets zero FK rows, while tables, views and
routines all still appear (SELECT is enough for those views) — so
--postgres built a graph with contains/reads_from edges but not a single
'references' edge, exactly as reported.

Query pg_catalog.pg_constraint (contype = 'f') instead, which is not
privilege-filtered. This also fixes a latent second bug: constraint
names in PostgreSQL are only unique per table, so the old name-based
key_column_usage joins cross-matched same-named constraints on sibling
tables (verified live: two tables sharing a 'fk_owner' constraint
produced columns = {user_id,user_id,user_id,user_id}). pg_constraint
keys by oid, and conkey/confkey unnest WITH ORDINALITY preserves
composite-FK column order.

Verified against a live PostgreSQL 16 with a SELECT-only role:
referential_constraints returned 0 FK rows, the new query returned all
3 (single-column, composite, and same-named sibling constraints), and
introspect_postgres emitted all 3 references edges end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

--postgres introspection emits no FK 'references' edges (only contains/reads_from)

1 participant