Skip to content

Commit

Permalink
perf(core): use JIT compiled PK getters/serializers
Browse files Browse the repository at this point in the history
Related: #732
  • Loading branch information
B4nan committed Oct 9, 2020
1 parent a492a64 commit 0ec99dc
Show file tree
Hide file tree
Showing 16 changed files with 164 additions and 113 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/drivers/DatabaseDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export abstract class DatabaseDriver<C extends Connection> implements IDatabaseD
async syncCollection<T extends AnyEntity<T>, O extends AnyEntity<O>>(coll: Collection<T, O>, ctx?: Transaction): Promise<void> {
const pk = this.metadata.find(coll.property.type)!.primaryKeys[0];
const data = { [coll.property.name]: coll.getIdentifiers(pk) } as EntityData<T>;
await this.nativeUpdate<T>(coll.owner.constructor.name, coll.owner.__helper!.__primaryKey, data, ctx);
await this.nativeUpdate<T>(coll.owner.constructor.name, coll.owner.__helper!.getPrimaryKey() as FilterQuery<T>, data, ctx);
}

mapResult<T extends AnyEntity<T>>(result: EntityData<T>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[] = []): EntityData<T> | null {
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/entity/Collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export class Collection<T, O = unknown> extends ArrayCollection<T, O> {

if (!this.initialized && this.property.reference === ReferenceType.MANY_TO_MANY && em.getDriver().getPlatform().usesPivotTable()) {
const map = await em.getDriver().loadFromPivotTable(this.property, [this.owner.__helper!.__primaryKeys], options.where, options.orderBy);
this.hydrate(map[this.owner.__helper!.__serializedPrimaryKey].map((item: EntityData<T>) => em.merge(this.property.type, item, false, true)));
this.hydrate(map[this.owner.__helper!.getSerializedPrimaryKey()].map((item: EntityData<T>) => em.merge(this.property.type, item, false, true)));
this._lazyInitialized = true;

return this;
Expand Down Expand Up @@ -213,7 +213,7 @@ export class Collection<T, O = unknown> extends ArrayCollection<T, O> {

private createCondition<T extends AnyEntity<T>>(cond: FilterQuery<T> = {}): FilterQuery<T> {
if (this.property.reference === ReferenceType.ONE_TO_MANY) {
cond[this.property.mappedBy as string] = this.owner.__helper!.__primaryKey;
cond[this.property.mappedBy] = this.owner.__helper!.getPrimaryKey();
} else { // MANY_TO_MANY
this.createManyToManyCondition(cond as Dictionary);
}
Expand All @@ -238,9 +238,9 @@ export class Collection<T, O = unknown> extends ArrayCollection<T, O> {
// we know there is at least one item as it was checked in load method
const pk = (this._firstItem as AnyEntity<T>).__meta!.primaryKeys[0];
cond[pk] = { $in: [] };
this.items.forEach((item: AnyEntity<T>) => cond[pk].$in.push(item.__helper!.__primaryKey));
this.items.forEach((item: AnyEntity<T>) => cond[pk].$in.push(item.__helper!.getPrimaryKey()));
} else {
cond[this.property.mappedBy] = this.owner.__helper!.__primaryKey;
cond[this.property.mappedBy] = this.owner.__helper!.getPrimaryKey();
}
}

Expand All @@ -256,7 +256,7 @@ export class Collection<T, O = unknown> extends ArrayCollection<T, O> {

private checkInitialized(): void {
if (!this.isInitialized()) {
throw new Error(`Collection<${this.property.type}> of entity ${this.owner.constructor.name}[${this.owner.__helper!.__primaryKey}] not initialized`);
throw new Error(`Collection<${this.property.type}> of entity ${this.owner.constructor.name}[${this.owner.__helper!.getPrimaryKey()}] not initialized`);
}
}

Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/entity/EntityHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class EntityHelper {
EntityHelper.defineIdProperty(meta, em.getDriver().getPlatform());
}

EntityHelper.defineBaseProperties(meta, meta.prototype, em.getDriver().getPlatform());
EntityHelper.defineBaseProperties(meta, meta.prototype, em);
const prototype = meta.prototype as Dictionary;

if (em.config.get('propagateToOneOwner')) {
Expand Down Expand Up @@ -52,16 +52,16 @@ export class EntityHelper {
});
}

private static defineBaseProperties<T extends AnyEntity<T>>(meta: EntityMetadata<T>, prototype: T, platform: Platform) {
private static defineBaseProperties<T extends AnyEntity<T>>(meta: EntityMetadata<T>, prototype: T, em: EntityManager) {
Object.defineProperties(prototype, {
__entity: { value: true },
__meta: { value: meta },
__platform: { value: platform },
__platform: { value: em.getDriver().getPlatform() },
[entityHelperSymbol]: { value: null, writable: true, enumerable: false },
__helper: {
get(): WrappedEntity<T, keyof T> {
if (!this[entityHelperSymbol]) {
this[entityHelperSymbol] = new WrappedEntity(this);
this[entityHelperSymbol] = new WrappedEntity(this, em.getComparator().getPkGetter(meta), em.getComparator().getPkSerializer(meta));
}

return this[entityHelperSymbol];
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/entity/EntityLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export class EntityLoader {
const children: AnyEntity[] = [];

for (const entity of filtered) {
const items = map[entity.__helper!.__serializedPrimaryKey as string].map(item => {
const items = map[entity.__helper!.getSerializedPrimaryKey()].map(item => {
const entity = this.em.getEntityFactory().create<T>(prop.type, item, { refresh, merge: true, convertCustomTypes: true });
return this.em.getUnitOfWork().registerManaged(entity, item, refresh);
});
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/entity/EntityTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class EntityTransformer {
return wrap(child).toJSON(...args) as T[keyof T];
}

return platform.normalizePrimaryKey(wrapped.__primaryKey as unknown as IPrimaryKey) as unknown as T[keyof T];
return platform.normalizePrimaryKey(wrapped.getPrimaryKey() as IPrimaryKey) as unknown as T[keyof T];
}

private static processCollection<T extends AnyEntity<T>>(prop: keyof T, entity: T): T[keyof T] | undefined {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/entity/Reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class Reference<T extends AnyEntity<T>> {
if (meta.serializedPrimaryKey && meta.primaryKeys[0] !== meta.serializedPrimaryKey) {
Object.defineProperty(this, meta.serializedPrimaryKey, {
get() {
return this.entity.__helper!.__serializedPrimaryKey;
return this.entity.__helper!.getSerializedPrimaryKey();
},
});
}
Expand Down Expand Up @@ -93,7 +93,7 @@ export class Reference<T extends AnyEntity<T>> {

getEntity(): T {
if (!this.isInitialized()) {
throw new Error(`Reference<${this.entity.__meta!.name}> ${(this.entity.__helper!.__primaryKey as Primary<T>)} not initialized`);
throw new Error(`Reference<${this.entity.__meta!.name}> ${this.entity.__helper!.getPrimaryKey()} not initialized`);
}

return this.entity;
Expand Down
67 changes: 15 additions & 52 deletions packages/core/src/entity/WrappedEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export class WrappedEntity<T extends AnyEntity<T>, PK extends keyof T> {
/** holds wrapped primary key so we can compute change set without eager commit */
__identifier?: EntityData<T>;

constructor(private readonly entity: T) { }
constructor(private readonly entity: T,
private readonly pkGetter: (e: T) => Primary<T>,
private readonly pkSerializer: (e: T) => string) { }

isInitialized(): boolean {
return this.__initialized;
Expand Down Expand Up @@ -73,45 +75,24 @@ export class WrappedEntity<T extends AnyEntity<T>, PK extends keyof T> {
});
}

get __meta(): EntityMetadata<T> {
return this.entity.__meta!;
getPrimaryKey(): Primary<T> | null {
return this.pkGetter(this.entity);
}

get __platform() {
return this.entity.__platform!;
setPrimaryKey(id: Primary<T> | null) {
this.entity[this.entity.__meta!.primaryKeys[0] as string] = id;
}

get __primaryKey(): Primary<T> | null {
const primaryKeys = this.entity.__meta!.primaryKeys;

if (primaryKeys.length > 1) {
const cond = primaryKeys.reduce((o, pk) => {
if (Utils.isEntity(this.entity[pk])) {
o[pk] = (this.entity[pk] as AnyEntity).__helper!.__primaryKey as Primary<T>;
} else {
o[pk] = this.entity[pk];
}

return o;
}, {} as any);

/* istanbul ignore if */
if (Object.values(cond).some(v => v === null)) {
return null;
}

return cond;
}

if (Utils.isEntity(this.entity[primaryKeys[0]])) {
return (this.entity[primaryKeys[0]] as AnyEntity).__helper!.__primaryKey as Primary<T>;
}
getSerializedPrimaryKey(): string {
return this.pkSerializer(this.entity);
}

return this.entity[primaryKeys[0]] as any;
get __meta(): EntityMetadata<T> {
return this.entity.__meta!;
}

set __primaryKey(id: Primary<T> | null) {
this.entity[this.entity.__meta!.primaryKeys[0] as string] = id;
get __platform() {
return this.entity.__platform!;
}

get __primaryKeys(): Primary<T>[] {
Expand All @@ -123,25 +104,7 @@ export class WrappedEntity<T extends AnyEntity<T>, PK extends keyof T> {
return this.__primaryKeys;
}

return this.__primaryKey;
}

get __serializedPrimaryKey(): Primary<T> | string {
if (this.entity.__meta!.simplePK) {
return '' + this.entity[this.entity.__meta!.serializedPrimaryKey];
}

if (this.entity.__meta!.compositePK) {
return Utils.getCompositeKeyHash(this.entity, this.entity.__meta!);
}

const value = this.entity[this.entity.__meta!.serializedPrimaryKey];

if (Utils.isEntity<T>(value)) {
return value.__helper!.__serializedPrimaryKey;
}

return '' + value;
return this.getPrimaryKey();
}

[inspect.custom]() {
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/metadata/MetadataDiscovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ export class MetadataDiscovery {
this.discovered.forEach(meta => {
const root = Utils.getRootEntity(this.metadata, meta);
const props = Object.values(meta.properties);
const pk = meta.properties[meta.primaryKeys[0]];
meta.simplePK = !meta.compositePK && pk?.reference === ReferenceType.SCALAR && !pk.customType;
meta.props = [...props.filter(p => p.primary), ...props.filter(p => !p.primary)];
meta.relations = meta.props.filter(prop => prop.reference !== ReferenceType.SCALAR && prop.reference !== ReferenceType.EMBEDDED);
meta.comparableProps = meta.props.filter(prop => EntityComparator.isComparable(prop, root));
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export type Primary<T> = T extends { [PrimaryKeyType]: infer PK }
? PK | string : T extends { uuid: infer PK }
? PK : T extends { id: infer PK }
? PK : never;
export type PrimaryMap<T extends AnyEntity<T>> = Record<keyof T, Primary<T>>;
export type IPrimaryKeyValue = number | string | bigint | Date | { toHexString(): string };
export type IPrimaryKey<T extends IPrimaryKeyValue = IPrimaryKeyValue> = T;

Expand Down Expand Up @@ -80,6 +79,9 @@ export interface IWrappedEntity<T extends AnyEntity<T>, PK extends keyof T, P =

export interface IWrappedEntityInternal<T, PK extends keyof T, P = keyof T> extends IWrappedEntity<T, PK, P> {
hasPrimaryKey(): boolean;
getPrimaryKey(): Primary<T>;
setPrimaryKey(val: Primary<T>): void;
getSerializedPrimaryKey(): string & keyof T;
__meta: EntityMetadata<T>;
__data: Dictionary;
__em?: any; // we cannot have `EntityManager` here as that causes a cycle
Expand All @@ -90,10 +92,8 @@ export interface IWrappedEntityInternal<T, PK extends keyof T, P = keyof T> exte
__managed: boolean;
__populated: boolean;
__lazyInitialized: boolean;
__primaryKey: PrimaryMap<T>;
__primaryKeys: Primary<T>[];
__primaryKeyCond: Primary<T> | Primary<T>[];
__serializedPrimaryKey: string & keyof T;
}

export type AnyEntity<T = any> = Partial<T> & {
Expand Down Expand Up @@ -185,7 +185,6 @@ export interface EntityMetadata<T extends AnyEntity<T> = any> {
path: string;
primaryKeys: (keyof T & string)[];
compositePK: boolean;
simplePK: boolean; // PK is scalar, no custom types or composite keys
versionProperty: keyof T & string;
serializedPrimaryKey: keyof T & string;
properties: { [K in keyof T & string]: EntityProperty<T> };
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/unit-of-work/ChangeSetPersister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export class ChangeSetPersister {

private async persistManagedEntitiesBatch<T extends AnyEntity<T>>(meta: EntityMetadata<T>, changeSets: ChangeSet<T>[], ctx?: Transaction): Promise<void> {
await this.checkOptimisticLocks(meta, changeSets, ctx);
await this.driver.nativeUpdateMany(meta.className, changeSets.map(cs => cs.entity.__helper!.__primaryKey as Dictionary), changeSets.map(cs => cs.payload), ctx);
await this.driver.nativeUpdateMany(meta.className, changeSets.map(cs => cs.entity.__helper!.getPrimaryKey() as Dictionary), changeSets.map(cs => cs.payload), ctx);
changeSets.forEach(cs => cs.persisted = true);
}

Expand All @@ -141,7 +141,7 @@ export class ChangeSetPersister {
const wrapped = changeSet.entity.__helper!;

if (!wrapped.hasPrimaryKey()) {
wrapped.__primaryKey = insertId;
wrapped.setPrimaryKey(insertId);
}

changeSet.payload[wrapped.__meta.primaryKeys[0]] = value;
Expand Down Expand Up @@ -170,7 +170,7 @@ export class ChangeSetPersister {

private async updateEntity<T extends AnyEntity<T>>(meta: EntityMetadata<T>, changeSet: ChangeSet<T>, ctx?: Transaction): Promise<QueryResult> {
if (!meta.versionProperty || !changeSet.entity[meta.versionProperty]) {
return this.driver.nativeUpdate(changeSet.name, changeSet.entity.__helper!.__primaryKey as Dictionary, changeSet.payload, ctx);
return this.driver.nativeUpdate(changeSet.name, changeSet.entity.__helper!.getPrimaryKey() as Dictionary, changeSet.payload, ctx);
}

const cond = {
Expand Down Expand Up @@ -217,10 +217,10 @@ export class ChangeSetPersister {
fields: [meta.versionProperty],
}, ctx);
const map = new Map<string, Date>();
data.forEach(e => map.set(Utils.getCompositeKeyHash<T>(e as T, meta), e[meta.versionProperty]));
data.forEach(e => map.set((e as T).__helper!.getSerializedPrimaryKey(), e[meta.versionProperty]));

for (const changeSet of changeSets) {
const version = map.get(changeSet.entity.__helper!.__serializedPrimaryKey);
const version = map.get(changeSet.entity.__helper!.getSerializedPrimaryKey());

// needed for sqlite
if (meta.properties[meta.versionProperty].type.toLowerCase() === 'date') {
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/unit-of-work/UnitOfWork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class UnitOfWork {
return;
}

this.identityMap.set(`${meta.root.name}-${wrapped.__serializedPrimaryKey}`, entity);
this.identityMap.set(`${meta.root.name}-${wrapped.getSerializedPrimaryKey()}`, entity);
entity.__helper!.__originalEntityData = this.comparator.prepareEntity(entity);

this.cascade(entity, Cascade.MERGE, visited);
Expand All @@ -55,7 +55,7 @@ export class UnitOfWork {
* @internal
*/
registerManaged<T extends AnyEntity<T>>(entity: T, data?: EntityData<T>, refresh?: boolean, newEntity?: boolean): T {
this.identityMap.set(`${entity.__meta!.root.name}-${entity.__helper!.__serializedPrimaryKey}`, entity);
this.identityMap.set(`${entity.__meta!.root.name}-${entity.__helper!.getSerializedPrimaryKey()}`, entity);

if (newEntity) {
return entity;
Expand Down Expand Up @@ -88,7 +88,7 @@ export class UnitOfWork {
return null;
}

return this.getById<T>(entityName, pk);
return this.getById<T>(entityName, pk as Primary<T>);
}

/**
Expand Down Expand Up @@ -252,7 +252,7 @@ export class UnitOfWork {

unsetIdentity(entity: AnyEntity): void {
const wrapped = entity.__helper!;
this.identityMap.delete(`${entity.__meta!.root.name}-${wrapped.__serializedPrimaryKey}`);
this.identityMap.delete(`${entity.__meta!.root.name}-${wrapped.getSerializedPrimaryKey()}`);
delete wrapped.__identifier;
delete wrapped.__originalEntityData;
}
Expand Down
Loading

0 comments on commit 0ec99dc

Please sign in to comment.