@@ -4,6 +4,7 @@ import type {
44 AnySchema ,
55 AnyTable ,
66} from "../../schema" ;
7+ import { Column } from "../../schema" ;
78import type {
89 AbstractQuery ,
910 AnySelectClause ,
@@ -12,7 +13,12 @@ import type {
1213 JoinBuilder ,
1314 OrderBy ,
1415} from ".." ;
15- import { buildCondition , createBuilder , type Condition } from "../condition-builder" ;
16+ import {
17+ buildCondition ,
18+ createBuilder ,
19+ type Condition ,
20+ ConditionType ,
21+ } from "../condition-builder" ;
1622
1723export interface CompiledJoin {
1824 relation : AnyRelation ;
@@ -231,6 +237,27 @@ const applyUpdatePolicies = async (
231237 return nextWhere ;
232238} ;
233239
240+ const conditionKey = ( condition : Condition | undefined ) : string => {
241+ if ( ! condition ) return "none" ;
242+ if ( condition . type === ConditionType . Compare ) {
243+ const right =
244+ condition . b instanceof Column ? { column : condition . b . ormName } : { value : condition . b } ;
245+ return JSON . stringify ( {
246+ type : "compare" ,
247+ left : condition . a . ormName ,
248+ operator : condition . operator ,
249+ right,
250+ } ) ;
251+ }
252+ if ( condition . type === ConditionType . Not ) {
253+ return JSON . stringify ( { type : "not" , item : conditionKey ( condition . item ) } ) ;
254+ }
255+ return JSON . stringify ( {
256+ type : condition . type === ConditionType . And ? "and" : "or" ,
257+ items : condition . items . map ( conditionKey ) ,
258+ } ) ;
259+ } ;
260+
234261const applyDeletePolicies = async (
235262 table : AnyTable ,
236263 where : Condition | undefined ,
@@ -284,6 +311,16 @@ export interface ORMAdapter<S extends AnySchema = AnySchema> {
284311 } ,
285312 ) => Promise < void > ;
286313
314+ upsertMany ?: (
315+ table : AnyTable ,
316+ v : {
317+ target : AnyColumn [ ] ;
318+ update : AnyColumn [ ] ;
319+ values : Record < string , unknown > [ ] ;
320+ where ?: Condition ;
321+ } ,
322+ ) => Promise < void > ;
323+
287324 create : (
288325 table : AnyTable ,
289326 values : Record < string , unknown > ,
@@ -367,6 +404,85 @@ export function toORM<S extends AnySchema>(
367404 ...options ,
368405 } ) ;
369406 } ,
407+ async upsertMany ( name , { target, update, values } ) {
408+ const table = toTable ( name ) ;
409+ if ( values . length === 0 ) return ;
410+
411+ const targetColumns = target . map ( ( columnName ) => {
412+ const column = table . columns [ columnName as string ] ;
413+ if ( ! column ) throw new Error ( `[FumaDB] unknown column name ${ String ( columnName ) } .` ) ;
414+ return column ;
415+ } ) ;
416+ const updateColumns = update . map ( ( columnName ) => {
417+ const column = table . columns [ columnName as string ] ;
418+ if ( ! column ) throw new Error ( `[FumaDB] unknown column name ${ String ( columnName ) } .` ) ;
419+ return column ;
420+ } ) ;
421+
422+ const builder = createBuilder ( table . columns ) ;
423+ const permittedRows : {
424+ readonly value : Record < string , unknown > ;
425+ readonly where : Condition | undefined ;
426+ } [ ] = [ ] ;
427+ for ( const value of values ) {
428+ const updateValues = Object . fromEntries (
429+ updateColumns . map ( ( column ) => [ column . ormName , value [ column . ormName ] ] ) ,
430+ ) ;
431+ const constrainedWhere = await applyUpdatePolicies (
432+ table ,
433+ undefined ,
434+ updateValues ,
435+ context ,
436+ "upsert" ,
437+ value ,
438+ ) ;
439+ if ( constrainedWhere === false ) continue ;
440+ await runCreatePolicies ( table , value , context ) ;
441+ permittedRows . push ( { value, where : constrainedWhere } ) ;
442+ }
443+ if ( permittedRows . length === 0 ) return ;
444+
445+ if ( internal . upsertMany ) {
446+ const groups = new Map <
447+ string ,
448+ { readonly where : Condition | undefined ; readonly values : Record < string , unknown > [ ] }
449+ > ( ) ;
450+ for ( const row of permittedRows ) {
451+ const key = conditionKey ( row . where ) ;
452+ const group = groups . get ( key ) ;
453+ if ( group ) {
454+ group . values . push ( row . value ) ;
455+ } else {
456+ groups . set ( key , { where : row . where , values : [ row . value ] } ) ;
457+ }
458+ }
459+ for ( const group of groups . values ( ) ) {
460+ await internal . upsertMany ( table , {
461+ target : targetColumns ,
462+ update : updateColumns ,
463+ values : group . values ,
464+ where : group . where ,
465+ } ) ;
466+ }
467+ return ;
468+ }
469+
470+ for ( const row of permittedRows ) {
471+ const value = row . value ;
472+ const targetWhere = builder . and (
473+ ...targetColumns . map ( ( column ) => builder ( column . ormName , "=" , value [ column . ormName ] ) ) ,
474+ ) ;
475+ const where = builder . and ( targetWhere , row . where ?? true ) ;
476+ if ( where === false ) continue ;
477+ await internal . upsert ( table , {
478+ where : where === true ? undefined : where ,
479+ update : Object . fromEntries (
480+ updateColumns . map ( ( column ) => [ column . ormName , value [ column . ormName ] ] ) ,
481+ ) ,
482+ create : value ,
483+ } ) ;
484+ }
485+ } ,
370486 async create ( name , values ) {
371487 const table = toTable ( name ) ;
372488 await runCreatePolicies ( table , values , context ) ;
0 commit comments