1
1
import {
2
- AbstractDeclaration ,
3
2
AttributeArg ,
4
3
BooleanLiteral ,
5
4
ConfigArrayExpr ,
@@ -295,17 +294,17 @@ export class PrismaSchemaGenerator {
295
294
decl . comments . forEach ( ( c ) => model . addComment ( c ) ) ;
296
295
this . getCustomAttributesAsComments ( decl ) . forEach ( ( c ) => model . addComment ( c ) ) ;
297
296
298
- // generate relation fields on base models linking to concrete models
297
+ // physical: generate relation fields on base models linking to concrete models
299
298
this . generateDelegateRelationForBase ( model , decl ) ;
300
299
301
- // generate reverse relation fields on concrete models
300
+ // physical: generate reverse relation fields on concrete models
302
301
this . generateDelegateRelationForConcrete ( model , decl ) ;
303
302
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
305
304
this . expandPolymorphicRelations ( model , decl ) ;
306
305
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 ) ;
309
308
}
310
309
311
310
private generateDelegateRelationForBase ( model : PrismaDataModel , decl : DataModel ) {
@@ -403,7 +402,7 @@ export class PrismaSchemaGenerator {
403
402
404
403
// find concrete models that inherit from this field's model type
405
404
const concreteModels = dataModel . $container . declarations . filter (
406
- ( d ) => isDataModel ( d ) && isDescendantOf ( d , fieldType )
405
+ ( d ) : d is DataModel => isDataModel ( d ) && isDescendantOf ( d , fieldType )
407
406
) ;
408
407
409
408
concreteModels . forEach ( ( concrete ) => {
@@ -418,10 +417,9 @@ export class PrismaSchemaGenerator {
418
417
) ;
419
418
420
419
const relAttr = getAttribute ( field , '@relation' ) ;
420
+ let relAttrAdded = false ;
421
421
if ( relAttr ) {
422
- const fieldsArg = getAttributeArg ( relAttr , 'fields' ) ;
423
- const nameArg = getAttributeArg ( relAttr , 'name' ) as LiteralExpr ;
424
- if ( fieldsArg ) {
422
+ if ( getAttributeArg ( relAttr , 'fields' ) ) {
425
423
// for reach foreign key field pointing to the delegate model, we need to create an aux foreign key
426
424
// to point to the concrete model
427
425
const relationFieldPairs = getRelationKeyPairs ( field ) ;
@@ -450,10 +448,7 @@ export class PrismaSchemaGenerator {
450
448
451
449
const addedRel = new PrismaFieldAttribute ( '@relation' , [
452
450
// 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 ) ) ,
457
452
new PrismaAttributeArg ( 'fields' , fieldsArg ) ,
458
453
new PrismaAttributeArg ( 'references' , referencesArg ) ,
459
454
] ) ;
@@ -467,12 +462,12 @@ export class PrismaSchemaGenerator {
467
462
)
468
463
) ;
469
464
}
470
-
471
465
auxRelationField . attributes . push ( addedRel ) ;
472
- } else {
473
- auxRelationField . attributes . push ( this . makeFieldAttribute ( relAttr as DataModelFieldAttribute ) ) ;
466
+ relAttrAdded = true ;
474
467
}
475
- } else {
468
+ }
469
+
470
+ if ( ! relAttrAdded ) {
476
471
auxRelationField . attributes . push (
477
472
new PrismaFieldAttribute ( '@relation' , [
478
473
// use field name as relation name for disambiguation
@@ -487,7 +482,7 @@ export class PrismaSchemaGenerator {
487
482
private replicateForeignKey (
488
483
model : PrismaDataModel ,
489
484
dataModel : DataModel ,
490
- concreteModel : AbstractDeclaration ,
485
+ concreteModel : DataModel ,
491
486
origForeignKey : DataModelField
492
487
) {
493
488
// aux fk name format: delegate_aux_[model]_[fkField]_[concrete]
@@ -499,24 +494,18 @@ export class PrismaSchemaGenerator {
499
494
// `@map` attribute should not be inherited
500
495
addedFkField . attributes = addedFkField . attributes . filter ( ( attr ) => ! ( 'name' in attr && attr . name === '@map' ) ) ;
501
496
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 ( `${ concreteModel . name } _${ addedFkField . name } _unique` ) ;
503
+ uniqueAttr . args . push ( new PrismaAttributeArg ( 'map' , new AttributeArgValue ( 'String' , constraintName ) ) ) ;
504
+
502
505
// fix its name
503
506
const addedFkFieldName = `${ dataModel . name } _${ origForeignKey . name } _${ concreteModel . name } ` ;
504
507
addedFkField . name = this . truncate ( `${ DELEGATE_AUX_RELATION_PREFIX } _${ addedFkFieldName } ` ) ;
505
508
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
-
520
509
// we also need to go through model-level `@@unique` and replicate those involving fk fields
521
510
this . replicateForeignKeyModelLevelUnique ( model , dataModel , origForeignKey , addedFkField ) ;
522
511
@@ -596,13 +585,11 @@ export class PrismaSchemaGenerator {
596
585
return shortName ;
597
586
}
598
587
599
- private nameRelationsInheritedFromDelegate ( model : PrismaDataModel , decl : DataModel ) {
588
+ private ensureRelationsInheritedFromDelegate ( model : PrismaDataModel , decl : DataModel ) {
600
589
if ( this . mode !== 'logical' ) {
601
590
return ;
602
591
}
603
592
604
- // the logical schema needs to name relations inherited from delegate base models for disambiguation
605
-
606
593
decl . fields . forEach ( ( f ) => {
607
594
if ( ! isDataModel ( f . type . reference ?. ref ) ) {
608
595
// only process relation fields
@@ -636,30 +623,68 @@ export class PrismaSchemaGenerator {
636
623
if ( ! oppositeRelationField ) {
637
624
return ;
638
625
}
626
+ const oppositeRelationAttr = getAttribute ( oppositeRelationField , '@relation' ) ;
639
627
640
628
const fieldType = f . type . reference . ref ;
641
629
642
630
// 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
+ ) ;
659
651
} 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
+
660
666
prismaField . attributes . push (
661
667
new PrismaFieldAttribute ( '@relation' , [
662
668
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
+ ) ,
663
688
] )
664
689
) ;
665
690
}
@@ -690,9 +715,24 @@ export class PrismaSchemaGenerator {
690
715
691
716
private getOppositeRelationField ( oppositeModel : DataModel , relationField : DataModelField ) {
692
717
const relName = this . getRelationName ( relationField ) ;
693
- return oppositeModel . fields . find (
718
+ const matches = oppositeModel . fields . filter (
694
719
( f ) => f . type . reference ?. ref === relationField . $container && this . getRelationName ( f ) === relName
695
720
) ;
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
+ }
696
736
}
697
737
698
738
private getRelationName ( field : DataModelField ) {
0 commit comments