UNPKG

typeorm

Version:

Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, MongoDB databases.

1,023 lines • 56.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.QueryBuilder = void 0; var tslib_1 = require("tslib"); var QueryExpressionMap_1 = require("./QueryExpressionMap"); var Brackets_1 = require("./Brackets"); var PostgresDriver_1 = require("../driver/postgres/PostgresDriver"); var CockroachDriver_1 = require("../driver/cockroachdb/CockroachDriver"); var SqlServerDriver_1 = require("../driver/sqlserver/SqlServerDriver"); var OracleDriver_1 = require("../driver/oracle/OracleDriver"); var EntitySchema_1 = require("../entity-schema/EntitySchema"); var FindOperator_1 = require("../find-options/FindOperator"); var In_1 = require("../find-options/operator/In"); var EntityColumnNotFound_1 = require("../error/EntityColumnNotFound"); var error_1 = require("../error"); // todo: completely cover query builder with tests // todo: entityOrProperty can be target name. implement proper behaviour if it is. // todo: check in persistment if id exist on object and throw exception (can be in partial selection?) // todo: fix problem with long aliases eg getMaxIdentifierLength // todo: fix replacing in .select("COUNT(post.id) AS cnt") statement // todo: implement joinAlways in relations and relationId // todo: finish partial selection // todo: sugar methods like: .addCount and .selectCount, selectCountAndMap, selectSum, selectSumAndMap, ... // todo: implement @Select decorator // todo: add select and map functions // todo: implement relation/entity loading and setting them into properties within a separate query // .loadAndMap("post.categories", "post.categories", qb => ...) // .loadAndMap("post.categories", Category, qb => ...) /** * Allows to build complex sql queries in a fashion way and execute those queries. */ var QueryBuilder = /** @class */ (function () { /** * QueryBuilder can be initialized from given Connection and QueryRunner objects or from given other QueryBuilder. */ function QueryBuilder(connectionOrQueryBuilder, queryRunner) { /** * Memo to help keep place of current parameter index for `createParameter` */ this.parameterIndex = 0; if (connectionOrQueryBuilder instanceof QueryBuilder) { this.connection = connectionOrQueryBuilder.connection; this.queryRunner = connectionOrQueryBuilder.queryRunner; this.expressionMap = connectionOrQueryBuilder.expressionMap.clone(); } else { this.connection = connectionOrQueryBuilder; this.queryRunner = queryRunner; this.expressionMap = new QueryExpressionMap_1.QueryExpressionMap(this.connection); } } Object.defineProperty(QueryBuilder.prototype, "alias", { // ------------------------------------------------------------------------- // Accessors // ------------------------------------------------------------------------- /** * Gets the main alias string used in this query builder. */ get: function () { if (!this.expressionMap.mainAlias) throw new error_1.TypeORMError("Main alias is not set"); // todo: better exception return this.expressionMap.mainAlias.name; }, enumerable: false, configurable: true }); /** * Creates SELECT query and selects given data. * Replaces all previous selections if they exist. */ QueryBuilder.prototype.select = function (selection, selectionAliasName) { this.expressionMap.queryType = "select"; if (Array.isArray(selection)) { this.expressionMap.selects = selection.map(function (selection) { return ({ selection: selection }); }); } else if (selection) { this.expressionMap.selects = [{ selection: selection, aliasName: selectionAliasName }]; } // loading it dynamically because of circular issue var SelectQueryBuilderCls = require("./SelectQueryBuilder").SelectQueryBuilder; if (this instanceof SelectQueryBuilderCls) return this; return new SelectQueryBuilderCls(this); }; /** * Creates INSERT query. */ QueryBuilder.prototype.insert = function () { this.expressionMap.queryType = "insert"; // loading it dynamically because of circular issue var InsertQueryBuilderCls = require("./InsertQueryBuilder").InsertQueryBuilder; if (this instanceof InsertQueryBuilderCls) return this; return new InsertQueryBuilderCls(this); }; /** * Creates UPDATE query and applies given update values. */ QueryBuilder.prototype.update = function (entityOrTableNameUpdateSet, maybeUpdateSet) { var updateSet = maybeUpdateSet ? maybeUpdateSet : entityOrTableNameUpdateSet; entityOrTableNameUpdateSet = entityOrTableNameUpdateSet instanceof EntitySchema_1.EntitySchema ? entityOrTableNameUpdateSet.options.name : entityOrTableNameUpdateSet; if (entityOrTableNameUpdateSet instanceof Function || typeof entityOrTableNameUpdateSet === "string") { var mainAlias = this.createFromAlias(entityOrTableNameUpdateSet); this.expressionMap.setMainAlias(mainAlias); } this.expressionMap.queryType = "update"; this.expressionMap.valuesSet = updateSet; // loading it dynamically because of circular issue var UpdateQueryBuilderCls = require("./UpdateQueryBuilder").UpdateQueryBuilder; if (this instanceof UpdateQueryBuilderCls) return this; return new UpdateQueryBuilderCls(this); }; /** * Creates DELETE query. */ QueryBuilder.prototype.delete = function () { this.expressionMap.queryType = "delete"; // loading it dynamically because of circular issue var DeleteQueryBuilderCls = require("./DeleteQueryBuilder").DeleteQueryBuilder; if (this instanceof DeleteQueryBuilderCls) return this; return new DeleteQueryBuilderCls(this); }; QueryBuilder.prototype.softDelete = function () { this.expressionMap.queryType = "soft-delete"; // loading it dynamically because of circular issue var SoftDeleteQueryBuilderCls = require("./SoftDeleteQueryBuilder").SoftDeleteQueryBuilder; if (this instanceof SoftDeleteQueryBuilderCls) return this; return new SoftDeleteQueryBuilderCls(this); }; QueryBuilder.prototype.restore = function () { this.expressionMap.queryType = "restore"; // loading it dynamically because of circular issue var SoftDeleteQueryBuilderCls = require("./SoftDeleteQueryBuilder").SoftDeleteQueryBuilder; if (this instanceof SoftDeleteQueryBuilderCls) return this; return new SoftDeleteQueryBuilderCls(this); }; /** * Sets entity's relation with which this query builder gonna work. */ QueryBuilder.prototype.relation = function (entityTargetOrPropertyPath, maybePropertyPath) { var entityTarget = arguments.length === 2 ? entityTargetOrPropertyPath : undefined; var propertyPath = arguments.length === 2 ? maybePropertyPath : entityTargetOrPropertyPath; this.expressionMap.queryType = "relation"; this.expressionMap.relationPropertyPath = propertyPath; if (entityTarget) { var mainAlias = this.createFromAlias(entityTarget); this.expressionMap.setMainAlias(mainAlias); } // loading it dynamically because of circular issue var RelationQueryBuilderCls = require("./RelationQueryBuilder").RelationQueryBuilder; if (this instanceof RelationQueryBuilderCls) return this; return new RelationQueryBuilderCls(this); }; /** * Checks if given relation or relations exist in the entity. * Returns true if relation exists, false otherwise. * * todo: move this method to manager? or create a shortcut? */ QueryBuilder.prototype.hasRelation = function (target, relation) { var entityMetadata = this.connection.getMetadata(target); var relations = Array.isArray(relation) ? relation : [relation]; return relations.every(function (relation) { return !!entityMetadata.findRelationWithPropertyPath(relation); }); }; /** * Check the existence of a parameter for this query builder. */ QueryBuilder.prototype.hasParameter = function (key) { var _a; return ((_a = this.parentQueryBuilder) === null || _a === void 0 ? void 0 : _a.hasParameter(key)) || key in this.expressionMap.parameters; }; /** * Sets parameter name and its value. * * The key for this parametere may contain numbers, letters, underscores, or periods. */ QueryBuilder.prototype.setParameter = function (key, value) { if (value instanceof Function) { throw new error_1.TypeORMError("Function parameter isn't supported in the parameters. Please check \"" + key + "\" parameter."); } if (!key.match(/^([A-Za-z0-9_.]+)$/)) { throw new error_1.TypeORMError("QueryBuilder parameter keys may only contain numbers, letters, underscores, or periods."); } if (this.parentQueryBuilder) { this.parentQueryBuilder.setParameter(key, value); } this.expressionMap.parameters[key] = value; return this; }; /** * Adds all parameters from the given object. */ QueryBuilder.prototype.setParameters = function (parameters) { var e_1, _a; try { for (var _b = tslib_1.__values(Object.entries(parameters)), _c = _b.next(); !_c.done; _c = _b.next()) { var _d = tslib_1.__read(_c.value, 2), key = _d[0], value = _d[1]; this.setParameter(key, value); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } return this; }; QueryBuilder.prototype.createParameter = function (value) { var parameterName; do { parameterName = "orm_param_" + this.parameterIndex++; } while (this.hasParameter(parameterName)); this.setParameter(parameterName, value); return ":" + parameterName; }; /** * Adds native parameters from the given object. * * @deprecated Use `setParameters` instead */ QueryBuilder.prototype.setNativeParameters = function (parameters) { var _this = this; // set parent query builder parameters as well in sub-query mode if (this.parentQueryBuilder) { this.parentQueryBuilder.setNativeParameters(parameters); } Object.keys(parameters).forEach(function (key) { _this.expressionMap.nativeParameters[key] = parameters[key]; }); return this; }; /** * Gets all parameters. */ QueryBuilder.prototype.getParameters = function () { var parameters = Object.assign({}, this.expressionMap.parameters); // add discriminator column parameter if it exist if (this.expressionMap.mainAlias && this.expressionMap.mainAlias.hasMetadata) { var metadata = this.expressionMap.mainAlias.metadata; if (metadata.discriminatorColumn && metadata.parentEntityMetadata) { var values = metadata.childEntityMetadatas .filter(function (childMetadata) { return childMetadata.discriminatorColumn; }) .map(function (childMetadata) { return childMetadata.discriminatorValue; }); values.push(metadata.discriminatorValue); parameters["discriminatorColumnValues"] = values; } } return parameters; }; /** * Prints sql to stdout using console.log. */ QueryBuilder.prototype.printSql = function () { var _a = tslib_1.__read(this.getQueryAndParameters(), 2), query = _a[0], parameters = _a[1]; this.connection.logger.logQuery(query, parameters); return this; }; /** * Gets generated sql that will be executed. * Parameters in the query are escaped for the currently used driver. */ QueryBuilder.prototype.getSql = function () { return this.getQueryAndParameters()[0]; }; /** * Gets query to be executed with all parameters used in it. */ QueryBuilder.prototype.getQueryAndParameters = function () { // this execution order is important because getQuery method generates this.expressionMap.nativeParameters values var query = this.getQuery(); var parameters = this.getParameters(); return this.connection.driver.escapeQueryWithParameters(query, parameters, this.expressionMap.nativeParameters); }; /** * Executes sql generated by query builder and returns raw database results. */ QueryBuilder.prototype.execute = function () { return tslib_1.__awaiter(this, void 0, void 0, function () { var _a, sql, parameters, queryRunner; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: _a = tslib_1.__read(this.getQueryAndParameters(), 2), sql = _a[0], parameters = _a[1]; queryRunner = this.obtainQueryRunner(); _b.label = 1; case 1: _b.trys.push([1, , 3, 6]); return [4 /*yield*/, queryRunner.query(sql, parameters)]; case 2: return [2 /*return*/, _b.sent()]; // await is needed here because we are using finally case 3: if (!(queryRunner !== this.queryRunner)) return [3 /*break*/, 5]; return [4 /*yield*/, queryRunner.release()]; case 4: _b.sent(); _b.label = 5; case 5: return [7 /*endfinally*/]; case 6: return [2 /*return*/]; } }); }); }; /** * Creates a completely new query builder. * Uses same query runner as current QueryBuilder. */ QueryBuilder.prototype.createQueryBuilder = function () { return new this.constructor(this.connection, this.queryRunner); }; /** * Clones query builder as it is. * Note: it uses new query runner, if you want query builder that uses exactly same query runner, * you can create query builder using its constructor, for example new SelectQueryBuilder(queryBuilder) * where queryBuilder is cloned QueryBuilder. */ QueryBuilder.prototype.clone = function () { return new this.constructor(this); }; /** * Includes a Query comment in the query builder. This is helpful for debugging purposes, * such as finding a specific query in the database server's logs, or for categorization using * an APM product. */ QueryBuilder.prototype.comment = function (comment) { this.expressionMap.comment = comment; return this; }; /** * Disables escaping. */ QueryBuilder.prototype.disableEscaping = function () { this.expressionMap.disableEscaping = false; return this; }; /** * Escapes table name, column name or alias name using current database's escaping character. */ QueryBuilder.prototype.escape = function (name) { if (!this.expressionMap.disableEscaping) return name; return this.connection.driver.escape(name); }; /** * Sets or overrides query builder's QueryRunner. */ QueryBuilder.prototype.setQueryRunner = function (queryRunner) { this.queryRunner = queryRunner; return this; }; /** * Indicates if listeners and subscribers must be called before and after query execution. * Enabled by default. */ QueryBuilder.prototype.callListeners = function (enabled) { this.expressionMap.callListeners = enabled; return this; }; /** * If set to true the query will be wrapped into a transaction. */ QueryBuilder.prototype.useTransaction = function (enabled) { this.expressionMap.useTransaction = enabled; return this; }; // ------------------------------------------------------------------------- // Protected Methods // ------------------------------------------------------------------------- /** * Gets escaped table name with schema name if SqlServer driver used with custom * schema name, otherwise returns escaped table name. */ QueryBuilder.prototype.getTableName = function (tablePath) { var _this = this; return tablePath.split(".") .map(function (i) { // this condition need because in SQL Server driver when custom database name was specified and schema name was not, we got `dbName..tableName` string, and doesn't need to escape middle empty string if (i === "") return i; return _this.escape(i); }).join("."); }; /** * Gets name of the table where insert should be performed. */ QueryBuilder.prototype.getMainTableName = function () { if (!this.expressionMap.mainAlias) throw new error_1.TypeORMError("Entity where values should be inserted is not specified. Call \"qb.into(entity)\" method to specify it."); if (this.expressionMap.mainAlias.hasMetadata) return this.expressionMap.mainAlias.metadata.tablePath; return this.expressionMap.mainAlias.tablePath; }; /** * Specifies FROM which entity's table select/update/delete will be executed. * Also sets a main string alias of the selection data. */ QueryBuilder.prototype.createFromAlias = function (entityTarget, aliasName) { // if table has a metadata then find it to properly escape its properties // const metadata = this.connection.entityMetadatas.find(metadata => metadata.tableName === tableName); if (this.connection.hasMetadata(entityTarget)) { var metadata = this.connection.getMetadata(entityTarget); return this.expressionMap.createAlias({ type: "from", name: aliasName, metadata: this.connection.getMetadata(entityTarget), tablePath: metadata.tablePath }); } else { if (typeof entityTarget === "string") { var isSubquery = entityTarget.substr(0, 1) === "(" && entityTarget.substr(-1) === ")"; return this.expressionMap.createAlias({ type: "from", name: aliasName, tablePath: !isSubquery ? entityTarget : undefined, subQuery: isSubquery ? entityTarget : undefined, }); } var subQueryBuilder = entityTarget(this.subQuery()); this.setParameters(subQueryBuilder.getParameters()); var subquery = subQueryBuilder.getQuery(); return this.expressionMap.createAlias({ type: "from", name: aliasName, subQuery: subquery }); } }; /** * Replaces all entity's propertyName to name in the given statement. */ QueryBuilder.prototype.replacePropertyNames = function (statement) { var e_2, _a; var _this = this; // Escape special characters in regular expressions // Per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping var escapeRegExp = function (s) { return s.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); }; var _loop_1 = function (alias) { var e_3, _d, e_4, _e, e_5, _f, e_6, _g, e_7, _h, e_8, _j; if (!alias.hasMetadata) return "continue"; var replaceAliasNamePrefix = this_1.expressionMap.aliasNamePrefixingEnabled ? alias.name + "." : ""; var replacementAliasNamePrefix = this_1.expressionMap.aliasNamePrefixingEnabled ? this_1.escape(alias.name) + "." : ""; var replacements = {}; try { // Insert & overwrite the replacements from least to most relevant in our replacements object. // To do this we iterate and overwrite in the order of relevance. // Least to Most Relevant: // * Relation Property Path to first join column key // * Relation Property Path + Column Path // * Column Database Name // * Column Propety Name // * Column Property Path for (var _k = (e_3 = void 0, tslib_1.__values(alias.metadata.relations)), _l = _k.next(); !_l.done; _l = _k.next()) { var relation = _l.value; if (relation.joinColumns.length > 0) replacements[relation.propertyPath] = relation.joinColumns[0].databaseName; } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (_l && !_l.done && (_d = _k.return)) _d.call(_k); } finally { if (e_3) throw e_3.error; } } try { for (var _m = (e_4 = void 0, tslib_1.__values(alias.metadata.relations)), _o = _m.next(); !_o.done; _o = _m.next()) { var relation = _o.value; try { for (var _p = (e_5 = void 0, tslib_1.__values(tslib_1.__spreadArray(tslib_1.__spreadArray([], tslib_1.__read(relation.joinColumns)), tslib_1.__read(relation.inverseJoinColumns)))), _q = _p.next(); !_q.done; _q = _p.next()) { var joinColumn = _q.value; var propertyKey = relation.propertyPath + "." + joinColumn.referencedColumn.propertyPath; replacements[propertyKey] = joinColumn.databaseName; } } catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (_q && !_q.done && (_f = _p.return)) _f.call(_p); } finally { if (e_5) throw e_5.error; } } } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (_o && !_o.done && (_e = _m.return)) _e.call(_m); } finally { if (e_4) throw e_4.error; } } try { for (var _r = (e_6 = void 0, tslib_1.__values(alias.metadata.columns)), _s = _r.next(); !_s.done; _s = _r.next()) { var column = _s.value; replacements[column.databaseName] = column.databaseName; } } catch (e_6_1) { e_6 = { error: e_6_1 }; } finally { try { if (_s && !_s.done && (_g = _r.return)) _g.call(_r); } finally { if (e_6) throw e_6.error; } } try { for (var _t = (e_7 = void 0, tslib_1.__values(alias.metadata.columns)), _u = _t.next(); !_u.done; _u = _t.next()) { var column = _u.value; replacements[column.propertyName] = column.databaseName; } } catch (e_7_1) { e_7 = { error: e_7_1 }; } finally { try { if (_u && !_u.done && (_h = _t.return)) _h.call(_t); } finally { if (e_7) throw e_7.error; } } try { for (var _v = (e_8 = void 0, tslib_1.__values(alias.metadata.columns)), _w = _v.next(); !_w.done; _w = _v.next()) { var column = _w.value; replacements[column.propertyPath] = column.databaseName; } } catch (e_8_1) { e_8 = { error: e_8_1 }; } finally { try { if (_w && !_w.done && (_j = _v.return)) _j.call(_v); } finally { if (e_8) throw e_8.error; } } var replacementKeys = Object.keys(replacements); if (replacementKeys.length) { statement = statement.replace(new RegExp( // Avoid a lookbehind here since it's not well supported "([ =(]|^.{0})" + (escapeRegExp(replaceAliasNamePrefix) + "(" + replacementKeys.map(escapeRegExp).join("|") + ")") + "(?=[ =),]|.{0}$)", "gm"), function (_, pre, p) { return "" + pre + replacementAliasNamePrefix + _this.escape(replacements[p]); }); } }; var this_1 = this; try { for (var _b = tslib_1.__values(this.expressionMap.aliases), _c = _b.next(); !_c.done; _c = _b.next()) { var alias = _c.value; _loop_1(alias); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_2) throw e_2.error; } } return statement; }; QueryBuilder.prototype.createComment = function () { if (!this.expressionMap.comment) { return ""; } // ANSI SQL 2003 support C style comments - comments that start with `/*` and end with `*/` // In some dialects query nesting is available - but not all. Because of this, we'll need // to scrub "ending" characters from the SQL but otherwise we can leave everything else // as-is and it should be valid. return "/* " + this.expressionMap.comment.replace("*/", "") + " */ "; }; /** * Creates "WHERE" expression. */ QueryBuilder.prototype.createWhereExpression = function () { var conditionsArray = []; var whereExpression = this.createWhereClausesExpression(this.expressionMap.wheres); if (whereExpression.length > 0 && whereExpression !== "1=1") { conditionsArray.push(this.replacePropertyNames(whereExpression)); } if (this.expressionMap.mainAlias.hasMetadata) { var metadata = this.expressionMap.mainAlias.metadata; // Adds the global condition of "non-deleted" for the entity with delete date columns in select query. if (this.expressionMap.queryType === "select" && !this.expressionMap.withDeleted && metadata.deleteDateColumn) { var column = this.expressionMap.aliasNamePrefixingEnabled ? this.expressionMap.mainAlias.name + "." + metadata.deleteDateColumn.propertyName : metadata.deleteDateColumn.propertyName; var condition = this.replacePropertyNames(column) + " IS NULL"; conditionsArray.push(condition); } if (metadata.discriminatorColumn && metadata.parentEntityMetadata) { var column = this.expressionMap.aliasNamePrefixingEnabled ? this.expressionMap.mainAlias.name + "." + metadata.discriminatorColumn.databaseName : metadata.discriminatorColumn.databaseName; var condition = this.replacePropertyNames(column) + " IN (:...discriminatorColumnValues)"; conditionsArray.push(condition); } } if (this.expressionMap.extraAppendedAndWhereCondition) { var condition = this.replacePropertyNames(this.expressionMap.extraAppendedAndWhereCondition); conditionsArray.push(condition); } if (!conditionsArray.length) { return ""; } else if (conditionsArray.length === 1) { return " WHERE " + conditionsArray[0]; } else { return " WHERE ( " + conditionsArray.join(" ) AND ( ") + " )"; } }; /** * Creates "RETURNING" / "OUTPUT" expression. */ QueryBuilder.prototype.createReturningExpression = function () { var _this = this; var columns = this.getReturningColumns(); var driver = this.connection.driver; // also add columns we must auto-return to perform entity updation // if user gave his own returning if (typeof this.expressionMap.returning !== "string" && this.expressionMap.extraReturningColumns.length > 0 && driver.isReturningSqlSupported()) { columns.push.apply(columns, tslib_1.__spreadArray([], tslib_1.__read(this.expressionMap.extraReturningColumns.filter(function (column) { return columns.indexOf(column) === -1; })))); } if (columns.length) { var columnsExpression = columns.map(function (column) { var name = _this.escape(column.databaseName); if (driver instanceof SqlServerDriver_1.SqlServerDriver) { if (_this.expressionMap.queryType === "insert" || _this.expressionMap.queryType === "update" || _this.expressionMap.queryType === "soft-delete" || _this.expressionMap.queryType === "restore") { return "INSERTED." + name; } else { return _this.escape(_this.getMainTableName()) + "." + name; } } else { return name; } }).join(", "); if (driver instanceof OracleDriver_1.OracleDriver) { columnsExpression += " INTO " + columns.map(function (column) { return _this.createParameter({ type: driver.columnTypeToNativeParameter(column.type), dir: driver.oracle.BIND_OUT }); }).join(", "); } if (driver instanceof SqlServerDriver_1.SqlServerDriver) { if (this.expressionMap.queryType === "insert" || this.expressionMap.queryType === "update") { columnsExpression += " INTO @OutputTable"; } } return columnsExpression; } else if (typeof this.expressionMap.returning === "string") { return this.expressionMap.returning; } return ""; }; /** * If returning / output cause is set to array of column names, * then this method will return all column metadatas of those column names. */ QueryBuilder.prototype.getReturningColumns = function () { var _this = this; var columns = []; if (Array.isArray(this.expressionMap.returning)) { this.expressionMap.returning.forEach(function (columnName) { if (_this.expressionMap.mainAlias.hasMetadata) { columns.push.apply(columns, tslib_1.__spreadArray([], tslib_1.__read(_this.expressionMap.mainAlias.metadata.findColumnsWithPropertyPath(columnName)))); } }); } return columns; }; QueryBuilder.prototype.createWhereClausesExpression = function (clauses) { var _this = this; return clauses.map(function (clause, index) { var expression = _this.createWhereConditionExpression(clause.condition); switch (clause.type) { case "and": return (index > 0 ? "AND " : "") + expression; case "or": return (index > 0 ? "OR " : "") + expression; } return expression; }).join(" ").trim(); }; /** * Computes given where argument - transforms to a where string all forms it can take. */ QueryBuilder.prototype.createWhereConditionExpression = function (condition, alwaysWrap) { if (alwaysWrap === void 0) { alwaysWrap = false; } if (typeof condition === "string") return condition; if (Array.isArray(condition)) { if (condition.length === 0) { return "1=1"; } // In the future we should probably remove this entire condition // but for now to prevent any breaking changes it exists. if (condition.length === 1 && !alwaysWrap) { return this.createWhereClausesExpression(condition); } return "(" + this.createWhereClausesExpression(condition) + ")"; } var driver = this.connection.driver; switch (condition.operator) { case "lessThan": return condition.parameters[0] + " < " + condition.parameters[1]; case "lessThanOrEqual": return condition.parameters[0] + " <= " + condition.parameters[1]; case "moreThan": return condition.parameters[0] + " > " + condition.parameters[1]; case "moreThanOrEqual": return condition.parameters[0] + " >= " + condition.parameters[1]; case "notEqual": return condition.parameters[0] + " != " + condition.parameters[1]; case "equal": return condition.parameters[0] + " = " + condition.parameters[1]; case "ilike": if (driver instanceof PostgresDriver_1.PostgresDriver || driver instanceof CockroachDriver_1.CockroachDriver) { return condition.parameters[0] + " ILIKE " + condition.parameters[1]; } return "UPPER(" + condition.parameters[0] + ") LIKE UPPER(" + condition.parameters[1] + ")"; case "like": return condition.parameters[0] + " LIKE " + condition.parameters[1]; case "between": return condition.parameters[0] + " BETWEEN " + condition.parameters[1] + " AND " + condition.parameters[2]; case "in": if (condition.parameters.length <= 1) { return "0=1"; } return condition.parameters[0] + " IN (" + condition.parameters.slice(1).join(", ") + ")"; case "any": return condition.parameters[0] + " = ANY(" + condition.parameters[1] + ")"; case "isNull": return condition.parameters[0] + " IS NULL"; case "not": return "NOT(" + this.createWhereConditionExpression(condition.condition) + ")"; case "brackets": return "" + this.createWhereConditionExpression(condition.condition, true); } throw new TypeError("Unsupported FindOperator " + FindOperator_1.FindOperator.constructor.name); }; /** * Creates "WHERE" condition for an in-ids condition. */ QueryBuilder.prototype.getWhereInIdsCondition = function (ids) { var _a; var metadata = this.expressionMap.mainAlias.metadata; var normalized = (Array.isArray(ids) ? ids : [ids]).map(function (id) { return metadata.ensureEntityIdMap(id); }); // using in(...ids) for single primary key entities if (!metadata.hasMultiplePrimaryKeys) { var primaryColumn_1 = metadata.primaryColumns[0]; // getEntityValue will try to transform `In`, it is a bug // todo: remove this transformer check after #2390 is fixed // This also fails for embedded & relation, so until that is fixed skip it. if (!primaryColumn_1.transformer && !primaryColumn_1.relationMetadata && !primaryColumn_1.embeddedMetadata) { return _a = {}, _a[primaryColumn_1.propertyName] = In_1.In(normalized.map(function (id) { return primaryColumn_1.getEntityValue(id, false); })), _a; } } return new Brackets_1.Brackets(function (qb) { var e_9, _a; var _loop_2 = function (data) { qb.orWhere(new Brackets_1.Brackets(function (qb) { return qb.where(data); })); }; try { for (var normalized_1 = tslib_1.__values(normalized), normalized_1_1 = normalized_1.next(); !normalized_1_1.done; normalized_1_1 = normalized_1.next()) { var data = normalized_1_1.value; _loop_2(data); } } catch (e_9_1) { e_9 = { error: e_9_1 }; } finally { try { if (normalized_1_1 && !normalized_1_1.done && (_a = normalized_1.return)) _a.call(normalized_1); } finally { if (e_9) throw e_9.error; } } }); }; QueryBuilder.prototype.findColumnsForPropertyPath = function (propertyPath) { // Make a helper to iterate the entity & relations? // Use that to set the correct alias? Or the other way around? // Start with the main alias with our property paths var alias = this.expressionMap.mainAlias; var root = []; var propertyPathParts = propertyPath.split("."); var _loop_3 = function () { var part = propertyPathParts[0]; if (!(alias === null || alias === void 0 ? void 0 : alias.hasMetadata)) { return "break"; } if (alias.metadata.hasEmbeddedWithPropertyPath(part)) { // If this is an embedded then we should combine the two as part of our lookup. // Instead of just breaking, we keep going with this in case there's an embedded/relation // inside an embedded. propertyPathParts.unshift(propertyPathParts.shift() + "." + propertyPathParts.shift()); return "continue"; } if (alias.metadata.hasRelationWithPropertyPath(part)) { // If this is a relation then we should find the aliases // that match the relation & then continue further down // the property path var joinAttr = this_2.expressionMap.joinAttributes.find(function (joinAttr) { return joinAttr.relationPropertyPath === part; }); if (!(joinAttr === null || joinAttr === void 0 ? void 0 : joinAttr.alias)) { var fullRelationPath = root.length > 0 ? root.join(".") + "." + part : part; throw new Error("Cannot find alias for relation at " + fullRelationPath); } alias = joinAttr.alias; root.push.apply(root, tslib_1.__spreadArray([], tslib_1.__read(part.split(".")))); propertyPathParts.shift(); return "continue"; } return "break"; }; var this_2 = this; while (propertyPathParts.length > 1) { var state_1 = _loop_3(); if (state_1 === "break") break; } if (!alias) { throw new Error("Cannot find alias for property " + propertyPath); } // Remaining parts are combined back and used to find the actual property path var aliasPropertyPath = propertyPathParts.join("."); var columns = alias.metadata.findColumnsWithPropertyPath(aliasPropertyPath); if (!columns.length) { throw new EntityColumnNotFound_1.EntityColumnNotFound(propertyPath); } return [alias, root, columns]; }; /** * Creates a property paths for a given ObjectLiteral. */ QueryBuilder.prototype.createPropertyPath = function (metadata, entity, prefix) { var e_10, _a; if (prefix === void 0) { prefix = ""; } var paths = []; var _loop_4 = function (key) { var path = prefix ? prefix + "." + key : key; // There's times where we don't actually want to traverse deeper. // If the value is a `FindOperator`, or null, or not an object, then we don't, for example. if (entity[key] === null || typeof entity[key] !== "object" || entity[key] instanceof FindOperator_1.FindOperator) { paths.push(path); return "continue"; } if (metadata.hasEmbeddedWithPropertyPath(path)) { var subPaths = this_3.createPropertyPath(metadata, entity[key], path); paths.push.apply(paths, tslib_1.__spreadArray([], tslib_1.__read(subPaths))); return "continue"; } if (metadata.hasRelationWithPropertyPath(path)) { var relation = metadata.findRelationWithPropertyPath(path); // There's also cases where we don't want to return back all of the properties. // These handles the situation where someone passes the model & we don't need to make // a HUGE `where` to uniquely look up the entity. // In the case of a *-to-one, there's only ever one possible entity on the other side // so if the join columns are all defined we can return just the relation itself // because it will fetch only the join columns and do the lookup. if (relation.relationType === "one-to-one" || relation.relationType === "many-to-one") { var joinColumns = relation.joinColumns .map(function (j) { return j.referencedColumn; }) .filter(function (j) { return !!j; }); var hasAllJoinColumns = joinColumns.length > 0 && joinColumns.every(function (column) { return column.getEntityValue(entity[key], false); }); if (hasAllJoinColumns) { paths.push(path); return "continue"; } } if (relation.relationType === "one-to-many" || relation.relationType === "many-to-many") { throw new Error("Cannot query across " + relation.relationType + " for property " + path); } // For any other case, if the `entity[key]` contains all of the primary keys we can do a // lookup via these. We don't need to look up via any other values 'cause these are // the unique primary keys. // This handles the situation where someone passes the model & we don't need to make // a HUGE where. var primaryColumns = relation.inverseEntityMetadata.primaryColumns; var hasAllPrimaryKeys = primaryColumns.length > 0 && primaryColumns.every(function (column) { return column.getEntityValue(entity[key], false); }); if (hasAllPrimaryKeys) { var subPaths_1 = primaryColumns.map(function (column) { return path + "." + column.propertyPath; }); paths.push.apply(paths, tslib_1.__spreadArray([], tslib_1.__read(subPaths_1))); return "continue"; } // If nothing else, just return every property that's being passed to us. var subPaths = this_3.createPropertyPath(relation.inverseEntityMetadata, entity[key]) .map(function (p) { return path + "." + p; }); paths.push.apply(paths, tslib_1.__spreadArray([], tslib_1.__read(subPaths))); return "continue"; } paths.push(path); }; var this_3 = this; try { for (var _b = tslib_1.__values(Object.keys(entity)), _c = _b.next(); !_c.done; _c = _b.next()) { var key = _c.value; _loop_4(key); } } catch (e_10_1) { e_10 = { error: e_10_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_10) throw e_10.error; } } return paths; }; QueryBuilder.prototype.getPredicates = function (where) { var propertyPaths, propertyPaths_1, propertyPaths_1_1, propertyPath, _a, alias, aliasPropertyPath, columns, columns_1, columns_1_1, column, containedWhere, aliasPropertyPath_1, aliasPropertyPath_1_1, part, aliasPath, parameterValue, e_11_1, e_12_1, _b, _c, key, parameterValue, aliasPath, e_13_1; var e_12, _d, e_11, _e, e_14, _f, e_13, _g; return tslib_1.__generator(this, function (_h) { switch (_h.label) { case 0: if (!this.expressionMap.mainAlias.hasMetadata) return [3 /*break*/, 15]; propertyPaths = this.createPropertyPath(this.expressionMap.mainAlias.metadata, where); _h.label = 1; case 1: _h.trys.push([1, 12, 13, 14]); propertyPaths_1 = tslib_1.__values(propertyPaths), propertyPaths_1_1 = propertyPaths_1.next(); _h.label = 2; case 2: if (!!propertyPaths_1_1.done) return [3 /*break*/, 11]; propertyPath = propertyPaths_1_1.value; _a = tslib_1.__read(this.findColumnsForPropertyPath(propertyPath), 3), alias = _a[0], aliasPropertyPath = _a[1], columns = _a[2]; _h.label = 3; case 3: _h.trys.push([3, 8, 9, 10]); columns_1 = (e_11 = void 0, tslib_1.__values(columns)), columns_1_1 = columns_1.next(); _h.label = 4; case 4: if (!!columns_1_1.done) return [3 /*break*/, 7]; column = columns_1_1.value; containedWhere = where; try { for (aliasPropertyPath_1 = (e_14 = void 0, tslib_1.__values(aliasPropertyPath)), aliasPropertyPath_1_1 = aliasPropertyPath_1.next(); !aliasPropertyPath_1_1.done; aliasPropertyPath_1_1 = aliasPropertyPath_1.next()) { part = aliasPropertyPath_1_1.value; if (!containedWhere || !(part in containedWhere)) { containedWhere = {}; break; } containedWhere = containedWhere[part]; } } catch (e_14_1) { e_14 = { error: e_14_1 }; } finally { try { if (aliasPropertyPath_1_1 && !aliasPropertyPath_1_1.done && (_f = aliasPropertyPath_1.return)) _f.call(aliasPropertyPath_1); } finally { if (e_14) throw e_14.error; } } aliasPath = this.expressionMap.aliasNamePrefixingEnabled ? alias.name + "." + column.propertyPath : column.propertyPath; parameterValue = column.getEntityValue(containedWhere, true); return [4 /*yield*/, [aliasPath, parameterValue]]; case 5: _h.sent(); _h.label = 6; case 6: columns_1_1 = columns_1.next(); return [3 /*break*/, 4]; case 7: return [3 /*break*/, 10]; case 8: e_11_1 = _h.sent(); e_11 = { error: e_11_1 }; return [3 /*break*/, 10]; case 9: try { if (columns_1_1 && !columns_1_1.done && (_e = columns_1.return)) _e.call(columns_1); } finally { if (e_11) throw e_11.error; } return [7 /*endfinally*/]; case 10: propertyPaths_1_1 = propertyPaths_1.next(); return [3 /*break*/, 2]; case 11: return [3 /*break*/, 14]; case 12: e_12_1 = _h.sent(); e_12 = { error: e_12_1 }; return [3 /*break*/, 14]; case 13: try { if (propertyPaths_1_1 && !propertyPaths_1_1.done && (_d = propertyPaths_1.return)) _d.call(propertyPaths_1); } finally { if (e_12) throw e_12.error; } return [7 /*endfinally*/]; case 14: return [3 /*break*/, 22]; case 15: _h.trys.push([15, 20, 21, 22]); _b = tslib_1.__values(Object.keys(where)), _c = _b.next(); _h.label = 16; case 16: if (!!_c.done) return [3 /*break*/, 19]; key = _c.value; parameterValue = where[key]; aliasPath = this.expressionMap.aliasNamePrefixingEnabled ? this.alias + "." + key : key; return [4 /*yield*/, [aliasPath, parameterValue]]; case 17: _h.sent(); _h.label = 18; case 18: _c = _b.next(); return [3 /*break*/, 16]; case 19: return [3 /*break*/, 22]; case 20: e_13_1 = _h.sent(); e_13 = { error: e_13_1 }; return [3 /*break*/, 22]; case 21: try { if (_c && !_c.done && (_g = _b.return)) _g.call(_b); }