@comunica/actor-query-operation-group
Version:
A group query-operation actor
75 lines • 4.24 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActorQueryOperationGroup = 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_algebra_1 = require("@comunica/utils-algebra");
const utils_bindings_factory_1 = require("@comunica/utils-bindings-factory");
const utils_query_operation_1 = require("@comunica/utils-query-operation");
const asynciterator_1 = require("asynciterator");
const GroupsState_1 = require("./GroupsState");
/**
* A comunica Group Query Operation Actor.
*/
class ActorQueryOperationGroup extends bus_query_operation_1.ActorQueryOperationTypedMediated {
mediatorMergeBindingsContext;
mediatorBindingsAggregatorFactory;
constructor(args) {
super(args, utils_algebra_1.Algebra.Types.GROUP);
this.mediatorMergeBindingsContext = args.mediatorMergeBindingsContext;
this.mediatorBindingsAggregatorFactory = args.mediatorBindingsAggregatorFactory;
}
async testOperation() {
return (0, core_1.passTestVoid)();
}
async runOperation(operation, context) {
const dataFactory = context.getSafe(context_entries_1.KeysInitQuery.dataFactory);
const bindingsFactory = await utils_bindings_factory_1.BindingsFactory.create(this.mediatorMergeBindingsContext, context, dataFactory);
// Get result stream for the input query
const { input, aggregates } = operation;
const outputRaw = await this.mediatorQueryOperation.mediate({ operation: input, context });
const output = (0, utils_query_operation_1.getSafeBindings)(outputRaw);
// The variables in scope are the variables on which we group, i.e. pattern.variables.
// For 'GROUP BY ?x, ?z', this is [?x, ?z], for 'GROUP by expr(?x) as ?e' this is [?e].
// But also in scope are the variables defined by the aggregations, since GROUP has to handle this.
const variables = [
...operation.variables,
...aggregates.map(agg => agg.variable),
].map(variable => ({ variable, canBeUndef: false }));
const variablesInner = (await output.metadata()).variables.map(v => v.variable);
// Wrap a new promise inside an iterator that completes when the stream has ended or when an error occurs
const bindingsStream = new asynciterator_1.TransformIterator(() => new Promise((resolve, reject) => {
const groups = new GroupsState_1.GroupsState(operation, this.mediatorBindingsAggregatorFactory, context, bindingsFactory, variablesInner);
// Phase 2: Collect aggregator results
// We can only return when the binding stream ends, when that happens
// we return the identified groups. Which are nothing more than Bindings
// of the grouping variables merged with the aggregate variables
// eslint-disable-next-line ts/no-misused-promises
output.bindingsStream.on('end', async () => {
try {
const bindingsStreamInner = new asynciterator_1.ArrayIterator(await groups.collectResults(), { autoStart: false });
resolve(bindingsStreamInner);
}
catch (error) {
reject(error);
}
});
// Make sure to propagate any errors in the binding stream
output.bindingsStream.on('error', reject);
// Phase 1: Consume the stream, identify the groups and populate the aggregators.
// We need to bind this after the 'error' and 'end' listeners to avoid the
// stream having ended before those listeners are bound.
output.bindingsStream.on('data', (bindings) => {
groups.consumeBindings(bindings).catch(reject);
});
}), { autoStart: false });
return {
type: 'bindings',
bindingsStream,
metadata: async () => ({ ...await output.metadata(), variables }),
};
}
}
exports.ActorQueryOperationGroup = ActorQueryOperationGroup;
//# sourceMappingURL=ActorQueryOperationGroup.js.map