typeorm-linq-repository
Version:
Wraps TypeORM repository pattern and QueryBuilder using fluent, LINQ-style queries.
724 lines (723 loc) • 35 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Query = void 0;
const ts_simple_nameof_1 = require("ts-simple-nameof");
const typeorm_1 = require("typeorm");
const SqlConstants_1 = require("../constants/SqlConstants");
const QueryMode_1 = require("../enums/QueryMode");
const QueryWhereType_1 = require("../enums/QueryWhereType");
const QueryBuilderPart_1 = require("./QueryBuilderPart");
class Query {
/**
* Constructs a Query wrapper.
* @param queryBuilder The QueryBuilder to wrap.
* @param getAction Either queryBuilder.getOne or queryBuilder.getMany.
*/
constructor(queryBuilder, getAction, includeAliasHistory = []) {
this._duplicateAliasHistory = [];
this._getAction = getAction;
this._includeAliasHistory = includeAliasHistory;
this._initialAlias = queryBuilder.alias;
this._query = queryBuilder;
this._queryParts = [];
this._lastAlias = this._initialAlias;
this._queryMode = QueryMode_1.QueryMode.Get;
this._queryWhereType = QueryWhereType_1.QueryWhereType.Normal;
this._selectedProperty = "";
}
get getAction() {
return this._getAction;
}
get query() {
return this._query;
}
get queryParts() {
return this._queryParts;
}
get selected() {
return this._selectedProperty;
}
and(propertySelector) {
return this.andOr(propertySelector, SqlConstants_1.SqlConstants.OPERATOR_AND, this._query.andWhere);
}
// TODO: Can we use something besides the VERY unfortunate "any" here?
// With return type "IQuery<T, R, T> | IComparableQuery<T, R, S>",
// TS is complaining about IComparableQuery not having a whole bunch of methods
// from IQuery (which is true).
andAny(relationSelector, relationCountPropSelector, conditionPropSelector) {
return this.relationCount(relationSelector, relationCountPropSelector, this._query.andHaving, ">", conditionPropSelector);
}
// TODO: Can we use something besides the VERY unfortunate "any" here?
// With return type "IQuery<T, R, T> | IComparableQuery<T, R, S>",
// TS is complaining about IComparableQuery not having a whole bunch of methods
// from IQuery (which is true).
andNone(relationSelector, relationCountPropSelector, conditionPropSelector) {
return this.relationCount(relationSelector, relationCountPropSelector, this._query.andHaving, "=", conditionPropSelector);
}
beginsWith(value, options) {
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_LIKE, value, {
beginsWith: true
}, options);
}
catch(rejected) {
return __awaiter(this, void 0, void 0, function* () {
return this.toPromise()
.catch(rejected);
});
}
contains(value, options) {
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_LIKE, value, {
beginsWith: true,
endsWith: true
}, options);
}
count() {
const targetQueryBuilder = this._query.clone();
this.compileQueryParts(this._queryParts, targetQueryBuilder);
return targetQueryBuilder.getCount();
}
endsWith(value, options) {
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_LIKE, value, {
endsWith: true
}, options);
}
equal(value, options) {
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_EQUAL, value, null, options);
}
equalJoined(selector, options) {
return this.completeJoinedWhere(SqlConstants_1.SqlConstants.OPERATOR_EQUAL, selector, options);
}
// <any> is necessary here because the usage of this method
// depends on the interface from which it was called.
from(foreignEntity) {
return this.joinForeignEntity(foreignEntity);
}
greaterThan(value) {
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_GREATER, value);
}
greaterThanJoined(selector) {
return this.completeJoinedWhere(SqlConstants_1.SqlConstants.OPERATOR_GREATER, selector);
}
greaterThanOrEqual(value) {
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_GREATER_EQUAL, value);
}
greaterThanOrEqualJoined(selector) {
return this.completeJoinedWhere(SqlConstants_1.SqlConstants.OPERATOR_GREATER_EQUAL, selector);
}
groupBy(propertySelector) {
const propertyName = (0, ts_simple_nameof_1.nameof)(propertySelector);
const groupProperty = `${this._lastAlias}.${propertyName}`;
return this.completeGroupBy(this._query.groupBy, groupProperty);
}
in(include, options) {
// If comparing strings, must escape them as strings in the query.
this.escapeStringArray(include);
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_IN, `(${include.join(", ")})`, { quoteString: false }, options);
}
include(propertySelector) {
return this.includePropertyUsingAlias(propertySelector, this._initialAlias);
}
inSelected(innerQuery) {
return this.includeOrExcludeFromInnerQuery(innerQuery, SqlConstants_1.SqlConstants.OPERATOR_IN);
}
isFalse() {
this.equal(false);
return this;
}
isNotNull() {
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_IS, SqlConstants_1.SqlConstants.OPERATOR_NOT_NULL, { quoteString: false });
}
isNull() {
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_IS, SqlConstants_1.SqlConstants.OPERATOR_NULL, { quoteString: false });
}
isolatedAnd(and) {
// TODO: These types are not lining up.
return this.isolatedConditions(and, this._query.andWhere);
}
isolatedOr(and) {
// TODO: These types are not lining up.
return this.isolatedConditions(and, this._query.orWhere);
}
isolatedWhere(where) {
// TODO: These types are not lining up.
return this.isolatedConditions(where, this._query.where);
}
isTrue() {
this.equal(true);
return this;
}
join(propertySelector) {
return this.joinPropertyUsingAlias(propertySelector, this._initialAlias);
}
joinAlso(propertySelector) {
return this.joinPropertyUsingAlias(propertySelector, this._initialAlias, this._query.leftJoin);
}
lessThan(value) {
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_LESS, value);
}
lessThanJoined(selector) {
return this.completeJoinedWhere(SqlConstants_1.SqlConstants.OPERATOR_LESS, selector);
}
lessThanOrEqual(value) {
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_LESS_EQUAL, value);
}
lessThanOrEqualJoined(selector) {
return this.completeJoinedWhere(SqlConstants_1.SqlConstants.OPERATOR_LESS_EQUAL, selector);
}
notEqual(value, options) {
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_NOT_EQUAL, value, null, options);
}
notEqualJoined(selector, options) {
return this.completeJoinedWhere(SqlConstants_1.SqlConstants.OPERATOR_NOT_EQUAL, selector, options);
}
notIn(exclude, options) {
// If comparing strings, must escape them as strings in the query.
this.escapeStringArray(exclude);
return this.completeWhere(SqlConstants_1.SqlConstants.OPERATOR_NOT_IN, `(${exclude.join(", ")})`, { quoteString: false }, options);
}
notInSelected(innerQuery) {
return this.includeOrExcludeFromInnerQuery(innerQuery, SqlConstants_1.SqlConstants.OPERATOR_NOT_IN);
}
or(propertySelector) {
return this.andOr(propertySelector, SqlConstants_1.SqlConstants.OPERATOR_OR, this._query.orWhere);
}
// TODO: Can we use something besides the VERY unfortunate "any" here?
// With return type "IQuery<T, R, T> | IComparableQuery<T, R, S>",
// TS is complaining about IComparableQuery not having a whole bunch of methods
// from IQuery (which is true).
orAny(relationSelector, relationCountPropSelector, conditionPropSelector) {
return this.relationCount(relationSelector, relationCountPropSelector, this._query.orHaving, ">", conditionPropSelector);
}
orderBy(propertySelector, options) {
const propertyName = (0, ts_simple_nameof_1.nameof)(propertySelector);
const orderProperty = `${this._lastAlias}.${propertyName}`;
return this.completeOrderBy(this._query.orderBy, [orderProperty, "ASC"], options);
}
orderByDescending(propertySelector, options) {
const propertyName = (0, ts_simple_nameof_1.nameof)(propertySelector);
const orderProperty = `${this._lastAlias}.${propertyName}`;
return this.completeOrderBy(this._query.orderBy, [orderProperty, "DESC"], options);
}
// TODO: Can we use something besides the VERY unfortunate "any" here?
// With return type "IQuery<T, R, T> | IComparableQuery<T, R, S>",
// TS is complaining about IComparableQuery not having a whole bunch of methods
// from IQuery (which is true).
orNone(relationSelector, relationCountPropSelector, conditionPropSelector) {
return this.relationCount(relationSelector, relationCountPropSelector, this._query.orHaving, "=", conditionPropSelector);
}
reset() {
this._lastAlias = this._initialAlias;
// Exit the "join chain" so that additional comparisons may be made on the base entity.
this._queryWhereType = QueryWhereType_1.QueryWhereType.Normal;
return this;
}
select(propertySelector) {
const selectedProperty = (0, ts_simple_nameof_1.nameof)(propertySelector);
let alias = null;
// If coming out of a comparison, query is back in "base mode" (where and select use base type).
if (this._queryMode === QueryMode_1.QueryMode.Get) {
alias = this._initialAlias;
}
// If in a join, use the last joined entity to select a property.
else {
alias = this._lastAlias;
}
this._selectedProperty = `${alias}.${selectedProperty}`;
return this;
}
skip(skip) {
if (skip > 0) {
this._queryParts.push(new QueryBuilderPart_1.QueryBuilderPart(this._query.skip, [skip]));
}
return this;
}
take(limit) {
if (limit > 0) {
this._queryParts.push(new QueryBuilderPart_1.QueryBuilderPart(this._query.take, [limit]));
}
return this;
}
then(resolved) {
return __awaiter(this, void 0, void 0, function* () {
return this.toPromise()
.then(resolved);
});
}
thenBy(propertySelector, options) {
const propertyName = (0, ts_simple_nameof_1.nameof)(propertySelector);
const orderProperty = `${this._lastAlias}.${propertyName}`;
return this.completeOrderBy(this._query.addOrderBy, [orderProperty, "ASC"], options);
}
thenByDescending(propertySelector, options) {
const propertyName = (0, ts_simple_nameof_1.nameof)(propertySelector);
const orderProperty = `${this._lastAlias}.${propertyName}`;
return this.completeOrderBy(this._query.addOrderBy, [orderProperty, "DESC"], options);
}
thenGroupBy(propertySelector) {
const propertyName = (0, ts_simple_nameof_1.nameof)(propertySelector);
const groupProperty = `${this._lastAlias}.${propertyName}`;
return this.completeGroupBy(this._query.addGroupBy, groupProperty);
}
thenInclude(propertySelector) {
return this.includePropertyUsingAlias(propertySelector, this._lastAlias);
}
thenJoin(propertySelector) {
return this.joinPropertyUsingAlias(propertySelector, this._lastAlias);
}
thenJoinAlso(propertySelector) {
return this.joinPropertyUsingAlias(propertySelector, this._lastAlias, this._query.leftJoin);
}
toPromise() {
return this._getAction.call(this.buildQuery(this));
}
usingBaseType() {
this._lastAlias = this._initialAlias;
return this;
}
where(propertySelector) {
const whereProperties = (0, ts_simple_nameof_1.nameof)(propertySelector);
let whereProperty = null;
// Keep up with the last alias in order to restore it after joinMultipleProperties.
let lastAlias = this._lastAlias;
// In the event of performing a normal where after a join-based where, use the initial alias.
if (this._queryMode === QueryMode_1.QueryMode.Get) {
this._queryWhereType = QueryWhereType_1.QueryWhereType.Normal;
// Following a normal where, restore last alias to the initial alias.
// Last alias may be changed in joinMultipleProperties, but in case it is not,
// it needs to be reset to the initial alias before setting the where property.
lastAlias = this._lastAlias = this._initialAlias;
// If accessing multiple properties, join relationships using an INNER JOIN.
whereProperty = this.joinMultipleProperties(whereProperties);
const where = `${this._lastAlias}.${whereProperty}`;
this._queryParts.push(new QueryBuilderPart_1.QueryBuilderPart(this._query.where, [where]));
}
// Otherwise, this where was performed on a join operation.
else {
// If accessing multiple properties, join relationships using an INNER JOIN.
whereProperty = this.joinMultipleProperties(whereProperties);
this._queryWhereType = QueryWhereType_1.QueryWhereType.Joined;
this.createJoinCondition(whereProperty);
}
// Restore the last alias after joinMultipleProperties.
this._lastAlias = lastAlias;
this._queryMode = QueryMode_1.QueryMode.Compare;
return this;
}
// TODO: Can we use something besides the VERY unfortunate "any" here?
// With return type "IQuery<T, R, T> | IComparableQuery<T, R, S>",
// TS is complaining about IComparableQuery not having a whole bunch of methods
// from IQuery (which is true).
whereAny(relationSelector, relationCountPropSelector, conditionPropSelector) {
return this.relationCount(relationSelector, relationCountPropSelector, this._query.having, ">", conditionPropSelector);
}
// TODO: Can we use something besides the VERY unfortunate "any" here?
// With return type "IQuery<T, R, T> | IComparableQuery<T, R, S>",
// TS is complaining about IComparableQuery not having a whole bunch of methods
// from IQuery (which is true).
whereNone(relationSelector, relationCountPropSelector, conditionPropSelector) {
return this.relationCount(relationSelector, relationCountPropSelector, this._query.having, "=", conditionPropSelector);
}
addJoinCondition(whereProperty, condition, targetQueryPart = null) {
// Result of calling .include(x => x.prop).where(...).<compare>(...).<and/or>(...)
// [
// QueryBuilder.leftJoinAndSelect,
// ["alias.includedProperty", "includedProperty", "includedProperty.property = 'something'"]
// ]
// OR
// Result of calling .join(x => x.pop).where(...).<compare>(...).<and/or>(...)
// [
// QueryBuilder.innerJoin,
// ["alias.includedProperty", "includedProperty", "includedProperty.property = 'something'"]
// ]
const part = targetQueryPart || this._queryParts.pop();
// "includedProperty.property = 'something'"
let joinCondition = part.queryParams.pop();
joinCondition += ` ${condition} ${this._lastAlias}.${whereProperty}`;
part.queryParams.push(joinCondition);
// If we did not receive the optional taretQueryPart argument,
// that means we used the last query part, which was popped from this._queryParts.
if (!targetQueryPart) {
this._queryParts.push(part);
}
}
andOr(propertySelector, operation, queryAction) {
const whereProperties = (0, ts_simple_nameof_1.nameof)(propertySelector);
// If accessing multiple properties during an AND, join relationships using an INNER JOIN.
// If accessing multiple properties during an OR, join relationships using a LEFT JOIN.
const joinAction = operation === "AND"
? this._query.innerJoin
: this._query.leftJoin;
// Keep up with the last alias in order to restore it after joinMultipleProperties.
const lastAlias = this._lastAlias;
// If accessing multiple properties, join relationships using an INNER JOIN.
const whereProperty = this.joinMultipleProperties(whereProperties, joinAction);
// A third parameter on the query parameters indicates additional join conditions.
// Only add a join condition if performing a conditional join.
if (this._queryWhereType === QueryWhereType_1.QueryWhereType.Joined &&
this._queryParts[this._queryParts.length - 1].queryParams.length === 3) {
this.addJoinCondition(whereProperty, operation);
}
else {
const where = `${this._lastAlias}.${whereProperty}`;
this._queryParts.push(new QueryBuilderPart_1.QueryBuilderPart(queryAction, [where]));
}
// Restore the last alias after joinMultipleProperties.
this._lastAlias = lastAlias;
this._queryMode = QueryMode_1.QueryMode.Compare;
return this;
}
buildQuery(query) {
// Unpack and apply the QueryBuilder parts.
this.compileQueryParts(query.queryParts, query.query);
return query.query;
}
compileQueryParts(queryParts, builder) {
if (queryParts.length) {
for (const queryPart of queryParts) {
queryPart.queryAction.call(builder, ...queryPart.queryParams);
}
}
}
completeGroupBy(groupAction, groupProperty) {
this._queryParts.push(new QueryBuilderPart_1.QueryBuilderPart(groupAction, [groupProperty]));
return this;
}
completeOrderBy(orderAction, orderParams, options) {
if (options) {
if (typeof (options.nullsFirst) === "boolean") {
orderParams.push(options.nullsFirst ? "NULLS FIRST" : "NULLS LAST");
}
}
this._queryParts.push(new QueryBuilderPart_1.QueryBuilderPart(orderAction, orderParams));
return this;
}
completeJoinedWhere(operator, selector, options) {
const selectedProperty = (0, ts_simple_nameof_1.nameof)(selector);
const compareValue = `${this._lastAlias}.${selectedProperty}`;
// compareValue is a string but should be treated as a join property
// (not a quoted string) in the query, so use "false" for the "quoteString" argument.
// If the user specifies a matchCase option, then assume the property is, in fact, a string
// and allow completeWhere to apply case insensitivity if necessary.
return this.completeWhere(operator, compareValue, {
joiningString: !!options && typeof (options.matchCase) === "boolean",
quoteString: false
}, options);
}
completeWhere(operator, value, optionsInternal, options) {
let beginsWith = false;
let endsWith = false;
let joiningString = false;
let quoteString = true;
let matchCase = false;
if (optionsInternal) {
if (typeof (optionsInternal.beginsWith) === "boolean") {
beginsWith = optionsInternal.beginsWith;
}
if (typeof (optionsInternal.endsWith) === "boolean") {
endsWith = optionsInternal.endsWith;
}
if (typeof (optionsInternal.joiningString) === "boolean") {
joiningString = optionsInternal.joiningString;
}
if (typeof (optionsInternal.quoteString) === "boolean") {
quoteString = optionsInternal.quoteString;
}
}
if (options) {
if (typeof (options.matchCase) === "boolean") {
matchCase = options.matchCase;
}
}
if (beginsWith) {
value += "%";
}
if (endsWith) {
value = `%${value}`;
}
if (typeof (value) === "string" && quoteString) {
value = value.replace(/'/g, "''");
value = `'${value}'`;
}
// In case of a from or join within a "where", must find the last "where" in the query parts.
const nonWheres = [];
let wherePart = null;
while (this._queryParts.length && !wherePart) {
const part = this._queryParts.pop();
if ((
// Could either be a normal where function:
this._queryWhereType === QueryWhereType_1.QueryWhereType.Normal
&& (
// tslint:disable-next-line: triple-equals
part.queryAction == this._query.where
// tslint:disable-next-line: triple-equals
|| part.queryAction == this._query.andWhere
// tslint:disable-next-line: triple-equals
|| part.queryAction == this._query.orWhere))
|| (
// or a join condition:
this._queryWhereType === QueryWhereType_1.QueryWhereType.Joined
&& (part.queryAction === this._query.innerJoin
|| part.queryAction === this._query.leftJoin
|| part.queryAction === this._query.leftJoinAndSelect
|| part.queryAction === this._query.innerJoinAndSelect)
&& part.queryParams.length === 3)) {
wherePart = part;
}
else {
nonWheres.unshift(part);
}
}
if (!wherePart) {
throw new Error("typeorm-linq-repository: Invalid use of conditional method.");
}
this._queryParts.push(...nonWheres);
// If processing a join condition.
if (this._queryWhereType === QueryWhereType_1.QueryWhereType.Joined) {
// [
// QueryBuilder.leftJoinAndSelect,
// ["alias.includedProperty", "includedProperty", "includedProperty.property"]
// ]
const part = wherePart;
// "includedProperty.property"
let joinCondition = part.queryParams.pop();
if (typeof (value) === "string" && (quoteString || joiningString) && !matchCase) {
value = value.toLowerCase();
joinCondition = `LOWER(${joinCondition})`;
}
else if (value instanceof Date) {
value = `'${value.toISOString()}'`;
}
// "includedProperty.property = 'something'"
joinCondition += ` ${operator} ${value}`;
part.queryParams.push(joinCondition);
this._queryParts.push(part);
}
// If processing a normal comparison.
else {
// [QueryBuilder.<where | andWhere | orWhere>, ["alias.property"]]
const part = wherePart;
// "alias.property"
let where = part.queryParams.pop();
if (typeof (value) === "string" && (quoteString || joiningString) && !matchCase) {
value = value.toLowerCase();
where = `LOWER(${where})`;
}
else if (value instanceof Date) {
value = `'${value.toISOString()}'`;
}
where += ` ${operator} ${value}`;
part.queryParams.push(where);
this._queryParts.push(part);
}
this._queryMode = QueryMode_1.QueryMode.Get;
return this;
}
createJoinCondition(joinConditionProperty) {
// Find the query part on which to add the condition. Usually will be the last, but not always.
let targetQueryPart = null;
const otherParts = [];
while (!targetQueryPart && this._queryParts.length) {
const part = this._queryParts.pop();
// See if this query part is the one in which the last alias was joined.
if (part.queryParams && part.queryParams.length > 1 && part.queryParams[1] === this._lastAlias) {
targetQueryPart = part;
}
else {
otherParts.unshift(part);
}
}
if (!targetQueryPart) {
throw new Error("typeorm-linq-repository: Invalid use of conditional join.");
}
this._queryParts.push(...otherParts);
// There should not already be a join condition on this query builder part.
// If there is, we want to add a join condition, not overwrite it.
if (targetQueryPart.queryParams.length === 3) {
this.addJoinCondition(joinConditionProperty, "AND", targetQueryPart);
}
else {
const joinCondition = `${this._lastAlias}.${joinConditionProperty}`;
targetQueryPart.queryParams.push(joinCondition);
}
this._queryParts.push(targetQueryPart);
}
escapeStringArray(array) {
array.forEach((value, i) => {
if (typeof (value) === "string") {
array[i] = `'${value}'`;
}
});
}
includeOrExcludeFromInnerQuery(innerQuery, operator) {
innerQuery.queryParts.unshift(new QueryBuilderPart_1.QueryBuilderPart(innerQuery.query.select, [innerQuery.selected]));
// Use <any> since all that matters is that the base type of any query
// contains a property named "id".
const query = this.buildQuery(innerQuery)
.getQuery();
this.completeWhere(operator, `(${query})`, { quoteString: false });
return this;
}
includePropertyUsingAlias(propertySelector, queryAlias) {
return this.joinOrIncludePropertyUsingAlias(propertySelector, queryAlias, this._query.leftJoinAndSelect);
}
isolatedConditions(conditions, conditionAction) {
const query = conditions(new Query(this._query, this._getAction, this._includeAliasHistory));
// Do not include joins in bracketed condition; perform those in the outer query.
const conditionParts = query
.queryParts
.filter(qp =>
// tslint:disable-next-line: triple-equals
qp.queryAction == query.query.where
// tslint:disable-next-line: triple-equals
|| qp.queryAction == query.query.andWhere
// tslint:disable-next-line: triple-equals
|| qp.queryAction == query.query.orWhere);
// Perform joins in the outer query.
const joinParts = query
.queryParts
.filter(qp => conditionParts.indexOf(qp) < 0);
this._queryParts.push(...joinParts);
this._queryParts.push(new QueryBuilderPart_1.QueryBuilderPart(conditionAction, [
new typeorm_1.Brackets(qb => {
this.compileQueryParts(conditionParts, qb);
})
]));
return this;
}
joinForeignEntity(foreignEntity) {
const entityName = (0, ts_simple_nameof_1.nameof)(foreignEntity);
const resultAlias = entityName;
this._lastAlias = resultAlias;
// If just passing through a chain of possibly already executed includes for semantics,
// don't execute the include again.
// Only execute the include if it has not been previously executed.
if (!(this._includeAliasHistory.find(a => a === resultAlias))) {
this._includeAliasHistory.push(resultAlias);
this._queryParts.push(new QueryBuilderPart_1.QueryBuilderPart(this._query.innerJoin, [foreignEntity, resultAlias, "true"]));
}
this.setJoinIfNotCompare();
return this;
}
joinMultipleProperties(whereProperties, joinAction = this._query.innerJoin, checkAliasHistory = true) {
// Array.map() is used to select a property from a relationship collection.
// .where(x => x.relationshipOne.map(y => y.relationshipTwo.map(z => z.relationshipThree)))...
// Becomes, via ts-simple-nameof...
// "relationshipOne.map(y => y.relationshipTwo.map(z => z.relationshipThree))"
// Now get...
// "relationshipOne.map(y=>y.relationshipTwo.map(z=>z.relationshipThree))"
whereProperties = whereProperties.replace(/ /g, "");
// "relationshipOne.relationshipTwo.relationshipThree"
// Regex allows:
// .map(y=>y.relationshipTwo)
// or:
// .map((y)=>y.relationshipTwo)
// or:
// .map((y:Entity)=>y.relationshipTwo)
whereProperties = whereProperties
.replace(/\.map\((\(?[a-zA-Z0-9_:]+\)?)=>[a-zA-Z0-9]+/g, "")
.replace(/\)/g, "");
const separatedProperties = whereProperties.split(".");
// If not checking alias history, we are performing a <and/or/where><Any/None>,
// so do not pop the last property, which is a relation rather than a primitive property.
let whereProperty = "";
if (checkAliasHistory) {
whereProperty = separatedProperties.pop();
}
for (let property of separatedProperties) {
// Array.map() is used to select a property from a relationship collection.
if (property.indexOf("map(") === 0) {
property = property.substring(4);
}
this.joinPropertyUsingAlias(property, this._lastAlias, joinAction, checkAliasHistory);
}
return whereProperty;
}
joinOrIncludePropertyUsingAlias(propertySelector, queryAlias, queryAction, checkAliasHistory = true) {
let propertyName = null;
if (propertySelector instanceof Function) {
propertyName = (0, ts_simple_nameof_1.nameof)(propertySelector);
}
else {
propertyName = propertySelector;
}
let resultAlias = `${queryAlias}_${propertyName}`;
// If including, do not set join mode.
if (queryAction !== this._query.leftJoinAndSelect) {
this.setJoinIfNotCompare();
}
// If not checking alias history, we are performing a <and/or/where><Any/None>,
// so make this instance of this relation's alias unique.
if (!checkAliasHistory) {
const existingAliasCount = this._duplicateAliasHistory
.filter(a => a === resultAlias)
.length;
this._duplicateAliasHistory.push(resultAlias);
resultAlias += existingAliasCount.toString();
}
this._lastAlias = resultAlias;
const aliasAlreadyIncluded = this._includeAliasHistory.some(a => a === resultAlias);
// If just passing through a chain of possibly already executed includes for semantics,
// don't execute the include again.
// Only execute the include if it has not been previously executed OR if not checking alias history,
// meaning we are performing an <and/or/where><Any/None>.
if (!checkAliasHistory || !aliasAlreadyIncluded) {
this._includeAliasHistory.push(resultAlias);
const queryProperty = `${queryAlias}.${propertyName}`;
this._queryParts.push(new QueryBuilderPart_1.QueryBuilderPart(queryAction, [queryProperty, resultAlias]));
}
// If passing through a previous include but restricting the previously included entities
// to a condition based on a deeper relationship, then restrict the previously used
// leftJoinAndSelect to an innerJoinAndSelect instead.
else if (checkAliasHistory
&& aliasAlreadyIncluded
&& queryAction === this._query.innerJoin) {
const includeQueryPartIndex = this._queryParts.findIndex(qp => qp.queryAction === this._query.leftJoinAndSelect
&& qp.queryParams[1] === resultAlias);
if (includeQueryPartIndex >= 0) {
const includeQueryPart = this._queryParts[includeQueryPartIndex];
this._queryParts[includeQueryPartIndex] = new QueryBuilderPart_1.QueryBuilderPart(this._query.innerJoinAndSelect, includeQueryPart.queryParams);
}
}
return this;
}
joinPropertyUsingAlias(propertySelector, queryAlias, queryAction = this._query.innerJoin, checkAliasHistory = true) {
return this.joinOrIncludePropertyUsingAlias(propertySelector, queryAlias, queryAction, checkAliasHistory);
}
relationCount(relationSelector, relationCountPropSelector, havingQueryAction, havingCountComparer, conditionPropSelector) {
// When using <and/or/where><Any/None>, always start at base type.
this.reset();
// Note: For simplicity, always LEFT JOIN the specified relationship and perform check on that instance of LEFT JOIN.
// There may be potential for optimization of this in the future, but it will be tricky.
const relations = (0, ts_simple_nameof_1.nameof)(relationSelector);
this.joinMultipleProperties(relations, this._query.leftJoin, false);
// Add arbitrary COUNT property to HAVING statement driving this check.
const countProp = (0, ts_simple_nameof_1.nameof)(relationCountPropSelector);
this._queryParts.push(new QueryBuilderPart_1.QueryBuilderPart(havingQueryAction, [`COUNT(${this._lastAlias}.${countProp}) ${havingCountComparer} 0`]));
// Create join condition if necessary.
if (conditionPropSelector) {
const conditionProp = (0, ts_simple_nameof_1.nameof)(conditionPropSelector);
// Set QueryWhereType.Joined to enable valid use of conditional method.
this._queryWhereType = QueryWhereType_1.QueryWhereType.Joined;
this.createJoinCondition(conditionProp);
}
return this;
}
setJoinIfNotCompare() {
// We may be joining a relation to make a comparison on that relation.
// If so, leave QueryMode as Compare.
// If not, set QueryMode to Join.
if (this._queryMode !== QueryMode_1.QueryMode.Compare) {
this._queryMode = QueryMode_1.QueryMode.Join;
}
}
}
exports.Query = Query;