@comake/skl-js-engine
Version:
Standard Knowledge Language Javascript Engine
1,042 lines (1,041 loc) • 53 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SparqlQueryBuilder = void 0;
/* eslint-disable no-inline-comments */
/* eslint-disable line-comment-position */
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/no-shadow */
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable multiline-comment-style */
/* eslint-disable capitalized-comments */
/* eslint-disable indent */
const data_model_1 = __importDefault(require("@rdfjs/data-model"));
const constants_1 = require("../../../constants");
const PerformanceLogger_1 = require("../../../util/PerformanceLogger");
const SparqlUtil_1 = require("../../../util/SparqlUtil");
const TripleUtil_1 = require("../../../util/TripleUtil");
const Util_1 = require("../../../util/Util");
const Vocabularies_1 = require("../../../util/Vocabularies");
const FindOperator_1 = require("../../FindOperator");
const VariableGenerator_1 = require("./VariableGenerator");
class SparqlQueryBuilder {
constructor() {
this.variableGenerator = new VariableGenerator_1.VariableGenerator();
}
buildEntitySelectPatternsFromOptions(subject, options) {
const span = PerformanceLogger_1.PerformanceLogger.startSpan('QueryBuilder.buildSelect', {
hasWhere: !!options?.where,
hasRelations: !!options?.relations
});
try {
const relations = options?.select ? undefined : options?.relations;
const whereQueryData = this.createWhereQueryData(subject, options?.where, true);
const orderQueryData = this.createOrderQueryData(subject, options?.order);
const relationsQueryData = this.createRelationsQueryData(subject, relations);
// Handle subqueries
if (options?.subQueries && options.subQueries.length > 0) {
const subQueryPatterns = this.createSubQueryPatterns(options.subQueries);
whereQueryData.values.unshift(...subQueryPatterns);
}
const patterns = whereQueryData.values;
if (whereQueryData.triples.length === 0 &&
(whereQueryData.filters.length > 0 ||
orderQueryData.triples.length > 0 ||
(whereQueryData.values.length === 0 &&
whereQueryData.graphValues.length === 0 &&
whereQueryData.graphTriples.length === 0))) {
if (relationsQueryData.unionPatterns.length > 0) {
/* relationsQueryData.unionPatterns.push(
createSparqlGraphPattern(subject, [ createSparqlBasicGraphPattern([ entityGraphTriple ]) ])
); */
}
else {
const entityGraphFilterPattern = this.createEntityGraphFilterPattern(subject);
// patterns.push(createSparqlGraphPattern(subject, [ createSparqlBasicGraphPattern([ entityGraphTriple ]) ]));
patterns.push(entityGraphFilterPattern);
}
}
else if (!options?.where?.id) {
const entityGraphFilterPattern = this.createEntityGraphFilterPattern(subject);
const entityIsGraphFilter = (0, SparqlUtil_1.createSparqlExistsOperation)([entityGraphFilterPattern]);
whereQueryData.filters.push(entityIsGraphFilter);
}
// Add union patterns to the patterns
if (relationsQueryData.unionPatterns.length > 0) {
patterns.push((0, SparqlUtil_1.createSparqlUnion)(relationsQueryData.unionPatterns));
}
const wherePatterns = this.createWherePatternsFromQueryData(patterns, whereQueryData.triples, whereQueryData.filters, orderQueryData.triples, orderQueryData.filters, whereQueryData.patterns ?? [], undefined, whereQueryData.binds);
// For ID-only queries, we need to include union patterns in graphWhere
// because that's what gets used in the CONSTRUCT query
const graphWhereRelationsPatterns = relationsQueryData.unionPatterns.length > 0
? [(0, SparqlUtil_1.createSparqlUnion)(relationsQueryData.unionPatterns), ...relationsQueryData.patterns]
: relationsQueryData.patterns;
const graphWherePatterns = this.createWherePatternsFromQueryData(whereQueryData.graphValues, whereQueryData.graphTriples, whereQueryData.graphFilters, undefined, undefined, graphWhereRelationsPatterns);
// Create variables for each order expression and update the orders to use them
const selectVariables = orderQueryData.orders.map(order => {
const variable = this.createVariable();
return {
variable,
expression: order.expression
};
});
const orders = selectVariables.map((selectVar, index) => ({
expression: selectVar.variable,
descending: orderQueryData.orders[index].descending
}));
if (orders.length === 0) {
orders.push({
expression: SparqlUtil_1.entityVariable
});
}
selectVariables.push(...(whereQueryData.selectVariables ?? []));
const isRelationsQueryDataEmpty = relationsQueryData.unionPatterns.length === 0 && relationsQueryData.patterns.length === 0 && relationsQueryData.selectionTriples.length === 0;
const returnData = {
where: wherePatterns,
orders,
...orderQueryData.groupByParent ? { group: subject } : {},
graphWhere: graphWherePatterns,
graphSelectionTriples: relationsQueryData.selectionTriples,
...isRelationsQueryDataEmpty ? {} : { relationsQueryData }
};
if (selectVariables.length > 0) {
returnData.selectVariables = selectVariables;
}
PerformanceLogger_1.PerformanceLogger.endSpan(span, {
patternCount: wherePatterns.length,
tripleCount: whereQueryData.triples.length,
hasFilters: whereQueryData.filters.length > 0,
hasOrders: orders.length > 0
});
return returnData;
}
catch (error) {
PerformanceLogger_1.PerformanceLogger.endSpan(span, { error: true });
throw error;
}
}
createSubQueryPatterns(subQueries) {
return subQueries.map((subQuery) => {
const subQueryWhere = this.createWhereQueryData(SparqlUtil_1.entityVariable, subQuery.where);
const queryGroup = [];
if (subQuery.groupBy && Array.isArray(subQuery.groupBy)) {
subQuery.groupBy.forEach((group) => {
queryGroup.push({
expression: data_model_1.default.variable(group)
});
});
}
const selectQuery = {
type: 'query',
queryType: 'SELECT',
variables: subQuery.select,
where: this.createWherePatternsFromQueryData(subQueryWhere.values, subQueryWhere.triples, subQueryWhere.filters, undefined, undefined, subQueryWhere.patterns ?? []),
group: queryGroup.length > 0 ? queryGroup : undefined,
having: subQuery.having ? this.createWhereQueryData(SparqlUtil_1.entityVariable, subQuery.having).filters : undefined,
prefixes: {}
};
return (0, SparqlUtil_1.createSparqlSelectGroup)([selectQuery]);
});
}
createEntityGraphFilterPattern(subject) {
const entityFilterTriple = { subject, predicate: this.createVariable(), object: this.createVariable() };
return (0, SparqlUtil_1.createSparqlGraphPattern)(subject, [(0, SparqlUtil_1.createSparqlBasicGraphPattern)([entityFilterTriple])]);
}
buildConstructFromEntitySelectQuery(graphWhere, graphSelectionTriples, select, selectVariables) {
const span = PerformanceLogger_1.PerformanceLogger.startSpan('QueryBuilder.buildConstruct', { hasSelect: !!select });
try {
let triples;
let where = [];
if (select) {
triples = this.createSelectPattern(select, SparqlUtil_1.entityVariable);
where = [(0, SparqlUtil_1.createSparqlOptional)([(0, SparqlUtil_1.createSparqlBasicGraphPattern)(triples)]), ...graphWhere];
}
else {
triples = [SparqlUtil_1.entityGraphTriple, ...graphSelectionTriples];
/* Skip if the where contains a union pattern */
if (graphWhere.some(pattern => pattern.type === 'union')) {
where = graphWhere;
}
else {
where = [
...graphWhere,
(0, SparqlUtil_1.createSparqlGraphPattern)(SparqlUtil_1.entityVariable, [(0, SparqlUtil_1.createSparqlBasicGraphPattern)([SparqlUtil_1.entityGraphTriple])])
];
}
}
// // Add select variables to the query
// if (selectVariables?.length) {
// where = [
// ...where,
// ...selectVariables.map(({ variable, expression }) => ({
// type: 'bind' as const,
// expression,
// variable,
// })),
// ];
// }
const result = (0, SparqlUtil_1.createSparqlConstructQuery)(triples, where);
PerformanceLogger_1.PerformanceLogger.endSpan(span, { tripleCount: triples.length, patternCount: where.length });
return result;
}
catch (error) {
PerformanceLogger_1.PerformanceLogger.endSpan(span, { error: true });
throw error;
}
}
createWhereQueryData(subject, where, isTopLevel = false) {
if (isTopLevel && Object.keys(where ?? {}).length === 1 && 'id' in where) {
const { values, filters, triples } = this.createWhereQueryDataForIdValue(subject, where.id);
return {
values: [],
filters: [],
triples: [],
graphValues: values,
graphFilters: filters,
graphTriples: triples,
binds: []
};
}
// Handle binds if specified in where options
const binds = [];
if (where?.binds) {
binds.push(...where.binds.map((bind) => ({
type: 'bind',
expression: bind.expression,
variable: bind.variable
})));
// Delete binds from where as it's a special key
const { binds: _, ...restWhere } = where;
where = restWhere;
}
const whereQueryData = Object.entries(where ?? {}).reduce((obj, [key, value]) => {
const whereQueryDataForField = this.createWhereQueryDataForField(subject, key, value);
return {
values: [...obj.values, ...whereQueryDataForField.values],
triples: [...obj.triples, ...whereQueryDataForField.triples],
filters: [...obj.filters, ...whereQueryDataForField.filters],
patterns: [...obj.patterns ?? [], ...whereQueryDataForField.patterns ?? []],
binds: [...obj.binds ?? [], ...whereQueryDataForField.binds ?? []]
};
}, { values: [], triples: [], filters: [], patterns: [], binds });
return {
...whereQueryData,
graphValues: [],
graphFilters: [],
graphTriples: [],
patterns: whereQueryData.patterns ?? [],
binds: whereQueryData.binds ?? []
};
}
createWhereQueryDataForField(subject, field, value) {
if (field === 'id') {
return this.createWhereQueryDataForIdValue(subject, value);
}
if (field === 'type') {
return this.createWhereQueryDataForType(subject, value);
}
const predicate = data_model_1.default.namedNode(field);
return this.createWhereQueryDataFromKeyValue(subject, predicate, value);
}
createWhereQueryDataForIdValue(term, value) {
let filters = [];
let values = [];
let triples = [];
if (FindOperator_1.FindOperator.isFindOperator(value)) {
({ filters, values, triples } = this.resolveFindOperatorAsExpressionForId(term, value));
}
else {
values = [
{
type: 'values',
values: [
{
[`?${term.value}`]: data_model_1.default.namedNode(value)
}
]
}
];
}
return {
values,
filters,
triples
};
}
createWhereQueryDataForType(subject, value) {
if (FindOperator_1.FindOperator.isFindOperator(value)) {
if (value.operator === 'and') {
// For AND on types, generate a triple for each type
const typeValues = value.value;
// Create a triple for each value in the array, safely converting to string
const triples = typeValues.map((typeVal) => {
const typeStr = typeof typeVal === 'string' ? typeVal : typeVal?.toString() || '';
return {
subject,
predicate: SparqlUtil_1.allTypesAndSuperTypesPath,
object: data_model_1.default.namedNode(typeStr)
};
});
return {
values: [],
filters: [],
triples: [
...triples
]
};
}
if (value.operator === 'inverse') {
const inversePredicate = (0, SparqlUtil_1.createSparqlInversePredicate)([SparqlUtil_1.allTypesAndSuperTypesPath]);
const inverseWhereQueryData = this.createWhereQueryDataFromKeyValue(subject, inversePredicate, value.value);
const variable = this.createVariable();
return {
values: inverseWhereQueryData.values,
filters: inverseWhereQueryData.filters,
triples: inverseWhereQueryData.triples
};
}
if (value.operator === 'sequence') {
const sequencePredicate = (0, SparqlUtil_1.createSparqlSequencePredicate)([SparqlUtil_1.allTypesAndSuperTypesPath]);
return this.createWhereQueryDataFromKeyValue(subject, sequencePredicate, value.value);
}
if (Array.isArray(value)) {
const triples = value.map(typeVal => ({
subject,
predicate: SparqlUtil_1.allTypesAndSuperTypesPath,
object: typeof typeVal === 'string' ? data_model_1.default.namedNode(typeVal) : data_model_1.default.namedNode(typeVal.toString())
}));
return {
values: [],
filters: [],
triples: [
...triples
]
};
}
const variable = this.createVariable();
const triple = { subject, predicate: SparqlUtil_1.allTypesAndSuperTypesPath, object: variable };
const { filter, valuePattern, tripleInFilter } = this.resolveFindOperatorAsExpressionWithMultipleValues(variable, value, triple);
return {
values: valuePattern ? [valuePattern] : [],
filters: filter ? [filter] : [],
triples: tripleInFilter ? [] : [triple]
};
}
return {
values: [],
filters: [],
triples: [
{
subject,
predicate: SparqlUtil_1.allTypesAndSuperTypesPath,
object: data_model_1.default.namedNode(value)
}
]
};
}
createWhereQueryDataFromKeyValue(subject, predicate, value) {
if (Array.isArray(value) && FindOperator_1.FindOperator.isFindOperator(value[0])) {
return this.createWhereQueryDataForMultipleFindOperators(subject, predicate, value);
}
if (FindOperator_1.FindOperator.isFindOperator(value)) {
return this.createWhereQueryDataForFindOperator(subject, predicate, value);
}
if (Array.isArray(value)) {
return value.reduce((obj, valueItem) => {
const valueWhereQueryData = this.createWhereQueryDataFromKeyValue(subject, predicate, valueItem);
return {
values: [...obj.values, ...valueWhereQueryData.values],
filters: [...obj.filters, ...valueWhereQueryData.filters],
triples: [...obj.triples, ...valueWhereQueryData.triples],
patterns: [...obj.patterns ?? [], ...valueWhereQueryData.patterns ?? []]
};
}, { values: [], filters: [], triples: [], patterns: [] });
}
if (typeof value === 'object') {
if ('@value' in value) {
return this.createWhereQueryDataForValueObject('subject' in value ? value.subject : subject, predicate, value);
}
return this.createWhereQueryDataForNestedWhere(subject, predicate, value);
}
const term = this.resolveValueToTerm(value);
return {
values: [],
filters: [],
triples: [{ subject, predicate, object: term }]
};
}
createWhereQueryDataForFindOperator(subject, predicate, operator) {
if (operator.operator === 'inverse') {
const inversePredicate = (0, SparqlUtil_1.createSparqlInversePredicate)([predicate]);
return this.createWhereQueryDataFromKeyValue(operator.subject ? operator.subject : subject, inversePredicate, operator.value);
}
if (operator.operator === 'sequence') {
const sequencePredicate = (0, SparqlUtil_1.createSparqlSequencePredicate)([predicate]);
return this.createWhereQueryDataFromKeyValue(operator.subject ? operator.subject : subject, sequencePredicate, operator.value);
}
if (FindOperator_1.FindOperator.isPathOperator(operator)) {
const pathPredicate = this.pathOperatorToPropertyPath(operator);
const combinedPredicate = (0, SparqlUtil_1.createSparqlSequencePredicate)([predicate, pathPredicate]);
return this.createWhereQueryDataFromKeyValue(subject, combinedPredicate, operator.value.value);
}
const variable = this.createVariable();
const triple = { subject, predicate, object: variable };
const { filter, valuePattern, tripleInFilter } = this.resolveFindOperatorAsExpressionWithMultipleValues(variable, operator, triple);
return {
values: valuePattern ? [valuePattern] : [],
filters: filter ? [filter] : [],
triples: tripleInFilter ? [] : [triple]
};
}
pathOperatorToPropertyPath(operator) {
if (operator.operator === 'inversePath') {
let subPredicate;
const { subPath } = operator.value;
if (typeof subPath === 'string') {
subPredicate = data_model_1.default.namedNode(subPath);
}
else {
subPredicate = this.pathOperatorToPropertyPath(subPath);
}
return (0, SparqlUtil_1.createSparqlInversePredicate)([subPredicate]);
}
if (operator.operator === 'sequencePath') {
const { subPath } = operator.value;
const subPredicates = subPath.map((sequencePart) => {
if (typeof sequencePart === 'string') {
return data_model_1.default.namedNode(sequencePart);
}
return this.pathOperatorToPropertyPath(sequencePart);
});
return (0, SparqlUtil_1.createSparqlSequencePredicate)(subPredicates);
}
if (operator.operator === 'zeroOrMorePath') {
const { subPath } = operator.value;
let subPredicate;
if (typeof subPath === 'string') {
subPredicate = data_model_1.default.namedNode(subPath);
}
else {
subPredicate = this.pathOperatorToPropertyPath(subPath);
}
return (0, SparqlUtil_1.createSparqlZeroOrMorePredicate)([subPredicate]);
}
if (operator.operator === 'oneOrMorePath') {
const { subPath } = operator.value;
let subPredicate;
if (typeof subPath === 'string') {
subPredicate = data_model_1.default.namedNode(subPath);
}
else {
subPredicate = this.pathOperatorToPropertyPath(subPath);
}
return (0, SparqlUtil_1.createSparqlOneOrMorePredicate)([subPredicate]);
}
throw new Error(`Operator ${operator.operator} not supported`);
}
createWhereQueryDataForMultipleFindOperators(subject, predicate, operators) {
const variable = this.createVariable();
const triple = { subject, predicate, object: variable };
const whereQueryData = {
values: [],
filters: [],
triples: [triple]
};
return operators.reduce((obj, operator) => {
const { filter, valuePattern } = this.resolveFindOperatorAsExpressionWithMultipleValues(variable, operator, triple);
if (valuePattern) {
obj.values.push(valuePattern);
}
if (filter) {
obj.filters.push(filter);
}
return obj;
}, whereQueryData);
}
createWhereQueryDataForNestedWhere(subject, predicate, where) {
const subNodeVariable = this.createVariable();
const subWhereQueryData = this.createWhereQueryData(subNodeVariable, where);
return {
values: [...subWhereQueryData.values, ...subWhereQueryData.graphValues],
filters: subWhereQueryData.filters,
triples: [{ subject, predicate, object: subNodeVariable }, ...subWhereQueryData.triples],
patterns: [...subWhereQueryData.patterns ?? []]
};
}
createWhereQueryDataForValueObject(subject, predicate, valueObject) {
const term = this.valueObjectToTerm(valueObject);
if (valueObject.isOptional) {
return {
values: [],
filters: [],
triples: [],
patterns: [(0, SparqlUtil_1.createSparqlOptional)([(0, SparqlUtil_1.createSparqlBasicGraphPattern)([{ subject, predicate, object: term }])])]
};
}
return {
values: [],
filters: [],
triples: [{ subject, predicate, object: term }]
};
}
valueObjectToTerm(valueObject) {
let typeOrLanguage;
let value;
if ('@type' in valueObject && valueObject['@type'] === '@json') {
typeOrLanguage = Vocabularies_1.RDF.JSON;
value = JSON.stringify(valueObject['@value']);
}
else {
typeOrLanguage = ('@type' in valueObject ? valueObject['@type'] : valueObject['@language']);
value = valueObject['@value'].toString();
}
return (0, TripleUtil_1.valueToLiteral)(value, typeOrLanguage);
}
resolveFindOperatorAsExpressionWithMultipleValues(leftSide, operator, triple, dontUseValuePattern = false) {
if (operator.operator === 'and') {
// For AND operators, we'll create an AND filter with multiple equality comparisons
const values = operator.value;
if (values.length === 0) {
// No conditions to add
return {};
}
if (values.length === 1) {
// If only one value, treat as equality
return {
filter: (0, SparqlUtil_1.createSparqlEqualOperation)(leftSide, this.resolveValueToExpression(values[0]))
};
}
// Create individual equality conditions for each value
const equalityConditions = values.map(val => (0, SparqlUtil_1.createSparqlEqualOperation)(leftSide, this.resolveValueToExpression(val)));
// Combine with AND operation
return {
filter: {
type: 'operation',
operator: '&&',
args: equalityConditions
}
};
}
if (operator.operator === 'in') {
const resolvedValue = this.resolveValueToExpression(operator.value);
if (Array.isArray(resolvedValue) && !dontUseValuePattern) {
return {
valuePattern: {
type: 'values',
values: resolvedValue.map((value) => ({ [`?${leftSide.value}`]: value }))
}
};
}
return {
filter: (0, SparqlUtil_1.createSparqlInOperation)(leftSide, resolvedValue)
};
}
if (operator.operator === 'not') {
const resolvedExpression = this.resolveValueToExpression(operator.value);
return {
filter: this.buildNotOperationForMultiValued(leftSide, resolvedExpression, triple),
tripleInFilter: true
};
}
if (operator.operator === 'exists') {
return {
filter: (0, SparqlUtil_1.createSparqlExistsOperation)([(0, SparqlUtil_1.createSparqlBasicGraphPattern)([triple])]),
tripleInFilter: true
};
}
if (operator.operator === 'contains') {
const searchString = this.resolveValueToExpression(operator.value);
const filter = (0, SparqlUtil_1.createSparqlContainsOperation)(
// Directly use the variable as an expression
(0, SparqlUtil_1.createSparqlLcaseOperation)(data_model_1.default.variable(leftSide.value)), (0, SparqlUtil_1.createSparqlLcaseOperation)(data_model_1.default.literal(searchString.value.toLowerCase())));
return {
filter
};
}
const resolvedExpression = this.resolveValueToExpression(operator.value);
switch (operator.operator) {
case 'equal':
return { filter: (0, SparqlUtil_1.createSparqlEqualOperation)(leftSide, resolvedExpression) };
case 'gt':
return { filter: (0, SparqlUtil_1.createSparqlGtOperation)(leftSide, resolvedExpression) };
case 'gte':
return { filter: (0, SparqlUtil_1.createSparqlGteOperation)(leftSide, resolvedExpression) };
case 'lt':
return { filter: (0, SparqlUtil_1.createSparqlLtOperation)(leftSide, resolvedExpression) };
case 'lte':
return { filter: (0, SparqlUtil_1.createSparqlLteOperation)(leftSide, resolvedExpression) };
default:
throw new Error(`Unsupported operator "${operator.operator}"`);
}
}
resolveFindOperatorAsExpressionForId(leftSide, operator) {
switch (operator.operator) {
case 'inversePath': {
const predicate = this.pathOperatorToPropertyPath(operator);
return this.createWhereQueryDataFromKeyValue(leftSide, predicate, operator.value.value);
}
case 'in': {
const resolvedValue = this.resolveValueToExpression(operator.value);
return {
triples: [],
filters: [],
values: [
{
type: 'values',
values: resolvedValue.map((value) => ({ [`?${leftSide.value}`]: value }))
}
]
};
}
case 'not':
return {
triples: [],
values: [],
filters: [
this.buildNotOperationForId(leftSide, this.resolveValueToExpression(operator.value))
]
};
case 'equal':
return {
triples: [],
values: [],
filters: [(0, SparqlUtil_1.createSparqlEqualOperation)(leftSide, this.resolveValueToExpression(operator.value))]
};
default:
throw new Error(`Unsupported operator "${operator.operator}"`);
}
}
resolveValueToExpression(value) {
if (FindOperator_1.FindOperator.isFindOperator(value)) {
return value;
}
if (Array.isArray(value)) {
return value.map((valueItem) => this.resolveValueToTerm(valueItem));
}
return this.resolveValueToTerm(value);
}
buildNotOperationForMultiValued(leftSide, rightSide, triple) {
let filterExpression;
const isFindOperator = FindOperator_1.FindOperator.isFindOperator(rightSide);
if (isFindOperator && rightSide.operator === 'exists') {
return (0, SparqlUtil_1.createSparqlNotExistsOperation)([(0, SparqlUtil_1.createSparqlBasicGraphPattern)([triple])]);
}
if (isFindOperator) {
let expression;
try {
({ filter: expression } = this.resolveFindOperatorAsExpressionWithMultipleValues(leftSide, rightSide, triple, true));
}
catch {
throw new Error(`Unsupported Not sub operator "${rightSide.operator}"`);
}
filterExpression = (0, SparqlUtil_1.createSparqlFilterWithExpression)(expression);
}
else {
filterExpression = (0, SparqlUtil_1.createSparqlFilterWithExpression)((0, SparqlUtil_1.createSparqlEqualOperation)(leftSide, rightSide));
}
return (0, SparqlUtil_1.createSparqlNotExistsOperation)([
(0, SparqlUtil_1.createSparqlSelectGroup)([(0, SparqlUtil_1.createSparqlBasicGraphPattern)([triple]), filterExpression])
]);
}
buildNotOperationForId(leftSide, rightSide) {
if (FindOperator_1.FindOperator.isFindOperator(rightSide)) {
const resolvedValue = this.resolveValueToExpression(rightSide.value);
switch (rightSide.operator) {
case 'in':
return (0, SparqlUtil_1.createSparqlNotInOperation)(leftSide, resolvedValue);
case 'equal':
return (0, SparqlUtil_1.createSparqlNotEqualOperation)(leftSide, resolvedValue);
default:
throw new Error(`Unsupported Not sub operator "${rightSide.operator}"`);
}
}
return (0, SparqlUtil_1.createSparqlNotEqualOperation)(leftSide, rightSide);
}
resolveValueToTerm(value) {
if (typeof value === 'object' && '@value' in value) {
return (0, TripleUtil_1.valueToLiteral)(value['@value'], '@type' in value ? value['@type'] : undefined);
}
if ((0, Util_1.isUrl)(value)) {
return data_model_1.default.namedNode(value);
}
return (0, TripleUtil_1.valueToLiteral)(value);
}
createOrderQueryData(subject, order, isNested = false) {
if (!order) {
// Default is descending by id
return { triples: [], orders: [], filters: [] };
}
return Object.entries(order).reduce((obj, [property, orderValue]) => {
const orderQueryData = this.createOrderQueryDataForProperty(subject, property, orderValue, isNested);
obj.orders = [...obj.orders, ...orderQueryData.orders];
obj.triples = [...obj.triples, ...orderQueryData.triples];
obj.filters = [...obj.filters, ...orderQueryData.filters];
obj.groupByParent = obj.groupByParent ?? orderQueryData.groupByParent;
return obj;
}, { triples: [], orders: [], filters: [] });
}
createOrderQueryDataForProperty(subject, property, orderValue, isNested = false) {
const predicate = data_model_1.default.namedNode(property);
if (FindOperator_1.FindOperator.isFindOperator(orderValue)) {
const variable = this.createVariable();
const inverseRelationTriple = {
subject,
predicate: (0, SparqlUtil_1.createSparqlInversePredicate)([predicate]),
object: variable
};
const subRelationOperatorValue = orderValue
.value;
const subRelationOrderQueryData = this.createOrderQueryData(variable, subRelationOperatorValue.order, true);
const subRelationWhereQueryData = this.createWhereQueryData(variable, subRelationOperatorValue.where);
// Create aggregate expressions for each order, but don't nest aggregates
const aggregateOrders = subRelationOrderQueryData.orders.map(order => {
const baseExpression = 'type' in order.expression && order.expression.type === 'aggregate'
? order.expression.expression
: order.expression;
// Create the aggregate expression first
const aggregateExpression = {
type: 'aggregate',
expression: baseExpression,
aggregation: order.descending ? 'max' : 'min'
};
return {
expression: aggregateExpression,
descending: order.descending
};
});
return {
triples: [inverseRelationTriple, ...subRelationOrderQueryData.triples, ...subRelationWhereQueryData.triples],
filters: subRelationWhereQueryData.filters,
orders: aggregateOrders,
groupByParent: true,
patterns: [...subRelationWhereQueryData.patterns ?? [], ...subRelationOrderQueryData.patterns ?? []]
};
}
if (property === 'id') {
return {
triples: [],
filters: [],
orders: [
{
expression: subject,
descending: orderValue === 'DESC' || orderValue === 'desc'
}
]
};
}
const variable = this.createVariable();
const isDescending = orderValue === 'DESC' || orderValue === 'desc';
return {
triples: [{ subject, predicate, object: variable }],
filters: [],
orders: [
{
expression: isNested
? {
type: 'aggregate',
expression: variable,
aggregation: isDescending ? 'max' : 'min'
}
: variable,
descending: isDescending
}
]
};
}
createRelationsQueryData(subject, relations) {
if (!relations) {
return { patterns: [], selectionTriples: [], unionPatterns: [] };
}
const response = Object.entries(relations).reduce((obj, [property, relationsValue]) => {
const predicate = data_model_1.default.namedNode(property);
if (typeof relationsValue === 'object') {
if (FindOperator_1.FindOperator.isFindOperator(relationsValue)) {
const { patterns, selectionTriples, unionPatterns } = this.createRelationsQueryDataForInverseRelation(subject, predicate, relationsValue);
return {
patterns: [...obj.patterns, ...patterns],
selectionTriples: [...obj.selectionTriples, ...selectionTriples],
unionPatterns: [...obj.unionPatterns ?? [], ...unionPatterns ?? []]
};
}
const { patterns, selectionTriples, unionPatterns } = this.createRelationsQueryDataForNestedRelation(subject, predicate, relationsValue);
return {
patterns: [...obj.patterns, ...patterns],
selectionTriples: [...obj.selectionTriples, ...selectionTriples],
unionPatterns: [
...obj.unionPatterns ?? [],
...unionPatterns ?? []
]
};
}
const variable = this.createVariable();
const relationPattern = (0, SparqlUtil_1.createSparqlSelectGroup)([
(0, SparqlUtil_1.createSparqlBasicGraphPattern)([{ subject, predicate, object: variable }]),
(0, SparqlUtil_1.createSparqlGraphPattern)(variable, [(0, SparqlUtil_1.createSparqlBasicGraphPattern)([SparqlUtil_1.entityGraphTriple])])
]);
return {
unionPatterns: [...obj.unionPatterns ?? [], relationPattern],
patterns: [...obj.patterns],
selectionTriples: [...obj.selectionTriples]
};
}, { patterns: [], selectionTriples: [], unionPatterns: [] });
return {
patterns: [...response?.patterns ?? []],
selectionTriples: [...response?.selectionTriples ?? []],
unionPatterns: [...response?.unionPatterns ?? []]
};
}
createRelationsQueryDataForInverseRelation(subject, predicate, relationsValue) {
const variable = this.createVariable();
const inverseRelationTriple = {
subject,
predicate: (0, SparqlUtil_1.createSparqlInversePredicate)([predicate]),
object: variable
};
if (typeof relationsValue.value === 'object' && relationsValue.value.relations) {
const subRelationsQueryData = this.createRelationsQueryData(variable, relationsValue.value.relations);
const unionPatterns = [];
unionPatterns.push((0, SparqlUtil_1.createSparqlSelectGroup)([
(0, SparqlUtil_1.createSparqlBasicGraphPattern)([inverseRelationTriple]),
(0, SparqlUtil_1.createSparqlGraphPattern)(variable, [(0, SparqlUtil_1.createSparqlBasicGraphPattern)([SparqlUtil_1.entityGraphTriple])])
]));
for (const subRelationUnionPattern of subRelationsQueryData.unionPatterns) {
unionPatterns.push((0, SparqlUtil_1.createSparqlSelectGroup)([
(0, SparqlUtil_1.createSparqlBasicGraphPattern)([inverseRelationTriple]),
...subRelationUnionPattern.type === 'group'
? subRelationUnionPattern.patterns
: [subRelationUnionPattern]
]));
}
return {
patterns: [],
selectionTriples: [...subRelationsQueryData.selectionTriples],
unionPatterns
};
}
const unionPatterns = [];
unionPatterns.push((0, SparqlUtil_1.createSparqlSelectGroup)([
(0, SparqlUtil_1.createSparqlBasicGraphPattern)([inverseRelationTriple]),
(0, SparqlUtil_1.createSparqlGraphPattern)(variable, [(0, SparqlUtil_1.createSparqlBasicGraphPattern)([SparqlUtil_1.entityGraphTriple])])
]));
return {
patterns: [],
selectionTriples: [],
unionPatterns
};
}
createRelationsQueryDataForNestedRelation(subject, predicate, relationsValue) {
const variable = this.createVariable();
const relationTriple = { subject, predicate, object: variable };
const unionPatterns = [];
const subRelationsQueryData = this.createRelationsQueryData(variable, relationsValue);
// subRelationsQueryData.patterns.push(createSparqlBasicGraphPattern([ relationTriple ]));
// unionPatterns.push(...subRelationsQueryData.patterns);
// unionPatterns.push(
// createSparqlGraphPattern(variable, [ createSparqlBasicGraphPattern([ entityGraphTriple ]) ])
// );
/* const relationPattern = subRelationsQueryData.patterns.length > 0
? createSparqlSelectGroup([
...patterns,
...subRelationsQueryData.patterns,
...subRelationsQueryData.unionPatterns
])
: undefined; */
/* We always need to include the first level of the property in the union patterns */
unionPatterns.push((0, SparqlUtil_1.createSparqlSelectGroup)([
(0, SparqlUtil_1.createSparqlBasicGraphPattern)([relationTriple]),
(0, SparqlUtil_1.createSparqlGraphPattern)(variable, [(0, SparqlUtil_1.createSparqlBasicGraphPattern)([SparqlUtil_1.entityGraphTriple])])
]));
for (const subRelationUnionPattern of subRelationsQueryData.unionPatterns) {
unionPatterns.push((0, SparqlUtil_1.createSparqlSelectGroup)([
(0, SparqlUtil_1.createSparqlBasicGraphPattern)([relationTriple]),
...subRelationUnionPattern.type === 'group' ? subRelationUnionPattern.patterns : [subRelationUnionPattern]
]));
}
return {
patterns: [],
selectionTriples: [...subRelationsQueryData.selectionTriples],
unionPatterns
};
}
createVariable() {
return data_model_1.default.variable(this.variableGenerator.getNext());
}
createSelectPattern(select, subject) {
if (Array.isArray(select)) {
return select.map((selectPredicate) => ({
subject,
predicate: data_model_1.default.namedNode(selectPredicate),
object: this.createVariable()
}));
}
return Object.entries(select).reduce((arr, [key, value]) => {
const variable = this.createVariable();
arr.push({ subject, predicate: data_model_1.default.namedNode(key), object: variable });
if (typeof value === 'object') {
arr = [...arr, ...this.createSelectPattern(value, variable)];
}
return arr;
}, []);
}
createWherePatternsFromQueryData(initialPatterns, triples, filters, orderTriples, orderFilters, additionalPatterns, serviceTriples, binds) {
let patterns = initialPatterns;
// Add binds at the beginning if they exist
if (binds && binds.length > 0) {
patterns = [...patterns, ...binds];
}
if (triples.length > 0) {
patterns.push((0, SparqlUtil_1.createSparqlBasicGraphPattern)(triples));
}
if (orderTriples && orderTriples.length > 0) {
const optionalPatterns = [(0, SparqlUtil_1.createSparqlBasicGraphPattern)(orderTriples)];
if (orderFilters && orderFilters.length > 0) {
optionalPatterns.push((0, SparqlUtil_1.createFilterPatternFromFilters)(orderFilters));
}
patterns.push((0, SparqlUtil_1.createSparqlOptional)(optionalPatterns));
}
if (filters.length > 0) {
patterns.push((0, SparqlUtil_1.createFilterPatternFromFilters)(filters));
}
if (serviceTriples) {
for (const [service, sTriples] of Object.entries(serviceTriples)) {
patterns.unshift((0, SparqlUtil_1.createSparqlServicePattern)(service, sTriples));
}
}
if (additionalPatterns) {
patterns = [...patterns, ...additionalPatterns];
}
return patterns;
}
createGroupPatternForPath(entityVariable, path) {
const segments = path.split('~');
let currentSubject = entityVariable;
const patterns = [];
// Create a chain of patterns for each segment
segments.forEach((predicate, index) => {
const object = this.createVariable();
patterns.push({
type: 'bgp',
triples: [
{
subject: currentSubject,
predicate: data_model_1.default.namedNode(predicate),
object
}
]
});
currentSubject = object;
});
// Return the final variable (last object) and all patterns
return {
variable: currentSubject,
patterns
};
}
async buildGroupByQuery(options) {
const span = PerformanceLogger_1.PerformanceLogger.startSpan('QueryBuilder.buildGroupBy', {
hasGroupBy: !!options.groupBy,
hasDateRange: !!options.dateRange
});
try {
const entityVariable = data_model_1.default.variable('entity');
const queryData = this.buildEntitySelectPatternsFromOptions(entityVariable, {
where: options.where || {}
});
// Add group variables and patterns with mapping
const groupVariables = [];
const groupPatterns = [];
const variableMapping = {};
if (options.groupBy) {
options.groupBy.forEach(path => {
const { variable: groupVar, patterns } = this.createGroupPatternForPath(entityVariable, path);
groupVariables.push(groupVar);
variableMapping[groupVar.value] = path;
groupPatterns.push(...patterns);
});
}
// Add date handling if specified
if (options.dateRange) {
const dateVar = this.createVariable();
variableMapping[dateVar.value] = 'date';
const datePattern = {
type: 'bgp',
triples: [
{
subject: entityVariable,
predicate: data_model_1.default.namedNode(constants_1.EngineConstants.prop.dateCreated),
object: dateVar
}
]
};
const dateFilter = {
type: 'filter',
expression: {
type: 'operation',
operator: '&&',
args: [
{
type: 'operation',
operator: '>=',
args: [dateVar, data_model_1.default.literal(options.dateRange.start, constants_1.XSD.dateTime)]
},
{
type: 'operation',
operator: '<=',
args: [dateVar, data_model_1.default.literal(options.dateRange.end, constants_1.XSD.dateTime)]
}
]
}
};
groupPatterns.push(datePattern, dateFilter);
if (options.dateGrouping) {
const dateGroupVar = this.createVariable();
groupVariables.push(dateGroupVar);
variableMapping[dateGroupVar.value] = 'dateGroup';
const dateGroupBind = this.createDateGroupingBind(dateVar, dateGroupVar, options.dateGrouping);
groupPatterns.push(dateGroupBind);
}
}
// Create count and entityIds variables
const countVar = this.createVariable();
const entityIdsVar = this.createVariable();
variableMapping[countVar.value] = 'count';
variableMapping[entityIdsVar.value] = 'entityIds';
// Combine all patterns
const combinedWhere = [...queryData.where, ...groupPatterns];
// Create select query with aggregations
const selectQuery = (0, SparqlUtil_1.createSparqlSelectQuery)([
...groupVariables,
{
expression: {
type: 'aggregate',
aggregation: 'count',
distinct: true,
expression: entityVariable
},
variable: countVar
},
{
expression: {
type: 'aggregate',
aggregation: 'group_concat',
distinct: true,
separator: ' ',
expression: entityVariable
},
variable: entityIdsVar
}
], combinedWhere, [], // Orders
groupVariables.length > 0 ? groupVariables : undefined, // Group by
options.limit, options.offset);
const result = { query: selectQuery, variableMapping };
PerformanceLogger_1.PerformanceLogger.endSpan(span, {
groupCount: groupVariables.length,
hasDateGrouping: !!options.dateGrouping
});
return result;
}
catch (error) {
PerformanceLogger_1.PerformanceLogger.endSpan(span, { error: true });