UNPKG

typeorm

Version:

Data-Mapper ORM for TypeScript and ES2021+. Supports MySQL/MariaDB, PostgreSQL, MS SQL Server, Oracle, SAP HANA, SQLite, MongoDB databases.

369 lines (367 loc) • 15.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SoftDeleteQueryBuilder = void 0; const QueryBuilder_1 = require("./QueryBuilder"); const UpdateResult_1 = require("./result/UpdateResult"); const ReturningStatementNotSupportedError_1 = require("../error/ReturningStatementNotSupportedError"); const ReturningResultsEntityUpdator_1 = require("./ReturningResultsEntityUpdator"); const LimitOnUpdateNotSupportedError_1 = require("../error/LimitOnUpdateNotSupportedError"); const MissingDeleteDateColumnError_1 = require("../error/MissingDeleteDateColumnError"); const UpdateValuesMissingError_1 = require("../error/UpdateValuesMissingError"); const error_1 = require("../error"); const DriverUtils_1 = require("../driver/DriverUtils"); const InstanceChecker_1 = require("../util/InstanceChecker"); /** * Allows to build complex sql queries in a fashion way and execute those queries. */ class SoftDeleteQueryBuilder extends QueryBuilder_1.QueryBuilder { // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- constructor(connectionOrQueryBuilder, queryRunner) { super(connectionOrQueryBuilder, queryRunner); this["@instanceof"] = Symbol.for("SoftDeleteQueryBuilder"); this.expressionMap.aliasNamePrefixingEnabled = false; } // ------------------------------------------------------------------------- // Public Implemented Methods // ------------------------------------------------------------------------- /** * Gets generated SQL query without parameters being replaced. */ getQuery() { let sql = this.createUpdateExpression(); sql += this.createCteExpression(); sql += this.createOrderByExpression(); sql += this.createLimitExpression(); return this.replacePropertyNamesForTheWholeQuery(sql.trim()); } /** * Executes sql generated by query builder and returns raw database results. */ async execute() { const queryRunner = this.obtainQueryRunner(); let transactionStartedByUs = false; try { // start transaction if it was enabled if (this.expressionMap.useTransaction === true && queryRunner.isTransactionActive === false) { await queryRunner.startTransaction(); transactionStartedByUs = true; } // call before soft remove and recover methods in listeners and subscribers if (this.expressionMap.callListeners === true && this.expressionMap.mainAlias.hasMetadata) { if (this.expressionMap.queryType === "soft-delete") await queryRunner.broadcaster.broadcast("BeforeSoftRemove", this.expressionMap.mainAlias.metadata); else if (this.expressionMap.queryType === "restore") await queryRunner.broadcaster.broadcast("BeforeRecover", this.expressionMap.mainAlias.metadata); } // if update entity mode is enabled we may need extra columns for the returning statement const returningResultsEntityUpdator = new ReturningResultsEntityUpdator_1.ReturningResultsEntityUpdator(queryRunner, this.expressionMap); if (this.expressionMap.updateEntity === true && this.expressionMap.mainAlias.hasMetadata && this.expressionMap.whereEntities.length > 0) { this.expressionMap.extraReturningColumns = returningResultsEntityUpdator.getSoftDeletionReturningColumns(); } // execute update query const [sql, parameters] = this.getQueryAndParameters(); const queryResult = await queryRunner.query(sql, parameters, true); const updateResult = UpdateResult_1.UpdateResult.from(queryResult); // if we are updating entities and entity updation is enabled we must update some of entity columns (like version, update date, etc.) if (this.expressionMap.updateEntity === true && this.expressionMap.mainAlias.hasMetadata && this.expressionMap.whereEntities.length > 0) { await returningResultsEntityUpdator.update(updateResult, this.expressionMap.whereEntities); } // call after soft remove and recover methods in listeners and subscribers if (this.expressionMap.callListeners === true && this.expressionMap.mainAlias.hasMetadata) { if (this.expressionMap.queryType === "soft-delete") await queryRunner.broadcaster.broadcast("AfterSoftRemove", this.expressionMap.mainAlias.metadata); else if (this.expressionMap.queryType === "restore") await queryRunner.broadcaster.broadcast("AfterRecover", this.expressionMap.mainAlias.metadata); } // close transaction if we started it if (transactionStartedByUs) await queryRunner.commitTransaction(); return updateResult; } catch (error) { // rollback transaction if we started it if (transactionStartedByUs) { try { await queryRunner.rollbackTransaction(); } catch (rollbackError) { } } throw error; } finally { if (queryRunner !== this.queryRunner) { // means we created our own query runner await queryRunner.release(); } } } // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- /** * Specifies FROM which entity's table select/update/delete/soft-delete will be executed. * Also sets a main string alias of the selection data. */ from(entityTarget, aliasName) { entityTarget = InstanceChecker_1.InstanceChecker.isEntitySchema(entityTarget) ? entityTarget.options.name : entityTarget; const mainAlias = this.createFromAlias(entityTarget, aliasName); this.expressionMap.setMainAlias(mainAlias); return this; } /** * Sets WHERE condition in the query builder. * If you had previously WHERE expression defined, * calling this function will override previously set WHERE conditions. * Additionally you can add parameters used in where expression. */ where(where, parameters) { this.expressionMap.wheres = []; // don't move this block below since computeWhereParameter can add where expressions const condition = this.getWhereCondition(where); if (condition) this.expressionMap.wheres = [ { type: "simple", condition: condition }, ]; if (parameters) this.setParameters(parameters); return this; } /** * Adds new AND WHERE condition in the query builder. * Additionally you can add parameters used in where expression. */ andWhere(where, parameters) { this.expressionMap.wheres.push({ type: "and", condition: this.getWhereCondition(where), }); if (parameters) this.setParameters(parameters); return this; } /** * Adds new OR WHERE condition in the query builder. * Additionally you can add parameters used in where expression. */ orWhere(where, parameters) { this.expressionMap.wheres.push({ type: "or", condition: this.getWhereCondition(where), }); if (parameters) this.setParameters(parameters); return this; } /** * Adds new AND WHERE with conditions for the given ids. */ whereInIds(ids) { return this.where(this.getWhereInIdsCondition(ids)); } /** * Adds new AND WHERE with conditions for the given ids. */ andWhereInIds(ids) { return this.andWhere(this.getWhereInIdsCondition(ids)); } /** * Adds new OR WHERE with conditions for the given ids. */ orWhereInIds(ids) { return this.orWhere(this.getWhereInIdsCondition(ids)); } /** * Optional returning/output clause. */ output(output) { return this.returning(output); } /** * Optional returning/output clause. */ returning(returning) { // not all databases support returning/output cause if (!this.connection.driver.isReturningSqlSupported("update")) { throw new ReturningStatementNotSupportedError_1.ReturningStatementNotSupportedError(); } this.expressionMap.returning = returning; return this; } /** * Sets ORDER BY condition in the query builder. * If you had previously ORDER BY expression defined, * calling this function will override previously set ORDER BY conditions. */ orderBy(sort, order = "ASC", nulls) { if (sort) { if (typeof sort === "object") { this.expressionMap.orderBys = sort; } else { if (nulls) { this.expressionMap.orderBys = { [sort]: { order, nulls }, }; } else { this.expressionMap.orderBys = { [sort]: order }; } } } else { this.expressionMap.orderBys = {}; } return this; } /** * Adds ORDER BY condition in the query builder. */ addOrderBy(sort, order = "ASC", nulls) { if (nulls) { this.expressionMap.orderBys[sort] = { order, nulls }; } else { this.expressionMap.orderBys[sort] = order; } return this; } /** * Sets LIMIT - maximum number of rows to be selected. */ limit(limit) { this.expressionMap.limit = limit; return this; } /** * Indicates if entity must be updated after update operation. * This may produce extra query or use RETURNING / OUTPUT statement (depend on database). * Enabled by default. */ whereEntity(entity) { if (!this.expressionMap.mainAlias.hasMetadata) throw new error_1.TypeORMError(`.whereEntity method can only be used on queries which update real entity table.`); this.expressionMap.wheres = []; const entities = Array.isArray(entity) ? entity : [entity]; entities.forEach((entity) => { const entityIdMap = this.expressionMap.mainAlias.metadata.getEntityIdMap(entity); if (!entityIdMap) throw new error_1.TypeORMError(`Provided entity does not have ids set, cannot perform operation.`); this.orWhereInIds(entityIdMap); }); this.expressionMap.whereEntities = entities; return this; } /** * Indicates if entity must be updated after update operation. * This may produce extra query or use RETURNING / OUTPUT statement (depend on database). * Enabled by default. */ updateEntity(enabled) { this.expressionMap.updateEntity = enabled; return this; } // ------------------------------------------------------------------------- // Protected Methods // ------------------------------------------------------------------------- /** * Creates UPDATE express used to perform insert query. */ createUpdateExpression() { const metadata = this.expressionMap.mainAlias.hasMetadata ? this.expressionMap.mainAlias.metadata : undefined; if (!metadata) throw new error_1.TypeORMError(`Cannot get entity metadata for the given alias "${this.expressionMap.mainAlias}"`); if (!metadata.deleteDateColumn) { throw new MissingDeleteDateColumnError_1.MissingDeleteDateColumnError(metadata); } // prepare columns and values to be updated const updateColumnAndValues = []; switch (this.expressionMap.queryType) { case "soft-delete": updateColumnAndValues.push(this.escape(metadata.deleteDateColumn.databaseName) + " = CURRENT_TIMESTAMP"); break; case "restore": updateColumnAndValues.push(this.escape(metadata.deleteDateColumn.databaseName) + " = NULL"); break; default: throw new error_1.TypeORMError(`The queryType must be "soft-delete" or "restore"`); } if (metadata.versionColumn) updateColumnAndValues.push(this.escape(metadata.versionColumn.databaseName) + " = " + this.escape(metadata.versionColumn.databaseName) + " + 1"); if (metadata.updateDateColumn) updateColumnAndValues.push(this.escape(metadata.updateDateColumn.databaseName) + " = CURRENT_TIMESTAMP"); // todo: fix issue with CURRENT_TIMESTAMP(6) being used, can "DEFAULT" be used?! if (updateColumnAndValues.length <= 0) { throw new UpdateValuesMissingError_1.UpdateValuesMissingError(); } // get a table name and all column database names const whereExpression = this.createWhereExpression(); const returningExpression = this.createReturningExpression("update"); if (returningExpression === "") { return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")}${whereExpression}`; // todo: how do we replace aliases in where to nothing? } if (this.connection.driver.options.type === "mssql") { return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")} OUTPUT ${returningExpression}${whereExpression}`; } return `UPDATE ${this.getTableName(this.getMainTableName())} SET ${updateColumnAndValues.join(", ")}${whereExpression} RETURNING ${returningExpression}`; } /** * Creates "ORDER BY" part of SQL query. */ createOrderByExpression() { const orderBys = this.expressionMap.orderBys; if (Object.keys(orderBys).length > 0) return (" ORDER BY " + Object.keys(orderBys) .map((columnName) => { if (typeof orderBys[columnName] === "string") { return (this.replacePropertyNames(columnName) + " " + orderBys[columnName]); } else { return (this.replacePropertyNames(columnName) + " " + orderBys[columnName].order + " " + orderBys[columnName].nulls); } }) .join(", ")); return ""; } /** * Creates "LIMIT" parts of SQL query. */ createLimitExpression() { const limit = this.expressionMap.limit; if (limit) { if (DriverUtils_1.DriverUtils.isMySQLFamily(this.connection.driver)) { return " LIMIT " + limit; } else { throw new LimitOnUpdateNotSupportedError_1.LimitOnUpdateNotSupportedError(); } } return ""; } } exports.SoftDeleteQueryBuilder = SoftDeleteQueryBuilder; //# sourceMappingURL=SoftDeleteQueryBuilder.js.map