@incremunica/actor-query-operation-filter
Version:
An incremental filter query-operation actor
176 lines • 9.54 kB
JavaScript
;
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