chore(catalog-backend): use code instead of db to implement similarity rule

This commit is contained in:
Fredrik Adelöw
2020-06-12 13:41:05 +02:00
parent 7de5e10408
commit 66fef7f2e4
3 changed files with 48 additions and 60 deletions
@@ -61,7 +61,6 @@ function serializeSpec(spec: Entity['spec']): DbEntitiesRow['spec'] {
function toEntityRow(
locationId: string | undefined,
entity: Entity,
normalize: (value: string) => string,
): DbEntitiesRow {
return {
id: entity.metadata.uid!,
@@ -72,9 +71,6 @@ function toEntityRow(
kind: entity.kind,
name: entity.metadata.name,
namespace: entity.metadata.namespace || null,
kind_normalized: normalize(entity.kind),
name_normalized: normalize(entity.metadata.name),
namespace_normalized: normalize(entity.metadata.namespace || ''),
metadata: serializeMetadata(entity.metadata),
spec: serializeSpec(entity.spec),
};
@@ -162,6 +158,8 @@ export class CommonDatabase implements Database {
throw new InputError('May not specify generation for new entities');
}
await this.ensureNoSimilarNames(tx, request.entity);
const newEntity = lodash.cloneDeep(request.entity);
newEntity.metadata = {
...newEntity.metadata,
@@ -170,7 +168,7 @@ export class CommonDatabase implements Database {
generation: 1,
};
const newRow = toEntityRow(request.locationId, newEntity, this.normalize);
const newRow = toEntityRow(request.locationId, newEntity);
await tx<DbEntitiesRow>('entities').insert(newRow);
await this.updateEntitiesSearch(tx, newRow.id, newEntity);
@@ -259,9 +257,11 @@ export class CommonDatabase implements Database {
}
}
await this.ensureNoSimilarNames(tx, newEntity);
// Store the updated entity; select on the old etag to ensure that we do
// not lose to another writer
const newRow = toEntityRow(request.locationId, newEntity, this.normalize);
const newRow = toEntityRow(request.locationId, newEntity);
const updatedRows = await tx<DbEntitiesRow>('entities')
.where({ id: oldRow.id, etag: oldRow.etag })
.update(newRow);
@@ -451,4 +451,46 @@ export class CommonDatabase implements Database {
// we got around to writing the entries
}
}
private async ensureNoSimilarNames(
tx: Knex.Transaction<any, any>,
data: Entity,
): Promise<void> {
const newKind = data.kind;
const newName = data.metadata.name;
const newNamespace = data.metadata.namespace;
const newKindNorm = this.normalize(newKind);
const newNameNorm = this.normalize(newName);
const newNamespaceNorm = this.normalize(newNamespace || '');
for (const item of await this.entities(tx)) {
if (data.metadata.uid === item.entity.metadata.uid) {
continue;
}
const oldKind = item.entity.kind;
const oldName = item.entity.metadata.name;
const oldNamespace = item.entity.metadata.namespace;
const oldKindNorm = this.normalize(oldKind);
const oldNameNorm = this.normalize(oldName);
const oldNamespaceNorm = this.normalize(oldNamespace || '');
if (
oldKindNorm === newKindNorm &&
oldNameNorm === newNameNorm &&
oldNamespaceNorm === newNamespaceNorm
) {
// Only throw if things were actually different - for completely equal
// things, we let the database handle the conflict
if (
oldKind !== newKind ||
oldName !== newName ||
oldNamespace !== newNamespace
) {
const message = `Kind, namespace, name are too similar to an existing entity`;
throw new ConflictError(message);
}
}
}
}
}
@@ -1,51 +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 * as Knex from 'knex';
export async function up(knex: Knex): Promise<any> {
return knex.schema.alterTable('entities', table => {
// these are added as nullable because sqlite does not support alter column
table
.string('kind_normalized')
.nullable()
.comment('The kind field of the entity, normalized');
table
.string('name_normalized')
.nullable()
.comment('The metadata.name field of the entity, normalized');
table
.string('namespace_normalized')
.nullable()
.comment('The metadata.namespace field of the entity, normalized');
table.dropUnique([], 'entities_unique_name');
table.unique(
['kind_normalized', 'name_normalized', 'namespace_normalized'],
'entities_unique_name_normalized',
);
});
}
export async function down(knex: Knex): Promise<any> {
return knex.schema.alterTable('entities', table => {
table.dropUnique([], 'entities_unique_name_normalized');
table.unique(['kind', 'name', 'namespace'], 'entities_unique_name');
table.dropColumns(
'kind_normalized',
'name_normalized',
'namespace_normalized',
);
});
}
@@ -23,9 +23,6 @@ export type DbEntitiesRow = {
kind: string;
name: string | null;
namespace: string | null;
kind_normalized: string;
name_normalized: string;
namespace_normalized: string;
etag: string;
generation: number;
metadata: string;