@neo4j/graphql
Version:
A GraphQL to Cypher query execution layer for Neo4j and JavaScript GraphQL implementations
155 lines • 6.71 kB
JavaScript
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* 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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.assertIndexesAndConstraints = assertIndexesAndConstraints;
const debug_1 = __importDefault(require("debug"));
const constants_1 = require("../../constants");
const debug = (0, debug_1.default)(constants_1.DEBUG_EXECUTE);
async function assertIndexesAndConstraints({ driver, sessionConfig, schemaModel, }) {
await driver.verifyConnectivity();
const session = driver.session(sessionConfig);
try {
await checkIndexesAndConstraints({ schemaModel, session });
}
finally {
await session.close();
}
}
async function getExistingIndexes({ session }) {
const existingIndexes = {};
const indexesCypher = `
SHOW INDEXES YIELD
name AS name,
type AS type,
entityType AS entityType,
labelsOrTypes AS labelsOrTypes,
properties AS properties,
options AS options
`;
debug(`About to execute Cypher: ${indexesCypher}`);
const indexesResult = await session.run(indexesCypher);
indexesResult.records.forEach((record) => {
const index = record.toObject();
if ((index.type !== "FULLTEXT" && index.type !== "VECTOR") || index.entityType !== "NODE") {
return;
}
if (existingIndexes[index.name]) {
return;
}
existingIndexes[index.name] = {
labelsOrTypes: index.labelsOrTypes,
properties: index.properties,
options: index.options,
};
});
return existingIndexes;
}
function checkVectorIndexes(entity, existingIndexes, indexErrors) {
if (entity.annotations.vector) {
entity.annotations.vector.indexes.forEach((index) => {
const existingIndex = existingIndexes[index.indexName];
if (!existingIndex) {
indexErrors.push(`Missing @vector index '${index.indexName}' on Node '${entity.name}'`);
return;
}
const propertyIsInIndex = existingIndex.properties.some((p) => p === index.embeddingProperty);
if (!propertyIsInIndex) {
indexErrors.push(`@vector index '${index.indexName}' on Node '${entity.name}' is missing embedding property '${index.embeddingProperty}'`);
}
if (!existingIndex.options) {
indexErrors.push(`@vector index '${index.indexName}' on Node '${entity.name}' is missing options`);
return;
}
const indexConfig = existingIndex.options["indexConfig"];
if (!indexConfig) {
indexErrors.push(`@vector index '${index.indexName}' on Node '${entity.name}' is missing indexConfig`);
return;
}
});
}
}
async function checkIndexesAndConstraints({ schemaModel, session, }) {
const missingConstraints = await getMissingConstraints({ session });
if (missingConstraints.length) {
const missingConstraintMessages = missingConstraints.map((constraint) => `Missing constraint for ${constraint.label}.${constraint.property}`);
throw new Error(missingConstraintMessages.join("\n"));
}
debug("Successfully checked for the existence of all necessary constraints");
const existingIndexes = await getExistingIndexes({ session });
const indexErrors = [];
for (const entity of schemaModel.concreteEntities) {
if (entity.annotations.fulltext) {
entity.annotations.fulltext.indexes.forEach((index) => {
const indexName = index.indexName;
const existingIndex = existingIndexes[indexName];
if (!existingIndex) {
indexErrors.push(`Missing @fulltext index '${indexName}' on Node '${entity.name}'`);
return;
}
// An index with the same name already exists, so we check that all index fields are included in the existing index
index.fields.forEach((field) => {
const attribute = entity.findAttribute(field);
if (!attribute) {
throw new Error(`Attribute '${field}' not found in entity '${entity.name}'`);
}
const propertyIsInIndex = existingIndex.properties.some((p) => p === attribute.databaseName);
if (!propertyIsInIndex) {
const aliasError = attribute.databaseName !== attribute.name
? ` aliased to field '${attribute.databaseName}'`
: "";
indexErrors.push(`@fulltext index '${indexName}' on Node '${entity.name}' is missing field '${field}'${aliasError}`);
}
});
});
}
checkVectorIndexes(entity, existingIndexes, indexErrors);
}
if (indexErrors.length) {
throw new Error(indexErrors.join("\n"));
}
debug("Successfully checked for the existence of all necessary indexes");
}
async function getMissingConstraints({ session }) {
const existingConstraints = {};
const constraintsCypher = "SHOW UNIQUE CONSTRAINTS";
debug(`About to execute Cypher: ${constraintsCypher}`);
const constraintsResult = await session.run(constraintsCypher);
constraintsResult.records
.map((record) => {
return record.toObject();
})
.forEach((constraint) => {
const label = constraint.labelsOrTypes[0];
const property = constraint.properties[0];
const existingConstraint = existingConstraints[label];
if (existingConstraint) {
existingConstraint.push(property);
}
else {
existingConstraints[label] = [property];
}
});
const missingConstraints = [];
return missingConstraints;
}
//# sourceMappingURL=asserts-indexes-and-constraints.js.map
;