@@ -441,4 +441,126 @@ ALTER TABLE "TableA" ADD CONSTRAINT "A_fkey" FOREIGN KEY ("type") REFERENCES "Ta
441441 ) ;
442442 } ) ;
443443 } ) ;
444+
445+ /**
446+ * Bug reproduction: FK relations disappear when another schema has same table names.
447+ * @see https://community.forestadmin.com/t/missing-related-data/8385
448+ *
449+ * Root cause: Sequelize's FK query joins constraint_column_usage on constraint_name
450+ * WITHOUT schema qualifier. When two schemas have identical table/column names,
451+ * PostgreSQL generates identical auto-constraint names in both schemas. The cross-schema
452+ * join produces extra rows, which the composite FK detection misinterprets as composite
453+ * foreign keys and filters out.
454+ */
455+ describe ( 'relations with same table names across schemas' , ( ) => {
456+ const db = 'database_introspector_multi_schema_fk' ;
457+
458+ describe . each ( POSTGRESQL_DETAILS ) ( 'on $name' , connectionDetails => {
459+ let sequelize : Sequelize ;
460+ let sequelizeSchema1 : Sequelize ;
461+
462+ beforeEach ( async ( ) => {
463+ sequelize = await setupEmptyDatabase ( connectionDetails , db ) ;
464+
465+ await sequelize
466+ . getQueryInterface ( )
467+ . dropSchema ( 'schema1' )
468+ . catch ( ( ) => { } ) ;
469+ await sequelize
470+ . getQueryInterface ( )
471+ . dropSchema ( 'schema2' )
472+ . catch ( ( ) => { } ) ;
473+
474+ await sequelize . getQueryInterface ( ) . createSchema ( 'schema1' ) ;
475+ await sequelize . getQueryInterface ( ) . createSchema ( 'schema2' ) ;
476+
477+ sequelizeSchema1 = new Sequelize ( connectionDetails . url ( db ) , {
478+ logging : false ,
479+ schema : 'schema1' ,
480+ } ) ;
481+ } ) ;
482+
483+ afterEach ( async ( ) => {
484+ await sequelizeSchema1 ?. close ( ) ;
485+ await sequelize ?. close ( ) ;
486+ } ) ;
487+
488+ it ( 'should not misdetect composite FK when another schema has same constraint names' , async ( ) => {
489+ // The Sequelize FK query joins information_schema.constraint_column_usage (ccu)
490+ // on constraint_name WITHOUT schema qualifier. When two schemas have tables with
491+ // the same name and FK columns with the same name, PostgreSQL generates identical
492+ // auto-constraint names (e.g. "xxx_model_code_fkey") in both schemas.
493+ // The cross-schema join produces extra rows that the code misdetects as composite FKs,
494+ // filtering them out and losing the relation.
495+ await sequelize . query ( `
496+ CREATE TABLE schema1.main_table (
497+ id SERIAL PRIMARY KEY,
498+ unique_code TEXT NOT NULL UNIQUE
499+ );
500+
501+ CREATE TABLE schema1.xxx_model (
502+ id SERIAL PRIMARY KEY,
503+ code TEXT NOT NULL REFERENCES schema1.main_table(unique_code)
504+ );
505+
506+ CREATE TABLE schema1.yyy_model (
507+ id SERIAL PRIMARY KEY,
508+ code TEXT NOT NULL REFERENCES schema1.main_table(unique_code)
509+ );
510+
511+ CREATE TABLE schema1.a_b_c (
512+ id SERIAL PRIMARY KEY,
513+ code TEXT NOT NULL REFERENCES schema1.main_table(unique_code)
514+ );
515+
516+ -- Schema2: same table names AND same FK column names → same auto-constraint names
517+ CREATE TABLE schema2.main_table (
518+ id SERIAL PRIMARY KEY,
519+ unique_code TEXT NOT NULL UNIQUE
520+ );
521+
522+ CREATE TABLE schema2.xxx_model (
523+ id SERIAL PRIMARY KEY,
524+ code TEXT NOT NULL REFERENCES schema2.main_table(unique_code)
525+ );
526+
527+ CREATE TABLE schema2.yyy_model (
528+ id SERIAL PRIMARY KEY,
529+ code TEXT NOT NULL REFERENCES schema2.main_table(unique_code)
530+ );
531+
532+ CREATE TABLE schema2.a_b_c (
533+ id SERIAL PRIMARY KEY,
534+ code TEXT NOT NULL REFERENCES schema2.main_table(unique_code)
535+ );
536+ ` ) ;
537+
538+ const logger = jest . fn ( ) ;
539+ const { tables } = await Introspector . introspect ( sequelizeSchema1 , logger ) ;
540+
541+ expect ( tables ) . toHaveLength ( 4 ) ;
542+
543+ const xxxModel = tables . find ( t => t . name === 'xxx_model' ) ;
544+ const yyyModel = tables . find ( t => t . name === 'yyy_model' ) ;
545+ const abcModel = tables . find ( t => t . name === 'a_b_c' ) ;
546+
547+ // All 3 models should preserve their FK constraint to main_table.unique_code
548+ expect ( xxxModel ?. columns . find ( c => c . name === 'code' ) ?. constraints ) . toEqual ( [
549+ { table : 'main_table' , column : 'unique_code' } ,
550+ ] ) ;
551+ expect ( yyyModel ?. columns . find ( c => c . name === 'code' ) ?. constraints ) . toEqual ( [
552+ { table : 'main_table' , column : 'unique_code' } ,
553+ ] ) ;
554+ expect ( abcModel ?. columns . find ( c => c . name === 'code' ) ?. constraints ) . toEqual ( [
555+ { table : 'main_table' , column : 'unique_code' } ,
556+ ] ) ;
557+
558+ // Should NOT log composite relation warnings for these single-column FKs
559+ expect ( logger ) . not . toHaveBeenCalledWith (
560+ 'Warn' ,
561+ expect . stringContaining ( 'Composite relations are not supported' ) ,
562+ ) ;
563+ } ) ;
564+ } ) ;
565+ } ) ;
444566} ) ;
0 commit comments