@@ -523,29 +523,16 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
523
523
let createResult = await Promise . all (
524
524
enumerate ( args . data ) . map ( async ( item ) => {
525
525
if ( args . skipDuplicates ) {
526
- // check unique constraint conflicts
527
- // we can't rely on try/catch/ignore constraint violation error: https://github.com/prisma/prisma/issues/20496
528
- // TODO: for simple cases we should be able to translate it to an `upsert` with empty `update` payload
529
-
530
- // for each unique constraint, check if the input item has all fields set, and if so, check if
531
- // an entity already exists, and ignore accordingly
532
- const uniqueConstraints = this . utils . getUniqueConstraints ( model ) ;
533
- for ( const constraint of Object . values ( uniqueConstraints ) ) {
534
- if ( constraint . fields . every ( ( f ) => item [ f ] !== undefined ) ) {
535
- const uniqueFilter = constraint . fields . reduce ( ( acc , f ) => ( { ...acc , [ f ] : item [ f ] } ) , { } ) ;
536
- const existing = await this . utils . checkExistence ( db , model , uniqueFilter ) ;
537
- if ( existing ) {
538
- if ( this . shouldLogQuery ) {
539
- this . logger . info ( `[policy] skipping duplicate ${ formatObject ( item ) } ` ) ;
540
- }
541
- return undefined ;
542
- }
526
+ if ( await this . hasDuplicatedUniqueConstraint ( model , item , db ) ) {
527
+ if ( this . shouldLogQuery ) {
528
+ this . logger . info ( `[policy] \`createMany\` skipping duplicate ${ formatObject ( item ) } ` ) ;
543
529
}
530
+ return undefined ;
544
531
}
545
532
}
546
533
547
534
if ( this . shouldLogQuery ) {
548
- this . logger . info ( `[policy] \`create\` ${ model } : ${ formatObject ( item ) } ` ) ;
535
+ this . logger . info ( `[policy] \`create\` for \`createMany\` ${ model } : ${ formatObject ( item ) } ` ) ;
549
536
}
550
537
return await db [ model ] . create ( { select : this . utils . makeIdSelection ( model ) , data : item } ) ;
551
538
} )
@@ -564,6 +551,26 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
564
551
} ;
565
552
}
566
553
554
+ private async hasDuplicatedUniqueConstraint ( model : string , createData : any , db : Record < string , DbOperations > ) {
555
+ // check unique constraint conflicts
556
+ // we can't rely on try/catch/ignore constraint violation error: https://github.com/prisma/prisma/issues/20496
557
+ // TODO: for simple cases we should be able to translate it to an `upsert` with empty `update` payload
558
+
559
+ // for each unique constraint, check if the input item has all fields set, and if so, check if
560
+ // an entity already exists, and ignore accordingly
561
+ const uniqueConstraints = this . utils . getUniqueConstraints ( model ) ;
562
+ for ( const constraint of Object . values ( uniqueConstraints ) ) {
563
+ if ( constraint . fields . every ( ( f ) => createData [ f ] !== undefined ) ) {
564
+ const uniqueFilter = constraint . fields . reduce ( ( acc , f ) => ( { ...acc , [ f ] : createData [ f ] } ) , { } ) ;
565
+ const existing = await this . utils . checkExistence ( db , model , uniqueFilter ) ;
566
+ if ( existing ) {
567
+ return true ;
568
+ }
569
+ }
570
+ }
571
+ return false ;
572
+ }
573
+
567
574
//#endregion
568
575
569
576
//#region Update & Upsert
@@ -707,17 +714,22 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
707
714
postWriteChecks . push ( ...checks ) ;
708
715
} ;
709
716
710
- const _createMany = async ( model : string , args : any , context : NestedWriteVisitorContext ) => {
711
- if ( context . field ?. backLink ) {
712
- // handles the connection to upstream entity
713
- const reversedQuery = this . utils . buildReversedQuery ( context ) ;
714
- for ( const item of enumerate ( args . data ) ) {
715
- Object . assign ( item , reversedQuery ) ;
717
+ const _createMany = async (
718
+ model : string ,
719
+ args : { data : any ; skipDuplicates ?: boolean } ,
720
+ context : NestedWriteVisitorContext
721
+ ) => {
722
+ for ( const item of enumerate ( args . data ) ) {
723
+ if ( args . skipDuplicates ) {
724
+ if ( await this . hasDuplicatedUniqueConstraint ( model , item , db ) ) {
725
+ if ( this . shouldLogQuery ) {
726
+ this . logger . info ( `[policy] \`createMany\` skipping duplicate ${ formatObject ( item ) } ` ) ;
727
+ }
728
+ continue ;
729
+ }
716
730
}
731
+ await _create ( model , item , context ) ;
717
732
}
718
- // proceed with the create and collect post-create checks
719
- const { postWriteChecks : checks } = await this . doCreateMany ( model , args , db ) ;
720
- postWriteChecks . push ( ...checks ) ;
721
733
} ;
722
734
723
735
const _connectDisconnect = async ( model : string , args : any , context : NestedWriteVisitorContext ) => {
@@ -797,9 +809,6 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
797
809
} ,
798
810
799
811
updateMany : async ( model , args , context ) => {
800
- // injects auth guard into where clause
801
- this . utils . injectAuthGuard ( db , args , model , 'update' ) ;
802
-
803
812
// prepare for post-update check
804
813
if ( this . utils . hasAuthGuard ( model , 'postUpdate' ) || this . utils . getZodSchema ( model ) ) {
805
814
let select = this . utils . makeIdSelection ( model ) ;
@@ -809,10 +818,12 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
809
818
}
810
819
const reversedQuery = this . utils . buildReversedQuery ( context ) ;
811
820
const currentSetQuery = { select, where : reversedQuery } ;
812
- this . utils . injectAuthGuard ( db , currentSetQuery , model , 'read' ) ;
821
+ this . utils . injectAuthGuardAsWhere ( db , currentSetQuery , model , 'read' ) ;
813
822
814
823
if ( this . shouldLogQuery ) {
815
- this . logger . info ( `[policy] \`findMany\` ${ model } :\n${ formatObject ( currentSetQuery ) } ` ) ;
824
+ this . logger . info (
825
+ `[policy] \`findMany\` for post update check ${ model } :\n${ formatObject ( currentSetQuery ) } `
826
+ ) ;
816
827
}
817
828
const currentSet = await db [ model ] . findMany ( currentSetQuery ) ;
818
829
@@ -825,6 +836,27 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
825
836
} ) )
826
837
) ;
827
838
}
839
+
840
+ const updateGuard = this . utils . getAuthGuard ( db , model , 'update' ) ;
841
+ if ( this . utils . isTrue ( updateGuard ) || this . utils . isFalse ( updateGuard ) ) {
842
+ // injects simple auth guard into where clause
843
+ this . utils . injectAuthGuardAsWhere ( db , args , model , 'update' ) ;
844
+ } else {
845
+ // we have to process `updateMany` separately because the guard may contain
846
+ // filters using relation fields which are not allowed in nested `updateMany`
847
+ const reversedQuery = this . utils . buildReversedQuery ( context ) ;
848
+ const updateWhere = this . utils . and ( reversedQuery , updateGuard ) ;
849
+ if ( this . shouldLogQuery ) {
850
+ this . logger . info (
851
+ `[policy] \`updateMany\` ${ model } :\n${ formatObject ( {
852
+ where : updateWhere ,
853
+ data : args . data ,
854
+ } ) } `
855
+ ) ;
856
+ }
857
+ await db [ model ] . updateMany ( { where : updateWhere , data : args . data } ) ;
858
+ delete context . parent . updateMany ;
859
+ }
828
860
} ,
829
861
830
862
create : async ( model , args , context ) => {
@@ -931,9 +963,21 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
931
963
} ,
932
964
933
965
deleteMany : async ( model , args , context ) => {
934
- // inject delete guard
935
966
const guard = await this . utils . getAuthGuard ( db , model , 'delete' ) ;
936
- context . parent . deleteMany = this . utils . and ( args , guard ) ;
967
+ if ( this . utils . isTrue ( guard ) || this . utils . isFalse ( guard ) ) {
968
+ // inject simple auth guard
969
+ context . parent . deleteMany = this . utils . and ( args , guard ) ;
970
+ } else {
971
+ // we have to process `deleteMany` separately because the guard may contain
972
+ // filters using relation fields which are not allowed in nested `deleteMany`
973
+ const reversedQuery = this . utils . buildReversedQuery ( context ) ;
974
+ const deleteWhere = this . utils . and ( reversedQuery , guard ) ;
975
+ if ( this . shouldLogQuery ) {
976
+ this . logger . info ( `[policy] \`deleteMany\` ${ model } :\n${ formatObject ( { where : deleteWhere } ) } ` ) ;
977
+ }
978
+ await db [ model ] . deleteMany ( { where : deleteWhere } ) ;
979
+ delete context . parent . deleteMany ;
980
+ }
937
981
} ,
938
982
} ) ;
939
983
@@ -958,13 +1002,17 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
958
1002
}
959
1003
for ( const k of Object . keys ( args ) ) {
960
1004
const field = resolveField ( this . modelMeta , model , k ) ;
961
- if ( field ?. isId || field ?. isForeignKey ) {
1005
+ if ( this . isAutoIncrementIdField ( field ) || field ?. isForeignKey ) {
962
1006
return true ;
963
1007
}
964
1008
}
965
1009
return false ;
966
1010
}
967
1011
1012
+ private isAutoIncrementIdField ( field : FieldInfo ) {
1013
+ return field . isId && field . isAutoIncrement ;
1014
+ }
1015
+
968
1016
async updateMany ( args : any ) {
969
1017
if ( ! args ) {
970
1018
throw prismaClientValidationError ( this . prisma , 'query argument is required' ) ;
@@ -976,7 +1024,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
976
1024
this . utils . tryReject ( this . prisma , this . model , 'update' ) ;
977
1025
978
1026
args = this . utils . clone ( args ) ;
979
- this . utils . injectAuthGuard ( this . prisma , args , this . model , 'update' ) ;
1027
+ this . utils . injectAuthGuardAsWhere ( this . prisma , args , this . model , 'update' ) ;
980
1028
981
1029
if ( this . utils . hasAuthGuard ( this . model , 'postUpdate' ) || this . utils . getZodSchema ( this . model ) ) {
982
1030
// use a transaction to do post-update checks
@@ -989,7 +1037,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
989
1037
select = { ...select , ...preValueSelect } ;
990
1038
}
991
1039
const currentSetQuery = { select, where : args . where } ;
992
- this . utils . injectAuthGuard ( tx , currentSetQuery , this . model , 'read' ) ;
1040
+ this . utils . injectAuthGuardAsWhere ( tx , currentSetQuery , this . model , 'read' ) ;
993
1041
994
1042
if ( this . shouldLogQuery ) {
995
1043
this . logger . info ( `[policy] \`findMany\` ${ this . model } : ${ formatObject ( currentSetQuery ) } ` ) ;
@@ -1118,7 +1166,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
1118
1166
1119
1167
// inject policy conditions
1120
1168
args = args ?? { } ;
1121
- this . utils . injectAuthGuard ( this . prisma , args , this . model , 'delete' ) ;
1169
+ this . utils . injectAuthGuardAsWhere ( this . prisma , args , this . model , 'delete' ) ;
1122
1170
1123
1171
// conduct the deletion
1124
1172
if ( this . shouldLogQuery ) {
@@ -1139,7 +1187,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
1139
1187
args = this . utils . clone ( args ) ;
1140
1188
1141
1189
// inject policy conditions
1142
- this . utils . injectAuthGuard ( this . prisma , args , this . model , 'read' ) ;
1190
+ this . utils . injectAuthGuardAsWhere ( this . prisma , args , this . model , 'read' ) ;
1143
1191
1144
1192
if ( this . shouldLogQuery ) {
1145
1193
this . logger . info ( `[policy] \`aggregate\` ${ this . model } :\n${ formatObject ( args ) } ` ) ;
@@ -1155,7 +1203,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
1155
1203
args = this . utils . clone ( args ) ;
1156
1204
1157
1205
// inject policy conditions
1158
- this . utils . injectAuthGuard ( this . prisma , args , this . model , 'read' ) ;
1206
+ this . utils . injectAuthGuardAsWhere ( this . prisma , args , this . model , 'read' ) ;
1159
1207
1160
1208
if ( this . shouldLogQuery ) {
1161
1209
this . logger . info ( `[policy] \`groupBy\` ${ this . model } :\n${ formatObject ( args ) } ` ) ;
@@ -1166,7 +1214,7 @@ export class PolicyProxyHandler<DbClient extends DbClientContract> implements Pr
1166
1214
async count ( args : any ) {
1167
1215
// inject policy conditions
1168
1216
args = args ? this . utils . clone ( args ) : { } ;
1169
- this . utils . injectAuthGuard ( this . prisma , args , this . model , 'read' ) ;
1217
+ this . utils . injectAuthGuardAsWhere ( this . prisma , args , this . model , 'read' ) ;
1170
1218
1171
1219
if ( this . shouldLogQuery ) {
1172
1220
this . logger . info ( `[policy] \`count\` ${ this . model } :\n${ formatObject ( args ) } ` ) ;
0 commit comments