♻️Removed cumulative queries to the database
First step to the transactions in PostgreSQL
This commit is contained in:
committed by
Anton Shepilov
parent
8b9fd36c8c
commit
492b636e93
@@ -11,7 +11,7 @@ export interface DatabaseServiceAPI extends TdriveServiceProvider {
|
|||||||
getConnector(): Connector;
|
getConnector(): Connector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get entities manager (TODO: Find a better name...)
|
* Get entities manager
|
||||||
*/
|
*/
|
||||||
getManager(): Manager<unknown>;
|
getManager(): Manager<unknown>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { Connector } from "./connectors";
|
import { Connector } from "./connectors";
|
||||||
import { getEntityDefinition, unwrapPrimarykey } from "./utils";
|
import { getEntityDefinition, unwrapPrimarykey } from "./utils";
|
||||||
@@ -9,13 +8,9 @@ import { DatabaseEntitiesRemovedEvent, DatabaseEntitiesSavedEvent } from "./type
|
|||||||
import { localEventBus } from "../../../../framework/event-bus";
|
import { localEventBus } from "../../../../framework/event-bus";
|
||||||
|
|
||||||
export default class EntityManager<EntityType extends Record<string, any>> {
|
export default class EntityManager<EntityType extends Record<string, any>> {
|
||||||
private toInsert: EntityType[] = [];
|
|
||||||
private toUpdate: EntityType[] = [];
|
|
||||||
private toRemove: EntityType[] = [];
|
|
||||||
|
|
||||||
constructor(readonly connector: Connector) {}
|
constructor(readonly connector: Connector) {}
|
||||||
|
|
||||||
public persist(entity: any): this {
|
public async persist(entity: any): Promise<this> {
|
||||||
logger.trace(
|
logger.trace(
|
||||||
`services.database.orm.entity-manager.persist - entity: ${JSON.stringify(entity)}`,
|
`services.database.orm.entity-manager.persist - entity: ${JSON.stringify(entity)}`,
|
||||||
);
|
);
|
||||||
@@ -62,18 +57,20 @@ export default class EntityManager<EntityType extends Record<string, any>> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
entity = _.cloneDeep(entity);
|
||||||
if (emptyPkFields.length > 0) {
|
if (emptyPkFields.length > 0) {
|
||||||
this.toInsert = this.toInsert.filter(e => e !== entity);
|
await this.connector.upsert([entity], { action: "INSERT" });
|
||||||
this.toInsert.push(_.cloneDeep(entity));
|
|
||||||
} else {
|
} else {
|
||||||
this.toUpdate = this.toUpdate.filter(e => e !== entity);
|
await this.connector.upsert([entity], { action: "UPDATE" });
|
||||||
this.toUpdate.push(_.cloneDeep(entity));
|
|
||||||
}
|
}
|
||||||
|
localEventBus.publish("database:entities:saved", {
|
||||||
|
entities: [entity],
|
||||||
|
} as DatabaseEntitiesSavedEvent);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public remove(entity: EntityType, entityType?: EntityType): this {
|
public async remove(entity: EntityType, entityType?: EntityType): Promise<this> {
|
||||||
if (entityType) {
|
if (entityType) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
entity = _.merge(new (entityType as any)(), entity);
|
entity = _.merge(new (entityType as any)(), entity);
|
||||||
@@ -81,45 +78,13 @@ export default class EntityManager<EntityType extends Record<string, any>> {
|
|||||||
if (!entity.constructor.prototype._entity || !entity.constructor.prototype._columns) {
|
if (!entity.constructor.prototype._entity || !entity.constructor.prototype._columns) {
|
||||||
throw Error("Cannot remove this object: it is not an entity.");
|
throw Error("Cannot remove this object: it is not an entity.");
|
||||||
}
|
}
|
||||||
this.toRemove = this.toRemove.filter(e => e !== entity);
|
|
||||||
this.toRemove.push(_.cloneDeep(entity));
|
|
||||||
|
|
||||||
return this;
|
await this.connector.remove([entity]);
|
||||||
}
|
|
||||||
|
|
||||||
public async flush(): Promise<this> {
|
|
||||||
this.toInsert = _.uniqWith(this.toInsert, _.isEqual);
|
|
||||||
this.toUpdate = _.uniqWith(this.toUpdate, _.isEqual);
|
|
||||||
this.toRemove = _.uniqWith(this.toRemove, _.isEqual);
|
|
||||||
|
|
||||||
localEventBus.publish("database:entities:saved", {
|
localEventBus.publish("database:entities:saved", {
|
||||||
entities: this.toInsert.map(e => _.cloneDeep(e)),
|
entities: [entity],
|
||||||
} as DatabaseEntitiesSavedEvent);
|
|
||||||
|
|
||||||
localEventBus.publish("database:entities:saved", {
|
|
||||||
entities: this.toUpdate.map(e => _.cloneDeep(e)),
|
|
||||||
} as DatabaseEntitiesSavedEvent);
|
|
||||||
|
|
||||||
localEventBus.publish("database:entities:saved", {
|
|
||||||
entities: this.toRemove.map(e => _.cloneDeep(e)),
|
|
||||||
} as DatabaseEntitiesRemovedEvent);
|
} as DatabaseEntitiesRemovedEvent);
|
||||||
|
|
||||||
if (this.toInsert.length > 0) {
|
|
||||||
await this.connector.upsert(this.toInsert, { action: "INSERT" });
|
|
||||||
}
|
|
||||||
if (this.toUpdate.length > 0) {
|
|
||||||
await this.connector.upsert(this.toUpdate, { action: "UPDATE" });
|
|
||||||
}
|
|
||||||
if (this.toRemove.length > 0) {
|
|
||||||
await this.connector.remove(this.toRemove);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {
|
|
||||||
this.toInsert = [];
|
|
||||||
this.toUpdate = [];
|
|
||||||
this.toRemove = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-8
@@ -135,25 +135,20 @@ export default class Repository<EntityType> {
|
|||||||
}> {
|
}> {
|
||||||
if (previousValue === newValue)
|
if (previousValue === newValue)
|
||||||
throw new Error(`Previous and new values are identical: ${JSON.stringify(previousValue)}`);
|
throw new Error(`Previous and new values are identical: ${JSON.stringify(previousValue)}`);
|
||||||
await this.manager.flush().then(manager => manager.reset());
|
|
||||||
return this.connector.atomicCompareAndSet(entity, fieldName, previousValue, newValue);
|
return this.connector.atomicCompareAndSet(entity, fieldName, previousValue, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(entity: EntityType, _context?: ExecutionContext): Promise<void> {
|
async save(entity: EntityType, _context?: ExecutionContext): Promise<void> {
|
||||||
this.manager.persist(entity);
|
await this.manager.persist(entity);
|
||||||
return this.manager.flush().then(manager => manager.reset());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveAll(entities: EntityType[] = [], _context?: ExecutionContext): Promise<void> {
|
async saveAll(entities: EntityType[] = [], _context?: ExecutionContext): Promise<void> {
|
||||||
logger.debug("services.database.repository - Saving entities");
|
logger.debug("services.database.repository - Saving entities");
|
||||||
|
await Promise.all(entities.map(entity => this.manager.persist(entity)));
|
||||||
entities.forEach(entity => this.manager.persist(entity));
|
|
||||||
return this.manager.flush().then(manager => manager.reset());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(entity: EntityType, _context?: ExecutionContext): Promise<void> {
|
async remove(entity: EntityType, _context?: ExecutionContext): Promise<void> {
|
||||||
this.manager.remove(entity);
|
await this.manager.remove(entity);
|
||||||
return this.manager.flush().then(manager => manager.reset());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Avoid using this except when no choice
|
//Avoid using this except when no choice
|
||||||
|
|||||||
@@ -9,50 +9,46 @@ import WorkspaceUser, { getInstance } from "../../../../../../../src/services/wo
|
|||||||
|
|
||||||
describe('EntityManager', () => {
|
describe('EntityManager', () => {
|
||||||
|
|
||||||
const subj: EntityManager<TestDbEntity> = new EntityManager<TestDbEntity>({ } as Connector);
|
let connector = { upsert: () => void 0};
|
||||||
|
const subj: EntityManager<TestDbEntity> = new EntityManager<TestDbEntity>(connector as unknown as Connector);
|
||||||
|
let upsert;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
upsert = jest.spyOn((subj as any).connector, "upsert");
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
subj.reset();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test ("persist should store entity to insert if all fields for pk is empty", () => {
|
test ("persist should store entity to insert if all fields for pk is empty", () => {
|
||||||
//when
|
//when
|
||||||
subj.persist(new TestDbEntity());
|
let entity = new TestDbEntity();
|
||||||
|
subj.persist(entity);
|
||||||
|
|
||||||
//then
|
//then
|
||||||
expect((subj as any).toInsert.length).toEqual(1);
|
expect(upsert).toBeCalledTimes(1);
|
||||||
expect((subj as any).toUpdate.length).toEqual(0);
|
expect(upsert).toBeCalledWith([entity], {"action": "INSERT"})
|
||||||
expect((subj as any).toRemove.length).toEqual(0);
|
|
||||||
expect((subj as any).toInsert[0].id).toBeDefined();
|
|
||||||
expect((subj as any).toInsert[0].company_id).toBeDefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test ("persist should store entity to insert if id is set", () => {
|
test ("persist should store entity to insert if id is set", () => {
|
||||||
//when
|
//when
|
||||||
subj.persist(new TestDbEntity({id: randomUUID()}));
|
let entity = new TestDbEntity({id: randomUUID()});
|
||||||
|
subj.persist(entity);
|
||||||
|
|
||||||
//then
|
//then
|
||||||
expect((subj as any).toInsert.length).toEqual(1);
|
expect(upsert).toBeCalledTimes(1);
|
||||||
expect((subj as any).toUpdate.length).toEqual(0);
|
expect(upsert).toBeCalledWith([entity], {"action": "INSERT"})
|
||||||
expect((subj as any).toRemove.length).toEqual(0);
|
|
||||||
expect((subj as any).toInsert[0].id).toBeDefined();
|
|
||||||
expect((subj as any).toInsert[0].company_id).toBeDefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test ("persist should store entity to insert if company_id is set", () => {
|
test ("persist should store entity to insert if company_id is set", () => {
|
||||||
//when
|
//when
|
||||||
subj.persist(new TestDbEntity({company_id: randomUUID()}));
|
let entity = new TestDbEntity({company_id: randomUUID()});
|
||||||
|
subj.persist(entity);
|
||||||
|
|
||||||
//then
|
//then
|
||||||
expect((subj as any).toInsert.length).toEqual(1);
|
expect(upsert).toBeCalledTimes(1);
|
||||||
expect((subj as any).toUpdate.length).toEqual(0);
|
expect(upsert).toBeCalledWith([entity], {"action": "INSERT"})
|
||||||
expect((subj as any).toRemove.length).toEqual(0);
|
|
||||||
expect((subj as any).toInsert[0].id).toBeDefined();
|
|
||||||
expect((subj as any).toInsert[0].company_id).toBeDefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test ("persist should store entity to update if all pk fields are set", () => {
|
test ("persist should store entity to update if all pk fields are set", () => {
|
||||||
@@ -61,10 +57,8 @@ describe('EntityManager', () => {
|
|||||||
subj.persist(entity);
|
subj.persist(entity);
|
||||||
|
|
||||||
//then
|
//then
|
||||||
expect((subj as any).toUpdate.length).toEqual(1);
|
expect(upsert).toBeCalledTimes(1);
|
||||||
expect((subj as any).toInsert.length).toEqual(0);
|
expect(upsert).toBeCalledWith([entity], {"action": "UPDATE"})
|
||||||
expect((subj as any).toRemove.length).toEqual(0);
|
|
||||||
expect((subj as any).toUpdate[0]).toEqual(entity)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test ("persist should store entity to update if all pk fields are set and column name is different from field name", () => {
|
test ("persist should store entity to update if all pk fields are set and column name is different from field name", () => {
|
||||||
@@ -73,10 +67,8 @@ describe('EntityManager', () => {
|
|||||||
subj.persist(entity);
|
subj.persist(entity);
|
||||||
|
|
||||||
//then
|
//then
|
||||||
expect((subj as any).toUpdate.length).toEqual(1);
|
expect(upsert).toBeCalledTimes(1)
|
||||||
expect((subj as any).toInsert.length).toEqual(0);
|
expect(upsert).toBeCalledWith([entity], {"action": "UPDATE"})
|
||||||
expect((subj as any).toRemove.length).toEqual(0);
|
|
||||||
expect((subj as any).toUpdate[0]).toEqual(entity)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test ("persist should store entity to insert if not all pk fields are set and column name is different from field name", () => {
|
test ("persist should store entity to insert if not all pk fields are set and column name is different from field name", () => {
|
||||||
@@ -85,11 +77,7 @@ describe('EntityManager', () => {
|
|||||||
subj.persist(entity);
|
subj.persist(entity);
|
||||||
|
|
||||||
//then
|
//then
|
||||||
expect((subj as any).toUpdate.length).toEqual(0);
|
expect(upsert).toBeCalledWith([entity], {"action": "INSERT"})
|
||||||
expect((subj as any).toInsert.length).toEqual(1);
|
|
||||||
expect((subj as any).toRemove.length).toEqual(0);
|
|
||||||
expect((subj as any).toInsert[0]).toEqual(entity);
|
|
||||||
expect(entity.userId).toBeDefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user