Found while working #11201 (which schema-scoped this query's WHERE, not its JOINs). Not fixed there: the bounded in-place-fix exemption did not apply — these are join-correlation defects, a different class from the unscoped-read defect that card owns, and each needs its own fixture. #11201 is a separate subject and is not addressed by this issue.
Both facts below were measured on a live PostgreSQL 16.13, against the query as it stands after#11201's fix — so neither is caused by, nor repaired by, that change.
What
packages/drivers/driver-sql/src/sql-driver.ts, SqlDriver.introspectForeignKeys, Postgres arm. The query joins:
FROMinformation_schema.table_constraintsAS tc
JOINinformation_schema.key_column_usageAS kcu
ONtc.constraint_name=kcu.constraint_nameANDtc.table_schema=kcu.table_schemaJOINinformation_schema.constraint_column_usageAS ccu
ONccu.constraint_name=tc.constraint_nameANDccu.table_schema=tc.table_schema
For a FOREIGN KEY constraint, constraint_column_usage describes the referenced (parent) side — that is exactly why the projection aliases it ccu.table_name AS referenced_table. Its table_schema is therefore the parent's schema, not the constraint's. Two consequences:
1. A foreign key whose target lives in another schema is dropped entirely
ccu.table_schema = tc.table_schema demands parent and child sit in the same schema. When they do not, the constraint contributes zero rows and disappears from the answer.
Measured, search_path = os11201_probe:
createtableos11201_probe_far.remote_parent (id varchar(64) primary key);
createtablecross_child (
id varchar(64) primary key, p varchar(64),
constraint fk_cross foreign key (p) referencesos11201_probe_far.remote_parent(id));
→ the arm's query returns 0 rows for cross_child. The table has a declared foreign key and introspectForeignKeys reports it has none.
This is the #7332 failure mode arriving through a different door: [] does not read downstream as "I could not see it", it reads as this table has no foreign keys, and federated-object codegen, the persisted external_catalog (ADR-0015) and schema-drift comparison all act on that. Cross-schema references are the normal shape for the federated remotes ADR-0015 points this driver at.
The sibling arm already spells the correct correlation: introspectUniqueConstraints joins ccu on tc.constraint_schema = ccu.constraint_schema — the constraint's schema, which is invariant to where the parent lives.
2. A composite foreign key comes back as the cartesian product of its columns
The kcu ↔ ccu join carries no ordinal_position correlation, so an N-column foreign key yields N x N rows pairing every child column with every parent column.
Measured:
createtablecomp_parent (a varchar(64), b varchar(64), primary key (a, b));
createtablecomp_child (
id varchar(64) primary key, x varchar(64), y varchar(64),
constraint fk_comp foreign key (x, y) references comp_parent(a, b));
→ 4 rows returned for a 2-column key:
x -> comp_parent.a
x -> comp_parent.b
y -> comp_parent.a
y -> comp_parent.b
The correct answer is x -> a, y -> b. IntrospectedForeignKey is a flat per-column record, so the two phantom pairs are indistinguishable from real ones to every consumer.
This is the same class the primary-key arm already paid for twice: #11101 (row order unspecified because the key ordinal was neither selected nor ordered on) and #10997 (SQLite composite key truncated). The FK arm never got that pass.
Suggested shape
One rewrite likely closes both, and the in-tree precedent is introspectPrimaryKeys: drop information_schema for pg_constraint, whose conkey / confkey are parallel int2vectors, and join them by shared ordinality — the unnest(...) WITH ORDINALITY shape #11101/#11162 already established in this file. That yields the ordered, correctly-paired column list directly, carries the parent's schema without constraining it, and removes the information_schema privilege-view layer these correlations were fighting.
Alternatively, and more conservatively: correlate ccu on constraint_schema (as introspectUniqueConstraints does) and add the kcu.ordinal_position ↔ referenced-ordinal correlation. Note constraint_column_usage exposes no ordinal, so the composite half probably cannot be fixed inside information_schema at all — which is the argument for the pg_constraint rewrite.
Scope note
Whether IntrospectedForeignKey needs a shape change is not settled here and should not be assumed: the flat per-column record can express a composite key correctly as ordered sibling rows, but nothing currently pins their order. That interface is being edited by #11270, so a fix touching its declaration needs to be sequenced against that PR rather than racing it.
Not measured here
Whether any in-tree consumer currently hits either shape. Both were reproduced directly against the driver's own query on a live server; neither depends on a consumer to be wrong.
Found while working #11201 (which schema-scoped this query's
WHERE, not itsJOINs). Not fixed there: the bounded in-place-fix exemption did not apply — these are join-correlation defects, a different class from the unscoped-read defect that card owns, and each needs its own fixture. #11201 is a separate subject and is not addressed by this issue.Both facts below were measured on a live PostgreSQL 16.13, against the query as it stands after#11201's fix — so neither is caused by, nor repaired by, that change.
What
packages/drivers/driver-sql/src/sql-driver.ts,SqlDriver.introspectForeignKeys, Postgres arm. The query joins:For a FOREIGN KEY constraint,
constraint_column_usagedescribes the referenced (parent) side — that is exactly why the projection aliases itccu.table_name AS referenced_table. Itstable_schemais therefore the parent's schema, not the constraint's. Two consequences:1. A foreign key whose target lives in another schema is dropped entirely
ccu.table_schema = tc.table_schemademands parent and child sit in the same schema. When they do not, the constraint contributes zero rows and disappears from the answer.Measured,
search_path = os11201_probe:→ the arm's query returns 0 rows for
cross_child. The table has a declared foreign key andintrospectForeignKeysreports it has none.This is the #7332 failure mode arriving through a different door:
[]does not read downstream as "I could not see it", it reads as this table has no foreign keys, and federated-object codegen, the persistedexternal_catalog(ADR-0015) and schema-drift comparison all act on that. Cross-schema references are the normal shape for the federated remotes ADR-0015 points this driver at.The sibling arm already spells the correct correlation:
introspectUniqueConstraintsjoinsccuontc.constraint_schema = ccu.constraint_schema— the constraint's schema, which is invariant to where the parent lives.2. A composite foreign key comes back as the cartesian product of its columns
The
kcu↔ccujoin carries noordinal_positioncorrelation, so an N-column foreign key yields N x N rows pairing every child column with every parent column.Measured:
→ 4 rows returned for a 2-column key:
The correct answer is
x -> a,y -> b.IntrospectedForeignKeyis a flat per-column record, so the two phantom pairs are indistinguishable from real ones to every consumer.This is the same class the primary-key arm already paid for twice: #11101 (row order unspecified because the key ordinal was neither selected nor ordered on) and #10997 (SQLite composite key truncated). The FK arm never got that pass.
Suggested shape
One rewrite likely closes both, and the in-tree precedent is
introspectPrimaryKeys: dropinformation_schemaforpg_constraint, whoseconkey/confkeyare parallelint2vectors, and join them by shared ordinality — theunnest(...) WITH ORDINALITYshape #11101/#11162 already established in this file. That yields the ordered, correctly-paired column list directly, carries the parent's schema without constraining it, and removes theinformation_schemaprivilege-view layer these correlations were fighting.Alternatively, and more conservatively: correlate
ccuonconstraint_schema(asintrospectUniqueConstraintsdoes) and add thekcu.ordinal_position↔ referenced-ordinal correlation. Noteconstraint_column_usageexposes no ordinal, so the composite half probably cannot be fixed insideinformation_schemaat all — which is the argument for thepg_constraintrewrite.Scope note
Whether
IntrospectedForeignKeyneeds a shape change is not settled here and should not be assumed: the flat per-column record can express a composite key correctly as ordered sibling rows, but nothing currently pins their order. That interface is being edited by #11270, so a fix touching its declaration needs to be sequenced against that PR rather than racing it.Not measured here
Whether any in-tree consumer currently hits either shape. Both were reproduced directly against the driver's own query on a live server; neither depends on a consumer to be wrong.