Skip to content

Commit

Permalink
refactor: use options parameters in IDatabaseDriver
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
Most of the methods on `IDatabaseDriver` interface now have different signature.
  • Loading branch information
B4nan committed Sep 16, 2021
1 parent 1fb9f06 commit c554cf4
Show file tree
Hide file tree
Showing 15 changed files with 151 additions and 111 deletions.
3 changes: 3 additions & 0 deletions docs/docs/upgrading-v4-to-v5.md
Expand Up @@ -28,13 +28,16 @@ List of such methods:
- `em.findAndCount()`
- `em.merge()`
- `em.fork()`
- `em.begin()`
- `repo.find()`
- `repo.findOne()`
- `repo.findOneOrFail()`
- `repo.findAndCount()`
- `repo.findAll()`
- `repo.merge()`

This also applies to the methods on `IDatabaseDriver` interface.

## Type-safe populate parameter with dot notation

`FindOptions.populate` parameter is now strictly typed and supports only array of strings or a boolean.
Expand Down
12 changes: 6 additions & 6 deletions packages/core/src/EntityManager.ts
Expand Up @@ -113,7 +113,7 @@ export class EntityManager<D extends IDatabaseDriver = IDatabaseDriver> {
return cached.data;
}

const results = await this.driver.find<T>(entityName, where, options, this.transactionContext);
const results = await this.driver.find<T, P>(entityName, where, { ctx: this.transactionContext, ...options });

if (results.length === 0) {
await this.storeCache(options.cache, cached!, []);
Expand Down Expand Up @@ -305,7 +305,7 @@ export class EntityManager<D extends IDatabaseDriver = IDatabaseDriver> {
return cached.data;
}

const data = await this.driver.findOne(entityName, where, options, this.transactionContext);
const data = await this.driver.findOne<T, P>(entityName, where, { ctx: this.transactionContext, ...options });

if (!data) {
await this.storeCache(options.cache, cached!, null);
Expand Down Expand Up @@ -413,7 +413,7 @@ export class EntityManager<D extends IDatabaseDriver = IDatabaseDriver> {

data = QueryHelper.processObjectParams(data) as EntityData<T>;
this.validator.validateParams(data, 'insert data');
const res = await this.driver.nativeInsert(entityName, data, this.transactionContext);
const res = await this.driver.nativeInsert(entityName, data, { ctx: this.transactionContext });

return res.insertId as Primary<T>;
}
Expand All @@ -427,7 +427,7 @@ export class EntityManager<D extends IDatabaseDriver = IDatabaseDriver> {
where = await this.processWhere(entityName, where, options, 'update');
this.validator.validateParams(data, 'update data');
this.validator.validateParams(where, 'update condition');
const res = await this.driver.nativeUpdate(entityName, where, data, this.transactionContext);
const res = await this.driver.nativeUpdate(entityName, where, data, { ctx: this.transactionContext });

return res.affectedRows;
}
Expand All @@ -439,7 +439,7 @@ export class EntityManager<D extends IDatabaseDriver = IDatabaseDriver> {
entityName = Utils.className(entityName);
where = await this.processWhere(entityName, where, options, 'delete');
this.validator.validateParams(where, 'delete condition');
const res = await this.driver.nativeDelete(entityName, where, this.transactionContext);
const res = await this.driver.nativeDelete(entityName, where, { ctx: this.transactionContext });

return res.affectedRows;
}
Expand Down Expand Up @@ -574,7 +574,7 @@ export class EntityManager<D extends IDatabaseDriver = IDatabaseDriver> {
return cached.data as number;
}

const count = await this.driver.count<T>(entityName, where, options, this.transactionContext);
const count = await this.driver.count<T, P>(entityName, where, { ctx: this.transactionContext, ...options });
await this.storeCache(options.cache, cached!, () => count);

return count;
Expand Down
26 changes: 13 additions & 13 deletions packages/core/src/drivers/DatabaseDriver.ts
@@ -1,4 +1,4 @@
import type { CountOptions, FindOneOptions, FindOptions, IDatabaseDriver } from './IDatabaseDriver';
import type { CountOptions, FindOneOptions, FindOptions, IDatabaseDriver, NativeInsertUpdateManyOptions, NativeInsertUpdateOptions } from './IDatabaseDriver';
import { EntityManagerType } from './IDatabaseDriver';
import type { AnyEntity, Dictionary, EntityData, EntityDictionary, EntityMetadata, EntityProperty, ObjectQuery, FilterQuery, PopulateOptions, Primary } from '../typings';
import type { MetadataStorage } from '../metadata';
Expand Down Expand Up @@ -27,23 +27,23 @@ export abstract class DatabaseDriver<C extends Connection> implements IDatabaseD
protected constructor(protected readonly config: Configuration,
protected readonly dependencies: string[]) { }

abstract find<T>(entityName: string, where: FilterQuery<T>, options?: FindOptions<T>, ctx?: Transaction): Promise<EntityData<T>[]>;
abstract find<T>(entityName: string, where: FilterQuery<T>, options?: FindOptions<T>): Promise<EntityData<T>[]>;

abstract findOne<T>(entityName: string, where: FilterQuery<T>, options?: FindOneOptions<T>, ctx?: Transaction): Promise<EntityData<T> | null>;
abstract findOne<T>(entityName: string, where: FilterQuery<T>, options?: FindOneOptions<T>): Promise<EntityData<T> | null>;

abstract nativeInsert<T>(entityName: string, data: EntityDictionary<T>, ctx?: Transaction, convertCustomTypes?: boolean): Promise<QueryResult<T>>;
abstract nativeInsert<T>(entityName: string, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;

abstract nativeInsertMany<T>(entityName: string, data: EntityDictionary<T>[], ctx?: Transaction, processCollections?: boolean, convertCustomTypes?: boolean): Promise<QueryResult<T>>;
abstract nativeInsertMany<T>(entityName: string, data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>): Promise<QueryResult<T>>;

abstract nativeUpdate<T>(entityName: string, where: FilterQuery<T>, data: EntityDictionary<T>, ctx?: Transaction, convertCustomTypes?: boolean): Promise<QueryResult<T>>;
abstract nativeUpdate<T>(entityName: string, where: FilterQuery<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;

async nativeUpdateMany<T>(entityName: string, where: FilterQuery<T>[], data: EntityDictionary<T>[], ctx?: Transaction, processCollections?: boolean, convertCustomTypes?: boolean): Promise<QueryResult<T>> {
async nativeUpdateMany<T>(entityName: string, where: FilterQuery<T>[], data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>): Promise<QueryResult<T>> {
throw new Error(`Batch updates are not supported by ${this.constructor.name} driver`);
}

abstract nativeDelete<T>(entityName: string, where: FilterQuery<T>, ctx?: Transaction): Promise<QueryResult<T>>;
abstract nativeDelete<T>(entityName: string, where: FilterQuery<T>, options?: { ctx?: Transaction }): Promise<QueryResult<T>>;

abstract count<T>(entityName: string, where: FilterQuery<T>, options?: CountOptions<T>, ctx?: Transaction): Promise<number>;
abstract count<T>(entityName: string, where: FilterQuery<T>, options?: CountOptions<T>): Promise<number>;

createEntityManager<D extends IDatabaseDriver = IDatabaseDriver>(useContext?: boolean): D[typeof EntityManagerType] {
return new EntityManager(this.config, this, this.metadata, useContext) as unknown as EntityManager<D>;
Expand All @@ -57,20 +57,20 @@ export abstract class DatabaseDriver<C extends Connection> implements IDatabaseD
throw new Error(`${this.constructor.name} does not use pivot tables`);
}

async syncCollection<T, O>(coll: Collection<T, O>, ctx?: Transaction): Promise<void> {
async syncCollection<T, O>(coll: Collection<T, O>, options?: { 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!.getPrimaryKey() as FilterQuery<T>, data, ctx);
await this.nativeUpdate<T>(coll.owner.constructor.name, coll.owner.__helper!.getPrimaryKey() as FilterQuery<T>, data, options);
}

async clearCollection<T, O>(coll: Collection<T, O>, ctx?: Transaction): Promise<void> {
async clearCollection<T, O>(coll: Collection<T, O>, options?: { ctx?: Transaction }): Promise<void> {
// this currently serves only for 1:m collections with orphan removal, m:n ones are handled via `syncCollection` method
const snapshot = coll.getSnapshot();
/* istanbul ignore next */
const deleteDiff = snapshot ? snapshot.map(item => (item as AnyEntity<T>).__helper!.__primaryKeyCond) : [];

const cond = { [Utils.getPrimaryKeyHash(coll.property.targetMeta!.primaryKeys)]: deleteDiff } as ObjectQuery<T>;
await this.nativeDelete<T>(coll.property.type, cond, ctx);
await this.nativeDelete<T>(coll.property.type, cond, options);
}

mapResult<T>(result: EntityDictionary<T>, meta: EntityMetadata<T>, populate: PopulateOptions<T>[] = []): EntityData<T> | null {
Expand Down
31 changes: 21 additions & 10 deletions packages/core/src/drivers/IDatabaseDriver.ts
Expand Up @@ -29,28 +29,28 @@ export interface IDatabaseDriver<C extends Connection = Connection> {
/**
* Finds selection of entities
*/
find<T extends AnyEntity<T>>(entityName: string, where: FilterQuery<T>, options?: FindOptions<T>, ctx?: Transaction): Promise<EntityData<T>[]>;
find<T extends AnyEntity<T>, P extends string = never>(entityName: string, where: FilterQuery<T>, options?: FindOptions<T, P>): Promise<EntityData<T>[]>;

/**
* Finds single entity (table row, document)
*/
findOne<T extends AnyEntity<T>>(entityName: string, where: FilterQuery<T>, options?: FindOneOptions<T>, ctx?: Transaction): Promise<EntityData<T> | null>;
findOne<T extends AnyEntity<T>, P extends string = never>(entityName: string, where: FilterQuery<T>, options?: FindOneOptions<T, P>): Promise<EntityData<T> | null>;

nativeInsert<T extends AnyEntity<T>>(entityName: string, data: EntityDictionary<T>, ctx?: Transaction, convertCustomTypes?: boolean): Promise<QueryResult<T>>;
nativeInsert<T extends AnyEntity<T>>(entityName: string, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;

nativeInsertMany<T extends AnyEntity<T>>(entityName: string, data: EntityDictionary<T>[], ctx?: Transaction, processCollections?: boolean, convertCustomTypes?: boolean): Promise<QueryResult<T>>;
nativeInsertMany<T extends AnyEntity<T>>(entityName: string, data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>): Promise<QueryResult<T>>;

nativeUpdate<T extends AnyEntity<T>>(entityName: string, where: FilterQuery<T>, data: EntityDictionary<T>, ctx?: Transaction, convertCustomTypes?: boolean): Promise<QueryResult<T>>;
nativeUpdate<T extends AnyEntity<T>>(entityName: string, where: FilterQuery<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;

nativeUpdateMany<T extends AnyEntity<T>>(entityName: string, where: FilterQuery<T>[], data: EntityDictionary<T>[], ctx?: Transaction, processCollections?: boolean, convertCustomTypes?: boolean): Promise<QueryResult<T>>;
nativeUpdateMany<T extends AnyEntity<T>>(entityName: string, where: FilterQuery<T>[], data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>): Promise<QueryResult<T>>;

nativeDelete<T extends AnyEntity<T>>(entityName: string, where: FilterQuery<T>, ctx?: Transaction): Promise<QueryResult<T>>;
nativeDelete<T extends AnyEntity<T>>(entityName: string, where: FilterQuery<T>, options?: { ctx?: Transaction }): Promise<QueryResult<T>>;

syncCollection<T, O>(collection: Collection<T, O>, ctx?: Transaction): Promise<void>;
syncCollection<T, O>(collection: Collection<T, O>, options?: { ctx?: Transaction }): Promise<void>;

clearCollection<T, O>(collection: Collection<T, O>, ctx?: Transaction): Promise<void>;
clearCollection<T, O>(collection: Collection<T, O>, options?: { ctx?: Transaction }): Promise<void>;

count<T extends AnyEntity<T>, P extends string = never>(entityName: string, where: FilterQuery<T>, options?: CountOptions<T, P>, ctx?: Transaction): Promise<number>;
count<T extends AnyEntity<T>, P extends string = never>(entityName: string, where: FilterQuery<T>, options?: CountOptions<T, P>): Promise<number>;

aggregate(entityName: string, pipeline: any[]): Promise<any[]>;

Expand Down Expand Up @@ -103,6 +103,7 @@ export interface FindOptions<T, P extends string = never> {
filters?: Dictionary<boolean | Dictionary> | string[] | boolean;
lockMode?: Exclude<LockMode, LockMode.OPTIMISTIC>;
lockTableAliases?: string[];
ctx?: Transaction;
}

export interface FindOneOptions<T, P extends string = never> extends Omit<FindOptions<T, P>, 'limit' | 'offset' | 'lockMode'> {
Expand All @@ -114,13 +115,23 @@ export interface FindOneOrFailOptions<T, P extends string = never> extends FindO
failHandler?: (entityName: string, where: Dictionary | IPrimaryKey | any) => Error;
}

export interface NativeInsertUpdateOptions<T> {
convertCustomTypes?: boolean;
ctx?: Transaction;
}

export interface NativeInsertUpdateManyOptions<T> extends NativeInsertUpdateOptions<T> {
processCollections?: boolean;
}

export interface CountOptions<T, P extends string = never> {
filters?: Dictionary<boolean | Dictionary> | string[] | boolean;
schema?: string;
groupBy?: string | readonly string[];
having?: QBFilterQuery<T>;
cache?: boolean | number | [string, number];
populate?: readonly AutoPath<T, P>[] | boolean;
ctx?: Transaction;
}

export interface UpdateOptions<T> {
Expand Down
28 changes: 20 additions & 8 deletions packages/core/src/unit-of-work/ChangeSetPersister.ts
Expand Up @@ -54,7 +54,7 @@ export class ChangeSetPersister {
for (let i = 0; i < changeSets.length; i += size) {
const chunk = changeSets.slice(i, i + size);
const pks = chunk.map(cs => cs.getPrimaryKey());
await this.driver.nativeDelete(meta.className, { [pk]: { $in: pks } }, ctx);
await this.driver.nativeDelete(meta.className, { [pk]: { $in: pks } }, { ctx });
}
}

Expand All @@ -68,7 +68,10 @@ export class ChangeSetPersister {

private async persistNewEntity<T extends AnyEntity<T>>(meta: EntityMetadata<T>, changeSet: ChangeSet<T>, ctx?: Transaction): Promise<void> {
const wrapped = changeSet.entity.__helper!;
const res = await this.driver.nativeInsert(changeSet.name, changeSet.payload, ctx, false);
const res = await this.driver.nativeInsert(changeSet.name, changeSet.payload, {
convertCustomTypes: false,
ctx,
});

if (!wrapped.hasPrimaryKey()) {
this.mapPrimaryKey(meta, res.insertId as number, changeSet);
Expand Down Expand Up @@ -100,7 +103,11 @@ export class ChangeSetPersister {
}

private async persistNewEntitiesBatch<T extends AnyEntity<T>>(meta: EntityMetadata<T>, changeSets: ChangeSet<T>[], ctx?: Transaction): Promise<void> {
const res = await this.driver.nativeInsertMany(meta.className, changeSets.map(cs => cs.payload), ctx, false, false);
const res = await this.driver.nativeInsertMany(meta.className, changeSets.map(cs => cs.payload), {
convertCustomTypes: false,
processCollections: false,
ctx,
});

for (let i = 0; i < changeSets.length; i++) {
const changeSet = changeSets[i];
Expand Down Expand Up @@ -139,7 +146,11 @@ 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.getPrimaryKey() as Dictionary), changeSets.map(cs => cs.payload), ctx, false, false);
await this.driver.nativeUpdateMany(meta.className, changeSets.map(cs => cs.getPrimaryKey() as Dictionary), changeSets.map(cs => cs.payload), {
convertCustomTypes: false,
processCollections: false,
ctx,
});
changeSets.forEach(cs => cs.persisted = true);
}

Expand Down Expand Up @@ -182,15 +193,15 @@ export class ChangeSetPersister {

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

const cond = {
...Utils.getPrimaryKeyCond<T>(changeSet.entity, meta.primaryKeys),
[meta.versionProperty]: this.platform.quoteVersionValue(changeSet.entity[meta.versionProperty] as unknown as Date, meta.properties[meta.versionProperty]),
} as FilterQuery<T>;

return this.driver.nativeUpdate<T>(changeSet.name, cond, changeSet.payload, ctx, false);
return this.driver.nativeUpdate<T>(changeSet.name, cond, changeSet.payload, { ctx, convertCustomTypes: false });
}

private async checkOptimisticLocks<T extends AnyEntity<T>>(meta: EntityMetadata<T>, changeSets: ChangeSet<T>[], ctx?: Transaction): Promise<void> {
Expand All @@ -203,7 +214,7 @@ export class ChangeSetPersister {
[meta.versionProperty]: this.platform.quoteVersionValue(cs.entity[meta.versionProperty] as unknown as Date, meta.properties[meta.versionProperty]),
}));

const res = await this.driver.find<T>(meta.className, { $or } as FilterQuery<T>, { fields: meta.primaryKeys }, ctx);
const res = await this.driver.find<T>(meta.className, { $or } as FilterQuery<T>, { fields: meta.primaryKeys, ctx });

if (res.length !== changeSets.length) {
const compare = (a: Dictionary, b: Dictionary, keys: string[]) => keys.every(k => a[k] === b[k]);
Expand All @@ -227,7 +238,8 @@ export class ChangeSetPersister {
const pks = changeSets.map(cs => cs.getPrimaryKey());
const data = await this.driver.find<T>(meta.name!, { [pk]: { $in: pks } } as FilterQuery<T>, {
fields: [meta.versionProperty],
}, ctx);
ctx,
});
const map = new Map<string, Date>();
data.forEach(e => map.set(Utils.getCompositeKeyHash<T>(e as T, meta), e[meta.versionProperty] as Date));

Expand Down
18 changes: 9 additions & 9 deletions packages/core/src/unit-of-work/UnitOfWork.ts
Expand Up @@ -588,28 +588,28 @@ export class UnitOfWork {
}
}

private async persistToDatabase(groups: { [K in ChangeSetType]: Map<string, ChangeSet<any>[]> }, tx?: Transaction): Promise<void> {
if (tx) {
this.em.setTransactionContext(tx);
private async persistToDatabase(groups: { [K in ChangeSetType]: Map<string, ChangeSet<any>[]> }, ctx?: Transaction): Promise<void> {
if (ctx) {
this.em.setTransactionContext(ctx);
}

const commitOrder = this.getCommitOrder();
const commitOrderReversed = [...commitOrder].reverse();

// 1. whole collection deletions
for (const coll of this.collectionDeletions) {
await this.em.getDriver().clearCollection(coll, tx);
await this.em.getDriver().clearCollection(coll, { ctx });
coll.takeSnapshot();
}

// 2. create
for (const name of commitOrder) {
await this.commitCreateChangeSets(groups[ChangeSetType.CREATE].get(name) ?? [], tx);
await this.commitCreateChangeSets(groups[ChangeSetType.CREATE].get(name) ?? [], ctx);
}

// 3. update
for (const name of commitOrder) {
await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE].get(name) ?? [], tx);
await this.commitUpdateChangeSets(groups[ChangeSetType.UPDATE].get(name) ?? [], ctx);
}

// 4. extra updates
Expand All @@ -629,17 +629,17 @@ export class UnitOfWork {
}
}

await this.commitUpdateChangeSets(extraUpdates, tx, false);
await this.commitUpdateChangeSets(extraUpdates, ctx, false);

// 5. collection updates
for (const coll of this.collectionUpdates) {
await this.em.getDriver().syncCollection(coll, tx);
await this.em.getDriver().syncCollection(coll, { ctx });
coll.takeSnapshot();
}

// 6. delete - entity deletions need to be in reverse commit order
for (const name of commitOrderReversed) {
await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE].get(name) ?? [], tx);
await this.commitDeleteChangeSets(groups[ChangeSetType.DELETE].get(name) ?? [], ctx);
}

// 7. take snapshots of all persisted collections
Expand Down

0 comments on commit c554cf4

Please sign in to comment.