mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
Merge pull request #330 from AlexMesser/master
implemented partial select, issues #329 #245
This commit is contained in:
commit
13f8036b2b
@ -16,6 +16,7 @@ import {OptimisticLockCanNotBeUsedError} from "./error/OptimisticLockCanNotBeUse
|
||||
import {PostgresDriver} from "../driver/postgres/PostgresDriver";
|
||||
import {MysqlDriver} from "../driver/mysql/MysqlDriver";
|
||||
import {LockNotSupportedOnGivenDriverError} from "./error/LockNotSupportedOnGivenDriverError";
|
||||
import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
|
||||
/**
|
||||
*/
|
||||
@ -57,6 +58,14 @@ export interface JoinMapping {
|
||||
isMany: boolean;
|
||||
}
|
||||
|
||||
// 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: implement @Where decorator
|
||||
// todo: add quoting functions
|
||||
// todo: .addCount and .addCountSelect()
|
||||
// todo: add selectAndMap
|
||||
|
||||
/**
|
||||
* Allows to build complex sql queries in a fashion way and execute those queries.
|
||||
*/
|
||||
@ -87,6 +96,7 @@ export class QueryBuilder<Entity> {
|
||||
protected lockVersion?: number|Date;
|
||||
protected skipNumber: number;
|
||||
protected takeNumber: number;
|
||||
protected enableQuoting: boolean = true;
|
||||
protected ignoreParentTablesJoins: boolean = false;
|
||||
|
||||
/**
|
||||
@ -118,6 +128,14 @@ export class QueryBuilder<Entity> {
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Disable escaping.
|
||||
*/
|
||||
disableQuoting(): this {
|
||||
this.enableQuoting = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates DELETE query.
|
||||
*/
|
||||
@ -150,7 +168,7 @@ export class QueryBuilder<Entity> {
|
||||
if (tableNameOrEntityOrUpdateSet instanceof Function) {
|
||||
const aliasName = (<any> tableNameOrEntityOrUpdateSet).name;
|
||||
const aliasObj = new Alias(aliasName);
|
||||
aliasObj.target = <Function> tableNameOrEntityOrUpdateSet;
|
||||
aliasObj.metadata = this.connection.getMetadata(tableNameOrEntityOrUpdateSet);
|
||||
this.aliasMap.addMainAlias(aliasObj);
|
||||
this.fromEntity = { alias: aliasObj };
|
||||
|
||||
@ -230,9 +248,24 @@ export class QueryBuilder<Entity> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets locking mode.
|
||||
*/
|
||||
setLock(lockMode: "optimistic", lockVersion: number): this;
|
||||
|
||||
/**
|
||||
* Sets locking mode.
|
||||
*/
|
||||
setLock(lockMode: "optimistic", lockVersion: Date): this;
|
||||
|
||||
/**
|
||||
* Sets locking mode.
|
||||
*/
|
||||
setLock(lockMode: "pessimistic_read"|"pessimistic_write"): this;
|
||||
|
||||
/**
|
||||
* Sets locking mode.
|
||||
*/
|
||||
setLock(lockMode: "optimistic"|"pessimistic_read"|"pessimistic_write", lockVersion?: number|Date): this {
|
||||
this.lockMode = lockMode;
|
||||
this.lockVersion = lockVersion;
|
||||
@ -245,7 +278,7 @@ export class QueryBuilder<Entity> {
|
||||
*/
|
||||
from(entityTarget: Function|string, alias: string): this {
|
||||
const aliasObj = new Alias(alias);
|
||||
aliasObj.target = entityTarget;
|
||||
aliasObj.metadata = this.connection.getMetadata(entityTarget);
|
||||
this.aliasMap.addMainAlias(aliasObj);
|
||||
this.fromEntity = {alias: aliasObj};
|
||||
return this;
|
||||
@ -861,7 +894,7 @@ export class QueryBuilder<Entity> {
|
||||
sql += this.createOffsetExpression();
|
||||
sql += this.createLockExpression();
|
||||
[sql] = this.connection.driver.escapeQueryWithParameters(sql, this.parameters);
|
||||
return sql;
|
||||
return sql.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -880,7 +913,7 @@ export class QueryBuilder<Entity> {
|
||||
sql += this.createLimitExpression();
|
||||
sql += this.createOffsetExpression();
|
||||
sql += this.createLockExpression();
|
||||
return sql;
|
||||
return sql.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -946,11 +979,11 @@ export class QueryBuilder<Entity> {
|
||||
const [sql, parameters] = this.getSqlWithParameters({ skipOrderBy: true });
|
||||
const [selects, orderBys] = this.createOrderByCombinedWithSelectExpression("distinctAlias");
|
||||
|
||||
const distinctAlias = this.connection.driver.escapeTableName("distinctAlias");
|
||||
const distinctAlias = this.escapeTable("distinctAlias");
|
||||
const metadata = this.connection.getMetadata(this.fromEntity.alias.target);
|
||||
let idsQuery = `SELECT `;
|
||||
idsQuery += metadata.primaryColumns.map((primaryColumn, index) => {
|
||||
const propertyName = this.connection.driver.escapeAliasName(mainAliasName + "_" + primaryColumn.fullName);
|
||||
const propertyName = this.escapeAlias(mainAliasName + "_" + primaryColumn.fullName);
|
||||
if (index === 0) {
|
||||
return `DISTINCT(${distinctAlias}.${propertyName}) as ids_${primaryColumn.fullName}`;
|
||||
} else {
|
||||
@ -1021,7 +1054,6 @@ export class QueryBuilder<Entity> {
|
||||
|
||||
return this.loadRelationCounts(queryRunner as QueryRunner, results)
|
||||
.then(counts => {
|
||||
// console.log("counts: ", counts);
|
||||
return results;
|
||||
});
|
||||
})
|
||||
@ -1051,7 +1083,6 @@ export class QueryBuilder<Entity> {
|
||||
|
||||
return this.loadRelationCounts(queryRunner as QueryRunner, results)
|
||||
.then(counts => {
|
||||
// console.log("counts: ", counts);
|
||||
return results;
|
||||
});
|
||||
})
|
||||
@ -1090,9 +1121,9 @@ export class QueryBuilder<Entity> {
|
||||
const mainAlias = this.fromTableName ? this.fromTableName : this.aliasMap.mainAlias.name; // todo: will this work with "fromTableName"?
|
||||
const metadata = this.connection.getMetadata(this.fromEntity.alias.target);
|
||||
|
||||
const distinctAlias = this.connection.driver.escapeAliasName(mainAlias);
|
||||
const distinctAlias = this.escapeAlias(mainAlias);
|
||||
let countSql = `COUNT(` + metadata.primaryColumnsWithParentIdColumns.map((primaryColumn, index) => {
|
||||
const propertyName = this.connection.driver.escapeColumnName(primaryColumn.fullName);
|
||||
const propertyName = this.escapeColumn(primaryColumn.fullName);
|
||||
if (index === 0) {
|
||||
return `DISTINCT(${distinctAlias}.${propertyName})`;
|
||||
} else {
|
||||
@ -1226,7 +1257,10 @@ export class QueryBuilder<Entity> {
|
||||
}
|
||||
|
||||
this.joins.forEach(join => {
|
||||
const property = join.tableName || join.alias.target || (join.alias.parentAliasName + "." + join.alias.parentPropertyName);
|
||||
let property = join.tableName || join.alias.target;
|
||||
if (join.alias.parentAliasName && join.alias.parentPropertyName) {
|
||||
property = join.alias.parentAliasName + "." + join.alias.parentPropertyName;
|
||||
}
|
||||
qb.join(join.type, property, join.alias.name, join.condition || "", undefined, join.mapToProperty, join.isMappingMany);
|
||||
});
|
||||
|
||||
@ -1277,6 +1311,24 @@ export class QueryBuilder<Entity> {
|
||||
return qb;
|
||||
}
|
||||
|
||||
escapeAlias(name: string) {
|
||||
if (!this.enableQuoting)
|
||||
return name;
|
||||
return this.connection.driver.escapeAliasName(name);
|
||||
}
|
||||
|
||||
escapeColumn(name: string) {
|
||||
if (!this.enableQuoting)
|
||||
return name;
|
||||
return this.connection.driver.escapeColumnName(name);
|
||||
}
|
||||
|
||||
escapeTable(name: string) {
|
||||
if (!this.enableQuoting)
|
||||
return name;
|
||||
return this.connection.driver.escapeTableName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables special query builder options.
|
||||
*/
|
||||
@ -1362,7 +1414,7 @@ export class QueryBuilder<Entity> {
|
||||
|
||||
return queryBuilder
|
||||
.select(`${parentMetadata.name + "." + parentMetadata.primaryColumn.propertyName} AS id`)
|
||||
.addSelect(`COUNT(${ this.connection.driver.escapeAliasName(relation.propertyName) + "." + this.connection.driver.escapeColumnName(relation.inverseEntityMetadata.primaryColumn.fullName) }) as cnt`)
|
||||
.addSelect(`COUNT(${ this.escapeAlias(relation.propertyName) + "." + this.escapeColumn(relation.inverseEntityMetadata.primaryColumn.fullName) }) as cnt`)
|
||||
.from(parentMetadata.target, parentMetadata.name)
|
||||
.leftJoin(parentMetadata.name + "." + relation.propertyName, relation.propertyName, relationCountMeta.condition)
|
||||
.setParameters(this.parameters)
|
||||
@ -1370,7 +1422,6 @@ export class QueryBuilder<Entity> {
|
||||
.groupBy(parentMetadata.name + "." + parentMetadata.primaryColumn.propertyName)
|
||||
.getRawMany()
|
||||
.then((results: { id: any, cnt: any }[]) => {
|
||||
// console.log(relationCountMeta.entities);
|
||||
relationCountMeta.entities.forEach(entityWithMetadata => {
|
||||
const entityId = entityWithMetadata.entity[entityWithMetadata.metadata.primaryColumn.propertyName];
|
||||
const entityResult = results.find(result => {
|
||||
@ -1400,57 +1451,72 @@ export class QueryBuilder<Entity> {
|
||||
return transformer.transform(results);
|
||||
}
|
||||
|
||||
protected buildEscapedEntityColumnSelects(alias: Alias): string[] {
|
||||
const hasMainAlias = this.selects.some(select => select === alias.name);
|
||||
|
||||
const columns: ColumnMetadata[] = hasMainAlias ? alias.metadata.columns : alias.metadata.columns.filter(column => {
|
||||
return this.selects.some(select => select === alias.name + "." + column.propertyName);
|
||||
});
|
||||
|
||||
return columns.map(column => {
|
||||
return this.escapeAlias(alias.name) + "." + this.escapeColumn(column.fullName) +
|
||||
" AS " + this.escapeAlias(alias.name + "_" + column.fullName);
|
||||
});
|
||||
};
|
||||
|
||||
protected findEntityColumnSelects(alias: Alias): string[] {
|
||||
const mainAlias = this.selects.find(select => select === alias.name);
|
||||
if (mainAlias)
|
||||
return [mainAlias];
|
||||
|
||||
return this.selects.filter(select => {
|
||||
return alias.metadata.columns.some(column => select === alias.name + "." + column.propertyName);
|
||||
});
|
||||
};
|
||||
|
||||
protected createSelectExpression() {
|
||||
// todo throw exception if selects or from is missing
|
||||
|
||||
let alias: string = "", tableName: string;
|
||||
const allSelects: string[] = [];
|
||||
const excludedSelects: string[] = [];
|
||||
|
||||
if (this.fromTableName) {
|
||||
tableName = this.fromTableName;
|
||||
alias = this.fromTableAlias;
|
||||
// console.log("ALIAS F:", alias);
|
||||
|
||||
} else if (this.fromEntity) {
|
||||
const metadata = this.aliasMap.getEntityMetadataByAlias(this.fromEntity.alias);
|
||||
if (!metadata)
|
||||
if (!this.fromEntity.alias.metadata)
|
||||
throw new Error("Cannot get entity metadata for the given alias " + this.fromEntity.alias.name);
|
||||
tableName = metadata.table.name;
|
||||
tableName = this.fromEntity.alias.metadata.table.name;
|
||||
alias = this.fromEntity.alias.name;
|
||||
// console.log("ALIAS N:", this.fromEntity.alias);
|
||||
// console.log("ALIAS N:", alias);
|
||||
|
||||
// add select from the main table
|
||||
if (this.selects.indexOf(alias) !== -1) {
|
||||
metadata.columns.forEach(column => {
|
||||
allSelects.push(this.connection.driver.escapeAliasName(alias) + "." + this.connection.driver.escapeColumnName(column.fullName) + " AS " + this.connection.driver.escapeAliasName(alias + "_" + column.fullName));
|
||||
});
|
||||
}
|
||||
|
||||
allSelects.push(...this.buildEscapedEntityColumnSelects(this.aliasMap.mainAlias));
|
||||
excludedSelects.push(...this.findEntityColumnSelects(this.aliasMap.mainAlias));
|
||||
} else {
|
||||
throw new Error("No from given");
|
||||
}
|
||||
|
||||
// add selects from joins
|
||||
this.joins
|
||||
.filter(join => this.selects.indexOf(join.alias.name) !== -1)
|
||||
.forEach(join => {
|
||||
const joinMetadata = this.aliasMap.getEntityMetadataByAlias(join.alias);
|
||||
if (joinMetadata) {
|
||||
joinMetadata.columns.forEach(column => {
|
||||
allSelects.push(this.connection.driver.escapeAliasName(join.alias.name) + "." + this.connection.driver.escapeColumnName(column.fullName) + " AS " + this.connection.driver.escapeAliasName(join.alias.name + "_" + column.fullName));
|
||||
});
|
||||
} else {
|
||||
allSelects.push(this.connection.driver.escapeAliasName(join.alias.name));
|
||||
this.joins.forEach(join => {
|
||||
if (join.alias.metadata) {
|
||||
allSelects.push(...this.buildEscapedEntityColumnSelects(join.alias));
|
||||
excludedSelects.push(...this.findEntityColumnSelects(join.alias));
|
||||
} else {
|
||||
const hasMainAlias = this.selects.some(select => select === join.alias.name);
|
||||
if (hasMainAlias) {
|
||||
allSelects.push(this.escapeAlias(join.alias.name) + ".*");
|
||||
excludedSelects.push(join.alias.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.ignoreParentTablesJoins && !this.fromTableName) {
|
||||
const metadata = this.connection.getMetadata(this.aliasMap.mainAlias.target);
|
||||
if (metadata.parentEntityMetadata && metadata.parentIdColumns) {
|
||||
const alias = "parentIdColumn_" + this.connection.driver.escapeAliasName(metadata.parentEntityMetadata.table.name);
|
||||
metadata.parentEntityMetadata.columns.forEach(column => {
|
||||
allSelects.push(alias + "." + this.connection.driver.escapeColumnName(column.fullName) + " AS " + alias + "_" + this.connection.driver.escapeAliasName(column.fullName));
|
||||
if (this.aliasMap.mainAlias.metadata.parentEntityMetadata && this.aliasMap.mainAlias.metadata.parentIdColumns) {
|
||||
const alias = "parentIdColumn_" + this.escapeAlias(this.aliasMap.mainAlias.metadata.parentEntityMetadata.table.name);
|
||||
this.aliasMap.mainAlias.metadata.parentEntityMetadata.columns.forEach(column => {
|
||||
// TODO implement partial select
|
||||
allSelects.push(alias + "." + this.escapeColumn(column.fullName) + " AS " + alias + "_" + this.escapeAlias(column.fullName));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1464,19 +1530,17 @@ export class QueryBuilder<Entity> {
|
||||
if (!foundAlias)
|
||||
throw new Error(`Alias "${parentAlias}" was not found`);
|
||||
|
||||
const parentMetadata = this.aliasMap.getEntityMetadataByAlias(foundAlias);
|
||||
if (!parentMetadata)
|
||||
if (!foundAlias.metadata)
|
||||
throw new Error("Cannot get entity metadata for the given alias " + foundAlias.name);
|
||||
const relation = parentMetadata.findRelationWithPropertyName(join.alias.parentPropertyName);
|
||||
const relation = foundAlias.metadata.findRelationWithPropertyName(join.alias.parentPropertyName);
|
||||
const junctionMetadata = relation.junctionEntityMetadata;
|
||||
// const junctionTable = junctionMetadata.table.name;
|
||||
|
||||
junctionMetadata.columns.forEach(column => {
|
||||
allSelects.push(this.connection.driver.escapeAliasName(join.alias.name) + "." + this.connection.driver.escapeColumnName(column.fullName) + " AS " + this.connection.driver.escapeAliasName(join.alias.name + "_" + column.fullName));
|
||||
allSelects.push(this.escapeAlias(join.alias.name) + "." + this.escapeColumn(column.fullName) + " AS " + this.escapeAlias(join.alias.name + "_" + column.fullName));
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
/*if (this.enableRelationIdValues) {
|
||||
const parentMetadata = this.aliasMap.getEntityMetadataByAlias(this.aliasMap.mainAlias);
|
||||
if (!parentMetadata)
|
||||
@ -1487,18 +1551,17 @@ export class QueryBuilder<Entity> {
|
||||
|
||||
const junctionMetadata = relation.junctionEntityMetadata;
|
||||
junctionMetadata.columns.forEach(column => {
|
||||
const select = this.connection.driver.escapeAliasName(this.aliasMap.mainAlias.name + "_" + junctionMetadata.table.name + "_ids") + "." +
|
||||
this.connection.driver.escapeColumnName(column.name) + " AS " +
|
||||
this.connection.driver.escapeAliasName(this.aliasMap.mainAlias.name + "_" + relation.name + "_ids_" + column.name);
|
||||
const select = this.escapeAlias(this.aliasMap.mainAlias.name + "_" + junctionMetadata.table.name + "_ids") + "." +
|
||||
this.escapeColumn(column.name) + " AS " +
|
||||
this.escapeAlias(this.aliasMap.mainAlias.name + "_" + relation.name + "_ids_" + column.name);
|
||||
allSelects.push(select);
|
||||
});
|
||||
});
|
||||
}*/
|
||||
|
||||
// add all other selects
|
||||
this.selects.filter(select => {
|
||||
return select !== alias && !this.joins.find(join => join.alias.name === select);
|
||||
}).forEach(select => allSelects.push(this.replacePropertyNames(select)));
|
||||
this.selects.filter(select => excludedSelects.indexOf(select) === -1)
|
||||
.forEach(select => allSelects.push(this.replacePropertyNames(select)));
|
||||
|
||||
// if still selection is empty, then simply set it to all (*)
|
||||
if (allSelects.length === 0)
|
||||
@ -1519,10 +1582,10 @@ export class QueryBuilder<Entity> {
|
||||
// create a selection query
|
||||
switch (this.type) {
|
||||
case "select":
|
||||
return "SELECT " + allSelects.join(", ") + " FROM " + this.connection.driver.escapeTableName(tableName) + " " + this.connection.driver.escapeAliasName(alias) + lock;
|
||||
return "SELECT " + allSelects.join(", ") + " FROM " + this.escapeTable(tableName) + " " + this.escapeAlias(alias) + lock;
|
||||
case "delete":
|
||||
return "DELETE FROM " + this.connection.driver.escapeTableName(tableName);
|
||||
// return "DELETE " + (alias ? this.connection.driver.escapeAliasName(alias) : "") + " FROM " + this.connection.driver.escapeTableName(tableName) + " " + (alias ? this.connection.driver.escapeAliasName(alias) : ""); // TODO: only mysql supports aliasing, so what to do with aliases in DELETE queries? right now aliases are used however we are relaying that they will always match a table names
|
||||
return "DELETE FROM " + this.escapeTable(tableName);
|
||||
// return "DELETE " + (alias ? this.escapeAlias(alias) : "") + " FROM " + this.escapeTable(tableName) + " " + (alias ? this.escapeAlias(alias) : ""); // TODO: only mysql supports aliasing, so what to do with aliases in DELETE queries? right now aliases are used however we are relaying that they will always match a table names
|
||||
case "update":
|
||||
const updateSet = Object.keys(this.updateQuerySet).map(key => key + "=:updateQuerySet_" + key);
|
||||
const params = Object.keys(this.updateQuerySet).reduce((object, key) => {
|
||||
@ -1531,7 +1594,7 @@ export class QueryBuilder<Entity> {
|
||||
return object;
|
||||
}, {});
|
||||
this.setParameters(params);
|
||||
return "UPDATE " + tableName + " " + (alias ? this.connection.driver.escapeAliasName(alias) : "") + " SET " + updateSet;
|
||||
return "UPDATE " + tableName + " " + (alias ? this.escapeAlias(alias) : "") + " SET " + updateSet;
|
||||
}
|
||||
|
||||
throw new Error("No query builder type is specified.");
|
||||
@ -1582,22 +1645,21 @@ export class QueryBuilder<Entity> {
|
||||
*/
|
||||
private replacePropertyNames(statement: string) {
|
||||
this.aliasMap.aliases.forEach(alias => {
|
||||
const metadata = this.aliasMap.getEntityMetadataByAlias(alias);
|
||||
if (!metadata) return;
|
||||
metadata.embeddeds.forEach(embedded => {
|
||||
if (!alias.metadata) return;
|
||||
alias.metadata.embeddeds.forEach(embedded => {
|
||||
embedded.columns.forEach(column => {
|
||||
const expression = alias.name + "\\." + embedded.propertyName + "\\." + column.propertyName + "([ =]|.{0}$)";
|
||||
statement = statement.replace(new RegExp(expression, "gm"), this.connection.driver.escapeAliasName(alias.name) + "." + this.connection.driver.escapeColumnName(column.fullName) + "$1");
|
||||
statement = statement.replace(new RegExp(expression, "gm"), this.escapeAlias(alias.name) + "." + this.escapeColumn(column.fullName) + "$1");
|
||||
});
|
||||
// todo: what about embedded relations here?
|
||||
});
|
||||
metadata.columns.filter(column => !column.isInEmbedded).forEach(column => {
|
||||
alias.metadata.columns.filter(column => !column.isInEmbedded).forEach(column => {
|
||||
const expression = alias.name + "\\." + column.propertyName + "([ =]|.{0}$)";
|
||||
statement = statement.replace(new RegExp(expression, "gm"), this.connection.driver.escapeAliasName(alias.name) + "." + this.connection.driver.escapeColumnName(column.fullName) + "$1");
|
||||
statement = statement.replace(new RegExp(expression, "gm"), this.escapeAlias(alias.name) + "." + this.escapeColumn(column.fullName) + "$1");
|
||||
});
|
||||
metadata.relationsWithJoinColumns/*.filter(relation => !relation.isInEmbedded)*/.forEach(relation => {
|
||||
alias.metadata.relationsWithJoinColumns/*.filter(relation => !relation.isInEmbedded)*/.forEach(relation => {
|
||||
const expression = alias.name + "\\." + relation.propertyName + "([ =]|.{0}$)";
|
||||
statement = statement.replace(new RegExp(expression, "gm"), this.connection.driver.escapeAliasName(alias.name) + "." + this.connection.driver.escapeColumnName(relation.name) + "$1");
|
||||
statement = statement.replace(new RegExp(expression, "gm"), this.escapeAlias(alias.name) + "." + this.escapeColumn(relation.name) + "$1");
|
||||
});
|
||||
});
|
||||
return statement;
|
||||
@ -1623,16 +1685,15 @@ export class QueryBuilder<Entity> {
|
||||
|
||||
let condition1 = "";
|
||||
if (relation.isOwning) {
|
||||
condition1 = this.connection.driver.escapeAliasName(junctionAlias) + "." + this.connection.driver.escapeColumnName(junctionMetadata.columns[0].fullName) + "=" + this.connection.driver.escapeAliasName(parentAlias) + "." + this.connection.driver.escapeColumnName(joinTableColumn);
|
||||
condition1 = this.escapeAlias(junctionAlias) + "." + this.escapeColumn(junctionMetadata.columns[0].fullName) + "=" + this.escapeAlias(parentAlias) + "." + this.escapeColumn(joinTableColumn);
|
||||
// condition2 = joinAlias + "." + inverseJoinColumnName + "=" + junctionAlias + "." + junctionMetadata.columns[1].name;
|
||||
} else {
|
||||
condition1 = this.connection.driver.escapeAliasName(junctionAlias) + "." + this.connection.driver.escapeColumnName(junctionMetadata.columns[1].fullName) + "=" + this.connection.driver.escapeAliasName(parentAlias) + "." + this.connection.driver.escapeColumnName(joinTableColumn);
|
||||
condition1 = this.escapeAlias(junctionAlias) + "." + this.escapeColumn(junctionMetadata.columns[1].fullName) + "=" + this.escapeAlias(parentAlias) + "." + this.escapeColumn(joinTableColumn);
|
||||
// condition2 = joinAlias + "." + inverseJoinColumnName + "=" + junctionAlias + "." + junctionMetadata.columns[0].name;
|
||||
}
|
||||
|
||||
return " " + join.type + " JOIN " + junctionTable + " " + this.connection.driver.escapeAliasName(junctionAlias) + " ON " + condition1;
|
||||
return " " + join.type + " JOIN " + junctionTable + " " + this.escapeAlias(junctionAlias) + " ON " + condition1;
|
||||
// " " + joinType + " JOIN " + joinTableName + " " + joinAlias + " " + join.conditionType + " " + condition2 + appendedCondition;
|
||||
// console.log(join);
|
||||
// return " " + join.type + " JOIN " + joinTableName + " " + join.alias.name + " " + (join.condition ? (join.conditionType + " " + join.condition) : "");
|
||||
});
|
||||
}
|
||||
@ -1642,16 +1703,16 @@ export class QueryBuilder<Entity> {
|
||||
const joinType = join.type; // === "INNER" ? "INNER" : "LEFT";
|
||||
let joinTableName: string = join.tableName;
|
||||
if (!joinTableName) {
|
||||
const metadata = this.aliasMap.getEntityMetadataByAlias(join.alias);
|
||||
if (!metadata)
|
||||
|
||||
if (!join.alias.metadata)
|
||||
throw new Error("Cannot get entity metadata for the given alias " + join.alias.name);
|
||||
|
||||
joinTableName = metadata.table.name;
|
||||
joinTableName = join.alias.metadata.table.name;
|
||||
}
|
||||
|
||||
const parentAlias = join.alias.parentAliasName;
|
||||
if (!parentAlias) {
|
||||
return " " + joinType + " JOIN " + this.connection.driver.escapeTableName(joinTableName) + " " + this.connection.driver.escapeAliasName(join.alias.name) + " " + (join.condition ? ( "ON " + this.replacePropertyNames(join.condition) ) : "");
|
||||
return " " + joinType + " JOIN " + this.escapeTable(joinTableName) + " " + this.escapeAlias(join.alias.name) + " " + (join.condition ? ( "ON " + this.replacePropertyNames(join.condition) ) : "");
|
||||
}
|
||||
|
||||
const foundAlias = this.aliasMap.findAliasByName(parentAlias);
|
||||
@ -1676,25 +1737,25 @@ export class QueryBuilder<Entity> {
|
||||
|
||||
let condition1 = "", condition2 = "";
|
||||
if (relation.isOwning) {
|
||||
condition1 = this.connection.driver.escapeAliasName(junctionAlias) + "." + this.connection.driver.escapeColumnName(junctionMetadata.columns[0].fullName) + "=" + this.connection.driver.escapeAliasName(parentAlias) + "." + this.connection.driver.escapeColumnName(joinTableColumn);
|
||||
condition2 = this.connection.driver.escapeAliasName(joinAlias) + "." + this.connection.driver.escapeColumnName(inverseJoinColumnName) + "=" + this.connection.driver.escapeAliasName(junctionAlias) + "." + this.connection.driver.escapeColumnName(junctionMetadata.columns[1].fullName);
|
||||
condition1 = this.escapeAlias(junctionAlias) + "." + this.escapeColumn(junctionMetadata.columns[0].fullName) + "=" + this.escapeAlias(parentAlias) + "." + this.escapeColumn(joinTableColumn);
|
||||
condition2 = this.escapeAlias(joinAlias) + "." + this.escapeColumn(inverseJoinColumnName) + "=" + this.escapeAlias(junctionAlias) + "." + this.escapeColumn(junctionMetadata.columns[1].fullName);
|
||||
} else {
|
||||
condition1 = this.connection.driver.escapeAliasName(junctionAlias) + "." + this.connection.driver.escapeColumnName(junctionMetadata.columns[1].fullName) + "=" + this.connection.driver.escapeAliasName(parentAlias) + "." + this.connection.driver.escapeColumnName(joinTableColumn);
|
||||
condition2 = this.connection.driver.escapeAliasName(joinAlias) + "." + this.connection.driver.escapeColumnName(inverseJoinColumnName) + "=" + this.connection.driver.escapeAliasName(junctionAlias) + "." + this.connection.driver.escapeColumnName(junctionMetadata.columns[0].fullName);
|
||||
condition1 = this.escapeAlias(junctionAlias) + "." + this.escapeColumn(junctionMetadata.columns[1].fullName) + "=" + this.escapeAlias(parentAlias) + "." + this.escapeColumn(joinTableColumn);
|
||||
condition2 = this.escapeAlias(joinAlias) + "." + this.escapeColumn(inverseJoinColumnName) + "=" + this.escapeAlias(junctionAlias) + "." + this.escapeColumn(junctionMetadata.columns[0].fullName);
|
||||
}
|
||||
|
||||
return " " + joinType + " JOIN " + this.connection.driver.escapeTableName(junctionTable) + " " + this.connection.driver.escapeAliasName(junctionAlias) + " ON " + condition1 +
|
||||
" " + joinType + " JOIN " + this.connection.driver.escapeTableName(joinTableName) + " " + this.connection.driver.escapeAliasName(joinAlias) + " ON " + condition2 + appendedCondition;
|
||||
return " " + joinType + " JOIN " + this.escapeTable(junctionTable) + " " + this.escapeAlias(junctionAlias) + " ON " + condition1 +
|
||||
" " + joinType + " JOIN " + this.escapeTable(joinTableName) + " " + this.escapeAlias(joinAlias) + " ON " + condition2 + appendedCondition;
|
||||
|
||||
} else if (relation.isManyToOne || (relation.isOneToOne && relation.isOwning)) {
|
||||
const joinTableColumn = relation.joinColumn.referencedColumn.fullName;
|
||||
const condition = this.connection.driver.escapeAliasName(join.alias.name) + "." + this.connection.driver.escapeColumnName(joinTableColumn) + "=" + this.connection.driver.escapeAliasName(parentAlias) + "." + this.connection.driver.escapeColumnName(relation.name);
|
||||
return " " + joinType + " JOIN " + this.connection.driver.escapeTableName(joinTableName) + " " + this.connection.driver.escapeAliasName(join.alias.name) + " ON " + condition + appendedCondition;
|
||||
const condition = this.escapeAlias(join.alias.name) + "." + this.escapeColumn(joinTableColumn) + "=" + this.escapeAlias(parentAlias) + "." + this.escapeColumn(relation.name);
|
||||
return " " + joinType + " JOIN " + this.escapeTable(joinTableName) + " " + this.escapeAlias(join.alias.name) + " ON " + condition + appendedCondition;
|
||||
|
||||
} else if (relation.isOneToMany || (relation.isOneToOne && !relation.isOwning)) {
|
||||
const joinTableColumn = relation.inverseRelation.joinColumn.referencedColumn.fullName;
|
||||
const condition = this.connection.driver.escapeAliasName(join.alias.name) + "." + this.connection.driver.escapeColumnName(relation.inverseRelation.name) + "=" + this.connection.driver.escapeAliasName(parentAlias) + "." + this.connection.driver.escapeColumnName(joinTableColumn);
|
||||
return " " + joinType + " JOIN " + this.connection.driver.escapeTableName(joinTableName) + " " + this.connection.driver.escapeAliasName(join.alias.name) + " ON " + condition + appendedCondition;
|
||||
const condition = this.escapeAlias(join.alias.name) + "." + this.escapeColumn(relation.inverseRelation.name) + "=" + this.escapeAlias(parentAlias) + "." + this.escapeColumn(joinTableColumn);
|
||||
return " " + joinType + " JOIN " + this.escapeTable(joinTableName) + " " + this.escapeAlias(join.alias.name) + " ON " + condition + appendedCondition;
|
||||
|
||||
} else {
|
||||
throw new Error("Unexpected relation type"); // this should not be possible
|
||||
@ -1704,8 +1765,8 @@ export class QueryBuilder<Entity> {
|
||||
if (!this.ignoreParentTablesJoins && !this.fromTableName) {
|
||||
const metadata = this.connection.getMetadata(this.aliasMap.mainAlias.target);
|
||||
if (metadata.parentEntityMetadata && metadata.parentIdColumns) {
|
||||
const alias = this.connection.driver.escapeAliasName("parentIdColumn_" + metadata.parentEntityMetadata.table.name);
|
||||
joins += " JOIN " + this.connection.driver.escapeTableName(metadata.parentEntityMetadata.table.name)
|
||||
const alias = this.escapeAlias("parentIdColumn_" + metadata.parentEntityMetadata.table.name);
|
||||
joins += " JOIN " + this.escapeTable(metadata.parentEntityMetadata.table.name)
|
||||
+ " " + alias + " ON ";
|
||||
joins += metadata.parentIdColumns.map(parentIdColumn => {
|
||||
return this.aliasMap.mainAlias.name + "." + parentIdColumn.fullName + "=" + alias + "." + parentIdColumn.propertyName;
|
||||
@ -1729,18 +1790,18 @@ export class QueryBuilder<Entity> {
|
||||
|
||||
let condition1 = "";
|
||||
if (relation.isOwning) {
|
||||
condition1 = this.connection.driver.escapeAliasName(junctionAlias) + "." +
|
||||
this.connection.driver.escapeColumnName(junctionMetadata.columns[0].name) + "=" +
|
||||
this.connection.driver.escapeAliasName(this.aliasMap.mainAlias.name) + "." +
|
||||
this.connection.driver.escapeColumnName(joinTableColumn);
|
||||
condition1 = this.escapeAlias(junctionAlias) + "." +
|
||||
this.escapeColumn(junctionMetadata.columns[0].name) + "=" +
|
||||
this.escapeAlias(this.aliasMap.mainAlias.name) + "." +
|
||||
this.escapeColumn(joinTableColumn);
|
||||
} else {
|
||||
condition1 = this.connection.driver.escapeAliasName(junctionAlias) + "." +
|
||||
this.connection.driver.escapeColumnName(junctionMetadata.columns[1].name) + "=" +
|
||||
this.connection.driver.escapeAliasName(this.aliasMap.mainAlias.name) + "." +
|
||||
this.connection.driver.escapeColumnName(joinTableColumn);
|
||||
condition1 = this.escapeAlias(junctionAlias) + "." +
|
||||
this.escapeColumn(junctionMetadata.columns[1].name) + "=" +
|
||||
this.escapeAlias(this.aliasMap.mainAlias.name) + "." +
|
||||
this.escapeColumn(joinTableColumn);
|
||||
}
|
||||
|
||||
return " LEFT JOIN " + junctionTable + " " + this.connection.driver.escapeAliasName(junctionAlias) + " ON " + condition1;
|
||||
return " LEFT JOIN " + junctionTable + " " + this.escapeAlias(junctionAlias) + " ON " + condition1;
|
||||
}).join(" ");
|
||||
}*/
|
||||
|
||||
@ -1764,14 +1825,14 @@ export class QueryBuilder<Entity> {
|
||||
const selectString = Object.keys(orderBys)
|
||||
.map(columnName => {
|
||||
const [alias, column, ...embeddedProperties] = columnName.split(".");
|
||||
return this.connection.driver.escapeAliasName(parentAlias) + "." + this.connection.driver.escapeColumnName(alias + "_" + column + embeddedProperties.join("_"));
|
||||
return this.escapeAlias(parentAlias) + "." + this.escapeColumn(alias + "_" + column + embeddedProperties.join("_"));
|
||||
})
|
||||
.join(", ");
|
||||
|
||||
const orderByString = Object.keys(orderBys)
|
||||
.map(columnName => {
|
||||
const [alias, column, ...embeddedProperties] = columnName.split(".");
|
||||
return this.connection.driver.escapeAliasName(parentAlias) + "." + this.connection.driver.escapeColumnName(alias + "_" + column + embeddedProperties.join("_")) + " " + this.orderBys[columnName];
|
||||
return this.escapeAlias(parentAlias) + "." + this.escapeColumn(alias + "_" + column + embeddedProperties.join("_")) + " " + this.orderBys[columnName];
|
||||
})
|
||||
.join(", ");
|
||||
|
||||
@ -1882,14 +1943,30 @@ export class QueryBuilder<Entity> {
|
||||
const aliasObj = new Alias(alias);
|
||||
this.aliasMap.addAlias(aliasObj);
|
||||
if (entityOrProperty instanceof Function) {
|
||||
aliasObj.target = entityOrProperty;
|
||||
aliasObj.metadata = this.connection.getMetadata(entityOrProperty);
|
||||
|
||||
} else if (this.isPropertyAlias(entityOrProperty)) {
|
||||
[aliasObj.parentAliasName, aliasObj.parentPropertyName] = entityOrProperty.split(".");
|
||||
|
||||
const parentAlias = this.aliasMap.findAliasByName(aliasObj.parentAliasName);
|
||||
// todo: throw exception if parentAlias not found
|
||||
// todo: throw exception if parentAlias.metadata not found
|
||||
// todo: throw exception if parentAlias not found
|
||||
// todo: throw exception if relation not found?
|
||||
const relation = parentAlias!.metadata.findRelationWithPropertyName(aliasObj.parentPropertyName);
|
||||
aliasObj.metadata = relation.inverseEntityMetadata;
|
||||
|
||||
} else if (typeof entityOrProperty === "string") {
|
||||
tableName = entityOrProperty;
|
||||
if (!mapToProperty)
|
||||
|
||||
// check if we have entity with such table name, and use its metadata if found
|
||||
const metadata = this.connection.entityMetadatas.find(metadata => metadata.table.name === entityOrProperty);
|
||||
if (metadata) {
|
||||
aliasObj.metadata = metadata;
|
||||
} else {
|
||||
tableName = entityOrProperty;
|
||||
}
|
||||
|
||||
if (!mapToProperty) // todo: comment why its needed
|
||||
mapToProperty = entityOrProperty;
|
||||
}
|
||||
|
||||
@ -1929,10 +2006,6 @@ export class QueryBuilder<Entity> {
|
||||
return this;
|
||||
}
|
||||
|
||||
private isValueSimpleString(str: any) {
|
||||
return /^[A-Za-z0-9_-]+$/.test(str);
|
||||
}
|
||||
|
||||
private isPropertyAlias(str: any): str is string {
|
||||
if (!(typeof str === "string"))
|
||||
return false;
|
||||
@ -1960,8 +2033,8 @@ export class QueryBuilder<Entity> {
|
||||
const metadata = this.connection.getMetadata(this.aliasMap.mainAlias.target);
|
||||
|
||||
// create shortcuts for better readability
|
||||
const escapeAlias = (alias: string) => this.connection.driver.escapeAliasName(alias);
|
||||
const escapeColumn = (column: string) => this.connection.driver.escapeColumnName(column);
|
||||
const escapeAlias = (alias: string) => this.escapeAlias(alias);
|
||||
const escapeColumn = (column: string) => this.escapeColumn(column);
|
||||
|
||||
const alias = this.aliasMap.mainAlias.name;
|
||||
const parameters: ObjectLiteral = {};
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import {EntityMetadata} from "../../metadata/EntityMetadata";
|
||||
/**
|
||||
*/
|
||||
export class Alias {
|
||||
isMain: boolean;
|
||||
name: string;
|
||||
target: Function|string;
|
||||
metadata: EntityMetadata; // TODO can be undefined
|
||||
parentPropertyName: string;
|
||||
parentAliasName: string;
|
||||
|
||||
@ -15,4 +16,8 @@ export class Alias {
|
||||
return this.parentAliasName + "." + this.parentPropertyName;
|
||||
}
|
||||
|
||||
get target(): Function|string {
|
||||
return this.metadata.target;
|
||||
}
|
||||
|
||||
}
|
||||
@ -57,10 +57,13 @@ export class AliasMap {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
getEntityMetadataByAlias(alias: Alias): EntityMetadata|undefined {
|
||||
if (alias.target) {
|
||||
if (alias.metadata) {
|
||||
// todo: use connection.getMetadata instead?
|
||||
return this.connection.getMetadata(alias.target);
|
||||
return alias.metadata;
|
||||
|
||||
} else if (alias.parentAliasName && alias.parentPropertyName) {
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ import {SqliteDriver} from "../../../../src/driver/sqlite/SqliteDriver";
|
||||
import {OracleDriver} from "../../../../src/driver/oracle/OracleDriver";
|
||||
import {LockNotSupportedOnGivenDriverError} from "../../../../src/query-builder/error/LockNotSupportedOnGivenDriverError";
|
||||
|
||||
describe.only("query builder > locking", () => {
|
||||
describe("query builder > locking", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await createTestingConnections({
|
||||
|
||||
21
test/functional/query-builder/select/entity/Category.ts
Normal file
21
test/functional/query-builder/select/entity/Category.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {Entity} from "../../../../../src/decorator/entity/Entity";
|
||||
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
|
||||
import {Column} from "../../../../../src/decorator/columns/Column";
|
||||
import {VersionColumn} from "../../../../../src/decorator/columns/VersionColumn";
|
||||
|
||||
@Entity()
|
||||
export class Category {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column()
|
||||
description: string;
|
||||
|
||||
@VersionColumn()
|
||||
version: string;
|
||||
|
||||
}
|
||||
29
test/functional/query-builder/select/entity/Post.ts
Normal file
29
test/functional/query-builder/select/entity/Post.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import {Entity} from "../../../../../src/decorator/entity/Entity";
|
||||
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
|
||||
import {Column} from "../../../../../src/decorator/columns/Column";
|
||||
import {VersionColumn} from "../../../../../src/decorator/columns/VersionColumn";
|
||||
import {Category} from "./Category";
|
||||
import {ManyToOne} from "../../../../../src/decorator/relations/ManyToOne";
|
||||
|
||||
@Entity()
|
||||
export class Post {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@Column()
|
||||
description: string;
|
||||
|
||||
@Column()
|
||||
rating: number;
|
||||
|
||||
@VersionColumn()
|
||||
version: string;
|
||||
|
||||
@ManyToOne(type => Category)
|
||||
category: Category;
|
||||
|
||||
}
|
||||
98
test/functional/query-builder/select/query-builder-select.ts
Normal file
98
test/functional/query-builder/select/query-builder-select.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import "reflect-metadata";
|
||||
import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
|
||||
import {Connection} from "../../../../src/connection/Connection";
|
||||
import {Post} from "./entity/Post";
|
||||
import {expect} from "chai";
|
||||
|
||||
describe("query builder > select", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await createTestingConnections({
|
||||
entities: [__dirname + "/entity/*{.js,.ts}"],
|
||||
schemaCreate: true,
|
||||
dropSchemaOnConnection: true,
|
||||
}));
|
||||
beforeEach(() => reloadTestingDatabases(connections));
|
||||
after(() => closeTestingConnections(connections));
|
||||
|
||||
it("should append all entity mapped columns from main selection to select statement", () => Promise.all(connections.map(async connection => {
|
||||
const sql = connection.entityManager.createQueryBuilder(Post, "post")
|
||||
.disableQuoting()
|
||||
.getSql();
|
||||
|
||||
expect(sql).to.equal("SELECT post.id AS post_id, " +
|
||||
"post.title AS post_title, " +
|
||||
"post.description AS post_description, " +
|
||||
"post.rating AS post_rating, post.version AS post_version, post.category AS post_category " +
|
||||
"FROM post post");
|
||||
})));
|
||||
|
||||
it("should append all entity mapped columns from both main selection and join selections to select statement", () => Promise.all(connections.map(async connection => {
|
||||
const sql = connection.entityManager.createQueryBuilder(Post, "post")
|
||||
.leftJoinAndSelect("category", "category")
|
||||
.disableQuoting()
|
||||
.getSql();
|
||||
|
||||
expect(sql).to.equal("SELECT post.id AS post_id, " +
|
||||
"post.title AS post_title, " +
|
||||
"post.description AS post_description, " +
|
||||
"post.rating AS post_rating, " +
|
||||
"post.version AS post_version, " +
|
||||
"post.category AS post_category, " +
|
||||
"category.id AS category_id, " +
|
||||
"category.name AS category_name," +
|
||||
" category.description AS category_description, " +
|
||||
"category.version AS category_version " +
|
||||
"FROM post post LEFT JOIN category category");
|
||||
})));
|
||||
|
||||
it("should append entity mapped columns from both main alias and join aliases to select statement", () => Promise.all(connections.map(async connection => {
|
||||
const sql = connection.entityManager.createQueryBuilder(Post, "post")
|
||||
.select("post.id")
|
||||
.addSelect("category.name")
|
||||
.leftJoin("category", "category")
|
||||
.disableQuoting()
|
||||
.getSql();
|
||||
|
||||
expect(sql).to.equal("SELECT post.id AS post_id, " +
|
||||
"category.name AS category_name " +
|
||||
"FROM post post LEFT JOIN category category");
|
||||
})));
|
||||
|
||||
it("should append entity mapped columns to select statement, if they passed as array", () => Promise.all(connections.map(async connection => {
|
||||
const sql = connection.entityManager.createQueryBuilder(Post, "post")
|
||||
.select(["post.id", "post.title"])
|
||||
.disableQuoting()
|
||||
.getSql();
|
||||
|
||||
expect(sql).to.equal("SELECT post.id AS post_id, post.title AS post_title FROM post post");
|
||||
})));
|
||||
|
||||
it("should append raw sql to select statement", () => Promise.all(connections.map(async connection => {
|
||||
const sql = connection.entityManager.createQueryBuilder(Post, "post")
|
||||
.select("COUNT(*) as cnt")
|
||||
.disableQuoting()
|
||||
.getSql();
|
||||
|
||||
expect(sql).to.equal("SELECT COUNT(*) as cnt FROM post post");
|
||||
})));
|
||||
|
||||
it("should append raw sql and entity mapped column to select statement", () => Promise.all(connections.map(async connection => {
|
||||
const sql = connection.entityManager.createQueryBuilder(Post, "post")
|
||||
.select(["COUNT(*) as cnt", "post.title"])
|
||||
.disableQuoting()
|
||||
.getSql();
|
||||
|
||||
expect(sql).to.equal("SELECT post.title AS post_title, COUNT(*) as cnt FROM post post");
|
||||
})));
|
||||
|
||||
it("should not create alias for selection, which is not entity mapped column", () => Promise.all(connections.map(async connection => {
|
||||
const sql = connection.entityManager.createQueryBuilder(Post, "post")
|
||||
.select("post.name")
|
||||
.disableQuoting()
|
||||
.getSql();
|
||||
|
||||
expect(sql).to.equal("SELECT post.name FROM post post");
|
||||
})));
|
||||
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user