mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
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:
parent
f1330ad6e2
commit
12e9db07b6
@ -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(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
},
|
||||
|
||||
@ -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(".", "_"),
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
44
test/benchmark/bulk-sql-build/bulk-sql-build.ts
Normal file
44
test/benchmark/bulk-sql-build/bulk-sql-build.ts
Normal 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()
|
||||
}
|
||||
}),
|
||||
))
|
||||
})
|
||||
12
test/benchmark/bulk-sql-build/entity/Category.ts
Normal file
12
test/benchmark/bulk-sql-build/entity/Category.ts
Normal 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
|
||||
}
|
||||
18
test/benchmark/bulk-sql-build/entity/Post.ts
Normal file
18
test/benchmark/bulk-sql-build/entity/Post.ts
Normal 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[]
|
||||
}
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user