UNPKG

nosql-constraints

Version:

Helpers to manage constrants (i.e. cascade delete) in a NoSQL database

198 lines 9.51 kB
import { DiGraph } from '../digraph'; export class ConstraintFactory { #containerSchemaAdapters = new Map(); #containerSchemaChunks = new Map(); #vertexProperties = new Map(); #edgeProperties = new Map(); #constraintGraph = new DiGraph(); addDocumentSchema(containerId, schema) { let adapters = this.#containerSchemaAdapters.get(containerId); if (!adapters) { adapters = []; this.#containerSchemaAdapters.set(containerId, adapters); } adapters.push(schema); let chunks = this.#containerSchemaChunks.get(containerId); if (!chunks) { chunks = []; this.#containerSchemaChunks.set(containerId, chunks); } const newChunks = schema.extractChunks(); chunks.push(...newChunks); } findDocumentSchemaChunks(docRef) { const { containerId, refDocType } = docRef; const chunks = this.#containerSchemaChunks.get(containerId); if (!chunks || chunks.length === 0) { throw new Error(`Missing schema for container ${containerId}`); } if (!refDocType || Object.keys(refDocType).length === 0) { return chunks; } const result = []; for (const chunk of chunks) { const foundRefDocType = {}; // Initialze foundRefDocType -> all properties are false for (const key of Object.keys(refDocType)) { foundRefDocType[key] = false; } for (const [refProperty, refValue] of Object.entries(refDocType)) { const schemaProperty = Object.entries(chunk.properties ?? {}).find(([property, _chunks]) => { if (property !== refProperty) return false; // So the name matches, now we need to check the type // The refValue is the concrete value // the _chunks is the schema, describing the property, it can be a literal, union of literals, or a just a type // So we check if literal matches the refValue, or if the type matches the type of refValue for (const _chunk of _chunks) { if (_chunk.type === 'literal') { if (_chunk.value === refValue) { return true; } } else if (_chunk.type === typeof refValue) { return true; } } }); if (!schemaProperty) { break; } foundRefDocType[refProperty] = true; } // Check if all properties are found const allFound = Object.values(foundRefDocType).every((value) => value); if (allFound) { result.push(chunk); } } return result; } findPartitionSchemaChunks(partitionRef) { const { containerId } = partitionRef; const chunks = this.#containerSchemaChunks.get(containerId); if (!chunks || chunks.length === 0) { throw new Error(`Missing schema for container ${containerId}`); } if (!partitionRef.partitionKeyProperties || partitionRef.partitionKeyProperties.length === 0) { return chunks; } const result = []; // partitionKeys are the properties that need to be present in the schema // they can be . separated paths for (const partitionKeyProperty of partitionRef.partitionKeyProperties) { const partitionChunks = this.findChunksForProperty(chunks, partitionKeyProperty); if (!partitionChunks || partitionChunks.length === 0) { break; } result.push(...partitionChunks); } return result; } validateDocumentReference(docRef) { // Check that vertex has schema const chunks = this.findDocumentSchemaChunks(docRef); if (!chunks || chunks.length === 0) { throw new Error(`Missing schema for container ${docRef.containerId} and refDocType ${JSON.stringify(docRef.refDocType)}`); } } findChunksForProperty(chunks, propertyPath) { // We need to drill down the property path // and find the chunks that match the whole path const path = propertyPath.split('.'); let foundChunks = []; let currentChunks = chunks; for (const property of path) { foundChunks = []; for (const chunk of currentChunks) { if (!chunk.properties) { continue; } const propertyChunks = chunk.properties[property]; if (!propertyChunks) { continue; } foundChunks.push(...propertyChunks); } if (foundChunks.length === 0) { return undefined; } currentChunks = foundChunks; } return foundChunks; } validateDoc2DocConstraint(referencing, constraint, referenced) { // Check that all refProperties are present in the referencing schema // At least one chunk should have all refProperties const referencingChunks = this.findDocumentSchemaChunks(referencing); const referencedChunks = this.findDocumentSchemaChunks(referenced); // Ref properties can be . separated paths for (const refProperty of Object.entries(constraint.refProperties)) { const [referencingProperty, referencedProperty] = refProperty; const foundReferencingChunks = this.findChunksForProperty(referencingChunks, referencingProperty); if (!foundReferencingChunks || foundReferencingChunks.length === 0) { throw new Error(`Failed to validate referencing constraint ${referencing.containerId}/${JSON.stringify(referencing.refDocType)}/${referencingProperty}: property not found`); } const foundReferencedChunks = this.findChunksForProperty(referencedChunks, referencedProperty); if (!foundReferencedChunks || foundReferencedChunks.length === 0) { throw new Error(`Failed to validate referenced constraint ${referenced.containerId}/${JSON.stringify(referenced.refDocType)}/${referencedProperty}: property not found`); } } } addDocument2DocumentConstraint(referencing, constraint, referenced) { // Validate first this.validateDocumentReference(referencing); this.validateDocumentReference(referenced); this.validateDoc2DocConstraint(referencing, constraint, referenced); // referenced = from, referencing = to const from = `${referenced.containerId}/${JSON.stringify(referenced.refDocType)}`; this.#constraintGraph.addVertex({ id: from }); this.#vertexProperties.set(from, referenced); const to = `${referencing.containerId}/${JSON.stringify(referencing.refDocType)}`; this.#constraintGraph.addVertex({ id: to }); this.#vertexProperties.set(to, referencing); // Check that the edge does not already exist const edgeId = `${from} -> ${JSON.stringify(constraint.refProperties)} -> ${to}`; if (this.#edgeProperties.has(edgeId)) { throw new Error(`Edge ${edgeId} already exists`); } this.#constraintGraph.addEdge({ from, to }); this.#edgeProperties.set(edgeId, constraint); } validatePartitionReference(partitionRef) { // Check that vertex has schema const chunks = this.findPartitionSchemaChunks(partitionRef); if (!chunks || chunks.length === 0) { throw new Error(`Missing schema for container ${partitionRef.containerId}`); } } validatePartition2DocConstraint(referencing, constraint, referenced) { // Check that all partitionKeyProperties are present in the referencing schema // At least one chunk should have all partitionKeyProperties // eslint-disable-next-line @typescript-eslint/no-unused-vars const referencingChunks = this.findPartitionSchemaChunks(referencing); // eslint-disable-next-line @typescript-eslint/no-unused-vars const referencedChunks = this.findDocumentSchemaChunks(referenced); } addPartition2DocumentConstraint(referencing, constraint, referenced) { // Validate first this.validatePartitionReference(referencing); this.validateDocumentReference(referenced); this.validatePartition2DocConstraint(referencing, constraint, referenced); // referenced = from, referencing = to const from = `${referenced.containerId}`; this.#constraintGraph.addVertex({ id: from }); this.#vertexProperties.set(from, referenced); const to = `${referencing.containerId}/partition`; this.#constraintGraph.addVertex({ id: to }); this.#vertexProperties.set(to, referencing); // Check that the edge does not already exist const edgeId = `${from} -> ${to}`; if (this.#edgeProperties.has(edgeId)) { throw new Error(`Edge ${edgeId} already exists`); } this.#constraintGraph.addEdge({ from, to }); this.#edgeProperties.set(edgeId, constraint); } } //# sourceMappingURL=factory.js.map