feat: QueryBuilder performance optimizations (#9914)

* small optimization in driver utils - shortening alias become a bit faster

* added entity metadatas as a map into DataSource for search optimization purposes

* ultra small optimization - looks like symbols work slow. Need to re-think its usage

* small optimizations to improve performance

* replace property names optimization

* tsc error fix

* big optimization - now replacePropertyNames executed only for the final SQL query

* trying to fix the bug in select query builder with orders not being properly replaced with their aliaces

* fixing tests
This commit is contained in:
Umed Khudoiberdiev 2023-04-06 14:21:40 +05:00 committed by GitHub
parent f1330ad6e2
commit 12e9db07b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 357 additions and 143 deletions

View File

@ -113,6 +113,12 @@ export class DataSource {
*/
readonly entityMetadatas: EntityMetadata[] = []
/**
* All entity metadatas that are registered for this connection.
* This is a copy of #.entityMetadatas property -> used for more performant searches.
*/
readonly entityMetadatasMap = new Map<EntityTarget<any>, EntityMetadata>()
/**
* Used to work with query result cache.
*/
@ -555,7 +561,7 @@ export class DataSource {
throw new TypeORMError(`Query Builder is not supported by MongoDB.`)
if (alias) {
alias = DriverUtils.buildAlias(this.driver, alias)
alias = DriverUtils.buildAlias(this.driver, undefined, alias)
const metadata = this.getMetadata(
entityOrRunner as EntityTarget<Entity>,
)
@ -628,37 +634,50 @@ export class DataSource {
protected findMetadata(
target: EntityTarget<any>,
): EntityMetadata | undefined {
return this.entityMetadatas.find((metadata) => {
if (metadata.target === target) return true
if (InstanceChecker.isEntitySchema(target)) {
return metadata.name === target.options.name
const metadataFromMap = this.entityMetadatasMap.get(target)
if (metadataFromMap) return metadataFromMap
for (let [_, metadata] of this.entityMetadatasMap) {
if (
InstanceChecker.isEntitySchema(target) &&
metadata.name === target.options.name
) {
return metadata
}
if (typeof target === "string") {
if (target.indexOf(".") !== -1) {
return metadata.tablePath === target
if (metadata.tablePath === target) {
return metadata
}
} else {
return (
if (
metadata.name === target ||
metadata.tableName === target
)
) {
return metadata
}
}
}
if (
ObjectUtils.isObject(target) &&
ObjectUtils.isObjectWithName(target) &&
typeof target.name === "string"
) {
if (target.name.indexOf(".") !== -1) {
return metadata.tablePath === target.name
if (metadata.tablePath === target.name) {
return metadata
}
} else {
return (
if (
metadata.name === target.name ||
metadata.tableName === target.name
)
) {
return metadata
}
}
}
}
return false
})
return undefined
}
/**
@ -685,7 +704,12 @@ export class DataSource {
await connectionMetadataBuilder.buildEntityMetadatas(
flattenedEntities,
)
ObjectUtils.assign(this, { entityMetadatas: entityMetadatas })
ObjectUtils.assign(this, {
entityMetadatas: entityMetadatas,
entityMetadatasMap: new Map(
entityMetadatas.map((metadata) => [metadata.target, metadata]),
),
})
// create migration instances
const flattenedMigrations = ObjectUtils.mixedListToArray(

View File

@ -124,27 +124,23 @@ export class DriverUtils {
*/
static buildAlias(
{ maxAliasLength }: Driver,
buildOptions: { shorten?: boolean; joiner?: string } | string,
buildOptions: { shorten?: boolean; joiner?: string } | undefined,
...alias: string[]
): string {
if (typeof buildOptions === "string") {
alias.unshift(buildOptions)
buildOptions = { shorten: false, joiner: "_" }
} else {
buildOptions = Object.assign(
{ shorten: false, joiner: "_" },
buildOptions,
)
}
const newAlias =
alias.length === 1 ? alias[0] : alias.join(buildOptions.joiner)
alias.length === 1
? alias[0]
: alias.join(
buildOptions && buildOptions.joiner
? buildOptions.joiner
: "_",
)
if (
maxAliasLength &&
maxAliasLength > 0 &&
newAlias.length > maxAliasLength
) {
if (buildOptions.shorten === true) {
if (buildOptions && buildOptions.shorten === true) {
const shortenedAlias = shorten(newAlias)
if (shortenedAlias.length < maxAliasLength) {
return shortenedAlias
@ -165,6 +161,15 @@ export class DriverUtils {
buildOptions: { shorten?: boolean; joiner?: string } | string,
...alias: string[]
) {
if (typeof buildOptions === "string") {
alias.unshift(buildOptions)
buildOptions = { shorten: false, joiner: "_" }
} else {
buildOptions = Object.assign(
{ shorten: false, joiner: "_" },
buildOptions,
)
}
return this.buildAlias(
{ maxAliasLength } as Driver,
buildOptions,

View File

@ -67,8 +67,9 @@ export class EntityManager {
/**
* Once created and then reused by repositories.
* Created as a future replacement for the #repositories to provide a bit more perf optimization.
*/
protected repositories: Repository<any>[] = []
protected repositories = new Map<EntityTarget<any>, Repository<any>>()
/**
* Once created and then reused by repositories.
@ -1377,10 +1378,8 @@ export class EntityManager {
target: EntityTarget<Entity>,
): Repository<Entity> {
// find already created repository instance and return it if found
const repository = this.repositories.find(
(repository) => repository.target === target,
)
if (repository) return repository
const repoFromMap = this.repositories.get(target)
if (repoFromMap) return repoFromMap
// if repository was not found then create it, store its instance and return it
if (this.connection.driver.options.type === "mongodb") {
@ -1389,7 +1388,7 @@ export class EntityManager {
this,
this.queryRunner,
)
this.repositories.push(newRepository as any)
this.repositories.set(target, newRepository)
return newRepository
} else {
const newRepository = new Repository<any>(
@ -1397,7 +1396,7 @@ export class EntityManager {
this,
this.queryRunner,
)
this.repositories.push(newRepository)
this.repositories.set(target, newRepository)
return newRepository
}
}

View File

@ -41,7 +41,7 @@ export class DeleteQueryBuilder<Entity extends ObjectLiteral>
let sql = this.createComment()
sql += this.createCteExpression()
sql += this.createDeleteExpression()
return sql.trim()
return this.replacePropertyNamesForTheWholeQuery(sql.trim())
}
/**

View File

@ -37,7 +37,7 @@ export class InsertQueryBuilder<
let sql = this.createComment()
sql += this.createCteExpression()
sql += this.createInsertExpression()
return sql.trim()
return this.replacePropertyNamesForTheWholeQuery(sql.trim())
}
/**

View File

@ -60,7 +60,9 @@ export class JoinAttribute {
private queryExpressionMap: QueryExpressionMap,
joinAttribute?: JoinAttribute,
) {
ObjectUtils.assign(this, joinAttribute || {})
if (joinAttribute) {
ObjectUtils.assign(this, joinAttribute)
}
}
// -------------------------------------------------------------------------
@ -228,22 +230,33 @@ export class JoinAttribute {
* Generates alias of junction table, whose ids we get.
*/
get junctionAlias(): string {
if (!this.relation)
if (!this.relation) {
throw new TypeORMError(
`Cannot get junction table for join without relation.`,
)
}
if (typeof this.entityOrProperty !== "string") {
throw new TypeORMError(`Junction property is not defined.`)
}
const aliasProperty = this.entityOrProperty.substr(
0,
this.entityOrProperty.indexOf("."),
)
if (this.relation.isOwning) {
return DriverUtils.buildAlias(
this.connection.driver,
this.parentAlias!,
undefined,
aliasProperty,
this.alias.name,
)
} else {
return DriverUtils.buildAlias(
this.connection.driver,
undefined,
this.alias.name,
this.parentAlias!,
aliasProperty,
)
}
}

View File

@ -101,14 +101,14 @@ export abstract class QueryBuilder<Entity extends ObjectLiteral> {
connectionOrQueryBuilder: DataSource | QueryBuilder<any>,
queryRunner?: QueryRunner,
) {
if (InstanceChecker.isQueryBuilder(connectionOrQueryBuilder)) {
this.connection = connectionOrQueryBuilder.connection
this.queryRunner = connectionOrQueryBuilder.queryRunner
this.expressionMap = connectionOrQueryBuilder.expressionMap.clone()
} else {
if (InstanceChecker.isDataSource(connectionOrQueryBuilder)) {
this.connection = connectionOrQueryBuilder
this.queryRunner = queryRunner
this.expressionMap = new QueryExpressionMap(this.connection)
} else {
this.connection = connectionOrQueryBuilder.connection
this.queryRunner = connectionOrQueryBuilder.queryRunner
this.expressionMap = connectionOrQueryBuilder.expressionMap.clone()
}
}
@ -700,22 +700,29 @@ export abstract class QueryBuilder<Entity extends ObjectLiteral> {
}
/**
* Replaces all entity's propertyName to name in the given statement.
* @deprecated this way of replace property names is too slow.
* Instead, we'll replace property names at the end - once query is build.
*/
protected replacePropertyNames(statement: string) {
return statement
}
/**
* Replaces all entity's propertyName to name in the given SQL string.
*/
protected replacePropertyNamesForTheWholeQuery(statement: string) {
const replacements: { [key: string]: { [key: string]: string } } = {}
for (const alias of this.expressionMap.aliases) {
if (!alias.hasMetadata) continue
const replaceAliasNamePrefix = this.expressionMap
.aliasNamePrefixingEnabled
? `${alias.name}.`
: ""
const replacementAliasNamePrefix = this.expressionMap
.aliasNamePrefixingEnabled
? `${this.escape(alias.name)}.`
: ""
const replaceAliasNamePrefix =
this.expressionMap.aliasNamePrefixingEnabled && alias.name
? `${alias.name}.`
: ""
const replacements: { [key: string]: string } = {}
if (!replacements[replaceAliasNamePrefix]) {
replacements[replaceAliasNamePrefix] = {}
}
// 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.
@ -728,51 +735,81 @@ export abstract class QueryBuilder<Entity extends ObjectLiteral> {
for (const relation of alias.metadata.relations) {
if (relation.joinColumns.length > 0)
replacements[relation.propertyPath] =
relation.joinColumns[0].databaseName
replacements[replaceAliasNamePrefix][
relation.propertyPath
] = relation.joinColumns[0].databaseName
}
for (const relation of alias.metadata.relations) {
for (const joinColumn of [
const allColumns = [
...relation.joinColumns,
...relation.inverseJoinColumns,
]) {
]
for (const joinColumn of allColumns) {
const propertyKey = `${relation.propertyPath}.${
joinColumn.referencedColumn!.propertyPath
}`
replacements[propertyKey] = joinColumn.databaseName
replacements[replaceAliasNamePrefix][propertyKey] =
joinColumn.databaseName
}
}
for (const column of alias.metadata.columns) {
replacements[column.databaseName] = column.databaseName
replacements[replaceAliasNamePrefix][column.databaseName] =
column.databaseName
}
for (const column of alias.metadata.columns) {
replacements[column.propertyName] = column.databaseName
replacements[replaceAliasNamePrefix][column.propertyName] =
column.databaseName
}
for (const column of alias.metadata.columns) {
replacements[column.propertyPath] = column.databaseName
replacements[replaceAliasNamePrefix][column.propertyPath] =
column.databaseName
}
}
const replacementKeys = Object.keys(replacements)
const replaceAliasNamePrefixes = replacementKeys
.map((key) => escapeRegExp(key))
.join("|")
if (replacementKeys.length > 0) {
statement = statement.replace(
new RegExp(
// Avoid a lookbehind here since it's not well supported
`([ =\(]|^.{0})` + // any of ' =(' or start of line
// followed by our prefix, e.g. 'tablename.' or ''
`${escapeRegExp(
replaceAliasNamePrefix,
)}([^ =\(\)\,]+)` + // a possible property name: sequence of anything but ' =(),'
`${
replaceAliasNamePrefixes
? "(" + replaceAliasNamePrefixes + ")"
: ""
}([^ =\(\)\,]+)` + // a possible property name: sequence of anything but ' =(),'
// terminated by ' =),' or end of line
`(?=[ =\)\,]|.{0}$)`,
"gm",
),
(match, pre, p) => {
if (replacements[p]) {
return `${pre}${replacementAliasNamePrefix}${this.escape(
replacements[p],
)}`
(...matches) => {
let match: string, pre: string, p: string
if (replaceAliasNamePrefixes) {
match = matches[0]
pre = matches[1]
p = matches[3]
if (replacements[matches[2]][p]) {
return `${pre}${this.escape(
matches[2].substring(0, matches[2].length - 1),
)}.${this.escape(replacements[matches[2]][p])}`
}
} else {
match = matches[0]
pre = matches[1]
p = matches[2]
if (replacements[""][p]) {
return `${pre}${this.escape(replacements[""][p])}`
}
}
return match
},

View File

@ -161,6 +161,7 @@ export class RelationIdLoader {
relationId[
DriverUtils.buildAlias(
this.connection.driver,
undefined,
column.entityMetadata.name +
"_" +
relation.propertyPath.replace(
@ -247,6 +248,7 @@ export class RelationIdLoader {
columns.forEach((column) => {
const columnName = DriverUtils.buildAlias(
this.connection.driver,
undefined,
column.referencedColumn!.entityMetadata.name +
"_" +
column.referencedColumn!.propertyPath.replace(".", "_"),
@ -256,6 +258,7 @@ export class RelationIdLoader {
inverseColumns.forEach((column) => {
const columnName = DriverUtils.buildAlias(
this.connection.driver,
undefined,
column.referencedColumn!.entityMetadata.name +
"_" +
relation.propertyPath.replace(".", "_") +
@ -487,6 +490,7 @@ export class RelationIdLoader {
relation.entityMetadata.primaryColumns.forEach((primaryColumn) => {
const columnName = DriverUtils.buildAlias(
this.connection.driver,
undefined,
primaryColumn.entityMetadata.name +
"_" +
primaryColumn.propertyPath.replace(".", "_"),
@ -499,6 +503,7 @@ export class RelationIdLoader {
relation.joinColumns.forEach((column) => {
const columnName = DriverUtils.buildAlias(
this.connection.driver,
undefined,
column.referencedColumn!.entityMetadata.name +
"_" +
relation.propertyPath.replace(".", "_") +
@ -624,6 +629,7 @@ export class RelationIdLoader {
relation.entityMetadata.primaryColumns.forEach((primaryColumn) => {
const columnName = DriverUtils.buildAlias(
this.connection.driver,
undefined,
primaryColumn.entityMetadata.name +
"_" +
relation.inverseRelation!.propertyPath.replace(".", "_") +
@ -638,6 +644,7 @@ export class RelationIdLoader {
relation.joinColumns.forEach((column) => {
const columnName = DriverUtils.buildAlias(
this.connection.driver,
undefined,
column.referencedColumn!.entityMetadata.name +
"_" +
column.referencedColumn!.propertyPath.replace(".", "_"),

View File

@ -92,7 +92,7 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
sql += this.createLockExpression()
sql = sql.trim()
if (this.expressionMap.subQuery) sql = "(" + sql + ")"
return sql
return this.replacePropertyNamesForTheWholeQuery(sql)
}
// -------------------------------------------------------------------------
@ -1155,10 +1155,11 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
): this {
this.expressionMap.wheres = [] // don't move this block below since computeWhereParameter can add where expressions
const condition = this.getWhereCondition(where)
if (condition)
if (condition) {
this.expressionMap.wheres = [
{ type: "simple", condition: condition },
]
}
if (parameters) this.setParameters(parameters)
return this
}
@ -2016,7 +2017,9 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
isMappingMany?: boolean,
mapAsEntity?: Function | string,
): void {
this.setParameters(parameters || {})
if (parameters) {
this.setParameters(parameters)
}
const joinAttribute = new JoinAttribute(
this.connection,
@ -2027,16 +2030,17 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
joinAttribute.mapToProperty = mapToProperty
joinAttribute.isMappingMany = isMappingMany
joinAttribute.entityOrProperty = entityOrProperty // relationName
joinAttribute.condition = condition ? condition : undefined // joinInverseSideCondition
joinAttribute.condition = condition // joinInverseSideCondition
// joinAttribute.junctionAlias = joinAttribute.relation.isOwning ? parentAlias + "_" + destinationTableAlias : destinationTableAlias + "_" + parentAlias;
this.expressionMap.joinAttributes.push(joinAttribute)
if (joinAttribute.metadata) {
const joinAttributeMetadata = joinAttribute.metadata
if (joinAttributeMetadata) {
if (
joinAttribute.metadata.deleteDateColumn &&
joinAttributeMetadata.deleteDateColumn &&
!this.expressionMap.withDeleted
) {
const conditionDeleteColumn = `${aliasName}.${joinAttribute.metadata.deleteDateColumn.propertyName} IS NULL`
const conditionDeleteColumn = `${aliasName}.${joinAttributeMetadata.deleteDateColumn.propertyName} IS NULL`
joinAttribute.condition = joinAttribute.condition
? ` ${joinAttribute.condition} AND ${conditionDeleteColumn}`
: `${conditionDeleteColumn}`
@ -2045,7 +2049,7 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
joinAttribute.alias = this.expressionMap.createAlias({
type: "join",
name: aliasName,
metadata: joinAttribute.metadata,
metadata: joinAttributeMetadata,
})
if (
joinAttribute.relation &&
@ -2472,31 +2476,59 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
*/
protected createOrderByExpression() {
const orderBys = this.expressionMap.allOrderBys
if (Object.keys(orderBys).length > 0)
return (
" ORDER BY " +
Object.keys(orderBys)
.map((columnName) => {
if (typeof orderBys[columnName] === "string") {
return (
this.replacePropertyNames(columnName) +
" " +
orderBys[columnName]
)
} else {
return (
this.replacePropertyNames(columnName) +
" " +
(orderBys[columnName] as any).order +
" " +
(orderBys[columnName] as any).nulls
)
}
})
.join(", ")
)
if (Object.keys(orderBys).length === 0) return ""
return ""
return (
" ORDER BY " +
Object.keys(orderBys)
.map((columnName) => {
const orderValue =
typeof orderBys[columnName] === "string"
? orderBys[columnName]
: (orderBys[columnName] as any).order +
" " +
(orderBys[columnName] as any).nulls
const selection = this.expressionMap.selects.find(
(s) => s.selection === columnName,
)
if (
selection &&
!selection.aliasName &&
columnName.indexOf(".") !== -1
) {
const criteriaParts = columnName.split(".")
const aliasName = criteriaParts[0]
const propertyPath = criteriaParts.slice(1).join(".")
const alias = this.expressionMap.aliases.find(
(alias) => alias.name === aliasName,
)
if (alias) {
const column =
alias.metadata.findColumnWithPropertyPath(
propertyPath,
)
if (column) {
const orderAlias = DriverUtils.buildAlias(
this.connection.driver,
undefined,
aliasName,
column.databaseName,
)
return (
this.escape(orderAlias) +
" " +
orderBys[columnName]
)
}
}
}
return (
this.replacePropertyNames(columnName) + " " + orderValue
)
})
.join(", ")
)
}
/**
@ -2837,6 +2869,7 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
? selection.aliasName
: DriverUtils.buildAlias(
this.connection.driver,
undefined,
aliasName,
column.databaseName,
),
@ -2849,6 +2882,7 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
selection: selectionPath,
aliasName: DriverUtils.buildAlias(
this.connection.driver,
undefined,
aliasName,
column.databaseName,
),
@ -3393,6 +3427,7 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
const columnAlias = this.escape(
DriverUtils.buildAlias(
this.connection.driver,
undefined,
mainAliasName,
primaryColumn.databaseName,
),
@ -3403,6 +3438,7 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
const alias = DriverUtils.buildAlias(
this.connection.driver,
undefined,
"ids_" + mainAliasName,
primaryColumn.databaseName,
)
@ -3467,6 +3503,7 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
} else {
const alias = DriverUtils.buildAlias(
this.connection.driver,
undefined,
"ids_" + mainAliasName,
metadata.primaryColumns[0].databaseName,
)
@ -3626,6 +3663,7 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
this.escape(
DriverUtils.buildAlias(
this.connection.driver,
undefined,
aliasName,
column!.databaseName,
),
@ -3639,7 +3677,11 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
select.aliasName === orderCriteria,
)
)
return this.escape(parentAlias) + "." + orderCriteria
return (
this.escape(parentAlias) +
"." +
this.escape(orderCriteria)
)
return ""
}
@ -3661,6 +3703,7 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
this.escape(
DriverUtils.buildAlias(
this.connection.driver,
undefined,
aliasName,
column!.databaseName,
),
@ -3675,7 +3718,9 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
)
) {
orderByObject[
this.escape(parentAlias) + "." + orderCriteria
this.escape(parentAlias) +
"." +
this.escape(orderCriteria)
] = orderBys[orderCriteria]
} else {
orderByObject[orderCriteria] = orderBys[orderCriteria]
@ -4079,23 +4124,27 @@ export class SelectQueryBuilder<Entity extends ObjectLiteral>
: undefined
let aliasPath = `${alias}.${propertyPath}`
if (column.isVirtualProperty && column.query) {
const selection = this.expressionMap.selects.find(
(s) => s.selection === aliasPath,
)
if (selection) {
// this is not building correctly now???
aliasPath = DriverUtils.buildAlias(
this.connection.driver,
alias,
column.databaseName,
)
selection.aliasName = aliasPath
} else {
aliasPath = `(${column.query(alias)})`
}
}
// const selection = this.expressionMap.selects.find(
// (s) => s.selection === aliasPath,
// )
// if (selection) {
// // this is not building correctly now???
// aliasPath = this.escape(
// DriverUtils.buildAlias(
// this.connection.driver,
// undefined,
// alias,
// column.databaseName,
// ),
// )
// // selection.aliasName = aliasPath
// } else {
// if (column.isVirtualProperty && column.query) {
// aliasPath = `(${column.query(alias)})`
// }
// }
// console.log("add sort", selection, aliasPath, direction, nulls)
this.addOrderBy(aliasPath, direction, nulls)
// this.orderBys.push({ alias: alias + "." + propertyPath, direction, nulls });
} else if (embed) {

View File

@ -49,7 +49,7 @@ export class SoftDeleteQueryBuilder<Entity extends ObjectLiteral>
sql += this.createCteExpression()
sql += this.createOrderByExpression()
sql += this.createLimitExpression()
return sql.trim()
return this.replacePropertyNamesForTheWholeQuery(sql.trim())
}
/**

View File

@ -53,7 +53,7 @@ export class UpdateQueryBuilder<Entity extends ObjectLiteral>
sql += this.createUpdateExpression()
sql += this.createOrderByExpression()
sql += this.createLimitExpression()
return sql.trim()
return this.replacePropertyNamesForTheWholeQuery(sql.trim())
}
/**

View File

@ -50,6 +50,7 @@ export class RelationIdLoader {
rawEntity[
DriverUtils.buildAlias(
this.connection.driver,
undefined,
relationIdAttr.parentAlias,
joinColumn.databaseName,
)
@ -76,6 +77,7 @@ export class RelationIdLoader {
rawEntity[
DriverUtils.buildAlias(
this.connection.driver,
undefined,
relationIdAttr.parentAlias,
primaryColumn.databaseName,
)
@ -139,6 +141,7 @@ export class RelationIdLoader {
rawEntity[
DriverUtils.buildAlias(
this.connection.driver,
undefined,
relationIdAttr.parentAlias,
joinColumn.referencedColumn!
.databaseName,
@ -270,6 +273,7 @@ export class RelationIdLoader {
rawEntity[
DriverUtils.buildAlias(
this.connection.driver,
undefined,
relationIdAttr.parentAlias,
joinColumn.referencedColumn!
.databaseName,

View File

@ -72,6 +72,7 @@ export class RawSqlResultsToEntityTransformer {
...alias.metadata.columns.map((column) =>
DriverUtils.buildAlias(
this.driver,
undefined,
alias.name,
column.databaseName,
),
@ -82,6 +83,7 @@ export class RawSqlResultsToEntityTransformer {
...alias.metadata.primaryColumns.map((column) =>
DriverUtils.buildAlias(
this.driver,
undefined,
alias.name,
column.databaseName,
),
@ -131,6 +133,7 @@ export class RawSqlResultsToEntityTransformer {
result[
DriverUtils.buildAlias(
this.driver,
undefined,
alias.name,
alias.metadata.discriminatorColumn!.databaseName,
)
@ -221,6 +224,7 @@ export class RawSqlResultsToEntityTransformer {
rawResults[0][
DriverUtils.buildAlias(
this.driver,
undefined,
alias.name,
column.databaseName,
)
@ -415,6 +419,7 @@ export class RawSqlResultsToEntityTransformer {
rawSqlResults[0][
DriverUtils.buildAlias(
this.driver,
undefined,
alias.name,
referenceColumnName,
)
@ -474,6 +479,7 @@ export class RawSqlResultsToEntityTransformer {
rawSqlResult[
DriverUtils.buildAlias(
this.driver,
undefined,
parentAlias,
column.databaseName,
)
@ -486,6 +492,7 @@ export class RawSqlResultsToEntityTransformer {
rawSqlResult[
DriverUtils.buildAlias(
this.driver,
undefined,
parentAlias,
column.referencedColumn!.databaseName,
)

View File

@ -6,7 +6,6 @@ import type { EqualOperator } from "../find-options/EqualOperator"
import type { Query } from "../driver/Query"
import type { RdbmsSchemaBuilder } from "../schema-builder/RdbmsSchemaBuilder"
import type { Subject } from "../persistence/Subject"
import type { QueryBuilder } from "../query-builder/QueryBuilder"
import type { SelectQueryBuilder } from "../query-builder/SelectQueryBuilder"
import type { UpdateQueryBuilder } from "../query-builder/UpdateQueryBuilder"
import type { DeleteQueryBuilder } from "../query-builder/DeleteQueryBuilder"
@ -39,17 +38,6 @@ export class InstanceChecker {
static isColumnMetadata(obj: unknown): obj is ColumnMetadata {
return this.check(obj, "ColumnMetadata")
}
static isQueryBuilder(obj: unknown): obj is QueryBuilder<any> {
return (
this.check(obj, "QueryBuilder") ||
this.check(obj, "SelectQueryBuilder") ||
this.check(obj, "InsertQueryBuilder") ||
this.check(obj, "DeleteQueryBuilder") ||
this.check(obj, "UpdateQueryBuilder") ||
this.check(obj, "SoftDeleteQueryBuilder") ||
this.check(obj, "RelationQueryBuilder")
)
}
static isSelectQueryBuilder(obj: unknown): obj is SelectQueryBuilder<any> {
return this.check(obj, "SelectQueryBuilder")
}

View File

@ -10,6 +10,17 @@ export class ObjectUtils {
return val !== null && typeof val === "object"
}
/**
* Checks if given value is an object.
* We cannot use instanceof because it has problems when running on different contexts.
* And we don't simply use typeof because typeof null === "object".
*/
static isObjectWithName(val: any): val is Object & { name: string } {
return (
val !== null && typeof val === "object" && val["name"] !== undefined
)
}
/**
* Copy the values of all of the enumerable own properties from one or more source objects to a
* target object.

View File

@ -117,15 +117,11 @@ interface IHashOptions {
* @param options.length Optionally, shorten the output to desired length.
*/
export function hash(input: string, options: IHashOptions = {}): string {
const hashFunction = shajs("sha256")
const hashFunction = shajs("sha1")
hashFunction.update(input, "utf8")
const hashedInput = hashFunction.digest("hex")
if (options.length) {
return hashedInput.slice(0, options.length)
}
return hashedInput
}

View File

@ -0,0 +1,44 @@
import "reflect-metadata"
import { DataSource } from "../../../src/data-source/DataSource"
import {
closeTestingConnections,
createTestingConnections,
reloadTestingDatabases,
} from "../../utils/test-utils"
import { Post } from "./entity/Post"
describe("benchmark > bulk-sql-build", () => {
let dataSources: DataSource[]
before(async () => {
dataSources = await createTestingConnections({
__dirname,
})
})
beforeEach(() => reloadTestingDatabases(dataSources))
after(() => closeTestingConnections(dataSources))
/**
* Before optimization (<0.3.12) execution time for 10.000 sqls was ~1.8s
* After optimization execution time for 10.000 sqls become ~0.380s
*/
it("testing bulk create of 10.000 sql with joins", () =>
Promise.all(
dataSources.map(async (dataSource) => {
for (let i = 0; i < 10_000; i++) {
dataSource
.getRepository(Post)
.createQueryBuilder("post")
.leftJoinAndSelect("post.categories", "categories1")
.leftJoinAndSelect("post.categories", "categories2")
.leftJoinAndSelect("post.categories", "categories3")
.leftJoinAndSelect("post.categories", "categories4")
.leftJoinAndSelect("post.categories", "categories5")
.leftJoinAndSelect("post.categories", "categories6")
.leftJoinAndSelect("post.categories", "categories7")
.where("post.id = 1")
.getQuery()
}
}),
))
})

View File

@ -0,0 +1,12 @@
import { Entity } from "../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
}

View File

@ -0,0 +1,18 @@
import { Entity } from "../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
import { Category } from "./Category"
import { JoinTable, ManyToMany } from "../../../../src/index"
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@ManyToMany(() => Category)
@JoinTable()
categories: Category[]
}

View File

@ -121,7 +121,7 @@ describe("github issues > #9323 Add new VirtualColumn decorator feature", () =>
}),
))
it("should be able to save and find sub-select data in the databse", () =>
it("should be able to save and find sub-select data in the database", () =>
Promise.all(
connections.map(async (connection) => {
const companyName = "My Company 1"