chore(catalog-backend): removing redudant classes and some functions
This commit is contained in:
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
|
||||
import { Logger } from 'winston';
|
||||
import { CoalescedEntitiesCatalog } from './CoalescedEntitiesCatalog';
|
||||
import { EntitiesCatalog } from './types';
|
||||
|
||||
describe('CoalescedEntitiesCatalog', () => {
|
||||
const e1: Entity = {
|
||||
apiVersion: 'a',
|
||||
kind: 'k',
|
||||
metadata: { name: 'n1' },
|
||||
};
|
||||
|
||||
const e2: Entity = {
|
||||
apiVersion: 'a',
|
||||
kind: 'k',
|
||||
metadata: { name: 'n2' },
|
||||
};
|
||||
|
||||
const c1: jest.Mocked<EntitiesCatalog> = {
|
||||
entities: jest.fn(),
|
||||
entityByUid: jest.fn(),
|
||||
entityByName: jest.fn(),
|
||||
addOrUpdateEntity: jest.fn(),
|
||||
addEntities: jest.fn(),
|
||||
removeEntityByUid: jest.fn(),
|
||||
};
|
||||
|
||||
const c2: jest.Mocked<EntitiesCatalog> = {
|
||||
entities: jest.fn(),
|
||||
entityByUid: jest.fn(),
|
||||
entityByName: jest.fn(),
|
||||
addOrUpdateEntity: jest.fn(),
|
||||
addEntities: jest.fn(),
|
||||
removeEntityByUid: jest.fn(),
|
||||
};
|
||||
|
||||
const mockLogger = {
|
||||
warn: jest.fn(),
|
||||
};
|
||||
const logger = (mockLogger as unknown) as Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('entities', () => {
|
||||
it('flattens results from multiple sources', async () => {
|
||||
c1.entities.mockResolvedValueOnce([e1]);
|
||||
c2.entities.mockResolvedValueOnce([e2]);
|
||||
const catalog = new CoalescedEntitiesCatalog([c1, c2], logger);
|
||||
await expect(catalog.entities()).resolves.toEqual(
|
||||
expect.arrayContaining([e1, e2]),
|
||||
);
|
||||
expect(c1.entities).toBeCalledTimes(1);
|
||||
expect(c2.entities).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('logs an error if any source throws', async () => {
|
||||
c1.entities.mockResolvedValueOnce([e1]);
|
||||
c2.entities.mockRejectedValueOnce(new Error('boo'));
|
||||
const catalog = new CoalescedEntitiesCatalog([c1, c2], logger);
|
||||
await expect(catalog.entities()).resolves.toEqual([e1]);
|
||||
expect(c1.entities).toBeCalledTimes(1);
|
||||
expect(c2.entities).toBeCalledTimes(1);
|
||||
expect(mockLogger.warn).toBeCalledWith(expect.stringMatching(/boo/));
|
||||
});
|
||||
});
|
||||
|
||||
describe('entityByUid', () => {
|
||||
it('returns the first non-undefined result', async () => {
|
||||
c1.entityByUid.mockResolvedValueOnce(undefined);
|
||||
c2.entityByUid.mockResolvedValueOnce(e2);
|
||||
const catalog = new CoalescedEntitiesCatalog([c1, c2], logger);
|
||||
await expect(catalog.entityByUid('e2')).resolves.toBe(e2);
|
||||
expect(c1.entityByUid).toBeCalledTimes(1);
|
||||
expect(c2.entityByUid).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns undefined if all results were undefined', async () => {
|
||||
c1.entityByUid.mockResolvedValueOnce(undefined);
|
||||
c2.entityByUid.mockResolvedValueOnce(undefined);
|
||||
const catalog = new CoalescedEntitiesCatalog([c1, c2], logger);
|
||||
await expect(catalog.entityByUid('e2')).resolves.toBeUndefined();
|
||||
expect(c1.entityByUid).toBeCalledTimes(1);
|
||||
expect(c2.entityByUid).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('logs an error if any source throws', async () => {
|
||||
c1.entityByUid.mockResolvedValueOnce(e1);
|
||||
c2.entityByUid.mockRejectedValueOnce(new Error('boo'));
|
||||
const catalog = new CoalescedEntitiesCatalog([c1, c2], logger);
|
||||
await expect(catalog.entityByUid('e2')).resolves.toBe(e1);
|
||||
expect(c1.entityByUid).toBeCalledTimes(1);
|
||||
expect(c2.entityByUid).toBeCalledTimes(1);
|
||||
expect(mockLogger.warn).toBeCalledWith(expect.stringMatching(/boo/));
|
||||
});
|
||||
});
|
||||
|
||||
describe('entityByName', () => {
|
||||
it('returns the first non-undefined result', async () => {
|
||||
c1.entityByName.mockResolvedValueOnce(undefined);
|
||||
c2.entityByName.mockResolvedValueOnce(e2);
|
||||
const catalog = new CoalescedEntitiesCatalog([c1, c2], logger);
|
||||
await expect(
|
||||
catalog.entityByName({
|
||||
kind: 'k',
|
||||
namespace: ENTITY_DEFAULT_NAMESPACE,
|
||||
name: 'n2',
|
||||
}),
|
||||
).resolves.toBe(e2);
|
||||
expect(c1.entityByName).toBeCalledTimes(1);
|
||||
expect(c2.entityByName).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns undefined if all results were undefined', async () => {
|
||||
c1.entityByName.mockResolvedValueOnce(undefined);
|
||||
c2.entityByName.mockResolvedValueOnce(undefined);
|
||||
const catalog = new CoalescedEntitiesCatalog([c1, c2], logger);
|
||||
await expect(
|
||||
catalog.entityByName({
|
||||
kind: 'k',
|
||||
namespace: ENTITY_DEFAULT_NAMESPACE,
|
||||
name: 'n2',
|
||||
}),
|
||||
).resolves.toBeUndefined();
|
||||
expect(c1.entityByName).toBeCalledTimes(1);
|
||||
expect(c2.entityByName).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('logs an error if any source throws', async () => {
|
||||
c1.entityByName.mockResolvedValueOnce(e1);
|
||||
c2.entityByName.mockRejectedValueOnce(new Error('boo'));
|
||||
const catalog = new CoalescedEntitiesCatalog([c1, c2], logger);
|
||||
await expect(
|
||||
catalog.entityByName({
|
||||
kind: 'k',
|
||||
namespace: ENTITY_DEFAULT_NAMESPACE,
|
||||
name: 'n2',
|
||||
}),
|
||||
).resolves.toBe(e1);
|
||||
expect(c1.entityByName).toBeCalledTimes(1);
|
||||
expect(c2.entityByName).toBeCalledTimes(1);
|
||||
expect(mockLogger.warn).toBeCalledWith(expect.stringMatching(/boo/));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity, EntityName } from '@backstage/catalog-model';
|
||||
import { Logger } from 'winston';
|
||||
import { EntityFilters } from '../database';
|
||||
import { EntitiesCatalog } from './types';
|
||||
|
||||
/**
|
||||
* A simple coalescing catalog wrapper, that acts as a front for collecting
|
||||
* catalog data from multiple sources.
|
||||
*
|
||||
* One possible usage could be to have this as a front to both a
|
||||
* DatabaseEntitiesCatalog that holds Component kinds, and another company-
|
||||
* specific catalog that is a thin wrapper on top of LDAP that supplies Group
|
||||
* and User entities. That way you'll get a coherent view of two very different
|
||||
* entity sources.
|
||||
*
|
||||
* This is mainly meant as a functional example, and you may want to provide
|
||||
* your own more specialized collector if you have this distinct need. This
|
||||
* one does not support adding/updating entities through the API for example.
|
||||
* A more competent implementation may direct the writes to different catalogs
|
||||
* based on entity kind or similar.
|
||||
*/
|
||||
export class CoalescedEntitiesCatalog implements EntitiesCatalog {
|
||||
private inner: EntitiesCatalog[];
|
||||
private logger: Logger;
|
||||
|
||||
constructor(inner: EntitiesCatalog[], logger: Logger) {
|
||||
this.inner = inner;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
async entities(filters?: EntityFilters): Promise<Entity[]> {
|
||||
const ops = this.inner.map(async catalog => {
|
||||
try {
|
||||
return await catalog.entities(filters);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Inner entities call failed, ${e}`);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(ops);
|
||||
return results.flat();
|
||||
}
|
||||
|
||||
async entityByUid(uid: string): Promise<Entity | undefined> {
|
||||
const ops = this.inner.map(async catalog => {
|
||||
try {
|
||||
return await catalog.entityByUid(uid);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Inner entityByUid call failed, ${e}`);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(ops);
|
||||
return results.find(Boolean);
|
||||
}
|
||||
|
||||
async entityByName(name: EntityName): Promise<Entity | undefined> {
|
||||
const ops = this.inner.map(async catalog => {
|
||||
try {
|
||||
return await catalog.entityByName(name);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Inner entityByName call failed, ${e}`);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(ops);
|
||||
return results.find(Boolean);
|
||||
}
|
||||
|
||||
addOrUpdateEntity(): Promise<Entity> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
addEntities(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
removeEntityByUid(): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
@@ -24,13 +24,12 @@ describe('DatabaseEntitiesCatalog', () => {
|
||||
beforeAll(() => {
|
||||
db = {
|
||||
transaction: jest.fn(),
|
||||
addEntity: jest.fn(),
|
||||
addEntities: jest.fn(),
|
||||
updateEntity: jest.fn(),
|
||||
entities: jest.fn(),
|
||||
entityByName: jest.fn(),
|
||||
entityByUid: jest.fn(),
|
||||
removeEntity: jest.fn(),
|
||||
removeEntityByUid: jest.fn(),
|
||||
addLocation: jest.fn(),
|
||||
removeLocation: jest.fn(),
|
||||
location: jest.fn(),
|
||||
@@ -57,7 +56,7 @@ describe('DatabaseEntitiesCatalog', () => {
|
||||
};
|
||||
|
||||
db.entities.mockResolvedValue([]);
|
||||
db.addEntity.mockResolvedValue({ entity });
|
||||
db.addEntities.mockResolvedValue([{ entity }]);
|
||||
|
||||
const catalog = new DatabaseEntitiesCatalog(db);
|
||||
const result = await catalog.addOrUpdateEntity(entity);
|
||||
@@ -68,7 +67,7 @@ describe('DatabaseEntitiesCatalog', () => {
|
||||
namespace: 'd',
|
||||
name: 'c',
|
||||
});
|
||||
expect(db.addEntity).toHaveBeenCalledTimes(1);
|
||||
expect(db.addEntities).toHaveBeenCalledTimes(1);
|
||||
expect(result).toBe(entity);
|
||||
});
|
||||
|
||||
|
||||
@@ -72,7 +72,10 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog {
|
||||
existing.entity.metadata.generation,
|
||||
);
|
||||
} else {
|
||||
response = await this.database.addEntity(tx, { locationId, entity });
|
||||
const added = await this.database.addEntities(tx, [
|
||||
{ locationId, entity },
|
||||
]);
|
||||
response = added[0];
|
||||
}
|
||||
|
||||
return response.entity;
|
||||
@@ -105,7 +108,10 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog {
|
||||
])
|
||||
: [entityResponse];
|
||||
for (const dbResponse of colocatedEntities) {
|
||||
await this.database.removeEntity(tx, dbResponse?.entity.metadata.uid!);
|
||||
await this.database.removeEntityByUid(
|
||||
tx,
|
||||
dbResponse?.entity.metadata.uid!,
|
||||
);
|
||||
}
|
||||
|
||||
if (entityResponse.locationId) {
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity, EntityName, getEntityName } from '@backstage/catalog-model';
|
||||
import lodash from 'lodash';
|
||||
import type { EntitiesCatalog } from './types';
|
||||
|
||||
export class StaticEntitiesCatalog implements EntitiesCatalog {
|
||||
private _entities: Entity[];
|
||||
|
||||
constructor(entities: Entity[]) {
|
||||
this._entities = entities;
|
||||
}
|
||||
|
||||
async entities(): Promise<Entity[]> {
|
||||
return lodash.cloneDeep(this._entities);
|
||||
}
|
||||
|
||||
async entityByUid(uid: string): Promise<Entity | undefined> {
|
||||
const item = this._entities.find(e => uid === e.metadata.uid);
|
||||
return item ? lodash.cloneDeep(item) : undefined;
|
||||
}
|
||||
|
||||
async entityByName(name: EntityName): Promise<Entity | undefined> {
|
||||
const item = this._entities.find(e => {
|
||||
const candidate = getEntityName(e);
|
||||
return (
|
||||
name.kind.toLowerCase() === candidate.kind.toLowerCase() &&
|
||||
name.namespace.toLowerCase() === candidate.namespace.toLowerCase() &&
|
||||
name.name.toLowerCase() === candidate.name.toLowerCase()
|
||||
);
|
||||
});
|
||||
return item ? lodash.cloneDeep(item) : undefined;
|
||||
}
|
||||
|
||||
async addOrUpdateEntity(): Promise<Entity> {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
|
||||
async addEntities(): Promise<void> {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
|
||||
async removeEntityByUid(): Promise<void> {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
}
|
||||
@@ -16,5 +16,4 @@
|
||||
|
||||
export { DatabaseEntitiesCatalog } from './DatabaseEntitiesCatalog';
|
||||
export { DatabaseLocationsCatalog } from './DatabaseLocationsCatalog';
|
||||
export { StaticEntitiesCatalog } from './StaticEntitiesCatalog';
|
||||
export type { EntitiesCatalog, LocationsCatalog } from './types';
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
import { ConflictError } from '@backstage/backend-common';
|
||||
import type { Entity, Location } from '@backstage/catalog-model';
|
||||
import { DatabaseManager } from './DatabaseManager';
|
||||
import { Database, DatabaseLocationUpdateLogStatus } from './types';
|
||||
import type {
|
||||
DbEntityRequest,
|
||||
DbEntityResponse,
|
||||
DbLocationsRowWithStatus,
|
||||
} from './types';
|
||||
import { Database, DatabaseLocationUpdateLogStatus } from './types';
|
||||
|
||||
const bootstrapLocation = {
|
||||
id: expect.any(String),
|
||||
@@ -135,43 +135,12 @@ describe('CommonDatabase', () => {
|
||||
await expect(db.location(location.id)).rejects.toThrow(/Found no location/);
|
||||
});
|
||||
|
||||
describe('addEntity', () => {
|
||||
it('happy path: adds entity to empty database', async () => {
|
||||
const added = await db.transaction(tx => db.addEntity(tx, entityRequest));
|
||||
expect(added).toStrictEqual(entityResponse);
|
||||
expect(added.entity.metadata.generation).toBe(1);
|
||||
});
|
||||
|
||||
it('rejects adding the same-named entity twice', async () => {
|
||||
await db.transaction(tx => db.addEntity(tx, entityRequest));
|
||||
await expect(
|
||||
db.transaction(tx => db.addEntity(tx, entityRequest)),
|
||||
).rejects.toThrow(ConflictError);
|
||||
});
|
||||
|
||||
it('rejects adding the almost-same-namespace entity twice', async () => {
|
||||
entityRequest.entity.metadata.namespace = undefined;
|
||||
await db.transaction(tx => db.addEntity(tx, entityRequest));
|
||||
entityRequest.entity.metadata.namespace = '';
|
||||
await expect(
|
||||
db.transaction(tx => db.addEntity(tx, entityRequest)),
|
||||
).rejects.toThrow(ConflictError);
|
||||
});
|
||||
|
||||
it('accepts adding the same-named entity twice if on different namespaces', async () => {
|
||||
entityRequest.entity.metadata.namespace = 'namespace1';
|
||||
await db.transaction(tx => db.addEntity(tx, entityRequest));
|
||||
entityRequest.entity.metadata.namespace = 'namespace2';
|
||||
await expect(
|
||||
db.transaction(tx => db.addEntity(tx, entityRequest)),
|
||||
).resolves.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('addEntities', () => {
|
||||
it('happy path: adds entities to empty database', async () => {
|
||||
await db.transaction(tx => db.addEntities(tx, [entityRequest]));
|
||||
expect(true).toBeTruthy();
|
||||
const result = await db.transaction(tx =>
|
||||
db.addEntities(tx, [entityRequest]),
|
||||
);
|
||||
expect(result).toEqual([entityResponse]);
|
||||
});
|
||||
|
||||
it('rejects adding the same-named entity twice', async () => {
|
||||
@@ -237,7 +206,28 @@ describe('CommonDatabase', () => {
|
||||
];
|
||||
await expect(
|
||||
db.transaction(tx => db.addEntities(tx, req)),
|
||||
).resolves.toBeUndefined();
|
||||
).resolves.toEqual([
|
||||
{
|
||||
entity: expect.objectContaining({
|
||||
metadata: expect.objectContaining({
|
||||
namespace: 'ns1',
|
||||
uid: expect.any(String),
|
||||
etag: expect.any(String),
|
||||
generation: expect.any(Number),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
entity: expect.objectContaining({
|
||||
metadata: expect.objectContaining({
|
||||
namespace: 'ns2',
|
||||
uid: expect.any(String),
|
||||
etag: expect.any(String),
|
||||
generation: expect.any(Number),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -289,7 +279,9 @@ describe('CommonDatabase', () => {
|
||||
|
||||
describe('updateEntity', () => {
|
||||
it('can read and no-op-update an entity', async () => {
|
||||
const added = await db.transaction(tx => db.addEntity(tx, entityRequest));
|
||||
const [added] = await db.transaction(tx =>
|
||||
db.addEntities(tx, [entityRequest]),
|
||||
);
|
||||
const updated = await db.transaction(tx =>
|
||||
db.updateEntity(tx, { entity: added.entity }),
|
||||
);
|
||||
@@ -306,7 +298,9 @@ describe('CommonDatabase', () => {
|
||||
});
|
||||
|
||||
it('can update name if uid matches', async () => {
|
||||
const added = await db.transaction(tx => db.addEntity(tx, entityRequest));
|
||||
const [added] = await db.transaction(tx =>
|
||||
db.addEntities(tx, [entityRequest]),
|
||||
);
|
||||
added.entity.metadata.name! = 'new!';
|
||||
const updated = await db.transaction(tx =>
|
||||
db.updateEntity(tx, { entity: added.entity }),
|
||||
@@ -315,7 +309,9 @@ describe('CommonDatabase', () => {
|
||||
});
|
||||
|
||||
it('fails to update an entity if etag does not match', async () => {
|
||||
const added = await db.transaction(tx => db.addEntity(tx, entityRequest));
|
||||
const [added] = await db.transaction(tx =>
|
||||
db.addEntities(tx, [entityRequest]),
|
||||
);
|
||||
await expect(
|
||||
db.transaction(tx =>
|
||||
db.updateEntity(tx, { entity: added.entity }, 'garbage'),
|
||||
@@ -324,7 +320,9 @@ describe('CommonDatabase', () => {
|
||||
});
|
||||
|
||||
it('fails to update an entity if generation does not match', async () => {
|
||||
const added = await db.transaction(tx => db.addEntity(tx, entityRequest));
|
||||
const [added] = await db.transaction(tx =>
|
||||
db.addEntities(tx, [entityRequest]),
|
||||
);
|
||||
await expect(
|
||||
db.transaction(tx =>
|
||||
db.updateEntity(tx, { entity: added.entity }, undefined, 1e20),
|
||||
@@ -347,8 +345,7 @@ describe('CommonDatabase', () => {
|
||||
spec: { c: null },
|
||||
};
|
||||
await db.transaction(async tx => {
|
||||
await db.addEntity(tx, { entity: e1 });
|
||||
await db.addEntity(tx, { entity: e2 });
|
||||
await db.addEntities(tx, [{ entity: e1 }, { entity: e2 }]);
|
||||
});
|
||||
const result = await db.transaction(async tx => db.entities(tx, []));
|
||||
expect(result.length).toEqual(2);
|
||||
@@ -384,9 +381,10 @@ describe('CommonDatabase', () => {
|
||||
];
|
||||
|
||||
await db.transaction(async tx => {
|
||||
for (const entity of entities) {
|
||||
await db.addEntity(tx, { entity });
|
||||
}
|
||||
await db.addEntities(
|
||||
tx,
|
||||
entities.map(entity => ({ entity })),
|
||||
);
|
||||
});
|
||||
|
||||
await expect(
|
||||
@@ -422,9 +420,10 @@ describe('CommonDatabase', () => {
|
||||
];
|
||||
|
||||
await db.transaction(async tx => {
|
||||
for (const entity of entities) {
|
||||
await db.addEntity(tx, { entity });
|
||||
}
|
||||
await db.addEntities(
|
||||
tx,
|
||||
entities.map(entity => ({ entity })),
|
||||
);
|
||||
});
|
||||
|
||||
const rows = await db.transaction(async tx =>
|
||||
@@ -471,9 +470,10 @@ describe('CommonDatabase', () => {
|
||||
];
|
||||
|
||||
await db.transaction(async tx => {
|
||||
for (const entity of entities) {
|
||||
await db.addEntity(tx, { entity });
|
||||
}
|
||||
await db.addEntities(
|
||||
tx,
|
||||
entities.map(entity => ({ entity })),
|
||||
);
|
||||
});
|
||||
|
||||
const rows = await db.transaction(async tx =>
|
||||
@@ -519,9 +519,10 @@ describe('CommonDatabase', () => {
|
||||
];
|
||||
|
||||
await db.transaction(async tx => {
|
||||
for (const entity of entities) {
|
||||
await db.addEntity(tx, { entity });
|
||||
}
|
||||
await db.addEntities(
|
||||
tx,
|
||||
entities.map(entity => ({ entity })),
|
||||
);
|
||||
});
|
||||
|
||||
const e1 = await db.transaction(async tx =>
|
||||
|
||||
@@ -109,9 +109,10 @@ export class CommonDatabase implements Database {
|
||||
async addEntities(
|
||||
txOpaque: unknown,
|
||||
request: DbEntityRequest[],
|
||||
): Promise<void> {
|
||||
): Promise<DbEntityResponse[]> {
|
||||
const tx = txOpaque as Knex.Transaction<any, any>;
|
||||
|
||||
const result: DbEntityResponse[] = [];
|
||||
const entityRows: DbEntitiesRow[] = [];
|
||||
const searchRows: DbEntitiesSearchRow[] = [];
|
||||
|
||||
@@ -134,6 +135,7 @@ export class CommonDatabase implements Database {
|
||||
},
|
||||
};
|
||||
|
||||
result.push({ entity: newEntity, locationId });
|
||||
entityRows.push(this.toEntityRow(locationId, newEntity));
|
||||
searchRows.push(...buildEntitySearch(newEntity.metadata.uid, newEntity));
|
||||
}
|
||||
@@ -146,6 +148,8 @@ export class CommonDatabase implements Database {
|
||||
)
|
||||
.del();
|
||||
await tx.batchInsert('entities_search', searchRows, BATCH_SIZE);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async updateEntity(
|
||||
@@ -315,7 +319,7 @@ export class CommonDatabase implements Database {
|
||||
return this.toEntityResponse(rows[0]);
|
||||
}
|
||||
|
||||
async removeEntity(txOpaque: unknown, uid: string): Promise<void> {
|
||||
async removeEntityByUid(txOpaque: unknown, uid: string): Promise<void> {
|
||||
const tx = txOpaque as Knex.Transaction<any, any>;
|
||||
|
||||
const result = await tx<DbEntitiesRow>('entities').where({ id: uid }).del();
|
||||
|
||||
@@ -88,22 +88,16 @@ export type Database = {
|
||||
*/
|
||||
transaction<T>(fn: (tx: unknown) => Promise<T>): Promise<T>;
|
||||
|
||||
/**
|
||||
* Adds a new entity to the catalog.
|
||||
*
|
||||
* @param tx An ongoing transaction
|
||||
* @param request The entity being added
|
||||
* @returns The added entity, with uid, etag and generation set
|
||||
*/
|
||||
addEntity(tx: unknown, request: DbEntityRequest): Promise<DbEntityResponse>;
|
||||
|
||||
/**
|
||||
* Adds a set of new entities to the catalog.
|
||||
*
|
||||
* @param tx An ongoing transaction
|
||||
* @param request The entities being added
|
||||
*/
|
||||
addEntities(tx: unknown, request: DbEntityRequest[]): Promise<void>;
|
||||
addEntities(
|
||||
tx: unknown,
|
||||
request: DbEntityRequest[],
|
||||
): Promise<DbEntityResponse[]>;
|
||||
|
||||
/**
|
||||
* Updates an existing entity in the catalog.
|
||||
@@ -140,7 +134,7 @@ export type Database = {
|
||||
|
||||
entityByUid(tx: unknown, uid: string): Promise<DbEntityResponse | undefined>;
|
||||
|
||||
removeEntity(tx: unknown, uid: string): Promise<void>;
|
||||
removeEntityByUid(tx: unknown, uid: string): Promise<void>;
|
||||
|
||||
addLocation(location: Location): Promise<DbLocationsRow>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user