@comake/skl-js-engine
Version:
Standard Knowledge Language Javascript Engine
1,029 lines • 46.8 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;
const data_model_1 = __importDefault(require("@rdfjs/data-model"));
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 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))) {
const entityGraphFilterPattern = this.createEntityGraphFilterPattern(subject);
patterns.push(entityGraphFilterPattern);
}
else if (!options?.where?.id) {
const entityGraphFilterPattern = this.createEntityGraphFilterPattern(subject);
const entityIsGraphFilter = (0, SparqlUtil_1.createSparqlExistsOperation)([entityGraphFilterPattern]);
whereQueryData.filters.push(entityIsGraphFilter);
}
const wherePatterns = this.createWherePatternsFromQueryData(patterns, whereQueryData.triples, whereQueryData.filters, orderQueryData.triples, orderQueryData.filters, whereQueryData.patterns ?? [], undefined, whereQueryData.binds);
const graphWherePatterns = this.createWherePatternsFromQueryData(whereQueryData.graphValues, whereQueryData.graphTriples, whereQueryData.graphFilters, undefined, undefined, relationsQueryData.patterns);
// 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,
});
}
const returnData = {
where: wherePatterns,
orders,
group: orderQueryData.groupByParent ? subject : undefined,
graphWhere: graphWherePatterns,
graphSelectionTriples: relationsQueryData.selectionTriples,
};
if (selectVariables.length > 0) {
returnData.selectVariables = selectVariables;
}
return returnData;
}
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) {
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];
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,
// })),
// ];
// }
return (0, SparqlUtil_1.createSparqlConstructQuery)(triples, where);
}
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,
};
}
if (value.operator === 'inverse') {
const inversePredicate = (0, SparqlUtil_1.createSparqlInversePredicate)([SparqlUtil_1.allTypesAndSuperTypesPath]);
const inverseWhereQueryData = this.createWhereQueryDataFromKeyValue(subject, inversePredicate, value.value);
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,
};
}
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) {
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: [] };
}
return 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 } = this.createRelationsQueryDataForInverseRelation(subject, predicate, relationsValue);
return {
patterns: [...obj.patterns, ...patterns],
selectionTriples: [...obj.selectionTriples, ...selectionTriples],
};
}
const { patterns, selectionTriples } = this.createRelationsQueryDataForNestedRelation(subject, predicate, relationsValue);
return {
patterns: [...obj.patterns, ...patterns],
selectionTriples: [...obj.selectionTriples, ...selectionTriples],
};
}
const variable = this.createVariable();
const graphTriple = {
subject: this.createVariable(),
predicate: this.createVariable(),
object: this.createVariable(),
};
const relationPattern = (0, SparqlUtil_1.createSparqlOptional)([
(0, SparqlUtil_1.createSparqlBasicGraphPattern)([{ subject, predicate, object: variable }]),
(0, SparqlUtil_1.createSparqlGraphPattern)(variable, [(0, SparqlUtil_1.createSparqlBasicGraphPattern)([graphTriple])]),
]);
return {
patterns: [...obj.patterns, relationPattern],
selectionTriples: [...obj.selectionTriples, graphTriple],
};
}, { patterns: [], selectionTriples: [] });
}
createRelationsQueryDataForInverseRelation(subject, predicate, relationsValue) {
const variable = this.createVariable();
const graphTriple = {
subject: this.createVariable(),
predicate: this.createVariable(),
object: 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 relationPattern = (0, SparqlUtil_1.createSparqlOptional)([
(0, SparqlUtil_1.createSparqlBasicGraphPattern)([inverseRelationTriple]),
(0, SparqlUtil_1.createSparqlGraphPattern)(variable, [(0, SparqlUtil_1.createSparqlBasicGraphPattern)([graphTriple])]),
...subRelationsQueryData.patterns,
]);
return {
patterns: [relationPattern],
selectionTriples: [graphTriple, ...subRelationsQueryData.selectionTriples],
};
}
const relationPattern = (0, SparqlUtil_1.createSparqlOptional)([
(0, SparqlUtil_1.createSparqlBasicGraphPattern)([inverseRelationTriple]),
(0, SparqlUtil_1.createSparqlGraphPattern)(variable, [(0, SparqlUtil_1.createSparqlBasicGraphPattern)([graphTriple])]),
]);
return {
patterns: [relationPattern],
selectionTriples: [graphTriple],
};
}
createRelationsQueryDataForNestedRelation(subject, predicate, relationsValue) {
const variable = this.createVariable();
const graphTriple = {
subject: this.createVariable(),
predicate: this.createVariable(),
object: this.createVariable(),
};
const relationTriple = { subject, predicate, object: variable };
const subRelationsQueryData = this.createRelationsQueryData(variable, relationsValue);
const relationPattern = (0, SparqlUtil_1.createSparqlOptional)([
(0, SparqlUtil_1.createSparqlBasicGraphPattern)([relationTriple]),
(0, SparqlUtil_1.createSparqlGraphPattern)(variable, [(0, SparqlUtil_1.createSparqlBasicGraphPattern)([graphTriple])]),
...subRelationsQueryData.patterns,
]);
return {
patterns: [relationPattern],
selectionTriples: [graphTriple, ...subRelationsQueryData.selectionTriples],
};
}
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 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('http://purl.org/dc/terms/created'),
object: dateVar,
},
],
};
const dateFilter = {
type: 'filter',
expression: {
type: 'operation',
operator: '&&',
args: [
{
type: 'operation',
operator: '>=',
args: [
dateVar,
data_model_1.default.literal(options.dateRange.start, 'http://www.w3.org/2001/XMLSchema#dateTime'),
],
},
{
type: 'operation',
operator: '<=',
args: [dateVar, data_model_1.default.literal(options.dateRange.end, 'http://www.w3.org/2001/XMLSchema#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);
return { query: selectQuery, variableMapping };
}
// Helper function for date grouping
createDateGroupingBind(dateVar, dateGroupVar, grouping) {
return {
type: 'bind',
expression: {
type: 'operation',
operator: 'CONCAT',
args: [
this.createYearExpression(dateVar),
data_model_1.default.literal('-'),
this.createMonthExpression(dateVar),
...this.createDayExpressionParts(dateVar, grouping),
],
},
variable: dateGroupVar,
};
}
createYearExpression(dateVar) {
return {
type: 'operation',
operator: 'STR',
args: [
{
type: 'operation',
operator: 'YEAR',
args: [dateVar],
},
],
};
}
createMonthExpression(dateVar) {
return this.createPaddedDatePartExpression(dateVar, 'MONTH');
}
createDayExpression(dateVar) {
return this.createPaddedDatePartExpression(dateVar, 'DAY');
}
createPaddedDatePartExpression(dateVar, datePart) {
const comparisonValue = data_model_1.default.literal('10', data_model_1.default.namedNode('http://www.w3.org/2001/XMLSchema#integer'));
return {
type: 'operation',
operator: 'IF',
args: [
this.createLessThanComparison(dateVar, datePart, comparisonValue),
this.createPaddedStringExpression(dateVar, datePart),
this.createUnpaddedStringExpression(dateVar, datePart),
],
};
}
createLessThanComparison(dateVar, datePart, comparisonValue) {
return {
type: 'operation',
operator: '<',
args: [
{
type: 'operation',
operator: datePart,
args: [dateVar],
},
comparisonValue,
],
};
}
createPaddedStringExpression(dateVar, datePart) {
return {
type: 'operation',
operator: 'CONCAT',
args: [data_model_1.default.literal('0'), this.createUnpaddedStringExpression(dateVar, datePart)],
};
}
createUnpaddedStringExpression(dateVar, datePart) {
return {
type: 'operation',
operator: 'STR',
args: [
{
type: 'operation',
operator: datePart,
args: [dateVar],
},
],
};
}
createDayExpressionParts(dateVar, grouping) {
if (grouping === 'day') {
return [data_model_1.default.literal('-'), this.createDayExpression(dateVar)];
}
return [data_model_1.default.literal('')];
}
}
exports.SparqlQueryBuilder = SparqlQueryBuilder;
//# sourceMappingURL=SparqlQueryBuilder.js.map