mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
fixed performance issues
This commit is contained in:
parent
1e71e853d9
commit
8e076bf94d
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "typeorm",
|
||||
"private": true,
|
||||
"version": "0.2.0-alpha.5",
|
||||
"version": "0.2.0-alpha.6",
|
||||
"description": "Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL, MongoDB databases.",
|
||||
"license": "MIT",
|
||||
"readmeFilename": "README.md",
|
||||
|
||||
@ -480,6 +480,8 @@ export class EntityMetadata {
|
||||
/**
|
||||
* Compares two different entity instances by their ids.
|
||||
* Returns true if they match, false otherwise.
|
||||
*
|
||||
* @deprecated performance bottleneck
|
||||
*/
|
||||
compareEntities(firstEntity: ObjectLiteral, secondEntity: ObjectLiteral): boolean {
|
||||
|
||||
|
||||
@ -7,7 +7,6 @@ import {CannotDetermineEntityError} from "../error/CannotDetermineEntityError";
|
||||
import {QueryRunner} from "../query-runner/QueryRunner";
|
||||
import {Connection} from "../connection/Connection";
|
||||
import {Subject} from "./Subject";
|
||||
import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {OneToManySubjectBuilder} from "./subject-builder/OneToManySubjectBuilder";
|
||||
import {OneToOneInverseSideSubjectBuilder} from "./subject-builder/OneToOneInverseSideSubjectBuilder";
|
||||
import {ManyToManySubjectBuilder} from "./subject-builder/ManyToManySubjectBuilder";
|
||||
@ -19,6 +18,15 @@ import {CascadesSubjectBuilder} from "./subject-builder/CascadesSubjectBuilder";
|
||||
*/
|
||||
export class EntityPersistExecutor {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* All subjects being persisted in this executor.
|
||||
*/
|
||||
protected subjects: Subject[] = [];
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
@ -59,25 +67,58 @@ export class EntityPersistExecutor {
|
||||
try {
|
||||
|
||||
// collect all operate subjects
|
||||
const subjects: Subject[] = [];
|
||||
const entities: ObjectLiteral[] = this.entity instanceof Array ? this.entity : [this.entity];
|
||||
// console.log("entities", entities);
|
||||
await Promise.all(entities.map(async entity => {
|
||||
// console.time("building subjects...");
|
||||
|
||||
// create subjects for all entities we received for the persistence
|
||||
entities.forEach(entity => {
|
||||
const entityTarget = this.target ? this.target : entity.constructor;
|
||||
if (entityTarget === Object)
|
||||
throw new CannotDetermineEntityError(this.mode);
|
||||
|
||||
const metadata = this.connection.getMetadata(entityTarget);
|
||||
if (this.mode === "save") {
|
||||
subjects.push(...await this.save(queryRunner, metadata, entity));
|
||||
} else { // remove
|
||||
subjects.push(...await this.remove(queryRunner, metadata, entity));
|
||||
}
|
||||
}));
|
||||
this.subjects.push(new Subject({
|
||||
metadata: this.connection.getMetadata(entityTarget),
|
||||
entity: entity,
|
||||
canBeInserted: this.mode === "save",
|
||||
canBeUpdated: this.mode === "save",
|
||||
mustBeRemoved: this.mode === "remove"
|
||||
}));
|
||||
});
|
||||
|
||||
// console.time("building cascades...");
|
||||
// go thought each entity with metadata and create subjects and subjects by cascades for them
|
||||
this.subjects.forEach(subject => {
|
||||
// next step we build list of subjects we will operate with
|
||||
// these subjects are subjects that we need to insert or update alongside with main persisted entity
|
||||
new CascadesSubjectBuilder(subject, this.subjects).build();
|
||||
});
|
||||
// console.timeEnd("building cascades...");
|
||||
|
||||
// load database entities for all subjects we have
|
||||
// next step is to load database entities for all operate subjects
|
||||
// console.time("loading...");
|
||||
await new SubjectDatabaseEntityLoader(queryRunner, this.subjects).load(this.mode);
|
||||
// console.timeEnd("loading...");
|
||||
|
||||
// console.time("other subjects...");
|
||||
// build all related subjects and change maps
|
||||
if (this.mode === "save") {
|
||||
new OneToManySubjectBuilder(this.subjects).build();
|
||||
new OneToOneInverseSideSubjectBuilder(this.subjects).build();
|
||||
new ManyToManySubjectBuilder(this.subjects).build();
|
||||
} else {
|
||||
this.subjects.forEach(subject => {
|
||||
if (subject.mustBeRemoved) {
|
||||
new ManyToManySubjectBuilder(this.subjects).buildForAllRemoval(subject);
|
||||
}
|
||||
});
|
||||
}
|
||||
// console.timeEnd("other subjects...");
|
||||
// console.timeEnd("building subjects...");
|
||||
// console.log("subjects", subjects);
|
||||
|
||||
// create a subject executor
|
||||
const executor = new SubjectExecutor(queryRunner, subjects);
|
||||
const executor = new SubjectExecutor(queryRunner, this.subjects);
|
||||
|
||||
// make sure we have at least one executable operation before we create a transaction and proceed
|
||||
// if we don't have operations it means we don't really need to update or remove something
|
||||
@ -100,8 +141,10 @@ export class EntityPersistExecutor {
|
||||
await executor.execute();
|
||||
|
||||
// commit transaction if it was started by us
|
||||
// console.time("commit");
|
||||
if (isTransactionStartedByUs === true)
|
||||
await queryRunner.commitTransaction();
|
||||
// console.timeEnd("commit");
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@ -123,58 +166,4 @@ export class EntityPersistExecutor {
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Builds operations for entity that is being inserted/updated.
|
||||
*/
|
||||
protected async save(queryRunner: QueryRunner, metadata: EntityMetadata, entity: ObjectLiteral): Promise<Subject[]> {
|
||||
|
||||
// create subject for currently persisted entity and mark that it can be inserted and updated
|
||||
const mainSubject = new Subject({
|
||||
metadata: metadata,
|
||||
entity: entity,
|
||||
canBeInserted: true,
|
||||
canBeUpdated: true,
|
||||
});
|
||||
|
||||
// next step we build list of subjects we will operate with
|
||||
// these subjects are subjects that we need to insert or update alongside with main persisted entity
|
||||
const subjects = await new CascadesSubjectBuilder(mainSubject).build();
|
||||
|
||||
// next step is to load database entities of all operate subjects
|
||||
await new SubjectDatabaseEntityLoader(queryRunner, subjects).load("save");
|
||||
|
||||
// build all related subjects and change maps
|
||||
new OneToManySubjectBuilder(subjects).build();
|
||||
new OneToOneInverseSideSubjectBuilder(subjects).build();
|
||||
new ManyToManySubjectBuilder(subjects).build();
|
||||
|
||||
return subjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds only remove operations for entity that is being removed.
|
||||
*/
|
||||
protected async remove(queryRunner: QueryRunner, metadata: EntityMetadata, entity: ObjectLiteral): Promise<Subject[]> {
|
||||
|
||||
// create subject for currently removed entity and mark that it must be removed
|
||||
const mainSubject = new Subject({
|
||||
metadata: metadata,
|
||||
entity: entity,
|
||||
mustBeRemoved: true,
|
||||
});
|
||||
const subjects: Subject[] = [mainSubject];
|
||||
|
||||
// next step is to load database entities for all operate subjects
|
||||
await new SubjectDatabaseEntityLoader(queryRunner, subjects).load("remove");
|
||||
|
||||
// build subjects for junction tables
|
||||
new ManyToManySubjectBuilder(subjects).buildForAllRemoval(mainSubject);
|
||||
|
||||
return subjects;
|
||||
}
|
||||
|
||||
}
|
||||
@ -76,6 +76,9 @@ export class SubjectExecutor {
|
||||
*/
|
||||
async execute(): Promise<void> {
|
||||
|
||||
// console.time("execution");
|
||||
// console.time("prepare");
|
||||
|
||||
// broadcast "before" events before we start insert / update / remove operations
|
||||
await this.broadcastBeforeEventsForAll();
|
||||
|
||||
@ -85,25 +88,34 @@ export class SubjectExecutor {
|
||||
// make sure our insert subjects are sorted (using topological sorting) to make cascade inserts work properly
|
||||
this.insertSubjects = new SubjectTopoligicalSorter(this.insertSubjects).sort("insert");
|
||||
|
||||
// console.timeEnd("prepare");
|
||||
|
||||
// execute all insert operations
|
||||
// console.time("insertion");
|
||||
await this.executeInsertOperations();
|
||||
// console.timeEnd("insertion");
|
||||
|
||||
// recompute update operations since insertion can create updation operations for the
|
||||
// properties it wasn't able to handle on its own (referenced columns)
|
||||
this.updateSubjects = this.allSubjects.filter(subject => subject.mustBeUpdated);
|
||||
|
||||
// execute update operations
|
||||
// console.time("updation");
|
||||
await this.executeUpdateOperations();
|
||||
// console.timeEnd("updation");
|
||||
|
||||
// make sure our remove subjects are sorted (using topological sorting) when multiple entities are passed for the removal
|
||||
// console.time("removal");
|
||||
this.removeSubjects = new SubjectTopoligicalSorter(this.removeSubjects).sort("delete");
|
||||
await this.executeRemoveOperations();
|
||||
// console.timeEnd("removal");
|
||||
|
||||
// update all special columns in persisted entities, like inserted id or remove ids from the removed entities
|
||||
await this.updateSpecialColumnsInPersistedEntities();
|
||||
|
||||
// finally broadcast "after" events after we finish insert / update / remove operations
|
||||
await this.broadcastAfterEventsForAll();
|
||||
// console.timeEnd("execution");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -11,7 +11,8 @@ export class CascadesSubjectBuilder {
|
||||
// Constructor
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
constructor(protected subject: Subject) {
|
||||
constructor(protected subject: Subject,
|
||||
protected allSubjects: Subject[]) {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@ -21,10 +22,8 @@ export class CascadesSubjectBuilder {
|
||||
/**
|
||||
* Builds a cascade subjects tree and pushes them in into the given array of subjects.
|
||||
*/
|
||||
build(): Subject[] {
|
||||
const subjects: Subject[] = [this.subject];
|
||||
this.buildRecursively(subjects, this.subject);
|
||||
return subjects;
|
||||
build() {
|
||||
this.buildRecursively(this.subject);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@ -34,10 +33,10 @@ export class CascadesSubjectBuilder {
|
||||
/**
|
||||
* Builds a cascade subjects recursively.
|
||||
*/
|
||||
protected buildRecursively(subjects: Subject[], subject: Subject) {
|
||||
protected buildRecursively(subject: Subject) {
|
||||
|
||||
subject.metadata
|
||||
.extractRelationValuesFromEntity(subject.entity!, subject.metadata.relations)
|
||||
.extractRelationValuesFromEntity(subject.entity!, subject.metadata.relations) // todo: we can create EntityMetadata.cascadeRelations
|
||||
.forEach(([relation, relationEntity, relationEntityMetadata]) => {
|
||||
|
||||
// we need only defined values and insert or update cascades of the relation should be set
|
||||
@ -52,7 +51,7 @@ export class CascadesSubjectBuilder {
|
||||
return;
|
||||
|
||||
// if we already has this entity in list of operated subjects then skip it to avoid recursion
|
||||
const alreadyExistRelationEntitySubject = this.findByPersistEntityLike(subjects, relationEntityMetadata.target, relationEntity);
|
||||
const alreadyExistRelationEntitySubject = this.findByPersistEntityLike(relationEntityMetadata.target, relationEntity);
|
||||
if (alreadyExistRelationEntitySubject) {
|
||||
if (alreadyExistRelationEntitySubject.canBeInserted === false) // if its not marked for insertion yet
|
||||
alreadyExistRelationEntitySubject.canBeInserted = relation.isCascadeInsert === true;
|
||||
@ -69,10 +68,10 @@ export class CascadesSubjectBuilder {
|
||||
canBeInserted: relation.isCascadeInsert === true,
|
||||
canBeUpdated: relation.isCascadeUpdate === true
|
||||
});
|
||||
subjects.push(relationEntitySubject);
|
||||
this.allSubjects.push(relationEntitySubject);
|
||||
|
||||
// go recursively and find other entities we need to insert/update
|
||||
this.buildRecursively(subjects, relationEntitySubject);
|
||||
this.buildRecursively(relationEntitySubject);
|
||||
});
|
||||
}
|
||||
|
||||
@ -80,8 +79,8 @@ export class CascadesSubjectBuilder {
|
||||
* Finds subject where entity like given subject's entity.
|
||||
* Comparision made by entity id.
|
||||
*/
|
||||
protected findByPersistEntityLike(subjects: Subject[], entityTarget: Function|string, entity: ObjectLiteral): Subject|undefined {
|
||||
return subjects.find(subject => {
|
||||
protected findByPersistEntityLike(entityTarget: Function|string, entity: ObjectLiteral): Subject|undefined {
|
||||
return this.allSubjects.find(subject => {
|
||||
if (!subject.entity)
|
||||
return false;
|
||||
|
||||
|
||||
@ -145,30 +145,21 @@ export class DeleteQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* Adds new AND WHERE with conditions for the given ids.
|
||||
*/
|
||||
whereInIds(ids: any|any[]): this {
|
||||
ids = ids instanceof Array ? ids : [ids];
|
||||
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
|
||||
this.where(whereExpression, parameters);
|
||||
return this;
|
||||
return this.where(this.createWhereIdsExpression(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new AND WHERE with conditions for the given ids.
|
||||
*/
|
||||
andWhereInIds(ids: any|any[]): this {
|
||||
ids = ids instanceof Array ? ids : [ids];
|
||||
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
|
||||
this.andWhere(whereExpression, parameters);
|
||||
return this;
|
||||
return this.andWhere(this.createWhereIdsExpression(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new OR WHERE with conditions for the given ids.
|
||||
*/
|
||||
orWhereInIds(ids: any|any[]): this {
|
||||
ids = ids instanceof Array ? ids : [ids];
|
||||
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
|
||||
this.orWhere(whereExpression, parameters);
|
||||
return this;
|
||||
return this.orWhere(this.createWhereIdsExpression(ids));
|
||||
}
|
||||
/**
|
||||
* Optional returning/output clause.
|
||||
|
||||
@ -659,28 +659,34 @@ export abstract class QueryBuilder<Entity> {
|
||||
/**
|
||||
* Creates "WHERE" expression and variables for the given "ids".
|
||||
*/
|
||||
protected createWhereIdsExpression(ids: any[]): [string, ObjectLiteral] {
|
||||
protected createWhereIdsExpression(ids: any|any[]): string {
|
||||
ids = ids instanceof Array ? ids : [ids];
|
||||
const metadata = this.expressionMap.mainAlias!.metadata;
|
||||
|
||||
// create shortcuts for better readability
|
||||
const alias = this.expressionMap.aliasNamePrefixingEnabled ? this.escape(this.expressionMap.mainAlias!.name) + "." : "";
|
||||
const parameters: ObjectLiteral = {};
|
||||
const whereStrings = ids.map((id, index) => {
|
||||
let parameterIndex = Object.keys(this.expressionMap.nativeParameters).length;
|
||||
const whereStrings = (ids as any[]).map((id, index) => {
|
||||
id = metadata.ensureEntityIdMap(id);
|
||||
const whereSubStrings: string[] = [];
|
||||
metadata.primaryColumns.forEach((primaryColumn, secondIndex) => {
|
||||
whereSubStrings.push(alias + this.escape(primaryColumn.databaseName) + "=:id_" + index + "_" + secondIndex);
|
||||
parameters["id_" + index + "_" + secondIndex] = primaryColumn.getEntityValue(id);
|
||||
const parameterName = "id_" + index + "_" + secondIndex;
|
||||
// whereSubStrings.push(alias + this.escape(primaryColumn.databaseName) + "=:id_" + index + "_" + secondIndex);
|
||||
whereSubStrings.push(alias + this.escape(primaryColumn.databaseName) + " = " + this.connection.driver.createParameter(parameterName, parameterIndex));
|
||||
this.expressionMap.nativeParameters[parameterName] = primaryColumn.getEntityValue(id);
|
||||
parameterIndex++;
|
||||
});
|
||||
metadata.parentIdColumns.forEach((parentIdColumn, secondIndex) => {
|
||||
whereSubStrings.push(alias + this.escape(parentIdColumn.databaseName) + "=:parentId_" + index + "_" + secondIndex);
|
||||
parameters["parentId_" + index + "_" + secondIndex] = parentIdColumn.getEntityValue(id);
|
||||
// whereSubStrings.push(alias + this.escape(parentIdColumn.databaseName) + "=:parentId_" + index + "_" + secondIndex);
|
||||
const parameterName = "parentId_" + index + "_" + secondIndex;
|
||||
whereSubStrings.push(alias + this.escape(parentIdColumn.databaseName) + " = " + this.connection.driver.createParameter(parameterName, parameterIndex));
|
||||
this.expressionMap.nativeParameters[parameterName] = parentIdColumn.getEntityValue(id);
|
||||
parameterIndex++;
|
||||
});
|
||||
return whereSubStrings.join(" AND ");
|
||||
});
|
||||
|
||||
const whereString = whereStrings.length > 1 ? whereStrings.map(whereString => "(" + whereString + ")").join(" OR ") : whereStrings[0];
|
||||
return [whereString, parameters];
|
||||
return whereStrings.length > 1 ? whereStrings.map(whereString => "(" + whereString + ")").join(" OR ") : whereStrings[0];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -720,10 +720,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* for example [{ firstId: 1, secondId: 2 }, { firstId: 2, secondId: 3 }, ...]
|
||||
*/
|
||||
whereInIds(ids: any|any[]): this {
|
||||
ids = ids instanceof Array ? ids : [ids];
|
||||
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
|
||||
this.where(whereExpression, parameters);
|
||||
return this;
|
||||
return this.where(this.createWhereIdsExpression(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -735,10 +732,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* for example [{ firstId: 1, secondId: 2 }, { firstId: 2, secondId: 3 }, ...]
|
||||
*/
|
||||
andWhereInIds(ids: any|any[]): this {
|
||||
ids = ids instanceof Array ? ids : [ids];
|
||||
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
|
||||
this.andWhere(whereExpression, parameters);
|
||||
return this;
|
||||
return this.andWhere(this.createWhereIdsExpression(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -750,10 +744,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* for example [{ firstId: 1, secondId: 2 }, { firstId: 2, secondId: 3 }, ...]
|
||||
*/
|
||||
orWhereInIds(ids: any|any[]): this {
|
||||
ids = ids instanceof Array ? ids : [ids];
|
||||
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
|
||||
this.orWhere(whereExpression, parameters);
|
||||
return this;
|
||||
return this.orWhere(this.createWhereIdsExpression(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -12,6 +12,8 @@ import {ReturningStatementNotSupportedError} from "../error/ReturningStatementNo
|
||||
import {ArrayParameter} from "./ArrayParameter";
|
||||
import {ReturningResultsEntityUpdator} from "./ReturningResultsEntityUpdator";
|
||||
import {SqljsDriver} from "../driver/sqljs/SqljsDriver";
|
||||
import {MysqlDriver} from "../driver/mysql/MysqlDriver";
|
||||
import {WebsqlDriver} from "../driver/websql/WebsqlDriver";
|
||||
|
||||
/**
|
||||
* Allows to build complex sql queries in a fashion way and execute those queries.
|
||||
@ -160,30 +162,21 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* Adds new AND WHERE with conditions for the given ids.
|
||||
*/
|
||||
whereInIds(ids: any|any[]): this {
|
||||
ids = ids instanceof Array ? ids : [ids];
|
||||
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
|
||||
this.where(whereExpression, parameters);
|
||||
return this;
|
||||
return this.where(this.createWhereIdsExpression(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new AND WHERE with conditions for the given ids.
|
||||
*/
|
||||
andWhereInIds(ids: any|any[]): this {
|
||||
ids = ids instanceof Array ? ids : [ids];
|
||||
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
|
||||
this.andWhere(whereExpression, parameters);
|
||||
return this;
|
||||
return this.andWhere(this.createWhereIdsExpression(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new OR WHERE with conditions for the given ids.
|
||||
*/
|
||||
orWhereInIds(ids: any|any[]): this {
|
||||
ids = ids instanceof Array ? ids : [ids];
|
||||
const [whereExpression, parameters] = this.createWhereIdsExpression(ids);
|
||||
this.orWhere(whereExpression, parameters);
|
||||
return this;
|
||||
return this.orWhere(this.createWhereIdsExpression(ids));
|
||||
}
|
||||
/**
|
||||
* Optional returning/output clause.
|
||||
@ -287,6 +280,8 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
|
||||
// prepare columns and values to be updated
|
||||
const updateColumnAndValues: string[] = [];
|
||||
const newParameters: ObjectLiteral = {};
|
||||
let parametersCount = this.connection.driver instanceof MysqlDriver || this.connection.driver instanceof WebsqlDriver ? 0 : Object.keys(this.expressionMap.nativeParameters).length;
|
||||
if (metadata) {
|
||||
EntityMetadataUtils.createPropertyPath(metadata, valuesSet).forEach(propertyPath => {
|
||||
// todo: make this and other query builder to work with properly with tables without metadata
|
||||
@ -306,25 +301,28 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
updateColumnAndValues.push(this.escape(column.databaseName) + " = " + value());
|
||||
} else {
|
||||
if (this.connection.driver instanceof SqlServerDriver) {
|
||||
this.setParameter(paramName, this.connection.driver.parametrizeValue(column, value));
|
||||
} else {
|
||||
value = this.connection.driver.parametrizeValue(column, value);
|
||||
|
||||
// we need to store array values in a special class to make sure parameter replacement will work correctly
|
||||
if (value instanceof Array)
|
||||
value = new ArrayParameter(value);
|
||||
|
||||
this.setParameter(paramName, value);
|
||||
} else if (value instanceof Array) {
|
||||
value = new ArrayParameter(value);
|
||||
}
|
||||
updateColumnAndValues.push(this.escape(column.databaseName) + " = :" + paramName);
|
||||
|
||||
if (this.connection.driver instanceof MysqlDriver || this.connection.driver instanceof WebsqlDriver) {
|
||||
newParameters[paramName] = value;
|
||||
} else {
|
||||
this.expressionMap.nativeParameters[paramName] = value;
|
||||
}
|
||||
|
||||
updateColumnAndValues.push(this.escape(column.databaseName) + " = " + this.connection.driver.createParameter(paramName, parametersCount));
|
||||
parametersCount++;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
if (metadata.versionColumn)
|
||||
updateColumnAndValues.push(this.escape(metadata.versionColumn.databaseName) + " = " + this.escape(metadata.versionColumn.databaseName) + " + 1");
|
||||
if (metadata.updateDateColumn)
|
||||
updateColumnAndValues.push(this.escape(metadata.updateDateColumn.databaseName) + " = CURRENT_TIMESTAMP"); // todo: fix issue with CURRENT_TIMESTAMP(6) being used
|
||||
updateColumnAndValues.push(this.escape(metadata.updateDateColumn.databaseName) + " = CURRENT_TIMESTAMP"); // todo: fix issue with CURRENT_TIMESTAMP(6) being used, can "DEFAULT" be used?!
|
||||
|
||||
} else {
|
||||
Object.keys(valuesSet).map(key => {
|
||||
@ -339,12 +337,24 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
if (value instanceof Array)
|
||||
value = new ArrayParameter(value);
|
||||
|
||||
updateColumnAndValues.push(this.escape(key) + " = :" + key);
|
||||
this.setParameter(key, value);
|
||||
if (this.connection.driver instanceof MysqlDriver || this.connection.driver instanceof WebsqlDriver) {
|
||||
newParameters[key] = value;
|
||||
} else {
|
||||
this.expressionMap.nativeParameters[key] = value;
|
||||
}
|
||||
|
||||
updateColumnAndValues.push(this.escape(key) + " = " + this.connection.driver.createParameter(key, parametersCount));
|
||||
parametersCount++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// we re-write parameters this way because we want our "UPDATE ... SET" parameters to be first in the list of "nativeParameters"
|
||||
// because some drivers like mysql depend on order of parameters
|
||||
if (this.connection.driver instanceof MysqlDriver || this.connection.driver instanceof WebsqlDriver) {
|
||||
this.expressionMap.nativeParameters = Object.assign(newParameters, this.expressionMap.nativeParameters);
|
||||
}
|
||||
|
||||
// get a table name and all column database names
|
||||
const whereExpression = this.createWhereExpression();
|
||||
const returningExpression = this.createReturningExpression();
|
||||
|
||||
@ -88,95 +88,6 @@ export class OrmUtils {
|
||||
static deepCompare(...args: any[]) {
|
||||
let i: any, l: any, leftChain: any, rightChain: any;
|
||||
|
||||
function compare2Objects(x: any, y: any) {
|
||||
let p;
|
||||
|
||||
// remember that NaN === NaN returns false
|
||||
// and isNaN(undefined) returns true
|
||||
if (isNaN(x) && isNaN(y) && typeof x === "number" && typeof y === "number")
|
||||
return true;
|
||||
|
||||
// Compare primitives and functions.
|
||||
// Check if both arguments link to the same object.
|
||||
// Especially useful on the step where we compare prototypes
|
||||
if (x === y)
|
||||
return true;
|
||||
|
||||
if (x.equals instanceof Function && x.equals(y))
|
||||
return true;
|
||||
|
||||
// Works in case when functions are created in constructor.
|
||||
// Comparing dates is a common scenario. Another built-ins?
|
||||
// We can even handle functions passed across iframes
|
||||
if ((typeof x === "function" && typeof y === "function") ||
|
||||
(x instanceof Date && y instanceof Date) ||
|
||||
(x instanceof RegExp && y instanceof RegExp) ||
|
||||
(x instanceof String && y instanceof String) ||
|
||||
(x instanceof Number && y instanceof Number))
|
||||
return x.toString() === y.toString();
|
||||
|
||||
// At last checking prototypes as good as we can
|
||||
if (!(x instanceof Object && y instanceof Object))
|
||||
return false;
|
||||
|
||||
if (x.isPrototypeOf(y) || y.isPrototypeOf(x))
|
||||
return false;
|
||||
|
||||
if (x.constructor !== y.constructor)
|
||||
return false;
|
||||
|
||||
if (x.prototype !== y.prototype)
|
||||
return false;
|
||||
|
||||
// Check for infinitive linking loops
|
||||
if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1)
|
||||
return false;
|
||||
|
||||
// Quick checking of one object being a subset of another.
|
||||
// todo: cache the structure of arguments[0] for performance
|
||||
for (p in y) {
|
||||
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
|
||||
return false;
|
||||
}
|
||||
else if (typeof y[p] !== typeof x[p]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (p in x) {
|
||||
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
|
||||
return false;
|
||||
}
|
||||
else if (typeof y[p] !== typeof x[p]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (typeof (x[p])) {
|
||||
case "object":
|
||||
case "function":
|
||||
|
||||
leftChain.push(x);
|
||||
rightChain.push(y);
|
||||
|
||||
if (!compare2Objects (x[p], y[p])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
leftChain.pop();
|
||||
rightChain.pop();
|
||||
break;
|
||||
|
||||
default:
|
||||
if (x[p] !== y[p]) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (arguments.length < 1) {
|
||||
return true; // Die silently? Don't know how to handle such case, please help...
|
||||
// throw "Need two or more arguments to compare";
|
||||
@ -187,7 +98,7 @@ export class OrmUtils {
|
||||
leftChain = []; // Todo: this can be cached
|
||||
rightChain = [];
|
||||
|
||||
if (!compare2Objects(arguments[0], arguments[i])) {
|
||||
if (!this.compare2Objects(leftChain, rightChain, arguments[0], arguments[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -220,4 +131,94 @@ export class OrmUtils {
|
||||
return object;
|
||||
}, {} as ObjectLiteral);
|
||||
}
|
||||
|
||||
private static compare2Objects(leftChain: any, rightChain: any, x: any, y: any) {
|
||||
let p;
|
||||
|
||||
// remember that NaN === NaN returns false
|
||||
// and isNaN(undefined) returns true
|
||||
if (isNaN(x) && isNaN(y) && typeof x === "number" && typeof y === "number")
|
||||
return true;
|
||||
|
||||
// Compare primitives and functions.
|
||||
// Check if both arguments link to the same object.
|
||||
// Especially useful on the step where we compare prototypes
|
||||
if (x === y)
|
||||
return true;
|
||||
|
||||
if (x.equals instanceof Function && x.equals(y))
|
||||
return true;
|
||||
|
||||
// Works in case when functions are created in constructor.
|
||||
// Comparing dates is a common scenario. Another built-ins?
|
||||
// We can even handle functions passed across iframes
|
||||
if ((typeof x === "function" && typeof y === "function") ||
|
||||
(x instanceof Date && y instanceof Date) ||
|
||||
(x instanceof RegExp && y instanceof RegExp) ||
|
||||
(x instanceof String && y instanceof String) ||
|
||||
(x instanceof Number && y instanceof Number))
|
||||
return x.toString() === y.toString();
|
||||
|
||||
// At last checking prototypes as good as we can
|
||||
if (!(x instanceof Object && y instanceof Object))
|
||||
return false;
|
||||
|
||||
if (x.isPrototypeOf(y) || y.isPrototypeOf(x))
|
||||
return false;
|
||||
|
||||
if (x.constructor !== y.constructor)
|
||||
return false;
|
||||
|
||||
if (x.prototype !== y.prototype)
|
||||
return false;
|
||||
|
||||
// Check for infinitive linking loops
|
||||
if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1)
|
||||
return false;
|
||||
|
||||
// Quick checking of one object being a subset of another.
|
||||
// todo: cache the structure of arguments[0] for performance
|
||||
for (p in y) {
|
||||
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
|
||||
return false;
|
||||
}
|
||||
else if (typeof y[p] !== typeof x[p]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (p in x) {
|
||||
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
|
||||
return false;
|
||||
}
|
||||
else if (typeof y[p] !== typeof x[p]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (typeof (x[p])) {
|
||||
case "object":
|
||||
case "function":
|
||||
|
||||
leftChain.push(x);
|
||||
rightChain.push(y);
|
||||
|
||||
if (!this.compare2Objects(leftChain, rightChain, x[p], y[p])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
leftChain.pop();
|
||||
rightChain.pop();
|
||||
break;
|
||||
|
||||
default:
|
||||
if (x[p] !== y[p]) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -6,7 +6,7 @@ import {Post} from "./entity/Post";
|
||||
describe("benchmark > bulk-save > case1", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await createTestingConnections({ __dirname, enabledDrivers: ["mysql", "postgres"] }));
|
||||
before(async () => connections = await createTestingConnections({ __dirname, enabledDrivers: ["postgres"] }));
|
||||
beforeEach(() => reloadTestingDatabases(connections));
|
||||
after(() => closeTestingConnections(connections));
|
||||
|
||||
@ -24,24 +24,51 @@ describe("benchmark > bulk-save > case1", () => {
|
||||
posts.push(post);
|
||||
}
|
||||
|
||||
// await connection.manager.save(posts);
|
||||
await connection.manager.insert(Post, posts);
|
||||
await connection.manager.save(posts);
|
||||
// await connection.manager.insert(Post, posts);
|
||||
|
||||
})));
|
||||
|
||||
/**
|
||||
* Before getters refactoring
|
||||
* Before persistence refactoring
|
||||
*
|
||||
√ testing bulk save of 1000 objects (3149ms)
|
||||
√ testing bulk save of 1000 objects (2008ms)
|
||||
√ testing bulk save of 1000 objects (1893ms)
|
||||
√ testing bulk save of 1000 objects (1744ms)
|
||||
√ testing bulk save of 1000 objects (1836ms)
|
||||
√ testing bulk save of 1000 objects (1787ms)
|
||||
√ testing bulk save of 1000 objects (1904ms)
|
||||
√ testing bulk save of 1000 objects (1848ms)
|
||||
√ testing bulk save of 1000 objects (1947ms)
|
||||
√ testing bulk save of 1000 objects (2004ms)
|
||||
* MySql
|
||||
*
|
||||
* √ testing bulk save of 1000 objects (2686ms)
|
||||
* √ testing bulk save of 1000 objects (1579ms)
|
||||
* √ testing bulk save of 1000 objects (1664ms)
|
||||
* √ testing bulk save of 1000 objects (1426ms)
|
||||
* √ testing bulk save of 1000 objects (1512ms)
|
||||
* √ testing bulk save of 1000 objects (1526ms)
|
||||
* √ testing bulk save of 1000 objects (1605ms)
|
||||
* √ testing bulk save of 1000 objects (1914ms)
|
||||
* √ testing bulk save of 1000 objects (1983ms)
|
||||
* √ testing bulk save of 1000 objects (1500ms)
|
||||
*
|
||||
* Postgres
|
||||
*
|
||||
* √ testing bulk save of 1000 objects (3704ms)
|
||||
* √ testing bulk save of 1000 objects (2080ms)
|
||||
* √ testing bulk save of 1000 objects (2176ms)
|
||||
* √ testing bulk save of 1000 objects (2447ms)
|
||||
* √ testing bulk save of 1000 objects (2259ms)
|
||||
* √ testing bulk save of 1000 objects (2112ms)
|
||||
* √ testing bulk save of 1000 objects (2193ms)
|
||||
* √ testing bulk save of 1000 objects (2211ms)
|
||||
* √ testing bulk save of 1000 objects (2282ms)
|
||||
* √ testing bulk save of 1000 objects (2551ms)
|
||||
*
|
||||
* SqlServer
|
||||
*
|
||||
* √ testing bulk save of 1000 objects (8098ms)
|
||||
* √ testing bulk save of 1000 objects (6534ms)
|
||||
* √ testing bulk save of 1000 objects (5789ms)
|
||||
* √ testing bulk save of 1000 objects (5505ms)
|
||||
* √ testing bulk save of 1000 objects (5813ms)
|
||||
* √ testing bulk save of 1000 objects (5932ms)
|
||||
* √ testing bulk save of 1000 objects (6114ms)
|
||||
* √ testing bulk save of 1000 objects (5960ms)
|
||||
* √ testing bulk save of 1000 objects (5755ms)
|
||||
* √ testing bulk save of 1000 objects (5935ms)
|
||||
*/
|
||||
|
||||
});
|
||||
@ -10,10 +10,10 @@ describe("benchmark > bulk-save > case2", () => {
|
||||
beforeEach(() => reloadTestingDatabases(connections));
|
||||
after(() => closeTestingConnections(connections));
|
||||
|
||||
it("testing bulk save of 5000 objects", () => Promise.all(connections.map(async connection => {
|
||||
it("testing bulk save of 10000 objects", () => Promise.all(connections.map(async connection => {
|
||||
|
||||
const documents: Document[] = [];
|
||||
for (let i = 0; i < 5000; i++) {
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
const document = new Document();
|
||||
|
||||
document.id = i.toString();
|
||||
@ -44,8 +44,7 @@ describe("benchmark > bulk-save > case2", () => {
|
||||
// await connection.manager.insert(Document, document);
|
||||
}
|
||||
|
||||
await connection.manager.insert(Document, documents);
|
||||
// await connection.manager.save(documents);
|
||||
await connection.manager.save(documents);
|
||||
// await connection.manager.insert(Document, documents);
|
||||
|
||||
})));
|
||||
|
||||
@ -323,6 +323,7 @@ describe("one-to-one", function() {
|
||||
});
|
||||
});
|
||||
|
||||
// todo: check why it generates extra query
|
||||
describe("cascade updates should be executed when cascadeUpdate option is set", function() {
|
||||
let newPost: Post, newImage: PostImage;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user