Found while implementing #11324 (the Postgres arm of the same method). Filed rather than fixed because it is a different dialect arm with a different mechanism, and — stated up front — it did not reproduce. #11324 is a separate subject and is not addressed by this issue.
What
packages/drivers/driver-sql/src/sql-driver.ts, SqlDriver.introspectForeignKeys, MySQL arm:
SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME, CONSTRAINT_NAME
FROMinformation_schema.KEY_COLUMN_USAGEWHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND REFERENCED_TABLE_NAME IS NOT NULL
There is no ORDER BY. KEY_COLUMN_USAGE.ORDINAL_POSITION is the key ordinal and is selected by neither the projection nor an order clause, so the row order of a composite foreign key's columns is whatever the plan yields.
This arm has no correlation defect — COLUMN_NAME and REFERENCED_COLUMN_NAME sit on one row, so child and parent columns are paired by construction and there is no cartesian product to produce. What is unpinned is only the order of those correctly-paired rows.
Why the order is load-bearing at all
#11324 establishes the contract that a composite foreign key is expressed as ordered sibling rows in a flat per-column record — (x, y) references p (a, b) is x -> p.a then y -> p.b, in that order, with no ordinal field to recover the position from. The Postgres arm now pins it with ORDER BY … k.ord. The MySQL arm relies on the server.
What was measured, and what it showed
Live MySQL 8.0.46, a key declared out of column sequence so key order and column order are different answers:
createtableooo_parent (pa varchar(64), pb varchar(64), primary key (pa, pb));
createtableooo_child (
id varchar(64) primary key, first_col varchar(64), second_col varchar(64),
constraint fk_ooo foreign key (second_col, first_col) references ooo_parent (pa, pb));
The arm's query, verbatim, returned key order — second_col -> pa then first_col -> pb. So on this server, this version and this shape, the answer was correct.
That is why this is filed as an observation rather than a bug: there is no wrong answer to point at.
Why it is still worth recording
The sibling introspectPrimaryKeys MySQL arm carries a measured comment saying the same view, queried the same way without ORDER BY, returned column order rather than ordinal order on an out-of-sequence key — which is what #11101 fixed there, and it explicitly notes that the InnoDB folklore about "tending to" return ordinal order does not hold on that shape. So the identical gap exists here, one arm over, and the only thing standing between it and a wrong answer is a plan choice nobody declared.
The fix, if triage wants it, is one line — ORDER BY ORDINAL_POSITION — matching what introspectPrimaryKeys already spells. The cost of leaving it is that the contract above is pinned on one dialect and left to the optimizer on another.
Not measured here
Whether any plan shape on any supported MySQL version actually returns this view out of ordinal order for a foreign key. Establishing that would need a fixture large enough to change the plan, which is beyond what an observation should spend.
Found while implementing #11324 (the Postgres arm of the same method). Filed rather than fixed because it is a different dialect arm with a different mechanism, and — stated up front — it did not reproduce. #11324 is a separate subject and is not addressed by this issue.
What
packages/drivers/driver-sql/src/sql-driver.ts,SqlDriver.introspectForeignKeys, MySQL arm:There is no
ORDER BY.KEY_COLUMN_USAGE.ORDINAL_POSITIONis the key ordinal and is selected by neither the projection nor an order clause, so the row order of a composite foreign key's columns is whatever the plan yields.This arm has no correlation defect —
COLUMN_NAMEandREFERENCED_COLUMN_NAMEsit on one row, so child and parent columns are paired by construction and there is no cartesian product to produce. What is unpinned is only the order of those correctly-paired rows.Why the order is load-bearing at all
#11324 establishes the contract that a composite foreign key is expressed as ordered sibling rows in a flat per-column record —
(x, y) references p (a, b)isx -> p.atheny -> p.b, in that order, with no ordinal field to recover the position from. The Postgres arm now pins it withORDER BY … k.ord. The MySQL arm relies on the server.What was measured, and what it showed
Live MySQL 8.0.46, a key declared out of column sequence so key order and column order are different answers:
The arm's query, verbatim, returned key order —
second_col -> pathenfirst_col -> pb. So on this server, this version and this shape, the answer was correct.That is why this is filed as an observation rather than a bug: there is no wrong answer to point at.
Why it is still worth recording
The sibling
introspectPrimaryKeysMySQL arm carries a measured comment saying the same view, queried the same way withoutORDER BY, returned column order rather than ordinal order on an out-of-sequence key — which is what #11101 fixed there, and it explicitly notes that the InnoDB folklore about "tending to" return ordinal order does not hold on that shape. So the identical gap exists here, one arm over, and the only thing standing between it and a wrong answer is a plan choice nobody declared.The fix, if triage wants it, is one line —
ORDER BY ORDINAL_POSITION— matching whatintrospectPrimaryKeysalready spells. The cost of leaving it is that the contract above is pinned on one dialect and left to the optimizer on another.Not measured here
Whether any plan shape on any supported MySQL version actually returns this view out of ordinal order for a foreign key. Establishing that would need a fixture large enough to change the plan, which is beyond what an observation should spend.