@comake/skl-js-engine
Version:
Standard Knowledge Language Javascript Engine
291 lines • 15.7 kB
JavaScript
"use strict";
/* eslint-disable capitalized-comments */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SparqlQueryAdapter = void 0;
const logger_1 = require("../../../logger");
const PerformanceLogger_1 = require("../../../util/PerformanceLogger");
const SparqlUtil_1 = require("../../../util/SparqlUtil");
const TripleUtil_1 = require("../../../util/TripleUtil");
const InMemorySparqlQueryExecutor_1 = require("./query-executor/InMemorySparqlQueryExecutor");
const SparqlEndpointQueryExecutor_1 = require("./query-executor/SparqlEndpointQueryExecutor");
const SparqlQueryBuilder_1 = require("./SparqlQueryBuilder");
const SparqlUpdateBuilder_1 = require("./SparqlUpdateBuilder");
/**
* A {@link QueryAdapter} that stores data in a database through a sparql endpoint.
*/
class SparqlQueryAdapter {
constructor(options) {
this.setTimestamps = options.setTimestamps ?? false;
switch (options.type) {
case 'memory':
this.queryExecutor = new InMemorySparqlQueryExecutor_1.InMemorySparqlQueryExecutor();
break;
case 'sparql':
this.queryExecutor = new SparqlEndpointQueryExecutor_1.SparqlEndpointQueryExecutor(options);
break;
default:
throw new Error('No schema source found in setSchema args.');
}
this.logger = logger_1.Logger.getInstance();
}
async executeRawQuery(query) {
const response = await this.queryExecutor.executeSparqlSelectAndGetDataRaw(query);
if (response.length === 0) {
return [];
}
return (0, SparqlUtil_1.selectQueryResultsAsJSValues)(response);
}
async executeRawConstructQuery(query, frame) {
const response = await this.queryExecutor.executeSparqlConstructAndGetDataRaw(query);
if (response.length === 0) {
return { '@graph': [] };
}
return await (0, TripleUtil_1.triplesToJsonldWithFrame)(response, frame);
}
async executeRawUpdate(query) {
await this.queryExecutor.executeRawSparqlUpdate(query);
}
async find(options) {
return PerformanceLogger_1.PerformanceLogger.withSpan('Adapter.find', async () => {
const jsonld = await this.findAllAsJsonLd({ ...options, limit: 1 });
if (Array.isArray(jsonld) && !options?.skipFraming) {
if (jsonld.length === 0) {
return null;
}
if (jsonld.length === 1) {
return jsonld[0];
}
}
return jsonld;
}, { options });
}
async findBy(where) {
return PerformanceLogger_1.PerformanceLogger.withSpan('Adapter.findBy', async () => this.find({ where }), { where });
}
async findAll(options) {
return PerformanceLogger_1.PerformanceLogger.withSpan('Adapter.findAll', async () => {
const jsonld = await this.findAllAsJsonLd(options);
if (Array.isArray(jsonld)) {
return jsonld;
}
return [jsonld];
}, { options });
}
async findAllAsJsonLd(options) {
const queryBuilder = new SparqlQueryBuilder_1.SparqlQueryBuilder();
const { where, selectionTriples, entityOrder, rdfTypes } = await this.buildFindAllQueryData(queryBuilder, options);
if (entityOrder && entityOrder.length === 0) {
return [];
}
const queryData = queryBuilder.buildEntitySelectPatternsFromOptions(SparqlUtil_1.entityVariable, options);
const query = queryBuilder.buildConstructFromEntitySelectQuery(where, selectionTriples, options?.select, queryData.selectVariables);
return await this.executeEntitySelectQuery(query, options, entityOrder, rdfTypes);
}
async buildFindAllQueryData(queryBuilder, options) {
const queryData = queryBuilder.buildEntitySelectPatternsFromOptions(SparqlUtil_1.entityVariable, options);
const selectQueryData = queryBuilder.buildEntitySelectPatternsFromOptions(SparqlUtil_1.entityVariable, {
...options,
relations: undefined
});
let rdfTypes;
const wherePatterns = [...selectQueryData.where, ...selectQueryData.graphWhere];
wherePatterns.push({
type: 'bgp',
triples: [
{
subject: SparqlUtil_1.entityVariable,
predicate: SparqlUtil_1.rdfTypeNamedNode,
object: SparqlUtil_1.rdfTypeVariable
}
]
});
const entitySelectQuery = selectQueryData.where.length > 0
? (0, SparqlUtil_1.createSparqlSelectQuery)([
options?.entitySelectVariable ?? SparqlUtil_1.entityVariable,
SparqlUtil_1.rdfTypeVariable,
...selectQueryData.selectVariables?.map(({ variable, expression }) => {
if (!expression)
return variable;
return {
variable,
expression
};
}) ?? []
], wherePatterns, selectQueryData.orders, selectQueryData.group ?? options?.group, options?.limit, options?.offset)
: undefined;
let entityOrder;
/* If relations are present add them to where */
if ((queryData?.relationsQueryData?.unionPatterns ?? []).length > 0) {
queryData?.relationsQueryData?.unionPatterns.push((0, SparqlUtil_1.createSparqlGraphPattern)(SparqlUtil_1.entityVariable, [(0, SparqlUtil_1.createSparqlBasicGraphPattern)([SparqlUtil_1.entityGraphTriple])]));
}
if (queryData.orders.length > 0 && options?.limit !== 1 && entitySelectQuery) {
const entitySelectResponse = await this.queryExecutor.executeSparqlSelectAndGetData(entitySelectQuery);
const valuesByVariable = (0, SparqlUtil_1.groupSelectQueryResultsByKey)(entitySelectResponse);
entityOrder = (0, SparqlUtil_1.getEntityVariableValuesFromVariables)(valuesByVariable);
if (entityOrder.length === 0) {
return {
where: queryData.where,
selectionTriples: queryData.graphSelectionTriples,
entityOrder: []
};
}
const variableValueFilters = (0, SparqlUtil_1.createValuesPatternsForVariables)({
[SparqlUtil_1.entityVariable.value]: valuesByVariable[SparqlUtil_1.entityVariable.value]
});
queryData.graphWhere = [...variableValueFilters, ...queryData.graphWhere];
}
else if (entitySelectQuery) {
// We need entity IDs for framing when:
// 1. There are relations (to distinguish root entities from related entities)
// 2. There's a type constraint (to handle subclass matching where SPARQL finds subclasses but JSON-LD needs exact types)
const hasRelations = (queryData?.relationsQueryData?.unionPatterns ?? []).length > 0;
const hasTypeConstraint = options?.where?.type !== undefined;
if ((hasRelations || hasTypeConstraint) && queryData.orders.length > 0) {
const entitySelectResponse = await this.queryExecutor.executeSparqlSelectAndGetData(entitySelectQuery);
const valuesByVariable = (0, SparqlUtil_1.groupSelectQueryResultsByKey)(entitySelectResponse);
entityOrder = queryData.orders.length > 0 ? (0, SparqlUtil_1.getEntityVariableValuesFromVariables)(valuesByVariable) : [];
if (entityOrder.length === 0) {
return {
where: queryData.where,
selectionTriples: queryData.graphSelectionTriples,
entityOrder: []
};
}
}
else if (hasRelations || hasTypeConstraint) {
const entitySelectResponse = await this.queryExecutor.executeSparqlSelectAndGetData(entitySelectQuery);
const groupedResults = (0, SparqlUtil_1.groupSelectQueryResultsByKey)(entitySelectResponse);
const valuesByVariable = (0, SparqlUtil_1.getRdfTypeVariableValuesFromVariables)(groupedResults);
rdfTypes = [...new Set(valuesByVariable)];
// Also get entity IDs for framing to distinguish root entities from related ones
// entityOrder = getEntityVariableValuesFromVariables(groupedResults);
// if (entityOrder.length === 0) {
// return {
// where: queryData.where,
// selectionTriples: queryData.graphSelectionTriples,
// entityOrder: [],
// rdfTypes
// };
// }
}
// Always add the select group query to the CONSTRUCT
const entitySelectGroupQuery = (0, SparqlUtil_1.createSparqlSelectGroup)([entitySelectQuery]);
queryData.graphWhere.unshift(entitySelectGroupQuery);
// queryData.graphWhere = [ ...queryData.where, ...queryData.graphWhere ];
}
return {
where: queryData.graphWhere,
selectionTriples: queryData.graphSelectionTriples,
entityOrder,
rdfTypes
};
}
async executeEntitySelectQuery(query, options, entityOrder, rdfTypes) {
const responseTriples = await this.queryExecutor.executeSparqlSelectAndGetData(query);
return await (0, TripleUtil_1.triplesToJsonld)(responseTriples, options?.skipFraming, options?.relations, options?.where, entityOrder, rdfTypes);
}
async findAllBy(where) {
return PerformanceLogger_1.PerformanceLogger.withSpan('Adapter.findAllBy', async () => this.findAll({ where }), { where });
}
async exists(options) {
return PerformanceLogger_1.PerformanceLogger.withSpan('Adapter.exists', async () => {
const queryBuilder = new SparqlQueryBuilder_1.SparqlQueryBuilder();
const queryData = queryBuilder.buildEntitySelectPatternsFromOptions(SparqlUtil_1.entityVariable, options);
const values = queryData.graphWhere.filter((pattern) => pattern.type === 'values');
const query = (0, SparqlUtil_1.creteSparqlAskQuery)([...values, ...queryData.where]);
return await this.queryExecutor.executeAskQueryAndGetResponse(query);
}, { options });
}
async count(options) {
return PerformanceLogger_1.PerformanceLogger.withSpan('Adapter.count', async () => {
const queryBuilder = new SparqlQueryBuilder_1.SparqlQueryBuilder();
const queryData = queryBuilder.buildEntitySelectPatternsFromOptions(SparqlUtil_1.entityVariable, options);
const values = queryData.graphWhere.filter((pattern) => pattern.type === 'values');
const query = (0, SparqlUtil_1.createSparqlCountSelectQuery)(SparqlUtil_1.entityVariable, [...values, ...queryData.where], queryData.orders, options?.offset);
return await this.queryExecutor.executeSelectCountAndGetResponse(query);
}, { options });
}
async save(entityOrEntities) {
return PerformanceLogger_1.PerformanceLogger.withSpan('Adapter.save', async () => {
const queryBuilder = new SparqlUpdateBuilder_1.SparqlUpdateBuilder({ setTimestamps: this.setTimestamps });
const query = queryBuilder.buildUpdate(entityOrEntities);
await this.queryExecutor.executeSparqlUpdate(query);
return entityOrEntities;
}, { entityCount: Array.isArray(entityOrEntities) ? entityOrEntities.length : 1 });
}
async groupBy(options) {
return PerformanceLogger_1.PerformanceLogger.withSpan('Adapter.groupBy', async () => {
const queryBuilder = new SparqlQueryBuilder_1.SparqlQueryBuilder();
const { query: selectQuery, variableMapping } = await queryBuilder.buildGroupByQuery(options);
const results = await this.queryExecutor.executeSparqlSelectAndGetData(selectQuery);
// Create reverse mapping from path to variable name
const reverseMapping = Object.entries(variableMapping).reduce((acc, [varName, path]) => {
acc[path] = varName;
return acc;
}, {});
// Transform results
const groupResults = results.map(result => {
const group = {};
options.groupBy?.forEach(path => {
const varName = reverseMapping[path];
if (!varName) {
throw new Error(`No variable mapping found for path: ${path}`);
}
const { value } = result[varName];
// Try to convert to number if possible
group[path] = Number.isNaN(Number(value)) ? value : Number(value);
});
if (options.dateGrouping) {
const dateGroupVarName = reverseMapping.dateGroup;
group.dateGroup = result[dateGroupVarName].value;
}
const countVarName = reverseMapping.count;
const entityIdsVarName = reverseMapping.entityIds;
return {
group,
count: Number.parseInt(result[countVarName].value, 10),
entityIds: result[entityIdsVarName].value.split(' ')
};
});
return {
results: groupResults,
meta: {
totalCount: groupResults.reduce((sum, curr) => sum + curr.count, 0),
dateRange: options.dateRange,
groupings: options.groupBy || []
}
};
}, { options });
}
async update(idOrIds, attributes) {
return PerformanceLogger_1.PerformanceLogger.withSpan('Adapter.update', async () => {
const queryBuilder = new SparqlUpdateBuilder_1.SparqlUpdateBuilder({ setTimestamps: this.setTimestamps });
const query = queryBuilder.buildPartialUpdate(idOrIds, attributes);
await this.queryExecutor.executeSparqlUpdate(query);
}, { idCount: Array.isArray(idOrIds) ? idOrIds.length : 1 });
}
async delete(idOrIds) {
return PerformanceLogger_1.PerformanceLogger.withSpan('Adapter.delete', async () => {
const queryBuilder = new SparqlUpdateBuilder_1.SparqlUpdateBuilder();
const query = queryBuilder.buildDeleteById(idOrIds);
await this.queryExecutor.executeSparqlUpdate(query);
}, { idCount: Array.isArray(idOrIds) ? idOrIds.length : 1 });
}
async destroy(entityOrEntities) {
return PerformanceLogger_1.PerformanceLogger.withSpan('Adapter.destroy', async () => {
const queryBuilder = new SparqlUpdateBuilder_1.SparqlUpdateBuilder();
const query = queryBuilder.buildDelete(entityOrEntities);
await this.queryExecutor.executeSparqlUpdate(query);
return entityOrEntities;
}, { entityCount: Array.isArray(entityOrEntities) ? entityOrEntities.length : 1 });
}
async destroyAll() {
return PerformanceLogger_1.PerformanceLogger.withSpan('Adapter.destroyAll', async () => {
const queryBuilder = new SparqlUpdateBuilder_1.SparqlUpdateBuilder();
const query = queryBuilder.buildDeleteAll();
await this.queryExecutor.executeSparqlUpdate(query);
});
}
}
exports.SparqlQueryAdapter = SparqlQueryAdapter;
//# sourceMappingURL=SparqlQueryAdapter.js.map