Skip to content

Commit

Permalink
no extension functions
Browse files Browse the repository at this point in the history
  • Loading branch information
jitsedesmet committed Oct 8, 2023
1 parent d563bd8 commit 8353dec
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 190 deletions.
141 changes: 0 additions & 141 deletions engines/query-sparql/test/QuerySparql-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,147 +341,6 @@ describe('System test: QuerySparql', () => {
});
});

describe('extension function', () => {
let funcAllow: string;
let store: Store;
let baseFunctions: Record<string, (args: RDF.Term[]) => Promise<RDF.Term>>;
let baseFunctionCreator: (functionName: RDF.NamedNode) => ((args: RDF.Term[]) => Promise<RDF.Term>) | undefined;
let quads: RDF.Quad[];
let stringType: RDF.NamedNode;
let booleanType: RDF.NamedNode;
let integerType: RDF.NamedNode;
beforeEach(() => {
stringType = DF.namedNode('http://www.w3.org/2001/XMLSchema#string');
booleanType = DF.namedNode('http://www.w3.org/2001/XMLSchema#boolean');
integerType = DF.namedNode('http://www.w3.org/2001/XMLSchema#integer');
funcAllow = 'allowAll';
baseFunctions = {
'http://example.org/functions#allowAll': async(args: RDF.Term[]) => DF.literal('true', booleanType),
};
baseFunctionCreator = (functionName: RDF.NamedNode) =>
async(args: RDF.Term[]) => DF.literal('true', booleanType);
store = new Store();
quads = [
DF.quad(DF.namedNode('ex:a'), DF.namedNode('ex:p1'), DF.literal('apple', stringType)),
DF.quad(DF.namedNode('ex:a'), DF.namedNode('ex:p2'), DF.literal('APPLE', stringType)),
DF.quad(DF.namedNode('ex:a'), DF.namedNode('ex:p3'), DF.literal('Apple', stringType)),
DF.quad(DF.namedNode('ex:a'), DF.namedNode('ex:p4'), DF.literal('aPPLE', stringType)),
];
store.addQuads(quads);
});
const baseQuery = (funcName: string) => `PREFIX func: <http://example.org/functions#>
SELECT * WHERE {
?s ?p ?o.
FILTER (func:${funcName}(?o))
}`;

it('rejects when record does not match', async() => {
const context = <any> { sources: [ store ]};
context.extensionFunctions = baseFunctions;
await expect(engine.query(baseQuery('nonExist'), context)).rejects.toThrow('Unknown named operator');
});

it('rejects when creator returns null', async() => {
const context = <any> { sources: [ store ]};
context.extensionFunctionCreator = () => null;
await expect(engine.query(baseQuery('nonExist'), context)).rejects.toThrow('Unknown named operator');
});

it('with results and pointless custom filter given by creator', async() => {
const context = <any> { sources: [ store ]};
context.extensionFunctionCreator = baseFunctionCreator;
const result = <QueryBindings> await engine.query(baseQuery(funcAllow), context);
expect((await arrayifyStream(await result.execute())).length).toEqual(store.size);
});

it('with results and pointless custom filter given by record', async() => {
const context = <any> { sources: [ store ]};
context.extensionFunctions = baseFunctions;
const result = <QueryBindings> await engine.query(baseQuery(funcAllow), context);
expect((await arrayifyStream(await result.execute())).length).toEqual(4);
});

it('with results but all filtered away', async() => {
const context = <any> { sources: [ store ]};
context.extensionFunctionCreator = () => () =>
DF.literal('false', booleanType);
const result = <QueryBindings> await engine.query(baseQuery('rejectAll'), context);
expect(await arrayifyStream(await result.execute())).toEqual([]);
});

it('throws error when supplying both record and creator', async() => {
const context = <any> { sources: [ store ]};
context.extensionFunctions = baseFunctions;
context.extensionFunctionCreator = baseFunctionCreator;
await expect(engine.query(baseQuery(funcAllow), context)).rejects
.toThrow('Illegal simultaneous usage of extensionFunctionCreator and extensionFunctions in context');
});

it('handles complex queries with BIND to', async() => {
const context = <any> { sources: [ store ]};
const complexQuery = `PREFIX func: <http://example.org/functions#>
SELECT ?caps WHERE {
?s ?p ?o.
BIND (func:to-upper-case(?o) AS ?caps)
}
`;
context.extensionFunctions = {
async 'http://example.org/functions#to-upper-case'(args: RDF.Term[]) {
const arg = args[0];
if (arg.termType === 'Literal' && arg.datatype.equals(DF.literal('', stringType).datatype)) {
return DF.literal(arg.value.toUpperCase(), stringType);
}
return arg;
},
};
const bindingsStream = await engine.queryBindings(complexQuery, context);
expect((await bindingsStream.toArray()).map(res => res.get(DF.variable('caps'))!.value)).toEqual(
quads.map(q => q.object.value.toUpperCase()),
);
});

describe('handles complex queries with groupBy', () => {
let context: any;
let complexQuery: string;
let extensionBuilder: (timout: boolean) => (args: RDF.Term[]) => Promise<RDF.Term>;

beforeEach(() => {
context = <any> { sources: [ store ]};
complexQuery = `PREFIX func: <http://example.org/functions#>
SELECT (SUM(func:count-chars(?o)) AS ?sum) WHERE {
?s ?p ?o.
}
`;
extensionBuilder = (timout: boolean) => async(args: RDF.Term[]) => {
const arg = args[0];
if (arg.termType === 'Literal' && arg.datatype.equals(DF.literal('', stringType).datatype)) {
if (timout) {
await new Promise(resolve => setTimeout(resolve, 1));
}
return DF.literal(String(arg.value.length), integerType);
}
return arg;
};
});

it('can be evaluated', async() => {
context.extensionFunctions = {
'http://example.org/functions#count-chars': extensionBuilder(false),
};
const bindingsStream = await engine.queryBindings(complexQuery, context);
expect((await bindingsStream.toArray()).map(res => res.get(DF.variable('sum'))!.value)).toEqual([ '20' ]);
});

it('can be truly async', async() => {
context.extensionFunctions = {
'http://example.org/functions#count-chars': extensionBuilder(true),
};
const bindingsStream = await engine.queryBindings(complexQuery, context);
expect((await bindingsStream.toArray()).map(res => res.get(DF.variable('sum'))!.value)).toEqual([ '20' ]);
});
});
});

describe('functionArgumentsCache', () => {
let query: string;
let stringType: RDF.NamedNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ActorQueryOperation } from '@comunica/bus-query-operation';
import { KeysInitQuery } from '@comunica/context-entries';
import { ActionContext, Bus } from '@comunica/core';
import * as sparqlee from '@comunica/expression-evaluator';
import { isExpressionError } from '@comunica/expression-evaluator';
import { ExpressionEvaluatorFactory, isExpressionError } from '@comunica/expression-evaluator';
import type { IQueryOperationResultBindings, Bindings } from '@comunica/types';
import { ArrayIterator } from 'asynciterator';
import { DataFactory } from 'rdf-data-factory';
Expand Down Expand Up @@ -85,9 +85,23 @@ describe('ActorQueryOperationFilterSparqlee', () => {
describe('An ActorQueryOperationFilterSparqlee instance', () => {
let actor: ActorQueryOperationFilterSparqlee;
let factory: Factory;
let expressionEvaluatorFactory: ExpressionEvaluatorFactory;

beforeEach(() => {
actor = new ActorQueryOperationFilterSparqlee({ name: 'actor', bus, mediatorQueryOperation });
expressionEvaluatorFactory = new ExpressionEvaluatorFactory({
mediatorQueryOperation,
mediatorBindingsAggregatorFactory: <any> {
mediate(arg: any) {
throw new Error('Not implemented');
},
},
});
actor = new ActorQueryOperationFilterSparqlee({
name: 'actor',
bus,
mediatorQueryOperation,
expressionEvaluatorFactory,
});
factory = new Factory();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@ import { Algebra } from 'sparqlalgebrajs';
import { ActorQueryOperationGroup } from '../lib';
import { GroupsState } from '../lib/GroupsState';
import '@comunica/jest';
import type {

Check failure on line 14 in packages/actor-query-operation-group/test/ActorQueryOperationGroup-test.ts

View workflow job for this annotation

GitHub Actions / lint

`@comunica/bus-bindings-aggeregator-factory` import should occur before import of `@comunica/bus-query-operation`
IActionBindingsAggregatorFactory,
IActorBindingsAggregatorFactoryOutput,
} from '@comunica/bus-bindings-aggeregator-factory';
import {

Check failure on line 18 in packages/actor-query-operation-group/test/ActorQueryOperationGroup-test.ts

View workflow job for this annotation

GitHub Actions / lint

`@comunica/actor-bindings-aggregator-factory-wildcard-count/lib/WildcardCountAggregator` import should occur before import of `@comunica/bindings-factory`
WildcardCountAggregator,
} from '@comunica/actor-bindings-aggregator-factory-wildcard-count/lib/WildcardCountAggregator';
import { CountAggregator } from '@comunica/actor-bindings-aggregator-factory-count/lib/CountAggregator';

Check failure on line 21 in packages/actor-query-operation-group/test/ActorQueryOperationGroup-test.ts

View workflow job for this annotation

GitHub Actions / lint

`@comunica/actor-bindings-aggregator-factory-count/lib/CountAggregator` import should occur before import of `@comunica/bindings-factory`
import { SumAggregator } from '@comunica/actor-bindings-aggregator-factory-sum/lib/SumAggregator';

Check failure on line 22 in packages/actor-query-operation-group/test/ActorQueryOperationGroup-test.ts

View workflow job for this annotation

GitHub Actions / lint

`@comunica/actor-bindings-aggregator-factory-sum/lib/SumAggregator` import should occur before import of `@comunica/bindings-factory`
import { AverageAggregator } from '@comunica/actor-bindings-aggregator-factory-average/lib/AverageAggregator';

Check failure on line 23 in packages/actor-query-operation-group/test/ActorQueryOperationGroup-test.ts

View workflow job for this annotation

GitHub Actions / lint

`@comunica/actor-bindings-aggregator-factory-average/lib/AverageAggregator` import should occur before import of `@comunica/bindings-factory`
import { MinAggregator } from '@comunica/actor-bindings-aggregator-factory-min/lib/MinAggregator';

Check failure on line 24 in packages/actor-query-operation-group/test/ActorQueryOperationGroup-test.ts

View workflow job for this annotation

GitHub Actions / lint

`@comunica/actor-bindings-aggregator-factory-min/lib/MinAggregator` import should occur before import of `@comunica/bindings-factory`
import { MaxAggregator } from '@comunica/actor-bindings-aggregator-factory-max/lib/MaxAggregator';

Check failure on line 25 in packages/actor-query-operation-group/test/ActorQueryOperationGroup-test.ts

View workflow job for this annotation

GitHub Actions / lint

`@comunica/actor-bindings-aggregator-factory-max/lib/MaxAggregator` import should occur before import of `@comunica/bindings-factory`
import { SampleAggregator } from '@comunica/actor-bindings-aggregator-factory-sample/lib/SampleAggregator';

Check failure on line 26 in packages/actor-query-operation-group/test/ActorQueryOperationGroup-test.ts

View workflow job for this annotation

GitHub Actions / lint

`@comunica/actor-bindings-aggregator-factory-sample/lib/SampleAggregator` import should occur before import of `@comunica/bindings-factory`
import {
GroupConcatAggregator,
} from '@comunica/actor-bindings-aggregator-factory-group-concat/lib/GroupConcatAggregator';

const DF = new DataFactory();
const BF = new BindingsFactory();
Expand Down Expand Up @@ -89,6 +105,45 @@ interface ICaseOutput {
actor: ActorQueryOperationGroup; bus: any; mediatorQueryOperation: any; op: IActionQueryOperation;
}

function aggregatorFactory({ expr, factory, context }: IActionBindingsAggregatorFactory):
IActorBindingsAggregatorFactoryOutput {
if (expr.aggregator === 'count') {
if (expr.expression.wildcard) {
return {
aggregator: new WildcardCountAggregator(expr, factory, context),
};
}
return {
aggregator: new CountAggregator(expr, factory, context),
};
} if (expr.aggregator === 'sum') {
return {
aggregator: new SumAggregator(expr, factory, context),
};
} if (expr.aggregator === 'avg') {
return {
aggregator: new AverageAggregator(expr, factory, context),
};
} if (expr.aggregator === 'min') {
return {
aggregator: new MinAggregator(expr, factory, context),
};
} if (expr.aggregator === 'max') {
return {
aggregator: new MaxAggregator(expr, factory, context),
};
} if (expr.aggregator === 'sample') {
return {
aggregator: new SampleAggregator(expr, factory, context),
};
} if (expr.aggregator === 'group_concat') {
return {
aggregator: new GroupConcatAggregator(expr, factory, context),
};
}
throw new Error(`Unsupported aggregator ${expr.aggregator}`);
}

function constructCase(
{ inputBindings, inputVariables = [], groupVariables = [], aggregates = [], inputOp }: ICaseOptions,
): ICaseOutput {
Expand Down Expand Up @@ -116,8 +171,9 @@ function constructCase(
const expressionEvaluatorFactory = new ExpressionEvaluatorFactory({
mediatorQueryOperation,
mediatorBindingsAggregatorFactory: <any> {
mediate(arg: any) {
throw new Error('Not implemented');
async mediate({ expr, factory, context }: IActionBindingsAggregatorFactory):
Promise<IActorBindingsAggregatorFactoryOutput> {
return aggregatorFactory({ expr, factory, context });
},
},
});
Expand Down Expand Up @@ -166,8 +222,9 @@ describe('ActorQueryOperationGroup', () => {
expressionEvaluatorFactory = new ExpressionEvaluatorFactory({
mediatorQueryOperation,
mediatorBindingsAggregatorFactory: <any> {
mediate(arg: any) {
throw new Error('Not implemented');
async mediate({ expr, factory, context }: IActionBindingsAggregatorFactory):
Promise<IActorBindingsAggregatorFactoryOutput> {
return aggregatorFactory({ expr, factory, context });
},
},
});
Expand Down
17 changes: 0 additions & 17 deletions packages/bus-query-operation/test/ActorQueryOperation-test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { BindingsFactory } from '@comunica/bindings-factory';
import { Bus } from '@comunica/core';
import { MetadataValidationState } from '@comunica/metadata';
import { ArrayIterator } from 'asynciterator';
import { ActorQueryOperation } from '..';

const BF = new BindingsFactory();
Expand Down Expand Up @@ -89,20 +88,4 @@ describe('ActorQueryOperation', () => {
expect(() => ActorQueryOperation.validateQueryOutput(<any>{ type: 'no-boolean' }, 'boolean')).toThrow();
});
});

describe('#getAsyncExpressionContext', () => {
let mediatorQueryOperation: any;

beforeEach(() => {
mediatorQueryOperation = {
mediate: (arg: any) => Promise.resolve({
bindingsStream: new ArrayIterator([], { autoStart: false }),
metadata: () => Promise.resolve({ cardinality: 0 }),
operated: arg,
type: 'bindings',
variables: [ 'a' ],
}),
};
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,6 @@ export class AsyncRecursiveEvaluator {
}

private async evalExistence(expr: E.Existence, mapping: RDF.Bindings): Promise<E.Term> {
if (!this.context.exists) {
throw new Err.NoExistenceHook();
}

return new E.BooleanLiteral(await this.context.exists(expr.expression, mapping));
}

Expand Down
6 changes: 0 additions & 6 deletions packages/expression-evaluator/lib/util/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,6 @@ export class NoAggregator extends Error {
}
}

export class NoExistenceHook extends Error {
public constructor() {
super('EXISTS found, but no existence hook provided.');
}
}

function pp<T>(object: T): string {
return JSON.stringify(object);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ describe('recursive evaluators', () => {
expect(await evaluator.evaluate(new E.IntegerLiteral(1), BF.bindings())).toEqual(new E.IntegerLiteral(1));
});

it('is not able to evaluate existence by default', async() => {
await expect(evaluator.evaluate(new E.Existence({
it('is able to evaluate existence by default', async() => {
expect(await evaluator.evaluate(new E.Existence({
type: types.EXPRESSION,
expressionType: expressionTypes.EXISTENCE,
not: false,
Expand All @@ -36,17 +36,23 @@ describe('recursive evaluators', () => {
variables: [],
bindings: [],
},
}), BF.bindings())).rejects.toThrow(Err.NoExistenceHook);
}), BF.bindings())).toEqual(new E.BooleanLiteral(false));
});

it('is able to evaluate existence if configured', async() => {
const customEvaluator = new AsyncRecursiveEvaluator(
getDefaultCompleteEEContext(getMockEEActionContext()),
getMockEEFactory().createEvaluator(translate('SELECT * WHERE { ?s ?p ?o FILTER (1 + 1)}').input.expression,
getMockEEActionContext()),
const customEvaluator = getMockEEFactory().createEvaluator(
translate('SELECT * WHERE { ?s ?p ?o FILTER (1 + 1)}').input.expression,
getMockEEActionContext(),
{
exists: async() => true,
},
);
const customAsyncRecursiveEvaluator = new AsyncRecursiveEvaluator(
customEvaluator.context,
customEvaluator,
);

expect(await customEvaluator.evaluate(new E.Existence({
expect(await customAsyncRecursiveEvaluator.evaluate(new E.Existence({
type: types.EXPRESSION,
expressionType: expressionTypes.EXISTENCE,
not: false,
Expand All @@ -73,13 +79,19 @@ describe('recursive evaluators', () => {
});

it('is able to evaluate aggregates if configured', async() => {
const customEvaluator = new AsyncRecursiveEvaluator(
getDefaultCompleteEEContext(getMockEEActionContext()),
getMockEEFactory().createEvaluator(translate('SELECT * WHERE { ?s ?p ?o FILTER (1 + 1)}').input.expression,
getMockEEActionContext()),
const customEvaluator = getMockEEFactory().createEvaluator(
translate('SELECT * WHERE { ?s ?p ?o FILTER (1 + 1)}').input.expression,
getMockEEActionContext(),
{
aggregate: async() => DF.literal('42'),
},
);
const customAsyncRecursiveEvaluator = new AsyncRecursiveEvaluator(
customEvaluator.context,
customEvaluator,
);

expect(await customEvaluator.evaluate(new E.Aggregate('count', {
expect(await customAsyncRecursiveEvaluator.evaluate(new E.Aggregate('count', {
type: types.EXPRESSION,
expressionType: expressionTypes.AGGREGATE,
aggregator: 'count',
Expand Down
Loading

0 comments on commit 8353dec

Please sign in to comment.