Skip to content

Commit 0e52444

Browse files
alban bertoliniclaude
andcommitted
fix(datasource-sql): ensure dedup keeps correct schema FK target + add composite+multi-schema test
- Sort filtered references by referencedTableSchema match before dedup to guarantee the correct schema's FK target is always preserved - Add assertion verifying deduplication warning is logged - Add test combining real composite FK with multi-schema collision - Make test comment self-contained (remove fragile "described above" ref) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ea02c0a commit 0e52444

2 files changed

Lines changed: 62 additions & 4 deletions

File tree

packages/datasource-sql/src/introspection/introspector.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,17 @@ export default class Introspector {
240240

241241
// Deduplicate: the filtered step above matches on the source table's schema (tableSchema),
242242
// but the Sequelize join bug produces rows that differ in referencedTableSchema (the FK
243-
// target's schema). Keep one per (constraintName, columnName) to collapse these duplicates.
243+
// target's schema). Sort so rows matching the source schema come first, then keep one per
244+
// (constraintName, columnName) to ensure we always preserve the correct FK target.
245+
const sorted = [...filtered].sort((a, b) => {
246+
const aMatch = a.referencedTableSchema === tableIdentifier.schema ? 0 : 1;
247+
const bMatch = b.referencedTableSchema === tableIdentifier.schema ? 0 : 1;
248+
249+
return aMatch - bMatch;
250+
});
251+
244252
const seen = new Set<string>();
245-
const deduplicated = filtered.filter(ref => {
253+
const deduplicated = sorted.filter(ref => {
246254
if (!ref.constraintName) return true;
247255

248256
const key = `${ref.constraintName}::${ref.columnName}`;

packages/datasource-sql/test/introspection/introspector.integration.test.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,8 +480,8 @@ ALTER TABLE "TableA" ADD CONSTRAINT "A_fkey" FOREIGN KEY ("type") REFERENCES "Ta
480480
});
481481

482482
it('should not misdetect composite FK when another schema has same constraint names', async () => {
483-
// Set up the exact conditions described above: two schemas with matching table names,
484-
// FK column names, and therefore identical auto-generated constraint names.
483+
// Two schemas with matching table names and FK column names cause PostgreSQL to generate
484+
// identical auto-constraint names, triggering the Sequelize cross-schema join bug.
485485
await sequelize.query(`
486486
CREATE TABLE schema1.main_table (
487487
id SERIAL PRIMARY KEY,
@@ -550,6 +550,56 @@ ALTER TABLE "TableA" ADD CONSTRAINT "A_fkey" FOREIGN KEY ("type") REFERENCES "Ta
550550
'Warn',
551551
expect.stringContaining('Composite relations are not supported'),
552552
);
553+
554+
// Should log that cross-schema duplicates were deduplicated
555+
expect(logger).toHaveBeenCalledWith('Warn', expect.stringContaining('Deduplicated'));
556+
});
557+
558+
it('should still detect real composite FKs when another schema has same constraint names', async () => {
559+
await sequelize.query(`
560+
CREATE TABLE schema1.target (
561+
type TEXT NOT NULL,
562+
code TEXT NOT NULL,
563+
PRIMARY KEY (type, code)
564+
);
565+
566+
CREATE TABLE schema1.source (
567+
id SERIAL PRIMARY KEY,
568+
fk_type TEXT NOT NULL,
569+
fk_code TEXT NOT NULL,
570+
CONSTRAINT source_composite_fkey FOREIGN KEY (fk_type, fk_code)
571+
REFERENCES schema1.target(type, code)
572+
);
573+
574+
-- Schema2: identical structure produces identical auto-constraint names
575+
CREATE TABLE schema2.target (
576+
type TEXT NOT NULL,
577+
code TEXT NOT NULL,
578+
PRIMARY KEY (type, code)
579+
);
580+
581+
CREATE TABLE schema2.source (
582+
id SERIAL PRIMARY KEY,
583+
fk_type TEXT NOT NULL,
584+
fk_code TEXT NOT NULL,
585+
CONSTRAINT source_composite_fkey FOREIGN KEY (fk_type, fk_code)
586+
REFERENCES schema2.target(type, code)
587+
);
588+
`);
589+
590+
const logger = jest.fn();
591+
const { tables } = await Introspector.introspect(sequelizeSchema1, logger);
592+
593+
const sourceTable = tables.find(t => t.name === 'source');
594+
595+
// Composite FKs should be filtered out (not supported by Sequelize)
596+
expect(sourceTable?.columns.find(c => c.name === 'fk_type')?.constraints).toEqual([]);
597+
expect(sourceTable?.columns.find(c => c.name === 'fk_code')?.constraints).toEqual([]);
598+
599+
expect(logger).toHaveBeenCalledWith(
600+
'Warn',
601+
expect.stringContaining('Composite relations are not supported'),
602+
);
553603
});
554604
});
555605
});

0 commit comments

Comments
 (0)