UNPKG

@comake/skl-js-engine

Version:

Standard Knowledge Language Javascript Engine

1,042 lines (1,041 loc) 53 kB
"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 });