11import {
2- AbstractDeclaration ,
32 AttributeArg ,
43 BooleanLiteral ,
54 ConfigArrayExpr ,
@@ -295,17 +294,17 @@ export class PrismaSchemaGenerator {
295294 decl . comments . forEach ( ( c ) => model . addComment ( c ) ) ;
296295 this . getCustomAttributesAsComments ( decl ) . forEach ( ( c ) => model . addComment ( c ) ) ;
297296
298- // generate relation fields on base models linking to concrete models
297+ // physical: generate relation fields on base models linking to concrete models
299298 this . generateDelegateRelationForBase ( model , decl ) ;
300299
301- // generate reverse relation fields on concrete models
300+ // physical: generate reverse relation fields on concrete models
302301 this . generateDelegateRelationForConcrete ( model , decl ) ;
303302
304- // expand relations on other models that reference delegated models to concrete models
303+ // logical: expand relations on other models that reference delegated models to concrete models
305304 this . expandPolymorphicRelations ( model , decl ) ;
306305
307- // name relations inherited from delegate base models for disambiguation
308- this . nameRelationsInheritedFromDelegate ( model , decl ) ;
306+ // logical: ensure relations inherited from delegate models
307+ this . ensureRelationsInheritedFromDelegate ( model , decl ) ;
309308 }
310309
311310 private generateDelegateRelationForBase ( model : PrismaDataModel , decl : DataModel ) {
@@ -403,7 +402,7 @@ export class PrismaSchemaGenerator {
403402
404403 // find concrete models that inherit from this field's model type
405404 const concreteModels = dataModel . $container . declarations . filter (
406- ( d ) => isDataModel ( d ) && isDescendantOf ( d , fieldType )
405+ ( d ) : d is DataModel => isDataModel ( d ) && isDescendantOf ( d , fieldType )
407406 ) ;
408407
409408 concreteModels . forEach ( ( concrete ) => {
@@ -418,10 +417,9 @@ export class PrismaSchemaGenerator {
418417 ) ;
419418
420419 const relAttr = getAttribute ( field , '@relation' ) ;
420+ let relAttrAdded = false ;
421421 if ( relAttr ) {
422- const fieldsArg = getAttributeArg ( relAttr , 'fields' ) ;
423- const nameArg = getAttributeArg ( relAttr , 'name' ) as LiteralExpr ;
424- if ( fieldsArg ) {
422+ if ( getAttributeArg ( relAttr , 'fields' ) ) {
425423 // for reach foreign key field pointing to the delegate model, we need to create an aux foreign key
426424 // to point to the concrete model
427425 const relationFieldPairs = getRelationKeyPairs ( field ) ;
@@ -450,10 +448,7 @@ export class PrismaSchemaGenerator {
450448
451449 const addedRel = new PrismaFieldAttribute ( '@relation' , [
452450 // use field name as relation name for disambiguation
453- new PrismaAttributeArg (
454- undefined ,
455- new AttributeArgValue ( 'String' , nameArg ?. value || auxRelationField . name )
456- ) ,
451+ new PrismaAttributeArg ( undefined , new AttributeArgValue ( 'String' , auxRelationField . name ) ) ,
457452 new PrismaAttributeArg ( 'fields' , fieldsArg ) ,
458453 new PrismaAttributeArg ( 'references' , referencesArg ) ,
459454 ] ) ;
@@ -467,12 +462,12 @@ export class PrismaSchemaGenerator {
467462 )
468463 ) ;
469464 }
470-
471465 auxRelationField . attributes . push ( addedRel ) ;
472- } else {
473- auxRelationField . attributes . push ( this . makeFieldAttribute ( relAttr as DataModelFieldAttribute ) ) ;
466+ relAttrAdded = true ;
474467 }
475- } else {
468+ }
469+
470+ if ( ! relAttrAdded ) {
476471 auxRelationField . attributes . push (
477472 new PrismaFieldAttribute ( '@relation' , [
478473 // use field name as relation name for disambiguation
@@ -486,8 +481,8 @@ export class PrismaSchemaGenerator {
486481
487482 private replicateForeignKey (
488483 model : PrismaDataModel ,
489- dataModel : DataModel ,
490- concreteModel : AbstractDeclaration ,
484+ delegateModel : DataModel ,
485+ concreteModel : DataModel ,
491486 origForeignKey : DataModelField
492487 ) {
493488 // aux fk name format: delegate_aux_[model]_[fkField]_[concrete]
@@ -499,26 +494,20 @@ export class PrismaSchemaGenerator {
499494 // `@map` attribute should not be inherited
500495 addedFkField . attributes = addedFkField . attributes . filter ( ( attr ) => ! ( 'name' in attr && attr . name === '@map' ) ) ;
501496
497+ // `@unique` attribute should be recreated with disambiguated name
498+ addedFkField . attributes = addedFkField . attributes . filter (
499+ ( attr ) => ! ( 'name' in attr && attr . name === '@unique' )
500+ ) ;
501+ const uniqueAttr = addedFkField . addAttribute ( '@unique' ) ;
502+ const constraintName = this . truncate ( `${ delegateModel . name } _${ addedFkField . name } _${ concreteModel . name } _unique` ) ;
503+ uniqueAttr . args . push ( new PrismaAttributeArg ( 'map' , new AttributeArgValue ( 'String' , constraintName ) ) ) ;
504+
502505 // fix its name
503- const addedFkFieldName = `${ dataModel . name } _${ origForeignKey . name } _${ concreteModel . name } ` ;
506+ const addedFkFieldName = `${ delegateModel . name } _${ origForeignKey . name } _${ concreteModel . name } ` ;
504507 addedFkField . name = this . truncate ( `${ DELEGATE_AUX_RELATION_PREFIX } _${ addedFkFieldName } ` ) ;
505508
506- // we also need to make sure `@unique` constraint's `map` parameter is fixed to avoid conflict
507- const uniqueAttr = addedFkField . attributes . find (
508- ( attr ) => ( attr as PrismaFieldAttribute ) . name === '@unique'
509- ) as PrismaFieldAttribute ;
510- if ( uniqueAttr ) {
511- const mapArg = uniqueAttr . args . find ( ( arg ) => arg . name === 'map' ) ;
512- const constraintName = this . truncate ( `${ addedFkField . name } _unique` ) ;
513- if ( mapArg ) {
514- mapArg . value = new AttributeArgValue ( 'String' , constraintName ) ;
515- } else {
516- uniqueAttr . args . push ( new PrismaAttributeArg ( 'map' , new AttributeArgValue ( 'String' , constraintName ) ) ) ;
517- }
518- }
519-
520509 // we also need to go through model-level `@@unique` and replicate those involving fk fields
521- this . replicateForeignKeyModelLevelUnique ( model , dataModel , origForeignKey , addedFkField ) ;
510+ this . replicateForeignKeyModelLevelUnique ( model , delegateModel , origForeignKey , addedFkField ) ;
522511
523512 return addedFkField ;
524513 }
@@ -596,13 +585,11 @@ export class PrismaSchemaGenerator {
596585 return shortName ;
597586 }
598587
599- private nameRelationsInheritedFromDelegate ( model : PrismaDataModel , decl : DataModel ) {
588+ private ensureRelationsInheritedFromDelegate ( model : PrismaDataModel , decl : DataModel ) {
600589 if ( this . mode !== 'logical' ) {
601590 return ;
602591 }
603592
604- // the logical schema needs to name relations inherited from delegate base models for disambiguation
605-
606593 decl . fields . forEach ( ( f ) => {
607594 if ( ! isDataModel ( f . type . reference ?. ref ) ) {
608595 // only process relation fields
@@ -636,30 +623,68 @@ export class PrismaSchemaGenerator {
636623 if ( ! oppositeRelationField ) {
637624 return ;
638625 }
626+ const oppositeRelationAttr = getAttribute ( oppositeRelationField , '@relation' ) ;
639627
640628 const fieldType = f . type . reference . ref ;
641629
642630 // relation name format: delegate_aux_[relationType]_[oppositeRelationField]_[concrete]
643- const relAttr = getAttribute ( f , '@relation' ) ;
644- const name = `${ fieldType . name } _${ oppositeRelationField . name } _${ decl . name } ` ;
645- const relName = this . truncate ( `${ DELEGATE_AUX_RELATION_PREFIX } _${ name } ` ) ;
646-
647- if ( relAttr ) {
648- const nameArg = getAttributeArg ( relAttr , 'name' ) ;
649- if ( ! nameArg ) {
650- const prismaRelAttr = prismaField . attributes . find (
651- ( attr ) => ( attr as PrismaFieldAttribute ) . name === '@relation'
652- ) as PrismaFieldAttribute ;
653- if ( prismaRelAttr ) {
654- prismaRelAttr . args . unshift (
655- new PrismaAttributeArg ( undefined , new AttributeArgValue ( 'String' , relName ) )
656- ) ;
657- }
658- }
631+ const relName = this . truncate (
632+ `${ DELEGATE_AUX_RELATION_PREFIX } _${ fieldType . name } _${ oppositeRelationField . name } _${ decl . name } `
633+ ) ;
634+
635+ // recreate `@relation` attribute
636+ prismaField . attributes = prismaField . attributes . filter (
637+ ( attr ) => ( attr as PrismaFieldAttribute ) . name !== '@relation'
638+ ) ;
639+
640+ if (
641+ // array relation doesn't need FK
642+ f . type . array ||
643+ // opposite relation already has FK, we don't need to generate on this side
644+ ( oppositeRelationAttr && getAttributeArg ( oppositeRelationAttr , 'fields' ) )
645+ ) {
646+ prismaField . attributes . push (
647+ new PrismaFieldAttribute ( '@relation' , [
648+ new PrismaAttributeArg ( undefined , new AttributeArgValue ( 'String' , relName ) ) ,
649+ ] )
650+ ) ;
659651 } else {
652+ // generate FK field
653+ const oppositeModelIds = getIdFields ( oppositeRelationField . $container as DataModel ) ;
654+ const fkFieldNames : string [ ] = [ ] ;
655+
656+ oppositeModelIds . forEach ( ( idField ) => {
657+ const fkFieldName = this . truncate ( `${ DELEGATE_AUX_RELATION_PREFIX } _${ f . name } _${ idField . name } ` ) ;
658+ model . addField ( fkFieldName , new ModelFieldType ( idField . type . type ! , false , f . type . optional ) , [
659+ // one-to-one relation requires FK field to be unique, we're just including it
660+ // in all cases since it doesn't hurt
661+ new PrismaFieldAttribute ( '@unique' ) ,
662+ ] ) ;
663+ fkFieldNames . push ( fkFieldName ) ;
664+ } ) ;
665+
660666 prismaField . attributes . push (
661667 new PrismaFieldAttribute ( '@relation' , [
662668 new PrismaAttributeArg ( undefined , new AttributeArgValue ( 'String' , relName ) ) ,
669+ new PrismaAttributeArg (
670+ 'fields' ,
671+ new AttributeArgValue (
672+ 'Array' ,
673+ fkFieldNames . map (
674+ ( fk ) => new AttributeArgValue ( 'FieldReference' , new PrismaFieldReference ( fk ) )
675+ )
676+ )
677+ ) ,
678+ new PrismaAttributeArg (
679+ 'references' ,
680+ new AttributeArgValue (
681+ 'Array' ,
682+ oppositeModelIds . map (
683+ ( idField ) =>
684+ new AttributeArgValue ( 'FieldReference' , new PrismaFieldReference ( idField . name ) )
685+ )
686+ )
687+ ) ,
663688 ] )
664689 ) ;
665690 }
@@ -690,9 +715,24 @@ export class PrismaSchemaGenerator {
690715
691716 private getOppositeRelationField ( oppositeModel : DataModel , relationField : DataModelField ) {
692717 const relName = this . getRelationName ( relationField ) ;
693- return oppositeModel . fields . find (
718+ const matches = oppositeModel . fields . filter (
694719 ( f ) => f . type . reference ?. ref === relationField . $container && this . getRelationName ( f ) === relName
695720 ) ;
721+
722+ if ( matches . length === 0 ) {
723+ return undefined ;
724+ } else if ( matches . length === 1 ) {
725+ return matches [ 0 ] ;
726+ } else {
727+ // if there are multiple matches, prefer to use the one with the same field name,
728+ // this can happen with self-relations
729+ const withNameMatch = matches . find ( ( f ) => f . name === relationField . name ) ;
730+ if ( withNameMatch ) {
731+ return withNameMatch ;
732+ } else {
733+ return matches [ 0 ] ;
734+ }
735+ }
696736 }
697737
698738 private getRelationName ( field : DataModelField ) {
0 commit comments