@@ -537,7 +537,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
537537 let createResult = await Promise . all (
538538 enumerate ( args . data ) . map ( async ( item ) => {
539539 if ( args . skipDuplicates ) {
540- if ( await this . hasDuplicatedUniqueConstraint ( model , item , db ) ) {
540+ if ( await this . hasDuplicatedUniqueConstraint ( model , item , undefined , db ) ) {
541541 if ( this . shouldLogQuery ) {
542542 this . logger . info ( `[policy] \`createMany\` skipping duplicate ${ formatObject ( item ) } ` ) ;
543543 }
@@ -565,23 +565,82 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
565565 } ;
566566 }
567567
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+ ) {
569574 // check unique constraint conflicts
570575 // we can't rely on try/catch/ignore constraint violation error: https://github.com/prisma/prisma/issues/20496
571576 // TODO: for simple cases we should be able to translate it to an `upsert` with empty `update` payload
572577
573578 // for each unique constraint, check if the input item has all fields set, and if so, check if
574579 // an entity already exists, and ignore accordingly
580+
575581 const uniqueConstraints = this . utils . getUniqueConstraints ( model ) ;
582+
576583 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
579637 const existing = await this . utils . checkExistence ( db , model , uniqueFilter ) ;
580638 if ( existing ) {
581639 return true ;
582640 }
583641 }
584642 }
643+
585644 return false ;
586645 }
587646
@@ -737,8 +796,8 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
737796 if ( args . skipDuplicates ) {
738797 // get a reversed query to include fields inherited from upstream mutation,
739798 // 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 ) ) {
742801 if ( this . shouldLogQuery ) {
743802 this . logger . info ( `[policy] \`createMany\` skipping duplicate ${ formatObject ( item ) } ` ) ;
744803 }
0 commit comments