2021-06-17 16:17:15 +02:00
'use strict' ;
2021-06-22 17:13:11 +02:00
const _ = require ( 'lodash/fp' ) ;
const types = require ( './types' ) ;
2021-06-28 12:34:29 +02:00
const { createField } = require ( './fields' ) ;
2021-06-17 16:17:15 +02:00
const { createQueryBuilder } = require ( './query' ) ;
const { createRepository } = require ( './entity-repository' ) ;
2021-07-05 18:35:16 +02:00
const { isBidirectional } = require ( './metadata/relations' ) ;
2021-06-17 16:17:15 +02:00
2021-06-28 21:37:44 +02:00
// TODO: move to query layer
const toRow = ( metadata , data = { } ) => {
2021-06-22 17:13:11 +02:00
const { attributes } = metadata ;
2021-06-23 15:37:20 +02:00
const obj = { } ;
2021-06-22 17:13:11 +02:00
2021-06-23 15:37:20 +02:00
for ( const attributeName in attributes ) {
const attribute = attributes [ attributeName ] ;
if ( types . isScalar ( attribute . type ) && _ . has ( attributeName , data ) ) {
2021-06-28 12:34:29 +02:00
// TODO: we convert to column name
2021-06-30 20:00:03 +02:00
// TODO: handle default value
2021-06-28 12:34:29 +02:00
const field = createField ( attribute . type , attribute ) ;
2021-06-28 21:37:44 +02:00
// TODO: validate data on creation
2021-06-28 12:34:29 +02:00
// field.validate(data[attributeName]);
2021-06-30 20:00:03 +02:00
2021-06-28 21:37:44 +02:00
const val = data [ attributeName ] === null ? null : field . toDB ( data [ attributeName ] ) ;
2021-06-28 12:34:29 +02:00
2021-06-28 21:37:44 +02:00
obj [ attributeName ] = val ;
2021-06-23 15:37:20 +02:00
}
if ( types . isRelation ( attribute . type ) ) {
// oneToOne & manyToOne
if ( attribute . joinColumn && attribute . owner ) {
// TODO: ensure joinColumn name respect convention ?
const joinColumnName = attribute . joinColumn . name ;
2021-06-25 12:07:32 +02:00
// allow setting to null
const attrValue = ! _ . isUndefined ( data [ attributeName ] )
? data [ attributeName ]
: data [ joinColumnName ] ;
2021-06-23 15:37:20 +02:00
if ( ! _ . isUndefined ( attrValue ) ) {
obj [ joinColumnName ] = attrValue ;
}
2021-07-26 17:52:59 +02:00
continue ;
}
if ( attribute . morphColumn && attribute . owner ) {
const { idColumn , typeColumn } = attribute . morphColumn ;
const value = data [ attributeName ] ;
if ( ! _ . isUndefined ( value ) ) {
if ( ! _ . has ( 'id' , value ) || ! _ . has ( '__type' , value ) ) {
throw new Error ( 'Expects properties `__type` an `id` to make a morph association' ) ;
}
obj [ idColumn . name ] = value . id ;
obj [ typeColumn . name ] = value . _ _type ;
}
2021-06-23 15:37:20 +02:00
}
}
}
return obj ;
} ;
2021-06-17 16:17:15 +02:00
const createEntityManager = db => {
const repoMap = { } ;
return {
2021-07-07 18:04:39 +02:00
findOne ( uid , params ) {
2021-06-17 16:17:15 +02:00
const qb = this . createQueryBuilder ( uid )
. init ( params )
. first ( ) ;
2021-07-07 18:04:39 +02:00
return qb . execute ( ) ;
2021-06-17 16:17:15 +02:00
} ,
// should we name it findOne because people are used to it ?
2021-07-07 18:04:39 +02:00
findMany ( uid , params ) {
2021-06-17 16:17:15 +02:00
const qb = this . createQueryBuilder ( uid ) . init ( params ) ;
2021-07-07 18:04:39 +02:00
return qb . execute ( ) ;
2021-06-17 16:17:15 +02:00
} ,
2021-06-22 17:13:11 +02:00
async count ( uid , params = { } ) {
2021-06-17 16:17:15 +02:00
const qb = this . createQueryBuilder ( uid ) . where ( params . where ) ;
const res = await qb
. count ( )
. first ( )
. execute ( ) ;
return Number ( res . count ) ;
} ,
2021-06-25 12:07:32 +02:00
async create ( uid , params = { } ) {
2021-06-17 16:17:15 +02:00
// create entry in DB
2021-06-23 15:37:20 +02:00
const metadata = db . metadata . get ( uid ) ;
2021-06-17 16:17:15 +02:00
const { data } = params ;
2021-06-25 12:07:32 +02:00
if ( ! _ . isPlainObject ( data ) ) {
throw new Error ( 'Create expects a data object' ) ;
}
2021-06-17 16:17:15 +02:00
// transform value to storage value
// apply programatic defaults if any -> I think this should be handled outside of this layer as we might have some applicative rules in the entity service
2021-06-28 21:37:44 +02:00
const dataToInsert = toRow ( metadata , data ) ;
2021-06-17 16:17:15 +02:00
const [ id ] = await this . createQueryBuilder ( uid )
2021-06-22 17:13:11 +02:00
. insert ( dataToInsert )
2021-06-17 16:17:15 +02:00
. execute ( ) ;
// create relation associations or move this to the entity service & call attach on the repo instead
2021-07-06 14:18:03 +02:00
await this . attachRelations ( uid , id , data ) ;
2021-06-17 16:17:15 +02:00
2021-06-25 12:07:32 +02:00
// TODO: in case there is not select or populate specified return the inserted data ?
2021-06-17 16:17:15 +02:00
return this . findOne ( uid , { where : { id } , select : params . select , populate : params . populate } ) ;
} ,
2021-06-25 12:07:32 +02:00
// TODO: where do we handle relation processing for many queries ?
async createMany ( uid , params = { } ) {
2021-06-17 16:17:15 +02:00
const { data } = params ;
2021-06-25 12:07:32 +02:00
if ( ! _ . isArray ( data ) ) {
throw new Error ( 'CreateMany expecets data to be an array' ) ;
}
2021-06-23 15:37:20 +02:00
const metadata = db . metadata . get ( uid ) ;
2021-06-28 21:37:44 +02:00
const dataToInsert = data . map ( datum => toRow ( metadata , datum ) ) ;
2021-06-22 17:13:11 +02:00
2021-06-24 18:28:36 +02:00
if ( _ . isEmpty ( dataToInsert ) ) {
2021-06-25 12:07:32 +02:00
throw new Error ( 'Nothing to insert' ) ;
2021-06-24 18:28:36 +02:00
}
await this . createQueryBuilder ( uid )
2021-06-22 17:13:11 +02:00
. insert ( dataToInsert )
2021-06-17 16:17:15 +02:00
. execute ( ) ;
2021-06-24 18:28:36 +02:00
return { count : data . length } ;
2021-06-17 16:17:15 +02:00
} ,
2021-06-25 12:07:32 +02:00
async update ( uid , params = { } ) {
2021-06-17 16:17:15 +02:00
const { where , data } = params ;
2021-06-23 15:37:20 +02:00
const metadata = db . metadata . get ( uid ) ;
2021-06-22 17:13:11 +02:00
2021-06-30 20:00:03 +02:00
if ( ! _ . isPlainObject ( data ) ) {
throw new Error ( 'Update requires a data object' ) ;
}
2021-06-25 12:07:32 +02:00
if ( _ . isEmpty ( where ) ) {
throw new Error ( 'Update requires a where parameter' ) ;
2021-06-24 18:28:36 +02:00
}
2021-06-25 12:07:32 +02:00
const entity = await this . createQueryBuilder ( uid )
. select ( 'id' )
2021-06-17 16:17:15 +02:00
. where ( where )
2021-06-25 12:07:32 +02:00
. first ( )
2021-06-17 16:17:15 +02:00
. execute ( ) ;
2021-06-25 12:07:32 +02:00
if ( ! entity ) {
// TODO: or throw ?
return null ;
}
const { id } = entity ;
2021-06-28 21:37:44 +02:00
const dataToUpdate = toRow ( metadata , data ) ;
2021-06-24 18:28:36 +02:00
2021-06-25 12:07:32 +02:00
if ( ! _ . isEmpty ( dataToUpdate ) ) {
await this . createQueryBuilder ( uid )
. where ( { id } )
. update ( dataToUpdate )
. execute ( ) ;
}
2021-07-06 14:18:03 +02:00
await this . updateRelations ( uid , id , data ) ;
2021-06-25 12:07:32 +02:00
return this . findOne ( uid , { where : { id } , select : params . select , populate : params . populate } ) ;
2021-06-17 16:17:15 +02:00
} ,
2021-06-25 12:07:32 +02:00
// TODO: where do we handle relation processing for many queries ?
async updateMany ( uid , params = { } ) {
2021-06-17 16:17:15 +02:00
const { where , data } = params ;
2021-06-23 15:37:20 +02:00
const metadata = db . metadata . get ( uid ) ;
2021-06-28 21:37:44 +02:00
const dataToUpdate = toRow ( metadata , data ) ;
2021-06-22 17:13:11 +02:00
2021-06-24 18:28:36 +02:00
if ( _ . isEmpty ( dataToUpdate ) ) {
throw new Error ( 'Update requires data' ) ;
}
2021-06-25 12:07:32 +02:00
const updatedRows = await this . createQueryBuilder ( uid )
2021-06-17 16:17:15 +02:00
. where ( where )
2021-06-22 17:13:11 +02:00
. update ( dataToUpdate )
2021-06-17 16:17:15 +02:00
. execute ( ) ;
2021-06-24 18:28:36 +02:00
2021-06-25 12:07:32 +02:00
return { count : updatedRows } ;
} ,
2021-06-24 18:28:36 +02:00
2021-06-25 12:07:32 +02:00
async delete ( uid , params = { } ) {
const { where , select , populate } = params ;
2021-06-24 18:28:36 +02:00
2021-06-25 12:07:32 +02:00
if ( _ . isEmpty ( where ) ) {
throw new Error ( 'Delete requires a where parameter' ) ;
}
2021-06-17 16:17:15 +02:00
2021-06-25 12:07:32 +02:00
const entity = await this . findOne ( uid , {
select : select && [ 'id' ] . concat ( select ) ,
2021-07-07 18:04:39 +02:00
where ,
2021-06-25 12:07:32 +02:00
populate ,
} ) ;
if ( ! entity ) {
return null ;
}
const { id } = entity ;
await this . createQueryBuilder ( uid )
. where ( { id } )
2021-06-17 16:17:15 +02:00
. delete ( )
. execute ( ) ;
2021-06-24 18:28:36 +02:00
2021-07-06 14:18:03 +02:00
await this . deleteRelations ( uid , id ) ;
2021-06-24 18:28:36 +02:00
2021-06-25 12:07:32 +02:00
return entity ;
2021-06-17 16:17:15 +02:00
} ,
2021-06-25 12:07:32 +02:00
// TODO: where do we handle relation processing for many queries ?
async deleteMany ( uid , params = { } ) {
2021-06-17 16:17:15 +02:00
const { where } = params ;
2021-06-25 12:07:32 +02:00
const deletedRows = await this . createQueryBuilder ( uid )
2021-06-17 16:17:15 +02:00
. where ( where )
. delete ( )
. execute ( ) ;
2021-06-24 18:28:36 +02:00
2021-06-25 12:07:32 +02:00
return { count : deletedRows } ;
2021-06-17 16:17:15 +02:00
} ,
2021-06-28 12:34:29 +02:00
/ * *
* Attach relations to a new entity
2021-06-30 21:17:32 +02:00
*
2021-06-28 12:34:29 +02:00
* @ param { EntityManager } em - entity manager instance
* @ param { Metadata } metadata - model metadta
* @ param { ID } id - entity ID
* @ param { object } data - data received for creation
* /
2021-07-05 18:35:16 +02:00
// TODO: wrap Transaction
2021-07-06 14:18:03 +02:00
async attachRelations ( uid , id , data ) {
const { attributes } = db . metadata . get ( uid ) ;
2021-06-28 12:34:29 +02:00
for ( const attributeName in attributes ) {
const attribute = attributes [ attributeName ] ;
2021-07-26 19:40:30 +02:00
if ( attribute . type !== 'relation' || ! _ . has ( attributeName , data ) ) {
2021-07-26 17:52:59 +02:00
continue ;
}
// TODO: handle cleaning before creating the assocaitions
2021-07-26 19:40:30 +02:00
if ( attribute . relation === 'morphOne' || attribute . relation === 'morphMany' ) {
const { target , morphBy } = attribute ;
2021-07-26 17:52:59 +02:00
2021-07-26 19:40:30 +02:00
const targetAttribute = db . metadata . get ( target ) . attributes [ morphBy ] ;
2021-07-26 17:52:59 +02:00
2021-07-26 19:40:30 +02:00
if ( targetAttribute . relation === 'morphToOne' ) {
// set columns
const { idColumn , typeColumn } = targetAttribute . morphColumn ;
2021-07-26 17:52:59 +02:00
2021-07-26 19:40:30 +02:00
await this . createQueryBuilder ( target )
. update ( { [ idColumn . name ] : id , [ typeColumn . name ] : uid } )
. where ( { id : data [ attributeName ] } )
. execute ( ) ;
} else if ( targetAttribute . type === 'morphToMany' ) {
const { joinTable } = targetAttribute ;
const { joinColumn , morphColumn } = joinTable ;
2021-07-26 17:52:59 +02:00
const { idColumn , typeColumn } = morphColumn ;
2021-07-26 19:40:30 +02:00
const rows = _ . castArray ( data [ attributeName ] ) . map ( ( dataID , idx ) => ( {
[ joinColumn . name ] : dataID ,
[ idColumn . name ] : id ,
[ typeColumn . name ] : uid ,
2021-07-26 17:52:59 +02:00
... ( joinTable . on || { } ) ,
order : idx ,
} ) ) ;
if ( _ . isEmpty ( rows ) ) {
continue ;
}
2021-07-26 19:40:30 +02:00
await this . createQueryBuilder ( joinTable . name )
2021-07-26 17:52:59 +02:00
. insert ( rows )
. execute ( ) ;
2021-07-26 19:40:30 +02:00
}
continue ;
} else if ( attribute . relation === 'morphToOne' ) {
// handled on the entry itself
continue ;
} else if ( attribute . relation === 'morphToMany' ) {
const { joinTable } = attribute ;
const { joinColumn , morphColumn } = joinTable ;
const { idColumn , typeColumn } = morphColumn ;
const rows = _ . castArray ( data [ attributeName ] ) . map ( ( data , idx ) => ( {
[ joinColumn . name ] : id ,
[ idColumn . name ] : data . id ,
[ typeColumn . name ] : data . _ _type ,
... ( joinTable . on || { } ) ,
order : idx ,
} ) ) ;
2021-07-26 17:52:59 +02:00
2021-07-26 19:40:30 +02:00
if ( _ . isEmpty ( rows ) ) {
2021-07-26 17:52:59 +02:00
continue ;
}
2021-07-26 19:40:30 +02:00
await this . createQueryBuilder ( joinTable . name )
. insert ( rows )
. execute ( ) ;
continue ;
2021-07-26 17:52:59 +02:00
}
2021-06-28 12:34:29 +02:00
if ( attribute . joinColumn && attribute . owner ) {
2021-07-05 18:35:16 +02:00
if (
attribute . relation === 'oneToOne' &&
isBidirectional ( attribute ) &&
data [ attributeName ]
) {
2021-07-06 14:18:03 +02:00
await this . createQueryBuilder ( uid )
2021-07-02 02:26:14 +02:00
. where ( { [ attribute . joinColumn . name ] : data [ attributeName ] , id : { $ne : id } } )
. update ( { [ attribute . joinColumn . name ] : null } )
. execute ( ) ;
}
2021-06-28 12:34:29 +02:00
continue ;
}
// oneToOne oneToMany on the non owning side
if ( attribute . joinColumn && ! attribute . owner ) {
// need to set the column on the target
const { target } = attribute ;
// TODO: check it is an id & the entity exists (will throw due to FKs otherwise so not a big pbl in SQL)
if ( data [ attributeName ] ) {
2021-07-02 02:26:14 +02:00
await this . createQueryBuilder ( target )
. where ( { [ attribute . joinColumn . referencedColumn ] : id } )
. update ( { [ attribute . joinColumn . referencedColumn ] : null } )
. execute ( ) ;
2021-06-28 12:34:29 +02:00
await this . createQueryBuilder ( target )
. update ( { [ attribute . joinColumn . referencedColumn ] : id } )
// NOTE: works if it is an array or a single id
. where ( { id : data [ attributeName ] } )
. execute ( ) ;
}
}
if ( attribute . joinTable ) {
// need to set the column on the target
const { joinTable } = attribute ;
const { joinColumn , inverseJoinColumn } = joinTable ;
2021-07-02 02:26:14 +02:00
// TODO: redefine
2021-06-28 12:34:29 +02:00
// TODO: check it is an id & the entity exists (will throw due to FKs otherwise so not a big pbl in SQL)
if ( data [ attributeName ] ) {
2021-07-05 18:35:16 +02:00
if (
[ 'oneToOne' , 'oneToMany' ] . includes ( attribute . relation ) &&
isBidirectional ( attribute )
) {
2021-07-02 02:26:14 +02:00
await this . createQueryBuilder ( joinTable . name )
. delete ( )
. where ( { [ inverseJoinColumn . name ] : _ . castArray ( data [ attributeName ] ) } )
. where ( joinTable . on ? joinTable . on : { } )
. execute ( ) ;
}
2021-06-28 12:34:29 +02:00
const insert = _ . castArray ( data [ attributeName ] ) . map ( datum => {
return {
[ joinColumn . name ] : id ,
[ inverseJoinColumn . name ] : datum ,
... ( joinTable . on || { } ) ,
} ;
} ) ;
// if there is nothing to insert
if ( insert . length === 0 ) {
2021-07-05 18:35:16 +02:00
continue ;
2021-06-28 12:34:29 +02:00
}
await this . createQueryBuilder ( joinTable . name )
. insert ( insert )
. execute ( ) ;
}
}
}
} ,
/ * *
* Updates relations of an existing entity
2021-06-30 21:17:32 +02:00
*
2021-06-28 12:34:29 +02:00
* @ param { EntityManager } em - entity manager instance
* @ param { Metadata } metadata - model metadta
* @ param { ID } id - entity ID
* @ param { object } data - data received for creation
* /
// TODO: check relation exists (handled by FKs except for polymorphics)
2021-07-05 18:35:16 +02:00
// TODO: wrap Transaction
2021-07-06 14:18:03 +02:00
async updateRelations ( uid , id , data ) {
const { attributes } = db . metadata . get ( uid ) ;
2021-06-28 12:34:29 +02:00
for ( const attributeName in attributes ) {
const attribute = attributes [ attributeName ] ;
2021-07-26 19:40:30 +02:00
if ( attribute . type !== 'relation' || ! _ . has ( attributeName , data ) ) {
continue ;
}
2021-07-26 17:52:59 +02:00
// TODO: implement polymorphic
2021-07-26 19:40:30 +02:00
/ *
if morphOne | morphMany
clear previous :
if morphBy is morphToOne
set null
set new
if morphBy is morphToMany
delete links
add links
* /
if ( attribute . relation === 'morphOne' || attribute . relation === 'morphMany' ) {
const { target , morphBy } = attribute ;
const targetAttribute = db . metadata . get ( target ) . attributes [ morphBy ] ;
if ( targetAttribute . relation === 'morphToOne' ) {
// set columns
const { idColumn , typeColumn } = targetAttribute . morphColumn ;
await this . createQueryBuilder ( target )
. update ( { [ idColumn . name ] : null , [ typeColumn . name ] : null } )
. where ( { [ idColumn . name ] : id , [ typeColumn . name ] : uid } )
. execute ( ) ;
await this . createQueryBuilder ( target )
. update ( { [ idColumn . name ] : id , [ typeColumn . name ] : uid } )
. where ( { id : data [ attributeName ] } )
. execute ( ) ;
} else if ( targetAttribute . type === 'morphToMany' ) {
const { joinTable } = targetAttribute ;
const { joinColumn , morphColumn } = joinTable ;
const { idColumn , typeColumn } = morphColumn ;
await this . createQueryBuilder ( joinTable . name )
. delete ( )
. where ( {
[ idColumn . name ] : id ,
[ typeColumn . name ] : uid ,
... ( joinTable . on || { } ) ,
} )
. execute ( ) ;
const rows = _ . castArray ( data [ attributeName ] ) . map ( ( dataID , idx ) => ( {
[ joinColumn . name ] : dataID ,
[ idColumn . name ] : id ,
[ typeColumn . name ] : uid ,
... ( joinTable . on || { } ) ,
order : idx ,
} ) ) ;
if ( _ . isEmpty ( rows ) ) {
continue ;
}
await this . createQueryBuilder ( joinTable . name )
. insert ( rows )
. execute ( ) ;
}
continue ;
}
/ *
if morphToOne
set new values in morph columns
* /
if ( attribute . relation === 'morphToOne' ) {
// do nothing
}
/ *
if morphToMany
delete old links
create new links
* /
if ( attribute . relation === 'morphToMany' ) {
const { joinTable } = attribute ;
const { joinColumn , morphColumn } = joinTable ;
const { idColumn , typeColumn } = morphColumn ;
await this . createQueryBuilder ( joinTable . name )
. delete ( )
. where ( {
[ joinColumn . name ] : id ,
... ( joinTable . on || { } ) ,
} )
. execute ( ) ;
const rows = _ . castArray ( data [ attributeName ] ) . map ( ( data , idx ) => ( {
[ joinColumn . name ] : id ,
[ idColumn . name ] : data . id ,
[ typeColumn . name ] : data . _ _type ,
... ( joinTable . on || { } ) ,
order : idx ,
} ) ) ;
if ( _ . isEmpty ( rows ) ) {
continue ;
}
await this . createQueryBuilder ( joinTable . name )
. insert ( rows )
. execute ( ) ;
continue ;
}
2021-07-26 17:52:59 +02:00
2021-06-28 12:34:29 +02:00
if ( attribute . joinColumn && attribute . owner ) {
2021-07-26 17:52:59 +02:00
// TODO: check edgecase
2021-07-02 02:26:14 +02:00
if ( attribute . relation === 'oneToOne' && _ . has ( attributeName , data ) ) {
2021-07-06 14:18:03 +02:00
await this . createQueryBuilder ( uid )
2021-07-02 02:26:14 +02:00
. where ( { [ attribute . joinColumn . name ] : data [ attributeName ] , id : { $ne : id } } )
. update ( { [ attribute . joinColumn . name ] : null } )
. execute ( ) ;
}
2021-06-28 12:34:29 +02:00
continue ;
}
// oneToOne oneToMany on the non owning side.
// Since it is a join column no need to remove previous relations
if ( attribute . joinColumn && ! attribute . owner ) {
// need to set the column on the target
const { target } = attribute ;
2021-07-02 02:26:14 +02:00
if ( _ . has ( attributeName , data ) ) {
2021-06-28 12:34:29 +02:00
await this . createQueryBuilder ( target )
2021-07-02 02:26:14 +02:00
. where ( { [ attribute . joinColumn . referencedColumn ] : id } )
. update ( { [ attribute . joinColumn . referencedColumn ] : null } )
2021-06-28 12:34:29 +02:00
. execute ( ) ;
2021-07-02 02:26:14 +02:00
if ( data [ attributeName ] ) {
await this . createQueryBuilder ( target )
// NOTE: works if it is an array or a single id
. where ( { id : data [ attributeName ] } )
. update ( { [ attribute . joinColumn . referencedColumn ] : id } )
. execute ( ) ;
}
2021-06-28 12:34:29 +02:00
}
}
if ( attribute . joinTable ) {
const { joinTable } = attribute ;
const { joinColumn , inverseJoinColumn } = joinTable ;
2021-07-02 02:26:14 +02:00
if ( _ . has ( attributeName , data ) ) {
2021-06-28 12:34:29 +02:00
// clear previous associations in the joinTable
await this . createQueryBuilder ( joinTable . name )
. delete ( )
. where ( { [ joinColumn . name ] : id } )
. where ( joinTable . on ? joinTable . on : { } )
. execute ( ) ;
2021-07-02 02:26:14 +02:00
if ( [ 'oneToOne' , 'oneToMany' ] . includes ( attribute . relation ) ) {
await this . createQueryBuilder ( joinTable . name )
. delete ( )
. where ( { [ inverseJoinColumn . name ] : _ . castArray ( data [ attributeName ] ) } )
. where ( joinTable . on ? joinTable . on : { } )
. execute ( ) ;
2021-06-28 12:34:29 +02:00
}
2021-07-02 02:26:14 +02:00
if ( data [ attributeName ] ) {
const insert = _ . castArray ( data [ attributeName ] ) . map ( datum => {
return {
[ joinColumn . name ] : id ,
[ inverseJoinColumn . name ] : datum ,
... ( joinTable . on || { } ) ,
} ;
} ) ;
// if there is nothing to insert
if ( insert . length === 0 ) {
2021-07-05 18:35:16 +02:00
continue ;
2021-07-02 02:26:14 +02:00
}
await this . createQueryBuilder ( joinTable . name )
. insert ( insert )
. execute ( ) ;
}
2021-06-28 12:34:29 +02:00
}
}
}
} ,
/ * *
* Delete relations of an existing entity
* This removes associations but doesn ' t do cascade deletions for components for example . This will be handled on the entity service layer instead
2021-06-28 21:37:44 +02:00
* NOTE : Most of the deletion should be handled by ON DELETE CASCADE for dialect that have FKs
2021-06-30 21:17:32 +02:00
*
2021-06-28 12:34:29 +02:00
* @ param { EntityManager } em - entity manager instance
* @ param { Metadata } metadata - model metadta
* @ param { ID } id - entity ID
* /
2021-07-05 18:35:16 +02:00
// TODO: wrap Transaction
2021-07-06 14:18:03 +02:00
async deleteRelations ( uid , id ) {
const { attributes } = db . metadata . get ( uid ) ;
2021-06-28 12:34:29 +02:00
for ( const attributeName in attributes ) {
const attribute = attributes [ attributeName ] ;
2021-07-26 19:40:30 +02:00
if ( attribute . type !== 'relation' ) {
continue ;
}
/ *
if morphOne | morphMany
if morphBy is morphToOne
set null
if morphBy is morphToOne
delete links
* /
if ( attribute . relation === 'morphOne' || attribute . relation === 'morphMany' ) {
const { target , morphBy } = attribute ;
const targetAttribute = db . metadata . get ( target ) . attributes [ morphBy ] ;
if ( targetAttribute . relation === 'morphToOne' ) {
// set columns
const { idColumn , typeColumn } = targetAttribute . morphColumn ;
await this . createQueryBuilder ( target )
. update ( { [ idColumn . name ] : null , [ typeColumn . name ] : null } )
. where ( { [ idColumn . name ] : id , [ typeColumn . name ] : uid } )
. execute ( ) ;
} else if ( targetAttribute . type === 'morphToMany' ) {
const { joinTable } = targetAttribute ;
const { morphColumn } = joinTable ;
const { idColumn , typeColumn } = morphColumn ;
await this . createQueryBuilder ( joinTable . name )
. delete ( )
. where ( {
[ idColumn . name ] : id ,
[ typeColumn . name ] : uid ,
... ( joinTable . on || { } ) ,
} )
. execute ( ) ;
}
continue ;
}
/ *
if morphToOne
nothing to do
* /
if ( attribute . relation === 'morphToOne' ) {
// do nothing
}
/ *
if morphToMany
delete links
* /
if ( attribute . relation === 'morphToMany' ) {
const { joinTable } = attribute ;
const { joinColumn } = joinTable ;
await this . createQueryBuilder ( joinTable . name )
. delete ( )
. where ( {
[ joinColumn . name ] : id ,
... ( joinTable . on || { } ) ,
} )
. execute ( ) ;
continue ;
}
if ( db . dialect . usesForeignKeys ( ) ) {
return ;
}
2021-07-26 17:52:59 +02:00
2021-06-28 12:34:29 +02:00
// NOTE: we do not remove existing associations with the target as it should handled by unique FKs instead
if ( attribute . joinColumn && attribute . owner ) {
// nothing to do => relation already added on the table
continue ;
}
// oneToOne oneToMany on the non owning side.
if ( attribute . joinColumn && ! attribute . owner ) {
// need to set the column on the target
const { target } = attribute ;
await this . createQueryBuilder ( target )
. where ( { [ attribute . joinColumn . referencedColumn ] : id } )
2021-07-02 02:26:14 +02:00
. update ( { [ attribute . joinColumn . referencedColumn ] : null } )
2021-06-28 12:34:29 +02:00
. execute ( ) ;
}
if ( attribute . joinTable ) {
const { joinTable } = attribute ;
const { joinColumn } = joinTable ;
await this . createQueryBuilder ( joinTable . name )
. delete ( )
. where ( { [ joinColumn . name ] : id } )
. where ( joinTable . on ? joinTable . on : { } )
. execute ( ) ;
}
}
} ,
2021-06-17 16:17:15 +02:00
2021-07-08 18:15:32 +02:00
// TODO: support multiple relations at once with the populate syntax
async populate ( uid , entity , populate ) {
const entry = await this . findOne ( uid , {
select : [ 'id' ] ,
where : { id : entity . id } ,
populate : populate ,
} ) ;
return Object . assign ( { } , entity , entry ) ;
2021-07-07 18:04:39 +02:00
} ,
2021-07-08 18:15:32 +02:00
// TODO: support multiple relations at once with the populate syntax
async load ( uid , entity , field , params ) {
2021-07-07 18:04:39 +02:00
const { attributes } = db . metadata . get ( uid ) ;
const attribute = attributes [ field ] ;
if ( ! attribute || attribute . type !== 'relation' ) {
2021-07-08 18:15:32 +02:00
throw new Error ( 'Invalid load. Expected a relational attribute' ) ;
2021-07-07 18:04:39 +02:00
}
const entry = await this . findOne ( uid , {
select : [ 'id' ] ,
2021-07-08 18:15:32 +02:00
where : { id : entity . id } ,
2021-07-07 18:04:39 +02:00
populate : {
[ field ] : params || true ,
} ,
} ) ;
return entry [ field ] ;
} ,
2021-06-17 16:17:15 +02:00
// cascading
// aggregations
// -> avg
// -> min
// -> max
// -> grouping
// formulas
// custom queries
// utilities
// -> format
// -> parse
// -> map result
// -> map input
// -> validation
// extra features
// -> virtuals
// -> private
2021-06-30 21:17:32 +02:00
2021-06-17 16:17:15 +02:00
createQueryBuilder ( uid ) {
return createQueryBuilder ( uid , db ) ;
} ,
getRepository ( uid ) {
if ( ! repoMap [ uid ] ) {
repoMap [ uid ] = createRepository ( uid , db ) ;
}
return repoMap [ uid ] ;
} ,
clearRepositories ( ) {
repoMap . clear ( ) ;
} ,
} ;
} ;
module . exports = {
createEntityManager ,
} ;