UNPKG

@incremunica/actor-query-operation-filter

Version:

An incremental filter query-operation actor

176 lines 9.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ActorQueryOperationFilter = void 0; const bus_query_operation_1 = require("@comunica/bus-query-operation"); const context_entries_1 = require("@comunica/context-entries"); const core_1 = require("@comunica/core"); const utils_bindings_factory_1 = require("@comunica/utils-bindings-factory"); const utils_expression_evaluator_1 = require("@comunica/utils-expression-evaluator"); const utils_query_operation_1 = require("@comunica/utils-query-operation"); const context_entries_2 = require("@incremunica/context-entries"); const asynciterator_1 = require("asynciterator"); const sparqlalgebrajs_1 = require("sparqlalgebrajs"); const algebra_1 = require("sparqlalgebrajs/lib/algebra"); /** * An Incremunica Filter Query Operation Actor. */ class ActorQueryOperationFilter extends bus_query_operation_1.ActorQueryOperationTypedMediated { constructor(args) { super(args, 'filter'); } async testOperation() { return (0, core_1.passTestVoid)(); } async runOperation(operation, context) { const outputRaw = await this.mediatorQueryOperation.mediate({ operation: operation.input, context }); const output = (0, utils_query_operation_1.getSafeBindings)(outputRaw); (0, utils_query_operation_1.validateQueryOutput)(output, 'bindings'); const variables = (await output.metadata()).variables.map(v => v.variable); const dataFactory = context.getSafe(context_entries_1.KeysInitQuery.dataFactory); const algebraFactory = new sparqlalgebrajs_1.Factory(dataFactory); const bindingsFactory = await utils_bindings_factory_1.BindingsFactory.create(this.mediatorMergeBindingsContext, context, dataFactory); if (operation.expression.expressionType === algebra_1.expressionTypes.EXISTENCE) { const transformMap = new Map(); const { hashFunction } = await this.mediatorHashBindings.mediate({ context }); const binder = async (bindings, done, push) => { const hash = hashFunction(bindings, variables); let hashData = transformMap.get(hash); if (bindings.getContextEntry(context_entries_2.KeysBindings.isAddition) ?? true) { if (hashData === undefined) { hashData = { count: 1, iterator: new asynciterator_1.EmptyIterator(), currentState: false, }; transformMap.set(hash, hashData); const materializedOperation = (0, utils_query_operation_1.materializeOperation)(operation.expression.input, bindings, algebraFactory, bindingsFactory); const intermediateOutputRaw = await this.mediatorQueryOperation.mediate({ operation: materializedOperation, context, }); const intermediateOutput = (0, utils_query_operation_1.getSafeBindings)(intermediateOutputRaw); let negBindings; let posBindings; if (operation.expression.not) { hashData.currentState = true; negBindings = bindingsFactory.fromBindings(bindings).setContextEntry(context_entries_2.KeysBindings.isAddition, true); posBindings = bindingsFactory.fromBindings(bindings).setContextEntry(context_entries_2.KeysBindings.isAddition, false); } else { negBindings = bindingsFactory.fromBindings(bindings).setContextEntry(context_entries_2.KeysBindings.isAddition, false); posBindings = bindingsFactory.fromBindings(bindings).setContextEntry(context_entries_2.KeysBindings.isAddition, true); } let count = 0; const constHashData = hashData; const transform = (item, doneTransform, pushTransform) => { if (item.getContextEntry(context_entries_2.KeysBindings.isAddition) ?? true) { if (count === 0) { constHashData.currentState = !operation.expression.not; for (let i = 0; i < constHashData.count; i++) { pushTransform(posBindings); } } count++; } else if (count > 1) { count--; } else if (count === 0) { doneTransform(); return; } else { count = 0; constHashData.currentState = operation.expression.not; for (let i = 0; i < constHashData.count; i++) { pushTransform(negBindings); } } doneTransform(); }; const it = intermediateOutput.bindingsStream.transform({ transform, // TODO [2026-10-01]: only prepend when the iterator becomes unreadable/is up to date prepend: operation.expression.not ? [bindings] : undefined, }); hashData.iterator = it; push(it); } else { hashData.count++; if (hashData.currentState) { push(new asynciterator_1.SingletonIterator(bindings)); } } } else { if (hashData === undefined) { done(); return; } if (hashData.currentState) { push(new asynciterator_1.SingletonIterator(bindings)); } if (hashData.count === 1) { hashData.iterator.close(); transformMap.delete(hash); } hashData.count--; } done(); }; const bindingsStream = new asynciterator_1.UnionIterator(output.bindingsStream.transform({ // eslint-disable-next-line ts/no-misused-promises transform: binder, }), { autoStart: false }); return { type: 'bindings', bindingsStream, metadata: output.metadata }; } const checkNestedExistence = (expression) => { if (expression.expressionType === algebra_1.expressionTypes.EXISTENCE) { throw new Error('Nested existence filters are currently not supported.'); } if (expression.args) { for (const arg of expression.args) { checkNestedExistence(arg); } } }; checkNestedExistence(operation.expression); const evaluator = await this.mediatorExpressionEvaluatorFactory .mediate({ algExpr: operation.expression, context }); const transform = async (item, next, push) => { try { const result = await evaluator.evaluateAsEBV(item); if (result) { push(item); } } catch (error) { // We ignore all Expression errors. // Other errors (likely programming mistakes) are still propagated. // // > Specifically, FILTERs eliminate any solutions that, // > when substituted into the expression, either result in // > an effective boolean value of false or produce an error. // > ... // > These errors have no effect outside of FILTER evaluation. // https://www.w3.org/TR/sparql11-query/#expressions if ((0, utils_expression_evaluator_1.isExpressionError)(error)) { // In many cases, this is a user error, where the user should manually cast the variable to a string. // In order to help users debug this, we should report these errors via the logger as warnings. this.logWarn(context, 'Error occurred while filtering.', () => ({ error, bindings: (0, utils_bindings_factory_1.bindingsToString)(item) })); } else { bindingsStream.emit('error', error); } } next(); }; const bindingsStream = output.bindingsStream // eslint-disable-next-line ts/no-misused-promises .transform({ transform, autoStart: false }); return { type: 'bindings', bindingsStream, metadata: output.metadata }; } } exports.ActorQueryOperationFilter = ActorQueryOperationFilter; //# sourceMappingURL=ActorQueryOperationFilter.js.map