fixed performance issues

This commit is contained in:
Umed Khudoiberdiev 2017-11-17 23:17:12 +05:00
parent 1e71e853d9
commit 8e076bf94d
13 changed files with 273 additions and 245 deletions

View File

@ -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",

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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");
}
// -------------------------------------------------------------------------

View File

@ -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;

View File

@ -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.

View File

@ -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];
}
/**

View File

@ -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));
}
/**

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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)
*/
});

View File

@ -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);
})));

View File

@ -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;