Skip to content

fix: qualify and order the foreign key lookup in rex_sql_table - #6623

Merged
gharlan merged 2 commits into
redaxo:5.xfrom
tyrant88:tyrant88-patch-254
Aug 5, 2026
Merged

fix: qualify and order the foreign key lookup in rex_sql_table#6623
gharlan merged 2 commits into
redaxo:5.xfrom
tyrant88:tyrant88-patch-254

Conversation

@tyrant88

@tyrant88 tyrant88 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

The INFORMATION_SCHEMA lookup joined REFERENTIAL_CONSTRAINTS to KEY_COLUMN_USAGE on the constraint name alone. Constraint names are only unique per schema, so on a server hosting several databases the join also picks up same-named constraints from other schemas. On a local setup with 39 REDAXO databases, reading the two foreign keys of rex_user_session returned 78 rows instead of 2.

The duplicates usually collapse again, because the columns are collected into a map keyed by column name. They stop being harmless as soon as a same-named constraint in another database covers different columns: the foreign key then carries columns that do not belong to it, and consumers comparing against a stored schema see a difference that is not there.

The query also had no ORDER BY. INFORMATION_SCHEMA gives no ordering guarantee, so both the order of the foreign keys and the column order within a composite key were left to chance. The latter matters, since that order ends up in the generated DDL.

Qualify the join by schema and table, and order by constraint name and ordinal position.

The INFORMATION_SCHEMA lookup joined REFERENTIAL_CONSTRAINTS to
KEY_COLUMN_USAGE on the constraint name alone. Constraint names are only
unique per schema, so on a server hosting several databases the join also
picks up same-named constraints from other schemas. On a local setup with
39 REDAXO databases, reading the two foreign keys of rex_user_session
returned 78 rows instead of 2.

The duplicates usually collapse again, because the columns are collected
into a map keyed by column name. They stop being harmless as soon as a
same-named constraint in another database covers different columns: the
foreign key then carries columns that do not belong to it, and consumers
comparing against a stored schema see a difference that is not there.

The query also had no ORDER BY. INFORMATION_SCHEMA gives no ordering
guarantee, so both the order of the foreign keys and the column order
within a composite key were left to chance. The latter matters, since
that order ends up in the generated DDL.

Qualify the join by schema and table, and order by constraint name and
ordinal position.
@rex-bot rex-bot added the bug label Aug 5, 2026
@gharlan gharlan added this to the 5.21.5 milestone Aug 5, 2026

@gharlan gharlan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note: This review was written by Claude (Claude Code), run by @gharlan. The findings were verified against a local MariaDB 12.3; @gharlan has not reviewed the wording line by line, so treat the reasoning as something to check rather than as a maintainer verdict.

The analysis holds up: joining REFERENTIAL_CONSTRAINTS to KEY_COLUMN_USAGE on the constraint name alone is genuinely broken, because KEY_COLUMN_USAGE spans all schemas while FK names are unique only per schema. And c.TABLE_NAME = k.TABLE_NAME is less redundant than it looks — index names are unique only per table, so a same-named UNIQUE index can sit on a different table of the same schema; that condition catches it.

The ORDER BY k.ORDINAL_POSITION part is arguably the more valuable half of the fix, since that order ends up in the generated DDL.

Two inline notes below, plus one optional test suggestion:

Test coverage (optional): testAddForeignKey verifies the round-trip with assertEquals($fk, ...), and == on arrays is order-insensitive — so the test cannot observe the column order this PR is fixing. One line would pin the intent down:

self::assertSame(
    ['config_namespace' => 'namespace', 'config_key' => 'key'],
    $table->getForeignKey('test1_fk_config')?->getColumns(),
);

Covering the cross-schema case properly would need a second database, which seems out of scope here.

Comment thread redaxo/src/core/lib/sql/table.php
Comment thread redaxo/src/core/lib/sql/table.php Outdated
KEY_COLUMN_USAGE lists primary, unique and foreign key constraints
alike, and a unique index may share its name with a foreign key on the
same table. Both statements are accepted:

    CREATE TABLE child (x varchar(20), y varchar(20), UNIQUE KEY myname (x,y));
    ALTER TABLE child ADD CONSTRAINT myname FOREIGN KEY (x,y) REFERENCES parent (a,b);

The unique index rows then come back alongside the foreign key rows.
They carry REFERENCED_COLUMN_NAME = NULL, which contradicts the string
type in the annotation above, and they share the ordinal position of the
real rows, so the ORDER BY does not decide which of them wins the column
assignment. Restricting the join to rows that belong to a referential
constraint drops them.

Also pin the column order in testAddForeignKey: assertEquals compares
arrays order-insensitively and could not observe it.

Verified on MySQL 8.4 and, in review, on MariaDB 12.3.

@gharlan gharlan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Claude sagt mir:

Alle drei Punkte übernommen, und zwar sauber. Ich hab's lokal durchgeprüft.

@tyrant88

tyrant88 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

meiner hatte alles auf mysql 8.4 getestet.

@tyrant88

tyrant88 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Viel zu viel KI involved, ist mir ein bisschen ungewohnt

@tyrant88

tyrant88 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

aber das thema nervte mich, hat mir claude rausgesucht über die Jahre:
Projekt │ Reine Umsortier-Commits

│ xxxos │ 19
│ xxxeurope │ 17
│ xxxxuehle │ 15
│ xxxx-filmfest│ 13
│ xxxrom │ 10

@gharlan

gharlan commented Aug 5, 2026

Copy link
Copy Markdown
Member

Bei uns kein einziges Mal gesehen, ggf MariaDB vs MySQL.

@gharlan
gharlan merged commit d3850fe into redaxo:5.x Aug 5, 2026
16 checks passed
@tyrant88

tyrant88 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

thx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

3 participants