@@ -537,7 +537,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
537
537
let createResult = await Promise . all (
538
538
enumerate ( args . data ) . map ( async ( item ) => {
539
539
if ( args . skipDuplicates ) {
540
- if ( await this . hasDuplicatedUniqueConstraint ( model , item , db ) ) {
540
+ if ( await this . hasDuplicatedUniqueConstraint ( model , item , undefined , db ) ) {
541
541
if ( this . shouldLogQuery ) {
542
542
this . logger . info ( `[policy] \`createMany\` skipping duplicate ${ formatObject ( item ) } ` ) ;
543
543
}
@@ -565,23 +565,82 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
565
565
} ;
566
566
}
567
567
568
- private async hasDuplicatedUniqueConstraint ( model : string , createData : any , db : Record < string , DbOperations > ) {
568
+ private async hasDuplicatedUniqueConstraint (
569
+ model : string ,
570
+ createData : any ,
571
+ upstreamQuery : any ,
572
+ db : Record < string , DbOperations >
573
+ ) {
569
574
// check unique constraint conflicts
570
575
// we can't rely on try/catch/ignore constraint violation error: https://github.com/prisma/prisma/issues/20496
571
576
// TODO: for simple cases we should be able to translate it to an `upsert` with empty `update` payload
572
577
573
578
// for each unique constraint, check if the input item has all fields set, and if so, check if
574
579
// an entity already exists, and ignore accordingly
580
+
575
581
const uniqueConstraints = this . utils . getUniqueConstraints ( model ) ;
582
+
576
583
for ( const constraint of Object . values ( uniqueConstraints ) ) {
577
- if ( constraint . fields . every ( ( f ) => createData [ f ] !== undefined ) ) {
578
- const uniqueFilter = constraint . fields . reduce ( ( acc , f ) => ( { ...acc , [ f ] : createData [ f ] } ) , { } ) ;
584
+ // the unique filter used to check existence
585
+ const uniqueFilter : any = { } ;
586
+
587
+ // unique constraint fields not covered yet
588
+ const remainingConstraintFields = new Set < string > ( constraint . fields ) ;
589
+
590
+ // collect constraint fields from the create data
591
+ for ( const [ k , v ] of Object . entries < any > ( createData ) ) {
592
+ if ( v === undefined ) {
593
+ continue ;
594
+ }
595
+
596
+ if ( remainingConstraintFields . has ( k ) ) {
597
+ uniqueFilter [ k ] = v ;
598
+ remainingConstraintFields . delete ( k ) ;
599
+ }
600
+ }
601
+
602
+ // collect constraint fields from the upstream query
603
+ if ( upstreamQuery ) {
604
+ for ( const [ k , v ] of Object . entries < any > ( upstreamQuery ) ) {
605
+ if ( v === undefined ) {
606
+ continue ;
607
+ }
608
+
609
+ if ( remainingConstraintFields . has ( k ) ) {
610
+ uniqueFilter [ k ] = v ;
611
+ remainingConstraintFields . delete ( k ) ;
612
+ continue ;
613
+ }
614
+
615
+ // check if the upstream query contains a relation field which covers
616
+ // a foreign key field constraint
617
+
618
+ const fieldInfo = requireField ( this . modelMeta , model , k ) ;
619
+ if ( ! fieldInfo . isDataModel ) {
620
+ // only care about relation fields
621
+ continue ;
622
+ }
623
+
624
+ // merge the upstream query into the unique filter
625
+ uniqueFilter [ k ] = v ;
626
+
627
+ // mark the corresponding foreign key fields as covered
628
+ const fkMapping = fieldInfo . foreignKeyMapping ?? { } ;
629
+ for ( const fk of Object . values ( fkMapping ) ) {
630
+ remainingConstraintFields . delete ( fk ) ;
631
+ }
632
+ }
633
+ }
634
+
635
+ if ( remainingConstraintFields . size === 0 ) {
636
+ // all constraint fields set, check existence
579
637
const existing = await this . utils . checkExistence ( db , model , uniqueFilter ) ;
580
638
if ( existing ) {
581
639
return true ;
582
640
}
583
641
}
584
642
}
643
+
585
644
return false ;
586
645
}
587
646
@@ -737,8 +796,8 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
737
796
if ( args . skipDuplicates ) {
738
797
// get a reversed query to include fields inherited from upstream mutation,
739
798
// it'll be merged with the create payload for unique constraint checking
740
- const reversedQuery = this . utils . buildReversedQuery ( context ) ;
741
- if ( await this . hasDuplicatedUniqueConstraint ( model , { ... reversedQuery , ... item } , db ) ) {
799
+ const upstreamQuery = this . utils . buildReversedQuery ( context ) ;
800
+ if ( await this . hasDuplicatedUniqueConstraint ( model , item , upstreamQuery , db ) ) {
742
801
if ( this . shouldLogQuery ) {
743
802
this . logger . info ( `[policy] \`createMany\` skipping duplicate ${ formatObject ( item ) } ` ) ;
744
803
}
0 commit comments