Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: QueryBuilder performance optimizations #9914

Merged
merged 10 commits into from
Apr 6, 2023
54 changes: 39 additions & 15 deletions src/data-source/DataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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>,
)
Expand Down Expand Up @@ -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
}

/**
Expand All @@ -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(
Expand Down
31 changes: 18 additions & 13 deletions src/driver/DriverUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
13 changes: 6 additions & 7 deletions src/entity-manager/EntityManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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") {
Expand All @@ -1389,15 +1388,15 @@ 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>(
target,
this,
this.queryRunner,
)
this.repositories.push(newRepository)
this.repositories.set(target, newRepository)
return newRepository
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/query-builder/DeleteQueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/query-builder/InsertQueryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}

/**
Expand Down
21 changes: 17 additions & 4 deletions src/query-builder/JoinAttribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ export class JoinAttribute {
private queryExpressionMap: QueryExpressionMap,
joinAttribute?: JoinAttribute,
) {
ObjectUtils.assign(this, joinAttribute || {})
if (joinAttribute) {
ObjectUtils.assign(this, joinAttribute)
}
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -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,
)
}
}
Expand Down
Loading