UNPKG

@cheetah.js/orm

Version:
243 lines (242 loc) 11.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SqlJoinManager = void 0; class SqlJoinManager { constructor(entityStorage, statements, entity, model, driver, logger, conditionBuilder, columnManager, modelTransformer, getOriginalColumnsCallback, getAliasCallback) { this.entityStorage = entityStorage; this.statements = statements; this.entity = entity; this.model = model; this.driver = driver; this.logger = logger; this.conditionBuilder = conditionBuilder; this.columnManager = columnManager; this.modelTransformer = modelTransformer; this.getOriginalColumnsCallback = getOriginalColumnsCallback; this.getAliasCallback = getAliasCallback; } addJoinForRelationshipPath(relationshipPath) { const relationshipNames = relationshipPath.split('.'); let currentEntity = this.entity; let currentAlias = this.statements.alias; for (const relationshipName of relationshipNames) { const relationship = currentEntity.relations.find((rel) => rel.propertyKey === relationshipName); if (!relationship) { throw new Error(`Relationship "${relationshipName}" not found in entity "${currentEntity.tableName}"`); } const statement = this.statements.strategy === 'joined' ? this.statements.join : this.statements.selectJoin; const nameAliasProperty = this.statements.strategy === 'joined' ? 'joinAlias' : 'alias'; const existingJoin = statement?.find((j) => j.joinProperty === relationshipName && j.originAlias === currentAlias); if (existingJoin) { currentAlias = existingJoin[nameAliasProperty]; currentEntity = this.entityStorage.get(relationship.entity()); continue; } this.applyJoin(relationship, {}, currentAlias); const newStatement = this.statements.strategy === 'joined' ? this.statements.join : this.statements.selectJoin; currentAlias = newStatement[newStatement.length - 1][nameAliasProperty]; currentEntity = this.entityStorage.get(relationship.entity()); } } applyJoin(relationShip, value, alias) { const { tableName, schema } = this.getTableName(); const { tableName: joinTableName, schema: joinSchema, hooks: joinHooks, } = this.entityStorage.get(relationShip.entity()) || { tableName: relationShip.entity().name.toLowerCase(), schema: 'public', }; const originPrimaryKey = this.getPrimaryKey(); const joinAlias = this.getAliasCallback(joinTableName); const joinWhere = this.conditionBuilder.build(value, joinAlias, relationShip.entity()); const on = this.buildJoinOn(relationShip, alias, joinAlias, originPrimaryKey); if (this.statements.strategy === 'joined') { this.addJoinedJoin(relationShip, joinTableName, joinSchema, joinAlias, joinWhere, on, alias, schema, tableName, joinHooks); } else { this.addSelectJoin(relationShip, joinTableName, joinSchema, joinAlias, joinWhere, originPrimaryKey, alias, joinHooks); } return joinWhere; } async handleSelectJoin(entities, models) { if (!this.statements.selectJoin || this.statements.selectJoin.length === 0) { return; } for (const join of this.statements.selectJoin.reverse()) { await this.processSelectJoin(join, entities, models); } return models; } getPathForSelectJoin(selectJoin) { const path = this.getPathForSelectJoinRecursive(selectJoin); return path.reverse(); } buildJoinOn(relationShip, alias, joinAlias, originPrimaryKey) { let on = ''; switch (relationShip.relation) { case "one-to-many": on = `${joinAlias}."${this.getFkKey(relationShip)}" = ${alias}."${originPrimaryKey}"`; break; case "many-to-one": on = `${alias}."${relationShip.columnName}" = ${joinAlias}."${this.getFkKey(relationShip)}"`; break; } return on; } addJoinedJoin(relationShip, joinTableName, joinSchema, joinAlias, joinWhere, on, alias, schema, tableName, joinHooks) { this.statements.join = this.statements.join || []; this.statements.join.push({ joinAlias: joinAlias, joinTable: joinTableName, joinSchema: joinSchema || 'public', joinWhere: joinWhere, joinProperty: relationShip.propertyKey, originAlias: alias, originSchema: schema, originTable: tableName, propertyKey: relationShip.propertyKey, joinEntity: relationShip.entity(), type: 'LEFT', on, originalEntity: relationShip.originalEntity, hooks: joinHooks, }); } addSelectJoin(relationShip, joinTableName, joinSchema, joinAlias, joinWhere, originPrimaryKey, alias, joinHooks) { this.statements.selectJoin = this.statements.selectJoin || []; this.statements.selectJoin.push({ statement: 'select', columns: this.getOriginalColumnsCallback().filter(column => column.startsWith(`${relationShip.propertyKey}`)).map(column => column.split('.')[1]) || [], table: `"${joinSchema || 'public'}"."${joinTableName}"`, alias: joinAlias, where: joinWhere, joinProperty: relationShip.propertyKey, fkKey: this.getFkKey(relationShip), primaryKey: originPrimaryKey, originAlias: alias, originProperty: relationShip.propertyKey, joinEntity: relationShip.entity(), originEntity: relationShip.originalEntity, hooks: joinHooks, }); } async processSelectJoin(join, entities, models) { let ids = this.getIds(join, entities, models); if (Array.isArray(ids)) { ids = ids.map((id) => this.formatValue(id)).join(', '); } this.updateJoinWhere(join, ids); this.updateJoinColumns(join); const child = await this.driver.executeStatement(join); this.logger.debug(`SQL: ${child.sql} [${Date.now() - child.startTime}ms]`); this.attachJoinResults(join, child, models); } getIds(join, entities, models) { let ids = entities[`${join.originAlias}_${join.primaryKey}`]; if (typeof ids === 'undefined') { const selectJoined = this.statements.selectJoin.find(j => j.joinEntity === join.originEntity); if (!selectJoined) { return undefined; } ids = this.findIdRecursively(models, selectJoined, join); } return ids; } updateJoinWhere(join, ids) { if (join.where) { join.where = `${join.where} AND ${join.alias}."${join.fkKey}" IN (${ids})`; } else { join.where = `${join.alias}."${join.fkKey}" IN (${ids})`; } } updateJoinColumns(join) { if (join.columns && join.columns.length > 0) { join.columns = join.columns.map((column) => `${join.alias}."${column}" as "${join.alias}_${column}"`); } else { join.columns = this.columnManager.getColumnsForEntity(join.joinEntity, join.alias); } } attachJoinResults(join, child, models) { const property = this.entityStorage.get(this.model).relations.find((rel) => rel.propertyKey === join.joinProperty); const values = child.query.rows.map((row) => this.modelTransformer.transform(join.joinEntity, join, row)); const path = this.getPathForSelectJoin(join); this.setValueByPath(models, path, property?.type === Array ? [...values] : values[0]); } setValueByPath(obj, path, value) { let currentObj = obj; for (let i = 0; i < path.length - 1; i++) { const key = path[i]; currentObj[key] = currentObj[key] || {}; currentObj = currentObj[key]; } currentObj[path[path.length - 1]] = value; } getPathForSelectJoinRecursive(selectJoin) { const originJoin = this.statements.selectJoin.find(j => j.joinEntity === selectJoin.originEntity); let pathInJoin = []; if (!originJoin) { return [selectJoin.joinProperty]; } if (originJoin.originEntity !== this.statements.originEntity) { pathInJoin = this.getPathForSelectJoinRecursive(originJoin); } return [selectJoin.joinProperty, ...pathInJoin]; } findIdRecursively(models, selectJoined, join) { let ids = models[selectJoined.originProperty][join.primaryKey]; if (typeof ids === 'undefined') { const nextSelectJoined = this.statements.selectJoin.find(j => j.joinEntity === selectJoined.originEntity); if (nextSelectJoined) { ids = this.findIdRecursively(models, nextSelectJoined, join); } } return ids; } getFkKey(relationShip) { if (typeof relationShip.fkKey === 'undefined') { return 'id'; } if (typeof relationShip.fkKey === 'string') { return relationShip.fkKey; } const match = /\.(?<propriedade>[\w]+)/.exec(relationShip.fkKey.toString()); const propertyKey = match ? match.groups.propriedade : ''; const entity = this.entityStorage.get(relationShip.entity()); if (!entity) { throw new Error(`Entity not found in storage for relationship. ` + `Make sure the entity ${relationShip.entity().name} is decorated with @Entity()`); } const property = Object.entries(entity.properties).find(([key, _value]) => key === propertyKey)?.[1]; if (property) { return property.options.columnName; } const relation = entity.relations.find(rel => rel.propertyKey === propertyKey); if (relation && relation.columnName) { return relation.columnName; } throw new Error(`Property or relation "${propertyKey}" not found in entity "${entity.tableName}". ` + `Available properties: ${Object.keys(entity.properties).join(', ')}. ` + `Available relations: ${entity.relations.map(r => r.propertyKey).join(', ')}`); } getTableName() { const tableName = this.entity.tableName || this.model.name.toLowerCase(); const schema = this.entity.schema || 'public'; return { tableName, schema }; } getPrimaryKey() { for (const prop in this.entity.properties) { if (this.entity.properties[prop].options.isPrimary) { return prop; } } return 'id'; } formatValue(value) { return (typeof value === 'string') ? `'${value}'` : value; } } exports.SqlJoinManager = SqlJoinManager;