| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @module relations | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const _ = require('lodash/fp'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-05 18:35:16 +02:00
										 |  |  | const hasInversedBy = _.has('inversedBy'); | 
					
						
							|  |  |  | const hasMappedBy = _.has('mappedBy'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-19 19:29:05 +01:00
										 |  |  | const isPolymorphic = (attribute) => | 
					
						
							|  |  |  |   ['morphOne', 'morphMany', 'morphToOne', 'morphToMany'].includes(attribute.relation); | 
					
						
							| 
									
										
										
										
											2022-08-08 23:33:39 +02:00
										 |  |  | const isOneToAny = (attribute) => ['oneToOne', 'oneToMany'].includes(attribute.relation); | 
					
						
							| 
									
										
										
										
											2022-09-14 11:30:00 +02:00
										 |  |  | const isManyToAny = (attribute) => ['manyToMany', 'manyToOne'].includes(attribute.relation); | 
					
						
							|  |  |  | const isAnyToOne = (attribute) => ['oneToOne', 'manyToOne'].includes(attribute.relation); | 
					
						
							|  |  |  | const isAnyToMany = (attribute) => ['oneToMany', 'manyToMany'].includes(attribute.relation); | 
					
						
							| 
									
										
										
										
											2022-08-08 23:33:39 +02:00
										 |  |  | const isBidirectional = (attribute) => hasInversedBy(attribute) || hasMappedBy(attribute); | 
					
						
							|  |  |  | const isOwner = (attribute) => !isBidirectional(attribute) || hasInversedBy(attribute); | 
					
						
							|  |  |  | const shouldUseJoinTable = (attribute) => attribute.useJoinTable !== false; | 
					
						
							| 
									
										
										
										
											2022-12-20 10:17:38 +01:00
										 |  |  | const getJoinTableName = (tableName, attributeName) => | 
					
						
							|  |  |  |   _.snakeCase(`${tableName}_${attributeName}_links`); | 
					
						
							| 
									
										
										
										
											2021-07-05 18:35:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Creates a oneToOne relation metadata | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * if owner then | 
					
						
							|  |  |  |  *   if with join table then | 
					
						
							|  |  |  |  *     create join table | 
					
						
							|  |  |  |  *   else | 
					
						
							|  |  |  |  *     create joinColumn | 
					
						
							|  |  |  |  *   if bidirectional then | 
					
						
							|  |  |  |  *     set inverse attribute joinCol or joinTable info correctly | 
					
						
							|  |  |  |  * else | 
					
						
							|  |  |  |  *   this property must be set by the owner side | 
					
						
							|  |  |  |  *   verify the owner side is valid // should be done before or at the same time ?
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param {string} attributeName | 
					
						
							|  |  |  |  * @param {Attribute} attribute | 
					
						
							|  |  |  |  * @param {ModelMetadata} meta | 
					
						
							|  |  |  |  * @param {Metadata} metadata | 
					
						
							|  |  |  |  * @retuns void | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const createOneToOne = (attributeName, attribute, meta, metadata) => { | 
					
						
							|  |  |  |   if (isOwner(attribute)) { | 
					
						
							|  |  |  |     if (shouldUseJoinTable(attribute)) { | 
					
						
							|  |  |  |       createJoinTable(metadata, { | 
					
						
							|  |  |  |         attribute, | 
					
						
							|  |  |  |         attributeName, | 
					
						
							|  |  |  |         meta, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       createJoinColum(metadata, { | 
					
						
							|  |  |  |         attribute, | 
					
						
							|  |  |  |         attributeName, | 
					
						
							|  |  |  |         meta, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     // TODO: verify other side is valid
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Creates a oneToMany relation metadata | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * if unidirectional then | 
					
						
							|  |  |  |  *   create join table | 
					
						
							|  |  |  |  * if bidirectional then | 
					
						
							|  |  |  |  *   cannot be owning side | 
					
						
							|  |  |  |  *   do nothing | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param {string} attributeName | 
					
						
							|  |  |  |  * @param {Attribute} attribute | 
					
						
							|  |  |  |  * @param {ModelMetadata} meta | 
					
						
							|  |  |  |  * @param {Metadata} metadata | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const createOneToMany = (attributeName, attribute, meta, metadata) => { | 
					
						
							|  |  |  |   if (!isBidirectional(attribute)) { | 
					
						
							|  |  |  |     createJoinTable(metadata, { | 
					
						
							|  |  |  |       attribute, | 
					
						
							|  |  |  |       attributeName, | 
					
						
							|  |  |  |       meta, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-08-08 15:50:34 +02:00
										 |  |  |   } else if (isOwner(attribute)) { | 
					
						
							|  |  |  |     throw new Error('one side of a oneToMany cannot be the owner side in a bidirectional relation'); | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Creates a manyToOne relation metadata | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * if unidirectional then | 
					
						
							|  |  |  |  *   if with join table then | 
					
						
							|  |  |  |  *     create join table | 
					
						
							|  |  |  |  *   else | 
					
						
							|  |  |  |  *     create join column | 
					
						
							|  |  |  |  * else | 
					
						
							|  |  |  |  *   must be the owner side | 
					
						
							|  |  |  |  *   if with join table then | 
					
						
							|  |  |  |  *     create join table | 
					
						
							|  |  |  |  *   else | 
					
						
							|  |  |  |  *     create join column | 
					
						
							|  |  |  |  *   set inverse attribute joinCol or joinTable info correctly | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param {string} attributeName | 
					
						
							|  |  |  |  * @param {Attribute} attribute | 
					
						
							|  |  |  |  * @param {ModelMetadata} meta | 
					
						
							|  |  |  |  * @param {Metadata} metadata | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const createManyToOne = (attributeName, attribute, meta, metadata) => { | 
					
						
							|  |  |  |   if (isBidirectional(attribute) && !isOwner(attribute)) { | 
					
						
							|  |  |  |     throw new Error('The many side of a manyToOne must be the owning side'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (shouldUseJoinTable(attribute)) { | 
					
						
							|  |  |  |     createJoinTable(metadata, { | 
					
						
							|  |  |  |       attribute, | 
					
						
							|  |  |  |       attributeName, | 
					
						
							|  |  |  |       meta, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     createJoinColum(metadata, { | 
					
						
							|  |  |  |       attribute, | 
					
						
							|  |  |  |       attributeName, | 
					
						
							|  |  |  |       meta, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Creates a manyToMany relation metadata | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * if unidirectional | 
					
						
							|  |  |  |  *   create join table | 
					
						
							|  |  |  |  * else | 
					
						
							|  |  |  |  *   if owner then | 
					
						
							|  |  |  |  *     if with join table then | 
					
						
							|  |  |  |  *       create join table | 
					
						
							|  |  |  |  *   else | 
					
						
							|  |  |  |  *     do nothing | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param {string} attributeName | 
					
						
							|  |  |  |  * @param {Attribute} attribute | 
					
						
							|  |  |  |  * @param {ModelMetadata} meta | 
					
						
							|  |  |  |  * @param {Metadata} metadata | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const createManyToMany = (attributeName, attribute, meta, metadata) => { | 
					
						
							|  |  |  |   if (!isBidirectional(attribute) || isOwner(attribute)) { | 
					
						
							|  |  |  |     createJoinTable(metadata, { | 
					
						
							|  |  |  |       attribute, | 
					
						
							|  |  |  |       attributeName, | 
					
						
							|  |  |  |       meta, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-26 17:52:59 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Creates a morphToOne relation metadata | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * if with join table then | 
					
						
							|  |  |  |  *   create join table | 
					
						
							|  |  |  |  * else | 
					
						
							|  |  |  |  *  create join columnsa | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * if bidirectionnal | 
					
						
							|  |  |  |  *  set info in the traget | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param {string} attributeName | 
					
						
							|  |  |  |  * @param {Attribute} attribute | 
					
						
							|  |  |  |  * @param {ModelMetadata} meta | 
					
						
							|  |  |  |  * @param {Metadata} metadata | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-08-08 15:50:34 +02:00
										 |  |  | const createMorphToOne = (attributeName, attribute /* meta, metadata */) => { | 
					
						
							| 
									
										
										
										
											2021-07-26 17:52:59 +02:00
										 |  |  |   const idColumnName = 'target_id'; | 
					
						
							|  |  |  |   const typeColumnName = 'target_type'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Object.assign(attribute, { | 
					
						
							|  |  |  |     owner: true, | 
					
						
							|  |  |  |     morphColumn: { | 
					
						
							|  |  |  |       // TODO: add referenced column
 | 
					
						
							|  |  |  |       typeColumn: { | 
					
						
							|  |  |  |         name: typeColumnName, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       idColumn: { | 
					
						
							|  |  |  |         name: idColumnName, | 
					
						
							|  |  |  |         referencedColumn: 'id', | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // TODO: implement bidirectional
 | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Creates a morphToMany relation metadata | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param {string} attributeName | 
					
						
							|  |  |  |  * @param {Attribute} attribute | 
					
						
							|  |  |  |  * @param {ModelMetadata} meta | 
					
						
							|  |  |  |  * @param {Metadata} metadata | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const createMorphToMany = (attributeName, attribute, meta, metadata) => { | 
					
						
							|  |  |  |   const joinTableName = _.snakeCase(`${meta.tableName}_${attributeName}_morphs`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const joinColumnName = _.snakeCase(`${meta.singularName}_id`); | 
					
						
							|  |  |  |   const morphColumnName = _.snakeCase(`${attributeName}`); | 
					
						
							|  |  |  |   const idColumnName = `${morphColumnName}_id`; | 
					
						
							|  |  |  |   const typeColumnName = `${morphColumnName}_type`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   metadata.add({ | 
					
						
							|  |  |  |     uid: joinTableName, | 
					
						
							|  |  |  |     tableName: joinTableName, | 
					
						
							|  |  |  |     attributes: { | 
					
						
							| 
									
										
										
										
											2022-07-01 19:20:59 +02:00
										 |  |  |       id: { | 
					
						
							|  |  |  |         type: 'increments', | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-07-26 17:52:59 +02:00
										 |  |  |       [joinColumnName]: { | 
					
						
							|  |  |  |         type: 'integer', | 
					
						
							|  |  |  |         column: { | 
					
						
							|  |  |  |           unsigned: true, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       [idColumnName]: { | 
					
						
							|  |  |  |         type: 'integer', | 
					
						
							|  |  |  |         column: { | 
					
						
							|  |  |  |           unsigned: true, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       [typeColumnName]: { | 
					
						
							|  |  |  |         type: 'string', | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-08-17 18:08:42 +02:00
										 |  |  |       field: { | 
					
						
							|  |  |  |         type: 'string', | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-07-26 17:52:59 +02:00
										 |  |  |       order: { | 
					
						
							| 
									
										
										
										
											2022-10-26 09:16:22 +02:00
										 |  |  |         type: 'float', | 
					
						
							| 
									
										
										
										
											2021-07-26 17:52:59 +02:00
										 |  |  |         column: { | 
					
						
							|  |  |  |           unsigned: true, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2021-09-16 11:37:44 +02:00
										 |  |  |     indexes: [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         name: `${joinTableName}_fk`, | 
					
						
							|  |  |  |         columns: [joinColumnName], | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2023-04-20 12:08:15 +02:00
										 |  |  |       { | 
					
						
							|  |  |  |         name: `${joinTableName}_order_index`, | 
					
						
							|  |  |  |         columns: ['order'], | 
					
						
							|  |  |  |         type: null, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         name: `${joinTableName}_id_column_index`, | 
					
						
							|  |  |  |         columns: [idColumnName], | 
					
						
							|  |  |  |         type: null, | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-09-16 11:37:44 +02:00
										 |  |  |     ], | 
					
						
							| 
									
										
										
										
											2021-07-26 17:52:59 +02:00
										 |  |  |     foreignKeys: [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         name: `${joinTableName}_fk`, | 
					
						
							|  |  |  |         columns: [joinColumnName], | 
					
						
							|  |  |  |         referencedColumns: ['id'], | 
					
						
							|  |  |  |         referencedTable: meta.tableName, | 
					
						
							|  |  |  |         onDelete: 'CASCADE', | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const joinTable = { | 
					
						
							|  |  |  |     name: joinTableName, | 
					
						
							|  |  |  |     joinColumn: { | 
					
						
							|  |  |  |       name: joinColumnName, | 
					
						
							|  |  |  |       referencedColumn: 'id', | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     morphColumn: { | 
					
						
							|  |  |  |       typeColumn: { | 
					
						
							|  |  |  |         name: typeColumnName, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       idColumn: { | 
					
						
							|  |  |  |         name: idColumnName, | 
					
						
							|  |  |  |         referencedColumn: 'id', | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2021-08-17 18:08:42 +02:00
										 |  |  |     orderBy: { | 
					
						
							|  |  |  |       order: 'asc', | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2022-09-26 16:22:22 +02:00
										 |  |  |     pivotColumns: [joinColumnName, typeColumnName, idColumnName], | 
					
						
							| 
									
										
										
										
											2021-07-26 17:52:59 +02:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   attribute.joinTable = joinTable; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Creates a morphOne relation metadata | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param {string} attributeName | 
					
						
							|  |  |  |  * @param {Attribute} attribute | 
					
						
							|  |  |  |  * @param {ModelMetadata} meta | 
					
						
							|  |  |  |  * @param {Metadata} metadata | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const createMorphOne = (attributeName, attribute, meta, metadata) => { | 
					
						
							|  |  |  |   const targetMeta = metadata.get(attribute.target); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!targetMeta) { | 
					
						
							|  |  |  |     throw new Error(`Morph target not found. Looking for ${attribute.target}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!_.has(attribute.morphBy, targetMeta.attributes)) { | 
					
						
							|  |  |  |     throw new Error(`Morph target attribute not found. Looking for ${attribute.morphBy}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Creates a morphMany relation metadata | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param {string} attributeName | 
					
						
							|  |  |  |  * @param {Attribute} attribute | 
					
						
							|  |  |  |  * @param {ModelMetadata} meta | 
					
						
							|  |  |  |  * @param {Metadata} metadata | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const createMorphMany = (attributeName, attribute, meta, metadata) => { | 
					
						
							|  |  |  |   const targetMeta = metadata.get(attribute.target); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!targetMeta) { | 
					
						
							|  |  |  |     throw new Error(`Morph target not found. Looking for ${attribute.target}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!_.has(attribute.morphBy, targetMeta.attributes)) { | 
					
						
							|  |  |  |     throw new Error(`Morph target attribute not found. Looking for ${attribute.morphBy}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Creates a relation metadata | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param {string} attributeName | 
					
						
							|  |  |  |  * @param {Attribute} attribute | 
					
						
							|  |  |  |  * @param {ModelMetadata} meta | 
					
						
							|  |  |  |  * @param {Metadata} metadata | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const createRelation = (attributeName, attribute, meta, metadata) => { | 
					
						
							| 
									
										
										
										
											2021-08-17 18:08:42 +02:00
										 |  |  |   switch (attribute.relation) { | 
					
						
							|  |  |  |     case 'oneToOne': | 
					
						
							|  |  |  |       return createOneToOne(attributeName, attribute, meta, metadata); | 
					
						
							|  |  |  |     case 'oneToMany': | 
					
						
							|  |  |  |       return createOneToMany(attributeName, attribute, meta, metadata); | 
					
						
							|  |  |  |     case 'manyToOne': | 
					
						
							|  |  |  |       return createManyToOne(attributeName, attribute, meta, metadata); | 
					
						
							|  |  |  |     case 'manyToMany': | 
					
						
							|  |  |  |       return createManyToMany(attributeName, attribute, meta, metadata); | 
					
						
							|  |  |  |     case 'morphToOne': | 
					
						
							|  |  |  |       return createMorphToOne(attributeName, attribute, meta, metadata); | 
					
						
							|  |  |  |     case 'morphToMany': | 
					
						
							|  |  |  |       return createMorphToMany(attributeName, attribute, meta, metadata); | 
					
						
							|  |  |  |     case 'morphOne': | 
					
						
							|  |  |  |       return createMorphOne(attributeName, attribute, meta, metadata); | 
					
						
							|  |  |  |     case 'morphMany': | 
					
						
							|  |  |  |       return createMorphMany(attributeName, attribute, meta, metadata); | 
					
						
							| 
									
										
										
										
											2022-08-08 23:33:39 +02:00
										 |  |  |     default: { | 
					
						
							|  |  |  |       throw new Error(`Unknown relation ${attribute.relation}`); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-07-26 17:52:59 +02:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-17 18:08:42 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Creates a join column info and add them to the attribute meta | 
					
						
							|  |  |  |  * @param {Object} metadata metadata registry | 
					
						
							|  |  |  |  * @param {Object} param | 
					
						
							|  |  |  |  * @param {Object} param.attribute associated attribute | 
					
						
							|  |  |  |  * @param {string} param.attributeName name of the associated attribute | 
					
						
							|  |  |  |  * @param {Object} param.meta model metadata | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-08-08 15:50:34 +02:00
										 |  |  | const createJoinColum = (metadata, { attribute, attributeName /* meta */ }) => { | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |   const targetMeta = metadata.get(attribute.target); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-07 18:04:39 +02:00
										 |  |  |   const joinColumnName = _.snakeCase(`${attributeName}_id`); | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |   const joinColumn = { | 
					
						
							|  |  |  |     name: joinColumnName, | 
					
						
							|  |  |  |     referencedColumn: 'id', | 
					
						
							|  |  |  |     referencedTable: targetMeta.tableName, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Object.assign(attribute, { owner: true, joinColumn }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (isBidirectional(attribute)) { | 
					
						
							|  |  |  |     const inverseAttribute = targetMeta.attributes[attribute.inversedBy]; | 
					
						
							| 
									
										
										
										
											2021-07-30 20:45:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |     Object.assign(inverseAttribute, { | 
					
						
							|  |  |  |       joinColumn: { | 
					
						
							|  |  |  |         name: joinColumn.referencedColumn, | 
					
						
							|  |  |  |         referencedColumn: joinColumn.name, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-17 18:08:42 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Creates a join table and add it to the attribute meta | 
					
						
							|  |  |  |  * @param {Object} metadata metadata registry | 
					
						
							|  |  |  |  * @param {Object} param | 
					
						
							|  |  |  |  * @param {Object} param.attribute associated attribute | 
					
						
							|  |  |  |  * @param {string} param.attributeName name of the associated attribute | 
					
						
							|  |  |  |  * @param {Object} param.meta model metadata | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  | const createJoinTable = (metadata, { attributeName, attribute, meta }) => { | 
					
						
							|  |  |  |   const targetMeta = metadata.get(attribute.target); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!targetMeta) { | 
					
						
							| 
									
										
										
										
											2021-12-22 19:59:03 +05:30
										 |  |  |     throw new Error(`Unknown target ${attribute.target}`); | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-20 10:17:38 +01:00
										 |  |  |   const joinTableName = getJoinTableName(meta.tableName, attributeName); | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-08 15:50:34 +02:00
										 |  |  |   const joinColumnName = _.snakeCase(`${meta.singularName}_id`); | 
					
						
							| 
									
										
										
										
											2021-07-05 18:35:16 +02:00
										 |  |  |   let inverseJoinColumnName = _.snakeCase(`${targetMeta.singularName}_id`); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-14 11:30:00 +02:00
										 |  |  |   // if relation is self referencing
 | 
					
						
							| 
									
										
										
										
											2021-07-05 18:35:16 +02:00
										 |  |  |   if (joinColumnName === inverseJoinColumnName) { | 
					
						
							|  |  |  |     inverseJoinColumnName = `inv_${inverseJoinColumnName}`; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-19 17:58:00 +02:00
										 |  |  |   const orderColumnName = _.snakeCase(`${targetMeta.singularName}_order`); | 
					
						
							|  |  |  |   let inverseOrderColumnName = _.snakeCase(`${meta.singularName}_order`); | 
					
						
							| 
									
										
										
										
											2022-09-14 11:30:00 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // if relation is self referencing
 | 
					
						
							| 
									
										
										
										
											2022-11-25 11:50:53 +01:00
										 |  |  |   if (attribute.relation === 'manyToMany' && orderColumnName === inverseOrderColumnName) { | 
					
						
							| 
									
										
										
										
											2022-09-14 11:30:00 +02:00
										 |  |  |     inverseOrderColumnName = `inv_${inverseOrderColumnName}`; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-07 18:21:54 +02:00
										 |  |  |   const metadataSchema = { | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |     uid: joinTableName, | 
					
						
							|  |  |  |     tableName: joinTableName, | 
					
						
							|  |  |  |     attributes: { | 
					
						
							| 
									
										
										
										
											2022-07-01 19:20:59 +02:00
										 |  |  |       id: { | 
					
						
							|  |  |  |         type: 'increments', | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |       [joinColumnName]: { | 
					
						
							|  |  |  |         type: 'integer', | 
					
						
							|  |  |  |         column: { | 
					
						
							|  |  |  |           unsigned: true, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       [inverseJoinColumnName]: { | 
					
						
							|  |  |  |         type: 'integer', | 
					
						
							|  |  |  |         column: { | 
					
						
							|  |  |  |           unsigned: true, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       // TODO: add extra pivot attributes -> user should use an intermediate entity
 | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2021-09-16 11:37:44 +02:00
										 |  |  |     indexes: [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         name: `${joinTableName}_fk`, | 
					
						
							|  |  |  |         columns: [joinColumnName], | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         name: `${joinTableName}_inv_fk`, | 
					
						
							|  |  |  |         columns: [inverseJoinColumnName], | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2022-09-20 15:53:17 +02:00
										 |  |  |       { | 
					
						
							|  |  |  |         name: `${joinTableName}_unique`, | 
					
						
							|  |  |  |         columns: [joinColumnName, inverseJoinColumnName], | 
					
						
							|  |  |  |         type: 'unique', | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-09-16 11:37:44 +02:00
										 |  |  |     ], | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |     foreignKeys: [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         name: `${joinTableName}_fk`, | 
					
						
							|  |  |  |         columns: [joinColumnName], | 
					
						
							|  |  |  |         referencedColumns: ['id'], | 
					
						
							|  |  |  |         referencedTable: meta.tableName, | 
					
						
							|  |  |  |         onDelete: 'CASCADE', | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         name: `${joinTableName}_inv_fk`, | 
					
						
							|  |  |  |         columns: [inverseJoinColumnName], | 
					
						
							|  |  |  |         referencedColumns: ['id'], | 
					
						
							|  |  |  |         referencedTable: targetMeta.tableName, | 
					
						
							|  |  |  |         onDelete: 'CASCADE', | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     ], | 
					
						
							| 
									
										
										
										
											2022-09-07 18:21:54 +02:00
										 |  |  |   }; | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const joinTable = { | 
					
						
							|  |  |  |     name: joinTableName, | 
					
						
							|  |  |  |     joinColumn: { | 
					
						
							|  |  |  |       name: joinColumnName, | 
					
						
							|  |  |  |       referencedColumn: 'id', | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     inverseJoinColumn: { | 
					
						
							|  |  |  |       name: inverseJoinColumnName, | 
					
						
							|  |  |  |       referencedColumn: 'id', | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2022-09-26 16:22:22 +02:00
										 |  |  |     pivotColumns: [joinColumnName, inverseJoinColumnName], | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-14 11:30:00 +02:00
										 |  |  |   // order
 | 
					
						
							|  |  |  |   if (isAnyToMany(attribute)) { | 
					
						
							|  |  |  |     metadataSchema.attributes[orderColumnName] = { | 
					
						
							| 
									
										
										
										
											2022-10-26 09:16:22 +02:00
										 |  |  |       type: 'float', | 
					
						
							| 
									
										
										
										
											2022-09-14 11:30:00 +02:00
										 |  |  |       column: { | 
					
						
							|  |  |  |         unsigned: true, | 
					
						
							| 
									
										
										
										
											2022-09-15 10:52:38 +02:00
										 |  |  |         defaultTo: null, | 
					
						
							| 
									
										
										
										
											2022-09-14 11:30:00 +02:00
										 |  |  |       }, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     metadataSchema.indexes.push({ | 
					
						
							|  |  |  |       name: `${joinTableName}_order_fk`, | 
					
						
							|  |  |  |       columns: [orderColumnName], | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     joinTable.orderColumnName = orderColumnName; | 
					
						
							|  |  |  |     joinTable.orderBy = { [orderColumnName]: 'asc' }; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-09-07 18:21:54 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-14 11:30:00 +02:00
										 |  |  |   // inv order
 | 
					
						
							|  |  |  |   if (isBidirectional(attribute) && isManyToAny(attribute)) { | 
					
						
							| 
									
										
										
										
											2022-09-07 18:21:54 +02:00
										 |  |  |     metadataSchema.attributes[inverseOrderColumnName] = { | 
					
						
							| 
									
										
										
										
											2022-10-26 09:16:22 +02:00
										 |  |  |       type: 'float', | 
					
						
							| 
									
										
										
										
											2022-09-07 18:21:54 +02:00
										 |  |  |       column: { | 
					
						
							|  |  |  |         unsigned: true, | 
					
						
							| 
									
										
										
										
											2022-09-15 10:52:38 +02:00
										 |  |  |         defaultTo: null, | 
					
						
							| 
									
										
										
										
											2022-09-07 18:21:54 +02:00
										 |  |  |       }, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     metadataSchema.indexes.push({ | 
					
						
							|  |  |  |       name: `${joinTableName}_order_inv_fk`, | 
					
						
							|  |  |  |       columns: [inverseOrderColumnName], | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     joinTable.inverseOrderColumnName = inverseOrderColumnName; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   metadata.add(metadataSchema); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |   attribute.joinTable = joinTable; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (isBidirectional(attribute)) { | 
					
						
							|  |  |  |     const inverseAttribute = targetMeta.attributes[attribute.inversedBy]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!inverseAttribute) { | 
					
						
							| 
									
										
										
										
											2021-07-08 18:15:32 +02:00
										 |  |  |       throw new Error( | 
					
						
							|  |  |  |         `inversedBy attribute ${attribute.inversedBy} not found target ${targetMeta.uid}` | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-02 02:26:14 +02:00
										 |  |  |     inverseAttribute.joinTable = { | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |       name: joinTableName, | 
					
						
							|  |  |  |       joinColumn: joinTable.inverseJoinColumn, | 
					
						
							|  |  |  |       inverseJoinColumn: joinTable.joinColumn, | 
					
						
							| 
									
										
										
										
											2022-09-26 16:22:22 +02:00
										 |  |  |       pivotColumns: joinTable.pivotColumns, | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2022-09-14 11:30:00 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (isManyToAny(attribute)) { | 
					
						
							|  |  |  |       inverseAttribute.joinTable.orderColumnName = inverseOrderColumnName; | 
					
						
							|  |  |  |       inverseAttribute.joinTable.orderBy = { [inverseOrderColumnName]: 'asc' }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (isAnyToMany(attribute)) { | 
					
						
							|  |  |  |       inverseAttribute.joinTable.inverseOrderColumnName = orderColumnName; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-27 11:56:46 +02:00
										 |  |  | const hasOrderColumn = (attribute) => isAnyToMany(attribute); | 
					
						
							|  |  |  | const hasInverseOrderColumn = (attribute) => isBidirectional(attribute) && isManyToAny(attribute); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  | module.exports = { | 
					
						
							|  |  |  |   createRelation, | 
					
						
							| 
									
										
										
										
											2023-03-19 19:29:05 +01:00
										 |  |  |   isPolymorphic, | 
					
						
							| 
									
										
										
										
											2021-07-05 18:35:16 +02:00
										 |  |  |   isBidirectional, | 
					
						
							| 
									
										
										
										
											2021-08-12 16:12:40 +02:00
										 |  |  |   isOneToAny, | 
					
						
							| 
									
										
										
										
											2022-09-14 11:30:00 +02:00
										 |  |  |   isManyToAny, | 
					
						
							|  |  |  |   isAnyToOne, | 
					
						
							|  |  |  |   isAnyToMany, | 
					
						
							| 
									
										
										
										
											2022-09-27 11:56:46 +02:00
										 |  |  |   hasOrderColumn, | 
					
						
							|  |  |  |   hasInverseOrderColumn, | 
					
						
							| 
									
										
										
										
											2022-12-20 10:17:38 +01:00
										 |  |  |   getJoinTableName, | 
					
						
							| 
									
										
										
										
											2021-07-01 14:32:50 +02:00
										 |  |  | }; |