From c40f019dbc06afea949e9de7342feead00adb0d7 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 18 Oct 2022 09:14:09 +0200 Subject: [PATCH 001/189] TREE traversal actor support for filter function added, but filter function are defined. --- .../lib/ActorExtractLinksTree.ts | 89 +++++- .../test/ActorExtractLinksTree-test.ts | 254 +++++++++++++++++- .../lib/Keys.ts | 10 +- packages/types-link-traversal/lib/index.ts | 1 + .../linkTraversalOptimizationLinkFilter.ts | 12 + 5 files changed, 350 insertions(+), 16 deletions(-) create mode 100644 packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index d5ffb9d10..fe7d24061 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -5,18 +5,41 @@ import type { import { ActorExtractLinks } from '@comunica/bus-extract-links'; import type { ILink } from '@comunica/bus-rdf-resolve-hypermedia-links'; import type { IActorTest } from '@comunica/core'; +import { LinkTraversalFilterOperator, LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; +import { KeyOptimizationLinkTraversal } from '@comunica/context-entries-link-traversal'; const DF = new DataFactory(); +interface RelationDescription { + subject: string; value: any; operator: LinkTraversalFilterOperator; +} + /** * A comunica Extract Links Tree Extract Links Actor. */ export class ActorExtractLinksTree extends ActorExtractLinks { public static readonly aNodeType = DF.namedNode('https://w3id.org/tree#node'); public static readonly aRelation = DF.namedNode('https://w3id.org/tree#relation'); + private static readonly rdfTypeNode = DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'); + private static readonly aTreePath = DF.namedNode('https://w3id.org/tree#path'); + private static readonly aTreeValue = DF.namedNode('https://w3id.org/tree#value'); + + private static readonly treeGreaterThan: [RDF.NamedNode, LinkTraversalFilterOperator] = [DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), LinkTraversalFilterOperator.GreaterThan]; + private static readonly treeGreaterThanOrEqual: [RDF.NamedNode, LinkTraversalFilterOperator] = [DF.namedNode('https://w3id.org/tree#GreaterThanOrEqualToRelation'), LinkTraversalFilterOperator.GreaterThanOrEqual]; + private static readonly treeLessThan: [RDF.NamedNode, LinkTraversalFilterOperator] = [DF.namedNode('https://w3id.org/tree#LessThanRelation'), LinkTraversalFilterOperator.LowerThan]; + private static readonly treeLessThanEqual: [RDF.NamedNode, LinkTraversalFilterOperator] = [DF.namedNode('https://w3id.org/tree#LessThanOrEqualToRelation'), LinkTraversalFilterOperator.LowerThanOrEqual]; + private static readonly treeEqual: [RDF.NamedNode, LinkTraversalFilterOperator] = [DF.namedNode('https://w3id.org/tree#EqualToRelation'), LinkTraversalFilterOperator.Equal]; + + private static readonly treeOperators: [RDF.NamedNode, LinkTraversalFilterOperator][] = [ + ActorExtractLinksTree.treeEqual, + ActorExtractLinksTree.treeLessThanEqual, + ActorExtractLinksTree.treeLessThan, + ActorExtractLinksTree.treeGreaterThanOrEqual, + ActorExtractLinksTree.treeGreaterThan, + ]; public constructor(args: IActorExtractLinksArgs) { super(args); @@ -31,7 +54,10 @@ export class ActorExtractLinksTree extends ActorExtractLinks { const metadata = action.metadata; const currentNodeUrl = action.url; const pageRelationNodes: Set = new Set(); + const relationDescriptions: Map = new Map(); const nodeLinks: [string, string][] = []; + const filterFunctions: LinkTraversalOptimizationLinkFilter[] = typeof action.context.get(KeyOptimizationLinkTraversal.filterFunctions) !== 'undefined' ? + action.context.get(KeyOptimizationLinkTraversal.filterFunctions) : []; const links: ILink[] = []; // Forward errors @@ -42,14 +68,28 @@ export class ActorExtractLinksTree extends ActorExtractLinks { this.getTreeQuadsRawRelations(quad, currentNodeUrl, pageRelationNodes, - nodeLinks)); + nodeLinks, + relationDescriptions)); // Resolve to discovered links metadata.on('end', () => { // Validate if the node forward have the current node as implicit subject - for (const [ nodeValue, link ] of nodeLinks) { + for (const [nodeValue, link] of nodeLinks) { if (pageRelationNodes.has(nodeValue)) { - links.push({ url: link }); + const relationDescription = relationDescriptions.get(nodeValue) + if (typeof relationDescription !== 'undefined' && filterFunctions.length !== 0) { + let addLink: boolean = true; + for (let filter of filterFunctions) { + if (!filter(relationDescription.subject, relationDescription.value, relationDescription.operator)) { + addLink = false + } + } + if (addLink) { + links.push({ url: link }); + } + } else { + links.push({ url: link }); + } } } resolve({ links }); @@ -72,13 +112,54 @@ export class ActorExtractLinksTree extends ActorExtractLinks { url: string, pageRelationNodes: Set, nodeLinks: [string, string][], + relationDescriptions: Map, ): void { // If it's a relation of the current node if (quad.subject.value === url && quad.predicate.equals(ActorExtractLinksTree.aRelation)) { pageRelationNodes.add(quad.object.value); // If it's a node forward } else if (quad.predicate.equals(ActorExtractLinksTree.aNodeType)) { - nodeLinks.push([ quad.subject.value, quad.object.value ]); + nodeLinks.push([quad.subject.value, quad.object.value]); + } + // set the operator of the relation + else if (quad.predicate.equals(ActorExtractLinksTree.rdfTypeNode)) { + let operator: LinkTraversalFilterOperator = LinkTraversalFilterOperator.Equal; + for (const treeOperator of ActorExtractLinksTree.treeOperators) { + if (quad.object.equals(treeOperator[0])) { + operator = treeOperator[1] + } + } + this.addRelationDescription(relationDescriptions, 'operator', quad, undefined, '', operator); + }// set the subject of the relation condition + else if (quad.predicate.equals(ActorExtractLinksTree.aTreePath)) { + this.addRelationDescription(relationDescriptions, 'subject', quad, undefined, quad.object.value); + } + // set the value of the relation condition + else if (quad.predicate.equals(ActorExtractLinksTree.aTreeValue)) { + this.addRelationDescription(relationDescriptions, 'value', quad, quad.object.value); + } + } + + private addRelationDescription(relationDescriptions: Map, field: string, quad: RDF.Quad, value: any = undefined, subject: string = '', operator: LinkTraversalFilterOperator = LinkTraversalFilterOperator.Equal) { + const newDescription = relationDescriptions.get(quad.subject.value); + if (typeof newDescription === 'undefined') { + relationDescriptions.set(quad.subject.value, { value: value, subject: subject, operator: operator }); + } else { + switch (field) { + case "value":{ + newDescription[field as keyof typeof newDescription] = value; + break; + } + case "subject": { + newDescription[field as keyof typeof newDescription] = subject; + break; + } + case "operator":{ + newDescription[field as keyof typeof newDescription] = operator; + break; + } + } + relationDescriptions.set(quad.subject.value, newDescription); } } } diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index f5dcf37a7..5809725dd 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -3,6 +3,9 @@ import { ActionContext, Bus } from '@comunica/core'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; +import { KeyOptimizationLinkTraversal } from '@comunica/context-entries-link-traversal'; +import { LinkTraversalFilterOperator } from '@comunica/types-link-traversal'; + const stream = require('streamify-array'); const DF = new DataFactory(); @@ -42,7 +45,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { actor = new ActorExtractLinksTree({ name: 'actor', bus }); }); - it('should return the links of a TREE with one relation', async() => { + it('should return the links of a TREE with one relation', async () => { const expectedUrl = 'http://foo.com'; const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), @@ -67,11 +70,11 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actor.run(action); - expect(result).toEqual({ links: [{ url: expectedUrl }]}); + expect(result).toEqual({ links: [{ url: expectedUrl }] }); }); - it('should return the links of a TREE with multiple relations', async() => { - const expectedUrl = [ 'http://foo.com', 'http://bar.com', 'http://example.com', 'http://example.com' ]; + it('should return the links of a TREE with multiple relations', async () => { + const expectedUrl = ['http://foo.com', 'http://bar.com', 'http://example.com', 'http://example.com']; const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('https://w3id.org/tree#relation'), @@ -126,7 +129,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toEqual({ links: expectedUrl.map(value => { return { url: value }; }) }); }); - it('should return the links of a TREE with one complex relation', async() => { + it('should return the links of a TREE with one complex relation', async () => { const expectedUrl = 'http://foo.com'; const input = stream([ DF.quad(DF.namedNode(treeUrl), @@ -162,11 +165,11 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actor.run(action); - expect(result).toEqual({ links: [{ url: expectedUrl }]}); + expect(result).toEqual({ links: [{ url: expectedUrl }] }); }); - it('should return the links of a TREE with multiple relations combining blank nodes and named nodes', async() => { - const expectedUrl = [ 'http://foo.com', 'http://bar.com', 'http://example.com', 'http://example.com' ]; + it('should return the links of a TREE with multiple relations combining blank nodes and named nodes', async () => { + const expectedUrl = ['http://foo.com', 'http://bar.com', 'http://example.com', 'http://example.com']; const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('https://w3id.org/tree#relation'), @@ -217,6 +220,237 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toEqual({ links: expectedUrl.map(value => { return { url: value }; }) }); }); }); + + describe('The ActorExtractLinksExtractLinksTree run method with filter', () => { + let actor: ActorExtractLinksTree; + const treeUrl = 'ex:s'; + + beforeEach(() => { + actor = new ActorExtractLinksTree({ name: 'actor', bus }); + }); + + it('should return the links of a TREE with one relation and filter that returns always true', async () => { + const expectedUrl = 'http://foo.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#path'), + DF.namedNode('ex:path'), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#value'), + DF.literal("value"), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + DF.namedNode("https://w3id.org/tree#GreaterThanRelation"), + DF.namedNode('ex:gx')) + ]); + const mock_filter = jest.fn((_subject, _value, _operator) => true ); + const context = new ActionContext({ + [KeysRdfResolveQuadPattern.source.name]: treeUrl, + [KeyOptimizationLinkTraversal.filterFunctions.name]: [mock_filter] + }); + + const action = { url: treeUrl, metadata: input, requestTime: 0, context }; + + const result = await actor.run(action); + + expect(result).toEqual({ links: [{ url: expectedUrl }] }); + expect(mock_filter).toBeCalledTimes(1); + expect(mock_filter).toBeCalledWith('ex:path', "value", LinkTraversalFilterOperator.GreaterThan) + }); + + it('should return no links of a TREE with one relation and filter that returns always false', async () => { + const expectedUrl = 'http://foo.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + DF.namedNode("https://w3id.org/tree#GreaterThanRelation"), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#path'), + DF.namedNode('ex:path'), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#value'), + DF.literal("value"), + DF.namedNode('ex:gx')), + + + ]); + const mock_filter = jest.fn((_subject, _value, _operator) => false ); + const context = new ActionContext({ + [KeysRdfResolveQuadPattern.source.name]: treeUrl, + [KeyOptimizationLinkTraversal.filterFunctions.name]: [mock_filter] + }); + + const action = { url: treeUrl, metadata: input, requestTime: 0, context }; + + const result = await actor.run(action); + + expect(result).toEqual({ links: [] }); + expect(mock_filter).toBeCalledTimes(1); + expect(mock_filter).toBeCalledWith('ex:path', "value", LinkTraversalFilterOperator.GreaterThan) + }); + + it('should return the links of a TREE with one relation and multiple filter that returns always true', async () => { + const expectedUrl = 'http://foo.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#value'), + DF.literal("value"), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + DF.namedNode("https://w3id.org/tree#GreaterThanRelation"), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#path'), + DF.namedNode('ex:path'), + DF.namedNode('ex:gx')), + ]); + const mock_filters = [ + jest.fn((_subject, _value, _operator) => true ), + jest.fn((_subject, _value, _operator) => true ), + jest.fn((_subject, _value, _operator) => true ), + jest.fn((_subject, _value, _operator) => true ), + ]; + const context = new ActionContext({ + [KeysRdfResolveQuadPattern.source.name]: treeUrl, + [KeyOptimizationLinkTraversal.filterFunctions.name]: mock_filters + }); + + const action = { url: treeUrl, metadata: input, requestTime: 0, context }; + + const result = await actor.run(action); + + expect(result).toEqual({ links: [{ url: expectedUrl }] }); + for (let mock of mock_filters){ + expect(mock).toBeCalledTimes(1); + expect(mock).toBeCalledWith('ex:path', "value", LinkTraversalFilterOperator.GreaterThan) + } + + }); + + it('should return no links of a TREE with one relation and multiple filter that returns true and one that return false', async () => { + const expectedUrl = 'http://foo.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#value'), + DF.literal("value"), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#path'), + DF.namedNode('ex:path'), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + DF.namedNode("https://w3id.org/tree#GreaterThanRelation"), + DF.namedNode('ex:gx')) + ]); + const mock_filters = [ + jest.fn((_subject, _value, _operator) => true ), + jest.fn((_subject, _value, _operator) => true ), + jest.fn((_subject, _value, _operator) => false ), + jest.fn((_subject, _value, _operator) => true ), + ]; + const context = new ActionContext({ + [KeysRdfResolveQuadPattern.source.name]: treeUrl, + [KeyOptimizationLinkTraversal.filterFunctions.name]: mock_filters + }); + + const action = { url: treeUrl, metadata: input, requestTime: 0, context }; + + const result = await actor.run(action); + + expect(result).toEqual({ links: [] }); + for (let mock of mock_filters){ + expect(mock).toBeCalledTimes(1); + expect(mock).toBeCalledWith('ex:path', "value", LinkTraversalFilterOperator.GreaterThan) + } + + }); + }); describe('The ActorExtractLinksExtractLinksTree test method', () => { let actor: ActorExtractLinksTree; const treeUrl = 'ex:s'; @@ -225,7 +459,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { actor = new ActorExtractLinksTree({ name: 'actor', bus }); }); - it('should test when giving a TREE', async() => { + it('should test when giving a TREE', async () => { const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), @@ -243,7 +477,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toBe(true); }); - it('should test when not given a TREE', async() => { + it('should test when not given a TREE', async () => { const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), diff --git a/packages/context-entries-link-traversal/lib/Keys.ts b/packages/context-entries-link-traversal/lib/Keys.ts index 99f7b319b..85f804507 100644 --- a/packages/context-entries-link-traversal/lib/Keys.ts +++ b/packages/context-entries-link-traversal/lib/Keys.ts @@ -1,6 +1,5 @@ import { ActionContextKey } from '@comunica/core'; -import type { AnnotateSourcesType } from '@comunica/types-link-traversal'; - +import type { AnnotateSourcesType, LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; /** * When adding entries to this file, also add a shortcut for them in the contextKeyShortcuts TSDoc comment in * ActorIniQueryBase in @comunica/actor-init-query if it makes sense to use this entry externally. @@ -19,3 +18,10 @@ export const KeysRdfResolveHypermediaLinks = { '@comunica/bus-rdf-resolve-hypermedia-links:annotateSources', ), }; + +export const KeyOptimizationLinkTraversal = { + /** + * Filter function to optimized the link traversal + */ + filterFunctions: new ActionContextKey('@comunica/optimization-link-traversal:filterFunction'), +}; diff --git a/packages/types-link-traversal/lib/index.ts b/packages/types-link-traversal/lib/index.ts index 394061f80..2c5301af4 100644 --- a/packages/types-link-traversal/lib/index.ts +++ b/packages/types-link-traversal/lib/index.ts @@ -1 +1,2 @@ export * from './AnnotateSourcesType'; +export * from './linkTraversalOptimizationLinkFilter'; diff --git a/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts b/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts new file mode 100644 index 000000000..2b61c6c94 --- /dev/null +++ b/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts @@ -0,0 +1,12 @@ +export type LinkTraversalOptimizationLinkFilter = (subject: string, value: any, operator: LinkTraversalFilterOperator) => boolean; + +export enum LinkTraversalFilterOperator { + GreaterThan, + GreaterThanOrEqual, + + LowerThan, + LowerThanOrEqual, + + Equal, + Not +} From ada26f67016d890e07a41d5d8f62283aaa05d389 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 18 Oct 2022 09:23:28 +0200 Subject: [PATCH 002/189] bus for link travel optimization and some lint changes that were not commited last time --- .../README.md | 2 +- .../lib/ActorExtractLinksTree.ts | 92 +++++---- .../package.json | 4 +- .../test/ActorExtractLinksTree-test.ts | 188 +++++++++--------- .../bus-optimize-link-traversal/.npmignore | 0 .../bus-optimize-link-traversal/README.md | 27 +++ .../lib/ActorOptimizeLinkTraversal.ts | 42 ++++ .../bus-optimize-link-traversal/lib/index.ts | 1 + .../bus-optimize-link-traversal/package.json | 40 ++++ .../lib/Keys.ts | 4 +- .../linkTraversalOptimizationLinkFilter.ts | 4 +- 11 files changed, 269 insertions(+), 135 deletions(-) create mode 100644 packages/bus-optimize-link-traversal/.npmignore create mode 100644 packages/bus-optimize-link-traversal/README.md create mode 100644 packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts create mode 100644 packages/bus-optimize-link-traversal/lib/index.ts create mode 100644 packages/bus-optimize-link-traversal/package.json diff --git a/packages/actor-extract-links-extract-tree/README.md b/packages/actor-extract-links-extract-tree/README.md index c1c69a173..7311f8198 100644 --- a/packages/actor-extract-links-extract-tree/README.md +++ b/packages/actor-extract-links-extract-tree/README.md @@ -22,7 +22,7 @@ After installing, this package can be added to your engine's configuration as fo { "@context": [ ... - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-extract-links-tree/^2.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-extract-links-tree/^0.0.1/components/context.jsonld" ], "actors": [ ... diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index fe7d24061..bc27e7223 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -4,15 +4,16 @@ import type { } from '@comunica/bus-extract-links'; import { ActorExtractLinks } from '@comunica/bus-extract-links'; import type { ILink } from '@comunica/bus-rdf-resolve-hypermedia-links'; +import { KeyOptimizationLinkTraversal } from '@comunica/context-entries-link-traversal'; import type { IActorTest } from '@comunica/core'; -import { LinkTraversalFilterOperator, LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; +import type { LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; +import { LinkTraversalFilterOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; -import { KeyOptimizationLinkTraversal } from '@comunica/context-entries-link-traversal'; const DF = new DataFactory(); -interface RelationDescription { +interface IRelationDescription { subject: string; value: any; operator: LinkTraversalFilterOperator; } @@ -27,11 +28,25 @@ export class ActorExtractLinksTree extends ActorExtractLinks { private static readonly aTreePath = DF.namedNode('https://w3id.org/tree#path'); private static readonly aTreeValue = DF.namedNode('https://w3id.org/tree#value'); - private static readonly treeGreaterThan: [RDF.NamedNode, LinkTraversalFilterOperator] = [DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), LinkTraversalFilterOperator.GreaterThan]; - private static readonly treeGreaterThanOrEqual: [RDF.NamedNode, LinkTraversalFilterOperator] = [DF.namedNode('https://w3id.org/tree#GreaterThanOrEqualToRelation'), LinkTraversalFilterOperator.GreaterThanOrEqual]; - private static readonly treeLessThan: [RDF.NamedNode, LinkTraversalFilterOperator] = [DF.namedNode('https://w3id.org/tree#LessThanRelation'), LinkTraversalFilterOperator.LowerThan]; - private static readonly treeLessThanEqual: [RDF.NamedNode, LinkTraversalFilterOperator] = [DF.namedNode('https://w3id.org/tree#LessThanOrEqualToRelation'), LinkTraversalFilterOperator.LowerThanOrEqual]; - private static readonly treeEqual: [RDF.NamedNode, LinkTraversalFilterOperator] = [DF.namedNode('https://w3id.org/tree#EqualToRelation'), LinkTraversalFilterOperator.Equal]; + private static readonly treeGreaterThan: [RDF.NamedNode, LinkTraversalFilterOperator] = [ + DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), + LinkTraversalFilterOperator.GreaterThan ]; + + private static readonly treeGreaterThanOrEqual: [RDF.NamedNode, LinkTraversalFilterOperator] = [ + DF.namedNode('https://w3id.org/tree#GreaterThanOrEqualToRelation'), + LinkTraversalFilterOperator.GreaterThanOrEqual ]; + + private static readonly treeLessThan: [RDF.NamedNode, LinkTraversalFilterOperator] = [ + DF.namedNode('https://w3id.org/tree#LessThanRelation'), + LinkTraversalFilterOperator.LowerThan ]; + + private static readonly treeLessThanEqual: [RDF.NamedNode, LinkTraversalFilterOperator] = [ + DF.namedNode('https://w3id.org/tree#LessThanOrEqualToRelation'), + LinkTraversalFilterOperator.LowerThanOrEqual ]; + + private static readonly treeEqual: [RDF.NamedNode, LinkTraversalFilterOperator] = [ + DF.namedNode('https://w3id.org/tree#EqualToRelation'), + LinkTraversalFilterOperator.Equal ]; private static readonly treeOperators: [RDF.NamedNode, LinkTraversalFilterOperator][] = [ ActorExtractLinksTree.treeEqual, @@ -54,10 +69,12 @@ export class ActorExtractLinksTree extends ActorExtractLinks { const metadata = action.metadata; const currentNodeUrl = action.url; const pageRelationNodes: Set = new Set(); - const relationDescriptions: Map = new Map(); + const relationDescriptions: Map = new Map(); const nodeLinks: [string, string][] = []; - const filterFunctions: LinkTraversalOptimizationLinkFilter[] = typeof action.context.get(KeyOptimizationLinkTraversal.filterFunctions) !== 'undefined' ? - action.context.get(KeyOptimizationLinkTraversal.filterFunctions) : []; + const filterFunctions: LinkTraversalOptimizationLinkFilter[] = + typeof action.context.get(KeyOptimizationLinkTraversal.filterFunctions) !== 'undefined' ? + action.context.get(KeyOptimizationLinkTraversal.filterFunctions)! : + []; const links: ILink[] = []; // Forward errors @@ -74,14 +91,14 @@ export class ActorExtractLinksTree extends ActorExtractLinks { // Resolve to discovered links metadata.on('end', () => { // Validate if the node forward have the current node as implicit subject - for (const [nodeValue, link] of nodeLinks) { + for (const [ nodeValue, link ] of nodeLinks) { if (pageRelationNodes.has(nodeValue)) { - const relationDescription = relationDescriptions.get(nodeValue) - if (typeof relationDescription !== 'undefined' && filterFunctions.length !== 0) { - let addLink: boolean = true; - for (let filter of filterFunctions) { + const relationDescription = relationDescriptions.get(nodeValue); + if (typeof relationDescription !== 'undefined' && filterFunctions.length > 0) { + let addLink = true; + for (const filter of filterFunctions) { if (!filter(relationDescription.subject, relationDescription.value, relationDescription.operator)) { - addLink = false + addLink = false; } } if (addLink) { @@ -112,50 +129,53 @@ export class ActorExtractLinksTree extends ActorExtractLinks { url: string, pageRelationNodes: Set, nodeLinks: [string, string][], - relationDescriptions: Map, + relationDescriptions: Map, ): void { // If it's a relation of the current node if (quad.subject.value === url && quad.predicate.equals(ActorExtractLinksTree.aRelation)) { pageRelationNodes.add(quad.object.value); // If it's a node forward } else if (quad.predicate.equals(ActorExtractLinksTree.aNodeType)) { - nodeLinks.push([quad.subject.value, quad.object.value]); - } - // set the operator of the relation - else if (quad.predicate.equals(ActorExtractLinksTree.rdfTypeNode)) { + nodeLinks.push([ quad.subject.value, quad.object.value ]); + } else if (quad.predicate.equals(ActorExtractLinksTree.rdfTypeNode)) { + // Set the operator of the relation let operator: LinkTraversalFilterOperator = LinkTraversalFilterOperator.Equal; for (const treeOperator of ActorExtractLinksTree.treeOperators) { if (quad.object.equals(treeOperator[0])) { - operator = treeOperator[1] + operator = treeOperator[1]; } } this.addRelationDescription(relationDescriptions, 'operator', quad, undefined, '', operator); - }// set the subject of the relation condition - else if (quad.predicate.equals(ActorExtractLinksTree.aTreePath)) { + } else if (quad.predicate.equals(ActorExtractLinksTree.aTreePath)) { + // Set the subject of the relation condition this.addRelationDescription(relationDescriptions, 'subject', quad, undefined, quad.object.value); - } - // set the value of the relation condition - else if (quad.predicate.equals(ActorExtractLinksTree.aTreeValue)) { + } else if (quad.predicate.equals(ActorExtractLinksTree.aTreeValue)) { + // Set the value of the relation condition this.addRelationDescription(relationDescriptions, 'value', quad, quad.object.value); } } - private addRelationDescription(relationDescriptions: Map, field: string, quad: RDF.Quad, value: any = undefined, subject: string = '', operator: LinkTraversalFilterOperator = LinkTraversalFilterOperator.Equal) { + private addRelationDescription(relationDescriptions: Map, + field: string, + quad: RDF.Quad, + value: any, + subject = '', + operator: LinkTraversalFilterOperator = LinkTraversalFilterOperator.Equal): void { const newDescription = relationDescriptions.get(quad.subject.value); if (typeof newDescription === 'undefined') { - relationDescriptions.set(quad.subject.value, { value: value, subject: subject, operator: operator }); + relationDescriptions.set(quad.subject.value, { value, subject, operator }); } else { switch (field) { - case "value":{ - newDescription[field as keyof typeof newDescription] = value; + case 'value': { + newDescription[ field] = value; break; } - case "subject": { - newDescription[field as keyof typeof newDescription] = subject; + case 'subject': { + newDescription[ field] = subject; break; } - case "operator":{ - newDescription[field as keyof typeof newDescription] = operator; + case 'operator': { + newDescription[ field] = operator; break; } } diff --git a/packages/actor-extract-links-extract-tree/package.json b/packages/actor-extract-links-extract-tree/package.json index f5b019b1e..9c3de1c59 100644 --- a/packages/actor-extract-links-extract-tree/package.json +++ b/packages/actor-extract-links-extract-tree/package.json @@ -35,7 +35,9 @@ "@comunica/bus-extract-links": "^0.0.1", "rdf-data-factory": "^1.1.1", "rdf-store-stream": "^1.3.0", - "@comunica/context-entries": "^2.4.0" + "@comunica/context-entries": "^2.4.0", + "@comunica/context-entries-link-traversal": "^0.0.1", + "@comunica/types-link-traversal": "^0.0.1" }, "scripts": { "build": "npm run build:ts && npm run build:components", diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 5809725dd..782a492d5 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -1,10 +1,10 @@ import { KeysRdfResolveQuadPattern } from '@comunica/context-entries'; +import { KeyOptimizationLinkTraversal } from '@comunica/context-entries-link-traversal'; import { ActionContext, Bus } from '@comunica/core'; +import { LinkTraversalFilterOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; -import { KeyOptimizationLinkTraversal } from '@comunica/context-entries-link-traversal'; -import { LinkTraversalFilterOperator } from '@comunica/types-link-traversal'; const stream = require('streamify-array'); @@ -45,7 +45,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { actor = new ActorExtractLinksTree({ name: 'actor', bus }); }); - it('should return the links of a TREE with one relation', async () => { + it('should return the links of a TREE with one relation', async() => { const expectedUrl = 'http://foo.com'; const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), @@ -70,11 +70,11 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actor.run(action); - expect(result).toEqual({ links: [{ url: expectedUrl }] }); + expect(result).toEqual({ links: [{ url: expectedUrl }]}); }); - it('should return the links of a TREE with multiple relations', async () => { - const expectedUrl = ['http://foo.com', 'http://bar.com', 'http://example.com', 'http://example.com']; + it('should return the links of a TREE with multiple relations', async() => { + const expectedUrl = [ 'http://foo.com', 'http://bar.com', 'http://example.com', 'http://example.com' ]; const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('https://w3id.org/tree#relation'), @@ -129,7 +129,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toEqual({ links: expectedUrl.map(value => { return { url: value }; }) }); }); - it('should return the links of a TREE with one complex relation', async () => { + it('should return the links of a TREE with one complex relation', async() => { const expectedUrl = 'http://foo.com'; const input = stream([ DF.quad(DF.namedNode(treeUrl), @@ -165,11 +165,11 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actor.run(action); - expect(result).toEqual({ links: [{ url: expectedUrl }] }); + expect(result).toEqual({ links: [{ url: expectedUrl }]}); }); - it('should return the links of a TREE with multiple relations combining blank nodes and named nodes', async () => { - const expectedUrl = ['http://foo.com', 'http://bar.com', 'http://example.com', 'http://example.com']; + it('should return the links of a TREE with multiple relations combining blank nodes and named nodes', async() => { + const expectedUrl = [ 'http://foo.com', 'http://bar.com', 'http://example.com', 'http://example.com' ]; const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('https://w3id.org/tree#relation'), @@ -229,7 +229,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { actor = new ActorExtractLinksTree({ name: 'actor', bus }); }); - it('should return the links of a TREE with one relation and filter that returns always true', async () => { + it('should return the links of a TREE with one relation and filter that returns always true', async() => { const expectedUrl = 'http://foo.com'; const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), @@ -257,30 +257,30 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.quad(DF.blankNode('_:_g1'), DF.namedNode('https://w3id.org/tree#value'), - DF.literal("value"), + DF.literal('value'), DF.namedNode('ex:gx')), DF.quad(DF.blankNode('_:_g1'), DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - DF.namedNode("https://w3id.org/tree#GreaterThanRelation"), - DF.namedNode('ex:gx')) + DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), + DF.namedNode('ex:gx')), ]); - const mock_filter = jest.fn((_subject, _value, _operator) => true ); + const mock_filter = jest.fn((_subject, _value, _operator) => true); const context = new ActionContext({ [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeyOptimizationLinkTraversal.filterFunctions.name]: [mock_filter] + [KeyOptimizationLinkTraversal.filterFunctions.name]: [ mock_filter ], }); const action = { url: treeUrl, metadata: input, requestTime: 0, context }; const result = await actor.run(action); - expect(result).toEqual({ links: [{ url: expectedUrl }] }); + expect(result).toEqual({ links: [{ url: expectedUrl }]}); expect(mock_filter).toBeCalledTimes(1); - expect(mock_filter).toBeCalledWith('ex:path', "value", LinkTraversalFilterOperator.GreaterThan) + expect(mock_filter).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); }); - it('should return no links of a TREE with one relation and filter that returns always false', async () => { + it('should return no links of a TREE with one relation and filter that returns always false', async() => { const expectedUrl = 'http://foo.com'; const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), @@ -301,9 +301,9 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.literal(expectedUrl), DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), + DF.quad(DF.blankNode('_:_g1'), DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - DF.namedNode("https://w3id.org/tree#GreaterThanRelation"), + DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), DF.namedNode('ex:gx')), DF.quad(DF.blankNode('_:_g1'), @@ -313,27 +313,26 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.quad(DF.blankNode('_:_g1'), DF.namedNode('https://w3id.org/tree#value'), - DF.literal("value"), + DF.literal('value'), DF.namedNode('ex:gx')), - ]); - const mock_filter = jest.fn((_subject, _value, _operator) => false ); + const mock_filter = jest.fn((_subject, _value, _operator) => false); const context = new ActionContext({ [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeyOptimizationLinkTraversal.filterFunctions.name]: [mock_filter] + [KeyOptimizationLinkTraversal.filterFunctions.name]: [ mock_filter ], }); const action = { url: treeUrl, metadata: input, requestTime: 0, context }; const result = await actor.run(action); - expect(result).toEqual({ links: [] }); + expect(result).toEqual({ links: []}); expect(mock_filter).toBeCalledTimes(1); - expect(mock_filter).toBeCalledWith('ex:path', "value", LinkTraversalFilterOperator.GreaterThan) + expect(mock_filter).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); }); - it('should return the links of a TREE with one relation and multiple filter that returns always true', async () => { + it('should return the links of a TREE with one relation and multiple filter that returns always true', async() => { const expectedUrl = 'http://foo.com'; const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), @@ -356,12 +355,12 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.quad(DF.blankNode('_:_g1'), DF.namedNode('https://w3id.org/tree#value'), - DF.literal("value"), + DF.literal('value'), DF.namedNode('ex:gx')), DF.quad(DF.blankNode('_:_g1'), DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - DF.namedNode("https://w3id.org/tree#GreaterThanRelation"), + DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), DF.namedNode('ex:gx')), DF.quad(DF.blankNode('_:_g1'), @@ -370,86 +369,85 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.namedNode('ex:gx')), ]); const mock_filters = [ - jest.fn((_subject, _value, _operator) => true ), - jest.fn((_subject, _value, _operator) => true ), - jest.fn((_subject, _value, _operator) => true ), - jest.fn((_subject, _value, _operator) => true ), + jest.fn((_subject, _value, _operator) => true), + jest.fn((_subject, _value, _operator) => true), + jest.fn((_subject, _value, _operator) => true), + jest.fn((_subject, _value, _operator) => true), ]; const context = new ActionContext({ [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeyOptimizationLinkTraversal.filterFunctions.name]: mock_filters + [KeyOptimizationLinkTraversal.filterFunctions.name]: mock_filters, }); const action = { url: treeUrl, metadata: input, requestTime: 0, context }; const result = await actor.run(action); - expect(result).toEqual({ links: [{ url: expectedUrl }] }); - for (let mock of mock_filters){ + expect(result).toEqual({ links: [{ url: expectedUrl }]}); + for (const mock of mock_filters) { expect(mock).toBeCalledTimes(1); - expect(mock).toBeCalledWith('ex:path', "value", LinkTraversalFilterOperator.GreaterThan) + expect(mock).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); } - }); - it('should return no links of a TREE with one relation and multiple filter that returns true and one that return false', async () => { - const expectedUrl = 'http://foo.com'; - const input = stream([ - DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#relation'), - DF.blankNode('_:_g1'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#node'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), + it('should return no links with one relation and multiple filter that returns true and one that return false', + async() => { + const expectedUrl = 'http://foo.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#value'), - DF.literal("value"), - DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#value'), + DF.literal('value'), + DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#path'), - DF.namedNode('ex:path'), - DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#path'), + DF.namedNode('ex:path'), + DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - DF.namedNode("https://w3id.org/tree#GreaterThanRelation"), - DF.namedNode('ex:gx')) - ]); - const mock_filters = [ - jest.fn((_subject, _value, _operator) => true ), - jest.fn((_subject, _value, _operator) => true ), - jest.fn((_subject, _value, _operator) => false ), - jest.fn((_subject, _value, _operator) => true ), - ]; - const context = new ActionContext({ - [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeyOptimizationLinkTraversal.filterFunctions.name]: mock_filters + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), + DF.namedNode('ex:gx')), + ]); + const mock_filters = [ + jest.fn((_subject, _value, _operator) => true), + jest.fn((_subject, _value, _operator) => true), + jest.fn((_subject, _value, _operator) => false), + jest.fn((_subject, _value, _operator) => true), + ]; + const context = new ActionContext({ + [KeysRdfResolveQuadPattern.source.name]: treeUrl, + [KeyOptimizationLinkTraversal.filterFunctions.name]: mock_filters, + }); + + const action = { url: treeUrl, metadata: input, requestTime: 0, context }; + + const result = await actor.run(action); + + expect(result).toEqual({ links: []}); + for (const mock of mock_filters) { + expect(mock).toBeCalledTimes(1); + expect(mock).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); + } }); - - const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - - const result = await actor.run(action); - - expect(result).toEqual({ links: [] }); - for (let mock of mock_filters){ - expect(mock).toBeCalledTimes(1); - expect(mock).toBeCalledWith('ex:path', "value", LinkTraversalFilterOperator.GreaterThan) - } - - }); }); describe('The ActorExtractLinksExtractLinksTree test method', () => { let actor: ActorExtractLinksTree; @@ -459,7 +457,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { actor = new ActorExtractLinksTree({ name: 'actor', bus }); }); - it('should test when giving a TREE', async () => { + it('should test when giving a TREE', async() => { const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), @@ -477,7 +475,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toBe(true); }); - it('should test when not given a TREE', async () => { + it('should test when not given a TREE', async() => { const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), diff --git a/packages/bus-optimize-link-traversal/.npmignore b/packages/bus-optimize-link-traversal/.npmignore new file mode 100644 index 000000000..e69de29bb diff --git a/packages/bus-optimize-link-traversal/README.md b/packages/bus-optimize-link-traversal/README.md new file mode 100644 index 000000000..f614a6ace --- /dev/null +++ b/packages/bus-optimize-link-traversal/README.md @@ -0,0 +1,27 @@ +# Comunica Bus Optimize Link Traversal + +[![npm version](https://badge.fury.io/js/%40comunica%2Fbus-optimize-link-traversal.svg)](https://www.npmjs.com/package/@comunica/bus-optimize-link-traversal) + +A comunica bus for optimization of link traversal + +This module is part of the [Comunica framework](https://github.com/comunica/comunica), +and should only be used by [developers that want to build their own query engine](https://comunica.dev/docs/modify/). + +[Click here if you just want to query with Comunica](https://comunica.dev/docs/query/). + +## Install + +```bash +$ yarn add @comunica/bus-optimize-link-traversal +``` + +## Usage + +## Bus usage + +* **Context**: `"https://linkedsoftwaredependencies.org/bundles/npm/@comunica/bus-optimize-link-traversal/^1.0.0/components/context.jsonld"` +* **Bus name**: `ActorOptimizeLinkTraversal:_default_bus` + +## Creating actors on this bus + +Actors extending [`ActorOptimizeLinkTraversal`](TODO:jsdoc_url) are automatically subscribed to this bus. diff --git a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts new file mode 100644 index 000000000..2c0ff24f3 --- /dev/null +++ b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts @@ -0,0 +1,42 @@ +import type { IAction, IActorArgs, IActorOutput, IActorTest, Mediate } from '@comunica/core'; +import { Actor } from '@comunica/core'; +import type { LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; +import type { Algebra } from 'sparqlalgebrajs'; +/** + * A comunica actor for optimization of link traversal + * + * Actor types: + * * Input: IActionOptimizeLinkTraversal: TODO: fill in. + * * Test: + * * Output: IActorOptimizeLinkTraversalOutput: TODO: fill in. + * + * @see IActionOptimizeLinkTraversal + * @see IActorOptimizeLinkTraversalOutput + */ +export abstract class ActorOptimizeLinkTraversal extends Actor { + /** + * @param args - @defaultNested { a } bus + */ + public constructor(args: IActorOptimizeLinkTraversalArgs) { + super(args); + } +} + +export interface IActionOptimizeLinkTraversal extends IAction { + operations: Algebra.Operation; +} + +export interface IActorOptimizeLinkTraversalOutput extends IActorOutput { + /** + * The filter functions to exclude links when traversing links + */ + filters: LinkTraversalOptimizationLinkFilter[]; +} + +export type IActorOptimizeLinkTraversalArgs = IActorArgs< +IActionOptimizeLinkTraversal, IActorTest, IActorOptimizeLinkTraversalOutput>; + +export type MediatorOptimizeLinkTraversal = Mediate< +IActionOptimizeLinkTraversal, IActorOptimizeLinkTraversalOutput>; diff --git a/packages/bus-optimize-link-traversal/lib/index.ts b/packages/bus-optimize-link-traversal/lib/index.ts new file mode 100644 index 000000000..a1a1d8527 --- /dev/null +++ b/packages/bus-optimize-link-traversal/lib/index.ts @@ -0,0 +1 @@ +export * from './ActorOptimizeLinkTraversal'; diff --git a/packages/bus-optimize-link-traversal/package.json b/packages/bus-optimize-link-traversal/package.json new file mode 100644 index 000000000..befacc6d8 --- /dev/null +++ b/packages/bus-optimize-link-traversal/package.json @@ -0,0 +1,40 @@ +{ + "name": "@comunica/bus-optimize-link-traversal", + "version": "1.0.0", + "description": "A comunica bus for optimization of link traversal", + "lsd:module": true, + "main": "lib/index.js", + "typings": "lib/index", + "repository": { + "type": "git", + "url": "https://github.com/comunica/comunica.git", + "directory": "packages/bus-optimize-link-traversal" + }, + "publishConfig": { + "access": "public" + }, + "sideEffects": false, + "keywords": [ + "comunica", + "bus", + "optimize-link-traversal" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/comunica/comunica/issues" + }, + "homepage": "https://comunica.dev/", + "files": [ + "components", + "lib/**/*.d.ts", + "lib/**/*.js" + ], + "dependencies": { + "@comunica/core": "^2.4.0" + }, + "scripts": { + "build": "npm run build:ts && npm run build:components", + "build:ts": "node \"../../node_modules/typescript/bin/tsc\"", + "build:components": "componentsjs-generator" + } +} diff --git a/packages/context-entries-link-traversal/lib/Keys.ts b/packages/context-entries-link-traversal/lib/Keys.ts index 85f804507..84323f1ca 100644 --- a/packages/context-entries-link-traversal/lib/Keys.ts +++ b/packages/context-entries-link-traversal/lib/Keys.ts @@ -23,5 +23,7 @@ export const KeyOptimizationLinkTraversal = { /** * Filter function to optimized the link traversal */ - filterFunctions: new ActionContextKey('@comunica/optimization-link-traversal:filterFunction'), + filterFunctions: new ActionContextKey( + '@comunica/optimization-link-traversal:filterFunction', + ), }; diff --git a/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts b/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts index 2b61c6c94..6b18c3e7c 100644 --- a/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts +++ b/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts @@ -1,4 +1,6 @@ -export type LinkTraversalOptimizationLinkFilter = (subject: string, value: any, operator: LinkTraversalFilterOperator) => boolean; +export type LinkTraversalOptimizationLinkFilter = (subject: string, + value: any, + operator: LinkTraversalFilterOperator) => boolean; export enum LinkTraversalFilterOperator { GreaterThan, From 5ec0762a2f293f1dbe804bf243366509d4b6c0bd Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 19 Oct 2022 09:47:06 +0200 Subject: [PATCH 003/189] bus-optimize-link-traversal integrated but does nothing --- .gitignore | 2 +- .../config/config-default.json | 5 +- .../config/extract-links/actors/tree.json | 6 +- .../actors/filter-tree.json | 14 +++ .../optimize-link-traversal/mediators.json | 9 ++ .../query-sparql-link-traversal/package.json | 3 +- .../lib/ActorExtractLinksTree.ts | 29 +++++-- .../package.json | 3 +- .../test/ActorExtractLinksTree-test.ts | 87 +++++++++++++++++-- .../.npmignore | 0 .../README.md | 41 +++++++++ ...torOptimizeLinkTraversalFilterTreeLinks.ts | 28 ++++++ .../lib/index.ts | 1 + .../package.json | 43 +++++++++ ...timizeLinkTraversalFilterTreeLinks-test.ts | 26 ++++++ .../bus-optimize-link-traversal/README.md | 2 +- .../bus-optimize-link-traversal/package.json | 2 +- 17 files changed, 280 insertions(+), 21 deletions(-) create mode 100644 engines/config-query-sparql-link-traversal/config/optimize-link-traversal/actors/filter-tree.json create mode 100644 engines/config-query-sparql-link-traversal/config/optimize-link-traversal/mediators.json create mode 100644 packages/actor-optimize-link-traversal-filter-tree-links/.npmignore create mode 100644 packages/actor-optimize-link-traversal-filter-tree-links/README.md create mode 100644 packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts create mode 100644 packages/actor-optimize-link-traversal-filter-tree-links/lib/index.ts create mode 100644 packages/actor-optimize-link-traversal-filter-tree-links/package.json create mode 100644 packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts diff --git a/.gitignore b/.gitignore index 706285460..3872cdb00 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ web-clients/builds componentsjs-error-state.json engines/*/components packages/*/components - +engines/config-query-sparql-link-traversal/config/debug-config.json \ No newline at end of file diff --git a/engines/config-query-sparql-link-traversal/config/config-default.json b/engines/config-query-sparql-link-traversal/config/config-default.json index c340922c4..3caa32209 100644 --- a/engines/config-query-sparql-link-traversal/config/config-default.json +++ b/engines/config-query-sparql-link-traversal/config/config-default.json @@ -8,6 +8,9 @@ "ccqslt:config/extract-links/actors/content-policies-conditional.json", "ccqslt:config/extract-links/actors/quad-pattern-query.json", "ccqslt:config/rdf-resolve-hypermedia-links/actors/traverse-replace-conditional.json", - "ccqslt:config/rdf-resolve-hypermedia-links-queue/actors/wrapper-limit-count.json" + "ccqslt:config/rdf-resolve-hypermedia-links-queue/actors/wrapper-limit-count.json", + "ccqslt:config/extract-links/actors/tree.json", + "ccqslt:config/optimize-link-traversal/mediators.json", + "ccqslt:config/optimize-link-traversal/actors/filter-tree.json" ] } diff --git a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json index 8da34c2b8..a99428eef 100644 --- a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json +++ b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json @@ -2,14 +2,16 @@ "@context": [ "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^2.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-extract-links-tree/^0.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-extract-links-tree/^0.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/bus-optimize-link-traversal/^0.0.0/components/context.jsonld" ], "@id": "urn:comunica:default:Runner", "@type": "Runner", "actors": [ { "@id": "urn:comunica:default:extract-links/actors#extract-links-tree", - "@type": "ActorExtractLinksTree" + "@type": "ActorExtractLinksTree", + "mediatorOptimizeLinkTraversal": { "@id": "urn:comunica:default:optimize-link-traversal/mediators#main" } } ] } diff --git a/engines/config-query-sparql-link-traversal/config/optimize-link-traversal/actors/filter-tree.json b/engines/config-query-sparql-link-traversal/config/optimize-link-traversal/actors/filter-tree.json new file mode 100644 index 000000000..42325da85 --- /dev/null +++ b/engines/config-query-sparql-link-traversal/config/optimize-link-traversal/actors/filter-tree.json @@ -0,0 +1,14 @@ +{ + "@context": [ + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^2.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-optimize-link-traversal-filter-tree-links/^0.0.0/components/context.jsonld" + ], + "@id": "urn:comunica:default:Runner", + "@type": "Runner", + "actors": [ + { + "@id": "urn:comunica:default:optimize-link-traversal/actors#filter-tree-links", + "@type": "ActorOptimizeLinkTraversalFilterTreeLinks" + } + ] + } \ No newline at end of file diff --git a/engines/config-query-sparql-link-traversal/config/optimize-link-traversal/mediators.json b/engines/config-query-sparql-link-traversal/config/optimize-link-traversal/mediators.json new file mode 100644 index 000000000..812dc4d6e --- /dev/null +++ b/engines/config-query-sparql-link-traversal/config/optimize-link-traversal/mediators.json @@ -0,0 +1,9 @@ +{ + "@context": [ + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/bus-optimize-link-traversal/^0.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/mediator-all/^2.0.0/components/context.jsonld" + ], + "@id": "urn:comunica:default:optimize-link-traversal/mediators#main", + "@type": "MediatorAll", + "bus": { "@id": "ActorOptimizeLinkTraversal:_default_bus" } +} diff --git a/engines/query-sparql-link-traversal/package.json b/engines/query-sparql-link-traversal/package.json index 62963580e..322219303 100644 --- a/engines/query-sparql-link-traversal/package.json +++ b/engines/query-sparql-link-traversal/package.json @@ -59,7 +59,8 @@ "@comunica/config-query-sparql-link-traversal": "^0.0.1", "@comunica/mediator-combine-array": "^0.0.1", "@comunica/actor-init-query": "^2.2.0", - "@comunica/runner-cli": "^2.2.0" + "@comunica/runner-cli": "^2.2.0", + "@comunica/actor-optimize-link-traversal-filter-tree-links": "^0.0.1" }, "scripts": { "build": "npm run build:ts", diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index bc27e7223..da5d5bc43 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -3,7 +3,9 @@ import type { IActorExtractLinksOutput, IActorExtractLinksArgs, } from '@comunica/bus-extract-links'; import { ActorExtractLinks } from '@comunica/bus-extract-links'; +import type { MediatorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; import type { ILink } from '@comunica/bus-rdf-resolve-hypermedia-links'; +import { KeysInitQuery } from '@comunica/context-entries'; import { KeyOptimizationLinkTraversal } from '@comunica/context-entries-link-traversal'; import type { IActorTest } from '@comunica/core'; import type { LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; @@ -56,7 +58,9 @@ export class ActorExtractLinksTree extends ActorExtractLinks { ActorExtractLinksTree.treeGreaterThan, ]; - public constructor(args: IActorExtractLinksArgs) { + private readonly mediatorOptimizeLinkTraversal: MediatorOptimizeLinkTraversal; + + public constructor(args: IActorExtractLinksTree) { super(args); } @@ -65,6 +69,10 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } public async run(action: IActionExtractLinks): Promise { + if (action.context.get(KeysInitQuery.query) !== undefined) { + await this.mediatorOptimizeLinkTraversal.mediate({ operations: action.context.get(KeysInitQuery.query)!, + context: action.context }); + } return new Promise((resolve, reject) => { const metadata = action.metadata; const currentNodeUrl = action.url; @@ -72,9 +80,9 @@ export class ActorExtractLinksTree extends ActorExtractLinks { const relationDescriptions: Map = new Map(); const nodeLinks: [string, string][] = []; const filterFunctions: LinkTraversalOptimizationLinkFilter[] = - typeof action.context.get(KeyOptimizationLinkTraversal.filterFunctions) !== 'undefined' ? - action.context.get(KeyOptimizationLinkTraversal.filterFunctions)! : - []; + typeof action.context.get(KeyOptimizationLinkTraversal.filterFunctions) !== 'undefined' ? + action.context.get(KeyOptimizationLinkTraversal.filterFunctions)! : + []; const links: ILink[] = []; // Forward errors @@ -167,15 +175,15 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } else { switch (field) { case 'value': { - newDescription[ field] = value; + newDescription[field] = value; break; } case 'subject': { - newDescription[ field] = subject; + newDescription[field] = subject; break; } case 'operator': { - newDescription[ field] = operator; + newDescription[field] = operator; break; } } @@ -183,3 +191,10 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } } } + +export interface IActorExtractLinksTree extends IActorExtractLinksArgs { + /** + * The optmize link traversal mediator + */ + mediatorOptimizeLinkTraversal: MediatorOptimizeLinkTraversal; +} diff --git a/packages/actor-extract-links-extract-tree/package.json b/packages/actor-extract-links-extract-tree/package.json index 9c3de1c59..e9f15190c 100644 --- a/packages/actor-extract-links-extract-tree/package.json +++ b/packages/actor-extract-links-extract-tree/package.json @@ -37,7 +37,8 @@ "rdf-store-stream": "^1.3.0", "@comunica/context-entries": "^2.4.0", "@comunica/context-entries-link-traversal": "^0.0.1", - "@comunica/types-link-traversal": "^0.0.1" + "@comunica/types-link-traversal": "^0.0.1", + "@comunica/bus-optimize-link-traversal": "^0.0.1" }, "scripts": { "build": "npm run build:ts && npm run build:components", diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 782a492d5..1dc09ba17 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -1,10 +1,13 @@ -import { KeysRdfResolveQuadPattern } from '@comunica/context-entries'; +import { KeysRdfResolveQuadPattern, KeysInitQuery } from '@comunica/context-entries'; import { KeyOptimizationLinkTraversal } from '@comunica/context-entries-link-traversal'; import { ActionContext, Bus } from '@comunica/core'; -import { LinkTraversalFilterOperator } from '@comunica/types-link-traversal'; +import { LinkTraversalFilterOperator, LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; -import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; +import { ActorExtractLinksTree, IActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; +import { MediatorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; +import type { Algebra } from 'sparqlalgebrajs'; + const stream = require('streamify-array'); @@ -12,8 +15,20 @@ const DF = new DataFactory(); describe('ActorExtractLinksExtractLinksTree', () => { let bus: any; + let mockMediator: any; + let spyMockMediator: any; beforeEach(() => { + mockMediator = { + mediate(arg: any) { + return Promise.resolve( + { + filters: [] + } + ) + } + }; + spyMockMediator = jest.spyOn(mockMediator, 'mediate'); bus = new Bus({ name: 'bus' }); }); @@ -40,9 +55,11 @@ describe('ActorExtractLinksExtractLinksTree', () => { const context = new ActionContext({ [KeysRdfResolveQuadPattern.source.name]: treeUrl, }); + + beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', bus }); + actor = new ActorExtractLinksTree({ name: 'actor', mediatorOptimizeLinkTraversal: mockMediator, bus }); }); it('should return the links of a TREE with one relation', async() => { @@ -226,7 +243,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const treeUrl = 'ex:s'; beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', bus }); + actor = new ActorExtractLinksTree({ name: 'actor', mediatorOptimizeLinkTraversal: mockMediator, bus }); }); it('should return the links of a TREE with one relation and filter that returns always true', async() => { @@ -448,13 +465,71 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(mock).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); } }); + + it('should call the mediator when a query is defined', async() => { + const expectedUrl = 'http://foo.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + ]); + + const operation = { + "type": "project", + "input": { + "type": "graph", + "input": { + "type": "bgp", + "patterns": [{ + "type": "pattern", + "termType": "Quad", + "subject": { "termType": "Variable", "value": "x" }, + "predicate": { "termType": "Variable", "value": "y" }, + "object": { "termType": "Variable", "value": "z" }, + "graph": { "termType": "DefaultGraph", "value": "" } + }] + }, + "name": { "termType": "Variable", "value": "g" } + }, + "variables": [{ "termType": "Variable", "value": "x" }] + }; + + const context = new ActionContext({ + [KeysRdfResolveQuadPattern.source.name]: treeUrl, + [KeysInitQuery.query.name]: operation + }); + + const action = { url: treeUrl, metadata: input, requestTime: 0, context }; + + const result = await actor.run(action); + + expect(result).toEqual({ links: [{ url: expectedUrl }]}); + + expect(spyMockMediator).toBeCalledWith(expect.objectContaining({ + operations: operation, + })); + }); }); describe('The ActorExtractLinksExtractLinksTree test method', () => { let actor: ActorExtractLinksTree; const treeUrl = 'ex:s'; beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', bus }); + actor = new ActorExtractLinksTree({ name: 'actor', mediatorOptimizeLinkTraversal: mockMediator, bus }); }); it('should test when giving a TREE', async() => { diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/.npmignore b/packages/actor-optimize-link-traversal-filter-tree-links/.npmignore new file mode 100644 index 000000000..e69de29bb diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/README.md b/packages/actor-optimize-link-traversal-filter-tree-links/README.md new file mode 100644 index 000000000..172676c01 --- /dev/null +++ b/packages/actor-optimize-link-traversal-filter-tree-links/README.md @@ -0,0 +1,41 @@ +# Comunica Filter Tree Links Optimize Link Traversal Actor + +[![npm version](https://badge.fury.io/js/%40comunica%2Factor-optimize-link-traversal-filter-tree-links.svg)](https://www.npmjs.com/package/@comunica/actor-optimize-link-traversal-filter-tree-links) + +A comunica Link traversal optimizer that filter link of document following the [TREE specification](https://treecg.github.io/specification/) + +This module is part of the [Comunica framework](https://github.com/comunica/comunica), +and should only be used by [developers that want to build their own query engine](https://comunica.dev/docs/modify/). + +[Click here if you just want to query with Comunica](https://comunica.dev/docs/query/). + +## Install + +```bash +$ yarn add @comunica/actor-optimize-link-traversal-filter-tree-links +``` + +## Configure + +After installing, this package can be added to your engine's configuration as follows: +```text +{ + "@context": [ + ... + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-optimize-link-traversal-filter-tree-links/^1.0.0/components/context.jsonld" + ], + "actors": [ + ... + { + "@id": "urn:comunica:default:optimize-link-traversal/actors#filter-tree-links", + "@type": "ActorOptimizeLinkTraversalFilterTreeLinks" + } + ] +} +``` + +### Config Parameters + +TODO: fill in parameters (this section can be removed if there are none) + +* `someParam`: Description of the param diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts new file mode 100644 index 000000000..43437d6c4 --- /dev/null +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -0,0 +1,28 @@ +import type { + IActorOptimizeLinkTraversalArgs, + IActionOptimizeLinkTraversal, + IActorOptimizeLinkTraversalOutput, +} from '@comunica/bus-optimize-link-traversal'; +import { ActorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; +import type { IActorTest } from '@comunica/core'; +import { Algebra } from 'sparqlalgebrajs'; + +/** + * A comunica Link traversal optimizer that filter link of document + * following the [TREE specification](https://treecg.github.io/specification/) + */ + +export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLinkTraversal { + public constructor(args: IActorOptimizeLinkTraversalArgs) { + super(args); + } + + public async test(action: IActionOptimizeLinkTraversal): Promise { + return action.operations.type === Algebra.types.FILTER; + } + + public async run(action: IActionOptimizeLinkTraversal): Promise { + return { filters: []}; + } +} + diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/index.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/index.ts new file mode 100644 index 000000000..4f4318ffc --- /dev/null +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/index.ts @@ -0,0 +1 @@ +export * from './ActorOptimizeLinkTraversalFilterTreeLinks'; diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/package.json b/packages/actor-optimize-link-traversal-filter-tree-links/package.json new file mode 100644 index 000000000..24ddf8726 --- /dev/null +++ b/packages/actor-optimize-link-traversal-filter-tree-links/package.json @@ -0,0 +1,43 @@ +{ + "name": "@comunica/actor-optimize-link-traversal-filter-tree-links", + "version": "0.0.1", + "description": "A filter-tree-links optimize-link-traversal actor", + "lsd:module": true, + "main": "lib/index.js", + "typings": "lib/index", + "repository": { + "type": "git", + "url": "https://github.com/comunica/comunica.git", + "directory": "packages/actor-optimize-link-traversal-filter-tree-links" + }, + "publishConfig": { + "access": "public" + }, + "sideEffects": false, + "keywords": [ + "comunica", + "actor", + "optimize-link-traversal", + "filter-tree-links" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/comunica/comunica/issues" + }, + "homepage": "https://comunica.dev/", + "files": [ + "components", + "lib/**/*.d.ts", + "lib/**/*.js" + ], + "dependencies": { + "@comunica/core": "^2.4.0", + "@comunica/bus-optimize-link-traversal": "^0.0.1", + "sparqlalgebrajs": "^4.0.0" + }, + "scripts": { + "build": "npm run build:ts && npm run build:components", + "build:ts": "node \"../../node_modules/typescript/bin/tsc\"", + "build:components": "componentsjs-generator" + } +} diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts b/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts new file mode 100644 index 000000000..12039c333 --- /dev/null +++ b/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts @@ -0,0 +1,26 @@ +import { Bus } from '@comunica/core'; +import type { ActorOptimizeLinkTraversalFilterTreeLinks } from '../lib/ActorOptimizeLinkTraversalFilterTreeLinks'; + +describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { + let bus: any; + + beforeEach(() => { + bus = new Bus({ name: 'bus' }); + }); + + describe('An ActorOptimizeLinkTraversalFilterTreeLinks instance', () => { + let actor: ActorOptimizeLinkTraversalFilterTreeLinks; + + beforeEach(() => { + // Actor = new ActorOptimizeLinkTraversalFilterTreeLinks({ name: 'actor', bus }); + }); + + it('should test', () => { + // Return expect(actor.test({ todo: true })).resolves.toEqual({ todo: true }); // TODO + }); + + it('should run', () => { + // Return expect(actor.run({ todo: true })).resolves.toMatchObject({ todo: true }); // TODO + }); + }); +}); diff --git a/packages/bus-optimize-link-traversal/README.md b/packages/bus-optimize-link-traversal/README.md index f614a6ace..868b189df 100644 --- a/packages/bus-optimize-link-traversal/README.md +++ b/packages/bus-optimize-link-traversal/README.md @@ -19,7 +19,7 @@ $ yarn add @comunica/bus-optimize-link-traversal ## Bus usage -* **Context**: `"https://linkedsoftwaredependencies.org/bundles/npm/@comunica/bus-optimize-link-traversal/^1.0.0/components/context.jsonld"` +* **Context**: `"https://linkedsoftwaredependencies.org/bundles/npm/@comunica/bus-optimize-link-traversal/^0.0.0/components/context.jsonld"` * **Bus name**: `ActorOptimizeLinkTraversal:_default_bus` ## Creating actors on this bus diff --git a/packages/bus-optimize-link-traversal/package.json b/packages/bus-optimize-link-traversal/package.json index befacc6d8..d97219708 100644 --- a/packages/bus-optimize-link-traversal/package.json +++ b/packages/bus-optimize-link-traversal/package.json @@ -1,6 +1,6 @@ { "name": "@comunica/bus-optimize-link-traversal", - "version": "1.0.0", + "version": "0.0.1", "description": "A comunica bus for optimization of link traversal", "lsd:module": true, "main": "lib/index.js", From 7c4bb96ef300bf560ecab1f59e0ed338ce74ca65 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 19 Oct 2022 09:47:44 +0200 Subject: [PATCH 004/189] lint-fix --- .../test/ActorExtractLinksTree-test.ts | 128 +++++++++--------- 1 file changed, 62 insertions(+), 66 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 1dc09ba17..d2222ff55 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -1,13 +1,11 @@ import { KeysRdfResolveQuadPattern, KeysInitQuery } from '@comunica/context-entries'; import { KeyOptimizationLinkTraversal } from '@comunica/context-entries-link-traversal'; import { ActionContext, Bus } from '@comunica/core'; -import { LinkTraversalFilterOperator, LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; +import type { LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; +import { LinkTraversalFilterOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; -import { ActorExtractLinksTree, IActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; -import { MediatorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; -import type { Algebra } from 'sparqlalgebrajs'; - +import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; const stream = require('streamify-array'); @@ -23,10 +21,10 @@ describe('ActorExtractLinksExtractLinksTree', () => { mediate(arg: any) { return Promise.resolve( { - filters: [] - } - ) - } + filters: [], + }, + ); + }, }; spyMockMediator = jest.spyOn(mockMediator, 'mediate'); bus = new Bus({ name: 'bus' }); @@ -55,11 +53,9 @@ describe('ActorExtractLinksExtractLinksTree', () => { const context = new ActionContext({ [KeysRdfResolveQuadPattern.source.name]: treeUrl, }); - - beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', mediatorOptimizeLinkTraversal: mockMediator, bus }); + actor = new ActorExtractLinksTree({ name: 'actor', mediatorOptimizeLinkTraversal: mockMediator, bus }); }); it('should return the links of a TREE with one relation', async() => { @@ -243,7 +239,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const treeUrl = 'ex:s'; beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', mediatorOptimizeLinkTraversal: mockMediator, bus }); + actor = new ActorExtractLinksTree({ name: 'actor', mediatorOptimizeLinkTraversal: mockMediator, bus }); }); it('should return the links of a TREE with one relation and filter that returns always true', async() => { @@ -466,70 +462,70 @@ describe('ActorExtractLinksExtractLinksTree', () => { } }); - it('should call the mediator when a query is defined', async() => { - const expectedUrl = 'http://foo.com'; - const input = stream([ - DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#relation'), - DF.blankNode('_:_g1'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#node'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - ]); + it('should call the mediator when a query is defined', async() => { + const expectedUrl = 'http://foo.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + ]); - const operation = { - "type": "project", - "input": { - "type": "graph", - "input": { - "type": "bgp", - "patterns": [{ - "type": "pattern", - "termType": "Quad", - "subject": { "termType": "Variable", "value": "x" }, - "predicate": { "termType": "Variable", "value": "y" }, - "object": { "termType": "Variable", "value": "z" }, - "graph": { "termType": "DefaultGraph", "value": "" } - }] - }, - "name": { "termType": "Variable", "value": "g" } + const operation = { + type: 'project', + input: { + type: 'graph', + input: { + type: 'bgp', + patterns: [{ + type: 'pattern', + termType: 'Quad', + subject: { termType: 'Variable', value: 'x' }, + predicate: { termType: 'Variable', value: 'y' }, + object: { termType: 'Variable', value: 'z' }, + graph: { termType: 'DefaultGraph', value: '' }, + }], }, - "variables": [{ "termType": "Variable", "value": "x" }] - }; + name: { termType: 'Variable', value: 'g' }, + }, + variables: [{ termType: 'Variable', value: 'x' }], + }; - const context = new ActionContext({ - [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeysInitQuery.query.name]: operation - }); + const context = new ActionContext({ + [KeysRdfResolveQuadPattern.source.name]: treeUrl, + [KeysInitQuery.query.name]: operation, + }); - const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - - const result = await actor.run(action); - - expect(result).toEqual({ links: [{ url: expectedUrl }]}); + const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - expect(spyMockMediator).toBeCalledWith(expect.objectContaining({ - operations: operation, - })); - }); + const result = await actor.run(action); + + expect(result).toEqual({ links: [{ url: expectedUrl }]}); + + expect(spyMockMediator).toBeCalledWith(expect.objectContaining({ + operations: operation, + })); + }); }); describe('The ActorExtractLinksExtractLinksTree test method', () => { let actor: ActorExtractLinksTree; const treeUrl = 'ex:s'; beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', mediatorOptimizeLinkTraversal: mockMediator, bus }); + actor = new ActorExtractLinksTree({ name: 'actor', mediatorOptimizeLinkTraversal: mockMediator, bus }); }); it('should test when giving a TREE', async() => { From f43a93134c237a4bebd2c3836f65e216266ec8a0 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 20 Oct 2022 10:48:25 +0200 Subject: [PATCH 005/189] support for multiple filter targeting a single key --- .../lib/ActorExtractLinksTree.ts | 32 ++++++++--- .../test/ActorExtractLinksTree-test.ts | 57 +++++++++++-------- .../lib/Keys.ts | 2 +- 3 files changed, 57 insertions(+), 34 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index da5d5bc43..5cc68d357 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -70,8 +70,10 @@ export class ActorExtractLinksTree extends ActorExtractLinks { public async run(action: IActionExtractLinks): Promise { if (action.context.get(KeysInitQuery.query) !== undefined) { - await this.mediatorOptimizeLinkTraversal.mediate({ operations: action.context.get(KeysInitQuery.query)!, - context: action.context }); + await this.mediatorOptimizeLinkTraversal.mediate({ + operations: action.context.get(KeysInitQuery.query)!, + context: action.context, + }); } return new Promise((resolve, reject) => { const metadata = action.metadata; @@ -79,10 +81,10 @@ export class ActorExtractLinksTree extends ActorExtractLinks { const pageRelationNodes: Set = new Set(); const relationDescriptions: Map = new Map(); const nodeLinks: [string, string][] = []; - const filterFunctions: LinkTraversalOptimizationLinkFilter[] = + const filterFunctions: Map = typeof action.context.get(KeyOptimizationLinkTraversal.filterFunctions) !== 'undefined' ? action.context.get(KeyOptimizationLinkTraversal.filterFunctions)! : - []; + new Map(); const links: ILink[] = []; // Forward errors @@ -102,14 +104,26 @@ export class ActorExtractLinksTree extends ActorExtractLinks { for (const [ nodeValue, link ] of nodeLinks) { if (pageRelationNodes.has(nodeValue)) { const relationDescription = relationDescriptions.get(nodeValue); - if (typeof relationDescription !== 'undefined' && filterFunctions.length > 0) { + if (typeof relationDescription !== 'undefined' && filterFunctions.size > 0) { let addLink = true; - for (const filter of filterFunctions) { - if (!filter(relationDescription.subject, relationDescription.value, relationDescription.operator)) { - addLink = false; + let nFilterApplied = 0; + for (const [ key, filterList ] of filterFunctions.entries()) { + // If the filter is to be apply to the subject and the filter is not respected + if (relationDescription.subject === key) { + for (const filter of filterList) { + if (!filter( + relationDescription.subject, + relationDescription.value, + relationDescription.operator, + )) { + addLink = false; + } else { + nFilterApplied += 1; + } + } } } - if (addLink) { + if (addLink && nFilterApplied !== 0) { links.push({ url: link }); } } else { diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index d2222ff55..05337244a 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -21,7 +21,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { mediate(arg: any) { return Promise.resolve( { - filters: [], + filters: > new Map(), }, ); }, @@ -281,16 +281,16 @@ describe('ActorExtractLinksExtractLinksTree', () => { const mock_filter = jest.fn((_subject, _value, _operator) => true); const context = new ActionContext({ [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeyOptimizationLinkTraversal.filterFunctions.name]: [ mock_filter ], + [KeyOptimizationLinkTraversal.filterFunctions.name]: new Map([[ 'ex:path', [ mock_filter ]]]), }); const action = { url: treeUrl, metadata: input, requestTime: 0, context }; const result = await actor.run(action); + expect(mock_filter).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); expect(result).toEqual({ links: [{ url: expectedUrl }]}); expect(mock_filter).toBeCalledTimes(1); - expect(mock_filter).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); }); it('should return no links of a TREE with one relation and filter that returns always false', async() => { @@ -333,7 +333,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const mock_filter = jest.fn((_subject, _value, _operator) => false); const context = new ActionContext({ [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeyOptimizationLinkTraversal.filterFunctions.name]: [ mock_filter ], + [KeyOptimizationLinkTraversal.filterFunctions.name]: new Map([[ 'ex:path', [ mock_filter ]]]), }); const action = { url: treeUrl, metadata: input, requestTime: 0, context }; @@ -381,12 +381,12 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.namedNode('ex:path'), DF.namedNode('ex:gx')), ]); - const mock_filters = [ - jest.fn((_subject, _value, _operator) => true), - jest.fn((_subject, _value, _operator) => true), - jest.fn((_subject, _value, _operator) => true), - jest.fn((_subject, _value, _operator) => true), - ]; + const mock_filters = new Map([ + [ 'ex:path', [ jest.fn((_subject, _value, _operator) => true), + jest.fn((_subject, _value, _operator) => true), + jest.fn((_subject, _value, _operator) => true), + jest.fn((_subject, _value, _operator) => true) ]], + ]); const context = new ActionContext({ [KeysRdfResolveQuadPattern.source.name]: treeUrl, [KeyOptimizationLinkTraversal.filterFunctions.name]: mock_filters, @@ -397,9 +397,11 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actor.run(action); expect(result).toEqual({ links: [{ url: expectedUrl }]}); - for (const mock of mock_filters) { - expect(mock).toBeCalledTimes(1); - expect(mock).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); + for (const mockList of mock_filters.values()) { + for (const mock of mockList) { + expect(mock).toBeCalledTimes(1); + expect(mock).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); + } } }); @@ -440,26 +442,33 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), DF.namedNode('ex:gx')), ]); - const mock_filters = [ - jest.fn((_subject, _value, _operator) => true), - jest.fn((_subject, _value, _operator) => true), - jest.fn((_subject, _value, _operator) => false), - jest.fn((_subject, _value, _operator) => true), - ]; + const mockFilters = new Map([ + [ 'ex:path', [ + jest.fn((_subject, _value, _operator) => true), + jest.fn((_subject, _value, _operator) => true), + jest.fn((_subject, _value, _operator) => false), + jest.fn((_subject, _value, _operator) => true), + ], + + ], + ]); const context = new ActionContext({ [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeyOptimizationLinkTraversal.filterFunctions.name]: mock_filters, + [KeyOptimizationLinkTraversal.filterFunctions.name]: mockFilters, }); const action = { url: treeUrl, metadata: input, requestTime: 0, context }; const result = await actor.run(action); - expect(result).toEqual({ links: []}); - for (const mock of mock_filters) { - expect(mock).toBeCalledTimes(1); - expect(mock).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); + for (const mockList of mockFilters.values()) { + for (const mock of mockList) { + expect(mock).toBeCalledTimes(1); + expect(mock).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); + } } + + expect(result).toEqual({ links: []}); }); it('should call the mediator when a query is defined', async() => { diff --git a/packages/context-entries-link-traversal/lib/Keys.ts b/packages/context-entries-link-traversal/lib/Keys.ts index 84323f1ca..7e338abfc 100644 --- a/packages/context-entries-link-traversal/lib/Keys.ts +++ b/packages/context-entries-link-traversal/lib/Keys.ts @@ -23,7 +23,7 @@ export const KeyOptimizationLinkTraversal = { /** * Filter function to optimized the link traversal */ - filterFunctions: new ActionContextKey( + filterFunctions: new ActionContextKey>( '@comunica/optimization-link-traversal:filterFunction', ), }; From 7212a6a431666b2c53b9cad3e0643579116392e2 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 21 Oct 2022 13:47:20 +0200 Subject: [PATCH 006/189] manual filter started --- ...torOptimizeLinkTraversalFilterTreeLinks.ts | 88 ++++++++++++++++++- .../lib/ActorOptimizeLinkTraversal.ts | 2 +- .../linkTraversalOptimizationLinkFilter.ts | 12 +-- 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 43437d6c4..bbab1a975 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -5,6 +5,7 @@ import type { } from '@comunica/bus-optimize-link-traversal'; import { ActorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; import type { IActorTest } from '@comunica/core'; +import type { LinkTraversalOptimizationLinkFilter, LinkTraversalFilterOperator } from '@comunica/types-link-traversal'; import { Algebra } from 'sparqlalgebrajs'; /** @@ -13,16 +14,99 @@ import { Algebra } from 'sparqlalgebrajs'; */ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLinkTraversal { + private static readonly dateTimeDataType = 'http://www.w3.org/2001/XMLSchema#dateTime'; public constructor(args: IActorOptimizeLinkTraversalArgs) { super(args); } public async test(action: IActionOptimizeLinkTraversal): Promise { - return action.operations.type === Algebra.types.FILTER; + return typeof this.findFilterOperation(action) !== 'undefined'; } public async run(action: IActionOptimizeLinkTraversal): Promise { - return { filters: []}; + const filter = this.findFilterOperation(action)!; + const operator = filter.expression.operator; + const target = filter.expression.args[0].term.value; + const value = filter.expression.args[1].term.value; + const valueDatatype = filter.expression.args[1].term.datatype.value; + + const filterFunction = this.selectFilters( + valueDatatype, + operator, + target, + value, + ); + if (typeof filterFunction === 'undefined') { + return { filters: new Map() }; + } + + return { filters: new Map([[ target, [ filterFunction ]]]) }; + } + + private selectFilters( + valueDatatype: string, + operatorFilter: any, + target: any, + valueFilter: any, + ): LinkTraversalOptimizationLinkFilter | undefined { + switch (valueDatatype) { + case ActorOptimizeLinkTraversalFilterTreeLinks.dateTimeDataType: { + return this.timeFilter(operatorFilter, target, valueFilter); + } + } + return undefined; + } + + private timeFilter(operatorFilter: any, target: any, valueFilter: any): + LinkTraversalOptimizationLinkFilter | undefined { + return this.generateFilterFunction(operatorFilter, + target, + valueFilter, + (variable: any) => new Date(variable).getTime()); + } + + private findFilterOperation(action: IActionOptimizeLinkTraversal): Algebra.Operation | undefined { + let nestedOperation = action.operations; + while ('input' in nestedOperation) { + if (nestedOperation.type === Algebra.types.FILTER) { + return nestedOperation; + } + nestedOperation = nestedOperation.input; + } + return undefined; + } + + private generateFilterFunction(operatorFilter: any, + target: any, + valueFilter: any, + modifierFunction: any): LinkTraversalOptimizationLinkFilter | undefined { + switch (operatorFilter) { + case '>': { + return (_subject: string, + value: any, + _operator: LinkTraversalFilterOperator) => modifierFunction(value) > modifierFunction(valueFilter); + } + case '<': { + return (_subject: string, + value: any, + _operator: LinkTraversalFilterOperator) => modifierFunction(value) < modifierFunction(valueFilter); + } + case '<=': { + return (_subject: string, + value: any, + _operator: LinkTraversalFilterOperator) => modifierFunction(value) <= modifierFunction(valueFilter); + } + case '>=': { + return (_subject: string, + value: any, + _operator: LinkTraversalFilterOperator) => modifierFunction(value) >= modifierFunction(valueFilter); + } + case '==': { + return (_subject: string, + value: any, + _operator: LinkTraversalFilterOperator) => modifierFunction(value) === modifierFunction(valueFilter); + } + } } } diff --git a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts index 2c0ff24f3..c17e68322 100644 --- a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts +++ b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts @@ -32,7 +32,7 @@ export interface IActorOptimizeLinkTraversalOutput extends IActorOutput { /** * The filter functions to exclude links when traversing links */ - filters: LinkTraversalOptimizationLinkFilter[]; + filters: Map; } export type IActorOptimizeLinkTraversalArgs = IActorArgs< diff --git a/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts b/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts index 6b18c3e7c..86ce6ed2a 100644 --- a/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts +++ b/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts @@ -3,12 +3,12 @@ export type LinkTraversalOptimizationLinkFilter = (subject: string, operator: LinkTraversalFilterOperator) => boolean; export enum LinkTraversalFilterOperator { - GreaterThan, - GreaterThanOrEqual, + GreaterThan = '>', + GreaterThanOrEqual = '>=', - LowerThan, - LowerThanOrEqual, + LowerThan = '<', + LowerThanOrEqual = '<=', - Equal, - Not + Equal = '==', + Not = '!' } From 6c4884c5658e700cff116f2e7ded74475bbcdc1e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 25 Oct 2022 10:30:31 +0200 Subject: [PATCH 007/189] metadata of TREE starting to be extracted using interface to describe them. --- .../lib/ActorExtractLinksTree.ts | 135 +------- .../lib/treeMetadataExtraction.ts | 76 +++++ .../lib/typeTreeMetadataExtraction.ts | 85 +++++ .../test/ActorExtractLinksTree-test.ts | 299 +----------------- .../test/treeMetadataExtraction-test.ts | 211 ++++++++++++ 5 files changed, 384 insertions(+), 422 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts create mode 100644 packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts create mode 100644 packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 5cc68d357..917a3c67d 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -4,60 +4,19 @@ import type { } from '@comunica/bus-extract-links'; import { ActorExtractLinks } from '@comunica/bus-extract-links'; import type { MediatorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; -import type { ILink } from '@comunica/bus-rdf-resolve-hypermedia-links'; -import { KeysInitQuery } from '@comunica/context-entries'; -import { KeyOptimizationLinkTraversal } from '@comunica/context-entries-link-traversal'; import type { IActorTest } from '@comunica/core'; -import type { LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; -import { LinkTraversalFilterOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; +import { buildRelations, collectRelation } from './treeMetadataExtraction'; +import type { IRelationDescription, IRelation } from './typeTreeMetadataExtraction'; +import { TreeNodes } from './typeTreeMetadataExtraction'; const DF = new DataFactory(); -interface IRelationDescription { - subject: string; value: any; operator: LinkTraversalFilterOperator; -} - /** * A comunica Extract Links Tree Extract Links Actor. */ export class ActorExtractLinksTree extends ActorExtractLinks { - public static readonly aNodeType = DF.namedNode('https://w3id.org/tree#node'); - public static readonly aRelation = DF.namedNode('https://w3id.org/tree#relation'); - - private static readonly rdfTypeNode = DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'); - private static readonly aTreePath = DF.namedNode('https://w3id.org/tree#path'); - private static readonly aTreeValue = DF.namedNode('https://w3id.org/tree#value'); - - private static readonly treeGreaterThan: [RDF.NamedNode, LinkTraversalFilterOperator] = [ - DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), - LinkTraversalFilterOperator.GreaterThan ]; - - private static readonly treeGreaterThanOrEqual: [RDF.NamedNode, LinkTraversalFilterOperator] = [ - DF.namedNode('https://w3id.org/tree#GreaterThanOrEqualToRelation'), - LinkTraversalFilterOperator.GreaterThanOrEqual ]; - - private static readonly treeLessThan: [RDF.NamedNode, LinkTraversalFilterOperator] = [ - DF.namedNode('https://w3id.org/tree#LessThanRelation'), - LinkTraversalFilterOperator.LowerThan ]; - - private static readonly treeLessThanEqual: [RDF.NamedNode, LinkTraversalFilterOperator] = [ - DF.namedNode('https://w3id.org/tree#LessThanOrEqualToRelation'), - LinkTraversalFilterOperator.LowerThanOrEqual ]; - - private static readonly treeEqual: [RDF.NamedNode, LinkTraversalFilterOperator] = [ - DF.namedNode('https://w3id.org/tree#EqualToRelation'), - LinkTraversalFilterOperator.Equal ]; - - private static readonly treeOperators: [RDF.NamedNode, LinkTraversalFilterOperator][] = [ - ActorExtractLinksTree.treeEqual, - ActorExtractLinksTree.treeLessThanEqual, - ActorExtractLinksTree.treeLessThan, - ActorExtractLinksTree.treeGreaterThanOrEqual, - ActorExtractLinksTree.treeGreaterThan, - ]; - private readonly mediatorOptimizeLinkTraversal: MediatorOptimizeLinkTraversal; public constructor(args: IActorExtractLinksTree) { @@ -69,23 +28,13 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } public async run(action: IActionExtractLinks): Promise { - if (action.context.get(KeysInitQuery.query) !== undefined) { - await this.mediatorOptimizeLinkTraversal.mediate({ - operations: action.context.get(KeysInitQuery.query)!, - context: action.context, - }); - } return new Promise((resolve, reject) => { const metadata = action.metadata; const currentNodeUrl = action.url; const pageRelationNodes: Set = new Set(); const relationDescriptions: Map = new Map(); + const relations: IRelation[] = []; const nodeLinks: [string, string][] = []; - const filterFunctions: Map = - typeof action.context.get(KeyOptimizationLinkTraversal.filterFunctions) !== 'undefined' ? - action.context.get(KeyOptimizationLinkTraversal.filterFunctions)! : - new Map(); - const links: ILink[] = []; // Forward errors metadata.on('error', reject); @@ -104,34 +53,14 @@ export class ActorExtractLinksTree extends ActorExtractLinks { for (const [ nodeValue, link ] of nodeLinks) { if (pageRelationNodes.has(nodeValue)) { const relationDescription = relationDescriptions.get(nodeValue); - if (typeof relationDescription !== 'undefined' && filterFunctions.size > 0) { - let addLink = true; - let nFilterApplied = 0; - for (const [ key, filterList ] of filterFunctions.entries()) { - // If the filter is to be apply to the subject and the filter is not respected - if (relationDescription.subject === key) { - for (const filter of filterList) { - if (!filter( - relationDescription.subject, - relationDescription.value, - relationDescription.operator, - )) { - addLink = false; - } else { - nFilterApplied += 1; - } - } - } - } - if (addLink && nFilterApplied !== 0) { - links.push({ url: link }); - } + if (typeof relationDescription !== 'undefined') { + relations.push(collectRelation(relationDescription, link)); } else { - links.push({ url: link }); + relations.push(collectRelation({}, link)); } } } - resolve({ links }); + resolve({ links: relations.map(el => ({ url: el.node })) }); }); }); } @@ -154,55 +83,13 @@ export class ActorExtractLinksTree extends ActorExtractLinks { relationDescriptions: Map, ): void { // If it's a relation of the current node - if (quad.subject.value === url && quad.predicate.equals(ActorExtractLinksTree.aRelation)) { + if (quad.subject.value === url && quad.predicate.value === TreeNodes.Relation) { pageRelationNodes.add(quad.object.value); // If it's a node forward - } else if (quad.predicate.equals(ActorExtractLinksTree.aNodeType)) { + } else if (quad.predicate.value === TreeNodes.Node) { nodeLinks.push([ quad.subject.value, quad.object.value ]); - } else if (quad.predicate.equals(ActorExtractLinksTree.rdfTypeNode)) { - // Set the operator of the relation - let operator: LinkTraversalFilterOperator = LinkTraversalFilterOperator.Equal; - for (const treeOperator of ActorExtractLinksTree.treeOperators) { - if (quad.object.equals(treeOperator[0])) { - operator = treeOperator[1]; - } - } - this.addRelationDescription(relationDescriptions, 'operator', quad, undefined, '', operator); - } else if (quad.predicate.equals(ActorExtractLinksTree.aTreePath)) { - // Set the subject of the relation condition - this.addRelationDescription(relationDescriptions, 'subject', quad, undefined, quad.object.value); - } else if (quad.predicate.equals(ActorExtractLinksTree.aTreeValue)) { - // Set the value of the relation condition - this.addRelationDescription(relationDescriptions, 'value', quad, quad.object.value); - } - } - - private addRelationDescription(relationDescriptions: Map, - field: string, - quad: RDF.Quad, - value: any, - subject = '', - operator: LinkTraversalFilterOperator = LinkTraversalFilterOperator.Equal): void { - const newDescription = relationDescriptions.get(quad.subject.value); - if (typeof newDescription === 'undefined') { - relationDescriptions.set(quad.subject.value, { value, subject, operator }); - } else { - switch (field) { - case 'value': { - newDescription[field] = value; - break; - } - case 'subject': { - newDescription[field] = subject; - break; - } - case 'operator': { - newDescription[field] = operator; - break; - } - } - relationDescriptions.set(quad.subject.value, newDescription); } + buildRelations(relationDescriptions, quad); } } diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts new file mode 100644 index 000000000..bae5504d5 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -0,0 +1,76 @@ +import type * as RDF from 'rdf-js'; +import type { IRelation, IRelationDescription } from './typeTreeMetadataExtraction'; +import { RelationOperator, TreeNodes } from './typeTreeMetadataExtraction'; + +export function collectRelation( + relationDescription: IRelationDescription, + nodeLinks: string, +): IRelation { + return { + '@type': relationDescription.operator, + remainingItems: relationDescription.remainingItems, + path: relationDescription.subject, + value: relationDescription.value, + node: nodeLinks, + }; +} + +export function buildRelations(relationDescriptions: Map, quad: RDF.Quad): void { + if (quad.predicate.value === TreeNodes.RDFTypeNode) { + // Set the operator of the relation + const enumIndexOperator = ( Object.values(RelationOperator)).indexOf(quad.object.value); + const operator: RelationOperator | undefined = + enumIndexOperator === -1 ? undefined : Object.values(RelationOperator)[enumIndexOperator]; + + if (typeof operator !== 'undefined') { + addRelationDescription({ relationDescriptions, quad, operator }); + } + } else if (quad.predicate.value === TreeNodes.Path) { + // Set the subject of the relation condition + addRelationDescription({ + relationDescriptions, + quad, + subject: quad.object.value, + }); + } else if (quad.predicate.value === TreeNodes.Value) { + // Set the value of the relation condition + addRelationDescription({ relationDescriptions, quad, value: quad.object.value }); + } else if (quad.predicate.value === TreeNodes.RemainingItems) { + const remainingItems = Number.parseInt(quad.object.value, 10); + if (!Number.isNaN(remainingItems)) { + addRelationDescription({ relationDescriptions, quad, remainingItems }); + } + } +} + +export function addRelationDescription({ + relationDescriptions, + quad, + value, + subject, + operator, + remainingItems, +}: { + relationDescriptions: Map; + quad: RDF.Quad; + value?: any; + subject?: string | undefined; + operator?: RelationOperator | undefined; + remainingItems?: number | undefined; +}): void { + const currentDescription: IRelationDescription | undefined = relationDescriptions.get(quad.subject.value); + if (typeof currentDescription === 'undefined') { + relationDescriptions.set(quad.subject.value, { value, subject, operator, remainingItems }); + } else { + /* eslint-disable prefer-rest-params */ + const newDescription: IRelationDescription = currentDescription; + const objectArgument = arguments[0]; + for (const [ arg, val ] of Object.entries(objectArgument)) { + if (typeof val !== 'undefined' && arg !== 'relationDescriptions' && arg !== 'quad') { + newDescription[arg] = val; + break; + } + } + /* eslint-enable prefer-rest-params */ + } +} diff --git a/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts new file mode 100644 index 000000000..19002f344 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts @@ -0,0 +1,85 @@ +// From +// https://github.com/TREEcg/tree-metadata-extraction/blob/42be38925cf6a033ddadaca5ecce929902ef1545/src/util/Util.ts +export enum RelationOperator { + PrefixRelation = 'https://w3id.org/tree#PrefixRelation', + SubstringRelation = 'https://w3id.org/tree#SubstringRelation', + GreaterThanRelation = 'https://w3id.org/tree#GreaterThanRelation', + GreaterThanOrEqualToRelation = 'https://w3id.org/tree#GreaterThanOrEqualToRelation', + LessThanRelation = 'https://w3id.org/tree#LessThanRelation', + LessThanOrEqualToRelation = 'https://w3id.org/tree#LessThanOrEqualToRelation', + EqualThanRelation = 'https://w3id.org/tree#EqualThanRelation', + GeospatiallyContainsRelation = 'https://w3id.org/tree#GeospatiallyContainsRelation', + InBetweenRelation = 'https://w3id.org/tree#InBetweenRelation', +} + +export enum TreeNodes { + Node = 'https://w3id.org/tree#node', + Relation = 'https://w3id.org/tree#relation', + RDFTypeNode = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', + Path = 'https://w3id.org/tree#path', + Value = 'https://w3id.org/tree#value', + RemainingItems = 'https://w3id.org/tree#remainingItems' +} + +export interface INode { + '@context'?: string | object; + '@id': string; + '@type'?: string[]; + // Note hydra:next + // as:next links are added as Relations of type tree:relation with the target node as the target of the next relation + 'relation'?: IURI[]; + 'search'?: IIriTemplate[]; + 'retentionPolicy'?: IRetentionPolicy[]; + 'import'?: IURI[]; + 'importStream'?: IURI[]; + 'conditionalImport'?: IConditionalImport[]; + [property: string]: any; + +} + +export interface IRelation { + '@type'?: string; + 'remainingItems'?: number; + 'path'?: any; + 'value'?: any; + 'node': string; + 'conditionalImport'?: IConditionalImport; +} + +export interface IMember { + '@id'?: string; + [property: string]: any; +} + +export interface IIriTemplate { + '@id'?: string; + [property: string]: any; +} +export interface IConditionalImport { + 'path'?: string; + 'import'?: string; + 'importStream'?: string; +} + +export interface IRetentionPolicy { + '@id'?: string; + '@type'?: string[]; + 'amount'?: ILiteral[]; + 'versionKey'?: string[]; + 'path'?: any[]; + 'value'?: any[]; +} + +export interface ILiteral { + '@value': string; + '@type'?: string; + '@language'?: string; +} + +export interface IURI { + '@id': string; +} +export interface IRelationDescription { + subject?: string; value?: any; operator?: RelationOperator; remainingItems?: number; +} + diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 05337244a..d1cec5d26 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -1,8 +1,6 @@ -import { KeysRdfResolveQuadPattern, KeysInitQuery } from '@comunica/context-entries'; -import { KeyOptimizationLinkTraversal } from '@comunica/context-entries-link-traversal'; +import { KeysRdfResolveQuadPattern } from '@comunica/context-entries'; import { ActionContext, Bus } from '@comunica/core'; import type { LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; -import { LinkTraversalFilterOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; @@ -234,301 +232,6 @@ describe('ActorExtractLinksExtractLinksTree', () => { }); }); - describe('The ActorExtractLinksExtractLinksTree run method with filter', () => { - let actor: ActorExtractLinksTree; - const treeUrl = 'ex:s'; - - beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', mediatorOptimizeLinkTraversal: mockMediator, bus }); - }); - - it('should return the links of a TREE with one relation and filter that returns always true', async() => { - const expectedUrl = 'http://foo.com'; - const input = stream([ - DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#relation'), - DF.blankNode('_:_g1'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#node'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#path'), - DF.namedNode('ex:path'), - DF.namedNode('ex:gx')), - - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#value'), - DF.literal('value'), - DF.namedNode('ex:gx')), - - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), - DF.namedNode('ex:gx')), - ]); - const mock_filter = jest.fn((_subject, _value, _operator) => true); - const context = new ActionContext({ - [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeyOptimizationLinkTraversal.filterFunctions.name]: new Map([[ 'ex:path', [ mock_filter ]]]), - }); - - const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - - const result = await actor.run(action); - - expect(mock_filter).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); - expect(result).toEqual({ links: [{ url: expectedUrl }]}); - expect(mock_filter).toBeCalledTimes(1); - }); - - it('should return no links of a TREE with one relation and filter that returns always false', async() => { - const expectedUrl = 'http://foo.com'; - const input = stream([ - DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#relation'), - DF.blankNode('_:_g1'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#node'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), - DF.namedNode('ex:gx')), - - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#path'), - DF.namedNode('ex:path'), - DF.namedNode('ex:gx')), - - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#value'), - DF.literal('value'), - DF.namedNode('ex:gx')), - - ]); - const mock_filter = jest.fn((_subject, _value, _operator) => false); - const context = new ActionContext({ - [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeyOptimizationLinkTraversal.filterFunctions.name]: new Map([[ 'ex:path', [ mock_filter ]]]), - }); - - const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - - const result = await actor.run(action); - - expect(result).toEqual({ links: []}); - expect(mock_filter).toBeCalledTimes(1); - expect(mock_filter).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); - }); - - it('should return the links of a TREE with one relation and multiple filter that returns always true', async() => { - const expectedUrl = 'http://foo.com'; - const input = stream([ - DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#relation'), - DF.blankNode('_:_g1'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#node'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#value'), - DF.literal('value'), - DF.namedNode('ex:gx')), - - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), - DF.namedNode('ex:gx')), - - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#path'), - DF.namedNode('ex:path'), - DF.namedNode('ex:gx')), - ]); - const mock_filters = new Map([ - [ 'ex:path', [ jest.fn((_subject, _value, _operator) => true), - jest.fn((_subject, _value, _operator) => true), - jest.fn((_subject, _value, _operator) => true), - jest.fn((_subject, _value, _operator) => true) ]], - ]); - const context = new ActionContext({ - [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeyOptimizationLinkTraversal.filterFunctions.name]: mock_filters, - }); - - const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - - const result = await actor.run(action); - - expect(result).toEqual({ links: [{ url: expectedUrl }]}); - for (const mockList of mock_filters.values()) { - for (const mock of mockList) { - expect(mock).toBeCalledTimes(1); - expect(mock).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); - } - } - }); - - it('should return no links with one relation and multiple filter that returns true and one that return false', - async() => { - const expectedUrl = 'http://foo.com'; - const input = stream([ - DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#relation'), - DF.blankNode('_:_g1'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#node'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#value'), - DF.literal('value'), - DF.namedNode('ex:gx')), - - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#path'), - DF.namedNode('ex:path'), - DF.namedNode('ex:gx')), - - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), - DF.namedNode('ex:gx')), - ]); - const mockFilters = new Map([ - [ 'ex:path', [ - jest.fn((_subject, _value, _operator) => true), - jest.fn((_subject, _value, _operator) => true), - jest.fn((_subject, _value, _operator) => false), - jest.fn((_subject, _value, _operator) => true), - ], - - ], - ]); - const context = new ActionContext({ - [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeyOptimizationLinkTraversal.filterFunctions.name]: mockFilters, - }); - - const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - - const result = await actor.run(action); - - for (const mockList of mockFilters.values()) { - for (const mock of mockList) { - expect(mock).toBeCalledTimes(1); - expect(mock).toBeCalledWith('ex:path', 'value', LinkTraversalFilterOperator.GreaterThan); - } - } - - expect(result).toEqual({ links: []}); - }); - - it('should call the mediator when a query is defined', async() => { - const expectedUrl = 'http://foo.com'; - const input = stream([ - DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#relation'), - DF.blankNode('_:_g1'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#node'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - ]); - - const operation = { - type: 'project', - input: { - type: 'graph', - input: { - type: 'bgp', - patterns: [{ - type: 'pattern', - termType: 'Quad', - subject: { termType: 'Variable', value: 'x' }, - predicate: { termType: 'Variable', value: 'y' }, - object: { termType: 'Variable', value: 'z' }, - graph: { termType: 'DefaultGraph', value: '' }, - }], - }, - name: { termType: 'Variable', value: 'g' }, - }, - variables: [{ termType: 'Variable', value: 'x' }], - }; - - const context = new ActionContext({ - [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeysInitQuery.query.name]: operation, - }); - - const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - - const result = await actor.run(action); - - expect(result).toEqual({ links: [{ url: expectedUrl }]}); - - expect(spyMockMediator).toBeCalledWith(expect.objectContaining({ - operations: operation, - })); - }); - }); describe('The ActorExtractLinksExtractLinksTree test method', () => { let actor: ActorExtractLinksTree; const treeUrl = 'ex:s'; diff --git a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts new file mode 100644 index 000000000..a721646bd --- /dev/null +++ b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts @@ -0,0 +1,211 @@ +import { DataFactory } from 'rdf-data-factory'; +import type * as RDF from 'rdf-js'; +import { buildRelations, addRelationDescription } from '../lib/treeMetadataExtraction'; +import type { IRelationDescription } from '../lib/typeTreeMetadataExtraction'; +import { TreeNodes, RelationOperator } from '../lib/typeTreeMetadataExtraction'; + +const DF = new DataFactory(); + +describe('treeMetadataExtraction', () => { + describe('addRelationDescription', () => { + it('should add relation to the map when an operator is provided and the relation map is empty', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RDFTypeNode), + DF.namedNode(RelationOperator.EqualThanRelation)); + const relationDescriptions: Map = new Map(); + addRelationDescription({ relationDescriptions, quad, operator: RelationOperator.EqualThanRelation }); + expect(relationDescriptions.size).toBe(1); + }); + + it(`should add relation to the map when an operator is provided and + the relation map at the current key is not empty`, + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RDFTypeNode), + DF.namedNode(RelationOperator.EqualThanRelation)); + const relationDescriptions: Map = new Map([[ 'ex:s', { value: 22 }]]); + addRelationDescription({ relationDescriptions, quad, operator: RelationOperator.EqualThanRelation }); + expect(relationDescriptions.size).toBe(1); + }); + + it('should add relation to the map when an operator is provided and the relation map is not empty', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RDFTypeNode), + DF.namedNode(RelationOperator.EqualThanRelation)); + const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); + addRelationDescription({ relationDescriptions, quad, operator: RelationOperator.EqualThanRelation }); + expect(relationDescriptions.size).toBe(2); + }); + + it('should add relation to the map when a value is provided and the relation map is empty', () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); + const relationDescriptions: Map = new Map(); + addRelationDescription({ relationDescriptions, quad, value: '5' }); + expect(relationDescriptions.size).toBe(1); + }); + + it('should add relation to the map when a value is provided and the relation map at the current key is not empty', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); + const relationDescriptions: Map = new Map([[ 'ex:s', { subject: 'ex:s' }]]); + addRelationDescription({ relationDescriptions, quad, value: '5' }); + expect(relationDescriptions.size).toBe(1); + }); + + it('should add relation to the map when a value is provided and the relation map is not empty', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); + const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); + addRelationDescription({ relationDescriptions, quad, value: '5' }); + expect(relationDescriptions.size).toBe(2); + }); + + it('should add relation to the map when a subject is provided and the relation map is empty', () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Path), DF.namedNode('ex:path')); + const relationDescriptions: Map = new Map(); + addRelationDescription({ relationDescriptions, quad, subject: 'ex:path' }); + expect(relationDescriptions.size).toBe(1); + }); + + it('should add relation to the map when a subject is provided and the relation map at the current key is not empty', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Path), DF.namedNode('ex:path')); + const relationDescriptions: Map = new Map([[ 'ex:s', { subject: 'ex:s' }]]); + addRelationDescription({ relationDescriptions, quad, subject: 'ex:path' }); + expect(relationDescriptions.size).toBe(1); + }); + + it('should add relation to the map when a subject is provided and the relation map is not empty', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); + const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); + addRelationDescription({ relationDescriptions, quad, subject: 'ex:path' }); + expect(relationDescriptions.size).toBe(2); + }); + }); + + describe('buildRelations', () => { + it('should not modify the relation map when the predicate is not related to the relations', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode('ex:p'), DF.namedNode('ex:o')); + const relationDescriptions: Map = new Map(); + buildRelations(relationDescriptions, quad); + expect(relationDescriptions.size).toBe(0); + }); + + it('should modify the relation map when the predicate is a rdf type with a supported relation', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RDFTypeNode), + DF.namedNode(RelationOperator.EqualThanRelation)); + const relationDescriptions: Map = new Map(); + + const expectedDescription = new Map([[ 'ex:s', { operator: RelationOperator.EqualThanRelation, + subject: undefined, + value: undefined, + remainingItems: undefined }]]); + buildRelations(relationDescriptions, quad); + expect(relationDescriptions.size).toBe(1); + + expect(relationDescriptions).toStrictEqual(expectedDescription); + }); + + it('should not modify the relation map when the predicate is a rdf type an supported relation', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RDFTypeNode), + DF.namedNode('foo')); + const relationDescriptions: Map = new Map(); + buildRelations(relationDescriptions, quad); + + expect(relationDescriptions.size).toBe(0); + }); + + it('should modify an map with another relation when the predicate is a rdf type an supported relation', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RDFTypeNode), + DF.namedNode(RelationOperator.EqualThanRelation)); + const relationDescriptions: Map = new Map([[ 'ex:s2', {}]]); + const expectedDescription: Map = new Map([[ 'ex:s2', {}], + [ 'ex:s', + { operator: RelationOperator.EqualThanRelation, + subject: undefined, + value: undefined, + remainingItems: undefined }]]); + + buildRelations(relationDescriptions, quad); + expect(relationDescriptions.size).toBe(2); + expect(relationDescriptions).toStrictEqual(expectedDescription); + }); + + it(`should modify an map with a relation that has already been started + to be defined when the predicate is a rdf type an supported relation`, + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RDFTypeNode), + DF.namedNode(RelationOperator.EqualThanRelation)); + const relationDescriptions: Map = new Map([[ 'ex:s', + { subject: 'ex:path', value: undefined, remainingItems: undefined }]]); + const expectedDescription: Map = new Map([[ 'ex:s', + { operator: RelationOperator.EqualThanRelation, + subject: 'ex:path', + value: undefined, + remainingItems: undefined }]]); + + buildRelations(relationDescriptions, quad); + + expect(relationDescriptions.size).toBe(1); + expect(relationDescriptions).toStrictEqual(expectedDescription); + }); + + it('should not modify the relation map when no new values are provided', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode('bar'), + DF.namedNode(RelationOperator.EqualThanRelation)); + const relationDescriptions: Map = new Map([[ 'ex:s', + { subject: 'ex:path', + value: undefined, + remainingItems: undefined }]]); + const expectedDescription: Map = relationDescriptions; + + buildRelations(relationDescriptions, quad); + + expect(relationDescriptions.size).toBe(1); + expect(relationDescriptions).toStrictEqual(expectedDescription); + }); + + it('should modify the relation map when a remainingItems field is provided with a valid number', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RemainingItems), + DF.namedNode('45')); + const relationDescriptions: Map = new Map([[ 'ex:s', + { subject: 'ex:path', + value: undefined, + remainingItems: undefined }]]); + const expectedDescription: Map = new Map([[ 'ex:s', + { subject: 'ex:path', value: undefined, remainingItems: 45 }]]); + + buildRelations(relationDescriptions, quad); + + expect(relationDescriptions.size).toBe(1); + expect(relationDescriptions).toStrictEqual(expectedDescription); + }); + + it('should not modify the relation map when a remainingItems field is provided with an unvalid number', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RemainingItems), + DF.namedNode('foo')); + const relationDescriptions: Map = new Map(); + + buildRelations(relationDescriptions, quad); + + expect(relationDescriptions.size).toBe(0); + }); + }); +}); From 449fc4a8c5e1f205b6681d6359b4a39136360754 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 25 Oct 2022 10:35:18 +0200 Subject: [PATCH 008/189] non usefull metadata of TREE deleted --- .../lib/typeTreeMetadataExtraction.ts | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts index 19002f344..b3568ac71 100644 --- a/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts @@ -21,22 +21,6 @@ export enum TreeNodes { RemainingItems = 'https://w3id.org/tree#remainingItems' } -export interface INode { - '@context'?: string | object; - '@id': string; - '@type'?: string[]; - // Note hydra:next - // as:next links are added as Relations of type tree:relation with the target node as the target of the next relation - 'relation'?: IURI[]; - 'search'?: IIriTemplate[]; - 'retentionPolicy'?: IRetentionPolicy[]; - 'import'?: IURI[]; - 'importStream'?: IURI[]; - 'conditionalImport'?: IConditionalImport[]; - [property: string]: any; - -} - export interface IRelation { '@type'?: string; 'remainingItems'?: number; @@ -46,39 +30,11 @@ export interface IRelation { 'conditionalImport'?: IConditionalImport; } -export interface IMember { - '@id'?: string; - [property: string]: any; -} - -export interface IIriTemplate { - '@id'?: string; - [property: string]: any; -} export interface IConditionalImport { 'path'?: string; 'import'?: string; 'importStream'?: string; } - -export interface IRetentionPolicy { - '@id'?: string; - '@type'?: string[]; - 'amount'?: ILiteral[]; - 'versionKey'?: string[]; - 'path'?: any[]; - 'value'?: any[]; -} - -export interface ILiteral { - '@value': string; - '@type'?: string; - '@language'?: string; -} - -export interface IURI { - '@id': string; -} export interface IRelationDescription { subject?: string; value?: any; operator?: RelationOperator; remainingItems?: number; } From 8cae03766ed2e9bac61347447ee139d8e2c7a882 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 25 Oct 2022 10:36:52 +0200 Subject: [PATCH 009/189] comment added for the description of an interface --- .../lib/typeTreeMetadataExtraction.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts index b3568ac71..70a6c69be 100644 --- a/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts @@ -35,6 +35,8 @@ export interface IConditionalImport { 'import'?: string; 'importStream'?: string; } + +// An helper to build the relation from a stream export interface IRelationDescription { subject?: string; value?: any; operator?: RelationOperator; remainingItems?: number; } From 81016c177da7a42e7f6b3ef7b354d93fd30e4e75 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 26 Oct 2022 08:48:21 +0200 Subject: [PATCH 010/189] conditional relation deleted --- .../lib/typeTreeMetadataExtraction.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts index 70a6c69be..6b3a8d8ce 100644 --- a/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts @@ -27,13 +27,6 @@ export interface IRelation { 'path'?: any; 'value'?: any; 'node': string; - 'conditionalImport'?: IConditionalImport; -} - -export interface IConditionalImport { - 'path'?: string; - 'import'?: string; - 'importStream'?: string; } // An helper to build the relation from a stream From b49b9742810dcc1603b1db2b943057ed59cf0731 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 26 Oct 2022 13:26:43 +0200 Subject: [PATCH 011/189] implementation with custom bus --- ...torOptimizeLinkTraversalFilterTreeLinks.ts | 118 +++++++----------- .../lib/ActorOptimizeLinkTraversal.ts | 6 +- .../linkTraversalOptimizationLinkFilter.ts | 14 --- 3 files changed, 50 insertions(+), 88 deletions(-) delete mode 100644 packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index bbab1a975..01f39328a 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -7,6 +7,10 @@ import { ActorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversa import type { IActorTest } from '@comunica/core'; import type { LinkTraversalOptimizationLinkFilter, LinkTraversalFilterOperator } from '@comunica/types-link-traversal'; import { Algebra } from 'sparqlalgebrajs'; +import { AsyncEvaluator, isExpressionError } from 'sparqlee'; +import { Bindings } from '@comunica/types'; +import { bindingsToString } from '@comunica/bindings-factory'; + /** * A comunica Link traversal optimizer that filter link of document @@ -14,99 +18,69 @@ import { Algebra } from 'sparqlalgebrajs'; */ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLinkTraversal { - private static readonly dateTimeDataType = 'http://www.w3.org/2001/XMLSchema#dateTime'; public constructor(args: IActorOptimizeLinkTraversalArgs) { super(args); } public async test(action: IActionOptimizeLinkTraversal): Promise { - return typeof this.findFilterOperation(action) !== 'undefined'; + return this.findFilterOperation(action, true).length === 1; } public async run(action: IActionOptimizeLinkTraversal): Promise { - const filter = this.findFilterOperation(action)!; - const operator = filter.expression.operator; - const target = filter.expression.args[0].term.value; - const value = filter.expression.args[1].term.value; - const valueDatatype = filter.expression.args[1].term.datatype.value; - - const filterFunction = this.selectFilters( - valueDatatype, - operator, - target, - value, - ); - if (typeof filterFunction === 'undefined') { - return { filters: new Map() }; - } + const filters = this.findFilterOperation(action); + const filterMap: Map = new Map(); + for (const bindingsStream of action.decisionZoneBindingStream) { + for (const filter of filters) { + const evaluator = new AsyncEvaluator(filter.expression); - return { filters: new Map([[ target, [ filterFunction ]]]) }; - } - - private selectFilters( - valueDatatype: string, - operatorFilter: any, - target: any, - valueFilter: any, - ): LinkTraversalOptimizationLinkFilter | undefined { - switch (valueDatatype) { - case ActorOptimizeLinkTraversalFilterTreeLinks.dateTimeDataType: { - return this.timeFilter(operatorFilter, target, valueFilter); + const transform = async(item: Bindings, next: any, push: (bindings: Bindings) => void): Promise => { + try { + const result = await evaluator.evaluateAsEBV(item); + if (result) { + push(item); + } + } catch (error: unknown) { + // 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 (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(action.context, 'Error occurred while filtering.', () => ({ error, bindings: bindingsToString(item) })); + } else { + bindingsStream.emit('error', error); + } + } + next(); + }; + const result = bindingsStream.transform({ transform }); } } - return undefined; - } - private timeFilter(operatorFilter: any, target: any, valueFilter: any): - LinkTraversalOptimizationLinkFilter | undefined { - return this.generateFilterFunction(operatorFilter, - target, - valueFilter, - (variable: any) => new Date(variable).getTime()); } + - private findFilterOperation(action: IActionOptimizeLinkTraversal): Algebra.Operation | undefined { + + private findFilterOperation(action: IActionOptimizeLinkTraversal, oneResult=false): Algebra.Operation[] { let nestedOperation = action.operations; + const filters: Algebra.Operation[] = [] while ('input' in nestedOperation) { if (nestedOperation.type === Algebra.types.FILTER) { - return nestedOperation; + filters.push(nestedOperation); + if (oneResult) { + break; + } } nestedOperation = nestedOperation.input; } - return undefined; + return filters } - private generateFilterFunction(operatorFilter: any, - target: any, - valueFilter: any, - modifierFunction: any): LinkTraversalOptimizationLinkFilter | undefined { - switch (operatorFilter) { - case '>': { - return (_subject: string, - value: any, - _operator: LinkTraversalFilterOperator) => modifierFunction(value) > modifierFunction(valueFilter); - } - case '<': { - return (_subject: string, - value: any, - _operator: LinkTraversalFilterOperator) => modifierFunction(value) < modifierFunction(valueFilter); - } - case '<=': { - return (_subject: string, - value: any, - _operator: LinkTraversalFilterOperator) => modifierFunction(value) <= modifierFunction(valueFilter); - } - case '>=': { - return (_subject: string, - value: any, - _operator: LinkTraversalFilterOperator) => modifierFunction(value) >= modifierFunction(valueFilter); - } - case '==': { - return (_subject: string, - value: any, - _operator: LinkTraversalFilterOperator) => modifierFunction(value) === modifierFunction(valueFilter); - } - } - } } diff --git a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts index c17e68322..f1df1e8ec 100644 --- a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts +++ b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts @@ -2,6 +2,7 @@ import type { IAction, IActorArgs, IActorOutput, IActorTest, Mediate } from '@co import { Actor } from '@comunica/core'; import type { LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; import type { Algebra } from 'sparqlalgebrajs'; +import { BindingsStream } from '@comunica/types'; /** * A comunica actor for optimization of link traversal * @@ -26,13 +27,14 @@ IActorOptimizeLinkTraversalOutput> { export interface IActionOptimizeLinkTraversal extends IAction { operations: Algebra.Operation; + decisionZoneBindingStream: BindingsStream[]; } export interface IActorOptimizeLinkTraversalOutput extends IActorOutput { /** - * The filter functions to exclude links when traversing links + * The decision weith the link specific link should be follow */ - filters: Map; + filters: Map; } export type IActorOptimizeLinkTraversalArgs = IActorArgs< diff --git a/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts b/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts deleted file mode 100644 index 86ce6ed2a..000000000 --- a/packages/types-link-traversal/lib/linkTraversalOptimizationLinkFilter.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type LinkTraversalOptimizationLinkFilter = (subject: string, - value: any, - operator: LinkTraversalFilterOperator) => boolean; - -export enum LinkTraversalFilterOperator { - GreaterThan = '>', - GreaterThanOrEqual = '>=', - - LowerThan = '<', - LowerThanOrEqual = '<=', - - Equal = '==', - Not = '!' -} From 175255a31dee43a8dc6cc952d87095b885b2dc26 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 31 Oct 2022 14:40:33 +0100 Subject: [PATCH 012/189] filter place in the new bus with sparqlee --- .../lib/ActorExtractLinksTree.ts | 16 +++- .../lib/treeMetadataExtraction.ts | 70 ++++++++++---- .../test/ActorExtractLinksTree-test.ts | 4 +- .../test/treeMetadataExtraction-test.ts | 32 ++++--- ...torOptimizeLinkTraversalFilterTreeLinks.ts | 94 +++++++++---------- .../.npmignore | 0 .../README.md | 42 +++++++++ ...ActorQueryOperationFilterLinksTraversal.ts | 36 +++++++ .../lib/index.ts | 1 + .../package.json | 42 +++++++++ ...QueryOperationFilterLinksTraversal-test.ts | 64 +++++++++++++ .../lib/ActorRdfMetadataExtractTraverse.ts | 1 + .../lib/ActorOptimizeLinkTraversal.ts | 9 +- .../lib/Keys.ts | 9 +- .../lib/TreeMetadata.ts} | 34 +++++-- packages/types-link-traversal/lib/index.ts | 2 +- 16 files changed, 353 insertions(+), 103 deletions(-) create mode 100644 packages/actor-query-operation-filter-links-traversal/.npmignore create mode 100644 packages/actor-query-operation-filter-links-traversal/README.md create mode 100644 packages/actor-query-operation-filter-links-traversal/lib/ActorQueryOperationFilterLinksTraversal.ts create mode 100644 packages/actor-query-operation-filter-links-traversal/lib/index.ts create mode 100644 packages/actor-query-operation-filter-links-traversal/package.json create mode 100644 packages/actor-query-operation-filter-links-traversal/test/ActorQueryOperationFilterLinksTraversal-test.ts rename packages/{actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts => types-link-traversal/lib/TreeMetadata.ts} (70%) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 917a3c67d..e805095df 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -6,10 +6,12 @@ import { ActorExtractLinks } from '@comunica/bus-extract-links'; import type { MediatorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; import type { IActorTest } from '@comunica/core'; import { DataFactory } from 'rdf-data-factory'; -import type * as RDF from 'rdf-js'; +import * as RDF from 'rdf-js'; import { buildRelations, collectRelation } from './treeMetadataExtraction'; -import type { IRelationDescription, IRelation } from './typeTreeMetadataExtraction'; -import { TreeNodes } from './typeTreeMetadataExtraction'; +import type { IRelationDescription, IRelation, INode } from '@comunica/types-link-traversal'; +import { TreeNodes } from '@comunica/types-link-traversal'; +import { url } from 'inspector'; +const streamifyArray = require('streamify-array'); const DF = new DataFactory(); @@ -60,6 +62,9 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } } } + + const node:INode = {relation: relations, subject: currentNodeUrl}; + this.mediatorOptimizeLinkTraversal.mediate({treeMetadata:node, context:action.context}); resolve({ links: relations.map(el => ({ url: el.node })) }); }); }); @@ -97,5 +102,6 @@ export interface IActorExtractLinksTree extends IActorExtractLinksArgs { /** * The optmize link traversal mediator */ - mediatorOptimizeLinkTraversal: MediatorOptimizeLinkTraversal; -} + mediatorOptimizeLinkTraversal: MediatorOptimizeLinkTraversal; + } + diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts index bae5504d5..ec703dba6 100644 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -1,21 +1,54 @@ import type * as RDF from 'rdf-js'; -import type { IRelation, IRelationDescription } from './typeTreeMetadataExtraction'; -import { RelationOperator, TreeNodes } from './typeTreeMetadataExtraction'; +import type { IRelation, IRelationDescription } from '@comunica/types-link-traversal'; +import { RelationOperator, TreeNodes } from '@comunica/types-link-traversal'; export function collectRelation( relationDescription: IRelationDescription, nodeLinks: string, ): IRelation { - return { - '@type': relationDescription.operator, - remainingItems: relationDescription.remainingItems, - path: relationDescription.subject, - value: relationDescription.value, - node: nodeLinks, - }; + const relation: IRelation = {node: nodeLinks}; + const typeRelation = typeof relationDescription.operator !== 'undefined'? ( + typeof relationDescription.operator[0] !== 'undefined'? relationDescription.operator[0]: undefined): undefined; + if (typeof typeRelation !== 'undefined' && typeof relationDescription.operator !== 'undefined'){ + relation['@type'] = { + value: typeRelation, + quad: relationDescription.operator[1], + } + } + const remainingItems = typeof relationDescription.remainingItems !== 'undefined'?( + typeof relationDescription.remainingItems[0] !== 'undefined'? relationDescription.remainingItems[0]: undefined):undefined; + if (typeof remainingItems !== 'undefined' && typeof relationDescription.remainingItems !== 'undefined') { + relation.remainingItems = { + value: remainingItems, + quad: relationDescription.remainingItems[1] + } + } + + const path = typeof relationDescription.subject !== 'undefined'?( + typeof relationDescription.subject[0] !=='undefined'? relationDescription.subject[0]: undefined):undefined; + if (typeof path !== 'undefined' && typeof relationDescription.subject !== 'undefined') { + relation.path = { + value: path, + quad: relationDescription.subject[1] + } + } + + const value = typeof relationDescription.value !== 'undefined'?( + typeof relationDescription.value[0] !== 'undefined'? relationDescription.value[0]: undefined):undefined; + if (typeof value !== 'undefined' && typeof relationDescription.value!== 'undefined'){ + relation.value = { + value:value, + quad: relationDescription.value[1] + } + } + + return relation + } -export function buildRelations(relationDescriptions: Map, quad: RDF.Quad): void { +export function buildRelations( + relationDescriptions: Map, + quad: RDF.Quad): void { if (quad.predicate.value === TreeNodes.RDFTypeNode) { // Set the operator of the relation const enumIndexOperator = ( Object.values(RelationOperator)).indexOf(quad.object.value); @@ -53,21 +86,26 @@ export function addRelationDescription({ }: { relationDescriptions: Map; quad: RDF.Quad; - value?: any; - subject?: string | undefined; - operator?: RelationOperator | undefined; - remainingItems?: number | undefined; + value?: string; + subject?: string; + operator?: RelationOperator; + remainingItems?: number; }): void { const currentDescription: IRelationDescription | undefined = relationDescriptions.get(quad.subject.value); if (typeof currentDescription === 'undefined') { - relationDescriptions.set(quad.subject.value, { value, subject, operator, remainingItems }); + relationDescriptions.set(quad.subject.value, { + value:typeof value !== 'undefined'?[value, quad]:undefined, + subject: typeof subject !== 'undefined'? [subject, quad]: undefined, + operator:typeof operator !== 'undefined'?[operator, quad]: undefined, + remainingItems: typeof remainingItems !== 'undefined'?[remainingItems, quad]: undefined, + }); } else { /* eslint-disable prefer-rest-params */ const newDescription: IRelationDescription = currentDescription; const objectArgument = arguments[0]; for (const [ arg, val ] of Object.entries(objectArgument)) { if (typeof val !== 'undefined' && arg !== 'relationDescriptions' && arg !== 'quad') { - newDescription[arg] = val; + newDescription[arg] = [val, quad]; break; } } diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index d1cec5d26..afb05d271 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -53,7 +53,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { }); beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', mediatorOptimizeLinkTraversal: mockMediator, bus }); + actor = new ActorExtractLinksTree({ name: 'actor', bus }); }); it('should return the links of a TREE with one relation', async() => { @@ -237,7 +237,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const treeUrl = 'ex:s'; beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', mediatorOptimizeLinkTraversal: mockMediator, bus }); + actor = new ActorExtractLinksTree({ name: 'actor', bus }); }); it('should test when giving a TREE', async() => { diff --git a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts index a721646bd..658c4c5f3 100644 --- a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts +++ b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts @@ -10,11 +10,15 @@ describe('treeMetadataExtraction', () => { describe('addRelationDescription', () => { it('should add relation to the map when an operator is provided and the relation map is empty', () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + const quad: RDF.Quad = DF.quad( + DF.namedNode('ex:s'), DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode(RelationOperator.EqualThanRelation)); + DF.namedNode(RelationOperator.EqualThanRelation + )); const relationDescriptions: Map = new Map(); addRelationDescription({ relationDescriptions, quad, operator: RelationOperator.EqualThanRelation }); + + expect(relationDescriptions.size).toBe(1); }); @@ -49,7 +53,7 @@ describe('treeMetadataExtraction', () => { it('should add relation to the map when a value is provided and the relation map at the current key is not empty', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); - const relationDescriptions: Map = new Map([[ 'ex:s', { subject: 'ex:s' }]]); + const relationDescriptions: Map = new Map([[ 'ex:s', { subject: ['ex:s', quad] }]]); addRelationDescription({ relationDescriptions, quad, value: '5' }); expect(relationDescriptions.size).toBe(1); }); @@ -72,7 +76,7 @@ describe('treeMetadataExtraction', () => { it('should add relation to the map when a subject is provided and the relation map at the current key is not empty', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Path), DF.namedNode('ex:path')); - const relationDescriptions: Map = new Map([[ 'ex:s', { subject: 'ex:s' }]]); + const relationDescriptions: Map = new Map([[ 'ex:s', { subject: ['ex:s', quad] }]]); addRelationDescription({ relationDescriptions, quad, subject: 'ex:path' }); expect(relationDescriptions.size).toBe(1); }); @@ -92,6 +96,7 @@ describe('treeMetadataExtraction', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode('ex:p'), DF.namedNode('ex:o')); const relationDescriptions: Map = new Map(); buildRelations(relationDescriptions, quad); + expect(relationDescriptions.size).toBe(0); }); @@ -102,7 +107,7 @@ describe('treeMetadataExtraction', () => { DF.namedNode(RelationOperator.EqualThanRelation)); const relationDescriptions: Map = new Map(); - const expectedDescription = new Map([[ 'ex:s', { operator: RelationOperator.EqualThanRelation, + const expectedDescription = new Map([[ 'ex:s', { operator: [RelationOperator.EqualThanRelation, quad], subject: undefined, value: undefined, remainingItems: undefined }]]); @@ -131,10 +136,12 @@ describe('treeMetadataExtraction', () => { const relationDescriptions: Map = new Map([[ 'ex:s2', {}]]); const expectedDescription: Map = new Map([[ 'ex:s2', {}], [ 'ex:s', - { operator: RelationOperator.EqualThanRelation, + { operator: [RelationOperator.EqualThanRelation, quad], subject: undefined, value: undefined, remainingItems: undefined }]]); + const relationQuads: Map = new Map(); + buildRelations(relationDescriptions, quad); expect(relationDescriptions.size).toBe(2); @@ -148,10 +155,10 @@ describe('treeMetadataExtraction', () => { DF.namedNode(TreeNodes.RDFTypeNode), DF.namedNode(RelationOperator.EqualThanRelation)); const relationDescriptions: Map = new Map([[ 'ex:s', - { subject: 'ex:path', value: undefined, remainingItems: undefined }]]); + { subject: ['ex:path', quad], value: undefined, remainingItems: undefined }]]); const expectedDescription: Map = new Map([[ 'ex:s', - { operator: RelationOperator.EqualThanRelation, - subject: 'ex:path', + { operator: [RelationOperator.EqualThanRelation, quad], + subject: ['ex:path', quad], value: undefined, remainingItems: undefined }]]); @@ -167,7 +174,7 @@ describe('treeMetadataExtraction', () => { DF.namedNode('bar'), DF.namedNode(RelationOperator.EqualThanRelation)); const relationDescriptions: Map = new Map([[ 'ex:s', - { subject: 'ex:path', + { subject: ['ex:path', quad], value: undefined, remainingItems: undefined }]]); const expectedDescription: Map = relationDescriptions; @@ -184,11 +191,11 @@ describe('treeMetadataExtraction', () => { DF.namedNode(TreeNodes.RemainingItems), DF.namedNode('45')); const relationDescriptions: Map = new Map([[ 'ex:s', - { subject: 'ex:path', + { subject: ['ex:path', quad], value: undefined, remainingItems: undefined }]]); const expectedDescription: Map = new Map([[ 'ex:s', - { subject: 'ex:path', value: undefined, remainingItems: 45 }]]); + { subject: ['ex:path', quad], value: undefined, remainingItems: [45, quad] }]]); buildRelations(relationDescriptions, quad); @@ -202,7 +209,6 @@ describe('treeMetadataExtraction', () => { DF.namedNode(TreeNodes.RemainingItems), DF.namedNode('foo')); const relationDescriptions: Map = new Map(); - buildRelations(relationDescriptions, quad); expect(relationDescriptions.size).toBe(0); diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 01f39328a..154ead4d8 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -5,12 +5,15 @@ import type { } from '@comunica/bus-optimize-link-traversal'; import { ActorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; import type { IActorTest } from '@comunica/core'; -import type { LinkTraversalOptimizationLinkFilter, LinkTraversalFilterOperator } from '@comunica/types-link-traversal'; import { Algebra } from 'sparqlalgebrajs'; import { AsyncEvaluator, isExpressionError } from 'sparqlee'; import { Bindings } from '@comunica/types'; -import { bindingsToString } from '@comunica/bindings-factory'; - +import { BindingsFactory } from '@comunica/bindings-factory'; +import { KeysInitQuery } from '@comunica/context-entries'; +import { IRelation } from '@comunica/types-link-traversal'; +import type * as RDF from 'rdf-js'; +import { stringToTerm } from "rdf-string"; +import { Literal } from 'rdf-data-factory'; /** * A comunica Link traversal optimizer that filter link of document @@ -23,64 +26,55 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink } public async test(action: IActionOptimizeLinkTraversal): Promise { - return this.findFilterOperation(action, true).length === 1; + const query = action.context.get(KeysInitQuery.query); + const relations: IRelation[] = typeof action.treeMetadata !== 'undefined'? + (typeof action.treeMetadata.relation !== 'undefined'? action.treeMetadata.relation:[]):[] + + return query.type === Algebra.types.FILTER && relations.length !== 0; } public async run(action: IActionOptimizeLinkTraversal): Promise { - const filters = this.findFilterOperation(action); - const filterMap: Map = new Map(); - for (const bindingsStream of action.decisionZoneBindingStream) { - for (const filter of filters) { - const evaluator = new AsyncEvaluator(filter.expression); + const filterMap: Map = new Map(); - const transform = async(item: Bindings, next: any, push: (bindings: Bindings) => void): Promise => { - try { - const result = await evaluator.evaluateAsEBV(item); - if (result) { - push(item); - } - } catch (error: unknown) { - // 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 (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(action.context, 'Error occurred while filtering.', () => ({ error, bindings: bindingsToString(item) })); - } else { - bindingsStream.emit('error', error); - } - } - next(); - }; - const result = bindingsStream.transform({ transform }); + const filterExpression = ( action.context.get(KeysInitQuery.query)).input.expression; + const queryBody = ( action.context.get(KeysInitQuery.query)).input.input.input; + const relations: IRelation[] = action.treeMetadata?.relation; + + for (const relation of relations ) { + if (typeof relation.path !== 'undefined' && typeof relation.value !== 'undefined') { + const evaluator = new AsyncEvaluator(filterExpression); + const relevantQuads = this.findRelavantQuad(queryBody, relation.path.value); + const bindings = this.createBinding(relevantQuads, relation.value.quad); + const result: boolean = await evaluator.evaluateAsEBV(bindings); + filterMap.set(relation, result); } } - + return { filters: filterMap }; } - - - private findFilterOperation(action: IActionOptimizeLinkTraversal, oneResult=false): Algebra.Operation[] { - let nestedOperation = action.operations; - const filters: Algebra.Operation[] = [] - while ('input' in nestedOperation) { - if (nestedOperation.type === Algebra.types.FILTER) { - filters.push(nestedOperation); - if (oneResult) { - break; - } + private findRelavantQuad(queryBody: RDF.Quad[], path: string): RDF.Quad[] { + const resp: RDF.Quad[] = []; + for (const quad of queryBody) { + if( quad.predicate.value === path && quad.object.termType ==='Variable'){ + resp.push(quad); } - nestedOperation = nestedOperation.input; } - return filters + return resp; } + private createBinding(relevantQuad: RDF.Quad[], relationValue: RDF.Quad): Bindings { + let binding: Bindings = new BindingsFactory().bindings(); + for (const quad of relevantQuad ) { + const object = quad.object.value; + const value:string = relationValue.object.termType ==='Literal'? + ((relationValue.object).datatype.value !== ''? + `${relationValue.object.value}^^${(relationValue.object).datatype.value}`:relationValue.object.value): + relationValue.object.value; + + binding = binding.set(`?${object}`, stringToTerm(value)); + + } + return binding; + } } diff --git a/packages/actor-query-operation-filter-links-traversal/.npmignore b/packages/actor-query-operation-filter-links-traversal/.npmignore new file mode 100644 index 000000000..e69de29bb diff --git a/packages/actor-query-operation-filter-links-traversal/README.md b/packages/actor-query-operation-filter-links-traversal/README.md new file mode 100644 index 000000000..f7a86a93c --- /dev/null +++ b/packages/actor-query-operation-filter-links-traversal/README.md @@ -0,0 +1,42 @@ +# Comunica Filter Links Traversal Query Operation Actor + +[![npm version](https://badge.fury.io/js/%40comunica%2Factor-query-operation-filter-links-traversal.svg)](https://www.npmjs.com/package/@comunica/actor-query-operation-filter-links-traversal) + +A [Query Operation](https://github.com/comunica/comunica/tree/master/packages/bus-query-operation) actor that handles filter operations in the context of links traversal + +This module is part of the [Comunica framework](https://github.com/comunica/comunica), +and should only be used by [developers that want to build their own query engine](https://comunica.dev/docs/modify/). + +[Click here if you just want to query with Comunica](https://comunica.dev/docs/query/). + +## Install + +```bash +$ yarn add @comunica/actor-query-operation-filter-links-traversal +``` + +## Configure + +After installing, this package can be added to your engine's configuration as follows: +```text +{ + "@context": [ + ... + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-query-operation-filter-links-traversal/^1.0.0/components/context.jsonld" + ], + "actors": [ + ... + { + "@id": "urn:comunica:default:query-operation/actors#filter-links-traversal", + "@type": "ActorQueryOperationFilterLinksTraversal", + "mediatorQueryOperation": { "@id": "config-sets:sparql-queryoperators.json#mediatorQueryOperation" } + } + ] +} +``` + +### Config Parameters + +TODO: fill in parameters (this section can be removed if there are none) + +* `mediatorQueryOperation`: A mediator over the [Query Operation bus](https://github.com/comunica/comunica/tree/master/packages/bus-query-operation). diff --git a/packages/actor-query-operation-filter-links-traversal/lib/ActorQueryOperationFilterLinksTraversal.ts b/packages/actor-query-operation-filter-links-traversal/lib/ActorQueryOperationFilterLinksTraversal.ts new file mode 100644 index 000000000..eb184a457 --- /dev/null +++ b/packages/actor-query-operation-filter-links-traversal/lib/ActorQueryOperationFilterLinksTraversal.ts @@ -0,0 +1,36 @@ + import { bindingsToString } from '@comunica/bindings-factory'; +import type { IActorQueryOperationTypedMediatedArgs } from '@comunica/bus-query-operation'; +import { + ActorQueryOperation, + ActorQueryOperationTypedMediated, +} from '@comunica/bus-query-operation'; +import type { IActorTest } from '@comunica/core'; +import type { Bindings, IActionContext, IQueryOperationResult } from '@comunica/types'; +import type { Algebra } from 'sparqlalgebrajs'; +import { AsyncEvaluator, isExpressionError } from 'sparqlee'; +import { KeyFilterLinksTraversal } from '@comunica/context-entries-link-traversal'; + + +/** + * A [Query Operation](https://github.com/comunica/comunica/tree/master/packages/bus-query-operation) actor that handles filter operations in the context of links traversal + */ +/** +export class ActorQueryOperationFilterLinksTraversal extends ActorQueryOperationTypedMediated { + public constructor(args: IActorQueryOperationTypedMediatedArgs) { + super(args, 'filter'); + } + + public async testOperation(operation: Algebra.Filter, context: IActionContext): Promise { + // Will throw error for unsupported operators + const config = { ...ActorQueryOperation.getAsyncExpressionContext(context, this.mediatorQueryOperation) }; + const _ = new AsyncEvaluator(operation.expression, config); + return context.has(KeyFilterLinksTraversal.zoneOfInterest) + } + + public async runOperation(operation: Algebra.Filter, context: IActionContext): + Promise { + + return { type: 'bindings', bindingsStream, metadata }; // TODO: implement + } +} +*/ diff --git a/packages/actor-query-operation-filter-links-traversal/lib/index.ts b/packages/actor-query-operation-filter-links-traversal/lib/index.ts new file mode 100644 index 000000000..611fdf583 --- /dev/null +++ b/packages/actor-query-operation-filter-links-traversal/lib/index.ts @@ -0,0 +1 @@ +export * from './ActorQueryOperationFilterLinksTraversal'; diff --git a/packages/actor-query-operation-filter-links-traversal/package.json b/packages/actor-query-operation-filter-links-traversal/package.json new file mode 100644 index 000000000..43f4eaf87 --- /dev/null +++ b/packages/actor-query-operation-filter-links-traversal/package.json @@ -0,0 +1,42 @@ +{ + "name": "@comunica/actor-query-operation-filter-links-traversal", + "version": "1.0.0", + "description": "A filter-links-traversal query-operation actor", + "lsd:module": true, + "main": "lib/index.js", + "typings": "lib/index", + "repository": { + "type": "git", + "url": "https://github.com/comunica/comunica.git", + "directory": "packages/actor-query-operation-filter-links-traversal" + }, + "publishConfig": { + "access": "public" + }, + "sideEffects": false, + "keywords": [ + "comunica", + "actor", + "query-operation", + "filter-links-traversal" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/comunica/comunica/issues" + }, + "homepage": "https://comunica.dev/", + "files": [ + "components", + "lib/**/*.d.ts", + "lib/**/*.js" + ], + "dependencies": { + "@comunica/core": "^2.4.0", + "@comunica/bus-query-operation": "^2.4.0" + }, + "scripts": { + "build": "npm run build:ts && npm run build:components", + "build:ts": "node \"../../node_modules/typescript/bin/tsc\"", + "build:components": "componentsjs-generator" + } +} diff --git a/packages/actor-query-operation-filter-links-traversal/test/ActorQueryOperationFilterLinksTraversal-test.ts b/packages/actor-query-operation-filter-links-traversal/test/ActorQueryOperationFilterLinksTraversal-test.ts new file mode 100644 index 000000000..c4941fb0d --- /dev/null +++ b/packages/actor-query-operation-filter-links-traversal/test/ActorQueryOperationFilterLinksTraversal-test.ts @@ -0,0 +1,64 @@ +/** +import { Bindings, IActorQueryOperationOutputBindings } from '@comunica/bus-query-operation'; +import { Bus } from '@comunica/core'; +import { literal } from '@rdfjs/data-model'; +import { ArrayIterator } from 'asynciterator'; +import { ActorQueryOperationFilterLinksTraversal } from '../lib/ActorQueryOperationFilterLinksTraversal'; +const arrayifyStream = require('arrayify-stream'); + +describe('ActorQueryOperationFilterLinksTraversal', () => { + let bus: any; + let mediatorQueryOperation: any; + + beforeEach(() => { + bus = new Bus({ name: 'bus' }); + mediatorQueryOperation = { + mediate: (arg: any) => Promise.resolve({ + bindingsStream: new ArrayIterator([ + Bindings({ '?a': literal('1') }), + Bindings({ '?a': literal('2') }), + Bindings({ '?a': literal('3') }), + ], { autoStart: false }), + metadata: () => Promise.resolve({ totalItems: 3 }), + operated: arg, + type: 'bindings', + variables: [ '?a' ], + canContainUndefs: false, + }), + }; + }); + + describe('An ActorQueryOperationFilterLinksTraversal instance', () => { + let actor: ActorQueryOperationFilterLinksTraversal; + + beforeEach(() => { + actor = new ActorQueryOperationFilterLinksTraversal({ name: 'actor', bus, mediatorQueryOperation }); + }); + + it('should test on filter', () => { + const op = { operation: { type: 'filter' }}; + return expect(actor.test(op)).resolves.toBeTruthy(); + }); + + it('should not test on non-filter', () => { + const op = { operation: { type: 'some-other-type' }}; + return expect(actor.test(op)).rejects.toBeTruthy(); + }); + + it('should run', () => { + const op = { operation: { type: 'filter' }}; + return actor.run(op).then(async(output: IActorQueryOperationOutputBindings) => { + expect(await output.metadata!()).toEqual({ totalItems: 3 }); + expect(output.variables).toEqual([ '?a' ]); + expect(output.type).toEqual('bindings'); + expect(output.canContainUndefs).toEqual(false); + expect(await arrayifyStream(output.bindingsStream)).toEqual([ + Bindings({ '?a': literal('1') }), + Bindings({ '?a': literal('2') }), + Bindings({ '?a': literal('3') }), + ]); + }); + }); + }); +}); +*/ \ No newline at end of file diff --git a/packages/actor-rdf-metadata-extract-traverse/lib/ActorRdfMetadataExtractTraverse.ts b/packages/actor-rdf-metadata-extract-traverse/lib/ActorRdfMetadataExtractTraverse.ts index b6799cb04..ddd40c0bc 100644 --- a/packages/actor-rdf-metadata-extract-traverse/lib/ActorRdfMetadataExtractTraverse.ts +++ b/packages/actor-rdf-metadata-extract-traverse/lib/ActorRdfMetadataExtractTraverse.ts @@ -19,6 +19,7 @@ export class ActorRdfMetadataExtractTraverse extends ActorRdfMetadataExtract { public async run(action: IActionRdfMetadataExtract): Promise { const result = await this.mediatorExtractLinks.mediate(action); + // here return { metadata: { traverse: result.links, diff --git a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts index f1df1e8ec..dabbb12bf 100644 --- a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts +++ b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts @@ -1,6 +1,6 @@ import type { IAction, IActorArgs, IActorOutput, IActorTest, Mediate } from '@comunica/core'; import { Actor } from '@comunica/core'; -import type { LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; +import type { INode, IRelation } from '@comunica/types-link-traversal'; import type { Algebra } from 'sparqlalgebrajs'; import { BindingsStream } from '@comunica/types'; /** @@ -26,15 +26,14 @@ IActorOptimizeLinkTraversalOutput> { } export interface IActionOptimizeLinkTraversal extends IAction { - operations: Algebra.Operation; - decisionZoneBindingStream: BindingsStream[]; + treeMetadata?: INode; } export interface IActorOptimizeLinkTraversalOutput extends IActorOutput { /** - * The decision weith the link specific link should be follow + * decision map whether the link should be filter or not */ - filters: Map; + filters?: Map; } export type IActorOptimizeLinkTraversalArgs = IActorArgs< diff --git a/packages/context-entries-link-traversal/lib/Keys.ts b/packages/context-entries-link-traversal/lib/Keys.ts index 7e338abfc..84bd39583 100644 --- a/packages/context-entries-link-traversal/lib/Keys.ts +++ b/packages/context-entries-link-traversal/lib/Keys.ts @@ -1,5 +1,6 @@ import { ActionContextKey } from '@comunica/core'; -import type { AnnotateSourcesType, LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; +import type { AnnotateSourcesType } from '@comunica/types-link-traversal'; +import * as RDF from 'rdf-js'; /** * When adding entries to this file, also add a shortcut for them in the contextKeyShortcuts TSDoc comment in * ActorIniQueryBase in @comunica/actor-init-query if it makes sense to use this entry externally. @@ -19,11 +20,11 @@ export const KeysRdfResolveHypermediaLinks = { ), }; -export const KeyOptimizationLinkTraversal = { +export const KeyFilterLinksTraversal = { /** * Filter function to optimized the link traversal */ - filterFunctions: new ActionContextKey>( - '@comunica/optimization-link-traversal:filterFunction', + zoneOfInterest: new ActionContextKey>( + '@comunica/filter-links-traversal:zoneOfInterest', ), }; diff --git a/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts b/packages/types-link-traversal/lib/TreeMetadata.ts similarity index 70% rename from packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts rename to packages/types-link-traversal/lib/TreeMetadata.ts index 6b3a8d8ce..3f21e467d 100644 --- a/packages/actor-extract-links-extract-tree/lib/typeTreeMetadataExtraction.ts +++ b/packages/types-link-traversal/lib/TreeMetadata.ts @@ -1,3 +1,4 @@ +import type * as RDF from 'rdf-js'; // From // https://github.com/TREEcg/tree-metadata-extraction/blob/42be38925cf6a033ddadaca5ecce929902ef1545/src/util/Util.ts export enum RelationOperator { @@ -21,16 +22,35 @@ export enum TreeNodes { RemainingItems = 'https://w3id.org/tree#remainingItems' } +export interface INode { + relation?: IRelation[]; + subject: string; +} + export interface IRelation { - '@type'?: string; - 'remainingItems'?: number; - 'path'?: any; - 'value'?: any; - 'node': string; + '@type'?:{ + value: string, + quad:RDF.Quad + }; + 'remainingItems'?: { + value: number, + quad:RDF.Quad + }; + 'path'?: { + value: string, + quad:RDF.Quad + }; + 'value'?: { + value: any, + quad:RDF.Quad + }; + 'node': string ; } // An helper to build the relation from a stream export interface IRelationDescription { - subject?: string; value?: any; operator?: RelationOperator; remainingItems?: number; + subject?: [string | undefined, RDF.Quad]; + value?: any; + operator?: [RelationOperator | undefined, RDF.Quad]; + remainingItems?: [number | undefined, RDF.Quad]; } - diff --git a/packages/types-link-traversal/lib/index.ts b/packages/types-link-traversal/lib/index.ts index 2c5301af4..bbf721486 100644 --- a/packages/types-link-traversal/lib/index.ts +++ b/packages/types-link-traversal/lib/index.ts @@ -1,2 +1,2 @@ export * from './AnnotateSourcesType'; -export * from './linkTraversalOptimizationLinkFilter'; +export * from './TreeMetadata'; From 89b8bbb032b75dfc8535a092684afccc5b131dba Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 3 Nov 2022 14:07:13 +0100 Subject: [PATCH 013/189] static filter with sparqlee implemented --- .../lib/ActorExtractLinksTree.ts | 16 +++-- ...torOptimizeLinkTraversalFilterTreeLinks.ts | 67 ++++++++++++++----- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index e805095df..456c1839a 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -10,10 +10,6 @@ import * as RDF from 'rdf-js'; import { buildRelations, collectRelation } from './treeMetadataExtraction'; import type { IRelationDescription, IRelation, INode } from '@comunica/types-link-traversal'; import { TreeNodes } from '@comunica/types-link-traversal'; -import { url } from 'inspector'; -const streamifyArray = require('streamify-array'); - -const DF = new DataFactory(); /** * A comunica Extract Links Tree Extract Links Actor. @@ -50,7 +46,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { relationDescriptions)); // Resolve to discovered links - metadata.on('end', () => { + metadata.on('end', async () => { // Validate if the node forward have the current node as implicit subject for (const [ nodeValue, link ] of nodeLinks) { if (pageRelationNodes.has(nodeValue)) { @@ -64,8 +60,14 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } const node:INode = {relation: relations, subject: currentNodeUrl}; - this.mediatorOptimizeLinkTraversal.mediate({treeMetadata:node, context:action.context}); - resolve({ links: relations.map(el => ({ url: el.node })) }); + const linkTraversalOptimisation = await this.mediatorOptimizeLinkTraversal.mediate({treeMetadata:node, context:action.context}); + let acceptedRelation = relations; + if(typeof linkTraversalOptimisation.filters !== 'undefined') { + acceptedRelation = relations.filter((relation)=>{ + return linkTraversalOptimisation.filters?.get(relation); + }); + } + resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); }); }); } diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 154ead4d8..83a94a19e 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -27,8 +27,8 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink public async test(action: IActionOptimizeLinkTraversal): Promise { const query = action.context.get(KeysInitQuery.query); - const relations: IRelation[] = typeof action.treeMetadata !== 'undefined'? - (typeof action.treeMetadata.relation !== 'undefined'? action.treeMetadata.relation:[]):[] + const relations: IRelation[] = typeof action.treeMetadata !== 'undefined' ? + (typeof action.treeMetadata.relation !== 'undefined' ? action.treeMetadata.relation : []) : [] return query.type === Algebra.types.FILTER && relations.length !== 0; } @@ -36,17 +36,22 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink public async run(action: IActionOptimizeLinkTraversal): Promise { const filterMap: Map = new Map(); - const filterExpression = ( action.context.get(KeysInitQuery.query)).input.expression; - const queryBody = ( action.context.get(KeysInitQuery.query)).input.input.input; - const relations: IRelation[] = action.treeMetadata?.relation; + let filterExpression = JSON.parse(JSON.stringify((action.context.get(KeysInitQuery.query)).input.expression)); + const queryBody = (action.context.get(KeysInitQuery.query)).input.input.input; + const relations: IRelation[] = action.treeMetadata?.relation; - for (const relation of relations ) { + for (const relation of relations) { if (typeof relation.path !== 'undefined' && typeof relation.value !== 'undefined') { - const evaluator = new AsyncEvaluator(filterExpression); const relevantQuads = this.findRelavantQuad(queryBody, relation.path.value); const bindings = this.createBinding(relevantQuads, relation.value.quad); - const result: boolean = await evaluator.evaluateAsEBV(bindings); - filterMap.set(relation, result); + filterExpression = this.deleteUnrelevantFilter(filterExpression, bindings); + if (filterExpression.args.length !== 0) { + const evaluator = new AsyncEvaluator(filterExpression); + const result: boolean = await evaluator.evaluateAsEBV(bindings); + filterMap.set(relation, result); + } else { + filterMap.set(relation, false); + } } } return { filters: filterMap }; @@ -55,24 +60,50 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink private findRelavantQuad(queryBody: RDF.Quad[], path: string): RDF.Quad[] { const resp: RDF.Quad[] = []; for (const quad of queryBody) { - if( quad.predicate.value === path && quad.object.termType ==='Variable'){ + if (quad.predicate.value === path && quad.object.termType === 'Variable') { resp.push(quad); } } return resp; } + private deleteUnrelevantFilter(filterExpression: Algebra.Operation, binding: Bindings): Algebra.Operation { + if ('args.args' in filterExpression) { + filterExpression.args = (filterExpression.args).filter((expression) => { + for (const arg of expression.args) { + if ('term' in arg) { + if (arg.term.termType === 'Variable') { + return binding.has(arg.term.value); + } + } + } + return true + }) + } else { + for( const arg of (filterExpression.args)){ + if ('term' in arg) { + if (arg.term.termType === 'Variable') { + if (!binding.has(arg.term.value)){ + filterExpression.args = []; + break + } + } + } + } + + } + return filterExpression; + } + private createBinding(relevantQuad: RDF.Quad[], relationValue: RDF.Quad): Bindings { let binding: Bindings = new BindingsFactory().bindings(); - for (const quad of relevantQuad ) { + for (const quad of relevantQuad) { const object = quad.object.value; - const value:string = relationValue.object.termType ==='Literal'? - ((relationValue.object).datatype.value !== ''? - `${relationValue.object.value}^^${(relationValue.object).datatype.value}`:relationValue.object.value): - relationValue.object.value; - - binding = binding.set(`?${object}`, stringToTerm(value)); - + const value: string = relationValue.object.termType === 'Literal' ? + ((relationValue.object).datatype.value !== '' ? + `"${relationValue.object.value}"^^${(relationValue.object).datatype.value}` : relationValue.object.value) : + relationValue.object.value; + binding = binding.set(object, stringToTerm(value)); } return binding; } From e7c9a00d62caff1216017cd6cb1dbf5488cec374 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 3 Nov 2022 14:10:07 +0100 Subject: [PATCH 014/189] filter tree traversal query operator deleted --- .../.npmignore | 0 .../README.md | 42 ------------ ...ActorQueryOperationFilterLinksTraversal.ts | 36 ----------- .../lib/index.ts | 1 - .../package.json | 42 ------------ ...QueryOperationFilterLinksTraversal-test.ts | 64 ------------------- 6 files changed, 185 deletions(-) delete mode 100644 packages/actor-query-operation-filter-links-traversal/.npmignore delete mode 100644 packages/actor-query-operation-filter-links-traversal/README.md delete mode 100644 packages/actor-query-operation-filter-links-traversal/lib/ActorQueryOperationFilterLinksTraversal.ts delete mode 100644 packages/actor-query-operation-filter-links-traversal/lib/index.ts delete mode 100644 packages/actor-query-operation-filter-links-traversal/package.json delete mode 100644 packages/actor-query-operation-filter-links-traversal/test/ActorQueryOperationFilterLinksTraversal-test.ts diff --git a/packages/actor-query-operation-filter-links-traversal/.npmignore b/packages/actor-query-operation-filter-links-traversal/.npmignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/actor-query-operation-filter-links-traversal/README.md b/packages/actor-query-operation-filter-links-traversal/README.md deleted file mode 100644 index f7a86a93c..000000000 --- a/packages/actor-query-operation-filter-links-traversal/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Comunica Filter Links Traversal Query Operation Actor - -[![npm version](https://badge.fury.io/js/%40comunica%2Factor-query-operation-filter-links-traversal.svg)](https://www.npmjs.com/package/@comunica/actor-query-operation-filter-links-traversal) - -A [Query Operation](https://github.com/comunica/comunica/tree/master/packages/bus-query-operation) actor that handles filter operations in the context of links traversal - -This module is part of the [Comunica framework](https://github.com/comunica/comunica), -and should only be used by [developers that want to build their own query engine](https://comunica.dev/docs/modify/). - -[Click here if you just want to query with Comunica](https://comunica.dev/docs/query/). - -## Install - -```bash -$ yarn add @comunica/actor-query-operation-filter-links-traversal -``` - -## Configure - -After installing, this package can be added to your engine's configuration as follows: -```text -{ - "@context": [ - ... - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-query-operation-filter-links-traversal/^1.0.0/components/context.jsonld" - ], - "actors": [ - ... - { - "@id": "urn:comunica:default:query-operation/actors#filter-links-traversal", - "@type": "ActorQueryOperationFilterLinksTraversal", - "mediatorQueryOperation": { "@id": "config-sets:sparql-queryoperators.json#mediatorQueryOperation" } - } - ] -} -``` - -### Config Parameters - -TODO: fill in parameters (this section can be removed if there are none) - -* `mediatorQueryOperation`: A mediator over the [Query Operation bus](https://github.com/comunica/comunica/tree/master/packages/bus-query-operation). diff --git a/packages/actor-query-operation-filter-links-traversal/lib/ActorQueryOperationFilterLinksTraversal.ts b/packages/actor-query-operation-filter-links-traversal/lib/ActorQueryOperationFilterLinksTraversal.ts deleted file mode 100644 index eb184a457..000000000 --- a/packages/actor-query-operation-filter-links-traversal/lib/ActorQueryOperationFilterLinksTraversal.ts +++ /dev/null @@ -1,36 +0,0 @@ - import { bindingsToString } from '@comunica/bindings-factory'; -import type { IActorQueryOperationTypedMediatedArgs } from '@comunica/bus-query-operation'; -import { - ActorQueryOperation, - ActorQueryOperationTypedMediated, -} from '@comunica/bus-query-operation'; -import type { IActorTest } from '@comunica/core'; -import type { Bindings, IActionContext, IQueryOperationResult } from '@comunica/types'; -import type { Algebra } from 'sparqlalgebrajs'; -import { AsyncEvaluator, isExpressionError } from 'sparqlee'; -import { KeyFilterLinksTraversal } from '@comunica/context-entries-link-traversal'; - - -/** - * A [Query Operation](https://github.com/comunica/comunica/tree/master/packages/bus-query-operation) actor that handles filter operations in the context of links traversal - */ -/** -export class ActorQueryOperationFilterLinksTraversal extends ActorQueryOperationTypedMediated { - public constructor(args: IActorQueryOperationTypedMediatedArgs) { - super(args, 'filter'); - } - - public async testOperation(operation: Algebra.Filter, context: IActionContext): Promise { - // Will throw error for unsupported operators - const config = { ...ActorQueryOperation.getAsyncExpressionContext(context, this.mediatorQueryOperation) }; - const _ = new AsyncEvaluator(operation.expression, config); - return context.has(KeyFilterLinksTraversal.zoneOfInterest) - } - - public async runOperation(operation: Algebra.Filter, context: IActionContext): - Promise { - - return { type: 'bindings', bindingsStream, metadata }; // TODO: implement - } -} -*/ diff --git a/packages/actor-query-operation-filter-links-traversal/lib/index.ts b/packages/actor-query-operation-filter-links-traversal/lib/index.ts deleted file mode 100644 index 611fdf583..000000000 --- a/packages/actor-query-operation-filter-links-traversal/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ActorQueryOperationFilterLinksTraversal'; diff --git a/packages/actor-query-operation-filter-links-traversal/package.json b/packages/actor-query-operation-filter-links-traversal/package.json deleted file mode 100644 index 43f4eaf87..000000000 --- a/packages/actor-query-operation-filter-links-traversal/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@comunica/actor-query-operation-filter-links-traversal", - "version": "1.0.0", - "description": "A filter-links-traversal query-operation actor", - "lsd:module": true, - "main": "lib/index.js", - "typings": "lib/index", - "repository": { - "type": "git", - "url": "https://github.com/comunica/comunica.git", - "directory": "packages/actor-query-operation-filter-links-traversal" - }, - "publishConfig": { - "access": "public" - }, - "sideEffects": false, - "keywords": [ - "comunica", - "actor", - "query-operation", - "filter-links-traversal" - ], - "license": "MIT", - "bugs": { - "url": "https://github.com/comunica/comunica/issues" - }, - "homepage": "https://comunica.dev/", - "files": [ - "components", - "lib/**/*.d.ts", - "lib/**/*.js" - ], - "dependencies": { - "@comunica/core": "^2.4.0", - "@comunica/bus-query-operation": "^2.4.0" - }, - "scripts": { - "build": "npm run build:ts && npm run build:components", - "build:ts": "node \"../../node_modules/typescript/bin/tsc\"", - "build:components": "componentsjs-generator" - } -} diff --git a/packages/actor-query-operation-filter-links-traversal/test/ActorQueryOperationFilterLinksTraversal-test.ts b/packages/actor-query-operation-filter-links-traversal/test/ActorQueryOperationFilterLinksTraversal-test.ts deleted file mode 100644 index c4941fb0d..000000000 --- a/packages/actor-query-operation-filter-links-traversal/test/ActorQueryOperationFilterLinksTraversal-test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** -import { Bindings, IActorQueryOperationOutputBindings } from '@comunica/bus-query-operation'; -import { Bus } from '@comunica/core'; -import { literal } from '@rdfjs/data-model'; -import { ArrayIterator } from 'asynciterator'; -import { ActorQueryOperationFilterLinksTraversal } from '../lib/ActorQueryOperationFilterLinksTraversal'; -const arrayifyStream = require('arrayify-stream'); - -describe('ActorQueryOperationFilterLinksTraversal', () => { - let bus: any; - let mediatorQueryOperation: any; - - beforeEach(() => { - bus = new Bus({ name: 'bus' }); - mediatorQueryOperation = { - mediate: (arg: any) => Promise.resolve({ - bindingsStream: new ArrayIterator([ - Bindings({ '?a': literal('1') }), - Bindings({ '?a': literal('2') }), - Bindings({ '?a': literal('3') }), - ], { autoStart: false }), - metadata: () => Promise.resolve({ totalItems: 3 }), - operated: arg, - type: 'bindings', - variables: [ '?a' ], - canContainUndefs: false, - }), - }; - }); - - describe('An ActorQueryOperationFilterLinksTraversal instance', () => { - let actor: ActorQueryOperationFilterLinksTraversal; - - beforeEach(() => { - actor = new ActorQueryOperationFilterLinksTraversal({ name: 'actor', bus, mediatorQueryOperation }); - }); - - it('should test on filter', () => { - const op = { operation: { type: 'filter' }}; - return expect(actor.test(op)).resolves.toBeTruthy(); - }); - - it('should not test on non-filter', () => { - const op = { operation: { type: 'some-other-type' }}; - return expect(actor.test(op)).rejects.toBeTruthy(); - }); - - it('should run', () => { - const op = { operation: { type: 'filter' }}; - return actor.run(op).then(async(output: IActorQueryOperationOutputBindings) => { - expect(await output.metadata!()).toEqual({ totalItems: 3 }); - expect(output.variables).toEqual([ '?a' ]); - expect(output.type).toEqual('bindings'); - expect(output.canContainUndefs).toEqual(false); - expect(await arrayifyStream(output.bindingsStream)).toEqual([ - Bindings({ '?a': literal('1') }), - Bindings({ '?a': literal('2') }), - Bindings({ '?a': literal('3') }), - ]); - }); - }); - }); -}); -*/ \ No newline at end of file From eabaa2cfba7d9d430ac32d20cf44d45b056d53b5 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 3 Nov 2022 14:15:40 +0100 Subject: [PATCH 015/189] useless comment deleted --- .../lib/ActorRdfMetadataExtractTraverse.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/actor-rdf-metadata-extract-traverse/lib/ActorRdfMetadataExtractTraverse.ts b/packages/actor-rdf-metadata-extract-traverse/lib/ActorRdfMetadataExtractTraverse.ts index ddd40c0bc..b6799cb04 100644 --- a/packages/actor-rdf-metadata-extract-traverse/lib/ActorRdfMetadataExtractTraverse.ts +++ b/packages/actor-rdf-metadata-extract-traverse/lib/ActorRdfMetadataExtractTraverse.ts @@ -19,7 +19,6 @@ export class ActorRdfMetadataExtractTraverse extends ActorRdfMetadataExtract { public async run(action: IActionRdfMetadataExtract): Promise { const result = await this.mediatorExtractLinks.mediate(action); - // here return { metadata: { traverse: result.links, From 4e7ceeb51370d562a34314d812109d6a770cbd75 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 3 Nov 2022 14:17:56 +0100 Subject: [PATCH 016/189] unused context key deleted --- packages/context-entries-link-traversal/lib/Keys.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/context-entries-link-traversal/lib/Keys.ts b/packages/context-entries-link-traversal/lib/Keys.ts index 84bd39583..c095f08c2 100644 --- a/packages/context-entries-link-traversal/lib/Keys.ts +++ b/packages/context-entries-link-traversal/lib/Keys.ts @@ -1,6 +1,5 @@ import { ActionContextKey } from '@comunica/core'; import type { AnnotateSourcesType } from '@comunica/types-link-traversal'; -import * as RDF from 'rdf-js'; /** * When adding entries to this file, also add a shortcut for them in the contextKeyShortcuts TSDoc comment in * ActorIniQueryBase in @comunica/actor-init-query if it makes sense to use this entry externally. @@ -19,12 +18,3 @@ export const KeysRdfResolveHypermediaLinks = { '@comunica/bus-rdf-resolve-hypermedia-links:annotateSources', ), }; - -export const KeyFilterLinksTraversal = { - /** - * Filter function to optimized the link traversal - */ - zoneOfInterest: new ActionContextKey>( - '@comunica/filter-links-traversal:zoneOfInterest', - ), -}; From d67fb0fa2ad75a8be7fffc1f282c1df88f850df1 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 3 Nov 2022 14:23:48 +0100 Subject: [PATCH 017/189] better reference for the Tree metadata --- packages/types-link-traversal/lib/TreeMetadata.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/types-link-traversal/lib/TreeMetadata.ts b/packages/types-link-traversal/lib/TreeMetadata.ts index 3f21e467d..1dab02bb5 100644 --- a/packages/types-link-traversal/lib/TreeMetadata.ts +++ b/packages/types-link-traversal/lib/TreeMetadata.ts @@ -1,6 +1,10 @@ +/** + * inspired from + * https://github.com/TREEcg/tree-metadata-extraction/blob/42be38925cf6a033ddadaca5ecce929902ef1545/src/util/Util.ts + */ + import type * as RDF from 'rdf-js'; -// From -// https://github.com/TREEcg/tree-metadata-extraction/blob/42be38925cf6a033ddadaca5ecce929902ef1545/src/util/Util.ts + export enum RelationOperator { PrefixRelation = 'https://w3id.org/tree#PrefixRelation', SubstringRelation = 'https://w3id.org/tree#SubstringRelation', From 188d4ee6f54b29094ce3867fbb9ebc8ee4436c77 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 3 Nov 2022 15:48:53 +0100 Subject: [PATCH 018/189] lint-fix --- .../lib/ActorExtractLinksTree.ts | 27 +++--- .../lib/treeMetadataExtraction.ts | 77 +++++++++------ .../test/treeMetadataExtraction-test.ts | 28 +++--- ...torOptimizeLinkTraversalFilterTreeLinks.ts | 95 +++++++++++-------- .../package.json | 7 +- .../lib/ActorOptimizeLinkTraversal.ts | 4 +- .../package.json | 1 + .../types-link-traversal/lib/TreeMetadata.ts | 20 ++-- 8 files changed, 149 insertions(+), 110 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 456c1839a..9ac09b235 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -5,11 +5,10 @@ import type { import { ActorExtractLinks } from '@comunica/bus-extract-links'; import type { MediatorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; import type { IActorTest } from '@comunica/core'; -import { DataFactory } from 'rdf-data-factory'; -import * as RDF from 'rdf-js'; -import { buildRelations, collectRelation } from './treeMetadataExtraction'; import type { IRelationDescription, IRelation, INode } from '@comunica/types-link-traversal'; import { TreeNodes } from '@comunica/types-link-traversal'; +import type * as RDF from 'rdf-js'; +import { buildRelations, collectRelation } from './treeMetadataExtraction'; /** * A comunica Extract Links Tree Extract Links Actor. @@ -46,7 +45,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { relationDescriptions)); // Resolve to discovered links - metadata.on('end', async () => { + metadata.on('end', async() => { // Validate if the node forward have the current node as implicit subject for (const [ nodeValue, link ] of nodeLinks) { if (pageRelationNodes.has(nodeValue)) { @@ -59,14 +58,14 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } } - const node:INode = {relation: relations, subject: currentNodeUrl}; - const linkTraversalOptimisation = await this.mediatorOptimizeLinkTraversal.mediate({treeMetadata:node, context:action.context}); + const node: INode = { relation: relations, subject: currentNodeUrl }; + const linkTraversalOptimisation = await this.mediatorOptimizeLinkTraversal.mediate( + { treeMetadata: node, context: action.context }, + ); let acceptedRelation = relations; - if(typeof linkTraversalOptimisation.filters !== 'undefined') { - acceptedRelation = relations.filter((relation)=>{ - return linkTraversalOptimisation.filters?.get(relation); - }); - } + if (typeof linkTraversalOptimisation.filters !== 'undefined') { + acceptedRelation = relations.filter(relation => linkTraversalOptimisation.filters?.get(relation)); + } resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); }); }); @@ -104,6 +103,6 @@ export interface IActorExtractLinksTree extends IActorExtractLinksArgs { /** * The optmize link traversal mediator */ - mediatorOptimizeLinkTraversal: MediatorOptimizeLinkTraversal; - } - + mediatorOptimizeLinkTraversal: MediatorOptimizeLinkTraversal; +} + diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts index ec703dba6..510be09ea 100644 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -1,54 +1,75 @@ -import type * as RDF from 'rdf-js'; import type { IRelation, IRelationDescription } from '@comunica/types-link-traversal'; import { RelationOperator, TreeNodes } from '@comunica/types-link-traversal'; +import type * as RDF from 'rdf-js'; export function collectRelation( relationDescription: IRelationDescription, nodeLinks: string, ): IRelation { - const relation: IRelation = {node: nodeLinks}; - const typeRelation = typeof relationDescription.operator !== 'undefined'? ( - typeof relationDescription.operator[0] !== 'undefined'? relationDescription.operator[0]: undefined): undefined; - if (typeof typeRelation !== 'undefined' && typeof relationDescription.operator !== 'undefined'){ + const relation: IRelation = { node: nodeLinks }; + const typeRelation = (() => { + if (typeof relationDescription.operator !== 'undefined') { + return typeof relationDescription.operator[0] !== 'undefined' ? relationDescription.operator[0] : undefined; + } + })(); + + if (typeof typeRelation !== 'undefined' && typeof relationDescription.operator !== 'undefined') { relation['@type'] = { value: typeRelation, quad: relationDescription.operator[1], - } + }; } - const remainingItems = typeof relationDescription.remainingItems !== 'undefined'?( - typeof relationDescription.remainingItems[0] !== 'undefined'? relationDescription.remainingItems[0]: undefined):undefined; + const remainingItems = ( + () => { + if (typeof relationDescription.remainingItems !== 'undefined') { + return typeof relationDescription.remainingItems[0] !== 'undefined' ? + relationDescription.remainingItems[0] : + undefined; + } + } + )(); if (typeof remainingItems !== 'undefined' && typeof relationDescription.remainingItems !== 'undefined') { relation.remainingItems = { value: remainingItems, - quad: relationDescription.remainingItems[1] - } + quad: relationDescription.remainingItems[1], + }; } - const path = typeof relationDescription.subject !== 'undefined'?( - typeof relationDescription.subject[0] !=='undefined'? relationDescription.subject[0]: undefined):undefined; + const path = (() => { + if (typeof relationDescription.subject !== 'undefined') { + return typeof relationDescription.subject[0] !== 'undefined' ? + relationDescription.subject[0] : + undefined; + } + })(); if (typeof path !== 'undefined' && typeof relationDescription.subject !== 'undefined') { relation.path = { value: path, - quad: relationDescription.subject[1] - } + quad: relationDescription.subject[1], + }; } - const value = typeof relationDescription.value !== 'undefined'?( - typeof relationDescription.value[0] !== 'undefined'? relationDescription.value[0]: undefined):undefined; - if (typeof value !== 'undefined' && typeof relationDescription.value!== 'undefined'){ - relation.value = { - value:value, - quad: relationDescription.value[1] + const value = (() => { + if (typeof relationDescription.value !== 'undefined') { + return typeof relationDescription.value[0] !== 'undefined' ? + relationDescription.value[0] : + undefined; } + })(); + if (typeof value !== 'undefined' && typeof relationDescription.value !== 'undefined') { + relation.value = { + value, + quad: relationDescription.value[1], + }; } - return relation - + return relation; } export function buildRelations( relationDescriptions: Map, - quad: RDF.Quad): void { + quad: RDF.Quad, +): void { if (quad.predicate.value === TreeNodes.RDFTypeNode) { // Set the operator of the relation const enumIndexOperator = ( Object.values(RelationOperator)).indexOf(quad.object.value); @@ -94,10 +115,10 @@ export function addRelationDescription({ const currentDescription: IRelationDescription | undefined = relationDescriptions.get(quad.subject.value); if (typeof currentDescription === 'undefined') { relationDescriptions.set(quad.subject.value, { - value:typeof value !== 'undefined'?[value, quad]:undefined, - subject: typeof subject !== 'undefined'? [subject, quad]: undefined, - operator:typeof operator !== 'undefined'?[operator, quad]: undefined, - remainingItems: typeof remainingItems !== 'undefined'?[remainingItems, quad]: undefined, + value: typeof value !== 'undefined' ? [ value, quad ] : undefined, + subject: typeof subject !== 'undefined' ? [ subject, quad ] : undefined, + operator: typeof operator !== 'undefined' ? [ operator, quad ] : undefined, + remainingItems: typeof remainingItems !== 'undefined' ? [ remainingItems, quad ] : undefined, }); } else { /* eslint-disable prefer-rest-params */ @@ -105,7 +126,7 @@ export function addRelationDescription({ const objectArgument = arguments[0]; for (const [ arg, val ] of Object.entries(objectArgument)) { if (typeof val !== 'undefined' && arg !== 'relationDescriptions' && arg !== 'quad') { - newDescription[arg] = [val, quad]; + newDescription[arg] = [ val, quad ]; break; } } diff --git a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts index 658c4c5f3..697e2ed00 100644 --- a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts +++ b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts @@ -13,12 +13,11 @@ describe('treeMetadataExtraction', () => { const quad: RDF.Quad = DF.quad( DF.namedNode('ex:s'), DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode(RelationOperator.EqualThanRelation - )); + DF.namedNode(RelationOperator.EqualThanRelation), + ); const relationDescriptions: Map = new Map(); addRelationDescription({ relationDescriptions, quad, operator: RelationOperator.EqualThanRelation }); - expect(relationDescriptions.size).toBe(1); }); @@ -53,7 +52,8 @@ describe('treeMetadataExtraction', () => { it('should add relation to the map when a value is provided and the relation map at the current key is not empty', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); - const relationDescriptions: Map = new Map([[ 'ex:s', { subject: ['ex:s', quad] }]]); + const relationDescriptions: Map = + new Map([[ 'ex:s', { subject: [ 'ex:s', quad ]}]]); addRelationDescription({ relationDescriptions, quad, value: '5' }); expect(relationDescriptions.size).toBe(1); }); @@ -76,7 +76,8 @@ describe('treeMetadataExtraction', () => { it('should add relation to the map when a subject is provided and the relation map at the current key is not empty', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Path), DF.namedNode('ex:path')); - const relationDescriptions: Map = new Map([[ 'ex:s', { subject: ['ex:s', quad] }]]); + const relationDescriptions: Map = + new Map([[ 'ex:s', { subject: [ 'ex:s', quad ]}]]); addRelationDescription({ relationDescriptions, quad, subject: 'ex:path' }); expect(relationDescriptions.size).toBe(1); }); @@ -107,7 +108,7 @@ describe('treeMetadataExtraction', () => { DF.namedNode(RelationOperator.EqualThanRelation)); const relationDescriptions: Map = new Map(); - const expectedDescription = new Map([[ 'ex:s', { operator: [RelationOperator.EqualThanRelation, quad], + const expectedDescription = new Map([[ 'ex:s', { operator: [ RelationOperator.EqualThanRelation, quad ], subject: undefined, value: undefined, remainingItems: undefined }]]); @@ -136,13 +137,12 @@ describe('treeMetadataExtraction', () => { const relationDescriptions: Map = new Map([[ 'ex:s2', {}]]); const expectedDescription: Map = new Map([[ 'ex:s2', {}], [ 'ex:s', - { operator: [RelationOperator.EqualThanRelation, quad], + { operator: [ RelationOperator.EqualThanRelation, quad ], subject: undefined, value: undefined, remainingItems: undefined }]]); const relationQuads: Map = new Map(); - buildRelations(relationDescriptions, quad); expect(relationDescriptions.size).toBe(2); expect(relationDescriptions).toStrictEqual(expectedDescription); @@ -155,10 +155,10 @@ describe('treeMetadataExtraction', () => { DF.namedNode(TreeNodes.RDFTypeNode), DF.namedNode(RelationOperator.EqualThanRelation)); const relationDescriptions: Map = new Map([[ 'ex:s', - { subject: ['ex:path', quad], value: undefined, remainingItems: undefined }]]); + { subject: [ 'ex:path', quad ], value: undefined, remainingItems: undefined }]]); const expectedDescription: Map = new Map([[ 'ex:s', - { operator: [RelationOperator.EqualThanRelation, quad], - subject: ['ex:path', quad], + { operator: [ RelationOperator.EqualThanRelation, quad ], + subject: [ 'ex:path', quad ], value: undefined, remainingItems: undefined }]]); @@ -174,7 +174,7 @@ describe('treeMetadataExtraction', () => { DF.namedNode('bar'), DF.namedNode(RelationOperator.EqualThanRelation)); const relationDescriptions: Map = new Map([[ 'ex:s', - { subject: ['ex:path', quad], + { subject: [ 'ex:path', quad ], value: undefined, remainingItems: undefined }]]); const expectedDescription: Map = relationDescriptions; @@ -191,11 +191,11 @@ describe('treeMetadataExtraction', () => { DF.namedNode(TreeNodes.RemainingItems), DF.namedNode('45')); const relationDescriptions: Map = new Map([[ 'ex:s', - { subject: ['ex:path', quad], + { subject: [ 'ex:path', quad ], value: undefined, remainingItems: undefined }]]); const expectedDescription: Map = new Map([[ 'ex:s', - { subject: ['ex:path', quad], value: undefined, remainingItems: [45, quad] }]]); + { subject: [ 'ex:path', quad ], value: undefined, remainingItems: [ 45, quad ]}]]); buildRelations(relationDescriptions, quad); diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 83a94a19e..68ba55801 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -1,19 +1,18 @@ +import { BindingsFactory } from '@comunica/bindings-factory'; import type { IActorOptimizeLinkTraversalArgs, IActionOptimizeLinkTraversal, IActorOptimizeLinkTraversalOutput, } from '@comunica/bus-optimize-link-traversal'; import { ActorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; -import type { IActorTest } from '@comunica/core'; -import { Algebra } from 'sparqlalgebrajs'; -import { AsyncEvaluator, isExpressionError } from 'sparqlee'; -import { Bindings } from '@comunica/types'; -import { BindingsFactory } from '@comunica/bindings-factory'; import { KeysInitQuery } from '@comunica/context-entries'; -import { IRelation } from '@comunica/types-link-traversal'; +import type { IActorTest } from '@comunica/core'; +import type { Bindings } from '@comunica/types'; +import type { IRelation } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; -import { stringToTerm } from "rdf-string"; -import { Literal } from 'rdf-data-factory'; +import { stringToTerm } from 'rdf-string'; +import { Algebra } from 'sparqlalgebrajs'; +import { AsyncEvaluator } from 'sparqlee'; /** * A comunica Link traversal optimizer that filter link of document @@ -26,26 +25,34 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink } public async test(action: IActionOptimizeLinkTraversal): Promise { - const query = action.context.get(KeysInitQuery.query); - const relations: IRelation[] = typeof action.treeMetadata !== 'undefined' ? - (typeof action.treeMetadata.relation !== 'undefined' ? action.treeMetadata.relation : []) : [] - - return query.type === Algebra.types.FILTER && relations.length !== 0; + const query: Algebra.Operation = action.context.get(KeysInitQuery.query)!; + const relations: IRelation[] = (() => { + if (typeof action.treeMetadata !== 'undefined') { + return typeof action.treeMetadata.relation !== 'undefined' ? action.treeMetadata.relation : []; + } + return []; + })(); + return query.type === Algebra.types.FILTER && relations.length > 0; } public async run(action: IActionOptimizeLinkTraversal): Promise { const filterMap: Map = new Map(); - let filterExpression = JSON.parse(JSON.stringify((action.context.get(KeysInitQuery.query)).input.expression)); - const queryBody = (action.context.get(KeysInitQuery.query)).input.input.input; - const relations: IRelation[] = action.treeMetadata?.relation; - + const filterOperation: Algebra.Expression = JSON.parse(JSON.stringify(action.context.get(KeysInitQuery.query))) + .input.expression; + const queryBody: RDF.Quad[] = this.findBgp(action.context.get(KeysInitQuery.query)!); + const relations: IRelation[] = (() => { + if (typeof action.treeMetadata !== 'undefined') { + return typeof action.treeMetadata.relation !== 'undefined' ? action.treeMetadata.relation : []; + } + return []; + })(); for (const relation of relations) { if (typeof relation.path !== 'undefined' && typeof relation.value !== 'undefined') { const relevantQuads = this.findRelavantQuad(queryBody, relation.path.value); const bindings = this.createBinding(relevantQuads, relation.value.quad); - filterExpression = this.deleteUnrelevantFilter(filterExpression, bindings); - if (filterExpression.args.length !== 0) { + const filterExpression: Algebra.Operation = this.deleteUnrelevantFilter(filterOperation, bindings); + if (filterExpression.args.length > 0) { const evaluator = new AsyncEvaluator(filterExpression); const result: boolean = await evaluator.evaluateAsEBV(bindings); filterMap.set(relation, result); @@ -67,30 +74,23 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink return resp; } - private deleteUnrelevantFilter(filterExpression: Algebra.Operation, binding: Bindings): Algebra.Operation { + private deleteUnrelevantFilter(filterExpression: Algebra.Expression, binding: Bindings): Algebra.Expression { if ('args.args' in filterExpression) { - filterExpression.args = (filterExpression.args).filter((expression) => { + filterExpression.args = (filterExpression.args).filter(expression => { for (const arg of expression.args) { - if ('term' in arg) { - if (arg.term.termType === 'Variable') { - return binding.has(arg.term.value); - } + if ('term' in arg && arg.term.termType === 'Variable') { + return binding.has(arg.term.value); } } - return true - }) + return true; + }); } else { - for( const arg of (filterExpression.args)){ - if ('term' in arg) { - if (arg.term.termType === 'Variable') { - if (!binding.has(arg.term.value)){ - filterExpression.args = []; - break - } - } + for (const arg of (filterExpression.args)) { + if ('term' in arg && arg.term.termType === 'Variable' && !binding.has(arg.term.value)) { + filterExpression.args = []; + break; } } - } return filterExpression; } @@ -99,13 +99,28 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink let binding: Bindings = new BindingsFactory().bindings(); for (const quad of relevantQuad) { const object = quad.object.value; - const value: string = relationValue.object.termType === 'Literal' ? - ((relationValue.object).datatype.value !== '' ? - `"${relationValue.object.value}"^^${(relationValue.object).datatype.value}` : relationValue.object.value) : - relationValue.object.value; + const value: string = (() => { + if (relationValue.object.termType === 'Literal') { + return relationValue.object.datatype.value !== '' ? + `"${relationValue.object.value}"^^${relationValue.object.datatype.value}` : + relationValue.object.value; + } + return relationValue.object.value; + })(); binding = binding.set(object, stringToTerm(value)); } return binding; } + + private findBgp(query: Algebra.Operation): RDF.Quad[] { + let currentNode = query.input; + do { + if (currentNode.type === 'join') { + return currentNode.input; + } + currentNode = currentNode.input; + } while ('input' in currentNode); + return []; + } } diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/package.json b/packages/actor-optimize-link-traversal-filter-tree-links/package.json index 24ddf8726..0f45b5b70 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/package.json +++ b/packages/actor-optimize-link-traversal-filter-tree-links/package.json @@ -33,7 +33,12 @@ "dependencies": { "@comunica/core": "^2.4.0", "@comunica/bus-optimize-link-traversal": "^0.0.1", - "sparqlalgebrajs": "^4.0.0" + "@comunica/bindings-factory":"^2.2.0", + "@comunica/context-entries":"^2.4.0", + "sparqlalgebrajs": "^4.0.0", + "rdf-string":"^1.6.0", + "sparqlee": "^2.1.0" + }, "scripts": { "build": "npm run build:ts && npm run build:components", diff --git a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts index dabbb12bf..c1ef78f5a 100644 --- a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts +++ b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts @@ -1,8 +1,6 @@ import type { IAction, IActorArgs, IActorOutput, IActorTest, Mediate } from '@comunica/core'; import { Actor } from '@comunica/core'; import type { INode, IRelation } from '@comunica/types-link-traversal'; -import type { Algebra } from 'sparqlalgebrajs'; -import { BindingsStream } from '@comunica/types'; /** * A comunica actor for optimization of link traversal * @@ -31,7 +29,7 @@ export interface IActionOptimizeLinkTraversal extends IAction { export interface IActorOptimizeLinkTraversalOutput extends IActorOutput { /** - * decision map whether the link should be filter or not + * Decision map whether the link should be filter or not */ filters?: Map; } diff --git a/packages/context-entries-link-traversal/package.json b/packages/context-entries-link-traversal/package.json index b52d9a056..2f561a663 100644 --- a/packages/context-entries-link-traversal/package.json +++ b/packages/context-entries-link-traversal/package.json @@ -2,6 +2,7 @@ "name": "@comunica/context-entries-link-traversal", "version": "0.0.1", "description": "A collection of reusable Comunica context key definitions for link traversal.", + "lsd:module": true, "main": "lib/index.js", "typings": "lib/index", "repository": { diff --git a/packages/types-link-traversal/lib/TreeMetadata.ts b/packages/types-link-traversal/lib/TreeMetadata.ts index 1dab02bb5..febe9639e 100644 --- a/packages/types-link-traversal/lib/TreeMetadata.ts +++ b/packages/types-link-traversal/lib/TreeMetadata.ts @@ -1,5 +1,5 @@ /** - * inspired from + * Inspired from * https://github.com/TREEcg/tree-metadata-extraction/blob/42be38925cf6a033ddadaca5ecce929902ef1545/src/util/Util.ts */ @@ -32,21 +32,21 @@ export interface INode { } export interface IRelation { - '@type'?:{ - value: string, - quad:RDF.Quad + '@type'?: { + value: string; + quad: RDF.Quad; }; 'remainingItems'?: { - value: number, - quad:RDF.Quad + value: number; + quad: RDF.Quad; }; 'path'?: { - value: string, - quad:RDF.Quad + value: string; + quad: RDF.Quad; }; 'value'?: { - value: any, - quad:RDF.Quad + value: any; + quad: RDF.Quad; }; 'node': string ; } From 96ad45fbbae0ba4f11a281f892d825bb2dd167cf Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 4 Nov 2022 13:07:43 +0100 Subject: [PATCH 019/189] test actor link tree extractor done --- .../lib/ActorExtractLinksTree.ts | 5 +- .../test/ActorExtractLinksTree-test.ts | 79 +++++++++++++++++-- ...torOptimizeLinkTraversalFilterTreeLinks.ts | 6 +- .../lib/ActorOptimizeLinkTraversal.ts | 2 +- 4 files changed, 81 insertions(+), 11 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 9ac09b235..00b8801f4 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -64,8 +64,9 @@ export class ActorExtractLinksTree extends ActorExtractLinks { ); let acceptedRelation = relations; if (typeof linkTraversalOptimisation.filters !== 'undefined') { - acceptedRelation = relations.filter(relation => linkTraversalOptimisation.filters?.get(relation)); - } + acceptedRelation = linkTraversalOptimisation.filters.size !==0? + relations.filter(relation => linkTraversalOptimisation.filters?.get(relation.node)): acceptedRelation; + } resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); }); }); diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index afb05d271..96363fff5 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -1,9 +1,10 @@ import { KeysRdfResolveQuadPattern } from '@comunica/context-entries'; import { ActionContext, Bus } from '@comunica/core'; -import type { LinkTraversalOptimizationLinkFilter } from '@comunica/types-link-traversal'; +import type { INode, IRelation } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; +import { IActorOptimizeLinkTraversalOutput } from '@comunica/bus-optimize-link-traversal'; const stream = require('streamify-array'); @@ -19,7 +20,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { mediate(arg: any) { return Promise.resolve( { - filters: > new Map(), + filters: > new Map(), }, ); }, @@ -53,7 +54,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { }); beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', bus }); + actor = new ActorExtractLinksTree({ name: 'actor', bus, mediatorOptimizeLinkTraversal:mockMediator }); }); it('should return the links of a TREE with one relation', async() => { @@ -78,10 +79,10 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.namedNode('ex:gx')), ]); const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - const result = await actor.run(action); expect(result).toEqual({ links: [{ url: expectedUrl }]}); + expect(spyMockMediator).toBeCalledTimes(1); }); it('should return the links of a TREE with multiple relations', async() => { @@ -138,6 +139,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actor.run(action); expect(result).toEqual({ links: expectedUrl.map(value => { return { url: value }; }) }); + expect(spyMockMediator).toBeCalledTimes(1); }); it('should return the links of a TREE with one complex relation', async() => { @@ -177,6 +179,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actor.run(action); expect(result).toEqual({ links: [{ url: expectedUrl }]}); + expect(spyMockMediator).toBeCalledTimes(1); }); it('should return the links of a TREE with multiple relations combining blank nodes and named nodes', async() => { @@ -229,7 +232,73 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actor.run(action); expect(result).toEqual({ links: expectedUrl.map(value => { return { url: value }; }) }); + expect(spyMockMediator).toBeCalledTimes(1); }); + + it('should prune the filtered link', async () =>{ + const expectedUrl = 'http://foo.com'; + const prunedUrl = 'http://bar.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(prunedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g2'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g2'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + ]); + const action = { url: treeUrl, metadata: input, requestTime: 0, context }; + const relations:IRelation[] =[ + { + node:prunedUrl + }, + { + node:expectedUrl + }, + ]; + const expectedNode: INode = { + relation: relations, + subject: treeUrl, + }; + const mediationOutput: Promise = Promise.resolve( + { + filters: > new Map([[relations[0].node, false], [relations[1].node, true]]), + }, + ); + const mediator: any = { + mediate(arg: any) { + return mediationOutput + }, + }; + const spyMock = jest.spyOn(mediator, 'mediate'); + const actor = new ActorExtractLinksTree({ name: 'actor', bus, mediatorOptimizeLinkTraversal:mediator }); + + const result = await actor.run(action); + expect(spyMock).toBeCalledTimes(1); + expect(spyMock).toBeCalledWith({context: action.context, treeMetadata:expectedNode}); + expect(spyMock).toHaveReturnedWith(mediationOutput); + + expect(result).toEqual({ links: [{ url: expectedUrl }]}); + }); + }); describe('The ActorExtractLinksExtractLinksTree test method', () => { @@ -237,7 +306,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const treeUrl = 'ex:s'; beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', bus }); + actor = new ActorExtractLinksTree({ name: 'actor', bus, mediatorOptimizeLinkTraversal:mockMediator }); }); it('should test when giving a TREE', async() => { diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 68ba55801..c23f7caa0 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -36,7 +36,7 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink } public async run(action: IActionOptimizeLinkTraversal): Promise { - const filterMap: Map = new Map(); + const filterMap: Map = new Map(); const filterOperation: Algebra.Expression = JSON.parse(JSON.stringify(action.context.get(KeysInitQuery.query))) .input.expression; @@ -55,9 +55,9 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink if (filterExpression.args.length > 0) { const evaluator = new AsyncEvaluator(filterExpression); const result: boolean = await evaluator.evaluateAsEBV(bindings); - filterMap.set(relation, result); + filterMap.set(relation.node, result); } else { - filterMap.set(relation, false); + filterMap.set(relation.node, false); } } } diff --git a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts index c1ef78f5a..44546e099 100644 --- a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts +++ b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts @@ -31,7 +31,7 @@ export interface IActorOptimizeLinkTraversalOutput extends IActorOutput { /** * Decision map whether the link should be filter or not */ - filters?: Map; + filters?: Map; } export type IActorOptimizeLinkTraversalArgs = IActorArgs< From b5de349203a6c4947b82d86842933b9010528fba Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 4 Nov 2022 13:09:37 +0100 Subject: [PATCH 020/189] lint-fix --- .../lib/ActorExtractLinksTree.ts | 7 +++-- .../test/ActorExtractLinksTree-test.ts | 29 ++++++++++--------- ...torOptimizeLinkTraversalFilterTreeLinks.ts | 2 +- .../lib/ActorOptimizeLinkTraversal.ts | 4 +-- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 00b8801f4..9e6014d79 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -64,9 +64,10 @@ export class ActorExtractLinksTree extends ActorExtractLinks { ); let acceptedRelation = relations; if (typeof linkTraversalOptimisation.filters !== 'undefined') { - acceptedRelation = linkTraversalOptimisation.filters.size !==0? - relations.filter(relation => linkTraversalOptimisation.filters?.get(relation.node)): acceptedRelation; - } + acceptedRelation = linkTraversalOptimisation.filters.size > 0 ? + relations.filter(relation => linkTraversalOptimisation.filters?.get(relation.node)) : + acceptedRelation; + } resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); }); }); diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 96363fff5..6382431a3 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -1,10 +1,10 @@ +import type { IActorOptimizeLinkTraversalOutput } from '@comunica/bus-optimize-link-traversal'; import { KeysRdfResolveQuadPattern } from '@comunica/context-entries'; import { ActionContext, Bus } from '@comunica/core'; import type { INode, IRelation } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; -import { IActorOptimizeLinkTraversalOutput } from '@comunica/bus-optimize-link-traversal'; const stream = require('streamify-array'); @@ -54,7 +54,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { }); beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', bus, mediatorOptimizeLinkTraversal:mockMediator }); + actor = new ActorExtractLinksTree({ name: 'actor', bus, mediatorOptimizeLinkTraversal: mockMediator }); }); it('should return the links of a TREE with one relation', async() => { @@ -235,7 +235,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(spyMockMediator).toBeCalledTimes(1); }); - it('should prune the filtered link', async () =>{ + it('should prune the filtered link', async() => { const expectedUrl = 'http://foo.com'; const prunedUrl = 'http://bar.com'; const input = stream([ @@ -266,12 +266,12 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.namedNode('ex:gx')), ]); const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - const relations:IRelation[] =[ + const relations: IRelation[] = [ { - node:prunedUrl + node: prunedUrl, }, { - node:expectedUrl + node: expectedUrl, }, ]; const expectedNode: INode = { @@ -280,25 +280,26 @@ describe('ActorExtractLinksExtractLinksTree', () => { }; const mediationOutput: Promise = Promise.resolve( { - filters: > new Map([[relations[0].node, false], [relations[1].node, true]]), + filters: new Map([[ relations[0].node, false ], [ relations[1].node, true ]]), }, ); const mediator: any = { mediate(arg: any) { - return mediationOutput + return mediationOutput; }, }; const spyMock = jest.spyOn(mediator, 'mediate'); - const actor = new ActorExtractLinksTree({ name: 'actor', bus, mediatorOptimizeLinkTraversal:mediator }); + const actorWithCustomMediator = new ActorExtractLinksTree( + { name: 'actor', bus, mediatorOptimizeLinkTraversal: mediator }, + ); - const result = await actor.run(action); + const result = await actorWithCustomMediator.run(action); expect(spyMock).toBeCalledTimes(1); - expect(spyMock).toBeCalledWith({context: action.context, treeMetadata:expectedNode}); + expect(spyMock).toBeCalledWith({ context: action.context, treeMetadata: expectedNode }); expect(spyMock).toHaveReturnedWith(mediationOutput); - + expect(result).toEqual({ links: [{ url: expectedUrl }]}); }); - }); describe('The ActorExtractLinksExtractLinksTree test method', () => { @@ -306,7 +307,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const treeUrl = 'ex:s'; beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', bus, mediatorOptimizeLinkTraversal:mockMediator }); + actor = new ActorExtractLinksTree({ name: 'actor', bus, mediatorOptimizeLinkTraversal: mockMediator }); }); it('should test when giving a TREE', async() => { diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index c23f7caa0..418c9b02a 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -36,7 +36,7 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink } public async run(action: IActionOptimizeLinkTraversal): Promise { - const filterMap: Map = new Map(); + const filterMap: Map = new Map(); const filterOperation: Algebra.Expression = JSON.parse(JSON.stringify(action.context.get(KeysInitQuery.query))) .input.expression; diff --git a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts index 44546e099..011d8a7c3 100644 --- a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts +++ b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts @@ -1,6 +1,6 @@ import type { IAction, IActorArgs, IActorOutput, IActorTest, Mediate } from '@comunica/core'; import { Actor } from '@comunica/core'; -import type { INode, IRelation } from '@comunica/types-link-traversal'; +import type { INode } from '@comunica/types-link-traversal'; /** * A comunica actor for optimization of link traversal * @@ -31,7 +31,7 @@ export interface IActorOptimizeLinkTraversalOutput extends IActorOutput { /** * Decision map whether the link should be filter or not */ - filters?: Map; + filters?: Map; } export type IActorOptimizeLinkTraversalArgs = IActorArgs< From 9eb377d63ee0e42b33d409a487d969c39beb37dd Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 4 Nov 2022 13:52:53 +0100 Subject: [PATCH 021/189] test coverage brought back to 100% by adding test for treeMetadataExtraction --- .../test/ActorExtractLinksTree-test.ts | 34 +++++++++++ .../test/treeMetadataExtraction-test.ts | 60 ++++++++++++++++++- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 6382431a3..491eca061 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -2,6 +2,7 @@ import type { IActorOptimizeLinkTraversalOutput } from '@comunica/bus-optimize-l import { KeysRdfResolveQuadPattern } from '@comunica/context-entries'; import { ActionContext, Bus } from '@comunica/core'; import type { INode, IRelation } from '@comunica/types-link-traversal'; +import { RelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; @@ -256,6 +257,18 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.namedNode('https://w3id.org/tree#node'), DF.literal(prunedUrl), DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#remainingItems'), + DF.literal('66'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#value'), + DF.literal('66'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), + DF.namedNode('ex:gx')), DF.quad(DF.namedNode(treeUrl), DF.namedNode('https://w3id.org/tree#relation'), DF.blankNode('_:_g2'), @@ -269,6 +282,27 @@ describe('ActorExtractLinksExtractLinksTree', () => { const relations: IRelation[] = [ { node: prunedUrl, + remainingItems: { + value: 66, + quad: DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#remainingItems'), + DF.literal('66'), + DF.namedNode('ex:gx')), + }, + '@type': { + value: RelationOperator.GreaterThanRelation, + quad: DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), + DF.namedNode('ex:gx')), + }, + value: { + value: '66', + quad: DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#value'), + DF.literal('66'), + DF.namedNode('ex:gx')), + }, }, { node: expectedUrl, diff --git a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts index 697e2ed00..6e5706275 100644 --- a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts +++ b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts @@ -1,6 +1,7 @@ +import type { IRelation } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; -import { buildRelations, addRelationDescription } from '../lib/treeMetadataExtraction'; +import { buildRelations, addRelationDescription, collectRelation } from '../lib/treeMetadataExtraction'; import type { IRelationDescription } from '../lib/typeTreeMetadataExtraction'; import { TreeNodes, RelationOperator } from '../lib/typeTreeMetadataExtraction'; @@ -214,4 +215,61 @@ describe('treeMetadataExtraction', () => { expect(relationDescriptions.size).toBe(0); }); }); + + describe('collectRelation with undefined description', () => { + const aQuad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.namedNode('ex:o'), + DF.namedNode('ex:gx')); + const nodeLink = 'http://aLink.com'; + + it('should not have the @type field when the operator field is defined but it\'s value is undefined', () => { + const relationDescription: IRelationDescription = { + operator: [ undefined, aQuad ], + }; + const expectedRelation: IRelation = { + node: nodeLink, + }; + const relation = collectRelation(relationDescription, nodeLink); + + expect(relation).toStrictEqual(expectedRelation); + }); + + it('should not have the remainingItems field when the operator field is defined but it\'s value is undefined', + () => { + const relationDescription: IRelationDescription = { + remainingItems: [ undefined, aQuad ], + }; + const expectedRelation: IRelation = { + node: nodeLink, + }; + const relation = collectRelation(relationDescription, nodeLink); + + expect(relation).toStrictEqual(expectedRelation); + }); + + it('should not have the path field when the operator field is defined but it\'s value is undefined', () => { + const relationDescription: IRelationDescription = { + subject: [ undefined, aQuad ], + }; + const expectedRelation: IRelation = { + node: nodeLink, + }; + const relation = collectRelation(relationDescription, nodeLink); + + expect(relation).toStrictEqual(expectedRelation); + }); + + it('should not have the value field when the operator field is defined but it\'s value is undefined', () => { + const relationDescription: IRelationDescription = { + value: [ undefined, aQuad ], + }; + const expectedRelation: IRelation = { + node: nodeLink, + }; + const relation = collectRelation(relationDescription, nodeLink); + + expect(relation).toStrictEqual(expectedRelation); + }); + }); }); From 4c80c1cfbf899d6761f5f4c584893e18d9b17667 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 4 Nov 2022 15:47:06 +0100 Subject: [PATCH 022/189] unit test test method of optmize link traversal --- .../lib/ActorExtractLinksTree.ts | 20 ++- .../test/ActorExtractLinksTree-test.ts | 98 ++++++++++++++ ...torOptimizeLinkTraversalFilterTreeLinks.ts | 26 +++- ...timizeLinkTraversalFilterTreeLinks-test.ts | 123 +++++++++++++++++- 4 files changed, 252 insertions(+), 15 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 9e6014d79..947b7943f 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -3,7 +3,8 @@ import type { IActorExtractLinksOutput, IActorExtractLinksArgs, } from '@comunica/bus-extract-links'; import { ActorExtractLinks } from '@comunica/bus-extract-links'; -import type { MediatorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; +import type { MediatorOptimizeLinkTraversal, + IActorOptimizeLinkTraversalOutput } from '@comunica/bus-optimize-link-traversal'; import type { IActorTest } from '@comunica/core'; import type { IRelationDescription, IRelation, INode } from '@comunica/types-link-traversal'; import { TreeNodes } from '@comunica/types-link-traversal'; @@ -63,16 +64,25 @@ export class ActorExtractLinksTree extends ActorExtractLinks { { treeMetadata: node, context: action.context }, ); let acceptedRelation = relations; - if (typeof linkTraversalOptimisation.filters !== 'undefined') { - acceptedRelation = linkTraversalOptimisation.filters.size > 0 ? - relations.filter(relation => linkTraversalOptimisation.filters?.get(relation.node)) : - acceptedRelation; + if (typeof linkTraversalOptimisation !== 'undefined') { + acceptedRelation = this.handleOptimization(linkTraversalOptimisation, relations); } + resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); }); }); } + private handleOptimization(linkTraversalOptimisation: IActorOptimizeLinkTraversalOutput, + relations: IRelation[]): IRelation[] { + if (typeof linkTraversalOptimisation.filters !== 'undefined') { + return linkTraversalOptimisation.filters.size > 0 ? + relations.filter(relation => linkTraversalOptimisation.filters?.get(relation.node)) : + relations; + } + return relations; + } + /** * A helper function to find all the relations of a TREE document and the possible next nodes to visit. * The next nodes are not guaranteed to have as subject the URL of the current page, diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 491eca061..84c9696c4 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -334,6 +334,104 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toEqual({ links: [{ url: expectedUrl }]}); }); + + it('should not filter the links if the mediator return an undefined filters', async() => { + const expectedUrl = 'http://foo.com'; + const secondExpectedLink = 'http://bar.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(secondExpectedLink), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#remainingItems'), + DF.literal('66'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#value'), + DF.literal('66'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g2'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g2'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + ]); + const action = { url: treeUrl, metadata: input, requestTime: 0, context }; + const relations: IRelation[] = [ + { + node: secondExpectedLink, + remainingItems: { + value: 66, + quad: DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#remainingItems'), + DF.literal('66'), + DF.namedNode('ex:gx')), + }, + '@type': { + value: RelationOperator.GreaterThanRelation, + quad: DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), + DF.namedNode('ex:gx')), + }, + value: { + value: '66', + quad: DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#value'), + DF.literal('66'), + DF.namedNode('ex:gx')), + }, + }, + { + node: expectedUrl, + }, + ]; + const expectedNode: INode = { + relation: relations, + subject: treeUrl, + }; + const mediationOutput: Promise = Promise.resolve( + { + }, + ); + const mediator: any = { + mediate(arg: any) { + return mediationOutput; + }, + }; + const spyMock = jest.spyOn(mediator, 'mediate'); + const actorWithCustomMediator = new ActorExtractLinksTree( + { name: 'actor', bus, mediatorOptimizeLinkTraversal: mediator }, + ); + + const result = await actorWithCustomMediator.run(action); + expect(spyMock).toBeCalledTimes(1); + expect(spyMock).toBeCalledWith({ context: action.context, treeMetadata: expectedNode }); + expect(spyMock).toHaveReturnedWith(mediationOutput); + + expect(result).toEqual({ links: [{ url: secondExpectedLink }, { url: expectedUrl }]}); + }); }); describe('The ActorExtractLinksExtractLinksTree test method', () => { diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 418c9b02a..757dd50af 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -32,12 +32,16 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink } return []; })(); - return query.type === Algebra.types.FILTER && relations.length > 0; + const filterExist: boolean = this.doesNodeExist(query, Algebra.types.FILTER); + return filterExist && relations.length > 0 ? + Promise.resolve(true) : + Promise.reject(new Error( + 'the action must contain TREE relation and the query must contain at least a filter', + )); } public async run(action: IActionOptimizeLinkTraversal): Promise { const filterMap: Map = new Map(); - const filterOperation: Algebra.Expression = JSON.parse(JSON.stringify(action.context.get(KeysInitQuery.query))) .input.expression; const queryBody: RDF.Quad[] = this.findBgp(action.context.get(KeysInitQuery.query)!); @@ -61,6 +65,7 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink } } } + return { filters: filterMap }; } @@ -118,9 +123,24 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink if (currentNode.type === 'join') { return currentNode.input; } - currentNode = currentNode.input; + if ('input' in currentNode) { + currentNode = currentNode.input; + } } while ('input' in currentNode); return []; } + + private doesNodeExist(query: Algebra.Operation, node: string): boolean { + let currentNode = query; + do { + if (currentNode.type === node) { + return true; + } + if ('input' in currentNode) { + currentNode = currentNode.input; + } + } while ('input' in currentNode); + return false; + } } diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts b/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts index 12039c333..d37d61fed 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts @@ -1,5 +1,9 @@ -import { Bus } from '@comunica/core'; -import type { ActorOptimizeLinkTraversalFilterTreeLinks } from '../lib/ActorOptimizeLinkTraversalFilterTreeLinks'; +import type { IActionOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; +import { KeysInitQuery } from '@comunica/context-entries'; +import { Bus, ActionContext } from '@comunica/core'; +import type { INode } from '@comunica/types-link-traversal'; +import { Algebra } from 'sparqlalgebrajs'; +import { ActorOptimizeLinkTraversalFilterTreeLinks } from '../lib/ActorOptimizeLinkTraversalFilterTreeLinks'; describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { let bus: any; @@ -12,15 +16,120 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { let actor: ActorOptimizeLinkTraversalFilterTreeLinks; beforeEach(() => { - // Actor = new ActorOptimizeLinkTraversalFilterTreeLinks({ name: 'actor', bus }); + actor = new ActorOptimizeLinkTraversalFilterTreeLinks({ name: 'actor', bus }); }); + describe('test method', () => { + const treeSubject = 'tree'; + it('should test when there is relations and a filter operation in the query', async() => { + const context = new ActionContext({ + [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, + }); + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + }, + ], + }; + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; - it('should test', () => { - // Return expect(actor.test({ todo: true })).resolves.toEqual({ todo: true }); // TODO + const response = await actor.test(action); + expect(response).toBe(true); + }); + + it('should not test when there is no relations and a filter operation in the query', async() => { + const context = new ActionContext({ + [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, + }); + const node: INode = { + subject: treeSubject, + relation: [], + }; + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + + await actor.test(action).then(v => { + expect(v).toBeUndefined(); + }).catch(error => { + expect(error).toBeDefined(); + }); + }); + + it('should not test when there is no tree metadata and a filter operation in the query', async() => { + const context = new ActionContext({ + [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, + }); + const node: INode = { + subject: treeSubject, + relation: [], + }; + const action: IActionOptimizeLinkTraversal = { + context, + }; + + await actor.test(action).then(v => { + expect(v).toBeUndefined(); + }).catch(error => { + expect(error).toBeDefined(); + }); + }); + + it('should no test when there no filter operation in the query', async() => { + const context = new ActionContext({ + [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, + }); + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + }, + ], + }; + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + + await actor.test(action).then(v => { + expect(v).toBeUndefined(); + }).catch(error => { + expect(error).toBeDefined(); + }); + }); + + it('should no test when there no filter operation in the query and no TREE relation', async() => { + const context = new ActionContext({ + [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, + }); + const node: INode = { + subject: treeSubject, + relation: [], + }; + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + + await actor.test(action).then(v => { + expect(v).toBeUndefined(); + }).catch(error => { + expect(error).toBeDefined(); + }); + }); }); + - it('should run', () => { - // Return expect(actor.run({ todo: true })).resolves.toMatchObject({ todo: true }); // TODO + describe('run method', () => { + it('should run', () => { + // Return expect(actor.run({ todo: true })).resolves.toMatchObject({ todo: true }); // TODO + }); }); }); }); From ecb1504cbd73cc3b9abf3aa39e0c98482ca9602d Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 7 Nov 2022 10:17:25 +0100 Subject: [PATCH 023/189] first test of run method --- ...timizeLinkTraversalFilterTreeLinks-test.ts | 103 ++++++++++++++++-- 1 file changed, 95 insertions(+), 8 deletions(-) diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts b/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts index d37d61fed..8ac582399 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts @@ -4,6 +4,11 @@ import { Bus, ActionContext } from '@comunica/core'; import type { INode } from '@comunica/types-link-traversal'; import { Algebra } from 'sparqlalgebrajs'; import { ActorOptimizeLinkTraversalFilterTreeLinks } from '../lib/ActorOptimizeLinkTraversalFilterTreeLinks'; +import type * as RDF from 'rdf-js'; +import { DataFactory } from 'rdf-data-factory'; + +const DF = new DataFactory(); + describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { let bus: any; @@ -20,7 +25,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); describe('test method', () => { const treeSubject = 'tree'; - it('should test when there is relations and a filter operation in the query', async() => { + it('should test when there is relations and a filter operation in the query', async () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -41,7 +46,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(response).toBe(true); }); - it('should not test when there is no relations and a filter operation in the query', async() => { + it('should not test when there is no relations and a filter operation in the query', async () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -61,7 +66,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); }); - it('should not test when there is no tree metadata and a filter operation in the query', async() => { + it('should not test when there is no tree metadata and a filter operation in the query', async () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -80,7 +85,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); }); - it('should no test when there no filter operation in the query', async() => { + it('should no test when there no filter operation in the query', async () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); @@ -104,7 +109,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); }); - it('should no test when there no filter operation in the query and no TREE relation', async() => { + it('should no test when there no filter operation in the query and no TREE relation', async () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); @@ -124,12 +129,94 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); }); }); - + describe('run method', () => { - it('should run', () => { - // Return expect(actor.run({ todo: true })).resolves.toMatchObject({ todo: true }); // TODO + const aQuad: RDF.Quad = DF.quad(DF.namedNode("ex:s"), + DF.namedNode("ex:p"), + DF.namedNode("ex:o") + ); + it('should run accept the relation when the filter respect the relation', async () => { + const treeSubject = 'tree'; + + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: "ex:path", + quad: aQuad + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode("ex:s"), + DF.namedNode("ex:p"), + DF.literal("5", DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))) + } + }, + ], + }; + const bgp: RDF.Quad[] = [ + DF.quad(DF.namedNode("ex:foo"), DF.namedNode("ex:path"), DF.variable("o")) + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args:[ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term:{ + termType: 'Variable', + value:'o' + } + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term:{ + termType: 'Literal', + langugage: '', + value:'5', + datatype:{ + termType:'namedNode', + value:'http://www.w3.org/2001/XMLSchema#integer' + } + } + } + ], + }; + const query = { + type: Algebra.types.PROJECT, + input:{ + type: Algebra.types.FILTER, + expression:filterExpression, + input: { + input: { + type: "join", + input: bgp, + } + }, + } + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + const result = await actor.run(action); + + expect(result.filters).toBeDefined(); + expect(result.filters).toStrictEqual( + new Map([['http://bar.com', true]]) + ); }); + }); }); }); From 2e1b318012304aafcaa708af861704d51483a211 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 7 Nov 2022 15:58:37 +0100 Subject: [PATCH 024/189] test almost done need to pass the coverage --- ...torOptimizeLinkTraversalFilterTreeLinks.ts | 64 +- ...timizeLinkTraversalFilterTreeLinks-test.ts | 892 +++++++++++++++++- 2 files changed, 899 insertions(+), 57 deletions(-) diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 757dd50af..3386f3db9 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -9,6 +9,7 @@ import { KeysInitQuery } from '@comunica/context-entries'; import type { IActorTest } from '@comunica/core'; import type { Bindings } from '@comunica/types'; import type { IRelation } from '@comunica/types-link-traversal'; +import { join } from 'path'; import type * as RDF from 'rdf-js'; import { stringToTerm } from 'rdf-string'; import { Algebra } from 'sparqlalgebrajs'; @@ -52,20 +53,25 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink return []; })(); for (const relation of relations) { - if (typeof relation.path !== 'undefined' && typeof relation.value !== 'undefined') { - const relevantQuads = this.findRelavantQuad(queryBody, relation.path.value); - const bindings = this.createBinding(relevantQuads, relation.value.quad); - const filterExpression: Algebra.Operation = this.deleteUnrelevantFilter(filterOperation, bindings); - if (filterExpression.args.length > 0) { - const evaluator = new AsyncEvaluator(filterExpression); - const result: boolean = await evaluator.evaluateAsEBV(bindings); - filterMap.set(relation.node, result); - } else { - filterMap.set(relation.node, false); - } + if (typeof relation.path === 'undefined' || typeof relation.value === 'undefined') { + filterMap.set(relation.node, true); + continue; + } + const relevantQuads = this.findRelavantQuad(queryBody, relation.path.value); + if (relevantQuads.length === 0) { + filterMap.set(relation.node, true); + continue; } + const bindings = this.createBinding(relevantQuads, relation.value.quad); + const filterExpression: Algebra.Operation = this.deleteUnrelevantFilter(filterOperation, bindings); + if (filterExpression.args.length === 0) { + filterMap.set(relation.node, true); + continue; + } + const evaluator = new AsyncEvaluator(filterExpression); + const result: boolean = await evaluator.evaluateAsEBV(bindings); + filterMap.set(relation.node, result); } - return { filters: filterMap }; } @@ -80,15 +86,18 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink } private deleteUnrelevantFilter(filterExpression: Algebra.Expression, binding: Bindings): Algebra.Expression { - if ('args.args' in filterExpression) { + if ('operator' in filterExpression.args[0]) { filterExpression.args = (filterExpression.args).filter(expression => { for (const arg of expression.args) { if ('term' in arg && arg.term.termType === 'Variable') { return binding.has(arg.term.value); } } - return true; + return false; }); + if(filterExpression.args.length === 1 ) { + filterExpression = filterExpression.args[0]; + } } else { for (const arg of (filterExpression.args)) { if ('term' in arg && arg.term.termType === 'Variable' && !binding.has(arg.term.value)) { @@ -104,14 +113,9 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink let binding: Bindings = new BindingsFactory().bindings(); for (const quad of relevantQuad) { const object = quad.object.value; - const value: string = (() => { - if (relationValue.object.termType === 'Literal') { - return relationValue.object.datatype.value !== '' ? - `"${relationValue.object.value}"^^${relationValue.object.datatype.value}` : - relationValue.object.value; - } - return relationValue.object.value; - })(); + const value: string =( relationValue.object).datatype.value !== '' ? + `"${relationValue.object.value}"^^${( relationValue.object).datatype.value}` : + relationValue.object.value; binding = binding.set(object, stringToTerm(value)); } return binding; @@ -121,7 +125,7 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink let currentNode = query.input; do { if (currentNode.type === 'join') { - return currentNode.input; + return this.formatBgp(currentNode.input); } if ('input' in currentNode) { currentNode = currentNode.input; @@ -130,6 +134,20 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink return []; } + private formatBgp(joins: any): RDF.Quad[] { + const bgp: RDF.Quad[] = []; + if (joins.length === 0) { + return []; + } + if (!('input' in joins[0])) { + return joins; + } + for (const join of joins) { + bgp.push(join.input[0]); + } + return bgp; + } + private doesNodeExist(query: Algebra.Operation, node: string): boolean { let currentNode = query; do { diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts b/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts index 8ac582399..f2b0d8fa5 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts @@ -2,14 +2,13 @@ import type { IActionOptimizeLinkTraversal } from '@comunica/bus-optimize-link-t import { KeysInitQuery } from '@comunica/context-entries'; import { Bus, ActionContext } from '@comunica/core'; import type { INode } from '@comunica/types-link-traversal'; +import { DataFactory } from 'rdf-data-factory'; +import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; import { ActorOptimizeLinkTraversalFilterTreeLinks } from '../lib/ActorOptimizeLinkTraversalFilterTreeLinks'; -import type * as RDF from 'rdf-js'; -import { DataFactory } from 'rdf-data-factory'; const DF = new DataFactory(); - describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { let bus: any; @@ -130,13 +129,13 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); }); - describe('run method', () => { - const aQuad: RDF.Quad = DF.quad(DF.namedNode("ex:s"), - DF.namedNode("ex:p"), - DF.namedNode("ex:o") - ); - it('should run accept the relation when the filter respect the relation', async () => { + const aQuad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.namedNode('ex:o')); + + + it('should accept the relation when the filter respect the relation', async () => { const treeSubject = 'tree'; const node: INode = { @@ -145,61 +144,366 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { { node: 'http://bar.com', path: { - value: "ex:path", - quad: aQuad + value: 'ex:path', + quad: aQuad, }, value: { value: '5', - quad: DF.quad(DF.namedNode("ex:s"), - DF.namedNode("ex:p"), - DF.literal("5", DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))) - } + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, }, ], }; const bgp: RDF.Quad[] = [ - DF.quad(DF.namedNode("ex:foo"), DF.namedNode("ex:path"), DF.variable("o")) + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), ]; const filterExpression = { expressionType: Algebra.expressionTypes.OPERATOR, operator: '=', type: Algebra.types.EXPRESSION, - args:[ + args: [ { expressionType: Algebra.expressionTypes.TERM, type: Algebra.types.EXPRESSION, - term:{ + term: { termType: 'Variable', - value:'o' - } + value: 'o', + }, }, { expressionType: Algebra.expressionTypes.TERM, type: Algebra.types.EXPRESSION, - term:{ + term: { termType: 'Literal', langugage: '', - value:'5', - datatype:{ - termType:'namedNode', - value:'http://www.w3.org/2001/XMLSchema#integer' - } - } - } + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: 'join', + input: bgp, + }, + }, + }, + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + const result = await actor.run(action); + + expect(result.filters).toBeDefined(); + expect(result.filters).toStrictEqual( + new Map([['http://bar.com', true]]), + ); + }); + + it('should not accept the relation when the filter is not respected by the relation', async () => { + const treeSubject = 'tree'; + + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, + }, + ], + }; + const bgp: RDF.Quad[] = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '88', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: 'join', + input: bgp, + }, + }, + }, + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + const result = await actor.run(action); + + expect(result.filters).toBeDefined(); + expect(result.filters).toStrictEqual( + new Map([['http://bar.com', false]]), + ); + }); + + it('should accept the relation when the query don\'t invoke the right path', async () => { + const treeSubject = 'tree'; + + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, + }, + ], + }; + const bgp: RDF.Quad[] = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '88', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: 'join', + input: bgp, + }, + }, + }, + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + const result = await actor.run(action); + + expect(result.filters).toBeDefined(); + expect(result.filters).toStrictEqual( + new Map([['http://bar.com', true]]), + ); + }); + + it('should return an empty map when there is no relation', async () => { + + + const bgp: RDF.Quad[] = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '88', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: 'join', + input: bgp, + }, + }, + }, + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const action: IActionOptimizeLinkTraversal = { + context, + }; + const result = await actor.run(action); + + expect(result.filters).toBeDefined(); + expect(result.filters).toStrictEqual( + new Map(), + ); + }); + + it('should accept the relation when the query don\'t invoke the right path', async () => { + const treeSubject = 'tree'; + + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, + }, + ], + }; + const bgp: RDF.Quad[] = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '88', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, ], }; const query = { type: Algebra.types.PROJECT, - input:{ + input: { type: Algebra.types.FILTER, - expression:filterExpression, + expression: filterExpression, input: { input: { - type: "join", + type: 'join', input: bgp, - } + }, + }, }, - } }; const context = new ActionContext({ [KeysInitQuery.query.name]: query, @@ -213,10 +517,530 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(result.filters).toBeDefined(); expect(result.filters).toStrictEqual( - new Map([['http://bar.com', true]]) + new Map([['http://bar.com', true]]), ); }); + it('should accept the relation when there is multiple filters and the query don\'t match the relation', async () => { + const treeSubject = 'tree'; + + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, + }, + ], + }; + const bgp: RDF.Quad[] = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), + ]; + const filterExpression = { + expressionType: 'operator', + operator: '&&', + type: 'expression', + args: [ + { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '88', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }, + { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + } + ] + }; + + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: 'join', + input: bgp, + }, + }, + }, + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + const result = await actor.run(action); + + expect(result.filters).toBeDefined(); + expect(result.filters).toStrictEqual( + new Map([['http://bar.com', true]]), + ); + }); + + it('should accept the relations when the filter respect the relation and a relation doesn\'t specify a path and/or', async () => { + const treeSubject = 'tree'; + + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, + }, + + { + node: 'http://foo.com', + }, + ], + }; + const bgp: RDF.Quad[] = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: 'join', + input: bgp, + }, + }, + }, + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + const result = await actor.run(action); + + expect(result.filters).toBeDefined(); + expect(result.filters).toStrictEqual( + new Map([['http://bar.com', true], ['http://foo.com', true]]), + ); + }); + + it('should accept the relation when the filter are not related to the relations', async () => { + const treeSubject = 'tree'; + + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, + }, + ], + }; + const bgp: RDF.Quad[] = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'p', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: 'join', + input: bgp, + }, + }, + }, + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + const result = await actor.run(action); + + expect(result.filters).toBeDefined(); + expect(result.filters).toStrictEqual( + new Map([['http://bar.com', true]]), + ); + }); + + it('should accept the relation when there is multiples filters and one is not relevant', async () => { + const treeSubject = 'tree'; + + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, + }, + ], + }; + const bgp: RDF.Quad[] = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '&&', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [{ + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ] + }, + { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [{ + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'p', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ] + } + ], + }; + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: 'join', + input: bgp, + }, + }, + }, + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + const result = await actor.run(action); + + expect(result.filters).toBeDefined(); + expect(result.filters).toStrictEqual( + new Map([['http://bar.com', true]]), + ); + }); + + it('should accept the relation when the filter compare two constant', async () => { + const treeSubject = 'tree'; + + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, + }, + ], + }; + const bgp: RDF.Quad[] = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '&&', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [{ + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ] + }, + { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [{ + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ] + } + ], + }; + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: 'join', + input: bgp, + }, + }, + }, + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + const result = await actor.run(action); + + expect(result.filters).toBeDefined(); + expect(result.filters).toStrictEqual( + new Map([['http://bar.com', true]]), + ); + }); }); }); }); From 3e2b5271e70137db535cc55f3681f90d95df99de Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 8 Nov 2022 08:41:51 +0100 Subject: [PATCH 025/189] test coverage brought to 100% and lint fix --- ...torOptimizeLinkTraversalFilterTreeLinks.ts | 19 +- ...timizeLinkTraversalFilterTreeLinks-test.ts | 442 +++++++++--------- 2 files changed, 241 insertions(+), 220 deletions(-) diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 3386f3db9..670e6b10c 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -9,7 +9,6 @@ import { KeysInitQuery } from '@comunica/context-entries'; import type { IActorTest } from '@comunica/core'; import type { Bindings } from '@comunica/types'; import type { IRelation } from '@comunica/types-link-traversal'; -import { join } from 'path'; import type * as RDF from 'rdf-js'; import { stringToTerm } from 'rdf-string'; import { Algebra } from 'sparqlalgebrajs'; @@ -46,9 +45,15 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink const filterOperation: Algebra.Expression = JSON.parse(JSON.stringify(action.context.get(KeysInitQuery.query))) .input.expression; const queryBody: RDF.Quad[] = this.findBgp(action.context.get(KeysInitQuery.query)!); + if (queryBody.length === 0) { + return { filters: filterMap }; + } const relations: IRelation[] = (() => { if (typeof action.treeMetadata !== 'undefined') { + // If the test pass the relation are defined, it's for the lint + /* istanbul ignore next */ return typeof action.treeMetadata.relation !== 'undefined' ? action.treeMetadata.relation : []; + /* istanbul ignore next */ } return []; })(); @@ -95,7 +100,7 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink } return false; }); - if(filterExpression.args.length === 1 ) { + if (filterExpression.args.length === 1) { filterExpression = filterExpression.args[0]; } } else { @@ -113,9 +118,8 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink let binding: Bindings = new BindingsFactory().bindings(); for (const quad of relevantQuad) { const object = quad.object.value; - const value: string =( relationValue.object).datatype.value !== '' ? - `"${relationValue.object.value}"^^${( relationValue.object).datatype.value}` : - relationValue.object.value; + const value = + `"${relationValue.object.value}"^^${(relationValue.object).datatype.value}`; binding = binding.set(object, stringToTerm(value)); } return binding; @@ -136,9 +140,6 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink private formatBgp(joins: any): RDF.Quad[] { const bgp: RDF.Quad[] = []; - if (joins.length === 0) { - return []; - } if (!('input' in joins[0])) { return joins; } @@ -154,9 +155,11 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink if (currentNode.type === node) { return true; } + /* istanbul ignore next */ if ('input' in currentNode) { currentNode = currentNode.input; } + /* istanbul ignore next */ } while ('input' in currentNode); return false; } diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts b/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts index f2b0d8fa5..2b5dc931e 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts @@ -24,7 +24,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); describe('test method', () => { const treeSubject = 'tree'; - it('should test when there is relations and a filter operation in the query', async () => { + it('should test when there is relations and a filter operation in the query', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -45,7 +45,26 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(response).toBe(true); }); - it('should not test when there is no relations and a filter operation in the query', async () => { + it('should no test when the TREE relation are undefined', async() => { + const context = new ActionContext({ + [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, + }); + const node: INode = { + subject: treeSubject, + }; + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + + await actor.test(action).then(v => { + expect(v).toBeUndefined(); + }).catch(error => { + expect(error).toBeDefined(); + }); + }); + + it('should not test when there is no relations and a filter operation in the query', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -65,7 +84,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); }); - it('should not test when there is no tree metadata and a filter operation in the query', async () => { + it('should not test when there is no tree metadata and a filter operation in the query', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -84,7 +103,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); }); - it('should no test when there no filter operation in the query', async () => { + it('should no test when there no filter operation in the query', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); @@ -108,7 +127,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); }); - it('should no test when there no filter operation in the query and no TREE relation', async () => { + it('should no test when there no filter operation in the query and no TREE relation', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); @@ -134,8 +153,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { DF.namedNode('ex:p'), DF.namedNode('ex:o')); - - it('should accept the relation when the filter respect the relation', async () => { + it('should accept the relation when the filter respect the relation', async() => { const treeSubject = 'tree'; const node: INode = { @@ -156,9 +174,17 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; - const bgp: RDF.Quad[] = [ + const bgp = ([ DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), - ]; + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), + DF.quad(DF.namedNode('ex:bar'), DF.namedNode('ex:p2'), DF.namedNode('ex:o2')), + DF.quad(DF.namedNode('ex:too'), DF.namedNode('ex:p3'), DF.namedNode('ex:o3')), + ]).map(quad => { + return { + input: [ quad ], + type: 'join', + }; + }); const filterExpression = { expressionType: Algebra.expressionTypes.OPERATOR, operator: '=', @@ -212,11 +238,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(result.filters).toBeDefined(); expect(result.filters).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should not accept the relation when the filter is not respected by the relation', async () => { + it('should not accept the relation when the filter is not respected by the relation', async() => { const treeSubject = 'tree'; const node: INode = { @@ -293,11 +319,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(result.filters).toBeDefined(); expect(result.filters).toStrictEqual( - new Map([['http://bar.com', false]]), + new Map([[ 'http://bar.com', false ]]), ); }); - it('should accept the relation when the query don\'t invoke the right path', async () => { + it('should accept the relation when the query don\'t invoke the right path', async() => { const treeSubject = 'tree'; const node: INode = { @@ -374,13 +400,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(result.filters).toBeDefined(); expect(result.filters).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should return an empty map when there is no relation', async () => { - - + it('should return an empty map when there is no relation', async() => { const bgp: RDF.Quad[] = [ DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), ]; @@ -440,205 +464,126 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it('should accept the relation when the query don\'t invoke the right path', async () => { - const treeSubject = 'tree'; + it('should accept the relation when there is multiple filters and the query don\'t match the relation', + async() => { + const treeSubject = 'tree'; - const node: INode = { - subject: treeSubject, - relation: [ - { - node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, - value: { - value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), - }, - }, - ], - }; - const bgp: RDF.Quad[] = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '88', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), }, }, - }, - ], - }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: 'join', - input: bgp, - }, - }, - }, - }; - const context = new ActionContext({ - [KeysInitQuery.query.name]: query, - }); - - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - const result = await actor.run(action); - - expect(result.filters).toBeDefined(); - expect(result.filters).toStrictEqual( - new Map([['http://bar.com', true]]), - ); - }); - - it('should accept the relation when there is multiple filters and the query don\'t match the relation', async () => { - const treeSubject = 'tree'; - - const node: INode = { - subject: treeSubject, - relation: [ - { - node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, - value: { - value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), - }, - }, - ], - }; - const bgp: RDF.Quad[] = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), - ]; - const filterExpression = { - expressionType: 'operator', - operator: '&&', - type: 'expression', - args: [ - { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', + ], + }; + const bgp: RDF.Quad[] = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), + ]; + const filterExpression = { + expressionType: 'operator', + operator: '&&', + type: 'expression', + args: [ + { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '88', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '88', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, }, }, - }, - ], - }, - { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', + ], + }, + { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, }, }, - }, - ], - } - ] - }; + ], + }, + ], + }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, + const query = { + type: Algebra.types.PROJECT, input: { + type: Algebra.types.FILTER, + expression: filterExpression, input: { - type: 'join', - input: bgp, + input: { + type: 'join', + input: bgp, + }, }, }, - }, - }; - const context = new ActionContext({ - [KeysInitQuery.query.name]: query, - }); + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - const result = await actor.run(action); + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + const result = await actor.run(action); - expect(result.filters).toBeDefined(); - expect(result.filters).toStrictEqual( - new Map([['http://bar.com', true]]), - ); - }); + expect(result.filters).toBeDefined(); + expect(result.filters).toStrictEqual( + new Map([[ 'http://bar.com', true ]]), + ); + }); - it('should accept the relations when the filter respect the relation and a relation doesn\'t specify a path and/or', async () => { + it(`should accept the relations when the filter respect the relation + and a relation doesn't specify a path and/or`, async() => { const treeSubject = 'tree'; const node: INode = { @@ -719,11 +664,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(result.filters).toBeDefined(); expect(result.filters).toStrictEqual( - new Map([['http://bar.com', true], ['http://foo.com', true]]), + new Map([[ 'http://bar.com', true ], [ 'http://foo.com', true ]]), ); }); - it('should accept the relation when the filter are not related to the relations', async () => { + it('should accept the relation when the filter are not related to the relations', async() => { const treeSubject = 'tree'; const node: INode = { @@ -800,11 +745,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(result.filters).toBeDefined(); expect(result.filters).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should accept the relation when there is multiples filters and one is not relevant', async () => { + it('should accept the relation when there is multiples filters and one is not relevant', async() => { const treeSubject = 'tree'; const node: INode = { @@ -858,7 +803,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, }, }, - ] + ], }, { expressionType: Algebra.expressionTypes.OPERATOR, @@ -885,8 +830,8 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, }, }, - ] - } + ], + }, ], }; const query = { @@ -914,11 +859,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(result.filters).toBeDefined(); expect(result.filters).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should accept the relation when the filter compare two constant', async () => { + it('should accept the relation when the filter compare two constant', async() => { const treeSubject = 'tree'; const node: INode = { @@ -977,7 +922,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, }, }, - ] + ], }, { expressionType: Algebra.expressionTypes.OPERATOR, @@ -1009,8 +954,8 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, }, }, - ] - } + ], + }, ], }; const query = { @@ -1038,7 +983,80 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(result.filters).toBeDefined(); expect(result.filters).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), + ); + }); + + it('should return an empty filter map if there is no bgp', async() => { + const treeSubject = 'tree'; + + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, + }, + ], + }; + const bgp: RDF.Quad[] = []; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + }, + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const action: IActionOptimizeLinkTraversal = { + context, + treeMetadata: node, + }; + const result = await actor.run(action); + + expect(result.filters).toBeDefined(); + expect(result.filters).toStrictEqual( + new Map(), ); }); }); From fc04a59270c8a99a2d0ed978d8a65ce6ed5caf05 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 8 Nov 2022 09:49:21 +0100 Subject: [PATCH 026/189] fix import metadataExtractor test --- .../test/treeMetadataExtraction-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts index 6e5706275..cf023303b 100644 --- a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts +++ b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts @@ -2,8 +2,8 @@ import type { IRelation } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { buildRelations, addRelationDescription, collectRelation } from '../lib/treeMetadataExtraction'; -import type { IRelationDescription } from '../lib/typeTreeMetadataExtraction'; -import { TreeNodes, RelationOperator } from '../lib/typeTreeMetadataExtraction'; +import type { IRelationDescription } from '@comunica/types-link-traversal'; +import { TreeNodes, RelationOperator } from '@comunica/types-link-traversal'; const DF = new DataFactory(); From 9cd62c2a1b780d1182c1229ab695717358962290 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 8 Nov 2022 09:50:12 +0100 Subject: [PATCH 027/189] lint-fix --- .../test/treeMetadataExtraction-test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts index cf023303b..c31089550 100644 --- a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts +++ b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts @@ -1,9 +1,8 @@ -import type { IRelation } from '@comunica/types-link-traversal'; +import type { IRelation, IRelationDescription } from '@comunica/types-link-traversal'; +import { TreeNodes, RelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { buildRelations, addRelationDescription, collectRelation } from '../lib/treeMetadataExtraction'; -import type { IRelationDescription } from '@comunica/types-link-traversal'; -import { TreeNodes, RelationOperator } from '@comunica/types-link-traversal'; const DF = new DataFactory(); From 200be0b485b96bb60bea41c655ef2616ec582e3e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 8 Nov 2022 10:05:43 +0100 Subject: [PATCH 028/189] documentation upgraded --- .../actor-optimize-link-traversal-filter-tree-links/README.md | 2 +- .../lib/ActorOptimizeLinkTraversal.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/README.md b/packages/actor-optimize-link-traversal-filter-tree-links/README.md index 172676c01..c212204ad 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/README.md +++ b/packages/actor-optimize-link-traversal-filter-tree-links/README.md @@ -22,7 +22,7 @@ After installing, this package can be added to your engine's configuration as fo { "@context": [ ... - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-optimize-link-traversal-filter-tree-links/^1.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-optimize-link-traversal-filter-tree-links/^0.0.1/components/context.jsonld" ], "actors": [ ... diff --git a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts index 011d8a7c3..04caceea2 100644 --- a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts +++ b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts @@ -5,9 +5,9 @@ import type { INode } from '@comunica/types-link-traversal'; * A comunica actor for optimization of link traversal * * Actor types: - * * Input: IActionOptimizeLinkTraversal: TODO: fill in. + * * Input: IActionOptimizeLinkTraversal: Metadata or relevant information for optimization. * * Test: - * * Output: IActorOptimizeLinkTraversalOutput: TODO: fill in. + * * Output: IActorOptimizeLinkTraversalOutput: Links prunning or reorder information. * * @see IActionOptimizeLinkTraversal * @see IActorOptimizeLinkTraversalOutput From 21aa6bbcc4683b5649459c85f39b17392f3b1e69 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 8 Nov 2022 10:21:20 +0100 Subject: [PATCH 029/189] some renaming of tests --- ...ActorOptimizeLinkTraversalFilterTreeLinks-test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts b/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts index 2b5dc931e..e80f2e9ed 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts @@ -24,7 +24,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); describe('test method', () => { const treeSubject = 'tree'; - it('should test when there is relations and a filter operation in the query', async() => { + it('should test when there are relations and a filter operation in the query', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -127,7 +127,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); }); - it('should no test when there no filter operation in the query and no TREE relation', async() => { + it('should no test when there is no filter operation in the query and no TREE relation', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); @@ -464,7 +464,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it('should accept the relation when there is multiple filters and the query don\'t match the relation', + it('should accept the relation when there is multiple filters and the query path don\'t match the relation', async() => { const treeSubject = 'tree'; @@ -583,7 +583,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); it(`should accept the relations when the filter respect the relation - and a relation doesn't specify a path and/or`, async() => { + and a relation doesn't specify a path`, async() => { const treeSubject = 'tree'; const node: INode = { @@ -668,7 +668,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it('should accept the relation when the filter are not related to the relations', async() => { + it('should accept the relation when the filter argument are not related to the query', async() => { const treeSubject = 'tree'; const node: INode = { @@ -863,7 +863,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it('should accept the relation when the filter compare two constant', async() => { + it('should accept the relation when the filter compare two constants', async() => { const treeSubject = 'tree'; const node: INode = { From 815da8a22f83993a7988f4f03d8c36c010172fa6 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 10 Nov 2022 08:21:28 +0100 Subject: [PATCH 030/189] better documentation --- .gitignore | 3 +- .../lib/ActorExtractLinksTree.ts | 26 ++++++----- .../lib/treeMetadataExtraction.ts | 18 +++++++- .../test/ActorExtractLinksTree-test.ts | 39 ++++++++++++++++ ...torOptimizeLinkTraversalFilterTreeLinks.ts | 44 +++++++++++++++---- .../package.json | 3 +- .../package.json | 1 - 7 files changed, 109 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 3872cdb00..f07e48754 100644 --- a/.gitignore +++ b/.gitignore @@ -23,5 +23,4 @@ earl.ttl web-clients/builds componentsjs-error-state.json engines/*/components -packages/*/components -engines/config-query-sparql-link-traversal/config/debug-config.json \ No newline at end of file +packages/*/components \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 947b7943f..e608ac8b1 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -17,7 +17,7 @@ import { buildRelations, collectRelation } from './treeMetadataExtraction'; export class ActorExtractLinksTree extends ActorExtractLinks { private readonly mediatorOptimizeLinkTraversal: MediatorOptimizeLinkTraversal; - public constructor(args: IActorExtractLinksTree) { + public constructor(args: IActorExtractLinksTreeArgs) { super(args); } @@ -46,7 +46,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { relationDescriptions)); // Resolve to discovered links - metadata.on('end', async() => { + metadata.on('end', () => { // Validate if the node forward have the current node as implicit subject for (const [ nodeValue, link ] of nodeLinks) { if (pageRelationNodes.has(nodeValue)) { @@ -60,15 +60,19 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } const node: INode = { relation: relations, subject: currentNodeUrl }; - const linkTraversalOptimisation = await this.mediatorOptimizeLinkTraversal.mediate( - { treeMetadata: node, context: action.context }, - ); let acceptedRelation = relations; - if (typeof linkTraversalOptimisation !== 'undefined') { - acceptedRelation = this.handleOptimization(linkTraversalOptimisation, relations); - } - - resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); + this.mediatorOptimizeLinkTraversal.mediate( + { treeMetadata: node, context: action.context }, + ) + .then(linkTraversalOptimisation => { + if (typeof linkTraversalOptimisation !== 'undefined') { + acceptedRelation = this.handleOptimization(linkTraversalOptimisation, relations); + } + resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); + }) + .catch(() => { + resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); + }); }); }); } @@ -111,7 +115,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } } -export interface IActorExtractLinksTree extends IActorExtractLinksArgs { +export interface IActorExtractLinksTreeArgs extends IActorExtractLinksArgs { /** * The optmize link traversal mediator */ diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts index 510be09ea..af65ce77a 100644 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -2,6 +2,13 @@ import type { IRelation, IRelationDescription } from '@comunica/types-link-trave import { RelationOperator, TreeNodes } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; +/** + * @param relationDescription + * @param nodeLinks + * @returns IRelation + * collect the relevant values and quad capture from a IRelationDescription object + * to create a IRelation object + */ export function collectRelation( relationDescription: IRelationDescription, nodeLinks: string, @@ -96,7 +103,16 @@ export function buildRelations( } } } - +/** + * @param relationDescriptions: Map + * @param quad: RDF.Quad + * @param value?: string + * @param subject?: string + * @param operator?: RelationOperator + * @param remainingItems?: number + * from a quad capture the TREE relation information and put it into + * a IRelationDescription map + */ export function addRelationDescription({ relationDescriptions, quad, diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 84c9696c4..99d976d3d 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -432,6 +432,45 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toEqual({ links: [{ url: secondExpectedLink }, { url: expectedUrl }]}); }); + + it('should return the links of a TREE with one relation and a filter than failled to return', async() => { + const expectedUrl = 'http://foo.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + ]); + const action = { url: treeUrl, metadata: input, requestTime: 0, context }; + + const mediationOutput: Promise = Promise.reject(new Error('failled request')); + const mediator: any = { + mediate(arg: any) { + return mediationOutput; + }, + }; + const spyMock = jest.spyOn(mediator, 'mediate'); + const actorWithCustomMediator = new ActorExtractLinksTree( + { name: 'actor', bus, mediatorOptimizeLinkTraversal: mediator }, + ); + const result = await actorWithCustomMediator.run(action); + + expect(result).toEqual({ links: [{ url: expectedUrl }]}); + expect(spyMock).toBeCalledTimes(1); + }); }); describe('The ActorExtractLinksExtractLinksTree test method', () => { diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 670e6b10c..7f251139d 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -9,14 +9,18 @@ import { KeysInitQuery } from '@comunica/context-entries'; import type { IActorTest } from '@comunica/core'; import type { Bindings } from '@comunica/types'; import type { IRelation } from '@comunica/types-link-traversal'; +import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; -import { stringToTerm } from 'rdf-string'; import { Algebra } from 'sparqlalgebrajs'; import { AsyncEvaluator } from 'sparqlee'; +const DF = new DataFactory(); + /** * A comunica Link traversal optimizer that filter link of document - * following the [TREE specification](https://treecg.github.io/specification/) + * following the [TREE specification](https://treecg.github.io/specification/). + * The actor apply the filter of a query into the TREE relation to determine if a + * link should be follow or not */ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLinkTraversal { @@ -42,18 +46,18 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink public async run(action: IActionOptimizeLinkTraversal): Promise { const filterMap: Map = new Map(); + // Extract the filter expression const filterOperation: Algebra.Expression = JSON.parse(JSON.stringify(action.context.get(KeysInitQuery.query))) .input.expression; + // Extract the bgp of the query const queryBody: RDF.Quad[] = this.findBgp(action.context.get(KeysInitQuery.query)!); if (queryBody.length === 0) { return { filters: filterMap }; } + // Capture the relation from the input const relations: IRelation[] = (() => { if (typeof action.treeMetadata !== 'undefined') { - // If the test pass the relation are defined, it's for the lint - /* istanbul ignore next */ - return typeof action.treeMetadata.relation !== 'undefined' ? action.treeMetadata.relation : []; - /* istanbul ignore next */ + return action.treeMetadata.relation!; } return []; })(); @@ -62,11 +66,14 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink filterMap.set(relation.node, true); continue; } + // Find the quad from the bgp that are related to the TREE relation const relevantQuads = this.findRelavantQuad(queryBody, relation.path.value); if (relevantQuads.length === 0) { filterMap.set(relation.node, true); continue; } + + // Create the binding in relation to the relevant quad const bindings = this.createBinding(relevantQuads, relation.value.quad); const filterExpression: Algebra.Operation = this.deleteUnrelevantFilter(filterOperation, bindings); if (filterExpression.args.length === 0) { @@ -74,12 +81,20 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink continue; } const evaluator = new AsyncEvaluator(filterExpression); + // Evaluate the filter with the relevant quad binding const result: boolean = await evaluator.evaluateAsEBV(bindings); filterMap.set(relation.node, result); } return { filters: filterMap }; } + /** + * + * @param queryBody + * @param path + * @returns RDF.Quad[] + * find the quad that has as predicate a the TREE:path of a relation + */ private findRelavantQuad(queryBody: RDF.Quad[], path: string): RDF.Quad[] { const resp: RDF.Quad[] = []; for (const quad of queryBody) { @@ -90,6 +105,12 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink return resp; } + /** + * @param filterExpression + * @param binding + * @returns Algebra.Expression + * delete the filters that are not related to TREE relation + */ private deleteUnrelevantFilter(filterExpression: Algebra.Expression, binding: Bindings): Algebra.Expression { if ('operator' in filterExpression.args[0]) { filterExpression.args = (filterExpression.args).filter(expression => { @@ -114,13 +135,18 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink return filterExpression; } + /** + * + * @param relevantQuad + * @param relationValue + * @returns Bindings + * create the binding from quad related to the TREE:path + */ private createBinding(relevantQuad: RDF.Quad[], relationValue: RDF.Quad): Bindings { let binding: Bindings = new BindingsFactory().bindings(); for (const quad of relevantQuad) { const object = quad.object.value; - const value = - `"${relationValue.object.value}"^^${(relationValue.object).datatype.value}`; - binding = binding.set(object, stringToTerm(value)); + binding = binding.set(object, relationValue.object); } return binding; } diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/package.json b/packages/actor-optimize-link-traversal-filter-tree-links/package.json index 0f45b5b70..01a1b28d5 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/package.json +++ b/packages/actor-optimize-link-traversal-filter-tree-links/package.json @@ -37,7 +37,8 @@ "@comunica/context-entries":"^2.4.0", "sparqlalgebrajs": "^4.0.0", "rdf-string":"^1.6.0", - "sparqlee": "^2.1.0" + "sparqlee": "^2.1.0", + "rdf-data-factory": "^1.1.1" }, "scripts": { diff --git a/packages/context-entries-link-traversal/package.json b/packages/context-entries-link-traversal/package.json index 2f561a663..b52d9a056 100644 --- a/packages/context-entries-link-traversal/package.json +++ b/packages/context-entries-link-traversal/package.json @@ -2,7 +2,6 @@ "name": "@comunica/context-entries-link-traversal", "version": "0.0.1", "description": "A collection of reusable Comunica context key definitions for link traversal.", - "lsd:module": true, "main": "lib/index.js", "typings": "lib/index", "repository": { From 9c5a29dba048038c3270460c94b5c85148f8ddee Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 10 Nov 2022 08:32:30 +0100 Subject: [PATCH 031/189] hard copy of filter operation deleted --- .../lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 7f251139d..2df04acf6 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -47,8 +47,11 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink public async run(action: IActionOptimizeLinkTraversal): Promise { const filterMap: Map = new Map(); // Extract the filter expression - const filterOperation: Algebra.Expression = JSON.parse(JSON.stringify(action.context.get(KeysInitQuery.query))) - .input.expression; + const filterOperation: Algebra.Expression = (() => { + const query: Algebra.Operation = action.context.get(KeysInitQuery.query)!; + return query.input.expression; + })(); + // Extract the bgp of the query const queryBody: RDF.Quad[] = this.findBgp(action.context.get(KeysInitQuery.query)!); if (queryBody.length === 0) { From 3e48b627e212e444bc04693f17228f86f7e97ed1 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 10 Nov 2022 09:18:27 +0100 Subject: [PATCH 032/189] deep copy reimplemented with lodash --- .../ActorOptimizeLinkTraversalFilterTreeLinks.ts | 3 ++- .../package.json | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 2df04acf6..58a7c439d 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -13,6 +13,7 @@ import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; import { AsyncEvaluator } from 'sparqlee'; +const clonedeep = require('lodash.clonedeep'); const DF = new DataFactory(); @@ -48,7 +49,7 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink const filterMap: Map = new Map(); // Extract the filter expression const filterOperation: Algebra.Expression = (() => { - const query: Algebra.Operation = action.context.get(KeysInitQuery.query)!; + const query: Algebra.Operation = clonedeep(action.context.get(KeysInitQuery.query)!); return query.input.expression; })(); diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/package.json b/packages/actor-optimize-link-traversal-filter-tree-links/package.json index 01a1b28d5..cb7077f28 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/package.json +++ b/packages/actor-optimize-link-traversal-filter-tree-links/package.json @@ -31,15 +31,15 @@ "lib/**/*.js" ], "dependencies": { - "@comunica/core": "^2.4.0", + "@comunica/bindings-factory": "^2.2.0", "@comunica/bus-optimize-link-traversal": "^0.0.1", - "@comunica/bindings-factory":"^2.2.0", - "@comunica/context-entries":"^2.4.0", + "@comunica/context-entries": "^2.4.0", + "@comunica/core": "^2.4.0", + "lodash.clonedeep": "^4.17.21", + "rdf-data-factory": "^1.1.1", + "rdf-string": "^1.6.0", "sparqlalgebrajs": "^4.0.0", - "rdf-string":"^1.6.0", - "sparqlee": "^2.1.0", - "rdf-data-factory": "^1.1.1" - + "sparqlee": "^2.1.0" }, "scripts": { "build": "npm run build:ts && npm run build:components", From 2329cb5680093ac64f9516cf733df7a82f4d2d87 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 10 Nov 2022 10:41:00 +0100 Subject: [PATCH 033/189] deep cloning of the filter expression is not necessary anymore in the TREE filter --- ...ActorOptimizeLinkTraversalFilterTreeLinks.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 2df04acf6..6b5ec6828 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -115,8 +115,14 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink * delete the filters that are not related to TREE relation */ private deleteUnrelevantFilter(filterExpression: Algebra.Expression, binding: Bindings): Algebra.Expression { + let newFilterExpression: Algebra.Expression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: filterExpression.operator, + type: Algebra.types.EXPRESSION, + args: [], + }; if ('operator' in filterExpression.args[0]) { - filterExpression.args = (filterExpression.args).filter(expression => { + newFilterExpression.args = (filterExpression.args).filter(expression => { for (const arg of expression.args) { if ('term' in arg && arg.term.termType === 'Variable') { return binding.has(arg.term.value); @@ -124,18 +130,19 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink } return false; }); - if (filterExpression.args.length === 1) { - filterExpression = filterExpression.args[0]; + if (newFilterExpression.args.length === 1) { + newFilterExpression = newFilterExpression.args[0]; } } else { for (const arg of (filterExpression.args)) { if ('term' in arg && arg.term.termType === 'Variable' && !binding.has(arg.term.value)) { - filterExpression.args = []; + newFilterExpression.args = []; break; } + newFilterExpression.args.push(arg); } } - return filterExpression; + return newFilterExpression; } /** From 6db03d24aa34fd1c42f3198096006e562d9dd67f Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 10 Nov 2022 10:48:21 +0100 Subject: [PATCH 034/189] lodash depency deleted --- .../actor-optimize-link-traversal-filter-tree-links/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/package.json b/packages/actor-optimize-link-traversal-filter-tree-links/package.json index cb7077f28..424a37c6f 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/package.json +++ b/packages/actor-optimize-link-traversal-filter-tree-links/package.json @@ -35,7 +35,6 @@ "@comunica/bus-optimize-link-traversal": "^0.0.1", "@comunica/context-entries": "^2.4.0", "@comunica/core": "^2.4.0", - "lodash.clonedeep": "^4.17.21", "rdf-data-factory": "^1.1.1", "rdf-string": "^1.6.0", "sparqlalgebrajs": "^4.0.0", From afce916a4c955bf2be5a19dea111a42f75de4918 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 10 Nov 2022 11:02:35 +0100 Subject: [PATCH 035/189] first --- .../config/debug-config.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 engines/config-query-sparql-link-traversal/config/debug-config.json diff --git a/engines/config-query-sparql-link-traversal/config/debug-config.json b/engines/config-query-sparql-link-traversal/config/debug-config.json new file mode 100644 index 000000000..3a8194b35 --- /dev/null +++ b/engines/config-query-sparql-link-traversal/config/debug-config.json @@ -0,0 +1,14 @@ +{ + "@context": [ + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql/^2.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql-link-traversal/^0.0.0/components/context.jsonld" + ], + "import": [ + "ccqslt:config/config-base.json", + "ccqslt:config/rdf-resolve-hypermedia-links/actors/traverse-replace-conditional.json", + "ccqslt:config/rdf-resolve-hypermedia-links-queue/actors/wrapper-limit-count.json", + "ccqslt:config/optimize-link-traversal/mediators.json", + "ccqslt:config/optimize-link-traversal/actors/filter-tree.json", + "ccqslt:config/extract-links/actors/tree.json" + ] +} \ No newline at end of file From 6c9acfb79e41983da001cd016d7f8c104ac60ad6 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 10 Nov 2022 11:03:02 +0100 Subject: [PATCH 036/189] first --- .../config/debug-config.json | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 engines/config-query-sparql-link-traversal/config/debug-config.json diff --git a/engines/config-query-sparql-link-traversal/config/debug-config.json b/engines/config-query-sparql-link-traversal/config/debug-config.json deleted file mode 100644 index 3a8194b35..000000000 --- a/engines/config-query-sparql-link-traversal/config/debug-config.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql/^2.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql-link-traversal/^0.0.0/components/context.jsonld" - ], - "import": [ - "ccqslt:config/config-base.json", - "ccqslt:config/rdf-resolve-hypermedia-links/actors/traverse-replace-conditional.json", - "ccqslt:config/rdf-resolve-hypermedia-links-queue/actors/wrapper-limit-count.json", - "ccqslt:config/optimize-link-traversal/mediators.json", - "ccqslt:config/optimize-link-traversal/actors/filter-tree.json", - "ccqslt:config/extract-links/actors/tree.json" - ] -} \ No newline at end of file From 512963f071673f1b0a9497ec5716670560d04c12 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 10 Nov 2022 11:12:11 +0100 Subject: [PATCH 037/189] deep copy for filter expression deleted --- .../lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 011e9d671..6b5ec6828 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -13,7 +13,6 @@ import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; import { AsyncEvaluator } from 'sparqlee'; -const clonedeep = require('lodash.clonedeep'); const DF = new DataFactory(); @@ -49,7 +48,7 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink const filterMap: Map = new Map(); // Extract the filter expression const filterOperation: Algebra.Expression = (() => { - const query: Algebra.Operation = clonedeep(action.context.get(KeysInitQuery.query)!); + const query: Algebra.Operation = action.context.get(KeysInitQuery.query)!; return query.input.expression; })(); From 369afeac965ce2097f1bfaef49fb8faa641a03db Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 14 Nov 2022 08:18:56 +0100 Subject: [PATCH 038/189] now it reject the TREE extractor actor when the optimize link traversal actor return a faulty promise --- .../lib/ActorExtractLinksTree.ts | 4 ++-- .../test/ActorExtractLinksTree-test.ts | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index e608ac8b1..80ad9cc71 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -70,8 +70,8 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); }) - .catch(() => { - resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); + .catch(error => { + reject(error); }); }); }); diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 99d976d3d..f7b81d76f 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -433,7 +433,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toEqual({ links: [{ url: secondExpectedLink }, { url: expectedUrl }]}); }); - it('should return the links of a TREE with one relation and a filter than failled to return', async() => { + it('should fail when the mediator return a fail promise', () => { const expectedUrl = 'http://foo.com'; const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), @@ -458,7 +458,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const mediationOutput: Promise = Promise.reject(new Error('failled request')); const mediator: any = { - mediate(arg: any) { + mediate(_arg: any) { return mediationOutput; }, }; @@ -466,10 +466,11 @@ describe('ActorExtractLinksExtractLinksTree', () => { const actorWithCustomMediator = new ActorExtractLinksTree( { name: 'actor', bus, mediatorOptimizeLinkTraversal: mediator }, ); - const result = await actorWithCustomMediator.run(action); - - expect(result).toEqual({ links: [{ url: expectedUrl }]}); - expect(spyMock).toBeCalledTimes(1); + actorWithCustomMediator.run(action).then(res => { + expect(res).toBeUndefined(); + }).catch(error => { + expect(error).toBeDefined(); + }); }); }); From 0641ce87da52561dbbf845e0adbaa8fe4798c77e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 14 Nov 2022 09:21:49 +0100 Subject: [PATCH 039/189] simplyfication of the handling of undefined values --- .../lib/ActorExtractLinksTree.ts | 2 +- .../lib/treeMetadataExtraction.ts | 75 +++++-------------- .../test/ActorExtractLinksTree-test.ts | 7 +- .../test/treeMetadataExtraction-test.ts | 61 +-------------- ...torOptimizeLinkTraversalFilterTreeLinks.ts | 35 +++++---- .../types-link-traversal/lib/TreeMetadata.ts | 6 +- 6 files changed, 48 insertions(+), 138 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 80ad9cc71..2e425ee4b 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -79,7 +79,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { private handleOptimization(linkTraversalOptimisation: IActorOptimizeLinkTraversalOutput, relations: IRelation[]): IRelation[] { - if (typeof linkTraversalOptimisation.filters !== 'undefined') { + if (linkTraversalOptimisation.filters) { return linkTraversalOptimisation.filters.size > 0 ? relations.filter(relation => linkTraversalOptimisation.filters?.get(relation.node)) : relations; diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts index af65ce77a..579808a51 100644 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -14,58 +14,30 @@ export function collectRelation( nodeLinks: string, ): IRelation { const relation: IRelation = { node: nodeLinks }; - const typeRelation = (() => { - if (typeof relationDescription.operator !== 'undefined') { - return typeof relationDescription.operator[0] !== 'undefined' ? relationDescription.operator[0] : undefined; - } - })(); - - if (typeof typeRelation !== 'undefined' && typeof relationDescription.operator !== 'undefined') { + if (relationDescription?.operator) { relation['@type'] = { - value: typeRelation, + value: relationDescription.operator[0], quad: relationDescription.operator[1], }; } - const remainingItems = ( - () => { - if (typeof relationDescription.remainingItems !== 'undefined') { - return typeof relationDescription.remainingItems[0] !== 'undefined' ? - relationDescription.remainingItems[0] : - undefined; - } - } - )(); - if (typeof remainingItems !== 'undefined' && typeof relationDescription.remainingItems !== 'undefined') { + + if (relationDescription?.remainingItems) { relation.remainingItems = { - value: remainingItems, + value: relationDescription.remainingItems[0], quad: relationDescription.remainingItems[1], }; } - const path = (() => { - if (typeof relationDescription.subject !== 'undefined') { - return typeof relationDescription.subject[0] !== 'undefined' ? - relationDescription.subject[0] : - undefined; - } - })(); - if (typeof path !== 'undefined' && typeof relationDescription.subject !== 'undefined') { + if (relationDescription?.subject) { relation.path = { - value: path, + value: relationDescription.subject[0], quad: relationDescription.subject[1], }; } - const value = (() => { - if (typeof relationDescription.value !== 'undefined') { - return typeof relationDescription.value[0] !== 'undefined' ? - relationDescription.value[0] : - undefined; - } - })(); - if (typeof value !== 'undefined' && typeof relationDescription.value !== 'undefined') { + if (relationDescription?.value) { relation.value = { - value, + value: relationDescription.value[0], quad: relationDescription.value[1], }; } @@ -128,24 +100,17 @@ export function addRelationDescription({ operator?: RelationOperator; remainingItems?: number; }): void { - const currentDescription: IRelationDescription | undefined = relationDescriptions.get(quad.subject.value); - if (typeof currentDescription === 'undefined') { - relationDescriptions.set(quad.subject.value, { - value: typeof value !== 'undefined' ? [ value, quad ] : undefined, - subject: typeof subject !== 'undefined' ? [ subject, quad ] : undefined, - operator: typeof operator !== 'undefined' ? [ operator, quad ] : undefined, - remainingItems: typeof remainingItems !== 'undefined' ? [ remainingItems, quad ] : undefined, - }); - } else { - /* eslint-disable prefer-rest-params */ - const newDescription: IRelationDescription = currentDescription; - const objectArgument = arguments[0]; - for (const [ arg, val ] of Object.entries(objectArgument)) { - if (typeof val !== 'undefined' && arg !== 'relationDescriptions' && arg !== 'quad') { - newDescription[arg] = [ val, quad ]; - break; - } + const newDescription: IRelationDescription = typeof relationDescriptions?.get(quad.subject.value) !== 'undefined' ? + relationDescriptions.get(quad.subject.value)! : + {}; + /* eslint-disable prefer-rest-params */ + const objectArgument = arguments[0]; + for (const [ arg, val ] of Object.entries(objectArgument)) { + if (val && arg !== 'relationDescriptions' && arg !== 'quad') { + newDescription[arg] = [ val, quad ]; + break; } - /* eslint-enable prefer-rest-params */ } + /* eslint-enable prefer-rest-params */ + relationDescriptions.set(quad.subject.value, newDescription); } diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index f7b81d76f..6bbdc766b 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -433,7 +433,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toEqual({ links: [{ url: secondExpectedLink }, { url: expectedUrl }]}); }); - it('should fail when the mediator return a fail promise', () => { + it('should fail when the mediator return a fail promise', async() => { const expectedUrl = 'http://foo.com'; const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), @@ -462,15 +462,18 @@ describe('ActorExtractLinksExtractLinksTree', () => { return mediationOutput; }, }; + const spyMock = jest.spyOn(mediator, 'mediate'); const actorWithCustomMediator = new ActorExtractLinksTree( { name: 'actor', bus, mediatorOptimizeLinkTraversal: mediator }, ); - actorWithCustomMediator.run(action).then(res => { + await actorWithCustomMediator.run(action).then(res => { expect(res).toBeUndefined(); }).catch(error => { expect(error).toBeDefined(); }); + + expect(spyMock).toBeCalledTimes(1); }); }); diff --git a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts index c31089550..1af1e8898 100644 --- a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts +++ b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts @@ -1,8 +1,8 @@ -import type { IRelation, IRelationDescription } from '@comunica/types-link-traversal'; +import type { IRelationDescription } from '@comunica/types-link-traversal'; import { TreeNodes, RelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; -import { buildRelations, addRelationDescription, collectRelation } from '../lib/treeMetadataExtraction'; +import { buildRelations, addRelationDescription } from '../lib/treeMetadataExtraction'; const DF = new DataFactory(); @@ -214,61 +214,4 @@ describe('treeMetadataExtraction', () => { expect(relationDescriptions.size).toBe(0); }); }); - - describe('collectRelation with undefined description', () => { - const aQuad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.namedNode('ex:o'), - DF.namedNode('ex:gx')); - const nodeLink = 'http://aLink.com'; - - it('should not have the @type field when the operator field is defined but it\'s value is undefined', () => { - const relationDescription: IRelationDescription = { - operator: [ undefined, aQuad ], - }; - const expectedRelation: IRelation = { - node: nodeLink, - }; - const relation = collectRelation(relationDescription, nodeLink); - - expect(relation).toStrictEqual(expectedRelation); - }); - - it('should not have the remainingItems field when the operator field is defined but it\'s value is undefined', - () => { - const relationDescription: IRelationDescription = { - remainingItems: [ undefined, aQuad ], - }; - const expectedRelation: IRelation = { - node: nodeLink, - }; - const relation = collectRelation(relationDescription, nodeLink); - - expect(relation).toStrictEqual(expectedRelation); - }); - - it('should not have the path field when the operator field is defined but it\'s value is undefined', () => { - const relationDescription: IRelationDescription = { - subject: [ undefined, aQuad ], - }; - const expectedRelation: IRelation = { - node: nodeLink, - }; - const relation = collectRelation(relationDescription, nodeLink); - - expect(relation).toStrictEqual(expectedRelation); - }); - - it('should not have the value field when the operator field is defined but it\'s value is undefined', () => { - const relationDescription: IRelationDescription = { - value: [ undefined, aQuad ], - }; - const expectedRelation: IRelation = { - node: nodeLink, - }; - const relation = collectRelation(relationDescription, nodeLink); - - expect(relation).toStrictEqual(expectedRelation); - }); - }); }); diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 6b5ec6828..4ece82765 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -29,19 +29,20 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink } public async test(action: IActionOptimizeLinkTraversal): Promise { + if (!action.treeMetadata) { + return Promise.reject(new Error('TREE metadata is not defined')); + } + + if (!action.treeMetadata.relation) { + return Promise.reject(new Error('There is no relation into the node')); + } + const query: Algebra.Operation = action.context.get(KeysInitQuery.query)!; - const relations: IRelation[] = (() => { - if (typeof action.treeMetadata !== 'undefined') { - return typeof action.treeMetadata.relation !== 'undefined' ? action.treeMetadata.relation : []; - } - return []; - })(); - const filterExist: boolean = this.doesNodeExist(query, Algebra.types.FILTER); - return filterExist && relations.length > 0 ? - Promise.resolve(true) : - Promise.reject(new Error( - 'the action must contain TREE relation and the query must contain at least a filter', - )); + if (!this.doesNodeExist(query, Algebra.types.FILTER)) { + return Promise.reject(new Error('there is no filter defined into the query')); + } + + return Promise.resolve(true); } public async run(action: IActionOptimizeLinkTraversal): Promise { @@ -58,12 +59,10 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink return { filters: filterMap }; } // Capture the relation from the input - const relations: IRelation[] = (() => { - if (typeof action.treeMetadata !== 'undefined') { - return action.treeMetadata.relation!; - } - return []; - })(); + const relations: IRelation[] = typeof action?.treeMetadata?.relation !== 'undefined' ? + action.treeMetadata.relation : + []; + for (const relation of relations) { if (typeof relation.path === 'undefined' || typeof relation.value === 'undefined') { filterMap.set(relation.node, true); diff --git a/packages/types-link-traversal/lib/TreeMetadata.ts b/packages/types-link-traversal/lib/TreeMetadata.ts index febe9639e..ffa7b8010 100644 --- a/packages/types-link-traversal/lib/TreeMetadata.ts +++ b/packages/types-link-traversal/lib/TreeMetadata.ts @@ -53,8 +53,8 @@ export interface IRelation { // An helper to build the relation from a stream export interface IRelationDescription { - subject?: [string | undefined, RDF.Quad]; + subject?: [string, RDF.Quad]; value?: any; - operator?: [RelationOperator | undefined, RDF.Quad]; - remainingItems?: [number | undefined, RDF.Quad]; + operator?: [RelationOperator, RDF.Quad]; + remainingItems?: [number, RDF.Quad]; } From ae21b4f77e0db7dfbe1f8fb15dc33d188e447206 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 14 Nov 2022 09:38:37 +0100 Subject: [PATCH 040/189] useless dependecies deleted --- .../lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts index 4ece82765..ac457da8a 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts @@ -9,13 +9,11 @@ import { KeysInitQuery } from '@comunica/context-entries'; import type { IActorTest } from '@comunica/core'; import type { Bindings } from '@comunica/types'; import type { IRelation } from '@comunica/types-link-traversal'; -import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; import { AsyncEvaluator } from 'sparqlee'; -const DF = new DataFactory(); - +const BF = new BindingsFactory(); /** * A comunica Link traversal optimizer that filter link of document * following the [TREE specification](https://treecg.github.io/specification/). @@ -120,6 +118,7 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink type: Algebra.types.EXPRESSION, args: [], }; + if ('operator' in filterExpression.args[0]) { newFilterExpression.args = (filterExpression.args).filter(expression => { for (const arg of expression.args) { @@ -152,7 +151,7 @@ export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLink * create the binding from quad related to the TREE:path */ private createBinding(relevantQuad: RDF.Quad[], relationValue: RDF.Quad): Bindings { - let binding: Bindings = new BindingsFactory().bindings(); + let binding: Bindings = BF.bindings(); for (const quad of relevantQuad) { const object = quad.object.value; binding = binding.set(object, relationValue.object); From 39c9f329cb7e4eb3be6a3009fd83d774cf6a7177 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 14 Nov 2022 10:59:16 +0100 Subject: [PATCH 041/189] filter actor translated into a class --- .../lib/ActorExtractLinksTree.ts | 32 +-- .../lib/FilterNode.ts | 192 ++++++++++++++++++ .../test/ActorExtractLinksTree-test.ts | 20 +- 3 files changed, 206 insertions(+), 38 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/lib/FilterNode.ts diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 2e425ee4b..e731ca2fc 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -10,14 +10,14 @@ import type { IRelationDescription, IRelation, INode } from '@comunica/types-lin import { TreeNodes } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { buildRelations, collectRelation } from './treeMetadataExtraction'; +import {FilterNode} from './FilterNode'; /** * A comunica Extract Links Tree Extract Links Actor. */ export class ActorExtractLinksTree extends ActorExtractLinks { - private readonly mediatorOptimizeLinkTraversal: MediatorOptimizeLinkTraversal; - public constructor(args: IActorExtractLinksTreeArgs) { + public constructor(args: IActorExtractLinksArgs) { super(args); } @@ -61,13 +61,10 @@ export class ActorExtractLinksTree extends ActorExtractLinks { const node: INode = { relation: relations, subject: currentNodeUrl }; let acceptedRelation = relations; - this.mediatorOptimizeLinkTraversal.mediate( - { treeMetadata: node, context: action.context }, - ) - .then(linkTraversalOptimisation => { - if (typeof linkTraversalOptimisation !== 'undefined') { - acceptedRelation = this.handleOptimization(linkTraversalOptimisation, relations); - } + + FilterNode.run(node, action.context) + .then(filters => { + acceptedRelation = this.handleOptimization(filters, relations); resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); }) .catch(error => { @@ -77,14 +74,11 @@ export class ActorExtractLinksTree extends ActorExtractLinks { }); } - private handleOptimization(linkTraversalOptimisation: IActorOptimizeLinkTraversalOutput, + private handleOptimization(filters: Map, relations: IRelation[]): IRelation[] { - if (linkTraversalOptimisation.filters) { - return linkTraversalOptimisation.filters.size > 0 ? - relations.filter(relation => linkTraversalOptimisation.filters?.get(relation.node)) : + return filters.size > 0 ? + relations.filter(relation => filters?.get(relation.node)) : relations; - } - return relations; } /** @@ -114,11 +108,3 @@ export class ActorExtractLinksTree extends ActorExtractLinks { buildRelations(relationDescriptions, quad); } } - -export interface IActorExtractLinksTreeArgs extends IActorExtractLinksArgs { - /** - * The optmize link traversal mediator - */ - mediatorOptimizeLinkTraversal: MediatorOptimizeLinkTraversal; -} - diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts new file mode 100644 index 000000000..1c110023c --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -0,0 +1,192 @@ +import { BindingsFactory } from '@comunica/bindings-factory'; +import { KeysInitQuery } from '@comunica/context-entries'; +import type { Bindings } from '@comunica/types'; +import type { IRelation } from '@comunica/types-link-traversal'; +import type * as RDF from 'rdf-js'; +import { Algebra } from 'sparqlalgebrajs'; +import { AsyncEvaluator } from 'sparqlee'; +import type { IActionContext } from '@comunica/types'; +import type { INode } from '@comunica/types-link-traversal'; + + +const BF = new BindingsFactory(); +/** + * A comunica Link traversal optimizer that filter link of document + * following the [TREE specification](https://treecg.github.io/specification/). + * The actor apply the filter of a query into the TREE relation to determine if a + * link should be follow or not + */ +export class FilterNode { + + private static test(node: INode, context:IActionContext): boolean { + + if (!node.relation) { + return false + } + + const query: Algebra.Operation = context.get(KeysInitQuery.query)!; + if (!this.doesNodeExist(query, Algebra.types.FILTER)) { + return false + } + + return true + } + + public static async run(node: INode, context: IActionContext): Promise> { + const filterMap: Map = new Map(); + if (this.test(node, context)){ + return filterMap + } + // Extract the filter expression + const filterOperation: Algebra.Expression = (() => { + const query: Algebra.Operation = context.get(KeysInitQuery.query)!; + return query.input.expression; + })(); + + // Extract the bgp of the query + const queryBody: RDF.Quad[] = this.findBgp(context.get(KeysInitQuery.query)!); + if (queryBody.length === 0) { + return filterMap ; + } + // Capture the relation from the input + const relations: IRelation[] = typeof node.relation !== 'undefined' ? + node.relation : + []; + + for (const relation of relations) { + if (typeof relation.path === 'undefined' || typeof relation.value === 'undefined') { + filterMap.set(relation.node, true); + continue; + } + // Find the quad from the bgp that are related to the TREE relation + const relevantQuads = this.findRelavantQuad(queryBody, relation.path.value); + if (relevantQuads.length === 0) { + filterMap.set(relation.node, true); + continue; + } + + // Create the binding in relation to the relevant quad + const bindings = this.createBinding(relevantQuads, relation.value.quad); + const filterExpression: Algebra.Operation = this.deleteUnrelevantFilter(filterOperation, bindings); + if (filterExpression.args.length === 0) { + filterMap.set(relation.node, true); + continue; + } + const evaluator = new AsyncEvaluator(filterExpression); + // Evaluate the filter with the relevant quad binding + const result: boolean = await evaluator.evaluateAsEBV(bindings); + filterMap.set(relation.node, result); + } + return filterMap; + } + + /** + * + * @param queryBody + * @param path + * @returns RDF.Quad[] + * find the quad that has as predicate a the TREE:path of a relation + */ + private static findRelavantQuad(queryBody: RDF.Quad[], path: string): RDF.Quad[] { + const resp: RDF.Quad[] = []; + for (const quad of queryBody) { + if (quad.predicate.value === path && quad.object.termType === 'Variable') { + resp.push(quad); + } + } + return resp; + } + + /** + * @param filterExpression + * @param binding + * @returns Algebra.Expression + * delete the filters that are not related to TREE relation + */ + private static deleteUnrelevantFilter(filterExpression: Algebra.Expression, binding: Bindings): Algebra.Expression { + let newFilterExpression: Algebra.Expression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: filterExpression.operator, + type: Algebra.types.EXPRESSION, + args: [], + }; + + if ('operator' in filterExpression.args[0]) { + newFilterExpression.args = (filterExpression.args).filter(expression => { + for (const arg of expression.args) { + if ('term' in arg && arg.term.termType === 'Variable') { + return binding.has(arg.term.value); + } + } + return false; + }); + if (newFilterExpression.args.length === 1) { + newFilterExpression = newFilterExpression.args[0]; + } + } else { + for (const arg of (filterExpression.args)) { + if ('term' in arg && arg.term.termType === 'Variable' && !binding.has(arg.term.value)) { + newFilterExpression.args = []; + break; + } + newFilterExpression.args.push(arg); + } + } + return newFilterExpression; + } + + /** + * + * @param relevantQuad + * @param relationValue + * @returns Bindings + * create the binding from quad related to the TREE:path + */ + private static createBinding(relevantQuad: RDF.Quad[], relationValue: RDF.Quad): Bindings { + let binding: Bindings = BF.bindings(); + for (const quad of relevantQuad) { + const object = quad.object.value; + binding = binding.set(object, relationValue.object); + } + return binding; + } + + private static findBgp(query: Algebra.Operation): RDF.Quad[] { + let currentNode = query.input; + do { + if (currentNode.type === 'join') { + return this.formatBgp(currentNode.input); + } + if ('input' in currentNode) { + currentNode = currentNode.input; + } + } while ('input' in currentNode); + return []; + } + + private static formatBgp(joins: any): RDF.Quad[] { + const bgp: RDF.Quad[] = []; + if (!('input' in joins[0])) { + return joins; + } + for (const join of joins) { + bgp.push(join.input[0]); + } + return bgp; + } + + private static doesNodeExist(query: Algebra.Operation, node: string): boolean { + let currentNode = query; + do { + if (currentNode.type === node) { + return true; + } + /* istanbul ignore next */ + if ('input' in currentNode) { + currentNode = currentNode.input; + } + /* istanbul ignore next */ + } while ('input' in currentNode); + return false; + } +} diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 6bbdc766b..0288e3a52 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -17,16 +17,6 @@ describe('ActorExtractLinksExtractLinksTree', () => { let spyMockMediator: any; beforeEach(() => { - mockMediator = { - mediate(arg: any) { - return Promise.resolve( - { - filters: > new Map(), - }, - ); - }, - }; - spyMockMediator = jest.spyOn(mockMediator, 'mediate'); bus = new Bus({ name: 'bus' }); }); @@ -55,7 +45,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { }); beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', bus, mediatorOptimizeLinkTraversal: mockMediator }); + actor = new ActorExtractLinksTree({ name: 'actor', bus }); }); it('should return the links of a TREE with one relation', async() => { @@ -324,7 +314,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { }; const spyMock = jest.spyOn(mediator, 'mediate'); const actorWithCustomMediator = new ActorExtractLinksTree( - { name: 'actor', bus, mediatorOptimizeLinkTraversal: mediator }, + { name: 'actor', bus}, ); const result = await actorWithCustomMediator.run(action); @@ -422,7 +412,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { }; const spyMock = jest.spyOn(mediator, 'mediate'); const actorWithCustomMediator = new ActorExtractLinksTree( - { name: 'actor', bus, mediatorOptimizeLinkTraversal: mediator }, + { name: 'actor', bus }, ); const result = await actorWithCustomMediator.run(action); @@ -465,7 +455,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const spyMock = jest.spyOn(mediator, 'mediate'); const actorWithCustomMediator = new ActorExtractLinksTree( - { name: 'actor', bus, mediatorOptimizeLinkTraversal: mediator }, + { name: 'actor', bus }, ); await actorWithCustomMediator.run(action).then(res => { expect(res).toBeUndefined(); @@ -482,7 +472,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const treeUrl = 'ex:s'; beforeEach(() => { - actor = new ActorExtractLinksTree({ name: 'actor', bus, mediatorOptimizeLinkTraversal: mockMediator }); + actor = new ActorExtractLinksTree({ name: 'actor', bus }); }); it('should test when giving a TREE', async() => { From a2d1e4cff9b870e76a6d80f70a6aaced794bfe2a Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 14 Nov 2022 14:53:02 +0100 Subject: [PATCH 042/189] bus-optmize-link-traversal deleted and the filter actor converted into a class --- .../config/config-default.json | 4 +- .../config/extract-links/actors/tree.json | 6 +- .../actors/filter-tree.json | 14 - .../optimize-link-traversal/mediators.json | 9 - .../query-sparql-link-traversal/package.json | 3 +- .../lib/ActorExtractLinksTree.ts | 36 +-- .../lib/FilterNode.ts | 52 ++-- .../package.json | 4 +- .../test/ActorExtractLinksTree-test.ts | 177 +----------- .../test/FilterNode-test.ts} | 269 ++++++++---------- .../.npmignore | 0 .../README.md | 41 --- ...torOptimizeLinkTraversalFilterTreeLinks.ts | 201 ------------- .../lib/index.ts | 1 - .../package.json | 48 ---- .../bus-optimize-link-traversal/.npmignore | 0 .../bus-optimize-link-traversal/README.md | 27 -- .../lib/ActorOptimizeLinkTraversal.ts | 41 --- .../bus-optimize-link-traversal/lib/index.ts | 1 - .../bus-optimize-link-traversal/package.json | 40 --- 20 files changed, 184 insertions(+), 790 deletions(-) delete mode 100644 engines/config-query-sparql-link-traversal/config/optimize-link-traversal/actors/filter-tree.json delete mode 100644 engines/config-query-sparql-link-traversal/config/optimize-link-traversal/mediators.json rename packages/{actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts => actor-extract-links-extract-tree/test/FilterNode-test.ts} (85%) delete mode 100644 packages/actor-optimize-link-traversal-filter-tree-links/.npmignore delete mode 100644 packages/actor-optimize-link-traversal-filter-tree-links/README.md delete mode 100644 packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts delete mode 100644 packages/actor-optimize-link-traversal-filter-tree-links/lib/index.ts delete mode 100644 packages/actor-optimize-link-traversal-filter-tree-links/package.json delete mode 100644 packages/bus-optimize-link-traversal/.npmignore delete mode 100644 packages/bus-optimize-link-traversal/README.md delete mode 100644 packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts delete mode 100644 packages/bus-optimize-link-traversal/lib/index.ts delete mode 100644 packages/bus-optimize-link-traversal/package.json diff --git a/engines/config-query-sparql-link-traversal/config/config-default.json b/engines/config-query-sparql-link-traversal/config/config-default.json index 3caa32209..c70b5845a 100644 --- a/engines/config-query-sparql-link-traversal/config/config-default.json +++ b/engines/config-query-sparql-link-traversal/config/config-default.json @@ -9,8 +9,6 @@ "ccqslt:config/extract-links/actors/quad-pattern-query.json", "ccqslt:config/rdf-resolve-hypermedia-links/actors/traverse-replace-conditional.json", "ccqslt:config/rdf-resolve-hypermedia-links-queue/actors/wrapper-limit-count.json", - "ccqslt:config/extract-links/actors/tree.json", - "ccqslt:config/optimize-link-traversal/mediators.json", - "ccqslt:config/optimize-link-traversal/actors/filter-tree.json" + "ccqslt:config/extract-links/actors/tree.json" ] } diff --git a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json index a99428eef..50361ea85 100644 --- a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json +++ b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json @@ -2,16 +2,14 @@ "@context": [ "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^2.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-extract-links-tree/^0.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/bus-optimize-link-traversal/^0.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-extract-links-tree/^0.0.0/components/context.jsonld" ], "@id": "urn:comunica:default:Runner", "@type": "Runner", "actors": [ { "@id": "urn:comunica:default:extract-links/actors#extract-links-tree", - "@type": "ActorExtractLinksTree", - "mediatorOptimizeLinkTraversal": { "@id": "urn:comunica:default:optimize-link-traversal/mediators#main" } + "@type": "ActorExtractLinksTree" } ] } diff --git a/engines/config-query-sparql-link-traversal/config/optimize-link-traversal/actors/filter-tree.json b/engines/config-query-sparql-link-traversal/config/optimize-link-traversal/actors/filter-tree.json deleted file mode 100644 index 42325da85..000000000 --- a/engines/config-query-sparql-link-traversal/config/optimize-link-traversal/actors/filter-tree.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^2.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-optimize-link-traversal-filter-tree-links/^0.0.0/components/context.jsonld" - ], - "@id": "urn:comunica:default:Runner", - "@type": "Runner", - "actors": [ - { - "@id": "urn:comunica:default:optimize-link-traversal/actors#filter-tree-links", - "@type": "ActorOptimizeLinkTraversalFilterTreeLinks" - } - ] - } \ No newline at end of file diff --git a/engines/config-query-sparql-link-traversal/config/optimize-link-traversal/mediators.json b/engines/config-query-sparql-link-traversal/config/optimize-link-traversal/mediators.json deleted file mode 100644 index 812dc4d6e..000000000 --- a/engines/config-query-sparql-link-traversal/config/optimize-link-traversal/mediators.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/bus-optimize-link-traversal/^0.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/mediator-all/^2.0.0/components/context.jsonld" - ], - "@id": "urn:comunica:default:optimize-link-traversal/mediators#main", - "@type": "MediatorAll", - "bus": { "@id": "ActorOptimizeLinkTraversal:_default_bus" } -} diff --git a/engines/query-sparql-link-traversal/package.json b/engines/query-sparql-link-traversal/package.json index 322219303..62963580e 100644 --- a/engines/query-sparql-link-traversal/package.json +++ b/engines/query-sparql-link-traversal/package.json @@ -59,8 +59,7 @@ "@comunica/config-query-sparql-link-traversal": "^0.0.1", "@comunica/mediator-combine-array": "^0.0.1", "@comunica/actor-init-query": "^2.2.0", - "@comunica/runner-cli": "^2.2.0", - "@comunica/actor-optimize-link-traversal-filter-tree-links": "^0.0.1" + "@comunica/runner-cli": "^2.2.0" }, "scripts": { "build": "npm run build:ts", diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index e731ca2fc..4da144676 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -3,20 +3,19 @@ import type { IActorExtractLinksOutput, IActorExtractLinksArgs, } from '@comunica/bus-extract-links'; import { ActorExtractLinks } from '@comunica/bus-extract-links'; -import type { MediatorOptimizeLinkTraversal, - IActorOptimizeLinkTraversalOutput } from '@comunica/bus-optimize-link-traversal'; + import type { IActorTest } from '@comunica/core'; +import type { IActionContext } from '@comunica/types'; import type { IRelationDescription, IRelation, INode } from '@comunica/types-link-traversal'; import { TreeNodes } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; +import { FilterNode } from './FilterNode'; import { buildRelations, collectRelation } from './treeMetadataExtraction'; -import {FilterNode} from './FilterNode'; /** * A comunica Extract Links Tree Extract Links Actor. */ export class ActorExtractLinksTree extends ActorExtractLinks { - public constructor(args: IActorExtractLinksArgs) { super(args); } @@ -46,7 +45,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { relationDescriptions)); // Resolve to discovered links - metadata.on('end', () => { + metadata.on('end', async() => { // Validate if the node forward have the current node as implicit subject for (const [ nodeValue, link ] of nodeLinks) { if (pageRelationNodes.has(nodeValue)) { @@ -62,23 +61,24 @@ export class ActorExtractLinksTree extends ActorExtractLinks { const node: INode = { relation: relations, subject: currentNodeUrl }; let acceptedRelation = relations; - FilterNode.run(node, action.context) - .then(filters => { - acceptedRelation = this.handleOptimization(filters, relations); - resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); - }) - .catch(error => { - reject(error); - }); + const filters = await this.applyFilter(node, action.context); + acceptedRelation = this.handleFilter(filters, acceptedRelation); + resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); }); }); } + + /* istanbul ignore next */ + public async applyFilter(node: INode, context: IActionContext): Promise> { + return await new FilterNode().run(node, context); + } + /* istanbul ignore next */ + - private handleOptimization(filters: Map, - relations: IRelation[]): IRelation[] { - return filters.size > 0 ? - relations.filter(relation => filters?.get(relation.node)) : - relations; + private handleFilter(filters: Map, acceptedRelation: IRelation[]): IRelation[] { + return filters.size > 0 ? + acceptedRelation.filter(relation => filters?.get(relation.node)) : + acceptedRelation; } /** diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 1c110023c..a1c98c7ca 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -1,13 +1,10 @@ import { BindingsFactory } from '@comunica/bindings-factory'; import { KeysInitQuery } from '@comunica/context-entries'; -import type { Bindings } from '@comunica/types'; -import type { IRelation } from '@comunica/types-link-traversal'; +import type { Bindings, IActionContext } from '@comunica/types'; +import type { IRelation, INode } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; import { AsyncEvaluator } from 'sparqlee'; -import type { IActionContext } from '@comunica/types'; -import type { INode } from '@comunica/types-link-traversal'; - const BF = new BindingsFactory(); /** @@ -17,26 +14,30 @@ const BF = new BindingsFactory(); * link should be follow or not */ export class FilterNode { - - private static test(node: INode, context:IActionContext): boolean { - + public test(node: INode, context: IActionContext): boolean { if (!node.relation) { - return false + return false; + } + + if (node.relation.length === 0) { + return false; } const query: Algebra.Operation = context.get(KeysInitQuery.query)!; - if (!this.doesNodeExist(query, Algebra.types.FILTER)) { - return false + if (!FilterNode.doesNodeExist(query, Algebra.types.FILTER)) { + return false; } - return true + return true; } - public static async run(node: INode, context: IActionContext): Promise> { + public async run(node: INode, context: IActionContext): Promise> { const filterMap: Map = new Map(); - if (this.test(node, context)){ - return filterMap + + if (!this.test(node, context)) { + return new Map(); } + // Extract the filter expression const filterOperation: Algebra.Expression = (() => { const query: Algebra.Operation = context.get(KeysInitQuery.query)!; @@ -44,14 +45,12 @@ export class FilterNode { })(); // Extract the bgp of the query - const queryBody: RDF.Quad[] = this.findBgp(context.get(KeysInitQuery.query)!); + const queryBody: RDF.Quad[] = FilterNode.findBgp(context.get(KeysInitQuery.query)!); if (queryBody.length === 0) { - return filterMap ; + return new Map(); } // Capture the relation from the input - const relations: IRelation[] = typeof node.relation !== 'undefined' ? - node.relation : - []; + const relations: IRelation[] = node.relation!; for (const relation of relations) { if (typeof relation.path === 'undefined' || typeof relation.value === 'undefined') { @@ -59,15 +58,15 @@ export class FilterNode { continue; } // Find the quad from the bgp that are related to the TREE relation - const relevantQuads = this.findRelavantQuad(queryBody, relation.path.value); + const relevantQuads = FilterNode.findRelavantQuad(queryBody, relation.path.value); if (relevantQuads.length === 0) { filterMap.set(relation.node, true); continue; } // Create the binding in relation to the relevant quad - const bindings = this.createBinding(relevantQuads, relation.value.quad); - const filterExpression: Algebra.Operation = this.deleteUnrelevantFilter(filterOperation, bindings); + const bindings = FilterNode.createBinding(relevantQuads, relation.value.quad); + const filterExpression: Algebra.Operation = FilterNode.deleteUnrelevantFilter(filterOperation, bindings); if (filterExpression.args.length === 0) { filterMap.set(relation.node, true); continue; @@ -77,7 +76,7 @@ export class FilterNode { const result: boolean = await evaluator.evaluateAsEBV(bindings); filterMap.set(relation.node, result); } - return filterMap; + return filterMap; } /** @@ -154,7 +153,7 @@ export class FilterNode { private static findBgp(query: Algebra.Operation): RDF.Quad[] { let currentNode = query.input; do { - if (currentNode.type === 'join') { + if (currentNode.type === Algebra.types.JOIN) { return this.formatBgp(currentNode.input); } if ('input' in currentNode) { @@ -166,6 +165,9 @@ export class FilterNode { private static formatBgp(joins: any): RDF.Quad[] { const bgp: RDF.Quad[] = []; + if (joins.length === 0) { + return []; + } if (!('input' in joins[0])) { return joins; } diff --git a/packages/actor-extract-links-extract-tree/package.json b/packages/actor-extract-links-extract-tree/package.json index e9f15190c..54f30efa0 100644 --- a/packages/actor-extract-links-extract-tree/package.json +++ b/packages/actor-extract-links-extract-tree/package.json @@ -38,7 +38,9 @@ "@comunica/context-entries": "^2.4.0", "@comunica/context-entries-link-traversal": "^0.0.1", "@comunica/types-link-traversal": "^0.0.1", - "@comunica/bus-optimize-link-traversal": "^0.0.1" + "sparqlalgebrajs": "^4.0.0", + "sparqlee": "^2.1.0", + "@comunica/bindings-factory": "^2.2.0" }, "scripts": { "build": "npm run build:ts && npm run build:components", diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 0288e3a52..54963a0af 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -1,7 +1,6 @@ -import type { IActorOptimizeLinkTraversalOutput } from '@comunica/bus-optimize-link-traversal'; import { KeysRdfResolveQuadPattern } from '@comunica/context-entries'; import { ActionContext, Bus } from '@comunica/core'; -import type { INode, IRelation } from '@comunica/types-link-traversal'; +import type { IRelation } from '@comunica/types-link-traversal'; import { RelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; @@ -13,8 +12,6 @@ const DF = new DataFactory(); describe('ActorExtractLinksExtractLinksTree', () => { let bus: any; - let mockMediator: any; - let spyMockMediator: any; beforeEach(() => { bus = new Bus({ name: 'bus' }); @@ -46,6 +43,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { beforeEach(() => { actor = new ActorExtractLinksTree({ name: 'actor', bus }); + jest.spyOn(actor, 'applyFilter').mockReturnValue(Promise.resolve(new Map())); }); it('should return the links of a TREE with one relation', async() => { @@ -73,7 +71,6 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actor.run(action); expect(result).toEqual({ links: [{ url: expectedUrl }]}); - expect(spyMockMediator).toBeCalledTimes(1); }); it('should return the links of a TREE with multiple relations', async() => { @@ -130,7 +127,6 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actor.run(action); expect(result).toEqual({ links: expectedUrl.map(value => { return { url: value }; }) }); - expect(spyMockMediator).toBeCalledTimes(1); }); it('should return the links of a TREE with one complex relation', async() => { @@ -170,7 +166,6 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actor.run(action); expect(result).toEqual({ links: [{ url: expectedUrl }]}); - expect(spyMockMediator).toBeCalledTimes(1); }); it('should return the links of a TREE with multiple relations combining blank nodes and named nodes', async() => { @@ -223,7 +218,6 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actor.run(action); expect(result).toEqual({ links: expectedUrl.map(value => { return { url: value }; }) }); - expect(spyMockMediator).toBeCalledTimes(1); }); it('should prune the filtered link', async() => { @@ -298,172 +292,17 @@ describe('ActorExtractLinksExtractLinksTree', () => { node: expectedUrl, }, ]; - const expectedNode: INode = { - relation: relations, - subject: treeUrl, - }; - const mediationOutput: Promise = Promise.resolve( - { - filters: new Map([[ relations[0].node, false ], [ relations[1].node, true ]]), - }, - ); - const mediator: any = { - mediate(arg: any) { - return mediationOutput; - }, - }; - const spyMock = jest.spyOn(mediator, 'mediate'); - const actorWithCustomMediator = new ActorExtractLinksTree( - { name: 'actor', bus}, - ); - - const result = await actorWithCustomMediator.run(action); - expect(spyMock).toBeCalledTimes(1); - expect(spyMock).toBeCalledWith({ context: action.context, treeMetadata: expectedNode }); - expect(spyMock).toHaveReturnedWith(mediationOutput); - - expect(result).toEqual({ links: [{ url: expectedUrl }]}); - }); - it('should not filter the links if the mediator return an undefined filters', async() => { - const expectedUrl = 'http://foo.com'; - const secondExpectedLink = 'http://bar.com'; - const input = stream([ - DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#relation'), - DF.blankNode('_:_g1'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#node'), - DF.literal(secondExpectedLink), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#remainingItems'), - DF.literal('66'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#value'), - DF.literal('66'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#relation'), - DF.blankNode('_:_g2'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g2'), - DF.namedNode('https://w3id.org/tree#node'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - ]); - const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - const relations: IRelation[] = [ - { - node: secondExpectedLink, - remainingItems: { - value: 66, - quad: DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#remainingItems'), - DF.literal('66'), - DF.namedNode('ex:gx')), - }, - '@type': { - value: RelationOperator.GreaterThanRelation, - quad: DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), - DF.namedNode('ex:gx')), - }, - value: { - value: '66', - quad: DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#value'), - DF.literal('66'), - DF.namedNode('ex:gx')), - }, - }, - { - node: expectedUrl, - }, - ]; - const expectedNode: INode = { - relation: relations, - subject: treeUrl, - }; - const mediationOutput: Promise = Promise.resolve( - { - }, + const filterOutput: Promise> = Promise.resolve( + new Map([[ relations[0].node, false ], [ relations[1].node, true ]]), ); - const mediator: any = { - mediate(arg: any) { - return mediationOutput; - }, - }; - const spyMock = jest.spyOn(mediator, 'mediate'); - const actorWithCustomMediator = new ActorExtractLinksTree( - { name: 'actor', bus }, - ); - - const result = await actorWithCustomMediator.run(action); - expect(spyMock).toBeCalledTimes(1); - expect(spyMock).toBeCalledWith({ context: action.context, treeMetadata: expectedNode }); - expect(spyMock).toHaveReturnedWith(mediationOutput); - expect(result).toEqual({ links: [{ url: secondExpectedLink }, { url: expectedUrl }]}); - }); - - it('should fail when the mediator return a fail promise', async() => { - const expectedUrl = 'http://foo.com'; - const input = stream([ - DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#relation'), - DF.blankNode('_:_g1'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#node'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - ]); - const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - - const mediationOutput: Promise = Promise.reject(new Error('failled request')); - const mediator: any = { - mediate(_arg: any) { - return mediationOutput; - }, - }; - - const spyMock = jest.spyOn(mediator, 'mediate'); - const actorWithCustomMediator = new ActorExtractLinksTree( + const actorWithCustomFilter = new ActorExtractLinksTree( { name: 'actor', bus }, ); - await actorWithCustomMediator.run(action).then(res => { - expect(res).toBeUndefined(); - }).catch(error => { - expect(error).toBeDefined(); - }); - - expect(spyMock).toBeCalledTimes(1); + jest.spyOn(actorWithCustomFilter, 'applyFilter').mockReturnValue(filterOutput); + const result = await actorWithCustomFilter.run(action); + expect(result).toEqual({ links: [{ url: expectedUrl }]}); }); }); diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts similarity index 85% rename from packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts rename to packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index e80f2e9ed..3c06c92d9 100644 --- a/packages/actor-optimize-link-traversal-filter-tree-links/test/ActorOptimizeLinkTraversalFilterTreeLinks-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -1,30 +1,23 @@ -import type { IActionOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; import { KeysInitQuery } from '@comunica/context-entries'; -import { Bus, ActionContext } from '@comunica/core'; +import { ActionContext } from '@comunica/core'; import type { INode } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; -import { ActorOptimizeLinkTraversalFilterTreeLinks } from '../lib/ActorOptimizeLinkTraversalFilterTreeLinks'; +import { FilterNode } from '../lib/FilterNode'; const DF = new DataFactory(); describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { - let bus: any; - - beforeEach(() => { - bus = new Bus({ name: 'bus' }); - }); - describe('An ActorOptimizeLinkTraversalFilterTreeLinks instance', () => { - let actor: ActorOptimizeLinkTraversalFilterTreeLinks; + let filterNode: FilterNode; beforeEach(() => { - actor = new ActorOptimizeLinkTraversalFilterTreeLinks({ name: 'actor', bus }); + filterNode = new FilterNode(); }); describe('test method', () => { const treeSubject = 'tree'; - it('should test when there are relations and a filter operation in the query', async() => { + it('should test when there are relations and a filter operation in the query', () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -36,12 +29,8 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - const response = await actor.test(action); + const response = filterNode.test(node, context); expect(response).toBe(true); }); @@ -52,16 +41,9 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const node: INode = { subject: treeSubject, }; - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - await actor.test(action).then(v => { - expect(v).toBeUndefined(); - }).catch(error => { - expect(error).toBeDefined(); - }); + const response = filterNode.test(node, context); + expect(response).toBe(false); }); it('should not test when there is no relations and a filter operation in the query', async() => { @@ -72,16 +54,8 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { subject: treeSubject, relation: [], }; - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - - await actor.test(action).then(v => { - expect(v).toBeUndefined(); - }).catch(error => { - expect(error).toBeDefined(); - }); + const response = filterNode.test(node, context); + expect(response).toBe(false); }); it('should not test when there is no tree metadata and a filter operation in the query', async() => { @@ -92,15 +66,8 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { subject: treeSubject, relation: [], }; - const action: IActionOptimizeLinkTraversal = { - context, - }; - - await actor.test(action).then(v => { - expect(v).toBeUndefined(); - }).catch(error => { - expect(error).toBeDefined(); - }); + const response = filterNode.test(node, context); + expect(response).toBe(false); }); it('should no test when there no filter operation in the query', async() => { @@ -115,16 +82,8 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - - await actor.test(action).then(v => { - expect(v).toBeUndefined(); - }).catch(error => { - expect(error).toBeDefined(); - }); + const response = filterNode.test(node, context); + expect(response).toBe(false); }); it('should no test when there is no filter operation in the query and no TREE relation', async() => { @@ -135,16 +94,8 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { subject: treeSubject, relation: [], }; - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - - await actor.test(action).then(v => { - expect(v).toBeUndefined(); - }).catch(error => { - expect(error).toBeDefined(); - }); + const response = filterNode.test(node, context); + expect(response).toBe(false); }); }); @@ -174,6 +125,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; + const bgp = ([ DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), @@ -182,7 +134,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ]).map(quad => { return { input: [ quad ], - type: 'join', + type: Algebra.types.JOIN, }; }); const filterExpression = { @@ -213,6 +165,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; + const query = { type: Algebra.types.PROJECT, input: { @@ -220,24 +173,20 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expression: filterExpression, input: { input: { - type: 'join', + type: Algebra.types.JOIN, input: bgp, }, }, }, }; + const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - const result = await actor.run(action); + const result = await filterNode.run(node, context); - expect(result.filters).toBeDefined(); - expect(result.filters).toStrictEqual( + expect(result).toStrictEqual( new Map([[ 'http://bar.com', true ]]), ); }); @@ -301,7 +250,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expression: filterExpression, input: { input: { - type: 'join', + type: Algebra.types.JOIN, input: bgp, }, }, @@ -311,14 +260,9 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { [KeysInitQuery.query.name]: query, }); - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - const result = await actor.run(action); + const result = await filterNode.run(node, context); - expect(result.filters).toBeDefined(); - expect(result.filters).toStrictEqual( + expect(result).toStrictEqual( new Map([[ 'http://bar.com', false ]]), ); }); @@ -382,7 +326,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expression: filterExpression, input: { input: { - type: 'join', + type: Algebra.types.JOIN, input: bgp, }, }, @@ -392,14 +336,9 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { [KeysInitQuery.query.name]: query, }); - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - const result = await actor.run(action); + const result = await filterNode.run(node, context); - expect(result.filters).toBeDefined(); - expect(result.filters).toStrictEqual( + expect(result).toStrictEqual( new Map([[ 'http://bar.com', true ]]), ); }); @@ -443,7 +382,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expression: filterExpression, input: { input: { - type: 'join', + type: Algebra.types.JOIN, input: bgp, }, }, @@ -452,14 +391,10 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); + const node: INode = { subject: 'foo' }; + const result = await filterNode.run(node, context); - const action: IActionOptimizeLinkTraversal = { - context, - }; - const result = await actor.run(action); - - expect(result.filters).toBeDefined(); - expect(result.filters).toStrictEqual( + expect(result).toStrictEqual( new Map(), ); }); @@ -560,7 +495,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expression: filterExpression, input: { input: { - type: 'join', + type: Algebra.types.JOIN, input: bgp, }, }, @@ -570,14 +505,9 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { [KeysInitQuery.query.name]: query, }); - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - const result = await actor.run(action); + const result = await filterNode.run(node, context); - expect(result.filters).toBeDefined(); - expect(result.filters).toStrictEqual( + expect(result).toStrictEqual( new Map([[ 'http://bar.com', true ]]), ); }); @@ -646,7 +576,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expression: filterExpression, input: { input: { - type: 'join', + type: Algebra.types.JOIN, input: bgp, }, }, @@ -656,14 +586,9 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { [KeysInitQuery.query.name]: query, }); - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - const result = await actor.run(action); + const result = await filterNode.run(node, context); - expect(result.filters).toBeDefined(); - expect(result.filters).toStrictEqual( + expect(result).toStrictEqual( new Map([[ 'http://bar.com', true ], [ 'http://foo.com', true ]]), ); }); @@ -727,7 +652,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expression: filterExpression, input: { input: { - type: 'join', + type: Algebra.types.JOIN, input: bgp, }, }, @@ -737,14 +662,9 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { [KeysInitQuery.query.name]: query, }); - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - const result = await actor.run(action); + const result = await filterNode.run(node, context); - expect(result.filters).toBeDefined(); - expect(result.filters).toStrictEqual( + expect(result).toStrictEqual( new Map([[ 'http://bar.com', true ]]), ); }); @@ -841,7 +761,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expression: filterExpression, input: { input: { - type: 'join', + type: Algebra.types.JOIN, input: bgp, }, }, @@ -851,14 +771,9 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { [KeysInitQuery.query.name]: query, }); - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - const result = await actor.run(action); + const result = await filterNode.run(node, context); - expect(result.filters).toBeDefined(); - expect(result.filters).toStrictEqual( + expect(result).toStrictEqual( new Map([[ 'http://bar.com', true ]]), ); }); @@ -965,7 +880,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expression: filterExpression, input: { input: { - type: 'join', + type: Algebra.types.JOIN, input: bgp, }, }, @@ -975,19 +890,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { [KeysInitQuery.query.name]: query, }); - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, - }; - const result = await actor.run(action); + const result = await filterNode.run(node, context); - expect(result.filters).toBeDefined(); - expect(result.filters).toStrictEqual( + expect(result).toStrictEqual( new Map([[ 'http://bar.com', true ]]), ); }); - it('should return an empty filter map if there is no bgp', async() => { + it('should return an empty filter map if there is bgp of lenght 0', async() => { const treeSubject = 'tree'; const node: INode = { @@ -1042,20 +952,89 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { input: { type: Algebra.types.FILTER, expression: filterExpression, + input: { + input: { + type: Algebra.types.JOIN, + input: bgp, + }, + }, }, }; const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const action: IActionOptimizeLinkTraversal = { - context, - treeMetadata: node, + const result = await filterNode.run(node, context); + + expect(result).toStrictEqual( + new Map(), + ); + }); + + it('should return an empty filter map if there is no bgp', async() => { + const treeSubject = 'tree'; + + const node: INode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, + }, + ], + }; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: {}, + }, }; - const result = await actor.run(action); + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const result = await filterNode.run(node, context); - expect(result.filters).toBeDefined(); - expect(result.filters).toStrictEqual( + expect(result).toStrictEqual( new Map(), ); }); diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/.npmignore b/packages/actor-optimize-link-traversal-filter-tree-links/.npmignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/README.md b/packages/actor-optimize-link-traversal-filter-tree-links/README.md deleted file mode 100644 index c212204ad..000000000 --- a/packages/actor-optimize-link-traversal-filter-tree-links/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Comunica Filter Tree Links Optimize Link Traversal Actor - -[![npm version](https://badge.fury.io/js/%40comunica%2Factor-optimize-link-traversal-filter-tree-links.svg)](https://www.npmjs.com/package/@comunica/actor-optimize-link-traversal-filter-tree-links) - -A comunica Link traversal optimizer that filter link of document following the [TREE specification](https://treecg.github.io/specification/) - -This module is part of the [Comunica framework](https://github.com/comunica/comunica), -and should only be used by [developers that want to build their own query engine](https://comunica.dev/docs/modify/). - -[Click here if you just want to query with Comunica](https://comunica.dev/docs/query/). - -## Install - -```bash -$ yarn add @comunica/actor-optimize-link-traversal-filter-tree-links -``` - -## Configure - -After installing, this package can be added to your engine's configuration as follows: -```text -{ - "@context": [ - ... - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-optimize-link-traversal-filter-tree-links/^0.0.1/components/context.jsonld" - ], - "actors": [ - ... - { - "@id": "urn:comunica:default:optimize-link-traversal/actors#filter-tree-links", - "@type": "ActorOptimizeLinkTraversalFilterTreeLinks" - } - ] -} -``` - -### Config Parameters - -TODO: fill in parameters (this section can be removed if there are none) - -* `someParam`: Description of the param diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts deleted file mode 100644 index ac457da8a..000000000 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/ActorOptimizeLinkTraversalFilterTreeLinks.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { BindingsFactory } from '@comunica/bindings-factory'; -import type { - IActorOptimizeLinkTraversalArgs, - IActionOptimizeLinkTraversal, - IActorOptimizeLinkTraversalOutput, -} from '@comunica/bus-optimize-link-traversal'; -import { ActorOptimizeLinkTraversal } from '@comunica/bus-optimize-link-traversal'; -import { KeysInitQuery } from '@comunica/context-entries'; -import type { IActorTest } from '@comunica/core'; -import type { Bindings } from '@comunica/types'; -import type { IRelation } from '@comunica/types-link-traversal'; -import type * as RDF from 'rdf-js'; -import { Algebra } from 'sparqlalgebrajs'; -import { AsyncEvaluator } from 'sparqlee'; - -const BF = new BindingsFactory(); -/** - * A comunica Link traversal optimizer that filter link of document - * following the [TREE specification](https://treecg.github.io/specification/). - * The actor apply the filter of a query into the TREE relation to determine if a - * link should be follow or not - */ - -export class ActorOptimizeLinkTraversalFilterTreeLinks extends ActorOptimizeLinkTraversal { - public constructor(args: IActorOptimizeLinkTraversalArgs) { - super(args); - } - - public async test(action: IActionOptimizeLinkTraversal): Promise { - if (!action.treeMetadata) { - return Promise.reject(new Error('TREE metadata is not defined')); - } - - if (!action.treeMetadata.relation) { - return Promise.reject(new Error('There is no relation into the node')); - } - - const query: Algebra.Operation = action.context.get(KeysInitQuery.query)!; - if (!this.doesNodeExist(query, Algebra.types.FILTER)) { - return Promise.reject(new Error('there is no filter defined into the query')); - } - - return Promise.resolve(true); - } - - public async run(action: IActionOptimizeLinkTraversal): Promise { - const filterMap: Map = new Map(); - // Extract the filter expression - const filterOperation: Algebra.Expression = (() => { - const query: Algebra.Operation = action.context.get(KeysInitQuery.query)!; - return query.input.expression; - })(); - - // Extract the bgp of the query - const queryBody: RDF.Quad[] = this.findBgp(action.context.get(KeysInitQuery.query)!); - if (queryBody.length === 0) { - return { filters: filterMap }; - } - // Capture the relation from the input - const relations: IRelation[] = typeof action?.treeMetadata?.relation !== 'undefined' ? - action.treeMetadata.relation : - []; - - for (const relation of relations) { - if (typeof relation.path === 'undefined' || typeof relation.value === 'undefined') { - filterMap.set(relation.node, true); - continue; - } - // Find the quad from the bgp that are related to the TREE relation - const relevantQuads = this.findRelavantQuad(queryBody, relation.path.value); - if (relevantQuads.length === 0) { - filterMap.set(relation.node, true); - continue; - } - - // Create the binding in relation to the relevant quad - const bindings = this.createBinding(relevantQuads, relation.value.quad); - const filterExpression: Algebra.Operation = this.deleteUnrelevantFilter(filterOperation, bindings); - if (filterExpression.args.length === 0) { - filterMap.set(relation.node, true); - continue; - } - const evaluator = new AsyncEvaluator(filterExpression); - // Evaluate the filter with the relevant quad binding - const result: boolean = await evaluator.evaluateAsEBV(bindings); - filterMap.set(relation.node, result); - } - return { filters: filterMap }; - } - - /** - * - * @param queryBody - * @param path - * @returns RDF.Quad[] - * find the quad that has as predicate a the TREE:path of a relation - */ - private findRelavantQuad(queryBody: RDF.Quad[], path: string): RDF.Quad[] { - const resp: RDF.Quad[] = []; - for (const quad of queryBody) { - if (quad.predicate.value === path && quad.object.termType === 'Variable') { - resp.push(quad); - } - } - return resp; - } - - /** - * @param filterExpression - * @param binding - * @returns Algebra.Expression - * delete the filters that are not related to TREE relation - */ - private deleteUnrelevantFilter(filterExpression: Algebra.Expression, binding: Bindings): Algebra.Expression { - let newFilterExpression: Algebra.Expression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: filterExpression.operator, - type: Algebra.types.EXPRESSION, - args: [], - }; - - if ('operator' in filterExpression.args[0]) { - newFilterExpression.args = (filterExpression.args).filter(expression => { - for (const arg of expression.args) { - if ('term' in arg && arg.term.termType === 'Variable') { - return binding.has(arg.term.value); - } - } - return false; - }); - if (newFilterExpression.args.length === 1) { - newFilterExpression = newFilterExpression.args[0]; - } - } else { - for (const arg of (filterExpression.args)) { - if ('term' in arg && arg.term.termType === 'Variable' && !binding.has(arg.term.value)) { - newFilterExpression.args = []; - break; - } - newFilterExpression.args.push(arg); - } - } - return newFilterExpression; - } - - /** - * - * @param relevantQuad - * @param relationValue - * @returns Bindings - * create the binding from quad related to the TREE:path - */ - private createBinding(relevantQuad: RDF.Quad[], relationValue: RDF.Quad): Bindings { - let binding: Bindings = BF.bindings(); - for (const quad of relevantQuad) { - const object = quad.object.value; - binding = binding.set(object, relationValue.object); - } - return binding; - } - - private findBgp(query: Algebra.Operation): RDF.Quad[] { - let currentNode = query.input; - do { - if (currentNode.type === 'join') { - return this.formatBgp(currentNode.input); - } - if ('input' in currentNode) { - currentNode = currentNode.input; - } - } while ('input' in currentNode); - return []; - } - - private formatBgp(joins: any): RDF.Quad[] { - const bgp: RDF.Quad[] = []; - if (!('input' in joins[0])) { - return joins; - } - for (const join of joins) { - bgp.push(join.input[0]); - } - return bgp; - } - - private doesNodeExist(query: Algebra.Operation, node: string): boolean { - let currentNode = query; - do { - if (currentNode.type === node) { - return true; - } - /* istanbul ignore next */ - if ('input' in currentNode) { - currentNode = currentNode.input; - } - /* istanbul ignore next */ - } while ('input' in currentNode); - return false; - } -} - diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/lib/index.ts b/packages/actor-optimize-link-traversal-filter-tree-links/lib/index.ts deleted file mode 100644 index 4f4318ffc..000000000 --- a/packages/actor-optimize-link-traversal-filter-tree-links/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ActorOptimizeLinkTraversalFilterTreeLinks'; diff --git a/packages/actor-optimize-link-traversal-filter-tree-links/package.json b/packages/actor-optimize-link-traversal-filter-tree-links/package.json deleted file mode 100644 index 424a37c6f..000000000 --- a/packages/actor-optimize-link-traversal-filter-tree-links/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@comunica/actor-optimize-link-traversal-filter-tree-links", - "version": "0.0.1", - "description": "A filter-tree-links optimize-link-traversal actor", - "lsd:module": true, - "main": "lib/index.js", - "typings": "lib/index", - "repository": { - "type": "git", - "url": "https://github.com/comunica/comunica.git", - "directory": "packages/actor-optimize-link-traversal-filter-tree-links" - }, - "publishConfig": { - "access": "public" - }, - "sideEffects": false, - "keywords": [ - "comunica", - "actor", - "optimize-link-traversal", - "filter-tree-links" - ], - "license": "MIT", - "bugs": { - "url": "https://github.com/comunica/comunica/issues" - }, - "homepage": "https://comunica.dev/", - "files": [ - "components", - "lib/**/*.d.ts", - "lib/**/*.js" - ], - "dependencies": { - "@comunica/bindings-factory": "^2.2.0", - "@comunica/bus-optimize-link-traversal": "^0.0.1", - "@comunica/context-entries": "^2.4.0", - "@comunica/core": "^2.4.0", - "rdf-data-factory": "^1.1.1", - "rdf-string": "^1.6.0", - "sparqlalgebrajs": "^4.0.0", - "sparqlee": "^2.1.0" - }, - "scripts": { - "build": "npm run build:ts && npm run build:components", - "build:ts": "node \"../../node_modules/typescript/bin/tsc\"", - "build:components": "componentsjs-generator" - } -} diff --git a/packages/bus-optimize-link-traversal/.npmignore b/packages/bus-optimize-link-traversal/.npmignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/bus-optimize-link-traversal/README.md b/packages/bus-optimize-link-traversal/README.md deleted file mode 100644 index 868b189df..000000000 --- a/packages/bus-optimize-link-traversal/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Comunica Bus Optimize Link Traversal - -[![npm version](https://badge.fury.io/js/%40comunica%2Fbus-optimize-link-traversal.svg)](https://www.npmjs.com/package/@comunica/bus-optimize-link-traversal) - -A comunica bus for optimization of link traversal - -This module is part of the [Comunica framework](https://github.com/comunica/comunica), -and should only be used by [developers that want to build their own query engine](https://comunica.dev/docs/modify/). - -[Click here if you just want to query with Comunica](https://comunica.dev/docs/query/). - -## Install - -```bash -$ yarn add @comunica/bus-optimize-link-traversal -``` - -## Usage - -## Bus usage - -* **Context**: `"https://linkedsoftwaredependencies.org/bundles/npm/@comunica/bus-optimize-link-traversal/^0.0.0/components/context.jsonld"` -* **Bus name**: `ActorOptimizeLinkTraversal:_default_bus` - -## Creating actors on this bus - -Actors extending [`ActorOptimizeLinkTraversal`](TODO:jsdoc_url) are automatically subscribed to this bus. diff --git a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts b/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts deleted file mode 100644 index 04caceea2..000000000 --- a/packages/bus-optimize-link-traversal/lib/ActorOptimizeLinkTraversal.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { IAction, IActorArgs, IActorOutput, IActorTest, Mediate } from '@comunica/core'; -import { Actor } from '@comunica/core'; -import type { INode } from '@comunica/types-link-traversal'; -/** - * A comunica actor for optimization of link traversal - * - * Actor types: - * * Input: IActionOptimizeLinkTraversal: Metadata or relevant information for optimization. - * * Test: - * * Output: IActorOptimizeLinkTraversalOutput: Links prunning or reorder information. - * - * @see IActionOptimizeLinkTraversal - * @see IActorOptimizeLinkTraversalOutput - */ -export abstract class ActorOptimizeLinkTraversal extends Actor { - /** - * @param args - @defaultNested { a } bus - */ - public constructor(args: IActorOptimizeLinkTraversalArgs) { - super(args); - } -} - -export interface IActionOptimizeLinkTraversal extends IAction { - treeMetadata?: INode; -} - -export interface IActorOptimizeLinkTraversalOutput extends IActorOutput { - /** - * Decision map whether the link should be filter or not - */ - filters?: Map; -} - -export type IActorOptimizeLinkTraversalArgs = IActorArgs< -IActionOptimizeLinkTraversal, IActorTest, IActorOptimizeLinkTraversalOutput>; - -export type MediatorOptimizeLinkTraversal = Mediate< -IActionOptimizeLinkTraversal, IActorOptimizeLinkTraversalOutput>; diff --git a/packages/bus-optimize-link-traversal/lib/index.ts b/packages/bus-optimize-link-traversal/lib/index.ts deleted file mode 100644 index a1a1d8527..000000000 --- a/packages/bus-optimize-link-traversal/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ActorOptimizeLinkTraversal'; diff --git a/packages/bus-optimize-link-traversal/package.json b/packages/bus-optimize-link-traversal/package.json deleted file mode 100644 index d97219708..000000000 --- a/packages/bus-optimize-link-traversal/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "@comunica/bus-optimize-link-traversal", - "version": "0.0.1", - "description": "A comunica bus for optimization of link traversal", - "lsd:module": true, - "main": "lib/index.js", - "typings": "lib/index", - "repository": { - "type": "git", - "url": "https://github.com/comunica/comunica.git", - "directory": "packages/bus-optimize-link-traversal" - }, - "publishConfig": { - "access": "public" - }, - "sideEffects": false, - "keywords": [ - "comunica", - "bus", - "optimize-link-traversal" - ], - "license": "MIT", - "bugs": { - "url": "https://github.com/comunica/comunica/issues" - }, - "homepage": "https://comunica.dev/", - "files": [ - "components", - "lib/**/*.d.ts", - "lib/**/*.js" - ], - "dependencies": { - "@comunica/core": "^2.4.0" - }, - "scripts": { - "build": "npm run build:ts && npm run build:components", - "build:ts": "node \"../../node_modules/typescript/bin/tsc\"", - "build:components": "componentsjs-generator" - } -} From 655a7ed6a9b095b4017d9b4215af32b105526959 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 14 Nov 2022 14:53:36 +0100 Subject: [PATCH 043/189] lint-fix --- .../lib/ActorExtractLinksTree.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 4da144676..612069385 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -67,14 +67,13 @@ export class ActorExtractLinksTree extends ActorExtractLinks { }); }); } - + /* istanbul ignore next */ public async applyFilter(node: INode, context: IActionContext): Promise> { return await new FilterNode().run(node, context); } /* istanbul ignore next */ - private handleFilter(filters: Map, acceptedRelation: IRelation[]): IRelation[] { return filters.size > 0 ? acceptedRelation.filter(relation => filters?.get(relation.node)) : From cfd01a7957546b5a7742bb69d97fca021e70c0a5 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 15 Nov 2022 08:39:45 +0100 Subject: [PATCH 044/189] extra space and faulty config deleted --- .gitignore | 2 +- .../config/config-default.json | 3 +-- .../config/extract-links/actors/tree.json | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index f07e48754..a33f96acb 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,4 @@ earl.ttl web-clients/builds componentsjs-error-state.json engines/*/components -packages/*/components \ No newline at end of file +packages/*/components diff --git a/engines/config-query-sparql-link-traversal/config/config-default.json b/engines/config-query-sparql-link-traversal/config/config-default.json index c70b5845a..c340922c4 100644 --- a/engines/config-query-sparql-link-traversal/config/config-default.json +++ b/engines/config-query-sparql-link-traversal/config/config-default.json @@ -8,7 +8,6 @@ "ccqslt:config/extract-links/actors/content-policies-conditional.json", "ccqslt:config/extract-links/actors/quad-pattern-query.json", "ccqslt:config/rdf-resolve-hypermedia-links/actors/traverse-replace-conditional.json", - "ccqslt:config/rdf-resolve-hypermedia-links-queue/actors/wrapper-limit-count.json", - "ccqslt:config/extract-links/actors/tree.json" + "ccqslt:config/rdf-resolve-hypermedia-links-queue/actors/wrapper-limit-count.json" ] } diff --git a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json index 50361ea85..3573fd383 100644 --- a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json +++ b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json @@ -2,7 +2,7 @@ "@context": [ "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^2.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-extract-links-tree/^0.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-extract-links-tree/^0.0.0/components/context.jsonld" ], "@id": "urn:comunica:default:Runner", "@type": "Runner", From 75677919bcb525f0cddbbec62315fef65add8266 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 15 Nov 2022 08:41:17 +0100 Subject: [PATCH 045/189] extra space added --- .gitignore | 2 +- .../config/extract-links/actors/tree.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index a33f96acb..f07e48754 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,4 @@ earl.ttl web-clients/builds componentsjs-error-state.json engines/*/components -packages/*/components +packages/*/components \ No newline at end of file diff --git a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json index 3573fd383..8da34c2b8 100644 --- a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json +++ b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json @@ -2,7 +2,7 @@ "@context": [ "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^2.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-extract-links-tree/^0.0.0/components/context.jsonld" + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-extract-links-tree/^0.0.0/components/context.jsonld" ], "@id": "urn:comunica:default:Runner", "@type": "Runner", From bb59cc29c9f02b823eaa4349aca1e84f85e3cc3f Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 15 Nov 2022 08:44:06 +0100 Subject: [PATCH 046/189] .gitignore file reverted --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f07e48754..706285460 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ earl.ttl web-clients/builds componentsjs-error-state.json engines/*/components -packages/*/components \ No newline at end of file +packages/*/components + From fe839e64453cae3ff28feb506072fccb1d3a615d Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 15 Nov 2022 08:49:45 +0100 Subject: [PATCH 047/189] renaming of tree metadata type --- .../lib/ActorExtractLinksTree.ts | 14 ++++---- .../lib/FilterNode.ts | 8 ++--- .../lib/treeMetadataExtraction.ts | 15 ++++---- .../test/FilterNode-test.ts | 36 +++++++++---------- .../types-link-traversal/lib/TreeMetadata.ts | 8 ++--- 5 files changed, 41 insertions(+), 40 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 612069385..4afe8a37c 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -6,7 +6,7 @@ import { ActorExtractLinks } from '@comunica/bus-extract-links'; import type { IActorTest } from '@comunica/core'; import type { IActionContext } from '@comunica/types'; -import type { IRelationDescription, IRelation, INode } from '@comunica/types-link-traversal'; +import type { ITreeRelationDescription, ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; import { TreeNodes } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { FilterNode } from './FilterNode'; @@ -29,8 +29,8 @@ export class ActorExtractLinksTree extends ActorExtractLinks { const metadata = action.metadata; const currentNodeUrl = action.url; const pageRelationNodes: Set = new Set(); - const relationDescriptions: Map = new Map(); - const relations: IRelation[] = []; + const relationDescriptions: Map = new Map(); + const relations: ITreeRelation[] = []; const nodeLinks: [string, string][] = []; // Forward errors @@ -58,7 +58,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } } - const node: INode = { relation: relations, subject: currentNodeUrl }; + const node: ITreeNode = { relation: relations, subject: currentNodeUrl }; let acceptedRelation = relations; const filters = await this.applyFilter(node, action.context); @@ -69,12 +69,12 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } /* istanbul ignore next */ - public async applyFilter(node: INode, context: IActionContext): Promise> { + public async applyFilter(node: ITreeNode, context: IActionContext): Promise> { return await new FilterNode().run(node, context); } /* istanbul ignore next */ - private handleFilter(filters: Map, acceptedRelation: IRelation[]): IRelation[] { + private handleFilter(filters: Map, acceptedRelation: ITreeRelation[]): ITreeRelation[] { return filters.size > 0 ? acceptedRelation.filter(relation => filters?.get(relation.node)) : acceptedRelation; @@ -95,7 +95,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { url: string, pageRelationNodes: Set, nodeLinks: [string, string][], - relationDescriptions: Map, + relationDescriptions: Map, ): void { // If it's a relation of the current node if (quad.subject.value === url && quad.predicate.value === TreeNodes.Relation) { diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index a1c98c7ca..7808b44a6 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -1,7 +1,7 @@ import { BindingsFactory } from '@comunica/bindings-factory'; import { KeysInitQuery } from '@comunica/context-entries'; import type { Bindings, IActionContext } from '@comunica/types'; -import type { IRelation, INode } from '@comunica/types-link-traversal'; +import type { ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; import { AsyncEvaluator } from 'sparqlee'; @@ -14,7 +14,7 @@ const BF = new BindingsFactory(); * link should be follow or not */ export class FilterNode { - public test(node: INode, context: IActionContext): boolean { + public test(node: ITreeNode, context: IActionContext): boolean { if (!node.relation) { return false; } @@ -31,7 +31,7 @@ export class FilterNode { return true; } - public async run(node: INode, context: IActionContext): Promise> { + public async run(node: ITreeNode, context: IActionContext): Promise> { const filterMap: Map = new Map(); if (!this.test(node, context)) { @@ -50,7 +50,7 @@ export class FilterNode { return new Map(); } // Capture the relation from the input - const relations: IRelation[] = node.relation!; + const relations: ITreeRelation[] = node.relation!; for (const relation of relations) { if (typeof relation.path === 'undefined' || typeof relation.value === 'undefined') { diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts index 579808a51..96e993cb3 100644 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -1,4 +1,4 @@ -import type { IRelation, IRelationDescription } from '@comunica/types-link-traversal'; +import type { ITreeRelation, ITreeRelationDescription } from '@comunica/types-link-traversal'; import { RelationOperator, TreeNodes } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; @@ -10,10 +10,10 @@ import type * as RDF from 'rdf-js'; * to create a IRelation object */ export function collectRelation( - relationDescription: IRelationDescription, + relationDescription: ITreeRelationDescription, nodeLinks: string, -): IRelation { - const relation: IRelation = { node: nodeLinks }; +): ITreeRelation { + const relation: ITreeRelation = { node: nodeLinks }; if (relationDescription?.operator) { relation['@type'] = { value: relationDescription.operator[0], @@ -46,7 +46,7 @@ export function collectRelation( } export function buildRelations( - relationDescriptions: Map, + relationDescriptions: Map, quad: RDF.Quad, ): void { if (quad.predicate.value === TreeNodes.RDFTypeNode) { @@ -93,14 +93,15 @@ export function addRelationDescription({ operator, remainingItems, }: { - relationDescriptions: Map; + relationDescriptions: Map; quad: RDF.Quad; value?: string; subject?: string; operator?: RelationOperator; remainingItems?: number; }): void { - const newDescription: IRelationDescription = typeof relationDescriptions?.get(quad.subject.value) !== 'undefined' ? + const newDescription: ITreeRelationDescription = + typeof relationDescriptions?.get(quad.subject.value) !== 'undefined' ? relationDescriptions.get(quad.subject.value)! : {}; /* eslint-disable prefer-rest-params */ diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index 3c06c92d9..a77f4d613 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -1,6 +1,6 @@ import { KeysInitQuery } from '@comunica/context-entries'; import { ActionContext } from '@comunica/core'; -import type { INode } from '@comunica/types-link-traversal'; +import type { ITreeNode } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; @@ -21,7 +21,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [ { @@ -38,7 +38,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); - const node: INode = { + const node: ITreeNode = { subject: treeSubject, }; @@ -50,7 +50,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [], }; @@ -62,7 +62,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [], }; @@ -74,7 +74,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [ { @@ -90,7 +90,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [], }; @@ -107,7 +107,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { it('should accept the relation when the filter respect the relation', async() => { const treeSubject = 'tree'; - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [ { @@ -194,7 +194,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { it('should not accept the relation when the filter is not respected by the relation', async() => { const treeSubject = 'tree'; - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [ { @@ -270,7 +270,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { it('should accept the relation when the query don\'t invoke the right path', async() => { const treeSubject = 'tree'; - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [ { @@ -391,7 +391,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const node: INode = { subject: 'foo' }; + const node: ITreeNode = { subject: 'foo' }; const result = await filterNode.run(node, context); expect(result).toStrictEqual( @@ -403,7 +403,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { async() => { const treeSubject = 'tree'; - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [ { @@ -516,7 +516,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { and a relation doesn't specify a path`, async() => { const treeSubject = 'tree'; - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [ { @@ -596,7 +596,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { it('should accept the relation when the filter argument are not related to the query', async() => { const treeSubject = 'tree'; - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [ { @@ -672,7 +672,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { it('should accept the relation when there is multiples filters and one is not relevant', async() => { const treeSubject = 'tree'; - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [ { @@ -781,7 +781,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { it('should accept the relation when the filter compare two constants', async() => { const treeSubject = 'tree'; - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [ { @@ -900,7 +900,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { it('should return an empty filter map if there is bgp of lenght 0', async() => { const treeSubject = 'tree'; - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [ { @@ -974,7 +974,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { it('should return an empty filter map if there is no bgp', async() => { const treeSubject = 'tree'; - const node: INode = { + const node: ITreeNode = { subject: treeSubject, relation: [ { diff --git a/packages/types-link-traversal/lib/TreeMetadata.ts b/packages/types-link-traversal/lib/TreeMetadata.ts index ffa7b8010..8f9ef27fb 100644 --- a/packages/types-link-traversal/lib/TreeMetadata.ts +++ b/packages/types-link-traversal/lib/TreeMetadata.ts @@ -26,12 +26,12 @@ export enum TreeNodes { RemainingItems = 'https://w3id.org/tree#remainingItems' } -export interface INode { - relation?: IRelation[]; +export interface ITreeNode { + relation?: ITreeRelation[]; subject: string; } -export interface IRelation { +export interface ITreeRelation { '@type'?: { value: string; quad: RDF.Quad; @@ -52,7 +52,7 @@ export interface IRelation { } // An helper to build the relation from a stream -export interface IRelationDescription { +export interface ITreeRelationDescription { subject?: [string, RDF.Quad]; value?: any; operator?: [RelationOperator, RDF.Quad]; From b0ec21ad2b341d01b3a9189ebbd8777d1742d869 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 16 Nov 2022 10:18:01 +0100 Subject: [PATCH 048/189] new tests for nested query and construct query --- .../lib/FilterNode.ts | 38 ++- .../test/FilterNode-test.ts | 282 +++++++++++++++++- 2 files changed, 305 insertions(+), 15 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 7808b44a6..e0cbcadeb 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -151,28 +151,50 @@ export class FilterNode { } private static findBgp(query: Algebra.Operation): RDF.Quad[] { - let currentNode = query.input; + let currentNode: any = query; + let bgp: RDF.Quad[] = []; do { if (currentNode.type === Algebra.types.JOIN) { - return this.formatBgp(currentNode.input); + const currentBgp = this.formatBgp(currentNode.input); + bgp = this.appendBgp(bgp, currentBgp); + } else if (currentNode.type === Algebra.types.CONSTRUCT && + 'template' in currentNode) { + // When it's a contruct query the where state + const currentBgp = this.formatBgp(currentNode.template); + bgp = this.appendBgp(bgp, currentBgp); } + if ('input' in currentNode) { currentNode = currentNode.input; } + + if (Array.isArray(currentNode)) { + for (const node of currentNode) { + if ('input' in node) { + currentNode = node.input; + } + } + } } while ('input' in currentNode); - return []; + return bgp; } private static formatBgp(joins: any): RDF.Quad[] { - const bgp: RDF.Quad[] = []; + const bgp = []; if (joins.length === 0) { return []; } - if (!('input' in joins[0])) { - return joins; - } for (const join of joins) { - bgp.push(join.input[0]); + if (!('input' in join)) { + bgp.push(join); + } + } + return bgp; + } + + private static appendBgp(bgp: RDF.Quad[], currentBgp: RDF.Quad[]): RDF.Quad[] { + if (Array.isArray(currentBgp)) { + bgp = bgp.concat(currentBgp); } return bgp; } diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index a77f4d613..bb599682f 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -126,17 +126,12 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ], }; - const bgp = ([ + const bgp = [ DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), DF.quad(DF.namedNode('ex:bar'), DF.namedNode('ex:p2'), DF.namedNode('ex:o2')), DF.quad(DF.namedNode('ex:too'), DF.namedNode('ex:p3'), DF.namedNode('ex:o3')), - ]).map(quad => { - return { - input: [ quad ], - type: Algebra.types.JOIN, - }; - }); + ]; const filterExpression = { expressionType: Algebra.expressionTypes.OPERATOR, operator: '=', @@ -1038,6 +1033,279 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { new Map(), ); }); + + it('should accept the relation when the filter respect the relation with a construct query', async() => { + const treeSubject = 'tree'; + + const node: ITreeNode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, + }, + ], + }; + + const bgp = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), + DF.quad(DF.namedNode('ex:bar'), DF.namedNode('ex:p2'), DF.namedNode('ex:o2')), + DF.quad(DF.namedNode('ex:too'), DF.namedNode('ex:p3'), DF.namedNode('ex:o3')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + + const query = { + type: Algebra.types.CONSTRUCT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: Algebra.types.JOIN, + input: bgp, + }, + }, + }, + template: bgp, + }; + + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const result = await filterNode.run(node, context); + + expect(result).toStrictEqual( + new Map([[ 'http://bar.com', true ]]), + ); + }); + + it('should accept the relation when the filter respect the relation with a nestedquery', async() => { + const treeSubject = 'tree'; + + const node: ITreeNode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, + }, + ], + }; + + const query = { + type: 'project', + input: { + type: 'filter', + input: { + type: 'join', + input: [ + { + type: 'project', + input: { + type: 'project', + input: { + type: 'join', + input: [ + { + termType: 'Quad', + value: '', + subject: { + termType: 'Variable', + value: 's', + }, + predicate: { + termType: 'NamedNode', + value: 'http://semweb.mmlab.be/ns/linkedconnections#departureTime', + }, + object: { + termType: 'Variable', + value: 'date', + }, + graph: { + termType: 'DefaultGraph', + value: '', + }, + type: 'pattern', + }, + ], + }, + variables: [ + { + termType: 'Variable', + value: 's', + }, + ], + }, + variables: [ + { + termType: 'Variable', + value: 's', + }, + ], + }, + { + termType: 'Quad', + value: '', + subject: { + termType: 'Variable', + value: 's', + }, + predicate: { + termType: 'NamedNode', + value: 'http://semweb.mmlab.be/ns/linkedconnections#departureStop', + }, + object: { + termType: 'Variable', + value: 'o', + }, + graph: { + termType: 'DefaultGraph', + value: '', + }, + type: 'pattern', + }, + ], + }, + expression: { + type: 'expression', + expressionType: 'operator', + operator: '&&', + args: [ + { + type: 'expression', + expressionType: 'operator', + operator: '>=', + args: [ + { + type: 'expression', + expressionType: 'term', + term: { + termType: 'Variable', + value: 'date', + }, + }, + { + type: 'expression', + expressionType: 'term', + term: { + termType: 'Literal', + value: '2022-11-08T08:00:00.000Z', + language: '', + datatype: { + termType: 'NamedNode', + value: 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + }, + }, + ], + }, + { + type: 'expression', + expressionType: 'operator', + operator: '=', + args: [ + { + type: 'expression', + expressionType: 'operator', + operator: 'str', + args: [ + { + type: 'expression', + expressionType: 'term', + term: { + termType: 'Variable', + value: 'o', + }, + }, + ], + }, + { + type: 'expression', + expressionType: 'term', + term: { + termType: 'Literal', + value: 'http://irail.be/stations/NMBS/008812146', + language: '', + datatype: { + termType: 'NamedNode', + value: 'http://www.w3.org/2001/XMLSchema#string', + }, + }, + }, + ], + }, + ], + }, + }, + variables: [ + { + termType: 'Variable', + value: 'o', + }, + { + termType: 'Variable', + value: 's', + }, + ], + }; + + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const result = await filterNode.run(node, context); + + expect(result).toStrictEqual( + new Map([[ 'http://bar.com', true ]]), + ); + }); }); }); }); From 83a71bb2738398bb67951ee1cd5b80ef9a40130a Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 16 Nov 2022 10:48:45 +0100 Subject: [PATCH 049/189] documentation of the tree metadata --- .../types-link-traversal/lib/TreeMetadata.ts | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/types-link-traversal/lib/TreeMetadata.ts b/packages/types-link-traversal/lib/TreeMetadata.ts index 8f9ef27fb..d4ed9bd8a 100644 --- a/packages/types-link-traversal/lib/TreeMetadata.ts +++ b/packages/types-link-traversal/lib/TreeMetadata.ts @@ -5,56 +5,92 @@ import type * as RDF from 'rdf-js'; +// Reference +// https://treecg.github.io/specification/#vocabulary export enum RelationOperator { + // All elements in the related node have this prefix PrefixRelation = 'https://w3id.org/tree#PrefixRelation', + // All elements in the related node have this substring SubstringRelation = 'https://w3id.org/tree#SubstringRelation', + // All members of this related node end with this suffix + SuffixRelation = 'https://w3id.org/tree#SuffixRelation', + // The related Node’s members are greater than the value. For string comparison, + // this relation can refer to a comparison configuration GreaterThanRelation = 'https://w3id.org/tree#GreaterThanRelation', + // Similar to GreaterThanRelation GreaterThanOrEqualToRelation = 'https://w3id.org/tree#GreaterThanOrEqualToRelation', + // Similar to GreaterThanRelation LessThanRelation = 'https://w3id.org/tree#LessThanRelation', + // Similar to GreaterThanRelation LessThanOrEqualToRelation = 'https://w3id.org/tree#LessThanOrEqualToRelation', + // Similar to GreaterThanRelation EqualThanRelation = 'https://w3id.org/tree#EqualThanRelation', + // A contains b iff no points of b lie in the exterior of a, and at least one point + // of the interior of b lies in the interior of a + // reference http://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html GeospatiallyContainsRelation = 'https://w3id.org/tree#GeospatiallyContainsRelation', - InBetweenRelation = 'https://w3id.org/tree#InBetweenRelation', } +// Reference +// https://treecg.github.io/specification/#classes +// https://treecg.github.io/specification/#properties export enum TreeNodes { + // A tree:Node is a node that may contain links to other dereferenceable resources + // that lead to a full overview of a tree:Collection. Node = 'https://w3id.org/tree#node', + // An entity that describes a relation between two tree:Nodes. Relation = 'https://w3id.org/tree#relation', + // The relation operator type describe by the enum RelationOperator RDFTypeNode = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', + // A property path, as defined by SHACL, that indicates what resource the tree:value affects. + // reference SHACL: https://www.w3.org/TR/shacl/ Path = 'https://w3id.org/tree#path', + // The contextual value of this node Value = 'https://w3id.org/tree#value', + // Remaining number of items of this node, the items in its children included. RemainingItems = 'https://w3id.org/tree#remainingItems' } export interface ITreeNode { + // Relation of the node relation?: ITreeRelation[]; + // Name/URL of the node subject: string; } export interface ITreeRelation { + // The relation operator type describe by the enum RelationOperator '@type'?: { value: string; quad: RDF.Quad; }; - 'remainingItems'?: { + // Refer to the TreeNodes of the similar name + remainingItems?: { value: number; quad: RDF.Quad; }; - 'path'?: { + // Refer to the TreeNodes of the similar name + path?: { value: string; quad: RDF.Quad; }; - 'value'?: { + // Refer to the TreeNodes of the similar name + value?: { value: any; quad: RDF.Quad; }; - 'node': string ; + // The URL to be derefenced when this relation cannot be pruned. + node: string ; } // An helper to build the relation from a stream export interface ITreeRelationDescription { + // Id of the blank node of the relation subject?: [string, RDF.Quad]; + // Refer to the TreeNodes of the similar name value?: any; + // The relation operator type describe by the enum RelationOperator operator?: [RelationOperator, RDF.Quad]; + // Refer to the TreeNodes of the similar name remainingItems?: [number, RDF.Quad]; } From 31b7e712a87898be2b6183054638bc88ed8a9b40 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 16 Nov 2022 10:49:44 +0100 Subject: [PATCH 050/189] added space deleted --- packages/context-entries-link-traversal/lib/Keys.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/context-entries-link-traversal/lib/Keys.ts b/packages/context-entries-link-traversal/lib/Keys.ts index c095f08c2..99f7b319b 100644 --- a/packages/context-entries-link-traversal/lib/Keys.ts +++ b/packages/context-entries-link-traversal/lib/Keys.ts @@ -1,5 +1,6 @@ import { ActionContextKey } from '@comunica/core'; import type { AnnotateSourcesType } from '@comunica/types-link-traversal'; + /** * When adding entries to this file, also add a shortcut for them in the contextKeyShortcuts TSDoc comment in * ActorIniQueryBase in @comunica/actor-init-query if it makes sense to use this entry externally. From a79e8a7d6db718247dce0c4c9ce631040c0ff39b Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 16 Nov 2022 10:59:38 +0100 Subject: [PATCH 051/189] better documentation for TREE link extractor --- .../lib/ActorExtractLinksTree.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 4afe8a37c..ae598f942 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -46,21 +46,22 @@ export class ActorExtractLinksTree extends ActorExtractLinks { // Resolve to discovered links metadata.on('end', async() => { - // Validate if the node forward have the current node as implicit subject - for (const [ nodeValue, link ] of nodeLinks) { - if (pageRelationNodes.has(nodeValue)) { - const relationDescription = relationDescriptions.get(nodeValue); - if (typeof relationDescription !== 'undefined') { - relations.push(collectRelation(relationDescription, link)); - } else { - relations.push(collectRelation({}, link)); - } + // Validate if the potential relation node are linked with the current page + // and add the relation description if it is connected + for (const [ blankNodeId, link ] of nodeLinks) { + // Check if the blank node id is the object of a relation of the current page + if (pageRelationNodes.has(blankNodeId)) { + const relationDescription = relationDescriptions.get(blankNodeId); + // Add the relation to the relation array + relations.push(collectRelation(relationDescription || {}, link)); } } + // Create a ITreeNode object const node: ITreeNode = { relation: relations, subject: currentNodeUrl }; let acceptedRelation = relations; + // Filter the relation based on the query const filters = await this.applyFilter(node, action.context); acceptedRelation = this.handleFilter(filters, acceptedRelation); resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); From b093a184e3577eb1c6933b0f6cd2bd535cdef0aa Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 16 Nov 2022 14:58:19 +0100 Subject: [PATCH 052/189] documentation added to the filter node class --- .../lib/FilterNode.ts | 86 +++++++++++++------ 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index e0cbcadeb..1c172a133 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -8,10 +8,10 @@ import { AsyncEvaluator } from 'sparqlee'; const BF = new BindingsFactory(); /** - * A comunica Link traversal optimizer that filter link of document - * following the [TREE specification](https://treecg.github.io/specification/). - * The actor apply the filter of a query into the TREE relation to determine if a - * link should be follow or not + * A class to apply [SPAQL filters](https://www.w3.org/TR/sparql11-query/#evaluation) + * to the [TREE specification](https://treecg.github.io/specification/). + * It use [sparqlee](https://github.com/comunica/sparqlee) to evaluate the filter where + * the binding are remplace by the [value of TREE relation](https://treecg.github.io/specification/#traversing) */ export class FilterNode { public test(node: ITreeNode, context: IActionContext): boolean { @@ -49,24 +49,29 @@ export class FilterNode { if (queryBody.length === 0) { return new Map(); } - // Capture the relation from the input + // Capture the relation from the function argument const relations: ITreeRelation[] = node.relation!; for (const relation of relations) { + // Accept the relation if the relation does't specify a condition if (typeof relation.path === 'undefined' || typeof relation.value === 'undefined') { filterMap.set(relation.node, true); continue; } // Find the quad from the bgp that are related to the TREE relation - const relevantQuads = FilterNode.findRelavantQuad(queryBody, relation.path.value); + const relevantQuads = FilterNode.findRelevantQuad(queryBody, relation.path.value); + + // Accept the relation if no quad are linked with the relation if (relevantQuads.length === 0) { filterMap.set(relation.node, true); continue; } - // Create the binding in relation to the relevant quad + // Create the binding from the relevant quad in association with the TREE relation const bindings = FilterNode.createBinding(relevantQuads, relation.value.quad); - const filterExpression: Algebra.Operation = FilterNode.deleteUnrelevantFilter(filterOperation, bindings); + const filterExpression: Algebra.Operation = FilterNode.generateTreeRelationFilter(filterOperation, bindings); + + // Accept the relation if no filter are associated with the relation if (filterExpression.args.length === 0) { filterMap.set(relation.node, true); continue; @@ -80,13 +85,13 @@ export class FilterNode { } /** - * - * @param queryBody - * @param path - * @returns RDF.Quad[] - * find the quad that has as predicate a the TREE:path of a relation + * Find the quad that match the predicate defined by the TREE:path from a TREE relation. + * The subject can be anyting. + * @param {RDF.Quad[]} queryBody - the body of the query + * @param {string} path - TREE path + * @returns {RDF.Quad[]} the quad that contain the TREE path as predicate and a variable as object */ - private static findRelavantQuad(queryBody: RDF.Quad[], path: string): RDF.Quad[] { + private static findRelevantQuad(queryBody: RDF.Quad[], path: string): RDF.Quad[] { const resp: RDF.Quad[] = []; for (const quad of queryBody) { if (quad.predicate.value === path && quad.object.termType === 'Variable') { @@ -97,33 +102,41 @@ export class FilterNode { } /** - * @param filterExpression - * @param binding - * @returns Algebra.Expression - * delete the filters that are not related to TREE relation + * Delete the filters that are not related to the TREE relation + * @param {Algebra.Expression} filterExpression - the expression of the filter taken from the original query + * @param {Bindings} binding - binding that the resulting filter should contain + * @returns {Algebra.Expression} the resulting filter expression contain only filter related to the TREE relation */ - private static deleteUnrelevantFilter(filterExpression: Algebra.Expression, binding: Bindings): Algebra.Expression { + private static generateTreeRelationFilter(filterExpression: Algebra.Expression, + binding: Bindings): Algebra.Expression { + // Generate an empty filter algebra let newFilterExpression: Algebra.Expression = { expressionType: Algebra.expressionTypes.OPERATOR, operator: filterExpression.operator, type: Algebra.types.EXPRESSION, args: [], }; - + // Check if there is one filter or multiple if ('operator' in filterExpression.args[0]) { + // Add the argument into the empty the new filter newFilterExpression.args = (filterExpression.args).filter(expression => { for (const arg of expression.args) { if ('term' in arg && arg.term.termType === 'Variable') { + // Check if the argument of the filter is present into the binding return binding.has(arg.term.value); } } return false; }); + + // If the filter has now a size of 1 change the form to respect the algebra specification if (newFilterExpression.args.length === 1) { newFilterExpression = newFilterExpression.args[0]; } } else { + // Add the argument into the empty the new filter for (const arg of (filterExpression.args)) { + // Check if the argument of the filter is present into the binding if ('term' in arg && arg.term.termType === 'Variable' && !binding.has(arg.term.value)) { newFilterExpression.args = []; break; @@ -135,17 +148,17 @@ export class FilterNode { } /** - * - * @param relevantQuad - * @param relationValue - * @returns Bindings - * create the binding from quad related to the TREE:path + * Create the binding from quad related to the TREE:path that will be used with sparqlee + * for filtering of relation + * @param {RDF.Quad[]} relevantQuad - the quads related to the TREE relation + * @param {RDF.Quad} relationValue - the quad related to the TREE path + * @returns {Bindings} the resulting binding */ private static createBinding(relevantQuad: RDF.Quad[], relationValue: RDF.Quad): Bindings { let binding: Bindings = BF.bindings(); for (const quad of relevantQuad) { const object = quad.object.value; - binding = binding.set(object, relationValue.object); + binding = binding.set(object, relationValue.object); } return binding; } @@ -179,6 +192,11 @@ export class FilterNode { return bgp; } + /** + * Format the section of the algebra graph contain a part of the bgp into an array of quad + * @param {any} joins - the join operation containing a part of the bgp + * @returns {RDF.Quad[]} the bgp in the form of an array of quad + */ private static formatBgp(joins: any): RDF.Quad[] { const bgp = []; if (joins.length === 0) { @@ -192,6 +210,12 @@ export class FilterNode { return bgp; } + /** + * Append the bgp of the query + * @param {RDF.Quad[]} bgp - the whole bgp + * @param {RDF.Quad[]} currentBgp - the bgp collected in the current node + * @returns {RDF.Quad[]} the bgp updated + */ private static appendBgp(bgp: RDF.Quad[], currentBgp: RDF.Quad[]): RDF.Quad[] { if (Array.isArray(currentBgp)) { bgp = bgp.concat(currentBgp); @@ -199,10 +223,16 @@ export class FilterNode { return bgp; } - private static doesNodeExist(query: Algebra.Operation, node: string): boolean { + /** + * Check if a specific node type exist inside the query + * @param {Algebra.Operation} query - the original query + * @param {string} nodeType - the tyoe of node requested + * @returns {boolean} + */ + private static doesNodeExist(query: Algebra.Operation, nodeType: string): boolean { let currentNode = query; do { - if (currentNode.type === node) { + if (currentNode.type === nodeType) { return true; } /* istanbul ignore next */ From c6bcd8225ea343f86a8f6b8b9ddb732fb471d4d9 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 16 Nov 2022 15:08:41 +0100 Subject: [PATCH 053/189] documentation updated for the getTreeQuadsRawRelations function --- .../lib/ActorExtractLinksTree.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index ae598f942..81f734b2c 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -85,11 +85,13 @@ export class ActorExtractLinksTree extends ActorExtractLinks { * A helper function to find all the relations of a TREE document and the possible next nodes to visit. * The next nodes are not guaranteed to have as subject the URL of the current page, * so filtering is necessary afterward. - * @param quad the current quad. - * @param url url of the page - * @param pageRelationNodes the url of the relation node of the page that have as subject the URL of the page - * @param nodeLinks the url of the next potential page that has to be visited, + * @param {RDF.Quad} quad - the current quad. + * @param {string} url - url of the page + * @param {Set} pageRelationNodes - the url of the relation node of the page that have as subject the URL of the page + * @param {[string, string][]} - nodeLinks the url of the next potential page that has to be visited, * regardless if the implicit subject is the node of the page + * @param {Map} relationDescriptions - a map where the key is the id of the blank node + * associated with the description of a relation */ private getTreeQuadsRawRelations( quad: RDF.Quad, From fe5cb9043318e1498fece053a2958b37af36858e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 16 Nov 2022 15:09:19 +0100 Subject: [PATCH 054/189] lint-fix --- .../lib/ActorExtractLinksTree.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 81f734b2c..894b7bbb8 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -87,11 +87,12 @@ export class ActorExtractLinksTree extends ActorExtractLinks { * so filtering is necessary afterward. * @param {RDF.Quad} quad - the current quad. * @param {string} url - url of the page - * @param {Set} pageRelationNodes - the url of the relation node of the page that have as subject the URL of the page + * @param {Set} pageRelationNodes - the url of the relation node of the page + * that have as subject the URL of the page * @param {[string, string][]} - nodeLinks the url of the next potential page that has to be visited, * regardless if the implicit subject is the node of the page - * @param {Map} relationDescriptions - a map where the key is the id of the blank node - * associated with the description of a relation + * @param {Map} relationDescriptions - a map where the key is the + * id of the blank node associated with the description of a relation */ private getTreeQuadsRawRelations( quad: RDF.Quad, From 6cab24420aa63c197b3ddd5570b71da01dc355c6 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 16 Nov 2022 15:13:14 +0100 Subject: [PATCH 055/189] documentation of the function findBgp --- packages/actor-extract-links-extract-tree/lib/FilterNode.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 1c172a133..d316182cd 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -163,6 +163,11 @@ export class FilterNode { return binding; } + /** + * Find the bgp of the original query of the user + * @param {Algebra.Operation} query - the original query + * @returns { RDF.Quad[]} the bgp of the query + */ private static findBgp(query: Algebra.Operation): RDF.Quad[] { let currentNode: any = query; let bgp: RDF.Quad[] = []; From 1d1cba637a42947a4a95740c280e50b97d3b5973 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 17 Nov 2022 08:08:18 +0100 Subject: [PATCH 056/189] whole doesNodeExist method coverded by the test --- packages/actor-extract-links-extract-tree/lib/FilterNode.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index d316182cd..c7f8db0c0 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -240,11 +240,9 @@ export class FilterNode { if (currentNode.type === nodeType) { return true; } - /* istanbul ignore next */ if ('input' in currentNode) { currentNode = currentNode.input; } - /* istanbul ignore next */ } while ('input' in currentNode); return false; } From 3f2508932c416c456b6baff5788fcab147b2e045 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 17 Nov 2022 08:56:53 +0100 Subject: [PATCH 057/189] test for the method applyFilter added --- .../lib/ActorExtractLinksTree.ts | 2 - .../test/ActorExtractLinksTree-test.ts | 280 +++++++++++++++++- 2 files changed, 277 insertions(+), 5 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 894b7bbb8..f9129130c 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -69,11 +69,9 @@ export class ActorExtractLinksTree extends ActorExtractLinks { }); } - /* istanbul ignore next */ public async applyFilter(node: ITreeNode, context: IActionContext): Promise> { return await new FilterNode().run(node, context); } - /* istanbul ignore next */ private handleFilter(filters: Map, acceptedRelation: ITreeRelation[]): ITreeRelation[] { return filters.size > 0 ? diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 54963a0af..8df51ac22 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -1,9 +1,10 @@ -import { KeysRdfResolveQuadPattern } from '@comunica/context-entries'; +import { KeysRdfResolveQuadPattern, KeysInitQuery } from '@comunica/context-entries'; import { ActionContext, Bus } from '@comunica/core'; -import type { IRelation } from '@comunica/types-link-traversal'; +import type { ITreeRelation } from '@comunica/types-link-traversal'; import { RelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; +import { Algebra } from 'sparqlalgebrajs'; import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; const stream = require('streamify-array'); @@ -263,7 +264,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.namedNode('ex:gx')), ]); const action = { url: treeUrl, metadata: input, requestTime: 0, context }; - const relations: IRelation[] = [ + const relations: ITreeRelation[] = [ { node: prunedUrl, remainingItems: { @@ -304,6 +305,279 @@ describe('ActorExtractLinksExtractLinksTree', () => { const result = await actorWithCustomFilter.run(action); expect(result).toEqual({ links: [{ url: expectedUrl }]}); }); + + it('should return the links of a TREE with one relation when using the real FilterNode class', async() => { + const expectedUrl = 'http://foo.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + ]); + + const bgp = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), + DF.quad(DF.namedNode('ex:bar'), DF.namedNode('ex:p2'), DF.namedNode('ex:o2')), + DF.quad(DF.namedNode('ex:too'), DF.namedNode('ex:p3'), DF.namedNode('ex:o3')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: Algebra.types.JOIN, + input: bgp, + }, + }, + }, + }; + const contextWithQuery = new ActionContext({ + [KeysRdfResolveQuadPattern.source.name]: treeUrl, + [KeysInitQuery.query.name]: query, + }); + + const actorWithFilterNodeClass = new ActorExtractLinksTree( + { name: 'actor', bus }, + ); + const action = { url: treeUrl, metadata: input, requestTime: 0, context: contextWithQuery }; + const result = await actorWithFilterNodeClass.run(action); + + expect(result).toEqual({ links: [{ url: expectedUrl }]}); + }); + + it('should return the links of a TREE with one with a path relation when using the real FilterNode class', + async() => { + const expectedUrl = 'http://foo.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#path'), + DF.literal('ex:path'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#value'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + DF.namedNode('ex:gx')), + + ]); + + const bgp = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), + DF.quad(DF.namedNode('ex:bar'), DF.namedNode('ex:p2'), DF.namedNode('ex:o2')), + DF.quad(DF.namedNode('ex:too'), DF.namedNode('ex:p3'), DF.namedNode('ex:o3')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: Algebra.types.JOIN, + input: bgp, + }, + }, + }, + }; + const contextWithQuery = new ActionContext({ + [KeysRdfResolveQuadPattern.source.name]: treeUrl, + [KeysInitQuery.query.name]: query, + }); + + const actorWithFilterNodeClass = new ActorExtractLinksTree( + { name: 'actor', bus }, + ); + const action = { url: treeUrl, metadata: input, requestTime: 0, context: contextWithQuery }; + const result = await actorWithFilterNodeClass.run(action); + + expect(result).toEqual({ links: [{ url: expectedUrl }]}); + }); + + it('should return no link when it does\'t respect the filter using the real FilterNode class', async() => { + const expectedUrl = 'http://foo.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), + + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#path'), + DF.literal('ex:path'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#value'), + DF.literal('500', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + DF.namedNode('ex:gx')), + + ]); + + const bgp = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), + DF.quad(DF.namedNode('ex:bar'), DF.namedNode('ex:p2'), DF.namedNode('ex:o2')), + DF.quad(DF.namedNode('ex:too'), DF.namedNode('ex:p3'), DF.namedNode('ex:o3')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, + }, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + + const query = { + type: Algebra.types.PROJECT, + input: { + type: Algebra.types.FILTER, + expression: filterExpression, + input: { + input: { + type: Algebra.types.JOIN, + input: bgp, + }, + }, + }, + }; + const contextWithQuery = new ActionContext({ + [KeysRdfResolveQuadPattern.source.name]: treeUrl, + [KeysInitQuery.query.name]: query, + }); + + const actorWithFilterNodeClass = new ActorExtractLinksTree( + { name: 'actor', bus }, + ); + const action = { url: treeUrl, metadata: input, requestTime: 0, context: contextWithQuery }; + const result = await actorWithFilterNodeClass.run(action); + + expect(result).toEqual({ links: []}); + }); }); describe('The ActorExtractLinksExtractLinksTree test method', () => { From 54a956e772f2dab83f33fa4b87ad87abb5f5eac8 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 17 Nov 2022 09:07:16 +0100 Subject: [PATCH 058/189] more documentation for the TREE extractor actor --- .../lib/ActorExtractLinksTree.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index f9129130c..fcc6ae38e 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -69,10 +69,20 @@ export class ActorExtractLinksTree extends ActorExtractLinks { }); } + /** + * @param {ITreeNode} node - TREE metadata + * @param {IActionContext} context - context of the action; containing the query + * @returns {Promise>} a map containing the filter + */ public async applyFilter(node: ITreeNode, context: IActionContext): Promise> { return await new FilterNode().run(node, context); } + /** + * @param { Map} filters + * @param {ITreeRelation[]} acceptedRelation - the current accepted relation + * @returns {ITreeRelation[]} the relation when the nodes has been filtered + */ private handleFilter(filters: Map, acceptedRelation: ITreeRelation[]): ITreeRelation[] { return filters.size > 0 ? acceptedRelation.filter(relation => filters?.get(relation.node)) : From d165b23b392555385c67cda2b47e7464ff6e7b4f Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 17 Nov 2022 09:43:30 +0100 Subject: [PATCH 059/189] generateTreeRelationFilter newFilterExpression build with the Algebra factory --- .../lib/FilterNode.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index c7f8db0c0..eff3db322 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -3,10 +3,12 @@ import { KeysInitQuery } from '@comunica/context-entries'; import type { Bindings, IActionContext } from '@comunica/types'; import type { ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; -import { Algebra } from 'sparqlalgebrajs'; +import { Algebra, Factory as AlgebraFactory } from 'sparqlalgebrajs'; import { AsyncEvaluator } from 'sparqlee'; +const AF = new AlgebraFactory(); const BF = new BindingsFactory(); + /** * A class to apply [SPAQL filters](https://www.w3.org/TR/sparql11-query/#evaluation) * to the [TREE specification](https://treecg.github.io/specification/). @@ -110,12 +112,8 @@ export class FilterNode { private static generateTreeRelationFilter(filterExpression: Algebra.Expression, binding: Bindings): Algebra.Expression { // Generate an empty filter algebra - let newFilterExpression: Algebra.Expression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: filterExpression.operator, - type: Algebra.types.EXPRESSION, - args: [], - }; + let newFilterExpression: Algebra.Expression = AF.createOperatorExpression(filterExpression.operator, []); + // Check if there is one filter or multiple if ('operator' in filterExpression.args[0]) { // Add the argument into the empty the new filter From 030bce07a7795a61d799ee3bdee2ae72b679146e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 17 Nov 2022 10:27:48 +0100 Subject: [PATCH 060/189] unit test description rework for the FilterNode and the ActorExtractLinkTree and some useless test are deleted --- .../test/ActorExtractLinksTree-test.ts | 165 ++++++++--------- .../test/FilterNode-test.ts | 168 ++++++++---------- 2 files changed, 155 insertions(+), 178 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 8df51ac22..318f742aa 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -47,7 +47,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { jest.spyOn(actor, 'applyFilter').mockReturnValue(Promise.resolve(new Map())); }); - it('should return the links of a TREE with one relation', async() => { + it('should return the link of a TREE with one relation', async() => { const expectedUrl = 'http://foo.com'; const input = stream([ DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), @@ -390,7 +390,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toEqual({ links: [{ url: expectedUrl }]}); }); - it('should return the links of a TREE with one with a path relation when using the real FilterNode class', + it('should return the links of a TREE with one relation with a path when using the real FilterNode class', async() => { const expectedUrl = 'http://foo.com'; const input = stream([ @@ -485,99 +485,100 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toEqual({ links: [{ url: expectedUrl }]}); }); - it('should return no link when it does\'t respect the filter using the real FilterNode class', async() => { - const expectedUrl = 'http://foo.com'; - const input = stream([ - DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#foo'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.namedNode(treeUrl), - DF.namedNode('https://w3id.org/tree#relation'), - DF.blankNode('_:_g1'), - DF.namedNode('ex:gx')), + it('should return no link when the relation doesn\'t respect the filter when using the real FilterNode class', + async() => { + const expectedUrl = 'http://foo.com'; + const input = stream([ + DF.quad(DF.namedNode(treeUrl), DF.namedNode('ex:p'), DF.namedNode('ex:o'), DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#foo'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.namedNode(treeUrl), + DF.namedNode('https://w3id.org/tree#relation'), + DF.blankNode('_:_g1'), + DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#node'), - DF.literal(expectedUrl), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#path'), - DF.literal('ex:path'), - DF.namedNode('ex:gx')), - DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#value'), - DF.literal('500', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#node'), + DF.literal(expectedUrl), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#path'), + DF.literal('ex:path'), + DF.namedNode('ex:gx')), + DF.quad(DF.blankNode('_:_g1'), + DF.namedNode('https://w3id.org/tree#value'), + DF.literal('500', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + DF.namedNode('ex:gx')), - ]); + ]); - const bgp = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), - DF.quad(DF.namedNode('ex:bar'), DF.namedNode('ex:p2'), DF.namedNode('ex:o2')), - DF.quad(DF.namedNode('ex:too'), DF.namedNode('ex:p3'), DF.namedNode('ex:o3')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', + const bgp = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), + DF.quad(DF.namedNode('ex:bar'), DF.namedNode('ex:p2'), DF.namedNode('ex:o2')), + DF.quad(DF.namedNode('ex:too'), DF.namedNode('ex:p3'), DF.namedNode('ex:o3')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', + }, }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, }, }, - }, - ], - }; + ], + }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, + const query = { + type: Algebra.types.PROJECT, input: { + type: Algebra.types.FILTER, + expression: filterExpression, input: { - type: Algebra.types.JOIN, - input: bgp, + input: { + type: Algebra.types.JOIN, + input: bgp, + }, }, }, - }, - }; - const contextWithQuery = new ActionContext({ - [KeysRdfResolveQuadPattern.source.name]: treeUrl, - [KeysInitQuery.query.name]: query, - }); + }; + const contextWithQuery = new ActionContext({ + [KeysRdfResolveQuadPattern.source.name]: treeUrl, + [KeysInitQuery.query.name]: query, + }); - const actorWithFilterNodeClass = new ActorExtractLinksTree( - { name: 'actor', bus }, - ); - const action = { url: treeUrl, metadata: input, requestTime: 0, context: contextWithQuery }; - const result = await actorWithFilterNodeClass.run(action); + const actorWithFilterNodeClass = new ActorExtractLinksTree( + { name: 'actor', bus }, + ); + const action = { url: treeUrl, metadata: input, requestTime: 0, context: contextWithQuery }; + const result = await actorWithFilterNodeClass.run(action); - expect(result).toEqual({ links: []}); - }); + expect(result).toEqual({ links: []}); + }); }); describe('The ActorExtractLinksExtractLinksTree test method', () => { diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index bb599682f..026277ffb 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -46,7 +46,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(response).toBe(false); }); - it('should not test when there is no relations and a filter operation in the query', async() => { + it('should not test when there is a filter operation in the query but no TREE relations', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -58,19 +58,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(response).toBe(false); }); - it('should not test when there is no tree metadata and a filter operation in the query', async() => { - const context = new ActionContext({ - [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, - }); - const node: ITreeNode = { - subject: treeSubject, - relation: [], - }; - const response = filterNode.test(node, context); - expect(response).toBe(false); - }); - - it('should no test when there no filter operation in the query', async() => { + it('should no test when there are no filter operation in the query but a TREE relation', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); @@ -85,18 +73,6 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const response = filterNode.test(node, context); expect(response).toBe(false); }); - - it('should no test when there is no filter operation in the query and no TREE relation', async() => { - const context = new ActionContext({ - [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, - }); - const node: ITreeNode = { - subject: treeSubject, - relation: [], - }; - const response = filterNode.test(node, context); - expect(response).toBe(false); - }); }); describe('run method', () => { @@ -507,86 +483,86 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it(`should accept the relations when the filter respect the relation - and a relation doesn't specify a path`, async() => { - const treeSubject = 'tree'; + it('should accept the relations when one respect the filter and another has no path and value defined', + async() => { + const treeSubject = 'tree'; - const node: ITreeNode = { - subject: treeSubject, - relation: [ - { - node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, - value: { - value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + const node: ITreeNode = { + subject: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: { + value: 'ex:path', + quad: aQuad, + }, + value: { + value: '5', + quad: DF.quad(DF.namedNode('ex:s'), + DF.namedNode('ex:p'), + DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + }, }, - }, - { - node: 'http://foo.com', - }, - ], - }; - const bgp: RDF.Quad[] = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', + { + node: 'http://foo.com', }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', + ], + }; + const bgp: RDF.Quad[] = [ + DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), + ]; + const filterExpression = { + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + type: Algebra.types.EXPRESSION, + args: [ + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Variable', + value: 'o', }, }, - }, - ], - }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, + { + expressionType: Algebra.expressionTypes.TERM, + type: Algebra.types.EXPRESSION, + term: { + termType: 'Literal', + langugage: '', + value: '5', + datatype: { + termType: 'namedNode', + value: 'http://www.w3.org/2001/XMLSchema#integer', + }, + }, + }, + ], + }; + const query = { + type: Algebra.types.PROJECT, input: { + type: Algebra.types.FILTER, + expression: filterExpression, input: { - type: Algebra.types.JOIN, - input: bgp, + input: { + type: Algebra.types.JOIN, + input: bgp, + }, }, }, - }, - }; - const context = new ActionContext({ - [KeysInitQuery.query.name]: query, - }); + }; + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); - const result = await filterNode.run(node, context); + const result = await filterNode.run(node, context); - expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ], [ 'http://foo.com', true ]]), - ); - }); + expect(result).toStrictEqual( + new Map([[ 'http://bar.com', true ], [ 'http://foo.com', true ]]), + ); + }); it('should accept the relation when the filter argument are not related to the query', async() => { const treeSubject = 'tree'; @@ -892,7 +868,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it('should return an empty filter map if there is bgp of lenght 0', async() => { + it('should return an empty filter map if the bgp if empty', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -1117,7 +1093,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it('should accept the relation when the filter respect the relation with a nestedquery', async() => { + it('should accept the relation when the filter respect the relation with a nested query', async() => { const treeSubject = 'tree'; const node: ITreeNode = { From e260e028f017046772d76b258c8a4d8fc38341d0 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 17 Nov 2022 11:11:22 +0100 Subject: [PATCH 061/189] documentation added for findBgp method --- packages/actor-extract-links-extract-tree/lib/FilterNode.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index eff3db322..eca870d52 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -183,11 +183,12 @@ export class FilterNode { if ('input' in currentNode) { currentNode = currentNode.input; } - + // If the node is an array if (Array.isArray(currentNode)) { for (const node of currentNode) { if ('input' in node) { currentNode = node.input; + break; } } } From f51c04127aa575b40adac95cb802737255e63550 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 18 Nov 2022 08:10:48 +0100 Subject: [PATCH 062/189] doesNodeExist method change for findNode --- .../lib/FilterNode.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index eca870d52..4fed07f1a 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -26,7 +26,7 @@ export class FilterNode { } const query: Algebra.Operation = context.get(KeysInitQuery.query)!; - if (!FilterNode.doesNodeExist(query, Algebra.types.FILTER)) { + if (!FilterNode.findNode(query, Algebra.types.FILTER)) { return false; } @@ -43,7 +43,7 @@ export class FilterNode { // Extract the filter expression const filterOperation: Algebra.Expression = (() => { const query: Algebra.Operation = context.get(KeysInitQuery.query)!; - return query.input.expression; + return FilterNode.findNode(query, Algebra.types.FILTER).expression; })(); // Extract the bgp of the query @@ -228,21 +228,22 @@ export class FilterNode { } /** - * Check if a specific node type exist inside the query + * Find the first node of type `nodeType`, if it doesn't exist + * it return undefined * @param {Algebra.Operation} query - the original query * @param {string} nodeType - the tyoe of node requested - * @returns {boolean} + * @returns {any} */ - private static doesNodeExist(query: Algebra.Operation, nodeType: string): boolean { + private static findNode(query: Algebra.Operation, nodeType: string): any { let currentNode = query; do { if (currentNode.type === nodeType) { - return true; + return currentNode; } if ('input' in currentNode) { currentNode = currentNode.input; } } while ('input' in currentNode); - return false; + return undefined; } } From 6ac1ab36f24b40f5b5cff1bd3a8130df0b93a86d Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Fri, 18 Nov 2022 12:55:18 +0100 Subject: [PATCH 063/189] WIP: Simplify tree logic --- .../lib/ActorExtractLinksTree.ts | 69 ++++++++++--------- .../lib/treeMetadataExtraction.ts | 69 +++++++++---------- .../package.json | 1 + .../types-link-traversal/lib/TreeMetadata.ts | 60 +++++++++++----- 4 files changed, 115 insertions(+), 84 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index fcc6ae38e..08c12b246 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -6,11 +6,12 @@ import { ActorExtractLinks } from '@comunica/bus-extract-links'; import type { IActorTest } from '@comunica/core'; import type { IActionContext } from '@comunica/types'; -import type { ITreeRelationDescription, ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; +import type { ITreeRelationRaw, ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; import { TreeNodes } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; +import { termToString } from 'rdf-string'; import { FilterNode } from './FilterNode'; -import { buildRelations, collectRelation } from './treeMetadataExtraction'; +import { buildRelations, materializeTreeRelation } from './treeMetadataExtraction'; /** * A comunica Extract Links Tree Extract Links Actor. @@ -27,38 +28,43 @@ export class ActorExtractLinksTree extends ActorExtractLinks { public async run(action: IActionExtractLinks): Promise { return new Promise((resolve, reject) => { const metadata = action.metadata; - const currentNodeUrl = action.url; - const pageRelationNodes: Set = new Set(); - const relationDescriptions: Map = new Map(); + const currentPageUrl = action.url; + // Identifiers of the relationships defined by the TREE document, represented as stringified RDF terms. + const relationIdentifiers: Set = new Set(); + // Maps relationship identifiers to their description. + // At this point, there's no guarantee yet that these relationships are linked to the current TREE document. + const relationDescriptions: Map = new Map(); const relations: ITreeRelation[] = []; + // An array of pairs of relationship identifiers and next page link to another TREE document, + // represented as stringified RDF terms. const nodeLinks: [string, string][] = []; // Forward errors metadata.on('error', reject); - // Invoke callback on each metadata quad + // Collect information about relationships spread over quads, so that we can accumulate them afterwards. metadata.on('data', (quad: RDF.Quad) => - this.getTreeQuadsRawRelations(quad, - currentNodeUrl, - pageRelationNodes, + this.interpretQuad(quad, + currentPageUrl, + relationIdentifiers, nodeLinks, relationDescriptions)); - // Resolve to discovered links + // Accumulate collected relationship information. metadata.on('end', async() => { // Validate if the potential relation node are linked with the current page - // and add the relation description if it is connected - for (const [ blankNodeId, link ] of nodeLinks) { - // Check if the blank node id is the object of a relation of the current page - if (pageRelationNodes.has(blankNodeId)) { - const relationDescription = relationDescriptions.get(blankNodeId); + // and add the relation description if it is connected. + for (const [ identifier, link ] of nodeLinks) { + // Check if the identifier is the object of a relation of the current page + if (relationIdentifiers.has(identifier)) { + const relationDescription = relationDescriptions.get(identifier); // Add the relation to the relation array - relations.push(collectRelation(relationDescription || {}, link)); + relations.push(materializeTreeRelation(relationDescription || {}, link)); } } // Create a ITreeNode object - const node: ITreeNode = { relation: relations, subject: currentNodeUrl }; + const node: ITreeNode = { relation: relations, identifier: currentPageUrl }; let acceptedRelation = relations; // Filter the relation based on the query @@ -93,28 +99,27 @@ export class ActorExtractLinksTree extends ActorExtractLinks { * A helper function to find all the relations of a TREE document and the possible next nodes to visit. * The next nodes are not guaranteed to have as subject the URL of the current page, * so filtering is necessary afterward. - * @param {RDF.Quad} quad - the current quad. - * @param {string} url - url of the page - * @param {Set} pageRelationNodes - the url of the relation node of the page - * that have as subject the URL of the page - * @param {[string, string][]} - nodeLinks the url of the next potential page that has to be visited, - * regardless if the implicit subject is the node of the page - * @param {Map} relationDescriptions - a map where the key is the - * id of the blank node associated with the description of a relation + * @param {RDF.Quad} quad - The current quad. + * @param {string} currentPageUrl - The url of the page. + * @param {Set} relationIdentifiers - Identifiers of the relationships defined by the TREE document, + * represented as stringified RDF terms. + * @param {[string, string][]} nodeLinks - An array of pairs of relationship identifiers and next page link to another + * TREE document, represented as stringified RDF terms. + * @param {Map} relationDescriptions - Maps relationship identifiers to their description. */ - private getTreeQuadsRawRelations( + private interpretQuad( quad: RDF.Quad, - url: string, - pageRelationNodes: Set, + currentPageUrl: string, + relationIdentifiers: Set, nodeLinks: [string, string][], - relationDescriptions: Map, + relationDescriptions: Map, ): void { // If it's a relation of the current node - if (quad.subject.value === url && quad.predicate.value === TreeNodes.Relation) { - pageRelationNodes.add(quad.object.value); + if (quad.subject.value === currentPageUrl && quad.predicate.value === TreeNodes.Relation) { + relationIdentifiers.add(termToString(quad.object)); // If it's a node forward } else if (quad.predicate.value === TreeNodes.Node) { - nodeLinks.push([ quad.subject.value, quad.object.value ]); + nodeLinks.push([ termToString(quad.subject), termToString(quad.object) ]); } buildRelations(relationDescriptions, quad); } diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts index 96e993cb3..d7ba08a24 100644 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -1,56 +1,57 @@ -import type { ITreeRelation, ITreeRelationDescription } from '@comunica/types-link-traversal'; -import { RelationOperator, TreeNodes } from '@comunica/types-link-traversal'; +import type { ITreeRelation, ITreeRelationRaw } from '@comunica/types-link-traversal'; +import { RelationOperator, RelationOperatorReversed, TreeNodes } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; /** - * @param relationDescription - * @param nodeLinks - * @returns IRelation - * collect the relevant values and quad capture from a IRelationDescription object - * to create a IRelation object + * Materialize a raw tree relation using the captured values. + * @param relationRaw Raw representation of a tree relation. + * @param nextLink Link to the next page. + * @returns ITreeRelation */ -export function collectRelation( - relationDescription: ITreeRelationDescription, - nodeLinks: string, +export function materializeTreeRelation( + relationRaw: ITreeRelationRaw, + nextLink: string, ): ITreeRelation { - const relation: ITreeRelation = { node: nodeLinks }; - if (relationDescription?.operator) { - relation['@type'] = { - value: relationDescription.operator[0], - quad: relationDescription.operator[1], + const relation: ITreeRelation = { node: nextLink }; + if (relationRaw?.operator) { + relation.type = { + value: relationRaw.operator[0], + quad: relationRaw.operator[1], }; } - if (relationDescription?.remainingItems) { + if (relationRaw?.remainingItems) { relation.remainingItems = { - value: relationDescription.remainingItems[0], - quad: relationDescription.remainingItems[1], + value: relationRaw.remainingItems[0], + quad: relationRaw.remainingItems[1], }; } - if (relationDescription?.subject) { + if (relationRaw?.subject) { relation.path = { - value: relationDescription.subject[0], - quad: relationDescription.subject[1], + value: relationRaw.subject[0], + quad: relationRaw.subject[1], }; } - if (relationDescription?.value) { + if (relationRaw?.value) { relation.value = { - value: relationDescription.value[0], - quad: relationDescription.value[1], + value: relationRaw.value[0], + quad: relationRaw.value[1], }; } return relation; } +// TODO: add doc export function buildRelations( - relationDescriptions: Map, + relationDescriptions: Map, // TODO: remove this param, and return ITreeRelationRaw | undefined instead. quad: RDF.Quad, ): void { if (quad.predicate.value === TreeNodes.RDFTypeNode) { // Set the operator of the relation + //const operator = RelationOperatorReversed[quad.object.value]; // TODO: make sure this happens in constant time const enumIndexOperator = ( Object.values(RelationOperator)).indexOf(quad.object.value); const operator: RelationOperator | undefined = enumIndexOperator === -1 ? undefined : Object.values(RelationOperator)[enumIndexOperator]; @@ -76,7 +77,8 @@ export function buildRelations( } } /** - * @param relationDescriptions: Map + * TODO: update docs + * @param rawRelations: Map * @param quad: RDF.Quad * @param value?: string * @param subject?: string @@ -86,32 +88,29 @@ export function buildRelations( * a IRelationDescription map */ export function addRelationDescription({ - relationDescriptions, + rawRelations, quad, value, subject, operator, remainingItems, }: { - relationDescriptions: Map; + rawRelations: Map; quad: RDF.Quad; value?: string; subject?: string; operator?: RelationOperator; remainingItems?: number; }): void { - const newDescription: ITreeRelationDescription = - typeof relationDescriptions?.get(quad.subject.value) !== 'undefined' ? - relationDescriptions.get(quad.subject.value)! : - {}; + const rawRelation: ITreeRelationRaw = rawRelations?.get(quad.subject.value) || {}; /* eslint-disable prefer-rest-params */ - const objectArgument = arguments[0]; + const objectArgument = arguments[0]; // TODO: make explicit for (const [ arg, val ] of Object.entries(objectArgument)) { if (val && arg !== 'relationDescriptions' && arg !== 'quad') { - newDescription[arg] = [ val, quad ]; + rawRelation[arg] = [ val, quad ]; break; } } /* eslint-enable prefer-rest-params */ - relationDescriptions.set(quad.subject.value, newDescription); + rawRelations.set(quad.subject.value, rawRelation); } diff --git a/packages/actor-extract-links-extract-tree/package.json b/packages/actor-extract-links-extract-tree/package.json index 54f30efa0..9d5773f5e 100644 --- a/packages/actor-extract-links-extract-tree/package.json +++ b/packages/actor-extract-links-extract-tree/package.json @@ -35,6 +35,7 @@ "@comunica/bus-extract-links": "^0.0.1", "rdf-data-factory": "^1.1.1", "rdf-store-stream": "^1.3.0", + "rdf-string": "^1.6.1", "@comunica/context-entries": "^2.4.0", "@comunica/context-entries-link-traversal": "^0.0.1", "@comunica/types-link-traversal": "^0.0.1", diff --git a/packages/types-link-traversal/lib/TreeMetadata.ts b/packages/types-link-traversal/lib/TreeMetadata.ts index d4ed9bd8a..c259a93ec 100644 --- a/packages/types-link-traversal/lib/TreeMetadata.ts +++ b/packages/types-link-traversal/lib/TreeMetadata.ts @@ -5,7 +5,7 @@ import type * as RDF from 'rdf-js'; -// Reference +// The type of the relationship. // https://treecg.github.io/specification/#vocabulary export enum RelationOperator { // All elements in the related node have this prefix @@ -30,6 +30,9 @@ export enum RelationOperator { // reference http://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html GeospatiallyContainsRelation = 'https://w3id.org/tree#GeospatiallyContainsRelation', } +export const RelationOperatorReversed: Record = Object.fromEntries(Object + .entries(RelationOperator) + .map(([ key, value ]) => [ value, key ])); // Reference // https://treecg.github.io/specification/#classes @@ -51,40 +54,63 @@ export enum TreeNodes { RemainingItems = 'https://w3id.org/tree#remainingItems' } +/** + * A TREE HTTP document with relationships. + */ export interface ITreeNode { - // Relation of the node + /** + * Page URL that identifies this node. + */ + identifier: string; + /** + * All available relationships in the node. + */ relation?: ITreeRelation[]; - // Name/URL of the node - subject: string; } +/** + * Represents a relationship between the members across two nodes. + */ export interface ITreeRelation { - // The relation operator type describe by the enum RelationOperator - '@type'?: { - value: string; - quad: RDF.Quad; + /** + * The type of relationship. + */ + type?: { + value: RelationOperator; + quad: RDF.Quad; // TODO: can this be removed? }; - // Refer to the TreeNodes of the similar name + /** + * How many members can be reached when following this relation. + */ remainingItems?: { value: number; - quad: RDF.Quad; + quad: RDF.Quad; // TODO: can this be removed? }; - // Refer to the TreeNodes of the similar name + /** + * A property path, as defined by SHACL, that indicates what resource the tree:value affects. + */ path?: { value: string; - quad: RDF.Quad; + quad: RDF.Quad; // TODO: can this be removed? }; - // Refer to the TreeNodes of the similar name + /** + * The contextual value of this node. + */ value?: { value: any; - quad: RDF.Quad; + quad: RDF.Quad; // TODO: can this be removed? And replaced by RDF.Term }; - // The URL to be derefenced when this relation cannot be pruned. + /** + * Link to the TREE node document for this relationship. + * This can be dereferenced. + */ node: string ; } -// An helper to build the relation from a stream -export interface ITreeRelationDescription { +/** + * A temporary helper object to build the relation while reading from a stream. + */ +export interface ITreeRelationRaw { // Id of the blank node of the relation subject?: [string, RDF.Quad]; // Refer to the TreeNodes of the similar name From 0d4aee3cc759f22d60d616758d932e4c6fd90465 Mon Sep 17 00:00:00 2001 From: constraintautomaton Date: Mon, 21 Nov 2022 09:35:58 -0500 Subject: [PATCH 064/189] WIP: fixing the variables and the tests --- .../lib/ActorExtractLinksTree.ts | 10 +- .../lib/FilterNode.ts | 9 +- .../lib/treeMetadataExtraction.ts | 95 ++++------- .../test/ActorExtractLinksTree-test.ts | 21 +-- .../test/FilterNode-test.ts | 146 +++++------------ .../test/treeMetadataExtraction-test.ts | 147 ++++-------------- .../types-link-traversal/lib/TreeMetadata.ts | 28 ++-- 7 files changed, 125 insertions(+), 331 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 08c12b246..7f9229889 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -11,7 +11,7 @@ import { TreeNodes } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { termToString } from 'rdf-string'; import { FilterNode } from './FilterNode'; -import { buildRelations, materializeTreeRelation } from './treeMetadataExtraction'; +import { buildRelations, materializeTreeRelation, addRelationDescription } from './treeMetadataExtraction'; /** * A comunica Extract Links Tree Extract Links Actor. @@ -119,8 +119,12 @@ export class ActorExtractLinksTree extends ActorExtractLinks { relationIdentifiers.add(termToString(quad.object)); // If it's a node forward } else if (quad.predicate.value === TreeNodes.Node) { - nodeLinks.push([ termToString(quad.subject), termToString(quad.object) ]); + nodeLinks.push([ termToString(quad.subject), quad.object.value ]); + } + const descriptionElement = buildRelations(quad); + if (descriptionElement) { + const [ value, key ] = descriptionElement; + addRelationDescription(relationDescriptions, quad, value, key); } - buildRelations(relationDescriptions, quad); } } diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 4fed07f1a..f1efc367c 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -61,7 +61,7 @@ export class FilterNode { continue; } // Find the quad from the bgp that are related to the TREE relation - const relevantQuads = FilterNode.findRelevantQuad(queryBody, relation.path.value); + const relevantQuads = FilterNode.findRelevantQuad(queryBody, relation.path); // Accept the relation if no quad are linked with the relation if (relevantQuads.length === 0) { @@ -70,7 +70,7 @@ export class FilterNode { } // Create the binding from the relevant quad in association with the TREE relation - const bindings = FilterNode.createBinding(relevantQuads, relation.value.quad); + const bindings = FilterNode.createBinding(relevantQuads, relation.value.term); const filterExpression: Algebra.Operation = FilterNode.generateTreeRelationFilter(filterOperation, bindings); // Accept the relation if no filter are associated with the relation @@ -152,11 +152,10 @@ export class FilterNode { * @param {RDF.Quad} relationValue - the quad related to the TREE path * @returns {Bindings} the resulting binding */ - private static createBinding(relevantQuad: RDF.Quad[], relationValue: RDF.Quad): Bindings { + private static createBinding(relevantQuad: RDF.Quad[], relationValue: RDF.Term): Bindings { let binding: Bindings = BF.bindings(); for (const quad of relevantQuad) { - const object = quad.object.value; - binding = binding.set(object, relationValue.object); + binding = binding.set(quad.object.value, relationValue); } return binding; } diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts index d7ba08a24..462071d37 100644 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -1,5 +1,5 @@ -import type { ITreeRelation, ITreeRelationRaw } from '@comunica/types-link-traversal'; -import { RelationOperator, RelationOperatorReversed, TreeNodes } from '@comunica/types-link-traversal'; +import type { ITreeRelation, ITreeRelationRaw, RelationOperator } from '@comunica/types-link-traversal'; +import { TreeNodes, RelationOperatorReversed } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; /** @@ -14,30 +14,21 @@ export function materializeTreeRelation( ): ITreeRelation { const relation: ITreeRelation = { node: nextLink }; if (relationRaw?.operator) { - relation.type = { - value: relationRaw.operator[0], - quad: relationRaw.operator[1], - }; + relation.type = relationRaw.operator[0]; } if (relationRaw?.remainingItems) { - relation.remainingItems = { - value: relationRaw.remainingItems[0], - quad: relationRaw.remainingItems[1], - }; + relation.remainingItems = relationRaw.remainingItems[0]; } if (relationRaw?.subject) { - relation.path = { - value: relationRaw.subject[0], - quad: relationRaw.subject[1], - }; + relation.path = relationRaw.subject[0]; } if (relationRaw?.value) { relation.value = { value: relationRaw.value[0], - quad: relationRaw.value[1], + term: relationRaw.value[1], }; } @@ -46,71 +37,45 @@ export function materializeTreeRelation( // TODO: add doc export function buildRelations( - relationDescriptions: Map, // TODO: remove this param, and return ITreeRelationRaw | undefined instead. quad: RDF.Quad, -): void { +): [RelationOperator | number | string, keyof ITreeRelationRaw] | undefined { if (quad.predicate.value === TreeNodes.RDFTypeNode) { + console.log(RelationOperatorReversed); // Set the operator of the relation - //const operator = RelationOperatorReversed[quad.object.value]; // TODO: make sure this happens in constant time - const enumIndexOperator = ( Object.values(RelationOperator)).indexOf(quad.object.value); - const operator: RelationOperator | undefined = - enumIndexOperator === -1 ? undefined : Object.values(RelationOperator)[enumIndexOperator]; - + const operator: RelationOperator | undefined = RelationOperatorReversed.get(quad.object.value); if (typeof operator !== 'undefined') { - addRelationDescription({ relationDescriptions, quad, operator }); + return [ operator, 'operator' ]; } } else if (quad.predicate.value === TreeNodes.Path) { // Set the subject of the relation condition - addRelationDescription({ - relationDescriptions, - quad, - subject: quad.object.value, - }); + return [ quad.object.value, 'subject' ]; } else if (quad.predicate.value === TreeNodes.Value) { // Set the value of the relation condition - addRelationDescription({ relationDescriptions, quad, value: quad.object.value }); + return [ quad.object.value, 'value' ]; } else if (quad.predicate.value === TreeNodes.RemainingItems) { const remainingItems = Number.parseInt(quad.object.value, 10); if (!Number.isNaN(remainingItems)) { - addRelationDescription({ relationDescriptions, quad, remainingItems }); + return [ remainingItems, 'remainingItems' ]; } } + return undefined; } /** - * TODO: update docs - * @param rawRelations: Map - * @param quad: RDF.Quad - * @param value?: string - * @param subject?: string - * @param operator?: RelationOperator - * @param remainingItems?: number - * from a quad capture the TREE relation information and put it into - * a IRelationDescription map + * Update the relationDescriptions with the new quad value + * @param {Map} relationDescriptions - Maps relationship identifiers to their description. + * @param {RDF.Quad} quad - Current quad of the steam. + * @param {RelationOperator | number | string} value - Current description value fetch + * @param {keyof ITreeRelationRaw} key - Key associated with the value. */ -export function addRelationDescription({ - rawRelations, - quad, - value, - subject, - operator, - remainingItems, -}: { - rawRelations: Map; - quad: RDF.Quad; - value?: string; - subject?: string; - operator?: RelationOperator; - remainingItems?: number; -}): void { - const rawRelation: ITreeRelationRaw = rawRelations?.get(quad.subject.value) || {}; - /* eslint-disable prefer-rest-params */ - const objectArgument = arguments[0]; // TODO: make explicit - for (const [ arg, val ] of Object.entries(objectArgument)) { - if (val && arg !== 'relationDescriptions' && arg !== 'quad') { - rawRelation[arg] = [ val, quad ]; - break; - } - } - /* eslint-enable prefer-rest-params */ - rawRelations.set(quad.subject.value, rawRelation); +export function addRelationDescription( + relationDescriptions: Map, + quad: RDF.Quad, + value: RelationOperator | number | string, + key: keyof ITreeRelationRaw, +): void { + const rawRelation: ITreeRelationRaw = relationDescriptions?.get(quad.subject.value) || {}; + rawRelation[key] = [ value, quad ]; + + relationDescriptions.set(quad.subject.value, rawRelation); } + diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 318f742aa..a6d8268ac 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -267,26 +267,11 @@ describe('ActorExtractLinksExtractLinksTree', () => { const relations: ITreeRelation[] = [ { node: prunedUrl, - remainingItems: { - value: 66, - quad: DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#remainingItems'), - DF.literal('66'), - DF.namedNode('ex:gx')), - }, - '@type': { - value: RelationOperator.GreaterThanRelation, - quad: DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - DF.namedNode('https://w3id.org/tree#GreaterThanRelation'), - DF.namedNode('ex:gx')), - }, + remainingItems: 66, + type: RelationOperator.GreaterThanRelation, value: { value: '66', - quad: DF.quad(DF.blankNode('_:_g1'), - DF.namedNode('https://w3id.org/tree#value'), - DF.literal('66'), - DF.namedNode('ex:gx')), + term: DF.literal('66'), }, }, { diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index 026277ffb..71e759871 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -22,7 +22,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', @@ -39,7 +39,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, }; const response = filterNode.test(node, context); @@ -51,7 +51,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [], }; const response = filterNode.test(node, context); @@ -63,7 +63,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', @@ -84,19 +84,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, + path: 'ex:path', value: { value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, }, ], @@ -166,19 +161,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, + path: 'ex:path', value: { value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, }, ], @@ -242,19 +232,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, + path: 'ex:path', value: { value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, }, ], @@ -362,7 +347,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const node: ITreeNode = { subject: 'foo' }; + const node: ITreeNode = { identifier: 'foo' }; const result = await filterNode.run(node, context); expect(result).toStrictEqual( @@ -375,19 +360,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, + path: 'ex:path', value: { value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, }, ], @@ -488,19 +468,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, + path: 'ex:path', value: { value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, }, @@ -568,19 +543,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, + path: 'ex:path', value: { value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, }, ], @@ -644,19 +614,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, + path: 'ex:path', value: { value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, }, ], @@ -753,19 +718,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, + path: 'ex:path', value: { value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, }, ], @@ -872,19 +832,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, + path: 'ex:path', value: { value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, }, ], @@ -946,19 +901,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, + path: 'ex:path', value: { value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, }, ], @@ -1014,19 +964,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, + path: 'ex:path', value: { value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, }, ], @@ -1097,19 +1042,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; const node: ITreeNode = { - subject: treeSubject, + identifier: treeSubject, relation: [ { node: 'http://bar.com', - path: { - value: 'ex:path', - quad: aQuad, - }, + path: 'ex:path', value: { value: '5', - quad: DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer'))), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, }, ], @@ -1132,7 +1072,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { { termType: 'Quad', value: '', - subject: { + identifier: { termType: 'Variable', value: 's', }, @@ -1169,7 +1109,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { { termType: 'Quad', value: '', - subject: { + identifier: { termType: 'Variable', value: 's', }, diff --git a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts index 1af1e8898..820ade156 100644 --- a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts +++ b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts @@ -1,4 +1,4 @@ -import type { IRelationDescription } from '@comunica/types-link-traversal'; +import type { ITreeRelationRaw } from '@comunica/types-link-traversal'; import { TreeNodes, RelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; @@ -15,8 +15,8 @@ describe('treeMetadataExtraction', () => { DF.namedNode(TreeNodes.RDFTypeNode), DF.namedNode(RelationOperator.EqualThanRelation), ); - const relationDescriptions: Map = new Map(); - addRelationDescription({ relationDescriptions, quad, operator: RelationOperator.EqualThanRelation }); + const relationDescriptions: Map = new Map(); + addRelationDescription(relationDescriptions, quad, RelationOperator.EqualThanRelation, 'operator'); expect(relationDescriptions.size).toBe(1); }); @@ -27,8 +27,8 @@ describe('treeMetadataExtraction', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.RDFTypeNode), DF.namedNode(RelationOperator.EqualThanRelation)); - const relationDescriptions: Map = new Map([[ 'ex:s', { value: 22 }]]); - addRelationDescription({ relationDescriptions, quad, operator: RelationOperator.EqualThanRelation }); + const relationDescriptions: Map = new Map([[ 'ex:s', { value: 22 }]]); + addRelationDescription(relationDescriptions, quad, RelationOperator.EqualThanRelation, 'operator'); expect(relationDescriptions.size).toBe(1); }); @@ -37,56 +37,56 @@ describe('treeMetadataExtraction', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.RDFTypeNode), DF.namedNode(RelationOperator.EqualThanRelation)); - const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); - addRelationDescription({ relationDescriptions, quad, operator: RelationOperator.EqualThanRelation }); + const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); + addRelationDescription(relationDescriptions, quad, RelationOperator.EqualThanRelation, 'operator'); expect(relationDescriptions.size).toBe(2); }); it('should add relation to the map when a value is provided and the relation map is empty', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); - const relationDescriptions: Map = new Map(); - addRelationDescription({ relationDescriptions, quad, value: '5' }); + const relationDescriptions: Map = new Map(); + addRelationDescription(relationDescriptions, quad, '5', 'value'); expect(relationDescriptions.size).toBe(1); }); it('should add relation to the map when a value is provided and the relation map at the current key is not empty', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); - const relationDescriptions: Map = + const relationDescriptions: Map = new Map([[ 'ex:s', { subject: [ 'ex:s', quad ]}]]); - addRelationDescription({ relationDescriptions, quad, value: '5' }); + addRelationDescription(relationDescriptions, quad, '5', 'value'); expect(relationDescriptions.size).toBe(1); }); it('should add relation to the map when a value is provided and the relation map is not empty', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); - const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); - addRelationDescription({ relationDescriptions, quad, value: '5' }); + const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); + addRelationDescription(relationDescriptions, quad, '5', 'value'); expect(relationDescriptions.size).toBe(2); }); it('should add relation to the map when a subject is provided and the relation map is empty', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Path), DF.namedNode('ex:path')); - const relationDescriptions: Map = new Map(); - addRelationDescription({ relationDescriptions, quad, subject: 'ex:path' }); + const relationDescriptions: Map = new Map(); + addRelationDescription(relationDescriptions, quad, 'ex:path', 'subject'); expect(relationDescriptions.size).toBe(1); }); it('should add relation to the map when a subject is provided and the relation map at the current key is not empty', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Path), DF.namedNode('ex:path')); - const relationDescriptions: Map = + const relationDescriptions: Map = new Map([[ 'ex:s', { subject: [ 'ex:s', quad ]}]]); - addRelationDescription({ relationDescriptions, quad, subject: 'ex:path' }); + addRelationDescription(relationDescriptions, quad, 'ex:path', 'subject'); expect(relationDescriptions.size).toBe(1); }); it('should add relation to the map when a subject is provided and the relation map is not empty', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); - const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); - addRelationDescription({ relationDescriptions, quad, subject: 'ex:path' }); + const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); + addRelationDescription(relationDescriptions, quad, 'ex:path', 'subject'); expect(relationDescriptions.size).toBe(2); }); }); @@ -95,10 +95,7 @@ describe('treeMetadataExtraction', () => { it('should not modify the relation map when the predicate is not related to the relations', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode('ex:p'), DF.namedNode('ex:o')); - const relationDescriptions: Map = new Map(); - buildRelations(relationDescriptions, quad); - - expect(relationDescriptions.size).toBe(0); + expect(buildRelations(quad)).toBeUndefined(); }); it('should modify the relation map when the predicate is a rdf type with a supported relation', @@ -106,16 +103,12 @@ describe('treeMetadataExtraction', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.RDFTypeNode), DF.namedNode(RelationOperator.EqualThanRelation)); - const relationDescriptions: Map = new Map(); - - const expectedDescription = new Map([[ 'ex:s', { operator: [ RelationOperator.EqualThanRelation, quad ], - subject: undefined, - value: undefined, - remainingItems: undefined }]]); - buildRelations(relationDescriptions, quad); - expect(relationDescriptions.size).toBe(1); - expect(relationDescriptions).toStrictEqual(expectedDescription); + const res = buildRelations(quad); + expect(res).toBeDefined(); + const [ value, key ] = res; + expect(key).toBe( 'operator'); + expect(value).toBe(RelationOperator.EqualThanRelation); }); it('should not modify the relation map when the predicate is a rdf type an supported relation', @@ -123,95 +116,9 @@ describe('treeMetadataExtraction', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.RDFTypeNode), DF.namedNode('foo')); - const relationDescriptions: Map = new Map(); - buildRelations(relationDescriptions, quad); - - expect(relationDescriptions.size).toBe(0); - }); - - it('should modify an map with another relation when the predicate is a rdf type an supported relation', - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), - DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode(RelationOperator.EqualThanRelation)); - const relationDescriptions: Map = new Map([[ 'ex:s2', {}]]); - const expectedDescription: Map = new Map([[ 'ex:s2', {}], - [ 'ex:s', - { operator: [ RelationOperator.EqualThanRelation, quad ], - subject: undefined, - value: undefined, - remainingItems: undefined }]]); - const relationQuads: Map = new Map(); - - buildRelations(relationDescriptions, quad); - expect(relationDescriptions.size).toBe(2); - expect(relationDescriptions).toStrictEqual(expectedDescription); - }); - - it(`should modify an map with a relation that has already been started - to be defined when the predicate is a rdf type an supported relation`, - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), - DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode(RelationOperator.EqualThanRelation)); - const relationDescriptions: Map = new Map([[ 'ex:s', - { subject: [ 'ex:path', quad ], value: undefined, remainingItems: undefined }]]); - const expectedDescription: Map = new Map([[ 'ex:s', - { operator: [ RelationOperator.EqualThanRelation, quad ], - subject: [ 'ex:path', quad ], - value: undefined, - remainingItems: undefined }]]); - - buildRelations(relationDescriptions, quad); - - expect(relationDescriptions.size).toBe(1); - expect(relationDescriptions).toStrictEqual(expectedDescription); - }); - - it('should not modify the relation map when no new values are provided', - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), - DF.namedNode('bar'), - DF.namedNode(RelationOperator.EqualThanRelation)); - const relationDescriptions: Map = new Map([[ 'ex:s', - { subject: [ 'ex:path', quad ], - value: undefined, - remainingItems: undefined }]]); - const expectedDescription: Map = relationDescriptions; - - buildRelations(relationDescriptions, quad); - - expect(relationDescriptions.size).toBe(1); - expect(relationDescriptions).toStrictEqual(expectedDescription); - }); - - it('should modify the relation map when a remainingItems field is provided with a valid number', - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), - DF.namedNode(TreeNodes.RemainingItems), - DF.namedNode('45')); - const relationDescriptions: Map = new Map([[ 'ex:s', - { subject: [ 'ex:path', quad ], - value: undefined, - remainingItems: undefined }]]); - const expectedDescription: Map = new Map([[ 'ex:s', - { subject: [ 'ex:path', quad ], value: undefined, remainingItems: [ 45, quad ]}]]); - - buildRelations(relationDescriptions, quad); - - expect(relationDescriptions.size).toBe(1); - expect(relationDescriptions).toStrictEqual(expectedDescription); - }); - - it('should not modify the relation map when a remainingItems field is provided with an unvalid number', - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), - DF.namedNode(TreeNodes.RemainingItems), - DF.namedNode('foo')); - const relationDescriptions: Map = new Map(); - buildRelations(relationDescriptions, quad); - expect(relationDescriptions.size).toBe(0); + const res = buildRelations(quad); + expect(res).toBeUndefined(); }); }); }); diff --git a/packages/types-link-traversal/lib/TreeMetadata.ts b/packages/types-link-traversal/lib/TreeMetadata.ts index c259a93ec..c73c52ba5 100644 --- a/packages/types-link-traversal/lib/TreeMetadata.ts +++ b/packages/types-link-traversal/lib/TreeMetadata.ts @@ -30,9 +30,12 @@ export enum RelationOperator { // reference http://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html GeospatiallyContainsRelation = 'https://w3id.org/tree#GeospatiallyContainsRelation', } -export const RelationOperatorReversed: Record = Object.fromEntries(Object - .entries(RelationOperator) - .map(([ key, value ]) => [ value, key ])); + +export const RelationOperatorReversed: Map = +new Map(Object.values(RelationOperator).map(value => { + const enumIndex = Object.values(RelationOperator).indexOf(value); + return [ Object.values(RelationOperator)[enumIndex], value ]; +})); // Reference // https://treecg.github.io/specification/#classes @@ -75,30 +78,21 @@ export interface ITreeRelation { /** * The type of relationship. */ - type?: { - value: RelationOperator; - quad: RDF.Quad; // TODO: can this be removed? - }; + type?: RelationOperator; /** * How many members can be reached when following this relation. */ - remainingItems?: { - value: number; - quad: RDF.Quad; // TODO: can this be removed? - }; + remainingItems?: number; /** * A property path, as defined by SHACL, that indicates what resource the tree:value affects. */ - path?: { - value: string; - quad: RDF.Quad; // TODO: can this be removed? - }; + path?: string; /** * The contextual value of this node. */ value?: { - value: any; - quad: RDF.Quad; // TODO: can this be removed? And replaced by RDF.Term + value: string; + term: RDF.Term; }; /** * Link to the TREE node document for this relationship. From f5ea05aaa5f77edb2221a982b15288045652f3e9 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 22 Nov 2022 10:27:59 +0100 Subject: [PATCH 065/189] code refactoring done --- .../lib/ActorExtractLinksTree.ts | 4 +- .../lib/FilterNode.ts | 42 +++---- .../lib/treeMetadataExtraction.ts | 17 ++- .../test/treeMetadataExtraction-test.ts | 105 ++++++++++++++++-- .../types-link-traversal/lib/TreeMetadata.ts | 100 ++++++++++++----- 5 files changed, 202 insertions(+), 66 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 7f9229889..4a24dc1dc 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -11,7 +11,7 @@ import { TreeNodes } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { termToString } from 'rdf-string'; import { FilterNode } from './FilterNode'; -import { buildRelations, materializeTreeRelation, addRelationDescription } from './treeMetadataExtraction'; +import { buildRelationElement, materializeTreeRelation, addRelationDescription } from './treeMetadataExtraction'; /** * A comunica Extract Links Tree Extract Links Actor. @@ -121,7 +121,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } else if (quad.predicate.value === TreeNodes.Node) { nodeLinks.push([ termToString(quad.subject), quad.object.value ]); } - const descriptionElement = buildRelations(quad); + const descriptionElement = buildRelationElement(quad); if (descriptionElement) { const [ value, key ] = descriptionElement; addRelationDescription(relationDescriptions, quad, value, key); diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index f1efc367c..d35ae7591 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -13,7 +13,7 @@ const BF = new BindingsFactory(); * A class to apply [SPAQL filters](https://www.w3.org/TR/sparql11-query/#evaluation) * to the [TREE specification](https://treecg.github.io/specification/). * It use [sparqlee](https://github.com/comunica/sparqlee) to evaluate the filter where - * the binding are remplace by the [value of TREE relation](https://treecg.github.io/specification/#traversing) + * the binding are remplace by the [value of TREE relation](https://treecg.github.io/specification/#traversing). */ export class FilterNode { public test(node: ITreeNode, context: IActionContext): boolean { @@ -40,46 +40,46 @@ export class FilterNode { return new Map(); } - // Extract the filter expression + // Extract the filter expression. const filterOperation: Algebra.Expression = (() => { const query: Algebra.Operation = context.get(KeysInitQuery.query)!; return FilterNode.findNode(query, Algebra.types.FILTER).expression; })(); - // Extract the bgp of the query + // Extract the bgp of the query. const queryBody: RDF.Quad[] = FilterNode.findBgp(context.get(KeysInitQuery.query)!); if (queryBody.length === 0) { return new Map(); } - // Capture the relation from the function argument + // Capture the relation from the function argument. const relations: ITreeRelation[] = node.relation!; for (const relation of relations) { - // Accept the relation if the relation does't specify a condition + // Accept the relation if the relation does't specify a condition. if (typeof relation.path === 'undefined' || typeof relation.value === 'undefined') { filterMap.set(relation.node, true); continue; } - // Find the quad from the bgp that are related to the TREE relation + // Find the quad from the bgp that are related to the TREE relation. const relevantQuads = FilterNode.findRelevantQuad(queryBody, relation.path); - // Accept the relation if no quad are linked with the relation + // Accept the relation if no quad are linked with the relation. if (relevantQuads.length === 0) { filterMap.set(relation.node, true); continue; } - // Create the binding from the relevant quad in association with the TREE relation + // Create the binding from the relevant quad in association with the TREE relation. const bindings = FilterNode.createBinding(relevantQuads, relation.value.term); const filterExpression: Algebra.Operation = FilterNode.generateTreeRelationFilter(filterOperation, bindings); - // Accept the relation if no filter are associated with the relation + // Accept the relation if no filter are associated with the relation. if (filterExpression.args.length === 0) { filterMap.set(relation.node, true); continue; } const evaluator = new AsyncEvaluator(filterExpression); - // Evaluate the filter with the relevant quad binding + // Evaluate the filter with the relevant quad binding. const result: boolean = await evaluator.evaluateAsEBV(bindings); filterMap.set(relation.node, result); } @@ -114,27 +114,27 @@ export class FilterNode { // Generate an empty filter algebra let newFilterExpression: Algebra.Expression = AF.createOperatorExpression(filterExpression.operator, []); - // Check if there is one filter or multiple + // Check if there is one filter or multiple. if ('operator' in filterExpression.args[0]) { - // Add the argument into the empty the new filter + // Add the argument into the empty the new filter. newFilterExpression.args = (filterExpression.args).filter(expression => { for (const arg of expression.args) { if ('term' in arg && arg.term.termType === 'Variable') { - // Check if the argument of the filter is present into the binding + // Check if the argument of the filter is present into the binding. return binding.has(arg.term.value); } } return false; }); - // If the filter has now a size of 1 change the form to respect the algebra specification + // If the filter has now a size of 1 change the form to respect the algebra specification. if (newFilterExpression.args.length === 1) { newFilterExpression = newFilterExpression.args[0]; } } else { - // Add the argument into the empty the new filter + // Add the argument into the empty the new filter. for (const arg of (filterExpression.args)) { - // Check if the argument of the filter is present into the binding + // Check if the argument of the filter is present into the binding. if ('term' in arg && arg.term.termType === 'Variable' && !binding.has(arg.term.value)) { newFilterExpression.args = []; break; @@ -147,7 +147,7 @@ export class FilterNode { /** * Create the binding from quad related to the TREE:path that will be used with sparqlee - * for filtering of relation + * for the filtering of relation. * @param {RDF.Quad[]} relevantQuad - the quads related to the TREE relation * @param {RDF.Quad} relationValue - the quad related to the TREE path * @returns {Bindings} the resulting binding @@ -161,7 +161,7 @@ export class FilterNode { } /** - * Find the bgp of the original query of the user + * Find the bgp of the original query of the user. * @param {Algebra.Operation} query - the original query * @returns { RDF.Quad[]} the bgp of the query */ @@ -196,7 +196,7 @@ export class FilterNode { } /** - * Format the section of the algebra graph contain a part of the bgp into an array of quad + * Format the section of the algebra graph contain a part of the bgp into an array of quad. * @param {any} joins - the join operation containing a part of the bgp * @returns {RDF.Quad[]} the bgp in the form of an array of quad */ @@ -214,7 +214,7 @@ export class FilterNode { } /** - * Append the bgp of the query + * Append the bgp of the query. * @param {RDF.Quad[]} bgp - the whole bgp * @param {RDF.Quad[]} currentBgp - the bgp collected in the current node * @returns {RDF.Quad[]} the bgp updated @@ -228,7 +228,7 @@ export class FilterNode { /** * Find the first node of type `nodeType`, if it doesn't exist - * it return undefined + * it return undefined. * @param {Algebra.Operation} query - the original query * @param {string} nodeType - the tyoe of node requested * @returns {any} diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts index 462071d37..0cdd0e3db 100644 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -1,6 +1,7 @@ import type { ITreeRelation, ITreeRelationRaw, RelationOperator } from '@comunica/types-link-traversal'; import { TreeNodes, RelationOperatorReversed } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; +import { termToString } from 'rdf-string'; /** * Materialize a raw tree relation using the captured values. @@ -28,19 +29,23 @@ export function materializeTreeRelation( if (relationRaw?.value) { relation.value = { value: relationRaw.value[0], - term: relationRaw.value[1], + term: relationRaw.value[1].object, }; } return relation; } -// TODO: add doc -export function buildRelations( +/** + * From a quad stream return a relation element if it exist + * @param {RDF.Quad} quad - Current quad of the stream. + * @returns {[RelationOperator | number | string, keyof ITreeRelationRaw] | undefined} The relation element + * and the key associated with it. + */ +export function buildRelationElement( quad: RDF.Quad, ): [RelationOperator | number | string, keyof ITreeRelationRaw] | undefined { if (quad.predicate.value === TreeNodes.RDFTypeNode) { - console.log(RelationOperatorReversed); // Set the operator of the relation const operator: RelationOperator | undefined = RelationOperatorReversed.get(quad.object.value); if (typeof operator !== 'undefined') { @@ -73,9 +78,9 @@ export function addRelationDescription( value: RelationOperator | number | string, key: keyof ITreeRelationRaw, ): void { - const rawRelation: ITreeRelationRaw = relationDescriptions?.get(quad.subject.value) || {}; + const rawRelation: ITreeRelationRaw = relationDescriptions?.get(termToString(quad.subject)) || {}; rawRelation[key] = [ value, quad ]; - relationDescriptions.set(quad.subject.value, rawRelation); + relationDescriptions.set(termToString(quad.subject), rawRelation); } diff --git a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts index 820ade156..84296d767 100644 --- a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts +++ b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts @@ -1,8 +1,8 @@ -import type { ITreeRelationRaw } from '@comunica/types-link-traversal'; +import type { ITreeRelationRaw, ITreeRelation } from '@comunica/types-link-traversal'; import { TreeNodes, RelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; -import { buildRelations, addRelationDescription } from '../lib/treeMetadataExtraction'; +import { buildRelationElement, addRelationDescription, materializeTreeRelation } from '../lib/treeMetadataExtraction'; const DF = new DataFactory(); @@ -92,33 +92,122 @@ describe('treeMetadataExtraction', () => { }); describe('buildRelations', () => { - it('should not modify the relation map when the predicate is not related to the relations', + it('should return undefined when the quad don\'t respect any relation', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode('ex:p'), DF.namedNode('ex:o')); - expect(buildRelations(quad)).toBeUndefined(); + expect(buildRelationElement(quad)).toBeUndefined(); }); - it('should modify the relation map when the predicate is a rdf type with a supported relation', + it('should return the relation element when the quad respect a relation semantic', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.RDFTypeNode), DF.namedNode(RelationOperator.EqualThanRelation)); - const res = buildRelations(quad); + const res = buildRelationElement(quad); expect(res).toBeDefined(); const [ value, key ] = res; expect(key).toBe( 'operator'); expect(value).toBe(RelationOperator.EqualThanRelation); }); - it('should not modify the relation map when the predicate is a rdf type an supported relation', + it('should return undefined when the type does not exist', () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.RDFTypeNode), DF.namedNode('foo')); - const res = buildRelations(quad); + const res = buildRelationElement(quad); expect(res).toBeUndefined(); }); }); + + describe('materializeTreeRelation', () => { + it('should materialize a tree Relation when all the raw relation are provided', () => { + const aSubject = 'foo'; + const aValue = '0'; + const anOperator = RelationOperator.PrefixRelation; + const aRemainingItemDefinition = 44; + const aQuad = DF.quad( + DF.blankNode(''), + DF.namedNode(''), + DF.blankNode(''), + ); + const aNode = 'test'; + const relationRaw: ITreeRelationRaw = { + subject: [ aSubject, aQuad ], + value: [ aValue, aQuad ], + operator: [ anOperator, aQuad ], + remainingItems: [ aRemainingItemDefinition, aQuad ], + }; + const expectedTreeRelation: ITreeRelation = { + type: anOperator, + remainingItems: aRemainingItemDefinition, + path: aSubject, + value: { + value: aValue, + term: aQuad.object, + }, + node: aNode, + }; + + const res = materializeTreeRelation(relationRaw, aNode); + + expect(res).toStrictEqual(expectedTreeRelation); + }); + + it('should materialize a tree Relation when the remaining item is missing', () => { + const aSubject = 'foo'; + const aValue = '0'; + const anOperator = RelationOperator.PrefixRelation; + const aQuad = DF.quad( + DF.blankNode(''), + DF.namedNode(''), + DF.blankNode(''), + ); + const aNode = 'test'; + const relationRaw: ITreeRelationRaw = { + subject: [ aSubject, aQuad ], + value: [ aValue, aQuad ], + operator: [ anOperator, aQuad ], + }; + const expectedTreeRelation: ITreeRelation = { + type: anOperator, + path: aSubject, + value: { + value: aValue, + term: aQuad.object, + }, + node: aNode, + }; + + const res = materializeTreeRelation(relationRaw, aNode); + + expect(res).toStrictEqual(expectedTreeRelation); + }); + + it('should materialize a tree Relation when the value is missing', () => { + const aSubject = 'foo'; + const anOperator = RelationOperator.PrefixRelation; + const aQuad = DF.quad( + DF.blankNode(''), + DF.namedNode(''), + DF.blankNode(''), + ); + const aNode = 'test'; + const relationRaw: ITreeRelationRaw = { + subject: [ aSubject, aQuad ], + operator: [ anOperator, aQuad ], + }; + const expectedTreeRelation: ITreeRelation = { + type: anOperator, + path: aSubject, + node: aNode, + }; + + const res = materializeTreeRelation(relationRaw, aNode); + + expect(res).toStrictEqual(expectedTreeRelation); + }); + }); }); diff --git a/packages/types-link-traversal/lib/TreeMetadata.ts b/packages/types-link-traversal/lib/TreeMetadata.ts index c73c52ba5..d609d2f8d 100644 --- a/packages/types-link-traversal/lib/TreeMetadata.ts +++ b/packages/types-link-traversal/lib/TreeMetadata.ts @@ -8,52 +8,86 @@ import type * as RDF from 'rdf-js'; // The type of the relationship. // https://treecg.github.io/specification/#vocabulary export enum RelationOperator { - // All elements in the related node have this prefix + /** + * All elements in the related node have this prefix. + */ PrefixRelation = 'https://w3id.org/tree#PrefixRelation', - // All elements in the related node have this substring + /** + * All elements in the related node have this substring. + */ SubstringRelation = 'https://w3id.org/tree#SubstringRelation', - // All members of this related node end with this suffix + /** + * All members of this related node end with this suffix. + */ SuffixRelation = 'https://w3id.org/tree#SuffixRelation', - // The related Node’s members are greater than the value. For string comparison, - // this relation can refer to a comparison configuration + /** + * The related Node’s members are greater than the value. For string comparison, + * this relation can refer to a comparison configuration. + */ GreaterThanRelation = 'https://w3id.org/tree#GreaterThanRelation', - // Similar to GreaterThanRelation + /** + * Similar to GreaterThanRelation. + */ GreaterThanOrEqualToRelation = 'https://w3id.org/tree#GreaterThanOrEqualToRelation', - // Similar to GreaterThanRelation - LessThanRelation = 'https://w3id.org/tree#LessThanRelation', - // Similar to GreaterThanRelation + /** + * Similar to GreaterThanRelation. + */ + LessThanRelation = 'https://w3id.org/tree#LessThanRelation', + /** + * Similar to GreaterThanRelation. + */ LessThanOrEqualToRelation = 'https://w3id.org/tree#LessThanOrEqualToRelation', - // Similar to GreaterThanRelation + /** + * Similar to GreaterThanRelation. + */ EqualThanRelation = 'https://w3id.org/tree#EqualThanRelation', - // A contains b iff no points of b lie in the exterior of a, and at least one point - // of the interior of b lies in the interior of a - // reference http://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html + /** + * A contains b iff no points of b lie in the exterior of a, and at least one point + * of the interior of b lies in the interior of a. + * reference http://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html + */ GeospatiallyContainsRelation = 'https://w3id.org/tree#GeospatiallyContainsRelation', } - +/** + * A map to access the value of the enum RelationOperator by it's value in O(1). + */ export const RelationOperatorReversed: Map = new Map(Object.values(RelationOperator).map(value => { const enumIndex = Object.values(RelationOperator).indexOf(value); return [ Object.values(RelationOperator)[enumIndex], value ]; })); -// Reference -// https://treecg.github.io/specification/#classes -// https://treecg.github.io/specification/#properties +/** + * Reference + * https://treecg.github.io/specification/#classes + * https://treecg.github.io/specification/#properties + */ export enum TreeNodes { - // A tree:Node is a node that may contain links to other dereferenceable resources - // that lead to a full overview of a tree:Collection. + /** + * A tree:Node is a node that may contain links to other dereferenceable resources + * that lead to a full overview of a tree:Collection. + */ Node = 'https://w3id.org/tree#node', - // An entity that describes a relation between two tree:Nodes. + /** + * An entity that describes a relation between two tree:Nodes. + */ Relation = 'https://w3id.org/tree#relation', - // The relation operator type describe by the enum RelationOperator + /** + * The relation operator type describe by the enum RelationOperator. + */ RDFTypeNode = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', - // A property path, as defined by SHACL, that indicates what resource the tree:value affects. - // reference SHACL: https://www.w3.org/TR/shacl/ + /** + * A property path, as defined by SHACL, that indicates what resource the tree:value affects. + * reference SHACL: https://www.w3.org/TR/shacl/ + */ Path = 'https://w3id.org/tree#path', - // The contextual value of this node + /** + * The contextual value of this node. + */ Value = 'https://w3id.org/tree#value', - // Remaining number of items of this node, the items in its children included. + /** + * Remaining number of items of this node, the items in its children included. + */ RemainingItems = 'https://w3id.org/tree#remainingItems' } @@ -105,12 +139,20 @@ export interface ITreeRelation { * A temporary helper object to build the relation while reading from a stream. */ export interface ITreeRelationRaw { - // Id of the blank node of the relation + /** + * Id of the blank node of the relation. + */ subject?: [string, RDF.Quad]; - // Refer to the TreeNodes of the similar name + /** + * Refer to the TreeNodes of the similar name. + */ value?: any; - // The relation operator type describe by the enum RelationOperator + /** + * The relation operator type describe by the enum RelationOperator. + */ operator?: [RelationOperator, RDF.Quad]; - // Refer to the TreeNodes of the similar name + /** + * Refer to the TreeNodes of the similar name. + */ remainingItems?: [number, RDF.Quad]; } From b69f1f132f3ce60f8e47abe7334af5e82b2a2e15 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 22 Nov 2022 11:16:41 +0100 Subject: [PATCH 066/189] lint-fix --- packages/types-link-traversal/lib/TreeMetadata.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types-link-traversal/lib/TreeMetadata.ts b/packages/types-link-traversal/lib/TreeMetadata.ts index d609d2f8d..da6c592ef 100644 --- a/packages/types-link-traversal/lib/TreeMetadata.ts +++ b/packages/types-link-traversal/lib/TreeMetadata.ts @@ -32,7 +32,7 @@ export enum RelationOperator { /** * Similar to GreaterThanRelation. */ - LessThanRelation = 'https://w3id.org/tree#LessThanRelation', + LessThanRelation = 'https://w3id.org/tree#LessThanRelation', /** * Similar to GreaterThanRelation. */ From fcc380c5db2e249f8372450270c5c5666c3c082e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 22 Nov 2022 11:25:34 +0100 Subject: [PATCH 067/189] dot added to a comment --- packages/actor-extract-links-extract-tree/lib/FilterNode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index d35ae7591..4adc4d69b 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -111,7 +111,7 @@ export class FilterNode { */ private static generateTreeRelationFilter(filterExpression: Algebra.Expression, binding: Bindings): Algebra.Expression { - // Generate an empty filter algebra + // Generate an empty filter algebra. let newFilterExpression: Algebra.Expression = AF.createOperatorExpression(filterExpression.operator, []); // Check if there is one filter or multiple. From 8ec97eb3e56f0f93ba26f24fa5eb055d94ad7fb4 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 22 Nov 2022 15:04:01 +0100 Subject: [PATCH 068/189] WIP: handling of equal operation --- .../lib/FilterNode.ts | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 4adc4d69b..23682ab32 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -2,6 +2,7 @@ import { BindingsFactory } from '@comunica/bindings-factory'; import { KeysInitQuery } from '@comunica/context-entries'; import type { Bindings, IActionContext } from '@comunica/types'; import type { ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; +import { RelationOperator } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra, Factory as AlgebraFactory } from 'sparqlalgebrajs'; import { AsyncEvaluator } from 'sparqlee'; @@ -160,6 +161,60 @@ export class FilterNode { return binding; } + public static adjusteBindingWithTreeRelationType(originalBinding: Bindings, + filterExpression: Algebra.Operation): Bindings { + let respBinding = originalBinding; + + // Check if there is one filter or multiple. + if (filterExpression.args[0]?.operator) { + // For each filter expression check if there is an equal expression. + (filterExpression.args).forEach(expression => { + if (expression.operator === '=') { + let variable: string | undefined; + let value: RDF.Term | undefined; + // Find the filter variable and the filter value. + for (const arg of expression.args) { + if ('term' in arg && arg.term.termType === 'Variable') { + variable = arg.term.value; + } else if ('term' in arg && arg.term.termType === 'Literal') { + value = arg.term; + } + } + // If there is a variable and value in the filter expression analyse + // modify the binding if necessary. + if (variable && value) { + respBinding = respBinding.set(variable, value); + } + } + }); + } else { + let variable: string | undefined; + let value: RDF.Term | undefined; + + for (const arg of (filterExpression.args)) { + if ('term' in arg && arg.term.termType === 'Variable') { + variable = arg.term.value; + } else if ('term' in arg && arg.term.termType === 'Literal') { + value = arg.term; + } + } + if (variable && value) { + respBinding = respBinding.set(variable, value); + } + } + return respBinding; + } + + private static evaluateRelationType(relation: ITreeRelation, filterValue: RDF.Term, filterVariable: string):void { + switch (relation.type) { + case RelationOperator.GreaterThanRelation: { + break; + } + default: + break; + } + } + /** * Find the bgp of the original query of the user. * @param {Algebra.Operation} query - the original query @@ -246,3 +301,26 @@ export class FilterNode { return undefined; } } + +enum SparqlOperandDataTypes{ + Integer = 'http://www.w3.org/2001/XMLSchema#integer', + Decimal = 'http://www.w3.org/2001/XMLSchema#decimal', + Float = 'http://www.w3.org/2001/XMLSchema#float', + Double = 'http://www.w3.org/2001/XMLSchema#double', + String = 'http://www.w3.org/2001/XMLSchema#string', + Boolean = 'http://www.w3.org/2001/XMLSchema#boolean', + DateTime = 'http://www.w3.org/2001/XMLSchema#dateTime', + + NonPositiveInteger = 'http://www.w3.org/2001/XMLSchema#nonPositiveInteger', + NegativeInteger = 'http://www.w3.org/2001/XMLSchema#negativeInteger', + Long = 'http://www.w3.org/2001/XMLSchema#long', + Int = 'http://www.w3.org/2001/XMLSchema#int', + Short = 'http://www.w3.org/2001/XMLSchema#short', + Byte = 'http://www.w3.org/2001/XMLSchema#byte', + NonNegativeInteger = 'http://www.w3.org/2001/XMLSchema#nonNegativeInteger', + UnsignedLong = 'http://www.w3.org/2001/XMLSchema#nunsignedLong', + UnsignedInt = 'http://www.w3.org/2001/XMLSchema#unsignedInt', + UnsignedShort = 'http://www.w3.org/2001/XMLSchema#unsignedShort', + UnsignedByte = 'http://www.w3.org/2001/XMLSchema#unsignedByte', + PositiveInteger = 'http://www.w3.org/2001/XMLSchema#positiveInteger' +} From 31e087b247ee08489ca83a818e956925d73d4e7b Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 24 Nov 2022 14:30:18 +0100 Subject: [PATCH 069/189] WIP: solver module started to remplace sparqlee --- .../lib/FilterNode.ts | 78 +----- .../lib/solver.ts | 222 ++++++++++++++++++ .../types-link-traversal/lib/TreeMetadata.ts | 5 +- 3 files changed, 224 insertions(+), 81 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/lib/solver.ts diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 23682ab32..4d30beb30 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -2,13 +2,13 @@ import { BindingsFactory } from '@comunica/bindings-factory'; import { KeysInitQuery } from '@comunica/context-entries'; import type { Bindings, IActionContext } from '@comunica/types'; import type { ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; -import { RelationOperator } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra, Factory as AlgebraFactory } from 'sparqlalgebrajs'; import { AsyncEvaluator } from 'sparqlee'; const AF = new AlgebraFactory(); const BF = new BindingsFactory(); +const Utf8Encode = new TextEncoder(); /** * A class to apply [SPAQL filters](https://www.w3.org/TR/sparql11-query/#evaluation) @@ -161,60 +161,6 @@ export class FilterNode { return binding; } - public static adjusteBindingWithTreeRelationType(originalBinding: Bindings, - filterExpression: Algebra.Operation): Bindings { - let respBinding = originalBinding; - - // Check if there is one filter or multiple. - if (filterExpression.args[0]?.operator) { - // For each filter expression check if there is an equal expression. - (filterExpression.args).forEach(expression => { - if (expression.operator === '=') { - let variable: string | undefined; - let value: RDF.Term | undefined; - // Find the filter variable and the filter value. - for (const arg of expression.args) { - if ('term' in arg && arg.term.termType === 'Variable') { - variable = arg.term.value; - } else if ('term' in arg && arg.term.termType === 'Literal') { - value = arg.term; - } - } - // If there is a variable and value in the filter expression analyse - // modify the binding if necessary. - if (variable && value) { - respBinding = respBinding.set(variable, value); - } - } - }); - } else { - let variable: string | undefined; - let value: RDF.Term | undefined; - - for (const arg of (filterExpression.args)) { - if ('term' in arg && arg.term.termType === 'Variable') { - variable = arg.term.value; - } else if ('term' in arg && arg.term.termType === 'Literal') { - value = arg.term; - } - } - if (variable && value) { - respBinding = respBinding.set(variable, value); - } - } - return respBinding; - } - - private static evaluateRelationType(relation: ITreeRelation, filterValue: RDF.Term, filterVariable: string):void { - switch (relation.type) { - case RelationOperator.GreaterThanRelation: { - break; - } - default: - break; - } - } - /** * Find the bgp of the original query of the user. * @param {Algebra.Operation} query - the original query @@ -302,25 +248,3 @@ export class FilterNode { } } -enum SparqlOperandDataTypes{ - Integer = 'http://www.w3.org/2001/XMLSchema#integer', - Decimal = 'http://www.w3.org/2001/XMLSchema#decimal', - Float = 'http://www.w3.org/2001/XMLSchema#float', - Double = 'http://www.w3.org/2001/XMLSchema#double', - String = 'http://www.w3.org/2001/XMLSchema#string', - Boolean = 'http://www.w3.org/2001/XMLSchema#boolean', - DateTime = 'http://www.w3.org/2001/XMLSchema#dateTime', - - NonPositiveInteger = 'http://www.w3.org/2001/XMLSchema#nonPositiveInteger', - NegativeInteger = 'http://www.w3.org/2001/XMLSchema#negativeInteger', - Long = 'http://www.w3.org/2001/XMLSchema#long', - Int = 'http://www.w3.org/2001/XMLSchema#int', - Short = 'http://www.w3.org/2001/XMLSchema#short', - Byte = 'http://www.w3.org/2001/XMLSchema#byte', - NonNegativeInteger = 'http://www.w3.org/2001/XMLSchema#nonNegativeInteger', - UnsignedLong = 'http://www.w3.org/2001/XMLSchema#nunsignedLong', - UnsignedInt = 'http://www.w3.org/2001/XMLSchema#unsignedInt', - UnsignedShort = 'http://www.w3.org/2001/XMLSchema#unsignedShort', - UnsignedByte = 'http://www.w3.org/2001/XMLSchema#unsignedByte', - PositiveInteger = 'http://www.w3.org/2001/XMLSchema#positiveInteger' -} diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts new file mode 100644 index 000000000..6515e2552 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -0,0 +1,222 @@ +import { RelationOperator } from '@comunica/types-link-traversal'; +import type { ITreeRelation } from '@comunica/types-link-traversal'; +import type * as RDF from 'rdf-js'; +import type { Algebra } from 'sparqlalgebrajs'; + +export function solveRelationWithFilter() { + +} + +function convertTreeRelationToSolverExpression(expression: ITreeRelation, variable: string): SolverExpression | undefined { + if (expression.value && expression.type) { + const valueType = SparqlOperandDataTypesReversed.get((expression.value.term).datatype.value); + if (!valueType) { + return undefined; + } + const valueNumber = castSparqlRdfTermIntoJs(expression.value.value, valueType); + if (!valueNumber) { + return undefined; + } + + return { + variable, + rawValue: expression.value.value, + valueType, + valueAsNumber: valueNumber, + + operator: expression.type, + }; + } +} + +function convertFilterExpressionToSolverExpression(expression: Algebra.OperatorExpression, variable: string): SolverExpression[] | undefined { + const solverExpressionSeries: SolverExpressionSeries = new Map(); + // Check if there is one filter or multiple. + if ('operator' in expression.args[0]) { + const currentSerieOperator = expression.operator; + expression.args.forEach(currentExpression => { + let variable: string | undefined; + let rawValue: string | undefined; + let valueType: SparqlOperandDataTypes | undefined; + let valueAsNumber: number | undefined; + for (const arg of currentExpression.args) { + if ('term' in arg && arg.term.termType === 'Variable') { + variable = arg.term.value; + } else if ('term' in arg && arg.term.termType === 'Literal') { + rawValue = arg.term.value; + valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); + if (valueType) { + valueAsNumber = castSparqlRdfTermIntoJs(rawValue!, valueType); + } + } + } + if (variable && rawValue && valueType && valueAsNumber) { + + } + }); + } + + return undefined; +} + +function areTypeCompatible(relation: ITreeRelation, filterValue: RDF.Term): boolean { + const filterValueType = SparqlOperandDataTypesReversed.get(( filterValue).datatype.value); + const relationValueType = SparqlOperandDataTypesReversed.get((relation.value?.term).datatype.value); + // Unvalid type we will let sparqlee handle the error. + if (!filterValueType || !relationValueType) { + return false; + } + // The type doesn't match we let sparqlee handle the error + if (filterValueType !== relationValueType && + !(isSparqlOperandNumberType(filterValueType) && isSparqlOperandNumberType(relationValueType))) { + return false; + } + return true; +} + +function convertValueToNumber() { + +} + +function evaluateRange() { + +} + +function checkIfRangeOverlap() { + +} + +function evaluateRelationType(relation: ITreeRelation, filterValue: RDF.Term, filterVariable: string): void { + const filterValueType = SparqlOperandDataTypesReversed.get(( filterValue).datatype.value); + const relationValueType = SparqlOperandDataTypesReversed.get((relation.value?.term).datatype.value); + // Unvalid type we will let sparqlee handle the error. + if (!filterValueType || !relationValueType) { + return; + } + // The type doesn't match we let sparqlee handle the error + if (filterValueType !== relationValueType && + !(isSparqlOperandNumberType(filterValueType) && isSparqlOperandNumberType(relationValueType))) { + return; + } + + const filterValueAsNumber = castSparqlRdfTermIntoJs(filterValue.value, filterValueType); +} + +function getPossibleRangeOfExpression(value: number, operator: RelationOperator): [number, number] | undefined { + switch (operator) { + case RelationOperator.GreaterThanRelation: + return [ value + Number.EPSILON, Number.POSITIVE_INFINITY ]; + case RelationOperator.GreaterThanOrEqualToRelation: + return [ value, Number.POSITIVE_INFINITY ]; + case RelationOperator.EqualThanRelation: + return [ value, value ]; + case RelationOperator.LessThanRelation: + return [ Number.NEGATIVE_INFINITY, value - Number.EPSILON ]; + case RelationOperator.LessThanOrEqualToRelation: + return [ Number.NEGATIVE_INFINITY, value ]; + default: + break; + } +} + +function castSparqlRdfTermIntoJs(rdfTermValue: string, rdfTermType: SparqlOperandDataTypes): number | undefined { + let jsValue: number | undefined; + if ( + isSparqlOperandNumberType(rdfTermType) + ) { + jsValue = Number.parseInt(rdfTermValue, 10); + } else if ( + rdfTermType === SparqlOperandDataTypes.Decimal || + rdfTermType === SparqlOperandDataTypes.Float || + rdfTermType === SparqlOperandDataTypes.Double + ) { + jsValue = Number.parseFloat(rdfTermValue); + } else if (rdfTermType === SparqlOperandDataTypes.DateTime) { + jsValue = new Date(rdfTermValue).getTime(); + } + return jsValue; +} + +/** + * Valid SPARQL data type for operation. + */ +enum SparqlOperandDataTypes{ + Integer = 'http://www.w3.org/2001/XMLSchema#integer', + Decimal = 'http://www.w3.org/2001/XMLSchema#decimal', + Float = 'http://www.w3.org/2001/XMLSchema#float', + Double = 'http://www.w3.org/2001/XMLSchema#double', + String = 'http://www.w3.org/2001/XMLSchema#string', + Boolean = 'http://www.w3.org/2001/XMLSchema#boolean', + DateTime = 'http://www.w3.org/2001/XMLSchema#dateTime', + + NonPositiveInteger = 'http://www.w3.org/2001/XMLSchema#nonPositiveInteger', + NegativeInteger = 'http://www.w3.org/2001/XMLSchema#negativeInteger', + Long = 'http://www.w3.org/2001/XMLSchema#long', + Int = 'http://www.w3.org/2001/XMLSchema#int', + Short = 'http://www.w3.org/2001/XMLSchema#short', + Byte = 'http://www.w3.org/2001/XMLSchema#byte', + NonNegativeInteger = 'http://www.w3.org/2001/XMLSchema#nonNegativeInteger', + UnsignedLong = 'http://www.w3.org/2001/XMLSchema#nunsignedLong', + UnsignedInt = 'http://www.w3.org/2001/XMLSchema#unsignedInt', + UnsignedShort = 'http://www.w3.org/2001/XMLSchema#unsignedShort', + UnsignedByte = 'http://www.w3.org/2001/XMLSchema#unsignedByte', + PositiveInteger = 'http://www.w3.org/2001/XMLSchema#positiveInteger' +} + +enum LinkOperator{ + And = '&&', + Or = '||', +} + +function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean { + return rdfTermType === SparqlOperandDataTypes.Integer || + rdfTermType === SparqlOperandDataTypes.NonPositiveInteger || + rdfTermType === SparqlOperandDataTypes.NegativeInteger || + rdfTermType === SparqlOperandDataTypes.Long || + rdfTermType === SparqlOperandDataTypes.Short || + rdfTermType === SparqlOperandDataTypes.NonNegativeInteger || + rdfTermType === SparqlOperandDataTypes.UnsignedLong || + rdfTermType === SparqlOperandDataTypes.UnsignedInt || + rdfTermType === SparqlOperandDataTypes.UnsignedShort || + rdfTermType === SparqlOperandDataTypes.PositiveInteger; +} + +/** + * A map to access the value of the enum SparqlOperandDataTypesReversed by it's value in O(1). + */ +const SparqlOperandDataTypesReversed: Map = + new Map(Object.values(SparqlOperandDataTypes).map(value => [ value, value ])); + +function filterOperatorToRelationOperator(filterOperator: string): RelationOperator | undefined { + switch (filterOperator) { + case '=': + return RelationOperator.EqualThanRelation; + case '<': + return RelationOperator.LessThanRelation; + case '<=': + return RelationOperator.LessThanOrEqualToRelation; + case '>': + return RelationOperator.GreaterThanRelation; + case '>=': + return RelationOperator.GreaterThanOrEqualToRelation; + default: + return undefined; + } +} + +interface SolverExpression{ + variable: Variable; + + rawValue: string; + valueType: SparqlOperandDataTypes; + valueAsNumber: number; + + operator: RelationOperator; + + expressionRange?: [number, number]; +} + type Variable = string; +interface ExpressionLinked { + expression: SolverExpression; link: LinkOperator; +} + type SolverExpressionSeries = Map; diff --git a/packages/types-link-traversal/lib/TreeMetadata.ts b/packages/types-link-traversal/lib/TreeMetadata.ts index da6c592ef..61746de75 100644 --- a/packages/types-link-traversal/lib/TreeMetadata.ts +++ b/packages/types-link-traversal/lib/TreeMetadata.ts @@ -52,10 +52,7 @@ export enum RelationOperator { * A map to access the value of the enum RelationOperator by it's value in O(1). */ export const RelationOperatorReversed: Map = -new Map(Object.values(RelationOperator).map(value => { - const enumIndex = Object.values(RelationOperator).indexOf(value); - return [ Object.values(RelationOperator)[enumIndex], value ]; -})); +new Map(Object.values(RelationOperator).map(value => [ value, value ])); /** * Reference From 8798016a1bd12d9a89b54d6b74e615b5c8f8340a Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 29 Nov 2022 15:41:27 +0100 Subject: [PATCH 070/189] transform the filter into a an expression with a list of operator --- .../lib/FilterNode.ts | 1 + .../lib/SolverType.ts | 128 +++++++++++++ .../lib/solver.ts | 171 ++++++++---------- 3 files changed, 208 insertions(+), 92 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/lib/SolverType.ts diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 4d30beb30..8da9eddc7 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -6,6 +6,7 @@ import type * as RDF from 'rdf-js'; import { Algebra, Factory as AlgebraFactory } from 'sparqlalgebrajs'; import { AsyncEvaluator } from 'sparqlee'; + const AF = new AlgebraFactory(); const BF = new BindingsFactory(); const Utf8Encode = new TextEncoder(); diff --git a/packages/actor-extract-links-extract-tree/lib/SolverType.ts b/packages/actor-extract-links-extract-tree/lib/SolverType.ts new file mode 100644 index 000000000..2d5ecadf2 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/SolverType.ts @@ -0,0 +1,128 @@ +import { RelationOperator } from '@comunica/types-link-traversal'; +/** + * Valid SPARQL data type for operation. + */ +export enum SparqlOperandDataTypes { + Integer = 'http://www.w3.org/2001/XMLSchema#integer', + Decimal = 'http://www.w3.org/2001/XMLSchema#decimal', + Float = 'http://www.w3.org/2001/XMLSchema#float', + Double = 'http://www.w3.org/2001/XMLSchema#double', + String = 'http://www.w3.org/2001/XMLSchema#string', + Boolean = 'http://www.w3.org/2001/XMLSchema#boolean', + DateTime = 'http://www.w3.org/2001/XMLSchema#dateTime', + + NonPositiveInteger = 'http://www.w3.org/2001/XMLSchema#nonPositiveInteger', + NegativeInteger = 'http://www.w3.org/2001/XMLSchema#negativeInteger', + Long = 'http://www.w3.org/2001/XMLSchema#long', + Int = 'http://www.w3.org/2001/XMLSchema#int', + Short = 'http://www.w3.org/2001/XMLSchema#short', + Byte = 'http://www.w3.org/2001/XMLSchema#byte', + NonNegativeInteger = 'http://www.w3.org/2001/XMLSchema#nonNegativeInteger', + UnsignedLong = 'http://www.w3.org/2001/XMLSchema#nunsignedLong', + UnsignedInt = 'http://www.w3.org/2001/XMLSchema#unsignedInt', + UnsignedShort = 'http://www.w3.org/2001/XMLSchema#unsignedShort', + UnsignedByte = 'http://www.w3.org/2001/XMLSchema#unsignedByte', + PositiveInteger = 'http://www.w3.org/2001/XMLSchema#positiveInteger' +} + +export enum LogicOperator { + And = '&&', + Or = '||', + Not = '!', +}; + +export interface LinkOperator { + operator: LogicOperator, + id: number, +} + +export interface SolverEquation { + chainOperator: LinkOperator[]; + expression: SolverExpression; + solutionDomain?: SolutionDomain; +} + +export type Variable = string; + +export interface SolverExpression { + variable: Variable; + + rawValue: string; + valueType: SparqlOperandDataTypes; + valueAsNumber: number; + + operator: RelationOperator; + chainOperator?: string[]; + + expressionRange?: SolutionRange; +}; + +export class SolutionRange { + public readonly upper: number; + public readonly lower: number; + + constructor(upper: number, lower: number) { + this.upper = upper; + this.lower = lower; + } + + public isOverlaping(otherRange: SolutionRange): boolean { + if (this.upper === otherRange.upper && this.lower === otherRange.lower) { + return true; + } else if (this.upper >= otherRange.lower && this.upper <= otherRange.upper) { + return true; + } else if (this.lower >= otherRange.lower && this.lower <= otherRange.upper) { + return true; + } + return false; + } + public fuseRangeIfOverlap(otherRange: SolutionRange): SolutionRange | undefined { + if (this.isOverlaping(otherRange)) { + const lowest = this.lower < otherRange.lower ? this.lower : otherRange.lower; + const uppest = this.upper > otherRange.upper ? this.upper : otherRange.upper; + return new SolutionRange(uppest, lowest); + } + return undefined; + } + + public clone(): SolutionRange { + return new SolutionRange(this.upper, this.lower); + } +} + + +export class SolutionDomain { + private canBeSatisfy: boolean = true; + private domain: SolutionRange[] = []; + private excludedDomain: SolutionRange[] = []; + constructor() { + } + + public add(range: SolutionRange): boolean { + for (const excluded of this.excludedDomain) { + if (excluded.isOverlaping(range)) { + this.canBeSatisfy = false; + return this.canBeSatisfy + } + } + + let currentRange = range.clone(); + let fusedIndex: number[] = []; + + this.domain.map((el, idx) => { + const fusedRange = el.fuseRangeIfOverlap(currentRange); + if (typeof fusedRange !== 'undefined') { + fusedIndex.push(idx); + currentRange = fusedRange; + return fusedRange; + } + return el + }); + + for (let i = 0; i < fusedIndex.length - 1; i++) { + this.domain.splice(fusedIndex[i]); + } + return this.canBeSatisfy; + } +} + diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 6515e2552..e20535a20 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -1,10 +1,19 @@ import { RelationOperator } from '@comunica/types-link-traversal'; import type { ITreeRelation } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; -import type { Algebra } from 'sparqlalgebrajs'; +import { Algebra } from 'sparqlalgebrajs'; +import { SparqlOperandDataTypes, LogicOperator, SolverExpression, LinkOperator, Variable } from './SolverType'; -export function solveRelationWithFilter() { + + + +export function solveRelationWithFilter({relation, filterExpression}:{ + relation:ITreeRelation, + filterExpression: Algebra.Expression, + variables: Set +}):boolean { + return true } function convertTreeRelationToSolverExpression(expression: ITreeRelation, variable: string): SolverExpression | undefined { @@ -29,38 +38,62 @@ function convertTreeRelationToSolverExpression(expression: ITreeRelation, variab } } -function convertFilterExpressionToSolverExpression(expression: Algebra.OperatorExpression, variable: string): SolverExpression[] | undefined { - const solverExpressionSeries: SolverExpressionSeries = new Map(); - // Check if there is one filter or multiple. - if ('operator' in expression.args[0]) { - const currentSerieOperator = expression.operator; - expression.args.forEach(currentExpression => { - let variable: string | undefined; - let rawValue: string | undefined; - let valueType: SparqlOperandDataTypes | undefined; - let valueAsNumber: number | undefined; - for (const arg of currentExpression.args) { - if ('term' in arg && arg.term.termType === 'Variable') { - variable = arg.term.value; - } else if ('term' in arg && arg.term.termType === 'Literal') { - rawValue = arg.term.value; - valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); - if (valueType) { - valueAsNumber = castSparqlRdfTermIntoJs(rawValue!, valueType); - } - } + +function resolveAFilterTerm(expression: Algebra.Expression, operator: RelationOperator, linksOperator: string[]): SolverExpression | undefined { + let variable: string | undefined; + let rawValue: string | undefined; + let valueType: SparqlOperandDataTypes | undefined; + let valueAsNumber: number | undefined; + + for (const arg of expression.args) { + if ('term' in arg && arg.term.termType === 'Variable') { + variable = arg.term.value; + } else if ('term' in arg && arg.term.termType === 'Literal') { + rawValue = arg.term.value; + valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); + if (valueType) { + valueAsNumber = castSparqlRdfTermIntoJs(rawValue!, valueType); } - if (variable && rawValue && valueType && valueAsNumber) { + } + } + if (variable && rawValue && valueType && valueAsNumber) { + return { + variable, + rawValue, + valueType, + valueAsNumber, + operator, + chainOperator: linksOperator, + } + } + return undefined +} +export function convertFilterExpressionToSolverExpression(expression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator:string[]): SolverExpression[] { + if (expression.args.length === 2 && + expression.args[0].expressionType === Algebra.expressionTypes.TERM + ) { + const rawOperator = expression.operator; + const operator = filterOperatorToRelationOperator(rawOperator) + if (typeof operator !== 'undefined') { + const solverExpression = resolveAFilterTerm(expression, operator, new Array(...linksOperator)); + if(typeof solverExpression !== 'undefined') { + filterExpressionList.push(solverExpression); + return filterExpressionList; } - }); + } + } else { + linksOperator.push(expression.operator); + for (const arg of expression.args) { + convertFilterExpressionToSolverExpression(arg, filterExpressionList, linksOperator); + } } - return undefined; + return filterExpressionList; } function areTypeCompatible(relation: ITreeRelation, filterValue: RDF.Term): boolean { - const filterValueType = SparqlOperandDataTypesReversed.get(( filterValue).datatype.value); + const filterValueType = SparqlOperandDataTypesReversed.get((filterValue).datatype.value); const relationValueType = SparqlOperandDataTypesReversed.get((relation.value?.term).datatype.value); // Unvalid type we will let sparqlee handle the error. if (!filterValueType || !relationValueType) { @@ -68,7 +101,7 @@ function areTypeCompatible(relation: ITreeRelation, filterValue: RDF.Term): bool } // The type doesn't match we let sparqlee handle the error if (filterValueType !== relationValueType && - !(isSparqlOperandNumberType(filterValueType) && isSparqlOperandNumberType(relationValueType))) { + !(isSparqlOperandNumberType(filterValueType) && isSparqlOperandNumberType(relationValueType))) { return false; } return true; @@ -87,7 +120,7 @@ function checkIfRangeOverlap() { } function evaluateRelationType(relation: ITreeRelation, filterValue: RDF.Term, filterVariable: string): void { - const filterValueType = SparqlOperandDataTypesReversed.get(( filterValue).datatype.value); + const filterValueType = SparqlOperandDataTypesReversed.get((filterValue).datatype.value); const relationValueType = SparqlOperandDataTypesReversed.get((relation.value?.term).datatype.value); // Unvalid type we will let sparqlee handle the error. if (!filterValueType || !relationValueType) { @@ -95,7 +128,7 @@ function evaluateRelationType(relation: ITreeRelation, filterValue: RDF.Term, fi } // The type doesn't match we let sparqlee handle the error if (filterValueType !== relationValueType && - !(isSparqlOperandNumberType(filterValueType) && isSparqlOperandNumberType(relationValueType))) { + !(isSparqlOperandNumberType(filterValueType) && isSparqlOperandNumberType(relationValueType))) { return; } @@ -105,15 +138,15 @@ function evaluateRelationType(relation: ITreeRelation, filterValue: RDF.Term, fi function getPossibleRangeOfExpression(value: number, operator: RelationOperator): [number, number] | undefined { switch (operator) { case RelationOperator.GreaterThanRelation: - return [ value + Number.EPSILON, Number.POSITIVE_INFINITY ]; + return [value + Number.EPSILON, Number.POSITIVE_INFINITY]; case RelationOperator.GreaterThanOrEqualToRelation: - return [ value, Number.POSITIVE_INFINITY ]; + return [value, Number.POSITIVE_INFINITY]; case RelationOperator.EqualThanRelation: - return [ value, value ]; + return [value, value]; case RelationOperator.LessThanRelation: - return [ Number.NEGATIVE_INFINITY, value - Number.EPSILON ]; + return [Number.NEGATIVE_INFINITY, value - Number.EPSILON]; case RelationOperator.LessThanOrEqualToRelation: - return [ Number.NEGATIVE_INFINITY, value ]; + return [Number.NEGATIVE_INFINITY, value]; default: break; } @@ -127,8 +160,8 @@ function castSparqlRdfTermIntoJs(rdfTermValue: string, rdfTermType: SparqlOperan jsValue = Number.parseInt(rdfTermValue, 10); } else if ( rdfTermType === SparqlOperandDataTypes.Decimal || - rdfTermType === SparqlOperandDataTypes.Float || - rdfTermType === SparqlOperandDataTypes.Double + rdfTermType === SparqlOperandDataTypes.Float || + rdfTermType === SparqlOperandDataTypes.Double ) { jsValue = Number.parseFloat(rdfTermValue); } else if (rdfTermType === SparqlOperandDataTypes.DateTime) { @@ -137,55 +170,24 @@ function castSparqlRdfTermIntoJs(rdfTermValue: string, rdfTermType: SparqlOperan return jsValue; } -/** - * Valid SPARQL data type for operation. - */ -enum SparqlOperandDataTypes{ - Integer = 'http://www.w3.org/2001/XMLSchema#integer', - Decimal = 'http://www.w3.org/2001/XMLSchema#decimal', - Float = 'http://www.w3.org/2001/XMLSchema#float', - Double = 'http://www.w3.org/2001/XMLSchema#double', - String = 'http://www.w3.org/2001/XMLSchema#string', - Boolean = 'http://www.w3.org/2001/XMLSchema#boolean', - DateTime = 'http://www.w3.org/2001/XMLSchema#dateTime', - - NonPositiveInteger = 'http://www.w3.org/2001/XMLSchema#nonPositiveInteger', - NegativeInteger = 'http://www.w3.org/2001/XMLSchema#negativeInteger', - Long = 'http://www.w3.org/2001/XMLSchema#long', - Int = 'http://www.w3.org/2001/XMLSchema#int', - Short = 'http://www.w3.org/2001/XMLSchema#short', - Byte = 'http://www.w3.org/2001/XMLSchema#byte', - NonNegativeInteger = 'http://www.w3.org/2001/XMLSchema#nonNegativeInteger', - UnsignedLong = 'http://www.w3.org/2001/XMLSchema#nunsignedLong', - UnsignedInt = 'http://www.w3.org/2001/XMLSchema#unsignedInt', - UnsignedShort = 'http://www.w3.org/2001/XMLSchema#unsignedShort', - UnsignedByte = 'http://www.w3.org/2001/XMLSchema#unsignedByte', - PositiveInteger = 'http://www.w3.org/2001/XMLSchema#positiveInteger' -} - -enum LinkOperator{ - And = '&&', - Or = '||', -} - function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean { return rdfTermType === SparqlOperandDataTypes.Integer || - rdfTermType === SparqlOperandDataTypes.NonPositiveInteger || - rdfTermType === SparqlOperandDataTypes.NegativeInteger || - rdfTermType === SparqlOperandDataTypes.Long || - rdfTermType === SparqlOperandDataTypes.Short || - rdfTermType === SparqlOperandDataTypes.NonNegativeInteger || - rdfTermType === SparqlOperandDataTypes.UnsignedLong || - rdfTermType === SparqlOperandDataTypes.UnsignedInt || - rdfTermType === SparqlOperandDataTypes.UnsignedShort || - rdfTermType === SparqlOperandDataTypes.PositiveInteger; + rdfTermType === SparqlOperandDataTypes.NonPositiveInteger || + rdfTermType === SparqlOperandDataTypes.NegativeInteger || + rdfTermType === SparqlOperandDataTypes.Long || + rdfTermType === SparqlOperandDataTypes.Short || + rdfTermType === SparqlOperandDataTypes.NonNegativeInteger || + rdfTermType === SparqlOperandDataTypes.UnsignedLong || + rdfTermType === SparqlOperandDataTypes.UnsignedInt || + rdfTermType === SparqlOperandDataTypes.UnsignedShort || + rdfTermType === SparqlOperandDataTypes.PositiveInteger; } /** * A map to access the value of the enum SparqlOperandDataTypesReversed by it's value in O(1). */ const SparqlOperandDataTypesReversed: Map = - new Map(Object.values(SparqlOperandDataTypes).map(value => [ value, value ])); + new Map(Object.values(SparqlOperandDataTypes).map(value => [value, value])); function filterOperatorToRelationOperator(filterOperator: string): RelationOperator | undefined { switch (filterOperator) { @@ -204,19 +206,4 @@ function filterOperatorToRelationOperator(filterOperator: string): RelationOpera } } -interface SolverExpression{ - variable: Variable; - - rawValue: string; - valueType: SparqlOperandDataTypes; - valueAsNumber: number; - operator: RelationOperator; - - expressionRange?: [number, number]; -} - type Variable = string; -interface ExpressionLinked { - expression: SolverExpression; link: LinkOperator; -} - type SolverExpressionSeries = Map; From 39144c7b928beaede3469e59df536ae7dc400a57 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 30 Nov 2022 15:02:24 +0100 Subject: [PATCH 071/189] Expression transform into equation. --- .../lib/SolverType.ts | 48 +++++-- .../lib/solver.ts | 130 ++++++++++-------- 2 files changed, 104 insertions(+), 74 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolverType.ts b/packages/actor-extract-links-extract-tree/lib/SolverType.ts index 2d5ecadf2..e1d4c178b 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverType.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverType.ts @@ -1,4 +1,5 @@ import { RelationOperator } from '@comunica/types-link-traversal'; +import { type } from 'os'; /** * Valid SPARQL data type for operation. */ @@ -31,17 +32,33 @@ export enum LogicOperator { Not = '!', }; -export interface LinkOperator { - operator: LogicOperator, - id: number, +export const LogicOperatorReversed: Map = + new Map(Object.values(LogicOperator).map(value => [value, value])); + +export class LinkOperator { + private readonly operator: LogicOperator; + private readonly id: number; + private static count: number = 0; + + constructor(operator: LogicOperator) { + this.operator = operator; + this.id = LinkOperator.count; + LinkOperator.count++; + } + + public toString(): string { + return `${this.operator}-${this.id}` + } } export interface SolverEquation { chainOperator: LinkOperator[]; - expression: SolverExpression; - solutionDomain?: SolutionDomain; + solutionDomain: SolutionDomain; } +export type SolverEquationSystem = Map; + +export type LastLogicalOperator = string; export type Variable = string; export interface SolverExpression { @@ -52,18 +69,16 @@ export interface SolverExpression { valueAsNumber: number; operator: RelationOperator; - chainOperator?: string[]; - - expressionRange?: SolutionRange; + chainOperator: LinkOperator[]; }; export class SolutionRange { public readonly upper: number; public readonly lower: number; - constructor(upper: number, lower: number) { - this.upper = upper; - this.lower = lower; + constructor(range: [number, number]) { + this.upper = range[0]; + this.lower = range[1]; } public isOverlaping(otherRange: SolutionRange): boolean { @@ -80,24 +95,27 @@ export class SolutionRange { if (this.isOverlaping(otherRange)) { const lowest = this.lower < otherRange.lower ? this.lower : otherRange.lower; const uppest = this.upper > otherRange.upper ? this.upper : otherRange.upper; - return new SolutionRange(uppest, lowest); + return new SolutionRange([uppest, lowest]); } return undefined; } public clone(): SolutionRange { - return new SolutionRange(this.upper, this.lower); + return new SolutionRange([this.upper, this.lower]); } } export class SolutionDomain { private canBeSatisfy: boolean = true; - private domain: SolutionRange[] = []; + private domain: SolutionRange[]; private excludedDomain: SolutionRange[] = []; - constructor() { + + constructor(initialDomain: SolutionRange) { + this.domain = [initialDomain]; } + public add(range: SolutionRange): boolean { for (const excluded of this.excludedDomain) { if (excluded.isOverlaping(range)) { diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index e20535a20..a27242d19 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -2,17 +2,30 @@ import { RelationOperator } from '@comunica/types-link-traversal'; import type { ITreeRelation } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; -import { SparqlOperandDataTypes, LogicOperator, SolverExpression, LinkOperator, Variable } from './SolverType'; - - - - - -export function solveRelationWithFilter({relation, filterExpression}:{ - relation:ITreeRelation, +import { + SparqlOperandDataTypes, SolverEquationSystem, + LogicOperatorReversed, LogicOperator, SolverExpression, + LinkOperator, Variable, SolverEquation, + SolutionRange, SolutionDomain +} from './SolverType'; + +export function solveRelationWithFilter({ relation, filterExpression, variable }: { + relation: ITreeRelation, filterExpression: Algebra.Expression, - variables: Set -}):boolean { + variable: Variable +}): boolean { + const relationsolverExpressions = convertTreeRelationToSolverExpression(relation, variable); + // the relation doesn't have a value or a type, so we accept it + if (typeof relationsolverExpressions === 'undefined'){ + return true; + } + const filtersolverExpressions = convertFilterExpressionToSolverExpression(filterExpression, [], []); + // the type are not compatible no evaluation is possible SPARQLEE will later return an error + if (!areTypeCompatible(filtersolverExpressions.concat(relationsolverExpressions))) { + return false; + } + const filterEquationSystem = createEquationSystem(filtersolverExpressions); + return true } @@ -32,6 +45,7 @@ function convertTreeRelationToSolverExpression(expression: ITreeRelation, variab rawValue: expression.value.value, valueType, valueAsNumber: valueNumber, + chainOperator: [], operator: expression.type, }; @@ -39,7 +53,7 @@ function convertTreeRelationToSolverExpression(expression: ITreeRelation, variab } -function resolveAFilterTerm(expression: Algebra.Expression, operator: RelationOperator, linksOperator: string[]): SolverExpression | undefined { +function resolveAFilterTerm(expression: Algebra.Expression, operator: RelationOperator, linksOperator: LinkOperator[]): SolverExpression | undefined { let variable: string | undefined; let rawValue: string | undefined; let valueType: SparqlOperandDataTypes | undefined; @@ -69,84 +83,82 @@ function resolveAFilterTerm(expression: Algebra.Expression, operator: RelationOp return undefined } -export function convertFilterExpressionToSolverExpression(expression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator:string[]): SolverExpression[] { - if (expression.args.length === 2 && +export function convertFilterExpressionToSolverExpression(expression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator: LinkOperator[]): SolverExpression[] { + + if ( expression.args[0].expressionType === Algebra.expressionTypes.TERM ) { const rawOperator = expression.operator; const operator = filterOperatorToRelationOperator(rawOperator) if (typeof operator !== 'undefined') { const solverExpression = resolveAFilterTerm(expression, operator, new Array(...linksOperator)); - if(typeof solverExpression !== 'undefined') { + if (typeof solverExpression !== 'undefined') { filterExpressionList.push(solverExpression); return filterExpressionList; } } } else { - linksOperator.push(expression.operator); - for (const arg of expression.args) { - convertFilterExpressionToSolverExpression(arg, filterExpressionList, linksOperator); + const logicOperator = LogicOperatorReversed.get(expression.operator); + if (typeof logicOperator !== 'undefined') { + const operator = new LinkOperator(logicOperator); + linksOperator.push(operator); + for (const arg of expression.args) { + convertFilterExpressionToSolverExpression(arg, filterExpressionList, linksOperator); + } } - } - - return filterExpressionList; -} -function areTypeCompatible(relation: ITreeRelation, filterValue: RDF.Term): boolean { - const filterValueType = SparqlOperandDataTypesReversed.get((filterValue).datatype.value); - const relationValueType = SparqlOperandDataTypesReversed.get((relation.value?.term).datatype.value); - // Unvalid type we will let sparqlee handle the error. - if (!filterValueType || !relationValueType) { - return false; - } - // The type doesn't match we let sparqlee handle the error - if (filterValueType !== relationValueType && - !(isSparqlOperandNumberType(filterValueType) && isSparqlOperandNumberType(relationValueType))) { - return false; } - return true; -} - -function convertValueToNumber() { - + return filterExpressionList; } -function evaluateRange() { +export function createEquationSystem(expressions: SolverExpression[]): SolverEquationSystem { + const system: SolverEquationSystem = new Map(); + for (const expression of expressions) { + const lastOperator = expression.chainOperator.slice(-1).toString(); -} + const systemElement = system.get(lastOperator); + const solutionRange = getPossibleRangeOfExpression(expression.valueAsNumber, expression.operator); -function checkIfRangeOverlap() { + if (typeof solutionRange !== 'undefined') { + const equation: SolverEquation = { + chainOperator: expression.chainOperator, + solutionDomain: new SolutionDomain(solutionRange) + }; + if (typeof systemElement !== 'undefined') { + systemElement.push(equation); + } else if (typeof solutionRange !== 'undefined') { + system.set(lastOperator, [equation]); + } + } + } + return system; } -function evaluateRelationType(relation: ITreeRelation, filterValue: RDF.Term, filterVariable: string): void { - const filterValueType = SparqlOperandDataTypesReversed.get((filterValue).datatype.value); - const relationValueType = SparqlOperandDataTypesReversed.get((relation.value?.term).datatype.value); - // Unvalid type we will let sparqlee handle the error. - if (!filterValueType || !relationValueType) { - return; - } - // The type doesn't match we let sparqlee handle the error - if (filterValueType !== relationValueType && - !(isSparqlOperandNumberType(filterValueType) && isSparqlOperandNumberType(relationValueType))) { - return; +function areTypeCompatible(expressions: SolverExpression[]): boolean { + const firstType = expressions[0].valueType; + for (const expression of expressions) { + if (expression.valueType !== firstType || + !(isSparqlOperandNumberType(firstType) && + isSparqlOperandNumberType(expression.valueType))) { + return false + } } - - const filterValueAsNumber = castSparqlRdfTermIntoJs(filterValue.value, filterValueType); + return true } -function getPossibleRangeOfExpression(value: number, operator: RelationOperator): [number, number] | undefined { +function getPossibleRangeOfExpression(value: number, operator: RelationOperator): SolutionRange | undefined { switch (operator) { case RelationOperator.GreaterThanRelation: - return [value + Number.EPSILON, Number.POSITIVE_INFINITY]; + return new SolutionRange([value + Number.EPSILON, Number.POSITIVE_INFINITY]); case RelationOperator.GreaterThanOrEqualToRelation: - return [value, Number.POSITIVE_INFINITY]; + return new SolutionRange([value, Number.POSITIVE_INFINITY]); case RelationOperator.EqualThanRelation: - return [value, value]; + return new SolutionRange([value, value]); case RelationOperator.LessThanRelation: - return [Number.NEGATIVE_INFINITY, value - Number.EPSILON]; + return new SolutionRange([Number.NEGATIVE_INFINITY, value - Number.EPSILON]); case RelationOperator.LessThanOrEqualToRelation: - return [Number.NEGATIVE_INFINITY, value]; + return new SolutionRange([Number.NEGATIVE_INFINITY, value]); default: break; } From 836574aceb3b4545430b016aba2cfea4e4fd53bd Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 5 Dec 2022 13:50:40 +0100 Subject: [PATCH 072/189] Unit test started for SolutionRange and unit test started for SolutionDomain for add or. --- .../lib/SolverType.ts | 107 +++++--- .../lib/solver.ts | 2 +- .../test/SolutionDomain-test.ts | 104 ++++++++ .../test/SolutionRange-test.ts | 233 ++++++++++++++++++ 4 files changed, 409 insertions(+), 37 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts create mode 100644 packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts diff --git a/packages/actor-extract-links-extract-tree/lib/SolverType.ts b/packages/actor-extract-links-extract-tree/lib/SolverType.ts index e1d4c178b..e3d0c909c 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverType.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverType.ts @@ -53,7 +53,7 @@ export class LinkOperator { export interface SolverEquation { chainOperator: LinkOperator[]; - solutionDomain: SolutionDomain; + solutionDomain: SolutionRange; } export type SolverEquationSystem = Map; @@ -77,70 +77,105 @@ export class SolutionRange { public readonly lower: number; constructor(range: [number, number]) { - this.upper = range[0]; - this.lower = range[1]; + if (range[0] > range[1]) { + throw new RangeError('the first element of the range should lower or equal to the second'); + } + this.upper = range[1]; + this.lower = range[0]; } - public isOverlaping(otherRange: SolutionRange): boolean { + public isOverlapping(otherRange: SolutionRange): boolean { if (this.upper === otherRange.upper && this.lower === otherRange.lower) { return true; } else if (this.upper >= otherRange.lower && this.upper <= otherRange.upper) { return true; } else if (this.lower >= otherRange.lower && this.lower <= otherRange.upper) { return true; + } else if (otherRange.lower >= this.lower && otherRange.upper <= this.upper) { + return true; } return false; } - public fuseRangeIfOverlap(otherRange: SolutionRange): SolutionRange | undefined { - if (this.isOverlaping(otherRange)) { - const lowest = this.lower < otherRange.lower ? this.lower : otherRange.lower; - const uppest = this.upper > otherRange.upper ? this.upper : otherRange.upper; - return new SolutionRange([uppest, lowest]); - } - return undefined; + + public isInside(otherRange: SolutionRange): boolean { + return otherRange.lower >= this.lower && otherRange.upper <= this.upper; } - public clone(): SolutionRange { - return new SolutionRange([this.upper, this.lower]); + public static fuseRange(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange[] { + if (subjectRange.isOverlapping(otherRange)) { + const lowest = subjectRange.lower < otherRange.lower ? subjectRange.lower : otherRange.lower; + const uppest = subjectRange.upper > otherRange.upper ? subjectRange.upper : otherRange.upper; + return [new SolutionRange([lowest, uppest])]; + } + return [subjectRange, otherRange]; } } export class SolutionDomain { - private canBeSatisfy: boolean = true; - private domain: SolutionRange[]; + private domain: SolutionRange[] = []; private excludedDomain: SolutionRange[] = []; - constructor(initialDomain: SolutionRange) { - this.domain = [initialDomain]; + constructor() { } + public get_domain(): SolutionRange[]{ + return new Array(...this.domain); + } + + public get_excluded_domain(): SolutionRange[]{ + return new Array(...this.excludedDomain); + } + + public static newWithInitialValue(initialRange: SolutionRange): SolutionDomain { + const newSolutionDomain = new SolutionDomain(); + newSolutionDomain.domain = [initialRange]; + return newSolutionDomain + } - public add(range: SolutionRange): boolean { - for (const excluded of this.excludedDomain) { - if (excluded.isOverlaping(range)) { - this.canBeSatisfy = false; - return this.canBeSatisfy + public clone(): SolutionDomain { + const newSolutionDomain = new SolutionDomain(); + newSolutionDomain.domain = this.domain; + newSolutionDomain.excludedDomain = this.excludedDomain; + return newSolutionDomain; + } + + + public add(range: SolutionRange, operator: LogicOperator): SolutionDomain { + // we check if the new range is inside an excluded solution + if (operator !== LogicOperator.Not) { + for (const excluded of this.excludedDomain) { + if (excluded.isInside(range)) { + return this.clone(); + } } } + return this.clone(); + } - let currentRange = range.clone(); - let fusedIndex: number[] = []; - - this.domain.map((el, idx) => { - const fusedRange = el.fuseRangeIfOverlap(currentRange); - if (typeof fusedRange !== 'undefined') { - fusedIndex.push(idx); - currentRange = fusedRange; - return fusedRange; + public addWithOrOperator(range: SolutionRange): SolutionDomain { + const newDomain = this.clone(); + let currentRange = range; + newDomain.domain = newDomain.domain.filter((el) => { + const resp = SolutionRange.fuseRange(el, currentRange); + if (resp.length === 1) { + currentRange = resp[0]; + return false; } - return el + return true; }); + newDomain.domain.push(currentRange); + newDomain.domain.sort(sortDomainRangeByLowerBound); + return newDomain + } +} - for (let i = 0; i < fusedIndex.length - 1; i++) { - this.domain.splice(fusedIndex[i]); - } - return this.canBeSatisfy; +function sortDomainRangeByLowerBound(firstRange: SolutionRange, secondRange: SolutionRange): number { + if (firstRange.lower < secondRange.lower) { + return -1 + } else if (firstRange.lower > secondRange.lower) { + return 1 } + return 0 } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index a27242d19..21ee58b55 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -122,7 +122,7 @@ export function createEquationSystem(expressions: SolverExpression[]): SolverEqu if (typeof solutionRange !== 'undefined') { const equation: SolverEquation = { chainOperator: expression.chainOperator, - solutionDomain: new SolutionDomain(solutionRange) + solutionDomain: solutionRange }; if (typeof systemElement !== 'undefined') { diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts new file mode 100644 index 000000000..b88995c55 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -0,0 +1,104 @@ +import { SolutionDomain, SolutionRange } from '../lib/SolverType'; + +describe('SolutionDomain', ()=>{ + describe('constructor', ()=>{ + it('should return an empty solution domain', ()=>{ + const solutionDomain = new SolutionDomain(); + + expect(solutionDomain.get_domain().length).toBe(0); + expect(solutionDomain.get_excluded_domain().length).toBe(0); + }); + }); + + describe('newWithInitialValue',()=>{ + it('should create a solution domain with the initial value', ()=>{ + const solutionRange = new SolutionRange([0,1]); + const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); + + expect(solutionDomain.get_domain().length).toBe(1); + expect(solutionDomain.get_domain()[0]).toStrictEqual(solutionRange); + expect(solutionDomain.get_excluded_domain().length).toBe(0); + }); + }); + + describe('addWithOrOperator', ()=>{ + let aDomain: SolutionDomain = new SolutionDomain(); + const aRanges = [ + new SolutionRange([-1,0]), + new SolutionRange([1,5]), + new SolutionRange([10,10]), + new SolutionRange([21,33]), + new SolutionRange([60,70]), + ]; + beforeEach(()=>{ + aDomain = new SolutionDomain(); + for (const r of aRanges){ + aDomain = aDomain.addWithOrOperator(r); + } + }); + it('given an empty domain should be able to add the subject range', ()=>{ + const range = new SolutionRange([0,1]); + const solutionDomain = new SolutionDomain(); + + const newDomain = solutionDomain.addWithOrOperator(range); + + expect(newDomain.get_domain().length).toBe(1); + expect(newDomain.get_domain()[0]).toStrictEqual(range); + }); + + it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', ()=>{ + const ranges = [ + new SolutionRange([10,10]), + new SolutionRange([1,2]), + new SolutionRange([-1,0]), + new SolutionRange([60,70]), + ]; + + let solutionDomain = new SolutionDomain(); + + ranges.forEach((range,idx)=>{ + solutionDomain = solutionDomain.addWithOrOperator(range); + expect(solutionDomain.get_domain().length).toBe(idx+1); + }); + + const expectedDomain = [ + new SolutionRange([-1,0]), + new SolutionRange([1,2]), + new SolutionRange([10,10]), + new SolutionRange([60,70]), + ]; + + expect(solutionDomain.get_domain()).toStrictEqual(expectedDomain); + }); + + it('given a domain should not add a range that is inside another', ()=>{ + const anOverlappingRange = new SolutionRange([22,23]); + const newDomain = aDomain.addWithOrOperator(anOverlappingRange); + + expect(newDomain.get_domain().length).toBe(aDomain.get_domain().length); + expect(newDomain.get_domain()).toStrictEqual(aRanges); + }); + + it('given a domain should create a single domain if all the domain segment are contain into the new range', ()=>{ + const anOverlappingRange = new SolutionRange([-100,100]); + const newDomain = aDomain.addWithOrOperator(anOverlappingRange); + + expect(newDomain.get_domain().length).toBe(1); + expect(newDomain.get_domain()).toStrictEqual([anOverlappingRange]); + }); + + it('given a domain should fuse multiple domain segment if the new range overlap with them', ()=>{ + const aNewRange = new SolutionRange([1,23]); + const newDomain = aDomain.addWithOrOperator(aNewRange); + + const expectedResultingDomainRange = [ + new SolutionRange([-1,0]), + new SolutionRange([1,33]), + new SolutionRange([60,70]), + ]; + + expect(newDomain.get_domain().length).toBe(3); + expect(newDomain.get_domain()).toStrictEqual(expectedResultingDomainRange); + }); + }); +}); \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts new file mode 100644 index 000000000..e02278169 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts @@ -0,0 +1,233 @@ +import { SolutionRange } from '../lib/SolverType'; + +describe('SolutionRange', ()=>{ + describe('constructor', ()=>{ + it('should have the right parameters when building', ()=>{ + const aRange: [number, number] = [0,1]; + const solutionRange = new SolutionRange(aRange); + + expect(solutionRange.lower).toBe(aRange[0]) + expect(solutionRange.upper).toBe(aRange[1]) + + }); + + it('should not throw an error when the domain is unitary', ()=>{ + const aRange: [number, number] = [0,0]; + const solutionRange = new SolutionRange(aRange); + + expect(solutionRange.lower).toBe(aRange[0]) + expect(solutionRange.upper).toBe(aRange[1]) + + }); + + it('should have throw an error when the first element of the range is greater than the second', ()=>{ + const aRange: [number, number] = [1,0]; + expect(()=>{new SolutionRange(aRange)}).toThrow(RangeError); + }); + }); + + describe('isOverlaping', ()=>{ + it('should return true when the solution range have the same range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [0,100]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); + }); + + it('should return true when the other range start before the subject range and end inside the subject range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [-1,99]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); + }); + + it('should return true when the other range start before the subject range and end after the subject range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [-1,101]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); + }); + + it('should return true when the other range start at the subject range and end after the range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [0,101]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); + }); + + it('should return true when the other range start inside the current range and end inside the current range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [1,50]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); + }); + + it('should return true when the other range start at the end of the subject range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [100,500]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); + }); + + it('should return false when the other range is located before the subject range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [-50, -20]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); + }); + + it('should return false when the other range is located after the subject range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [101, 200]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); + }); + }); + describe('isInside', ()=>{ + it('should return true when the other range is inside the subject range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [1,50]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(true); + }); + + it('should return false when the other range is not inside the subject range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [-1,50]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(false); + }); + }); + + describe('fuseRange', ()=>{ + it('given an non overlapping range return both range should return the correct range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [101, 200]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + + expect(resp.length).toBe(2); + expect(resp[0]).toStrictEqual(aSolutionRange); + expect(resp[1]).toStrictEqual(aSecondSolutionRange); + }); + + it('given an overlapping range where the solution range have the same range should return the correct range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [0,100]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(aSolutionRange); + expect(resp[0]).toStrictEqual(aSecondSolutionRange); + }); + + it('given an overlapping range where the other range start before the subject range and end inside the subject range should return the correct range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [-1,99]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + + const expectedRange = new SolutionRange([-1, 100]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); + + it('given an overlapping range where the other range start before the subject range and end after the subject range should return the correct range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [-1,101]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + + const expectedRange = new SolutionRange([-1, 101]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); + + it('given an overlapping range where the other range start at the subject range and end after the range should return the correct range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [0,101]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + + const expectedRange = new SolutionRange([0, 101]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); + + it('given an overlapping range where the other range start inside the current range and end inside the current range should return the correct range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [1,50]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + + const expectedRange = new SolutionRange([0, 100]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); + + it('given an overlapping range where the other range start at the end of the subject range should return the correct range', ()=>{ + const aRange: [number, number] = [0,100]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondRange: [number, number] = [100,500]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + + const expectedRange = new SolutionRange([0, 500]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); + + + }); +}); \ No newline at end of file From deaa784b3bc0559cdcfef8af5cd26d9a090cf70b Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 5 Dec 2022 14:41:32 +0100 Subject: [PATCH 073/189] inverse function for SolutionRange created and tested --- .../lib/SolverType.ts | 44 ++++++++----- .../test/SolutionDomain-test.ts | 2 - .../test/SolutionRange-test.ts | 63 +++++++++++++++++++ 3 files changed, 90 insertions(+), 19 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolverType.ts b/packages/actor-extract-links-extract-tree/lib/SolverType.ts index e3d0c909c..3b86c69d8 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverType.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverType.ts @@ -109,12 +109,27 @@ export class SolutionRange { } return [subjectRange, otherRange]; } + + public inverse(): SolutionRange[]{ + if( this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY){ + return []; + } else if( this.lower === this.upper) { + return []; + } else if( this.lower === Number.NEGATIVE_INFINITY){ + return [new SolutionRange([this.upper+ Number.EPSILON, Number.POSITIVE_INFINITY])] + }else if( this.upper === Number.POSITIVE_INFINITY){ + return [new SolutionRange([Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON])]; + } + return [ + new SolutionRange([Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON]), + new SolutionRange([this.upper, Number.POSITIVE_INFINITY]), + ]; + } } export class SolutionDomain { private domain: SolutionRange[] = []; - private excludedDomain: SolutionRange[] = []; constructor() { } @@ -123,10 +138,6 @@ export class SolutionDomain { return new Array(...this.domain); } - public get_excluded_domain(): SolutionRange[]{ - return new Array(...this.excludedDomain); - } - public static newWithInitialValue(initialRange: SolutionRange): SolutionDomain { const newSolutionDomain = new SolutionDomain(); newSolutionDomain.domain = [initialRange]; @@ -136,20 +147,12 @@ export class SolutionDomain { public clone(): SolutionDomain { const newSolutionDomain = new SolutionDomain(); newSolutionDomain.domain = this.domain; - newSolutionDomain.excludedDomain = this.excludedDomain; return newSolutionDomain; } public add(range: SolutionRange, operator: LogicOperator): SolutionDomain { - // we check if the new range is inside an excluded solution - if (operator !== LogicOperator.Not) { - for (const excluded of this.excludedDomain) { - if (excluded.isInside(range)) { - return this.clone(); - } - } - } + return this.clone(); } @@ -166,16 +169,23 @@ export class SolutionDomain { }); newDomain.domain.push(currentRange); newDomain.domain.sort(sortDomainRangeByLowerBound); + return newDomain; + } + + public notOperation(): SolutionDomain{ + const newDomain = this.clone(); + return newDomain } + } function sortDomainRangeByLowerBound(firstRange: SolutionRange, secondRange: SolutionRange): number { if (firstRange.lower < secondRange.lower) { - return -1 + return -1; } else if (firstRange.lower > secondRange.lower) { - return 1 + return 1; } - return 0 + return 0; } diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index b88995c55..f5c26613d 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -6,7 +6,6 @@ describe('SolutionDomain', ()=>{ const solutionDomain = new SolutionDomain(); expect(solutionDomain.get_domain().length).toBe(0); - expect(solutionDomain.get_excluded_domain().length).toBe(0); }); }); @@ -17,7 +16,6 @@ describe('SolutionDomain', ()=>{ expect(solutionDomain.get_domain().length).toBe(1); expect(solutionDomain.get_domain()[0]).toStrictEqual(solutionRange); - expect(solutionDomain.get_excluded_domain().length).toBe(0); }); }); diff --git a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts index e02278169..4cd2a11bf 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts @@ -227,7 +227,70 @@ describe('SolutionRange', ()=>{ expect(resp.length).toBe(1); expect(resp[0]).toStrictEqual(expectedRange); }); + }); + + describe('inverse', ()=>{ + it('given an real solution range it should return no range', ()=>{ + const aSolutionRange = new SolutionRange([ + Number.NEGATIVE_INFINITY, + Number.POSITIVE_INFINITY + ]); + + expect(aSolutionRange.inverse().length).toBe(0); + }); + + it('given an unique solution should return no range', ()=>{ + const aSolutionRange = new SolutionRange([ + 1, + 1 + ]); + + expect(aSolutionRange.inverse().length).toBe(0); + }); + + it('given a range with an infinite upper bound should return a new range', ()=>{ + const aSolutionRange = new SolutionRange([ + 21, + Number.POSITIVE_INFINITY + ]); + + const expectedRange = new SolutionRange([Number.NEGATIVE_INFINITY, 21- Number.EPSILON]); + + const resp = aSolutionRange.inverse(); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); + + it('given a range with an infinite lower bound should return a new range',()=>{ + const aSolutionRange = new SolutionRange([ + Number.NEGATIVE_INFINITY, + -21 + ]); + + const expectedRange = new SolutionRange([-21 + Number.EPSILON, Number.POSITIVE_INFINITY]); + + const resp = aSolutionRange.inverse(); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); + + it('given a range that is not unitary and doesn\t have infinite bound should return 2 ranges',()=>{ + const aSolutionRange = new SolutionRange([ + -33, + 21 + ]); + const expectedRange = [ + new SolutionRange([Number.NEGATIVE_INFINITY, -33 - Number.EPSILON]), + new SolutionRange([21+ Number.EPSILON, Number.POSITIVE_INFINITY]), + ]; + const resp = aSolutionRange.inverse(); + + expect(resp.length).toBe(2); + expect(resp).toStrictEqual(expectedRange); + }); }); }); \ No newline at end of file From 8e6abf85b636d5539dcf23ead3e20a4b5294d190 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 5 Dec 2022 15:33:58 +0100 Subject: [PATCH 074/189] not operation for SolutionDomain implemented and tested --- .../lib/SolverType.ts | 28 ++++++++------ .../test/SolutionDomain-test.ts | 38 +++++++++++++++++++ 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolverType.ts b/packages/actor-extract-links-extract-tree/lib/SolverType.ts index 3b86c69d8..d88b44b8d 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverType.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverType.ts @@ -110,19 +110,19 @@ export class SolutionRange { return [subjectRange, otherRange]; } - public inverse(): SolutionRange[]{ - if( this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY){ + public inverse(): SolutionRange[] { + if (this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY) { return []; - } else if( this.lower === this.upper) { + } else if (this.lower === this.upper) { return []; - } else if( this.lower === Number.NEGATIVE_INFINITY){ - return [new SolutionRange([this.upper+ Number.EPSILON, Number.POSITIVE_INFINITY])] - }else if( this.upper === Number.POSITIVE_INFINITY){ + } else if (this.lower === Number.NEGATIVE_INFINITY) { + return [new SolutionRange([this.upper + Number.EPSILON, Number.POSITIVE_INFINITY])] + } else if (this.upper === Number.POSITIVE_INFINITY) { return [new SolutionRange([Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON])]; } return [ new SolutionRange([Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON]), - new SolutionRange([this.upper, Number.POSITIVE_INFINITY]), + new SolutionRange([this.upper + Number.EPSILON, Number.POSITIVE_INFINITY]), ]; } } @@ -134,7 +134,7 @@ export class SolutionDomain { constructor() { } - public get_domain(): SolutionRange[]{ + public get_domain(): SolutionRange[] { return new Array(...this.domain); } @@ -152,7 +152,7 @@ export class SolutionDomain { public add(range: SolutionRange, operator: LogicOperator): SolutionDomain { - + return this.clone(); } @@ -172,9 +172,13 @@ export class SolutionDomain { return newDomain; } - public notOperation(): SolutionDomain{ - const newDomain = this.clone(); - + public notOperation(): SolutionDomain { + let newDomain = new SolutionDomain(); + for (const domainElement of this.domain) { + domainElement.inverse().forEach((el) => { + newDomain = newDomain.addWithOrOperator(el); + }) + } return newDomain } diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index f5c26613d..fdb384017 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -99,4 +99,42 @@ describe('SolutionDomain', ()=>{ expect(newDomain.get_domain()).toStrictEqual(expectedResultingDomainRange); }); }); + + describe('notOperation', ()=>{ + it('given a domain with one range should return the inverse of the domain',()=>{ + const solutionRange = new SolutionRange([0,1]); + const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); + + const expectedDomain = [ + new SolutionRange([Number.NEGATIVE_INFINITY,0-Number.EPSILON]), + new SolutionRange([1+Number.EPSILON,Number.POSITIVE_INFINITY]) + ]; + const newDomain = solutionDomain.notOperation(); + + expect(newDomain.get_domain().length).toBe(2); + expect(newDomain.get_domain()).toStrictEqual(expectedDomain); + }); + + it('given a domain with multiple range should return the inverted domain',()=>{ + let domain = new SolutionDomain(); + const ranges = [ + new SolutionRange([0,1]), + new SolutionRange([2,2]), + new SolutionRange([44,55]), + ]; + const expectedDomain = [ + new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]), + ]; + + for(const r of ranges) { + domain = domain.addWithOrOperator(r); + } + domain = domain.notOperation(); + + expect(domain.get_domain()).toStrictEqual(expectedDomain); + + }); + + + }); }); \ No newline at end of file From ddc7a3fec989b83b377d967fe99d414a2143f363 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 6 Dec 2022 07:10:34 +0100 Subject: [PATCH 075/189] SolutionDomain and solution range moved to there own file --- .../lib/SolutionDomain.ts | 68 ++++++++++ .../lib/SolutionRange.ts | 54 ++++++++ .../lib/SolverType.ts | 119 +----------------- .../lib/solver.ts | 3 +- .../test/SolutionDomain-test.ts | 3 +- .../test/SolutionRange-test.ts | 2 +- 6 files changed, 128 insertions(+), 121 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts create mode 100644 packages/actor-extract-links-extract-tree/lib/SolutionRange.ts diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts new file mode 100644 index 000000000..d3b377bc4 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -0,0 +1,68 @@ +import { SolutionRange } from './SolutionRange'; +import { LogicOperator } from './SolverType'; + + +export class SolutionDomain { + private domain: SolutionRange[] = []; + + constructor() { + } + + public get_domain(): SolutionRange[] { + return new Array(...this.domain); + } + + public static newWithInitialValue(initialRange: SolutionRange): SolutionDomain { + const newSolutionDomain = new SolutionDomain(); + newSolutionDomain.domain = [initialRange]; + return newSolutionDomain + } + + public clone(): SolutionDomain { + const newSolutionDomain = new SolutionDomain(); + newSolutionDomain.domain = this.domain; + return newSolutionDomain; + } + + + public add(range: SolutionRange, operator: LogicOperator): SolutionDomain { + + return this.clone(); + } + + public addWithOrOperator(range: SolutionRange): SolutionDomain { + const newDomain = this.clone(); + let currentRange = range; + newDomain.domain = newDomain.domain.filter((el) => { + const resp = SolutionRange.fuseRange(el, currentRange); + if (resp.length === 1) { + currentRange = resp[0]; + return false; + } + return true; + }); + newDomain.domain.push(currentRange); + newDomain.domain.sort(SolutionDomain.sortDomainRangeByLowerBound); + return newDomain; + } + + public notOperation(): SolutionDomain { + let newDomain = new SolutionDomain(); + for (const domainElement of this.domain) { + domainElement.inverse().forEach((el) => { + newDomain = newDomain.addWithOrOperator(el); + }) + } + return newDomain + } + + private static sortDomainRangeByLowerBound(firstRange: SolutionRange, secondRange: SolutionRange): number { + if (firstRange.lower < secondRange.lower) { + return -1; + } else if (firstRange.lower > secondRange.lower) { + return 1; + } + return 0; + } + +} diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts new file mode 100644 index 000000000..194e3de81 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts @@ -0,0 +1,54 @@ +export class SolutionRange { + public readonly upper: number; + public readonly lower: number; + + constructor(range: [number, number]) { + if (range[0] > range[1]) { + throw new RangeError('the first element of the range should lower or equal to the second'); + } + this.upper = range[1]; + this.lower = range[0]; + } + + public isOverlapping(otherRange: SolutionRange): boolean { + if (this.upper === otherRange.upper && this.lower === otherRange.lower) { + return true; + } else if (this.upper >= otherRange.lower && this.upper <= otherRange.upper) { + return true; + } else if (this.lower >= otherRange.lower && this.lower <= otherRange.upper) { + return true; + } else if (otherRange.lower >= this.lower && otherRange.upper <= this.upper) { + return true; + } + return false; + } + + public isInside(otherRange: SolutionRange): boolean { + return otherRange.lower >= this.lower && otherRange.upper <= this.upper; + } + + public static fuseRange(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange[] { + if (subjectRange.isOverlapping(otherRange)) { + const lowest = subjectRange.lower < otherRange.lower ? subjectRange.lower : otherRange.lower; + const uppest = subjectRange.upper > otherRange.upper ? subjectRange.upper : otherRange.upper; + return [new SolutionRange([lowest, uppest])]; + } + return [subjectRange, otherRange]; + } + + public inverse(): SolutionRange[] { + if (this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY) { + return []; + } else if (this.lower === this.upper) { + return []; + } else if (this.lower === Number.NEGATIVE_INFINITY) { + return [new SolutionRange([this.upper + Number.EPSILON, Number.POSITIVE_INFINITY])] + } else if (this.upper === Number.POSITIVE_INFINITY) { + return [new SolutionRange([Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON])]; + } + return [ + new SolutionRange([Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON]), + new SolutionRange([this.upper + Number.EPSILON, Number.POSITIVE_INFINITY]), + ]; + } +} \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/lib/SolverType.ts b/packages/actor-extract-links-extract-tree/lib/SolverType.ts index d88b44b8d..e99fcfded 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverType.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverType.ts @@ -1,5 +1,5 @@ import { RelationOperator } from '@comunica/types-link-traversal'; -import { type } from 'os'; +import { SolutionRange } from './SolutionRange'; /** * Valid SPARQL data type for operation. */ @@ -72,124 +72,7 @@ export interface SolverExpression { chainOperator: LinkOperator[]; }; -export class SolutionRange { - public readonly upper: number; - public readonly lower: number; - constructor(range: [number, number]) { - if (range[0] > range[1]) { - throw new RangeError('the first element of the range should lower or equal to the second'); - } - this.upper = range[1]; - this.lower = range[0]; - } - - public isOverlapping(otherRange: SolutionRange): boolean { - if (this.upper === otherRange.upper && this.lower === otherRange.lower) { - return true; - } else if (this.upper >= otherRange.lower && this.upper <= otherRange.upper) { - return true; - } else if (this.lower >= otherRange.lower && this.lower <= otherRange.upper) { - return true; - } else if (otherRange.lower >= this.lower && otherRange.upper <= this.upper) { - return true; - } - return false; - } - - public isInside(otherRange: SolutionRange): boolean { - return otherRange.lower >= this.lower && otherRange.upper <= this.upper; - } - - public static fuseRange(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange[] { - if (subjectRange.isOverlapping(otherRange)) { - const lowest = subjectRange.lower < otherRange.lower ? subjectRange.lower : otherRange.lower; - const uppest = subjectRange.upper > otherRange.upper ? subjectRange.upper : otherRange.upper; - return [new SolutionRange([lowest, uppest])]; - } - return [subjectRange, otherRange]; - } - - public inverse(): SolutionRange[] { - if (this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY) { - return []; - } else if (this.lower === this.upper) { - return []; - } else if (this.lower === Number.NEGATIVE_INFINITY) { - return [new SolutionRange([this.upper + Number.EPSILON, Number.POSITIVE_INFINITY])] - } else if (this.upper === Number.POSITIVE_INFINITY) { - return [new SolutionRange([Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON])]; - } - return [ - new SolutionRange([Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON]), - new SolutionRange([this.upper + Number.EPSILON, Number.POSITIVE_INFINITY]), - ]; - } -} - - -export class SolutionDomain { - private domain: SolutionRange[] = []; - constructor() { - } - - public get_domain(): SolutionRange[] { - return new Array(...this.domain); - } - public static newWithInitialValue(initialRange: SolutionRange): SolutionDomain { - const newSolutionDomain = new SolutionDomain(); - newSolutionDomain.domain = [initialRange]; - return newSolutionDomain - } - - public clone(): SolutionDomain { - const newSolutionDomain = new SolutionDomain(); - newSolutionDomain.domain = this.domain; - return newSolutionDomain; - } - - - public add(range: SolutionRange, operator: LogicOperator): SolutionDomain { - - return this.clone(); - } - - public addWithOrOperator(range: SolutionRange): SolutionDomain { - const newDomain = this.clone(); - let currentRange = range; - newDomain.domain = newDomain.domain.filter((el) => { - const resp = SolutionRange.fuseRange(el, currentRange); - if (resp.length === 1) { - currentRange = resp[0]; - return false; - } - return true; - }); - newDomain.domain.push(currentRange); - newDomain.domain.sort(sortDomainRangeByLowerBound); - return newDomain; - } - - public notOperation(): SolutionDomain { - let newDomain = new SolutionDomain(); - for (const domainElement of this.domain) { - domainElement.inverse().forEach((el) => { - newDomain = newDomain.addWithOrOperator(el); - }) - } - return newDomain - } - -} - -function sortDomainRangeByLowerBound(firstRange: SolutionRange, secondRange: SolutionRange): number { - if (firstRange.lower < secondRange.lower) { - return -1; - } else if (firstRange.lower > secondRange.lower) { - return 1; - } - return 0; -} diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 21ee58b55..e559d960b 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -2,11 +2,12 @@ import { RelationOperator } from '@comunica/types-link-traversal'; import type { ITreeRelation } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; +import { SolutionDomain } from './SolutionDomain'; +import { SolutionRange } from './SolutionRange'; import { SparqlOperandDataTypes, SolverEquationSystem, LogicOperatorReversed, LogicOperator, SolverExpression, LinkOperator, Variable, SolverEquation, - SolutionRange, SolutionDomain } from './SolverType'; export function solveRelationWithFilter({ relation, filterExpression, variable }: { diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index fdb384017..f50763e40 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -1,4 +1,5 @@ -import { SolutionDomain, SolutionRange } from '../lib/SolverType'; +import { SolutionDomain } from '../lib/SolutionDomain'; +import {SolutionRange} from '../lib//SolutionRange'; describe('SolutionDomain', ()=>{ describe('constructor', ()=>{ diff --git a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts index 4cd2a11bf..2cc5b1d5b 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts @@ -1,4 +1,4 @@ -import { SolutionRange } from '../lib/SolverType'; +import {SolutionRange} from '../lib//SolutionRange'; describe('SolutionRange', ()=>{ describe('constructor', ()=>{ From bac52ea308467f5583453813685281daf5af27b9 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 6 Dec 2022 11:02:39 +0100 Subject: [PATCH 076/189] add method and addWithAndOperator implemented and tested --- .../lib/SolutionDomain.ts | 43 ++- .../lib/SolutionRange.ts | 12 +- .../lib/SolverType.ts | 6 + .../lib/solver.ts | 9 +- .../test/SolutionDomain-test.ts | 246 +++++++++++++----- .../test/SolutionRange-test.ts | 25 ++ 6 files changed, 269 insertions(+), 72 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index d3b377bc4..4a54ade25 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -25,9 +25,28 @@ export class SolutionDomain { } - public add(range: SolutionRange, operator: LogicOperator): SolutionDomain { + public add({ range, operator }: { range?: SolutionRange, operator: LogicOperator }): SolutionDomain { + switch (operator) { + case LogicOperator.And: { + if (typeof range === 'undefined') { + throw ReferenceError('range should be defined with "AND" operator'); + } + return this.addWithAndOperator(range); + } + case LogicOperator.Or: { + if (typeof range === 'undefined') { + throw ReferenceError('range should be defined with "OR" operator'); + } + return this.addWithOrOperator(range); + } - return this.clone(); + case LogicOperator.Not: { + if (range) { + throw ReferenceError('range should not be defined with "NOT" operator') + } + return this.notOperation(); + } + } } public addWithOrOperator(range: SolutionRange): SolutionDomain { @@ -46,6 +65,24 @@ export class SolutionDomain { return newDomain; } + public addWithAndOperator(range: SolutionRange): SolutionDomain { + const newDomain = new SolutionDomain(); + + if (this.domain.length === 0) { + newDomain.domain.push(range); + return newDomain; + } + + this.domain.forEach((el) => { + const intersection = el.getIntersection(range); + if (typeof intersection !== 'undefined') { + newDomain.domain.push(intersection); + } + }); + newDomain.domain.sort(SolutionDomain.sortDomainRangeByLowerBound); + return newDomain; + } + public notOperation(): SolutionDomain { let newDomain = new SolutionDomain(); for (const domainElement of this.domain) { @@ -53,7 +90,7 @@ export class SolutionDomain { newDomain = newDomain.addWithOrOperator(el); }) } - return newDomain + return newDomain; } private static sortDomainRangeByLowerBound(firstRange: SolutionRange, secondRange: SolutionRange): number { diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts index 194e3de81..ebe22be1c 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts @@ -6,8 +6,8 @@ export class SolutionRange { if (range[0] > range[1]) { throw new RangeError('the first element of the range should lower or equal to the second'); } - this.upper = range[1]; this.lower = range[0]; + this.upper = range[1]; } public isOverlapping(otherRange: SolutionRange): boolean { @@ -51,4 +51,14 @@ export class SolutionRange { new SolutionRange([this.upper + Number.EPSILON, Number.POSITIVE_INFINITY]), ]; } + + public getIntersection(otherRange: SolutionRange): SolutionRange | undefined { + if (!this.isOverlapping(otherRange)) { + return undefined; + } + const lower = this.lower > otherRange.lower ? this.lower : otherRange.lower; + const upper = this.upper < otherRange.upper ? this.upper : otherRange.upper; + + return new SolutionRange([lower, upper]); + } } \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/lib/SolverType.ts b/packages/actor-extract-links-extract-tree/lib/SolverType.ts index e99fcfded..b6385d76e 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverType.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverType.ts @@ -26,6 +26,12 @@ export enum SparqlOperandDataTypes { PositiveInteger = 'http://www.w3.org/2001/XMLSchema#positiveInteger' } +/** + * A map to access the value of the enum SparqlOperandDataTypesReversed by it's value in O(1). + */ + export const SparqlOperandDataTypesReversed: Map = + new Map(Object.values(SparqlOperandDataTypes).map(value => [value, value])); + export enum LogicOperator { And = '&&', Or = '||', diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index e559d960b..7c9b82828 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -7,7 +7,7 @@ import { SolutionRange } from './SolutionRange'; import { SparqlOperandDataTypes, SolverEquationSystem, LogicOperatorReversed, LogicOperator, SolverExpression, - LinkOperator, Variable, SolverEquation, + LinkOperator, Variable, SolverEquation, SparqlOperandDataTypesReversed } from './SolverType'; export function solveRelationWithFilter({ relation, filterExpression, variable }: { @@ -53,7 +53,6 @@ function convertTreeRelationToSolverExpression(expression: ITreeRelation, variab } } - function resolveAFilterTerm(expression: Algebra.Expression, operator: RelationOperator, linksOperator: LinkOperator[]): SolverExpression | undefined { let variable: string | undefined; let rawValue: string | undefined; @@ -196,12 +195,6 @@ function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean rdfTermType === SparqlOperandDataTypes.PositiveInteger; } -/** - * A map to access the value of the enum SparqlOperandDataTypesReversed by it's value in O(1). - */ -const SparqlOperandDataTypesReversed: Map = - new Map(Object.values(SparqlOperandDataTypes).map(value => [value, value])); - function filterOperatorToRelationOperator(filterOperator: string): RelationOperator | undefined { switch (filterOperator) { case '=': diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index f50763e40..a916d3388 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -1,18 +1,19 @@ import { SolutionDomain } from '../lib/SolutionDomain'; -import {SolutionRange} from '../lib//SolutionRange'; +import { SolutionRange } from '../lib//SolutionRange'; +import { LogicOperator } from '../lib/SolverType'; -describe('SolutionDomain', ()=>{ - describe('constructor', ()=>{ - it('should return an empty solution domain', ()=>{ +describe('SolutionDomain', () => { + describe('constructor', () => { + it('should return an empty solution domain', () => { const solutionDomain = new SolutionDomain(); expect(solutionDomain.get_domain().length).toBe(0); }); }); - describe('newWithInitialValue',()=>{ - it('should create a solution domain with the initial value', ()=>{ - const solutionRange = new SolutionRange([0,1]); + describe('newWithInitialValue', () => { + it('should create a solution domain with the initial value', () => { + const solutionRange = new SolutionRange([0, 1]); const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); expect(solutionDomain.get_domain().length).toBe(1); @@ -20,23 +21,23 @@ describe('SolutionDomain', ()=>{ }); }); - describe('addWithOrOperator', ()=>{ + describe('addWithOrOperator', () => { let aDomain: SolutionDomain = new SolutionDomain(); const aRanges = [ - new SolutionRange([-1,0]), - new SolutionRange([1,5]), - new SolutionRange([10,10]), - new SolutionRange([21,33]), - new SolutionRange([60,70]), + new SolutionRange([-1, 0]), + new SolutionRange([1, 5]), + new SolutionRange([10, 10]), + new SolutionRange([21, 33]), + new SolutionRange([60, 70]), ]; - beforeEach(()=>{ - aDomain = new SolutionDomain(); - for (const r of aRanges){ + beforeEach(() => { + aDomain = new SolutionDomain(); + for (const r of aRanges) { aDomain = aDomain.addWithOrOperator(r); } }); - it('given an empty domain should be able to add the subject range', ()=>{ - const range = new SolutionRange([0,1]); + it('given an empty domain should be able to add the subject range', () => { + const range = new SolutionRange([0, 1]); const solutionDomain = new SolutionDomain(); const newDomain = solutionDomain.addWithOrOperator(range); @@ -45,70 +46,70 @@ describe('SolutionDomain', ()=>{ expect(newDomain.get_domain()[0]).toStrictEqual(range); }); - it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', ()=>{ + it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { const ranges = [ - new SolutionRange([10,10]), - new SolutionRange([1,2]), - new SolutionRange([-1,0]), - new SolutionRange([60,70]), + new SolutionRange([10, 10]), + new SolutionRange([1, 2]), + new SolutionRange([-1, 0]), + new SolutionRange([60, 70]), ]; let solutionDomain = new SolutionDomain(); - ranges.forEach((range,idx)=>{ + ranges.forEach((range, idx) => { solutionDomain = solutionDomain.addWithOrOperator(range); - expect(solutionDomain.get_domain().length).toBe(idx+1); + expect(solutionDomain.get_domain().length).toBe(idx + 1); }); const expectedDomain = [ - new SolutionRange([-1,0]), - new SolutionRange([1,2]), - new SolutionRange([10,10]), - new SolutionRange([60,70]), + new SolutionRange([-1, 0]), + new SolutionRange([1, 2]), + new SolutionRange([10, 10]), + new SolutionRange([60, 70]), ]; expect(solutionDomain.get_domain()).toStrictEqual(expectedDomain); }); - it('given a domain should not add a range that is inside another', ()=>{ - const anOverlappingRange = new SolutionRange([22,23]); + it('given a domain should not add a range that is inside another', () => { + const anOverlappingRange = new SolutionRange([22, 23]); const newDomain = aDomain.addWithOrOperator(anOverlappingRange); - + expect(newDomain.get_domain().length).toBe(aDomain.get_domain().length); expect(newDomain.get_domain()).toStrictEqual(aRanges); }); - it('given a domain should create a single domain if all the domain segment are contain into the new range', ()=>{ - const anOverlappingRange = new SolutionRange([-100,100]); + it('given a domain should create a single domain if all the domain segment are contain into the new range', () => { + const anOverlappingRange = new SolutionRange([-100, 100]); const newDomain = aDomain.addWithOrOperator(anOverlappingRange); - + expect(newDomain.get_domain().length).toBe(1); expect(newDomain.get_domain()).toStrictEqual([anOverlappingRange]); }); - it('given a domain should fuse multiple domain segment if the new range overlap with them', ()=>{ - const aNewRange = new SolutionRange([1,23]); + it('given a domain should fuse multiple domain segment if the new range overlap with them', () => { + const aNewRange = new SolutionRange([1, 23]); const newDomain = aDomain.addWithOrOperator(aNewRange); const expectedResultingDomainRange = [ - new SolutionRange([-1,0]), - new SolutionRange([1,33]), - new SolutionRange([60,70]), + new SolutionRange([-1, 0]), + new SolutionRange([1, 33]), + new SolutionRange([60, 70]), ]; - + expect(newDomain.get_domain().length).toBe(3); expect(newDomain.get_domain()).toStrictEqual(expectedResultingDomainRange); }); }); - describe('notOperation', ()=>{ - it('given a domain with one range should return the inverse of the domain',()=>{ - const solutionRange = new SolutionRange([0,1]); + describe('notOperation', () => { + it('given a domain with one range should return the inverse of the domain', () => { + const solutionRange = new SolutionRange([0, 1]); const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); const expectedDomain = [ - new SolutionRange([Number.NEGATIVE_INFINITY,0-Number.EPSILON]), - new SolutionRange([1+Number.EPSILON,Number.POSITIVE_INFINITY]) + new SolutionRange([Number.NEGATIVE_INFINITY, 0 - Number.EPSILON]), + new SolutionRange([1 + Number.EPSILON, Number.POSITIVE_INFINITY]) ]; const newDomain = solutionDomain.notOperation(); @@ -116,26 +117,151 @@ describe('SolutionDomain', ()=>{ expect(newDomain.get_domain()).toStrictEqual(expectedDomain); }); - it('given a domain with multiple range should return the inverted domain',()=>{ - let domain = new SolutionDomain(); + it('given a domain with multiple range should return the inverted domain', () => { + let domain = new SolutionDomain(); const ranges = [ - new SolutionRange([0,1]), - new SolutionRange([2,2]), - new SolutionRange([44,55]), - ]; - const expectedDomain = [ - new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]), - ]; + new SolutionRange([0, 1]), + new SolutionRange([2, 2]), + new SolutionRange([44, 55]), + ]; + const expectedDomain = [ + new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]), + ]; + + for (const r of ranges) { + domain = domain.addWithOrOperator(r); + } + domain = domain.notOperation(); - for(const r of ranges) { - domain = domain.addWithOrOperator(r); - } - domain = domain.notOperation(); + expect(domain.get_domain()).toStrictEqual(expectedDomain); - expect(domain.get_domain()).toStrictEqual(expectedDomain); + }); + + }); + + describe('addWithAndOperator', () => { + let aDomain: SolutionDomain = new SolutionDomain(); + const aRanges = [ + new SolutionRange([-1, 0]), + new SolutionRange([1, 5]), + new SolutionRange([10, 10]), + new SolutionRange([21, 33]), + new SolutionRange([60, 70]), + ]; + beforeEach(() => { + aDomain = new SolutionDomain(); + for (const r of aRanges) { + aDomain = aDomain.addWithOrOperator(r); + } + }); + + it('should add a range when the domain is empty', () => { + const domain = new SolutionDomain(); + const aRange = new SolutionRange([0, 1]); + + const newDomain = domain.addWithAndOperator(aRange); + + expect(newDomain.get_domain()).toStrictEqual([aRange]); + }); + + it('should return an empty domain if there is no intersection with the new range', () => { + const aRange = new SolutionRange([-200, -100]); + + const newDomain = aDomain.addWithAndOperator(aRange); + + expect(newDomain.get_domain().length).toBe(0); + }); + + it('given a new range that is inside a part of the domain should only return the intersection', () => { + const aRange = new SolutionRange([22, 30]); + + const newDomain = aDomain.addWithAndOperator(aRange); + + expect(newDomain.get_domain()).toStrictEqual([aRange]); + }); + + it('given a new range that intersect a part of the domain should only return the intersection', () => { + const aRange = new SolutionRange([19, 25]); + + const expectedDomain = [ + new SolutionRange([21, 25]), + ]; + + const newDomain = aDomain.addWithAndOperator(aRange); + + expect(newDomain.get_domain()).toStrictEqual(expectedDomain); + }); + + it('given a new range that intersect multiple part of the domain should only return the intersections', () => { + const aRange = new SolutionRange([-2, 25]); + + const expectedDomain = [ + new SolutionRange([-1, 0]), + new SolutionRange([1, 5]), + new SolutionRange([10, 10]), + new SolutionRange([21, 25]), + ]; + + const newDomain = aDomain.addWithAndOperator(aRange); + + expect(newDomain.get_domain()).toStrictEqual(expectedDomain); + }); + }); + + describe('add', () => { + const aDomain = new SolutionDomain(); + const aRange = new SolutionRange([0, 1]); + + let spyAddWithOrOperator; + + let spyAddWithAndOperator; + + let spyNotOperation; + + beforeEach(() => { + spyAddWithOrOperator = jest.spyOn(aDomain, 'addWithOrOperator') + .mockImplementation((_range: SolutionRange) => { + return new SolutionDomain() + }); + + spyAddWithAndOperator = jest.spyOn(aDomain, 'addWithAndOperator') + .mockImplementation((_range: SolutionRange) => { + return new SolutionDomain() + }); + + spyNotOperation = jest.spyOn(aDomain, 'notOperation') + .mockImplementation(() => { + return new SolutionDomain() + }); + }); + + it('should call "addWithOrOperator" method given the "OR" operator and a new range', () => { + aDomain.add({range:aRange, operator:LogicOperator.Or}) + expect(spyAddWithOrOperator).toBeCalledTimes(1); + }); + + it('should throw an error when adding range with a "OR" operator and no new range', () => { + expect(()=>{aDomain.add({operator:LogicOperator.Or})}).toThrow(); + }); + + it('should call "addWithAndOperator" method given the "AND" operator and a new range', () => { + aDomain.add({range:aRange, operator:LogicOperator.And}) + expect(spyAddWithOrOperator).toBeCalledTimes(1); + }); + + it('should throw an error when adding range with a "AND" operator and no new range', () => { + expect(()=>{aDomain.add({operator:LogicOperator.And})}).toThrow(); }); + it('should call "notOperation" method given the "NOT" operator', () => { + aDomain.add({operator:LogicOperator.Not}) + expect(spyAddWithOrOperator).toBeCalledTimes(1); + }); + + it('should throw an error when adding range with a "NOT" operator and a new range', () => { + expect(()=>{aDomain.add({range:aRange, operator:LogicOperator.Not})}).toThrow(); + }); }); }); \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts index 2cc5b1d5b..0156dd397 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts @@ -293,4 +293,29 @@ describe('SolutionRange', ()=>{ expect(resp).toStrictEqual(expectedRange); }); }); + + describe('getIntersection', ()=>{ + it('given two range that doesn\'t overlap should return no intersection', ()=>{ + const aSolutionRange = new SolutionRange([0, 20]); + const aSecondSolutionRange = new SolutionRange([30, 40]); + + expect(aSolutionRange.getIntersection(aSecondSolutionRange)).toBeUndefined(); + }); + + it('given two range when one is inside the other should return the range at the inside', ()=>{ + const aSolutionRange = new SolutionRange([0, 20]); + const aSecondSolutionRange = new SolutionRange([5, 10]); + + expect(aSolutionRange.getIntersection(aSecondSolutionRange)).toStrictEqual(aSecondSolutionRange); + }); + + it('given two range when they overlap should return the intersection', ()=>{ + const aSolutionRange = new SolutionRange([0, 20]); + const aSecondSolutionRange = new SolutionRange([5, 80]); + + const expectedIntersection = new SolutionRange([5, 20]); + + expect(aSolutionRange.getIntersection(aSecondSolutionRange)).toStrictEqual(expectedIntersection); + }); + }); }); \ No newline at end of file From c20072d958e0382fd7311c2c7516e32acc6a6528 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 6 Dec 2022 11:34:29 +0100 Subject: [PATCH 077/189] LinkOperator implemented in a separated file and tested --- .../lib/LinkOperator.ts | 21 ++++++++++ .../lib/solver.ts | 6 ++- .../{SolverType.ts => solverInterfaces.ts} | 18 +-------- .../test/LinkOperator-test.ts | 39 +++++++++++++++++++ 4 files changed, 66 insertions(+), 18 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/lib/LinkOperator.ts rename packages/actor-extract-links-extract-tree/lib/{SolverType.ts => solverInterfaces.ts} (86%) create mode 100644 packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts diff --git a/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts b/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts new file mode 100644 index 000000000..27880d2ba --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts @@ -0,0 +1,21 @@ +import { LogicOperator } from './solverInterfaces'; + +export class LinkOperator { + private readonly operator: LogicOperator; + public readonly id: number; + private static count: number = 0; + + constructor(operator: LogicOperator) { + this.operator = operator; + this.id = LinkOperator.count; + LinkOperator.count++; + } + + public toString(): string { + return `${this.operator}-${this.id}` + } + + public static resetIdCount(){ + LinkOperator.count = 0; + } +} \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 7c9b82828..50b2980c9 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -4,17 +4,19 @@ import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; import { SolutionDomain } from './SolutionDomain'; import { SolutionRange } from './SolutionRange'; +import { LinkOperator } from './LinkOperator'; import { SparqlOperandDataTypes, SolverEquationSystem, LogicOperatorReversed, LogicOperator, SolverExpression, - LinkOperator, Variable, SolverEquation, SparqlOperandDataTypesReversed -} from './SolverType'; + Variable, SolverEquation, SparqlOperandDataTypesReversed +} from './solverInterfaces'; export function solveRelationWithFilter({ relation, filterExpression, variable }: { relation: ITreeRelation, filterExpression: Algebra.Expression, variable: Variable }): boolean { + LinkOperator.resetIdCount(); const relationsolverExpressions = convertTreeRelationToSolverExpression(relation, variable); // the relation doesn't have a value or a type, so we accept it if (typeof relationsolverExpressions === 'undefined'){ diff --git a/packages/actor-extract-links-extract-tree/lib/SolverType.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts similarity index 86% rename from packages/actor-extract-links-extract-tree/lib/SolverType.ts rename to packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index b6385d76e..6c86c420d 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverType.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -1,5 +1,6 @@ import { RelationOperator } from '@comunica/types-link-traversal'; import { SolutionRange } from './SolutionRange'; +import { LinkOperator } from './LinkOperator'; /** * Valid SPARQL data type for operation. */ @@ -40,22 +41,7 @@ export enum LogicOperator { export const LogicOperatorReversed: Map = new Map(Object.values(LogicOperator).map(value => [value, value])); - -export class LinkOperator { - private readonly operator: LogicOperator; - private readonly id: number; - private static count: number = 0; - - constructor(operator: LogicOperator) { - this.operator = operator; - this.id = LinkOperator.count; - LinkOperator.count++; - } - - public toString(): string { - return `${this.operator}-${this.id}` - } -} + export interface SolverEquation { chainOperator: LinkOperator[]; diff --git a/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts new file mode 100644 index 000000000..15fc7839a --- /dev/null +++ b/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts @@ -0,0 +1,39 @@ +import {LinkOperator} from '../lib/LinkOperator'; +import {LogicOperator} from '../lib/solverInterfaces'; + +describe('LinkOperator', ()=>{ + describe('constructor',()=>{ + it('when constructing multiple linkOperator the id should be incremented', ()=>{ + const operators = [LogicOperator.And, LogicOperator.Not, LogicOperator.Or]; + for(let i=0;i<100;i++){ + expect((new LinkOperator(operators[i%operators.length])).id).toBe(i); + } + }) + }); + describe('toString', ()=>{ + beforeAll(()=>{ + LinkOperator.resetIdCount(); + }); + it('should return a string expressing the operator and the id', ()=>{ + const operators = [LogicOperator.And, LogicOperator.Not, LogicOperator.Or]; + for(let i=0;i<100;i++){ + const operator = operators[i%operators.length]; + expect((new LinkOperator(operator)).toString()).toBe(`${operator}-${i}`); + } + }); + }); + + describe('resetIdCount', ()=>{ + beforeAll(()=>{ + LinkOperator.resetIdCount(); + }); + it('should reset the count when the function is called', ()=>{ + const operators = [LogicOperator.And, LogicOperator.Not, LogicOperator.Or]; + for(let i=0;i<10;i++){ + expect((new LinkOperator(operators[i%operators.length])).id).toBe(i); + } + LinkOperator.resetIdCount(); + expect((new LinkOperator(operators[0])).id).toBe(0); + }); + }); +}); \ No newline at end of file From 7a286ef12cc960315c70877ea09134e50f5eb97a Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 6 Dec 2022 15:17:19 +0100 Subject: [PATCH 078/189] WIP: resolve domain function started. --- .../lib/LinkOperator.ts | 2 +- .../lib/SolutionDomain.ts | 3 - .../lib/solver.ts | 68 +++++++++++++++---- .../lib/solverInterfaces.ts | 2 +- .../test/SolutionDomain-test.ts | 4 -- 5 files changed, 56 insertions(+), 23 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts b/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts index 27880d2ba..0d39cc348 100644 --- a/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts @@ -1,7 +1,7 @@ import { LogicOperator } from './solverInterfaces'; export class LinkOperator { - private readonly operator: LogicOperator; + public readonly operator: LogicOperator; public readonly id: number; private static count: number = 0; diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 4a54ade25..6b23a2760 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -41,9 +41,6 @@ export class SolutionDomain { } case LogicOperator.Not: { - if (range) { - throw ReferenceError('range should not be defined with "NOT" operator') - } return this.notOperation(); } } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 50b2980c9..77c11d1df 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -10,6 +10,7 @@ import { LogicOperatorReversed, LogicOperator, SolverExpression, Variable, SolverEquation, SparqlOperandDataTypesReversed } from './solverInterfaces'; +import { LastLogicalOperator } from './SolverType'; export function solveRelationWithFilter({ relation, filterExpression, variable }: { relation: ITreeRelation, @@ -24,7 +25,7 @@ export function solveRelationWithFilter({ relation, filterExpression, variable } } const filtersolverExpressions = convertFilterExpressionToSolverExpression(filterExpression, [], []); // the type are not compatible no evaluation is possible SPARQLEE will later return an error - if (!areTypeCompatible(filtersolverExpressions.concat(relationsolverExpressions))) { + if (!areTypesCompatible(filtersolverExpressions.concat(relationsolverExpressions))) { return false; } const filterEquationSystem = createEquationSystem(filtersolverExpressions); @@ -38,7 +39,7 @@ function convertTreeRelationToSolverExpression(expression: ITreeRelation, variab if (!valueType) { return undefined; } - const valueNumber = castSparqlRdfTermIntoJs(expression.value.value, valueType); + const valueNumber = castSparqlRdfTermIntoNumber(expression.value.value, valueType); if (!valueNumber) { return undefined; } @@ -68,7 +69,7 @@ function resolveAFilterTerm(expression: Algebra.Expression, operator: RelationOp rawValue = arg.term.value; valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); if (valueType) { - valueAsNumber = castSparqlRdfTermIntoJs(rawValue!, valueType); + valueAsNumber = castSparqlRdfTermIntoNumber(rawValue!, valueType); } } } @@ -113,13 +114,16 @@ export function convertFilterExpressionToSolverExpression(expression: Algebra.Ex return filterExpressionList; } -export function createEquationSystem(expressions: SolverExpression[]): SolverEquationSystem { +export function createEquationSystem(expressions: SolverExpression[]): [SolverEquationSystem, LastLogicalOperator, SolverEquation[]] { const system: SolverEquationSystem = new Map(); + let firstEquationToEvaluate:[string, SolverEquation[]]| undefined = undefined; + + for (const expression of expressions) { const lastOperator = expression.chainOperator.slice(-1).toString(); const systemElement = system.get(lastOperator); - const solutionRange = getPossibleRangeOfExpression(expression.valueAsNumber, expression.operator); + const solutionRange = getSolutionRange(expression.valueAsNumber, expression.operator); if (typeof solutionRange !== 'undefined') { const equation: SolverEquation = { @@ -127,17 +131,55 @@ export function createEquationSystem(expressions: SolverExpression[]): SolverEqu solutionDomain: solutionRange }; - if (typeof systemElement !== 'undefined') { + if (Array.isArray(systemElement) ) { systemElement.push(equation); + if(firstEquationToEvaluate){ + throw Error('there should not be multiple first equation to resolve'); + } + firstEquationToEvaluate = [lastOperator, systemElement]; } else if (typeof solutionRange !== 'undefined') { - system.set(lastOperator, [equation]); + system.set(lastOperator, equation); } } } - return system; + if(typeof firstEquationToEvaluate === 'undefined'){ + throw Error('there should be one possible equation to resolve'); + } + return [system, firstEquationToEvaluate[0], firstEquationToEvaluate[1]]; } -function areTypeCompatible(expressions: SolverExpression[]): boolean { +export function resolveSolutionDomain(equationSystem:SolverEquationSystem, firstEquation: [LastLogicalOperator, [SolverEquation, SolverEquation]]): SolutionDomain{ + const localEquationSystem = new Map(equationSystem); + const currentEquations: [SolverEquation, SolverEquation] = firstEquation[1]; + const chainOperator = currentEquations[0].chainOperator; + if(chainOperator.length===0){ + throw Error('there should be at least one operator in the first equation'); + } + const operator: LogicOperator = currentEquations[0].chainOperator.pop()?.operator; + + let indexEquation = firstEquation[0]; + + let domain = SolutionDomain.newWithInitialValue(currentEquations[0].solutionDomain); + domain = domain.add({range:currentEquations[1].solutionDomain, operator:operator}); + + indexEquation = currentEquations[0].chainOperator.pop()?.toString(); + + localEquationSystem.delete(indexEquation); + while(localEquationSystem.size !==0){ + const nextEquation = localEquationSystem.get(indexEquation); + if(typeof nextEquation === 'undefined'){ + throw Error('operation does\'t exist in the system of equation'); + }else if(Array.isArray(nextEquation)){ + throw Error('there should be one equation in the rest of the equation system'); + } + const operator = nextEquation.chainOperator.slice(-1)[0].operator; + domain = domain.add({range:nextEquation.solutionDomain, operator}); + } + + return domain; +} + +function areTypesCompatible(expressions: SolverExpression[]): boolean { const firstType = expressions[0].valueType; for (const expression of expressions) { if (expression.valueType !== firstType || @@ -149,7 +191,7 @@ function areTypeCompatible(expressions: SolverExpression[]): boolean { return true } -function getPossibleRangeOfExpression(value: number, operator: RelationOperator): SolutionRange | undefined { +function getSolutionRange(value: number, operator: RelationOperator): SolutionRange | undefined { switch (operator) { case RelationOperator.GreaterThanRelation: return new SolutionRange([value + Number.EPSILON, Number.POSITIVE_INFINITY]); @@ -166,7 +208,7 @@ function getPossibleRangeOfExpression(value: number, operator: RelationOperator) } } -function castSparqlRdfTermIntoJs(rdfTermValue: string, rdfTermType: SparqlOperandDataTypes): number | undefined { +function castSparqlRdfTermIntoNumber(rdfTermValue: string, rdfTermType: SparqlOperandDataTypes): number | undefined { let jsValue: number | undefined; if ( isSparqlOperandNumberType(rdfTermType) @@ -212,6 +254,4 @@ function filterOperatorToRelationOperator(filterOperator: string): RelationOpera default: return undefined; } -} - - +} \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index 6c86c420d..c4efc7722 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -48,7 +48,7 @@ export interface SolverEquation { solutionDomain: SolutionRange; } -export type SolverEquationSystem = Map; +export type SolverEquationSystem = Map; export type LastLogicalOperator = string; export type Variable = string; diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index a916d3388..edf77905f 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -259,9 +259,5 @@ describe('SolutionDomain', () => { aDomain.add({operator:LogicOperator.Not}) expect(spyAddWithOrOperator).toBeCalledTimes(1); }); - - it('should throw an error when adding range with a "NOT" operator and a new range', () => { - expect(()=>{aDomain.add({range:aRange, operator:LogicOperator.Not})}).toThrow(); - }); }); }); \ No newline at end of file From 29975b0e7add3897e6a1f568c4b5d26f6714da1b Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 8 Dec 2022 09:48:15 +0100 Subject: [PATCH 079/189] resolveEquationSystem created but not tested. --- .../lib/solver.ts | 113 ++++++++++-------- .../lib/solverInterfaces.ts | 2 +- 2 files changed, 65 insertions(+), 50 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 77c11d1df..eb5f1e5a6 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -8,7 +8,7 @@ import { LinkOperator } from './LinkOperator'; import { SparqlOperandDataTypes, SolverEquationSystem, LogicOperatorReversed, LogicOperator, SolverExpression, - Variable, SolverEquation, SparqlOperandDataTypesReversed + Variable, SolverEquation, SparqlOperandDataTypesReversed } from './solverInterfaces'; import { LastLogicalOperator } from './SolverType'; @@ -20,7 +20,7 @@ export function solveRelationWithFilter({ relation, filterExpression, variable } LinkOperator.resetIdCount(); const relationsolverExpressions = convertTreeRelationToSolverExpression(relation, variable); // the relation doesn't have a value or a type, so we accept it - if (typeof relationsolverExpressions === 'undefined'){ + if (typeof relationsolverExpressions === 'undefined') { return true; } const filtersolverExpressions = convertFilterExpressionToSolverExpression(filterExpression, [], []); @@ -114,71 +114,86 @@ export function convertFilterExpressionToSolverExpression(expression: Algebra.Ex return filterExpressionList; } -export function createEquationSystem(expressions: SolverExpression[]): [SolverEquationSystem, LastLogicalOperator, SolverEquation[]] { +export function createEquationSystem(expressions: SolverExpression[]): [SolverEquationSystem, [SolverEquation, SolverEquation]] | undefined { const system: SolverEquationSystem = new Map(); - let firstEquationToEvaluate:[string, SolverEquation[]]| undefined = undefined; - + let firstEquationToEvaluate: [SolverEquation, SolverEquation] | undefined = undefined; for (const expression of expressions) { const lastOperator = expression.chainOperator.slice(-1).toString(); - - const systemElement = system.get(lastOperator); const solutionRange = getSolutionRange(expression.valueAsNumber, expression.operator); - - if (typeof solutionRange !== 'undefined') { - const equation: SolverEquation = { - chainOperator: expression.chainOperator, - solutionDomain: solutionRange - }; - - if (Array.isArray(systemElement) ) { - systemElement.push(equation); - if(firstEquationToEvaluate){ - throw Error('there should not be multiple first equation to resolve'); - } - firstEquationToEvaluate = [lastOperator, systemElement]; - } else if (typeof solutionRange !== 'undefined') { - system.set(lastOperator, equation); + if (!solutionRange) { + return undefined; + } + const equation: SolverEquation = { + chainOperator: expression.chainOperator, + solutionDomain: solutionRange + }; + const lastEquation = system.get(lastOperator); + if (lastEquation) { + if (firstEquationToEvaluate) { + return undefined; } + firstEquationToEvaluate = [lastEquation, equation]; + system.delete(lastOperator); + } else { + system.set(lastOperator, equation); } + } - if(typeof firstEquationToEvaluate === 'undefined'){ - throw Error('there should be one possible equation to resolve'); + if (!firstEquationToEvaluate) { + return undefined; } - return [system, firstEquationToEvaluate[0], firstEquationToEvaluate[1]]; + return [system, firstEquationToEvaluate]; } -export function resolveSolutionDomain(equationSystem:SolverEquationSystem, firstEquation: [LastLogicalOperator, [SolverEquation, SolverEquation]]): SolutionDomain{ - const localEquationSystem = new Map(equationSystem); - const currentEquations: [SolverEquation, SolverEquation] = firstEquation[1]; - const chainOperator = currentEquations[0].chainOperator; - if(chainOperator.length===0){ - throw Error('there should be at least one operator in the first equation'); - } - const operator: LogicOperator = currentEquations[0].chainOperator.pop()?.operator; +export function resolveEquationSystem(equationSystem: SolverEquationSystem, firstEquation: [SolverEquation, SolverEquation]): SolutionDomain | undefined { + const localEquationSystem = new Map(equationSystem); + const localFistEquation = new Array(...firstEquation); + let domain: SolutionDomain = SolutionDomain.newWithInitialValue(localFistEquation[0].solutionDomain); + let idx: string = ""; + let currentEquation: SolverEquation | undefined = localFistEquation[1]; - let indexEquation = firstEquation[0]; - - let domain = SolutionDomain.newWithInitialValue(currentEquations[0].solutionDomain); - domain = domain.add({range:currentEquations[1].solutionDomain, operator:operator}); + do { + const resp = resolveEquation(currentEquation, domain); + if (!resp) { + return undefined; + } + [domain, idx] = resp; - indexEquation = currentEquations[0].chainOperator.pop()?.toString(); + currentEquation = localEquationSystem.get(idx); - localEquationSystem.delete(indexEquation); - while(localEquationSystem.size !==0){ - const nextEquation = localEquationSystem.get(indexEquation); - if(typeof nextEquation === 'undefined'){ - throw Error('operation does\'t exist in the system of equation'); - }else if(Array.isArray(nextEquation)){ - throw Error('there should be one equation in the rest of the equation system'); + } while (currentEquation) + + return domain; +} + +function resolveEquation(equation: SolverEquation, domain: SolutionDomain): [SolutionDomain, LastLogicalOperator] | undefined { + let localDomain = domain.clone(); + let i = -1; + let currentLastOperator = equation.chainOperator.at(i); + if (!currentLastOperator) { + return undefined; + } + i--; + localDomain = localDomain.add({ range: equation.solutionDomain, operator: currentLastOperator?.operator }); + + currentLastOperator = equation.chainOperator.at(i); + if (!currentLastOperator) { + return [localDomain, ""]; + } + while (currentLastOperator?.operator === LogicOperator.Not) { + localDomain = localDomain.add({ operator: currentLastOperator?.operator }); + i--; + currentLastOperator = equation.chainOperator.at(i); + if (!currentLastOperator?.operator) { + return [localDomain, ""]; } - const operator = nextEquation.chainOperator.slice(-1)[0].operator; - domain = domain.add({range:nextEquation.solutionDomain, operator}); } - return domain; + return [localDomain, currentLastOperator.toString()] } + function areTypesCompatible(expressions: SolverExpression[]): boolean { const firstType = expressions[0].valueType; for (const expression of expressions) { @@ -254,4 +269,4 @@ function filterOperatorToRelationOperator(filterOperator: string): RelationOpera default: return undefined; } -} \ No newline at end of file +} diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index c4efc7722..056abbcef 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -48,7 +48,7 @@ export interface SolverEquation { solutionDomain: SolutionRange; } -export type SolverEquationSystem = Map; +export type SolverEquationSystem = Map; export type LastLogicalOperator = string; export type Variable = string; From 0e5cccac38e1c7f7f10fab6b1f0bf7e94631ff2b Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 8 Dec 2022 10:24:52 +0100 Subject: [PATCH 080/189] test for filterOperatorToRelationOperator --- .../lib/solver.ts | 5 ++-- .../test/solver-test.ts | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/test/solver-test.ts diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index eb5f1e5a6..5e08a8a82 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -193,7 +193,6 @@ function resolveEquation(equation: SolverEquation, domain: SolutionDomain): [Sol return [localDomain, currentLastOperator.toString()] } - function areTypesCompatible(expressions: SolverExpression[]): boolean { const firstType = expressions[0].valueType; for (const expression of expressions) { @@ -241,7 +240,7 @@ function castSparqlRdfTermIntoNumber(rdfTermValue: string, rdfTermType: SparqlOp return jsValue; } -function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean { +export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean { return rdfTermType === SparqlOperandDataTypes.Integer || rdfTermType === SparqlOperandDataTypes.NonPositiveInteger || rdfTermType === SparqlOperandDataTypes.NegativeInteger || @@ -254,7 +253,7 @@ function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean rdfTermType === SparqlOperandDataTypes.PositiveInteger; } -function filterOperatorToRelationOperator(filterOperator: string): RelationOperator | undefined { +export function filterOperatorToRelationOperator(filterOperator: string): RelationOperator | undefined { switch (filterOperator) { case '=': return RelationOperator.EqualThanRelation; diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts new file mode 100644 index 000000000..2c3c74d40 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -0,0 +1,24 @@ +import { filterOperatorToRelationOperator } from '../lib/solver'; +import { RelationOperator } from '@comunica/types-link-traversal'; + + +describe('solver function',()=>{ + describe('filterOperatorToRelationOperator', ()=>{ + it('should return the RelationOperator given a string representation',()=>{ + const testTable: [string, RelationOperator][] =[ + ['=',RelationOperator.EqualThanRelation], + ['<', RelationOperator.LessThanRelation], + ['<=', RelationOperator.LessThanOrEqualToRelation], + ['>', RelationOperator.GreaterThanRelation], + ['>=', RelationOperator.GreaterThanOrEqualToRelation] + ]; + + for(const [value, expectedAnswer] of testTable){ + expect(filterOperatorToRelationOperator(value)).toBe(expectedAnswer); + } + }); + it('should return undefined given a string not representing a RelationOperator', ()=>{ + expect(filterOperatorToRelationOperator("foo")).toBeUndefined(); + }); + }); +}); \ No newline at end of file From 897e880a7ba51e586197ee2e752b06c638ce894e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 8 Dec 2022 13:34:56 +0100 Subject: [PATCH 081/189] unit test function filterOperatorToRelationOperator, isSparqlOperandNumberType, castSparqlRdfTermIntoNumber, getSolutionRange and areTypesCompatible --- .../lib/solver.ts | 83 ++--- .../test/solver-test.ts | 295 +++++++++++++++++- 2 files changed, 333 insertions(+), 45 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 5e08a8a82..2ebe8e735 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -33,28 +33,7 @@ export function solveRelationWithFilter({ relation, filterExpression, variable } return true } -function convertTreeRelationToSolverExpression(expression: ITreeRelation, variable: string): SolverExpression | undefined { - if (expression.value && expression.type) { - const valueType = SparqlOperandDataTypesReversed.get((expression.value.term).datatype.value); - if (!valueType) { - return undefined; - } - const valueNumber = castSparqlRdfTermIntoNumber(expression.value.value, valueType); - if (!valueNumber) { - return undefined; - } - return { - variable, - rawValue: expression.value.value, - valueType, - valueAsNumber: valueNumber, - chainOperator: [], - - operator: expression.type, - }; - } -} function resolveAFilterTerm(expression: Algebra.Expression, operator: RelationOperator, linksOperator: LinkOperator[]): SolverExpression | undefined { let variable: string | undefined; @@ -193,19 +172,45 @@ function resolveEquation(equation: SolverEquation, domain: SolutionDomain): [Sol return [localDomain, currentLastOperator.toString()] } -function areTypesCompatible(expressions: SolverExpression[]): boolean { +export function convertTreeRelationToSolverExpression(expression: ITreeRelation, variable: string): SolverExpression | undefined { + if (expression.value && expression.type) { + const valueType = SparqlOperandDataTypesReversed.get((expression.value.term).datatype.value); + if (!valueType) { + return undefined; + } + const valueNumber = castSparqlRdfTermIntoNumber(expression.value.value, valueType); + if (!valueNumber) { + return undefined; + } + + return { + variable, + rawValue: expression.value.value, + valueType, + valueAsNumber: valueNumber, + chainOperator: [], + + operator: expression.type, + }; + } +} + +export function areTypesCompatible(expressions: SolverExpression[]): boolean { const firstType = expressions[0].valueType; for (const expression of expressions) { - if (expression.valueType !== firstType || - !(isSparqlOperandNumberType(firstType) && - isSparqlOperandNumberType(expression.valueType))) { + + const areIdentical = expression.valueType === firstType; + const areNumbers = isSparqlOperandNumberType(firstType) && + isSparqlOperandNumberType(expression.valueType); + + if (!(areIdentical || areNumbers)) { return false } } return true } -function getSolutionRange(value: number, operator: RelationOperator): SolutionRange | undefined { +export function getSolutionRange(value: number, operator: RelationOperator): SolutionRange | undefined { switch (operator) { case RelationOperator.GreaterThanRelation: return new SolutionRange([value + Number.EPSILON, Number.POSITIVE_INFINITY]); @@ -222,22 +227,24 @@ function getSolutionRange(value: number, operator: RelationOperator): SolutionRa } } -function castSparqlRdfTermIntoNumber(rdfTermValue: string, rdfTermType: SparqlOperandDataTypes): number | undefined { - let jsValue: number | undefined; +export function castSparqlRdfTermIntoNumber(rdfTermValue: string, rdfTermType: SparqlOperandDataTypes): number | undefined { if ( - isSparqlOperandNumberType(rdfTermType) - ) { - jsValue = Number.parseInt(rdfTermValue, 10); - } else if ( rdfTermType === SparqlOperandDataTypes.Decimal || rdfTermType === SparqlOperandDataTypes.Float || rdfTermType === SparqlOperandDataTypes.Double ) { - jsValue = Number.parseFloat(rdfTermValue); + const val = Number.parseFloat(rdfTermValue) + return Number.isNaN(val) ? undefined : val; + } else if ( + isSparqlOperandNumberType(rdfTermType) && !rdfTermValue.includes('.') + ) { + const val = Number.parseInt(rdfTermValue, 10) + return Number.isNaN(val) ? undefined : val; } else if (rdfTermType === SparqlOperandDataTypes.DateTime) { - jsValue = new Date(rdfTermValue).getTime(); + const val = new Date(rdfTermValue).getTime() + return Number.isNaN(val) ? undefined : val; } - return jsValue; + return undefined; } export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean { @@ -250,7 +257,11 @@ export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): rdfTermType === SparqlOperandDataTypes.UnsignedLong || rdfTermType === SparqlOperandDataTypes.UnsignedInt || rdfTermType === SparqlOperandDataTypes.UnsignedShort || - rdfTermType === SparqlOperandDataTypes.PositiveInteger; + rdfTermType === SparqlOperandDataTypes.PositiveInteger || + rdfTermType === SparqlOperandDataTypes.Float || + rdfTermType === SparqlOperandDataTypes.Double || + rdfTermType === SparqlOperandDataTypes.Decimal|| + rdfTermType === SparqlOperandDataTypes.Int; } export function filterOperatorToRelationOperator(filterOperator: string): RelationOperator | undefined { diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 2c3c74d40..3284376ae 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1,24 +1,301 @@ -import { filterOperatorToRelationOperator } from '../lib/solver'; +import { + filterOperatorToRelationOperator, + isSparqlOperandNumberType, + castSparqlRdfTermIntoNumber, + getSolutionRange, + areTypesCompatible +} from '../lib/solver'; +import { SolutionRange } from '../lib/SolutionRange'; import { RelationOperator } from '@comunica/types-link-traversal'; +import { SparqlOperandDataTypes, SolverExpression } from '../lib/solverInterfaces'; -describe('solver function',()=>{ - describe('filterOperatorToRelationOperator', ()=>{ - it('should return the RelationOperator given a string representation',()=>{ - const testTable: [string, RelationOperator][] =[ - ['=',RelationOperator.EqualThanRelation], +describe('solver function', () => { + describe('filterOperatorToRelationOperator', () => { + it('should return the RelationOperator given a string representation', () => { + const testTable: [string, RelationOperator][] = [ + ['=', RelationOperator.EqualThanRelation], ['<', RelationOperator.LessThanRelation], ['<=', RelationOperator.LessThanOrEqualToRelation], ['>', RelationOperator.GreaterThanRelation], ['>=', RelationOperator.GreaterThanOrEqualToRelation] ]; - for(const [value, expectedAnswer] of testTable){ - expect(filterOperatorToRelationOperator(value)).toBe(expectedAnswer); + for (const [value, expectedAnswer] of testTable) { + expect(filterOperatorToRelationOperator(value)).toBe(expectedAnswer); } }); - it('should return undefined given a string not representing a RelationOperator', ()=>{ + it('should return undefined given a string not representing a RelationOperator', () => { expect(filterOperatorToRelationOperator("foo")).toBeUndefined(); }); }); + + describe('isSparqlOperandNumberType', () => { + it('should return true when a SparqlOperandDataTypes is a number type', () => { + const testTable: SparqlOperandDataTypes[] = [ + SparqlOperandDataTypes.Integer, + SparqlOperandDataTypes.NonPositiveInteger, + SparqlOperandDataTypes.NegativeInteger, + SparqlOperandDataTypes.Long, + SparqlOperandDataTypes.Short, + SparqlOperandDataTypes.NonNegativeInteger, + SparqlOperandDataTypes.UnsignedLong, + SparqlOperandDataTypes.UnsignedInt, + SparqlOperandDataTypes.UnsignedShort, + SparqlOperandDataTypes.PositiveInteger, + SparqlOperandDataTypes.Double, + SparqlOperandDataTypes.Float, + SparqlOperandDataTypes.Decimal, + ]; + + for (const value of testTable) { + expect(isSparqlOperandNumberType(value)).toBe(true); + } + }); + + it('should return false when a SparqlOperandDataTypes is not a number', () => { + const testTable: SparqlOperandDataTypes[] = [ + SparqlOperandDataTypes.String, + SparqlOperandDataTypes.Boolean, + SparqlOperandDataTypes.DateTime, + SparqlOperandDataTypes.Byte + ]; + + for (const value of testTable) { + expect(isSparqlOperandNumberType(value)).toBe(false); + } + }); + + }); + + describe('castSparqlRdfTermIntoNumber', () => { + it('should return the expected number when given an integer', () => { + const testTable: [string, SparqlOperandDataTypes, number][] = [ + ['19273', SparqlOperandDataTypes.Integer, 19273], + ['0', SparqlOperandDataTypes.NonPositiveInteger, 0], + ['-12313459', SparqlOperandDataTypes.NegativeInteger, -12313459], + ['121312321321321321', SparqlOperandDataTypes.Long, 121312321321321321], + ['1213123213213213', SparqlOperandDataTypes.Short, 1213123213213213], + ['283', SparqlOperandDataTypes.NonNegativeInteger, 283], + ['-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12131293912831], + ['-1234', SparqlOperandDataTypes.UnsignedInt, -1234], + ['-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123341231231234], + ['1234', SparqlOperandDataTypes.PositiveInteger, 1234] + ]; + + for (const [value, valueType, expectedNumber] of testTable) { + expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); + } + }); + + it('should return undefined if a non integer is pass with SparqlOperandDataTypes integer compatible type', () => { + const testTable: [string, SparqlOperandDataTypes][] = [ + ['1.6751', SparqlOperandDataTypes.Integer], + ['asbd', SparqlOperandDataTypes.PositiveInteger], + ['', SparqlOperandDataTypes.NegativeInteger] + ]; + + for (const [value, valueType] of testTable) { + expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); + } + }); + + it('should return the expected number when given an decimal', () => { + const testTable: [string, SparqlOperandDataTypes, number][] = [ + ['1.1', SparqlOperandDataTypes.Decimal, 1.1], + ['2132131.121321321421', SparqlOperandDataTypes.Float, 2132131.121321321421], + ['1234.123123132132143423424235324324', SparqlOperandDataTypes.Double, 1234.123123132132143423424235324324], + ]; + + for (const [value, valueType, expectedNumber] of testTable) { + expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); + } + }); + + it('should return the expected unix time given a date time', () => { + const value = '1994-11-05T13:15:30Z'; + const expectedUnixTime = 784041330000; + + expect(castSparqlRdfTermIntoNumber( + value, + SparqlOperandDataTypes.DateTime + )).toBe(expectedUnixTime); + }); + + it('should return undefined given a non date time', () => { + const value = '1994-11-T13:15:30'; + + expect(castSparqlRdfTermIntoNumber( + value, + SparqlOperandDataTypes.DateTime + )).toBeUndefined(); + }); + }); + + describe('getSolutionRange', () => { + it('given a boolean compatible RelationOperator and a value should return a valid SolutionRange', () => { + const value = -1; + const testTable: [RelationOperator, SolutionRange][] = [ + [ + RelationOperator.GreaterThanRelation, + new SolutionRange([value + Number.EPSILON, Number.POSITIVE_INFINITY]) + ], + [ + RelationOperator.GreaterThanOrEqualToRelation, + new SolutionRange([value, Number.POSITIVE_INFINITY]) + ], + [ + RelationOperator.EqualThanRelation, + new SolutionRange([value, value]) + ], + [ + RelationOperator.LessThanRelation, + new SolutionRange([Number.NEGATIVE_INFINITY, value - Number.EPSILON]) + ], + [ + RelationOperator.LessThanOrEqualToRelation, + new SolutionRange([Number.NEGATIVE_INFINITY, value]) + ] + ]; + + for(const [operator, expectedRange] of testTable) { + expect(getSolutionRange(value, operator)).toStrictEqual(expectedRange); + } + }); + + it('should return undefined given an RelationOperator that is not boolean compatible', ()=>{ + const value = -1; + const operator = RelationOperator.PrefixRelation; + + expect(getSolutionRange(value, operator)).toBeUndefined(); + }); + }); + + describe('areTypesCompatible', ()=>{ + it('given expression with identical value type should return true', ()=>{ + const expressions:SolverExpression[] = [ + { + variable:"a", + rawValue:"true", + valueType:SparqlOperandDataTypes.Boolean, + valueAsNumber:1, + operator:RelationOperator.EqualThanRelation, + chainOperator:[] + }, + { + variable:"a", + rawValue:"false", + valueType:SparqlOperandDataTypes.Boolean, + valueAsNumber:0, + operator:RelationOperator.EqualThanRelation, + chainOperator:[] + }, + { + variable:"a", + rawValue:"false", + valueType:SparqlOperandDataTypes.Boolean, + valueAsNumber:0, + operator:RelationOperator.EqualThanRelation, + chainOperator:[] + } + ]; + + expect(areTypesCompatible(expressions)).toBe(true); + }); + + it('should return true when all the types are numbers', ()=>{ + const expressions:SolverExpression[] = [ + { + variable:"a", + rawValue:"true", + valueType:SparqlOperandDataTypes.Int, + valueAsNumber:1, + operator:RelationOperator.EqualThanRelation, + chainOperator:[] + }, + { + variable:"a", + rawValue:"false", + valueType:SparqlOperandDataTypes.NonNegativeInteger, + valueAsNumber:0, + operator:RelationOperator.EqualThanRelation, + chainOperator:[] + }, + { + variable:"a", + rawValue:"false", + valueType:SparqlOperandDataTypes.Decimal, + valueAsNumber:0, + operator:RelationOperator.EqualThanRelation, + chainOperator:[] + } + ]; + + expect(areTypesCompatible(expressions)).toBe(true); + }); + + it('should return false when one type is not identical', ()=>{ + const expressions:SolverExpression[] = [ + { + variable:"a", + rawValue:"true", + valueType:SparqlOperandDataTypes.Boolean, + valueAsNumber:1, + operator:RelationOperator.EqualThanRelation, + chainOperator:[] + }, + { + variable:"a", + rawValue:"false", + valueType:SparqlOperandDataTypes.Boolean, + valueAsNumber:0, + operator:RelationOperator.EqualThanRelation, + chainOperator:[] + }, + { + variable:"a", + rawValue:"false", + valueType:SparqlOperandDataTypes.Byte, + valueAsNumber:0, + operator:RelationOperator.EqualThanRelation, + chainOperator:[] + } + ]; + + expect(areTypesCompatible(expressions)).toBe(false); + }); + + it('should return false when one type is not identical and the other are number', ()=>{ + const expressions:SolverExpression[] = [ + { + variable:"a", + rawValue:"true", + valueType:SparqlOperandDataTypes.UnsignedInt, + valueAsNumber:1, + operator:RelationOperator.EqualThanRelation, + chainOperator:[] + }, + { + variable:"a", + rawValue:"false", + valueType:SparqlOperandDataTypes.Float, + valueAsNumber:0, + operator:RelationOperator.EqualThanRelation, + chainOperator:[] + }, + { + variable:"a", + rawValue:"false", + valueType:SparqlOperandDataTypes.Byte, + valueAsNumber:0, + operator:RelationOperator.EqualThanRelation, + chainOperator:[] + } + ]; + + expect(areTypesCompatible(expressions)).toBe(false); + }); + }); + + }); \ No newline at end of file From 5c31d94a903644574b378487d30b92fde4ada078 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 9 Dec 2022 08:18:20 +0100 Subject: [PATCH 082/189] small formating --- packages/actor-extract-links-extract-tree/lib/solver.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 2ebe8e735..d00766c0d 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -33,9 +33,7 @@ export function solveRelationWithFilter({ relation, filterExpression, variable } return true } - - -function resolveAFilterTerm(expression: Algebra.Expression, operator: RelationOperator, linksOperator: LinkOperator[]): SolverExpression | undefined { +export function resolveAFilterTerm(expression: Algebra.Expression, operator: RelationOperator, linksOperator: LinkOperator[]): SolverExpression | undefined { let variable: string | undefined; let rawValue: string | undefined; let valueType: SparqlOperandDataTypes | undefined; @@ -146,7 +144,7 @@ export function resolveEquationSystem(equationSystem: SolverEquationSystem, firs return domain; } -function resolveEquation(equation: SolverEquation, domain: SolutionDomain): [SolutionDomain, LastLogicalOperator] | undefined { +export function resolveEquation(equation: SolverEquation, domain: SolutionDomain): [SolutionDomain, LastLogicalOperator] | undefined { let localDomain = domain.clone(); let i = -1; let currentLastOperator = equation.chainOperator.at(i); From d1143de5e3bc453e6d917f2ece896a3c64c26373 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 9 Dec 2022 08:55:49 +0100 Subject: [PATCH 083/189] Fix outdated importing. --- packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts | 2 +- packages/actor-extract-links-extract-tree/lib/solver.ts | 2 +- .../test/SolutionDomain-test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 6b23a2760..e858d4025 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -1,5 +1,5 @@ import { SolutionRange } from './SolutionRange'; -import { LogicOperator } from './SolverType'; +import { LogicOperator } from './solverInterfaces'; export class SolutionDomain { diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index d00766c0d..02dc0901f 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -10,7 +10,7 @@ import { LogicOperatorReversed, LogicOperator, SolverExpression, Variable, SolverEquation, SparqlOperandDataTypesReversed } from './solverInterfaces'; -import { LastLogicalOperator } from './SolverType'; +import { LastLogicalOperator } from './solverInterfaces'; export function solveRelationWithFilter({ relation, filterExpression, variable }: { relation: ITreeRelation, diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index edf77905f..b235ea721 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -1,6 +1,6 @@ import { SolutionDomain } from '../lib/SolutionDomain'; import { SolutionRange } from '../lib//SolutionRange'; -import { LogicOperator } from '../lib/SolverType'; +import { LogicOperator } from '../lib/solverInterfaces'; describe('SolutionDomain', () => { describe('constructor', () => { From aa695458871a3e8c9a44aac225b0d231dc94863e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 13 Dec 2022 08:45:57 +0100 Subject: [PATCH 084/189] unit test convertTreeRelationToSolverExpression --- .../test/solver-test.ts | 271 ++++++++++++------ 1 file changed, 185 insertions(+), 86 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 3284376ae..1c1696d52 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -3,12 +3,16 @@ import { isSparqlOperandNumberType, castSparqlRdfTermIntoNumber, getSolutionRange, - areTypesCompatible + areTypesCompatible, + convertTreeRelationToSolverExpression } from '../lib/solver'; import { SolutionRange } from '../lib/SolutionRange'; -import { RelationOperator } from '@comunica/types-link-traversal'; +import { ITreeRelation, RelationOperator } from '@comunica/types-link-traversal'; import { SparqlOperandDataTypes, SolverExpression } from '../lib/solverInterfaces'; +import { DataFactory } from 'rdf-data-factory'; +import type * as RDF from 'rdf-js'; +const DF = new DataFactory(); describe('solver function', () => { describe('filterOperatorToRelationOperator', () => { @@ -158,12 +162,12 @@ describe('solver function', () => { ] ]; - for(const [operator, expectedRange] of testTable) { + for (const [operator, expectedRange] of testTable) { expect(getSolutionRange(value, operator)).toStrictEqual(expectedRange); } }); - it('should return undefined given an RelationOperator that is not boolean compatible', ()=>{ + it('should return undefined given an RelationOperator that is not boolean compatible', () => { const value = -1; const operator = RelationOperator.PrefixRelation; @@ -171,125 +175,125 @@ describe('solver function', () => { }); }); - describe('areTypesCompatible', ()=>{ - it('given expression with identical value type should return true', ()=>{ - const expressions:SolverExpression[] = [ + describe('areTypesCompatible', () => { + it('given expression with identical value type should return true', () => { + const expressions: SolverExpression[] = [ { - variable:"a", - rawValue:"true", - valueType:SparqlOperandDataTypes.Boolean, - valueAsNumber:1, - operator:RelationOperator.EqualThanRelation, - chainOperator:[] + variable: "a", + rawValue: "true", + valueType: SparqlOperandDataTypes.Boolean, + valueAsNumber: 1, + operator: RelationOperator.EqualThanRelation, + chainOperator: [] }, { - variable:"a", - rawValue:"false", - valueType:SparqlOperandDataTypes.Boolean, - valueAsNumber:0, - operator:RelationOperator.EqualThanRelation, - chainOperator:[] + variable: "a", + rawValue: "false", + valueType: SparqlOperandDataTypes.Boolean, + valueAsNumber: 0, + operator: RelationOperator.EqualThanRelation, + chainOperator: [] }, { - variable:"a", - rawValue:"false", - valueType:SparqlOperandDataTypes.Boolean, - valueAsNumber:0, - operator:RelationOperator.EqualThanRelation, - chainOperator:[] + variable: "a", + rawValue: "false", + valueType: SparqlOperandDataTypes.Boolean, + valueAsNumber: 0, + operator: RelationOperator.EqualThanRelation, + chainOperator: [] } ]; expect(areTypesCompatible(expressions)).toBe(true); }); - it('should return true when all the types are numbers', ()=>{ - const expressions:SolverExpression[] = [ + it('should return true when all the types are numbers', () => { + const expressions: SolverExpression[] = [ { - variable:"a", - rawValue:"true", - valueType:SparqlOperandDataTypes.Int, - valueAsNumber:1, - operator:RelationOperator.EqualThanRelation, - chainOperator:[] + variable: "a", + rawValue: "true", + valueType: SparqlOperandDataTypes.Int, + valueAsNumber: 1, + operator: RelationOperator.EqualThanRelation, + chainOperator: [] }, { - variable:"a", - rawValue:"false", - valueType:SparqlOperandDataTypes.NonNegativeInteger, - valueAsNumber:0, - operator:RelationOperator.EqualThanRelation, - chainOperator:[] + variable: "a", + rawValue: "false", + valueType: SparqlOperandDataTypes.NonNegativeInteger, + valueAsNumber: 0, + operator: RelationOperator.EqualThanRelation, + chainOperator: [] }, { - variable:"a", - rawValue:"false", - valueType:SparqlOperandDataTypes.Decimal, - valueAsNumber:0, - operator:RelationOperator.EqualThanRelation, - chainOperator:[] + variable: "a", + rawValue: "false", + valueType: SparqlOperandDataTypes.Decimal, + valueAsNumber: 0, + operator: RelationOperator.EqualThanRelation, + chainOperator: [] } ]; expect(areTypesCompatible(expressions)).toBe(true); }); - it('should return false when one type is not identical', ()=>{ - const expressions:SolverExpression[] = [ + it('should return false when one type is not identical', () => { + const expressions: SolverExpression[] = [ { - variable:"a", - rawValue:"true", - valueType:SparqlOperandDataTypes.Boolean, - valueAsNumber:1, - operator:RelationOperator.EqualThanRelation, - chainOperator:[] + variable: "a", + rawValue: "true", + valueType: SparqlOperandDataTypes.Boolean, + valueAsNumber: 1, + operator: RelationOperator.EqualThanRelation, + chainOperator: [] }, { - variable:"a", - rawValue:"false", - valueType:SparqlOperandDataTypes.Boolean, - valueAsNumber:0, - operator:RelationOperator.EqualThanRelation, - chainOperator:[] + variable: "a", + rawValue: "false", + valueType: SparqlOperandDataTypes.Boolean, + valueAsNumber: 0, + operator: RelationOperator.EqualThanRelation, + chainOperator: [] }, { - variable:"a", - rawValue:"false", - valueType:SparqlOperandDataTypes.Byte, - valueAsNumber:0, - operator:RelationOperator.EqualThanRelation, - chainOperator:[] + variable: "a", + rawValue: "false", + valueType: SparqlOperandDataTypes.Byte, + valueAsNumber: 0, + operator: RelationOperator.EqualThanRelation, + chainOperator: [] } ]; expect(areTypesCompatible(expressions)).toBe(false); }); - it('should return false when one type is not identical and the other are number', ()=>{ - const expressions:SolverExpression[] = [ + it('should return false when one type is not identical and the other are number', () => { + const expressions: SolverExpression[] = [ { - variable:"a", - rawValue:"true", - valueType:SparqlOperandDataTypes.UnsignedInt, - valueAsNumber:1, - operator:RelationOperator.EqualThanRelation, - chainOperator:[] + variable: "a", + rawValue: "true", + valueType: SparqlOperandDataTypes.UnsignedInt, + valueAsNumber: 1, + operator: RelationOperator.EqualThanRelation, + chainOperator: [] }, { - variable:"a", - rawValue:"false", - valueType:SparqlOperandDataTypes.Float, - valueAsNumber:0, - operator:RelationOperator.EqualThanRelation, - chainOperator:[] + variable: "a", + rawValue: "false", + valueType: SparqlOperandDataTypes.Float, + valueAsNumber: 0, + operator: RelationOperator.EqualThanRelation, + chainOperator: [] }, { - variable:"a", - rawValue:"false", - valueType:SparqlOperandDataTypes.Byte, - valueAsNumber:0, - operator:RelationOperator.EqualThanRelation, - chainOperator:[] + variable: "a", + rawValue: "false", + valueType: SparqlOperandDataTypes.Byte, + valueAsNumber: 0, + operator: RelationOperator.EqualThanRelation, + chainOperator: [] } ]; @@ -297,5 +301,100 @@ describe('solver function', () => { }); }); - + describe('convertTreeRelationToSolverExpression', () => { + it('given a TREE relation with all the parameters should return a valid expression', () => { + const relation: ITreeRelation = { + type: RelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + }, + node: "https://www.example.be" + }; + const variable = "x"; + + const expectedExpression: SolverExpression = { + variable: variable, + rawValue: '5', + valueType: SparqlOperandDataTypes.Integer, + valueAsNumber: 5, + chainOperator: [], + operator: RelationOperator.EqualThanRelation + }; + + expect(convertTreeRelationToSolverExpression(relation, variable)).toStrictEqual(expectedExpression); + }); + + it('should return undefined given a relation witn a value term containing an unknowed value type', () => { + const relation: ITreeRelation = { + type: RelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')) + }, + node: "https://www.example.be" + }; + const variable = "x"; + + expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it('should return undefined given a relation with a term containing an incompatible value in relation to its value type', () => { + const relation: ITreeRelation = { + type: RelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "a", + term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + }, + node: "https://www.example.be" + }; + const variable = "x"; + + expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it('should return undefined given a relation without a value and a type', () => { + const relation: ITreeRelation = { + remainingItems: 10, + path: "ex:path", + node: "https://www.example.be" + }; + const variable = "x"; + + expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it('should return undefined given a relation without a value', () => { + const relation: ITreeRelation = { + type: RelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + node: "https://www.example.be" + }; + const variable = "x"; + + expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it('should return undefined given a relation without a type', () => { + const relation: ITreeRelation = { + remainingItems: 10, + path: "ex:path", + value: { + value: "a", + term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + }, + node: "https://www.example.be" + }; + const variable = "x"; + + expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + }); }); \ No newline at end of file From e96a761014a3dceae5958f4a3f749981c6404faf Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 13 Dec 2022 10:11:36 +0100 Subject: [PATCH 085/189] unit test resolveEquation --- .../test/solver-test.ts | 146 +++++++++++++++++- 1 file changed, 144 insertions(+), 2 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 1c1696d52..8b536cfad 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -4,13 +4,16 @@ import { castSparqlRdfTermIntoNumber, getSolutionRange, areTypesCompatible, - convertTreeRelationToSolverExpression + convertTreeRelationToSolverExpression, + resolveEquation } from '../lib/solver'; import { SolutionRange } from '../lib/SolutionRange'; import { ITreeRelation, RelationOperator } from '@comunica/types-link-traversal'; -import { SparqlOperandDataTypes, SolverExpression } from '../lib/solverInterfaces'; +import { SparqlOperandDataTypes, SolverExpression, SolverEquation, LogicOperator } from '../lib/solverInterfaces'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; +import { SolutionDomain } from '../lib/SolutionDomain'; +import { LinkOperator } from '../lib/LinkOperator'; const DF = new DataFactory(); @@ -397,4 +400,143 @@ describe('solver function', () => { expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); }); }); + + describe('resolveEquation', () => { + it('given an empty domain and an equation with 2 operation chained that are not "NOT" should return a valid new domain and the last chained operator', () => { + const domain = new SolutionDomain(); + const equation: SolverEquation = { + chainOperator: [ + new LinkOperator(LogicOperator.And), + new LinkOperator(LogicOperator.Or), + ], + solutionDomain: new SolutionRange([0, 1]) + }; + + const expectedDomain = SolutionDomain.newWithInitialValue(equation.solutionDomain); + const expectedLastLogicalOperator = equation.chainOperator.at(-2)?.toString(); + + const resp = resolveEquation(equation, domain); + if (resp) { + const [respDomain, respLastLogicalOperator] = resp; + expect(respDomain).toStrictEqual(expectedDomain); + expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); + } else { + expect(resp).toBeDefined(); + } + }); + + it('given a domain and an equation with multiple chained that are not "NOT" should return a valid new domain and the next chained operator', () => { + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); + const equation: SolverEquation = { + chainOperator: [ + new LinkOperator(LogicOperator.And), + new LinkOperator(LogicOperator.Or), + new LinkOperator(LogicOperator.Or), + new LinkOperator(LogicOperator.And), + new LinkOperator(LogicOperator.Or), + ], + solutionDomain: new SolutionRange([100, 221.3]) + }; + + const expectedDomain = domain.add({ range: equation.solutionDomain, operator: equation.chainOperator.at(-1)?.operator }); + const expectedLastLogicalOperator = equation.chainOperator.at(-2)?.toString(); + + const resp = resolveEquation(equation, domain); + if (resp) { + const [respDomain, respLastLogicalOperator] = resp; + expect(respDomain).toStrictEqual(expectedDomain); + expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); + } else { + expect(resp).toBeDefined(); + } + }); + + it('given a domain and an equation one chained operation should return a valid new domain and an empty string has the next chained operator', () => { + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); + const equation: SolverEquation = { + chainOperator: [ + new LinkOperator(LogicOperator.Or), + ], + solutionDomain: new SolutionRange([100, 221.3]) + }; + + const expectedDomain = domain.add({ range: equation.solutionDomain, operator: equation.chainOperator.at(-1)?.operator }); + const expectedLastLogicalOperator = ""; + + const resp = resolveEquation(equation, domain); + if (resp) { + const [respDomain, respLastLogicalOperator] = resp; + expect(respDomain).toStrictEqual(expectedDomain); + expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); + } else { + expect(resp).toBeDefined(); + } + }); + + it('given a domain and an equation with multiple chainned operator where the later elements are "NOT" operators and the last element an "AND" operator should return a valid domain and the next last operator', () => { + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); + const equation: SolverEquation = { + chainOperator: [ + new LinkOperator(LogicOperator.And), + new LinkOperator(LogicOperator.Not), + new LinkOperator(LogicOperator.Not), + new LinkOperator(LogicOperator.Or), + ], + solutionDomain: new SolutionRange([100, 221.3]) + }; + + let expectedDomain = domain.add({ range: equation.solutionDomain, operator: equation.chainOperator.at(-1)?.operator }); + expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); + expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); + + const expectedLastLogicalOperator = equation.chainOperator[0].toString(); + + const resp = resolveEquation(equation, domain); + if (resp) { + const [respDomain, respLastLogicalOperator] = resp; + expect(respDomain).toStrictEqual(expectedDomain); + expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); + } else { + expect(resp).toBeDefined(); + } + }); + + it('given a domain and an equation with multiple chainned operator where the last elements are "NOT" operators should return a valid domain and an empty string as the next operator', () => { + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); + const equation: SolverEquation = { + chainOperator: [ + new LinkOperator(LogicOperator.Not), + new LinkOperator(LogicOperator.Not), + new LinkOperator(LogicOperator.Or), + ], + solutionDomain: new SolutionRange([100, 221.3]) + }; + + let expectedDomain = domain.add({ range: equation.solutionDomain, operator: equation.chainOperator.at(-1)?.operator }); + expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); + expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); + + const expectedLastLogicalOperator = ""; + + const resp = resolveEquation(equation, domain); + if (resp) { + const [respDomain, respLastLogicalOperator] = resp; + expect(respDomain).toStrictEqual(expectedDomain); + expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); + } else { + expect(resp).toBeDefined(); + } + }); + + it('given an empty domain and an equation with no chained operation should return undefined', () => { + const domain = new SolutionDomain(); + const equation: SolverEquation = { + chainOperator: [], + solutionDomain: new SolutionRange([0, 1]) + }; + + expect(resolveEquation(equation, domain)).toBeUndefined(); + + }); + }); }); \ No newline at end of file From b00079020798d7c8f4195b2b5780a2e79ced74fd Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 13 Dec 2022 14:54:57 +0100 Subject: [PATCH 086/189] unit test for createEquationSystem --- .../lib/solver.ts | 81 +++--- .../lib/solverInterfaces.ts | 4 +- .../lib/treeMetadataExtraction.ts | 12 +- .../test/solver-test.ts | 263 +++++++++++++++--- .../types-link-traversal/lib/TreeMetadata.ts | 10 +- 5 files changed, 285 insertions(+), 85 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 02dc0901f..6b0da8ce4 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -1,4 +1,4 @@ -import { RelationOperator } from '@comunica/types-link-traversal'; +import { SparqlRelationOperator } from '@comunica/types-link-traversal'; import type { ITreeRelation } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; @@ -33,7 +33,7 @@ export function solveRelationWithFilter({ relation, filterExpression, variable } return true } -export function resolveAFilterTerm(expression: Algebra.Expression, operator: RelationOperator, linksOperator: LinkOperator[]): SolverExpression | undefined { +export function resolveAFilterTerm(expression: Algebra.Expression, operator: SparqlRelationOperator, linksOperator: LinkOperator[]): SolverExpression | undefined { let variable: string | undefined; let rawValue: string | undefined; let valueType: SparqlOperandDataTypes | undefined; @@ -91,9 +91,31 @@ export function convertFilterExpressionToSolverExpression(expression: Algebra.Ex return filterExpressionList; } +export function resolveEquationSystem(equationSystem: SolverEquationSystem, firstEquation: [SolverEquation, SolverEquation]): SolutionDomain | undefined { + const localEquationSystem = new Map(equationSystem); + const localFistEquation = new Array(...firstEquation); + let domain: SolutionDomain = SolutionDomain.newWithInitialValue(localFistEquation[0].solutionDomain); + let idx: string = ""; + let currentEquation: SolverEquation | undefined = localFistEquation[1]; + + do { + const resp = resolveEquation(currentEquation, domain); + if (!resp) { + return undefined; + } + [domain, idx] = resp; + + currentEquation = localEquationSystem.get(idx); + + } while (currentEquation) + + return domain; +} + export function createEquationSystem(expressions: SolverExpression[]): [SolverEquationSystem, [SolverEquation, SolverEquation]] | undefined { const system: SolverEquationSystem = new Map(); let firstEquationToEvaluate: [SolverEquation, SolverEquation] | undefined = undefined; + let firstEquationLastOperator: string = ""; for (const expression of expressions) { const lastOperator = expression.chainOperator.slice(-1).toString(); @@ -107,41 +129,22 @@ export function createEquationSystem(expressions: SolverExpression[]): [SolverEq }; const lastEquation = system.get(lastOperator); if (lastEquation) { - if (firstEquationToEvaluate) { + if(firstEquationLastOperator !== ""){ return undefined; } firstEquationToEvaluate = [lastEquation, equation]; - system.delete(lastOperator); + firstEquationLastOperator = lastOperator; } else { system.set(lastOperator, equation); } - } + if (!firstEquationToEvaluate) { return undefined; } - return [system, firstEquationToEvaluate]; -} - -export function resolveEquationSystem(equationSystem: SolverEquationSystem, firstEquation: [SolverEquation, SolverEquation]): SolutionDomain | undefined { - const localEquationSystem = new Map(equationSystem); - const localFistEquation = new Array(...firstEquation); - let domain: SolutionDomain = SolutionDomain.newWithInitialValue(localFistEquation[0].solutionDomain); - let idx: string = ""; - let currentEquation: SolverEquation | undefined = localFistEquation[1]; + system.delete(firstEquationLastOperator); - do { - const resp = resolveEquation(currentEquation, domain); - if (!resp) { - return undefined; - } - [domain, idx] = resp; - - currentEquation = localEquationSystem.get(idx); - - } while (currentEquation) - - return domain; + return [system, firstEquationToEvaluate]; } export function resolveEquation(equation: SolverEquation, domain: SolutionDomain): [SolutionDomain, LastLogicalOperator] | undefined { @@ -208,17 +211,17 @@ export function areTypesCompatible(expressions: SolverExpression[]): boolean { return true } -export function getSolutionRange(value: number, operator: RelationOperator): SolutionRange | undefined { +export function getSolutionRange(value: number, operator: SparqlRelationOperator): SolutionRange | undefined { switch (operator) { - case RelationOperator.GreaterThanRelation: + case SparqlRelationOperator.GreaterThanRelation: return new SolutionRange([value + Number.EPSILON, Number.POSITIVE_INFINITY]); - case RelationOperator.GreaterThanOrEqualToRelation: + case SparqlRelationOperator.GreaterThanOrEqualToRelation: return new SolutionRange([value, Number.POSITIVE_INFINITY]); - case RelationOperator.EqualThanRelation: + case SparqlRelationOperator.EqualThanRelation: return new SolutionRange([value, value]); - case RelationOperator.LessThanRelation: + case SparqlRelationOperator.LessThanRelation: return new SolutionRange([Number.NEGATIVE_INFINITY, value - Number.EPSILON]); - case RelationOperator.LessThanOrEqualToRelation: + case SparqlRelationOperator.LessThanOrEqualToRelation: return new SolutionRange([Number.NEGATIVE_INFINITY, value]); default: break; @@ -258,22 +261,22 @@ export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): rdfTermType === SparqlOperandDataTypes.PositiveInteger || rdfTermType === SparqlOperandDataTypes.Float || rdfTermType === SparqlOperandDataTypes.Double || - rdfTermType === SparqlOperandDataTypes.Decimal|| + rdfTermType === SparqlOperandDataTypes.Decimal || rdfTermType === SparqlOperandDataTypes.Int; } -export function filterOperatorToRelationOperator(filterOperator: string): RelationOperator | undefined { +export function filterOperatorToRelationOperator(filterOperator: string): SparqlRelationOperator | undefined { switch (filterOperator) { case '=': - return RelationOperator.EqualThanRelation; + return SparqlRelationOperator.EqualThanRelation; case '<': - return RelationOperator.LessThanRelation; + return SparqlRelationOperator.LessThanRelation; case '<=': - return RelationOperator.LessThanOrEqualToRelation; + return SparqlRelationOperator.LessThanOrEqualToRelation; case '>': - return RelationOperator.GreaterThanRelation; + return SparqlRelationOperator.GreaterThanRelation; case '>=': - return RelationOperator.GreaterThanOrEqualToRelation; + return SparqlRelationOperator.GreaterThanOrEqualToRelation; default: return undefined; } diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index 056abbcef..fb031bb97 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -1,4 +1,4 @@ -import { RelationOperator } from '@comunica/types-link-traversal'; +import { SparqlRelationOperator } from '@comunica/types-link-traversal'; import { SolutionRange } from './SolutionRange'; import { LinkOperator } from './LinkOperator'; /** @@ -60,7 +60,7 @@ export interface SolverExpression { valueType: SparqlOperandDataTypes; valueAsNumber: number; - operator: RelationOperator; + operator: SparqlRelationOperator; chainOperator: LinkOperator[]; }; diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts index 0cdd0e3db..375f3d3b0 100644 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -1,4 +1,4 @@ -import type { ITreeRelation, ITreeRelationRaw, RelationOperator } from '@comunica/types-link-traversal'; +import type { ITreeRelation, ITreeRelationRaw, SparqlRelationOperator } from '@comunica/types-link-traversal'; import { TreeNodes, RelationOperatorReversed } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { termToString } from 'rdf-string'; @@ -39,15 +39,15 @@ export function materializeTreeRelation( /** * From a quad stream return a relation element if it exist * @param {RDF.Quad} quad - Current quad of the stream. - * @returns {[RelationOperator | number | string, keyof ITreeRelationRaw] | undefined} The relation element + * @returns {[SparqlRelationOperator | number | string, keyof ITreeRelationRaw] | undefined} The relation element * and the key associated with it. */ export function buildRelationElement( quad: RDF.Quad, -): [RelationOperator | number | string, keyof ITreeRelationRaw] | undefined { +): [SparqlRelationOperator | number | string, keyof ITreeRelationRaw] | undefined { if (quad.predicate.value === TreeNodes.RDFTypeNode) { // Set the operator of the relation - const operator: RelationOperator | undefined = RelationOperatorReversed.get(quad.object.value); + const operator: SparqlRelationOperator | undefined = RelationOperatorReversed.get(quad.object.value); if (typeof operator !== 'undefined') { return [ operator, 'operator' ]; } @@ -69,13 +69,13 @@ export function buildRelationElement( * Update the relationDescriptions with the new quad value * @param {Map} relationDescriptions - Maps relationship identifiers to their description. * @param {RDF.Quad} quad - Current quad of the steam. - * @param {RelationOperator | number | string} value - Current description value fetch + * @param {SparqlRelationOperator | number | string} value - Current description value fetch * @param {keyof ITreeRelationRaw} key - Key associated with the value. */ export function addRelationDescription( relationDescriptions: Map, quad: RDF.Quad, - value: RelationOperator | number | string, + value: SparqlRelationOperator | number | string, key: keyof ITreeRelationRaw, ): void { const rawRelation: ITreeRelationRaw = relationDescriptions?.get(termToString(quad.subject)) || {}; diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 8b536cfad..2f290ee58 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -5,11 +5,12 @@ import { getSolutionRange, areTypesCompatible, convertTreeRelationToSolverExpression, - resolveEquation + resolveEquation, + createEquationSystem } from '../lib/solver'; import { SolutionRange } from '../lib/SolutionRange'; -import { ITreeRelation, RelationOperator } from '@comunica/types-link-traversal'; -import { SparqlOperandDataTypes, SolverExpression, SolverEquation, LogicOperator } from '../lib/solverInterfaces'; +import { ITreeRelation, SparqlRelationOperator } from '@comunica/types-link-traversal'; +import { SparqlOperandDataTypes, SolverExpression, SolverEquation, LogicOperator, SolverEquationSystem } from '../lib/solverInterfaces'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { SolutionDomain } from '../lib/SolutionDomain'; @@ -20,12 +21,12 @@ const DF = new DataFactory(); describe('solver function', () => { describe('filterOperatorToRelationOperator', () => { it('should return the RelationOperator given a string representation', () => { - const testTable: [string, RelationOperator][] = [ - ['=', RelationOperator.EqualThanRelation], - ['<', RelationOperator.LessThanRelation], - ['<=', RelationOperator.LessThanOrEqualToRelation], - ['>', RelationOperator.GreaterThanRelation], - ['>=', RelationOperator.GreaterThanOrEqualToRelation] + const testTable: [string, SparqlRelationOperator][] = [ + ['=', SparqlRelationOperator.EqualThanRelation], + ['<', SparqlRelationOperator.LessThanRelation], + ['<=', SparqlRelationOperator.LessThanOrEqualToRelation], + ['>', SparqlRelationOperator.GreaterThanRelation], + ['>=', SparqlRelationOperator.GreaterThanOrEqualToRelation] ]; for (const [value, expectedAnswer] of testTable) { @@ -142,25 +143,25 @@ describe('solver function', () => { describe('getSolutionRange', () => { it('given a boolean compatible RelationOperator and a value should return a valid SolutionRange', () => { const value = -1; - const testTable: [RelationOperator, SolutionRange][] = [ + const testTable: [SparqlRelationOperator, SolutionRange][] = [ [ - RelationOperator.GreaterThanRelation, + SparqlRelationOperator.GreaterThanRelation, new SolutionRange([value + Number.EPSILON, Number.POSITIVE_INFINITY]) ], [ - RelationOperator.GreaterThanOrEqualToRelation, + SparqlRelationOperator.GreaterThanOrEqualToRelation, new SolutionRange([value, Number.POSITIVE_INFINITY]) ], [ - RelationOperator.EqualThanRelation, + SparqlRelationOperator.EqualThanRelation, new SolutionRange([value, value]) ], [ - RelationOperator.LessThanRelation, + SparqlRelationOperator.LessThanRelation, new SolutionRange([Number.NEGATIVE_INFINITY, value - Number.EPSILON]) ], [ - RelationOperator.LessThanOrEqualToRelation, + SparqlRelationOperator.LessThanOrEqualToRelation, new SolutionRange([Number.NEGATIVE_INFINITY, value]) ] ]; @@ -172,7 +173,7 @@ describe('solver function', () => { it('should return undefined given an RelationOperator that is not boolean compatible', () => { const value = -1; - const operator = RelationOperator.PrefixRelation; + const operator = SparqlRelationOperator.PrefixRelation; expect(getSolutionRange(value, operator)).toBeUndefined(); }); @@ -186,7 +187,7 @@ describe('solver function', () => { rawValue: "true", valueType: SparqlOperandDataTypes.Boolean, valueAsNumber: 1, - operator: RelationOperator.EqualThanRelation, + operator: SparqlRelationOperator.EqualThanRelation, chainOperator: [] }, { @@ -194,7 +195,7 @@ describe('solver function', () => { rawValue: "false", valueType: SparqlOperandDataTypes.Boolean, valueAsNumber: 0, - operator: RelationOperator.EqualThanRelation, + operator: SparqlRelationOperator.EqualThanRelation, chainOperator: [] }, { @@ -202,7 +203,7 @@ describe('solver function', () => { rawValue: "false", valueType: SparqlOperandDataTypes.Boolean, valueAsNumber: 0, - operator: RelationOperator.EqualThanRelation, + operator: SparqlRelationOperator.EqualThanRelation, chainOperator: [] } ]; @@ -217,7 +218,7 @@ describe('solver function', () => { rawValue: "true", valueType: SparqlOperandDataTypes.Int, valueAsNumber: 1, - operator: RelationOperator.EqualThanRelation, + operator: SparqlRelationOperator.EqualThanRelation, chainOperator: [] }, { @@ -225,7 +226,7 @@ describe('solver function', () => { rawValue: "false", valueType: SparqlOperandDataTypes.NonNegativeInteger, valueAsNumber: 0, - operator: RelationOperator.EqualThanRelation, + operator: SparqlRelationOperator.EqualThanRelation, chainOperator: [] }, { @@ -233,7 +234,7 @@ describe('solver function', () => { rawValue: "false", valueType: SparqlOperandDataTypes.Decimal, valueAsNumber: 0, - operator: RelationOperator.EqualThanRelation, + operator: SparqlRelationOperator.EqualThanRelation, chainOperator: [] } ]; @@ -248,7 +249,7 @@ describe('solver function', () => { rawValue: "true", valueType: SparqlOperandDataTypes.Boolean, valueAsNumber: 1, - operator: RelationOperator.EqualThanRelation, + operator: SparqlRelationOperator.EqualThanRelation, chainOperator: [] }, { @@ -256,7 +257,7 @@ describe('solver function', () => { rawValue: "false", valueType: SparqlOperandDataTypes.Boolean, valueAsNumber: 0, - operator: RelationOperator.EqualThanRelation, + operator: SparqlRelationOperator.EqualThanRelation, chainOperator: [] }, { @@ -264,7 +265,7 @@ describe('solver function', () => { rawValue: "false", valueType: SparqlOperandDataTypes.Byte, valueAsNumber: 0, - operator: RelationOperator.EqualThanRelation, + operator: SparqlRelationOperator.EqualThanRelation, chainOperator: [] } ]; @@ -279,7 +280,7 @@ describe('solver function', () => { rawValue: "true", valueType: SparqlOperandDataTypes.UnsignedInt, valueAsNumber: 1, - operator: RelationOperator.EqualThanRelation, + operator: SparqlRelationOperator.EqualThanRelation, chainOperator: [] }, { @@ -287,7 +288,7 @@ describe('solver function', () => { rawValue: "false", valueType: SparqlOperandDataTypes.Float, valueAsNumber: 0, - operator: RelationOperator.EqualThanRelation, + operator: SparqlRelationOperator.EqualThanRelation, chainOperator: [] }, { @@ -295,7 +296,7 @@ describe('solver function', () => { rawValue: "false", valueType: SparqlOperandDataTypes.Byte, valueAsNumber: 0, - operator: RelationOperator.EqualThanRelation, + operator: SparqlRelationOperator.EqualThanRelation, chainOperator: [] } ]; @@ -307,7 +308,7 @@ describe('solver function', () => { describe('convertTreeRelationToSolverExpression', () => { it('given a TREE relation with all the parameters should return a valid expression', () => { const relation: ITreeRelation = { - type: RelationOperator.EqualThanRelation, + type: SparqlRelationOperator.EqualThanRelation, remainingItems: 10, path: "ex:path", value: { @@ -324,7 +325,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Integer, valueAsNumber: 5, chainOperator: [], - operator: RelationOperator.EqualThanRelation + operator: SparqlRelationOperator.EqualThanRelation }; expect(convertTreeRelationToSolverExpression(relation, variable)).toStrictEqual(expectedExpression); @@ -332,7 +333,7 @@ describe('solver function', () => { it('should return undefined given a relation witn a value term containing an unknowed value type', () => { const relation: ITreeRelation = { - type: RelationOperator.EqualThanRelation, + type: SparqlRelationOperator.EqualThanRelation, remainingItems: 10, path: "ex:path", value: { @@ -348,7 +349,7 @@ describe('solver function', () => { it('should return undefined given a relation with a term containing an incompatible value in relation to its value type', () => { const relation: ITreeRelation = { - type: RelationOperator.EqualThanRelation, + type: SparqlRelationOperator.EqualThanRelation, remainingItems: 10, path: "ex:path", value: { @@ -375,7 +376,7 @@ describe('solver function', () => { it('should return undefined given a relation without a value', () => { const relation: ITreeRelation = { - type: RelationOperator.EqualThanRelation, + type: SparqlRelationOperator.EqualThanRelation, remainingItems: 10, path: "ex:path", node: "https://www.example.be" @@ -539,4 +540,200 @@ describe('solver function', () => { }); }); + + describe('createEquationSystem', () => { + it('given multiple equations that are consistent with one and another should return a valid equation system and the first equation to resolve', () => { + const lastOperator = new LinkOperator(LogicOperator.And); + const firstOperator = new LinkOperator(LogicOperator.Or); + const secondOperator = new LinkOperator(LogicOperator.Or); + const thirdOperator = new LinkOperator(LogicOperator.Not); + const aVariable = 'x'; + const aRawValue = '1'; + const aValueType = SparqlOperandDataTypes.Int; + const avalueAsNumber = 1; + const anOperator = SparqlRelationOperator.EqualThanRelation; + + const operationTemplate = (c: LinkOperator[]): SolverExpression => { + return { + variable: aVariable, + rawValue: aRawValue, + valueType: aValueType, + valueAsNumber: avalueAsNumber, + operator: anOperator, + chainOperator: c + } + }; + + const firstOperation = operationTemplate([firstOperator]); + const firstEquation: SolverEquation = { chainOperator: firstOperation.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; + const secondOperation = operationTemplate([firstOperator, secondOperator]); + const secondEquation: SolverEquation = { chainOperator: secondOperation.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; + const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); + const thirdEquation: SolverEquation = { chainOperator: thirdOperation.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; + const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); + const expectedFirstEquation1: SolverEquation = { chainOperator: lastOperation1.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; + const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); + const expectedFirstEquation2: SolverEquation = { chainOperator: lastOperation2.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; + + const equations: SolverExpression[] = [ + firstOperation, + secondOperation, + thirdOperation, + lastOperation1, + lastOperation2 + ] + equations.sort(() => Math.random() - 0.5); + + const expectedEquationSystem: SolverEquationSystem = new Map([ + [firstOperator.toString(), firstEquation], + [secondOperator.toString(), secondEquation], + [thirdOperator.toString(), thirdEquation] + ]); + + const resp = createEquationSystem(equations); + if (resp) { + const [respEquationSystem, [respFirstEquation1, respFirstEquation2]] = resp; + expect(respEquationSystem).toStrictEqual(expectedEquationSystem); + expect(respFirstEquation1).toStrictEqual(expectedFirstEquation1); + expect(respFirstEquation2).toStrictEqual(expectedFirstEquation2); + } else { + expect(resp).toBeDefined(); + } + + }); + + it('given multiples equations where it is not possible to get the solution range of an equation it should return undefined', () => { + const lastOperator = new LinkOperator(LogicOperator.And); + const firstOperator = new LinkOperator(LogicOperator.Or); + const secondOperator = new LinkOperator(LogicOperator.Or); + const thirdOperator = new LinkOperator(LogicOperator.Not); + const aVariable = 'x'; + const aRawValue = '1'; + const aValueType = SparqlOperandDataTypes.Int; + const avalueAsNumber = 1; + const anOperator = SparqlRelationOperator.EqualThanRelation; + + const operationTemplate = (c: LinkOperator[]): SolverExpression => { + return { + variable: aVariable, + rawValue: aRawValue, + valueType: aValueType, + valueAsNumber: avalueAsNumber, + operator: anOperator, + chainOperator: c + } + }; + + const firstOperation = { + variable: aVariable, + rawValue: aRawValue, + valueType: aValueType, + valueAsNumber: avalueAsNumber, + operator: SparqlRelationOperator.GeospatiallyContainsRelation, + chainOperator: [firstOperator] + }; + const secondOperation = operationTemplate([firstOperator, secondOperator]); + const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); + const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); + const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); + + const equations: SolverExpression[] = [ + firstOperation, + secondOperation, + thirdOperation, + lastOperation1, + lastOperation2 + ]; + equations.sort(() => Math.random() - 0.5); + + expect(createEquationSystem(equations)).toBeUndefined(); + }); + + it('given multiples equations where there is multiple equation that could be the first equation to be resolved it should return undefined', () => { + const lastOperator = new LinkOperator(LogicOperator.And); + const firstOperator = new LinkOperator(LogicOperator.Or); + const secondOperator = new LinkOperator(LogicOperator.Or); + const thirdOperator = new LinkOperator(LogicOperator.Not); + const aVariable = 'x'; + const aRawValue = '1'; + const aValueType = SparqlOperandDataTypes.Int; + const avalueAsNumber = 1; + const anOperator = SparqlRelationOperator.EqualThanRelation; + + const operationTemplate = (c: LinkOperator[]): SolverExpression => { + return { + variable: aVariable, + rawValue: aRawValue, + valueType: aValueType, + valueAsNumber: avalueAsNumber, + operator: anOperator, + chainOperator: c + } + }; + + const firstOperation = operationTemplate([firstOperator]); + const secondOperation = operationTemplate([firstOperator, secondOperator]); + const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); + const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); + const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); + const lastOperation3 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); + + const equations: SolverExpression[] = [ + firstOperation, + secondOperation, + thirdOperation, + lastOperation1, + lastOperation2, + lastOperation3 + ]; + equations.sort(() => Math.random() - 0.5); + + expect(createEquationSystem(equations)).toBeUndefined(); + }); + + it('given multiples equations where there is no first equation to be resolved should return undefined', () => { + const lastOperator = new LinkOperator(LogicOperator.And); + const firstOperator = new LinkOperator(LogicOperator.Or); + const secondOperator = new LinkOperator(LogicOperator.Or); + const thirdOperator = new LinkOperator(LogicOperator.Not); + const aVariable = 'x'; + const aRawValue = '1'; + const aValueType = SparqlOperandDataTypes.Int; + const avalueAsNumber = 1; + const anOperator = SparqlRelationOperator.EqualThanRelation; + + const operationTemplate = (c: LinkOperator[]): SolverExpression => { + return { + variable: aVariable, + rawValue: aRawValue, + valueType: aValueType, + valueAsNumber: avalueAsNumber, + operator: anOperator, + chainOperator: c + } + }; + + const firstOperation = { + variable: aVariable, + rawValue: aRawValue, + valueType: aValueType, + valueAsNumber: avalueAsNumber, + operator: SparqlRelationOperator.GeospatiallyContainsRelation, + chainOperator: [firstOperator] + }; + const secondOperation = operationTemplate([firstOperator, secondOperator]); + const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); + const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); + + const equations: SolverExpression[] = [ + firstOperation, + secondOperation, + thirdOperation, + lastOperation1, + ]; + equations.sort(() => Math.random() - 0.5); + + expect(createEquationSystem(equations)).toBeUndefined(); + }); + }); }); \ No newline at end of file diff --git a/packages/types-link-traversal/lib/TreeMetadata.ts b/packages/types-link-traversal/lib/TreeMetadata.ts index 61746de75..22164e713 100644 --- a/packages/types-link-traversal/lib/TreeMetadata.ts +++ b/packages/types-link-traversal/lib/TreeMetadata.ts @@ -7,7 +7,7 @@ import type * as RDF from 'rdf-js'; // The type of the relationship. // https://treecg.github.io/specification/#vocabulary -export enum RelationOperator { +export enum SparqlRelationOperator { /** * All elements in the related node have this prefix. */ @@ -51,8 +51,8 @@ export enum RelationOperator { /** * A map to access the value of the enum RelationOperator by it's value in O(1). */ -export const RelationOperatorReversed: Map = -new Map(Object.values(RelationOperator).map(value => [ value, value ])); +export const RelationOperatorReversed: Map = +new Map(Object.values(SparqlRelationOperator).map(value => [ value, value ])); /** * Reference @@ -109,7 +109,7 @@ export interface ITreeRelation { /** * The type of relationship. */ - type?: RelationOperator; + type?: SparqlRelationOperator; /** * How many members can be reached when following this relation. */ @@ -147,7 +147,7 @@ export interface ITreeRelationRaw { /** * The relation operator type describe by the enum RelationOperator. */ - operator?: [RelationOperator, RDF.Quad]; + operator?: [SparqlRelationOperator, RDF.Quad]; /** * Refer to the TreeNodes of the similar name. */ From 53679c5e8f26f05116919cefdc1c9b1a50c9f62c Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 14 Dec 2022 11:14:23 +0100 Subject: [PATCH 087/189] Unit test resolveAFilterTerm. --- .../lib/solver.ts | 71 +++-- .../test/solver-test.ts | 289 +++++++++++++++++- 2 files changed, 319 insertions(+), 41 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 6b0da8ce4..cba6696b0 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -33,6 +33,34 @@ export function solveRelationWithFilter({ relation, filterExpression, variable } return true } +export function convertFilterExpressionToSolverExpression(expression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator: LinkOperator[]): SolverExpression[] { + + if ( + expression.args[0].expressionType === Algebra.expressionTypes.TERM + ) { + const rawOperator = expression.operator; + const operator = filterOperatorToRelationOperator(rawOperator) + if (typeof operator !== 'undefined') { + const solverExpression = resolveAFilterTerm(expression, operator, new Array(...linksOperator)); + if (typeof solverExpression !== 'undefined') { + filterExpressionList.push(solverExpression); + return filterExpressionList; + } + } + } else { + const logicOperator = LogicOperatorReversed.get(expression.operator); + if (typeof logicOperator !== 'undefined') { + const operator = new LinkOperator(logicOperator); + linksOperator.push(operator); + for (const arg of expression.args) { + convertFilterExpressionToSolverExpression(arg, filterExpressionList, linksOperator); + } + } + + } + return filterExpressionList; +} + export function resolveAFilterTerm(expression: Algebra.Expression, operator: SparqlRelationOperator, linksOperator: LinkOperator[]): SolverExpression | undefined { let variable: string | undefined; let rawValue: string | undefined; @@ -60,43 +88,14 @@ export function resolveAFilterTerm(expression: Algebra.Expression, operator: Spa chainOperator: linksOperator, } } - return undefined -} - -export function convertFilterExpressionToSolverExpression(expression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator: LinkOperator[]): SolverExpression[] { - - if ( - expression.args[0].expressionType === Algebra.expressionTypes.TERM - ) { - const rawOperator = expression.operator; - const operator = filterOperatorToRelationOperator(rawOperator) - if (typeof operator !== 'undefined') { - const solverExpression = resolveAFilterTerm(expression, operator, new Array(...linksOperator)); - if (typeof solverExpression !== 'undefined') { - filterExpressionList.push(solverExpression); - return filterExpressionList; - } - } - } else { - const logicOperator = LogicOperatorReversed.get(expression.operator); - if (typeof logicOperator !== 'undefined') { - const operator = new LinkOperator(logicOperator); - linksOperator.push(operator); - for (const arg of expression.args) { - convertFilterExpressionToSolverExpression(arg, filterExpressionList, linksOperator); - } - } - - } - return filterExpressionList; } export function resolveEquationSystem(equationSystem: SolverEquationSystem, firstEquation: [SolverEquation, SolverEquation]): SolutionDomain | undefined { - const localEquationSystem = new Map(equationSystem); - const localFistEquation = new Array(...firstEquation); - let domain: SolutionDomain = SolutionDomain.newWithInitialValue(localFistEquation[0].solutionDomain); + let domain: SolutionDomain = SolutionDomain.newWithInitialValue(firstEquation[0].solutionDomain); let idx: string = ""; - let currentEquation: SolverEquation | undefined = localFistEquation[1]; + // safety for no infinite loop + let i = 0; + let currentEquation: SolverEquation | undefined = firstEquation[1]; do { const resp = resolveEquation(currentEquation, domain); @@ -105,9 +104,9 @@ export function resolveEquationSystem(equationSystem: SolverEquationSystem, firs } [domain, idx] = resp; - currentEquation = localEquationSystem.get(idx); - - } while (currentEquation) + currentEquation = equationSystem.get(idx); + i++ + } while (currentEquation && i!= equationSystem.size + 1) return domain; } diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 2f290ee58..13ff9040e 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -6,15 +6,19 @@ import { areTypesCompatible, convertTreeRelationToSolverExpression, resolveEquation, - createEquationSystem + createEquationSystem, + resolveEquationSystem, + resolveAFilterTerm } from '../lib/solver'; import { SolutionRange } from '../lib/SolutionRange'; import { ITreeRelation, SparqlRelationOperator } from '@comunica/types-link-traversal'; import { SparqlOperandDataTypes, SolverExpression, SolverEquation, LogicOperator, SolverEquationSystem } from '../lib/solverInterfaces'; -import { DataFactory } from 'rdf-data-factory'; +import { DataFactory, DefaultGraph } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { SolutionDomain } from '../lib/SolutionDomain'; import { LinkOperator } from '../lib/LinkOperator'; +import { Algebra } from 'sparqlalgebrajs'; + const DF = new DataFactory(); @@ -645,7 +649,7 @@ describe('solver function', () => { lastOperation2 ]; equations.sort(() => Math.random() - 0.5); - + expect(createEquationSystem(equations)).toBeUndefined(); }); @@ -687,7 +691,7 @@ describe('solver function', () => { lastOperation3 ]; equations.sort(() => Math.random() - 0.5); - + expect(createEquationSystem(equations)).toBeUndefined(); }); @@ -732,8 +736,283 @@ describe('solver function', () => { lastOperation1, ]; equations.sort(() => Math.random() - 0.5); - + expect(createEquationSystem(equations)).toBeUndefined(); }); }); + + describe('resolveEquationSystem', () => { + it('should return a valid domain given a valid equation system', () => { + + const firstOperator = new LinkOperator(LogicOperator.Or); + const secondOperator = new LinkOperator(LogicOperator.And); + const thirdOperator = new LinkOperator(LogicOperator.Not); + const forthOperator = new LinkOperator(LogicOperator.Or); + const fifthOperator = new LinkOperator(LogicOperator.And); + const firstEquation: SolverEquation = { + solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33]), + chainOperator: [ + firstOperator, + ] + }; + + const secondEquation: SolverEquation = { + solutionDomain: new SolutionRange([75, 75]), + chainOperator: [ + firstOperator, + secondOperator, + ] + }; + + const thirdEquation: SolverEquation = { + solutionDomain: new SolutionRange([100, Number.POSITIVE_INFINITY]), + chainOperator: [ + firstOperator, + secondOperator, + thirdOperator, + forthOperator + ] + }; + const equationSystem: SolverEquationSystem = new Map([ + [firstEquation.chainOperator.slice(-1)[0].toString(), firstEquation], + [secondEquation.chainOperator.slice(-1)[0].toString(), secondEquation], + [thirdEquation.chainOperator.slice(-1)[0].toString(), thirdEquation] + ]); + + const firstEquationToSolve: [SolverEquation, SolverEquation] = [ + { + solutionDomain: new SolutionRange([1000, Number.POSITIVE_INFINITY]), + chainOperator: [ + firstOperator, + secondOperator, + thirdOperator, + forthOperator, + fifthOperator, + ] + }, + { + solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33 + Number.EPSILON]), + chainOperator: [ + firstOperator, + secondOperator, + thirdOperator, + forthOperator, + fifthOperator, + ] + } + ]; + // Nothing => [100, infinity] =>[-infinity, 100- epsilon] => [75,75]=> [75, 75], [-infinity, 33] + const expectedDomain: SolutionRange[] = [new SolutionRange([Number.NEGATIVE_INFINITY, 33]), new SolutionRange([75, 75])]; + + const resp = resolveEquationSystem(equationSystem, firstEquationToSolve); + + if (resp) { + expect(resp.get_domain()).toStrictEqual(expectedDomain); + } else { + expect(resp).toBeDefined(); + } + + }); + + it('should return undefined an equation system where the chain of operator is inconsistent', () => { + const firstOperator = new LinkOperator(LogicOperator.Or); + const secondOperator = new LinkOperator(LogicOperator.And); + const thirdOperator = new LinkOperator(LogicOperator.Not); + const forthOperator = new LinkOperator(LogicOperator.Or); + const fifthOperator = new LinkOperator(LogicOperator.And); + const firstEquation: SolverEquation = { + solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33]), + chainOperator: [ + firstOperator, + ] + }; + + const secondEquation: SolverEquation = { + solutionDomain: new SolutionRange([75, 75]), + chainOperator: [ + firstOperator, + secondOperator, + ] + }; + + const thirdEquation: SolverEquation = { + solutionDomain: new SolutionRange([100, Number.POSITIVE_INFINITY]), + chainOperator: [ + ] + }; + const equationSystem: SolverEquationSystem = new Map([ + [firstEquation.chainOperator.slice(-1)[0].toString(), firstEquation], + [secondEquation.chainOperator.slice(-1)[0].toString(), secondEquation], + [forthOperator.toString(), thirdEquation] + ]); + + const firstEquationToSolve: [SolverEquation, SolverEquation] = [ + { + solutionDomain: new SolutionRange([1000, Number.POSITIVE_INFINITY]), + chainOperator: [ + firstOperator, + secondOperator, + thirdOperator, + forthOperator, + fifthOperator, + ] + }, + { + solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33 + Number.EPSILON]), + chainOperator: [ + firstOperator, + secondOperator, + thirdOperator, + forthOperator, + fifthOperator, + ] + } + ]; + + expect(resolveEquationSystem(equationSystem, firstEquationToSolve)).toBeUndefined(); + }); + }); + + describe('resolveAFilterTerm', () => { + it('given an algebra expression with all the solver expression parameters should return a valid expression', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: "=", + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable('x'), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + + const expectedSolverExpression: SolverExpression = { + variable: 'x', + rawValue: '6', + valueType: SparqlOperandDataTypes.Integer, + valueAsNumber: 6, + operator: operator, + chainOperator: linksOperator + }; + + const resp = resolveAFilterTerm(expression, operator, linksOperator); + + if (resp) { + expect(resp).toStrictEqual(expectedSolverExpression); + } else { + expect(resp).toBeDefined(); + } + }); + + it('given an algebra expression without a variable than should return undefined', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: "=", + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + + expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + + }); + + it('given an algebra expression without a litteral than should return undefined', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: "=", + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable('x'), + } + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + + expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + + }); + + it('given an algebra expression without args than should return undefined', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: "=", + args: [], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + + expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + + }); + + it('given an algebra expression with a litteral containing an invalid datatype than should return undefined', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: "=", + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable('x'), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + + expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + }); + + it('given an algebra expression with a litteral containing a literal that cannot be converted into number should return undefined', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: "=", + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable('x'), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + + expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + }); + }); }); \ No newline at end of file From bde73fbd836b4dc72aaec10153fd2a304fd04e7a Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 15 Dec 2022 11:04:58 +0100 Subject: [PATCH 088/189] unit test recursifFilterExpressionToSolverExpression --- .../lib/solver.ts | 16 +- .../test/solver-test.ts | 200 +++++++++++++++++- 2 files changed, 198 insertions(+), 18 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index cba6696b0..f5066b250 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -23,7 +23,7 @@ export function solveRelationWithFilter({ relation, filterExpression, variable } if (typeof relationsolverExpressions === 'undefined') { return true; } - const filtersolverExpressions = convertFilterExpressionToSolverExpression(filterExpression, [], []); + const filtersolverExpressions = recursifFilterExpressionToSolverExpression(filterExpression, [], []); // the type are not compatible no evaluation is possible SPARQLEE will later return an error if (!areTypesCompatible(filtersolverExpressions.concat(relationsolverExpressions))) { return false; @@ -33,27 +33,25 @@ export function solveRelationWithFilter({ relation, filterExpression, variable } return true } -export function convertFilterExpressionToSolverExpression(expression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator: LinkOperator[]): SolverExpression[] { - +export function recursifFilterExpressionToSolverExpression(expression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator: LinkOperator[]): SolverExpression[] { if ( expression.args[0].expressionType === Algebra.expressionTypes.TERM ) { const rawOperator = expression.operator; const operator = filterOperatorToRelationOperator(rawOperator) - if (typeof operator !== 'undefined') { + if (operator) { const solverExpression = resolveAFilterTerm(expression, operator, new Array(...linksOperator)); - if (typeof solverExpression !== 'undefined') { + if (solverExpression) { filterExpressionList.push(solverExpression); return filterExpressionList; } } } else { const logicOperator = LogicOperatorReversed.get(expression.operator); - if (typeof logicOperator !== 'undefined') { + if (logicOperator) { const operator = new LinkOperator(logicOperator); - linksOperator.push(operator); for (const arg of expression.args) { - convertFilterExpressionToSolverExpression(arg, filterExpressionList, linksOperator); + recursifFilterExpressionToSolverExpression(arg, filterExpressionList, linksOperator.concat(operator)); } } @@ -106,7 +104,7 @@ export function resolveEquationSystem(equationSystem: SolverEquationSystem, firs currentEquation = equationSystem.get(idx); i++ - } while (currentEquation && i!= equationSystem.size + 1) + } while (currentEquation && i != equationSystem.size + 1) return domain; } diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 13ff9040e..ccdd45ad5 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -8,16 +8,17 @@ import { resolveEquation, createEquationSystem, resolveEquationSystem, - resolveAFilterTerm + resolveAFilterTerm, + recursifFilterExpressionToSolverExpression } from '../lib/solver'; import { SolutionRange } from '../lib/SolutionRange'; import { ITreeRelation, SparqlRelationOperator } from '@comunica/types-link-traversal'; -import { SparqlOperandDataTypes, SolverExpression, SolverEquation, LogicOperator, SolverEquationSystem } from '../lib/solverInterfaces'; -import { DataFactory, DefaultGraph } from 'rdf-data-factory'; +import { SparqlOperandDataTypes, SolverExpression, SolverEquation, LogicOperator, SolverEquationSystem, Variable } from '../lib/solverInterfaces'; +import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { SolutionDomain } from '../lib/SolutionDomain'; import { LinkOperator } from '../lib/LinkOperator'; -import { Algebra } from 'sparqlalgebrajs'; +import { Algebra, translate } from 'sparqlalgebrajs'; const DF = new DataFactory(); @@ -930,7 +931,7 @@ describe('solver function', () => { const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); - + }); it('given an algebra expression without a litteral than should return undefined', () => { @@ -950,7 +951,7 @@ describe('solver function', () => { const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); - + }); it('given an algebra expression without args than should return undefined', () => { @@ -964,7 +965,7 @@ describe('solver function', () => { const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); - + }); it('given an algebra expression with a litteral containing an invalid datatype than should return undefined', () => { @@ -988,7 +989,7 @@ describe('solver function', () => { const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); }); it('given an algebra expression with a litteral containing a literal that cannot be converted into number should return undefined', () => { @@ -1012,7 +1013,188 @@ describe('solver function', () => { const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + }); + }); + + describe('recursifFilterExpressionToSolverExpression', () => { + it('given an algebra expression with two logicals operators should return a list of solver expression', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x>5)) + }`).input.expression; + + const buildSolverExpression = ( + variable: Variable, + rawValue: string, + valueType: SparqlOperandDataTypes, + valueAsNumber: number, + operator: SparqlRelationOperator, + chainOperator: LinkOperator[]): SolverExpression => { + return { + rawValue, + variable, + valueType, + valueAsNumber, + operator, + chainOperator + } + }; + const variable = 'x'; + + LinkOperator.resetIdCount(); + const notOperator = new LinkOperator(LogicOperator.Not); + const andOperator = new LinkOperator(LogicOperator.And); + + const expectedEquation: SolverExpression[] = [ + buildSolverExpression( + variable, + '2', + SparqlOperandDataTypes.Integer, + 2, + SparqlRelationOperator.EqualThanRelation, + [notOperator, andOperator]), + + buildSolverExpression( + variable, + '5', + SparqlOperandDataTypes.Integer, + 5, + SparqlRelationOperator.GreaterThanRelation, + [notOperator, andOperator]), + ]; + + LinkOperator.resetIdCount(); + expect(recursifFilterExpressionToSolverExpression(expression, [], [])).toStrictEqual(expectedEquation); + + }); + + it('given an algebra expression with tree logicals operators should return a list of solver expression', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x>5) || ?x < 88.3) + }`).input.expression; + + const buildSolverExpression = ( + variable: Variable, + rawValue: string, + valueType: SparqlOperandDataTypes, + valueAsNumber: number, + operator: SparqlRelationOperator, + chainOperator: LinkOperator[]): SolverExpression => { + return { + rawValue, + variable, + valueType, + valueAsNumber, + operator, + chainOperator + } + }; + const variable = 'x'; + + LinkOperator.resetIdCount(); + const orOperator = new LinkOperator(LogicOperator.Or); + const notOperator = new LinkOperator(LogicOperator.Not); + const andOperator = new LinkOperator(LogicOperator.And); + + const expectedEquation: SolverExpression[] = [ + buildSolverExpression( + variable, + '2', + SparqlOperandDataTypes.Integer, + 2, + SparqlRelationOperator.EqualThanRelation, + [orOperator, notOperator, andOperator]), + + buildSolverExpression( + variable, + '5', + SparqlOperandDataTypes.Integer, + 5, + SparqlRelationOperator.GreaterThanRelation, + [orOperator, notOperator, andOperator]), + + buildSolverExpression( + variable, + '88.3', + SparqlOperandDataTypes.Decimal, + 88.3, + SparqlRelationOperator.LessThanRelation, + [orOperator]), + ]; + + LinkOperator.resetIdCount(); + expect(recursifFilterExpressionToSolverExpression(expression, [], [])).toStrictEqual(expectedEquation); + }); + + it('given an algebra expression with four logicals operators should return a list of solver expression', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x>5 || ?x>6) || ?x < 88.3) + }`).input.expression; + + const buildSolverExpression = ( + variable: Variable, + rawValue: string, + valueType: SparqlOperandDataTypes, + valueAsNumber: number, + operator: SparqlRelationOperator, + chainOperator: LinkOperator[]): SolverExpression => { + return { + rawValue, + variable, + valueType, + valueAsNumber, + operator, + chainOperator + } + }; + const variable = 'x'; + + LinkOperator.resetIdCount(); + const firstOrOperator = new LinkOperator(LogicOperator.Or); + const notOperator = new LinkOperator(LogicOperator.Not); + const secondOrOperator = new LinkOperator(LogicOperator.Or); + const andOperator = new LinkOperator(LogicOperator.And); + + + const expectedEquation: SolverExpression[] = [ + buildSolverExpression( + variable, + '2', + SparqlOperandDataTypes.Integer, + 2, + SparqlRelationOperator.EqualThanRelation, + [firstOrOperator, notOperator, secondOrOperator, andOperator]), + + buildSolverExpression( + variable, + '5', + SparqlOperandDataTypes.Integer, + 5, + SparqlRelationOperator.GreaterThanRelation, + [firstOrOperator, notOperator,secondOrOperator, andOperator]), + + buildSolverExpression( + variable, + '6', + SparqlOperandDataTypes.Integer, + 6, + SparqlRelationOperator.GreaterThanRelation, + [firstOrOperator, notOperator,secondOrOperator]), + + buildSolverExpression( + variable, + '88.3', + SparqlOperandDataTypes.Decimal, + 88.3, + SparqlRelationOperator.LessThanRelation, + [firstOrOperator]), + ]; + + LinkOperator.resetIdCount(); + expect(recursifFilterExpressionToSolverExpression(expression, [], [])).toStrictEqual(expectedEquation); }); }); }); \ No newline at end of file From 70ae60f86a670e9affe1fa37e333da1fba055344 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 15 Dec 2022 15:40:25 +0100 Subject: [PATCH 089/189] unit test for isRelationFilterExpressionDomainEmpty done. --- .../lib/SolutionDomain.ts | 4 + .../lib/solver.ts | 39 +++- .../test/ActorExtractLinksTree-test.ts | 4 +- .../test/SolutionDomain-test.ts | 14 ++ .../test/solver-test.ts | 221 +++++++++++++++++- .../test/treeMetadataExtraction-test.ts | 24 +- 6 files changed, 276 insertions(+), 30 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index e858d4025..98d671ab3 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -12,6 +12,10 @@ export class SolutionDomain { return new Array(...this.domain); } + public isDomainEmpty(): boolean { + return this.domain.length === 0; + } + public static newWithInitialValue(initialRange: SolutionRange): SolutionDomain { const newSolutionDomain = new SolutionDomain(); newSolutionDomain.domain = [initialRange]; diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index f5066b250..29751cf70 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -12,7 +12,7 @@ import { } from './solverInterfaces'; import { LastLogicalOperator } from './solverInterfaces'; -export function solveRelationWithFilter({ relation, filterExpression, variable }: { +export function isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable }: { relation: ITreeRelation, filterExpression: Algebra.Expression, variable: Variable @@ -20,17 +20,42 @@ export function solveRelationWithFilter({ relation, filterExpression, variable } LinkOperator.resetIdCount(); const relationsolverExpressions = convertTreeRelationToSolverExpression(relation, variable); // the relation doesn't have a value or a type, so we accept it - if (typeof relationsolverExpressions === 'undefined') { + if (!relationsolverExpressions) { return true; } const filtersolverExpressions = recursifFilterExpressionToSolverExpression(filterExpression, [], []); // the type are not compatible no evaluation is possible SPARQLEE will later return an error if (!areTypesCompatible(filtersolverExpressions.concat(relationsolverExpressions))) { + return true; + } + + const relationSolutionRange = getSolutionRange(relationsolverExpressions.valueAsNumber, relationsolverExpressions.operator); + // the relation is invalid so we filter it + if (!relationSolutionRange) { return false; } - const filterEquationSystem = createEquationSystem(filtersolverExpressions); + const equationSystemFirstEquation = createEquationSystem(filtersolverExpressions); - return true + // cannot create the equation system we don't filter the relation in case the error is internal to not + // loss results + if (!equationSystemFirstEquation) { + return true + } + + const [equationSystem, firstEquationToResolved] = equationSystemFirstEquation; + + // we check if the filter expression itself has a solution + let solutionDomain = resolveEquationSystem(equationSystem, firstEquationToResolved); + + // don't pass the relation if the filter cannot be resolved + if (solutionDomain.isDomainEmpty()) { + return false; + } + + solutionDomain = solutionDomain.add({ range: relationSolutionRange, operator: LogicOperator.And }); + + // if there is a possible solution we don't filter the link + return !solutionDomain.isDomainEmpty(); } export function recursifFilterExpressionToSolverExpression(expression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator: LinkOperator[]): SolverExpression[] { @@ -88,7 +113,7 @@ export function resolveAFilterTerm(expression: Algebra.Expression, operator: Spa } } -export function resolveEquationSystem(equationSystem: SolverEquationSystem, firstEquation: [SolverEquation, SolverEquation]): SolutionDomain | undefined { +export function resolveEquationSystem(equationSystem: SolverEquationSystem, firstEquation: [SolverEquation, SolverEquation]): SolutionDomain { let domain: SolutionDomain = SolutionDomain.newWithInitialValue(firstEquation[0].solutionDomain); let idx: string = ""; // safety for no infinite loop @@ -98,7 +123,7 @@ export function resolveEquationSystem(equationSystem: SolverEquationSystem, firs do { const resp = resolveEquation(currentEquation, domain); if (!resp) { - return undefined; + throw new Error(`unable to resolve the equation ${currentEquation}`) } [domain, idx] = resp; @@ -126,7 +151,7 @@ export function createEquationSystem(expressions: SolverExpression[]): [SolverEq }; const lastEquation = system.get(lastOperator); if (lastEquation) { - if(firstEquationLastOperator !== ""){ + if (firstEquationLastOperator !== "") { return undefined; } firstEquationToEvaluate = [lastEquation, equation]; diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index a6d8268ac..896518c7d 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -1,7 +1,7 @@ import { KeysRdfResolveQuadPattern, KeysInitQuery } from '@comunica/context-entries'; import { ActionContext, Bus } from '@comunica/core'; import type { ITreeRelation } from '@comunica/types-link-traversal'; -import { RelationOperator } from '@comunica/types-link-traversal'; +import { SparqlRelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; @@ -268,7 +268,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { { node: prunedUrl, remainingItems: 66, - type: RelationOperator.GreaterThanRelation, + type: SparqlRelationOperator.GreaterThanRelation, value: { value: '66', term: DF.literal('66'), diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index b235ea721..b3572876b 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -260,4 +260,18 @@ describe('SolutionDomain', () => { expect(spyAddWithOrOperator).toBeCalledTimes(1); }); }); + + describe('isDomainEmpty', ()=>{ + it('should return true when the domain is empty', ()=>{ + const domain = new SolutionDomain(); + + expect(domain.isDomainEmpty()).toBe(true); + }); + + it('should return false when the domain is not empty', ()=>{ + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0,1])); + + expect(domain.isDomainEmpty()).toBe(false); + }); + }); }); \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index ccdd45ad5..fadc74cb9 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -9,7 +9,8 @@ import { createEquationSystem, resolveEquationSystem, resolveAFilterTerm, - recursifFilterExpressionToSolverExpression + recursifFilterExpressionToSolverExpression, + isRelationFilterExpressionDomainEmpty } from '../lib/solver'; import { SolutionRange } from '../lib/SolutionRange'; import { ITreeRelation, SparqlRelationOperator } from '@comunica/types-link-traversal'; @@ -870,7 +871,7 @@ describe('solver function', () => { } ]; - expect(resolveEquationSystem(equationSystem, firstEquationToSolve)).toBeUndefined(); + expect(()=>{resolveEquationSystem(equationSystem, firstEquationToSolve)}).toThrow(); }); }); @@ -1169,12 +1170,12 @@ describe('solver function', () => { [firstOrOperator, notOperator, secondOrOperator, andOperator]), buildSolverExpression( - variable, - '5', - SparqlOperandDataTypes.Integer, - 5, - SparqlRelationOperator.GreaterThanRelation, - [firstOrOperator, notOperator,secondOrOperator, andOperator]), + variable, + '5', + SparqlOperandDataTypes.Integer, + 5, + SparqlRelationOperator.GreaterThanRelation, + [firstOrOperator, notOperator, secondOrOperator, andOperator]), buildSolverExpression( variable, @@ -1182,7 +1183,7 @@ describe('solver function', () => { SparqlOperandDataTypes.Integer, 6, SparqlRelationOperator.GreaterThanRelation, - [firstOrOperator, notOperator,secondOrOperator]), + [firstOrOperator, notOperator, secondOrOperator]), buildSolverExpression( variable, @@ -1197,4 +1198,206 @@ describe('solver function', () => { expect(recursifFilterExpressionToSolverExpression(expression, [], [])).toStrictEqual(expectedEquation); }); }); + + describe('isRelationFilterExpressionDomainEmpty', () => { + it('given a relation that is not able to be converted into a solverExpression should return true', () => { + const relation: ITreeRelation = { + node: "https://www.example.com", + value: { + value: "abc", + term: DF.literal('abc', DF.namedNode('foo')) + } + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x>5) || ?x < 88.3) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true) + }); + + it('should return true given a relation and a filter operation where types are not compatible', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')) + }, + node: "https://www.example.be" + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x>5) || ?x < 88.3) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it('should return false given a relation and a filter operation where types are not compatible', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')) + }, + node: "https://www.example.be" + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x>5) || ?x < 88.3) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it('should return true when the solution range is not valid of the relation', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.GeospatiallyContainsRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')) + }, + node: "https://www.example.be" + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x>5) || ?x < 88.3) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it('should return true when the equation system is not valid', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.GeospatiallyContainsRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')) + }, + node: "https://www.example.be" + }; + + const filterExpression:Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: "&&", + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: "=", + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable('x'), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('2', DF.namedNode("http://www.w3.org/2001/XMLSchema#integer")), + }, + ], + } + ] + }; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it('should return true when there is a solution for the filter expression and the relation', ()=>{ + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + }, + node: "https://www.example.be" + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x>5) || ?x < 88.3) + }`).input.expression; + + const solver = require('../lib/solver'); + const variable = 'x'; + jest.spyOn(solver, 'resolveEquationSystem').mockReturnValue(undefined); + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it('should return false when the filter expression has no solution ', ()=>{ + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + }, + node: "https://www.example.be" + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x>5 && ?x > 88.3) + }`).input.expression; + + const solver = require('../lib/solver'); + const variable = 'x'; + jest.spyOn(solver, 'resolveEquationSystem').mockReturnValue(undefined); + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + + it('should return false when the filter has a possible solution but the addition of the relation produce no possible solution', ()=>{ + const relation: ITreeRelation = { + type: SparqlRelationOperator.GreaterThanOrEqualToRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "100", + term: DF.literal('100', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + }, + node: "https://www.example.be" + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x>5) || ?x < 88.3) + }`).input.expression; + + const solver = require('../lib/solver'); + const variable = 'x'; + jest.spyOn(solver, 'resolveEquationSystem').mockReturnValue(undefined); + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + + }); }); \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts index 84296d767..80dde7b4a 100644 --- a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts +++ b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts @@ -1,5 +1,5 @@ import type { ITreeRelationRaw, ITreeRelation } from '@comunica/types-link-traversal'; -import { TreeNodes, RelationOperator } from '@comunica/types-link-traversal'; +import { TreeNodes, SparqlRelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { buildRelationElement, addRelationDescription, materializeTreeRelation } from '../lib/treeMetadataExtraction'; @@ -13,10 +13,10 @@ describe('treeMetadataExtraction', () => { const quad: RDF.Quad = DF.quad( DF.namedNode('ex:s'), DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode(RelationOperator.EqualThanRelation), + DF.namedNode(SparqlRelationOperator.EqualThanRelation), ); const relationDescriptions: Map = new Map(); - addRelationDescription(relationDescriptions, quad, RelationOperator.EqualThanRelation, 'operator'); + addRelationDescription(relationDescriptions, quad, SparqlRelationOperator.EqualThanRelation, 'operator'); expect(relationDescriptions.size).toBe(1); }); @@ -26,9 +26,9 @@ describe('treeMetadataExtraction', () => { () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode(RelationOperator.EqualThanRelation)); + DF.namedNode(SparqlRelationOperator.EqualThanRelation)); const relationDescriptions: Map = new Map([[ 'ex:s', { value: 22 }]]); - addRelationDescription(relationDescriptions, quad, RelationOperator.EqualThanRelation, 'operator'); + addRelationDescription(relationDescriptions, quad, SparqlRelationOperator.EqualThanRelation, 'operator'); expect(relationDescriptions.size).toBe(1); }); @@ -36,9 +36,9 @@ describe('treeMetadataExtraction', () => { () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode(RelationOperator.EqualThanRelation)); + DF.namedNode(SparqlRelationOperator.EqualThanRelation)); const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); - addRelationDescription(relationDescriptions, quad, RelationOperator.EqualThanRelation, 'operator'); + addRelationDescription(relationDescriptions, quad, SparqlRelationOperator.EqualThanRelation, 'operator'); expect(relationDescriptions.size).toBe(2); }); @@ -102,13 +102,13 @@ describe('treeMetadataExtraction', () => { () => { const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode(RelationOperator.EqualThanRelation)); + DF.namedNode(SparqlRelationOperator.EqualThanRelation)); const res = buildRelationElement(quad); expect(res).toBeDefined(); const [ value, key ] = res; expect(key).toBe( 'operator'); - expect(value).toBe(RelationOperator.EqualThanRelation); + expect(value).toBe(SparqlRelationOperator.EqualThanRelation); }); it('should return undefined when the type does not exist', @@ -126,7 +126,7 @@ describe('treeMetadataExtraction', () => { it('should materialize a tree Relation when all the raw relation are provided', () => { const aSubject = 'foo'; const aValue = '0'; - const anOperator = RelationOperator.PrefixRelation; + const anOperator = SparqlRelationOperator.PrefixRelation; const aRemainingItemDefinition = 44; const aQuad = DF.quad( DF.blankNode(''), @@ -159,7 +159,7 @@ describe('treeMetadataExtraction', () => { it('should materialize a tree Relation when the remaining item is missing', () => { const aSubject = 'foo'; const aValue = '0'; - const anOperator = RelationOperator.PrefixRelation; + const anOperator = SparqlRelationOperator.PrefixRelation; const aQuad = DF.quad( DF.blankNode(''), DF.namedNode(''), @@ -188,7 +188,7 @@ describe('treeMetadataExtraction', () => { it('should materialize a tree Relation when the value is missing', () => { const aSubject = 'foo'; - const anOperator = RelationOperator.PrefixRelation; + const anOperator = SparqlRelationOperator.PrefixRelation; const aQuad = DF.quad( DF.blankNode(''), DF.namedNode(''), From 3f8f172bf81b033cbaa2ddd241fd44b3e2175d28 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 15 Dec 2022 15:51:05 +0100 Subject: [PATCH 090/189] management of variable consistency --- .../lib/solver.ts | 20 +++++++----- .../test/solver-test.ts | 31 ++++++++++--------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 29751cf70..f21e04df9 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -23,7 +23,7 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi if (!relationsolverExpressions) { return true; } - const filtersolverExpressions = recursifFilterExpressionToSolverExpression(filterExpression, [], []); + const filtersolverExpressions = recursifFilterExpressionToSolverExpression(filterExpression, [], [], variable); // the type are not compatible no evaluation is possible SPARQLEE will later return an error if (!areTypesCompatible(filtersolverExpressions.concat(relationsolverExpressions))) { return true; @@ -58,14 +58,14 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi return !solutionDomain.isDomainEmpty(); } -export function recursifFilterExpressionToSolverExpression(expression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator: LinkOperator[]): SolverExpression[] { +export function recursifFilterExpressionToSolverExpression(expression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator: LinkOperator[], variable: Variable): SolverExpression[] { if ( expression.args[0].expressionType === Algebra.expressionTypes.TERM ) { const rawOperator = expression.operator; const operator = filterOperatorToRelationOperator(rawOperator) if (operator) { - const solverExpression = resolveAFilterTerm(expression, operator, new Array(...linksOperator)); + const solverExpression = resolveAFilterTerm(expression, operator, new Array(...linksOperator), variable); if (solverExpression) { filterExpressionList.push(solverExpression); return filterExpressionList; @@ -76,7 +76,7 @@ export function recursifFilterExpressionToSolverExpression(expression: Algebra.E if (logicOperator) { const operator = new LinkOperator(logicOperator); for (const arg of expression.args) { - recursifFilterExpressionToSolverExpression(arg, filterExpressionList, linksOperator.concat(operator)); + recursifFilterExpressionToSolverExpression(arg, filterExpressionList, linksOperator.concat(operator), variable); } } @@ -84,15 +84,19 @@ export function recursifFilterExpressionToSolverExpression(expression: Algebra.E return filterExpressionList; } -export function resolveAFilterTerm(expression: Algebra.Expression, operator: SparqlRelationOperator, linksOperator: LinkOperator[]): SolverExpression | undefined { - let variable: string | undefined; +export function resolveAFilterTerm(expression: Algebra.Expression, operator: SparqlRelationOperator, linksOperator: LinkOperator[], variable: Variable): SolverExpression | undefined { let rawValue: string | undefined; let valueType: SparqlOperandDataTypes | undefined; let valueAsNumber: number | undefined; + let hasVariable = false; for (const arg of expression.args) { if ('term' in arg && arg.term.termType === 'Variable') { - variable = arg.term.value; + if(arg.term.value !== variable){ + return undefined; + }else{ + hasVariable = true; + } } else if ('term' in arg && arg.term.termType === 'Literal') { rawValue = arg.term.value; valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); @@ -101,7 +105,7 @@ export function resolveAFilterTerm(expression: Algebra.Expression, operator: Spa } } } - if (variable && rawValue && valueType && valueAsNumber) { + if (hasVariable && rawValue && valueType && valueAsNumber) { return { variable, rawValue, diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index fadc74cb9..b1652b6a3 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -896,9 +896,9 @@ describe('solver function', () => { }; const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - + const variable = 'x' const expectedSolverExpression: SolverExpression = { - variable: 'x', + variable, rawValue: '6', valueType: SparqlOperandDataTypes.Integer, valueAsNumber: 6, @@ -906,7 +906,7 @@ describe('solver function', () => { chainOperator: linksOperator }; - const resp = resolveAFilterTerm(expression, operator, linksOperator); + const resp = resolveAFilterTerm(expression, operator, linksOperator, variable); if (resp) { expect(resp).toStrictEqual(expectedSolverExpression); @@ -931,11 +931,12 @@ describe('solver function', () => { const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeUndefined(); }); it('given an algebra expression without a litteral than should return undefined', () => { + const variable = 'x'; const expression: Algebra.Expression = { type: Algebra.types.EXPRESSION, expressionType: Algebra.expressionTypes.OPERATOR, @@ -944,14 +945,14 @@ describe('solver function', () => { { type: Algebra.types.EXPRESSION, expressionType: Algebra.expressionTypes.TERM, - term: DF.variable('x'), + term: DF.variable(variable), } ], }; const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); }); @@ -965,11 +966,12 @@ describe('solver function', () => { const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeUndefined(); }); it('given an algebra expression with a litteral containing an invalid datatype than should return undefined', () => { + const variable = 'x'; const expression: Algebra.Expression = { type: Algebra.types.EXPRESSION, expressionType: Algebra.expressionTypes.OPERATOR, @@ -978,7 +980,7 @@ describe('solver function', () => { { type: Algebra.types.EXPRESSION, expressionType: Algebra.expressionTypes.TERM, - term: DF.variable('x'), + term: DF.variable(variable), }, { type: Algebra.types.EXPRESSION, @@ -990,10 +992,11 @@ describe('solver function', () => { const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); }); it('given an algebra expression with a litteral containing a literal that cannot be converted into number should return undefined', () => { + const variable = 'x'; const expression: Algebra.Expression = { type: Algebra.types.EXPRESSION, expressionType: Algebra.expressionTypes.OPERATOR, @@ -1002,7 +1005,7 @@ describe('solver function', () => { { type: Algebra.types.EXPRESSION, expressionType: Algebra.expressionTypes.TERM, - term: DF.variable('x'), + term: DF.variable(variable), }, { type: Algebra.types.EXPRESSION, @@ -1014,7 +1017,7 @@ describe('solver function', () => { const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - expect(resolveAFilterTerm(expression, operator, linksOperator)).toBeUndefined(); + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); }); }); @@ -1066,7 +1069,7 @@ describe('solver function', () => { ]; LinkOperator.resetIdCount(); - expect(recursifFilterExpressionToSolverExpression(expression, [], [])).toStrictEqual(expectedEquation); + expect(recursifFilterExpressionToSolverExpression(expression, [], [], variable)).toStrictEqual(expectedEquation); }); @@ -1126,7 +1129,7 @@ describe('solver function', () => { ]; LinkOperator.resetIdCount(); - expect(recursifFilterExpressionToSolverExpression(expression, [], [])).toStrictEqual(expectedEquation); + expect(recursifFilterExpressionToSolverExpression(expression, [], [], variable)).toStrictEqual(expectedEquation); }); it('given an algebra expression with four logicals operators should return a list of solver expression', () => { @@ -1195,7 +1198,7 @@ describe('solver function', () => { ]; LinkOperator.resetIdCount(); - expect(recursifFilterExpressionToSolverExpression(expression, [], [])).toStrictEqual(expectedEquation); + expect(recursifFilterExpressionToSolverExpression(expression, [], [], variable)).toStrictEqual(expectedEquation); }); }); From d4c9c0f328f4ce7a02861cba3d876f67286510c9 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 16 Dec 2022 08:27:57 +0100 Subject: [PATCH 091/189] solverInterface documented. --- .../lib/solver.ts | 14 ++-- .../lib/solverInterfaces.ts | 64 ++++++++++++++++--- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index f21e04df9..f89718c41 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -8,7 +8,7 @@ import { LinkOperator } from './LinkOperator'; import { SparqlOperandDataTypes, SolverEquationSystem, LogicOperatorReversed, LogicOperator, SolverExpression, - Variable, SolverEquation, SparqlOperandDataTypesReversed + Variable, SolverExpressionRange, SparqlOperandDataTypesReversed } from './solverInterfaces'; import { LastLogicalOperator } from './solverInterfaces'; @@ -117,12 +117,12 @@ export function resolveAFilterTerm(expression: Algebra.Expression, operator: Spa } } -export function resolveEquationSystem(equationSystem: SolverEquationSystem, firstEquation: [SolverEquation, SolverEquation]): SolutionDomain { +export function resolveEquationSystem(equationSystem: SolverEquationSystem, firstEquation: [SolverExpressionRange, SolverExpressionRange]): SolutionDomain { let domain: SolutionDomain = SolutionDomain.newWithInitialValue(firstEquation[0].solutionDomain); let idx: string = ""; // safety for no infinite loop let i = 0; - let currentEquation: SolverEquation | undefined = firstEquation[1]; + let currentEquation: SolverExpressionRange | undefined = firstEquation[1]; do { const resp = resolveEquation(currentEquation, domain); @@ -138,9 +138,9 @@ export function resolveEquationSystem(equationSystem: SolverEquationSystem, firs return domain; } -export function createEquationSystem(expressions: SolverExpression[]): [SolverEquationSystem, [SolverEquation, SolverEquation]] | undefined { +export function createEquationSystem(expressions: SolverExpression[]): [SolverEquationSystem, [SolverExpressionRange, SolverExpressionRange]] | undefined { const system: SolverEquationSystem = new Map(); - let firstEquationToEvaluate: [SolverEquation, SolverEquation] | undefined = undefined; + let firstEquationToEvaluate: [SolverExpressionRange, SolverExpressionRange] | undefined = undefined; let firstEquationLastOperator: string = ""; for (const expression of expressions) { @@ -149,7 +149,7 @@ export function createEquationSystem(expressions: SolverExpression[]): [SolverEq if (!solutionRange) { return undefined; } - const equation: SolverEquation = { + const equation: SolverExpressionRange = { chainOperator: expression.chainOperator, solutionDomain: solutionRange }; @@ -173,7 +173,7 @@ export function createEquationSystem(expressions: SolverExpression[]): [SolverEq return [system, firstEquationToEvaluate]; } -export function resolveEquation(equation: SolverEquation, domain: SolutionDomain): [SolutionDomain, LastLogicalOperator] | undefined { +export function resolveEquation(equation: SolverExpressionRange, domain: SolutionDomain): [SolutionDomain, LastLogicalOperator] | undefined { let localDomain = domain.clone(); let i = -1; let currentLastOperator = equation.chainOperator.at(i); diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index fb031bb97..fb3b48fb9 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -3,6 +3,7 @@ import { SolutionRange } from './SolutionRange'; import { LinkOperator } from './LinkOperator'; /** * Valid SPARQL data type for operation. + * reference: https://www.w3.org/TR/sparql11-query/#operandDataTypes */ export enum SparqlOperandDataTypes { Integer = 'http://www.w3.org/2001/XMLSchema#integer', @@ -28,39 +29,82 @@ export enum SparqlOperandDataTypes { } /** - * A map to access the value of the enum SparqlOperandDataTypesReversed by it's value in O(1). - */ - export const SparqlOperandDataTypesReversed: Map = - new Map(Object.values(SparqlOperandDataTypes).map(value => [value, value])); + * A map to access the value of the enum SparqlOperandDataTypesReversed by it's value in O(1) time complexity. +*/ +export const SparqlOperandDataTypesReversed: Map = + new Map(Object.values(SparqlOperandDataTypes).map(value => [value, value])); +/** + * Logical operator that linked logical expression together. + */ export enum LogicOperator { And = '&&', Or = '||', Not = '!', }; +/** + * A map to access the value of the enum LogicOperator by it's value in O(1) time complexity. + */ export const LogicOperatorReversed: Map = new Map(Object.values(LogicOperator).map(value => [value, value])); - -export interface SolverEquation { +/** + * A range of a solver expression with his chain of logical operation. + */ +export interface SolverExpressionRange { + /** + * The chain of operation attached to the expression. + */ chainOperator: LinkOperator[]; + /** + * The domain of the solution of this expression. + */ solutionDomain: SolutionRange; } -export type SolverEquationSystem = Map; +/** + * A system of equation to be solved by the solver. It is indexed by the last logical operation that has to be apply + * to the expression + */ +export type SolverEquationSystem = Map; +/** + * A last logical expression of a chain of logical expression. + */ export type LastLogicalOperator = string; +/** + * A variable to be solved by the solver. + */ export type Variable = string; - +/** + * An expression of the solver containing also the chain of operation attached to it. + * eg: x>=5.5 chained with &&, ||, ! + */ export interface SolverExpression { + /** + * The variable of the expression + */ variable: Variable; - + /** + * The constant value attached to the expression as a String. + */ rawValue: string; + /** + * The data type of the constant value. + */ valueType: SparqlOperandDataTypes; + /** + * The value repressented as a number + */ valueAsNumber: number; - + /** + * The operator binding the value and the variable. + */ operator: SparqlRelationOperator; + /** + * The chain of logical operator attached to the expression. + */ chainOperator: LinkOperator[]; }; From 62b7c6494f48ab5e84c973a6b8db2d70b2179c02 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 16 Dec 2022 16:40:27 +0100 Subject: [PATCH 092/189] solver documented. --- .../lib/solver.ts | 163 ++++++++++++++---- 1 file changed, 128 insertions(+), 35 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index f89718c41..b219cd2fe 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -12,6 +12,16 @@ import { } from './solverInterfaces'; import { LastLogicalOperator } from './solverInterfaces'; + +/** + * Check if the solution domain of a system of equation compose of the expressions of the filter + * expression and the relation is not empty. + * @param {ITreeRelation} relation - The tree:relation that we wish to determine if there is a possible solution + * when we combine it with a filter expression. + * @param {Algebra.Expression} filterExpression - The Algebra expression of the filter. + * @param {variable} variable - The variable to be resolved. + * @returns {boolean} Return true if the domain + */ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable }: { relation: ITreeRelation, filterExpression: Algebra.Expression, @@ -45,7 +55,7 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi const [equationSystem, firstEquationToResolved] = equationSystemFirstEquation; // we check if the filter expression itself has a solution - let solutionDomain = resolveEquationSystem(equationSystem, firstEquationToResolved); + let solutionDomain = resolveSolutionDomainEquationSystem(equationSystem, firstEquationToResolved); // don't pass the relation if the filter cannot be resolved if (solutionDomain.isDomainEmpty()) { @@ -57,25 +67,38 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi // if there is a possible solution we don't filter the link return !solutionDomain.isDomainEmpty(); } - -export function recursifFilterExpressionToSolverExpression(expression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator: LinkOperator[], variable: Variable): SolverExpression[] { +/** + * A recursif function that traverse the Algebra expression to capture each boolean expression and there associated + * chain of logical expression. On the first call the filterExpressionList and linksOperator must be empty, they serve + * as states to build the expressions. + * @param {Algebra.Expression} filterExpression - The expression of the filter. + * @param {SolverExpression[]} filterExpressionList - The solver expression acquire until then. + * Should be empty on the first call. + * @param {LinkOperator[]} linksOperator - The logical operator acquire until then. + * Should be empty on the first call. + * @param {Variable} variable - The variable the solver expression must posses. + * @returns {SolverExpression[]} Return the solver expression converted from the filter expression + */ +export function recursifFilterExpressionToSolverExpression(filterExpression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator: LinkOperator[], variable: Variable): SolverExpression[] { + // if it's an array of term then we should be able to create a solver expression if ( - expression.args[0].expressionType === Algebra.expressionTypes.TERM + filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM ) { - const rawOperator = expression.operator; - const operator = filterOperatorToRelationOperator(rawOperator) + const rawOperator = filterExpression.operator; + const operator = filterOperatorToSparqlRelationOperator(rawOperator) if (operator) { - const solverExpression = resolveAFilterTerm(expression, operator, new Array(...linksOperator), variable); + const solverExpression = resolveAFilterTerm(filterExpression, operator, new Array(...linksOperator), variable); if (solverExpression) { filterExpressionList.push(solverExpression); return filterExpressionList; } } + // else we store the logical operator an go deeper into the Algebra graph } else { - const logicOperator = LogicOperatorReversed.get(expression.operator); + const logicOperator = LogicOperatorReversed.get(filterExpression.operator); if (logicOperator) { const operator = new LinkOperator(logicOperator); - for (const arg of expression.args) { + for (const arg of filterExpression.args) { recursifFilterExpressionToSolverExpression(arg, filterExpressionList, linksOperator.concat(operator), variable); } } @@ -83,18 +106,27 @@ export function recursifFilterExpressionToSolverExpression(expression: Algebra.E } return filterExpressionList; } - +/** + * From an Algebra expression return an solver expression if possible + * @param {Algebra.Expression} expression - Algebra expression containing the a variable and a litteral. + * @param {SparqlRelationOperator} operator - The SPARQL operator defining the expression. + * @param {LinkOperator[]} linksOperator - The logical operator prior to this expression. + * @param {Variable} variable - The variable the expression should have to be part of a system of equation. + * @returns {SolverExpression | undefined} Return a solver expression if possible + */ export function resolveAFilterTerm(expression: Algebra.Expression, operator: SparqlRelationOperator, linksOperator: LinkOperator[], variable: Variable): SolverExpression | undefined { let rawValue: string | undefined; let valueType: SparqlOperandDataTypes | undefined; let valueAsNumber: number | undefined; let hasVariable = false; + // find the constituant element of the solver expression for (const arg of expression.args) { if ('term' in arg && arg.term.termType === 'Variable') { - if(arg.term.value !== variable){ + // check if the expression has the same variable as the one the solver try to resolved + if (arg.term.value !== variable) { return undefined; - }else{ + } else { hasVariable = true; } } else if ('term' in arg && arg.term.termType === 'Literal') { @@ -105,6 +137,7 @@ export function resolveAFilterTerm(expression: Algebra.Expression, operator: Spa } } } + // return if a fully form solver expression can be created if (hasVariable && rawValue && valueType && valueAsNumber) { return { variable, @@ -116,16 +149,22 @@ export function resolveAFilterTerm(expression: Algebra.Expression, operator: Spa } } } - -export function resolveEquationSystem(equationSystem: SolverEquationSystem, firstEquation: [SolverExpressionRange, SolverExpressionRange]): SolutionDomain { - let domain: SolutionDomain = SolutionDomain.newWithInitialValue(firstEquation[0].solutionDomain); +/** + * Find the domain of the possible solutions of a system of equations. + * Will thrown an error if an equation cannot be resolved. + * @param {SolverEquationSystem} equationSystem + * @param {[SolverExpressionRange, SolverExpressionRange]} firstExpression - The first expression to evaluate. + * @returns {SolutionDomain} + */ +export function resolveSolutionDomainEquationSystem(equationSystem: SolverEquationSystem, firstExpression: [SolverExpressionRange, SolverExpressionRange]): SolutionDomain { + let domain: SolutionDomain = SolutionDomain.newWithInitialValue(firstExpression[0].solutionDomain); let idx: string = ""; - // safety for no infinite loop + // safety to avoid infinite loop let i = 0; - let currentEquation: SolverExpressionRange | undefined = firstEquation[1]; + let currentEquation: SolverExpressionRange | undefined = firstExpression[1]; do { - const resp = resolveEquation(currentEquation, domain); + const resp = resolveSolutionDomainWithAnExpression(currentEquation, domain); if (!resp) { throw new Error(`unable to resolve the equation ${currentEquation}`) } @@ -137,10 +176,17 @@ export function resolveEquationSystem(equationSystem: SolverEquationSystem, firs return domain; } - +/** + * Create a system of equation from the provided expression and return separatly the first expression to evaluate. + * @param {SolverExpression[]} expressions - the expression composing the equation system + * @returns {[SolverEquationSystem, [SolverExpressionRange, SolverExpressionRange]] | undefined} if the expression form a possible system of equation return + * the system of equation and the first expression to evaluate. + */ export function createEquationSystem(expressions: SolverExpression[]): [SolverEquationSystem, [SolverExpressionRange, SolverExpressionRange]] | undefined { const system: SolverEquationSystem = new Map(); + // the first expression that has to be evaluated let firstEquationToEvaluate: [SolverExpressionRange, SolverExpressionRange] | undefined = undefined; + // the last logical operator apply to the first expression to be evaluated let firstEquationLastOperator: string = ""; for (const expression of expressions) { @@ -155,6 +201,7 @@ export function createEquationSystem(expressions: SolverExpression[]): [SolverEq }; const lastEquation = system.get(lastOperator); if (lastEquation) { + // there cannot be two first equation to be evaluated if (firstEquationLastOperator !== "") { return undefined; } @@ -164,33 +211,49 @@ export function createEquationSystem(expressions: SolverExpression[]): [SolverEq system.set(lastOperator, equation); } } - + // there should be a first expression to be evaluated if (!firstEquationToEvaluate) { return undefined; } + // we delete the fist equation to be evaluated from the system of equation because it is a value returned system.delete(firstEquationLastOperator); return [system, firstEquationToEvaluate]; } - -export function resolveEquation(equation: SolverExpressionRange, domain: SolutionDomain): [SolutionDomain, LastLogicalOperator] | undefined { +/** + * Resolve the solution domain when we add a new expression and returned the new domain with the next expression that has to be evaluated. + * @param {SolverExpressionRange} equation - Current solver expression. + * @param {SolutionDomain} domain - Current solution domain of the system of equation. + * @returns {[SolutionDomain, LastLogicalOperator] | undefined} If the equation can be solved returned the new domain and the next + * indexed logical operator that has to be resolved. The system of equation is indexed by their last logical operator hence + * the next expression can be found using the returned operator. An empty string is returned if it was the last expression instead of + * undefined to simply implementation, because the system of equation will retuned an undefined value with an empty string. + */ +export function resolveSolutionDomainWithAnExpression(equation: SolverExpressionRange, domain: SolutionDomain): [SolutionDomain, LastLogicalOperator] | undefined { let localDomain = domain.clone(); + // to keep track of the last expression because we resolved all the not operator + // next to the current last logical operator let i = -1; + // we find the last logical expression that has to be resolved let currentLastOperator = equation.chainOperator.at(i); if (!currentLastOperator) { return undefined; } i--; + // resolve the new domain localDomain = localDomain.add({ range: equation.solutionDomain, operator: currentLastOperator?.operator }); currentLastOperator = equation.chainOperator.at(i); + // if it was the last expression if (!currentLastOperator) { return [localDomain, ""]; } + // we solved all the NOT operator next to the last logical operator while (currentLastOperator?.operator === LogicOperator.Not) { localDomain = localDomain.add({ operator: currentLastOperator?.operator }); i--; currentLastOperator = equation.chainOperator.at(i); + // it the last operator was a NOT if (!currentLastOperator?.operator) { return [localDomain, ""]; } @@ -198,30 +261,41 @@ export function resolveEquation(equation: SolverExpressionRange, domain: Solutio return [localDomain, currentLastOperator.toString()] } - -export function convertTreeRelationToSolverExpression(expression: ITreeRelation, variable: string): SolverExpression | undefined { - if (expression.value && expression.type) { - const valueType = SparqlOperandDataTypesReversed.get((expression.value.term).datatype.value); +/** + * Convert a TREE relation into a solver expression. + * @param {ITreeRelation} relation - TREE relation. + * @param {Variable} variable - variable of the SPARQL query associated with the tree:path of the relation. + * @returns {SolverExpression | undefined} Resulting solver expression if the data type is supported by SPARQL + * and the value can be cast into a number. + */ +export function convertTreeRelationToSolverExpression(relation: ITreeRelation, variable: Variable): SolverExpression | undefined { + if (relation.value && relation.type) { + const valueType = SparqlOperandDataTypesReversed.get((relation.value.term).datatype.value); if (!valueType) { return undefined; } - const valueNumber = castSparqlRdfTermIntoNumber(expression.value.value, valueType); + const valueNumber = castSparqlRdfTermIntoNumber(relation.value.value, valueType); if (!valueNumber) { return undefined; } return { variable, - rawValue: expression.value.value, + rawValue: relation.value.value, valueType, valueAsNumber: valueNumber, chainOperator: [], - operator: expression.type, + operator: relation.type, }; } } - +/** + * Check if all the expression provided have a SparqlOperandDataTypes compatible type + * it is considered that all number types are compatible between them. + * @param {SolverExpression[]} expressions - The subject expression. + * @returns {boolean} Return true if the type are compatible. + */ export function areTypesCompatible(expressions: SolverExpression[]): boolean { const firstType = expressions[0].valueType; for (const expression of expressions) { @@ -236,7 +310,12 @@ export function areTypesCompatible(expressions: SolverExpression[]): boolean { } return true } - +/** + * Find the solution range of a value and operator which is analogue to an expression. + * @param {number} value + * @param {SparqlRelationOperator} operator + * @returns {SolutionRange | undefined} The solution range associated with the value and the operator. + */ export function getSolutionRange(value: number, operator: SparqlRelationOperator): SolutionRange | undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: @@ -250,10 +329,16 @@ export function getSolutionRange(value: number, operator: SparqlRelationOperator case SparqlRelationOperator.LessThanOrEqualToRelation: return new SolutionRange([Number.NEGATIVE_INFINITY, value]); default: + // not an operator that is compatible with number. break; } } - +/** + * Convert a RDF value into a number. + * @param {string} rdfTermValue - The raw value + * @param {SparqlOperandDataTypes} rdfTermType - The type of the value + * @returns {number | undefined} The resulting number or undefined if the convertion is not possible. + */ export function castSparqlRdfTermIntoNumber(rdfTermValue: string, rdfTermType: SparqlOperandDataTypes): number | undefined { if ( rdfTermType === SparqlOperandDataTypes.Decimal || @@ -273,7 +358,11 @@ export function castSparqlRdfTermIntoNumber(rdfTermValue: string, rdfTermType: S } return undefined; } - +/** + * Determine if the type is a number. + * @param {SparqlOperandDataTypes} rdfTermType - The subject type + * @returns {boolean} Return true if the type is a number. + */ export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean { return rdfTermType === SparqlOperandDataTypes.Integer || rdfTermType === SparqlOperandDataTypes.NonPositiveInteger || @@ -290,8 +379,12 @@ export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): rdfTermType === SparqlOperandDataTypes.Decimal || rdfTermType === SparqlOperandDataTypes.Int; } - -export function filterOperatorToRelationOperator(filterOperator: string): SparqlRelationOperator | undefined { +/** + * Convert a filter operator to SparqlRelationOperator. + * @param {string} filterOperator - The filter operator. + * @returns {SparqlRelationOperator | undefined} The SparqlRelationOperator corresponding to the filter operator + */ +export function filterOperatorToSparqlRelationOperator(filterOperator: string): SparqlRelationOperator | undefined { switch (filterOperator) { case '=': return SparqlRelationOperator.EqualThanRelation; From 602b0a451caddd59302c34b34d62a5cb8ed0f4b4 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 16 Dec 2022 16:44:49 +0100 Subject: [PATCH 093/189] fix test function name. --- .../test/solver-test.ts | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index b1652b6a3..ac5ed2a66 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1,20 +1,20 @@ import { - filterOperatorToRelationOperator, + filterOperatorToSparqlRelationOperator, isSparqlOperandNumberType, castSparqlRdfTermIntoNumber, getSolutionRange, areTypesCompatible, convertTreeRelationToSolverExpression, - resolveEquation, + resolveSolutionDomainWithAnExpression, createEquationSystem, - resolveEquationSystem, + resolveSolutionDomainEquationSystem, resolveAFilterTerm, recursifFilterExpressionToSolverExpression, isRelationFilterExpressionDomainEmpty } from '../lib/solver'; import { SolutionRange } from '../lib/SolutionRange'; import { ITreeRelation, SparqlRelationOperator } from '@comunica/types-link-traversal'; -import { SparqlOperandDataTypes, SolverExpression, SolverEquation, LogicOperator, SolverEquationSystem, Variable } from '../lib/solverInterfaces'; +import { SparqlOperandDataTypes, SolverExpression, SolverExpressionRange, LogicOperator, SolverEquationSystem, Variable } from '../lib/solverInterfaces'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { SolutionDomain } from '../lib/SolutionDomain'; @@ -25,7 +25,7 @@ import { Algebra, translate } from 'sparqlalgebrajs'; const DF = new DataFactory(); describe('solver function', () => { - describe('filterOperatorToRelationOperator', () => { + describe('filterOperatorToSparqlRelationOperator', () => { it('should return the RelationOperator given a string representation', () => { const testTable: [string, SparqlRelationOperator][] = [ ['=', SparqlRelationOperator.EqualThanRelation], @@ -36,11 +36,11 @@ describe('solver function', () => { ]; for (const [value, expectedAnswer] of testTable) { - expect(filterOperatorToRelationOperator(value)).toBe(expectedAnswer); + expect(filterOperatorToSparqlRelationOperator(value)).toBe(expectedAnswer); } }); it('should return undefined given a string not representing a RelationOperator', () => { - expect(filterOperatorToRelationOperator("foo")).toBeUndefined(); + expect(filterOperatorToSparqlRelationOperator("foo")).toBeUndefined(); }); }); @@ -408,10 +408,10 @@ describe('solver function', () => { }); }); - describe('resolveEquation', () => { + describe('resolveSolutionDomainWithAnExpression', () => { it('given an empty domain and an equation with 2 operation chained that are not "NOT" should return a valid new domain and the last chained operator', () => { const domain = new SolutionDomain(); - const equation: SolverEquation = { + const equation: SolverExpressionRange = { chainOperator: [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or), @@ -422,7 +422,7 @@ describe('solver function', () => { const expectedDomain = SolutionDomain.newWithInitialValue(equation.solutionDomain); const expectedLastLogicalOperator = equation.chainOperator.at(-2)?.toString(); - const resp = resolveEquation(equation, domain); + const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { const [respDomain, respLastLogicalOperator] = resp; expect(respDomain).toStrictEqual(expectedDomain); @@ -434,7 +434,7 @@ describe('solver function', () => { it('given a domain and an equation with multiple chained that are not "NOT" should return a valid new domain and the next chained operator', () => { const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); - const equation: SolverEquation = { + const equation: SolverExpressionRange = { chainOperator: [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or), @@ -448,7 +448,7 @@ describe('solver function', () => { const expectedDomain = domain.add({ range: equation.solutionDomain, operator: equation.chainOperator.at(-1)?.operator }); const expectedLastLogicalOperator = equation.chainOperator.at(-2)?.toString(); - const resp = resolveEquation(equation, domain); + const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { const [respDomain, respLastLogicalOperator] = resp; expect(respDomain).toStrictEqual(expectedDomain); @@ -460,7 +460,7 @@ describe('solver function', () => { it('given a domain and an equation one chained operation should return a valid new domain and an empty string has the next chained operator', () => { const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); - const equation: SolverEquation = { + const equation: SolverExpressionRange = { chainOperator: [ new LinkOperator(LogicOperator.Or), ], @@ -470,7 +470,7 @@ describe('solver function', () => { const expectedDomain = domain.add({ range: equation.solutionDomain, operator: equation.chainOperator.at(-1)?.operator }); const expectedLastLogicalOperator = ""; - const resp = resolveEquation(equation, domain); + const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { const [respDomain, respLastLogicalOperator] = resp; expect(respDomain).toStrictEqual(expectedDomain); @@ -482,7 +482,7 @@ describe('solver function', () => { it('given a domain and an equation with multiple chainned operator where the later elements are "NOT" operators and the last element an "AND" operator should return a valid domain and the next last operator', () => { const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); - const equation: SolverEquation = { + const equation: SolverExpressionRange = { chainOperator: [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Not), @@ -498,7 +498,7 @@ describe('solver function', () => { const expectedLastLogicalOperator = equation.chainOperator[0].toString(); - const resp = resolveEquation(equation, domain); + const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { const [respDomain, respLastLogicalOperator] = resp; expect(respDomain).toStrictEqual(expectedDomain); @@ -510,7 +510,7 @@ describe('solver function', () => { it('given a domain and an equation with multiple chainned operator where the last elements are "NOT" operators should return a valid domain and an empty string as the next operator', () => { const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); - const equation: SolverEquation = { + const equation: SolverExpressionRange = { chainOperator: [ new LinkOperator(LogicOperator.Not), new LinkOperator(LogicOperator.Not), @@ -525,7 +525,7 @@ describe('solver function', () => { const expectedLastLogicalOperator = ""; - const resp = resolveEquation(equation, domain); + const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { const [respDomain, respLastLogicalOperator] = resp; expect(respDomain).toStrictEqual(expectedDomain); @@ -537,12 +537,12 @@ describe('solver function', () => { it('given an empty domain and an equation with no chained operation should return undefined', () => { const domain = new SolutionDomain(); - const equation: SolverEquation = { + const equation: SolverExpressionRange = { chainOperator: [], solutionDomain: new SolutionRange([0, 1]) }; - expect(resolveEquation(equation, domain)).toBeUndefined(); + expect(resolveSolutionDomainWithAnExpression(equation, domain)).toBeUndefined(); }); }); @@ -571,15 +571,15 @@ describe('solver function', () => { }; const firstOperation = operationTemplate([firstOperator]); - const firstEquation: SolverEquation = { chainOperator: firstOperation.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; + const firstEquation: SolverExpressionRange = { chainOperator: firstOperation.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; const secondOperation = operationTemplate([firstOperator, secondOperator]); - const secondEquation: SolverEquation = { chainOperator: secondOperation.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; + const secondEquation: SolverExpressionRange = { chainOperator: secondOperation.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); - const thirdEquation: SolverEquation = { chainOperator: thirdOperation.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; + const thirdEquation: SolverExpressionRange = { chainOperator: thirdOperation.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - const expectedFirstEquation1: SolverEquation = { chainOperator: lastOperation1.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; + const expectedFirstEquation1: SolverExpressionRange = { chainOperator: lastOperation1.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - const expectedFirstEquation2: SolverEquation = { chainOperator: lastOperation2.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; + const expectedFirstEquation2: SolverExpressionRange = { chainOperator: lastOperation2.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; const equations: SolverExpression[] = [ firstOperation, @@ -743,7 +743,7 @@ describe('solver function', () => { }); }); - describe('resolveEquationSystem', () => { + describe('resolveSolutionDomainEquationSystem', () => { it('should return a valid domain given a valid equation system', () => { const firstOperator = new LinkOperator(LogicOperator.Or); @@ -751,14 +751,14 @@ describe('solver function', () => { const thirdOperator = new LinkOperator(LogicOperator.Not); const forthOperator = new LinkOperator(LogicOperator.Or); const fifthOperator = new LinkOperator(LogicOperator.And); - const firstEquation: SolverEquation = { + const firstEquation: SolverExpressionRange = { solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33]), chainOperator: [ firstOperator, ] }; - const secondEquation: SolverEquation = { + const secondEquation: SolverExpressionRange = { solutionDomain: new SolutionRange([75, 75]), chainOperator: [ firstOperator, @@ -766,7 +766,7 @@ describe('solver function', () => { ] }; - const thirdEquation: SolverEquation = { + const thirdEquation: SolverExpressionRange = { solutionDomain: new SolutionRange([100, Number.POSITIVE_INFINITY]), chainOperator: [ firstOperator, @@ -781,7 +781,7 @@ describe('solver function', () => { [thirdEquation.chainOperator.slice(-1)[0].toString(), thirdEquation] ]); - const firstEquationToSolve: [SolverEquation, SolverEquation] = [ + const firstEquationToSolve: [SolverExpressionRange, SolverExpressionRange] = [ { solutionDomain: new SolutionRange([1000, Number.POSITIVE_INFINITY]), chainOperator: [ @@ -806,7 +806,7 @@ describe('solver function', () => { // Nothing => [100, infinity] =>[-infinity, 100- epsilon] => [75,75]=> [75, 75], [-infinity, 33] const expectedDomain: SolutionRange[] = [new SolutionRange([Number.NEGATIVE_INFINITY, 33]), new SolutionRange([75, 75])]; - const resp = resolveEquationSystem(equationSystem, firstEquationToSolve); + const resp = resolveSolutionDomainEquationSystem(equationSystem, firstEquationToSolve); if (resp) { expect(resp.get_domain()).toStrictEqual(expectedDomain); @@ -822,14 +822,14 @@ describe('solver function', () => { const thirdOperator = new LinkOperator(LogicOperator.Not); const forthOperator = new LinkOperator(LogicOperator.Or); const fifthOperator = new LinkOperator(LogicOperator.And); - const firstEquation: SolverEquation = { + const firstEquation: SolverExpressionRange = { solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33]), chainOperator: [ firstOperator, ] }; - const secondEquation: SolverEquation = { + const secondEquation: SolverExpressionRange = { solutionDomain: new SolutionRange([75, 75]), chainOperator: [ firstOperator, @@ -837,7 +837,7 @@ describe('solver function', () => { ] }; - const thirdEquation: SolverEquation = { + const thirdEquation: SolverExpressionRange = { solutionDomain: new SolutionRange([100, Number.POSITIVE_INFINITY]), chainOperator: [ ] @@ -848,7 +848,7 @@ describe('solver function', () => { [forthOperator.toString(), thirdEquation] ]); - const firstEquationToSolve: [SolverEquation, SolverEquation] = [ + const firstEquationToSolve: [SolverExpressionRange, SolverExpressionRange] = [ { solutionDomain: new SolutionRange([1000, Number.POSITIVE_INFINITY]), chainOperator: [ @@ -871,7 +871,7 @@ describe('solver function', () => { } ]; - expect(()=>{resolveEquationSystem(equationSystem, firstEquationToSolve)}).toThrow(); + expect(()=>{resolveSolutionDomainEquationSystem(equationSystem, firstEquationToSolve)}).toThrow(); }); }); @@ -1349,7 +1349,7 @@ describe('solver function', () => { const solver = require('../lib/solver'); const variable = 'x'; - jest.spyOn(solver, 'resolveEquationSystem').mockReturnValue(undefined); + jest.spyOn(solver, 'resolveSolutionDomainEquationSystem').mockReturnValue(undefined); expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); @@ -1373,7 +1373,7 @@ describe('solver function', () => { const solver = require('../lib/solver'); const variable = 'x'; - jest.spyOn(solver, 'resolveEquationSystem').mockReturnValue(undefined); + jest.spyOn(solver, 'resolveSolutionDomainEquationSystem').mockReturnValue(undefined); expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); }); @@ -1397,7 +1397,7 @@ describe('solver function', () => { const solver = require('../lib/solver'); const variable = 'x'; - jest.spyOn(solver, 'resolveEquationSystem').mockReturnValue(undefined); + jest.spyOn(solver, 'resolveSolutionDomainEquationSystem').mockReturnValue(undefined); expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); }); From 3866cca2f4531c260b7a2cf5d3fadc8a25e9c5b7 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Sat, 17 Dec 2022 20:13:13 +0100 Subject: [PATCH 094/189] unit test for clone method of class SolutionDomain. --- .../lib/SolutionRange.ts | 3 +++ .../test/SolutionDomain-test.ts | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts index ebe22be1c..435068257 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts @@ -1,3 +1,6 @@ +/** + * + */ export class SolutionRange { public readonly upper: number; public readonly lower: number; diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index b3572876b..a3de90e6c 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -274,4 +274,14 @@ describe('SolutionDomain', () => { expect(domain.isDomainEmpty()).toBe(false); }); }); + + describe('clone', ()=>{ + it('should return a deep copy of an existing domain', ()=>{ + let domain = SolutionDomain.newWithInitialValue(new SolutionRange([0,1])); + const clonedDomain = domain.clone(); + domain = domain.addWithOrOperator(new SolutionRange([100,200])); + expect(clonedDomain.get_domain()).not.toStrictEqual(domain.get_domain()); + + }) + }) }); \ No newline at end of file From 1d34c2b228409c6c74f4ffc5c9a8596bd5e1bcae Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 19 Dec 2022 17:11:27 +0100 Subject: [PATCH 095/189] Documentation of SolutionRange and modification of getIntersection which is now a static method. --- .../lib/SolutionDomain.ts | 2 +- .../lib/SolutionRange.ts | 56 +++++++++++++++---- .../test/SolutionRange-test.ts | 15 +---- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 98d671ab3..a3f3176c4 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -75,7 +75,7 @@ export class SolutionDomain { } this.domain.forEach((el) => { - const intersection = el.getIntersection(range); + const intersection = SolutionRange.getIntersection(el, range); if (typeof intersection !== 'undefined') { newDomain.domain.push(intersection); } diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts index 435068257..f3f89fcd3 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts @@ -1,10 +1,22 @@ /** - * + * A class representing the range of a solution it contain method to + * facilitate operation between subdomain. */ export class SolutionRange { + /** + * The upper bound of the range. + */ public readonly upper: number; + /** + * The lower bound of the range. + */ public readonly lower: number; + /** + * Constructor of a solution range, will throw an error if the lower range is greater than the upper range. + * @param {[number, number]} range - An array where the first memeber is the lower bound of the range + * and the second the upper bound + */ constructor(range: [number, number]) { if (range[0] > range[1]) { throw new RangeError('the first element of the range should lower or equal to the second'); @@ -12,7 +24,11 @@ export class SolutionRange { this.lower = range[0]; this.upper = range[1]; } - + /** + * Check if the two ranges overlap. + * @param {SolutionRange} otherRange + * @returns {boolean} Return true if the two range overlap. + */ public isOverlapping(otherRange: SolutionRange): boolean { if (this.upper === otherRange.upper && this.lower === otherRange.lower) { return true; @@ -25,11 +41,20 @@ export class SolutionRange { } return false; } - + /** + * Check whether the other range is inside the subject range. + * @param {SolutionRange} otherRange + * @returns {boolean} Return true if the other range is inside this range. + */ public isInside(otherRange: SolutionRange): boolean { return otherRange.lower >= this.lower && otherRange.upper <= this.upper; } - + /** + * Fuse two ranges if they overlap. + * @param {SolutionRange} subjectRange + * @param {SolutionRange} otherRange + * @returns {SolutionRange[]} Return the fused range if they overlap else return the input ranges. + */ public static fuseRange(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange[] { if (subjectRange.isOverlapping(otherRange)) { const lowest = subjectRange.lower < otherRange.lower ? subjectRange.lower : otherRange.lower; @@ -38,12 +63,14 @@ export class SolutionRange { } return [subjectRange, otherRange]; } - + /** + * Inverse the range, in a way that the range become everything that it excluded. Might + * 0 or return multiple ranges. + * @returns {SolutionRange[]} The resulting ranges. + */ public inverse(): SolutionRange[] { if (this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY) { return []; - } else if (this.lower === this.upper) { - return []; } else if (this.lower === Number.NEGATIVE_INFINITY) { return [new SolutionRange([this.upper + Number.EPSILON, Number.POSITIVE_INFINITY])] } else if (this.upper === Number.POSITIVE_INFINITY) { @@ -54,13 +81,18 @@ export class SolutionRange { new SolutionRange([this.upper + Number.EPSILON, Number.POSITIVE_INFINITY]), ]; } - - public getIntersection(otherRange: SolutionRange): SolutionRange | undefined { - if (!this.isOverlapping(otherRange)) { + /** + * Get the range that intersect the other range and the subject range. + * @param {SolutionRange} subjectRange + * @param {SolutionRange} otherRange + * @returns {SolutionRange | undefined} Return the intersection if the range overlap otherwise return undefined + */ + public static getIntersection(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange | undefined { + if (!subjectRange.isOverlapping(otherRange)) { return undefined; } - const lower = this.lower > otherRange.lower ? this.lower : otherRange.lower; - const upper = this.upper < otherRange.upper ? this.upper : otherRange.upper; + const lower = subjectRange.lower > otherRange.lower ? subjectRange.lower : otherRange.lower; + const upper = subjectRange.upper < otherRange.upper ? subjectRange.upper : otherRange.upper; return new SolutionRange([lower, upper]); } diff --git a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts index 0156dd397..03bab9221 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts @@ -239,15 +239,6 @@ describe('SolutionRange', ()=>{ expect(aSolutionRange.inverse().length).toBe(0); }); - it('given an unique solution should return no range', ()=>{ - const aSolutionRange = new SolutionRange([ - 1, - 1 - ]); - - expect(aSolutionRange.inverse().length).toBe(0); - }); - it('given a range with an infinite upper bound should return a new range', ()=>{ const aSolutionRange = new SolutionRange([ 21, @@ -299,14 +290,14 @@ describe('SolutionRange', ()=>{ const aSolutionRange = new SolutionRange([0, 20]); const aSecondSolutionRange = new SolutionRange([30, 40]); - expect(aSolutionRange.getIntersection(aSecondSolutionRange)).toBeUndefined(); + expect(SolutionRange.getIntersection(aSolutionRange, aSecondSolutionRange)).toBeUndefined(); }); it('given two range when one is inside the other should return the range at the inside', ()=>{ const aSolutionRange = new SolutionRange([0, 20]); const aSecondSolutionRange = new SolutionRange([5, 10]); - expect(aSolutionRange.getIntersection(aSecondSolutionRange)).toStrictEqual(aSecondSolutionRange); + expect(SolutionRange.getIntersection(aSolutionRange, aSecondSolutionRange)).toStrictEqual(aSecondSolutionRange); }); it('given two range when they overlap should return the intersection', ()=>{ @@ -315,7 +306,7 @@ describe('SolutionRange', ()=>{ const expectedIntersection = new SolutionRange([5, 20]); - expect(aSolutionRange.getIntersection(aSecondSolutionRange)).toStrictEqual(expectedIntersection); + expect(SolutionRange.getIntersection(aSolutionRange, aSecondSolutionRange)).toStrictEqual(expectedIntersection); }); }); }); \ No newline at end of file From 87c1c638b0149ddef2f26def0978200c07f05f43 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 19 Dec 2022 17:37:31 +0100 Subject: [PATCH 096/189] Documentation for SolutionDomain done. --- .../lib/SolutionDomain.ts | 87 ++++++++++++++++--- 1 file changed, 74 insertions(+), 13 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index a3f3176c4..e3178ac6c 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -1,34 +1,59 @@ import { SolutionRange } from './SolutionRange'; import { LogicOperator } from './solverInterfaces'; - +/** + * A class representing the domain of a solution of system of boolean equation. + * Every operation return a new object. + */ export class SolutionDomain { + /** + * The multiple segment of the domain, it is always order by the lower bound + * of the SolutionRange. + */ private domain: SolutionRange[] = []; constructor() { } - + /** + * Get the multiple segment of the domain. + * @returns {SolutionRange[]} + */ public get_domain(): SolutionRange[] { return new Array(...this.domain); } - + /** + * Check whether the domain is empty + * @returns {boolean} Return true if the domain is empty + */ public isDomainEmpty(): boolean { return this.domain.length === 0; } - + /** + * Create a new SolutionDomain with an inititial value. + * @param {SolutionRange} initialRange + * @returns {SolutionDomain} + */ public static newWithInitialValue(initialRange: SolutionRange): SolutionDomain { const newSolutionDomain = new SolutionDomain(); newSolutionDomain.domain = [initialRange]; return newSolutionDomain } - + /** + * Clone the current solution domain. + * @returns {SolutionDomain} + */ public clone(): SolutionDomain { const newSolutionDomain = new SolutionDomain(); - newSolutionDomain.domain = this.domain; + newSolutionDomain.domain = new Array(...this.domain); return newSolutionDomain; } - + /** + * Modifify the current solution range by applying a logical operation. + * @param {SolutionRange} range - The range of the incoming operation to apply. + * @param {LogicOperator} operator - The logical operation to apply. + * @returns {SolutionDomain} - A new SolutionDomain with the operation applied. + */ public add({ range, operator }: { range?: SolutionRange, operator: LogicOperator }): SolutionDomain { switch (operator) { case LogicOperator.And: { @@ -49,51 +74,87 @@ export class SolutionDomain { } } } - + /** + * Apply an "OR" operator to the current solution domain with the input + * solution range. It fuse the SolutionRange if needed to keep the simplest domain possible. + * It also keep the domain order. + * @param {SolutionRange} range + * @returns {SolutionDomain} + */ public addWithOrOperator(range: SolutionRange): SolutionDomain { const newDomain = this.clone(); let currentRange = range; + // we iterate over all the domain newDomain.domain = newDomain.domain.filter((el) => { + // we check if we can fuse the new range with the current range + // let's not forget that the domain is sorted by the lowest bound + // of the SolutionRange const resp = SolutionRange.fuseRange(el, currentRange); if (resp.length === 1) { + // if we can we consider the current the fused range the current range + // and we delete the range from the domain has the fused range will be added + // at the end currentRange = resp[0]; return false; } return true; }); + // we had the potentialy fused range. newDomain.domain.push(currentRange); + // we keep the domain sorted newDomain.domain.sort(SolutionDomain.sortDomainRangeByLowerBound); return newDomain; } - + /** + * Apply an "AND" operator to the current solution domain with the input + * solution range. It will keep only the insection of the subdomain with the input + * range. It keep the domain ordered. + * @param {SolutionRange} range + * @returns {SolutionDomain} + */ public addWithAndOperator(range: SolutionRange): SolutionDomain { const newDomain = new SolutionDomain(); + // if the domain is empty then simply add the new range if (this.domain.length === 0) { newDomain.domain.push(range); return newDomain; } - + // considering the current domain if there is an intersection + // add the intersection to the new domain this.domain.forEach((el) => { const intersection = SolutionRange.getIntersection(el, range); - if (typeof intersection !== 'undefined') { + if (intersection) { newDomain.domain.push(intersection); } }); + // keep the domain sorted newDomain.domain.sort(SolutionDomain.sortDomainRangeByLowerBound); return newDomain; } - + /** + * Apply a "NOT" operator to the current solution domain. + * Will inverse every subdomain and keep the domain simplest domain. + * The order is preserved. + * @returns {SolutionDomain} + */ public notOperation(): SolutionDomain { let newDomain = new SolutionDomain(); for (const domainElement of this.domain) { + // inverse the domain and had it with care for the overlap + // wich is similar to apply an or operator domainElement.inverse().forEach((el) => { newDomain = newDomain.addWithOrOperator(el); }) } return newDomain; } - + /** + * Simple sort function to order the domain by the lower bound of SolutionRange. + * @param {SolutionRange} firstRange + * @param {SolutionRange} secondRange + * @returns {number} see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort + */ private static sortDomainRangeByLowerBound(firstRange: SolutionRange, secondRange: SolutionRange): number { if (firstRange.lower < secondRange.lower) { return -1; From 514aad487f465e875dfca8d8145779e4f3719db1 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 19 Dec 2022 17:40:43 +0100 Subject: [PATCH 097/189] Documentation of LinkOperator. --- .../lib/LinkOperator.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts b/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts index 0d39cc348..d9451d8e6 100644 --- a/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts @@ -1,20 +1,40 @@ import { LogicOperator } from './solverInterfaces'; +/** + * A class representing a LogicOperation with an unique ID. + */ export class LinkOperator { + /** + * The logic operator + */ public readonly operator: LogicOperator; + /** + * The unique ID + */ public readonly id: number; + /** + * The next unique ID to provide to a new instance of LinkOperator + */ private static count: number = 0; + /** + * Build a new LinkOperator with an unique ID. + * @param operator + */ constructor(operator: LogicOperator) { this.operator = operator; this.id = LinkOperator.count; LinkOperator.count++; } - + /** + * @returns {string} string representation of this LogicOperator. + */ public toString(): string { return `${this.operator}-${this.id}` } - + /** + * Reset the count of the unique ID. + */ public static resetIdCount(){ LinkOperator.count = 0; } From 60e0d6667aa9f095e41c005740d911936a013a06 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 16 Jan 2023 12:53:11 +0100 Subject: [PATCH 098/189] Made previous test work with the solver. --- .../lib/FilterNode.ts | 44 ++-- .../lib/solver.ts | 35 +++- .../test/FilterNode-test.ts | 129 +++++------- .../test/solver-test.ts | 195 +++++++++++++++++- 4 files changed, 289 insertions(+), 114 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 8da9eddc7..16e278910 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -4,12 +4,12 @@ import type { Bindings, IActionContext } from '@comunica/types'; import type { ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra, Factory as AlgebraFactory } from 'sparqlalgebrajs'; -import { AsyncEvaluator } from 'sparqlee'; +import { Variable } from './solverInterfaces'; +import { isRelationFilterExpressionDomainEmpty } from './solver'; const AF = new AlgebraFactory(); const BF = new BindingsFactory(); -const Utf8Encode = new TextEncoder(); /** * A class to apply [SPAQL filters](https://www.w3.org/TR/sparql11-query/#evaluation) @@ -53,6 +53,7 @@ export class FilterNode { if (queryBody.length === 0) { return new Map(); } + // Capture the relation from the function argument. const relations: ITreeRelation[] = node.relation!; @@ -63,43 +64,36 @@ export class FilterNode { continue; } // Find the quad from the bgp that are related to the TREE relation. - const relevantQuads = FilterNode.findRelevantQuad(queryBody, relation.path); + const variables = FilterNode.findRelevantVariableFromBgp(queryBody, relation.path); - // Accept the relation if no quad are linked with the relation. - if (relevantQuads.length === 0) { + // Accept the relation if no variable are linked with the relation. + if (variables.length === 0) { filterMap.set(relation.node, true); continue; } - - // Create the binding from the relevant quad in association with the TREE relation. - const bindings = FilterNode.createBinding(relevantQuads, relation.value.term); - const filterExpression: Algebra.Operation = FilterNode.generateTreeRelationFilter(filterOperation, bindings); - - // Accept the relation if no filter are associated with the relation. - if (filterExpression.args.length === 0) { - filterMap.set(relation.node, true); - continue; + let filtered = false; + // For all the variable check if one is has a possible solutions. + for (const variable of variables) { + filtered = filtered || isRelationFilterExpressionDomainEmpty({ relation, filterExpression: filterOperation, variable }) } - const evaluator = new AsyncEvaluator(filterExpression); - // Evaluate the filter with the relevant quad binding. - const result: boolean = await evaluator.evaluateAsEBV(bindings); - filterMap.set(relation.node, result); + + filterMap.set(relation.node, filtered); } return filterMap; } /** - * Find the quad that match the predicate defined by the TREE:path from a TREE relation. + * Find the variables from the BGP that match the predicate defined by the TREE:path from a TREE relation. * The subject can be anyting. * @param {RDF.Quad[]} queryBody - the body of the query * @param {string} path - TREE path - * @returns {RDF.Quad[]} the quad that contain the TREE path as predicate and a variable as object + * @returns {Variable[]} the variables of the Quad objects that contain the TREE path as predicate */ - private static findRelevantQuad(queryBody: RDF.Quad[], path: string): RDF.Quad[] { - const resp: RDF.Quad[] = []; + private static findRelevantVariableFromBgp(queryBody: RDF.Quad[], path: string): Variable[] { + const resp: Variable[] = []; for (const quad of queryBody) { if (quad.predicate.value === path && quad.object.termType === 'Variable') { - resp.push(quad); + resp.push(quad.object.value); } } return resp; @@ -184,6 +178,10 @@ export class FilterNode { if ('input' in currentNode) { currentNode = currentNode.input; } + + if (currentNode.patterns) { + return currentNode.patterns; + } // If the node is an array if (Array.isArray(currentNode)) { for (const node of currentNode) { diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index b219cd2fe..33389c974 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -52,20 +52,30 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi return true } - const [equationSystem, firstEquationToResolved] = equationSystemFirstEquation; + let solutionDomain: SolutionDomain; - // we check if the filter expression itself has a solution - let solutionDomain = resolveSolutionDomainEquationSystem(equationSystem, firstEquationToResolved); + if (Array.isArray(equationSystemFirstEquation)) { + const [equationSystem, firstEquationToResolved] = equationSystemFirstEquation; - // don't pass the relation if the filter cannot be resolved - if (solutionDomain.isDomainEmpty()) { - return false; + // we check if the filter expression itself has a solution + solutionDomain = resolveSolutionDomainEquationSystem(equationSystem, firstEquationToResolved); + + // don't pass the relation if the filter cannot be resolved + if (solutionDomain.isDomainEmpty()) { + return false; + } + + + }else { + solutionDomain = SolutionDomain.newWithInitialValue(equationSystemFirstEquation.solutionDomain); } + // evaluate the solution domain when adding the relation solutionDomain = solutionDomain.add({ range: relationSolutionRange, operator: LogicOperator.And }); // if there is a possible solution we don't filter the link return !solutionDomain.isDomainEmpty(); + } /** * A recursif function that traverse the Algebra expression to capture each boolean expression and there associated @@ -182,7 +192,18 @@ export function resolveSolutionDomainEquationSystem(equationSystem: SolverEquati * @returns {[SolverEquationSystem, [SolverExpressionRange, SolverExpressionRange]] | undefined} if the expression form a possible system of equation return * the system of equation and the first expression to evaluate. */ -export function createEquationSystem(expressions: SolverExpression[]): [SolverEquationSystem, [SolverExpressionRange, SolverExpressionRange]] | undefined { +export function createEquationSystem(expressions: SolverExpression[]): [SolverEquationSystem, [SolverExpressionRange, SolverExpressionRange]] | SolverExpressionRange | undefined { + if (expressions.length === 1) { + const solutionRange = getSolutionRange(expressions[0].valueAsNumber, expressions[0].operator); + if (!solutionRange) { + return undefined; + } + return { + chainOperator: [], + solutionDomain: solutionRange + }; + } + const system: SolverEquationSystem = new Map(); // the first expression that has to be evaluated let firstEquationToEvaluate: [SolverExpressionRange, SolverExpressionRange] | undefined = undefined; diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index 71e759871..6045297ca 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -1,10 +1,13 @@ import { KeysInitQuery } from '@comunica/context-entries'; import { ActionContext } from '@comunica/core'; import type { ITreeNode } from '@comunica/types-link-traversal'; +import { SparqlRelationOperator } from '@comunica/types-link-traversal'; + import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; -import { Algebra } from 'sparqlalgebrajs'; import { FilterNode } from '../lib/FilterNode'; +import { Algebra, translate } from 'sparqlalgebrajs'; + const DF = new DataFactory(); @@ -34,7 +37,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(response).toBe(true); }); - it('should no test when the TREE relation are undefined', async() => { + it('should no test when the TREE relation are undefined', async () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -46,7 +49,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(response).toBe(false); }); - it('should not test when there is a filter operation in the query but no TREE relations', async() => { + it('should not test when there is a filter operation in the query but no TREE relations', async () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -58,7 +61,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(response).toBe(false); }); - it('should no test when there are no filter operation in the query but a TREE relation', async() => { + it('should no test when there are no filter operation in the query but a TREE relation', async () => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); @@ -76,11 +79,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); describe('run method', () => { - const aQuad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), - DF.namedNode('ex:p'), - DF.namedNode('ex:o')); - - it('should accept the relation when the filter respect the relation', async() => { + it('should accept the relation when the filter respect the relation', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -93,58 +92,18 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, + type: SparqlRelationOperator.EqualThanRelation }, ], }; - const bgp = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), - DF.quad(DF.namedNode('ex:bar'), DF.namedNode('ex:p2'), DF.namedNode('ex:o2')), - DF.quad(DF.namedNode('ex:too'), DF.namedNode('ex:p3'), DF.namedNode('ex:o3')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }; - - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - }; + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + ex:foo ex:p ex:o. + FILTER(?o=5) + } + `, { prefixes: { 'ex': 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, @@ -153,11 +112,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should not accept the relation when the filter is not respected by the relation', async() => { + it('should not accept the relation when the filter is not respected by the relation', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -170,6 +129,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, + type: SparqlRelationOperator.EqualThanRelation }, ], }; @@ -224,11 +184,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', false ]]), + new Map([['http://bar.com', false]]), ); }); - it('should accept the relation when the query don\'t invoke the right path', async() => { + it('should accept the relation when the query don\'t invoke the right path', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -241,6 +201,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, + type: SparqlRelationOperator.EqualThanRelation }, ], }; @@ -295,11 +256,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should return an empty map when there is no relation', async() => { + it('should return an empty map when there is no relation', async () => { const bgp: RDF.Quad[] = [ DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), ]; @@ -356,7 +317,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); it('should accept the relation when there is multiple filters and the query path don\'t match the relation', - async() => { + async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -369,6 +330,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, + type: SparqlRelationOperator.EqualThanRelation }, ], }; @@ -459,12 +421,12 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); it('should accept the relations when one respect the filter and another has no path and value defined', - async() => { + async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -477,6 +439,8 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, + type: SparqlRelationOperator.EqualThanRelation + }, { @@ -535,11 +499,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ], [ 'http://foo.com', true ]]), + new Map([['http://bar.com', true], ['http://foo.com', true]]), ); }); - it('should accept the relation when the filter argument are not related to the query', async() => { + it('should accept the relation when the filter argument are not related to the query', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -552,6 +516,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, + type: SparqlRelationOperator.EqualThanRelation }, ], }; @@ -606,11 +571,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should accept the relation when there is multiples filters and one is not relevant', async() => { + it('should accept the relation when there is multiples filters and one is not relevant', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -623,6 +588,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, + type: SparqlRelationOperator.EqualThanRelation }, ], }; @@ -710,11 +676,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should accept the relation when the filter compare two constants', async() => { + it('should accept the relation when the filter compare two constants', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -727,6 +693,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, + type: SparqlRelationOperator.EqualThanRelation }, ], }; @@ -824,11 +791,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should return an empty filter map if the bgp if empty', async() => { + it('should return an empty filter map if the bgp if empty', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -841,6 +808,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, + type: SparqlRelationOperator.EqualThanRelation }, ], }; @@ -897,7 +865,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it('should return an empty filter map if there is no bgp', async() => { + it('should return an empty filter map if there is no bgp', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -910,6 +878,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, + type: SparqlRelationOperator.EqualThanRelation }, ], }; @@ -960,7 +929,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it('should accept the relation when the filter respect the relation with a construct query', async() => { + it('should accept the relation when the filter respect the relation with a construct query', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -973,6 +942,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, + type: SparqlRelationOperator.EqualThanRelation }, ], }; @@ -1034,11 +1004,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should accept the relation when the filter respect the relation with a nested query', async() => { + it('should accept the relation when the filter respect the relation with a nested query', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -1051,6 +1021,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, + type: SparqlRelationOperator.EqualThanRelation }, ], }; @@ -1219,7 +1190,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); }); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index ac5ed2a66..55f61bc34 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -597,7 +597,10 @@ describe('solver function', () => { ]); const resp = createEquationSystem(equations); - if (resp) { + if(!Array.isArray(resp)){ + fail('should return an array'); + } + else if (resp) { const [respEquationSystem, [respFirstEquation1, respFirstEquation2]] = resp; expect(respEquationSystem).toStrictEqual(expectedEquationSystem); expect(respFirstEquation1).toStrictEqual(expectedFirstEquation1); @@ -741,6 +744,82 @@ describe('solver function', () => { expect(createEquationSystem(equations)).toBeUndefined(); }); + + it('given an equation should return a valid system of equation', ()=>{ + const equation: SolverExpression[] = [ + { + chainOperator:[], + operator: SparqlRelationOperator.EqualThanRelation, + rawValue:'88', + valueAsNumber:88, + valueType: SparqlOperandDataTypes.Int, + variable:'x' + } + ]; + + const expectedEquationSystem: SolverExpressionRange = { + chainOperator: [], + solutionDomain: new SolutionRange([88,88]) + }; + + const resp = createEquationSystem(equation); + if (Array.isArray(resp)){ + fail('should not return an array'); + } + else if (resp && !Array.isArray(resp)) { + expect(resp).toStrictEqual(expectedEquationSystem); + } else { + expect(resp).toBeDefined(); + } + + }); + + it('given two equations should return a valid system of equation', ()=>{ + const lastOperator = new LinkOperator(LogicOperator.And); + + const equation: SolverExpression[] = [ + { + chainOperator:[lastOperator], + operator: SparqlRelationOperator.EqualThanRelation, + rawValue:'88', + valueAsNumber:88, + valueType: SparqlOperandDataTypes.Int, + variable:'x' + }, + { + chainOperator:[lastOperator], + operator: SparqlRelationOperator.EqualThanRelation, + rawValue:'33', + valueAsNumber:33, + valueType: SparqlOperandDataTypes.Int, + variable:'x' + } + ]; + + const expectedFirstEquation1: SolverExpressionRange = { + chainOperator: [lastOperator], + solutionDomain: new SolutionRange([88,88]) + }; + const expectedFirstEquation2: SolverExpressionRange = { + chainOperator: [lastOperator], + solutionDomain: new SolutionRange([33,33]) + }; + + const resp = createEquationSystem(equation); + if (!Array.isArray(resp)){ + fail('should return an array'); + } + else if (resp && Array.isArray(resp)) { + const [respEquationSystem, [respFirstEquation1, respFirstEquation2]] = resp; + expect(respEquationSystem.size).toBe(0); + expect(respFirstEquation1).toStrictEqual(expectedFirstEquation1); + expect(respFirstEquation2).toStrictEqual(expectedFirstEquation2); + + } else { + expect(resp).toBeDefined(); + } + + }); }); describe('resolveSolutionDomainEquationSystem', () => { @@ -1347,9 +1426,7 @@ describe('solver function', () => { FILTER( !(?x=2 && ?x>5) || ?x < 88.3) }`).input.expression; - const solver = require('../lib/solver'); const variable = 'x'; - jest.spyOn(solver, 'resolveSolutionDomainEquationSystem').mockReturnValue(undefined); expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); @@ -1395,12 +1472,120 @@ describe('solver function', () => { FILTER( !(?x=2 && ?x>5) || ?x < 88.3) }`).input.expression; - const solver = require('../lib/solver'); const variable = 'x'; - jest.spyOn(solver, 'resolveSolutionDomainEquationSystem').mockReturnValue(undefined); expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); }); + it('should return true when there is a solution for the filter expression with one expression and the relation', ()=>{ + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + }, + node: "https://www.example.be" + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x>=5) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it('should return false when there is no solution for the filter expression with one expression and the relation', ()=>{ + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + }, + node: "https://www.example.be" + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x>=6) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + + it('should return false when there is no solution for the filter expression with one expression and the relation', ()=>{ + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + }, + node: "https://www.example.be" + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x>=6) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + + it('should return false when there is no solution for the filter expression with two expressions and the relation', ()=>{ + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + }, + node: "https://www.example.be" + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x>=6 && ?x>= 7) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + + it('should return true when there is a solution for the filter expression with one expression and the relation', ()=>{ + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: "ex:path", + value: { + value: "5", + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + }, + node: "https://www.example.be" + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x>=5 && ?x>=-1) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + }); }); \ No newline at end of file From a05784bfc1dbf95f2b1efa497703fa3f51912dfa Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 16 Jan 2023 12:54:16 +0100 Subject: [PATCH 099/189] small formating --- packages/actor-extract-links-extract-tree/lib/solver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 33389c974..b430d70f7 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -75,8 +75,8 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi // if there is a possible solution we don't filter the link return !solutionDomain.isDomainEmpty(); - } + /** * A recursif function that traverse the Algebra expression to capture each boolean expression and there associated * chain of logical expression. On the first call the filterExpressionList and linksOperator must be empty, they serve From fad999dd72076da1d14280dd2824ae33b0b25090 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 16 Jan 2023 13:54:11 +0100 Subject: [PATCH 100/189] Fix the test of the actor link tree traversal actor related to the filter node class. --- .../test/ActorExtractLinksTree-test.ts | 71 +++++-------------- .../test/FilterNode-test.ts | 38 ++++++++++ 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 896518c7d..96cf8c3ed 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -1,12 +1,14 @@ import { KeysRdfResolveQuadPattern, KeysInitQuery } from '@comunica/context-entries'; import { ActionContext, Bus } from '@comunica/core'; import type { ITreeRelation } from '@comunica/types-link-traversal'; -import { SparqlRelationOperator } from '@comunica/types-link-traversal'; +import { SparqlRelationOperator, TreeNodes } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; -import { Algebra } from 'sparqlalgebrajs'; +import { Algebra, translate } from 'sparqlalgebrajs'; import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; + + const stream = require('streamify-array'); const DF = new DataFactory(); @@ -494,63 +496,28 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.namedNode('ex:gx')), DF.quad(DF.blankNode('_:_g1'), DF.namedNode('https://w3id.org/tree#path'), - DF.literal('ex:path'), + DF.literal('http://example.com#path'), DF.namedNode('ex:gx')), DF.quad(DF.blankNode('_:_g1'), DF.namedNode('https://w3id.org/tree#value'), DF.literal('500', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), DF.namedNode('ex:gx')), - + DF.quad(DF.blankNode('_:_g1'), + DF.blankNode(TreeNodes.RDFTypeNode), + DF.namedNode(SparqlRelationOperator.LessThanOrEqualToRelation)) ]); - const bgp = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), - DF.quad(DF.namedNode('ex:bar'), DF.namedNode('ex:p2'), DF.namedNode('ex:o2')), - DF.quad(DF.namedNode('ex:too'), DF.namedNode('ex:p3'), DF.namedNode('ex:o3')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }; - - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - }; + + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + ex:foo ex:p ex:o. + ex:foo ex:p2 ex:o2. + ex:foo ex:p3 ex:o3. + FILTER(?o=550) + } + `, { prefixes: { 'ex': 'http://example.com#' } }); + const contextWithQuery = new ActionContext({ [KeysRdfResolveQuadPattern.source.name]: treeUrl, [KeysInitQuery.query.name]: query, diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index 6045297ca..fd2229f9d 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -1193,6 +1193,44 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { new Map([['http://bar.com', true]]), ); }); + + it('should accept the relation when a complex filter respect the relation', async () => { + const treeSubject = 'tree'; + + const node: ITreeNode = { + identifier: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + type: SparqlRelationOperator.GreaterThanOrEqualToRelation + }, + ], + }; + + const query = translate(` + SELECT ?o ?x WHERE { + ex:foo ex:path ?o. + ex:foo ex:p ex:o. + ex:foo ex:p2 ?x. + FILTER(?o>2|| ?x=4 || (?x<3 && ?o<3) ) + } + `, { prefixes: { 'ex': 'http://example.com#' } }); + + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const result = await filterNode.run(node, context); + + expect(result).toStrictEqual( + new Map([['http://bar.com', true]]), + ); + }); }); }); }); From 6bf424ebfbdd2713e2bf9e10333fc52737c5f70c Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 16 Jan 2023 16:07:08 +0100 Subject: [PATCH 101/189] lint-fix --- .../lib/FilterNode.ts | 9 +- .../lib/LinkOperator.ts | 48 +- .../lib/SolutionDomain.ts | 227 +- .../lib/SolutionRange.ts | 143 +- .../lib/solver.ts | 236 +- .../lib/solverInterfaces.ts | 100 +- .../test/ActorExtractLinksTree-test.ts | 9 +- .../test/FilterNode-test.ts | 89 +- .../test/LinkOperator-test.ts | 68 +- .../test/SolutionDomain-test.ts | 441 ++- .../test/SolutionRange-test.ts | 452 +-- .../test/solver-test.ts | 3043 +++++++++-------- 12 files changed, 2490 insertions(+), 2375 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 16e278910..d956c41c1 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -4,9 +4,8 @@ import type { Bindings, IActionContext } from '@comunica/types'; import type { ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra, Factory as AlgebraFactory } from 'sparqlalgebrajs'; -import { Variable } from './solverInterfaces'; import { isRelationFilterExpressionDomainEmpty } from './solver'; - +import type { Variable } from './solverInterfaces'; const AF = new AlgebraFactory(); const BF = new BindingsFactory(); @@ -74,7 +73,9 @@ export class FilterNode { let filtered = false; // For all the variable check if one is has a possible solutions. for (const variable of variables) { - filtered = filtered || isRelationFilterExpressionDomainEmpty({ relation, filterExpression: filterOperation, variable }) + filtered = filtered || isRelationFilterExpressionDomainEmpty( + { relation, filterExpression: filterOperation, variable }, + ); } filterMap.set(relation.node, filtered); @@ -178,7 +179,7 @@ export class FilterNode { if ('input' in currentNode) { currentNode = currentNode.input; } - + if (currentNode.patterns) { return currentNode.patterns; } diff --git a/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts b/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts index d9451d8e6..d62ff3fdb 100644 --- a/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts @@ -1,41 +1,43 @@ -import { LogicOperator } from './solverInterfaces'; +import type { LogicOperator } from './solverInterfaces'; /** * A class representing a LogicOperation with an unique ID. */ export class LinkOperator { - /** + /** * The logic operator */ - public readonly operator: LogicOperator; - /** + public readonly operator: LogicOperator; + /** * The unique ID */ - public readonly id: number; - /** + public readonly id: number; + /** * The next unique ID to provide to a new instance of LinkOperator */ - private static count: number = 0; + private static count = 0; - /** + /** * Build a new LinkOperator with an unique ID. - * @param operator + * @param operator */ - constructor(operator: LogicOperator) { - this.operator = operator; - this.id = LinkOperator.count; - LinkOperator.count++; - } - /** + public constructor(operator: LogicOperator) { + this.operator = operator; + this.id = LinkOperator.count; + LinkOperator.count++; + } + + /** * @returns {string} string representation of this LogicOperator. */ - public toString(): string { - return `${this.operator}-${this.id}` - } - /** + public toString(): string { + return `${this.operator}-${this.id}`; + } + + /** * Reset the count of the unique ID. */ - public static resetIdCount(){ - LinkOperator.count = 0; - } -} \ No newline at end of file + public static resetIdCount(): void { + LinkOperator.count = 0; + } +} diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index e3178ac6c..1cf985705 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -6,162 +6,169 @@ import { LogicOperator } from './solverInterfaces'; * Every operation return a new object. */ export class SolutionDomain { - /** + /** * The multiple segment of the domain, it is always order by the lower bound * of the SolutionRange. */ - private domain: SolutionRange[] = []; + private domain: SolutionRange[] = []; - constructor() { - } - /** + /** * Get the multiple segment of the domain. * @returns {SolutionRange[]} */ - public get_domain(): SolutionRange[] { - return new Array(...this.domain); - } - /** + public get_domain(): SolutionRange[] { + return new Array(...this.domain); + } + + /** * Check whether the domain is empty * @returns {boolean} Return true if the domain is empty */ - public isDomainEmpty(): boolean { - return this.domain.length === 0; - } - /** + public isDomainEmpty(): boolean { + return this.domain.length === 0; + } + + /** * Create a new SolutionDomain with an inititial value. * @param {SolutionRange} initialRange * @returns {SolutionDomain} */ - public static newWithInitialValue(initialRange: SolutionRange): SolutionDomain { - const newSolutionDomain = new SolutionDomain(); - newSolutionDomain.domain = [initialRange]; - return newSolutionDomain - } - /** + public static newWithInitialValue(initialRange: SolutionRange): SolutionDomain { + const newSolutionDomain = new SolutionDomain(); + newSolutionDomain.domain = [ initialRange ]; + return newSolutionDomain; + } + + /** * Clone the current solution domain. * @returns {SolutionDomain} */ - public clone(): SolutionDomain { - const newSolutionDomain = new SolutionDomain(); - newSolutionDomain.domain = new Array(...this.domain); - return newSolutionDomain; - } + public clone(): SolutionDomain { + const newSolutionDomain = new SolutionDomain(); + newSolutionDomain.domain = new Array(...this.domain); + return newSolutionDomain; + } - /** + /** * Modifify the current solution range by applying a logical operation. * @param {SolutionRange} range - The range of the incoming operation to apply. * @param {LogicOperator} operator - The logical operation to apply. * @returns {SolutionDomain} - A new SolutionDomain with the operation applied. */ - public add({ range, operator }: { range?: SolutionRange, operator: LogicOperator }): SolutionDomain { - switch (operator) { - case LogicOperator.And: { - if (typeof range === 'undefined') { - throw ReferenceError('range should be defined with "AND" operator'); - } - return this.addWithAndOperator(range); - } - case LogicOperator.Or: { - if (typeof range === 'undefined') { - throw ReferenceError('range should be defined with "OR" operator'); - } - return this.addWithOrOperator(range); - } - - case LogicOperator.Not: { - return this.notOperation(); - } + public add({ range, operator }: { range?: SolutionRange; operator: LogicOperator }): SolutionDomain { + switch (operator) { + case LogicOperator.And: { + if (typeof range === 'undefined') { + throw new ReferenceError('range should be defined with "AND" operator'); } + return this.addWithAndOperator(range); + } + case LogicOperator.Or: { + if (typeof range === 'undefined') { + throw new ReferenceError('range should be defined with "OR" operator'); + } + return this.addWithOrOperator(range); + } + + case LogicOperator.Not: { + return this.notOperation(); + } } - /** + } + + /** * Apply an "OR" operator to the current solution domain with the input * solution range. It fuse the SolutionRange if needed to keep the simplest domain possible. * It also keep the domain order. - * @param {SolutionRange} range + * @param {SolutionRange} range * @returns {SolutionDomain} */ - public addWithOrOperator(range: SolutionRange): SolutionDomain { - const newDomain = this.clone(); - let currentRange = range; - // we iterate over all the domain - newDomain.domain = newDomain.domain.filter((el) => { - // we check if we can fuse the new range with the current range - // let's not forget that the domain is sorted by the lowest bound - // of the SolutionRange - const resp = SolutionRange.fuseRange(el, currentRange); - if (resp.length === 1) { - // if we can we consider the current the fused range the current range - // and we delete the range from the domain has the fused range will be added - // at the end - currentRange = resp[0]; - return false; - } - return true; - }); - // we had the potentialy fused range. - newDomain.domain.push(currentRange); - // we keep the domain sorted - newDomain.domain.sort(SolutionDomain.sortDomainRangeByLowerBound); - return newDomain; - } - /** + public addWithOrOperator(range: SolutionRange): SolutionDomain { + const newDomain = this.clone(); + let currentRange = range; + // We iterate over all the domain + newDomain.domain = newDomain.domain.filter(el => { + // We check if we can fuse the new range with the current range + // let's not forget that the domain is sorted by the lowest bound + // of the SolutionRange + const resp = SolutionRange.fuseRange(el, currentRange); + if (resp.length === 1) { + // If we can we consider the current the fused range the current range + // and we delete the range from the domain has the fused range will be added + // at the end + currentRange = resp[0]; + return false; + } + return true; + }); + // We had the potentialy fused range. + newDomain.domain.push(currentRange); + // We keep the domain sorted + newDomain.domain.sort(SolutionDomain.sortDomainRangeByLowerBound); + return newDomain; + } + + /** * Apply an "AND" operator to the current solution domain with the input * solution range. It will keep only the insection of the subdomain with the input * range. It keep the domain ordered. - * @param {SolutionRange} range + * @param {SolutionRange} range * @returns {SolutionDomain} */ - public addWithAndOperator(range: SolutionRange): SolutionDomain { - const newDomain = new SolutionDomain(); + public addWithAndOperator(range: SolutionRange): SolutionDomain { + const newDomain = new SolutionDomain(); - // if the domain is empty then simply add the new range - if (this.domain.length === 0) { - newDomain.domain.push(range); - return newDomain; - } - // considering the current domain if there is an intersection - // add the intersection to the new domain - this.domain.forEach((el) => { - const intersection = SolutionRange.getIntersection(el, range); - if (intersection) { - newDomain.domain.push(intersection); - } - }); - // keep the domain sorted - newDomain.domain.sort(SolutionDomain.sortDomainRangeByLowerBound); - return newDomain; + // If the domain is empty then simply add the new range + if (this.domain.length === 0) { + newDomain.domain.push(range); + return newDomain; } - /** + // Considering the current domain if there is an intersection + // add the intersection to the new domain + this.domain.forEach(el => { + const intersection = SolutionRange.getIntersection(el, range); + if (intersection) { + newDomain.domain.push(intersection); + } + }); + // Keep the domain sorted + newDomain.domain.sort(SolutionDomain.sortDomainRangeByLowerBound); + return newDomain; + } + + /** * Apply a "NOT" operator to the current solution domain. * Will inverse every subdomain and keep the domain simplest domain. * The order is preserved. * @returns {SolutionDomain} */ - public notOperation(): SolutionDomain { - let newDomain = new SolutionDomain(); - for (const domainElement of this.domain) { - // inverse the domain and had it with care for the overlap - // wich is similar to apply an or operator - domainElement.inverse().forEach((el) => { - newDomain = newDomain.addWithOrOperator(el); - }) - } - return newDomain; + public notOperation(): SolutionDomain { + let newDomain = new SolutionDomain(); + for (const domainElement of this.domain) { + // Inverse the domain and had it with care for the overlap + // wich is similar to apply an or operator + for (const el of domainElement.inverse()) { + newDomain = newDomain.addWithOrOperator(el); + } } - /** + return newDomain; + } + + /** * Simple sort function to order the domain by the lower bound of SolutionRange. - * @param {SolutionRange} firstRange - * @param {SolutionRange} secondRange + * @param {SolutionRange} firstRange + * @param {SolutionRange} secondRange * @returns {number} see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort */ - private static sortDomainRangeByLowerBound(firstRange: SolutionRange, secondRange: SolutionRange): number { - if (firstRange.lower < secondRange.lower) { - return -1; - } else if (firstRange.lower > secondRange.lower) { - return 1; - } - return 0; + private static sortDomainRangeByLowerBound(firstRange: SolutionRange, secondRange: SolutionRange): number { + if (firstRange.lower < secondRange.lower) { + return -1; + } + + if (firstRange.lower > secondRange.lower) { + return 1; } + return 0; + } } diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts index f3f89fcd3..61483e907 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts @@ -3,97 +3,112 @@ * facilitate operation between subdomain. */ export class SolutionRange { - /** + /** * The upper bound of the range. */ - public readonly upper: number; - /** + public readonly upper: number; + /** * The lower bound of the range. */ - public readonly lower: number; + public readonly lower: number; - /** + /** * Constructor of a solution range, will throw an error if the lower range is greater than the upper range. * @param {[number, number]} range - An array where the first memeber is the lower bound of the range * and the second the upper bound */ - constructor(range: [number, number]) { - if (range[0] > range[1]) { - throw new RangeError('the first element of the range should lower or equal to the second'); - } - this.lower = range[0]; - this.upper = range[1]; + public constructor(range: [number, number]) { + if (range[0] > range[1]) { + throw new RangeError('the first element of the range should lower or equal to the second'); } - /** + this.lower = range[0]; + this.upper = range[1]; + } + + /** * Check if the two ranges overlap. - * @param {SolutionRange} otherRange + * @param {SolutionRange} otherRange * @returns {boolean} Return true if the two range overlap. */ - public isOverlapping(otherRange: SolutionRange): boolean { - if (this.upper === otherRange.upper && this.lower === otherRange.lower) { - return true; - } else if (this.upper >= otherRange.lower && this.upper <= otherRange.upper) { - return true; - } else if (this.lower >= otherRange.lower && this.lower <= otherRange.upper) { - return true; - } else if (otherRange.lower >= this.lower && otherRange.upper <= this.upper) { - return true; - } - return false; + public isOverlapping(otherRange: SolutionRange): boolean { + if (this.upper === otherRange.upper && this.lower === otherRange.lower) { + return true; + } + + if (this.upper >= otherRange.lower && this.upper <= otherRange.upper) { + return true; + } + + if (this.lower >= otherRange.lower && this.lower <= otherRange.upper) { + return true; } - /** - * Check whether the other range is inside the subject range. - * @param {SolutionRange} otherRange + + if (otherRange.lower >= this.lower && otherRange.upper <= this.upper) { + return true; + } + return false; + } + + /** + * Check whether the other range is inside the subject range. + * @param {SolutionRange} otherRange * @returns {boolean} Return true if the other range is inside this range. */ - public isInside(otherRange: SolutionRange): boolean { - return otherRange.lower >= this.lower && otherRange.upper <= this.upper; - } - /** + public isInside(otherRange: SolutionRange): boolean { + return otherRange.lower >= this.lower && otherRange.upper <= this.upper; + } + + /** * Fuse two ranges if they overlap. - * @param {SolutionRange} subjectRange - * @param {SolutionRange} otherRange + * @param {SolutionRange} subjectRange + * @param {SolutionRange} otherRange * @returns {SolutionRange[]} Return the fused range if they overlap else return the input ranges. */ - public static fuseRange(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange[] { - if (subjectRange.isOverlapping(otherRange)) { - const lowest = subjectRange.lower < otherRange.lower ? subjectRange.lower : otherRange.lower; - const uppest = subjectRange.upper > otherRange.upper ? subjectRange.upper : otherRange.upper; - return [new SolutionRange([lowest, uppest])]; - } - return [subjectRange, otherRange]; + public static fuseRange(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange[] { + if (subjectRange.isOverlapping(otherRange)) { + const lowest = subjectRange.lower < otherRange.lower ? subjectRange.lower : otherRange.lower; + const uppest = subjectRange.upper > otherRange.upper ? subjectRange.upper : otherRange.upper; + return [ new SolutionRange([ lowest, uppest ]) ]; } - /** + return [ subjectRange, otherRange ]; + } + + /** * Inverse the range, in a way that the range become everything that it excluded. Might * 0 or return multiple ranges. * @returns {SolutionRange[]} The resulting ranges. */ - public inverse(): SolutionRange[] { - if (this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY) { - return []; - } else if (this.lower === Number.NEGATIVE_INFINITY) { - return [new SolutionRange([this.upper + Number.EPSILON, Number.POSITIVE_INFINITY])] - } else if (this.upper === Number.POSITIVE_INFINITY) { - return [new SolutionRange([Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON])]; - } - return [ - new SolutionRange([Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON]), - new SolutionRange([this.upper + Number.EPSILON, Number.POSITIVE_INFINITY]), - ]; + public inverse(): SolutionRange[] { + if (this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY) { + return []; } - /** + + if (this.lower === Number.NEGATIVE_INFINITY) { + return [ new SolutionRange([ this.upper + Number.EPSILON, Number.POSITIVE_INFINITY ]) ]; + } + + if (this.upper === Number.POSITIVE_INFINITY) { + return [ new SolutionRange([ Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON ]) ]; + } + return [ + new SolutionRange([ Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON ]), + new SolutionRange([ this.upper + Number.EPSILON, Number.POSITIVE_INFINITY ]), + ]; + } + + /** * Get the range that intersect the other range and the subject range. * @param {SolutionRange} subjectRange - * @param {SolutionRange} otherRange + * @param {SolutionRange} otherRange * @returns {SolutionRange | undefined} Return the intersection if the range overlap otherwise return undefined */ - public static getIntersection(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange | undefined { - if (!subjectRange.isOverlapping(otherRange)) { - return undefined; - } - const lower = subjectRange.lower > otherRange.lower ? subjectRange.lower : otherRange.lower; - const upper = subjectRange.upper < otherRange.upper ? subjectRange.upper : otherRange.upper; - - return new SolutionRange([lower, upper]); + public static getIntersection(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange | undefined { + if (!subjectRange.isOverlapping(otherRange)) { + return undefined; } -} \ No newline at end of file + const lower = subjectRange.lower > otherRange.lower ? subjectRange.lower : otherRange.lower; + const upper = subjectRange.upper < otherRange.upper ? subjectRange.upper : otherRange.upper; + + return new SolutionRange([ lower, upper ]); + } +} diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index b430d70f7..c22ce3bac 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -2,78 +2,78 @@ import { SparqlRelationOperator } from '@comunica/types-link-traversal'; import type { ITreeRelation } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; +import { LinkOperator } from './LinkOperator'; import { SolutionDomain } from './SolutionDomain'; import { SolutionRange } from './SolutionRange'; -import { LinkOperator } from './LinkOperator'; import { - SparqlOperandDataTypes, SolverEquationSystem, - LogicOperatorReversed, LogicOperator, SolverExpression, - Variable, SolverExpressionRange, SparqlOperandDataTypesReversed + SparqlOperandDataTypes, + LogicOperatorReversed, LogicOperator, SparqlOperandDataTypesReversed, } from './solverInterfaces'; -import { LastLogicalOperator } from './solverInterfaces'; - +import type { LastLogicalOperator, SolverEquationSystem, ISolverExpression, + Variable, ISolverExpressionRange } from './solverInterfaces'; /** * Check if the solution domain of a system of equation compose of the expressions of the filter * expression and the relation is not empty. - * @param {ITreeRelation} relation - The tree:relation that we wish to determine if there is a possible solution + * @param {ITreeRelation} relation - The tree:relation that we wish to determine if there is a possible solution * when we combine it with a filter expression. * @param {Algebra.Expression} filterExpression - The Algebra expression of the filter. * @param {variable} variable - The variable to be resolved. * @returns {boolean} Return true if the domain */ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable }: { - relation: ITreeRelation, - filterExpression: Algebra.Expression, - variable: Variable + relation: ITreeRelation; + filterExpression: Algebra.Expression; + variable: Variable; }): boolean { LinkOperator.resetIdCount(); const relationsolverExpressions = convertTreeRelationToSolverExpression(relation, variable); - // the relation doesn't have a value or a type, so we accept it + // The relation doesn't have a value or a type, so we accept it if (!relationsolverExpressions) { return true; } const filtersolverExpressions = recursifFilterExpressionToSolverExpression(filterExpression, [], [], variable); - // the type are not compatible no evaluation is possible SPARQLEE will later return an error + // The type are not compatible no evaluation is possible SPARQLEE will later return an error if (!areTypesCompatible(filtersolverExpressions.concat(relationsolverExpressions))) { return true; } - const relationSolutionRange = getSolutionRange(relationsolverExpressions.valueAsNumber, relationsolverExpressions.operator); - // the relation is invalid so we filter it + const relationSolutionRange = getSolutionRange( + relationsolverExpressions.valueAsNumber, + relationsolverExpressions.operator, + ); + // The relation is invalid so we filter it if (!relationSolutionRange) { return false; } const equationSystemFirstEquation = createEquationSystem(filtersolverExpressions); - // cannot create the equation system we don't filter the relation in case the error is internal to not + // Cannot create the equation system we don't filter the relation in case the error is internal to not // loss results if (!equationSystemFirstEquation) { - return true + return true; } let solutionDomain: SolutionDomain; if (Array.isArray(equationSystemFirstEquation)) { - const [equationSystem, firstEquationToResolved] = equationSystemFirstEquation; + const [ equationSystem, firstEquationToResolved ] = equationSystemFirstEquation; - // we check if the filter expression itself has a solution + // We check if the filter expression itself has a solution solutionDomain = resolveSolutionDomainEquationSystem(equationSystem, firstEquationToResolved); - // don't pass the relation if the filter cannot be resolved + // Don't pass the relation if the filter cannot be resolved if (solutionDomain.isDomainEmpty()) { return false; } - - - }else { + } else { solutionDomain = SolutionDomain.newWithInitialValue(equationSystemFirstEquation.solutionDomain); } - // evaluate the solution domain when adding the relation + // Evaluate the solution domain when adding the relation solutionDomain = solutionDomain.add({ range: relationSolutionRange, operator: LogicOperator.And }); - // if there is a possible solution we don't filter the link + // If there is a possible solution we don't filter the link return !solutionDomain.isDomainEmpty(); } @@ -82,20 +82,24 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi * chain of logical expression. On the first call the filterExpressionList and linksOperator must be empty, they serve * as states to build the expressions. * @param {Algebra.Expression} filterExpression - The expression of the filter. - * @param {SolverExpression[]} filterExpressionList - The solver expression acquire until then. - * Should be empty on the first call. + * @param {ISolverExpression[]} filterExpressionList - The solver expression acquire until then. + * Should be empty on the first call. * @param {LinkOperator[]} linksOperator - The logical operator acquire until then. - * Should be empty on the first call. + * Should be empty on the first call. * @param {Variable} variable - The variable the solver expression must posses. - * @returns {SolverExpression[]} Return the solver expression converted from the filter expression + * @returns {ISolverExpression[]} Return the solver expression converted from the filter expression */ -export function recursifFilterExpressionToSolverExpression(filterExpression: Algebra.Expression, filterExpressionList: SolverExpression[], linksOperator: LinkOperator[], variable: Variable): SolverExpression[] { - // if it's an array of term then we should be able to create a solver expression +export function recursifFilterExpressionToSolverExpression(filterExpression: Algebra.Expression, + filterExpressionList: ISolverExpression[], + linksOperator: LinkOperator[], + variable: Variable): + ISolverExpression[] { + // If it's an array of term then we should be able to create a solver expression if ( filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM ) { const rawOperator = filterExpression.operator; - const operator = filterOperatorToSparqlRelationOperator(rawOperator) + const operator = filterOperatorToSparqlRelationOperator(rawOperator); if (operator) { const solverExpression = resolveAFilterTerm(filterExpression, operator, new Array(...linksOperator), variable); if (solverExpression) { @@ -103,7 +107,7 @@ export function recursifFilterExpressionToSolverExpression(filterExpression: Alg return filterExpressionList; } } - // else we store the logical operator an go deeper into the Algebra graph + // Else we store the logical operator an go deeper into the Algebra graph } else { const logicOperator = LogicOperatorReversed.get(filterExpression.operator); if (logicOperator) { @@ -112,7 +116,6 @@ export function recursifFilterExpressionToSolverExpression(filterExpression: Alg recursifFilterExpressionToSolverExpression(arg, filterExpressionList, linksOperator.concat(operator), variable); } } - } return filterExpressionList; } @@ -122,23 +125,26 @@ export function recursifFilterExpressionToSolverExpression(filterExpression: Alg * @param {SparqlRelationOperator} operator - The SPARQL operator defining the expression. * @param {LinkOperator[]} linksOperator - The logical operator prior to this expression. * @param {Variable} variable - The variable the expression should have to be part of a system of equation. - * @returns {SolverExpression | undefined} Return a solver expression if possible + * @returns {ISolverExpression | undefined} Return a solver expression if possible */ -export function resolveAFilterTerm(expression: Algebra.Expression, operator: SparqlRelationOperator, linksOperator: LinkOperator[], variable: Variable): SolverExpression | undefined { +export function resolveAFilterTerm(expression: Algebra.Expression, + operator: SparqlRelationOperator, + linksOperator: LinkOperator[], + variable: Variable): + ISolverExpression | undefined { let rawValue: string | undefined; let valueType: SparqlOperandDataTypes | undefined; let valueAsNumber: number | undefined; let hasVariable = false; - // find the constituant element of the solver expression + // Find the constituant element of the solver expression for (const arg of expression.args) { if ('term' in arg && arg.term.termType === 'Variable') { - // check if the expression has the same variable as the one the solver try to resolved + // Check if the expression has the same variable as the one the solver try to resolved if (arg.term.value !== variable) { return undefined; - } else { - hasVariable = true; } + hasVariable = true; } else if ('term' in arg && arg.term.termType === 'Literal') { rawValue = arg.term.value; valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); @@ -147,7 +153,7 @@ export function resolveAFilterTerm(expression: Algebra.Expression, operator: Spa } } } - // return if a fully form solver expression can be created + // Return if a fully form solver expression can be created if (hasVariable && rawValue && valueType && valueAsNumber) { return { variable, @@ -156,43 +162,48 @@ export function resolveAFilterTerm(expression: Algebra.Expression, operator: Spa valueAsNumber, operator, chainOperator: linksOperator, - } + }; } } /** * Find the domain of the possible solutions of a system of equations. * Will thrown an error if an equation cannot be resolved. * @param {SolverEquationSystem} equationSystem - * @param {[SolverExpressionRange, SolverExpressionRange]} firstExpression - The first expression to evaluate. + * @param {[ISolverExpressionRange, ISolverExpressionRange]} firstExpression - The first expression to evaluate. * @returns {SolutionDomain} */ -export function resolveSolutionDomainEquationSystem(equationSystem: SolverEquationSystem, firstExpression: [SolverExpressionRange, SolverExpressionRange]): SolutionDomain { +export function resolveSolutionDomainEquationSystem(equationSystem: SolverEquationSystem, + firstExpression: [ISolverExpressionRange, + ISolverExpressionRange]): + SolutionDomain { let domain: SolutionDomain = SolutionDomain.newWithInitialValue(firstExpression[0].solutionDomain); - let idx: string = ""; - // safety to avoid infinite loop + let idx = ''; + // Safety to avoid infinite loop let i = 0; - let currentEquation: SolverExpressionRange | undefined = firstExpression[1]; + let currentEquation: ISolverExpressionRange | undefined = firstExpression[1]; do { const resp = resolveSolutionDomainWithAnExpression(currentEquation, domain); if (!resp) { - throw new Error(`unable to resolve the equation ${currentEquation}`) + throw new Error(`unable to resolve the equation ${currentEquation.chainOperator}`); } - [domain, idx] = resp; + [ domain, idx ] = resp; currentEquation = equationSystem.get(idx); - i++ - } while (currentEquation && i != equationSystem.size + 1) + i++; + } while (currentEquation && i !== equationSystem.size + 1); return domain; } /** * Create a system of equation from the provided expression and return separatly the first expression to evaluate. - * @param {SolverExpression[]} expressions - the expression composing the equation system - * @returns {[SolverEquationSystem, [SolverExpressionRange, SolverExpressionRange]] | undefined} if the expression form a possible system of equation return + * @param {ISolverExpression[]} expressions - the expression composing the equation system + * @returns {[SolverEquationSystem, [ISolverExpressionRange, ISolverExpressionRange]] + * | undefined} if the expression form a possible system of equation return * the system of equation and the first expression to evaluate. */ -export function createEquationSystem(expressions: SolverExpression[]): [SolverEquationSystem, [SolverExpressionRange, SolverExpressionRange]] | SolverExpressionRange | undefined { +export function createEquationSystem(expressions: ISolverExpression[]): +[SolverEquationSystem, [ISolverExpressionRange, ISolverExpressionRange]] | ISolverExpressionRange | undefined { if (expressions.length === 1) { const solutionRange = getSolutionRange(expressions[0].valueAsNumber, expressions[0].operator); if (!solutionRange) { @@ -200,15 +211,15 @@ export function createEquationSystem(expressions: SolverExpression[]): [SolverEq } return { chainOperator: [], - solutionDomain: solutionRange + solutionDomain: solutionRange, }; } const system: SolverEquationSystem = new Map(); - // the first expression that has to be evaluated - let firstEquationToEvaluate: [SolverExpressionRange, SolverExpressionRange] | undefined = undefined; - // the last logical operator apply to the first expression to be evaluated - let firstEquationLastOperator: string = ""; + // The first expression that has to be evaluated + let firstEquationToEvaluate: [ISolverExpressionRange, ISolverExpressionRange] | undefined; + // The last logical operator apply to the first expression to be evaluated + let firstEquationLastOperator = ''; for (const expression of expressions) { const lastOperator = expression.chainOperator.slice(-1).toString(); @@ -216,80 +227,89 @@ export function createEquationSystem(expressions: SolverExpression[]): [SolverEq if (!solutionRange) { return undefined; } - const equation: SolverExpressionRange = { + const equation: ISolverExpressionRange = { chainOperator: expression.chainOperator, - solutionDomain: solutionRange + solutionDomain: solutionRange, }; const lastEquation = system.get(lastOperator); if (lastEquation) { - // there cannot be two first equation to be evaluated - if (firstEquationLastOperator !== "") { + // There cannot be two first equation to be evaluated + if (firstEquationLastOperator !== '') { return undefined; } - firstEquationToEvaluate = [lastEquation, equation]; + firstEquationToEvaluate = [ lastEquation, equation ]; firstEquationLastOperator = lastOperator; } else { system.set(lastOperator, equation); } } - // there should be a first expression to be evaluated + // There should be a first expression to be evaluated if (!firstEquationToEvaluate) { return undefined; } - // we delete the fist equation to be evaluated from the system of equation because it is a value returned + // We delete the fist equation to be evaluated from the system of equation because it is a value returned system.delete(firstEquationLastOperator); - return [system, firstEquationToEvaluate]; + return [ system, firstEquationToEvaluate ]; } /** - * Resolve the solution domain when we add a new expression and returned the new domain with the next expression that has to be evaluated. - * @param {SolverExpressionRange} equation - Current solver expression. - * @param {SolutionDomain} domain - Current solution domain of the system of equation. - * @returns {[SolutionDomain, LastLogicalOperator] | undefined} If the equation can be solved returned the new domain and the next - * indexed logical operator that has to be resolved. The system of equation is indexed by their last logical operator hence - * the next expression can be found using the returned operator. An empty string is returned if it was the last expression instead of - * undefined to simply implementation, because the system of equation will retuned an undefined value with an empty string. + * Resolve the solution domain when we add a new expression and + * returned the new domain with the next expression that has to be evaluated. + * @param {ISolverExpressionRange} equation - Current solver expression. + * @param {SolutionDomain} domain - Current solution domain of the system of equation. + * @returns {[SolutionDomain, LastLogicalOperator] | + * undefined} If the equation can be solved returned the new domain and the next + * indexed logical operator that has to be resolved. + * The system of equation is indexed by their last logical operator hence + * the next expression can be found using the returned operator. + * An empty string is returned if it was the last expression instead of + * undefined to simply implementation, + * because the system of equation will retuned an undefined value with an empty string. */ -export function resolveSolutionDomainWithAnExpression(equation: SolverExpressionRange, domain: SolutionDomain): [SolutionDomain, LastLogicalOperator] | undefined { +export function resolveSolutionDomainWithAnExpression(equation: ISolverExpressionRange, + domain: SolutionDomain): + [SolutionDomain, LastLogicalOperator] | undefined { let localDomain = domain.clone(); - // to keep track of the last expression because we resolved all the not operator + // To keep track of the last expression because we resolved all the not operator // next to the current last logical operator let i = -1; - // we find the last logical expression that has to be resolved + // We find the last logical expression that has to be resolved let currentLastOperator = equation.chainOperator.at(i); if (!currentLastOperator) { return undefined; } i--; - // resolve the new domain + // Resolve the new domain localDomain = localDomain.add({ range: equation.solutionDomain, operator: currentLastOperator?.operator }); currentLastOperator = equation.chainOperator.at(i); - // if it was the last expression + // If it was the last expression if (!currentLastOperator) { - return [localDomain, ""]; + return [ localDomain, '' ]; } - // we solved all the NOT operator next to the last logical operator + // We solved all the NOT operator next to the last logical operator while (currentLastOperator?.operator === LogicOperator.Not) { localDomain = localDomain.add({ operator: currentLastOperator?.operator }); i--; currentLastOperator = equation.chainOperator.at(i); - // it the last operator was a NOT + // It the last operator was a NOT if (!currentLastOperator?.operator) { - return [localDomain, ""]; + return [ localDomain, '' ]; } } - return [localDomain, currentLastOperator.toString()] + return [ localDomain, currentLastOperator.toString() ]; } /** * Convert a TREE relation into a solver expression. - * @param {ITreeRelation} relation - TREE relation. + * @param {ITreeRelation} relation - TREE relation. * @param {Variable} variable - variable of the SPARQL query associated with the tree:path of the relation. - * @returns {SolverExpression | undefined} Resulting solver expression if the data type is supported by SPARQL + * @returns {ISolverExpression | undefined} Resulting solver expression if the data type is supported by SPARQL * and the value can be cast into a number. */ -export function convertTreeRelationToSolverExpression(relation: ITreeRelation, variable: Variable): SolverExpression | undefined { +export function convertTreeRelationToSolverExpression(relation: ITreeRelation, + variable: Variable): + ISolverExpression | undefined { if (relation.value && relation.type) { const valueType = SparqlOperandDataTypesReversed.get((relation.value.term).datatype.value); if (!valueType) { @@ -314,43 +334,42 @@ export function convertTreeRelationToSolverExpression(relation: ITreeRelation, v /** * Check if all the expression provided have a SparqlOperandDataTypes compatible type * it is considered that all number types are compatible between them. - * @param {SolverExpression[]} expressions - The subject expression. - * @returns {boolean} Return true if the type are compatible. + * @param {ISolverExpression[]} expressions - The subject expression. + * @returns {boolean} Return true if the type are compatible. */ -export function areTypesCompatible(expressions: SolverExpression[]): boolean { +export function areTypesCompatible(expressions: ISolverExpression[]): boolean { const firstType = expressions[0].valueType; for (const expression of expressions) { - const areIdentical = expression.valueType === firstType; const areNumbers = isSparqlOperandNumberType(firstType) && isSparqlOperandNumberType(expression.valueType); if (!(areIdentical || areNumbers)) { - return false + return false; } } - return true + return true; } /** * Find the solution range of a value and operator which is analogue to an expression. * @param {number} value - * @param {SparqlRelationOperator} operator + * @param {SparqlRelationOperator} operator * @returns {SolutionRange | undefined} The solution range associated with the value and the operator. */ export function getSolutionRange(value: number, operator: SparqlRelationOperator): SolutionRange | undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: - return new SolutionRange([value + Number.EPSILON, Number.POSITIVE_INFINITY]); + return new SolutionRange([ value + Number.EPSILON, Number.POSITIVE_INFINITY ]); case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return new SolutionRange([value, Number.POSITIVE_INFINITY]); + return new SolutionRange([ value, Number.POSITIVE_INFINITY ]); case SparqlRelationOperator.EqualThanRelation: - return new SolutionRange([value, value]); + return new SolutionRange([ value, value ]); case SparqlRelationOperator.LessThanRelation: - return new SolutionRange([Number.NEGATIVE_INFINITY, value - Number.EPSILON]); + return new SolutionRange([ Number.NEGATIVE_INFINITY, value - Number.EPSILON ]); case SparqlRelationOperator.LessThanOrEqualToRelation: - return new SolutionRange([Number.NEGATIVE_INFINITY, value]); + return new SolutionRange([ Number.NEGATIVE_INFINITY, value ]); default: - // not an operator that is compatible with number. + // Not an operator that is compatible with number. break; } } @@ -360,23 +379,30 @@ export function getSolutionRange(value: number, operator: SparqlRelationOperator * @param {SparqlOperandDataTypes} rdfTermType - The type of the value * @returns {number | undefined} The resulting number or undefined if the convertion is not possible. */ -export function castSparqlRdfTermIntoNumber(rdfTermValue: string, rdfTermType: SparqlOperandDataTypes): number | undefined { +export function castSparqlRdfTermIntoNumber(rdfTermValue: string, + rdfTermType: SparqlOperandDataTypes): + number | undefined { if ( rdfTermType === SparqlOperandDataTypes.Decimal || rdfTermType === SparqlOperandDataTypes.Float || rdfTermType === SparqlOperandDataTypes.Double ) { - const val = Number.parseFloat(rdfTermValue) + const val = Number.parseFloat(rdfTermValue); return Number.isNaN(val) ? undefined : val; - } else if ( + } + + if ( isSparqlOperandNumberType(rdfTermType) && !rdfTermValue.includes('.') ) { - const val = Number.parseInt(rdfTermValue, 10) + const val = Number.parseInt(rdfTermValue, 10); return Number.isNaN(val) ? undefined : val; - } else if (rdfTermType === SparqlOperandDataTypes.DateTime) { - const val = new Date(rdfTermValue).getTime() + } + + if (rdfTermType === SparqlOperandDataTypes.DateTime) { + const val = new Date(rdfTermValue).getTime(); return Number.isNaN(val) ? undefined : val; } + return undefined; } /** diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index fb3b48fb9..82a7a338b 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -1,73 +1,73 @@ -import { SparqlRelationOperator } from '@comunica/types-link-traversal'; -import { SolutionRange } from './SolutionRange'; -import { LinkOperator } from './LinkOperator'; +import type { SparqlRelationOperator } from '@comunica/types-link-traversal'; +import type { LinkOperator } from './LinkOperator'; +import type { SolutionRange } from './SolutionRange'; /** * Valid SPARQL data type for operation. * reference: https://www.w3.org/TR/sparql11-query/#operandDataTypes */ export enum SparqlOperandDataTypes { - Integer = 'http://www.w3.org/2001/XMLSchema#integer', - Decimal = 'http://www.w3.org/2001/XMLSchema#decimal', - Float = 'http://www.w3.org/2001/XMLSchema#float', - Double = 'http://www.w3.org/2001/XMLSchema#double', - String = 'http://www.w3.org/2001/XMLSchema#string', - Boolean = 'http://www.w3.org/2001/XMLSchema#boolean', - DateTime = 'http://www.w3.org/2001/XMLSchema#dateTime', + Integer = 'http://www.w3.org/2001/XMLSchema#integer', + Decimal = 'http://www.w3.org/2001/XMLSchema#decimal', + Float = 'http://www.w3.org/2001/XMLSchema#float', + Double = 'http://www.w3.org/2001/XMLSchema#double', + String = 'http://www.w3.org/2001/XMLSchema#string', + Boolean = 'http://www.w3.org/2001/XMLSchema#boolean', + DateTime = 'http://www.w3.org/2001/XMLSchema#dateTime', - NonPositiveInteger = 'http://www.w3.org/2001/XMLSchema#nonPositiveInteger', - NegativeInteger = 'http://www.w3.org/2001/XMLSchema#negativeInteger', - Long = 'http://www.w3.org/2001/XMLSchema#long', - Int = 'http://www.w3.org/2001/XMLSchema#int', - Short = 'http://www.w3.org/2001/XMLSchema#short', - Byte = 'http://www.w3.org/2001/XMLSchema#byte', - NonNegativeInteger = 'http://www.w3.org/2001/XMLSchema#nonNegativeInteger', - UnsignedLong = 'http://www.w3.org/2001/XMLSchema#nunsignedLong', - UnsignedInt = 'http://www.w3.org/2001/XMLSchema#unsignedInt', - UnsignedShort = 'http://www.w3.org/2001/XMLSchema#unsignedShort', - UnsignedByte = 'http://www.w3.org/2001/XMLSchema#unsignedByte', - PositiveInteger = 'http://www.w3.org/2001/XMLSchema#positiveInteger' + NonPositiveInteger = 'http://www.w3.org/2001/XMLSchema#nonPositiveInteger', + NegativeInteger = 'http://www.w3.org/2001/XMLSchema#negativeInteger', + Long = 'http://www.w3.org/2001/XMLSchema#long', + Int = 'http://www.w3.org/2001/XMLSchema#int', + Short = 'http://www.w3.org/2001/XMLSchema#short', + Byte = 'http://www.w3.org/2001/XMLSchema#byte', + NonNegativeInteger = 'http://www.w3.org/2001/XMLSchema#nonNegativeInteger', + UnsignedLong = 'http://www.w3.org/2001/XMLSchema#nunsignedLong', + UnsignedInt = 'http://www.w3.org/2001/XMLSchema#unsignedInt', + UnsignedShort = 'http://www.w3.org/2001/XMLSchema#unsignedShort', + UnsignedByte = 'http://www.w3.org/2001/XMLSchema#unsignedByte', + PositiveInteger = 'http://www.w3.org/2001/XMLSchema#positiveInteger' } /** * A map to access the value of the enum SparqlOperandDataTypesReversed by it's value in O(1) time complexity. */ export const SparqlOperandDataTypesReversed: Map = - new Map(Object.values(SparqlOperandDataTypes).map(value => [value, value])); + new Map(Object.values(SparqlOperandDataTypes).map(value => [ value, value ])); /** * Logical operator that linked logical expression together. */ export enum LogicOperator { - And = '&&', - Or = '||', - Not = '!', -}; + And = '&&', + Or = '||', + Not = '!', +} /** * A map to access the value of the enum LogicOperator by it's value in O(1) time complexity. */ export const LogicOperatorReversed: Map = - new Map(Object.values(LogicOperator).map(value => [value, value])); + new Map(Object.values(LogicOperator).map(value => [ value, value ])); /** * A range of a solver expression with his chain of logical operation. */ -export interface SolverExpressionRange { - /** +export interface ISolverExpressionRange { + /** * The chain of operation attached to the expression. */ - chainOperator: LinkOperator[]; - /** + chainOperator: LinkOperator[]; + /** * The domain of the solution of this expression. */ - solutionDomain: SolutionRange; + solutionDomain: SolutionRange; } /** * A system of equation to be solved by the solver. It is indexed by the last logical operation that has to be apply * to the expression */ -export type SolverEquationSystem = Map; +export type SolverEquationSystem = Map; /** * A last logical expression of a chain of logical expression. @@ -81,34 +81,30 @@ export type Variable = string; * An expression of the solver containing also the chain of operation attached to it. * eg: x>=5.5 chained with &&, ||, ! */ -export interface SolverExpression { - /** +export interface ISolverExpression { + /** * The variable of the expression */ - variable: Variable; - /** + variable: Variable; + /** * The constant value attached to the expression as a String. */ - rawValue: string; - /** + rawValue: string; + /** * The data type of the constant value. */ - valueType: SparqlOperandDataTypes; - /** + valueType: SparqlOperandDataTypes; + /** * The value repressented as a number */ - valueAsNumber: number; - /** + valueAsNumber: number; + /** * The operator binding the value and the variable. */ - operator: SparqlRelationOperator; - /** + operator: SparqlRelationOperator; + /** * The chain of logical operator attached to the expression. */ - chainOperator: LinkOperator[]; -}; - - - - + chainOperator: LinkOperator[]; +} diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 96cf8c3ed..f7626226d 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -7,8 +7,6 @@ import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; - - const stream = require('streamify-array'); const DF = new DataFactory(); @@ -504,10 +502,9 @@ describe('ActorExtractLinksExtractLinksTree', () => { DF.namedNode('ex:gx')), DF.quad(DF.blankNode('_:_g1'), DF.blankNode(TreeNodes.RDFTypeNode), - DF.namedNode(SparqlRelationOperator.LessThanOrEqualToRelation)) + DF.namedNode(SparqlRelationOperator.LessThanOrEqualToRelation)), ]); - const query = translate(` SELECT ?o WHERE { ex:foo ex:path ?o. @@ -516,8 +513,8 @@ describe('ActorExtractLinksExtractLinksTree', () => { ex:foo ex:p3 ex:o3. FILTER(?o=550) } - `, { prefixes: { 'ex': 'http://example.com#' } }); - + `, { prefixes: { ex: 'http://example.com#' }}); + const contextWithQuery = new ActionContext({ [KeysRdfResolveQuadPattern.source.name]: treeUrl, [KeysInitQuery.query.name]: query, diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index fd2229f9d..6841f9305 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -5,9 +5,8 @@ import { SparqlRelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; -import { FilterNode } from '../lib/FilterNode'; import { Algebra, translate } from 'sparqlalgebrajs'; - +import { FilterNode } from '../lib/FilterNode'; const DF = new DataFactory(); @@ -37,7 +36,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(response).toBe(true); }); - it('should no test when the TREE relation are undefined', async () => { + it('should no test when the TREE relation are undefined', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -49,7 +48,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(response).toBe(false); }); - it('should not test when there is a filter operation in the query but no TREE relations', async () => { + it('should not test when there is a filter operation in the query but no TREE relations', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -61,7 +60,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { expect(response).toBe(false); }); - it('should no test when there are no filter operation in the query but a TREE relation', async () => { + it('should no test when there are no filter operation in the query but a TREE relation', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); @@ -79,7 +78,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); describe('run method', () => { - it('should accept the relation when the filter respect the relation', async () => { + it('should accept the relation when the filter respect the relation', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -92,7 +91,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.EqualThanRelation + type: SparqlRelationOperator.EqualThanRelation, }, ], }; @@ -103,7 +102,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ex:foo ex:p ex:o. FILTER(?o=5) } - `, { prefixes: { 'ex': 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, @@ -112,11 +111,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should not accept the relation when the filter is not respected by the relation', async () => { + it('should not accept the relation when the filter is not respected by the relation', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -129,7 +128,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.EqualThanRelation + type: SparqlRelationOperator.EqualThanRelation, }, ], }; @@ -184,11 +183,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([['http://bar.com', false]]), + new Map([[ 'http://bar.com', false ]]), ); }); - it('should accept the relation when the query don\'t invoke the right path', async () => { + it('should accept the relation when the query don\'t invoke the right path', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -201,7 +200,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.EqualThanRelation + type: SparqlRelationOperator.EqualThanRelation, }, ], }; @@ -256,11 +255,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should return an empty map when there is no relation', async () => { + it('should return an empty map when there is no relation', async() => { const bgp: RDF.Quad[] = [ DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), ]; @@ -317,7 +316,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); it('should accept the relation when there is multiple filters and the query path don\'t match the relation', - async () => { + async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -330,7 +329,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.EqualThanRelation + type: SparqlRelationOperator.EqualThanRelation, }, ], }; @@ -421,12 +420,12 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); it('should accept the relations when one respect the filter and another has no path and value defined', - async () => { + async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -439,7 +438,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.EqualThanRelation + type: SparqlRelationOperator.EqualThanRelation, }, @@ -499,11 +498,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([['http://bar.com', true], ['http://foo.com', true]]), + new Map([[ 'http://bar.com', true ], [ 'http://foo.com', true ]]), ); }); - it('should accept the relation when the filter argument are not related to the query', async () => { + it('should accept the relation when the filter argument are not related to the query', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -516,7 +515,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.EqualThanRelation + type: SparqlRelationOperator.EqualThanRelation, }, ], }; @@ -571,11 +570,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should accept the relation when there is multiples filters and one is not relevant', async () => { + it('should accept the relation when there is multiples filters and one is not relevant', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -588,7 +587,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.EqualThanRelation + type: SparqlRelationOperator.EqualThanRelation, }, ], }; @@ -676,11 +675,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should accept the relation when the filter compare two constants', async () => { + it('should accept the relation when the filter compare two constants', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -693,7 +692,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.EqualThanRelation + type: SparqlRelationOperator.EqualThanRelation, }, ], }; @@ -791,11 +790,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should return an empty filter map if the bgp if empty', async () => { + it('should return an empty filter map if the bgp if empty', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -808,7 +807,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.EqualThanRelation + type: SparqlRelationOperator.EqualThanRelation, }, ], }; @@ -865,7 +864,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it('should return an empty filter map if there is no bgp', async () => { + it('should return an empty filter map if there is no bgp', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -878,7 +877,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.EqualThanRelation + type: SparqlRelationOperator.EqualThanRelation, }, ], }; @@ -929,7 +928,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it('should accept the relation when the filter respect the relation with a construct query', async () => { + it('should accept the relation when the filter respect the relation with a construct query', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -942,7 +941,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.EqualThanRelation + type: SparqlRelationOperator.EqualThanRelation, }, ], }; @@ -1004,11 +1003,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should accept the relation when the filter respect the relation with a nested query', async () => { + it('should accept the relation when the filter respect the relation with a nested query', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -1021,7 +1020,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.EqualThanRelation + type: SparqlRelationOperator.EqualThanRelation, }, ], }; @@ -1190,11 +1189,11 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should accept the relation when a complex filter respect the relation', async () => { + it('should accept the relation when a complex filter respect the relation', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -1207,7 +1206,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, - type: SparqlRelationOperator.GreaterThanOrEqualToRelation + type: SparqlRelationOperator.GreaterThanOrEqualToRelation, }, ], }; @@ -1219,7 +1218,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ex:foo ex:p2 ?x. FILTER(?o>2|| ?x=4 || (?x<3 && ?o<3) ) } - `, { prefixes: { 'ex': 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, @@ -1228,7 +1227,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const result = await filterNode.run(node, context); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); }); diff --git a/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts index 15fc7839a..c0420ba2a 100644 --- a/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts @@ -1,39 +1,39 @@ -import {LinkOperator} from '../lib/LinkOperator'; -import {LogicOperator} from '../lib/solverInterfaces'; +import { LinkOperator } from '../lib/LinkOperator'; +import { LogicOperator } from '../lib/solverInterfaces'; -describe('LinkOperator', ()=>{ - describe('constructor',()=>{ - it('when constructing multiple linkOperator the id should be incremented', ()=>{ - const operators = [LogicOperator.And, LogicOperator.Not, LogicOperator.Or]; - for(let i=0;i<100;i++){ - expect((new LinkOperator(operators[i%operators.length])).id).toBe(i); - } - }) +describe('LinkOperator', () => { + describe('constructor', () => { + it('when constructing multiple linkOperator the id should be incremented', () => { + const operators = [ LogicOperator.And, LogicOperator.Not, LogicOperator.Or ]; + for (let i = 0; i < 100; i++) { + expect((new LinkOperator(operators[i % operators.length])).id).toBe(i); + } }); - describe('toString', ()=>{ - beforeAll(()=>{ - LinkOperator.resetIdCount(); - }); - it('should return a string expressing the operator and the id', ()=>{ - const operators = [LogicOperator.And, LogicOperator.Not, LogicOperator.Or]; - for(let i=0;i<100;i++){ - const operator = operators[i%operators.length]; - expect((new LinkOperator(operator)).toString()).toBe(`${operator}-${i}`); - } - }); + }); + describe('toString', () => { + beforeAll(() => { + LinkOperator.resetIdCount(); }); + it('should return a string expressing the operator and the id', () => { + const operators = [ LogicOperator.And, LogicOperator.Not, LogicOperator.Or ]; + for (let i = 0; i < 100; i++) { + const operator = operators[i % operators.length]; + expect((new LinkOperator(operator)).toString()).toBe(`${operator}-${i}`); + } + }); + }); - describe('resetIdCount', ()=>{ - beforeAll(()=>{ - LinkOperator.resetIdCount(); - }); - it('should reset the count when the function is called', ()=>{ - const operators = [LogicOperator.And, LogicOperator.Not, LogicOperator.Or]; - for(let i=0;i<10;i++){ - expect((new LinkOperator(operators[i%operators.length])).id).toBe(i); - } - LinkOperator.resetIdCount(); - expect((new LinkOperator(operators[0])).id).toBe(0); - }); + describe('resetIdCount', () => { + beforeAll(() => { + LinkOperator.resetIdCount(); + }); + it('should reset the count when the function is called', () => { + const operators = [ LogicOperator.And, LogicOperator.Not, LogicOperator.Or ]; + for (let i = 0; i < 10; i++) { + expect((new LinkOperator(operators[i % operators.length])).id).toBe(i); + } + LinkOperator.resetIdCount(); + expect((new LinkOperator(operators[0])).id).toBe(0); }); -}); \ No newline at end of file + }); +}); diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index a3de90e6c..af2dc1dab 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -1,287 +1,282 @@ -import { SolutionDomain } from '../lib/SolutionDomain'; import { SolutionRange } from '../lib//SolutionRange'; +import { SolutionDomain } from '../lib/SolutionDomain'; import { LogicOperator } from '../lib/solverInterfaces'; describe('SolutionDomain', () => { - describe('constructor', () => { - it('should return an empty solution domain', () => { - const solutionDomain = new SolutionDomain(); + describe('constructor', () => { + it('should return an empty solution domain', () => { + const solutionDomain = new SolutionDomain(); - expect(solutionDomain.get_domain().length).toBe(0); - }); + expect(solutionDomain.get_domain().length).toBe(0); }); + }); - describe('newWithInitialValue', () => { - it('should create a solution domain with the initial value', () => { - const solutionRange = new SolutionRange([0, 1]); - const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); + describe('newWithInitialValue', () => { + it('should create a solution domain with the initial value', () => { + const solutionRange = new SolutionRange([ 0, 1 ]); + const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); - expect(solutionDomain.get_domain().length).toBe(1); - expect(solutionDomain.get_domain()[0]).toStrictEqual(solutionRange); - }); + expect(solutionDomain.get_domain().length).toBe(1); + expect(solutionDomain.get_domain()[0]).toStrictEqual(solutionRange); }); + }); + + describe('addWithOrOperator', () => { + let aDomain: SolutionDomain = new SolutionDomain(); + const aRanges = [ + new SolutionRange([ -1, 0 ]), + new SolutionRange([ 1, 5 ]), + new SolutionRange([ 10, 10 ]), + new SolutionRange([ 21, 33 ]), + new SolutionRange([ 60, 70 ]), + ]; + beforeEach(() => { + aDomain = new SolutionDomain(); + for (const r of aRanges) { + aDomain = aDomain.addWithOrOperator(r); + } + }); + it('given an empty domain should be able to add the subject range', () => { + const range = new SolutionRange([ 0, 1 ]); + const solutionDomain = new SolutionDomain(); - describe('addWithOrOperator', () => { - let aDomain: SolutionDomain = new SolutionDomain(); - const aRanges = [ - new SolutionRange([-1, 0]), - new SolutionRange([1, 5]), - new SolutionRange([10, 10]), - new SolutionRange([21, 33]), - new SolutionRange([60, 70]), - ]; - beforeEach(() => { - aDomain = new SolutionDomain(); - for (const r of aRanges) { - aDomain = aDomain.addWithOrOperator(r); - } - }); - it('given an empty domain should be able to add the subject range', () => { - const range = new SolutionRange([0, 1]); - const solutionDomain = new SolutionDomain(); - - const newDomain = solutionDomain.addWithOrOperator(range); - - expect(newDomain.get_domain().length).toBe(1); - expect(newDomain.get_domain()[0]).toStrictEqual(range); - }); - - it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { - const ranges = [ - new SolutionRange([10, 10]), - new SolutionRange([1, 2]), - new SolutionRange([-1, 0]), - new SolutionRange([60, 70]), - ]; - - let solutionDomain = new SolutionDomain(); - - ranges.forEach((range, idx) => { - solutionDomain = solutionDomain.addWithOrOperator(range); - expect(solutionDomain.get_domain().length).toBe(idx + 1); - }); - - const expectedDomain = [ - new SolutionRange([-1, 0]), - new SolutionRange([1, 2]), - new SolutionRange([10, 10]), - new SolutionRange([60, 70]), - ]; - - expect(solutionDomain.get_domain()).toStrictEqual(expectedDomain); - }); - - it('given a domain should not add a range that is inside another', () => { - const anOverlappingRange = new SolutionRange([22, 23]); - const newDomain = aDomain.addWithOrOperator(anOverlappingRange); - - expect(newDomain.get_domain().length).toBe(aDomain.get_domain().length); - expect(newDomain.get_domain()).toStrictEqual(aRanges); - }); - - it('given a domain should create a single domain if all the domain segment are contain into the new range', () => { - const anOverlappingRange = new SolutionRange([-100, 100]); - const newDomain = aDomain.addWithOrOperator(anOverlappingRange); + const newDomain = solutionDomain.addWithOrOperator(range); - expect(newDomain.get_domain().length).toBe(1); - expect(newDomain.get_domain()).toStrictEqual([anOverlappingRange]); - }); + expect(newDomain.get_domain().length).toBe(1); + expect(newDomain.get_domain()[0]).toStrictEqual(range); + }); - it('given a domain should fuse multiple domain segment if the new range overlap with them', () => { - const aNewRange = new SolutionRange([1, 23]); - const newDomain = aDomain.addWithOrOperator(aNewRange); + it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { + const ranges = [ + new SolutionRange([ 10, 10 ]), + new SolutionRange([ 1, 2 ]), + new SolutionRange([ -1, 0 ]), + new SolutionRange([ 60, 70 ]), + ]; + + let solutionDomain = new SolutionDomain(); + + ranges.forEach((range, idx) => { + solutionDomain = solutionDomain.addWithOrOperator(range); + expect(solutionDomain.get_domain().length).toBe(idx + 1); + }); + + const expectedDomain = [ + new SolutionRange([ -1, 0 ]), + new SolutionRange([ 1, 2 ]), + new SolutionRange([ 10, 10 ]), + new SolutionRange([ 60, 70 ]), + ]; + + expect(solutionDomain.get_domain()).toStrictEqual(expectedDomain); + }); - const expectedResultingDomainRange = [ - new SolutionRange([-1, 0]), - new SolutionRange([1, 33]), - new SolutionRange([60, 70]), - ]; + it('given a domain should not add a range that is inside another', () => { + const anOverlappingRange = new SolutionRange([ 22, 23 ]); + const newDomain = aDomain.addWithOrOperator(anOverlappingRange); - expect(newDomain.get_domain().length).toBe(3); - expect(newDomain.get_domain()).toStrictEqual(expectedResultingDomainRange); - }); + expect(newDomain.get_domain().length).toBe(aDomain.get_domain().length); + expect(newDomain.get_domain()).toStrictEqual(aRanges); }); - describe('notOperation', () => { - it('given a domain with one range should return the inverse of the domain', () => { - const solutionRange = new SolutionRange([0, 1]); - const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); + it('given a domain should create a single domain if all the domain segment are contain into the new range', () => { + const anOverlappingRange = new SolutionRange([ -100, 100 ]); + const newDomain = aDomain.addWithOrOperator(anOverlappingRange); - const expectedDomain = [ - new SolutionRange([Number.NEGATIVE_INFINITY, 0 - Number.EPSILON]), - new SolutionRange([1 + Number.EPSILON, Number.POSITIVE_INFINITY]) - ]; - const newDomain = solutionDomain.notOperation(); - - expect(newDomain.get_domain().length).toBe(2); - expect(newDomain.get_domain()).toStrictEqual(expectedDomain); - }); + expect(newDomain.get_domain().length).toBe(1); + expect(newDomain.get_domain()).toStrictEqual([ anOverlappingRange ]); + }); - it('given a domain with multiple range should return the inverted domain', () => { - let domain = new SolutionDomain(); - const ranges = [ - new SolutionRange([0, 1]), - new SolutionRange([2, 2]), - new SolutionRange([44, 55]), - ]; - const expectedDomain = [ - new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]), - ]; + it('given a domain should fuse multiple domain segment if the new range overlap with them', () => { + const aNewRange = new SolutionRange([ 1, 23 ]); + const newDomain = aDomain.addWithOrOperator(aNewRange); - for (const r of ranges) { - domain = domain.addWithOrOperator(r); - } - domain = domain.notOperation(); + const expectedResultingDomainRange = [ + new SolutionRange([ -1, 0 ]), + new SolutionRange([ 1, 33 ]), + new SolutionRange([ 60, 70 ]), + ]; - expect(domain.get_domain()).toStrictEqual(expectedDomain); + expect(newDomain.get_domain().length).toBe(3); + expect(newDomain.get_domain()).toStrictEqual(expectedResultingDomainRange); + }); + }); - }); + describe('notOperation', () => { + it('given a domain with one range should return the inverse of the domain', () => { + const solutionRange = new SolutionRange([ 0, 1 ]); + const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); + const expectedDomain = [ + new SolutionRange([ Number.NEGATIVE_INFINITY, 0 - Number.EPSILON ]), + new SolutionRange([ 1 + Number.EPSILON, Number.POSITIVE_INFINITY ]), + ]; + const newDomain = solutionDomain.notOperation(); + expect(newDomain.get_domain().length).toBe(2); + expect(newDomain.get_domain()).toStrictEqual(expectedDomain); }); - describe('addWithAndOperator', () => { - let aDomain: SolutionDomain = new SolutionDomain(); - const aRanges = [ - new SolutionRange([-1, 0]), - new SolutionRange([1, 5]), - new SolutionRange([10, 10]), - new SolutionRange([21, 33]), - new SolutionRange([60, 70]), - ]; - beforeEach(() => { - aDomain = new SolutionDomain(); - for (const r of aRanges) { - aDomain = aDomain.addWithOrOperator(r); - } - }); + it('given a domain with multiple range should return the inverted domain', () => { + let domain = new SolutionDomain(); + const ranges = [ + new SolutionRange([ 0, 1 ]), + new SolutionRange([ 2, 2 ]), + new SolutionRange([ 44, 55 ]), + ]; + const expectedDomain = [ + new SolutionRange([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), + ]; + + for (const r of ranges) { + domain = domain.addWithOrOperator(r); + } + domain = domain.notOperation(); + + expect(domain.get_domain()).toStrictEqual(expectedDomain); + }); + }); + + describe('addWithAndOperator', () => { + let aDomain: SolutionDomain = new SolutionDomain(); + const aRanges = [ + new SolutionRange([ -1, 0 ]), + new SolutionRange([ 1, 5 ]), + new SolutionRange([ 10, 10 ]), + new SolutionRange([ 21, 33 ]), + new SolutionRange([ 60, 70 ]), + ]; + beforeEach(() => { + aDomain = new SolutionDomain(); + for (const r of aRanges) { + aDomain = aDomain.addWithOrOperator(r); + } + }); - it('should add a range when the domain is empty', () => { - const domain = new SolutionDomain(); - const aRange = new SolutionRange([0, 1]); + it('should add a range when the domain is empty', () => { + const domain = new SolutionDomain(); + const aRange = new SolutionRange([ 0, 1 ]); - const newDomain = domain.addWithAndOperator(aRange); + const newDomain = domain.addWithAndOperator(aRange); - expect(newDomain.get_domain()).toStrictEqual([aRange]); - }); + expect(newDomain.get_domain()).toStrictEqual([ aRange ]); + }); - it('should return an empty domain if there is no intersection with the new range', () => { - const aRange = new SolutionRange([-200, -100]); + it('should return an empty domain if there is no intersection with the new range', () => { + const aRange = new SolutionRange([ -200, -100 ]); - const newDomain = aDomain.addWithAndOperator(aRange); + const newDomain = aDomain.addWithAndOperator(aRange); - expect(newDomain.get_domain().length).toBe(0); - }); + expect(newDomain.get_domain().length).toBe(0); + }); - it('given a new range that is inside a part of the domain should only return the intersection', () => { - const aRange = new SolutionRange([22, 30]); + it('given a new range that is inside a part of the domain should only return the intersection', () => { + const aRange = new SolutionRange([ 22, 30 ]); - const newDomain = aDomain.addWithAndOperator(aRange); + const newDomain = aDomain.addWithAndOperator(aRange); - expect(newDomain.get_domain()).toStrictEqual([aRange]); - }); + expect(newDomain.get_domain()).toStrictEqual([ aRange ]); + }); - it('given a new range that intersect a part of the domain should only return the intersection', () => { - const aRange = new SolutionRange([19, 25]); + it('given a new range that intersect a part of the domain should only return the intersection', () => { + const aRange = new SolutionRange([ 19, 25 ]); - const expectedDomain = [ - new SolutionRange([21, 25]), - ]; + const expectedDomain = [ + new SolutionRange([ 21, 25 ]), + ]; - const newDomain = aDomain.addWithAndOperator(aRange); + const newDomain = aDomain.addWithAndOperator(aRange); - expect(newDomain.get_domain()).toStrictEqual(expectedDomain); - }); + expect(newDomain.get_domain()).toStrictEqual(expectedDomain); + }); - it('given a new range that intersect multiple part of the domain should only return the intersections', () => { - const aRange = new SolutionRange([-2, 25]); + it('given a new range that intersect multiple part of the domain should only return the intersections', () => { + const aRange = new SolutionRange([ -2, 25 ]); - const expectedDomain = [ - new SolutionRange([-1, 0]), - new SolutionRange([1, 5]), - new SolutionRange([10, 10]), - new SolutionRange([21, 25]), - ]; + const expectedDomain = [ + new SolutionRange([ -1, 0 ]), + new SolutionRange([ 1, 5 ]), + new SolutionRange([ 10, 10 ]), + new SolutionRange([ 21, 25 ]), + ]; - const newDomain = aDomain.addWithAndOperator(aRange); + const newDomain = aDomain.addWithAndOperator(aRange); - expect(newDomain.get_domain()).toStrictEqual(expectedDomain); - }); + expect(newDomain.get_domain()).toStrictEqual(expectedDomain); }); + }); - describe('add', () => { - const aDomain = new SolutionDomain(); - const aRange = new SolutionRange([0, 1]); - - let spyAddWithOrOperator; - - let spyAddWithAndOperator; + describe('add', () => { + const aDomain = new SolutionDomain(); + const aRange = new SolutionRange([ 0, 1 ]); - let spyNotOperation; + let spyAddWithOrOperator; - beforeEach(() => { - spyAddWithOrOperator = jest.spyOn(aDomain, 'addWithOrOperator') - .mockImplementation((_range: SolutionRange) => { - return new SolutionDomain() - }); + let spyAddWithAndOperator; - spyAddWithAndOperator = jest.spyOn(aDomain, 'addWithAndOperator') - .mockImplementation((_range: SolutionRange) => { - return new SolutionDomain() - }); + let spyNotOperation; - spyNotOperation = jest.spyOn(aDomain, 'notOperation') - .mockImplementation(() => { - return new SolutionDomain() - }); + beforeEach(() => { + spyAddWithOrOperator = jest.spyOn(aDomain, 'addWithOrOperator') + .mockImplementation((_range: SolutionRange) => { + return new SolutionDomain(); }); - it('should call "addWithOrOperator" method given the "OR" operator and a new range', () => { - aDomain.add({range:aRange, operator:LogicOperator.Or}) - expect(spyAddWithOrOperator).toBeCalledTimes(1); + spyAddWithAndOperator = jest.spyOn(aDomain, 'addWithAndOperator') + .mockImplementation((_range: SolutionRange) => { + return new SolutionDomain(); }); - it('should throw an error when adding range with a "OR" operator and no new range', () => { - expect(()=>{aDomain.add({operator:LogicOperator.Or})}).toThrow(); + spyNotOperation = jest.spyOn(aDomain, 'notOperation') + .mockImplementation(() => { + return new SolutionDomain(); }); + }); - it('should call "addWithAndOperator" method given the "AND" operator and a new range', () => { - aDomain.add({range:aRange, operator:LogicOperator.And}) - expect(spyAddWithOrOperator).toBeCalledTimes(1); - }); + it('should call "addWithOrOperator" method given the "OR" operator and a new range', () => { + aDomain.add({ range: aRange, operator: LogicOperator.Or }); + expect(spyAddWithOrOperator).toBeCalledTimes(1); + }); - it('should throw an error when adding range with a "AND" operator and no new range', () => { - expect(()=>{aDomain.add({operator:LogicOperator.And})}).toThrow(); - }); + it('should throw an error when adding range with a "OR" operator and no new range', () => { + expect(() => { aDomain.add({ operator: LogicOperator.Or }); }).toThrow(); + }); - - it('should call "notOperation" method given the "NOT" operator', () => { - aDomain.add({operator:LogicOperator.Not}) - expect(spyAddWithOrOperator).toBeCalledTimes(1); - }); + it('should call "addWithAndOperator" method given the "AND" operator and a new range', () => { + aDomain.add({ range: aRange, operator: LogicOperator.And }); + expect(spyAddWithOrOperator).toBeCalledTimes(1); }); - describe('isDomainEmpty', ()=>{ - it('should return true when the domain is empty', ()=>{ - const domain = new SolutionDomain(); + it('should throw an error when adding range with a "AND" operator and no new range', () => { + expect(() => { aDomain.add({ operator: LogicOperator.And }); }).toThrow(); + }); - expect(domain.isDomainEmpty()).toBe(true); - }); + it('should call "notOperation" method given the "NOT" operator', () => { + aDomain.add({ operator: LogicOperator.Not }); + expect(spyAddWithOrOperator).toBeCalledTimes(1); + }); + }); - it('should return false when the domain is not empty', ()=>{ - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0,1])); + describe('isDomainEmpty', () => { + it('should return true when the domain is empty', () => { + const domain = new SolutionDomain(); - expect(domain.isDomainEmpty()).toBe(false); - }); + expect(domain.isDomainEmpty()).toBe(true); }); - describe('clone', ()=>{ - it('should return a deep copy of an existing domain', ()=>{ - let domain = SolutionDomain.newWithInitialValue(new SolutionRange([0,1])); - const clonedDomain = domain.clone(); - domain = domain.addWithOrOperator(new SolutionRange([100,200])); - expect(clonedDomain.get_domain()).not.toStrictEqual(domain.get_domain()); + it('should return false when the domain is not empty', () => { + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([ 0, 1 ])); - }) - }) -}); \ No newline at end of file + expect(domain.isDomainEmpty()).toBe(false); + }); + }); + + describe('clone', () => { + it('should return a deep copy of an existing domain', () => { + let domain = SolutionDomain.newWithInitialValue(new SolutionRange([ 0, 1 ])); + const clonedDomain = domain.clone(); + domain = domain.addWithOrOperator(new SolutionRange([ 100, 200 ])); + expect(clonedDomain.get_domain()).not.toStrictEqual(domain.get_domain()); + }); + }); +}); diff --git a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts index 03bab9221..756a0ac5c 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts @@ -1,312 +1,318 @@ -import {SolutionRange} from '../lib//SolutionRange'; +import { SolutionRange } from '../lib//SolutionRange'; -describe('SolutionRange', ()=>{ - describe('constructor', ()=>{ - it('should have the right parameters when building', ()=>{ - const aRange: [number, number] = [0,1]; - const solutionRange = new SolutionRange(aRange); +describe('SolutionRange', () => { + describe('constructor', () => { + it('should have the right parameters when building', () => { + const aRange: [number, number] = [ 0, 1 ]; + const solutionRange = new SolutionRange(aRange); - expect(solutionRange.lower).toBe(aRange[0]) - expect(solutionRange.upper).toBe(aRange[1]) - - }); - - it('should not throw an error when the domain is unitary', ()=>{ - const aRange: [number, number] = [0,0]; - const solutionRange = new SolutionRange(aRange); + expect(solutionRange.lower).toBe(aRange[0]); + expect(solutionRange.upper).toBe(aRange[1]); + }); - expect(solutionRange.lower).toBe(aRange[0]) - expect(solutionRange.upper).toBe(aRange[1]) + it('should not throw an error when the domain is unitary', () => { + const aRange: [number, number] = [ 0, 0 ]; + const solutionRange = new SolutionRange(aRange); - }); + expect(solutionRange.lower).toBe(aRange[0]); + expect(solutionRange.upper).toBe(aRange[1]); + }); - it('should have throw an error when the first element of the range is greater than the second', ()=>{ - const aRange: [number, number] = [1,0]; - expect(()=>{new SolutionRange(aRange)}).toThrow(RangeError); - }); + it('should have throw an error when the first element of the range is greater than the second', () => { + const aRange: [number, number] = [ 1, 0 ]; + expect(() => { new SolutionRange(aRange); }).toThrow(RangeError); }); + }); - describe('isOverlaping', ()=>{ - it('should return true when the solution range have the same range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + describe('isOverlaping', () => { + it('should return true when the solution range have the same range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [0,100]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ 0, 100 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); - }); + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); + }); - it('should return true when the other range start before the subject range and end inside the subject range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it('should return true when the other range start before the subject range and end inside the subject range', + () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [-1,99]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ -1, 99 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); - }); + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); + }); - it('should return true when the other range start before the subject range and end after the subject range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it('should return true when the other range start before the subject range and end after the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [-1,101]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ -1, 101 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); - }); + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); + }); - it('should return true when the other range start at the subject range and end after the range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it('should return true when the other range start at the subject range and end after the range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [0,101]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ 0, 101 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); - }); + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); + }); - it('should return true when the other range start inside the current range and end inside the current range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it('should return true when the other range start inside the current range and end inside the current range', + () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [1,50]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ 1, 50 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); - }); + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); + }); - it('should return true when the other range start at the end of the subject range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it('should return true when the other range start at the end of the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [100,500]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ 100, 500 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); - }); + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); + }); - it('should return false when the other range is located before the subject range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it('should return false when the other range is located before the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [-50, -20]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ -50, -20 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); - }); + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); + }); - it('should return false when the other range is located after the subject range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it('should return false when the other range is located after the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [101, 200]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ 101, 200 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); - }); + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); }); - describe('isInside', ()=>{ - it('should return true when the other range is inside the subject range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + }); + describe('isInside', () => { + it('should return true when the other range is inside the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [1,50]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ 1, 50 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(true); - }); + expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(true); + }); - it('should return false when the other range is not inside the subject range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it('should return false when the other range is not inside the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [-1,50]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ -1, 50 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(false); - }); + expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(false); }); + }); - describe('fuseRange', ()=>{ - it('given an non overlapping range return both range should return the correct range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + describe('fuseRange', () => { + it('given an non overlapping range return both range should return the correct range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [101, 200]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ 101, 200 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - expect(resp.length).toBe(2); - expect(resp[0]).toStrictEqual(aSolutionRange); - expect(resp[1]).toStrictEqual(aSecondSolutionRange); - }); + expect(resp.length).toBe(2); + expect(resp[0]).toStrictEqual(aSolutionRange); + expect(resp[1]).toStrictEqual(aSecondSolutionRange); + }); - it('given an overlapping range where the solution range have the same range should return the correct range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it('given an overlapping range where the solution range have the same range should return the correct range', + () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [0,100]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ 0, 100 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(aSolutionRange); - expect(resp[0]).toStrictEqual(aSecondSolutionRange); - }); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(aSolutionRange); + expect(resp[0]).toStrictEqual(aSecondSolutionRange); + }); - it('given an overlapping range where the other range start before the subject range and end inside the subject range should return the correct range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it(`given an overlapping range where the other range start before the subject range and end + inside the subject range should return the correct range`, () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [-1,99]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ -1, 99 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - const expectedRange = new SolutionRange([-1, 100]); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); + const expectedRange = new SolutionRange([ -1, 100 ]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); - it('given an overlapping range where the other range start before the subject range and end after the subject range should return the correct range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it(`given an overlapping range where the other range start before the subject range + and end after the subject range should return the correct range`, () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [-1,101]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ -1, 101 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - const expectedRange = new SolutionRange([-1, 101]); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); + const expectedRange = new SolutionRange([ -1, 101 ]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); - it('given an overlapping range where the other range start at the subject range and end after the range should return the correct range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it(`given an overlapping range where the other range start at the subject range and + end after the range should return the correct range`, () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [0,101]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ 0, 101 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - const expectedRange = new SolutionRange([0, 101]); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); + const expectedRange = new SolutionRange([ 0, 101 ]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); - it('given an overlapping range where the other range start inside the current range and end inside the current range should return the correct range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it(`given an overlapping range where the other range start inside the current range and + end inside the current range should return the correct range`, () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [1,50]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ 1, 50 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - const expectedRange = new SolutionRange([0, 100]); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); + const expectedRange = new SolutionRange([ 0, 100 ]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); - it('given an overlapping range where the other range start at the end of the subject range should return the correct range', ()=>{ - const aRange: [number, number] = [0,100]; - const aSolutionRange = new SolutionRange(aRange); + it(`given an overlapping range where the other range start at the end + of the subject range should return the correct range`, () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); - const aSecondRange: [number, number] = [100,500]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); + const aSecondRange: [number, number] = [ 100, 500 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - const expectedRange = new SolutionRange([0, 500]); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); + const expectedRange = new SolutionRange([ 0, 500 ]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); }); + }); - describe('inverse', ()=>{ - it('given an real solution range it should return no range', ()=>{ - const aSolutionRange = new SolutionRange([ - Number.NEGATIVE_INFINITY, - Number.POSITIVE_INFINITY - ]); + describe('inverse', () => { + it('given an real solution range it should return no range', () => { + const aSolutionRange = new SolutionRange([ + Number.NEGATIVE_INFINITY, + Number.POSITIVE_INFINITY, + ]); - expect(aSolutionRange.inverse().length).toBe(0); - }); + expect(aSolutionRange.inverse().length).toBe(0); + }); - it('given a range with an infinite upper bound should return a new range', ()=>{ - const aSolutionRange = new SolutionRange([ - 21, - Number.POSITIVE_INFINITY - ]); + it('given a range with an infinite upper bound should return a new range', () => { + const aSolutionRange = new SolutionRange([ + 21, + Number.POSITIVE_INFINITY, + ]); - const expectedRange = new SolutionRange([Number.NEGATIVE_INFINITY, 21- Number.EPSILON]); + const expectedRange = new SolutionRange([ Number.NEGATIVE_INFINITY, 21 - Number.EPSILON ]); - const resp = aSolutionRange.inverse(); + const resp = aSolutionRange.inverse(); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); - it('given a range with an infinite lower bound should return a new range',()=>{ - const aSolutionRange = new SolutionRange([ - Number.NEGATIVE_INFINITY, - -21 - ]); + it('given a range with an infinite lower bound should return a new range', () => { + const aSolutionRange = new SolutionRange([ + Number.NEGATIVE_INFINITY, + -21, + ]); - const expectedRange = new SolutionRange([-21 + Number.EPSILON, Number.POSITIVE_INFINITY]); + const expectedRange = new SolutionRange([ -21 + Number.EPSILON, Number.POSITIVE_INFINITY ]); - const resp = aSolutionRange.inverse(); + const resp = aSolutionRange.inverse(); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedRange); + }); - it('given a range that is not unitary and doesn\t have infinite bound should return 2 ranges',()=>{ - const aSolutionRange = new SolutionRange([ - -33, - 21 - ]); + it('given a range that is not unitary and doesn\t have infinite bound should return 2 ranges', () => { + const aSolutionRange = new SolutionRange([ + -33, + 21, + ]); - const expectedRange = [ - new SolutionRange([Number.NEGATIVE_INFINITY, -33 - Number.EPSILON]), - new SolutionRange([21+ Number.EPSILON, Number.POSITIVE_INFINITY]), - ]; + const expectedRange = [ + new SolutionRange([ Number.NEGATIVE_INFINITY, -33 - Number.EPSILON ]), + new SolutionRange([ 21 + Number.EPSILON, Number.POSITIVE_INFINITY ]), + ]; - const resp = aSolutionRange.inverse(); + const resp = aSolutionRange.inverse(); - expect(resp.length).toBe(2); - expect(resp).toStrictEqual(expectedRange); - }); + expect(resp.length).toBe(2); + expect(resp).toStrictEqual(expectedRange); }); + }); - describe('getIntersection', ()=>{ - it('given two range that doesn\'t overlap should return no intersection', ()=>{ - const aSolutionRange = new SolutionRange([0, 20]); - const aSecondSolutionRange = new SolutionRange([30, 40]); + describe('getIntersection', () => { + it('given two range that doesn\'t overlap should return no intersection', () => { + const aSolutionRange = new SolutionRange([ 0, 20 ]); + const aSecondSolutionRange = new SolutionRange([ 30, 40 ]); - expect(SolutionRange.getIntersection(aSolutionRange, aSecondSolutionRange)).toBeUndefined(); - }); + expect(SolutionRange.getIntersection(aSolutionRange, aSecondSolutionRange)).toBeUndefined(); + }); - it('given two range when one is inside the other should return the range at the inside', ()=>{ - const aSolutionRange = new SolutionRange([0, 20]); - const aSecondSolutionRange = new SolutionRange([5, 10]); + it('given two range when one is inside the other should return the range at the inside', () => { + const aSolutionRange = new SolutionRange([ 0, 20 ]); + const aSecondSolutionRange = new SolutionRange([ 5, 10 ]); - expect(SolutionRange.getIntersection(aSolutionRange, aSecondSolutionRange)).toStrictEqual(aSecondSolutionRange); - }); + expect(SolutionRange.getIntersection(aSolutionRange, aSecondSolutionRange)).toStrictEqual(aSecondSolutionRange); + }); - it('given two range when they overlap should return the intersection', ()=>{ - const aSolutionRange = new SolutionRange([0, 20]); - const aSecondSolutionRange = new SolutionRange([5, 80]); + it('given two range when they overlap should return the intersection', () => { + const aSolutionRange = new SolutionRange([ 0, 20 ]); + const aSecondSolutionRange = new SolutionRange([ 5, 80 ]); - const expectedIntersection = new SolutionRange([5, 20]); + const expectedIntersection = new SolutionRange([ 5, 20 ]); - expect(SolutionRange.getIntersection(aSolutionRange, aSecondSolutionRange)).toStrictEqual(expectedIntersection); - }); + expect(SolutionRange.getIntersection(aSolutionRange, aSecondSolutionRange)).toStrictEqual(expectedIntersection); }); -}); \ No newline at end of file + }); +}); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 55f61bc34..24c4f7e34 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1,1591 +1,1662 @@ -import { - filterOperatorToSparqlRelationOperator, - isSparqlOperandNumberType, - castSparqlRdfTermIntoNumber, - getSolutionRange, - areTypesCompatible, - convertTreeRelationToSolverExpression, - resolveSolutionDomainWithAnExpression, - createEquationSystem, - resolveSolutionDomainEquationSystem, - resolveAFilterTerm, - recursifFilterExpressionToSolverExpression, - isRelationFilterExpressionDomainEmpty -} from '../lib/solver'; -import { SolutionRange } from '../lib/SolutionRange'; -import { ITreeRelation, SparqlRelationOperator } from '@comunica/types-link-traversal'; -import { SparqlOperandDataTypes, SolverExpression, SolverExpressionRange, LogicOperator, SolverEquationSystem, Variable } from '../lib/solverInterfaces'; +import type { ITreeRelation } from '@comunica/types-link-traversal'; +import { SparqlRelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; -import { SolutionDomain } from '../lib/SolutionDomain'; -import { LinkOperator } from '../lib/LinkOperator'; import { Algebra, translate } from 'sparqlalgebrajs'; - +import { LinkOperator } from '../lib/LinkOperator'; +import { SolutionDomain } from '../lib/SolutionDomain'; +import { SolutionRange } from '../lib/SolutionRange'; +import { + filterOperatorToSparqlRelationOperator, + isSparqlOperandNumberType, + castSparqlRdfTermIntoNumber, + getSolutionRange, + areTypesCompatible, + convertTreeRelationToSolverExpression, + resolveSolutionDomainWithAnExpression, + createEquationSystem, + resolveSolutionDomainEquationSystem, + resolveAFilterTerm, + recursifFilterExpressionToSolverExpression, + isRelationFilterExpressionDomainEmpty, +} from '../lib/solver'; +import { SparqlOperandDataTypes, LogicOperator } from '../lib/solverInterfaces'; +import type { ISolverExpression, + ISolverExpressionRange, + SolverEquationSystem, + Variable } from '../lib/solverInterfaces'; const DF = new DataFactory(); describe('solver function', () => { - describe('filterOperatorToSparqlRelationOperator', () => { - it('should return the RelationOperator given a string representation', () => { - const testTable: [string, SparqlRelationOperator][] = [ - ['=', SparqlRelationOperator.EqualThanRelation], - ['<', SparqlRelationOperator.LessThanRelation], - ['<=', SparqlRelationOperator.LessThanOrEqualToRelation], - ['>', SparqlRelationOperator.GreaterThanRelation], - ['>=', SparqlRelationOperator.GreaterThanOrEqualToRelation] - ]; - - for (const [value, expectedAnswer] of testTable) { - expect(filterOperatorToSparqlRelationOperator(value)).toBe(expectedAnswer); - } - }); - it('should return undefined given a string not representing a RelationOperator', () => { - expect(filterOperatorToSparqlRelationOperator("foo")).toBeUndefined(); - }); + describe('filterOperatorToSparqlRelationOperator', () => { + it('should return the RelationOperator given a string representation', () => { + const testTable: [string, SparqlRelationOperator][] = [ + [ '=', SparqlRelationOperator.EqualThanRelation ], + [ '<', SparqlRelationOperator.LessThanRelation ], + [ '<=', SparqlRelationOperator.LessThanOrEqualToRelation ], + [ '>', SparqlRelationOperator.GreaterThanRelation ], + [ '>=', SparqlRelationOperator.GreaterThanOrEqualToRelation ], + ]; + + for (const [ value, expectedAnswer ] of testTable) { + expect(filterOperatorToSparqlRelationOperator(value)).toBe(expectedAnswer); + } + }); + it('should return undefined given a string not representing a RelationOperator', () => { + expect(filterOperatorToSparqlRelationOperator('foo')).toBeUndefined(); + }); + }); + + describe('isSparqlOperandNumberType', () => { + it('should return true when a SparqlOperandDataTypes is a number type', () => { + const testTable: SparqlOperandDataTypes[] = [ + SparqlOperandDataTypes.Integer, + SparqlOperandDataTypes.NonPositiveInteger, + SparqlOperandDataTypes.NegativeInteger, + SparqlOperandDataTypes.Long, + SparqlOperandDataTypes.Short, + SparqlOperandDataTypes.NonNegativeInteger, + SparqlOperandDataTypes.UnsignedLong, + SparqlOperandDataTypes.UnsignedInt, + SparqlOperandDataTypes.UnsignedShort, + SparqlOperandDataTypes.PositiveInteger, + SparqlOperandDataTypes.Double, + SparqlOperandDataTypes.Float, + SparqlOperandDataTypes.Decimal, + ]; + + for (const value of testTable) { + expect(isSparqlOperandNumberType(value)).toBe(true); + } + }); + + it('should return false when a SparqlOperandDataTypes is not a number', () => { + const testTable: SparqlOperandDataTypes[] = [ + SparqlOperandDataTypes.String, + SparqlOperandDataTypes.Boolean, + SparqlOperandDataTypes.DateTime, + SparqlOperandDataTypes.Byte, + ]; + + for (const value of testTable) { + expect(isSparqlOperandNumberType(value)).toBe(false); + } + }); + }); + + describe('castSparqlRdfTermIntoNumber', () => { + it('should return the expected number when given an integer', () => { + const testTable: [string, SparqlOperandDataTypes, number][] = [ + [ '19273', SparqlOperandDataTypes.Integer, 19_273 ], + [ '0', SparqlOperandDataTypes.NonPositiveInteger, 0 ], + [ '-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459 ], + [ '121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321 ], + [ '1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213 ], + [ '283', SparqlOperandDataTypes.NonNegativeInteger, 283 ], + [ '-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831 ], + [ '-1234', SparqlOperandDataTypes.UnsignedInt, -1_234 ], + [ '-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234 ], + [ '1234', SparqlOperandDataTypes.PositiveInteger, 1_234 ], + ]; + + for (const [ value, valueType, expectedNumber ] of testTable) { + expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); + } }); - describe('isSparqlOperandNumberType', () => { - it('should return true when a SparqlOperandDataTypes is a number type', () => { - const testTable: SparqlOperandDataTypes[] = [ - SparqlOperandDataTypes.Integer, - SparqlOperandDataTypes.NonPositiveInteger, - SparqlOperandDataTypes.NegativeInteger, - SparqlOperandDataTypes.Long, - SparqlOperandDataTypes.Short, - SparqlOperandDataTypes.NonNegativeInteger, - SparqlOperandDataTypes.UnsignedLong, - SparqlOperandDataTypes.UnsignedInt, - SparqlOperandDataTypes.UnsignedShort, - SparqlOperandDataTypes.PositiveInteger, - SparqlOperandDataTypes.Double, - SparqlOperandDataTypes.Float, - SparqlOperandDataTypes.Decimal, - ]; - - for (const value of testTable) { - expect(isSparqlOperandNumberType(value)).toBe(true); - } - }); - - it('should return false when a SparqlOperandDataTypes is not a number', () => { - const testTable: SparqlOperandDataTypes[] = [ - SparqlOperandDataTypes.String, - SparqlOperandDataTypes.Boolean, - SparqlOperandDataTypes.DateTime, - SparqlOperandDataTypes.Byte - ]; - - for (const value of testTable) { - expect(isSparqlOperandNumberType(value)).toBe(false); - } - }); + it('should return undefined if a non integer is pass with SparqlOperandDataTypes integer compatible type', () => { + const testTable: [string, SparqlOperandDataTypes][] = [ + [ '1.6751', SparqlOperandDataTypes.Integer ], + [ 'asbd', SparqlOperandDataTypes.PositiveInteger ], + [ '', SparqlOperandDataTypes.NegativeInteger ], + ]; + for (const [ value, valueType ] of testTable) { + expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); + } }); - describe('castSparqlRdfTermIntoNumber', () => { - it('should return the expected number when given an integer', () => { - const testTable: [string, SparqlOperandDataTypes, number][] = [ - ['19273', SparqlOperandDataTypes.Integer, 19273], - ['0', SparqlOperandDataTypes.NonPositiveInteger, 0], - ['-12313459', SparqlOperandDataTypes.NegativeInteger, -12313459], - ['121312321321321321', SparqlOperandDataTypes.Long, 121312321321321321], - ['1213123213213213', SparqlOperandDataTypes.Short, 1213123213213213], - ['283', SparqlOperandDataTypes.NonNegativeInteger, 283], - ['-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12131293912831], - ['-1234', SparqlOperandDataTypes.UnsignedInt, -1234], - ['-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123341231231234], - ['1234', SparqlOperandDataTypes.PositiveInteger, 1234] - ]; - - for (const [value, valueType, expectedNumber] of testTable) { - expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); - } - }); - - it('should return undefined if a non integer is pass with SparqlOperandDataTypes integer compatible type', () => { - const testTable: [string, SparqlOperandDataTypes][] = [ - ['1.6751', SparqlOperandDataTypes.Integer], - ['asbd', SparqlOperandDataTypes.PositiveInteger], - ['', SparqlOperandDataTypes.NegativeInteger] - ]; - - for (const [value, valueType] of testTable) { - expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); - } - }); - - it('should return the expected number when given an decimal', () => { - const testTable: [string, SparqlOperandDataTypes, number][] = [ - ['1.1', SparqlOperandDataTypes.Decimal, 1.1], - ['2132131.121321321421', SparqlOperandDataTypes.Float, 2132131.121321321421], - ['1234.123123132132143423424235324324', SparqlOperandDataTypes.Double, 1234.123123132132143423424235324324], - ]; - - for (const [value, valueType, expectedNumber] of testTable) { - expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); - } - }); - - it('should return the expected unix time given a date time', () => { - const value = '1994-11-05T13:15:30Z'; - const expectedUnixTime = 784041330000; - - expect(castSparqlRdfTermIntoNumber( - value, - SparqlOperandDataTypes.DateTime - )).toBe(expectedUnixTime); - }); - - it('should return undefined given a non date time', () => { - const value = '1994-11-T13:15:30'; - - expect(castSparqlRdfTermIntoNumber( - value, - SparqlOperandDataTypes.DateTime - )).toBeUndefined(); - }); + it('should return the expected number when given an decimal', () => { + const testTable: [string, SparqlOperandDataTypes, number][] = [ + [ '1.1', SparqlOperandDataTypes.Decimal, 1.1 ], + [ '2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321 ], + [ '1234.123', SparqlOperandDataTypes.Double, 1_234.123 ], + ]; + + for (const [ value, valueType, expectedNumber ] of testTable) { + expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); + } }); - describe('getSolutionRange', () => { - it('given a boolean compatible RelationOperator and a value should return a valid SolutionRange', () => { - const value = -1; - const testTable: [SparqlRelationOperator, SolutionRange][] = [ - [ - SparqlRelationOperator.GreaterThanRelation, - new SolutionRange([value + Number.EPSILON, Number.POSITIVE_INFINITY]) - ], - [ - SparqlRelationOperator.GreaterThanOrEqualToRelation, - new SolutionRange([value, Number.POSITIVE_INFINITY]) - ], - [ - SparqlRelationOperator.EqualThanRelation, - new SolutionRange([value, value]) - ], - [ - SparqlRelationOperator.LessThanRelation, - new SolutionRange([Number.NEGATIVE_INFINITY, value - Number.EPSILON]) - ], - [ - SparqlRelationOperator.LessThanOrEqualToRelation, - new SolutionRange([Number.NEGATIVE_INFINITY, value]) - ] - ]; - - for (const [operator, expectedRange] of testTable) { - expect(getSolutionRange(value, operator)).toStrictEqual(expectedRange); - } - }); - - it('should return undefined given an RelationOperator that is not boolean compatible', () => { - const value = -1; - const operator = SparqlRelationOperator.PrefixRelation; - - expect(getSolutionRange(value, operator)).toBeUndefined(); - }); + it('should return the expected unix time given a date time', () => { + const value = '1994-11-05T13:15:30Z'; + const expectedUnixTime = 784_041_330_000; + + expect(castSparqlRdfTermIntoNumber( + value, + SparqlOperandDataTypes.DateTime, + )).toBe(expectedUnixTime); }); - describe('areTypesCompatible', () => { - it('given expression with identical value type should return true', () => { - const expressions: SolverExpression[] = [ - { - variable: "a", - rawValue: "true", - valueType: SparqlOperandDataTypes.Boolean, - valueAsNumber: 1, - operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [] - }, - { - variable: "a", - rawValue: "false", - valueType: SparqlOperandDataTypes.Boolean, - valueAsNumber: 0, - operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [] - }, - { - variable: "a", - rawValue: "false", - valueType: SparqlOperandDataTypes.Boolean, - valueAsNumber: 0, - operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [] - } - ]; - - expect(areTypesCompatible(expressions)).toBe(true); - }); - - it('should return true when all the types are numbers', () => { - const expressions: SolverExpression[] = [ - { - variable: "a", - rawValue: "true", - valueType: SparqlOperandDataTypes.Int, - valueAsNumber: 1, - operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [] - }, - { - variable: "a", - rawValue: "false", - valueType: SparqlOperandDataTypes.NonNegativeInteger, - valueAsNumber: 0, - operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [] - }, - { - variable: "a", - rawValue: "false", - valueType: SparqlOperandDataTypes.Decimal, - valueAsNumber: 0, - operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [] - } - ]; - - expect(areTypesCompatible(expressions)).toBe(true); - }); - - it('should return false when one type is not identical', () => { - const expressions: SolverExpression[] = [ - { - variable: "a", - rawValue: "true", - valueType: SparqlOperandDataTypes.Boolean, - valueAsNumber: 1, - operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [] - }, - { - variable: "a", - rawValue: "false", - valueType: SparqlOperandDataTypes.Boolean, - valueAsNumber: 0, - operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [] - }, - { - variable: "a", - rawValue: "false", - valueType: SparqlOperandDataTypes.Byte, - valueAsNumber: 0, - operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [] - } - ]; - - expect(areTypesCompatible(expressions)).toBe(false); - }); - - it('should return false when one type is not identical and the other are number', () => { - const expressions: SolverExpression[] = [ - { - variable: "a", - rawValue: "true", - valueType: SparqlOperandDataTypes.UnsignedInt, - valueAsNumber: 1, - operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [] - }, - { - variable: "a", - rawValue: "false", - valueType: SparqlOperandDataTypes.Float, - valueAsNumber: 0, - operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [] - }, - { - variable: "a", - rawValue: "false", - valueType: SparqlOperandDataTypes.Byte, - valueAsNumber: 0, - operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [] - } - ]; - - expect(areTypesCompatible(expressions)).toBe(false); - }); + it('should return undefined given a non date time', () => { + const value = '1994-11-T13:15:30'; + + expect(castSparqlRdfTermIntoNumber( + value, + SparqlOperandDataTypes.DateTime, + )).toBeUndefined(); + }); + }); + + describe('getSolutionRange', () => { + it('given a boolean compatible RelationOperator and a value should return a valid SolutionRange', () => { + const value = -1; + const testTable: [SparqlRelationOperator, SolutionRange][] = [ + [ + SparqlRelationOperator.GreaterThanRelation, + new SolutionRange([ value + Number.EPSILON, Number.POSITIVE_INFINITY ]), + ], + [ + SparqlRelationOperator.GreaterThanOrEqualToRelation, + new SolutionRange([ value, Number.POSITIVE_INFINITY ]), + ], + [ + SparqlRelationOperator.EqualThanRelation, + new SolutionRange([ value, value ]), + ], + [ + SparqlRelationOperator.LessThanRelation, + new SolutionRange([ Number.NEGATIVE_INFINITY, value - Number.EPSILON ]), + ], + [ + SparqlRelationOperator.LessThanOrEqualToRelation, + new SolutionRange([ Number.NEGATIVE_INFINITY, value ]), + ], + ]; + + for (const [ operator, expectedRange ] of testTable) { + expect(getSolutionRange(value, operator)).toStrictEqual(expectedRange); + } }); - describe('convertTreeRelationToSolverExpression', () => { - it('given a TREE relation with all the parameters should return a valid expression', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) - }, - node: "https://www.example.be" - }; - const variable = "x"; - - const expectedExpression: SolverExpression = { - variable: variable, - rawValue: '5', - valueType: SparqlOperandDataTypes.Integer, - valueAsNumber: 5, - chainOperator: [], - operator: SparqlRelationOperator.EqualThanRelation - }; - - expect(convertTreeRelationToSolverExpression(relation, variable)).toStrictEqual(expectedExpression); - }); - - it('should return undefined given a relation witn a value term containing an unknowed value type', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')) - }, - node: "https://www.example.be" - }; - const variable = "x"; - - expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - - it('should return undefined given a relation with a term containing an incompatible value in relation to its value type', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "a", - term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) - }, - node: "https://www.example.be" - }; - const variable = "x"; - - expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - - it('should return undefined given a relation without a value and a type', () => { - const relation: ITreeRelation = { - remainingItems: 10, - path: "ex:path", - node: "https://www.example.be" - }; - const variable = "x"; - - expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - - it('should return undefined given a relation without a value', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - node: "https://www.example.be" - }; - const variable = "x"; - - expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - - it('should return undefined given a relation without a type', () => { - const relation: ITreeRelation = { - remainingItems: 10, - path: "ex:path", - value: { - value: "a", - term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) - }, - node: "https://www.example.be" - }; - const variable = "x"; - - expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); + it('should return undefined given an RelationOperator that is not boolean compatible', () => { + const value = -1; + const operator = SparqlRelationOperator.PrefixRelation; + + expect(getSolutionRange(value, operator)).toBeUndefined(); + }); + }); + + describe('areTypesCompatible', () => { + it('given expression with identical value type should return true', () => { + const expressions: ISolverExpression[] = [ + { + variable: 'a', + rawValue: 'true', + valueType: SparqlOperandDataTypes.Boolean, + valueAsNumber: 1, + operator: SparqlRelationOperator.EqualThanRelation, + chainOperator: [], + }, + { + variable: 'a', + rawValue: 'false', + valueType: SparqlOperandDataTypes.Boolean, + valueAsNumber: 0, + operator: SparqlRelationOperator.EqualThanRelation, + chainOperator: [], + }, + { + variable: 'a', + rawValue: 'false', + valueType: SparqlOperandDataTypes.Boolean, + valueAsNumber: 0, + operator: SparqlRelationOperator.EqualThanRelation, + chainOperator: [], + }, + ]; + + expect(areTypesCompatible(expressions)).toBe(true); }); - describe('resolveSolutionDomainWithAnExpression', () => { - it('given an empty domain and an equation with 2 operation chained that are not "NOT" should return a valid new domain and the last chained operator', () => { - const domain = new SolutionDomain(); - const equation: SolverExpressionRange = { - chainOperator: [ - new LinkOperator(LogicOperator.And), - new LinkOperator(LogicOperator.Or), - ], - solutionDomain: new SolutionRange([0, 1]) - }; - - const expectedDomain = SolutionDomain.newWithInitialValue(equation.solutionDomain); - const expectedLastLogicalOperator = equation.chainOperator.at(-2)?.toString(); - - const resp = resolveSolutionDomainWithAnExpression(equation, domain); - if (resp) { - const [respDomain, respLastLogicalOperator] = resp; - expect(respDomain).toStrictEqual(expectedDomain); - expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); - } else { - expect(resp).toBeDefined(); - } - }); - - it('given a domain and an equation with multiple chained that are not "NOT" should return a valid new domain and the next chained operator', () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); - const equation: SolverExpressionRange = { - chainOperator: [ - new LinkOperator(LogicOperator.And), - new LinkOperator(LogicOperator.Or), - new LinkOperator(LogicOperator.Or), - new LinkOperator(LogicOperator.And), - new LinkOperator(LogicOperator.Or), - ], - solutionDomain: new SolutionRange([100, 221.3]) - }; - - const expectedDomain = domain.add({ range: equation.solutionDomain, operator: equation.chainOperator.at(-1)?.operator }); - const expectedLastLogicalOperator = equation.chainOperator.at(-2)?.toString(); - - const resp = resolveSolutionDomainWithAnExpression(equation, domain); - if (resp) { - const [respDomain, respLastLogicalOperator] = resp; - expect(respDomain).toStrictEqual(expectedDomain); - expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); - } else { - expect(resp).toBeDefined(); - } - }); - - it('given a domain and an equation one chained operation should return a valid new domain and an empty string has the next chained operator', () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); - const equation: SolverExpressionRange = { - chainOperator: [ - new LinkOperator(LogicOperator.Or), - ], - solutionDomain: new SolutionRange([100, 221.3]) - }; - - const expectedDomain = domain.add({ range: equation.solutionDomain, operator: equation.chainOperator.at(-1)?.operator }); - const expectedLastLogicalOperator = ""; - - const resp = resolveSolutionDomainWithAnExpression(equation, domain); - if (resp) { - const [respDomain, respLastLogicalOperator] = resp; - expect(respDomain).toStrictEqual(expectedDomain); - expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); - } else { - expect(resp).toBeDefined(); - } - }); - - it('given a domain and an equation with multiple chainned operator where the later elements are "NOT" operators and the last element an "AND" operator should return a valid domain and the next last operator', () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); - const equation: SolverExpressionRange = { - chainOperator: [ - new LinkOperator(LogicOperator.And), - new LinkOperator(LogicOperator.Not), - new LinkOperator(LogicOperator.Not), - new LinkOperator(LogicOperator.Or), - ], - solutionDomain: new SolutionRange([100, 221.3]) - }; - - let expectedDomain = domain.add({ range: equation.solutionDomain, operator: equation.chainOperator.at(-1)?.operator }); - expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); - expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); - - const expectedLastLogicalOperator = equation.chainOperator[0].toString(); - - const resp = resolveSolutionDomainWithAnExpression(equation, domain); - if (resp) { - const [respDomain, respLastLogicalOperator] = resp; - expect(respDomain).toStrictEqual(expectedDomain); - expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); - } else { - expect(resp).toBeDefined(); - } - }); - - it('given a domain and an equation with multiple chainned operator where the last elements are "NOT" operators should return a valid domain and an empty string as the next operator', () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); - const equation: SolverExpressionRange = { - chainOperator: [ - new LinkOperator(LogicOperator.Not), - new LinkOperator(LogicOperator.Not), - new LinkOperator(LogicOperator.Or), - ], - solutionDomain: new SolutionRange([100, 221.3]) - }; - - let expectedDomain = domain.add({ range: equation.solutionDomain, operator: equation.chainOperator.at(-1)?.operator }); - expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); - expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); - - const expectedLastLogicalOperator = ""; - - const resp = resolveSolutionDomainWithAnExpression(equation, domain); - if (resp) { - const [respDomain, respLastLogicalOperator] = resp; - expect(respDomain).toStrictEqual(expectedDomain); - expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); - } else { - expect(resp).toBeDefined(); - } - }); - - it('given an empty domain and an equation with no chained operation should return undefined', () => { - const domain = new SolutionDomain(); - const equation: SolverExpressionRange = { - chainOperator: [], - solutionDomain: new SolutionRange([0, 1]) - }; - - expect(resolveSolutionDomainWithAnExpression(equation, domain)).toBeUndefined(); - - }); + it('should return true when all the types are numbers', () => { + const expressions: ISolverExpression[] = [ + { + variable: 'a', + rawValue: 'true', + valueType: SparqlOperandDataTypes.Int, + valueAsNumber: 1, + operator: SparqlRelationOperator.EqualThanRelation, + chainOperator: [], + }, + { + variable: 'a', + rawValue: 'false', + valueType: SparqlOperandDataTypes.NonNegativeInteger, + valueAsNumber: 0, + operator: SparqlRelationOperator.EqualThanRelation, + chainOperator: [], + }, + { + variable: 'a', + rawValue: 'false', + valueType: SparqlOperandDataTypes.Decimal, + valueAsNumber: 0, + operator: SparqlRelationOperator.EqualThanRelation, + chainOperator: [], + }, + ]; + + expect(areTypesCompatible(expressions)).toBe(true); }); - describe('createEquationSystem', () => { - it('given multiple equations that are consistent with one and another should return a valid equation system and the first equation to resolve', () => { - const lastOperator = new LinkOperator(LogicOperator.And); - const firstOperator = new LinkOperator(LogicOperator.Or); - const secondOperator = new LinkOperator(LogicOperator.Or); - const thirdOperator = new LinkOperator(LogicOperator.Not); - const aVariable = 'x'; - const aRawValue = '1'; - const aValueType = SparqlOperandDataTypes.Int; - const avalueAsNumber = 1; - const anOperator = SparqlRelationOperator.EqualThanRelation; - - const operationTemplate = (c: LinkOperator[]): SolverExpression => { - return { - variable: aVariable, - rawValue: aRawValue, - valueType: aValueType, - valueAsNumber: avalueAsNumber, - operator: anOperator, - chainOperator: c - } - }; - - const firstOperation = operationTemplate([firstOperator]); - const firstEquation: SolverExpressionRange = { chainOperator: firstOperation.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; - const secondOperation = operationTemplate([firstOperator, secondOperator]); - const secondEquation: SolverExpressionRange = { chainOperator: secondOperation.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; - const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); - const thirdEquation: SolverExpressionRange = { chainOperator: thirdOperation.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; - const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - const expectedFirstEquation1: SolverExpressionRange = { chainOperator: lastOperation1.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; - const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - const expectedFirstEquation2: SolverExpressionRange = { chainOperator: lastOperation2.chainOperator, solutionDomain: new SolutionRange([1, 1]) }; - - const equations: SolverExpression[] = [ - firstOperation, - secondOperation, - thirdOperation, - lastOperation1, - lastOperation2 - ] - equations.sort(() => Math.random() - 0.5); - - const expectedEquationSystem: SolverEquationSystem = new Map([ - [firstOperator.toString(), firstEquation], - [secondOperator.toString(), secondEquation], - [thirdOperator.toString(), thirdEquation] - ]); - - const resp = createEquationSystem(equations); - if(!Array.isArray(resp)){ - fail('should return an array'); - } - else if (resp) { - const [respEquationSystem, [respFirstEquation1, respFirstEquation2]] = resp; - expect(respEquationSystem).toStrictEqual(expectedEquationSystem); - expect(respFirstEquation1).toStrictEqual(expectedFirstEquation1); - expect(respFirstEquation2).toStrictEqual(expectedFirstEquation2); - } else { - expect(resp).toBeDefined(); - } - - }); - - it('given multiples equations where it is not possible to get the solution range of an equation it should return undefined', () => { - const lastOperator = new LinkOperator(LogicOperator.And); - const firstOperator = new LinkOperator(LogicOperator.Or); - const secondOperator = new LinkOperator(LogicOperator.Or); - const thirdOperator = new LinkOperator(LogicOperator.Not); - const aVariable = 'x'; - const aRawValue = '1'; - const aValueType = SparqlOperandDataTypes.Int; - const avalueAsNumber = 1; - const anOperator = SparqlRelationOperator.EqualThanRelation; - - const operationTemplate = (c: LinkOperator[]): SolverExpression => { - return { - variable: aVariable, - rawValue: aRawValue, - valueType: aValueType, - valueAsNumber: avalueAsNumber, - operator: anOperator, - chainOperator: c - } - }; - - const firstOperation = { - variable: aVariable, - rawValue: aRawValue, - valueType: aValueType, - valueAsNumber: avalueAsNumber, - operator: SparqlRelationOperator.GeospatiallyContainsRelation, - chainOperator: [firstOperator] - }; - const secondOperation = operationTemplate([firstOperator, secondOperator]); - const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); - const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - - const equations: SolverExpression[] = [ - firstOperation, - secondOperation, - thirdOperation, - lastOperation1, - lastOperation2 - ]; - equations.sort(() => Math.random() - 0.5); - - expect(createEquationSystem(equations)).toBeUndefined(); - }); - - it('given multiples equations where there is multiple equation that could be the first equation to be resolved it should return undefined', () => { - const lastOperator = new LinkOperator(LogicOperator.And); - const firstOperator = new LinkOperator(LogicOperator.Or); - const secondOperator = new LinkOperator(LogicOperator.Or); - const thirdOperator = new LinkOperator(LogicOperator.Not); - const aVariable = 'x'; - const aRawValue = '1'; - const aValueType = SparqlOperandDataTypes.Int; - const avalueAsNumber = 1; - const anOperator = SparqlRelationOperator.EqualThanRelation; - - const operationTemplate = (c: LinkOperator[]): SolverExpression => { - return { - variable: aVariable, - rawValue: aRawValue, - valueType: aValueType, - valueAsNumber: avalueAsNumber, - operator: anOperator, - chainOperator: c - } - }; - - const firstOperation = operationTemplate([firstOperator]); - const secondOperation = operationTemplate([firstOperator, secondOperator]); - const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); - const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - const lastOperation3 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - - const equations: SolverExpression[] = [ - firstOperation, - secondOperation, - thirdOperation, - lastOperation1, - lastOperation2, - lastOperation3 - ]; - equations.sort(() => Math.random() - 0.5); - - expect(createEquationSystem(equations)).toBeUndefined(); - }); - - it('given multiples equations where there is no first equation to be resolved should return undefined', () => { - const lastOperator = new LinkOperator(LogicOperator.And); - const firstOperator = new LinkOperator(LogicOperator.Or); - const secondOperator = new LinkOperator(LogicOperator.Or); - const thirdOperator = new LinkOperator(LogicOperator.Not); - const aVariable = 'x'; - const aRawValue = '1'; - const aValueType = SparqlOperandDataTypes.Int; - const avalueAsNumber = 1; - const anOperator = SparqlRelationOperator.EqualThanRelation; - - const operationTemplate = (c: LinkOperator[]): SolverExpression => { - return { - variable: aVariable, - rawValue: aRawValue, - valueType: aValueType, - valueAsNumber: avalueAsNumber, - operator: anOperator, - chainOperator: c - } - }; - - const firstOperation = { - variable: aVariable, - rawValue: aRawValue, - valueType: aValueType, - valueAsNumber: avalueAsNumber, - operator: SparqlRelationOperator.GeospatiallyContainsRelation, - chainOperator: [firstOperator] - }; - const secondOperation = operationTemplate([firstOperator, secondOperator]); - const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); - const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - - const equations: SolverExpression[] = [ - firstOperation, - secondOperation, - thirdOperation, - lastOperation1, - ]; - equations.sort(() => Math.random() - 0.5); - - expect(createEquationSystem(equations)).toBeUndefined(); - }); - - it('given an equation should return a valid system of equation', ()=>{ - const equation: SolverExpression[] = [ - { - chainOperator:[], - operator: SparqlRelationOperator.EqualThanRelation, - rawValue:'88', - valueAsNumber:88, - valueType: SparqlOperandDataTypes.Int, - variable:'x' - } - ]; - - const expectedEquationSystem: SolverExpressionRange = { - chainOperator: [], - solutionDomain: new SolutionRange([88,88]) - }; - - const resp = createEquationSystem(equation); - if (Array.isArray(resp)){ - fail('should not return an array'); - } - else if (resp && !Array.isArray(resp)) { - expect(resp).toStrictEqual(expectedEquationSystem); - } else { - expect(resp).toBeDefined(); - } - - }); - - it('given two equations should return a valid system of equation', ()=>{ - const lastOperator = new LinkOperator(LogicOperator.And); - - const equation: SolverExpression[] = [ - { - chainOperator:[lastOperator], - operator: SparqlRelationOperator.EqualThanRelation, - rawValue:'88', - valueAsNumber:88, - valueType: SparqlOperandDataTypes.Int, - variable:'x' - }, - { - chainOperator:[lastOperator], - operator: SparqlRelationOperator.EqualThanRelation, - rawValue:'33', - valueAsNumber:33, - valueType: SparqlOperandDataTypes.Int, - variable:'x' - } - ]; - - const expectedFirstEquation1: SolverExpressionRange = { - chainOperator: [lastOperator], - solutionDomain: new SolutionRange([88,88]) - }; - const expectedFirstEquation2: SolverExpressionRange = { - chainOperator: [lastOperator], - solutionDomain: new SolutionRange([33,33]) - }; - - const resp = createEquationSystem(equation); - if (!Array.isArray(resp)){ - fail('should return an array'); - } - else if (resp && Array.isArray(resp)) { - const [respEquationSystem, [respFirstEquation1, respFirstEquation2]] = resp; - expect(respEquationSystem.size).toBe(0); - expect(respFirstEquation1).toStrictEqual(expectedFirstEquation1); - expect(respFirstEquation2).toStrictEqual(expectedFirstEquation2); - - } else { - expect(resp).toBeDefined(); - } - - }); + it('should return false when one type is not identical', () => { + const expressions: ISolverExpression[] = [ + { + variable: 'a', + rawValue: 'true', + valueType: SparqlOperandDataTypes.Boolean, + valueAsNumber: 1, + operator: SparqlRelationOperator.EqualThanRelation, + chainOperator: [], + }, + { + variable: 'a', + rawValue: 'false', + valueType: SparqlOperandDataTypes.Boolean, + valueAsNumber: 0, + operator: SparqlRelationOperator.EqualThanRelation, + chainOperator: [], + }, + { + variable: 'a', + rawValue: 'false', + valueType: SparqlOperandDataTypes.Byte, + valueAsNumber: 0, + operator: SparqlRelationOperator.EqualThanRelation, + chainOperator: [], + }, + ]; + + expect(areTypesCompatible(expressions)).toBe(false); }); - describe('resolveSolutionDomainEquationSystem', () => { - it('should return a valid domain given a valid equation system', () => { - - const firstOperator = new LinkOperator(LogicOperator.Or); - const secondOperator = new LinkOperator(LogicOperator.And); - const thirdOperator = new LinkOperator(LogicOperator.Not); - const forthOperator = new LinkOperator(LogicOperator.Or); - const fifthOperator = new LinkOperator(LogicOperator.And); - const firstEquation: SolverExpressionRange = { - solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33]), - chainOperator: [ - firstOperator, - ] - }; - - const secondEquation: SolverExpressionRange = { - solutionDomain: new SolutionRange([75, 75]), - chainOperator: [ - firstOperator, - secondOperator, - ] - }; - - const thirdEquation: SolverExpressionRange = { - solutionDomain: new SolutionRange([100, Number.POSITIVE_INFINITY]), - chainOperator: [ - firstOperator, - secondOperator, - thirdOperator, - forthOperator - ] - }; - const equationSystem: SolverEquationSystem = new Map([ - [firstEquation.chainOperator.slice(-1)[0].toString(), firstEquation], - [secondEquation.chainOperator.slice(-1)[0].toString(), secondEquation], - [thirdEquation.chainOperator.slice(-1)[0].toString(), thirdEquation] - ]); - - const firstEquationToSolve: [SolverExpressionRange, SolverExpressionRange] = [ - { - solutionDomain: new SolutionRange([1000, Number.POSITIVE_INFINITY]), - chainOperator: [ - firstOperator, - secondOperator, - thirdOperator, - forthOperator, - fifthOperator, - ] - }, - { - solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33 + Number.EPSILON]), - chainOperator: [ - firstOperator, - secondOperator, - thirdOperator, - forthOperator, - fifthOperator, - ] - } - ]; - // Nothing => [100, infinity] =>[-infinity, 100- epsilon] => [75,75]=> [75, 75], [-infinity, 33] - const expectedDomain: SolutionRange[] = [new SolutionRange([Number.NEGATIVE_INFINITY, 33]), new SolutionRange([75, 75])]; - - const resp = resolveSolutionDomainEquationSystem(equationSystem, firstEquationToSolve); - - if (resp) { - expect(resp.get_domain()).toStrictEqual(expectedDomain); - } else { - expect(resp).toBeDefined(); - } - - }); - - it('should return undefined an equation system where the chain of operator is inconsistent', () => { - const firstOperator = new LinkOperator(LogicOperator.Or); - const secondOperator = new LinkOperator(LogicOperator.And); - const thirdOperator = new LinkOperator(LogicOperator.Not); - const forthOperator = new LinkOperator(LogicOperator.Or); - const fifthOperator = new LinkOperator(LogicOperator.And); - const firstEquation: SolverExpressionRange = { - solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33]), - chainOperator: [ - firstOperator, - ] - }; - - const secondEquation: SolverExpressionRange = { - solutionDomain: new SolutionRange([75, 75]), - chainOperator: [ - firstOperator, - secondOperator, - ] - }; - - const thirdEquation: SolverExpressionRange = { - solutionDomain: new SolutionRange([100, Number.POSITIVE_INFINITY]), - chainOperator: [ - ] - }; - const equationSystem: SolverEquationSystem = new Map([ - [firstEquation.chainOperator.slice(-1)[0].toString(), firstEquation], - [secondEquation.chainOperator.slice(-1)[0].toString(), secondEquation], - [forthOperator.toString(), thirdEquation] - ]); - - const firstEquationToSolve: [SolverExpressionRange, SolverExpressionRange] = [ - { - solutionDomain: new SolutionRange([1000, Number.POSITIVE_INFINITY]), - chainOperator: [ - firstOperator, - secondOperator, - thirdOperator, - forthOperator, - fifthOperator, - ] - }, - { - solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33 + Number.EPSILON]), - chainOperator: [ - firstOperator, - secondOperator, - thirdOperator, - forthOperator, - fifthOperator, - ] - } - ]; - - expect(()=>{resolveSolutionDomainEquationSystem(equationSystem, firstEquationToSolve)}).toThrow(); - }); + it('should return false when one type is not identical and the other are number', () => { + const expressions: ISolverExpression[] = [ + { + variable: 'a', + rawValue: 'true', + valueType: SparqlOperandDataTypes.UnsignedInt, + valueAsNumber: 1, + operator: SparqlRelationOperator.EqualThanRelation, + chainOperator: [], + }, + { + variable: 'a', + rawValue: 'false', + valueType: SparqlOperandDataTypes.Float, + valueAsNumber: 0, + operator: SparqlRelationOperator.EqualThanRelation, + chainOperator: [], + }, + { + variable: 'a', + rawValue: 'false', + valueType: SparqlOperandDataTypes.Byte, + valueAsNumber: 0, + operator: SparqlRelationOperator.EqualThanRelation, + chainOperator: [], + }, + ]; + + expect(areTypesCompatible(expressions)).toBe(false); + }); + }); + + describe('convertTreeRelationToSolverExpression', () => { + it('given a TREE relation with all the parameters should return a valid expression', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + const expectedExpression: ISolverExpression = { + variable, + rawValue: '5', + valueType: SparqlOperandDataTypes.Integer, + valueAsNumber: 5, + chainOperator: [], + operator: SparqlRelationOperator.EqualThanRelation, + }; + + expect(convertTreeRelationToSolverExpression(relation, variable)).toStrictEqual(expectedExpression); }); - describe('resolveAFilterTerm', () => { - it('given an algebra expression with all the solver expression parameters should return a valid expression', () => { - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: "=", - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable('x'), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - const variable = 'x' - const expectedSolverExpression: SolverExpression = { - variable, - rawValue: '6', - valueType: SparqlOperandDataTypes.Integer, - valueAsNumber: 6, - operator: operator, - chainOperator: linksOperator - }; - - const resp = resolveAFilterTerm(expression, operator, linksOperator, variable); - - if (resp) { - expect(resp).toStrictEqual(expectedSolverExpression); - } else { - expect(resp).toBeDefined(); - } - }); - - it('given an algebra expression without a variable than should return undefined', () => { - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: "=", - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - - expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeUndefined(); - - }); - - it('given an algebra expression without a litteral than should return undefined', () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: "=", - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - } - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); - - }); - - it('given an algebra expression without args than should return undefined', () => { - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: "=", - args: [], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + it('should return undefined given a relation witn a value term containing an unknowed value type', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); - expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeUndefined(); + it(`should return undefined given a relation with + a term containing an incompatible value in relation to its value type`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: 'a', + term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); - }); + it('should return undefined given a relation without a value and a type', () => { + const relation: ITreeRelation = { + remainingItems: 10, + path: 'ex:path', + node: 'https://www.example.be', + }; + const variable = 'x'; - it('given an algebra expression with a litteral containing an invalid datatype than should return undefined', () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: "=", - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); - }); - - it('given an algebra expression with a litteral containing a literal that cannot be converted into number should return undefined', () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: "=", - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; - - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); - }); + expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it('should return undefined given a relation without a value', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it('should return undefined given a relation without a type', () => { + const relation: ITreeRelation = { + remainingItems: 10, + path: 'ex:path', + value: { + value: 'a', + term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + }); + + describe('resolveSolutionDomainWithAnExpression', () => { + it(`given an empty domain and an equation with 2 operation chained + that are not "NOT" should return a valid new domain and the last chained operator`, () => { + const domain = new SolutionDomain(); + const equation: ISolverExpressionRange = { + chainOperator: [ + new LinkOperator(LogicOperator.And), + new LinkOperator(LogicOperator.Or), + ], + solutionDomain: new SolutionRange([ 0, 1 ]), + }; + + const expectedDomain = SolutionDomain.newWithInitialValue(equation.solutionDomain); + const expectedLastLogicalOperator = equation.chainOperator.at(-2)?.toString(); + + const resp = resolveSolutionDomainWithAnExpression(equation, domain); + if (resp) { + const [ respDomain, respLastLogicalOperator ] = resp; + expect(respDomain).toStrictEqual(expectedDomain); + expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); + } else { + expect(resp).toBeDefined(); + } + }); + + it(`given a domain and an equation with multiple chained that are + not "NOT" should return a valid new domain and the next chained operator`, () => { + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([ 0, 1 ])); + const equation: ISolverExpressionRange = { + chainOperator: [ + new LinkOperator(LogicOperator.And), + new LinkOperator(LogicOperator.Or), + new LinkOperator(LogicOperator.Or), + new LinkOperator(LogicOperator.And), + new LinkOperator(LogicOperator.Or), + ], + solutionDomain: new SolutionRange([ 100, 221.3 ]), + }; + const expectedOperator = equation.chainOperator.at(-1); + if (!expectedOperator) { + fail('should be able to get the expected operator check the test implementation'); + } + const expectedDomain = domain.add( + { range: equation.solutionDomain, + operator: expectedOperator.operator }, + ); + const expectedLastLogicalOperator = equation.chainOperator.at(-2)?.toString(); + + const resp = resolveSolutionDomainWithAnExpression(equation, domain); + if (resp) { + const [ respDomain, respLastLogicalOperator ] = resp; + expect(respDomain).toStrictEqual(expectedDomain); + expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); + } else { + expect(resp).toBeDefined(); + } + }); + + it(`given a domain and an equation one chained operation + should return a valid new domain and an empty string has the next chained operator`, () => { + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([ 0, 1 ])); + const equation: ISolverExpressionRange = { + chainOperator: [ + new LinkOperator(LogicOperator.Or), + ], + solutionDomain: new SolutionRange([ 100, 221.3 ]), + }; + + const expectedOperator = equation.chainOperator.at(-1); + if (!expectedOperator) { + fail('should be able to get the expected operator check the test implementation'); + } + + const expectedDomain = domain.add( + { + range: equation.solutionDomain, + operator: expectedOperator.operator, + }, + ); + const expectedLastLogicalOperator = ''; + + const resp = resolveSolutionDomainWithAnExpression(equation, domain); + if (resp) { + const [ respDomain, respLastLogicalOperator ] = resp; + expect(respDomain).toStrictEqual(expectedDomain); + expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); + } else { + expect(resp).toBeDefined(); + } + }); + + it(`given a domain and an equation with multiple chainned operator where the later elements + are "NOT" operators and the last element an "AND" + operator should return a valid domain and the next last operator`, () => { + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([ 0, 1 ])); + const equation: ISolverExpressionRange = { + chainOperator: [ + new LinkOperator(LogicOperator.And), + new LinkOperator(LogicOperator.Not), + new LinkOperator(LogicOperator.Not), + new LinkOperator(LogicOperator.Or), + ], + solutionDomain: new SolutionRange([ 100, 221.3 ]), + }; + + const expectedOperator = equation.chainOperator.at(-1); + if (!expectedOperator) { + fail('should be able to get the expected operator check the test implementation'); + } + + let expectedDomain = domain.add( + { + range: equation.solutionDomain, + operator: expectedOperator.operator, + }, + ); + expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); + expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); + + const expectedLastLogicalOperator = equation.chainOperator[0].toString(); + + const resp = resolveSolutionDomainWithAnExpression(equation, domain); + if (resp) { + const [ respDomain, respLastLogicalOperator ] = resp; + expect(respDomain).toStrictEqual(expectedDomain); + expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); + } else { + expect(resp).toBeDefined(); + } + }); + + it(`given a domain and an equation with multiple chainned operator + where the last elements are "NOT" operators should + return a valid domain and an empty string as the next operator`, () => { + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([ 0, 1 ])); + const equation: ISolverExpressionRange = { + chainOperator: [ + new LinkOperator(LogicOperator.Not), + new LinkOperator(LogicOperator.Not), + new LinkOperator(LogicOperator.Or), + ], + solutionDomain: new SolutionRange([ 100, 221.3 ]), + }; + + const expectedOperator = equation.chainOperator.at(-1); + if (!expectedOperator) { + fail('should be able to get the expected operator check the test implementation'); + } + + let expectedDomain = domain.add( + { + range: equation.solutionDomain, + operator: expectedOperator.operator, + }, + ); + expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); + expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); + + const expectedLastLogicalOperator = ''; + + const resp = resolveSolutionDomainWithAnExpression(equation, domain); + if (resp) { + const [ respDomain, respLastLogicalOperator ] = resp; + expect(respDomain).toStrictEqual(expectedDomain); + expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); + } else { + expect(resp).toBeDefined(); + } + }); + + it('given an empty domain and an equation with no chained operation should return undefined', () => { + const domain = new SolutionDomain(); + const equation: ISolverExpressionRange = { + chainOperator: [], + solutionDomain: new SolutionRange([ 0, 1 ]), + }; + + expect(resolveSolutionDomainWithAnExpression(equation, domain)).toBeUndefined(); + }); + }); + + describe('createEquationSystem', () => { + it(`given multiple equations that are consistent with one + and another should return a valid equation system and the first equation to resolve`, () => { + const lastOperator = new LinkOperator(LogicOperator.And); + const firstOperator = new LinkOperator(LogicOperator.Or); + const secondOperator = new LinkOperator(LogicOperator.Or); + const thirdOperator = new LinkOperator(LogicOperator.Not); + const aVariable = 'x'; + const aRawValue = '1'; + const aValueType = SparqlOperandDataTypes.Int; + const avalueAsNumber = 1; + const anOperator = SparqlRelationOperator.EqualThanRelation; + + const operationTemplate = (c: LinkOperator[]): ISolverExpression => { + return { + variable: aVariable, + rawValue: aRawValue, + valueType: aValueType, + valueAsNumber: avalueAsNumber, + operator: anOperator, + chainOperator: c, + }; + }; + + const firstOperation = operationTemplate([ firstOperator ]); + const firstEquation: ISolverExpressionRange = { + chainOperator: firstOperation.chainOperator, + solutionDomain: new SolutionRange([ 1, 1 ]), + }; + const secondOperation = operationTemplate([ firstOperator, secondOperator ]); + const secondEquation: ISolverExpressionRange = { + chainOperator: secondOperation.chainOperator, + solutionDomain: new SolutionRange([ 1, 1 ]), + }; + const thirdOperation = operationTemplate([ firstOperator, secondOperator, thirdOperator ]); + const thirdEquation: ISolverExpressionRange = { + chainOperator: thirdOperation.chainOperator, + solutionDomain: new SolutionRange([ 1, 1 ]), + }; + const lastOperation1 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + const expectedFirstEquation1: ISolverExpressionRange = { + chainOperator: lastOperation1.chainOperator, + solutionDomain: new SolutionRange([ 1, 1 ]), + }; + const lastOperation2 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + const expectedFirstEquation2: ISolverExpressionRange = { + chainOperator: lastOperation2.chainOperator, + solutionDomain: new SolutionRange([ 1, 1 ]), + }; + + const equations: ISolverExpression[] = [ + firstOperation, + secondOperation, + thirdOperation, + lastOperation1, + lastOperation2, + ]; + equations.sort(() => Math.random() - 0.5); + + const expectedEquationSystem: SolverEquationSystem = new Map([ + [ firstOperator.toString(), firstEquation ], + [ secondOperator.toString(), secondEquation ], + [ thirdOperator.toString(), thirdEquation ], + ]); + + const resp = createEquationSystem(equations); + if (!Array.isArray(resp)) { + fail('should return an array'); + } + else if (resp) { + const [ respEquationSystem, [ respFirstEquation1, respFirstEquation2 ]] = resp; + expect(respEquationSystem).toStrictEqual(expectedEquationSystem); + expect(respFirstEquation1).toStrictEqual(expectedFirstEquation1); + expect(respFirstEquation2).toStrictEqual(expectedFirstEquation2); + } else { + expect(resp).toBeDefined(); + } + }); + + it(`given multiples equations where it is not possible to + get the solution range of an equation it should return undefined`, () => { + const lastOperator = new LinkOperator(LogicOperator.And); + const firstOperator = new LinkOperator(LogicOperator.Or); + const secondOperator = new LinkOperator(LogicOperator.Or); + const thirdOperator = new LinkOperator(LogicOperator.Not); + const aVariable = 'x'; + const aRawValue = '1'; + const aValueType = SparqlOperandDataTypes.Int; + const avalueAsNumber = 1; + const anOperator = SparqlRelationOperator.EqualThanRelation; + + const operationTemplate = (c: LinkOperator[]): ISolverExpression => { + return { + variable: aVariable, + rawValue: aRawValue, + valueType: aValueType, + valueAsNumber: avalueAsNumber, + operator: anOperator, + chainOperator: c, + }; + }; + + const firstOperation = { + variable: aVariable, + rawValue: aRawValue, + valueType: aValueType, + valueAsNumber: avalueAsNumber, + operator: SparqlRelationOperator.GeospatiallyContainsRelation, + chainOperator: [ firstOperator ], + }; + const secondOperation = operationTemplate([ firstOperator, secondOperator ]); + const thirdOperation = operationTemplate([ firstOperator, secondOperator, thirdOperator ]); + const lastOperation1 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + const lastOperation2 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + + const equations: ISolverExpression[] = [ + firstOperation, + secondOperation, + thirdOperation, + lastOperation1, + lastOperation2, + ]; + equations.sort(() => Math.random() - 0.5); + + expect(createEquationSystem(equations)).toBeUndefined(); + }); + + it(`given multiples equations where there is multiple equation + that could be the first equation to be resolved it should return undefined`, () => { + const lastOperator = new LinkOperator(LogicOperator.And); + const firstOperator = new LinkOperator(LogicOperator.Or); + const secondOperator = new LinkOperator(LogicOperator.Or); + const thirdOperator = new LinkOperator(LogicOperator.Not); + const aVariable = 'x'; + const aRawValue = '1'; + const aValueType = SparqlOperandDataTypes.Int; + const avalueAsNumber = 1; + const anOperator = SparqlRelationOperator.EqualThanRelation; + + const operationTemplate = (c: LinkOperator[]): ISolverExpression => { + return { + variable: aVariable, + rawValue: aRawValue, + valueType: aValueType, + valueAsNumber: avalueAsNumber, + operator: anOperator, + chainOperator: c, + }; + }; + + const firstOperation = operationTemplate([ firstOperator ]); + const secondOperation = operationTemplate([ firstOperator, secondOperator ]); + const thirdOperation = operationTemplate([ firstOperator, secondOperator, thirdOperator ]); + const lastOperation1 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + const lastOperation2 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + const lastOperation3 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + + const equations: ISolverExpression[] = [ + firstOperation, + secondOperation, + thirdOperation, + lastOperation1, + lastOperation2, + lastOperation3, + ]; + equations.sort(() => Math.random() - 0.5); + + expect(createEquationSystem(equations)).toBeUndefined(); + }); + + it('given multiples equations where there is no first equation to be resolved should return undefined', () => { + const lastOperator = new LinkOperator(LogicOperator.And); + const firstOperator = new LinkOperator(LogicOperator.Or); + const secondOperator = new LinkOperator(LogicOperator.Or); + const thirdOperator = new LinkOperator(LogicOperator.Not); + const aVariable = 'x'; + const aRawValue = '1'; + const aValueType = SparqlOperandDataTypes.Int; + const avalueAsNumber = 1; + const anOperator = SparqlRelationOperator.EqualThanRelation; + + const operationTemplate = (c: LinkOperator[]): ISolverExpression => { + return { + variable: aVariable, + rawValue: aRawValue, + valueType: aValueType, + valueAsNumber: avalueAsNumber, + operator: anOperator, + chainOperator: c, + }; + }; + + const firstOperation = { + variable: aVariable, + rawValue: aRawValue, + valueType: aValueType, + valueAsNumber: avalueAsNumber, + operator: SparqlRelationOperator.GeospatiallyContainsRelation, + chainOperator: [ firstOperator ], + }; + const secondOperation = operationTemplate([ firstOperator, secondOperator ]); + const thirdOperation = operationTemplate([ firstOperator, secondOperator, thirdOperator ]); + const lastOperation1 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + + const equations: ISolverExpression[] = [ + firstOperation, + secondOperation, + thirdOperation, + lastOperation1, + ]; + equations.sort(() => Math.random() - 0.5); + + expect(createEquationSystem(equations)).toBeUndefined(); + }); + + it('given an equation should return a valid system of equation', () => { + const equation: ISolverExpression[] = [ + { + chainOperator: [], + operator: SparqlRelationOperator.EqualThanRelation, + rawValue: '88', + valueAsNumber: 88, + valueType: SparqlOperandDataTypes.Int, + variable: 'x', + }, + ]; + + const expectedEquationSystem: ISolverExpressionRange = { + chainOperator: [], + solutionDomain: new SolutionRange([ 88, 88 ]), + }; + + const resp = createEquationSystem(equation); + if (Array.isArray(resp)) { + fail('should not return an array'); + } + else if (resp && !Array.isArray(resp)) { + expect(resp).toStrictEqual(expectedEquationSystem); + } else { + expect(resp).toBeDefined(); + } + }); + + it('given two equations should return a valid system of equation', () => { + const lastOperator = new LinkOperator(LogicOperator.And); + + const equation: ISolverExpression[] = [ + { + chainOperator: [ lastOperator ], + operator: SparqlRelationOperator.EqualThanRelation, + rawValue: '88', + valueAsNumber: 88, + valueType: SparqlOperandDataTypes.Int, + variable: 'x', + }, + { + chainOperator: [ lastOperator ], + operator: SparqlRelationOperator.EqualThanRelation, + rawValue: '33', + valueAsNumber: 33, + valueType: SparqlOperandDataTypes.Int, + variable: 'x', + }, + ]; + + const expectedFirstEquation1: ISolverExpressionRange = { + chainOperator: [ lastOperator ], + solutionDomain: new SolutionRange([ 88, 88 ]), + }; + const expectedFirstEquation2: ISolverExpressionRange = { + chainOperator: [ lastOperator ], + solutionDomain: new SolutionRange([ 33, 33 ]), + }; + + const resp = createEquationSystem(equation); + if (!Array.isArray(resp)) { + fail('should return an array'); + } + else if (resp && Array.isArray(resp)) { + const [ respEquationSystem, [ respFirstEquation1, respFirstEquation2 ]] = resp; + expect(respEquationSystem.size).toBe(0); + expect(respFirstEquation1).toStrictEqual(expectedFirstEquation1); + expect(respFirstEquation2).toStrictEqual(expectedFirstEquation2); + } else { + expect(resp).toBeDefined(); + } + }); + }); + + describe('resolveSolutionDomainEquationSystem', () => { + it('should return a valid domain given a valid equation system', () => { + const firstOperator = new LinkOperator(LogicOperator.Or); + const secondOperator = new LinkOperator(LogicOperator.And); + const thirdOperator = new LinkOperator(LogicOperator.Not); + const forthOperator = new LinkOperator(LogicOperator.Or); + const fifthOperator = new LinkOperator(LogicOperator.And); + const firstEquation: ISolverExpressionRange = { + solutionDomain: new SolutionRange([ Number.NEGATIVE_INFINITY, 33 ]), + chainOperator: [ + firstOperator, + ], + }; + + const secondEquation: ISolverExpressionRange = { + solutionDomain: new SolutionRange([ 75, 75 ]), + chainOperator: [ + firstOperator, + secondOperator, + ], + }; + + const thirdEquation: ISolverExpressionRange = { + solutionDomain: new SolutionRange([ 100, Number.POSITIVE_INFINITY ]), + chainOperator: [ + firstOperator, + secondOperator, + thirdOperator, + forthOperator, + ], + }; + const lastOperatorFirstEquation = firstEquation.chainOperator.at(-1); + const lastOperatorSecondEquation = secondEquation.chainOperator.at(-1); + const lastOperatorThirdEquation = thirdEquation.chainOperator.at(-1); + if ( + !(lastOperatorFirstEquation && + lastOperatorSecondEquation && + lastOperatorThirdEquation) + ) { + fail('should be able to retrieved the last chain operator of the equation check the test implementation'); + } + const equationSystem: SolverEquationSystem = new Map([ + [ lastOperatorFirstEquation.toString(), firstEquation ], + [ lastOperatorSecondEquation.toString(), secondEquation ], + [ lastOperatorThirdEquation.toString(), thirdEquation ], + ]); + + const firstEquationToSolve: [ISolverExpressionRange, ISolverExpressionRange] = [ + { + solutionDomain: new SolutionRange([ 1_000, Number.POSITIVE_INFINITY ]), + chainOperator: [ + firstOperator, + secondOperator, + thirdOperator, + forthOperator, + fifthOperator, + ], + }, + { + solutionDomain: new SolutionRange([ Number.NEGATIVE_INFINITY, 33 + Number.EPSILON ]), + chainOperator: [ + firstOperator, + secondOperator, + thirdOperator, + forthOperator, + fifthOperator, + ], + }, + ]; + // Nothing => [100, infinity] =>[-infinity, 100- epsilon] => [75,75]=> [75, 75], [-infinity, 33] + const expectedDomain: SolutionRange[] = [ + new SolutionRange([ Number.NEGATIVE_INFINITY, 33 ]), + new SolutionRange([ 75, 75 ]), + ]; + + const resp = resolveSolutionDomainEquationSystem(equationSystem, firstEquationToSolve); + + if (resp) { + expect(resp.get_domain()).toStrictEqual(expectedDomain); + } else { + expect(resp).toBeDefined(); + } + }); + + it('should return undefined an equation system where the chain of operator is inconsistent', () => { + const firstOperator = new LinkOperator(LogicOperator.Or); + const secondOperator = new LinkOperator(LogicOperator.And); + const thirdOperator = new LinkOperator(LogicOperator.Not); + const forthOperator = new LinkOperator(LogicOperator.Or); + const fifthOperator = new LinkOperator(LogicOperator.And); + const firstEquation: ISolverExpressionRange = { + solutionDomain: new SolutionRange([ Number.NEGATIVE_INFINITY, 33 ]), + chainOperator: [ + firstOperator, + ], + }; + + const secondEquation: ISolverExpressionRange = { + solutionDomain: new SolutionRange([ 75, 75 ]), + chainOperator: [ + firstOperator, + secondOperator, + ], + }; + + const thirdEquation: ISolverExpressionRange = { + solutionDomain: new SolutionRange([ 100, Number.POSITIVE_INFINITY ]), + chainOperator: [ + ], + }; + const lastOperatorFirstEquation = firstEquation.chainOperator.at(-1); + const lastOperatorSecondEquation = secondEquation.chainOperator.at(-1); + if ( + !(lastOperatorFirstEquation && + lastOperatorSecondEquation) + ) { + fail('should be able to retrieved the last chain operator of the equation check the test implementation'); + } + const equationSystem: SolverEquationSystem = new Map([ + [ lastOperatorFirstEquation.toString(), firstEquation ], + [ lastOperatorSecondEquation.toString(), secondEquation ], + [ forthOperator.toString(), thirdEquation ], + ]); + + const firstEquationToSolve: [ISolverExpressionRange, ISolverExpressionRange] = [ + { + solutionDomain: new SolutionRange([ 1_000, Number.POSITIVE_INFINITY ]), + chainOperator: [ + firstOperator, + secondOperator, + thirdOperator, + forthOperator, + fifthOperator, + ], + }, + { + solutionDomain: new SolutionRange([ Number.NEGATIVE_INFINITY, 33 + Number.EPSILON ]), + chainOperator: [ + firstOperator, + secondOperator, + thirdOperator, + forthOperator, + fifthOperator, + ], + }, + ]; + + expect(() => { resolveSolutionDomainEquationSystem(equationSystem, firstEquationToSolve); }).toThrow(); + }); + }); + + describe('resolveAFilterTerm', () => { + it('given an algebra expression with all the solver expression parameters should return a valid expression', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable('x'), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + const variable = 'x'; + const expectedSolverExpression: ISolverExpression = { + variable, + rawValue: '6', + valueType: SparqlOperandDataTypes.Integer, + valueAsNumber: 6, + operator, + chainOperator: linksOperator, + }; + + const resp = resolveAFilterTerm(expression, operator, linksOperator, variable); + + if (resp) { + expect(resp).toStrictEqual(expectedSolverExpression); + } else { + expect(resp).toBeDefined(); + } + }); + + it('given an algebra expression without a variable than should return undefined', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + + expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeUndefined(); + }); + + it('given an algebra expression without a litteral than should return undefined', () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); + }); + + it('given an algebra expression without args than should return undefined', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + + expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeUndefined(); + }); + + it('given an algebra expression with a litteral containing an invalid datatype than should return undefined', + () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [ + new LinkOperator(LogicOperator.And), + new LinkOperator(LogicOperator.Or), + ]; + + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); + }); + + it(`given an algebra expression with a litteral containing a + literal that cannot be converted into number should return undefined`, () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); }); + }); - describe('recursifFilterExpressionToSolverExpression', () => { - it('given an algebra expression with two logicals operators should return a list of solver expression', () => { - const expression = translate(` + describe('recursifFilterExpressionToSolverExpression', () => { + it('given an algebra expression with two logicals operators should return a list of solver expression', () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(?x=2 && ?x>5)) }`).input.expression; - const buildSolverExpression = ( - variable: Variable, - rawValue: string, - valueType: SparqlOperandDataTypes, - valueAsNumber: number, - operator: SparqlRelationOperator, - chainOperator: LinkOperator[]): SolverExpression => { - return { - rawValue, - variable, - valueType, - valueAsNumber, - operator, - chainOperator - } - }; - const variable = 'x'; - - LinkOperator.resetIdCount(); - const notOperator = new LinkOperator(LogicOperator.Not); - const andOperator = new LinkOperator(LogicOperator.And); - - const expectedEquation: SolverExpression[] = [ - buildSolverExpression( - variable, - '2', - SparqlOperandDataTypes.Integer, - 2, - SparqlRelationOperator.EqualThanRelation, - [notOperator, andOperator]), - - buildSolverExpression( - variable, - '5', - SparqlOperandDataTypes.Integer, - 5, - SparqlRelationOperator.GreaterThanRelation, - [notOperator, andOperator]), - ]; - - LinkOperator.resetIdCount(); - expect(recursifFilterExpressionToSolverExpression(expression, [], [], variable)).toStrictEqual(expectedEquation); - - }); - - it('given an algebra expression with tree logicals operators should return a list of solver expression', () => { - const expression = translate(` + const buildSolverExpression = ( + variable: Variable, + rawValue: string, + valueType: SparqlOperandDataTypes, + valueAsNumber: number, + operator: SparqlRelationOperator, + chainOperator: LinkOperator[], + ): ISolverExpression => { + return { + rawValue, + variable, + valueType, + valueAsNumber, + operator, + chainOperator, + }; + }; + const variable = 'x'; + + LinkOperator.resetIdCount(); + const notOperator = new LinkOperator(LogicOperator.Not); + const andOperator = new LinkOperator(LogicOperator.And); + + const expectedEquation: ISolverExpression[] = [ + buildSolverExpression( + variable, + '2', + SparqlOperandDataTypes.Integer, + 2, + SparqlRelationOperator.EqualThanRelation, + [ notOperator, andOperator ], + ), + + buildSolverExpression( + variable, + '5', + SparqlOperandDataTypes.Integer, + 5, + SparqlRelationOperator.GreaterThanRelation, + [ notOperator, andOperator ], + ), + ]; + + LinkOperator.resetIdCount(); + expect(recursifFilterExpressionToSolverExpression(expression, [], [], variable)).toStrictEqual(expectedEquation); + }); + + it('given an algebra expression with tree logicals operators should return a list of solver expression', () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(?x=2 && ?x>5) || ?x < 88.3) }`).input.expression; - const buildSolverExpression = ( - variable: Variable, - rawValue: string, - valueType: SparqlOperandDataTypes, - valueAsNumber: number, - operator: SparqlRelationOperator, - chainOperator: LinkOperator[]): SolverExpression => { - return { - rawValue, - variable, - valueType, - valueAsNumber, - operator, - chainOperator - } - }; - const variable = 'x'; - - LinkOperator.resetIdCount(); - const orOperator = new LinkOperator(LogicOperator.Or); - const notOperator = new LinkOperator(LogicOperator.Not); - const andOperator = new LinkOperator(LogicOperator.And); - - const expectedEquation: SolverExpression[] = [ - buildSolverExpression( - variable, - '2', - SparqlOperandDataTypes.Integer, - 2, - SparqlRelationOperator.EqualThanRelation, - [orOperator, notOperator, andOperator]), - - buildSolverExpression( - variable, - '5', - SparqlOperandDataTypes.Integer, - 5, - SparqlRelationOperator.GreaterThanRelation, - [orOperator, notOperator, andOperator]), - - buildSolverExpression( - variable, - '88.3', - SparqlOperandDataTypes.Decimal, - 88.3, - SparqlRelationOperator.LessThanRelation, - [orOperator]), - ]; - - LinkOperator.resetIdCount(); - expect(recursifFilterExpressionToSolverExpression(expression, [], [], variable)).toStrictEqual(expectedEquation); - }); - - it('given an algebra expression with four logicals operators should return a list of solver expression', () => { - const expression = translate(` + const buildSolverExpression = ( + variable: Variable, + rawValue: string, + valueType: SparqlOperandDataTypes, + valueAsNumber: number, + operator: SparqlRelationOperator, + chainOperator: LinkOperator[], + ): ISolverExpression => { + return { + rawValue, + variable, + valueType, + valueAsNumber, + operator, + chainOperator, + }; + }; + const variable = 'x'; + + LinkOperator.resetIdCount(); + const orOperator = new LinkOperator(LogicOperator.Or); + const notOperator = new LinkOperator(LogicOperator.Not); + const andOperator = new LinkOperator(LogicOperator.And); + + const expectedEquation: ISolverExpression[] = [ + buildSolverExpression( + variable, + '2', + SparqlOperandDataTypes.Integer, + 2, + SparqlRelationOperator.EqualThanRelation, + [ orOperator, notOperator, andOperator ], + ), + + buildSolverExpression( + variable, + '5', + SparqlOperandDataTypes.Integer, + 5, + SparqlRelationOperator.GreaterThanRelation, + [ orOperator, notOperator, andOperator ], + ), + + buildSolverExpression( + variable, + '88.3', + SparqlOperandDataTypes.Decimal, + 88.3, + SparqlRelationOperator.LessThanRelation, + [ orOperator ], + ), + ]; + + LinkOperator.resetIdCount(); + expect(recursifFilterExpressionToSolverExpression(expression, [], [], variable)).toStrictEqual(expectedEquation); + }); + + it('given an algebra expression with four logicals operators should return a list of solver expression', () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(?x=2 && ?x>5 || ?x>6) || ?x < 88.3) }`).input.expression; - const buildSolverExpression = ( - variable: Variable, - rawValue: string, - valueType: SparqlOperandDataTypes, - valueAsNumber: number, - operator: SparqlRelationOperator, - chainOperator: LinkOperator[]): SolverExpression => { - return { - rawValue, - variable, - valueType, - valueAsNumber, - operator, - chainOperator - } - }; - const variable = 'x'; - - LinkOperator.resetIdCount(); - const firstOrOperator = new LinkOperator(LogicOperator.Or); - const notOperator = new LinkOperator(LogicOperator.Not); - const secondOrOperator = new LinkOperator(LogicOperator.Or); - const andOperator = new LinkOperator(LogicOperator.And); - - - const expectedEquation: SolverExpression[] = [ - buildSolverExpression( - variable, - '2', - SparqlOperandDataTypes.Integer, - 2, - SparqlRelationOperator.EqualThanRelation, - [firstOrOperator, notOperator, secondOrOperator, andOperator]), - - buildSolverExpression( - variable, - '5', - SparqlOperandDataTypes.Integer, - 5, - SparqlRelationOperator.GreaterThanRelation, - [firstOrOperator, notOperator, secondOrOperator, andOperator]), - - buildSolverExpression( - variable, - '6', - SparqlOperandDataTypes.Integer, - 6, - SparqlRelationOperator.GreaterThanRelation, - [firstOrOperator, notOperator, secondOrOperator]), - - buildSolverExpression( - variable, - '88.3', - SparqlOperandDataTypes.Decimal, - 88.3, - SparqlRelationOperator.LessThanRelation, - [firstOrOperator]), - ]; - - LinkOperator.resetIdCount(); - expect(recursifFilterExpressionToSolverExpression(expression, [], [], variable)).toStrictEqual(expectedEquation); - }); + const buildSolverExpression = ( + variable: Variable, + rawValue: string, + valueType: SparqlOperandDataTypes, + valueAsNumber: number, + operator: SparqlRelationOperator, + chainOperator: LinkOperator[], + ): ISolverExpression => { + return { + rawValue, + variable, + valueType, + valueAsNumber, + operator, + chainOperator, + }; + }; + const variable = 'x'; + + LinkOperator.resetIdCount(); + const firstOrOperator = new LinkOperator(LogicOperator.Or); + const notOperator = new LinkOperator(LogicOperator.Not); + const secondOrOperator = new LinkOperator(LogicOperator.Or); + const andOperator = new LinkOperator(LogicOperator.And); + + const expectedEquation: ISolverExpression[] = [ + buildSolverExpression( + variable, + '2', + SparqlOperandDataTypes.Integer, + 2, + SparqlRelationOperator.EqualThanRelation, + [ firstOrOperator, notOperator, secondOrOperator, andOperator ], + ), + + buildSolverExpression( + variable, + '5', + SparqlOperandDataTypes.Integer, + 5, + SparqlRelationOperator.GreaterThanRelation, + [ firstOrOperator, notOperator, secondOrOperator, andOperator ], + ), + + buildSolverExpression( + variable, + '6', + SparqlOperandDataTypes.Integer, + 6, + SparqlRelationOperator.GreaterThanRelation, + [ firstOrOperator, notOperator, secondOrOperator ], + ), + + buildSolverExpression( + variable, + '88.3', + SparqlOperandDataTypes.Decimal, + 88.3, + SparqlRelationOperator.LessThanRelation, + [ firstOrOperator ], + ), + ]; + + LinkOperator.resetIdCount(); + expect(recursifFilterExpressionToSolverExpression(expression, [], [], variable)).toStrictEqual(expectedEquation); }); - - describe('isRelationFilterExpressionDomainEmpty', () => { - it('given a relation that is not able to be converted into a solverExpression should return true', () => { - const relation: ITreeRelation = { - node: "https://www.example.com", - value: { - value: "abc", - term: DF.literal('abc', DF.namedNode('foo')) - } - }; - - const filterExpression = translate(` + }); + + describe('isRelationFilterExpressionDomainEmpty', () => { + it('given a relation that is not able to be converted into a solverExpression should return true', () => { + const relation: ITreeRelation = { + node: 'https://www.example.com', + value: { + value: 'abc', + term: DF.literal('abc', DF.namedNode('foo')), + }, + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(?x=2 && ?x>5) || ?x < 88.3) }`).input.expression; - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true) - }); + const variable = 'x'; - it('should return true given a relation and a filter operation where types are not compatible', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')) - }, - node: "https://www.example.be" - }; + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); - const filterExpression = translate(` + it('should return true given a relation and a filter operation where types are not compatible', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(?x=2 && ?x>5) || ?x < 88.3) }`).input.expression; - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); - }); + const variable = 'x'; - it('should return false given a relation and a filter operation where types are not compatible', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')) - }, - node: "https://www.example.be" - }; + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); - const filterExpression = translate(` + it('should return false given a relation and a filter operation where types are not compatible', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(?x=2 && ?x>5) || ?x < 88.3) }`).input.expression; - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); - }); + const variable = 'x'; - it('should return true when the solution range is not valid of the relation', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.GeospatiallyContainsRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')) - }, - node: "https://www.example.be" - }; + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); - const filterExpression = translate(` + it('should return true when the solution range is not valid of the relation', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.GeospatiallyContainsRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(?x=2 && ?x>5) || ?x < 88.3) }`).input.expression; - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); - }); + const variable = 'x'; - it('should return true when the equation system is not valid', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.GeospatiallyContainsRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')) - }, - node: "https://www.example.be" - }; + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); - const filterExpression:Algebra.Expression = { + it('should return true when the equation system is not valid', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.GeospatiallyContainsRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), + }, + node: 'https://www.example.be', + }; + + const filterExpression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '&&', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: "&&", - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: "=", - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable('x'), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('2', DF.namedNode("http://www.w3.org/2001/XMLSchema#integer")), - }, - ], - } - ] - }; - - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); - }); - - it('should return true when there is a solution for the filter expression and the relation', ()=>{ - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) - }, - node: "https://www.example.be" - }; - - const filterExpression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( !(?x=2 && ?x>5) || ?x < 88.3) - }`).input.expression; - - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); - }); + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable('x'), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('2', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + ], + }, + ], + }; - it('should return false when the filter expression has no solution ', ()=>{ - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) - }, - node: "https://www.example.be" - }; + const variable = 'x'; - const filterExpression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2 && ?x>5 && ?x > 88.3) - }`).input.expression; + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); - const solver = require('../lib/solver'); - const variable = 'x'; - jest.spyOn(solver, 'resolveSolutionDomainEquationSystem').mockReturnValue(undefined); - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); - }); - - it('should return false when the filter has a possible solution but the addition of the relation produce no possible solution', ()=>{ - const relation: ITreeRelation = { - type: SparqlRelationOperator.GreaterThanOrEqualToRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "100", - term: DF.literal('100', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) - }, - node: "https://www.example.be" - }; - - const filterExpression = translate(` + it('should return true when there is a solution for the filter expression and the relation', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(?x=2 && ?x>5) || ?x < 88.3) }`).input.expression; - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); - }); + const variable = 'x'; - it('should return true when there is a solution for the filter expression with one expression and the relation', ()=>{ - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) - }, - node: "https://www.example.be" - }; + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); - const filterExpression = translate(` + it('should return false when the filter expression has no solution ', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z - FILTER(?x>=5) + FILTER( ?x=2 && ?x>5 && ?x > 88.3) }`).input.expression; - const variable = 'x'; + const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); - }); - - it('should return false when there is no solution for the filter expression with one expression and the relation', ()=>{ - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) - }, - node: "https://www.example.be" - }; + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); - const filterExpression = translate(` + it(`should return false when the filter has a possible + solution but the addition of the relation produce no possible solution`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.GreaterThanOrEqualToRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '100', + term: DF.literal('100', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z - FILTER(?x>=6) + FILTER( !(?x=2 && ?x>5) || ?x < 88.3) }`).input.expression; - const variable = 'x'; + const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); - }); + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); - it('should return false when there is no solution for the filter expression with one expression and the relation', ()=>{ - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) - }, - node: "https://www.example.be" - }; + it('should return true when there is a solution for the filter expression with one expression and the relation', + () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x>=5) + }`).input.expression; - const filterExpression = translate(` + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it('should return false when there is no solution for the filter expression with one expression and the relation', + () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(?x>=6) }`).input.expression; - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); - }); - - it('should return false when there is no solution for the filter expression with two expressions and the relation', ()=>{ - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) - }, - node: "https://www.example.be" - }; - - const filterExpression = translate(` + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + + it(`should return false when there is no solution for the filter + expression with two expressions and the relation`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(?x>=6 && ?x>= 7) }`).input.expression; - const variable = 'x'; + const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); - }); - - it('should return true when there is a solution for the filter expression with one expression and the relation', ()=>{ - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: "ex:path", - value: { - value: "5", - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')) - }, - node: "https://www.example.be" - }; + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); - const filterExpression = translate(` + it(`should return true when there is a solution for + the filter expression with one expression and the relation`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(?x>=5 && ?x>=-1) }`).input.expression; - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); - }); + const variable = 'x'; + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); -}); \ No newline at end of file + }); +}); From dd0152faa37cd9783d2a0288b5d730fec6dc34dc Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 17 Jan 2023 11:58:17 +0100 Subject: [PATCH 102/189] Full coverage of unit test restore. --- .../lib/FilterNode.ts | 59 +---------------- .../lib/SolutionDomain.ts | 26 +++++--- .../lib/solver.ts | 25 +++++-- .../test/solver-test.ts | 65 ++++++++++++++++--- 4 files changed, 94 insertions(+), 81 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index d956c41c1..71bd27e5a 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -1,6 +1,6 @@ import { BindingsFactory } from '@comunica/bindings-factory'; import { KeysInitQuery } from '@comunica/context-entries'; -import type { Bindings, IActionContext } from '@comunica/types'; +import type { IActionContext } from '@comunica/types'; import type { ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra, Factory as AlgebraFactory } from 'sparqlalgebrajs'; @@ -100,63 +100,6 @@ export class FilterNode { return resp; } - /** - * Delete the filters that are not related to the TREE relation - * @param {Algebra.Expression} filterExpression - the expression of the filter taken from the original query - * @param {Bindings} binding - binding that the resulting filter should contain - * @returns {Algebra.Expression} the resulting filter expression contain only filter related to the TREE relation - */ - private static generateTreeRelationFilter(filterExpression: Algebra.Expression, - binding: Bindings): Algebra.Expression { - // Generate an empty filter algebra. - let newFilterExpression: Algebra.Expression = AF.createOperatorExpression(filterExpression.operator, []); - - // Check if there is one filter or multiple. - if ('operator' in filterExpression.args[0]) { - // Add the argument into the empty the new filter. - newFilterExpression.args = (filterExpression.args).filter(expression => { - for (const arg of expression.args) { - if ('term' in arg && arg.term.termType === 'Variable') { - // Check if the argument of the filter is present into the binding. - return binding.has(arg.term.value); - } - } - return false; - }); - - // If the filter has now a size of 1 change the form to respect the algebra specification. - if (newFilterExpression.args.length === 1) { - newFilterExpression = newFilterExpression.args[0]; - } - } else { - // Add the argument into the empty the new filter. - for (const arg of (filterExpression.args)) { - // Check if the argument of the filter is present into the binding. - if ('term' in arg && arg.term.termType === 'Variable' && !binding.has(arg.term.value)) { - newFilterExpression.args = []; - break; - } - newFilterExpression.args.push(arg); - } - } - return newFilterExpression; - } - - /** - * Create the binding from quad related to the TREE:path that will be used with sparqlee - * for the filtering of relation. - * @param {RDF.Quad[]} relevantQuad - the quads related to the TREE relation - * @param {RDF.Quad} relationValue - the quad related to the TREE path - * @returns {Bindings} the resulting binding - */ - private static createBinding(relevantQuad: RDF.Quad[], relationValue: RDF.Term): Bindings { - let binding: Bindings = BF.bindings(); - for (const quad of relevantQuad) { - binding = binding.set(quad.object.value, relationValue); - } - return binding; - } - /** * Find the bgp of the original query of the user. * @param {Algebra.Operation} query - the original query diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 1cf985705..9a05476a8 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -12,6 +12,8 @@ export class SolutionDomain { */ private domain: SolutionRange[] = []; + private lastOperation: LogicOperator | undefined = undefined; + /** * Get the multiple segment of the domain. * @returns {SolutionRange[]} @@ -46,6 +48,7 @@ export class SolutionDomain { public clone(): SolutionDomain { const newSolutionDomain = new SolutionDomain(); newSolutionDomain.domain = new Array(...this.domain); + newSolutionDomain.lastOperation = this.lastOperation; return newSolutionDomain; } @@ -61,17 +64,23 @@ export class SolutionDomain { if (typeof range === 'undefined') { throw new ReferenceError('range should be defined with "AND" operator'); } - return this.addWithAndOperator(range); + const newDomain = this.addWithAndOperator(range); + newDomain.lastOperation = LogicOperator.And; + return newDomain; } case LogicOperator.Or: { if (typeof range === 'undefined') { throw new ReferenceError('range should be defined with "OR" operator'); } - return this.addWithOrOperator(range); + const newDomain = this.addWithOrOperator(range); + newDomain.lastOperation = LogicOperator.Or; + return newDomain; } case LogicOperator.Not: { - return this.notOperation(); + const newDomain = this.notOperation(); + newDomain.lastOperation = LogicOperator.Not; + return newDomain; } } } @@ -118,6 +127,10 @@ export class SolutionDomain { public addWithAndOperator(range: SolutionRange): SolutionDomain { const newDomain = new SolutionDomain(); + // If the domain is empty and the last operation was an "AND" + if (this.domain.length === 0 && this.lastOperation === LogicOperator.And) { + return newDomain; + } // If the domain is empty then simply add the new range if (this.domain.length === 0) { newDomain.domain.push(range); @@ -164,11 +177,6 @@ export class SolutionDomain { if (firstRange.lower < secondRange.lower) { return -1; } - - if (firstRange.lower > secondRange.lower) { - return 1; - } - - return 0; + return 1; } } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index c22ce3bac..be51721e7 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -9,8 +9,10 @@ import { SparqlOperandDataTypes, LogicOperatorReversed, LogicOperator, SparqlOperandDataTypesReversed, } from './solverInterfaces'; -import type { LastLogicalOperator, SolverEquationSystem, ISolverExpression, - Variable, ISolverExpressionRange } from './solverInterfaces'; +import type { + LastLogicalOperator, SolverEquationSystem, ISolverExpression, + Variable, ISolverExpressionRange, +} from './solverInterfaces'; /** * Check if the solution domain of a system of equation compose of the expressions of the filter @@ -42,9 +44,9 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi relationsolverExpressions.valueAsNumber, relationsolverExpressions.operator, ); - // The relation is invalid so we filter it + // We don't prune the relation because we do not implement yet the solution range for this expression if (!relationSolutionRange) { - return false; + return true; } const equationSystemFirstEquation = createEquationSystem(filtersolverExpressions); @@ -56,6 +58,7 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi let solutionDomain: SolutionDomain; + // If the filter has multiple expression if (Array.isArray(equationSystemFirstEquation)) { const [ equationSystem, firstEquationToResolved ] = equationSystemFirstEquation; @@ -316,7 +319,7 @@ export function convertTreeRelationToSolverExpression(relation: ITreeRelation, return undefined; } const valueNumber = castSparqlRdfTermIntoNumber(relation.value.value, valueType); - if (!valueNumber) { + if (!valueNumber && valueNumber !== 0) { return undefined; } @@ -391,6 +394,18 @@ export function castSparqlRdfTermIntoNumber(rdfTermValue: string, return Number.isNaN(val) ? undefined : val; } + if (rdfTermType === SparqlOperandDataTypes.Boolean) { + if (rdfTermValue === 'true') { + return 1; + } + + if (rdfTermValue === 'false') { + return 0; + } + + return undefined; + } + if ( isSparqlOperandNumberType(rdfTermType) && !rdfTermValue.includes('.') ) { diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 24c4f7e34..0518d19d6 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -117,6 +117,17 @@ describe('solver function', () => { } }); + it('should return undefined if a non fraction is pass with SparqlOperandDataTypes fraction compatible type', () => { + const testTable: [string, SparqlOperandDataTypes][] = [ + [ 'asbd', SparqlOperandDataTypes.Double ], + [ '', SparqlOperandDataTypes.Float ], + ]; + + for (const [ value, valueType ] of testTable) { + expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); + } + }); + it('should return the expected number when given an decimal', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ [ '1.1', SparqlOperandDataTypes.Decimal, 1.1 ], @@ -129,6 +140,21 @@ describe('solver function', () => { } }); + it('should return the expected number given a boolean', () => { + const testTable: [string, number][] = [ + [ 'true', 1 ], + [ 'false', 0 ], + ]; + + for (const [ value, expectedNumber ] of testTable) { + expect(castSparqlRdfTermIntoNumber(value, SparqlOperandDataTypes.Boolean)).toBe(expectedNumber); + } + }); + + it('should return undefined if the boolean string is not "true" or "false"', () => { + expect(castSparqlRdfTermIntoNumber('abc', SparqlOperandDataTypes.Boolean)).toBeUndefined(); + }); + it('should return the expected unix time given a date time', () => { const value = '1994-11-05T13:15:30Z'; const expectedUnixTime = 784_041_330_000; @@ -430,7 +456,7 @@ describe('solver function', () => { const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { const [ respDomain, respLastLogicalOperator ] = resp; - expect(respDomain).toStrictEqual(expectedDomain); + expect(respDomain.get_domain()).toStrictEqual(expectedDomain.get_domain()); expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); } else { expect(resp).toBeDefined(); @@ -455,15 +481,17 @@ describe('solver function', () => { fail('should be able to get the expected operator check the test implementation'); } const expectedDomain = domain.add( - { range: equation.solutionDomain, - operator: expectedOperator.operator }, + { + range: equation.solutionDomain, + operator: expectedOperator.operator, + }, ); const expectedLastLogicalOperator = equation.chainOperator.at(-2)?.toString(); const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { const [ respDomain, respLastLogicalOperator ] = resp; - expect(respDomain).toStrictEqual(expectedDomain); + expect(respDomain.get_domain()).toStrictEqual(expectedDomain.get_domain()); expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); } else { expect(resp).toBeDefined(); @@ -496,7 +524,7 @@ describe('solver function', () => { const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { const [ respDomain, respLastLogicalOperator ] = resp; - expect(respDomain).toStrictEqual(expectedDomain); + expect(respDomain.get_domain()).toStrictEqual(expectedDomain.get_domain()); expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); } else { expect(resp).toBeDefined(); @@ -880,6 +908,25 @@ describe('solver function', () => { expect(resp).toBeDefined(); } }); + + it('given one equation where one we cannot derived a solution range should return undefined', () => { + const lastOperator = new LinkOperator(LogicOperator.And); + + const equation: ISolverExpression[] = [ + { + chainOperator: [ lastOperator ], + operator: SparqlRelationOperator.GeospatiallyContainsRelation, + rawValue: '88', + valueAsNumber: 88, + valueType: SparqlOperandDataTypes.Int, + variable: 'x', + }, + ]; + + const resp = createEquationSystem(equation); + + expect(resp).toBeUndefined(); + }); }); describe('resolveSolutionDomainEquationSystem', () => { @@ -1398,8 +1445,8 @@ describe('solver function', () => { remainingItems: 10, path: 'ex:path', value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), + value: 'false', + term: DF.literal('false', DF.namedNode('http://www.w3.org/2001/XMLSchema#boolean')), }, node: 'https://www.example.be', }; @@ -1443,7 +1490,7 @@ describe('solver function', () => { path: 'ex:path', value: { value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), }, node: 'https://www.example.be', }; @@ -1536,7 +1583,7 @@ describe('solver function', () => { const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2 && ?x>5 && ?x > 88.3) + FILTER( ?x = 2 && ?x > 5 && ?x > 88.3) }`).input.expression; const variable = 'x'; From 43f5b4ad4b3ca0a5f4be325b3df54af83b3280d2 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 17 Jan 2023 14:57:45 +0100 Subject: [PATCH 103/189] Comment added to lastOperation. --- .../actor-extract-links-extract-tree/lib/SolutionDomain.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 9a05476a8..dd61404f6 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -11,7 +11,11 @@ export class SolutionDomain { * of the SolutionRange. */ private domain: SolutionRange[] = []; - + + /** + * The last operation apply to the domain + * + */ private lastOperation: LogicOperator | undefined = undefined; /** From ebce330d44160356d1fa69ededfc455cfdd19d70 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 18 Jan 2023 08:23:06 +0100 Subject: [PATCH 104/189] Small typo corrected. --- packages/actor-extract-links-extract-tree/lib/solver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index be51721e7..e038ef475 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -51,7 +51,7 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi const equationSystemFirstEquation = createEquationSystem(filtersolverExpressions); // Cannot create the equation system we don't filter the relation in case the error is internal to not - // loss results + // lose results if (!equationSystemFirstEquation) { return true; } From cf5aa53c68c3b50103649de99c27431b7b92a276 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 18 Jan 2023 08:24:23 +0100 Subject: [PATCH 105/189] Small typo corrected. --- packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index dd61404f6..a4b0153af 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -11,7 +11,7 @@ export class SolutionDomain { * of the SolutionRange. */ private domain: SolutionRange[] = []; - + /** * The last operation apply to the domain * From 211fddcd9322dd36f9bdc4529a0e0251d336a11e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 18 Jan 2023 13:51:01 +0100 Subject: [PATCH 106/189] Array.at method delete from the codebase because it is not implemented in node v14. --- packages/actor-extract-links-extract-tree/lib/solver.ts | 8 ++++---- .../actor-extract-links-extract-tree/test/solver-test.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index e038ef475..cb205e123 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -275,9 +275,9 @@ export function resolveSolutionDomainWithAnExpression(equation: ISolverExpressio let localDomain = domain.clone(); // To keep track of the last expression because we resolved all the not operator // next to the current last logical operator - let i = -1; + let i = equation.chainOperator.length - 1; // We find the last logical expression that has to be resolved - let currentLastOperator = equation.chainOperator.at(i); + let currentLastOperator = equation.chainOperator[i]; if (!currentLastOperator) { return undefined; } @@ -285,7 +285,7 @@ export function resolveSolutionDomainWithAnExpression(equation: ISolverExpressio // Resolve the new domain localDomain = localDomain.add({ range: equation.solutionDomain, operator: currentLastOperator?.operator }); - currentLastOperator = equation.chainOperator.at(i); + currentLastOperator = equation.chainOperator[i]; // If it was the last expression if (!currentLastOperator) { return [ localDomain, '' ]; @@ -294,7 +294,7 @@ export function resolveSolutionDomainWithAnExpression(equation: ISolverExpressio while (currentLastOperator?.operator === LogicOperator.Not) { localDomain = localDomain.add({ operator: currentLastOperator?.operator }); i--; - currentLastOperator = equation.chainOperator.at(i); + currentLastOperator = equation.chainOperator[i]; // It the last operator was a NOT if (!currentLastOperator?.operator) { return [ localDomain, '' ]; diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 0518d19d6..283031bb1 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -451,7 +451,7 @@ describe('solver function', () => { }; const expectedDomain = SolutionDomain.newWithInitialValue(equation.solutionDomain); - const expectedLastLogicalOperator = equation.chainOperator.at(-2)?.toString(); + const expectedLastLogicalOperator = equation.chainOperator.at(-2).toString(); const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { @@ -486,7 +486,7 @@ describe('solver function', () => { operator: expectedOperator.operator, }, ); - const expectedLastLogicalOperator = equation.chainOperator.at(-2)?.toString(); + const expectedLastLogicalOperator = equation.chainOperator.at(-2).toString(); const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { From ad175bc21abf823f1acadefc240a87bd9380997b Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 18 Jan 2023 13:58:16 +0100 Subject: [PATCH 107/189] unicorn/prefer-at lint rule disable as it is not compatible with node v14 and lint-fix --- .eslintrc.js | 1 + .../test/solver-test.ts | 22 +++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index dd1d10c07..3d714f91c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -75,6 +75,7 @@ module.exports = { 'unicorn/consistent-destructuring': 'off', 'unicorn/no-array-callback-reference': 'off', 'unicorn/no-new-array': 'off', + 'unicorn/prefer-at': 'off', // not compatible with node v14 // TS '@typescript-eslint/lines-between-class-members': ['error', { exceptAfterSingleLine: true }], diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 283031bb1..e6cce5a63 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -451,7 +451,7 @@ describe('solver function', () => { }; const expectedDomain = SolutionDomain.newWithInitialValue(equation.solutionDomain); - const expectedLastLogicalOperator = equation.chainOperator.at(-2).toString(); + const expectedLastLogicalOperator = equation.chainOperator[equation.chainOperator.length - 2].toString(); const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { @@ -476,7 +476,7 @@ describe('solver function', () => { ], solutionDomain: new SolutionRange([ 100, 221.3 ]), }; - const expectedOperator = equation.chainOperator.at(-1); + const expectedOperator = equation.chainOperator[equation.chainOperator.length - 1]; if (!expectedOperator) { fail('should be able to get the expected operator check the test implementation'); } @@ -486,7 +486,7 @@ describe('solver function', () => { operator: expectedOperator.operator, }, ); - const expectedLastLogicalOperator = equation.chainOperator.at(-2).toString(); + const expectedLastLogicalOperator = equation.chainOperator[equation.chainOperator.length - 2].toString(); const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { @@ -508,7 +508,7 @@ describe('solver function', () => { solutionDomain: new SolutionRange([ 100, 221.3 ]), }; - const expectedOperator = equation.chainOperator.at(-1); + const expectedOperator = equation.chainOperator[equation.chainOperator.length - 1]; if (!expectedOperator) { fail('should be able to get the expected operator check the test implementation'); } @@ -545,7 +545,7 @@ describe('solver function', () => { solutionDomain: new SolutionRange([ 100, 221.3 ]), }; - const expectedOperator = equation.chainOperator.at(-1); + const expectedOperator = equation.chainOperator[equation.chainOperator.length - 1]; if (!expectedOperator) { fail('should be able to get the expected operator check the test implementation'); } @@ -584,7 +584,7 @@ describe('solver function', () => { solutionDomain: new SolutionRange([ 100, 221.3 ]), }; - const expectedOperator = equation.chainOperator.at(-1); + const expectedOperator = equation.chainOperator[equation.chainOperator.length - 1]; if (!expectedOperator) { fail('should be able to get the expected operator check the test implementation'); } @@ -960,9 +960,9 @@ describe('solver function', () => { forthOperator, ], }; - const lastOperatorFirstEquation = firstEquation.chainOperator.at(-1); - const lastOperatorSecondEquation = secondEquation.chainOperator.at(-1); - const lastOperatorThirdEquation = thirdEquation.chainOperator.at(-1); + const lastOperatorFirstEquation = firstEquation.chainOperator[firstEquation.chainOperator.length - 1]; + const lastOperatorSecondEquation = secondEquation.chainOperator[secondEquation.chainOperator.length - 1]; + const lastOperatorThirdEquation = thirdEquation.chainOperator[thirdEquation.chainOperator.length - 1]; if ( !(lastOperatorFirstEquation && lastOperatorSecondEquation && @@ -1039,8 +1039,8 @@ describe('solver function', () => { chainOperator: [ ], }; - const lastOperatorFirstEquation = firstEquation.chainOperator.at(-1); - const lastOperatorSecondEquation = secondEquation.chainOperator.at(-1); + const lastOperatorFirstEquation = firstEquation.chainOperator[firstEquation.chainOperator.length - 1]; + const lastOperatorSecondEquation = secondEquation.chainOperator[secondEquation.chainOperator.length - 1]; if ( !(lastOperatorFirstEquation && lastOperatorSecondEquation) From df83685847645ff7c8a0ad7b67126bfac1bcbb54 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Tue, 24 Jan 2023 15:12:48 +0100 Subject: [PATCH 108/189] Add edge-case unit tests on isRelationFilterExpressionDomainEmpty --- .../test/solver-test.ts | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index e6cce5a63..74d03af84 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1637,6 +1637,52 @@ describe('solver function', () => { expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); + it('edge-case 1', + () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x>5) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + + it('edge-case 2', + () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '4.999', + term: DF.literal('4.999', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x>=5) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + it('should return false when there is no solution for the filter expression with one expression and the relation', () => { const relation: ITreeRelation = { @@ -1705,5 +1751,115 @@ describe('solver function', () => { expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); + + it(`edge-case 3`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(!(?x<-1 || ?x<5) || true) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it(`edge-case 4`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(!(?x<-1 || ?x<5) || false) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it(`edge-case 5`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x = 5 || true) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it(`edge-case 6`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER((?x = 5 && NOW() = true && false) || (?x = 5 && NOW() = true)) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it(`edge-case 7`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER((?x = 5 && NOW() = true && false) || (?x = 5 && (NOW() = true || ?x >= 5))) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); }); }); From b043adb477f471bcd89a91a7b525bbda549bb62e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 2 Feb 2023 15:24:19 +0100 Subject: [PATCH 109/189] Comment added to the first operation more clear. --- packages/actor-extract-links-extract-tree/lib/solver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index cb205e123..a13c2125c 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -222,6 +222,7 @@ export function createEquationSystem(expressions: ISolverExpression[]): // The first expression that has to be evaluated let firstEquationToEvaluate: [ISolverExpressionRange, ISolverExpressionRange] | undefined; // The last logical operator apply to the first expression to be evaluated + // it is the one at the end of the chain of operation let firstEquationLastOperator = ''; for (const expression of expressions) { From 6656bea229be2483282c9ad035e238bb556eae7e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 9 Feb 2023 09:31:21 +0100 Subject: [PATCH 110/189] Recursive solution started. --- .../lib/solver.ts | 66 +++- .../test/solver-test.ts | 327 +++++++++++------- 2 files changed, 252 insertions(+), 141 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index a13c2125c..8c71a070a 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -60,7 +60,7 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi // If the filter has multiple expression if (Array.isArray(equationSystemFirstEquation)) { - const [ equationSystem, firstEquationToResolved ] = equationSystemFirstEquation; + const [equationSystem, firstEquationToResolved] = equationSystemFirstEquation; // We check if the filter expression itself has a solution solutionDomain = resolveSolutionDomainEquationSystem(equationSystem, firstEquationToResolved); @@ -122,6 +122,44 @@ export function recursifFilterExpressionToSolverExpression(filterExpression: Alg } return filterExpressionList; } + +export function recursifResolve(filterExpression: Algebra.Expression, + domain: SolutionDomain, + logicOperator: LogicOperator, + variable: Variable, + notExpression: boolean +): SolutionDomain { + // If it's an array of term then we should be able to create a solver expression + if ( + filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM + ) { + const rawOperator = filterExpression.operator; + const operator = filterOperatorToSparqlRelationOperator(rawOperator); + if (operator) { + const solverExpression = resolveAFilterTerm(filterExpression, operator, [], variable); + if (!solverExpression?.valueAsNumber) { + throw new Error('unable to get the number value of the expression'); + } + const solverRange = getSolutionRange(solverExpression?.valueAsNumber, solverExpression?.operator); + domain = domain.add({ range: solverRange, operator: logicOperator }); + } + // Else we store the logical operator an go deeper into the Algebra graph + } else { + const newLogicOperator = LogicOperatorReversed.get(filterExpression.operator); + notExpression = newLogicOperator === LogicOperator.Not; + if (newLogicOperator) { + for (const arg of filterExpression.args) { + if (notExpression) { + recursifResolve(arg, domain, logicOperator, variable, notExpression); + } + recursifResolve(arg, domain, newLogicOperator, variable, notExpression); + } + } + } + return domain; +} + + /** * From an Algebra expression return an solver expression if possible * @param {Algebra.Expression} expression - Algebra expression containing the a variable and a litteral. @@ -190,7 +228,7 @@ export function resolveSolutionDomainEquationSystem(equationSystem: SolverEquati if (!resp) { throw new Error(`unable to resolve the equation ${currentEquation.chainOperator}`); } - [ domain, idx ] = resp; + [domain, idx] = resp; currentEquation = equationSystem.get(idx); i++; @@ -206,7 +244,7 @@ export function resolveSolutionDomainEquationSystem(equationSystem: SolverEquati * the system of equation and the first expression to evaluate. */ export function createEquationSystem(expressions: ISolverExpression[]): -[SolverEquationSystem, [ISolverExpressionRange, ISolverExpressionRange]] | ISolverExpressionRange | undefined { + [SolverEquationSystem, [ISolverExpressionRange, ISolverExpressionRange]] | ISolverExpressionRange | undefined { if (expressions.length === 1) { const solutionRange = getSolutionRange(expressions[0].valueAsNumber, expressions[0].operator); if (!solutionRange) { @@ -241,7 +279,7 @@ export function createEquationSystem(expressions: ISolverExpression[]): if (firstEquationLastOperator !== '') { return undefined; } - firstEquationToEvaluate = [ lastEquation, equation ]; + firstEquationToEvaluate = [lastEquation, equation]; firstEquationLastOperator = lastOperator; } else { system.set(lastOperator, equation); @@ -254,7 +292,7 @@ export function createEquationSystem(expressions: ISolverExpression[]): // We delete the fist equation to be evaluated from the system of equation because it is a value returned system.delete(firstEquationLastOperator); - return [ system, firstEquationToEvaluate ]; + return [system, firstEquationToEvaluate]; } /** * Resolve the solution domain when we add a new expression and @@ -289,7 +327,7 @@ export function resolveSolutionDomainWithAnExpression(equation: ISolverExpressio currentLastOperator = equation.chainOperator[i]; // If it was the last expression if (!currentLastOperator) { - return [ localDomain, '' ]; + return [localDomain, '']; } // We solved all the NOT operator next to the last logical operator while (currentLastOperator?.operator === LogicOperator.Not) { @@ -298,11 +336,11 @@ export function resolveSolutionDomainWithAnExpression(equation: ISolverExpressio currentLastOperator = equation.chainOperator[i]; // It the last operator was a NOT if (!currentLastOperator?.operator) { - return [ localDomain, '' ]; + return [localDomain, '']; } } - return [ localDomain, currentLastOperator.toString() ]; + return [localDomain, currentLastOperator.toString()]; } /** * Convert a TREE relation into a solver expression. @@ -363,15 +401,15 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { export function getSolutionRange(value: number, operator: SparqlRelationOperator): SolutionRange | undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: - return new SolutionRange([ value + Number.EPSILON, Number.POSITIVE_INFINITY ]); + return new SolutionRange([value + Number.EPSILON, Number.POSITIVE_INFINITY]); case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return new SolutionRange([ value, Number.POSITIVE_INFINITY ]); + return new SolutionRange([value, Number.POSITIVE_INFINITY]); case SparqlRelationOperator.EqualThanRelation: - return new SolutionRange([ value, value ]); + return new SolutionRange([value, value]); case SparqlRelationOperator.LessThanRelation: - return new SolutionRange([ Number.NEGATIVE_INFINITY, value - Number.EPSILON ]); + return new SolutionRange([Number.NEGATIVE_INFINITY, value - Number.EPSILON]); case SparqlRelationOperator.LessThanOrEqualToRelation: - return new SolutionRange([ Number.NEGATIVE_INFINITY, value ]); + return new SolutionRange([Number.NEGATIVE_INFINITY, value]); default: // Not an operator that is compatible with number. break; @@ -462,4 +500,4 @@ export function filterOperatorToSparqlRelationOperator(filterOperator: string): default: return undefined; } -} +} \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index e6cce5a63..ad5bc274f 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -21,10 +21,12 @@ import { isRelationFilterExpressionDomainEmpty, } from '../lib/solver'; import { SparqlOperandDataTypes, LogicOperator } from '../lib/solverInterfaces'; -import type { ISolverExpression, +import type { + ISolverExpression, ISolverExpressionRange, SolverEquationSystem, - Variable } from '../lib/solverInterfaces'; + Variable +} from '../lib/solverInterfaces'; const DF = new DataFactory(); @@ -32,14 +34,14 @@ describe('solver function', () => { describe('filterOperatorToSparqlRelationOperator', () => { it('should return the RelationOperator given a string representation', () => { const testTable: [string, SparqlRelationOperator][] = [ - [ '=', SparqlRelationOperator.EqualThanRelation ], - [ '<', SparqlRelationOperator.LessThanRelation ], - [ '<=', SparqlRelationOperator.LessThanOrEqualToRelation ], - [ '>', SparqlRelationOperator.GreaterThanRelation ], - [ '>=', SparqlRelationOperator.GreaterThanOrEqualToRelation ], + ['=', SparqlRelationOperator.EqualThanRelation], + ['<', SparqlRelationOperator.LessThanRelation], + ['<=', SparqlRelationOperator.LessThanOrEqualToRelation], + ['>', SparqlRelationOperator.GreaterThanRelation], + ['>=', SparqlRelationOperator.GreaterThanOrEqualToRelation], ]; - for (const [ value, expectedAnswer ] of testTable) { + for (const [value, expectedAnswer] of testTable) { expect(filterOperatorToSparqlRelationOperator(value)).toBe(expectedAnswer); } }); @@ -88,65 +90,65 @@ describe('solver function', () => { describe('castSparqlRdfTermIntoNumber', () => { it('should return the expected number when given an integer', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - [ '19273', SparqlOperandDataTypes.Integer, 19_273 ], - [ '0', SparqlOperandDataTypes.NonPositiveInteger, 0 ], - [ '-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459 ], - [ '121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321 ], - [ '1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213 ], - [ '283', SparqlOperandDataTypes.NonNegativeInteger, 283 ], - [ '-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831 ], - [ '-1234', SparqlOperandDataTypes.UnsignedInt, -1_234 ], - [ '-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234 ], - [ '1234', SparqlOperandDataTypes.PositiveInteger, 1_234 ], + ['19273', SparqlOperandDataTypes.Integer, 19_273], + ['0', SparqlOperandDataTypes.NonPositiveInteger, 0], + ['-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459], + ['121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321], + ['1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213], + ['283', SparqlOperandDataTypes.NonNegativeInteger, 283], + ['-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831], + ['-1234', SparqlOperandDataTypes.UnsignedInt, -1_234], + ['-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234], + ['1234', SparqlOperandDataTypes.PositiveInteger, 1_234], ]; - for (const [ value, valueType, expectedNumber ] of testTable) { + for (const [value, valueType, expectedNumber] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return undefined if a non integer is pass with SparqlOperandDataTypes integer compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - [ '1.6751', SparqlOperandDataTypes.Integer ], - [ 'asbd', SparqlOperandDataTypes.PositiveInteger ], - [ '', SparqlOperandDataTypes.NegativeInteger ], + ['1.6751', SparqlOperandDataTypes.Integer], + ['asbd', SparqlOperandDataTypes.PositiveInteger], + ['', SparqlOperandDataTypes.NegativeInteger], ]; - for (const [ value, valueType ] of testTable) { + for (const [value, valueType] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return undefined if a non fraction is pass with SparqlOperandDataTypes fraction compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - [ 'asbd', SparqlOperandDataTypes.Double ], - [ '', SparqlOperandDataTypes.Float ], + ['asbd', SparqlOperandDataTypes.Double], + ['', SparqlOperandDataTypes.Float], ]; - for (const [ value, valueType ] of testTable) { + for (const [value, valueType] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return the expected number when given an decimal', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - [ '1.1', SparqlOperandDataTypes.Decimal, 1.1 ], - [ '2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321 ], - [ '1234.123', SparqlOperandDataTypes.Double, 1_234.123 ], + ['1.1', SparqlOperandDataTypes.Decimal, 1.1], + ['2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321], + ['1234.123', SparqlOperandDataTypes.Double, 1_234.123], ]; - for (const [ value, valueType, expectedNumber ] of testTable) { + for (const [value, valueType, expectedNumber] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return the expected number given a boolean', () => { const testTable: [string, number][] = [ - [ 'true', 1 ], - [ 'false', 0 ], + ['true', 1], + ['false', 0], ]; - for (const [ value, expectedNumber ] of testTable) { + for (const [value, expectedNumber] of testTable) { expect(castSparqlRdfTermIntoNumber(value, SparqlOperandDataTypes.Boolean)).toBe(expectedNumber); } }); @@ -181,27 +183,27 @@ describe('solver function', () => { const testTable: [SparqlRelationOperator, SolutionRange][] = [ [ SparqlRelationOperator.GreaterThanRelation, - new SolutionRange([ value + Number.EPSILON, Number.POSITIVE_INFINITY ]), + new SolutionRange([value + Number.EPSILON, Number.POSITIVE_INFINITY]), ], [ SparqlRelationOperator.GreaterThanOrEqualToRelation, - new SolutionRange([ value, Number.POSITIVE_INFINITY ]), + new SolutionRange([value, Number.POSITIVE_INFINITY]), ], [ SparqlRelationOperator.EqualThanRelation, - new SolutionRange([ value, value ]), + new SolutionRange([value, value]), ], [ SparqlRelationOperator.LessThanRelation, - new SolutionRange([ Number.NEGATIVE_INFINITY, value - Number.EPSILON ]), + new SolutionRange([Number.NEGATIVE_INFINITY, value - Number.EPSILON]), ], [ SparqlRelationOperator.LessThanOrEqualToRelation, - new SolutionRange([ Number.NEGATIVE_INFINITY, value ]), + new SolutionRange([Number.NEGATIVE_INFINITY, value]), ], ]; - for (const [ operator, expectedRange ] of testTable) { + for (const [operator, expectedRange] of testTable) { expect(getSolutionRange(value, operator)).toStrictEqual(expectedRange); } }); @@ -447,7 +449,7 @@ describe('solver function', () => { new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or), ], - solutionDomain: new SolutionRange([ 0, 1 ]), + solutionDomain: new SolutionRange([0, 1]), }; const expectedDomain = SolutionDomain.newWithInitialValue(equation.solutionDomain); @@ -455,7 +457,7 @@ describe('solver function', () => { const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { - const [ respDomain, respLastLogicalOperator ] = resp; + const [respDomain, respLastLogicalOperator] = resp; expect(respDomain.get_domain()).toStrictEqual(expectedDomain.get_domain()); expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); } else { @@ -465,7 +467,7 @@ describe('solver function', () => { it(`given a domain and an equation with multiple chained that are not "NOT" should return a valid new domain and the next chained operator`, () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([ 0, 1 ])); + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); const equation: ISolverExpressionRange = { chainOperator: [ new LinkOperator(LogicOperator.And), @@ -474,7 +476,7 @@ describe('solver function', () => { new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or), ], - solutionDomain: new SolutionRange([ 100, 221.3 ]), + solutionDomain: new SolutionRange([100, 221.3]), }; const expectedOperator = equation.chainOperator[equation.chainOperator.length - 1]; if (!expectedOperator) { @@ -490,7 +492,7 @@ describe('solver function', () => { const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { - const [ respDomain, respLastLogicalOperator ] = resp; + const [respDomain, respLastLogicalOperator] = resp; expect(respDomain.get_domain()).toStrictEqual(expectedDomain.get_domain()); expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); } else { @@ -500,12 +502,12 @@ describe('solver function', () => { it(`given a domain and an equation one chained operation should return a valid new domain and an empty string has the next chained operator`, () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([ 0, 1 ])); + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); const equation: ISolverExpressionRange = { chainOperator: [ new LinkOperator(LogicOperator.Or), ], - solutionDomain: new SolutionRange([ 100, 221.3 ]), + solutionDomain: new SolutionRange([100, 221.3]), }; const expectedOperator = equation.chainOperator[equation.chainOperator.length - 1]; @@ -523,7 +525,7 @@ describe('solver function', () => { const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { - const [ respDomain, respLastLogicalOperator ] = resp; + const [respDomain, respLastLogicalOperator] = resp; expect(respDomain.get_domain()).toStrictEqual(expectedDomain.get_domain()); expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); } else { @@ -534,7 +536,7 @@ describe('solver function', () => { it(`given a domain and an equation with multiple chainned operator where the later elements are "NOT" operators and the last element an "AND" operator should return a valid domain and the next last operator`, () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([ 0, 1 ])); + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); const equation: ISolverExpressionRange = { chainOperator: [ new LinkOperator(LogicOperator.And), @@ -542,7 +544,7 @@ describe('solver function', () => { new LinkOperator(LogicOperator.Not), new LinkOperator(LogicOperator.Or), ], - solutionDomain: new SolutionRange([ 100, 221.3 ]), + solutionDomain: new SolutionRange([100, 221.3]), }; const expectedOperator = equation.chainOperator[equation.chainOperator.length - 1]; @@ -563,7 +565,7 @@ describe('solver function', () => { const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { - const [ respDomain, respLastLogicalOperator ] = resp; + const [respDomain, respLastLogicalOperator] = resp; expect(respDomain).toStrictEqual(expectedDomain); expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); } else { @@ -574,14 +576,14 @@ describe('solver function', () => { it(`given a domain and an equation with multiple chainned operator where the last elements are "NOT" operators should return a valid domain and an empty string as the next operator`, () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([ 0, 1 ])); + const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); const equation: ISolverExpressionRange = { chainOperator: [ new LinkOperator(LogicOperator.Not), new LinkOperator(LogicOperator.Not), new LinkOperator(LogicOperator.Or), ], - solutionDomain: new SolutionRange([ 100, 221.3 ]), + solutionDomain: new SolutionRange([100, 221.3]), }; const expectedOperator = equation.chainOperator[equation.chainOperator.length - 1]; @@ -602,7 +604,7 @@ describe('solver function', () => { const resp = resolveSolutionDomainWithAnExpression(equation, domain); if (resp) { - const [ respDomain, respLastLogicalOperator ] = resp; + const [respDomain, respLastLogicalOperator] = resp; expect(respDomain).toStrictEqual(expectedDomain); expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); } else { @@ -614,7 +616,7 @@ describe('solver function', () => { const domain = new SolutionDomain(); const equation: ISolverExpressionRange = { chainOperator: [], - solutionDomain: new SolutionRange([ 0, 1 ]), + solutionDomain: new SolutionRange([0, 1]), }; expect(resolveSolutionDomainWithAnExpression(equation, domain)).toBeUndefined(); @@ -645,30 +647,30 @@ describe('solver function', () => { }; }; - const firstOperation = operationTemplate([ firstOperator ]); + const firstOperation = operationTemplate([firstOperator]); const firstEquation: ISolverExpressionRange = { chainOperator: firstOperation.chainOperator, - solutionDomain: new SolutionRange([ 1, 1 ]), + solutionDomain: new SolutionRange([1, 1]), }; - const secondOperation = operationTemplate([ firstOperator, secondOperator ]); + const secondOperation = operationTemplate([firstOperator, secondOperator]); const secondEquation: ISolverExpressionRange = { chainOperator: secondOperation.chainOperator, - solutionDomain: new SolutionRange([ 1, 1 ]), + solutionDomain: new SolutionRange([1, 1]), }; - const thirdOperation = operationTemplate([ firstOperator, secondOperator, thirdOperator ]); + const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); const thirdEquation: ISolverExpressionRange = { chainOperator: thirdOperation.chainOperator, - solutionDomain: new SolutionRange([ 1, 1 ]), + solutionDomain: new SolutionRange([1, 1]), }; - const lastOperation1 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); const expectedFirstEquation1: ISolverExpressionRange = { chainOperator: lastOperation1.chainOperator, - solutionDomain: new SolutionRange([ 1, 1 ]), + solutionDomain: new SolutionRange([1, 1]), }; - const lastOperation2 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); const expectedFirstEquation2: ISolverExpressionRange = { chainOperator: lastOperation2.chainOperator, - solutionDomain: new SolutionRange([ 1, 1 ]), + solutionDomain: new SolutionRange([1, 1]), }; const equations: ISolverExpression[] = [ @@ -681,9 +683,9 @@ describe('solver function', () => { equations.sort(() => Math.random() - 0.5); const expectedEquationSystem: SolverEquationSystem = new Map([ - [ firstOperator.toString(), firstEquation ], - [ secondOperator.toString(), secondEquation ], - [ thirdOperator.toString(), thirdEquation ], + [firstOperator.toString(), firstEquation], + [secondOperator.toString(), secondEquation], + [thirdOperator.toString(), thirdEquation], ]); const resp = createEquationSystem(equations); @@ -691,7 +693,7 @@ describe('solver function', () => { fail('should return an array'); } else if (resp) { - const [ respEquationSystem, [ respFirstEquation1, respFirstEquation2 ]] = resp; + const [respEquationSystem, [respFirstEquation1, respFirstEquation2]] = resp; expect(respEquationSystem).toStrictEqual(expectedEquationSystem); expect(respFirstEquation1).toStrictEqual(expectedFirstEquation1); expect(respFirstEquation2).toStrictEqual(expectedFirstEquation2); @@ -729,12 +731,12 @@ describe('solver function', () => { valueType: aValueType, valueAsNumber: avalueAsNumber, operator: SparqlRelationOperator.GeospatiallyContainsRelation, - chainOperator: [ firstOperator ], + chainOperator: [firstOperator], }; - const secondOperation = operationTemplate([ firstOperator, secondOperator ]); - const thirdOperation = operationTemplate([ firstOperator, secondOperator, thirdOperator ]); - const lastOperation1 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); - const lastOperation2 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + const secondOperation = operationTemplate([firstOperator, secondOperator]); + const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); + const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); + const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); const equations: ISolverExpression[] = [ firstOperation, @@ -771,12 +773,12 @@ describe('solver function', () => { }; }; - const firstOperation = operationTemplate([ firstOperator ]); - const secondOperation = operationTemplate([ firstOperator, secondOperator ]); - const thirdOperation = operationTemplate([ firstOperator, secondOperator, thirdOperator ]); - const lastOperation1 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); - const lastOperation2 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); - const lastOperation3 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + const firstOperation = operationTemplate([firstOperator]); + const secondOperation = operationTemplate([firstOperator, secondOperator]); + const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); + const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); + const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); + const lastOperation3 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); const equations: ISolverExpression[] = [ firstOperation, @@ -819,11 +821,11 @@ describe('solver function', () => { valueType: aValueType, valueAsNumber: avalueAsNumber, operator: SparqlRelationOperator.GeospatiallyContainsRelation, - chainOperator: [ firstOperator ], + chainOperator: [firstOperator], }; - const secondOperation = operationTemplate([ firstOperator, secondOperator ]); - const thirdOperation = operationTemplate([ firstOperator, secondOperator, thirdOperator ]); - const lastOperation1 = operationTemplate([ firstOperator, secondOperator, thirdOperator, lastOperator ]); + const secondOperation = operationTemplate([firstOperator, secondOperator]); + const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); + const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); const equations: ISolverExpression[] = [ firstOperation, @@ -850,7 +852,7 @@ describe('solver function', () => { const expectedEquationSystem: ISolverExpressionRange = { chainOperator: [], - solutionDomain: new SolutionRange([ 88, 88 ]), + solutionDomain: new SolutionRange([88, 88]), }; const resp = createEquationSystem(equation); @@ -869,7 +871,7 @@ describe('solver function', () => { const equation: ISolverExpression[] = [ { - chainOperator: [ lastOperator ], + chainOperator: [lastOperator], operator: SparqlRelationOperator.EqualThanRelation, rawValue: '88', valueAsNumber: 88, @@ -877,7 +879,7 @@ describe('solver function', () => { variable: 'x', }, { - chainOperator: [ lastOperator ], + chainOperator: [lastOperator], operator: SparqlRelationOperator.EqualThanRelation, rawValue: '33', valueAsNumber: 33, @@ -887,12 +889,12 @@ describe('solver function', () => { ]; const expectedFirstEquation1: ISolverExpressionRange = { - chainOperator: [ lastOperator ], - solutionDomain: new SolutionRange([ 88, 88 ]), + chainOperator: [lastOperator], + solutionDomain: new SolutionRange([88, 88]), }; const expectedFirstEquation2: ISolverExpressionRange = { - chainOperator: [ lastOperator ], - solutionDomain: new SolutionRange([ 33, 33 ]), + chainOperator: [lastOperator], + solutionDomain: new SolutionRange([33, 33]), }; const resp = createEquationSystem(equation); @@ -900,7 +902,7 @@ describe('solver function', () => { fail('should return an array'); } else if (resp && Array.isArray(resp)) { - const [ respEquationSystem, [ respFirstEquation1, respFirstEquation2 ]] = resp; + const [respEquationSystem, [respFirstEquation1, respFirstEquation2]] = resp; expect(respEquationSystem.size).toBe(0); expect(respFirstEquation1).toStrictEqual(expectedFirstEquation1); expect(respFirstEquation2).toStrictEqual(expectedFirstEquation2); @@ -914,7 +916,7 @@ describe('solver function', () => { const equation: ISolverExpression[] = [ { - chainOperator: [ lastOperator ], + chainOperator: [lastOperator], operator: SparqlRelationOperator.GeospatiallyContainsRelation, rawValue: '88', valueAsNumber: 88, @@ -927,6 +929,77 @@ describe('solver function', () => { expect(resp).toBeUndefined(); }); + + + it('given three equation with one ending with a "NOT" operator should apply localy the "NOT" operation and return the valid equation system', () => { + const orOperator = new LinkOperator(LogicOperator.Or); + const andOperator = new LinkOperator(LogicOperator.And); + const notOperator = new LinkOperator(LogicOperator.Not); + const insertedAndOperator = new LinkOperator(LogicOperator.And); + + const aVariable = 'x'; + const aRawValue = '1'; + const aValueType = SparqlOperandDataTypes.Int; + const avalueAsNumber = 1; + const anOperator = SparqlRelationOperator.EqualThanRelation; + + const firstExpression: ISolverExpression = { + variable: aVariable, + rawValue: aRawValue, + valueAsNumber: avalueAsNumber, + valueType: aValueType, + operator: anOperator, + chainOperator: [orOperator, andOperator] + }; + const firstEquation: ISolverExpressionRange = { + chainOperator: firstExpression.chainOperator, + solutionDomain: new SolutionRange([1, 1]) + }; + + const secondExpression: ISolverExpression = { + variable: aVariable, + rawValue: aRawValue, + valueAsNumber: avalueAsNumber, + valueType: aValueType, + operator: anOperator, + chainOperator: [orOperator, andOperator] + }; + const secondEquation: ISolverExpressionRange = { + chainOperator: secondExpression.chainOperator, + solutionDomain: new SolutionRange([1, 1]) + }; + + const thirdExpression: ISolverExpression = { + variable: aVariable, + rawValue: aRawValue, + valueAsNumber: avalueAsNumber, + valueType: aValueType, + operator: anOperator, + chainOperator: [orOperator, notOperator] + }; + const thirdEquation: ISolverExpressionRange = { + chainOperator: [orOperator, insertedAndOperator], + solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 1 - Number.EPSILON]) + }; + + const forthEquation: ISolverExpressionRange = { + chainOperator: [orOperator, insertedAndOperator], + solutionDomain: new SolutionRange([1 + Number.EPSILON, Number.POSITIVE_INFINITY]) + }; + + const equations: ISolverExpression[] = [ + firstExpression, + secondExpression, + thirdExpression + ]; + + equations.sort(() => Math.random() - 0.5); + + const expectedSystem: IEquationSystemStartingWithNot = { + notEquation: + }; + + }); }); describe('resolveSolutionDomainEquationSystem', () => { @@ -937,14 +1010,14 @@ describe('solver function', () => { const forthOperator = new LinkOperator(LogicOperator.Or); const fifthOperator = new LinkOperator(LogicOperator.And); const firstEquation: ISolverExpressionRange = { - solutionDomain: new SolutionRange([ Number.NEGATIVE_INFINITY, 33 ]), + solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33]), chainOperator: [ firstOperator, ], }; const secondEquation: ISolverExpressionRange = { - solutionDomain: new SolutionRange([ 75, 75 ]), + solutionDomain: new SolutionRange([75, 75]), chainOperator: [ firstOperator, secondOperator, @@ -952,7 +1025,7 @@ describe('solver function', () => { }; const thirdEquation: ISolverExpressionRange = { - solutionDomain: new SolutionRange([ 100, Number.POSITIVE_INFINITY ]), + solutionDomain: new SolutionRange([100, Number.POSITIVE_INFINITY]), chainOperator: [ firstOperator, secondOperator, @@ -965,20 +1038,20 @@ describe('solver function', () => { const lastOperatorThirdEquation = thirdEquation.chainOperator[thirdEquation.chainOperator.length - 1]; if ( !(lastOperatorFirstEquation && - lastOperatorSecondEquation && - lastOperatorThirdEquation) + lastOperatorSecondEquation && + lastOperatorThirdEquation) ) { fail('should be able to retrieved the last chain operator of the equation check the test implementation'); } const equationSystem: SolverEquationSystem = new Map([ - [ lastOperatorFirstEquation.toString(), firstEquation ], - [ lastOperatorSecondEquation.toString(), secondEquation ], - [ lastOperatorThirdEquation.toString(), thirdEquation ], + [lastOperatorFirstEquation.toString(), firstEquation], + [lastOperatorSecondEquation.toString(), secondEquation], + [lastOperatorThirdEquation.toString(), thirdEquation], ]); const firstEquationToSolve: [ISolverExpressionRange, ISolverExpressionRange] = [ { - solutionDomain: new SolutionRange([ 1_000, Number.POSITIVE_INFINITY ]), + solutionDomain: new SolutionRange([1_000, Number.POSITIVE_INFINITY]), chainOperator: [ firstOperator, secondOperator, @@ -988,7 +1061,7 @@ describe('solver function', () => { ], }, { - solutionDomain: new SolutionRange([ Number.NEGATIVE_INFINITY, 33 + Number.EPSILON ]), + solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33 + Number.EPSILON]), chainOperator: [ firstOperator, secondOperator, @@ -1000,8 +1073,8 @@ describe('solver function', () => { ]; // Nothing => [100, infinity] =>[-infinity, 100- epsilon] => [75,75]=> [75, 75], [-infinity, 33] const expectedDomain: SolutionRange[] = [ - new SolutionRange([ Number.NEGATIVE_INFINITY, 33 ]), - new SolutionRange([ 75, 75 ]), + new SolutionRange([Number.NEGATIVE_INFINITY, 33]), + new SolutionRange([75, 75]), ]; const resp = resolveSolutionDomainEquationSystem(equationSystem, firstEquationToSolve); @@ -1020,14 +1093,14 @@ describe('solver function', () => { const forthOperator = new LinkOperator(LogicOperator.Or); const fifthOperator = new LinkOperator(LogicOperator.And); const firstEquation: ISolverExpressionRange = { - solutionDomain: new SolutionRange([ Number.NEGATIVE_INFINITY, 33 ]), + solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33]), chainOperator: [ firstOperator, ], }; const secondEquation: ISolverExpressionRange = { - solutionDomain: new SolutionRange([ 75, 75 ]), + solutionDomain: new SolutionRange([75, 75]), chainOperator: [ firstOperator, secondOperator, @@ -1035,7 +1108,7 @@ describe('solver function', () => { }; const thirdEquation: ISolverExpressionRange = { - solutionDomain: new SolutionRange([ 100, Number.POSITIVE_INFINITY ]), + solutionDomain: new SolutionRange([100, Number.POSITIVE_INFINITY]), chainOperator: [ ], }; @@ -1043,19 +1116,19 @@ describe('solver function', () => { const lastOperatorSecondEquation = secondEquation.chainOperator[secondEquation.chainOperator.length - 1]; if ( !(lastOperatorFirstEquation && - lastOperatorSecondEquation) + lastOperatorSecondEquation) ) { fail('should be able to retrieved the last chain operator of the equation check the test implementation'); } const equationSystem: SolverEquationSystem = new Map([ - [ lastOperatorFirstEquation.toString(), firstEquation ], - [ lastOperatorSecondEquation.toString(), secondEquation ], - [ forthOperator.toString(), thirdEquation ], + [lastOperatorFirstEquation.toString(), firstEquation], + [lastOperatorSecondEquation.toString(), secondEquation], + [forthOperator.toString(), thirdEquation], ]); const firstEquationToSolve: [ISolverExpressionRange, ISolverExpressionRange] = [ { - solutionDomain: new SolutionRange([ 1_000, Number.POSITIVE_INFINITY ]), + solutionDomain: new SolutionRange([1_000, Number.POSITIVE_INFINITY]), chainOperator: [ firstOperator, secondOperator, @@ -1065,7 +1138,7 @@ describe('solver function', () => { ], }, { - solutionDomain: new SolutionRange([ Number.NEGATIVE_INFINITY, 33 + Number.EPSILON ]), + solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33 + Number.EPSILON]), chainOperator: [ firstOperator, secondOperator, @@ -1100,7 +1173,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; const variable = 'x'; const expectedSolverExpression: ISolverExpression = { variable, @@ -1134,7 +1207,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeUndefined(); }); @@ -1154,7 +1227,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); }); @@ -1167,7 +1240,7 @@ describe('solver function', () => { args: [], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeUndefined(); }); @@ -1222,7 +1295,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); }); @@ -1265,7 +1338,7 @@ describe('solver function', () => { SparqlOperandDataTypes.Integer, 2, SparqlRelationOperator.EqualThanRelation, - [ notOperator, andOperator ], + [notOperator, andOperator], ), buildSolverExpression( @@ -1274,7 +1347,7 @@ describe('solver function', () => { SparqlOperandDataTypes.Integer, 5, SparqlRelationOperator.GreaterThanRelation, - [ notOperator, andOperator ], + [notOperator, andOperator], ), ]; @@ -1319,7 +1392,7 @@ describe('solver function', () => { SparqlOperandDataTypes.Integer, 2, SparqlRelationOperator.EqualThanRelation, - [ orOperator, notOperator, andOperator ], + [orOperator, notOperator, andOperator], ), buildSolverExpression( @@ -1328,7 +1401,7 @@ describe('solver function', () => { SparqlOperandDataTypes.Integer, 5, SparqlRelationOperator.GreaterThanRelation, - [ orOperator, notOperator, andOperator ], + [orOperator, notOperator, andOperator], ), buildSolverExpression( @@ -1337,7 +1410,7 @@ describe('solver function', () => { SparqlOperandDataTypes.Decimal, 88.3, SparqlRelationOperator.LessThanRelation, - [ orOperator ], + [orOperator], ), ]; @@ -1383,7 +1456,7 @@ describe('solver function', () => { SparqlOperandDataTypes.Integer, 2, SparqlRelationOperator.EqualThanRelation, - [ firstOrOperator, notOperator, secondOrOperator, andOperator ], + [firstOrOperator, notOperator, secondOrOperator, andOperator], ), buildSolverExpression( @@ -1392,7 +1465,7 @@ describe('solver function', () => { SparqlOperandDataTypes.Integer, 5, SparqlRelationOperator.GreaterThanRelation, - [ firstOrOperator, notOperator, secondOrOperator, andOperator ], + [firstOrOperator, notOperator, secondOrOperator, andOperator], ), buildSolverExpression( @@ -1401,7 +1474,7 @@ describe('solver function', () => { SparqlOperandDataTypes.Integer, 6, SparqlRelationOperator.GreaterThanRelation, - [ firstOrOperator, notOperator, secondOrOperator ], + [firstOrOperator, notOperator, secondOrOperator], ), buildSolverExpression( @@ -1410,7 +1483,7 @@ describe('solver function', () => { SparqlOperandDataTypes.Decimal, 88.3, SparqlRelationOperator.LessThanRelation, - [ firstOrOperator ], + [firstOrOperator], ), ]; From ce5cb448f0b3005db8c1d148eb552450314fe5c6 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 15 Feb 2023 15:12:33 +0100 Subject: [PATCH 111/189] Ulp added to manage value just above and just below. --- .../lib/SolutionRange.ts | 10 +- .../lib/solver.ts | 55 ++++-- .../package.json | 11 +- .../test/SolutionDomain-test.ts | 7 +- .../test/SolutionRange-test.ts | 11 +- .../test/solver-test.ts | 167 ++++++++++-------- yarn.lock | 28 ++- 7 files changed, 185 insertions(+), 104 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts index 61483e907..8d7c77810 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts @@ -1,3 +1,5 @@ +const nextUp = require('ulp').nextUp; +const nextDown = require('ulp').nextDown; /** * A class representing the range of a solution it contain method to * facilitate operation between subdomain. @@ -84,15 +86,15 @@ export class SolutionRange { } if (this.lower === Number.NEGATIVE_INFINITY) { - return [ new SolutionRange([ this.upper + Number.EPSILON, Number.POSITIVE_INFINITY ]) ]; + return [ new SolutionRange([ nextUp(this.upper), Number.POSITIVE_INFINITY ]) ]; } if (this.upper === Number.POSITIVE_INFINITY) { - return [ new SolutionRange([ Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON ]) ]; + return [ new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(this.lower) ]) ]; } return [ - new SolutionRange([ Number.NEGATIVE_INFINITY, this.lower - Number.EPSILON ]), - new SolutionRange([ this.upper + Number.EPSILON, Number.POSITIVE_INFINITY ]), + new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(this.lower) ]), + new SolutionRange([ nextUp(this.upper), Number.POSITIVE_INFINITY ]), ]; } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 8c71a070a..0a58fca59 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -14,6 +14,10 @@ import type { Variable, ISolverExpressionRange, } from './solverInterfaces'; +const nextUp = require('ulp').nextUp; +const nextDown = require('ulp').nextDown; + +const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]); /** * Check if the solution domain of a system of equation compose of the expressions of the filter * expression and the relation is not empty. @@ -123,13 +127,30 @@ export function recursifFilterExpressionToSolverExpression(filterExpression: Alg return filterExpressionList; } -export function recursifResolve(filterExpression: Algebra.Expression, +/** + * + * @param {Algebra.Expression} filterExpression - The current filter expression that we are traversing + * @param {SolutionDomain} domain - The current resultant solution domain + * @param {LogicOperator} logicOperator - The current logic operator that we have to apply to the boolean expression + * @param {Variable} variable - The variable targeted inside the filter expression + * @param {boolean} notExpression + * @returns {SolutionDomain} The solution domain of the whole expression + */ +export function recursifResolve( + filterExpression: Algebra.Expression, domain: SolutionDomain, - logicOperator: LogicOperator, + logicOperator: LogicOperator | undefined, variable: Variable, - notExpression: boolean + notExpression: boolean, ): SolutionDomain { + + // we apply an or operator by default + if (!logicOperator) { + logicOperator = LogicOperator.Or; + } // If it's an array of term then we should be able to create a solver expression + // hence get a subdomain appendable to the current global domain with consideration + // to the logic operator if ( filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM ) { @@ -141,25 +162,37 @@ export function recursifResolve(filterExpression: Algebra.Expression, throw new Error('unable to get the number value of the expression'); } const solverRange = getSolutionRange(solverExpression?.valueAsNumber, solverExpression?.operator); - domain = domain.add({ range: solverRange, operator: logicOperator }); + if (!solverRange) { + throw new Error('unable to get the range of an expression'); + } + // We can distribute a not expression, so we inverse each statement + if (notExpression) { + const invertedRanges = solverRange.inverse(); + // We first solve the new inverted expression of the form + // (E1 AND E2) after that we apply the original operator + for (const range of invertedRanges) { + domain = domain.add({ range, operator: logicOperator }); + } + } else { + domain = domain.add({ range: solverRange, operator: logicOperator }); + } } // Else we store the logical operator an go deeper into the Algebra graph } else { const newLogicOperator = LogicOperatorReversed.get(filterExpression.operator); - notExpression = newLogicOperator === LogicOperator.Not; + notExpression = newLogicOperator === LogicOperator.Not || notExpression; if (newLogicOperator) { for (const arg of filterExpression.args) { if (notExpression) { - recursifResolve(arg, domain, logicOperator, variable, notExpression); + domain = recursifResolve(arg, domain, logicOperator, variable, notExpression); } - recursifResolve(arg, domain, newLogicOperator, variable, notExpression); + domain = recursifResolve(arg, domain, newLogicOperator, variable, notExpression); } } } return domain; } - /** * From an Algebra expression return an solver expression if possible * @param {Algebra.Expression} expression - Algebra expression containing the a variable and a litteral. @@ -401,13 +434,13 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { export function getSolutionRange(value: number, operator: SparqlRelationOperator): SolutionRange | undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: - return new SolutionRange([value + Number.EPSILON, Number.POSITIVE_INFINITY]); + return new SolutionRange([nextUp(value), Number.POSITIVE_INFINITY]); case SparqlRelationOperator.GreaterThanOrEqualToRelation: return new SolutionRange([value, Number.POSITIVE_INFINITY]); case SparqlRelationOperator.EqualThanRelation: return new SolutionRange([value, value]); case SparqlRelationOperator.LessThanRelation: - return new SolutionRange([Number.NEGATIVE_INFINITY, value - Number.EPSILON]); + return new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(value)]); case SparqlRelationOperator.LessThanOrEqualToRelation: return new SolutionRange([Number.NEGATIVE_INFINITY, value]); default: @@ -500,4 +533,4 @@ export function filterOperatorToSparqlRelationOperator(filterOperator: string): default: return undefined; } -} \ No newline at end of file +} diff --git a/packages/actor-extract-links-extract-tree/package.json b/packages/actor-extract-links-extract-tree/package.json index 9d5773f5e..80c4f55c2 100644 --- a/packages/actor-extract-links-extract-tree/package.json +++ b/packages/actor-extract-links-extract-tree/package.json @@ -31,17 +31,18 @@ "lib/**/*.js" ], "dependencies": { - "@comunica/core": "^2.4.0", + "@comunica/bindings-factory": "^2.2.0", "@comunica/bus-extract-links": "^0.0.1", - "rdf-data-factory": "^1.1.1", - "rdf-store-stream": "^1.3.0", - "rdf-string": "^1.6.1", "@comunica/context-entries": "^2.4.0", "@comunica/context-entries-link-traversal": "^0.0.1", + "@comunica/core": "^2.4.0", "@comunica/types-link-traversal": "^0.0.1", + "rdf-data-factory": "^1.1.1", + "rdf-store-stream": "^1.3.0", + "rdf-string": "^1.6.1", "sparqlalgebrajs": "^4.0.0", "sparqlee": "^2.1.0", - "@comunica/bindings-factory": "^2.2.0" + "ulp": "^1.0.1" }, "scripts": { "build": "npm run build:ts && npm run build:components", diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index af2dc1dab..360b90a03 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -2,6 +2,9 @@ import { SolutionRange } from '../lib//SolutionRange'; import { SolutionDomain } from '../lib/SolutionDomain'; import { LogicOperator } from '../lib/solverInterfaces'; +const nextUp = require('ulp').nextUp; +const nextDown = require('ulp').nextDown; + describe('SolutionDomain', () => { describe('constructor', () => { it('should return an empty solution domain', () => { @@ -108,8 +111,8 @@ describe('SolutionDomain', () => { const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); const expectedDomain = [ - new SolutionRange([ Number.NEGATIVE_INFINITY, 0 - Number.EPSILON ]), - new SolutionRange([ 1 + Number.EPSILON, Number.POSITIVE_INFINITY ]), + new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(0) ]), + new SolutionRange([ nextUp(1) , Number.POSITIVE_INFINITY ]), ]; const newDomain = solutionDomain.notOperation(); diff --git a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts index 756a0ac5c..9c4f912d9 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts @@ -1,5 +1,8 @@ import { SolutionRange } from '../lib//SolutionRange'; +const nextUp = require('ulp').nextUp; +const nextDown = require('ulp').nextDown; + describe('SolutionRange', () => { describe('constructor', () => { it('should have the right parameters when building', () => { @@ -251,7 +254,7 @@ describe('SolutionRange', () => { Number.POSITIVE_INFINITY, ]); - const expectedRange = new SolutionRange([ Number.NEGATIVE_INFINITY, 21 - Number.EPSILON ]); + const expectedRange = new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(21) ]); const resp = aSolutionRange.inverse(); @@ -265,7 +268,7 @@ describe('SolutionRange', () => { -21, ]); - const expectedRange = new SolutionRange([ -21 + Number.EPSILON, Number.POSITIVE_INFINITY ]); + const expectedRange = new SolutionRange([ nextUp(-21), Number.POSITIVE_INFINITY ]); const resp = aSolutionRange.inverse(); @@ -280,8 +283,8 @@ describe('SolutionRange', () => { ]); const expectedRange = [ - new SolutionRange([ Number.NEGATIVE_INFINITY, -33 - Number.EPSILON ]), - new SolutionRange([ 21 + Number.EPSILON, Number.POSITIVE_INFINITY ]), + new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(-33) ]), + new SolutionRange([ nextUp(21), Number.POSITIVE_INFINITY ]), ]; const resp = aSolutionRange.inverse(); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index ad5bc274f..b0dc83afd 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -19,6 +19,7 @@ import { resolveAFilterTerm, recursifFilterExpressionToSolverExpression, isRelationFilterExpressionDomainEmpty, + recursifResolve, } from '../lib/solver'; import { SparqlOperandDataTypes, LogicOperator } from '../lib/solverInterfaces'; import type { @@ -28,6 +29,9 @@ import type { Variable } from '../lib/solverInterfaces'; +const nextUp = require('ulp').nextUp; +const nextDown = require('ulp').nextDown; + const DF = new DataFactory(); describe('solver function', () => { @@ -183,7 +187,7 @@ describe('solver function', () => { const testTable: [SparqlRelationOperator, SolutionRange][] = [ [ SparqlRelationOperator.GreaterThanRelation, - new SolutionRange([value + Number.EPSILON, Number.POSITIVE_INFINITY]), + new SolutionRange([nextUp(value), Number.POSITIVE_INFINITY]), ], [ SparqlRelationOperator.GreaterThanOrEqualToRelation, @@ -195,7 +199,7 @@ describe('solver function', () => { ], [ SparqlRelationOperator.LessThanRelation, - new SolutionRange([Number.NEGATIVE_INFINITY, value - Number.EPSILON]), + new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(value)]), ], [ SparqlRelationOperator.LessThanOrEqualToRelation, @@ -930,76 +934,6 @@ describe('solver function', () => { expect(resp).toBeUndefined(); }); - - it('given three equation with one ending with a "NOT" operator should apply localy the "NOT" operation and return the valid equation system', () => { - const orOperator = new LinkOperator(LogicOperator.Or); - const andOperator = new LinkOperator(LogicOperator.And); - const notOperator = new LinkOperator(LogicOperator.Not); - const insertedAndOperator = new LinkOperator(LogicOperator.And); - - const aVariable = 'x'; - const aRawValue = '1'; - const aValueType = SparqlOperandDataTypes.Int; - const avalueAsNumber = 1; - const anOperator = SparqlRelationOperator.EqualThanRelation; - - const firstExpression: ISolverExpression = { - variable: aVariable, - rawValue: aRawValue, - valueAsNumber: avalueAsNumber, - valueType: aValueType, - operator: anOperator, - chainOperator: [orOperator, andOperator] - }; - const firstEquation: ISolverExpressionRange = { - chainOperator: firstExpression.chainOperator, - solutionDomain: new SolutionRange([1, 1]) - }; - - const secondExpression: ISolverExpression = { - variable: aVariable, - rawValue: aRawValue, - valueAsNumber: avalueAsNumber, - valueType: aValueType, - operator: anOperator, - chainOperator: [orOperator, andOperator] - }; - const secondEquation: ISolverExpressionRange = { - chainOperator: secondExpression.chainOperator, - solutionDomain: new SolutionRange([1, 1]) - }; - - const thirdExpression: ISolverExpression = { - variable: aVariable, - rawValue: aRawValue, - valueAsNumber: avalueAsNumber, - valueType: aValueType, - operator: anOperator, - chainOperator: [orOperator, notOperator] - }; - const thirdEquation: ISolverExpressionRange = { - chainOperator: [orOperator, insertedAndOperator], - solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 1 - Number.EPSILON]) - }; - - const forthEquation: ISolverExpressionRange = { - chainOperator: [orOperator, insertedAndOperator], - solutionDomain: new SolutionRange([1 + Number.EPSILON, Number.POSITIVE_INFINITY]) - }; - - const equations: ISolverExpression[] = [ - firstExpression, - secondExpression, - thirdExpression - ]; - - equations.sort(() => Math.random() - 0.5); - - const expectedSystem: IEquationSystemStartingWithNot = { - notEquation: - }; - - }); }); describe('resolveSolutionDomainEquationSystem', () => { @@ -1061,7 +995,7 @@ describe('solver function', () => { ], }, { - solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33 + Number.EPSILON]), + solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, nextUp(33)]), chainOperator: [ firstOperator, secondOperator, @@ -1138,7 +1072,7 @@ describe('solver function', () => { ], }, { - solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33 + Number.EPSILON]), + solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, nextUp(33)]), chainOperator: [ firstOperator, secondOperator, @@ -1780,3 +1714,88 @@ describe('solver function', () => { }); }); }); + +describe('recursifResolve', () => { + it('given an algebra expression with two logicals operators should return the valid solution domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x<5) + }`).input.expression; + + + const resp = recursifResolve( + expression, + new SolutionDomain(), + undefined, + 'x', + false, + ); + + const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([2, 2])); + + expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + + }); + + it('given an algebra expression with two logicals operators that cannot be satified should return an empty domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x>5) + }`).input.expression; + + + const resp = recursifResolve( + expression, + new SolutionDomain(), + undefined, + 'x', + false, + ); + + const expectedDomain = new SolutionDomain(); + + expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + }); + + it('given an algebra expression with two logicals operators that are negated should return the valid solution domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x<5)) + }`).input.expression; + + + const resp = recursifResolve( + expression, + new SolutionDomain(), + undefined, + 'x', + false, + ); + + const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([5, Number.POSITIVE_INFINITY])); + + expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + + }); + + it('given an algebra expression with three logicals operators where the priority of operation should start with the not operator than should return the valid solution domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x>2 || !(?x=3)) + }`).input.expression; + + + const resp = recursifResolve( + expression, + new SolutionDomain(), + undefined, + 'x', + false, + ); + + let expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(3)])); + expectedDomain = expectedDomain.addWithOrOperator(new SolutionRange([nextUp(3), Number.POSITIVE_INFINITY])); + expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + + }); +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 08f848ba2..caf68aa39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1792,7 +1792,7 @@ "@comunica/types" "^2.4.0" asyncjoin "^1.1.1" -"@comunica/actor-rdf-join-inner-multi-bind@2.4.0", "@comunica/actor-rdf-join-inner-multi-bind@^2.4.0": +"@comunica/actor-rdf-join-inner-multi-bind@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-join-inner-multi-bind/-/actor-rdf-join-inner-multi-bind-2.4.0.tgz#ee3c1347480acd22980f2693ef11f92394bdc67f" integrity sha512-DNsSn8DmmqPDAvtYKLuIBiSO5T00L/AZqeID0uQ1ub1fTVgYlIggnm+fTSS6R687iwN0yqfwLmhCIg4GERfiSQ== @@ -2156,7 +2156,7 @@ rdf-terms "^1.6.2" sparqlalgebrajs "^4.0.0" -"@comunica/actor-rdf-resolve-quad-pattern-federated@2.4.0", "@comunica/actor-rdf-resolve-quad-pattern-federated@^2.4.0": +"@comunica/actor-rdf-resolve-quad-pattern-federated@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-resolve-quad-pattern-federated/-/actor-rdf-resolve-quad-pattern-federated-2.4.0.tgz#005139d7d5eaeff2be460baae60bdb35da5d14c7" integrity sha512-617CVy2S9FNnmlelrwl8pHtNgFWl/xSSKl5w9dxqw8CikthnYtgCEsyCySoukKISmpCyjzSDc4XYCW5UAlSh8w== @@ -4603,11 +4603,26 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== -"@types/node@*", "@types/node@18.0.0", "@types/node@^14.14.7", "@types/node@^16.0.0", "@types/node@^18.0.0", "@types/node@^18.0.3": +"@types/node@*", "@types/node@^18.0.0": version "18.0.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a" integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA== +"@types/node@^14.14.7": + version "14.18.36" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.36.tgz#c414052cb9d43fab67d679d5f3c641be911f5835" + integrity sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ== + +"@types/node@^16.0.0": + version "16.18.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.12.tgz#e3bfea80e31523fde4292a6118f19ffa24fd6f65" + integrity sha512-vzLe5NaNMjIE3mcddFVGlAXN1LEWueUsMsOJWaT6wWMJGyljHAWHznqfnKUQWGzu7TLPrGvWdNAsvQYW+C0xtw== + +"@types/node@^18.0.3": + version "18.13.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.13.0.tgz#0400d1e6ce87e9d3032c19eb6c58205b0d3f7850" + integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -4678,7 +4693,7 @@ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== -"@types/yargs@17.0.13", "@types/yargs@^17.0.2", "@types/yargs@^17.0.8": +"@types/yargs@^17.0.2", "@types/yargs@^17.0.8": version "17.0.13" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== @@ -11443,6 +11458,11 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.0.tgz#55bd6e9d19ce5eef0d5ad17cd1f587d85b180a85" integrity sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg== +ulp@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ulp/-/ulp-1.0.1.tgz#e3a4a5b4de9bd0faae63e4544ee63a0d9bd34bc5" + integrity sha512-fXPzKroJ6XJXrepjd4AIHfeN84qUtZfLojIPYExlF/rm/BWb8/2o64lsGEKSMSwjH9X6VKmDv9+zbd+ZNHa4nw== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" From 9a5b1e66439305a9d872e7d322d795f8c1dc3e8a Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 16 Feb 2023 11:30:37 +0100 Subject: [PATCH 112/189] Recursive solving work with the current test. --- .../actor-extract-links-extract-tree/lib/solver.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 0a58fca59..6674ed351 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -144,8 +144,8 @@ export function recursifResolve( notExpression: boolean, ): SolutionDomain { - // we apply an or operator by default - if (!logicOperator) { + // we apply an or operator by default or if the domain is empty + if (!logicOperator || domain.isDomainEmpty()) { logicOperator = LogicOperator.Or; } // If it's an array of term then we should be able to create a solver expression @@ -179,13 +179,11 @@ export function recursifResolve( } // Else we store the logical operator an go deeper into the Algebra graph } else { - const newLogicOperator = LogicOperatorReversed.get(filterExpression.operator); + let newLogicOperator = LogicOperatorReversed.get(filterExpression.operator); notExpression = newLogicOperator === LogicOperator.Not || notExpression; if (newLogicOperator) { + newLogicOperator = newLogicOperator === LogicOperator.Not ? logicOperator : newLogicOperator for (const arg of filterExpression.args) { - if (notExpression) { - domain = recursifResolve(arg, domain, logicOperator, variable, notExpression); - } domain = recursifResolve(arg, domain, newLogicOperator, variable, notExpression); } } From 96f6d044e1b486400ff6643eebb53b8051c9aea7 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 16 Feb 2023 15:15:23 +0100 Subject: [PATCH 113/189] lint-fix --- .../lib/solver.ts | 251 +--- .../test/SolutionDomain-test.ts | 2 +- .../test/solver-test.ts | 1071 ++--------------- 3 files changed, 148 insertions(+), 1176 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 6674ed351..a1f245a54 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -10,14 +10,14 @@ import { LogicOperatorReversed, LogicOperator, SparqlOperandDataTypesReversed, } from './solverInterfaces'; import type { - LastLogicalOperator, SolverEquationSystem, ISolverExpression, - Variable, ISolverExpressionRange, + ISolverExpression, + Variable, } from './solverInterfaces'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; -const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]); +const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]); /** * Check if the solution domain of a system of equation compose of the expressions of the filter * expression and the relation is not empty. @@ -38,11 +38,6 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi if (!relationsolverExpressions) { return true; } - const filtersolverExpressions = recursifFilterExpressionToSolverExpression(filterExpression, [], [], variable); - // The type are not compatible no evaluation is possible SPARQLEE will later return an error - if (!areTypesCompatible(filtersolverExpressions.concat(relationsolverExpressions))) { - return true; - } const relationSolutionRange = getSolutionRange( relationsolverExpressions.valueAsNumber, @@ -52,29 +47,27 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi if (!relationSolutionRange) { return true; } - const equationSystemFirstEquation = createEquationSystem(filtersolverExpressions); - - // Cannot create the equation system we don't filter the relation in case the error is internal to not - // lose results - if (!equationSystemFirstEquation) { - return true; - } - - let solutionDomain: SolutionDomain; - - // If the filter has multiple expression - if (Array.isArray(equationSystemFirstEquation)) { - const [equationSystem, firstEquationToResolved] = equationSystemFirstEquation; - - // We check if the filter expression itself has a solution - solutionDomain = resolveSolutionDomainEquationSystem(equationSystem, firstEquationToResolved); + let solutionDomain: SolutionDomain = new SolutionDomain(); + try { + solutionDomain = recursifResolve( + filterExpression, + solutionDomain, + undefined, + variable, + false, + ); + } catch (error: unknown) { + // Was not able to create a boolean expression from a filter argument + // it is because either the TREE document is badly formated or the variable doesn't match + if (error instanceof SyntaxError) { + return true; + } - // Don't pass the relation if the filter cannot be resolved - if (solutionDomain.isDomainEmpty()) { - return false; + // We don't support the data type of relation yet, so we don't prune the link + if (error instanceof TypeError) { + return true; } - } else { - solutionDomain = SolutionDomain.newWithInitialValue(equationSystemFirstEquation.solutionDomain); + throw error; } // Evaluate the solution domain when adding the relation @@ -85,50 +78,8 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi } /** - * A recursif function that traverse the Algebra expression to capture each boolean expression and there associated - * chain of logical expression. On the first call the filterExpressionList and linksOperator must be empty, they serve - * as states to build the expressions. - * @param {Algebra.Expression} filterExpression - The expression of the filter. - * @param {ISolverExpression[]} filterExpressionList - The solver expression acquire until then. - * Should be empty on the first call. - * @param {LinkOperator[]} linksOperator - The logical operator acquire until then. - * Should be empty on the first call. - * @param {Variable} variable - The variable the solver expression must posses. - * @returns {ISolverExpression[]} Return the solver expression converted from the filter expression - */ -export function recursifFilterExpressionToSolverExpression(filterExpression: Algebra.Expression, - filterExpressionList: ISolverExpression[], - linksOperator: LinkOperator[], - variable: Variable): - ISolverExpression[] { - // If it's an array of term then we should be able to create a solver expression - if ( - filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM - ) { - const rawOperator = filterExpression.operator; - const operator = filterOperatorToSparqlRelationOperator(rawOperator); - if (operator) { - const solverExpression = resolveAFilterTerm(filterExpression, operator, new Array(...linksOperator), variable); - if (solverExpression) { - filterExpressionList.push(solverExpression); - return filterExpressionList; - } - } - // Else we store the logical operator an go deeper into the Algebra graph - } else { - const logicOperator = LogicOperatorReversed.get(filterExpression.operator); - if (logicOperator) { - const operator = new LinkOperator(logicOperator); - for (const arg of filterExpression.args) { - recursifFilterExpressionToSolverExpression(arg, filterExpressionList, linksOperator.concat(operator), variable); - } - } - } - return filterExpressionList; -} - -/** - * + * Recursively traverse the filter expression and calculate the domain until it get to the current expression. + * It will thrown an error if the expression is badly formated or if it's impossible to get the solution range. * @param {Algebra.Expression} filterExpression - The current filter expression that we are traversing * @param {SolutionDomain} domain - The current resultant solution domain * @param {LogicOperator} logicOperator - The current logic operator that we have to apply to the boolean expression @@ -143,8 +94,7 @@ export function recursifResolve( variable: Variable, notExpression: boolean, ): SolutionDomain { - - // we apply an or operator by default or if the domain is empty + // We apply an or operator by default or if the domain is empty if (!logicOperator || domain.isDomainEmpty()) { logicOperator = LogicOperator.Or; } @@ -159,11 +109,11 @@ export function recursifResolve( if (operator) { const solverExpression = resolveAFilterTerm(filterExpression, operator, [], variable); if (!solverExpression?.valueAsNumber) { - throw new Error('unable to get the number value of the expression'); + throw new SyntaxError('unable to get the number value of the expression'); } const solverRange = getSolutionRange(solverExpression?.valueAsNumber, solverExpression?.operator); if (!solverRange) { - throw new Error('unable to get the range of an expression'); + throw new TypeError('unable to get the range of an expression'); } // We can distribute a not expression, so we inverse each statement if (notExpression) { @@ -182,7 +132,7 @@ export function recursifResolve( let newLogicOperator = LogicOperatorReversed.get(filterExpression.operator); notExpression = newLogicOperator === LogicOperator.Not || notExpression; if (newLogicOperator) { - newLogicOperator = newLogicOperator === LogicOperator.Not ? logicOperator : newLogicOperator + newLogicOperator = newLogicOperator === LogicOperator.Not ? logicOperator : newLogicOperator; for (const arg of filterExpression.args) { domain = recursifResolve(arg, domain, newLogicOperator, variable, notExpression); } @@ -237,142 +187,7 @@ export function resolveAFilterTerm(expression: Algebra.Expression, }; } } -/** - * Find the domain of the possible solutions of a system of equations. - * Will thrown an error if an equation cannot be resolved. - * @param {SolverEquationSystem} equationSystem - * @param {[ISolverExpressionRange, ISolverExpressionRange]} firstExpression - The first expression to evaluate. - * @returns {SolutionDomain} - */ -export function resolveSolutionDomainEquationSystem(equationSystem: SolverEquationSystem, - firstExpression: [ISolverExpressionRange, - ISolverExpressionRange]): - SolutionDomain { - let domain: SolutionDomain = SolutionDomain.newWithInitialValue(firstExpression[0].solutionDomain); - let idx = ''; - // Safety to avoid infinite loop - let i = 0; - let currentEquation: ISolverExpressionRange | undefined = firstExpression[1]; - do { - const resp = resolveSolutionDomainWithAnExpression(currentEquation, domain); - if (!resp) { - throw new Error(`unable to resolve the equation ${currentEquation.chainOperator}`); - } - [domain, idx] = resp; - - currentEquation = equationSystem.get(idx); - i++; - } while (currentEquation && i !== equationSystem.size + 1); - - return domain; -} -/** - * Create a system of equation from the provided expression and return separatly the first expression to evaluate. - * @param {ISolverExpression[]} expressions - the expression composing the equation system - * @returns {[SolverEquationSystem, [ISolverExpressionRange, ISolverExpressionRange]] - * | undefined} if the expression form a possible system of equation return - * the system of equation and the first expression to evaluate. - */ -export function createEquationSystem(expressions: ISolverExpression[]): - [SolverEquationSystem, [ISolverExpressionRange, ISolverExpressionRange]] | ISolverExpressionRange | undefined { - if (expressions.length === 1) { - const solutionRange = getSolutionRange(expressions[0].valueAsNumber, expressions[0].operator); - if (!solutionRange) { - return undefined; - } - return { - chainOperator: [], - solutionDomain: solutionRange, - }; - } - - const system: SolverEquationSystem = new Map(); - // The first expression that has to be evaluated - let firstEquationToEvaluate: [ISolverExpressionRange, ISolverExpressionRange] | undefined; - // The last logical operator apply to the first expression to be evaluated - // it is the one at the end of the chain of operation - let firstEquationLastOperator = ''; - - for (const expression of expressions) { - const lastOperator = expression.chainOperator.slice(-1).toString(); - const solutionRange = getSolutionRange(expression.valueAsNumber, expression.operator); - if (!solutionRange) { - return undefined; - } - const equation: ISolverExpressionRange = { - chainOperator: expression.chainOperator, - solutionDomain: solutionRange, - }; - const lastEquation = system.get(lastOperator); - if (lastEquation) { - // There cannot be two first equation to be evaluated - if (firstEquationLastOperator !== '') { - return undefined; - } - firstEquationToEvaluate = [lastEquation, equation]; - firstEquationLastOperator = lastOperator; - } else { - system.set(lastOperator, equation); - } - } - // There should be a first expression to be evaluated - if (!firstEquationToEvaluate) { - return undefined; - } - // We delete the fist equation to be evaluated from the system of equation because it is a value returned - system.delete(firstEquationLastOperator); - - return [system, firstEquationToEvaluate]; -} -/** - * Resolve the solution domain when we add a new expression and - * returned the new domain with the next expression that has to be evaluated. - * @param {ISolverExpressionRange} equation - Current solver expression. - * @param {SolutionDomain} domain - Current solution domain of the system of equation. - * @returns {[SolutionDomain, LastLogicalOperator] | - * undefined} If the equation can be solved returned the new domain and the next - * indexed logical operator that has to be resolved. - * The system of equation is indexed by their last logical operator hence - * the next expression can be found using the returned operator. - * An empty string is returned if it was the last expression instead of - * undefined to simply implementation, - * because the system of equation will retuned an undefined value with an empty string. - */ -export function resolveSolutionDomainWithAnExpression(equation: ISolverExpressionRange, - domain: SolutionDomain): - [SolutionDomain, LastLogicalOperator] | undefined { - let localDomain = domain.clone(); - // To keep track of the last expression because we resolved all the not operator - // next to the current last logical operator - let i = equation.chainOperator.length - 1; - // We find the last logical expression that has to be resolved - let currentLastOperator = equation.chainOperator[i]; - if (!currentLastOperator) { - return undefined; - } - i--; - // Resolve the new domain - localDomain = localDomain.add({ range: equation.solutionDomain, operator: currentLastOperator?.operator }); - - currentLastOperator = equation.chainOperator[i]; - // If it was the last expression - if (!currentLastOperator) { - return [localDomain, '']; - } - // We solved all the NOT operator next to the last logical operator - while (currentLastOperator?.operator === LogicOperator.Not) { - localDomain = localDomain.add({ operator: currentLastOperator?.operator }); - i--; - currentLastOperator = equation.chainOperator[i]; - // It the last operator was a NOT - if (!currentLastOperator?.operator) { - return [localDomain, '']; - } - } - - return [localDomain, currentLastOperator.toString()]; -} /** * Convert a TREE relation into a solver expression. * @param {ITreeRelation} relation - TREE relation. @@ -432,15 +247,15 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { export function getSolutionRange(value: number, operator: SparqlRelationOperator): SolutionRange | undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: - return new SolutionRange([nextUp(value), Number.POSITIVE_INFINITY]); + return new SolutionRange([ nextUp(value), Number.POSITIVE_INFINITY ]); case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return new SolutionRange([value, Number.POSITIVE_INFINITY]); + return new SolutionRange([ value, Number.POSITIVE_INFINITY ]); case SparqlRelationOperator.EqualThanRelation: - return new SolutionRange([value, value]); + return new SolutionRange([ value, value ]); case SparqlRelationOperator.LessThanRelation: - return new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(value)]); + return new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(value) ]); case SparqlRelationOperator.LessThanOrEqualToRelation: - return new SolutionRange([Number.NEGATIVE_INFINITY, value]); + return new SolutionRange([ Number.NEGATIVE_INFINITY, value ]); default: // Not an operator that is compatible with number. break; diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index 360b90a03..fb5b41448 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -112,7 +112,7 @@ describe('SolutionDomain', () => { const expectedDomain = [ new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(0) ]), - new SolutionRange([ nextUp(1) , Number.POSITIVE_INFINITY ]), + new SolutionRange([ nextUp(1), Number.POSITIVE_INFINITY ]), ]; const newDomain = solutionDomain.notOperation(); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index b0dc83afd..3375cbea1 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -13,20 +13,13 @@ import { getSolutionRange, areTypesCompatible, convertTreeRelationToSolverExpression, - resolveSolutionDomainWithAnExpression, - createEquationSystem, - resolveSolutionDomainEquationSystem, resolveAFilterTerm, - recursifFilterExpressionToSolverExpression, isRelationFilterExpressionDomainEmpty, recursifResolve, } from '../lib/solver'; import { SparqlOperandDataTypes, LogicOperator } from '../lib/solverInterfaces'; import type { ISolverExpression, - ISolverExpressionRange, - SolverEquationSystem, - Variable } from '../lib/solverInterfaces'; const nextUp = require('ulp').nextUp; @@ -38,14 +31,14 @@ describe('solver function', () => { describe('filterOperatorToSparqlRelationOperator', () => { it('should return the RelationOperator given a string representation', () => { const testTable: [string, SparqlRelationOperator][] = [ - ['=', SparqlRelationOperator.EqualThanRelation], - ['<', SparqlRelationOperator.LessThanRelation], - ['<=', SparqlRelationOperator.LessThanOrEqualToRelation], - ['>', SparqlRelationOperator.GreaterThanRelation], - ['>=', SparqlRelationOperator.GreaterThanOrEqualToRelation], + [ '=', SparqlRelationOperator.EqualThanRelation ], + [ '<', SparqlRelationOperator.LessThanRelation ], + [ '<=', SparqlRelationOperator.LessThanOrEqualToRelation ], + [ '>', SparqlRelationOperator.GreaterThanRelation ], + [ '>=', SparqlRelationOperator.GreaterThanOrEqualToRelation ], ]; - for (const [value, expectedAnswer] of testTable) { + for (const [ value, expectedAnswer ] of testTable) { expect(filterOperatorToSparqlRelationOperator(value)).toBe(expectedAnswer); } }); @@ -94,65 +87,65 @@ describe('solver function', () => { describe('castSparqlRdfTermIntoNumber', () => { it('should return the expected number when given an integer', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - ['19273', SparqlOperandDataTypes.Integer, 19_273], - ['0', SparqlOperandDataTypes.NonPositiveInteger, 0], - ['-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459], - ['121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321], - ['1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213], - ['283', SparqlOperandDataTypes.NonNegativeInteger, 283], - ['-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831], - ['-1234', SparqlOperandDataTypes.UnsignedInt, -1_234], - ['-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234], - ['1234', SparqlOperandDataTypes.PositiveInteger, 1_234], + [ '19273', SparqlOperandDataTypes.Integer, 19_273 ], + [ '0', SparqlOperandDataTypes.NonPositiveInteger, 0 ], + [ '-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459 ], + [ '121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321 ], + [ '1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213 ], + [ '283', SparqlOperandDataTypes.NonNegativeInteger, 283 ], + [ '-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831 ], + [ '-1234', SparqlOperandDataTypes.UnsignedInt, -1_234 ], + [ '-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234 ], + [ '1234', SparqlOperandDataTypes.PositiveInteger, 1_234 ], ]; - for (const [value, valueType, expectedNumber] of testTable) { + for (const [ value, valueType, expectedNumber ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return undefined if a non integer is pass with SparqlOperandDataTypes integer compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - ['1.6751', SparqlOperandDataTypes.Integer], - ['asbd', SparqlOperandDataTypes.PositiveInteger], - ['', SparqlOperandDataTypes.NegativeInteger], + [ '1.6751', SparqlOperandDataTypes.Integer ], + [ 'asbd', SparqlOperandDataTypes.PositiveInteger ], + [ '', SparqlOperandDataTypes.NegativeInteger ], ]; - for (const [value, valueType] of testTable) { + for (const [ value, valueType ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return undefined if a non fraction is pass with SparqlOperandDataTypes fraction compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - ['asbd', SparqlOperandDataTypes.Double], - ['', SparqlOperandDataTypes.Float], + [ 'asbd', SparqlOperandDataTypes.Double ], + [ '', SparqlOperandDataTypes.Float ], ]; - for (const [value, valueType] of testTable) { + for (const [ value, valueType ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return the expected number when given an decimal', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - ['1.1', SparqlOperandDataTypes.Decimal, 1.1], - ['2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321], - ['1234.123', SparqlOperandDataTypes.Double, 1_234.123], + [ '1.1', SparqlOperandDataTypes.Decimal, 1.1 ], + [ '2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321 ], + [ '1234.123', SparqlOperandDataTypes.Double, 1_234.123 ], ]; - for (const [value, valueType, expectedNumber] of testTable) { + for (const [ value, valueType, expectedNumber ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return the expected number given a boolean', () => { const testTable: [string, number][] = [ - ['true', 1], - ['false', 0], + [ 'true', 1 ], + [ 'false', 0 ], ]; - for (const [value, expectedNumber] of testTable) { + for (const [ value, expectedNumber ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, SparqlOperandDataTypes.Boolean)).toBe(expectedNumber); } }); @@ -187,27 +180,27 @@ describe('solver function', () => { const testTable: [SparqlRelationOperator, SolutionRange][] = [ [ SparqlRelationOperator.GreaterThanRelation, - new SolutionRange([nextUp(value), Number.POSITIVE_INFINITY]), + new SolutionRange([ nextUp(value), Number.POSITIVE_INFINITY ]), ], [ SparqlRelationOperator.GreaterThanOrEqualToRelation, - new SolutionRange([value, Number.POSITIVE_INFINITY]), + new SolutionRange([ value, Number.POSITIVE_INFINITY ]), ], [ SparqlRelationOperator.EqualThanRelation, - new SolutionRange([value, value]), + new SolutionRange([ value, value ]), ], [ SparqlRelationOperator.LessThanRelation, - new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(value)]), + new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(value) ]), ], [ SparqlRelationOperator.LessThanOrEqualToRelation, - new SolutionRange([Number.NEGATIVE_INFINITY, value]), + new SolutionRange([ Number.NEGATIVE_INFINITY, value ]), ], ]; - for (const [operator, expectedRange] of testTable) { + for (const [ operator, expectedRange ] of testTable) { expect(getSolutionRange(value, operator)).toStrictEqual(expectedRange); } }); @@ -444,649 +437,6 @@ describe('solver function', () => { }); }); - describe('resolveSolutionDomainWithAnExpression', () => { - it(`given an empty domain and an equation with 2 operation chained - that are not "NOT" should return a valid new domain and the last chained operator`, () => { - const domain = new SolutionDomain(); - const equation: ISolverExpressionRange = { - chainOperator: [ - new LinkOperator(LogicOperator.And), - new LinkOperator(LogicOperator.Or), - ], - solutionDomain: new SolutionRange([0, 1]), - }; - - const expectedDomain = SolutionDomain.newWithInitialValue(equation.solutionDomain); - const expectedLastLogicalOperator = equation.chainOperator[equation.chainOperator.length - 2].toString(); - - const resp = resolveSolutionDomainWithAnExpression(equation, domain); - if (resp) { - const [respDomain, respLastLogicalOperator] = resp; - expect(respDomain.get_domain()).toStrictEqual(expectedDomain.get_domain()); - expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); - } else { - expect(resp).toBeDefined(); - } - }); - - it(`given a domain and an equation with multiple chained that are - not "NOT" should return a valid new domain and the next chained operator`, () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); - const equation: ISolverExpressionRange = { - chainOperator: [ - new LinkOperator(LogicOperator.And), - new LinkOperator(LogicOperator.Or), - new LinkOperator(LogicOperator.Or), - new LinkOperator(LogicOperator.And), - new LinkOperator(LogicOperator.Or), - ], - solutionDomain: new SolutionRange([100, 221.3]), - }; - const expectedOperator = equation.chainOperator[equation.chainOperator.length - 1]; - if (!expectedOperator) { - fail('should be able to get the expected operator check the test implementation'); - } - const expectedDomain = domain.add( - { - range: equation.solutionDomain, - operator: expectedOperator.operator, - }, - ); - const expectedLastLogicalOperator = equation.chainOperator[equation.chainOperator.length - 2].toString(); - - const resp = resolveSolutionDomainWithAnExpression(equation, domain); - if (resp) { - const [respDomain, respLastLogicalOperator] = resp; - expect(respDomain.get_domain()).toStrictEqual(expectedDomain.get_domain()); - expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); - } else { - expect(resp).toBeDefined(); - } - }); - - it(`given a domain and an equation one chained operation - should return a valid new domain and an empty string has the next chained operator`, () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); - const equation: ISolverExpressionRange = { - chainOperator: [ - new LinkOperator(LogicOperator.Or), - ], - solutionDomain: new SolutionRange([100, 221.3]), - }; - - const expectedOperator = equation.chainOperator[equation.chainOperator.length - 1]; - if (!expectedOperator) { - fail('should be able to get the expected operator check the test implementation'); - } - - const expectedDomain = domain.add( - { - range: equation.solutionDomain, - operator: expectedOperator.operator, - }, - ); - const expectedLastLogicalOperator = ''; - - const resp = resolveSolutionDomainWithAnExpression(equation, domain); - if (resp) { - const [respDomain, respLastLogicalOperator] = resp; - expect(respDomain.get_domain()).toStrictEqual(expectedDomain.get_domain()); - expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); - } else { - expect(resp).toBeDefined(); - } - }); - - it(`given a domain and an equation with multiple chainned operator where the later elements - are "NOT" operators and the last element an "AND" - operator should return a valid domain and the next last operator`, () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); - const equation: ISolverExpressionRange = { - chainOperator: [ - new LinkOperator(LogicOperator.And), - new LinkOperator(LogicOperator.Not), - new LinkOperator(LogicOperator.Not), - new LinkOperator(LogicOperator.Or), - ], - solutionDomain: new SolutionRange([100, 221.3]), - }; - - const expectedOperator = equation.chainOperator[equation.chainOperator.length - 1]; - if (!expectedOperator) { - fail('should be able to get the expected operator check the test implementation'); - } - - let expectedDomain = domain.add( - { - range: equation.solutionDomain, - operator: expectedOperator.operator, - }, - ); - expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); - expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); - - const expectedLastLogicalOperator = equation.chainOperator[0].toString(); - - const resp = resolveSolutionDomainWithAnExpression(equation, domain); - if (resp) { - const [respDomain, respLastLogicalOperator] = resp; - expect(respDomain).toStrictEqual(expectedDomain); - expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); - } else { - expect(resp).toBeDefined(); - } - }); - - it(`given a domain and an equation with multiple chainned operator - where the last elements are "NOT" operators should - return a valid domain and an empty string as the next operator`, () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([0, 1])); - const equation: ISolverExpressionRange = { - chainOperator: [ - new LinkOperator(LogicOperator.Not), - new LinkOperator(LogicOperator.Not), - new LinkOperator(LogicOperator.Or), - ], - solutionDomain: new SolutionRange([100, 221.3]), - }; - - const expectedOperator = equation.chainOperator[equation.chainOperator.length - 1]; - if (!expectedOperator) { - fail('should be able to get the expected operator check the test implementation'); - } - - let expectedDomain = domain.add( - { - range: equation.solutionDomain, - operator: expectedOperator.operator, - }, - ); - expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); - expectedDomain = expectedDomain.add({ operator: LogicOperator.Not }); - - const expectedLastLogicalOperator = ''; - - const resp = resolveSolutionDomainWithAnExpression(equation, domain); - if (resp) { - const [respDomain, respLastLogicalOperator] = resp; - expect(respDomain).toStrictEqual(expectedDomain); - expect(respLastLogicalOperator).toBe(expectedLastLogicalOperator); - } else { - expect(resp).toBeDefined(); - } - }); - - it('given an empty domain and an equation with no chained operation should return undefined', () => { - const domain = new SolutionDomain(); - const equation: ISolverExpressionRange = { - chainOperator: [], - solutionDomain: new SolutionRange([0, 1]), - }; - - expect(resolveSolutionDomainWithAnExpression(equation, domain)).toBeUndefined(); - }); - }); - - describe('createEquationSystem', () => { - it(`given multiple equations that are consistent with one - and another should return a valid equation system and the first equation to resolve`, () => { - const lastOperator = new LinkOperator(LogicOperator.And); - const firstOperator = new LinkOperator(LogicOperator.Or); - const secondOperator = new LinkOperator(LogicOperator.Or); - const thirdOperator = new LinkOperator(LogicOperator.Not); - const aVariable = 'x'; - const aRawValue = '1'; - const aValueType = SparqlOperandDataTypes.Int; - const avalueAsNumber = 1; - const anOperator = SparqlRelationOperator.EqualThanRelation; - - const operationTemplate = (c: LinkOperator[]): ISolverExpression => { - return { - variable: aVariable, - rawValue: aRawValue, - valueType: aValueType, - valueAsNumber: avalueAsNumber, - operator: anOperator, - chainOperator: c, - }; - }; - - const firstOperation = operationTemplate([firstOperator]); - const firstEquation: ISolverExpressionRange = { - chainOperator: firstOperation.chainOperator, - solutionDomain: new SolutionRange([1, 1]), - }; - const secondOperation = operationTemplate([firstOperator, secondOperator]); - const secondEquation: ISolverExpressionRange = { - chainOperator: secondOperation.chainOperator, - solutionDomain: new SolutionRange([1, 1]), - }; - const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); - const thirdEquation: ISolverExpressionRange = { - chainOperator: thirdOperation.chainOperator, - solutionDomain: new SolutionRange([1, 1]), - }; - const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - const expectedFirstEquation1: ISolverExpressionRange = { - chainOperator: lastOperation1.chainOperator, - solutionDomain: new SolutionRange([1, 1]), - }; - const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - const expectedFirstEquation2: ISolverExpressionRange = { - chainOperator: lastOperation2.chainOperator, - solutionDomain: new SolutionRange([1, 1]), - }; - - const equations: ISolverExpression[] = [ - firstOperation, - secondOperation, - thirdOperation, - lastOperation1, - lastOperation2, - ]; - equations.sort(() => Math.random() - 0.5); - - const expectedEquationSystem: SolverEquationSystem = new Map([ - [firstOperator.toString(), firstEquation], - [secondOperator.toString(), secondEquation], - [thirdOperator.toString(), thirdEquation], - ]); - - const resp = createEquationSystem(equations); - if (!Array.isArray(resp)) { - fail('should return an array'); - } - else if (resp) { - const [respEquationSystem, [respFirstEquation1, respFirstEquation2]] = resp; - expect(respEquationSystem).toStrictEqual(expectedEquationSystem); - expect(respFirstEquation1).toStrictEqual(expectedFirstEquation1); - expect(respFirstEquation2).toStrictEqual(expectedFirstEquation2); - } else { - expect(resp).toBeDefined(); - } - }); - - it(`given multiples equations where it is not possible to - get the solution range of an equation it should return undefined`, () => { - const lastOperator = new LinkOperator(LogicOperator.And); - const firstOperator = new LinkOperator(LogicOperator.Or); - const secondOperator = new LinkOperator(LogicOperator.Or); - const thirdOperator = new LinkOperator(LogicOperator.Not); - const aVariable = 'x'; - const aRawValue = '1'; - const aValueType = SparqlOperandDataTypes.Int; - const avalueAsNumber = 1; - const anOperator = SparqlRelationOperator.EqualThanRelation; - - const operationTemplate = (c: LinkOperator[]): ISolverExpression => { - return { - variable: aVariable, - rawValue: aRawValue, - valueType: aValueType, - valueAsNumber: avalueAsNumber, - operator: anOperator, - chainOperator: c, - }; - }; - - const firstOperation = { - variable: aVariable, - rawValue: aRawValue, - valueType: aValueType, - valueAsNumber: avalueAsNumber, - operator: SparqlRelationOperator.GeospatiallyContainsRelation, - chainOperator: [firstOperator], - }; - const secondOperation = operationTemplate([firstOperator, secondOperator]); - const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); - const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - - const equations: ISolverExpression[] = [ - firstOperation, - secondOperation, - thirdOperation, - lastOperation1, - lastOperation2, - ]; - equations.sort(() => Math.random() - 0.5); - - expect(createEquationSystem(equations)).toBeUndefined(); - }); - - it(`given multiples equations where there is multiple equation - that could be the first equation to be resolved it should return undefined`, () => { - const lastOperator = new LinkOperator(LogicOperator.And); - const firstOperator = new LinkOperator(LogicOperator.Or); - const secondOperator = new LinkOperator(LogicOperator.Or); - const thirdOperator = new LinkOperator(LogicOperator.Not); - const aVariable = 'x'; - const aRawValue = '1'; - const aValueType = SparqlOperandDataTypes.Int; - const avalueAsNumber = 1; - const anOperator = SparqlRelationOperator.EqualThanRelation; - - const operationTemplate = (c: LinkOperator[]): ISolverExpression => { - return { - variable: aVariable, - rawValue: aRawValue, - valueType: aValueType, - valueAsNumber: avalueAsNumber, - operator: anOperator, - chainOperator: c, - }; - }; - - const firstOperation = operationTemplate([firstOperator]); - const secondOperation = operationTemplate([firstOperator, secondOperator]); - const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); - const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - const lastOperation2 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - const lastOperation3 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - - const equations: ISolverExpression[] = [ - firstOperation, - secondOperation, - thirdOperation, - lastOperation1, - lastOperation2, - lastOperation3, - ]; - equations.sort(() => Math.random() - 0.5); - - expect(createEquationSystem(equations)).toBeUndefined(); - }); - - it('given multiples equations where there is no first equation to be resolved should return undefined', () => { - const lastOperator = new LinkOperator(LogicOperator.And); - const firstOperator = new LinkOperator(LogicOperator.Or); - const secondOperator = new LinkOperator(LogicOperator.Or); - const thirdOperator = new LinkOperator(LogicOperator.Not); - const aVariable = 'x'; - const aRawValue = '1'; - const aValueType = SparqlOperandDataTypes.Int; - const avalueAsNumber = 1; - const anOperator = SparqlRelationOperator.EqualThanRelation; - - const operationTemplate = (c: LinkOperator[]): ISolverExpression => { - return { - variable: aVariable, - rawValue: aRawValue, - valueType: aValueType, - valueAsNumber: avalueAsNumber, - operator: anOperator, - chainOperator: c, - }; - }; - - const firstOperation = { - variable: aVariable, - rawValue: aRawValue, - valueType: aValueType, - valueAsNumber: avalueAsNumber, - operator: SparqlRelationOperator.GeospatiallyContainsRelation, - chainOperator: [firstOperator], - }; - const secondOperation = operationTemplate([firstOperator, secondOperator]); - const thirdOperation = operationTemplate([firstOperator, secondOperator, thirdOperator]); - const lastOperation1 = operationTemplate([firstOperator, secondOperator, thirdOperator, lastOperator]); - - const equations: ISolverExpression[] = [ - firstOperation, - secondOperation, - thirdOperation, - lastOperation1, - ]; - equations.sort(() => Math.random() - 0.5); - - expect(createEquationSystem(equations)).toBeUndefined(); - }); - - it('given an equation should return a valid system of equation', () => { - const equation: ISolverExpression[] = [ - { - chainOperator: [], - operator: SparqlRelationOperator.EqualThanRelation, - rawValue: '88', - valueAsNumber: 88, - valueType: SparqlOperandDataTypes.Int, - variable: 'x', - }, - ]; - - const expectedEquationSystem: ISolverExpressionRange = { - chainOperator: [], - solutionDomain: new SolutionRange([88, 88]), - }; - - const resp = createEquationSystem(equation); - if (Array.isArray(resp)) { - fail('should not return an array'); - } - else if (resp && !Array.isArray(resp)) { - expect(resp).toStrictEqual(expectedEquationSystem); - } else { - expect(resp).toBeDefined(); - } - }); - - it('given two equations should return a valid system of equation', () => { - const lastOperator = new LinkOperator(LogicOperator.And); - - const equation: ISolverExpression[] = [ - { - chainOperator: [lastOperator], - operator: SparqlRelationOperator.EqualThanRelation, - rawValue: '88', - valueAsNumber: 88, - valueType: SparqlOperandDataTypes.Int, - variable: 'x', - }, - { - chainOperator: [lastOperator], - operator: SparqlRelationOperator.EqualThanRelation, - rawValue: '33', - valueAsNumber: 33, - valueType: SparqlOperandDataTypes.Int, - variable: 'x', - }, - ]; - - const expectedFirstEquation1: ISolverExpressionRange = { - chainOperator: [lastOperator], - solutionDomain: new SolutionRange([88, 88]), - }; - const expectedFirstEquation2: ISolverExpressionRange = { - chainOperator: [lastOperator], - solutionDomain: new SolutionRange([33, 33]), - }; - - const resp = createEquationSystem(equation); - if (!Array.isArray(resp)) { - fail('should return an array'); - } - else if (resp && Array.isArray(resp)) { - const [respEquationSystem, [respFirstEquation1, respFirstEquation2]] = resp; - expect(respEquationSystem.size).toBe(0); - expect(respFirstEquation1).toStrictEqual(expectedFirstEquation1); - expect(respFirstEquation2).toStrictEqual(expectedFirstEquation2); - } else { - expect(resp).toBeDefined(); - } - }); - - it('given one equation where one we cannot derived a solution range should return undefined', () => { - const lastOperator = new LinkOperator(LogicOperator.And); - - const equation: ISolverExpression[] = [ - { - chainOperator: [lastOperator], - operator: SparqlRelationOperator.GeospatiallyContainsRelation, - rawValue: '88', - valueAsNumber: 88, - valueType: SparqlOperandDataTypes.Int, - variable: 'x', - }, - ]; - - const resp = createEquationSystem(equation); - - expect(resp).toBeUndefined(); - }); - - }); - - describe('resolveSolutionDomainEquationSystem', () => { - it('should return a valid domain given a valid equation system', () => { - const firstOperator = new LinkOperator(LogicOperator.Or); - const secondOperator = new LinkOperator(LogicOperator.And); - const thirdOperator = new LinkOperator(LogicOperator.Not); - const forthOperator = new LinkOperator(LogicOperator.Or); - const fifthOperator = new LinkOperator(LogicOperator.And); - const firstEquation: ISolverExpressionRange = { - solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33]), - chainOperator: [ - firstOperator, - ], - }; - - const secondEquation: ISolverExpressionRange = { - solutionDomain: new SolutionRange([75, 75]), - chainOperator: [ - firstOperator, - secondOperator, - ], - }; - - const thirdEquation: ISolverExpressionRange = { - solutionDomain: new SolutionRange([100, Number.POSITIVE_INFINITY]), - chainOperator: [ - firstOperator, - secondOperator, - thirdOperator, - forthOperator, - ], - }; - const lastOperatorFirstEquation = firstEquation.chainOperator[firstEquation.chainOperator.length - 1]; - const lastOperatorSecondEquation = secondEquation.chainOperator[secondEquation.chainOperator.length - 1]; - const lastOperatorThirdEquation = thirdEquation.chainOperator[thirdEquation.chainOperator.length - 1]; - if ( - !(lastOperatorFirstEquation && - lastOperatorSecondEquation && - lastOperatorThirdEquation) - ) { - fail('should be able to retrieved the last chain operator of the equation check the test implementation'); - } - const equationSystem: SolverEquationSystem = new Map([ - [lastOperatorFirstEquation.toString(), firstEquation], - [lastOperatorSecondEquation.toString(), secondEquation], - [lastOperatorThirdEquation.toString(), thirdEquation], - ]); - - const firstEquationToSolve: [ISolverExpressionRange, ISolverExpressionRange] = [ - { - solutionDomain: new SolutionRange([1_000, Number.POSITIVE_INFINITY]), - chainOperator: [ - firstOperator, - secondOperator, - thirdOperator, - forthOperator, - fifthOperator, - ], - }, - { - solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, nextUp(33)]), - chainOperator: [ - firstOperator, - secondOperator, - thirdOperator, - forthOperator, - fifthOperator, - ], - }, - ]; - // Nothing => [100, infinity] =>[-infinity, 100- epsilon] => [75,75]=> [75, 75], [-infinity, 33] - const expectedDomain: SolutionRange[] = [ - new SolutionRange([Number.NEGATIVE_INFINITY, 33]), - new SolutionRange([75, 75]), - ]; - - const resp = resolveSolutionDomainEquationSystem(equationSystem, firstEquationToSolve); - - if (resp) { - expect(resp.get_domain()).toStrictEqual(expectedDomain); - } else { - expect(resp).toBeDefined(); - } - }); - - it('should return undefined an equation system where the chain of operator is inconsistent', () => { - const firstOperator = new LinkOperator(LogicOperator.Or); - const secondOperator = new LinkOperator(LogicOperator.And); - const thirdOperator = new LinkOperator(LogicOperator.Not); - const forthOperator = new LinkOperator(LogicOperator.Or); - const fifthOperator = new LinkOperator(LogicOperator.And); - const firstEquation: ISolverExpressionRange = { - solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, 33]), - chainOperator: [ - firstOperator, - ], - }; - - const secondEquation: ISolverExpressionRange = { - solutionDomain: new SolutionRange([75, 75]), - chainOperator: [ - firstOperator, - secondOperator, - ], - }; - - const thirdEquation: ISolverExpressionRange = { - solutionDomain: new SolutionRange([100, Number.POSITIVE_INFINITY]), - chainOperator: [ - ], - }; - const lastOperatorFirstEquation = firstEquation.chainOperator[firstEquation.chainOperator.length - 1]; - const lastOperatorSecondEquation = secondEquation.chainOperator[secondEquation.chainOperator.length - 1]; - if ( - !(lastOperatorFirstEquation && - lastOperatorSecondEquation) - ) { - fail('should be able to retrieved the last chain operator of the equation check the test implementation'); - } - const equationSystem: SolverEquationSystem = new Map([ - [lastOperatorFirstEquation.toString(), firstEquation], - [lastOperatorSecondEquation.toString(), secondEquation], - [forthOperator.toString(), thirdEquation], - ]); - - const firstEquationToSolve: [ISolverExpressionRange, ISolverExpressionRange] = [ - { - solutionDomain: new SolutionRange([1_000, Number.POSITIVE_INFINITY]), - chainOperator: [ - firstOperator, - secondOperator, - thirdOperator, - forthOperator, - fifthOperator, - ], - }, - { - solutionDomain: new SolutionRange([Number.NEGATIVE_INFINITY, nextUp(33)]), - chainOperator: [ - firstOperator, - secondOperator, - thirdOperator, - forthOperator, - fifthOperator, - ], - }, - ]; - - expect(() => { resolveSolutionDomainEquationSystem(equationSystem, firstEquationToSolve); }).toThrow(); - }); - }); - describe('resolveAFilterTerm', () => { it('given an algebra expression with all the solver expression parameters should return a valid expression', () => { const expression: Algebra.Expression = { @@ -1107,7 +457,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; const variable = 'x'; const expectedSolverExpression: ISolverExpression = { variable, @@ -1141,7 +491,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeUndefined(); }); @@ -1161,7 +511,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); }); @@ -1174,7 +524,7 @@ describe('solver function', () => { args: [], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeUndefined(); }); @@ -1229,203 +579,12 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); }); }); - describe('recursifFilterExpressionToSolverExpression', () => { - it('given an algebra expression with two logicals operators should return a list of solver expression', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( !(?x=2 && ?x>5)) - }`).input.expression; - - const buildSolverExpression = ( - variable: Variable, - rawValue: string, - valueType: SparqlOperandDataTypes, - valueAsNumber: number, - operator: SparqlRelationOperator, - chainOperator: LinkOperator[], - ): ISolverExpression => { - return { - rawValue, - variable, - valueType, - valueAsNumber, - operator, - chainOperator, - }; - }; - const variable = 'x'; - - LinkOperator.resetIdCount(); - const notOperator = new LinkOperator(LogicOperator.Not); - const andOperator = new LinkOperator(LogicOperator.And); - - const expectedEquation: ISolverExpression[] = [ - buildSolverExpression( - variable, - '2', - SparqlOperandDataTypes.Integer, - 2, - SparqlRelationOperator.EqualThanRelation, - [notOperator, andOperator], - ), - - buildSolverExpression( - variable, - '5', - SparqlOperandDataTypes.Integer, - 5, - SparqlRelationOperator.GreaterThanRelation, - [notOperator, andOperator], - ), - ]; - - LinkOperator.resetIdCount(); - expect(recursifFilterExpressionToSolverExpression(expression, [], [], variable)).toStrictEqual(expectedEquation); - }); - - it('given an algebra expression with tree logicals operators should return a list of solver expression', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( !(?x=2 && ?x>5) || ?x < 88.3) - }`).input.expression; - - const buildSolverExpression = ( - variable: Variable, - rawValue: string, - valueType: SparqlOperandDataTypes, - valueAsNumber: number, - operator: SparqlRelationOperator, - chainOperator: LinkOperator[], - ): ISolverExpression => { - return { - rawValue, - variable, - valueType, - valueAsNumber, - operator, - chainOperator, - }; - }; - const variable = 'x'; - - LinkOperator.resetIdCount(); - const orOperator = new LinkOperator(LogicOperator.Or); - const notOperator = new LinkOperator(LogicOperator.Not); - const andOperator = new LinkOperator(LogicOperator.And); - - const expectedEquation: ISolverExpression[] = [ - buildSolverExpression( - variable, - '2', - SparqlOperandDataTypes.Integer, - 2, - SparqlRelationOperator.EqualThanRelation, - [orOperator, notOperator, andOperator], - ), - - buildSolverExpression( - variable, - '5', - SparqlOperandDataTypes.Integer, - 5, - SparqlRelationOperator.GreaterThanRelation, - [orOperator, notOperator, andOperator], - ), - - buildSolverExpression( - variable, - '88.3', - SparqlOperandDataTypes.Decimal, - 88.3, - SparqlRelationOperator.LessThanRelation, - [orOperator], - ), - ]; - - LinkOperator.resetIdCount(); - expect(recursifFilterExpressionToSolverExpression(expression, [], [], variable)).toStrictEqual(expectedEquation); - }); - - it('given an algebra expression with four logicals operators should return a list of solver expression', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( !(?x=2 && ?x>5 || ?x>6) || ?x < 88.3) - }`).input.expression; - - const buildSolverExpression = ( - variable: Variable, - rawValue: string, - valueType: SparqlOperandDataTypes, - valueAsNumber: number, - operator: SparqlRelationOperator, - chainOperator: LinkOperator[], - ): ISolverExpression => { - return { - rawValue, - variable, - valueType, - valueAsNumber, - operator, - chainOperator, - }; - }; - const variable = 'x'; - - LinkOperator.resetIdCount(); - const firstOrOperator = new LinkOperator(LogicOperator.Or); - const notOperator = new LinkOperator(LogicOperator.Not); - const secondOrOperator = new LinkOperator(LogicOperator.Or); - const andOperator = new LinkOperator(LogicOperator.And); - - const expectedEquation: ISolverExpression[] = [ - buildSolverExpression( - variable, - '2', - SparqlOperandDataTypes.Integer, - 2, - SparqlRelationOperator.EqualThanRelation, - [firstOrOperator, notOperator, secondOrOperator, andOperator], - ), - - buildSolverExpression( - variable, - '5', - SparqlOperandDataTypes.Integer, - 5, - SparqlRelationOperator.GreaterThanRelation, - [firstOrOperator, notOperator, secondOrOperator, andOperator], - ), - - buildSolverExpression( - variable, - '6', - SparqlOperandDataTypes.Integer, - 6, - SparqlRelationOperator.GreaterThanRelation, - [firstOrOperator, notOperator, secondOrOperator], - ), - - buildSolverExpression( - variable, - '88.3', - SparqlOperandDataTypes.Decimal, - 88.3, - SparqlRelationOperator.LessThanRelation, - [firstOrOperator], - ), - ]; - - LinkOperator.resetIdCount(); - expect(recursifFilterExpressionToSolverExpression(expression, [], [], variable)).toStrictEqual(expectedEquation); - }); - }); - describe('isRelationFilterExpressionDomainEmpty', () => { it('given a relation that is not able to be converted into a solverExpression should return true', () => { const relation: ITreeRelation = { @@ -1713,89 +872,87 @@ describe('solver function', () => { expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); }); -}); - -describe('recursifResolve', () => { - it('given an algebra expression with two logicals operators should return the valid solution domain', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2 && ?x<5) - }`).input.expression; - - - const resp = recursifResolve( - expression, - new SolutionDomain(), - undefined, - 'x', - false, - ); - - const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([2, 2])); - - expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); - - }); - - it('given an algebra expression with two logicals operators that cannot be satified should return an empty domain', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2 && ?x>5) - }`).input.expression; + describe('recursifResolve', () => { + it('given an algebra expression with two logicals operators should return the valid solution domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x<5) + }`).input.expression; + + const resp = recursifResolve( + expression, + new SolutionDomain(), + undefined, + 'x', + false, + ); + const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([ 2, 2 ])); - const resp = recursifResolve( - expression, - new SolutionDomain(), - undefined, - 'x', - false, - ); + expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + }); - const expectedDomain = new SolutionDomain(); + it(`given an algebra expression with two logicals operators + that cannot be satified should return an empty domain`, () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x>5) + }`).input.expression; + + const resp = recursifResolve( + expression, + new SolutionDomain(), + undefined, + 'x', + false, + ); - expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); - }); + const expectedDomain = new SolutionDomain(); - it('given an algebra expression with two logicals operators that are negated should return the valid solution domain', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( !(?x=2 && ?x<5)) - }`).input.expression; + expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + }); + it(`given an algebra expression with two logicals operators that are negated + should return the valid solution domain`, () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x<5)) + }`).input.expression; + + const resp = recursifResolve( + expression, + new SolutionDomain(), + undefined, + 'x', + false, + ); - const resp = recursifResolve( - expression, - new SolutionDomain(), - undefined, - 'x', - false, - ); + const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([ 5, Number.POSITIVE_INFINITY ])); - const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([5, Number.POSITIVE_INFINITY])); + expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + }); - expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + it(`given an algebra expression with three logicals operators where the priority of operation should start with the not operator than + should return the valid solution domain`, () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x>2 || !(?x=3)) + }`).input.expression; + + const resp = recursifResolve( + expression, + new SolutionDomain(), + undefined, + 'x', + false, + ); + let expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange( + [ Number.NEGATIVE_INFINITY, nextDown(3) ], + )); + expectedDomain = expectedDomain.addWithOrOperator(new SolutionRange([ nextUp(3), Number.POSITIVE_INFINITY ])); + expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + }); }); +}); - it('given an algebra expression with three logicals operators where the priority of operation should start with the not operator than should return the valid solution domain', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2 && ?x>2 || !(?x=3)) - }`).input.expression; - - - const resp = recursifResolve( - expression, - new SolutionDomain(), - undefined, - 'x', - false, - ); - - let expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(3)])); - expectedDomain = expectedDomain.addWithOrOperator(new SolutionRange([nextUp(3), Number.POSITIVE_INFINITY])); - expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); - - }); -}); \ No newline at end of file From db4195e7950f8b8d9283aef83cdcb43481514cf5 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 20 Feb 2023 09:33:27 +0100 Subject: [PATCH 114/189] new system of error added for the traversal of the filter expression. --- .../lib/SolutionDomain.ts | 2 +- .../lib/error.ts | 27 +++ .../lib/solver.ts | 158 +++++++------ .../test/SolutionDomain-test.ts | 14 ++ .../test/solver-test.ts | 207 ++++++++++-------- 5 files changed, 254 insertions(+), 154 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/lib/error.ts diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index a4b0153af..a85312772 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -41,7 +41,7 @@ export class SolutionDomain { */ public static newWithInitialValue(initialRange: SolutionRange): SolutionDomain { const newSolutionDomain = new SolutionDomain(); - newSolutionDomain.domain = [ initialRange ]; + newSolutionDomain.domain = [initialRange]; return newSolutionDomain; } diff --git a/packages/actor-extract-links-extract-tree/lib/error.ts b/packages/actor-extract-links-extract-tree/lib/error.ts new file mode 100644 index 000000000..ab8c3a985 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/error.ts @@ -0,0 +1,27 @@ +export class MissMatchVariableError extends Error { + constructor(message: string) { + super(message); + this.name = "MissMatchVariableError"; + } +} + +export class MisformatedFilterTermError extends Error{ + constructor(message: string) { + super(message); + this.name = "MisformatedFilterTermError"; + } +} + +export class UnsupportedDataTypeError extends Error{ + constructor(message: string) { + super(message); + this.name = "UnsupportedDataTypeError"; + } +} + +export class UnsupportedOperatorError extends Error{ + constructor(message: string) { + super(message); + this.name = "UnsupportedOperatorError"; + } +} \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index a1f245a54..7b7df103d 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -13,11 +13,17 @@ import type { ISolverExpression, Variable, } from './solverInterfaces'; +import { + MissMatchVariableError, + MisformatedFilterTermError, + UnsupportedDataTypeError, + UnsupportedOperatorError +} from './error'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; -const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]); +const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]); /** * Check if the solution domain of a system of equation compose of the expressions of the filter * expression and the relation is not empty. @@ -57,16 +63,23 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi false, ); } catch (error: unknown) { - // Was not able to create a boolean expression from a filter argument - // it is because either the TREE document is badly formated or the variable doesn't match - if (error instanceof SyntaxError) { + // A filter term was missformed we let the query engine return an error to the user and by precaution we accept the link + // in case the error is from the solver and not the filter expression + if (error instanceof MisformatedFilterTermError) { + return true; + } + + // We don't support the data type so let need to explore that link to not diminush the completness of the result + if (error instanceof UnsupportedDataTypeError) { return true; } - // We don't support the data type of relation yet, so we don't prune the link - if (error instanceof TypeError) { + // We don't support the operator so let need to explore that link to not diminush the completness of the result + if (error instanceof UnsupportedOperatorError) { return true; } + + // if it's unexpected error we throw it throw error; } @@ -77,6 +90,66 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi return !solutionDomain.isDomainEmpty(); } +/** + * From an Algebra expression return an solver expression if possible + * @param {Algebra.Expression} expression - Algebra expression containing the a variable and a litteral. + * @param {SparqlRelationOperator} operator - The SPARQL operator defining the expression. + * @param {LinkOperator[]} linksOperator - The logical operator prior to this expression. + * @param {Variable} variable - The variable the expression should have to be part of a system of equation. + * @returns {ISolverExpression | undefined} Return a solver expression if possible + */ +export function resolveAFilterTerm(expression: Algebra.Expression, + operator: SparqlRelationOperator, + linksOperator: LinkOperator[], + variable: Variable): + ISolverExpression | Error { + let rawValue: string | undefined; + let valueType: SparqlOperandDataTypes | undefined; + let valueAsNumber: number | undefined; + let hasVariable = false; + + // Find the constituant element of the solver expression + for (const arg of expression.args) { + if ('term' in arg && arg.term.termType === 'Variable') { + // Check if the expression has the same variable as the one the solver try to resolved + if (arg.term.value !== variable) { + return new MissMatchVariableError(`the variable ${arg.term.value} is in the filter whereas we are looking for the varibale ${variable}`); + } + hasVariable = true; + } else if ('term' in arg && arg.term.termType === 'Literal') { + rawValue = arg.term.value; + valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); + if (valueType) { + valueAsNumber = castSparqlRdfTermIntoNumber(rawValue!, valueType); + if( !valueAsNumber){ + return new UnsupportedDataTypeError(`we do not support the datatype "${valueType}" in the solver for the moment`) + + } + } else { + return new UnsupportedDataTypeError(`The datatype "${valueType}" is not supported by the SPARQL 1.1 Query Language W3C recommandation`) + } + } + } + // Return if a fully form solver expression can be created + if (hasVariable && rawValue && valueType && valueAsNumber) { + return { + variable, + rawValue, + valueType, + valueAsNumber, + operator, + chainOperator: linksOperator, + }; + } + const missingTerm = []; + !hasVariable ? missingTerm.push('Variable') : null; + !rawValue ? missingTerm.push('Litteral') : null; + + return new MisformatedFilterTermError(`the filter expressions doesn\'t have the term ${missingTerm.toString()}`); + +} + + /** * Recursively traverse the filter expression and calculate the domain until it get to the current expression. * It will thrown an error if the expression is badly formated or if it's impossible to get the solution range. @@ -108,12 +181,16 @@ export function recursifResolve( const operator = filterOperatorToSparqlRelationOperator(rawOperator); if (operator) { const solverExpression = resolveAFilterTerm(filterExpression, operator, [], variable); - if (!solverExpression?.valueAsNumber) { - throw new SyntaxError('unable to get the number value of the expression'); - } - const solverRange = getSolutionRange(solverExpression?.valueAsNumber, solverExpression?.operator); - if (!solverRange) { - throw new TypeError('unable to get the range of an expression'); + let solverRange: SolutionRange | undefined; + if (solverExpression instanceof MissMatchVariableError) { + solverRange = A_TRUE_EXPRESSION; + } else if (solverExpression instanceof Error) { + throw solverExpression; + } else { + solverRange = getSolutionRange(solverExpression.valueAsNumber, solverExpression.operator); + if (!solverRange) { + throw new UnsupportedOperatorError(`the operator "${solverExpression.operator}" of the ISolverExpression is not supported yet`); + } } // We can distribute a not expression, so we inverse each statement if (notExpression) { @@ -141,53 +218,6 @@ export function recursifResolve( return domain; } -/** - * From an Algebra expression return an solver expression if possible - * @param {Algebra.Expression} expression - Algebra expression containing the a variable and a litteral. - * @param {SparqlRelationOperator} operator - The SPARQL operator defining the expression. - * @param {LinkOperator[]} linksOperator - The logical operator prior to this expression. - * @param {Variable} variable - The variable the expression should have to be part of a system of equation. - * @returns {ISolverExpression | undefined} Return a solver expression if possible - */ -export function resolveAFilterTerm(expression: Algebra.Expression, - operator: SparqlRelationOperator, - linksOperator: LinkOperator[], - variable: Variable): - ISolverExpression | undefined { - let rawValue: string | undefined; - let valueType: SparqlOperandDataTypes | undefined; - let valueAsNumber: number | undefined; - let hasVariable = false; - - // Find the constituant element of the solver expression - for (const arg of expression.args) { - if ('term' in arg && arg.term.termType === 'Variable') { - // Check if the expression has the same variable as the one the solver try to resolved - if (arg.term.value !== variable) { - return undefined; - } - hasVariable = true; - } else if ('term' in arg && arg.term.termType === 'Literal') { - rawValue = arg.term.value; - valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); - if (valueType) { - valueAsNumber = castSparqlRdfTermIntoNumber(rawValue!, valueType); - } - } - } - // Return if a fully form solver expression can be created - if (hasVariable && rawValue && valueType && valueAsNumber) { - return { - variable, - rawValue, - valueType, - valueAsNumber, - operator, - chainOperator: linksOperator, - }; - } -} - /** * Convert a TREE relation into a solver expression. * @param {ITreeRelation} relation - TREE relation. @@ -247,15 +277,15 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { export function getSolutionRange(value: number, operator: SparqlRelationOperator): SolutionRange | undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: - return new SolutionRange([ nextUp(value), Number.POSITIVE_INFINITY ]); + return new SolutionRange([nextUp(value), Number.POSITIVE_INFINITY]); case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return new SolutionRange([ value, Number.POSITIVE_INFINITY ]); + return new SolutionRange([value, Number.POSITIVE_INFINITY]); case SparqlRelationOperator.EqualThanRelation: - return new SolutionRange([ value, value ]); + return new SolutionRange([value, value]); case SparqlRelationOperator.LessThanRelation: - return new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(value) ]); + return new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(value)]); case SparqlRelationOperator.LessThanOrEqualToRelation: - return new SolutionRange([ Number.NEGATIVE_INFINITY, value ]); + return new SolutionRange([Number.NEGATIVE_INFINITY, value]); default: // Not an operator that is compatible with number. break; diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index fb5b41448..2a29c1cbd 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -1,3 +1,4 @@ +import { assert } from 'console'; import { SolutionRange } from '../lib//SolutionRange'; import { SolutionDomain } from '../lib/SolutionDomain'; import { LogicOperator } from '../lib/solverInterfaces'; @@ -207,6 +208,19 @@ describe('SolutionDomain', () => { expect(newDomain.get_domain()).toStrictEqual(expectedDomain); }); + + it('given an empty domain and a last operator and should return an empty domain', ()=>{ + const aRange = new SolutionRange([ -2, 25 ]); + const anotherRangeNonOverlapping = new SolutionRange([ 2000, 3000 ]); + + let newDomain = aDomain.addWithAndOperator(aRange); + newDomain = newDomain.add({range: anotherRangeNonOverlapping, operator: LogicOperator.And}); + expect(newDomain.isDomainEmpty()).toBe(true); + + newDomain = newDomain.addWithAndOperator(aRange); + + expect(newDomain.isDomainEmpty()).toBe(true); + }); }); describe('add', () => { diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 3375cbea1..5a3a1eda2 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -21,6 +21,12 @@ import { SparqlOperandDataTypes, LogicOperator } from '../lib/solverInterfaces'; import type { ISolverExpression, } from '../lib/solverInterfaces'; +import { + MissMatchVariableError, + MisformatedFilterTermError, + UnsupportedDataTypeError, + UnsupportedOperatorError +} from '../lib/error'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; @@ -477,7 +483,7 @@ describe('solver function', () => { } }); - it('given an algebra expression without a variable than should return undefined', () => { + it('given an algebra expression without a variable than should return an misformated error', () => { const expression: Algebra.Expression = { type: Algebra.types.EXPRESSION, expressionType: Algebra.expressionTypes.OPERATOR, @@ -493,10 +499,10 @@ describe('solver function', () => { const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeUndefined(); + expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeInstanceOf(MisformatedFilterTermError); }); - it('given an algebra expression without a litteral than should return undefined', () => { + it('given an algebra expression without a litteral than should return an misformated error', () => { const variable = 'x'; const expression: Algebra.Expression = { type: Algebra.types.EXPRESSION, @@ -513,10 +519,10 @@ describe('solver function', () => { const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeInstanceOf(MisformatedFilterTermError); }); - it('given an algebra expression without args than should return undefined', () => { + it('given an algebra expression without args than should return a misformated error', () => { const expression: Algebra.Expression = { type: Algebra.types.EXPRESSION, expressionType: Algebra.expressionTypes.OPERATOR, @@ -526,10 +532,10 @@ describe('solver function', () => { const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeUndefined(); + expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeInstanceOf(MisformatedFilterTermError); }); - it('given an algebra expression with a litteral containing an invalid datatype than should return undefined', + it('given an algebra expression with a litteral containing an invalid datatype than should return an unsupported datatype error', () => { const variable = 'x'; const expression: Algebra.Expression = { @@ -555,11 +561,11 @@ describe('solver function', () => { new LinkOperator(LogicOperator.Or), ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeInstanceOf(UnsupportedDataTypeError); }); it(`given an algebra expression with a litteral containing a - literal that cannot be converted into number should return undefined`, () => { + literal that cannot be converted into number should return an unsupported datatype error`, () => { const variable = 'x'; const expression: Algebra.Expression = { type: Algebra.types.EXPRESSION, @@ -581,7 +587,90 @@ describe('solver function', () => { const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeUndefined(); + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeInstanceOf(UnsupportedDataTypeError); + }); + }); + + describe('recursifResolve', () => { + it('given an algebra expression with two logicals operators should return the valid solution domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x<5) + }`).input.expression; + + const resp = recursifResolve( + expression, + new SolutionDomain(), + undefined, + 'x', + false, + ); + + const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([ 2, 2 ])); + + expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + }); + + it(`given an algebra expression with two logicals operators + that cannot be satified should return an empty domain`, () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x>5) + }`).input.expression; + + const resp = recursifResolve( + expression, + new SolutionDomain(), + undefined, + 'x', + false, + ); + + const expectedDomain = new SolutionDomain(); + + expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + }); + + it(`given an algebra expression with two logicals operators that are negated + should return the valid solution domain`, () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x<5)) + }`).input.expression; + + const resp = recursifResolve( + expression, + new SolutionDomain(), + undefined, + 'x', + false, + ); + + const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([ 5, Number.POSITIVE_INFINITY ])); + + expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + }); + + it(`given an algebra expression with three logicals operators where the priority of operation should start with the not operator than + should return the valid solution domain`, () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x>2 || !(?x=3)) + }`).input.expression; + + const resp = recursifResolve( + expression, + new SolutionDomain(), + undefined, + 'x', + false, + ); + + let expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange( + [ Number.NEGATIVE_INFINITY, nextDown(3) ], + )); + expectedDomain = expectedDomain.addWithOrOperator(new SolutionRange([ nextUp(3), Number.POSITIVE_INFINITY ])); + expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); }); }); @@ -871,88 +960,28 @@ describe('solver function', () => { expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); - }); - describe('recursifResolve', () => { - it('given an algebra expression with two logicals operators should return the valid solution domain', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2 && ?x<5) - }`).input.expression; - - const resp = recursifResolve( - expression, - new SolutionDomain(), - undefined, - 'x', - false, - ); - - const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([ 2, 2 ])); - - expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); - }); - it(`given an algebra expression with two logicals operators - that cannot be satified should return an empty domain`, () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2 && ?x>5) - }`).input.expression; - - const resp = recursifResolve( - expression, - new SolutionDomain(), - undefined, - 'x', - false, - ); - - const expectedDomain = new SolutionDomain(); - - expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); - }); - - it(`given an algebra expression with two logicals operators that are negated - should return the valid solution domain`, () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( !(?x=2 && ?x<5)) - }`).input.expression; - - const resp = recursifResolve( - expression, - new SolutionDomain(), - undefined, - 'x', - false, - ); - - const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([ 5, Number.POSITIVE_INFINITY ])); - - expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); - }); + it('should accept the link given that recursifResolve return a SyntaxError', ()=>{ + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; - it(`given an algebra expression with three logicals operators where the priority of operation should start with the not operator than - should return the valid solution domain`, () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2 && ?x>2 || !(?x=3)) - }`).input.expression; + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x = 2 && ?x > 5 && ?x > 88.3) + }`).input.expression; - const resp = recursifResolve( - expression, - new SolutionDomain(), - undefined, - 'x', - false, - ); + const variable = 'x'; - let expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange( - [ Number.NEGATIVE_INFINITY, nextDown(3) ], - )); - expectedDomain = expectedDomain.addWithOrOperator(new SolutionRange([ nextUp(3), Number.POSITIVE_INFINITY ])); - expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); }); }); -}); - + +}); \ No newline at end of file From b198356f35264b48968bbe837e4182f4a54b6a6c Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 20 Feb 2023 09:41:34 +0100 Subject: [PATCH 115/189] lint fix --- .../lib/SolutionDomain.ts | 2 +- .../lib/error.ts | 40 +++++----- .../lib/solver.ts | 49 ++++++------ .../lib/solverInterfaces.ts | 25 ------ .../test/SolutionDomain-test.ts | 7 +- .../test/solver-test.ts | 77 ++++++++++--------- 6 files changed, 88 insertions(+), 112 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index a85312772..a4b0153af 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -41,7 +41,7 @@ export class SolutionDomain { */ public static newWithInitialValue(initialRange: SolutionRange): SolutionDomain { const newSolutionDomain = new SolutionDomain(); - newSolutionDomain.domain = [initialRange]; + newSolutionDomain.domain = [ initialRange ]; return newSolutionDomain; } diff --git a/packages/actor-extract-links-extract-tree/lib/error.ts b/packages/actor-extract-links-extract-tree/lib/error.ts index ab8c3a985..cef899a4a 100644 --- a/packages/actor-extract-links-extract-tree/lib/error.ts +++ b/packages/actor-extract-links-extract-tree/lib/error.ts @@ -1,27 +1,27 @@ export class MissMatchVariableError extends Error { - constructor(message: string) { - super(message); - this.name = "MissMatchVariableError"; - } + public constructor(message: string) { + super(message); + this.name = 'MissMatchVariableError'; + } } -export class MisformatedFilterTermError extends Error{ - constructor(message: string) { - super(message); - this.name = "MisformatedFilterTermError"; - } +export class MisformatedFilterTermError extends Error { + public constructor(message: string) { + super(message); + this.name = 'MisformatedFilterTermError'; + } } -export class UnsupportedDataTypeError extends Error{ - constructor(message: string) { - super(message); - this.name = "UnsupportedDataTypeError"; - } +export class UnsupportedDataTypeError extends Error { + public constructor(message: string) { + super(message); + this.name = 'UnsupportedDataTypeError'; + } } -export class UnsupportedOperatorError extends Error{ - constructor(message: string) { - super(message); - this.name = "UnsupportedOperatorError"; - } -} \ No newline at end of file +export class UnsupportedOperatorError extends Error { + public constructor(message: string) { + super(message); + this.name = 'UnsupportedOperatorError'; + } +} diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 7b7df103d..c5e3f8e2e 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -2,6 +2,12 @@ import { SparqlRelationOperator } from '@comunica/types-link-traversal'; import type { ITreeRelation } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; +import { + MissMatchVariableError, + MisformatedFilterTermError, + UnsupportedDataTypeError, + UnsupportedOperatorError, +} from './error'; import { LinkOperator } from './LinkOperator'; import { SolutionDomain } from './SolutionDomain'; import { SolutionRange } from './SolutionRange'; @@ -13,17 +19,11 @@ import type { ISolverExpression, Variable, } from './solverInterfaces'; -import { - MissMatchVariableError, - MisformatedFilterTermError, - UnsupportedDataTypeError, - UnsupportedOperatorError -} from './error'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; -const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]); +const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]); /** * Check if the solution domain of a system of equation compose of the expressions of the filter * expression and the relation is not empty. @@ -63,8 +63,8 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi false, ); } catch (error: unknown) { - // A filter term was missformed we let the query engine return an error to the user and by precaution we accept the link - // in case the error is from the solver and not the filter expression + // A filter term was missformed we let the query engine return an error to the user and by precaution + // we accept the link in case the error is from the solver and not the filter expression if (error instanceof MisformatedFilterTermError) { return true; } @@ -79,7 +79,7 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi return true; } - // if it's unexpected error we throw it + // If it's unexpected error we throw it throw error; } @@ -121,12 +121,11 @@ export function resolveAFilterTerm(expression: Algebra.Expression, valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); if (valueType) { valueAsNumber = castSparqlRdfTermIntoNumber(rawValue!, valueType); - if( !valueAsNumber){ - return new UnsupportedDataTypeError(`we do not support the datatype "${valueType}" in the solver for the moment`) - + if (!valueAsNumber) { + return new UnsupportedDataTypeError(`we do not support the datatype "${valueType}" in the solver for the moment`); } } else { - return new UnsupportedDataTypeError(`The datatype "${valueType}" is not supported by the SPARQL 1.1 Query Language W3C recommandation`) + return new UnsupportedDataTypeError(`The datatype "${valueType}" is not supported by the SPARQL 1.1 Query Language W3C recommandation`); } } } @@ -142,14 +141,16 @@ export function resolveAFilterTerm(expression: Algebra.Expression, }; } const missingTerm = []; - !hasVariable ? missingTerm.push('Variable') : null; - !rawValue ? missingTerm.push('Litteral') : null; - - return new MisformatedFilterTermError(`the filter expressions doesn\'t have the term ${missingTerm.toString()}`); + if (!hasVariable) { + missingTerm.push('Variable'); + } + if (!rawValue) { + missingTerm.push('Litteral'); + } + return new MisformatedFilterTermError(`the filter expressions doesn't have the term ${missingTerm.toString()}`); } - /** * Recursively traverse the filter expression and calculate the domain until it get to the current expression. * It will thrown an error if the expression is badly formated or if it's impossible to get the solution range. @@ -277,15 +278,15 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { export function getSolutionRange(value: number, operator: SparqlRelationOperator): SolutionRange | undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: - return new SolutionRange([nextUp(value), Number.POSITIVE_INFINITY]); + return new SolutionRange([ nextUp(value), Number.POSITIVE_INFINITY ]); case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return new SolutionRange([value, Number.POSITIVE_INFINITY]); + return new SolutionRange([ value, Number.POSITIVE_INFINITY ]); case SparqlRelationOperator.EqualThanRelation: - return new SolutionRange([value, value]); + return new SolutionRange([ value, value ]); case SparqlRelationOperator.LessThanRelation: - return new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(value)]); + return new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(value) ]); case SparqlRelationOperator.LessThanOrEqualToRelation: - return new SolutionRange([Number.NEGATIVE_INFINITY, value]); + return new SolutionRange([ Number.NEGATIVE_INFINITY, value ]); default: // Not an operator that is compatible with number. break; diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index 82a7a338b..4f420d212 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -1,6 +1,5 @@ import type { SparqlRelationOperator } from '@comunica/types-link-traversal'; import type { LinkOperator } from './LinkOperator'; -import type { SolutionRange } from './SolutionRange'; /** * Valid SPARQL data type for operation. * reference: https://www.w3.org/TR/sparql11-query/#operandDataTypes @@ -49,30 +48,6 @@ export enum LogicOperator { export const LogicOperatorReversed: Map = new Map(Object.values(LogicOperator).map(value => [ value, value ])); -/** - * A range of a solver expression with his chain of logical operation. - */ -export interface ISolverExpressionRange { - /** - * The chain of operation attached to the expression. - */ - chainOperator: LinkOperator[]; - /** - * The domain of the solution of this expression. - */ - solutionDomain: SolutionRange; -} - -/** - * A system of equation to be solved by the solver. It is indexed by the last logical operation that has to be apply - * to the expression - */ -export type SolverEquationSystem = Map; - -/** - * A last logical expression of a chain of logical expression. - */ -export type LastLogicalOperator = string; /** * A variable to be solved by the solver. */ diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index 2a29c1cbd..294a78173 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -1,4 +1,3 @@ -import { assert } from 'console'; import { SolutionRange } from '../lib//SolutionRange'; import { SolutionDomain } from '../lib/SolutionDomain'; import { LogicOperator } from '../lib/solverInterfaces'; @@ -209,12 +208,12 @@ describe('SolutionDomain', () => { expect(newDomain.get_domain()).toStrictEqual(expectedDomain); }); - it('given an empty domain and a last operator and should return an empty domain', ()=>{ + it('given an empty domain and a last operator and should return an empty domain', () => { const aRange = new SolutionRange([ -2, 25 ]); - const anotherRangeNonOverlapping = new SolutionRange([ 2000, 3000 ]); + const anotherRangeNonOverlapping = new SolutionRange([ 2_000, 3_000 ]); let newDomain = aDomain.addWithAndOperator(aRange); - newDomain = newDomain.add({range: anotherRangeNonOverlapping, operator: LogicOperator.And}); + newDomain = newDomain.add({ range: anotherRangeNonOverlapping, operator: LogicOperator.And }); expect(newDomain.isDomainEmpty()).toBe(true); newDomain = newDomain.addWithAndOperator(aRange); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 5a3a1eda2..154cbdcb0 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -3,6 +3,10 @@ import { SparqlRelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; +import { + MisformatedFilterTermError, + UnsupportedDataTypeError, +} from '../lib/error'; import { LinkOperator } from '../lib/LinkOperator'; import { SolutionDomain } from '../lib/SolutionDomain'; import { SolutionRange } from '../lib/SolutionRange'; @@ -21,12 +25,6 @@ import { SparqlOperandDataTypes, LogicOperator } from '../lib/solverInterfaces'; import type { ISolverExpression, } from '../lib/solverInterfaces'; -import { - MissMatchVariableError, - MisformatedFilterTermError, - UnsupportedDataTypeError, - UnsupportedOperatorError -} from '../lib/error'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; @@ -519,7 +517,8 @@ describe('solver function', () => { const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeInstanceOf(MisformatedFilterTermError); + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) + .toBeInstanceOf(MisformatedFilterTermError); }); it('given an algebra expression without args than should return a misformated error', () => { @@ -535,34 +534,36 @@ describe('solver function', () => { expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeInstanceOf(MisformatedFilterTermError); }); - it('given an algebra expression with a litteral containing an invalid datatype than should return an unsupported datatype error', - () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ - new LinkOperator(LogicOperator.And), - new LinkOperator(LogicOperator.Or), - ]; + it(`given an algebra expression with a litteral containing an invalid datatype than + should return an unsupported datatype error`, + () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [ + new LinkOperator(LogicOperator.And), + new LinkOperator(LogicOperator.Or), + ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeInstanceOf(UnsupportedDataTypeError); - }); + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) + .toBeInstanceOf(UnsupportedDataTypeError); + }); it(`given an algebra expression with a litteral containing a literal that cannot be converted into number should return an unsupported datatype error`, () => { @@ -587,7 +588,8 @@ describe('solver function', () => { const operator = SparqlRelationOperator.EqualThanRelation; const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)).toBeInstanceOf(UnsupportedDataTypeError); + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) + .toBeInstanceOf(UnsupportedDataTypeError); }); }); @@ -961,7 +963,7 @@ describe('solver function', () => { expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); - it('should accept the link given that recursifResolve return a SyntaxError', ()=>{ + it('should accept the link given that recursifResolve return a SyntaxError', () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, remainingItems: 10, @@ -983,5 +985,4 @@ describe('solver function', () => { expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); }); }); - -}); \ No newline at end of file +}); From a6e99d28775259a2e45a54ed174ac20ec135b8d7 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 22 Feb 2023 13:38:16 +0100 Subject: [PATCH 116/189] Test coverage made to 100%. --- .../lib/error.ts | 7 - .../lib/solver.ts | 12 +- .../test/solver-test.ts | 202 +++++++++++------- 3 files changed, 129 insertions(+), 92 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/error.ts b/packages/actor-extract-links-extract-tree/lib/error.ts index cef899a4a..40c275cdd 100644 --- a/packages/actor-extract-links-extract-tree/lib/error.ts +++ b/packages/actor-extract-links-extract-tree/lib/error.ts @@ -18,10 +18,3 @@ export class UnsupportedDataTypeError extends Error { this.name = 'UnsupportedDataTypeError'; } } - -export class UnsupportedOperatorError extends Error { - public constructor(message: string) { - super(message); - this.name = 'UnsupportedOperatorError'; - } -} diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index c5e3f8e2e..7dfd6507c 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -6,7 +6,6 @@ import { MissMatchVariableError, MisformatedFilterTermError, UnsupportedDataTypeError, - UnsupportedOperatorError, } from './error'; import { LinkOperator } from './LinkOperator'; import { SolutionDomain } from './SolutionDomain'; @@ -74,11 +73,7 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi return true; } - // We don't support the operator so let need to explore that link to not diminush the completness of the result - if (error instanceof UnsupportedOperatorError) { - return true; - } - + /* istanbul ignore next */ // If it's unexpected error we throw it throw error; } @@ -188,10 +183,7 @@ export function recursifResolve( } else if (solverExpression instanceof Error) { throw solverExpression; } else { - solverRange = getSolutionRange(solverExpression.valueAsNumber, solverExpression.operator); - if (!solverRange) { - throw new UnsupportedOperatorError(`the operator "${solverExpression.operator}" of the ISolverExpression is not supported yet`); - } + solverRange = getSolutionRange(solverExpression.valueAsNumber, solverExpression.operator)!; } // We can distribute a not expression, so we inverse each statement if (notExpression) { diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 154cbdcb0..d6c8ac34f 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1,7 +1,7 @@ import type { ITreeRelation } from '@comunica/types-link-traversal'; import { SparqlRelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; -import type * as RDF from 'rdf-js'; +import * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; import { MisformatedFilterTermError, @@ -35,14 +35,14 @@ describe('solver function', () => { describe('filterOperatorToSparqlRelationOperator', () => { it('should return the RelationOperator given a string representation', () => { const testTable: [string, SparqlRelationOperator][] = [ - [ '=', SparqlRelationOperator.EqualThanRelation ], - [ '<', SparqlRelationOperator.LessThanRelation ], - [ '<=', SparqlRelationOperator.LessThanOrEqualToRelation ], - [ '>', SparqlRelationOperator.GreaterThanRelation ], - [ '>=', SparqlRelationOperator.GreaterThanOrEqualToRelation ], + ['=', SparqlRelationOperator.EqualThanRelation], + ['<', SparqlRelationOperator.LessThanRelation], + ['<=', SparqlRelationOperator.LessThanOrEqualToRelation], + ['>', SparqlRelationOperator.GreaterThanRelation], + ['>=', SparqlRelationOperator.GreaterThanOrEqualToRelation], ]; - for (const [ value, expectedAnswer ] of testTable) { + for (const [value, expectedAnswer] of testTable) { expect(filterOperatorToSparqlRelationOperator(value)).toBe(expectedAnswer); } }); @@ -91,65 +91,65 @@ describe('solver function', () => { describe('castSparqlRdfTermIntoNumber', () => { it('should return the expected number when given an integer', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - [ '19273', SparqlOperandDataTypes.Integer, 19_273 ], - [ '0', SparqlOperandDataTypes.NonPositiveInteger, 0 ], - [ '-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459 ], - [ '121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321 ], - [ '1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213 ], - [ '283', SparqlOperandDataTypes.NonNegativeInteger, 283 ], - [ '-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831 ], - [ '-1234', SparqlOperandDataTypes.UnsignedInt, -1_234 ], - [ '-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234 ], - [ '1234', SparqlOperandDataTypes.PositiveInteger, 1_234 ], + ['19273', SparqlOperandDataTypes.Integer, 19_273], + ['0', SparqlOperandDataTypes.NonPositiveInteger, 0], + ['-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459], + ['121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321], + ['1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213], + ['283', SparqlOperandDataTypes.NonNegativeInteger, 283], + ['-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831], + ['-1234', SparqlOperandDataTypes.UnsignedInt, -1_234], + ['-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234], + ['1234', SparqlOperandDataTypes.PositiveInteger, 1_234], ]; - for (const [ value, valueType, expectedNumber ] of testTable) { + for (const [value, valueType, expectedNumber] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return undefined if a non integer is pass with SparqlOperandDataTypes integer compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - [ '1.6751', SparqlOperandDataTypes.Integer ], - [ 'asbd', SparqlOperandDataTypes.PositiveInteger ], - [ '', SparqlOperandDataTypes.NegativeInteger ], + ['1.6751', SparqlOperandDataTypes.Integer], + ['asbd', SparqlOperandDataTypes.PositiveInteger], + ['', SparqlOperandDataTypes.NegativeInteger], ]; - for (const [ value, valueType ] of testTable) { + for (const [value, valueType] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return undefined if a non fraction is pass with SparqlOperandDataTypes fraction compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - [ 'asbd', SparqlOperandDataTypes.Double ], - [ '', SparqlOperandDataTypes.Float ], + ['asbd', SparqlOperandDataTypes.Double], + ['', SparqlOperandDataTypes.Float], ]; - for (const [ value, valueType ] of testTable) { + for (const [value, valueType] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return the expected number when given an decimal', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - [ '1.1', SparqlOperandDataTypes.Decimal, 1.1 ], - [ '2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321 ], - [ '1234.123', SparqlOperandDataTypes.Double, 1_234.123 ], + ['1.1', SparqlOperandDataTypes.Decimal, 1.1], + ['2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321], + ['1234.123', SparqlOperandDataTypes.Double, 1_234.123], ]; - for (const [ value, valueType, expectedNumber ] of testTable) { + for (const [value, valueType, expectedNumber] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return the expected number given a boolean', () => { const testTable: [string, number][] = [ - [ 'true', 1 ], - [ 'false', 0 ], + ['true', 1], + ['false', 0], ]; - for (const [ value, expectedNumber ] of testTable) { + for (const [value, expectedNumber] of testTable) { expect(castSparqlRdfTermIntoNumber(value, SparqlOperandDataTypes.Boolean)).toBe(expectedNumber); } }); @@ -184,27 +184,27 @@ describe('solver function', () => { const testTable: [SparqlRelationOperator, SolutionRange][] = [ [ SparqlRelationOperator.GreaterThanRelation, - new SolutionRange([ nextUp(value), Number.POSITIVE_INFINITY ]), + new SolutionRange([nextUp(value), Number.POSITIVE_INFINITY]), ], [ SparqlRelationOperator.GreaterThanOrEqualToRelation, - new SolutionRange([ value, Number.POSITIVE_INFINITY ]), + new SolutionRange([value, Number.POSITIVE_INFINITY]), ], [ SparqlRelationOperator.EqualThanRelation, - new SolutionRange([ value, value ]), + new SolutionRange([value, value]), ], [ SparqlRelationOperator.LessThanRelation, - new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(value) ]), + new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(value)]), ], [ SparqlRelationOperator.LessThanOrEqualToRelation, - new SolutionRange([ Number.NEGATIVE_INFINITY, value ]), + new SolutionRange([Number.NEGATIVE_INFINITY, value]), ], ]; - for (const [ operator, expectedRange ] of testTable) { + for (const [operator, expectedRange] of testTable) { expect(getSolutionRange(value, operator)).toStrictEqual(expectedRange); } }); @@ -461,7 +461,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; const variable = 'x'; const expectedSolverExpression: ISolverExpression = { variable, @@ -495,7 +495,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeInstanceOf(MisformatedFilterTermError); }); @@ -515,7 +515,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) .toBeInstanceOf(MisformatedFilterTermError); @@ -529,41 +529,41 @@ describe('solver function', () => { args: [], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeInstanceOf(MisformatedFilterTermError); }); it(`given an algebra expression with a litteral containing an invalid datatype than should return an unsupported datatype error`, - () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ - new LinkOperator(LogicOperator.And), - new LinkOperator(LogicOperator.Or), - ]; - - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) - .toBeInstanceOf(UnsupportedDataTypeError); - }); + () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [ + new LinkOperator(LogicOperator.And), + new LinkOperator(LogicOperator.Or), + ]; + + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) + .toBeInstanceOf(UnsupportedDataTypeError); + }); it(`given an algebra expression with a litteral containing a literal that cannot be converted into number should return an unsupported datatype error`, () => { @@ -586,7 +586,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; + const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) .toBeInstanceOf(UnsupportedDataTypeError); @@ -608,7 +608,7 @@ describe('solver function', () => { false, ); - const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([ 2, 2 ])); + const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([2, 2])); expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); }); @@ -648,7 +648,7 @@ describe('solver function', () => { false, ); - const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([ 5, Number.POSITIVE_INFINITY ])); + const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([5, Number.POSITIVE_INFINITY])); expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); }); @@ -669,9 +669,9 @@ describe('solver function', () => { ); let expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange( - [ Number.NEGATIVE_INFINITY, nextDown(3) ], + [Number.NEGATIVE_INFINITY, nextDown(3)], )); - expectedDomain = expectedDomain.addWithOrOperator(new SolutionRange([ nextUp(3), Number.POSITIVE_INFINITY ])); + expectedDomain = expectedDomain.addWithOrOperator(new SolutionRange([nextUp(3), Number.POSITIVE_INFINITY])); expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); }); }); @@ -984,5 +984,57 @@ describe('solver function', () => { expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); }); + + it('should accept the link if the data type of the filter is unsupported', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x = 2 && ?x > "a" && ?x > 88.3) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it('should accept the link if a filter term is malformated', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: "=", + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term:DF.variable('x') + } + ], + }; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); }); }); From ac9a9ddbd552e0b2dee33ec339de51875162338a Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 22 Feb 2023 13:59:30 +0100 Subject: [PATCH 117/189] lint-fix --- .../test/solver-test.ts | 156 +++++++++--------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index d6c8ac34f..2ab99bf4f 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1,7 +1,7 @@ import type { ITreeRelation } from '@comunica/types-link-traversal'; import { SparqlRelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; -import * as RDF from 'rdf-js'; +import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; import { MisformatedFilterTermError, @@ -35,14 +35,14 @@ describe('solver function', () => { describe('filterOperatorToSparqlRelationOperator', () => { it('should return the RelationOperator given a string representation', () => { const testTable: [string, SparqlRelationOperator][] = [ - ['=', SparqlRelationOperator.EqualThanRelation], - ['<', SparqlRelationOperator.LessThanRelation], - ['<=', SparqlRelationOperator.LessThanOrEqualToRelation], - ['>', SparqlRelationOperator.GreaterThanRelation], - ['>=', SparqlRelationOperator.GreaterThanOrEqualToRelation], + [ '=', SparqlRelationOperator.EqualThanRelation ], + [ '<', SparqlRelationOperator.LessThanRelation ], + [ '<=', SparqlRelationOperator.LessThanOrEqualToRelation ], + [ '>', SparqlRelationOperator.GreaterThanRelation ], + [ '>=', SparqlRelationOperator.GreaterThanOrEqualToRelation ], ]; - for (const [value, expectedAnswer] of testTable) { + for (const [ value, expectedAnswer ] of testTable) { expect(filterOperatorToSparqlRelationOperator(value)).toBe(expectedAnswer); } }); @@ -91,65 +91,65 @@ describe('solver function', () => { describe('castSparqlRdfTermIntoNumber', () => { it('should return the expected number when given an integer', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - ['19273', SparqlOperandDataTypes.Integer, 19_273], - ['0', SparqlOperandDataTypes.NonPositiveInteger, 0], - ['-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459], - ['121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321], - ['1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213], - ['283', SparqlOperandDataTypes.NonNegativeInteger, 283], - ['-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831], - ['-1234', SparqlOperandDataTypes.UnsignedInt, -1_234], - ['-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234], - ['1234', SparqlOperandDataTypes.PositiveInteger, 1_234], + [ '19273', SparqlOperandDataTypes.Integer, 19_273 ], + [ '0', SparqlOperandDataTypes.NonPositiveInteger, 0 ], + [ '-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459 ], + [ '121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321 ], + [ '1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213 ], + [ '283', SparqlOperandDataTypes.NonNegativeInteger, 283 ], + [ '-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831 ], + [ '-1234', SparqlOperandDataTypes.UnsignedInt, -1_234 ], + [ '-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234 ], + [ '1234', SparqlOperandDataTypes.PositiveInteger, 1_234 ], ]; - for (const [value, valueType, expectedNumber] of testTable) { + for (const [ value, valueType, expectedNumber ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return undefined if a non integer is pass with SparqlOperandDataTypes integer compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - ['1.6751', SparqlOperandDataTypes.Integer], - ['asbd', SparqlOperandDataTypes.PositiveInteger], - ['', SparqlOperandDataTypes.NegativeInteger], + [ '1.6751', SparqlOperandDataTypes.Integer ], + [ 'asbd', SparqlOperandDataTypes.PositiveInteger ], + [ '', SparqlOperandDataTypes.NegativeInteger ], ]; - for (const [value, valueType] of testTable) { + for (const [ value, valueType ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return undefined if a non fraction is pass with SparqlOperandDataTypes fraction compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - ['asbd', SparqlOperandDataTypes.Double], - ['', SparqlOperandDataTypes.Float], + [ 'asbd', SparqlOperandDataTypes.Double ], + [ '', SparqlOperandDataTypes.Float ], ]; - for (const [value, valueType] of testTable) { + for (const [ value, valueType ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return the expected number when given an decimal', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - ['1.1', SparqlOperandDataTypes.Decimal, 1.1], - ['2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321], - ['1234.123', SparqlOperandDataTypes.Double, 1_234.123], + [ '1.1', SparqlOperandDataTypes.Decimal, 1.1 ], + [ '2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321 ], + [ '1234.123', SparqlOperandDataTypes.Double, 1_234.123 ], ]; - for (const [value, valueType, expectedNumber] of testTable) { + for (const [ value, valueType, expectedNumber ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return the expected number given a boolean', () => { const testTable: [string, number][] = [ - ['true', 1], - ['false', 0], + [ 'true', 1 ], + [ 'false', 0 ], ]; - for (const [value, expectedNumber] of testTable) { + for (const [ value, expectedNumber ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, SparqlOperandDataTypes.Boolean)).toBe(expectedNumber); } }); @@ -184,27 +184,27 @@ describe('solver function', () => { const testTable: [SparqlRelationOperator, SolutionRange][] = [ [ SparqlRelationOperator.GreaterThanRelation, - new SolutionRange([nextUp(value), Number.POSITIVE_INFINITY]), + new SolutionRange([ nextUp(value), Number.POSITIVE_INFINITY ]), ], [ SparqlRelationOperator.GreaterThanOrEqualToRelation, - new SolutionRange([value, Number.POSITIVE_INFINITY]), + new SolutionRange([ value, Number.POSITIVE_INFINITY ]), ], [ SparqlRelationOperator.EqualThanRelation, - new SolutionRange([value, value]), + new SolutionRange([ value, value ]), ], [ SparqlRelationOperator.LessThanRelation, - new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(value)]), + new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(value) ]), ], [ SparqlRelationOperator.LessThanOrEqualToRelation, - new SolutionRange([Number.NEGATIVE_INFINITY, value]), + new SolutionRange([ Number.NEGATIVE_INFINITY, value ]), ], ]; - for (const [operator, expectedRange] of testTable) { + for (const [ operator, expectedRange ] of testTable) { expect(getSolutionRange(value, operator)).toStrictEqual(expectedRange); } }); @@ -461,7 +461,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; const variable = 'x'; const expectedSolverExpression: ISolverExpression = { variable, @@ -495,7 +495,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeInstanceOf(MisformatedFilterTermError); }); @@ -515,7 +515,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) .toBeInstanceOf(MisformatedFilterTermError); @@ -529,41 +529,41 @@ describe('solver function', () => { args: [], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeInstanceOf(MisformatedFilterTermError); }); it(`given an algebra expression with a litteral containing an invalid datatype than should return an unsupported datatype error`, - () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ - new LinkOperator(LogicOperator.And), - new LinkOperator(LogicOperator.Or), - ]; - - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) - .toBeInstanceOf(UnsupportedDataTypeError); - }); + () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const linksOperator: LinkOperator[] = [ + new LinkOperator(LogicOperator.And), + new LinkOperator(LogicOperator.Or), + ]; + + expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) + .toBeInstanceOf(UnsupportedDataTypeError); + }); it(`given an algebra expression with a litteral containing a literal that cannot be converted into number should return an unsupported datatype error`, () => { @@ -586,7 +586,7 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or)]; + const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) .toBeInstanceOf(UnsupportedDataTypeError); @@ -608,7 +608,7 @@ describe('solver function', () => { false, ); - const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([2, 2])); + const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([ 2, 2 ])); expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); }); @@ -648,7 +648,7 @@ describe('solver function', () => { false, ); - const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([5, Number.POSITIVE_INFINITY])); + const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([ 5, Number.POSITIVE_INFINITY ])); expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); }); @@ -669,9 +669,9 @@ describe('solver function', () => { ); let expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange( - [Number.NEGATIVE_INFINITY, nextDown(3)], + [ Number.NEGATIVE_INFINITY, nextDown(3) ], )); - expectedDomain = expectedDomain.addWithOrOperator(new SolutionRange([nextUp(3), Number.POSITIVE_INFINITY])); + expectedDomain = expectedDomain.addWithOrOperator(new SolutionRange([ nextUp(3), Number.POSITIVE_INFINITY ])); expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); }); }); @@ -1022,13 +1022,13 @@ describe('solver function', () => { const filterExpression: Algebra.Expression = { type: Algebra.types.EXPRESSION, expressionType: Algebra.expressionTypes.OPERATOR, - operator: "=", + operator: '=', args: [ { type: Algebra.types.EXPRESSION, expressionType: Algebra.expressionTypes.TERM, - term:DF.variable('x') - } + term: DF.variable('x'), + }, ], }; From 231be46c021228c6c0bdcdcf29088debf5ea8203 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 23 Feb 2023 13:39:12 +0100 Subject: [PATCH 118/189] Starting to implement the concept of empty range. --- .../lib/SolutionRange.ts | 63 ++++++++++--- .../lib/solver.ts | 20 +++- .../test/solver-test.ts | 92 +++++++++---------- 3 files changed, 110 insertions(+), 65 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts index 8d7c77810..a98b6a678 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts @@ -13,18 +13,29 @@ export class SolutionRange { * The lower bound of the range. */ public readonly lower: number; + /** + * Weither the range is empty + */ + public readonly isEmpty: boolean; /** * Constructor of a solution range, will throw an error if the lower range is greater than the upper range. * @param {[number, number]} range - An array where the first memeber is the lower bound of the range * and the second the upper bound */ - public constructor(range: [number, number]) { - if (range[0] > range[1]) { - throw new RangeError('the first element of the range should lower or equal to the second'); + public constructor(range: [number, number] | undefined) { + if (range) { + if (range[0] > range[1]) { + throw new RangeError('the first element of the range should lower or equal to the second'); + } + this.lower = range[0]; + this.upper = range[1]; + this.isEmpty = false; + }else{ + this.isEmpty = true; + this.lower = Number.NaN; + this.upper = Number.NaN; } - this.lower = range[0]; - this.upper = range[1]; } /** @@ -33,6 +44,10 @@ export class SolutionRange { * @returns {boolean} Return true if the two range overlap. */ public isOverlapping(otherRange: SolutionRange): boolean { + if (this.isEmpty) { + return false; + } + if (this.upper === otherRange.upper && this.lower === otherRange.lower) { return true; } @@ -57,6 +72,9 @@ export class SolutionRange { * @returns {boolean} Return true if the other range is inside this range. */ public isInside(otherRange: SolutionRange): boolean { + if (this.isEmpty) { + return false; + } return otherRange.lower >= this.lower && otherRange.upper <= this.upper; } @@ -65,14 +83,27 @@ export class SolutionRange { * @param {SolutionRange} subjectRange * @param {SolutionRange} otherRange * @returns {SolutionRange[]} Return the fused range if they overlap else return the input ranges. + * It also take into consideration if the range is empty. */ public static fuseRange(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange[] { + if (subjectRange.isEmpty && otherRange.isEmpty) { + return [new SolutionRange(undefined)] + } + + if (subjectRange.isEmpty && !otherRange.isEmpty) { + return [otherRange] + } + + if (!subjectRange.isEmpty && otherRange.isEmpty) { + return [subjectRange] + } + if (subjectRange.isOverlapping(otherRange)) { const lowest = subjectRange.lower < otherRange.lower ? subjectRange.lower : otherRange.lower; const uppest = subjectRange.upper > otherRange.upper ? subjectRange.upper : otherRange.upper; - return [ new SolutionRange([ lowest, uppest ]) ]; + return [new SolutionRange([lowest, uppest])]; } - return [ subjectRange, otherRange ]; + return [subjectRange, otherRange]; } /** @@ -81,20 +112,24 @@ export class SolutionRange { * @returns {SolutionRange[]} The resulting ranges. */ public inverse(): SolutionRange[] { - if (this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY) { + + if (this.isEmpty) { + return [new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY])] + } + if ((this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY) || this.isEmpty) { return []; } if (this.lower === Number.NEGATIVE_INFINITY) { - return [ new SolutionRange([ nextUp(this.upper), Number.POSITIVE_INFINITY ]) ]; + return [new SolutionRange([nextUp(this.upper), Number.POSITIVE_INFINITY])]; } if (this.upper === Number.POSITIVE_INFINITY) { - return [ new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(this.lower) ]) ]; + return [new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(this.lower)])]; } return [ - new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(this.lower) ]), - new SolutionRange([ nextUp(this.upper), Number.POSITIVE_INFINITY ]), + new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(this.lower)]), + new SolutionRange([nextUp(this.upper), Number.POSITIVE_INFINITY]), ]; } @@ -105,12 +140,12 @@ export class SolutionRange { * @returns {SolutionRange | undefined} Return the intersection if the range overlap otherwise return undefined */ public static getIntersection(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange | undefined { - if (!subjectRange.isOverlapping(otherRange)) { + if (!subjectRange.isOverlapping(otherRange) || subjectRange.isEmpty || otherRange.isEmpty) { return undefined; } const lower = subjectRange.lower > otherRange.lower ? subjectRange.lower : otherRange.lower; const upper = subjectRange.upper < otherRange.upper ? subjectRange.upper : otherRange.upper; - return new SolutionRange([ lower, upper ]); + return new SolutionRange([lower, upper]); } } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 7dfd6507c..78d206ac8 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -23,6 +23,8 @@ const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]); +const A_FALSE_EXPRESSION: SolutionRange = new SolutionRange(undefined); + /** * Check if the solution domain of a system of equation compose of the expressions of the filter * expression and the relation is not empty. @@ -171,7 +173,8 @@ export function recursifResolve( // hence get a subdomain appendable to the current global domain with consideration // to the logic operator if ( - filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM + filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && + filterExpression.args.length == 2 ) { const rawOperator = filterExpression.operator; const operator = filterOperatorToSparqlRelationOperator(rawOperator); @@ -197,8 +200,17 @@ export function recursifResolve( domain = domain.add({ range: solverRange, operator: logicOperator }); } } - // Else we store the logical operator an go deeper into the Algebra graph - } else { + } else if (filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && + filterExpression.args.length == 1 + ) { + if (filterExpression.args[0].term.value === 'false') { + domain = domain.add({ range: A_FALSE_EXPRESSION, operator: logicOperator }); + } + if (filterExpression.args[0].term.value === 'true') { + domain = domain.add({ range: A_TRUE_EXPRESSION, operator: logicOperator }); + } + // Else we store the logical operator an go deeper into the Algebra graph + } else { let newLogicOperator = LogicOperatorReversed.get(filterExpression.operator); notExpression = newLogicOperator === LogicOperator.Not || notExpression; if (newLogicOperator) { @@ -315,7 +327,7 @@ export function castSparqlRdfTermIntoNumber(rdfTermValue: string, } if ( - isSparqlOperandNumberType(rdfTermType) && !rdfTermValue.includes('.') + isSparqlOperandNumberType(rdfTermType) ) { const val = Number.parseInt(rdfTermValue, 10); return Number.isNaN(val) ? undefined : val; diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index ed3ca2161..d8ff7db01 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -110,7 +110,6 @@ describe('solver function', () => { it('should return undefined if a non integer is pass with SparqlOperandDataTypes integer compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - [ '1.6751', SparqlOperandDataTypes.Integer ], [ 'asbd', SparqlOperandDataTypes.PositiveInteger ], [ '', SparqlOperandDataTypes.NegativeInteger ], ]; @@ -894,52 +893,6 @@ describe('solver function', () => { expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); - it('edge-case 1', - () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(?x>5) - }`).input.expression; - - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); - }); - - it('edge-case 2', - () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '4.999', - term: DF.literal('4.999', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(?x>=5) - }`).input.expression; - - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); - }); - it('should return false when there is no solution for the filter expression with one expression and the relation', () => { const relation: ITreeRelation = { @@ -1084,6 +1037,51 @@ describe('solver function', () => { }); }); + it('edge-case 1', + () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x>5) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + +it('edge-case 2', + () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '4.999', + term: DF.literal('4.999', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x>=5) + }`).input.expression; + + const variable = 'x'; + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + it(`edge-case 3`, () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, From c43dd16fd90c9d3f3f0c75ffd7edd91f2916c46b Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 6 Mar 2023 13:09:31 +0100 Subject: [PATCH 119/189] made stable for the benchmark --- config.json | 14 +++++++ demo.js | 22 +++++++++++ .../config/extract-links/actors/tree.json | 3 +- .../lib/ActorExtractLinksTree.ts | 26 ++++++++++--- yarn.lock | 38 ++++++++++++++++++- 5 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 config.json create mode 100644 demo.js diff --git a/config.json b/config.json new file mode 100644 index 000000000..fb95fcc4f --- /dev/null +++ b/config.json @@ -0,0 +1,14 @@ +{ + "@context": [ + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql/^2.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql-link-traversal/^0.0.0/components/context.jsonld" + ], + "import": [ + "ccqslt:config/config-base.json", + "ccqslt:config/extract-links/actors/content-policies-conditional.json", + "ccqslt:config/extract-links/actors/all.json", + "ccqslt:config/rdf-resolve-hypermedia-links/actors/traverse-replace-conditional.json", + "ccqslt:config/rdf-resolve-hypermedia-links-queue/actors/wrapper-limit-count.json" + ] + } + \ No newline at end of file diff --git a/demo.js b/demo.js new file mode 100644 index 000000000..8d6e9df82 --- /dev/null +++ b/demo.js @@ -0,0 +1,22 @@ +const communica = require("@comunica/query-sparql-link-traversal"); +const log = require("@comunica/logger-pretty"); + +const myConfigPath = './config.json' + +new communica.QueryEngineFactory().create({ configPath: myConfigPath, }).then( + (engine) => { + engine.queryBindings(` + SELECT ?s WHERE { + ?s . + }`, { + sources: ['https://treecg.github.io/demo_data/vtmk/f.ttl'], + lenient: true, + log: new log.LoggerPretty({ level: 'trace' }), + }).then((bindingsStream) => { + bindingsStream.on('data', (binding) => { + console.log(binding.toString()); + }); + + }); + } +); \ No newline at end of file diff --git a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json index 8da34c2b8..f0696d398 100644 --- a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json +++ b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json @@ -9,7 +9,8 @@ "actors": [ { "@id": "urn:comunica:default:extract-links/actors#extract-links-tree", - "@type": "ActorExtractLinksTree" + "@type": "ActorExtractLinksTree", + "reachabilityCriterionUseSPARQLFilter": true } ] } diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 4a24dc1dc..39994a730 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -4,7 +4,7 @@ import type { } from '@comunica/bus-extract-links'; import { ActorExtractLinks } from '@comunica/bus-extract-links'; -import type { IActorTest } from '@comunica/core'; +import type {IActorArgs, IActorTest } from '@comunica/core'; import type { IActionContext } from '@comunica/types'; import type { ITreeRelationRaw, ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; import { TreeNodes } from '@comunica/types-link-traversal'; @@ -17,8 +17,11 @@ import { buildRelationElement, materializeTreeRelation, addRelationDescription } * A comunica Extract Links Tree Extract Links Actor. */ export class ActorExtractLinksTree extends ActorExtractLinks { - public constructor(args: IActorExtractLinksArgs) { + + private readonly reachabilityCriterionUseSPARQLFilter: boolean = true; + public constructor(args: IActorExtractLinksTree) { super(args); + this.reachabilityCriterionUseSPARQLFilter = args.reachabilityCriterionUseSPARQLFilter || true; } public async test(action: IActionExtractLinks): Promise { @@ -66,10 +69,11 @@ export class ActorExtractLinksTree extends ActorExtractLinks { // Create a ITreeNode object const node: ITreeNode = { relation: relations, identifier: currentPageUrl }; let acceptedRelation = relations; - - // Filter the relation based on the query - const filters = await this.applyFilter(node, action.context); - acceptedRelation = this.handleFilter(filters, acceptedRelation); + if (this.reachabilityCriterionUseSPARQLFilter) { + // Filter the relation based on the query + const filters = await this.applyFilter(node, action.context); + acceptedRelation = this.handleFilter(filters, acceptedRelation); + } resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); }); }); @@ -128,3 +132,13 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } } } + +export interface IActorExtractLinksTree + extends IActorArgs{ + /** + * If true (default), then we use a reachability criterion that prune links that don't respect the + * SPARQL filter + * @default {true} + */ + reachabilityCriterionUseSPARQLFilter: boolean; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 46df621cf..3b8a866d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2327,7 +2327,7 @@ rdf-data-factory "^1.1.1" rdf-string "^1.6.1" -"@comunica/bindings-factory@^2.0.1", "@comunica/bindings-factory@^2.5.1": +"@comunica/bindings-factory@^2.0.1", "@comunica/bindings-factory@^2.2.0", "@comunica/bindings-factory@^2.5.1": version "2.5.1" resolved "https://registry.yarnpkg.com/@comunica/bindings-factory/-/bindings-factory-2.5.1.tgz#18fc7269dc8d11f8de28b8c8c21c80ea80464250" integrity sha512-w8L9t17EaBibuyDXQAjSK04E4E3s/WWsSCNQz7QzG8dpLqscLfwiE0OBJpqpqGZ8pBkgQPt/JyC2WAqwPi5CHg== @@ -2367,6 +2367,13 @@ "@comunica/types" "^2.6.7" readable-stream "^4.2.0" +"@comunica/bus-extract-links@^0.0.1": + version "0.1.0" + dependencies: + "@comunica/bus-rdf-metadata-extract" "^2.6.7" + "@comunica/bus-rdf-resolve-hypermedia-links" "^2.6.7" + "@comunica/core" "^2.6.7" + "@comunica/bus-hash-bindings@^2.6.7": version "2.6.7" resolved "https://registry.yarnpkg.com/@comunica/bus-hash-bindings/-/bus-hash-bindings-2.6.7.tgz#a31a795c379e4e3cad381f979f78239767742362" @@ -2590,6 +2597,12 @@ resolved "https://registry.yarnpkg.com/@comunica/config-query-sparql/-/config-query-sparql-2.6.0.tgz#a52af16bf592e91db2bc148c1b20bfe98a4c76d2" integrity sha512-Ih02KeThu1RWdiV7JfpD8u0lc3hu547EG5pDhe9igGPjU+ijNbahfJJzKrR7LcJrMTGtydEN+z2allIlBKSftA== +"@comunica/context-entries-link-traversal@^0.0.1": + version "0.1.0" + dependencies: + "@comunica/core" "^2.6.7" + "@comunica/types-link-traversal" "^0.1.0" + "@comunica/context-entries@^2.4.0", "@comunica/context-entries@^2.5.1", "@comunica/context-entries@^2.6.7": version "2.6.7" resolved "https://registry.yarnpkg.com/@comunica/context-entries/-/context-entries-2.6.7.tgz#c7afdaa0f6d4c9e03f54c5f4443841395a25f65d" @@ -2990,6 +3003,9 @@ "@comunica/core" "^2.6.7" componentsjs "^5.3.2" +"@comunica/types-link-traversal@^0.0.1": + version "0.1.0" + "@comunica/types@^2.6.7": version "2.6.7" resolved "https://registry.yarnpkg.com/@comunica/types/-/types-2.6.7.tgz#a5dc8287ba19766c1e4c03469608562794c5e4c5" @@ -10552,6 +10568,26 @@ sparqlalgebrajs@^4.0.0, sparqlalgebrajs@^4.0.2, sparqlalgebrajs@^4.0.3, sparqlal rdf-string "^1.6.0" sparqljs "^3.6.1" +sparqlee@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/sparqlee/-/sparqlee-2.2.1.tgz#56a7b237fdebb42053e6be608fc5265cebbd4440" + integrity sha512-nRBiqNpqUzI4F3/EMyTy+mKc7ijxnw+BpbTuRuc5e25TmYdyzPQzAwGeLFIfWPNd8biJ2fqK4ZxIjHdWdRbhfQ== + dependencies: + "@comunica/bindings-factory" "^2.0.1" + "@rdfjs/types" "^1.1.0" + "@types/lru-cache" "^5.1.1" + "@types/spark-md5" "^3.0.2" + "@types/uuid" "^8.0.0" + bignumber.js "^9.0.1" + hash.js "^1.1.7" + lru-cache "^6.0.0" + rdf-data-factory "^1.1.0" + rdf-string "^1.6.0" + relative-to-absolute-iri "^1.0.6" + spark-md5 "^3.0.1" + sparqlalgebrajs "^4.0.3" + uuid "^8.0.0" + sparqlee@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/sparqlee/-/sparqlee-2.2.0.tgz#7d68c0da3c38dc101345bd80a5bb3c664f39bf9e" From ef4047139a739ed4d63783779e86aa494fac6aa8 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 9 Mar 2023 15:37:48 +0100 Subject: [PATCH 120/189] modification of the argument interface of the TREE actor. --- .../lib/ActorExtractLinksTree.ts | 6 ++-- yarn.lock | 31 ++++++------------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 39994a730..59a7ae7f1 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -19,7 +19,7 @@ import { buildRelationElement, materializeTreeRelation, addRelationDescription } export class ActorExtractLinksTree extends ActorExtractLinks { private readonly reachabilityCriterionUseSPARQLFilter: boolean = true; - public constructor(args: IActorExtractLinksTree) { + public constructor(args: IActorExtractLinksTreeArgs) { super(args); this.reachabilityCriterionUseSPARQLFilter = args.reachabilityCriterionUseSPARQLFilter || true; } @@ -133,8 +133,8 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } } -export interface IActorExtractLinksTree - extends IActorArgs{ +export interface IActorExtractLinksTreeArgs +extends IActorArgs { /** * If true (default), then we use a reachability criterion that prune links that don't respect the * SPARQL filter diff --git a/yarn.lock b/yarn.lock index 6f9c9085d..7feae9689 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2592,6 +2592,12 @@ resolved "https://registry.yarnpkg.com/@comunica/config-query-sparql/-/config-query-sparql-2.6.0.tgz#a52af16bf592e91db2bc148c1b20bfe98a4c76d2" integrity sha512-Ih02KeThu1RWdiV7JfpD8u0lc3hu547EG5pDhe9igGPjU+ijNbahfJJzKrR7LcJrMTGtydEN+z2allIlBKSftA== +"@comunica/context-entries-link-traversal@^0.0.1": + version "0.1.1" + dependencies: + "@comunica/core" "^2.6.8" + "@comunica/types-link-traversal" "^0.1.0" + "@comunica/context-entries@^2.4.0", "@comunica/context-entries@^2.5.1", "@comunica/context-entries@^2.6.7", "@comunica/context-entries@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/context-entries/-/context-entries-2.6.8.tgz#deb86cc01d0b1ebb6c1f2abdbcb91dc0c25bd8f3" @@ -2992,6 +2998,9 @@ "@comunica/core" "^2.6.8" componentsjs "^5.3.2" +"@comunica/types-link-traversal@^0.0.1": + version "0.1.0" + "@comunica/types@^2.6.7", "@comunica/types@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/types/-/types-2.6.8.tgz#366ac851d6bad8c284eeda5bff901cb14415e1f5" @@ -10567,27 +10576,7 @@ sparqlalgebrajs@^4.0.0, sparqlalgebrajs@^4.0.2, sparqlalgebrajs@^4.0.3, sparqlal rdf-string "^1.6.0" sparqljs "^3.6.1" -sparqlee@^2.1.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/sparqlee/-/sparqlee-2.2.1.tgz#56a7b237fdebb42053e6be608fc5265cebbd4440" - integrity sha512-nRBiqNpqUzI4F3/EMyTy+mKc7ijxnw+BpbTuRuc5e25TmYdyzPQzAwGeLFIfWPNd8biJ2fqK4ZxIjHdWdRbhfQ== - dependencies: - "@comunica/bindings-factory" "^2.0.1" - "@rdfjs/types" "^1.1.0" - "@types/lru-cache" "^5.1.1" - "@types/spark-md5" "^3.0.2" - "@types/uuid" "^8.0.0" - bignumber.js "^9.0.1" - hash.js "^1.1.7" - lru-cache "^6.0.0" - rdf-data-factory "^1.1.0" - rdf-string "^1.6.0" - relative-to-absolute-iri "^1.0.6" - spark-md5 "^3.0.1" - sparqlalgebrajs "^4.0.3" - uuid "^8.0.0" - -sparqlee@^2.2.0: +sparqlee@^2.1.0, sparqlee@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/sparqlee/-/sparqlee-2.2.1.tgz#56a7b237fdebb42053e6be608fc5265cebbd4440" integrity sha512-nRBiqNpqUzI4F3/EMyTy+mKc7ijxnw+BpbTuRuc5e25TmYdyzPQzAwGeLFIfWPNd8biJ2fqK4ZxIjHdWdRbhfQ== From 01fe014c189de292e5576b876486f68d40c8ad88 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 13 Mar 2023 15:36:33 +0100 Subject: [PATCH 121/189] Fix the problem that the solver was could not be disable. --- .../lib/ActorExtractLinksTree.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 59a7ae7f1..ce1dc7c29 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -21,7 +21,8 @@ export class ActorExtractLinksTree extends ActorExtractLinks { private readonly reachabilityCriterionUseSPARQLFilter: boolean = true; public constructor(args: IActorExtractLinksTreeArgs) { super(args); - this.reachabilityCriterionUseSPARQLFilter = args.reachabilityCriterionUseSPARQLFilter || true; + this.reachabilityCriterionUseSPARQLFilter = args.reachabilityCriterionUseSPARQLFilter === undefined? + true: args.reachabilityCriterionUseSPARQLFilter ; } public async test(action: IActionExtractLinks): Promise { From 158e429cf310df9477db77ebb30420648f18abe2 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 29 Mar 2023 10:52:02 +0200 Subject: [PATCH 122/189] config file for the tree traversal edited, one for no solver created and the default one contain the solver. --- .../extract-links/actors/tree-no-solver.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 engines/config-query-sparql-link-traversal/config/extract-links/actors/tree-no-solver.json diff --git a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree-no-solver.json b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree-no-solver.json new file mode 100644 index 000000000..e965fefe1 --- /dev/null +++ b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree-no-solver.json @@ -0,0 +1,17 @@ +{ + "@context": [ + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/runner/^2.0.0/components/context.jsonld", + + "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/actor-extract-links-tree/^0.0.0/components/context.jsonld" + ], + "@id": "urn:comunica:default:Runner", + "@type": "Runner", + "actors": [ + { + "@id": "urn:comunica:default:extract-links/actors#extract-links-tree", + "@type": "ActorExtractLinksTree", + "reachabilityCriterionUseSPARQLFilter": false + } + ] + } + \ No newline at end of file From 3b57e3ca17386c34c2f1d15430e71a13ddd3cb98 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 14:34:19 +0200 Subject: [PATCH 123/189] edge test cases passed. --- .../lib/SolutionRange.ts | 2 +- .../lib/solver.ts | 38 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts index a98b6a678..d333ba7ea 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts @@ -116,7 +116,7 @@ export class SolutionRange { if (this.isEmpty) { return [new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY])] } - if ((this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY) || this.isEmpty) { + if ((this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY)) { return []; } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 78d206ac8..d37601bfe 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -22,7 +22,7 @@ import type { const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; -const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]); +const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]); const A_FALSE_EXPRESSION: SolutionRange = new SolutionRange(undefined); /** @@ -169,11 +169,21 @@ export function recursifResolve( if (!logicOperator || domain.isDomainEmpty()) { logicOperator = LogicOperator.Or; } + + if (filterExpression.expressionType === Algebra.expressionTypes.TERM + ) { + if (filterExpression.term.value === 'false') { + domain = domain.add({ range: A_FALSE_EXPRESSION, operator: logicOperator }); + } + if (filterExpression.term.value === 'true') { + domain = domain.add({ range: A_TRUE_EXPRESSION, operator: logicOperator }); + } + } // If it's an array of term then we should be able to create a solver expression // hence get a subdomain appendable to the current global domain with consideration // to the logic operator - if ( - filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && + else if ( + filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && filterExpression.args.length == 2 ) { const rawOperator = filterExpression.operator; @@ -200,17 +210,7 @@ export function recursifResolve( domain = domain.add({ range: solverRange, operator: logicOperator }); } } - } else if (filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && - filterExpression.args.length == 1 - ) { - if (filterExpression.args[0].term.value === 'false') { - domain = domain.add({ range: A_FALSE_EXPRESSION, operator: logicOperator }); - } - if (filterExpression.args[0].term.value === 'true') { - domain = domain.add({ range: A_TRUE_EXPRESSION, operator: logicOperator }); - } - // Else we store the logical operator an go deeper into the Algebra graph - } else { + } else { let newLogicOperator = LogicOperatorReversed.get(filterExpression.operator); notExpression = newLogicOperator === LogicOperator.Not || notExpression; if (newLogicOperator) { @@ -282,15 +282,15 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { export function getSolutionRange(value: number, operator: SparqlRelationOperator): SolutionRange | undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: - return new SolutionRange([ nextUp(value), Number.POSITIVE_INFINITY ]); + return new SolutionRange([nextUp(value), Number.POSITIVE_INFINITY]); case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return new SolutionRange([ value, Number.POSITIVE_INFINITY ]); + return new SolutionRange([value, Number.POSITIVE_INFINITY]); case SparqlRelationOperator.EqualThanRelation: - return new SolutionRange([ value, value ]); + return new SolutionRange([value, value]); case SparqlRelationOperator.LessThanRelation: - return new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(value) ]); + return new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(value)]); case SparqlRelationOperator.LessThanOrEqualToRelation: - return new SolutionRange([ Number.NEGATIVE_INFINITY, value ]); + return new SolutionRange([Number.NEGATIVE_INFINITY, value]); default: // Not an operator that is compatible with number. break; From 96f5245fd4b2f776144185e59721f32e6c422350 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 15:20:25 +0200 Subject: [PATCH 124/189] Test coverage back to 100% and name given to edge cases test. --- .../lib/ActorExtractLinksTree.ts | 4 + .../lib/SolutionRange.ts | 8 +- .../test/ActorExtractLinksTree-test.ts | 16 + .../test/SolutionRange-test.ts | 106 ++++++- .../test/solver-test.ts | 299 +++++++++--------- 5 files changed, 278 insertions(+), 155 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index ce1dc7c29..7007a59a7 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -29,6 +29,10 @@ export class ActorExtractLinksTree extends ActorExtractLinks { return true; } + public isUsingReachabilitySPARQLFilter(): boolean { + return this.reachabilityCriterionUseSPARQLFilter + } + public async run(action: IActionExtractLinks): Promise { return new Promise((resolve, reject) => { const metadata = action.metadata; diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts index d333ba7ea..535212edd 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts @@ -31,7 +31,7 @@ export class SolutionRange { this.lower = range[0]; this.upper = range[1]; this.isEmpty = false; - }else{ + } else { this.isEmpty = true; this.lower = Number.NaN; this.upper = Number.NaN; @@ -44,7 +44,7 @@ export class SolutionRange { * @returns {boolean} Return true if the two range overlap. */ public isOverlapping(otherRange: SolutionRange): boolean { - if (this.isEmpty) { + if (this.isEmpty || otherRange.isEmpty) { return false; } @@ -72,7 +72,7 @@ export class SolutionRange { * @returns {boolean} Return true if the other range is inside this range. */ public isInside(otherRange: SolutionRange): boolean { - if (this.isEmpty) { + if (this.isEmpty || otherRange.isEmpty) { return false; } return otherRange.lower >= this.lower && otherRange.upper <= this.upper; @@ -117,7 +117,7 @@ export class SolutionRange { return [new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY])] } if ((this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY)) { - return []; + return [new SolutionRange(undefined)]; } if (this.lower === Number.NEGATIVE_INFINITY) { diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index f7626226d..5278b2d70 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -33,6 +33,22 @@ describe('ActorExtractLinksExtractLinksTree', () => { it('should not be able to create new ActorExtractLinksExtractLinksTree objects without \'new\'', () => { expect(() => { (ActorExtractLinksTree)(); }).toThrow(); }); + + it('should apply the activate the reachability criterion based on the constructor parameter', ()=>{ + let reachabilityCriterionUseSPARQLFilter = true; + let actor = new ActorExtractLinksTree({ name: 'actor', bus, reachabilityCriterionUseSPARQLFilter }); + expect(actor.isUsingReachabilitySPARQLFilter()).toBe(true); + + reachabilityCriterionUseSPARQLFilter = false; + actor = new ActorExtractLinksTree({ name: 'actor', bus, reachabilityCriterionUseSPARQLFilter }); + expect(actor.isUsingReachabilitySPARQLFilter()).toBe(false); + + }); + + it('should apply the activate the reachability when it is not defined in the config', ()=>{ + const actor = new ActorExtractLinksTree({ name: 'actor', bus}); + expect(actor.isUsingReachabilitySPARQLFilter()).toBe(true); + }); }); describe('The ActorExtractLinksExtractLinksTree run method', () => { diff --git a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts index 9c4f912d9..e83c9d6b4 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts @@ -109,6 +109,32 @@ describe('SolutionRange', () => { expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); }); + + it('should return false when the subject range is empty', () => { + const aSolutionRange = new SolutionRange(undefined); + + const aSecondRange: [number, number] = [ 101, 200 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); + }); + + it('should return false when the other range is empty and the subject range is not', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondSolutionRange = new SolutionRange(undefined); + + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); + }); + + it('should return false when the other range and the subject range are empty', () => { + const aSolutionRange = new SolutionRange(undefined); + + const aSecondSolutionRange = new SolutionRange(undefined); + + expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); + }); }); describe('isInside', () => { it('should return true when the other range is inside the subject range', () => { @@ -130,6 +156,32 @@ describe('SolutionRange', () => { expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(false); }); + + it('should return false when the other range is empty and not the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondSolutionRange = new SolutionRange(undefined); + + expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(false); + }); + + it('should return false when the subject range is empty and not the other range', () => { + const aSolutionRange = new SolutionRange(undefined); + + const aSecondRange: [number, number] = [ -1, 50 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(false); + }); + + it('should return false when the subject range and the other range are empty', () => { + const aSolutionRange = new SolutionRange(undefined); + + const aSecondSolutionRange = new SolutionRange(undefined); + + expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(false); + }); }); describe('fuseRange', () => { @@ -236,16 +288,54 @@ describe('SolutionRange', () => { expect(resp.length).toBe(1); expect(resp[0]).toStrictEqual(expectedRange); }); + + it('given two empty ranges should return an empty range', () => { + const aSolutionRange = new SolutionRange(undefined); + + const aSecondSolutionRange = new SolutionRange(undefined); + + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(new SolutionRange(undefined)); + }); + + it('given an empty subject range and an non empty other range should return the second range', () => { + const aSolutionRange = new SolutionRange(undefined); + + const aSecondRange: [number, number] = [ 101, 200 ]; + const aSecondSolutionRange = new SolutionRange(aSecondRange); + + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(aSecondSolutionRange); + }); + + it('given an empty other range and an non empty subject range should return the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionRange = new SolutionRange(aRange); + + const aSecondSolutionRange = new SolutionRange(undefined); + + const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(aSolutionRange); + }); }); describe('inverse', () => { - it('given an real solution range it should return no range', () => { + it('given an infinite solution range it should return an empty range', () => { const aSolutionRange = new SolutionRange([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY, ]); - expect(aSolutionRange.inverse().length).toBe(0); + const resp = aSolutionRange.inverse(); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(new SolutionRange(undefined)); }); it('given a range with an infinite upper bound should return a new range', () => { @@ -292,6 +382,18 @@ describe('SolutionRange', () => { expect(resp.length).toBe(2); expect(resp).toStrictEqual(expectedRange); }); + + it('given an empty solution range it should return an infinite range', () => { + const aSolutionRange = new SolutionRange(undefined); + + const resp = aSolutionRange.inverse(); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(new SolutionRange([ + Number.NEGATIVE_INFINITY, + Number.POSITIVE_INFINITY, + ])); + }); }); describe('getIntersection', () => { diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index d8ff7db01..8f227d341 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1035,160 +1035,161 @@ describe('solver function', () => { expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); - }); - - it('edge-case 1', - () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(?x>5) - }`).input.expression; - - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); - }); - -it('edge-case 2', - () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '4.999', - term: DF.literal('4.999', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(?x>=5) - }`).input.expression; - - const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); - }); - - it(`edge-case 3`, () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(!(?x<-1 || ?x<5) || true) - }`).input.expression; - - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); - }); - - it(`edge-case 4`, () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(!(?x<-1 || ?x<5) || false) - }`).input.expression; - - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); - }); - it(`edge-case 5`, () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` + it('should accept the link with a solvable simple filter', + () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z - FILTER(?x = 5 || true) + FILTER(?x>5) }`).input.expression; - - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); - }); - - it(`edge-case 6`, () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + + it('should refuse the link with an unsolvable simple filter', + () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '4.999', + term: DF.literal('4.999', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z - FILTER((?x = 5 && NOW() = true && false) || (?x = 5 && NOW() = true)) + FILTER(?x>=5) }`).input.expression; - - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + + const variable = 'x'; + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + + it(`should accept the link with a solvable boolean expression with a true boolean statement`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(!(?x<-1 || ?x<5) || true) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it(`should accept the link with a solvable boolean expression with a false boolean statement`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(!(?x<-1 || ?x<5) || false) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it(`should accept the link with a solvable simple boolean expression with a true boolean statement`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x = 5 || true) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); + + it(`Should ignore the SPARQL function when prunning links`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER((?x = 5 && NOW() = true && false) || (false && NOW() = true)) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + + it(`Should ignore the SPARQL function when prunning links with complex filter expression`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER((?x = 5 && NOW() = true && false) || (?x = 5 && (NOW() = true || ?x >= 5))) + }`).input.expression; + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + }); }); - it(`edge-case 7`, () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER((?x = 5 && NOW() = true && false) || (?x = 5 && (NOW() = true || ?x >= 5))) - }`).input.expression; - - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); - }); }); From 2583d9ffc0648cbe70ddf21636facbe291ff7aa5 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 15:22:33 +0200 Subject: [PATCH 125/189] lint-fix --- .../lib/ActorExtractLinksTree.ts | 16 +-- .../lib/SolutionRange.ts | 27 ++-- .../lib/solver.ts | 23 ++-- .../test/ActorExtractLinksTree-test.ts | 9 +- .../test/solver-test.ts | 115 +++++++++--------- 5 files changed, 93 insertions(+), 97 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 7007a59a7..e75df4daa 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -1,10 +1,10 @@ import type { IActionExtractLinks, - IActorExtractLinksOutput, IActorExtractLinksArgs, + IActorExtractLinksOutput, } from '@comunica/bus-extract-links'; import { ActorExtractLinks } from '@comunica/bus-extract-links'; -import type {IActorArgs, IActorTest } from '@comunica/core'; +import type { IActorArgs, IActorTest } from '@comunica/core'; import type { IActionContext } from '@comunica/types'; import type { ITreeRelationRaw, ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; import { TreeNodes } from '@comunica/types-link-traversal'; @@ -17,12 +17,12 @@ import { buildRelationElement, materializeTreeRelation, addRelationDescription } * A comunica Extract Links Tree Extract Links Actor. */ export class ActorExtractLinksTree extends ActorExtractLinks { - private readonly reachabilityCriterionUseSPARQLFilter: boolean = true; public constructor(args: IActorExtractLinksTreeArgs) { super(args); - this.reachabilityCriterionUseSPARQLFilter = args.reachabilityCriterionUseSPARQLFilter === undefined? - true: args.reachabilityCriterionUseSPARQLFilter ; + this.reachabilityCriterionUseSPARQLFilter = args.reachabilityCriterionUseSPARQLFilter === undefined ? + true : + args.reachabilityCriterionUseSPARQLFilter; } public async test(action: IActionExtractLinks): Promise { @@ -30,7 +30,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } public isUsingReachabilitySPARQLFilter(): boolean { - return this.reachabilityCriterionUseSPARQLFilter + return this.reachabilityCriterionUseSPARQLFilter; } public async run(action: IActionExtractLinks): Promise { @@ -139,11 +139,11 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } export interface IActorExtractLinksTreeArgs -extends IActorArgs { + extends IActorArgs { /** * If true (default), then we use a reachability criterion that prune links that don't respect the * SPARQL filter * @default {true} */ reachabilityCriterionUseSPARQLFilter: boolean; -} \ No newline at end of file +} diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts index 535212edd..212a9230d 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts @@ -87,23 +87,23 @@ export class SolutionRange { */ public static fuseRange(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange[] { if (subjectRange.isEmpty && otherRange.isEmpty) { - return [new SolutionRange(undefined)] + return [ new SolutionRange(undefined) ]; } if (subjectRange.isEmpty && !otherRange.isEmpty) { - return [otherRange] + return [ otherRange ]; } if (!subjectRange.isEmpty && otherRange.isEmpty) { - return [subjectRange] + return [ subjectRange ]; } if (subjectRange.isOverlapping(otherRange)) { const lowest = subjectRange.lower < otherRange.lower ? subjectRange.lower : otherRange.lower; const uppest = subjectRange.upper > otherRange.upper ? subjectRange.upper : otherRange.upper; - return [new SolutionRange([lowest, uppest])]; + return [ new SolutionRange([ lowest, uppest ]) ]; } - return [subjectRange, otherRange]; + return [ subjectRange, otherRange ]; } /** @@ -112,24 +112,23 @@ export class SolutionRange { * @returns {SolutionRange[]} The resulting ranges. */ public inverse(): SolutionRange[] { - if (this.isEmpty) { - return [new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY])] + return [ new SolutionRange([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]) ]; } - if ((this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY)) { - return [new SolutionRange(undefined)]; + if (this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY) { + return [ new SolutionRange(undefined) ]; } if (this.lower === Number.NEGATIVE_INFINITY) { - return [new SolutionRange([nextUp(this.upper), Number.POSITIVE_INFINITY])]; + return [ new SolutionRange([ nextUp(this.upper), Number.POSITIVE_INFINITY ]) ]; } if (this.upper === Number.POSITIVE_INFINITY) { - return [new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(this.lower)])]; + return [ new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(this.lower) ]) ]; } return [ - new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(this.lower)]), - new SolutionRange([nextUp(this.upper), Number.POSITIVE_INFINITY]), + new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(this.lower) ]), + new SolutionRange([ nextUp(this.upper), Number.POSITIVE_INFINITY ]), ]; } @@ -146,6 +145,6 @@ export class SolutionRange { const lower = subjectRange.lower > otherRange.lower ? subjectRange.lower : otherRange.lower; const upper = subjectRange.upper < otherRange.upper ? subjectRange.upper : otherRange.upper; - return new SolutionRange([lower, upper]); + return new SolutionRange([ lower, upper ]); } } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index d37601bfe..143eb5df2 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -22,7 +22,7 @@ import type { const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; -const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]); +const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]); const A_FALSE_EXPRESSION: SolutionRange = new SolutionRange(undefined); /** @@ -178,13 +178,12 @@ export function recursifResolve( if (filterExpression.term.value === 'true') { domain = domain.add({ range: A_TRUE_EXPRESSION, operator: logicOperator }); } - } - // If it's an array of term then we should be able to create a solver expression - // hence get a subdomain appendable to the current global domain with consideration - // to the logic operator - else if ( + } else if ( + // If it's an array of term then we should be able to create a solver expression + // hence get a subdomain appendable to the current global domain with consideration + // to the logic operator filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && - filterExpression.args.length == 2 + filterExpression.args.length === 2 ) { const rawOperator = filterExpression.operator; const operator = filterOperatorToSparqlRelationOperator(rawOperator); @@ -282,15 +281,15 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { export function getSolutionRange(value: number, operator: SparqlRelationOperator): SolutionRange | undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: - return new SolutionRange([nextUp(value), Number.POSITIVE_INFINITY]); + return new SolutionRange([ nextUp(value), Number.POSITIVE_INFINITY ]); case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return new SolutionRange([value, Number.POSITIVE_INFINITY]); + return new SolutionRange([ value, Number.POSITIVE_INFINITY ]); case SparqlRelationOperator.EqualThanRelation: - return new SolutionRange([value, value]); + return new SolutionRange([ value, value ]); case SparqlRelationOperator.LessThanRelation: - return new SolutionRange([Number.NEGATIVE_INFINITY, nextDown(value)]); + return new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(value) ]); case SparqlRelationOperator.LessThanOrEqualToRelation: - return new SolutionRange([Number.NEGATIVE_INFINITY, value]); + return new SolutionRange([ Number.NEGATIVE_INFINITY, value ]); default: // Not an operator that is compatible with number. break; diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 5278b2d70..9b1587f5f 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -34,7 +34,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(() => { (ActorExtractLinksTree)(); }).toThrow(); }); - it('should apply the activate the reachability criterion based on the constructor parameter', ()=>{ + it('should apply the activate the reachability criterion based on the constructor parameter', () => { let reachabilityCriterionUseSPARQLFilter = true; let actor = new ActorExtractLinksTree({ name: 'actor', bus, reachabilityCriterionUseSPARQLFilter }); expect(actor.isUsingReachabilitySPARQLFilter()).toBe(true); @@ -42,13 +42,12 @@ describe('ActorExtractLinksExtractLinksTree', () => { reachabilityCriterionUseSPARQLFilter = false; actor = new ActorExtractLinksTree({ name: 'actor', bus, reachabilityCriterionUseSPARQLFilter }); expect(actor.isUsingReachabilitySPARQLFilter()).toBe(false); - }); - it('should apply the activate the reachability when it is not defined in the config', ()=>{ - const actor = new ActorExtractLinksTree({ name: 'actor', bus}); + it('should apply the activate the reachability when it is not defined in the config', () => { + const actor = new ActorExtractLinksTree({ name: 'actor', bus }); expect(actor.isUsingReachabilitySPARQLFilter()).toBe(true); - }); + }); }); describe('The ActorExtractLinksExtractLinksTree run method', () => { diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 8f227d341..16667a507 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1037,50 +1037,50 @@ describe('solver function', () => { }); it('should accept the link with a solvable simple filter', - () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` + () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(?x>5) }`).input.expression; - - const variable = 'x'; - - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); - }); - - it('should refuse the link with an unsolvable simple filter', - () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '4.999', - term: DF.literal('4.999', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` + + const variable = 'x'; + + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + + it('should refuse the link with an unsolvable simple filter', + () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '4.999', + term: DF.literal('4.999', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(?x>=5) }`).input.expression; - - const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); - }); - + + const variable = 'x'; + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + }); + it(`should accept the link with a solvable boolean expression with a true boolean statement`, () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, @@ -1092,17 +1092,17 @@ describe('solver function', () => { }, node: 'https://www.example.be', }; - + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(!(?x<-1 || ?x<5) || true) }`).input.expression; - + const variable = 'x'; - + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); - + it(`should accept the link with a solvable boolean expression with a false boolean statement`, () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, @@ -1114,17 +1114,17 @@ describe('solver function', () => { }, node: 'https://www.example.be', }; - + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(!(?x<-1 || ?x<5) || false) }`).input.expression; - + const variable = 'x'; - + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); - + it(`should accept the link with a solvable simple boolean expression with a true boolean statement`, () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, @@ -1136,17 +1136,17 @@ describe('solver function', () => { }, node: 'https://www.example.be', }; - + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(?x = 5 || true) }`).input.expression; - + const variable = 'x'; - + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); - + it(`Should ignore the SPARQL function when prunning links`, () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, @@ -1158,17 +1158,17 @@ describe('solver function', () => { }, node: 'https://www.example.be', }; - + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER((?x = 5 && NOW() = true && false) || (false && NOW() = true)) }`).input.expression; - + const variable = 'x'; - + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); }); - + it(`Should ignore the SPARQL function when prunning links with complex filter expression`, () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, @@ -1180,16 +1180,15 @@ describe('solver function', () => { }, node: 'https://www.example.be', }; - + const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER((?x = 5 && NOW() = true && false) || (?x = 5 && (NOW() = true || ?x >= 5))) }`).input.expression; - + const variable = 'x'; - + expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); }); }); - }); From d69c9f71ad9118ed24343ca3c8d96c6e6dc2ea33 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 15:25:34 +0200 Subject: [PATCH 126/189] useless file deleted --- config.json | 14 -------------- demo.js | 22 ---------------------- 2 files changed, 36 deletions(-) delete mode 100644 config.json delete mode 100644 demo.js diff --git a/config.json b/config.json deleted file mode 100644 index fb95fcc4f..000000000 --- a/config.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "@context": [ - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql/^2.0.0/components/context.jsonld", - "https://linkedsoftwaredependencies.org/bundles/npm/@comunica/config-query-sparql-link-traversal/^0.0.0/components/context.jsonld" - ], - "import": [ - "ccqslt:config/config-base.json", - "ccqslt:config/extract-links/actors/content-policies-conditional.json", - "ccqslt:config/extract-links/actors/all.json", - "ccqslt:config/rdf-resolve-hypermedia-links/actors/traverse-replace-conditional.json", - "ccqslt:config/rdf-resolve-hypermedia-links-queue/actors/wrapper-limit-count.json" - ] - } - \ No newline at end of file diff --git a/demo.js b/demo.js deleted file mode 100644 index 8d6e9df82..000000000 --- a/demo.js +++ /dev/null @@ -1,22 +0,0 @@ -const communica = require("@comunica/query-sparql-link-traversal"); -const log = require("@comunica/logger-pretty"); - -const myConfigPath = './config.json' - -new communica.QueryEngineFactory().create({ configPath: myConfigPath, }).then( - (engine) => { - engine.queryBindings(` - SELECT ?s WHERE { - ?s . - }`, { - sources: ['https://treecg.github.io/demo_data/vtmk/f.ttl'], - lenient: true, - log: new log.LoggerPretty({ level: 'trace' }), - }).then((bindingsStream) => { - bindingsStream.on('data', (binding) => { - console.log(binding.toString()); - }); - - }); - } -); \ No newline at end of file From ee593af74fed1f9c1163e75d072bcd3b5d93c343 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 15:41:05 +0200 Subject: [PATCH 127/189] Test function replace by a function that get the filter expression to avoid double search of the filter sub tree. --- .../lib/FilterNode.ts | 25 ++++++++----------- .../test/FilterNode-test.ts | 18 ++++++------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 71bd27e5a..75ef70a0d 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -17,36 +17,33 @@ const BF = new BindingsFactory(); * the binding are remplace by the [value of TREE relation](https://treecg.github.io/specification/#traversing). */ export class FilterNode { - public test(node: ITreeNode, context: IActionContext): boolean { + public getFilterExpressionIfTreeNodeHasConstraint(node: ITreeNode, context: IActionContext): Algebra.Expression|undefined { if (!node.relation) { - return false; + return undefined; } if (node.relation.length === 0) { - return false; + return undefined; } const query: Algebra.Operation = context.get(KeysInitQuery.query)!; - if (!FilterNode.findNode(query, Algebra.types.FILTER)) { - return false; + const filterExpression = FilterNode.findNode(query, Algebra.types.FILTER); + if (!filterExpression) { + return undefined; } - return true; + return filterExpression.expression; } public async run(node: ITreeNode, context: IActionContext): Promise> { const filterMap: Map = new Map(); - if (!this.test(node, context)) { + const filterOperation: Algebra.Expression|undefined = this.getFilterExpressionIfTreeNodeHasConstraint(node, context); + + if (!filterOperation) { return new Map(); } - // Extract the filter expression. - const filterOperation: Algebra.Expression = (() => { - const query: Algebra.Operation = context.get(KeysInitQuery.query)!; - return FilterNode.findNode(query, Algebra.types.FILTER).expression; - })(); - // Extract the bgp of the query. const queryBody: RDF.Quad[] = FilterNode.findBgp(context.get(KeysInitQuery.query)!); if (queryBody.length === 0) { @@ -174,7 +171,7 @@ export class FilterNode { * Find the first node of type `nodeType`, if it doesn't exist * it return undefined. * @param {Algebra.Operation} query - the original query - * @param {string} nodeType - the tyoe of node requested + * @param {string} nodeType - the type of node requested * @returns {any} */ private static findNode(query: Algebra.Operation, nodeType: string): any { diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index 6841f9305..797030c7c 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -17,7 +17,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { beforeEach(() => { filterNode = new FilterNode(); }); - describe('test method', () => { + describe('getFilterExpressionIfTreeNodeHasConstraint method', () => { const treeSubject = 'tree'; it('should test when there are relations and a filter operation in the query', () => { const context = new ActionContext({ @@ -32,8 +32,8 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ], }; - const response = filterNode.test(node, context); - expect(response).toBe(true); + const response = filterNode.getFilterExpressionIfTreeNodeHasConstraint(node, context); + expect(response).toBeDefined; }); it('should no test when the TREE relation are undefined', async() => { @@ -44,8 +44,8 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { identifier: treeSubject, }; - const response = filterNode.test(node, context); - expect(response).toBe(false); + const response = filterNode.getFilterExpressionIfTreeNodeHasConstraint(node, context); + expect(response).toBeUndefined; }); it('should not test when there is a filter operation in the query but no TREE relations', async() => { @@ -56,8 +56,8 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { identifier: treeSubject, relation: [], }; - const response = filterNode.test(node, context); - expect(response).toBe(false); + const response = filterNode.getFilterExpressionIfTreeNodeHasConstraint(node, context); + expect(response).toBeUndefined; }); it('should no test when there are no filter operation in the query but a TREE relation', async() => { @@ -72,8 +72,8 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; - const response = filterNode.test(node, context); - expect(response).toBe(false); + const response = filterNode.getFilterExpressionIfTreeNodeHasConstraint(node, context); + expect(response).toBeUndefined; }); }); From 6571d262552c8f53382461dcdfa2e3b615c27ed6 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 15:43:48 +0200 Subject: [PATCH 128/189] Simplification of the procedure to check undefined values --- .../lib/FilterNode.ts | 8 +++++--- .../test/FilterNode-test.ts | 13 ++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 75ef70a0d..f3f26e857 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -17,7 +17,8 @@ const BF = new BindingsFactory(); * the binding are remplace by the [value of TREE relation](https://treecg.github.io/specification/#traversing). */ export class FilterNode { - public getFilterExpressionIfTreeNodeHasConstraint(node: ITreeNode, context: IActionContext): Algebra.Expression|undefined { + public getFilterExpressionIfTreeNodeHasConstraint(node: ITreeNode, + context: IActionContext): Algebra.Expression | undefined { if (!node.relation) { return undefined; } @@ -38,7 +39,8 @@ export class FilterNode { public async run(node: ITreeNode, context: IActionContext): Promise> { const filterMap: Map = new Map(); - const filterOperation: Algebra.Expression|undefined = this.getFilterExpressionIfTreeNodeHasConstraint(node, context); + const filterOperation: Algebra.Expression | undefined = + this.getFilterExpressionIfTreeNodeHasConstraint(node, context); if (!filterOperation) { return new Map(); @@ -55,7 +57,7 @@ export class FilterNode { for (const relation of relations) { // Accept the relation if the relation does't specify a condition. - if (typeof relation.path === 'undefined' || typeof relation.value === 'undefined') { + if (!relation.path || !relation.value) { filterMap.set(relation.node, true); continue; } diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index 797030c7c..2ba6065c0 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -21,7 +21,10 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const treeSubject = 'tree'; it('should test when there are relations and a filter operation in the query', () => { const context = new ActionContext({ - [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, + [KeysInitQuery.query.name]: translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x = 5 || true) + }`), }); const node: ITreeNode = { identifier: treeSubject, @@ -33,7 +36,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }; const response = filterNode.getFilterExpressionIfTreeNodeHasConstraint(node, context); - expect(response).toBeDefined; + expect(response).toBeDefined(); }); it('should no test when the TREE relation are undefined', async() => { @@ -45,7 +48,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }; const response = filterNode.getFilterExpressionIfTreeNodeHasConstraint(node, context); - expect(response).toBeUndefined; + expect(response).toBeUndefined(); }); it('should not test when there is a filter operation in the query but no TREE relations', async() => { @@ -57,7 +60,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [], }; const response = filterNode.getFilterExpressionIfTreeNodeHasConstraint(node, context); - expect(response).toBeUndefined; + expect(response).toBeUndefined(); }); it('should no test when there are no filter operation in the query but a TREE relation', async() => { @@ -73,7 +76,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ], }; const response = filterNode.getFilterExpressionIfTreeNodeHasConstraint(node, context); - expect(response).toBeUndefined; + expect(response).toBeUndefined(); }); }); From 8519feec9fbcde1deb58edbaa3541558582b816b Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 15:58:13 +0200 Subject: [PATCH 129/189] LinkOperator doesn't rely on a global value to have an unique ID. --- .../lib/LinkOperator.ts | 18 ++------ .../lib/solver.ts | 3 +- .../package.json | 7 +-- .../test/LinkOperator-test.ts | 29 ++++-------- yarn.lock | 44 +++++++++++++++++-- 5 files changed, 56 insertions(+), 45 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts b/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts index d62ff3fdb..c06e43421 100644 --- a/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts @@ -1,5 +1,5 @@ +import { v4 as uuidv4 } from 'uuid'; import type { LogicOperator } from './solverInterfaces'; - /** * A class representing a LogicOperation with an unique ID. */ @@ -11,11 +11,7 @@ export class LinkOperator { /** * The unique ID */ - public readonly id: number; - /** - * The next unique ID to provide to a new instance of LinkOperator - */ - private static count = 0; + public readonly id: string; /** * Build a new LinkOperator with an unique ID. @@ -23,8 +19,7 @@ export class LinkOperator { */ public constructor(operator: LogicOperator) { this.operator = operator; - this.id = LinkOperator.count; - LinkOperator.count++; + this.id = uuidv4(); } /** @@ -33,11 +28,4 @@ export class LinkOperator { public toString(): string { return `${this.operator}-${this.id}`; } - - /** - * Reset the count of the unique ID. - */ - public static resetIdCount(): void { - LinkOperator.count = 0; - } } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 143eb5df2..4efaf8086 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -7,7 +7,7 @@ import { MisformatedFilterTermError, UnsupportedDataTypeError, } from './error'; -import { LinkOperator } from './LinkOperator'; +import type { LinkOperator } from './LinkOperator'; import { SolutionDomain } from './SolutionDomain'; import { SolutionRange } from './SolutionRange'; import { @@ -39,7 +39,6 @@ export function isRelationFilterExpressionDomainEmpty({ relation, filterExpressi filterExpression: Algebra.Expression; variable: Variable; }): boolean { - LinkOperator.resetIdCount(); const relationsolverExpressions = convertTreeRelationToSolverExpression(relation, variable); // The relation doesn't have a value or a type, so we accept it if (!relationsolverExpressions) { diff --git a/packages/actor-extract-links-extract-tree/package.json b/packages/actor-extract-links-extract-tree/package.json index 4d6cabeab..9b7160c94 100644 --- a/packages/actor-extract-links-extract-tree/package.json +++ b/packages/actor-extract-links-extract-tree/package.json @@ -31,17 +31,18 @@ "lib/**/*.js" ], "dependencies": { + "@comunica/bindings-factory": "^2.2.0", "@comunica/bus-extract-links": "^0.1.1", "@comunica/context-entries": "^2.6.8", - "@comunica/core": "^2.6.8", - "@comunica/bindings-factory": "^2.2.0", "@comunica/context-entries-link-traversal": "^0.0.1", + "@comunica/core": "^2.6.8", "@comunica/types-link-traversal": "^0.0.1", "rdf-data-factory": "^1.1.1", "rdf-string": "^1.6.1", "sparqlalgebrajs": "^4.0.0", "sparqlee": "^2.1.0", - "ulp": "^1.0.1" + "ulp": "^1.0.1", + "uuid": "^9.0.0" }, "scripts": { "build": "npm run build:ts && npm run build:components", diff --git a/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts index c0420ba2a..41bbea6c8 100644 --- a/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts @@ -3,37 +3,24 @@ import { LogicOperator } from '../lib/solverInterfaces'; describe('LinkOperator', () => { describe('constructor', () => { - it('when constructing multiple linkOperator the id should be incremented', () => { + it('when constructing multiple linkOperator the id should be different', () => { const operators = [ LogicOperator.And, LogicOperator.Not, LogicOperator.Or ]; + const existingId = new Set(); + for (let i = 0; i < 100; i++) { - expect((new LinkOperator(operators[i % operators.length])).id).toBe(i); + const operator = new LinkOperator(operators[i % operators.length]); + expect(existingId.has(operator.id)).toBe(false); + existingId.add(operator.id); } }); }); describe('toString', () => { - beforeAll(() => { - LinkOperator.resetIdCount(); - }); it('should return a string expressing the operator and the id', () => { const operators = [ LogicOperator.And, LogicOperator.Not, LogicOperator.Or ]; for (let i = 0; i < 100; i++) { - const operator = operators[i % operators.length]; - expect((new LinkOperator(operator)).toString()).toBe(`${operator}-${i}`); - } - }); - }); - - describe('resetIdCount', () => { - beforeAll(() => { - LinkOperator.resetIdCount(); - }); - it('should reset the count when the function is called', () => { - const operators = [ LogicOperator.And, LogicOperator.Not, LogicOperator.Or ]; - for (let i = 0; i < 10; i++) { - expect((new LinkOperator(operators[i % operators.length])).id).toBe(i); + const operator = new LinkOperator(operators[i % operators.length]); + expect(operator.toString()).toBe(`${operator.operator}-${operator.id}`); } - LinkOperator.resetIdCount(); - expect((new LinkOperator(operators[0])).id).toBe(0); }); }); }); diff --git a/yarn.lock b/yarn.lock index 7feae9689..ae0ff5659 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1779,7 +1779,7 @@ "@comunica/types" "^2.6.8" asyncjoin "^1.1.1" -"@comunica/actor-rdf-join-inner-multi-bind@2.6.7", "@comunica/actor-rdf-join-inner-multi-bind@^2.5.1", "@comunica/actor-rdf-join-inner-multi-bind@^2.6.8": +"@comunica/actor-rdf-join-inner-multi-bind@^2.5.1": version "2.6.7" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-join-inner-multi-bind/-/actor-rdf-join-inner-multi-bind-2.6.7.tgz#ea599f47e9809d8b4d38b10a64959fcf3f6295e9" integrity sha512-DzqMRO7s7uTwBxXiuQBvHximW9wNofqRrwJhn61LEboFdUMpW8Hfy3PhjT71yfWlB9zrRO3Sn/i1ScxLg5tMUA== @@ -1793,6 +1793,20 @@ asynciterator "^3.8.0" sparqlalgebrajs "^4.0.5" +"@comunica/actor-rdf-join-inner-multi-bind@^2.6.8": + version "2.6.8" + resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-join-inner-multi-bind/-/actor-rdf-join-inner-multi-bind-2.6.8.tgz#9659c11c4e533480a0ed18b13a62e547583ee764" + integrity sha512-J8ddecK4dgrpwi738q1dzVgjU9+6GkOWeN2XetklZwRMzF6llCfxae11560eYjN+KhORZ4Q4ptk1iPfGGz+tFg== + dependencies: + "@comunica/bus-query-operation" "^2.6.8" + "@comunica/bus-rdf-join" "^2.6.8" + "@comunica/bus-rdf-join-entries-sort" "^2.6.8" + "@comunica/context-entries" "^2.6.8" + "@comunica/mediatortype-join-coefficients" "^2.6.8" + "@comunica/types" "^2.6.8" + asynciterator "^3.8.0" + sparqlalgebrajs "^4.0.5" + "@comunica/actor-rdf-join-inner-multi-empty@^2.5.1", "@comunica/actor-rdf-join-inner-multi-empty@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-join-inner-multi-empty/-/actor-rdf-join-inner-multi-empty-2.6.8.tgz#06c3b4ff98e4bc52c3334427b71049b5325294a3" @@ -2156,7 +2170,7 @@ rdf-terms "^1.9.1" sparqlalgebrajs "^4.0.5" -"@comunica/actor-rdf-resolve-quad-pattern-federated@2.6.7", "@comunica/actor-rdf-resolve-quad-pattern-federated@^2.5.1", "@comunica/actor-rdf-resolve-quad-pattern-federated@^2.6.8": +"@comunica/actor-rdf-resolve-quad-pattern-federated@^2.5.1": version "2.6.7" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-resolve-quad-pattern-federated/-/actor-rdf-resolve-quad-pattern-federated-2.6.7.tgz#763a2180b11aefa9055cc712f395301c4391e455" integrity sha512-rldWtvuQpg1yEwaSqKA6SGgt5mNDS90lVL2TMSZVDg4Gk8DnQiqQwiZWGk09KkiJshCZVXnL8TeSlebtlJR2OQ== @@ -2173,6 +2187,23 @@ rdf-terms "^1.9.1" sparqlalgebrajs "^4.0.5" +"@comunica/actor-rdf-resolve-quad-pattern-federated@^2.6.8": + version "2.6.8" + resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-resolve-quad-pattern-federated/-/actor-rdf-resolve-quad-pattern-federated-2.6.8.tgz#00b6a711996872e0702358e21cdedd079af5ed06" + integrity sha512-4ZnJ4WY+DCf7S20U8P4m3w6rwdOgbhziF6bI9njSoAJYRgxQVvNSvYNi2pAARjlwBNp4VvvaCsEbizwyxJGwqw== + dependencies: + "@comunica/bus-query-operation" "^2.6.8" + "@comunica/bus-rdf-resolve-quad-pattern" "^2.6.8" + "@comunica/context-entries" "^2.6.8" + "@comunica/core" "^2.6.8" + "@comunica/data-factory" "^2.5.1" + "@comunica/types" "^2.6.8" + "@rdfjs/types" "*" + asynciterator "^3.8.0" + rdf-data-factory "^1.1.1" + rdf-terms "^1.9.1" + sparqlalgebrajs "^4.0.5" + "@comunica/actor-rdf-resolve-quad-pattern-hypermedia@^2.5.1", "@comunica/actor-rdf-resolve-quad-pattern-hypermedia@^2.6.9": version "2.6.9" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-resolve-quad-pattern-hypermedia/-/actor-rdf-resolve-quad-pattern-hypermedia-2.6.9.tgz#5e11382912a7db948b16709968f9cd628cc972bd" @@ -4128,11 +4159,16 @@ "@types/node" "*" rdf-js "^4.0.2" -"@types/node@*", "@types/node@^14.14.7", "@types/node@^18.0.0", "@types/node@^18.0.3": +"@types/node@*", "@types/node@^18.0.0", "@types/node@^18.0.3": version "18.14.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.6.tgz#ae1973dd2b1eeb1825695bb11ebfb746d27e3e93" integrity sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA== +"@types/node@^14.14.7": + version "14.18.42" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.42.tgz#fa39b2dc8e0eba61bdf51c66502f84e23b66e114" + integrity sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -4208,7 +4244,7 @@ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== -"@types/yargs@17.0.13", "@types/yargs@^17.0.13", "@types/yargs@^17.0.8": +"@types/yargs@^17.0.13", "@types/yargs@^17.0.8": version "17.0.13" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== From 65693a5e61e7072d2c627c93e1f32bc3b13125aa Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 16:05:47 +0200 Subject: [PATCH 130/189] isRelationFilterExpressionDomainEmpty renamed for isBooleanExpressionRelationFilterExpressionSolvable. --- .../lib/FilterNode.ts | 4 +- .../lib/solver.ts | 2 +- .../test/solver-test.ts | 58 +++++++++++-------- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index f3f26e857..d33a465af 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -4,7 +4,7 @@ import type { IActionContext } from '@comunica/types'; import type { ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra, Factory as AlgebraFactory } from 'sparqlalgebrajs'; -import { isRelationFilterExpressionDomainEmpty } from './solver'; +import { isBooleanExpressionRelationFilterExpressionSolvable } from './solver'; import type { Variable } from './solverInterfaces'; const AF = new AlgebraFactory(); @@ -72,7 +72,7 @@ export class FilterNode { let filtered = false; // For all the variable check if one is has a possible solutions. for (const variable of variables) { - filtered = filtered || isRelationFilterExpressionDomainEmpty( + filtered = filtered || isBooleanExpressionRelationFilterExpressionSolvable( { relation, filterExpression: filterOperation, variable }, ); } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 4efaf8086..6e530169f 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -34,7 +34,7 @@ const A_FALSE_EXPRESSION: SolutionRange = new SolutionRange(undefined); * @param {variable} variable - The variable to be resolved. * @returns {boolean} Return true if the domain */ -export function isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable }: { +export function isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable }: { relation: ITreeRelation; filterExpression: Algebra.Expression; variable: Variable; diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 16667a507..b0c820780 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -18,7 +18,7 @@ import { areTypesCompatible, convertTreeRelationToSolverExpression, resolveAFilterTerm, - isRelationFilterExpressionDomainEmpty, + isBooleanExpressionRelationFilterExpressionSolvable, recursifResolve, } from '../lib/solver'; import { SparqlOperandDataTypes, LogicOperator } from '../lib/solverInterfaces'; @@ -692,7 +692,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should return true given a relation and a filter operation where types are not compatible', () => { @@ -714,7 +714,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should return false given a relation and a filter operation where types are not compatible', () => { @@ -736,7 +736,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should return true when the solution range is not valid of the relation', () => { @@ -758,7 +758,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should return true when the equation system is not valid', () => { @@ -800,7 +800,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should return true when there is a solution for the filter expression and the relation', () => { @@ -822,7 +822,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should return false when the filter expression has no solution ', () => { @@ -844,7 +844,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(false); }); it(`should return false when the filter has a possible @@ -867,7 +867,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(false); }); it('should return true when there is a solution for the filter expression with one expression and the relation', @@ -890,7 +890,10 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable( + { relation, filterExpression, variable }, + )) + .toBe(true); }); it('should return false when there is no solution for the filter expression with one expression and the relation', @@ -913,7 +916,10 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionRelationFilterExpressionSolvable( + { relation, filterExpression, variable }, + )) + .toBe(false); }); it(`should return false when there is no solution for the filter @@ -936,7 +942,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(false); }); it(`should return true when there is a solution for @@ -959,7 +965,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should accept the link given that recursifResolve return a SyntaxError', () => { @@ -981,7 +987,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(false); }); it('should accept the link if the data type of the filter is unsupported', () => { @@ -1003,7 +1009,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should accept the link if a filter term is malformated', () => { @@ -1033,7 +1039,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should accept the link with a solvable simple filter', @@ -1056,7 +1062,10 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionRelationFilterExpressionSolvable( + { relation, filterExpression, variable }, + )) + .toBe(false); }); it('should refuse the link with an unsolvable simple filter', @@ -1078,7 +1087,10 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionRelationFilterExpressionSolvable( + { relation, filterExpression, variable }, + )) + .toBe(false); }); it(`should accept the link with a solvable boolean expression with a true boolean statement`, () => { @@ -1100,7 +1112,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); it(`should accept the link with a solvable boolean expression with a false boolean statement`, () => { @@ -1122,7 +1134,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); it(`should accept the link with a solvable simple boolean expression with a true boolean statement`, () => { @@ -1144,7 +1156,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); it(`Should ignore the SPARQL function when prunning links`, () => { @@ -1166,7 +1178,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(false); }); it(`Should ignore the SPARQL function when prunning links with complex filter expression`, () => { @@ -1188,7 +1200,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isRelationFilterExpressionDomainEmpty({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); }); }); From 4f53a5bfa078ad595026552069f765b9708d2503 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 16:15:32 +0200 Subject: [PATCH 131/189] Fix faulty depencies version inside the tree actor. --- packages/actor-extract-links-extract-tree/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/package.json b/packages/actor-extract-links-extract-tree/package.json index 9b7160c94..482917432 100644 --- a/packages/actor-extract-links-extract-tree/package.json +++ b/packages/actor-extract-links-extract-tree/package.json @@ -34,9 +34,9 @@ "@comunica/bindings-factory": "^2.2.0", "@comunica/bus-extract-links": "^0.1.1", "@comunica/context-entries": "^2.6.8", - "@comunica/context-entries-link-traversal": "^0.0.1", + "@comunica/context-entries-link-traversal": "^0.1.1", "@comunica/core": "^2.6.8", - "@comunica/types-link-traversal": "^0.0.1", + "@comunica/types-link-traversal": "^0.1.0", "rdf-data-factory": "^1.1.1", "rdf-string": "^1.6.1", "sparqlalgebrajs": "^4.0.0", From f754ab0e135f9fe2186933836909e1457c8c7ebf Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 16:26:37 +0200 Subject: [PATCH 132/189] Readme updated. --- packages/actor-extract-links-extract-tree/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/README.md b/packages/actor-extract-links-extract-tree/README.md index 7311f8198..e3d971bd7 100644 --- a/packages/actor-extract-links-extract-tree/README.md +++ b/packages/actor-extract-links-extract-tree/README.md @@ -2,7 +2,9 @@ [![npm version](https://badge.fury.io/js/%40comunica%2Factor-extract-links-tree.svg)](https://www.npmjs.com/package/@comunica/actor-extract-links-tree) -A comunica [Extract Links](https://github.com/comunica/comunica-feature-link-traversal/tree/master/packages/bus-extract-links) [TREE](https://treecg.github.io/specification/) Actor. +A comunica [Extract Links Actor](https://github.com/comunica/comunica-feature-link-traversal/tree/master/packages/bus-extract-links) for the [TREE](https://treecg.github.io/specification/). + +There is also a [Guided Linked Traversal Query Processing](https://arxiv.org/abs/2005.02239) option that can be enabled using the `reachabilityCriterionUseSPARQLFilte` flag based on the solvability of the query filter expression and the [`tree:relation`](https://treecg.github.io/specification/#Relation) as explained in the poster article ["How TREE hypermedia can speed up Link Traversal-based Query Processing for SPARQL queries with filters"](https://constraintautomaton.github.io/How-TREE-hypermedia-can-speed-up-Link-Traversal-based-Query-Processing-queries/) This module is part of the [Comunica framework](https://github.com/comunica/comunica), and should only be used by [developers that want to build their own query engine](https://comunica.dev/docs/modify/). @@ -28,7 +30,9 @@ After installing, this package can be added to your engine's configuration as fo ... { "@id": "urn:comunica:default:extract-links/actors#extract-links-tree", - "@type": "ActorExtractLinksTree" + "@type": "ActorExtractLinksTree", + "reachabilityCriterionUseSPARQLFilter": true + } ] } From 6e4d3a259008d7b5aecf5f21e9ad26b093596ad0 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 16:27:55 +0200 Subject: [PATCH 133/189] made the readme multi line. --- packages/actor-extract-links-extract-tree/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/README.md b/packages/actor-extract-links-extract-tree/README.md index e3d971bd7..0bd0666bf 100644 --- a/packages/actor-extract-links-extract-tree/README.md +++ b/packages/actor-extract-links-extract-tree/README.md @@ -4,7 +4,11 @@ A comunica [Extract Links Actor](https://github.com/comunica/comunica-feature-link-traversal/tree/master/packages/bus-extract-links) for the [TREE](https://treecg.github.io/specification/). -There is also a [Guided Linked Traversal Query Processing](https://arxiv.org/abs/2005.02239) option that can be enabled using the `reachabilityCriterionUseSPARQLFilte` flag based on the solvability of the query filter expression and the [`tree:relation`](https://treecg.github.io/specification/#Relation) as explained in the poster article ["How TREE hypermedia can speed up Link Traversal-based Query Processing for SPARQL queries with filters"](https://constraintautomaton.github.io/How-TREE-hypermedia-can-speed-up-Link-Traversal-based-Query-Processing-queries/) +There is also a [Guided Linked Traversal Query Processing](https://arxiv.org/abs/2005.02239) +option that can be enabled using the `reachabilityCriterionUseSPARQLFilte` flag based on the solvability of the query filter expression +and the [`tree:relation`](https://treecg.github.io/specification/#Relation) as explained in +the poster article +["How TREE hypermedia can speed up Link Traversal-based Query Processing for SPARQL queries with filters"](https://constraintautomaton.github.io/How-TREE-hypermedia-can-speed-up-Link-Traversal-based-Query-Processing-queries/) This module is part of the [Comunica framework](https://github.com/comunica/comunica), and should only be used by [developers that want to build their own query engine](https://comunica.dev/docs/modify/). From 34a9b8653da8c9b4906ac76f692879b72b014599 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 17:35:22 +0200 Subject: [PATCH 134/189] A better description of the actor. --- packages/actor-extract-links-extract-tree/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/README.md b/packages/actor-extract-links-extract-tree/README.md index 0bd0666bf..8327e16ed 100644 --- a/packages/actor-extract-links-extract-tree/README.md +++ b/packages/actor-extract-links-extract-tree/README.md @@ -4,10 +4,10 @@ A comunica [Extract Links Actor](https://github.com/comunica/comunica-feature-link-traversal/tree/master/packages/bus-extract-links) for the [TREE](https://treecg.github.io/specification/). -There is also a [Guided Linked Traversal Query Processing](https://arxiv.org/abs/2005.02239) -option that can be enabled using the `reachabilityCriterionUseSPARQLFilte` flag based on the solvability of the query filter expression -and the [`tree:relation`](https://treecg.github.io/specification/#Relation) as explained in -the poster article +The [Guided Linked Traversal Query Processing](https://arxiv.org/abs/2005.02239) +option that can be enabled using the `reachabilityCriterionUseSPARQLFilte` flag. The traversal algorithm will consider the solvability of the query filter expression +combined with the [`tree:relation`](https://treecg.github.io/specification/#Relation) of each data source encountered. +A more thorough explanation is available in the poster article ["How TREE hypermedia can speed up Link Traversal-based Query Processing for SPARQL queries with filters"](https://constraintautomaton.github.io/How-TREE-hypermedia-can-speed-up-Link-Traversal-based-Query-Processing-queries/) This module is part of the [Comunica framework](https://github.com/comunica/comunica), From 47d3937705811902792c1af59745267113698813 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 17:45:24 +0200 Subject: [PATCH 135/189] TREE metadata move with the tree actor. --- .../lib/ActorExtractLinksTree.ts | 2 +- .../lib/FilterNode.ts | 2 +- .../lib/TreeMetadata.ts | 0 .../lib/solver.ts | 2 +- .../lib/treeMetadataExtraction.ts | 2 +- .../test/ActorExtractLinksTree-test.ts | 4 +- .../test/FilterNode-test.ts | 4 +- .../test/solver-test.ts | 4 +- .../test/treeMetadataExtraction-test.ts | 4 +- yarn.lock | 53 ++----------------- 10 files changed, 16 insertions(+), 61 deletions(-) rename packages/{types-link-traversal => actor-extract-links-extract-tree}/lib/TreeMetadata.ts (100%) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index e75df4daa..5866c924f 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -6,11 +6,11 @@ import { ActorExtractLinks } from '@comunica/bus-extract-links'; import type { IActorArgs, IActorTest } from '@comunica/core'; import type { IActionContext } from '@comunica/types'; -import type { ITreeRelationRaw, ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; import { TreeNodes } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { termToString } from 'rdf-string'; import { FilterNode } from './FilterNode'; +import type { ITreeRelationRaw, ITreeRelation, ITreeNode } from './TreeMetadata'; import { buildRelationElement, materializeTreeRelation, addRelationDescription } from './treeMetadataExtraction'; /** diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index d33a465af..3c58d036d 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -1,11 +1,11 @@ import { BindingsFactory } from '@comunica/bindings-factory'; import { KeysInitQuery } from '@comunica/context-entries'; import type { IActionContext } from '@comunica/types'; -import type { ITreeRelation, ITreeNode } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra, Factory as AlgebraFactory } from 'sparqlalgebrajs'; import { isBooleanExpressionRelationFilterExpressionSolvable } from './solver'; import type { Variable } from './solverInterfaces'; +import type { ITreeRelation, ITreeNode } from './TreeMetadata'; const AF = new AlgebraFactory(); const BF = new BindingsFactory(); diff --git a/packages/types-link-traversal/lib/TreeMetadata.ts b/packages/actor-extract-links-extract-tree/lib/TreeMetadata.ts similarity index 100% rename from packages/types-link-traversal/lib/TreeMetadata.ts rename to packages/actor-extract-links-extract-tree/lib/TreeMetadata.ts diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 6e530169f..4fa1c90aa 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -1,5 +1,4 @@ import { SparqlRelationOperator } from '@comunica/types-link-traversal'; -import type { ITreeRelation } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; import { @@ -18,6 +17,7 @@ import type { ISolverExpression, Variable, } from './solverInterfaces'; +import type { ITreeRelation } from './TreeMetadata'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts index 375f3d3b0..53a97f212 100644 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -1,7 +1,7 @@ -import type { ITreeRelation, ITreeRelationRaw, SparqlRelationOperator } from '@comunica/types-link-traversal'; import { TreeNodes, RelationOperatorReversed } from '@comunica/types-link-traversal'; import type * as RDF from 'rdf-js'; import { termToString } from 'rdf-string'; +import type { ITreeRelation, ITreeRelationRaw, SparqlRelationOperator } from './TreeMetadata'; /** * Materialize a raw tree relation using the captured values. diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 9b1587f5f..dba9f0d1c 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -1,11 +1,11 @@ import { KeysRdfResolveQuadPattern, KeysInitQuery } from '@comunica/context-entries'; import { ActionContext, Bus } from '@comunica/core'; -import type { ITreeRelation } from '@comunica/types-link-traversal'; -import { SparqlRelationOperator, TreeNodes } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; +import { SparqlRelationOperator, TreeNodes } from '../lib/TreeMetadata'; +import type { ITreeRelation } from '../lib/TreeMetadata'; const stream = require('streamify-array'); diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index 2ba6065c0..cabf8d01b 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -1,12 +1,12 @@ import { KeysInitQuery } from '@comunica/context-entries'; import { ActionContext } from '@comunica/core'; -import type { ITreeNode } from '@comunica/types-link-traversal'; -import { SparqlRelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; import { FilterNode } from '../lib/FilterNode'; +import { SparqlRelationOperator } from '../lib/TreeMetadata'; +import type { ITreeNode } from '../lib/TreeMetadata'; const DF = new DataFactory(); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index b0c820780..c66a6ef78 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1,5 +1,3 @@ -import type { ITreeRelation } from '@comunica/types-link-traversal'; -import { SparqlRelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; @@ -25,6 +23,8 @@ import { SparqlOperandDataTypes, LogicOperator } from '../lib/solverInterfaces'; import type { ISolverExpression, } from '../lib/solverInterfaces'; +import { SparqlRelationOperator } from '../lib/TreeMetadata'; +import type { ITreeRelation } from '../lib/TreeMetadata'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; diff --git a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts index 80dde7b4a..969820169 100644 --- a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts +++ b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts @@ -1,7 +1,7 @@ -import type { ITreeRelationRaw, ITreeRelation } from '@comunica/types-link-traversal'; -import { TreeNodes, SparqlRelationOperator } from '@comunica/types-link-traversal'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; +import type { ITreeRelationRaw, ITreeRelation } from '../lib/TreeMetadata'; +import { TreeNodes, SparqlRelationOperator } from '../lib/TreeMetadata'; import { buildRelationElement, addRelationDescription, materializeTreeRelation } from '../lib/treeMetadataExtraction'; const DF = new DataFactory(); diff --git a/yarn.lock b/yarn.lock index ae0ff5659..241cf631a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1779,7 +1779,7 @@ "@comunica/types" "^2.6.8" asyncjoin "^1.1.1" -"@comunica/actor-rdf-join-inner-multi-bind@^2.5.1": +"@comunica/actor-rdf-join-inner-multi-bind@2.6.7", "@comunica/actor-rdf-join-inner-multi-bind@^2.5.1", "@comunica/actor-rdf-join-inner-multi-bind@^2.6.8": version "2.6.7" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-join-inner-multi-bind/-/actor-rdf-join-inner-multi-bind-2.6.7.tgz#ea599f47e9809d8b4d38b10a64959fcf3f6295e9" integrity sha512-DzqMRO7s7uTwBxXiuQBvHximW9wNofqRrwJhn61LEboFdUMpW8Hfy3PhjT71yfWlB9zrRO3Sn/i1ScxLg5tMUA== @@ -1793,20 +1793,6 @@ asynciterator "^3.8.0" sparqlalgebrajs "^4.0.5" -"@comunica/actor-rdf-join-inner-multi-bind@^2.6.8": - version "2.6.8" - resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-join-inner-multi-bind/-/actor-rdf-join-inner-multi-bind-2.6.8.tgz#9659c11c4e533480a0ed18b13a62e547583ee764" - integrity sha512-J8ddecK4dgrpwi738q1dzVgjU9+6GkOWeN2XetklZwRMzF6llCfxae11560eYjN+KhORZ4Q4ptk1iPfGGz+tFg== - dependencies: - "@comunica/bus-query-operation" "^2.6.8" - "@comunica/bus-rdf-join" "^2.6.8" - "@comunica/bus-rdf-join-entries-sort" "^2.6.8" - "@comunica/context-entries" "^2.6.8" - "@comunica/mediatortype-join-coefficients" "^2.6.8" - "@comunica/types" "^2.6.8" - asynciterator "^3.8.0" - sparqlalgebrajs "^4.0.5" - "@comunica/actor-rdf-join-inner-multi-empty@^2.5.1", "@comunica/actor-rdf-join-inner-multi-empty@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-join-inner-multi-empty/-/actor-rdf-join-inner-multi-empty-2.6.8.tgz#06c3b4ff98e4bc52c3334427b71049b5325294a3" @@ -2170,7 +2156,7 @@ rdf-terms "^1.9.1" sparqlalgebrajs "^4.0.5" -"@comunica/actor-rdf-resolve-quad-pattern-federated@^2.5.1": +"@comunica/actor-rdf-resolve-quad-pattern-federated@2.6.7", "@comunica/actor-rdf-resolve-quad-pattern-federated@^2.5.1", "@comunica/actor-rdf-resolve-quad-pattern-federated@^2.6.8": version "2.6.7" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-resolve-quad-pattern-federated/-/actor-rdf-resolve-quad-pattern-federated-2.6.7.tgz#763a2180b11aefa9055cc712f395301c4391e455" integrity sha512-rldWtvuQpg1yEwaSqKA6SGgt5mNDS90lVL2TMSZVDg4Gk8DnQiqQwiZWGk09KkiJshCZVXnL8TeSlebtlJR2OQ== @@ -2187,23 +2173,6 @@ rdf-terms "^1.9.1" sparqlalgebrajs "^4.0.5" -"@comunica/actor-rdf-resolve-quad-pattern-federated@^2.6.8": - version "2.6.8" - resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-resolve-quad-pattern-federated/-/actor-rdf-resolve-quad-pattern-federated-2.6.8.tgz#00b6a711996872e0702358e21cdedd079af5ed06" - integrity sha512-4ZnJ4WY+DCf7S20U8P4m3w6rwdOgbhziF6bI9njSoAJYRgxQVvNSvYNi2pAARjlwBNp4VvvaCsEbizwyxJGwqw== - dependencies: - "@comunica/bus-query-operation" "^2.6.8" - "@comunica/bus-rdf-resolve-quad-pattern" "^2.6.8" - "@comunica/context-entries" "^2.6.8" - "@comunica/core" "^2.6.8" - "@comunica/data-factory" "^2.5.1" - "@comunica/types" "^2.6.8" - "@rdfjs/types" "*" - asynciterator "^3.8.0" - rdf-data-factory "^1.1.1" - rdf-terms "^1.9.1" - sparqlalgebrajs "^4.0.5" - "@comunica/actor-rdf-resolve-quad-pattern-hypermedia@^2.5.1", "@comunica/actor-rdf-resolve-quad-pattern-hypermedia@^2.6.9": version "2.6.9" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-resolve-quad-pattern-hypermedia/-/actor-rdf-resolve-quad-pattern-hypermedia-2.6.9.tgz#5e11382912a7db948b16709968f9cd628cc972bd" @@ -2623,12 +2592,6 @@ resolved "https://registry.yarnpkg.com/@comunica/config-query-sparql/-/config-query-sparql-2.6.0.tgz#a52af16bf592e91db2bc148c1b20bfe98a4c76d2" integrity sha512-Ih02KeThu1RWdiV7JfpD8u0lc3hu547EG5pDhe9igGPjU+ijNbahfJJzKrR7LcJrMTGtydEN+z2allIlBKSftA== -"@comunica/context-entries-link-traversal@^0.0.1": - version "0.1.1" - dependencies: - "@comunica/core" "^2.6.8" - "@comunica/types-link-traversal" "^0.1.0" - "@comunica/context-entries@^2.4.0", "@comunica/context-entries@^2.5.1", "@comunica/context-entries@^2.6.7", "@comunica/context-entries@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/context-entries/-/context-entries-2.6.8.tgz#deb86cc01d0b1ebb6c1f2abdbcb91dc0c25bd8f3" @@ -3029,9 +2992,6 @@ "@comunica/core" "^2.6.8" componentsjs "^5.3.2" -"@comunica/types-link-traversal@^0.0.1": - version "0.1.0" - "@comunica/types@^2.6.7", "@comunica/types@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/types/-/types-2.6.8.tgz#366ac851d6bad8c284eeda5bff901cb14415e1f5" @@ -4159,16 +4119,11 @@ "@types/node" "*" rdf-js "^4.0.2" -"@types/node@*", "@types/node@^18.0.0", "@types/node@^18.0.3": +"@types/node@*", "@types/node@^14.14.7", "@types/node@^18.0.0", "@types/node@^18.0.3": version "18.14.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.6.tgz#ae1973dd2b1eeb1825695bb11ebfb746d27e3e93" integrity sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA== -"@types/node@^14.14.7": - version "14.18.42" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.42.tgz#fa39b2dc8e0eba61bdf51c66502f84e23b66e114" - integrity sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg== - "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -4244,7 +4199,7 @@ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== -"@types/yargs@^17.0.13", "@types/yargs@^17.0.8": +"@types/yargs@17.0.13", "@types/yargs@^17.0.13", "@types/yargs@^17.0.8": version "17.0.13" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== From 9110beb518f5ad13e53e70ab7c84a8b186acf42a Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 17:57:14 +0200 Subject: [PATCH 136/189] documentation added of the method of FilterNode. --- .../lib/FilterNode.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 3c58d036d..ea615939d 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -17,6 +17,12 @@ const BF = new BindingsFactory(); * the binding are remplace by the [value of TREE relation](https://treecg.github.io/specification/#traversing). */ export class FilterNode { + /** + * Return the filter expression if the TREE node has relations + * @param {ITreeNode} node - The current TREE node + * @param {IActionContext} context - The context + * @returns {Algebra.Expression | undefined} The filter expression or undefined if the TREE node has no relations + */ public getFilterExpressionIfTreeNodeHasConstraint(node: ITreeNode, context: IActionContext): Algebra.Expression | undefined { if (!node.relation) { @@ -36,11 +42,19 @@ export class FilterNode { return filterExpression.expression; } + /** + * Analyze if the tree:relation(s) of a tree:Node should be followed and return a map + * where if the value of the key representing the URL to follow is true than the link must be followed + * if it is false then it should be ignored. + * @param {ITreeNode} node - The current TREE node + * @param {IActionContext} context - The context + * @returns {Promise>} A map of the indicating if a tree:relation should be follow + */ public async run(node: ITreeNode, context: IActionContext): Promise> { const filterMap: Map = new Map(); const filterOperation: Algebra.Expression | undefined = - this.getFilterExpressionIfTreeNodeHasConstraint(node, context); + this.getFilterExpressionIfTreeNodeHasConstraint(node, context); if (!filterOperation) { return new Map(); From d1ed29b72b3f0494615ba28d05cea942985bf74a Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 18:01:56 +0200 Subject: [PATCH 137/189] faulty depency fix. --- .../lib/ActorExtractLinksTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 5866c924f..c78876e84 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -6,7 +6,7 @@ import { ActorExtractLinks } from '@comunica/bus-extract-links'; import type { IActorArgs, IActorTest } from '@comunica/core'; import type { IActionContext } from '@comunica/types'; -import { TreeNodes } from '@comunica/types-link-traversal'; +import { TreeNodes } from './TreeMetadata'; import type * as RDF from 'rdf-js'; import { termToString } from 'rdf-string'; import { FilterNode } from './FilterNode'; From 938bec9cfc057e244f4755096fed37de219d31e5 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 18:06:08 +0200 Subject: [PATCH 138/189] faulty depency fix. --- packages/actor-extract-links-extract-tree/lib/solver.ts | 2 +- .../actor-extract-links-extract-tree/lib/solverInterfaces.ts | 2 +- .../lib/treeMetadataExtraction.ts | 2 +- packages/types-link-traversal/lib/index.ts | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 4fa1c90aa..c978f6917 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -1,4 +1,4 @@ -import { SparqlRelationOperator } from '@comunica/types-link-traversal'; +import { SparqlRelationOperator } from './TreeMetadata'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; import { diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index 4f420d212..3b9e42c97 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -1,4 +1,4 @@ -import type { SparqlRelationOperator } from '@comunica/types-link-traversal'; +import type { SparqlRelationOperator } from './TreeMetadata'; import type { LinkOperator } from './LinkOperator'; /** * Valid SPARQL data type for operation. diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts index 53a97f212..e57c1693a 100644 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -1,4 +1,4 @@ -import { TreeNodes, RelationOperatorReversed } from '@comunica/types-link-traversal'; +import { TreeNodes, RelationOperatorReversed } from './TreeMetadata'; import type * as RDF from 'rdf-js'; import { termToString } from 'rdf-string'; import type { ITreeRelation, ITreeRelationRaw, SparqlRelationOperator } from './TreeMetadata'; diff --git a/packages/types-link-traversal/lib/index.ts b/packages/types-link-traversal/lib/index.ts index bbf721486..a48973e24 100644 --- a/packages/types-link-traversal/lib/index.ts +++ b/packages/types-link-traversal/lib/index.ts @@ -1,2 +1 @@ -export * from './AnnotateSourcesType'; -export * from './TreeMetadata'; +export * from './AnnotateSourcesType'; \ No newline at end of file From dcdf16b864e689501720e78d5a12885916ed6c8e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 18:10:14 +0200 Subject: [PATCH 139/189] lint-fix --- .../lib/ActorExtractLinksTree.ts | 2 +- packages/actor-extract-links-extract-tree/lib/solver.ts | 2 +- .../actor-extract-links-extract-tree/lib/solverInterfaces.ts | 2 +- .../lib/treeMetadataExtraction.ts | 2 +- packages/types-link-traversal/lib/index.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index c78876e84..0f502afa6 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -6,10 +6,10 @@ import { ActorExtractLinks } from '@comunica/bus-extract-links'; import type { IActorArgs, IActorTest } from '@comunica/core'; import type { IActionContext } from '@comunica/types'; -import { TreeNodes } from './TreeMetadata'; import type * as RDF from 'rdf-js'; import { termToString } from 'rdf-string'; import { FilterNode } from './FilterNode'; +import { TreeNodes } from './TreeMetadata'; import type { ITreeRelationRaw, ITreeRelation, ITreeNode } from './TreeMetadata'; import { buildRelationElement, materializeTreeRelation, addRelationDescription } from './treeMetadataExtraction'; diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index c978f6917..f34879b73 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -1,4 +1,3 @@ -import { SparqlRelationOperator } from './TreeMetadata'; import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; import { @@ -17,6 +16,7 @@ import type { ISolverExpression, Variable, } from './solverInterfaces'; +import { SparqlRelationOperator } from './TreeMetadata'; import type { ITreeRelation } from './TreeMetadata'; const nextUp = require('ulp').nextUp; diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index 3b9e42c97..25a9b919e 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -1,5 +1,5 @@ -import type { SparqlRelationOperator } from './TreeMetadata'; import type { LinkOperator } from './LinkOperator'; +import type { SparqlRelationOperator } from './TreeMetadata'; /** * Valid SPARQL data type for operation. * reference: https://www.w3.org/TR/sparql11-query/#operandDataTypes diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts index e57c1693a..52b1cafe4 100644 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts @@ -1,6 +1,6 @@ -import { TreeNodes, RelationOperatorReversed } from './TreeMetadata'; import type * as RDF from 'rdf-js'; import { termToString } from 'rdf-string'; +import { TreeNodes, RelationOperatorReversed } from './TreeMetadata'; import type { ITreeRelation, ITreeRelationRaw, SparqlRelationOperator } from './TreeMetadata'; /** diff --git a/packages/types-link-traversal/lib/index.ts b/packages/types-link-traversal/lib/index.ts index a48973e24..394061f80 100644 --- a/packages/types-link-traversal/lib/index.ts +++ b/packages/types-link-traversal/lib/index.ts @@ -1 +1 @@ -export * from './AnnotateSourcesType'; \ No newline at end of file +export * from './AnnotateSourcesType'; From 33838abb3292dbcf549926720015fff9563440a6 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 19:57:26 +0200 Subject: [PATCH 140/189] typo in comment fix --- .../actor-extract-links-extract-tree/lib/SolutionDomain.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index a4b0153af..a4e19670f 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -106,15 +106,14 @@ export class SolutionDomain { // of the SolutionRange const resp = SolutionRange.fuseRange(el, currentRange); if (resp.length === 1) { - // If we can we consider the current the fused range the current range - // and we delete the range from the domain has the fused range will be added - // at the end + // If we fuse the range and consider this new range as our current range + // and we delete the old range from the domain as we now have a new range that contained the old currentRange = resp[0]; return false; } return true; }); - // We had the potentialy fused range. + // We add the potentialy fused range. newDomain.domain.push(currentRange); // We keep the domain sorted newDomain.domain.sort(SolutionDomain.sortDomainRangeByLowerBound); From d8102dbe770ba3f50d58d90116db06ccc20f47bf Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 3 Apr 2023 20:10:03 +0200 Subject: [PATCH 141/189] handling of a domain with only an empty range. --- .../lib/SolutionDomain.ts | 3 +- .../test/solver-test.ts | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index a4e19670f..041176e8e 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -1,6 +1,7 @@ import { SolutionRange } from './SolutionRange'; import { LogicOperator } from './solverInterfaces'; +const AN_EMPTY_SOLUTION_RANGE = new SolutionRange(undefined); /** * A class representing the domain of a solution of system of boolean equation. * Every operation return a new object. @@ -31,7 +32,7 @@ export class SolutionDomain { * @returns {boolean} Return true if the domain is empty */ public isDomainEmpty(): boolean { - return this.domain.length === 0; + return this.domain.filter(el => el !== AN_EMPTY_SOLUTION_RANGE).length === 0; } /** diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index c66a6ef78..719ae3ced 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1202,5 +1202,49 @@ describe('solver function', () => { expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); + + it(`should prune a false filter expression`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(false) + }`).input.expression; + + const variable = 'x'; + + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(false); + }); + + it(`should keep a true filter expression`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(true) + }`).input.expression; + + const variable = 'x'; + + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + }); }); }); From 75efc78ba784dd5f7a229973cfd7a46a0f5ed9a1 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 5 Apr 2023 08:51:49 +0200 Subject: [PATCH 142/189] Made so that we have an empty domain if the only element inside it is an empty range. --- .../lib/SolutionDomain.ts | 28 +++++++++++++------ .../lib/solver.ts | 17 +++++++++-- .../test/SolutionDomain-test.ts | 22 +++++++++++++++ .../test/solver-test.ts | 24 ++++++++++++++++ 4 files changed, 81 insertions(+), 10 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 041176e8e..1bac689fb 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -32,7 +32,7 @@ export class SolutionDomain { * @returns {boolean} Return true if the domain is empty */ public isDomainEmpty(): boolean { - return this.domain.filter(el => el !== AN_EMPTY_SOLUTION_RANGE).length === 0; + return this.domain.length === 0; } /** @@ -42,7 +42,9 @@ export class SolutionDomain { */ public static newWithInitialValue(initialRange: SolutionRange): SolutionDomain { const newSolutionDomain = new SolutionDomain(); - newSolutionDomain.domain = [ initialRange ]; + if (!initialRange.isEmpty) { + newSolutionDomain.domain = [ initialRange ]; + } return newSolutionDomain; } @@ -64,30 +66,40 @@ export class SolutionDomain { * @returns {SolutionDomain} - A new SolutionDomain with the operation applied. */ public add({ range, operator }: { range?: SolutionRange; operator: LogicOperator }): SolutionDomain { + let newDomain: SolutionDomain = this.clone(); + switch (operator) { case LogicOperator.And: { if (typeof range === 'undefined') { throw new ReferenceError('range should be defined with "AND" operator'); } - const newDomain = this.addWithAndOperator(range); + newDomain = this.addWithAndOperator(range); newDomain.lastOperation = LogicOperator.And; - return newDomain; + break; } case LogicOperator.Or: { if (typeof range === 'undefined') { throw new ReferenceError('range should be defined with "OR" operator'); } - const newDomain = this.addWithOrOperator(range); + newDomain = this.addWithOrOperator(range); newDomain.lastOperation = LogicOperator.Or; - return newDomain; + break; } case LogicOperator.Not: { - const newDomain = this.notOperation(); + newDomain = this.notOperation(); newDomain.lastOperation = LogicOperator.Not; - return newDomain; + break; } } + // Since we rely on the size of the domain to determine if the domain is empty or not + // and that we have the concept of an empty solution range for the sake of simplicity + // we delete the empty solution range if it is the only element. + if (newDomain.domain.length === 1 && newDomain.domain[0].isEmpty) { + newDomain.domain = []; + } + + return newDomain; } /** diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index f34879b73..6b3989a32 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -79,6 +79,12 @@ export function isBooleanExpressionRelationFilterExpressionSolvable({ relation, throw error; } + // If the filter expression is false on it's own then it's impossible to find anything. + // POSSIBLE OPTIMIZATION: reused domain of the filter domain when appropriate. + if (solutionDomain.isDomainEmpty()) { + return false; + } + // Evaluate the solution domain when adding the relation solutionDomain = solutionDomain.add({ range: relationSolutionRange, operator: LogicOperator.And }); @@ -173,9 +179,10 @@ export function recursifResolve( ) { if (filterExpression.term.value === 'false') { domain = domain.add({ range: A_FALSE_EXPRESSION, operator: logicOperator }); - } - if (filterExpression.term.value === 'true') { + } else if (filterExpression.term.value === 'true') { domain = domain.add({ range: A_TRUE_EXPRESSION, operator: logicOperator }); + } else { + throw new MisformatedFilterTermError(`The term sent is not a boolean but is this value {${filterExpression.term.value}}`); } } else if ( // If it's an array of term then we should be able to create a solver expression @@ -208,6 +215,12 @@ export function recursifResolve( domain = domain.add({ range: solverRange, operator: logicOperator }); } } + } else if ( + // It is a malformed expression + filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && + filterExpression.args.length === 1 + ) { + throw new MisformatedFilterTermError(`The expression should have a variable and a value, but is {${filterExpression.args}}`); } else { let newLogicOperator = LogicOperatorReversed.get(filterExpression.operator); notExpression = newLogicOperator === LogicOperator.Not || notExpression; diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index 294a78173..0b77e5f8b 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -271,6 +271,28 @@ describe('SolutionDomain', () => { aDomain.add({ operator: LogicOperator.Not }); expect(spyAddWithOrOperator).toBeCalledTimes(1); }); + + it('should on any operator return an empty domain if the only solution range is empty', () => { + const an_empty_solution_range = new SolutionRange(undefined); + const operations: [LogicOperator, SolutionRange][] = [ + [ LogicOperator.Or, an_empty_solution_range ], + [ LogicOperator.And, an_empty_solution_range ], + [ LogicOperator.Not, an_empty_solution_range.inverse()[0] ], + ]; + + for (const [ logicOperator, solutionRange ] of operations) { + if (logicOperator !== LogicOperator.Not) { + let domain = new SolutionDomain(); + domain = domain.add({ range: solutionRange, operator: logicOperator }); + expect(domain.get_domain().length).toBe(0); + } else { + let domain = new SolutionDomain(); + domain = domain.addWithOrOperator(solutionRange); + domain = domain.add({ operator: logicOperator }); + expect(domain.get_domain().length).toBe(0); + } + } + }); }); describe('isDomainEmpty', () => { diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 719ae3ced..eb1a9e6a3 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1042,6 +1042,30 @@ describe('solver function', () => { expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); }); + it('should accept the link if a filter term with no args is not a boolean', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression: Algebra.Expression = + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable('x'), + }; + + const variable = 'x'; + + expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + }); + it('should accept the link with a solvable simple filter', () => { const relation: ITreeRelation = { From e258ba97735e4988020c2e666563599d272204a8 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 5 Apr 2023 08:56:05 +0200 Subject: [PATCH 143/189] Typo in comment fix. --- packages/actor-extract-links-extract-tree/lib/solver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 6b3989a32..e1b001777 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -80,7 +80,7 @@ export function isBooleanExpressionRelationFilterExpressionSolvable({ relation, } // If the filter expression is false on it's own then it's impossible to find anything. - // POSSIBLE OPTIMIZATION: reused domain of the filter domain when appropriate. + // POSSIBLE OPTIMIZATION: reused solution domain of the filter when appropriate. if (solutionDomain.isDomainEmpty()) { return false; } From 7c9e31ec7624a989d0202bf221bdc371a0d70ba4 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 5 Apr 2023 09:00:21 +0200 Subject: [PATCH 144/189] function renamed. --- .../lib/FilterNode.ts | 4 +- .../lib/solver.ts | 2 +- .../test/solver-test.ts | 54 +++++++++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index ea615939d..a27cb1ff3 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -3,7 +3,7 @@ import { KeysInitQuery } from '@comunica/context-entries'; import type { IActionContext } from '@comunica/types'; import type * as RDF from 'rdf-js'; import { Algebra, Factory as AlgebraFactory } from 'sparqlalgebrajs'; -import { isBooleanExpressionRelationFilterExpressionSolvable } from './solver'; +import { isBooleanExpressionTreeRelationFilterSolvable } from './solver'; import type { Variable } from './solverInterfaces'; import type { ITreeRelation, ITreeNode } from './TreeMetadata'; @@ -86,7 +86,7 @@ export class FilterNode { let filtered = false; // For all the variable check if one is has a possible solutions. for (const variable of variables) { - filtered = filtered || isBooleanExpressionRelationFilterExpressionSolvable( + filtered = filtered || isBooleanExpressionTreeRelationFilterSolvable( { relation, filterExpression: filterOperation, variable }, ); } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index e1b001777..b59fb9e78 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -34,7 +34,7 @@ const A_FALSE_EXPRESSION: SolutionRange = new SolutionRange(undefined); * @param {variable} variable - The variable to be resolved. * @returns {boolean} Return true if the domain */ -export function isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable }: { +export function isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable }: { relation: ITreeRelation; filterExpression: Algebra.Expression; variable: Variable; diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index eb1a9e6a3..8f60f71ce 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -16,7 +16,7 @@ import { areTypesCompatible, convertTreeRelationToSolverExpression, resolveAFilterTerm, - isBooleanExpressionRelationFilterExpressionSolvable, + isBooleanExpressionTreeRelationFilterSolvable, recursifResolve, } from '../lib/solver'; import { SparqlOperandDataTypes, LogicOperator } from '../lib/solverInterfaces'; @@ -675,7 +675,7 @@ describe('solver function', () => { }); }); - describe('isRelationFilterExpressionDomainEmpty', () => { + describe('isBooleanExpressionTreeRelationFilterSolvable', () => { it('given a relation that is not able to be converted into a solverExpression should return true', () => { const relation: ITreeRelation = { node: 'https://www.example.com', @@ -692,7 +692,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should return true given a relation and a filter operation where types are not compatible', () => { @@ -714,7 +714,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should return false given a relation and a filter operation where types are not compatible', () => { @@ -736,7 +736,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should return true when the solution range is not valid of the relation', () => { @@ -758,7 +758,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should return true when the equation system is not valid', () => { @@ -800,7 +800,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should return true when there is a solution for the filter expression and the relation', () => { @@ -822,7 +822,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should return false when the filter expression has no solution ', () => { @@ -844,7 +844,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(false); }); it(`should return false when the filter has a possible @@ -867,7 +867,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(false); }); it('should return true when there is a solution for the filter expression with one expression and the relation', @@ -890,7 +890,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable( + expect(isBooleanExpressionTreeRelationFilterSolvable( { relation, filterExpression, variable }, )) .toBe(true); @@ -916,7 +916,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable( + expect(isBooleanExpressionTreeRelationFilterSolvable( { relation, filterExpression, variable }, )) .toBe(false); @@ -942,7 +942,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(false); }); it(`should return true when there is a solution for @@ -965,7 +965,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should accept the link given that recursifResolve return a SyntaxError', () => { @@ -987,7 +987,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(false); }); it('should accept the link if the data type of the filter is unsupported', () => { @@ -1009,7 +1009,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should accept the link if a filter term is malformated', () => { @@ -1039,7 +1039,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should accept the link if a filter term with no args is not a boolean', () => { @@ -1063,7 +1063,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it('should accept the link with a solvable simple filter', @@ -1086,7 +1086,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable( + expect(isBooleanExpressionTreeRelationFilterSolvable( { relation, filterExpression, variable }, )) .toBe(false); @@ -1111,7 +1111,7 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable( + expect(isBooleanExpressionTreeRelationFilterSolvable( { relation, filterExpression, variable }, )) .toBe(false); @@ -1136,7 +1136,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it(`should accept the link with a solvable boolean expression with a false boolean statement`, () => { @@ -1158,7 +1158,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it(`should accept the link with a solvable simple boolean expression with a true boolean statement`, () => { @@ -1180,7 +1180,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it(`Should ignore the SPARQL function when prunning links`, () => { @@ -1202,7 +1202,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(false); }); it(`Should ignore the SPARQL function when prunning links with complex filter expression`, () => { @@ -1224,7 +1224,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); it(`should prune a false filter expression`, () => { @@ -1246,7 +1246,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(false); }); it(`should keep a true filter expression`, () => { @@ -1268,7 +1268,7 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionRelationFilterExpressionSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); }); }); From 951929ad3f8e837885ff9075c58ee903a4f1964d Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 5 Apr 2023 09:03:40 +0200 Subject: [PATCH 145/189] Config file renamed. --- .../actors/{tree-no-solver.json => tree-not-guided.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename engines/config-query-sparql-link-traversal/config/extract-links/actors/{tree-no-solver.json => tree-not-guided.json} (100%) diff --git a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree-no-solver.json b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree-not-guided.json similarity index 100% rename from engines/config-query-sparql-link-traversal/config/extract-links/actors/tree-no-solver.json rename to engines/config-query-sparql-link-traversal/config/extract-links/actors/tree-not-guided.json From dbafb299e3cee84ae71f2698fc2600e1095ae73b Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 14 Apr 2023 14:41:29 +0200 Subject: [PATCH 146/189] SolutionRange renamed to SolutionInterval, undefined argument in the constructor changed for an empty array and all the property of the object are now frozen. --- .../lib/SolutionDomain.ts | 35 +- .../{SolutionRange.ts => SolutionInterval.ts} | 57 +-- .../lib/solver.ts | 28 +- .../test/SolutionDomain-test.ts | 108 ++--- .../test/SolutionInterval-test.ts | 425 ++++++++++++++++++ .../test/SolutionRange-test.ts | 423 ----------------- .../test/solver-test.ts | 28 +- 7 files changed, 556 insertions(+), 548 deletions(-) rename packages/actor-extract-links-extract-tree/lib/{SolutionRange.ts => SolutionInterval.ts} (65%) create mode 100644 packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts delete mode 100644 packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 1bac689fb..25b44ead8 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -1,7 +1,6 @@ -import { SolutionRange } from './SolutionRange'; +import { SolutionInterval } from './SolutionInterval'; import { LogicOperator } from './solverInterfaces'; -const AN_EMPTY_SOLUTION_RANGE = new SolutionRange(undefined); /** * A class representing the domain of a solution of system of boolean equation. * Every operation return a new object. @@ -11,7 +10,7 @@ export class SolutionDomain { * The multiple segment of the domain, it is always order by the lower bound * of the SolutionRange. */ - private domain: SolutionRange[] = []; + private domain: SolutionInterval[] = []; /** * The last operation apply to the domain @@ -21,9 +20,9 @@ export class SolutionDomain { /** * Get the multiple segment of the domain. - * @returns {SolutionRange[]} + * @returns {SolutionInterval[]} */ - public get_domain(): SolutionRange[] { + public get_domain(): SolutionInterval[] { return new Array(...this.domain); } @@ -37,10 +36,10 @@ export class SolutionDomain { /** * Create a new SolutionDomain with an inititial value. - * @param {SolutionRange} initialRange + * @param {SolutionInterval} initialRange * @returns {SolutionDomain} */ - public static newWithInitialValue(initialRange: SolutionRange): SolutionDomain { + public static newWithInitialValue(initialRange: SolutionInterval): SolutionDomain { const newSolutionDomain = new SolutionDomain(); if (!initialRange.isEmpty) { newSolutionDomain.domain = [ initialRange ]; @@ -61,11 +60,11 @@ export class SolutionDomain { /** * Modifify the current solution range by applying a logical operation. - * @param {SolutionRange} range - The range of the incoming operation to apply. + * @param {SolutionInterval} range - The range of the incoming operation to apply. * @param {LogicOperator} operator - The logical operation to apply. * @returns {SolutionDomain} - A new SolutionDomain with the operation applied. */ - public add({ range, operator }: { range?: SolutionRange; operator: LogicOperator }): SolutionDomain { + public add({ range, operator }: { range?: SolutionInterval; operator: LogicOperator }): SolutionDomain { let newDomain: SolutionDomain = this.clone(); switch (operator) { @@ -106,10 +105,10 @@ export class SolutionDomain { * Apply an "OR" operator to the current solution domain with the input * solution range. It fuse the SolutionRange if needed to keep the simplest domain possible. * It also keep the domain order. - * @param {SolutionRange} range + * @param {SolutionInterval} range * @returns {SolutionDomain} */ - public addWithOrOperator(range: SolutionRange): SolutionDomain { + public addWithOrOperator(range: SolutionInterval): SolutionDomain { const newDomain = this.clone(); let currentRange = range; // We iterate over all the domain @@ -117,7 +116,7 @@ export class SolutionDomain { // We check if we can fuse the new range with the current range // let's not forget that the domain is sorted by the lowest bound // of the SolutionRange - const resp = SolutionRange.fuseRange(el, currentRange); + const resp = SolutionInterval.fuseRange(el, currentRange); if (resp.length === 1) { // If we fuse the range and consider this new range as our current range // and we delete the old range from the domain as we now have a new range that contained the old @@ -137,10 +136,10 @@ export class SolutionDomain { * Apply an "AND" operator to the current solution domain with the input * solution range. It will keep only the insection of the subdomain with the input * range. It keep the domain ordered. - * @param {SolutionRange} range + * @param {SolutionInterval} range * @returns {SolutionDomain} */ - public addWithAndOperator(range: SolutionRange): SolutionDomain { + public addWithAndOperator(range: SolutionInterval): SolutionDomain { const newDomain = new SolutionDomain(); // If the domain is empty and the last operation was an "AND" @@ -155,7 +154,7 @@ export class SolutionDomain { // Considering the current domain if there is an intersection // add the intersection to the new domain this.domain.forEach(el => { - const intersection = SolutionRange.getIntersection(el, range); + const intersection = SolutionInterval.getIntersection(el, range); if (intersection) { newDomain.domain.push(intersection); } @@ -185,11 +184,11 @@ export class SolutionDomain { /** * Simple sort function to order the domain by the lower bound of SolutionRange. - * @param {SolutionRange} firstRange - * @param {SolutionRange} secondRange + * @param {SolutionInterval} firstRange + * @param {SolutionInterval} secondRange * @returns {number} see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort */ - private static sortDomainRangeByLowerBound(firstRange: SolutionRange, secondRange: SolutionRange): number { + private static sortDomainRangeByLowerBound(firstRange: SolutionInterval, secondRange: SolutionInterval): number { if (firstRange.lower < secondRange.lower) { return -1; } diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts b/packages/actor-extract-links-extract-tree/lib/SolutionInterval.ts similarity index 65% rename from packages/actor-extract-links-extract-tree/lib/SolutionRange.ts rename to packages/actor-extract-links-extract-tree/lib/SolutionInterval.ts index 212a9230d..4f40fa6bf 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionRange.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionInterval.ts @@ -4,7 +4,7 @@ const nextDown = require('ulp').nextDown; * A class representing the range of a solution it contain method to * facilitate operation between subdomain. */ -export class SolutionRange { +export class SolutionInterval { /** * The upper bound of the range. */ @@ -23,8 +23,8 @@ export class SolutionRange { * @param {[number, number]} range - An array where the first memeber is the lower bound of the range * and the second the upper bound */ - public constructor(range: [number, number] | undefined) { - if (range) { + public constructor(range: [number, number] | []) { + if (range.length === 2) { if (range[0] > range[1]) { throw new RangeError('the first element of the range should lower or equal to the second'); } @@ -36,14 +36,18 @@ export class SolutionRange { this.lower = Number.NaN; this.upper = Number.NaN; } + Object.freeze(this); + Object.freeze(this.upper); + Object.freeze(this.lower); + Object.freeze(this.isEmpty); } /** * Check if the two ranges overlap. - * @param {SolutionRange} otherRange + * @param {SolutionInterval} otherRange * @returns {boolean} Return true if the two range overlap. */ - public isOverlapping(otherRange: SolutionRange): boolean { + public isOverlapping(otherRange: SolutionInterval): boolean { if (this.isEmpty || otherRange.isEmpty) { return false; } @@ -68,10 +72,10 @@ export class SolutionRange { /** * Check whether the other range is inside the subject range. - * @param {SolutionRange} otherRange + * @param {SolutionInterval} otherRange * @returns {boolean} Return true if the other range is inside this range. */ - public isInside(otherRange: SolutionRange): boolean { + public isInside(otherRange: SolutionInterval): boolean { if (this.isEmpty || otherRange.isEmpty) { return false; } @@ -80,14 +84,14 @@ export class SolutionRange { /** * Fuse two ranges if they overlap. - * @param {SolutionRange} subjectRange - * @param {SolutionRange} otherRange - * @returns {SolutionRange[]} Return the fused range if they overlap else return the input ranges. + * @param {SolutionInterval} subjectRange + * @param {SolutionInterval} otherRange + * @returns {SolutionInterval[]} Return the fused range if they overlap else return the input ranges. * It also take into consideration if the range is empty. */ - public static fuseRange(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange[] { + public static fuseRange(subjectRange: SolutionInterval, otherRange: SolutionInterval): SolutionInterval[] { if (subjectRange.isEmpty && otherRange.isEmpty) { - return [ new SolutionRange(undefined) ]; + return [ new SolutionInterval([]) ]; } if (subjectRange.isEmpty && !otherRange.isEmpty) { @@ -101,7 +105,7 @@ export class SolutionRange { if (subjectRange.isOverlapping(otherRange)) { const lowest = subjectRange.lower < otherRange.lower ? subjectRange.lower : otherRange.lower; const uppest = subjectRange.upper > otherRange.upper ? subjectRange.upper : otherRange.upper; - return [ new SolutionRange([ lowest, uppest ]) ]; + return [ new SolutionInterval([ lowest, uppest ]) ]; } return [ subjectRange, otherRange ]; } @@ -109,42 +113,43 @@ export class SolutionRange { /** * Inverse the range, in a way that the range become everything that it excluded. Might * 0 or return multiple ranges. - * @returns {SolutionRange[]} The resulting ranges. + * @returns {SolutionInterval[]} The resulting ranges. */ - public inverse(): SolutionRange[] { + public inverse(): SolutionInterval[] { if (this.isEmpty) { - return [ new SolutionRange([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]) ]; + return [ new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]) ]; } if (this.lower === Number.NEGATIVE_INFINITY && this.upper === Number.POSITIVE_INFINITY) { - return [ new SolutionRange(undefined) ]; + return [ new SolutionInterval([]) ]; } if (this.lower === Number.NEGATIVE_INFINITY) { - return [ new SolutionRange([ nextUp(this.upper), Number.POSITIVE_INFINITY ]) ]; + return [ new SolutionInterval([ nextUp(this.upper), Number.POSITIVE_INFINITY ]) ]; } if (this.upper === Number.POSITIVE_INFINITY) { - return [ new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(this.lower) ]) ]; + return [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(this.lower) ]) ]; } return [ - new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(this.lower) ]), - new SolutionRange([ nextUp(this.upper), Number.POSITIVE_INFINITY ]), + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(this.lower) ]), + new SolutionInterval([ nextUp(this.upper), Number.POSITIVE_INFINITY ]), ]; } /** * Get the range that intersect the other range and the subject range. - * @param {SolutionRange} subjectRange - * @param {SolutionRange} otherRange - * @returns {SolutionRange | undefined} Return the intersection if the range overlap otherwise return undefined + * @param {SolutionInterval} subjectRange + * @param {SolutionInterval} otherRange + * @returns {SolutionInterval | undefined} Return the intersection if the range overlap otherwise return undefined */ - public static getIntersection(subjectRange: SolutionRange, otherRange: SolutionRange): SolutionRange | undefined { + public static getIntersection(subjectRange: SolutionInterval, + otherRange: SolutionInterval): SolutionInterval | undefined { if (!subjectRange.isOverlapping(otherRange) || subjectRange.isEmpty || otherRange.isEmpty) { return undefined; } const lower = subjectRange.lower > otherRange.lower ? subjectRange.lower : otherRange.lower; const upper = subjectRange.upper < otherRange.upper ? subjectRange.upper : otherRange.upper; - return new SolutionRange([ lower, upper ]); + return new SolutionInterval([ lower, upper ]); } } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index b59fb9e78..4a728719e 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -7,7 +7,7 @@ import { } from './error'; import type { LinkOperator } from './LinkOperator'; import { SolutionDomain } from './SolutionDomain'; -import { SolutionRange } from './SolutionRange'; +import { SolutionInterval } from './SolutionInterval'; import { SparqlOperandDataTypes, LogicOperatorReversed, LogicOperator, SparqlOperandDataTypesReversed, @@ -22,8 +22,10 @@ import type { ITreeRelation } from './TreeMetadata'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; -const A_TRUE_EXPRESSION: SolutionRange = new SolutionRange([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]); -const A_FALSE_EXPRESSION: SolutionRange = new SolutionRange(undefined); +const A_TRUE_EXPRESSION: SolutionInterval = new SolutionInterval( + [ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ], +); +const A_FALSE_EXPRESSION: SolutionInterval = new SolutionInterval([]); /** * Check if the solution domain of a system of equation compose of the expressions of the filter @@ -45,7 +47,7 @@ export function isBooleanExpressionTreeRelationFilterSolvable({ relation, filter return true; } - const relationSolutionRange = getSolutionRange( + const relationSolutionRange = getSolutionInterval( relationsolverExpressions.valueAsNumber, relationsolverExpressions.operator, ); @@ -195,13 +197,13 @@ export function recursifResolve( const operator = filterOperatorToSparqlRelationOperator(rawOperator); if (operator) { const solverExpression = resolveAFilterTerm(filterExpression, operator, [], variable); - let solverRange: SolutionRange | undefined; + let solverRange: SolutionInterval | undefined; if (solverExpression instanceof MissMatchVariableError) { solverRange = A_TRUE_EXPRESSION; } else if (solverExpression instanceof Error) { throw solverExpression; } else { - solverRange = getSolutionRange(solverExpression.valueAsNumber, solverExpression.operator)!; + solverRange = getSolutionInterval(solverExpression.valueAsNumber, solverExpression.operator)!; } // We can distribute a not expression, so we inverse each statement if (notExpression) { @@ -288,20 +290,20 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { * Find the solution range of a value and operator which is analogue to an expression. * @param {number} value * @param {SparqlRelationOperator} operator - * @returns {SolutionRange | undefined} The solution range associated with the value and the operator. + * @returns {SolutionInterval | undefined} The solution range associated with the value and the operator. */ -export function getSolutionRange(value: number, operator: SparqlRelationOperator): SolutionRange | undefined { +export function getSolutionInterval(value: number, operator: SparqlRelationOperator): SolutionInterval | undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: - return new SolutionRange([ nextUp(value), Number.POSITIVE_INFINITY ]); + return new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]); case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return new SolutionRange([ value, Number.POSITIVE_INFINITY ]); + return new SolutionInterval([ value, Number.POSITIVE_INFINITY ]); case SparqlRelationOperator.EqualThanRelation: - return new SolutionRange([ value, value ]); + return new SolutionInterval([ value, value ]); case SparqlRelationOperator.LessThanRelation: - return new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(value) ]); + return new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]); case SparqlRelationOperator.LessThanOrEqualToRelation: - return new SolutionRange([ Number.NEGATIVE_INFINITY, value ]); + return new SolutionInterval([ Number.NEGATIVE_INFINITY, value ]); default: // Not an operator that is compatible with number. break; diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index 0b77e5f8b..9027465c2 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -1,4 +1,4 @@ -import { SolutionRange } from '../lib//SolutionRange'; +import { SolutionInterval } from '../lib//SolutionInterval'; import { SolutionDomain } from '../lib/SolutionDomain'; import { LogicOperator } from '../lib/solverInterfaces'; @@ -16,7 +16,7 @@ describe('SolutionDomain', () => { describe('newWithInitialValue', () => { it('should create a solution domain with the initial value', () => { - const solutionRange = new SolutionRange([ 0, 1 ]); + const solutionRange = new SolutionInterval([ 0, 1 ]); const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); expect(solutionDomain.get_domain().length).toBe(1); @@ -27,11 +27,11 @@ describe('SolutionDomain', () => { describe('addWithOrOperator', () => { let aDomain: SolutionDomain = new SolutionDomain(); const aRanges = [ - new SolutionRange([ -1, 0 ]), - new SolutionRange([ 1, 5 ]), - new SolutionRange([ 10, 10 ]), - new SolutionRange([ 21, 33 ]), - new SolutionRange([ 60, 70 ]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 33 ]), + new SolutionInterval([ 60, 70 ]), ]; beforeEach(() => { aDomain = new SolutionDomain(); @@ -40,7 +40,7 @@ describe('SolutionDomain', () => { } }); it('given an empty domain should be able to add the subject range', () => { - const range = new SolutionRange([ 0, 1 ]); + const range = new SolutionInterval([ 0, 1 ]); const solutionDomain = new SolutionDomain(); const newDomain = solutionDomain.addWithOrOperator(range); @@ -51,10 +51,10 @@ describe('SolutionDomain', () => { it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { const ranges = [ - new SolutionRange([ 10, 10 ]), - new SolutionRange([ 1, 2 ]), - new SolutionRange([ -1, 0 ]), - new SolutionRange([ 60, 70 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 1, 2 ]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 60, 70 ]), ]; let solutionDomain = new SolutionDomain(); @@ -65,17 +65,17 @@ describe('SolutionDomain', () => { }); const expectedDomain = [ - new SolutionRange([ -1, 0 ]), - new SolutionRange([ 1, 2 ]), - new SolutionRange([ 10, 10 ]), - new SolutionRange([ 60, 70 ]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 2 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 60, 70 ]), ]; expect(solutionDomain.get_domain()).toStrictEqual(expectedDomain); }); it('given a domain should not add a range that is inside another', () => { - const anOverlappingRange = new SolutionRange([ 22, 23 ]); + const anOverlappingRange = new SolutionInterval([ 22, 23 ]); const newDomain = aDomain.addWithOrOperator(anOverlappingRange); expect(newDomain.get_domain().length).toBe(aDomain.get_domain().length); @@ -83,7 +83,7 @@ describe('SolutionDomain', () => { }); it('given a domain should create a single domain if all the domain segment are contain into the new range', () => { - const anOverlappingRange = new SolutionRange([ -100, 100 ]); + const anOverlappingRange = new SolutionInterval([ -100, 100 ]); const newDomain = aDomain.addWithOrOperator(anOverlappingRange); expect(newDomain.get_domain().length).toBe(1); @@ -91,13 +91,13 @@ describe('SolutionDomain', () => { }); it('given a domain should fuse multiple domain segment if the new range overlap with them', () => { - const aNewRange = new SolutionRange([ 1, 23 ]); + const aNewRange = new SolutionInterval([ 1, 23 ]); const newDomain = aDomain.addWithOrOperator(aNewRange); const expectedResultingDomainRange = [ - new SolutionRange([ -1, 0 ]), - new SolutionRange([ 1, 33 ]), - new SolutionRange([ 60, 70 ]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 33 ]), + new SolutionInterval([ 60, 70 ]), ]; expect(newDomain.get_domain().length).toBe(3); @@ -107,12 +107,12 @@ describe('SolutionDomain', () => { describe('notOperation', () => { it('given a domain with one range should return the inverse of the domain', () => { - const solutionRange = new SolutionRange([ 0, 1 ]); + const solutionRange = new SolutionInterval([ 0, 1 ]); const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); const expectedDomain = [ - new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(0) ]), - new SolutionRange([ nextUp(1), Number.POSITIVE_INFINITY ]), + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(0) ]), + new SolutionInterval([ nextUp(1), Number.POSITIVE_INFINITY ]), ]; const newDomain = solutionDomain.notOperation(); @@ -123,12 +123,12 @@ describe('SolutionDomain', () => { it('given a domain with multiple range should return the inverted domain', () => { let domain = new SolutionDomain(); const ranges = [ - new SolutionRange([ 0, 1 ]), - new SolutionRange([ 2, 2 ]), - new SolutionRange([ 44, 55 ]), + new SolutionInterval([ 0, 1 ]), + new SolutionInterval([ 2, 2 ]), + new SolutionInterval([ 44, 55 ]), ]; const expectedDomain = [ - new SolutionRange([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), + new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), ]; for (const r of ranges) { @@ -143,11 +143,11 @@ describe('SolutionDomain', () => { describe('addWithAndOperator', () => { let aDomain: SolutionDomain = new SolutionDomain(); const aRanges = [ - new SolutionRange([ -1, 0 ]), - new SolutionRange([ 1, 5 ]), - new SolutionRange([ 10, 10 ]), - new SolutionRange([ 21, 33 ]), - new SolutionRange([ 60, 70 ]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 33 ]), + new SolutionInterval([ 60, 70 ]), ]; beforeEach(() => { aDomain = new SolutionDomain(); @@ -158,7 +158,7 @@ describe('SolutionDomain', () => { it('should add a range when the domain is empty', () => { const domain = new SolutionDomain(); - const aRange = new SolutionRange([ 0, 1 ]); + const aRange = new SolutionInterval([ 0, 1 ]); const newDomain = domain.addWithAndOperator(aRange); @@ -166,7 +166,7 @@ describe('SolutionDomain', () => { }); it('should return an empty domain if there is no intersection with the new range', () => { - const aRange = new SolutionRange([ -200, -100 ]); + const aRange = new SolutionInterval([ -200, -100 ]); const newDomain = aDomain.addWithAndOperator(aRange); @@ -174,7 +174,7 @@ describe('SolutionDomain', () => { }); it('given a new range that is inside a part of the domain should only return the intersection', () => { - const aRange = new SolutionRange([ 22, 30 ]); + const aRange = new SolutionInterval([ 22, 30 ]); const newDomain = aDomain.addWithAndOperator(aRange); @@ -182,10 +182,10 @@ describe('SolutionDomain', () => { }); it('given a new range that intersect a part of the domain should only return the intersection', () => { - const aRange = new SolutionRange([ 19, 25 ]); + const aRange = new SolutionInterval([ 19, 25 ]); const expectedDomain = [ - new SolutionRange([ 21, 25 ]), + new SolutionInterval([ 21, 25 ]), ]; const newDomain = aDomain.addWithAndOperator(aRange); @@ -194,13 +194,13 @@ describe('SolutionDomain', () => { }); it('given a new range that intersect multiple part of the domain should only return the intersections', () => { - const aRange = new SolutionRange([ -2, 25 ]); + const aRange = new SolutionInterval([ -2, 25 ]); const expectedDomain = [ - new SolutionRange([ -1, 0 ]), - new SolutionRange([ 1, 5 ]), - new SolutionRange([ 10, 10 ]), - new SolutionRange([ 21, 25 ]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 25 ]), ]; const newDomain = aDomain.addWithAndOperator(aRange); @@ -209,8 +209,8 @@ describe('SolutionDomain', () => { }); it('given an empty domain and a last operator and should return an empty domain', () => { - const aRange = new SolutionRange([ -2, 25 ]); - const anotherRangeNonOverlapping = new SolutionRange([ 2_000, 3_000 ]); + const aRange = new SolutionInterval([ -2, 25 ]); + const anotherRangeNonOverlapping = new SolutionInterval([ 2_000, 3_000 ]); let newDomain = aDomain.addWithAndOperator(aRange); newDomain = newDomain.add({ range: anotherRangeNonOverlapping, operator: LogicOperator.And }); @@ -224,7 +224,7 @@ describe('SolutionDomain', () => { describe('add', () => { const aDomain = new SolutionDomain(); - const aRange = new SolutionRange([ 0, 1 ]); + const aRange = new SolutionInterval([ 0, 1 ]); let spyAddWithOrOperator; @@ -234,12 +234,12 @@ describe('SolutionDomain', () => { beforeEach(() => { spyAddWithOrOperator = jest.spyOn(aDomain, 'addWithOrOperator') - .mockImplementation((_range: SolutionRange) => { + .mockImplementation((_range: SolutionInterval) => { return new SolutionDomain(); }); spyAddWithAndOperator = jest.spyOn(aDomain, 'addWithAndOperator') - .mockImplementation((_range: SolutionRange) => { + .mockImplementation((_range: SolutionInterval) => { return new SolutionDomain(); }); @@ -273,8 +273,8 @@ describe('SolutionDomain', () => { }); it('should on any operator return an empty domain if the only solution range is empty', () => { - const an_empty_solution_range = new SolutionRange(undefined); - const operations: [LogicOperator, SolutionRange][] = [ + const an_empty_solution_range = new SolutionInterval([]); + const operations: [LogicOperator, SolutionInterval][] = [ [ LogicOperator.Or, an_empty_solution_range ], [ LogicOperator.And, an_empty_solution_range ], [ LogicOperator.Not, an_empty_solution_range.inverse()[0] ], @@ -303,7 +303,7 @@ describe('SolutionDomain', () => { }); it('should return false when the domain is not empty', () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionRange([ 0, 1 ])); + const domain = SolutionDomain.newWithInitialValue(new SolutionInterval([ 0, 1 ])); expect(domain.isDomainEmpty()).toBe(false); }); @@ -311,9 +311,9 @@ describe('SolutionDomain', () => { describe('clone', () => { it('should return a deep copy of an existing domain', () => { - let domain = SolutionDomain.newWithInitialValue(new SolutionRange([ 0, 1 ])); + let domain = SolutionDomain.newWithInitialValue(new SolutionInterval([ 0, 1 ])); const clonedDomain = domain.clone(); - domain = domain.addWithOrOperator(new SolutionRange([ 100, 200 ])); + domain = domain.addWithOrOperator(new SolutionInterval([ 100, 200 ])); expect(clonedDomain.get_domain()).not.toStrictEqual(domain.get_domain()); }); }); diff --git a/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts new file mode 100644 index 000000000..50b2a7a8b --- /dev/null +++ b/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts @@ -0,0 +1,425 @@ +import { SolutionInterval } from '../lib//SolutionInterval'; + +const nextUp = require('ulp').nextUp; +const nextDown = require('ulp').nextDown; + +describe('SolutionRange', () => { + describe('constructor', () => { + it('should have the right parameters when building', () => { + const aRange: [number, number] = [ 0, 1 ]; + const solutionRange = new SolutionInterval(aRange); + + expect(solutionRange.lower).toBe(aRange[0]); + expect(solutionRange.upper).toBe(aRange[1]); + }); + + it('should not throw an error when the domain is unitary', () => { + const aRange: [number, number] = [ 0, 0 ]; + const solutionRange = new SolutionInterval(aRange); + + expect(solutionRange.lower).toBe(aRange[0]); + expect(solutionRange.upper).toBe(aRange[1]); + }); + + it('should have throw an error when the first element of the range is greater than the second', () => { + const aRange: [number, number] = [ 1, 0 ]; + expect(() => { new SolutionInterval(aRange); }).toThrow(RangeError); + }); + }); + + describe('isOverlaping', () => { + it('should return true when the solution range have the same range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ 0, 100 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); + }); + + it('should return true when the other range start before the subject range and end inside the subject range', + () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ -1, 99 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); + }); + + it('should return true when the other range start before the subject range and end after the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ -1, 101 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); + }); + + it('should return true when the other range start at the subject range and end after the range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ 0, 101 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); + }); + + it('should return true when the other range start inside the current range and end inside the current range', + () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ 1, 50 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); + }); + + it('should return true when the other range start at the end of the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ 100, 500 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); + }); + + it('should return false when the other range is located before the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ -50, -20 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(false); + }); + + it('should return false when the other range is located after the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ 101, 200 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(false); + }); + + it('should return false when the subject range is empty', () => { + const aSolutionInterval = new SolutionInterval([]); + + const aSecondRange: [number, number] = [ 101, 200 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(false); + }); + + it('should return false when the other range is empty and the subject range is not', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondSolutionInterval = new SolutionInterval([]); + + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(false); + }); + + it('should return false when the other range and the subject range are empty', () => { + const aSolutionInterval = new SolutionInterval([]); + + const aSecondSolutionInterval = new SolutionInterval([]); + + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(false); + }); + }); + describe('isInside', () => { + it('should return true when the other range is inside the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ 1, 50 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + expect(aSolutionInterval.isInside(aSecondSolutionInterval)).toBe(true); + }); + + it('should return false when the other range is not inside the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ -1, 50 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + expect(aSolutionInterval.isInside(aSecondSolutionInterval)).toBe(false); + }); + + it('should return false when the other range is empty and not the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondSolutionInterval = new SolutionInterval([]); + + expect(aSolutionInterval.isInside(aSecondSolutionInterval)).toBe(false); + }); + + it('should return false when the subject range is empty and not the other range', () => { + const aSolutionInterval = new SolutionInterval([]); + + const aSecondRange: [number, number] = [ -1, 50 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + expect(aSolutionInterval.isInside(aSecondSolutionInterval)).toBe(false); + }); + + it('should return false when the subject range and the other range are empty', () => { + const aSolutionInterval = new SolutionInterval([]); + + const aSecondSolutionInterval = new SolutionInterval([]); + + expect(aSolutionInterval.isInside(aSecondSolutionInterval)).toBe(false); + }); + }); + + describe('fuseRange', () => { + it('given an non overlapping range return both range should return the correct range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ 101, 200 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + + expect(resp.length).toBe(2); + expect(resp[0]).toStrictEqual(aSolutionInterval); + expect(resp[1]).toStrictEqual(aSecondSolutionInterval); + }); + + it('given an overlapping range where the solution range have the same range should return the correct range', + () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ 0, 100 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(aSolutionInterval); + expect(resp[0]).toStrictEqual(aSecondSolutionInterval); + }); + + it(`given an overlapping range where the other range start before the subject range and end + inside the subject range should return the correct range`, () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ -1, 99 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + + const expectedInterval = new SolutionInterval([ -1, 100 ]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedInterval); + }); + + it(`given an overlapping range where the other range start before the subject range + and end after the subject range should return the correct range`, () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ -1, 101 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + + const expectedInterval = new SolutionInterval([ -1, 101 ]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedInterval); + }); + + it(`given an overlapping range where the other range start at the subject range and + end after the range should return the correct range`, () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ 0, 101 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + + const expectedInterval = new SolutionInterval([ 0, 101 ]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedInterval); + }); + + it(`given an overlapping range where the other range start inside the current range and + end inside the current range should return the correct range`, () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ 1, 50 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + + const expectedInterval = new SolutionInterval([ 0, 100 ]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedInterval); + }); + + it(`given an overlapping range where the other range start at the end + of the subject range should return the correct range`, () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondRange: [number, number] = [ 100, 500 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + + const expectedInterval = new SolutionInterval([ 0, 500 ]); + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedInterval); + }); + + it('given two empty ranges should return an empty range', () => { + const aSolutionInterval = new SolutionInterval([]); + + const aSecondSolutionInterval = new SolutionInterval([]); + + const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(new SolutionInterval([])); + }); + + it('given an empty subject range and an non empty other range should return the second range', () => { + const aSolutionInterval = new SolutionInterval([]); + + const aSecondRange: [number, number] = [ 101, 200 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + + const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(aSecondSolutionInterval); + }); + + it('given an empty other range and an non empty subject range should return the subject range', () => { + const aRange: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(aRange); + + const aSecondSolutionInterval = new SolutionInterval([]); + + const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(aSolutionInterval); + }); + }); + + describe('inverse', () => { + it('given an infinite solution range it should return an empty range', () => { + const aSolutionInterval = new SolutionInterval([ + Number.NEGATIVE_INFINITY, + Number.POSITIVE_INFINITY, + ]); + + const resp = aSolutionInterval.inverse(); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(new SolutionInterval([])); + }); + + it('given a range with an infinite upper bound should return a new range', () => { + const aSolutionInterval = new SolutionInterval([ + 21, + Number.POSITIVE_INFINITY, + ]); + + const expectedInterval = new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(21) ]); + + const resp = aSolutionInterval.inverse(); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedInterval); + }); + + it('given a range with an infinite lower bound should return a new range', () => { + const aSolutionInterval = new SolutionInterval([ + Number.NEGATIVE_INFINITY, + -21, + ]); + + const expectedInterval = new SolutionInterval([ nextUp(-21), Number.POSITIVE_INFINITY ]); + + const resp = aSolutionInterval.inverse(); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(expectedInterval); + }); + + it('given a range that is not unitary and doesn\t have infinite bound should return 2 ranges', () => { + const aSolutionInterval = new SolutionInterval([ + -33, + 21, + ]); + + const expectedInterval = [ + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(-33) ]), + new SolutionInterval([ nextUp(21), Number.POSITIVE_INFINITY ]), + ]; + + const resp = aSolutionInterval.inverse(); + + expect(resp.length).toBe(2); + expect(resp).toStrictEqual(expectedInterval); + }); + + it('given an empty solution range it should return an infinite range', () => { + const aSolutionInterval = new SolutionInterval([]); + + const resp = aSolutionInterval.inverse(); + + expect(resp.length).toBe(1); + expect(resp[0]).toStrictEqual(new SolutionInterval([ + Number.NEGATIVE_INFINITY, + Number.POSITIVE_INFINITY, + ])); + }); + }); + + describe('getIntersection', () => { + it('given two range that doesn\'t overlap should return no intersection', () => { + const aSolutionInterval = new SolutionInterval([ 0, 20 ]); + const aSecondSolutionInterval = new SolutionInterval([ 30, 40 ]); + + expect(SolutionInterval.getIntersection(aSolutionInterval, aSecondSolutionInterval)).toBeUndefined(); + }); + + it('given two range when one is inside the other should return the range at the inside', () => { + const aSolutionInterval = new SolutionInterval([ 0, 20 ]); + const aSecondSolutionInterval = new SolutionInterval([ 5, 10 ]); + + expect(SolutionInterval.getIntersection(aSolutionInterval, aSecondSolutionInterval)) + .toStrictEqual(aSecondSolutionInterval); + }); + + it('given two range when they overlap should return the intersection', () => { + const aSolutionInterval = new SolutionInterval([ 0, 20 ]); + const aSecondSolutionInterval = new SolutionInterval([ 5, 80 ]); + + const expectedIntersection = new SolutionInterval([ 5, 20 ]); + + expect(SolutionInterval.getIntersection(aSolutionInterval, aSecondSolutionInterval)) + .toStrictEqual(expectedIntersection); + }); + }); +}); diff --git a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts deleted file mode 100644 index e83c9d6b4..000000000 --- a/packages/actor-extract-links-extract-tree/test/SolutionRange-test.ts +++ /dev/null @@ -1,423 +0,0 @@ -import { SolutionRange } from '../lib//SolutionRange'; - -const nextUp = require('ulp').nextUp; -const nextDown = require('ulp').nextDown; - -describe('SolutionRange', () => { - describe('constructor', () => { - it('should have the right parameters when building', () => { - const aRange: [number, number] = [ 0, 1 ]; - const solutionRange = new SolutionRange(aRange); - - expect(solutionRange.lower).toBe(aRange[0]); - expect(solutionRange.upper).toBe(aRange[1]); - }); - - it('should not throw an error when the domain is unitary', () => { - const aRange: [number, number] = [ 0, 0 ]; - const solutionRange = new SolutionRange(aRange); - - expect(solutionRange.lower).toBe(aRange[0]); - expect(solutionRange.upper).toBe(aRange[1]); - }); - - it('should have throw an error when the first element of the range is greater than the second', () => { - const aRange: [number, number] = [ 1, 0 ]; - expect(() => { new SolutionRange(aRange); }).toThrow(RangeError); - }); - }); - - describe('isOverlaping', () => { - it('should return true when the solution range have the same range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ 0, 100 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); - }); - - it('should return true when the other range start before the subject range and end inside the subject range', - () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ -1, 99 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); - }); - - it('should return true when the other range start before the subject range and end after the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ -1, 101 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); - }); - - it('should return true when the other range start at the subject range and end after the range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ 0, 101 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); - }); - - it('should return true when the other range start inside the current range and end inside the current range', - () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ 1, 50 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); - }); - - it('should return true when the other range start at the end of the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ 100, 500 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(true); - }); - - it('should return false when the other range is located before the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ -50, -20 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); - }); - - it('should return false when the other range is located after the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ 101, 200 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); - }); - - it('should return false when the subject range is empty', () => { - const aSolutionRange = new SolutionRange(undefined); - - const aSecondRange: [number, number] = [ 101, 200 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); - }); - - it('should return false when the other range is empty and the subject range is not', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondSolutionRange = new SolutionRange(undefined); - - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); - }); - - it('should return false when the other range and the subject range are empty', () => { - const aSolutionRange = new SolutionRange(undefined); - - const aSecondSolutionRange = new SolutionRange(undefined); - - expect(aSolutionRange.isOverlapping(aSecondSolutionRange)).toBe(false); - }); - }); - describe('isInside', () => { - it('should return true when the other range is inside the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ 1, 50 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(true); - }); - - it('should return false when the other range is not inside the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ -1, 50 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(false); - }); - - it('should return false when the other range is empty and not the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondSolutionRange = new SolutionRange(undefined); - - expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(false); - }); - - it('should return false when the subject range is empty and not the other range', () => { - const aSolutionRange = new SolutionRange(undefined); - - const aSecondRange: [number, number] = [ -1, 50 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(false); - }); - - it('should return false when the subject range and the other range are empty', () => { - const aSolutionRange = new SolutionRange(undefined); - - const aSecondSolutionRange = new SolutionRange(undefined); - - expect(aSolutionRange.isInside(aSecondSolutionRange)).toBe(false); - }); - }); - - describe('fuseRange', () => { - it('given an non overlapping range return both range should return the correct range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ 101, 200 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - - expect(resp.length).toBe(2); - expect(resp[0]).toStrictEqual(aSolutionRange); - expect(resp[1]).toStrictEqual(aSecondSolutionRange); - }); - - it('given an overlapping range where the solution range have the same range should return the correct range', - () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ 0, 100 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(aSolutionRange); - expect(resp[0]).toStrictEqual(aSecondSolutionRange); - }); - - it(`given an overlapping range where the other range start before the subject range and end - inside the subject range should return the correct range`, () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ -1, 99 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - - const expectedRange = new SolutionRange([ -1, 100 ]); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); - - it(`given an overlapping range where the other range start before the subject range - and end after the subject range should return the correct range`, () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ -1, 101 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - - const expectedRange = new SolutionRange([ -1, 101 ]); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); - - it(`given an overlapping range where the other range start at the subject range and - end after the range should return the correct range`, () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ 0, 101 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - - const expectedRange = new SolutionRange([ 0, 101 ]); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); - - it(`given an overlapping range where the other range start inside the current range and - end inside the current range should return the correct range`, () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ 1, 50 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - - const expectedRange = new SolutionRange([ 0, 100 ]); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); - - it(`given an overlapping range where the other range start at the end - of the subject range should return the correct range`, () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondRange: [number, number] = [ 100, 500 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - - const expectedRange = new SolutionRange([ 0, 500 ]); - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); - - it('given two empty ranges should return an empty range', () => { - const aSolutionRange = new SolutionRange(undefined); - - const aSecondSolutionRange = new SolutionRange(undefined); - - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(new SolutionRange(undefined)); - }); - - it('given an empty subject range and an non empty other range should return the second range', () => { - const aSolutionRange = new SolutionRange(undefined); - - const aSecondRange: [number, number] = [ 101, 200 ]; - const aSecondSolutionRange = new SolutionRange(aSecondRange); - - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(aSecondSolutionRange); - }); - - it('given an empty other range and an non empty subject range should return the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionRange = new SolutionRange(aRange); - - const aSecondSolutionRange = new SolutionRange(undefined); - - const resp = SolutionRange.fuseRange(aSolutionRange, aSecondSolutionRange); - - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(aSolutionRange); - }); - }); - - describe('inverse', () => { - it('given an infinite solution range it should return an empty range', () => { - const aSolutionRange = new SolutionRange([ - Number.NEGATIVE_INFINITY, - Number.POSITIVE_INFINITY, - ]); - - const resp = aSolutionRange.inverse(); - - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(new SolutionRange(undefined)); - }); - - it('given a range with an infinite upper bound should return a new range', () => { - const aSolutionRange = new SolutionRange([ - 21, - Number.POSITIVE_INFINITY, - ]); - - const expectedRange = new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(21) ]); - - const resp = aSolutionRange.inverse(); - - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); - - it('given a range with an infinite lower bound should return a new range', () => { - const aSolutionRange = new SolutionRange([ - Number.NEGATIVE_INFINITY, - -21, - ]); - - const expectedRange = new SolutionRange([ nextUp(-21), Number.POSITIVE_INFINITY ]); - - const resp = aSolutionRange.inverse(); - - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(expectedRange); - }); - - it('given a range that is not unitary and doesn\t have infinite bound should return 2 ranges', () => { - const aSolutionRange = new SolutionRange([ - -33, - 21, - ]); - - const expectedRange = [ - new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(-33) ]), - new SolutionRange([ nextUp(21), Number.POSITIVE_INFINITY ]), - ]; - - const resp = aSolutionRange.inverse(); - - expect(resp.length).toBe(2); - expect(resp).toStrictEqual(expectedRange); - }); - - it('given an empty solution range it should return an infinite range', () => { - const aSolutionRange = new SolutionRange(undefined); - - const resp = aSolutionRange.inverse(); - - expect(resp.length).toBe(1); - expect(resp[0]).toStrictEqual(new SolutionRange([ - Number.NEGATIVE_INFINITY, - Number.POSITIVE_INFINITY, - ])); - }); - }); - - describe('getIntersection', () => { - it('given two range that doesn\'t overlap should return no intersection', () => { - const aSolutionRange = new SolutionRange([ 0, 20 ]); - const aSecondSolutionRange = new SolutionRange([ 30, 40 ]); - - expect(SolutionRange.getIntersection(aSolutionRange, aSecondSolutionRange)).toBeUndefined(); - }); - - it('given two range when one is inside the other should return the range at the inside', () => { - const aSolutionRange = new SolutionRange([ 0, 20 ]); - const aSecondSolutionRange = new SolutionRange([ 5, 10 ]); - - expect(SolutionRange.getIntersection(aSolutionRange, aSecondSolutionRange)).toStrictEqual(aSecondSolutionRange); - }); - - it('given two range when they overlap should return the intersection', () => { - const aSolutionRange = new SolutionRange([ 0, 20 ]); - const aSecondSolutionRange = new SolutionRange([ 5, 80 ]); - - const expectedIntersection = new SolutionRange([ 5, 20 ]); - - expect(SolutionRange.getIntersection(aSolutionRange, aSecondSolutionRange)).toStrictEqual(expectedIntersection); - }); - }); -}); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 8f60f71ce..29abd7168 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -7,12 +7,12 @@ import { } from '../lib/error'; import { LinkOperator } from '../lib/LinkOperator'; import { SolutionDomain } from '../lib/SolutionDomain'; -import { SolutionRange } from '../lib/SolutionRange'; +import { SolutionInterval } from '../lib/SolutionInterval'; import { filterOperatorToSparqlRelationOperator, isSparqlOperandNumberType, castSparqlRdfTermIntoNumber, - getSolutionRange, + getSolutionInterval, areTypesCompatible, convertTreeRelationToSolverExpression, resolveAFilterTerm, @@ -180,31 +180,31 @@ describe('solver function', () => { describe('getSolutionRange', () => { it('given a boolean compatible RelationOperator and a value should return a valid SolutionRange', () => { const value = -1; - const testTable: [SparqlRelationOperator, SolutionRange][] = [ + const testTable: [SparqlRelationOperator, SolutionInterval][] = [ [ SparqlRelationOperator.GreaterThanRelation, - new SolutionRange([ nextUp(value), Number.POSITIVE_INFINITY ]), + new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]), ], [ SparqlRelationOperator.GreaterThanOrEqualToRelation, - new SolutionRange([ value, Number.POSITIVE_INFINITY ]), + new SolutionInterval([ value, Number.POSITIVE_INFINITY ]), ], [ SparqlRelationOperator.EqualThanRelation, - new SolutionRange([ value, value ]), + new SolutionInterval([ value, value ]), ], [ SparqlRelationOperator.LessThanRelation, - new SolutionRange([ Number.NEGATIVE_INFINITY, nextDown(value) ]), + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]), ], [ SparqlRelationOperator.LessThanOrEqualToRelation, - new SolutionRange([ Number.NEGATIVE_INFINITY, value ]), + new SolutionInterval([ Number.NEGATIVE_INFINITY, value ]), ], ]; for (const [ operator, expectedRange ] of testTable) { - expect(getSolutionRange(value, operator)).toStrictEqual(expectedRange); + expect(getSolutionInterval(value, operator)).toStrictEqual(expectedRange); } }); @@ -212,7 +212,7 @@ describe('solver function', () => { const value = -1; const operator = SparqlRelationOperator.PrefixRelation; - expect(getSolutionRange(value, operator)).toBeUndefined(); + expect(getSolutionInterval(value, operator)).toBeUndefined(); }); }); @@ -607,7 +607,7 @@ describe('solver function', () => { false, ); - const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([ 2, 2 ])); + const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionInterval([ 2, 2 ])); expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); }); @@ -647,7 +647,7 @@ describe('solver function', () => { false, ); - const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange([ 5, Number.POSITIVE_INFINITY ])); + const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionInterval([ 5, Number.POSITIVE_INFINITY ])); expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); }); @@ -667,10 +667,10 @@ describe('solver function', () => { false, ); - let expectedDomain = SolutionDomain.newWithInitialValue(new SolutionRange( + let expectedDomain = SolutionDomain.newWithInitialValue(new SolutionInterval( [ Number.NEGATIVE_INFINITY, nextDown(3) ], )); - expectedDomain = expectedDomain.addWithOrOperator(new SolutionRange([ nextUp(3), Number.POSITIVE_INFINITY ])); + expectedDomain = expectedDomain.addWithOrOperator(new SolutionInterval([ nextUp(3), Number.POSITIVE_INFINITY ])); expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); }); }); From a7e02d5908a0f52c9875e344e7c82e3454abc357 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 14 Apr 2023 14:49:58 +0200 Subject: [PATCH 147/189] made domain fully immutable and get_domain function renamed for getDomain. --- .../lib/SolutionDomain.ts | 11 +++-- .../test/SolutionDomain-test.ts | 48 +++++++++---------- .../test/solver-test.ts | 8 ++-- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 25b44ead8..41f6f7553 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -22,8 +22,8 @@ export class SolutionDomain { * Get the multiple segment of the domain. * @returns {SolutionInterval[]} */ - public get_domain(): SolutionInterval[] { - return new Array(...this.domain); + public getDomain(): SolutionInterval[] { + return this.domain; } /** @@ -44,6 +44,8 @@ export class SolutionDomain { if (!initialRange.isEmpty) { newSolutionDomain.domain = [ initialRange ]; } + Object.freeze(newSolutionDomain); + Object.freeze(newSolutionDomain.domain); return newSolutionDomain; } @@ -97,7 +99,10 @@ export class SolutionDomain { if (newDomain.domain.length === 1 && newDomain.domain[0].isEmpty) { newDomain.domain = []; } - + // When we operate on a domain we don't want it to be mutable in any way. + Object.freeze(newDomain); + Object.freeze(newDomain.domain); + Object.freeze(newDomain.lastOperation); return newDomain; } diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index 9027465c2..666491559 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -10,7 +10,7 @@ describe('SolutionDomain', () => { it('should return an empty solution domain', () => { const solutionDomain = new SolutionDomain(); - expect(solutionDomain.get_domain().length).toBe(0); + expect(solutionDomain.getDomain().length).toBe(0); }); }); @@ -19,8 +19,8 @@ describe('SolutionDomain', () => { const solutionRange = new SolutionInterval([ 0, 1 ]); const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); - expect(solutionDomain.get_domain().length).toBe(1); - expect(solutionDomain.get_domain()[0]).toStrictEqual(solutionRange); + expect(solutionDomain.getDomain().length).toBe(1); + expect(solutionDomain.getDomain()[0]).toStrictEqual(solutionRange); }); }); @@ -45,8 +45,8 @@ describe('SolutionDomain', () => { const newDomain = solutionDomain.addWithOrOperator(range); - expect(newDomain.get_domain().length).toBe(1); - expect(newDomain.get_domain()[0]).toStrictEqual(range); + expect(newDomain.getDomain().length).toBe(1); + expect(newDomain.getDomain()[0]).toStrictEqual(range); }); it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { @@ -61,7 +61,7 @@ describe('SolutionDomain', () => { ranges.forEach((range, idx) => { solutionDomain = solutionDomain.addWithOrOperator(range); - expect(solutionDomain.get_domain().length).toBe(idx + 1); + expect(solutionDomain.getDomain().length).toBe(idx + 1); }); const expectedDomain = [ @@ -71,23 +71,23 @@ describe('SolutionDomain', () => { new SolutionInterval([ 60, 70 ]), ]; - expect(solutionDomain.get_domain()).toStrictEqual(expectedDomain); + expect(solutionDomain.getDomain()).toStrictEqual(expectedDomain); }); it('given a domain should not add a range that is inside another', () => { const anOverlappingRange = new SolutionInterval([ 22, 23 ]); const newDomain = aDomain.addWithOrOperator(anOverlappingRange); - expect(newDomain.get_domain().length).toBe(aDomain.get_domain().length); - expect(newDomain.get_domain()).toStrictEqual(aRanges); + expect(newDomain.getDomain().length).toBe(aDomain.getDomain().length); + expect(newDomain.getDomain()).toStrictEqual(aRanges); }); it('given a domain should create a single domain if all the domain segment are contain into the new range', () => { const anOverlappingRange = new SolutionInterval([ -100, 100 ]); const newDomain = aDomain.addWithOrOperator(anOverlappingRange); - expect(newDomain.get_domain().length).toBe(1); - expect(newDomain.get_domain()).toStrictEqual([ anOverlappingRange ]); + expect(newDomain.getDomain().length).toBe(1); + expect(newDomain.getDomain()).toStrictEqual([ anOverlappingRange ]); }); it('given a domain should fuse multiple domain segment if the new range overlap with them', () => { @@ -100,8 +100,8 @@ describe('SolutionDomain', () => { new SolutionInterval([ 60, 70 ]), ]; - expect(newDomain.get_domain().length).toBe(3); - expect(newDomain.get_domain()).toStrictEqual(expectedResultingDomainRange); + expect(newDomain.getDomain().length).toBe(3); + expect(newDomain.getDomain()).toStrictEqual(expectedResultingDomainRange); }); }); @@ -116,8 +116,8 @@ describe('SolutionDomain', () => { ]; const newDomain = solutionDomain.notOperation(); - expect(newDomain.get_domain().length).toBe(2); - expect(newDomain.get_domain()).toStrictEqual(expectedDomain); + expect(newDomain.getDomain().length).toBe(2); + expect(newDomain.getDomain()).toStrictEqual(expectedDomain); }); it('given a domain with multiple range should return the inverted domain', () => { @@ -136,7 +136,7 @@ describe('SolutionDomain', () => { } domain = domain.notOperation(); - expect(domain.get_domain()).toStrictEqual(expectedDomain); + expect(domain.getDomain()).toStrictEqual(expectedDomain); }); }); @@ -162,7 +162,7 @@ describe('SolutionDomain', () => { const newDomain = domain.addWithAndOperator(aRange); - expect(newDomain.get_domain()).toStrictEqual([ aRange ]); + expect(newDomain.getDomain()).toStrictEqual([ aRange ]); }); it('should return an empty domain if there is no intersection with the new range', () => { @@ -170,7 +170,7 @@ describe('SolutionDomain', () => { const newDomain = aDomain.addWithAndOperator(aRange); - expect(newDomain.get_domain().length).toBe(0); + expect(newDomain.getDomain().length).toBe(0); }); it('given a new range that is inside a part of the domain should only return the intersection', () => { @@ -178,7 +178,7 @@ describe('SolutionDomain', () => { const newDomain = aDomain.addWithAndOperator(aRange); - expect(newDomain.get_domain()).toStrictEqual([ aRange ]); + expect(newDomain.getDomain()).toStrictEqual([ aRange ]); }); it('given a new range that intersect a part of the domain should only return the intersection', () => { @@ -190,7 +190,7 @@ describe('SolutionDomain', () => { const newDomain = aDomain.addWithAndOperator(aRange); - expect(newDomain.get_domain()).toStrictEqual(expectedDomain); + expect(newDomain.getDomain()).toStrictEqual(expectedDomain); }); it('given a new range that intersect multiple part of the domain should only return the intersections', () => { @@ -205,7 +205,7 @@ describe('SolutionDomain', () => { const newDomain = aDomain.addWithAndOperator(aRange); - expect(newDomain.get_domain()).toStrictEqual(expectedDomain); + expect(newDomain.getDomain()).toStrictEqual(expectedDomain); }); it('given an empty domain and a last operator and should return an empty domain', () => { @@ -284,12 +284,12 @@ describe('SolutionDomain', () => { if (logicOperator !== LogicOperator.Not) { let domain = new SolutionDomain(); domain = domain.add({ range: solutionRange, operator: logicOperator }); - expect(domain.get_domain().length).toBe(0); + expect(domain.getDomain().length).toBe(0); } else { let domain = new SolutionDomain(); domain = domain.addWithOrOperator(solutionRange); domain = domain.add({ operator: logicOperator }); - expect(domain.get_domain().length).toBe(0); + expect(domain.getDomain().length).toBe(0); } } }); @@ -314,7 +314,7 @@ describe('SolutionDomain', () => { let domain = SolutionDomain.newWithInitialValue(new SolutionInterval([ 0, 1 ])); const clonedDomain = domain.clone(); domain = domain.addWithOrOperator(new SolutionInterval([ 100, 200 ])); - expect(clonedDomain.get_domain()).not.toStrictEqual(domain.get_domain()); + expect(clonedDomain.getDomain()).not.toStrictEqual(domain.getDomain()); }); }); }); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 29abd7168..d337d401e 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -609,7 +609,7 @@ describe('solver function', () => { const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionInterval([ 2, 2 ])); - expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); it(`given an algebra expression with two logicals operators @@ -629,7 +629,7 @@ describe('solver function', () => { const expectedDomain = new SolutionDomain(); - expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); it(`given an algebra expression with two logicals operators that are negated @@ -649,7 +649,7 @@ describe('solver function', () => { const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionInterval([ 5, Number.POSITIVE_INFINITY ])); - expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); it(`given an algebra expression with three logicals operators where the priority of operation should start with the not operator than @@ -671,7 +671,7 @@ describe('solver function', () => { [ Number.NEGATIVE_INFINITY, nextDown(3) ], )); expectedDomain = expectedDomain.addWithOrOperator(new SolutionInterval([ nextUp(3), Number.POSITIVE_INFINITY ])); - expect(resp.get_domain()).toStrictEqual(expectedDomain.get_domain()); + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); }); From 6515831964aa8eec990ad188e5fbd3d867860fd5 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 14 Apr 2023 14:54:23 +0200 Subject: [PATCH 148/189] made the last operator field of SolutionDomain frozen. --- packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 41f6f7553..cb7cc8c6e 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -46,6 +46,7 @@ export class SolutionDomain { } Object.freeze(newSolutionDomain); Object.freeze(newSolutionDomain.domain); + Object.freeze(newSolutionDomain.lastOperation); return newSolutionDomain; } From c7339eaaab4dc39426e1296606df22d886242082 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 14 Apr 2023 15:01:21 +0200 Subject: [PATCH 149/189] LinkOperator class removed. --- .../lib/LinkOperator.ts | 31 ------------- .../lib/solver.ts | 7 +-- .../lib/solverInterfaces.ts | 5 -- .../test/LinkOperator-test.ts | 26 ----------- .../test/solver-test.ts | 46 +++++++------------ 5 files changed, 17 insertions(+), 98 deletions(-) delete mode 100644 packages/actor-extract-links-extract-tree/lib/LinkOperator.ts delete mode 100644 packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts diff --git a/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts b/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts deleted file mode 100644 index c06e43421..000000000 --- a/packages/actor-extract-links-extract-tree/lib/LinkOperator.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; -import type { LogicOperator } from './solverInterfaces'; -/** - * A class representing a LogicOperation with an unique ID. - */ -export class LinkOperator { - /** - * The logic operator - */ - public readonly operator: LogicOperator; - /** - * The unique ID - */ - public readonly id: string; - - /** - * Build a new LinkOperator with an unique ID. - * @param operator - */ - public constructor(operator: LogicOperator) { - this.operator = operator; - this.id = uuidv4(); - } - - /** - * @returns {string} string representation of this LogicOperator. - */ - public toString(): string { - return `${this.operator}-${this.id}`; - } -} diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 4a728719e..c1289e8e2 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -5,7 +5,6 @@ import { MisformatedFilterTermError, UnsupportedDataTypeError, } from './error'; -import type { LinkOperator } from './LinkOperator'; import { SolutionDomain } from './SolutionDomain'; import { SolutionInterval } from './SolutionInterval'; import { @@ -98,13 +97,11 @@ export function isBooleanExpressionTreeRelationFilterSolvable({ relation, filter * From an Algebra expression return an solver expression if possible * @param {Algebra.Expression} expression - Algebra expression containing the a variable and a litteral. * @param {SparqlRelationOperator} operator - The SPARQL operator defining the expression. - * @param {LinkOperator[]} linksOperator - The logical operator prior to this expression. * @param {Variable} variable - The variable the expression should have to be part of a system of equation. * @returns {ISolverExpression | undefined} Return a solver expression if possible */ export function resolveAFilterTerm(expression: Algebra.Expression, operator: SparqlRelationOperator, - linksOperator: LinkOperator[], variable: Variable): ISolverExpression | Error { let rawValue: string | undefined; @@ -141,7 +138,6 @@ export function resolveAFilterTerm(expression: Algebra.Expression, valueType, valueAsNumber, operator, - chainOperator: linksOperator, }; } const missingTerm = []; @@ -196,7 +192,7 @@ export function recursifResolve( const rawOperator = filterExpression.operator; const operator = filterOperatorToSparqlRelationOperator(rawOperator); if (operator) { - const solverExpression = resolveAFilterTerm(filterExpression, operator, [], variable); + const solverExpression = resolveAFilterTerm(filterExpression, operator, variable); let solverRange: SolutionInterval | undefined; if (solverExpression instanceof MissMatchVariableError) { solverRange = A_TRUE_EXPRESSION; @@ -261,7 +257,6 @@ export function convertTreeRelationToSolverExpression(relation: ITreeRelation, rawValue: relation.value.value, valueType, valueAsNumber: valueNumber, - chainOperator: [], operator: relation.type, }; diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index 25a9b919e..e72eb2f95 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -1,4 +1,3 @@ -import type { LinkOperator } from './LinkOperator'; import type { SparqlRelationOperator } from './TreeMetadata'; /** * Valid SPARQL data type for operation. @@ -77,9 +76,5 @@ export interface ISolverExpression { * The operator binding the value and the variable. */ operator: SparqlRelationOperator; - /** - * The chain of logical operator attached to the expression. - */ - chainOperator: LinkOperator[]; } diff --git a/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts deleted file mode 100644 index 41bbea6c8..000000000 --- a/packages/actor-extract-links-extract-tree/test/LinkOperator-test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { LinkOperator } from '../lib/LinkOperator'; -import { LogicOperator } from '../lib/solverInterfaces'; - -describe('LinkOperator', () => { - describe('constructor', () => { - it('when constructing multiple linkOperator the id should be different', () => { - const operators = [ LogicOperator.And, LogicOperator.Not, LogicOperator.Or ]; - const existingId = new Set(); - - for (let i = 0; i < 100; i++) { - const operator = new LinkOperator(operators[i % operators.length]); - expect(existingId.has(operator.id)).toBe(false); - existingId.add(operator.id); - } - }); - }); - describe('toString', () => { - it('should return a string expressing the operator and the id', () => { - const operators = [ LogicOperator.And, LogicOperator.Not, LogicOperator.Or ]; - for (let i = 0; i < 100; i++) { - const operator = new LinkOperator(operators[i % operators.length]); - expect(operator.toString()).toBe(`${operator.operator}-${operator.id}`); - } - }); - }); -}); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index d337d401e..d86c28181 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -5,7 +5,6 @@ import { MisformatedFilterTermError, UnsupportedDataTypeError, } from '../lib/error'; -import { LinkOperator } from '../lib/LinkOperator'; import { SolutionDomain } from '../lib/SolutionDomain'; import { SolutionInterval } from '../lib/SolutionInterval'; import { @@ -225,7 +224,6 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Boolean, valueAsNumber: 1, operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [], }, { variable: 'a', @@ -233,7 +231,6 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Boolean, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [], }, { variable: 'a', @@ -241,7 +238,6 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Boolean, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [], }, ]; @@ -256,7 +252,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Int, valueAsNumber: 1, operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [], + }, { variable: 'a', @@ -264,7 +260,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.NonNegativeInteger, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [], + }, { variable: 'a', @@ -272,7 +268,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Decimal, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [], + }, ]; @@ -287,7 +283,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Boolean, valueAsNumber: 1, operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [], + }, { variable: 'a', @@ -295,7 +291,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Boolean, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [], + }, { variable: 'a', @@ -303,7 +299,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Byte, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [], + }, ]; @@ -318,7 +314,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.UnsignedInt, valueAsNumber: 1, operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [], + }, { variable: 'a', @@ -326,7 +322,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Float, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [], + }, { variable: 'a', @@ -334,7 +330,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Byte, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - chainOperator: [], + }, ]; @@ -361,7 +357,7 @@ describe('solver function', () => { rawValue: '5', valueType: SparqlOperandDataTypes.Integer, valueAsNumber: 5, - chainOperator: [], + operator: SparqlRelationOperator.EqualThanRelation, }; @@ -460,7 +456,6 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; const variable = 'x'; const expectedSolverExpression: ISolverExpression = { variable, @@ -468,10 +463,9 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Integer, valueAsNumber: 6, operator, - chainOperator: linksOperator, }; - const resp = resolveAFilterTerm(expression, operator, linksOperator, variable); + const resp = resolveAFilterTerm(expression, operator, variable); if (resp) { expect(resp).toStrictEqual(expectedSolverExpression); @@ -494,9 +488,8 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeInstanceOf(MisformatedFilterTermError); + expect(resolveAFilterTerm(expression, operator, 'x')).toBeInstanceOf(MisformatedFilterTermError); }); it('given an algebra expression without a litteral than should return an misformated error', () => { @@ -514,9 +507,8 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) + expect(resolveAFilterTerm(expression, operator, variable)) .toBeInstanceOf(MisformatedFilterTermError); }); @@ -528,9 +520,8 @@ describe('solver function', () => { args: [], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, 'x')).toBeInstanceOf(MisformatedFilterTermError); + expect(resolveAFilterTerm(expression, operator, 'x')).toBeInstanceOf(MisformatedFilterTermError); }); it(`given an algebra expression with a litteral containing an invalid datatype than @@ -555,12 +546,8 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ - new LinkOperator(LogicOperator.And), - new LinkOperator(LogicOperator.Or), - ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) + expect(resolveAFilterTerm(expression, operator, variable)) .toBeInstanceOf(UnsupportedDataTypeError); }); @@ -585,9 +572,8 @@ describe('solver function', () => { ], }; const operator = SparqlRelationOperator.EqualThanRelation; - const linksOperator: LinkOperator[] = [ new LinkOperator(LogicOperator.And), new LinkOperator(LogicOperator.Or) ]; - expect(resolveAFilterTerm(expression, operator, linksOperator, variable)) + expect(resolveAFilterTerm(expression, operator, variable)) .toBeInstanceOf(UnsupportedDataTypeError); }); }); From b4a435786b9058020d1d2ab38909604ea23ed1bf Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 14 Apr 2023 17:06:26 +0200 Subject: [PATCH 150/189] Util library used to find the relevant variables. --- .../lib/FilterNode.ts | 94 +- .../test/FilterNode-test.ts | 891 ++---------------- .../test/solver-test.ts | 22 +- 3 files changed, 107 insertions(+), 900 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index a27cb1ff3..2f2cdc339 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -2,7 +2,7 @@ import { BindingsFactory } from '@comunica/bindings-factory'; import { KeysInitQuery } from '@comunica/context-entries'; import type { IActionContext } from '@comunica/types'; import type * as RDF from 'rdf-js'; -import { Algebra, Factory as AlgebraFactory } from 'sparqlalgebrajs'; +import { Algebra, Factory as AlgebraFactory, Util } from 'sparqlalgebrajs'; import { isBooleanExpressionTreeRelationFilterSolvable } from './solver'; import type { Variable } from './solverInterfaces'; import type { ITreeRelation, ITreeNode } from './TreeMetadata'; @@ -61,10 +61,7 @@ export class FilterNode { } // Extract the bgp of the query. - const queryBody: RDF.Quad[] = FilterNode.findBgp(context.get(KeysInitQuery.query)!); - if (queryBody.length === 0) { - return new Map(); - } + const queryBody: Algebra.Operation = context.get(KeysInitQuery.query)!; // Capture the relation from the function argument. const relations: ITreeRelation[] = node.relation!; @@ -96,93 +93,32 @@ export class FilterNode { return filterMap; } + + /** * Find the variables from the BGP that match the predicate defined by the TREE:path from a TREE relation. * The subject can be anyting. - * @param {RDF.Quad[]} queryBody - the body of the query + * @param {Algebra.Operation} queryBody - the body of the query * @param {string} path - TREE path * @returns {Variable[]} the variables of the Quad objects that contain the TREE path as predicate */ - private static findRelevantVariableFromBgp(queryBody: RDF.Quad[], path: string): Variable[] { + private static findRelevantVariableFromBgp(queryBody: Algebra.Operation, path: string): Variable[] { const resp: Variable[] = []; - for (const quad of queryBody) { + const addVariable = (quad:any) =>{ if (quad.predicate.value === path && quad.object.termType === 'Variable') { resp.push(quad.object.value); } - } + return true; + }; + + Util.recurseOperation(queryBody,{ + [Algebra.types.PATH]: addVariable, + [Algebra.types.PATTERN]: addVariable + + }); return resp; } - /** - * Find the bgp of the original query of the user. - * @param {Algebra.Operation} query - the original query - * @returns { RDF.Quad[]} the bgp of the query - */ - private static findBgp(query: Algebra.Operation): RDF.Quad[] { - let currentNode: any = query; - let bgp: RDF.Quad[] = []; - do { - if (currentNode.type === Algebra.types.JOIN) { - const currentBgp = this.formatBgp(currentNode.input); - bgp = this.appendBgp(bgp, currentBgp); - } else if (currentNode.type === Algebra.types.CONSTRUCT && - 'template' in currentNode) { - // When it's a contruct query the where state - const currentBgp = this.formatBgp(currentNode.template); - bgp = this.appendBgp(bgp, currentBgp); - } - - if ('input' in currentNode) { - currentNode = currentNode.input; - } - - if (currentNode.patterns) { - return currentNode.patterns; - } - // If the node is an array - if (Array.isArray(currentNode)) { - for (const node of currentNode) { - if ('input' in node) { - currentNode = node.input; - break; - } - } - } - } while ('input' in currentNode); - return bgp; - } - - /** - * Format the section of the algebra graph contain a part of the bgp into an array of quad. - * @param {any} joins - the join operation containing a part of the bgp - * @returns {RDF.Quad[]} the bgp in the form of an array of quad - */ - private static formatBgp(joins: any): RDF.Quad[] { - const bgp = []; - if (joins.length === 0) { - return []; - } - for (const join of joins) { - if (!('input' in join)) { - bgp.push(join); - } - } - return bgp; - } - - /** - * Append the bgp of the query. - * @param {RDF.Quad[]} bgp - the whole bgp - * @param {RDF.Quad[]} currentBgp - the bgp collected in the current node - * @returns {RDF.Quad[]} the bgp updated - */ - private static appendBgp(bgp: RDF.Quad[], currentBgp: RDF.Quad[]): RDF.Quad[] { - if (Array.isArray(currentBgp)) { - bgp = bgp.concat(currentBgp); - } - return bgp; - } - /** * Find the first node of type `nodeType`, if it doesn't exist * it return undefined. diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index cabf8d01b..b8016d568 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -89,7 +89,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'ex:path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -126,7 +126,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'ex:path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -135,50 +135,13 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; - const bgp: RDF.Quad[] = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '88', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - }; + + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER(?o=88) + } + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -198,7 +161,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'ex:path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -207,50 +170,12 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; - const bgp: RDF.Quad[] = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '88', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - }; + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:superPath ?o. + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -263,50 +188,12 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); it('should return an empty map when there is no relation', async() => { - const bgp: RDF.Quad[] = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '88', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - }; + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER(?o=88) + } + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -327,7 +214,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'ex:path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -339,83 +226,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { const bgp: RDF.Quad[] = [ DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:superPath'), DF.variable('o')), ]; - const filterExpression = { - expressionType: 'operator', - operator: '&&', - type: 'expression', - args: [ - { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '88', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }, - { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }, - ], - }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - }; + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER(?o=5 && ?o<=5 ) + } + `, { prefixes: { ex: 'http://example.com#' }}); + const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -436,7 +254,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'ex:path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -450,50 +268,12 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; - const bgp: RDF.Quad[] = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - }; + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -513,7 +293,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'ex:path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -522,50 +302,12 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; - const bgp: RDF.Quad[] = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'p', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - }; + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER(?p=88) + } + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -585,7 +327,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'ex:path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -594,83 +336,12 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; - const bgp: RDF.Quad[] = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '&&', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [{ - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }, - { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [{ - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'p', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }, - ], - }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - }; + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER((?p=88 && ?p>3) || true) + } + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -690,7 +361,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'ex:path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -699,93 +370,12 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; - const bgp: RDF.Quad[] = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '&&', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [{ - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }, - { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [{ - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }, - ], - }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - }; + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER(5=5) + } + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -797,139 +387,6 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it('should return an empty filter map if the bgp if empty', async() => { - const treeSubject = 'tree'; - - const node: ITreeNode = { - identifier: treeSubject, - relation: [ - { - node: 'http://bar.com', - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - type: SparqlRelationOperator.EqualThanRelation, - }, - ], - }; - const bgp: RDF.Quad[] = []; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - }; - const context = new ActionContext({ - [KeysInitQuery.query.name]: query, - }); - - const result = await filterNode.run(node, context); - - expect(result).toStrictEqual( - new Map(), - ); - }); - - it('should return an empty filter map if there is no bgp', async() => { - const treeSubject = 'tree'; - - const node: ITreeNode = { - identifier: treeSubject, - relation: [ - { - node: 'http://bar.com', - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - type: SparqlRelationOperator.EqualThanRelation, - }, - ], - }; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }; - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: {}, - }, - }; - const context = new ActionContext({ - [KeysInitQuery.query.name]: query, - }); - - const result = await filterNode.run(node, context); - - expect(result).toStrictEqual( - new Map(), - ); - }); it('should accept the relation when the filter respect the relation with a construct query', async() => { const treeSubject = 'tree'; @@ -939,7 +396,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'ex:path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -949,55 +406,14 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ], }; - const bgp = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), - DF.quad(DF.namedNode('ex:bar'), DF.namedNode('ex:p2'), DF.namedNode('ex:o2')), - DF.quad(DF.namedNode('ex:too'), DF.namedNode('ex:p3'), DF.namedNode('ex:o3')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }; - - const query = { - type: Algebra.types.CONSTRUCT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - template: bgp, - }; + const query = translate(` + CONSTRUCT { + + } WHERE{ + ex:foo ex:path ?o. + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, @@ -1018,7 +434,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'ex:path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -1028,162 +444,17 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ], }; - const query = { - type: 'project', - input: { - type: 'filter', - input: { - type: 'join', - input: [ - { - type: 'project', - input: { - type: 'project', - input: { - type: 'join', - input: [ - { - termType: 'Quad', - value: '', - identifier: { - termType: 'Variable', - value: 's', - }, - predicate: { - termType: 'NamedNode', - value: 'http://semweb.mmlab.be/ns/linkedconnections#departureTime', - }, - object: { - termType: 'Variable', - value: 'date', - }, - graph: { - termType: 'DefaultGraph', - value: '', - }, - type: 'pattern', - }, - ], - }, - variables: [ - { - termType: 'Variable', - value: 's', - }, - ], - }, - variables: [ - { - termType: 'Variable', - value: 's', - }, - ], - }, - { - termType: 'Quad', - value: '', - identifier: { - termType: 'Variable', - value: 's', - }, - predicate: { - termType: 'NamedNode', - value: 'http://semweb.mmlab.be/ns/linkedconnections#departureStop', - }, - object: { - termType: 'Variable', - value: 'o', - }, - graph: { - termType: 'DefaultGraph', - value: '', - }, - type: 'pattern', - }, - ], - }, - expression: { - type: 'expression', - expressionType: 'operator', - operator: '&&', - args: [ - { - type: 'expression', - expressionType: 'operator', - operator: '>=', - args: [ - { - type: 'expression', - expressionType: 'term', - term: { - termType: 'Variable', - value: 'date', - }, - }, - { - type: 'expression', - expressionType: 'term', - term: { - termType: 'Literal', - value: '2022-11-08T08:00:00.000Z', - language: '', - datatype: { - termType: 'NamedNode', - value: 'http://www.w3.org/2001/XMLSchema#dateTime', - }, - }, - }, - ], - }, - { - type: 'expression', - expressionType: 'operator', - operator: '=', - args: [ - { - type: 'expression', - expressionType: 'operator', - operator: 'str', - args: [ - { - type: 'expression', - expressionType: 'term', - term: { - termType: 'Variable', - value: 'o', - }, - }, - ], - }, - { - type: 'expression', - expressionType: 'term', - term: { - termType: 'Literal', - value: 'http://irail.be/stations/NMBS/008812146', - language: '', - datatype: { - termType: 'NamedNode', - value: 'http://www.w3.org/2001/XMLSchema#string', - }, - }, - }, - ], - }, - ], - }, - }, - variables: [ - { - termType: 'Variable', - value: 'o', - }, - { - termType: 'Variable', - value: 's', - }, - ], - }; + const query = translate(` + SELECT * WHERE { + ?o ex:resp ?v . + { + SELECT ?o WHERE { + ex:foo ex:path ?o. + } + } + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, @@ -1204,7 +475,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'ex:path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -1219,7 +490,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ex:foo ex:path ?o. ex:foo ex:p ex:o. ex:foo ex:p2 ?x. - FILTER(?o>2|| ?x=4 || (?x<3 && ?o<3) ) + FILTER(?o>2 || ?x=4 || (?x<3 && ?o<3) ) } `, { prefixes: { ex: 'http://example.com#' }}); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index d86c28181..16f05d9fa 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -18,7 +18,7 @@ import { isBooleanExpressionTreeRelationFilterSolvable, recursifResolve, } from '../lib/solver'; -import { SparqlOperandDataTypes, LogicOperator } from '../lib/solverInterfaces'; +import { SparqlOperandDataTypes } from '../lib/solverInterfaces'; import type { ISolverExpression, } from '../lib/solverInterfaces'; @@ -252,7 +252,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Int, valueAsNumber: 1, operator: SparqlRelationOperator.EqualThanRelation, - + }, { variable: 'a', @@ -260,7 +260,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.NonNegativeInteger, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - + }, { variable: 'a', @@ -268,7 +268,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Decimal, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - + }, ]; @@ -283,7 +283,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Boolean, valueAsNumber: 1, operator: SparqlRelationOperator.EqualThanRelation, - + }, { variable: 'a', @@ -291,7 +291,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Boolean, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - + }, { variable: 'a', @@ -299,7 +299,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Byte, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - + }, ]; @@ -314,7 +314,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.UnsignedInt, valueAsNumber: 1, operator: SparqlRelationOperator.EqualThanRelation, - + }, { variable: 'a', @@ -322,7 +322,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Float, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - + }, { variable: 'a', @@ -330,7 +330,7 @@ describe('solver function', () => { valueType: SparqlOperandDataTypes.Byte, valueAsNumber: 0, operator: SparqlRelationOperator.EqualThanRelation, - + }, ]; @@ -357,7 +357,7 @@ describe('solver function', () => { rawValue: '5', valueType: SparqlOperandDataTypes.Integer, valueAsNumber: 5, - + operator: SparqlRelationOperator.EqualThanRelation, }; From c381791b50f11489487756d7cc3b28b33ca52841 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 14 Apr 2023 17:26:46 +0200 Subject: [PATCH 151/189] lint-fix --- .../lib/FilterNode.ts | 13 ++++----- .../test/FilterNode-test.ts | 27 +++++++++---------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 2f2cdc339..367c24a0f 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -1,7 +1,6 @@ import { BindingsFactory } from '@comunica/bindings-factory'; import { KeysInitQuery } from '@comunica/context-entries'; import type { IActionContext } from '@comunica/types'; -import type * as RDF from 'rdf-js'; import { Algebra, Factory as AlgebraFactory, Util } from 'sparqlalgebrajs'; import { isBooleanExpressionTreeRelationFilterSolvable } from './solver'; import type { Variable } from './solverInterfaces'; @@ -93,8 +92,6 @@ export class FilterNode { return filterMap; } - - /** * Find the variables from the BGP that match the predicate defined by the TREE:path from a TREE relation. * The subject can be anyting. @@ -102,18 +99,18 @@ export class FilterNode { * @param {string} path - TREE path * @returns {Variable[]} the variables of the Quad objects that contain the TREE path as predicate */ - private static findRelevantVariableFromBgp(queryBody: Algebra.Operation, path: string): Variable[] { + private static findRelevantVariableFromBgp(queryBody: Algebra.Operation, path: string): Variable[] { const resp: Variable[] = []; - const addVariable = (quad:any) =>{ + const addVariable = (quad: any): boolean => { if (quad.predicate.value === path && quad.object.termType === 'Variable') { resp.push(quad.object.value); } return true; }; - - Util.recurseOperation(queryBody,{ + + Util.recurseOperation(queryBody, { [Algebra.types.PATH]: addVariable, - [Algebra.types.PATTERN]: addVariable + [Algebra.types.PATTERN]: addVariable, }); return resp; diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index b8016d568..f17f5cd10 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -89,7 +89,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'http://example.com#path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -135,7 +135,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }, ], }; - + const query = translate(` SELECT ?o WHERE { ex:foo ex:path ?o. @@ -161,7 +161,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'http://example.com#path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -214,7 +214,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'http://example.com#path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -233,7 +233,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { FILTER(?o=5 && ?o<=5 ) } `, { prefixes: { ex: 'http://example.com#' }}); - + const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -254,7 +254,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'http://example.com#path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -293,7 +293,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'http://example.com#path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -327,7 +327,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'http://example.com#path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -361,7 +361,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'http://example.com#path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -387,7 +387,6 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ); }); - it('should accept the relation when the filter respect the relation with a construct query', async() => { const treeSubject = 'tree'; @@ -396,7 +395,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'http://example.com#path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -434,7 +433,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'http://example.com#path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -475,7 +474,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { relation: [ { node: 'http://bar.com', - path: 'http://example.com#path', + path: 'http://example.com#path', value: { value: '5', term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), @@ -490,7 +489,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ex:foo ex:path ?o. ex:foo ex:p ex:o. ex:foo ex:p2 ?x. - FILTER(?o>2 || ?x=4 || (?x<3 && ?o<3) ) + FILTER(?o>2 || ?x=4 || (?o<3 && ?o<3) ) } `, { prefixes: { ex: 'http://example.com#' }}); From d4b70cd6db669201b6ef6a5fae1733b11abd02c3 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 5 May 2023 10:32:46 +0200 Subject: [PATCH 152/189] made operator independent classes --- .../lib/LogicOperator.ts | 110 +++++++++++ .../lib/SolutionDomain.ts | 183 +++--------------- .../lib/SolutionInterval.ts | 4 +- .../lib/solver.ts | 84 ++++---- .../lib/solverInterfaces.ts | 6 +- .../test/FilterNode-test.ts | 2 +- .../test/LogicOperator-test.ts | 32 +++ .../test/SolutionDomain-test.ts | 117 +++-------- 8 files changed, 247 insertions(+), 291 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/lib/LogicOperator.ts create mode 100644 packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts new file mode 100644 index 000000000..b8dae12a7 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -0,0 +1,110 @@ +import { SolutionDomain } from "./SolutionDomain"; +import { SolutionInterval } from "./SolutionInterval"; +import { LogicOperatorSymbol } from './solverInterfaces'; +export interface LogicOperator { + apply({ interval, domain }: { interval?: SolutionInterval, domain: SolutionDomain }): SolutionDomain, + //merge(domain1: SolutionDomain, domain2: SolutionDomain): SolutionDomain, + operatorName(): LogicOperatorSymbol +} + + +export class Or implements LogicOperator { + public apply({ interval, domain }: { interval?: SolutionInterval | undefined; domain: SolutionDomain; }): SolutionDomain { + let newDomain: SolutionInterval[] = []; + if (!interval) { + return domain; + } + let currentInterval = interval; + + // We iterate over all the domain + newDomain = domain.getDomain().filter(el => { + // We check if we can fuse the new range with the current range + // let's not forget that the domain is sorted by the lowest bound + // of the SolutionRange + const resp = SolutionInterval.fuseRange(el, currentInterval); + if (resp.length === 1) { + // If we fuse the range and consider this new range as our current range + // and we delete the old range from the domain as we now have a new range that contained the old + currentInterval = resp[0]; + return false; + } + return true; + }); + // We add the potentialy fused range. + newDomain.push(currentInterval); + + return SolutionDomain.newWithInitialIntervals(newDomain); + } + + public operatorName(): LogicOperatorSymbol { + return LogicOperatorSymbol.Or; + } +} + +export class Not implements LogicOperator { + private readonly orOperator: Or = new Or() + apply({ domain }: { interval?: SolutionInterval | undefined; domain: SolutionDomain; }): SolutionDomain { + let newDomain: SolutionInterval[] = []; + for (const domainElement of domain.getDomain()) { + // Inverse the domain and had it with care for the overlap + // wich is similar to apply an or operator + for (const el of domainElement.inverse()) { + newDomain = this.orOperator.apply({ interval: el, domain }).getDomain(); + } + } + return SolutionDomain.newWithInitialIntervals(newDomain); + } + + public operatorName(): LogicOperatorSymbol { + return LogicOperatorSymbol.Not; + } +} + +export class And implements LogicOperator { + apply({ interval, domain }: { interval?: SolutionInterval | undefined; domain: SolutionDomain; }): SolutionDomain { + let newDomain: SolutionInterval[] = []; + if (!interval) { + return domain; + } + // If the domain is empty then simply add the new range + if (domain.getDomain().length === 0) { + newDomain.push(interval); + return SolutionDomain.newWithInitialIntervals(interval); + } + // Considering the current domain if there is an intersection + // add the intersection to the new domain + domain.getDomain().forEach(el => { + const intersection = SolutionInterval.getIntersection(el, interval); + if (!intersection.isEmpty) { + newDomain.push(intersection); + } + }); + + if (newDomain.length===0){ + newDomain.push(new SolutionInterval([])); + } + + return SolutionDomain.newWithInitialIntervals(newDomain); + } + public operatorName(): LogicOperatorSymbol { + return LogicOperatorSymbol.And; + } +} + + +const OPERATOR_MAP = new Map( + [ + [new Or().operatorName(), new Or()], + [new And().operatorName(), new And()], + [new Not().operatorName(), new Not()], + + ] +); + +export function operatorFactory(operatorSymbol: LogicOperatorSymbol): LogicOperator{ + const operator = OPERATOR_MAP.get(operatorSymbol); + if (!operator){ + throw new RangeError("The operator doesn't exist or is not supported.") + } + return operator; +} \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index cb7cc8c6e..ae61b7beb 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -1,9 +1,7 @@ import { SolutionInterval } from './SolutionInterval'; -import { LogicOperator } from './solverInterfaces'; /** * A class representing the domain of a solution of system of boolean equation. - * Every operation return a new object. */ export class SolutionDomain { /** @@ -12,12 +10,6 @@ export class SolutionDomain { */ private domain: SolutionInterval[] = []; - /** - * The last operation apply to the domain - * - */ - private lastOperation: LogicOperator | undefined = undefined; - /** * Get the multiple segment of the domain. * @returns {SolutionInterval[]} @@ -31,173 +23,58 @@ export class SolutionDomain { * @returns {boolean} Return true if the domain is empty */ public isDomainEmpty(): boolean { - return this.domain.length === 0; + if (this.domain.length === 0) { + return true + } else if (this.domain.length === 1 && this.domain[0].isEmpty) { + return true + } else { + return false + } } /** * Create a new SolutionDomain with an inititial value. - * @param {SolutionInterval} initialRange + * @param {SolutionInterval} initialIntervals * @returns {SolutionDomain} */ - public static newWithInitialValue(initialRange: SolutionInterval): SolutionDomain { + public static newWithInitialIntervals(initialIntervals: SolutionInterval | SolutionInterval[]): SolutionDomain { const newSolutionDomain = new SolutionDomain(); - if (!initialRange.isEmpty) { - newSolutionDomain.domain = [ initialRange ]; + if (Array.isArray(initialIntervals)) { + // We keep the domain sorted + initialIntervals.sort(SolutionDomain.sortDomainRangeByLowerBound); + newSolutionDomain.domain = initialIntervals; + if(newSolutionDomain.isThereOverlapInsideDomain()){ + throw new RangeError('There is overlap inside the domain.') + } + } else { + if (!initialIntervals.isEmpty) { + newSolutionDomain.domain = [initialIntervals]; + } } Object.freeze(newSolutionDomain); Object.freeze(newSolutionDomain.domain); - Object.freeze(newSolutionDomain.lastOperation); - return newSolutionDomain; - } - - /** - * Clone the current solution domain. - * @returns {SolutionDomain} - */ - public clone(): SolutionDomain { - const newSolutionDomain = new SolutionDomain(); - newSolutionDomain.domain = new Array(...this.domain); - newSolutionDomain.lastOperation = this.lastOperation; return newSolutionDomain; } - /** - * Modifify the current solution range by applying a logical operation. - * @param {SolutionInterval} range - The range of the incoming operation to apply. - * @param {LogicOperator} operator - The logical operation to apply. - * @returns {SolutionDomain} - A new SolutionDomain with the operation applied. - */ - public add({ range, operator }: { range?: SolutionInterval; operator: LogicOperator }): SolutionDomain { - let newDomain: SolutionDomain = this.clone(); - - switch (operator) { - case LogicOperator.And: { - if (typeof range === 'undefined') { - throw new ReferenceError('range should be defined with "AND" operator'); - } - newDomain = this.addWithAndOperator(range); - newDomain.lastOperation = LogicOperator.And; - break; - } - case LogicOperator.Or: { - if (typeof range === 'undefined') { - throw new ReferenceError('range should be defined with "OR" operator'); - } - newDomain = this.addWithOrOperator(range); - newDomain.lastOperation = LogicOperator.Or; - break; - } - - case LogicOperator.Not: { - newDomain = this.notOperation(); - newDomain.lastOperation = LogicOperator.Not; - break; - } - } - // Since we rely on the size of the domain to determine if the domain is empty or not - // and that we have the concept of an empty solution range for the sake of simplicity - // we delete the empty solution range if it is the only element. - if (newDomain.domain.length === 1 && newDomain.domain[0].isEmpty) { - newDomain.domain = []; - } - // When we operate on a domain we don't want it to be mutable in any way. - Object.freeze(newDomain); - Object.freeze(newDomain.domain); - Object.freeze(newDomain.lastOperation); - return newDomain; - } - - /** - * Apply an "OR" operator to the current solution domain with the input - * solution range. It fuse the SolutionRange if needed to keep the simplest domain possible. - * It also keep the domain order. - * @param {SolutionInterval} range - * @returns {SolutionDomain} - */ - public addWithOrOperator(range: SolutionInterval): SolutionDomain { - const newDomain = this.clone(); - let currentRange = range; - // We iterate over all the domain - newDomain.domain = newDomain.domain.filter(el => { - // We check if we can fuse the new range with the current range - // let's not forget that the domain is sorted by the lowest bound - // of the SolutionRange - const resp = SolutionInterval.fuseRange(el, currentRange); - if (resp.length === 1) { - // If we fuse the range and consider this new range as our current range - // and we delete the old range from the domain as we now have a new range that contained the old - currentRange = resp[0]; - return false; - } - return true; - }); - // We add the potentialy fused range. - newDomain.domain.push(currentRange); - // We keep the domain sorted - newDomain.domain.sort(SolutionDomain.sortDomainRangeByLowerBound); - return newDomain; - } - - /** - * Apply an "AND" operator to the current solution domain with the input - * solution range. It will keep only the insection of the subdomain with the input - * range. It keep the domain ordered. - * @param {SolutionInterval} range - * @returns {SolutionDomain} - */ - public addWithAndOperator(range: SolutionInterval): SolutionDomain { - const newDomain = new SolutionDomain(); - - // If the domain is empty and the last operation was an "AND" - if (this.domain.length === 0 && this.lastOperation === LogicOperator.And) { - return newDomain; - } - // If the domain is empty then simply add the new range - if (this.domain.length === 0) { - newDomain.domain.push(range); - return newDomain; - } - // Considering the current domain if there is an intersection - // add the intersection to the new domain - this.domain.forEach(el => { - const intersection = SolutionInterval.getIntersection(el, range); - if (intersection) { - newDomain.domain.push(intersection); - } - }); - // Keep the domain sorted - newDomain.domain.sort(SolutionDomain.sortDomainRangeByLowerBound); - return newDomain; - } - - /** - * Apply a "NOT" operator to the current solution domain. - * Will inverse every subdomain and keep the domain simplest domain. - * The order is preserved. - * @returns {SolutionDomain} - */ - public notOperation(): SolutionDomain { - let newDomain = new SolutionDomain(); - for (const domainElement of this.domain) { - // Inverse the domain and had it with care for the overlap - // wich is similar to apply an or operator - for (const el of domainElement.inverse()) { - newDomain = newDomain.addWithOrOperator(el); - } - } - return newDomain; - } - /** * Simple sort function to order the domain by the lower bound of SolutionRange. * @param {SolutionInterval} firstRange * @param {SolutionInterval} secondRange * @returns {number} see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort */ - private static sortDomainRangeByLowerBound(firstRange: SolutionInterval, secondRange: SolutionInterval): number { + public static sortDomainRangeByLowerBound(firstRange: SolutionInterval, secondRange: SolutionInterval): number { if (firstRange.lower < secondRange.lower) { return -1; } return 1; } + + private isThereOverlapInsideDomain(): boolean{ + for (let i=0;i otherRange.lower ? subjectRange.lower : otherRange.lower; const upper = subjectRange.upper < otherRange.upper ? subjectRange.upper : otherRange.upper; diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index c1289e8e2..2820eb7ab 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -9,7 +9,7 @@ import { SolutionDomain } from './SolutionDomain'; import { SolutionInterval } from './SolutionInterval'; import { SparqlOperandDataTypes, - LogicOperatorReversed, LogicOperator, SparqlOperandDataTypesReversed, + LogicOperatorReversed, LogicOperatorSymbol, SparqlOperandDataTypesReversed, } from './solverInterfaces'; import type { ISolverExpression, @@ -17,12 +17,13 @@ import type { } from './solverInterfaces'; import { SparqlRelationOperator } from './TreeMetadata'; import type { ITreeRelation } from './TreeMetadata'; +import { And, LogicOperator, Not, Or, operatorFactory } from './LogicOperator'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; const A_TRUE_EXPRESSION: SolutionInterval = new SolutionInterval( - [ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ], + [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY], ); const A_FALSE_EXPRESSION: SolutionInterval = new SolutionInterval([]); @@ -46,12 +47,12 @@ export function isBooleanExpressionTreeRelationFilterSolvable({ relation, filter return true; } - const relationSolutionRange = getSolutionInterval( + const relationSolutionInterval = getSolutionInterval( relationsolverExpressions.valueAsNumber, relationsolverExpressions.operator, ); // We don't prune the relation because we do not implement yet the solution range for this expression - if (!relationSolutionRange) { + if (!relationSolutionInterval) { return true; } let solutionDomain: SolutionDomain = new SolutionDomain(); @@ -59,9 +60,8 @@ export function isBooleanExpressionTreeRelationFilterSolvable({ relation, filter solutionDomain = recursifResolve( filterExpression, solutionDomain, - undefined, + Or, variable, - false, ); } catch (error: unknown) { // A filter term was missformed we let the query engine return an error to the user and by precaution @@ -87,7 +87,7 @@ export function isBooleanExpressionTreeRelationFilterSolvable({ relation, filter } // Evaluate the solution domain when adding the relation - solutionDomain = solutionDomain.add({ range: relationSolutionRange, operator: LogicOperator.And }); + solutionDomain = new And().apply({ interval: relationSolutionInterval, domain: solutionDomain }) // If there is a possible solution we don't filter the link return !solutionDomain.isDomainEmpty(); @@ -156,7 +156,7 @@ export function resolveAFilterTerm(expression: Algebra.Expression, * It will thrown an error if the expression is badly formated or if it's impossible to get the solution range. * @param {Algebra.Expression} filterExpression - The current filter expression that we are traversing * @param {SolutionDomain} domain - The current resultant solution domain - * @param {LogicOperator} logicOperator - The current logic operator that we have to apply to the boolean expression + * @param {LogicOperatorSymbol} logicOperator - The current logic operator that we have to apply to the boolean expression * @param {Variable} variable - The variable targeted inside the filter expression * @param {boolean} notExpression * @returns {SolutionDomain} The solution domain of the whole expression @@ -164,28 +164,26 @@ export function resolveAFilterTerm(expression: Algebra.Expression, export function recursifResolve( filterExpression: Algebra.Expression, domain: SolutionDomain, - logicOperator: LogicOperator | undefined, + logicOperator: LogicOperator, variable: Variable, - notExpression: boolean, ): SolutionDomain { - // We apply an or operator by default or if the domain is empty - if (!logicOperator || domain.isDomainEmpty()) { - logicOperator = LogicOperator.Or; - } if (filterExpression.expressionType === Algebra.expressionTypes.TERM ) { + // In that case we are confronted with a boolean expression + // add the associated interval into the domain in relation to + // the logic operator. if (filterExpression.term.value === 'false') { - domain = domain.add({ range: A_FALSE_EXPRESSION, operator: logicOperator }); + domain = logicOperator.apply({ interval: A_FALSE_EXPRESSION, domain }); } else if (filterExpression.term.value === 'true') { - domain = domain.add({ range: A_TRUE_EXPRESSION, operator: logicOperator }); + domain = logicOperator.apply({ interval: A_TRUE_EXPRESSION, domain }); } else { throw new MisformatedFilterTermError(`The term sent is not a boolean but is this value {${filterExpression.term.value}}`); } } else if ( - // If it's an array of term then we should be able to create a solver expression - // hence get a subdomain appendable to the current global domain with consideration - // to the logic operator + // If it's an array of terms then we should be able to create a solver expression. + // Given the resulting solver expression we can calculate a solution interval + // that we will add to the domain with regards to the logic operator. filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && filterExpression.args.length === 2 ) { @@ -193,45 +191,41 @@ export function recursifResolve( const operator = filterOperatorToSparqlRelationOperator(rawOperator); if (operator) { const solverExpression = resolveAFilterTerm(filterExpression, operator, variable); - let solverRange: SolutionInterval | undefined; + let solutionInterval: SolutionInterval | undefined; if (solverExpression instanceof MissMatchVariableError) { - solverRange = A_TRUE_EXPRESSION; + solutionInterval = A_TRUE_EXPRESSION; } else if (solverExpression instanceof Error) { throw solverExpression; } else { - solverRange = getSolutionInterval(solverExpression.valueAsNumber, solverExpression.operator)!; - } - // We can distribute a not expression, so we inverse each statement - if (notExpression) { - const invertedRanges = solverRange.inverse(); - // We first solve the new inverted expression of the form - // (E1 AND E2) after that we apply the original operator - for (const range of invertedRanges) { - domain = domain.add({ range, operator: logicOperator }); - } - } else { - domain = domain.add({ range: solverRange, operator: logicOperator }); + solutionInterval = getSolutionInterval(solverExpression.valueAsNumber, solverExpression.operator)!; } + domain = logicOperator.apply({ interval: solutionInterval, domain }); } } else if ( - // It is a malformed expression filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && filterExpression.args.length === 1 ) { + // We consider that if we only have one term in an array then it is a malformed expression, because + // we don't support functions throw new MisformatedFilterTermError(`The expression should have a variable and a value, but is {${filterExpression.args}}`); } else { - let newLogicOperator = LogicOperatorReversed.get(filterExpression.operator); - notExpression = newLogicOperator === LogicOperator.Not || notExpression; - if (newLogicOperator) { - newLogicOperator = newLogicOperator === LogicOperator.Not ? logicOperator : newLogicOperator; + // In that case we are traversing the filter expression TREE. + // We prepare the next recursion and we compute the accumulation of results. + const logicOperatorSymbol = LogicOperatorReversed.get(filterExpression.operator); + if (logicOperatorSymbol) { + const logicOperator = operatorFactory(logicOperatorSymbol); for (const arg of filterExpression.args) { - domain = recursifResolve(arg, domain, newLogicOperator, variable, notExpression); + if (logicOperator.operatorName() !== LogicOperatorSymbol.Not) { + domain = recursifResolve(arg, domain, logicOperator, variable); + } + } + if (logicOperator.operatorName() === LogicOperatorSymbol.Not) { + domain = logicOperator.apply({ domain: domain }); } } } return domain; } - /** * Convert a TREE relation into a solver expression. * @param {ITreeRelation} relation - TREE relation. @@ -290,15 +284,15 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { export function getSolutionInterval(value: number, operator: SparqlRelationOperator): SolutionInterval | undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: - return new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]); + return new SolutionInterval([nextUp(value), Number.POSITIVE_INFINITY]); case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return new SolutionInterval([ value, Number.POSITIVE_INFINITY ]); + return new SolutionInterval([value, Number.POSITIVE_INFINITY]); case SparqlRelationOperator.EqualThanRelation: - return new SolutionInterval([ value, value ]); + return new SolutionInterval([value, value]); case SparqlRelationOperator.LessThanRelation: - return new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]); + return new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(value)]); case SparqlRelationOperator.LessThanOrEqualToRelation: - return new SolutionInterval([ Number.NEGATIVE_INFINITY, value ]); + return new SolutionInterval([Number.NEGATIVE_INFINITY, value]); default: // Not an operator that is compatible with number. break; diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index e72eb2f95..62e39bd48 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -35,7 +35,7 @@ export const SparqlOperandDataTypesReversed: Map /** * Logical operator that linked logical expression together. */ -export enum LogicOperator { +export enum LogicOperatorSymbol { And = '&&', Or = '||', Not = '!', @@ -44,8 +44,8 @@ export enum LogicOperator { /** * A map to access the value of the enum LogicOperator by it's value in O(1) time complexity. */ -export const LogicOperatorReversed: Map = - new Map(Object.values(LogicOperator).map(value => [ value, value ])); +export const LogicOperatorReversed: Map = + new Map(Object.values(LogicOperatorSymbol).map(value => [ value, value ])); /** * A variable to be solved by the solver. diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index f17f5cd10..f2751ba7b 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -489,7 +489,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ex:foo ex:path ?o. ex:foo ex:p ex:o. ex:foo ex:p2 ?x. - FILTER(?o>2 || ?x=4 || (?o<3 && ?o<3) ) + FILTER(?o>2 || ?x=4 || (?x<3 && ?o<3) ) } `, { prefixes: { ex: 'http://example.com#' }}); diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts new file mode 100644 index 000000000..d90113505 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -0,0 +1,32 @@ +import { Or } from '../lib/LogicOperator'; +import { SolutionInterval } from '../lib/SolutionInterval'; +import { LogicOperatorSymbol } from '../lib/solverInterfaces'; +import { SolutionDomain } from '../lib/SolutionDomain'; + +describe('LogicOperator', () => { + describe('Or', () => { + const or = new Or(); + describe('operatorName', () => { + it('Should return the associated operator name', () => { + expect(or.operatorName()).toBe(LogicOperatorSymbol.Or); + }); + }); + describe('apply', () => { + const intervals = [ + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 5]), + new SolutionInterval([10, 10]), + new SolutionInterval([21, 33]), + new SolutionInterval([60, 70]), + ]; + const aDomain = SolutionDomain.newWithInitialIntervals(intervals); + it('given an empty domain should be able to add an interval', ()=>{ + const interval = new SolutionInterval([ 0, 1 ]); + const solutionDomain = new SolutionDomain(); + const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals(interval); + expect(or.apply({domain: solutionDomain, interval:interval})).toStrictEqual(expectedSolutionDomain); + + }) + }); + }); +}); \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index 666491559..d3ab47059 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -1,6 +1,6 @@ import { SolutionInterval } from '../lib//SolutionInterval'; import { SolutionDomain } from '../lib/SolutionDomain'; -import { LogicOperator } from '../lib/solverInterfaces'; +import { LogicOperatorSymbol } from '../lib/solverInterfaces'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; @@ -14,14 +14,39 @@ describe('SolutionDomain', () => { }); }); - describe('newWithInitialValue', () => { - it('should create a solution domain with the initial value', () => { + describe('newWithInitialIntervals', () => { + it('should create a solution domain with an initial value', () => { const solutionRange = new SolutionInterval([ 0, 1 ]); - const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); + const solutionDomain = SolutionDomain.newWithInitialIntervals(solutionRange); expect(solutionDomain.getDomain().length).toBe(1); expect(solutionDomain.getDomain()[0]).toStrictEqual(solutionRange); }); + + it('should create a solution domain with multiple initial value', () => { + const solutionIntervals = [ + new SolutionInterval([ 0, 1 ]), + new SolutionInterval([ 2, 3 ]), + new SolutionInterval([ -33, -3 ]), + new SolutionInterval([ 100, 3000 ]) + ]; + const solutionDomain = SolutionDomain.newWithInitialIntervals(solutionIntervals); + + expect(solutionDomain.getDomain().length).toBe(solutionIntervals.length); + expect(solutionDomain.getDomain()).toStrictEqual(solutionIntervals); + }); + + it('should throw an error when creating a solution domain with multiple intervals overlaping', () => { + const solutionIntervals = [ + new SolutionInterval([ 0, 1 ]), + new SolutionInterval([ 2, 3 ]), + new SolutionInterval([ 2, 10 ]), + ]; + expect(()=>{ + SolutionDomain.newWithInitialIntervals(solutionIntervals); + }).toThrow(RangeError); + }); + }); describe('addWithOrOperator', () => { @@ -39,15 +64,6 @@ describe('SolutionDomain', () => { aDomain = aDomain.addWithOrOperator(r); } }); - it('given an empty domain should be able to add the subject range', () => { - const range = new SolutionInterval([ 0, 1 ]); - const solutionDomain = new SolutionDomain(); - - const newDomain = solutionDomain.addWithOrOperator(range); - - expect(newDomain.getDomain().length).toBe(1); - expect(newDomain.getDomain()[0]).toStrictEqual(range); - }); it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { const ranges = [ @@ -213,7 +229,7 @@ describe('SolutionDomain', () => { const anotherRangeNonOverlapping = new SolutionInterval([ 2_000, 3_000 ]); let newDomain = aDomain.addWithAndOperator(aRange); - newDomain = newDomain.add({ range: anotherRangeNonOverlapping, operator: LogicOperator.And }); + newDomain = newDomain.add({ range: anotherRangeNonOverlapping, operator: LogicOperatorSymbol.And }); expect(newDomain.isDomainEmpty()).toBe(true); newDomain = newDomain.addWithAndOperator(aRange); @@ -222,79 +238,6 @@ describe('SolutionDomain', () => { }); }); - describe('add', () => { - const aDomain = new SolutionDomain(); - const aRange = new SolutionInterval([ 0, 1 ]); - - let spyAddWithOrOperator; - - let spyAddWithAndOperator; - - let spyNotOperation; - - beforeEach(() => { - spyAddWithOrOperator = jest.spyOn(aDomain, 'addWithOrOperator') - .mockImplementation((_range: SolutionInterval) => { - return new SolutionDomain(); - }); - - spyAddWithAndOperator = jest.spyOn(aDomain, 'addWithAndOperator') - .mockImplementation((_range: SolutionInterval) => { - return new SolutionDomain(); - }); - - spyNotOperation = jest.spyOn(aDomain, 'notOperation') - .mockImplementation(() => { - return new SolutionDomain(); - }); - }); - - it('should call "addWithOrOperator" method given the "OR" operator and a new range', () => { - aDomain.add({ range: aRange, operator: LogicOperator.Or }); - expect(spyAddWithOrOperator).toBeCalledTimes(1); - }); - - it('should throw an error when adding range with a "OR" operator and no new range', () => { - expect(() => { aDomain.add({ operator: LogicOperator.Or }); }).toThrow(); - }); - - it('should call "addWithAndOperator" method given the "AND" operator and a new range', () => { - aDomain.add({ range: aRange, operator: LogicOperator.And }); - expect(spyAddWithOrOperator).toBeCalledTimes(1); - }); - - it('should throw an error when adding range with a "AND" operator and no new range', () => { - expect(() => { aDomain.add({ operator: LogicOperator.And }); }).toThrow(); - }); - - it('should call "notOperation" method given the "NOT" operator', () => { - aDomain.add({ operator: LogicOperator.Not }); - expect(spyAddWithOrOperator).toBeCalledTimes(1); - }); - - it('should on any operator return an empty domain if the only solution range is empty', () => { - const an_empty_solution_range = new SolutionInterval([]); - const operations: [LogicOperator, SolutionInterval][] = [ - [ LogicOperator.Or, an_empty_solution_range ], - [ LogicOperator.And, an_empty_solution_range ], - [ LogicOperator.Not, an_empty_solution_range.inverse()[0] ], - ]; - - for (const [ logicOperator, solutionRange ] of operations) { - if (logicOperator !== LogicOperator.Not) { - let domain = new SolutionDomain(); - domain = domain.add({ range: solutionRange, operator: logicOperator }); - expect(domain.getDomain().length).toBe(0); - } else { - let domain = new SolutionDomain(); - domain = domain.addWithOrOperator(solutionRange); - domain = domain.add({ operator: logicOperator }); - expect(domain.getDomain().length).toBe(0); - } - } - }); - }); - describe('isDomainEmpty', () => { it('should return true when the domain is empty', () => { const domain = new SolutionDomain(); From 501e6a0fe5f05d96cf35feefa7c13c38f1ef2af8 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 24 May 2023 15:48:12 +0200 Subject: [PATCH 153/189] Unit test logic operator done. --- .../lib/LogicOperator.ts | 8 +- .../lib/solver.ts | 2 +- .../test/LogicOperator-test.ts | 205 +++++++++++++++++- .../test/SolutionDomain-test.ts | 191 +--------------- 4 files changed, 206 insertions(+), 200 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index b8dae12a7..4bbd772fb 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -44,15 +44,15 @@ export class Or implements LogicOperator { export class Not implements LogicOperator { private readonly orOperator: Or = new Or() apply({ domain }: { interval?: SolutionInterval | undefined; domain: SolutionDomain; }): SolutionDomain { - let newDomain: SolutionInterval[] = []; + let newDomain = new SolutionDomain(); for (const domainElement of domain.getDomain()) { - // Inverse the domain and had it with care for the overlap + // Inverse the domain and add it with care for the overlap // wich is similar to apply an or operator for (const el of domainElement.inverse()) { - newDomain = this.orOperator.apply({ interval: el, domain }).getDomain(); + newDomain = this.orOperator.apply({ interval: el, domain:newDomain }); } } - return SolutionDomain.newWithInitialIntervals(newDomain); + return newDomain; } public operatorName(): LogicOperatorSymbol { diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 2820eb7ab..4e37d8ef7 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -60,7 +60,7 @@ export function isBooleanExpressionTreeRelationFilterSolvable({ relation, filter solutionDomain = recursifResolve( filterExpression, solutionDomain, - Or, + new Or(), variable, ); } catch (error: unknown) { diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index d90113505..e0647a42b 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -1,8 +1,11 @@ -import { Or } from '../lib/LogicOperator'; +import { Or, Not, And } from '../lib/LogicOperator'; import { SolutionInterval } from '../lib/SolutionInterval'; import { LogicOperatorSymbol } from '../lib/solverInterfaces'; import { SolutionDomain } from '../lib/SolutionDomain'; +const nextUp = require('ulp').nextUp; +const nextDown = require('ulp').nextDown; + describe('LogicOperator', () => { describe('Or', () => { const or = new Or(); @@ -20,13 +23,205 @@ describe('LogicOperator', () => { new SolutionInterval([60, 70]), ]; const aDomain = SolutionDomain.newWithInitialIntervals(intervals); - it('given an empty domain should be able to add an interval', ()=>{ - const interval = new SolutionInterval([ 0, 1 ]); + it('given an empty domain should be able to add an interval', () => { + const interval = new SolutionInterval([0, 1]); const solutionDomain = new SolutionDomain(); const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals(interval); - expect(or.apply({domain: solutionDomain, interval:interval})).toStrictEqual(expectedSolutionDomain); + expect(or.apply({ domain: solutionDomain, interval: interval })).toStrictEqual(expectedSolutionDomain); + + }); + + it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { + const intervals = [ + new SolutionInterval([10, 10]), + new SolutionInterval([1, 2]), + new SolutionInterval([-1, 0]), + new SolutionInterval([60, 70]), + ]; + + let solutionDomain = new SolutionDomain(); + + intervals.forEach((interval, idx) => { + solutionDomain = or.apply({ domain: solutionDomain, interval: interval }); + expect(solutionDomain.getDomain().length).toBe(idx + 1); + }); + + const expectedDomain = [ + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 2]), + new SolutionInterval([10, 10]), + new SolutionInterval([60, 70]), + ]; + + expect(solutionDomain.getDomain()).toStrictEqual(expectedDomain); + }); + + it('given a domain should not add a range that is inside another', () => { + const anOverlappingInterval = new SolutionInterval([22, 23]); + const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); + + expect(newDomain.getDomain().length).toBe(aDomain.getDomain().length); + expect(newDomain.getDomain()).toStrictEqual(intervals); + }); + + it('given a domain should create a single domain if all the domain segment are contain into the new range', () => { + const anOverlappingInterval = new SolutionInterval([-100, 100]); + const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); + + expect(newDomain.getDomain().length).toBe(1); + expect(newDomain.getDomain()).toStrictEqual([anOverlappingInterval]); + }); + + it('given a domain should fuse multiple domain segment if the new range overlap with them', () => { + const aNewInterval = new SolutionInterval([1, 23]); + const newDomain = or.apply({ domain: aDomain, interval: aNewInterval }); + + const expectedResultingDomainInterval = [ + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 33]), + new SolutionInterval([60, 70]), + ]; + + expect(newDomain.getDomain().length).toBe(3); + expect(newDomain.getDomain()).toStrictEqual(expectedResultingDomainInterval); + }); + + }); + }); + + describe('Not', () => { + const not = new Not(); + describe('operatorName', () => { + it('Should return the associated operator name', () => { + expect(not.operatorName()).toBe(LogicOperatorSymbol.Not); + }); + }); + describe('apply', () => { + it('given a domain with one range should return the inverse of the domain', () => { + const solutionInterval = new SolutionInterval([0, 1]); + const solutionDomain = SolutionDomain.newWithInitialIntervals(solutionInterval); + + const expectedDomain = [ + new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(0)]), + new SolutionInterval([nextUp(1), Number.POSITIVE_INFINITY]), + ]; + const newDomain = not.apply({ domain: solutionDomain }); + + expect(newDomain.getDomain().length).toBe(2); + expect(newDomain.getDomain()).toStrictEqual(expectedDomain); + }); + + it('given a domain with multiple range should return the inverted domain', () => { + let domain = new SolutionDomain(); + const intervals = [ + new SolutionInterval([0, 1]), + new SolutionInterval([2, 2]), + new SolutionInterval([44, 55]), + ]; + const expectedDomain = [ + new SolutionInterval([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]), + ]; + + for (const interval of intervals) { + domain = new Or().apply({ interval: interval, domain }) + } + domain = not.apply({ domain }); + + expect(domain.getDomain()).toStrictEqual(expectedDomain); + }); + }); + }); + + describe('And', () => { + const and = new And(); + describe('operatorName', () => { + it('Should return the associated operator name', () => { + expect(and.operatorName()).toBe(LogicOperatorSymbol.And); + }); + }); + describe('apply', () => { + let aDomain: SolutionDomain = new SolutionDomain(); + const intervals = [ + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 5]), + new SolutionInterval([10, 10]), + new SolutionInterval([21, 33]), + new SolutionInterval([60, 70]), + ]; + beforeEach(() => { + aDomain = new SolutionDomain(); + for (const interval of intervals) { + aDomain = new Or().apply({ interval, domain: aDomain }); + } + }); + + it('should add a range when the domain is empty', () => { + const domain = new SolutionDomain(); + const interval = new SolutionInterval([0, 1]); + + const newDomain = and.apply({ interval, domain }); + + expect(newDomain.getDomain()).toStrictEqual([interval]); + }); + + it('should return an empty domain if there is no intersection with the new range', () => { + const interval = new SolutionInterval([ -200, -100 ]); + + const newDomain = and.apply({ interval, domain: aDomain }); + + expect(newDomain.isDomainEmpty()).toBe(true); + }); + + it('given a new range that is inside a part of the domain should only return the intersection', () => { + const interval = new SolutionInterval([ 22, 30 ]); + + const newDomain = and.apply({ interval, domain: aDomain }); + + expect(newDomain.getDomain()).toStrictEqual([ interval ]); + }); + + it('given a new range that intersect a part of the domain should only return the intersection', () => { + const interval = new SolutionInterval([ 19, 25 ]); + + const expectedDomain = [ + new SolutionInterval([ 21, 25 ]), + ]; + + const newDomain = and.apply({ interval, domain: aDomain }); + + expect(newDomain.getDomain()).toStrictEqual(expectedDomain); + }); + + it('given a new range that intersect multiple part of the domain should only return the intersections', () => { + const interval = new SolutionInterval([ -2, 25 ]); + + const expectedDomain = [ + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 25 ]), + ]; + + const newDomain = and.apply({ interval, domain: aDomain }); + + expect(newDomain.getDomain()).toStrictEqual(expectedDomain); + }); + + it('given an empty domain and a last operator and should return an empty domain', () => { + const interval = new SolutionInterval([ -2, 25 ]); + const anotherIntervalNonOverlapping = new SolutionInterval([ 2_000, 3_000 ]); + + let newDomain = and.apply({ interval, domain: aDomain }); + newDomain = and.apply({ interval:anotherIntervalNonOverlapping, domain: newDomain }); + + expect(newDomain.isDomainEmpty()).toBe(true); + + newDomain = and.apply({ interval, domain: newDomain }); + + expect(newDomain.isDomainEmpty()).toBe(true); + }); + - }) }); }); }); \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index d3ab47059..ccec25201 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -48,196 +48,7 @@ describe('SolutionDomain', () => { }); }); - - describe('addWithOrOperator', () => { - let aDomain: SolutionDomain = new SolutionDomain(); - const aRanges = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 5 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 21, 33 ]), - new SolutionInterval([ 60, 70 ]), - ]; - beforeEach(() => { - aDomain = new SolutionDomain(); - for (const r of aRanges) { - aDomain = aDomain.addWithOrOperator(r); - } - }); - - it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { - const ranges = [ - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 1, 2 ]), - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 60, 70 ]), - ]; - - let solutionDomain = new SolutionDomain(); - - ranges.forEach((range, idx) => { - solutionDomain = solutionDomain.addWithOrOperator(range); - expect(solutionDomain.getDomain().length).toBe(idx + 1); - }); - - const expectedDomain = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 2 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 60, 70 ]), - ]; - - expect(solutionDomain.getDomain()).toStrictEqual(expectedDomain); - }); - - it('given a domain should not add a range that is inside another', () => { - const anOverlappingRange = new SolutionInterval([ 22, 23 ]); - const newDomain = aDomain.addWithOrOperator(anOverlappingRange); - - expect(newDomain.getDomain().length).toBe(aDomain.getDomain().length); - expect(newDomain.getDomain()).toStrictEqual(aRanges); - }); - - it('given a domain should create a single domain if all the domain segment are contain into the new range', () => { - const anOverlappingRange = new SolutionInterval([ -100, 100 ]); - const newDomain = aDomain.addWithOrOperator(anOverlappingRange); - - expect(newDomain.getDomain().length).toBe(1); - expect(newDomain.getDomain()).toStrictEqual([ anOverlappingRange ]); - }); - - it('given a domain should fuse multiple domain segment if the new range overlap with them', () => { - const aNewRange = new SolutionInterval([ 1, 23 ]); - const newDomain = aDomain.addWithOrOperator(aNewRange); - - const expectedResultingDomainRange = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 33 ]), - new SolutionInterval([ 60, 70 ]), - ]; - - expect(newDomain.getDomain().length).toBe(3); - expect(newDomain.getDomain()).toStrictEqual(expectedResultingDomainRange); - }); - }); - - describe('notOperation', () => { - it('given a domain with one range should return the inverse of the domain', () => { - const solutionRange = new SolutionInterval([ 0, 1 ]); - const solutionDomain = SolutionDomain.newWithInitialValue(solutionRange); - - const expectedDomain = [ - new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(0) ]), - new SolutionInterval([ nextUp(1), Number.POSITIVE_INFINITY ]), - ]; - const newDomain = solutionDomain.notOperation(); - - expect(newDomain.getDomain().length).toBe(2); - expect(newDomain.getDomain()).toStrictEqual(expectedDomain); - }); - - it('given a domain with multiple range should return the inverted domain', () => { - let domain = new SolutionDomain(); - const ranges = [ - new SolutionInterval([ 0, 1 ]), - new SolutionInterval([ 2, 2 ]), - new SolutionInterval([ 44, 55 ]), - ]; - const expectedDomain = [ - new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), - ]; - - for (const r of ranges) { - domain = domain.addWithOrOperator(r); - } - domain = domain.notOperation(); - - expect(domain.getDomain()).toStrictEqual(expectedDomain); - }); - }); - - describe('addWithAndOperator', () => { - let aDomain: SolutionDomain = new SolutionDomain(); - const aRanges = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 5 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 21, 33 ]), - new SolutionInterval([ 60, 70 ]), - ]; - beforeEach(() => { - aDomain = new SolutionDomain(); - for (const r of aRanges) { - aDomain = aDomain.addWithOrOperator(r); - } - }); - - it('should add a range when the domain is empty', () => { - const domain = new SolutionDomain(); - const aRange = new SolutionInterval([ 0, 1 ]); - - const newDomain = domain.addWithAndOperator(aRange); - - expect(newDomain.getDomain()).toStrictEqual([ aRange ]); - }); - - it('should return an empty domain if there is no intersection with the new range', () => { - const aRange = new SolutionInterval([ -200, -100 ]); - - const newDomain = aDomain.addWithAndOperator(aRange); - - expect(newDomain.getDomain().length).toBe(0); - }); - - it('given a new range that is inside a part of the domain should only return the intersection', () => { - const aRange = new SolutionInterval([ 22, 30 ]); - - const newDomain = aDomain.addWithAndOperator(aRange); - - expect(newDomain.getDomain()).toStrictEqual([ aRange ]); - }); - - it('given a new range that intersect a part of the domain should only return the intersection', () => { - const aRange = new SolutionInterval([ 19, 25 ]); - - const expectedDomain = [ - new SolutionInterval([ 21, 25 ]), - ]; - - const newDomain = aDomain.addWithAndOperator(aRange); - - expect(newDomain.getDomain()).toStrictEqual(expectedDomain); - }); - - it('given a new range that intersect multiple part of the domain should only return the intersections', () => { - const aRange = new SolutionInterval([ -2, 25 ]); - - const expectedDomain = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 5 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 21, 25 ]), - ]; - - const newDomain = aDomain.addWithAndOperator(aRange); - - expect(newDomain.getDomain()).toStrictEqual(expectedDomain); - }); - - it('given an empty domain and a last operator and should return an empty domain', () => { - const aRange = new SolutionInterval([ -2, 25 ]); - const anotherRangeNonOverlapping = new SolutionInterval([ 2_000, 3_000 ]); - - let newDomain = aDomain.addWithAndOperator(aRange); - newDomain = newDomain.add({ range: anotherRangeNonOverlapping, operator: LogicOperatorSymbol.And }); - expect(newDomain.isDomainEmpty()).toBe(true); - - newDomain = newDomain.addWithAndOperator(aRange); - - expect(newDomain.isDomainEmpty()).toBe(true); - }); - }); - + describe('isDomainEmpty', () => { it('should return true when the domain is empty', () => { const domain = new SolutionDomain(); From ce284453da4204ad92e2702f78bf990438157595 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 24 May 2023 15:49:55 +0200 Subject: [PATCH 154/189] Solution domain test done. --- .../test/SolutionDomain-test.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index ccec25201..9138023e4 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -1,9 +1,5 @@ import { SolutionInterval } from '../lib//SolutionInterval'; import { SolutionDomain } from '../lib/SolutionDomain'; -import { LogicOperatorSymbol } from '../lib/solverInterfaces'; - -const nextUp = require('ulp').nextUp; -const nextDown = require('ulp').nextDown; describe('SolutionDomain', () => { describe('constructor', () => { @@ -48,7 +44,7 @@ describe('SolutionDomain', () => { }); }); - + describe('isDomainEmpty', () => { it('should return true when the domain is empty', () => { const domain = new SolutionDomain(); @@ -57,18 +53,10 @@ describe('SolutionDomain', () => { }); it('should return false when the domain is not empty', () => { - const domain = SolutionDomain.newWithInitialValue(new SolutionInterval([ 0, 1 ])); + const domain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 0, 1 ])); expect(domain.isDomainEmpty()).toBe(false); }); }); - describe('clone', () => { - it('should return a deep copy of an existing domain', () => { - let domain = SolutionDomain.newWithInitialValue(new SolutionInterval([ 0, 1 ])); - const clonedDomain = domain.clone(); - domain = domain.addWithOrOperator(new SolutionInterval([ 100, 200 ])); - expect(clonedDomain.getDomain()).not.toStrictEqual(domain.getDomain()); - }); - }); }); From 7b26e5c64645a2ce900d20f00f64b1f6d283e2b1 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 25 May 2023 15:08:48 +0200 Subject: [PATCH 155/189] unit test resolve working until the not operator. --- .../lib/solver.ts | 3 +- .../test/FilterNode-test.ts | 2 +- .../test/solver-test.ts | 161 +++++++++--------- 3 files changed, 82 insertions(+), 84 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 4e37d8ef7..1394c8163 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -215,9 +215,8 @@ export function recursifResolve( if (logicOperatorSymbol) { const logicOperator = operatorFactory(logicOperatorSymbol); for (const arg of filterExpression.args) { - if (logicOperator.operatorName() !== LogicOperatorSymbol.Not) { domain = recursifResolve(arg, domain, logicOperator, variable); - } + } if (logicOperator.operatorName() === LogicOperatorSymbol.Not) { domain = logicOperator.apply({ domain: domain }); diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts index f2751ba7b..d2a2aec8e 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts @@ -489,7 +489,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ex:foo ex:path ?o. ex:foo ex:p ex:o. ex:foo ex:p2 ?x. - FILTER(?o>2 || ?x=4 || (?x<3 && ?o<3) ) + FILTER(?o>2 || ?x=4 || (?x<3 && ?o<6) ) } `, { prefixes: { ex: 'http://example.com#' }}); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 16f05d9fa..8f2db363e 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -24,6 +24,7 @@ import type { } from '../lib/solverInterfaces'; import { SparqlRelationOperator } from '../lib/TreeMetadata'; import type { ITreeRelation } from '../lib/TreeMetadata'; +import { Or } from '../lib/LogicOperator'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; @@ -34,14 +35,14 @@ describe('solver function', () => { describe('filterOperatorToSparqlRelationOperator', () => { it('should return the RelationOperator given a string representation', () => { const testTable: [string, SparqlRelationOperator][] = [ - [ '=', SparqlRelationOperator.EqualThanRelation ], - [ '<', SparqlRelationOperator.LessThanRelation ], - [ '<=', SparqlRelationOperator.LessThanOrEqualToRelation ], - [ '>', SparqlRelationOperator.GreaterThanRelation ], - [ '>=', SparqlRelationOperator.GreaterThanOrEqualToRelation ], + ['=', SparqlRelationOperator.EqualThanRelation], + ['<', SparqlRelationOperator.LessThanRelation], + ['<=', SparqlRelationOperator.LessThanOrEqualToRelation], + ['>', SparqlRelationOperator.GreaterThanRelation], + ['>=', SparqlRelationOperator.GreaterThanOrEqualToRelation], ]; - for (const [ value, expectedAnswer ] of testTable) { + for (const [value, expectedAnswer] of testTable) { expect(filterOperatorToSparqlRelationOperator(value)).toBe(expectedAnswer); } }); @@ -90,64 +91,64 @@ describe('solver function', () => { describe('castSparqlRdfTermIntoNumber', () => { it('should return the expected number when given an integer', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - [ '19273', SparqlOperandDataTypes.Integer, 19_273 ], - [ '0', SparqlOperandDataTypes.NonPositiveInteger, 0 ], - [ '-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459 ], - [ '121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321 ], - [ '1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213 ], - [ '283', SparqlOperandDataTypes.NonNegativeInteger, 283 ], - [ '-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831 ], - [ '-1234', SparqlOperandDataTypes.UnsignedInt, -1_234 ], - [ '-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234 ], - [ '1234', SparqlOperandDataTypes.PositiveInteger, 1_234 ], + ['19273', SparqlOperandDataTypes.Integer, 19_273], + ['0', SparqlOperandDataTypes.NonPositiveInteger, 0], + ['-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459], + ['121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321], + ['1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213], + ['283', SparqlOperandDataTypes.NonNegativeInteger, 283], + ['-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831], + ['-1234', SparqlOperandDataTypes.UnsignedInt, -1_234], + ['-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234], + ['1234', SparqlOperandDataTypes.PositiveInteger, 1_234], ]; - for (const [ value, valueType, expectedNumber ] of testTable) { + for (const [value, valueType, expectedNumber] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return undefined if a non integer is pass with SparqlOperandDataTypes integer compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - [ 'asbd', SparqlOperandDataTypes.PositiveInteger ], - [ '', SparqlOperandDataTypes.NegativeInteger ], + ['asbd', SparqlOperandDataTypes.PositiveInteger], + ['', SparqlOperandDataTypes.NegativeInteger], ]; - for (const [ value, valueType ] of testTable) { + for (const [value, valueType] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return undefined if a non fraction is pass with SparqlOperandDataTypes fraction compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - [ 'asbd', SparqlOperandDataTypes.Double ], - [ '', SparqlOperandDataTypes.Float ], + ['asbd', SparqlOperandDataTypes.Double], + ['', SparqlOperandDataTypes.Float], ]; - for (const [ value, valueType ] of testTable) { + for (const [value, valueType] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return the expected number when given an decimal', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - [ '1.1', SparqlOperandDataTypes.Decimal, 1.1 ], - [ '2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321 ], - [ '1234.123', SparqlOperandDataTypes.Double, 1_234.123 ], + ['1.1', SparqlOperandDataTypes.Decimal, 1.1], + ['2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321], + ['1234.123', SparqlOperandDataTypes.Double, 1_234.123], ]; - for (const [ value, valueType, expectedNumber ] of testTable) { + for (const [value, valueType, expectedNumber] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return the expected number given a boolean', () => { const testTable: [string, number][] = [ - [ 'true', 1 ], - [ 'false', 0 ], + ['true', 1], + ['false', 0], ]; - for (const [ value, expectedNumber ] of testTable) { + for (const [value, expectedNumber] of testTable) { expect(castSparqlRdfTermIntoNumber(value, SparqlOperandDataTypes.Boolean)).toBe(expectedNumber); } }); @@ -182,27 +183,27 @@ describe('solver function', () => { const testTable: [SparqlRelationOperator, SolutionInterval][] = [ [ SparqlRelationOperator.GreaterThanRelation, - new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]), + new SolutionInterval([nextUp(value), Number.POSITIVE_INFINITY]), ], [ SparqlRelationOperator.GreaterThanOrEqualToRelation, - new SolutionInterval([ value, Number.POSITIVE_INFINITY ]), + new SolutionInterval([value, Number.POSITIVE_INFINITY]), ], [ SparqlRelationOperator.EqualThanRelation, - new SolutionInterval([ value, value ]), + new SolutionInterval([value, value]), ], [ SparqlRelationOperator.LessThanRelation, - new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]), + new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(value)]), ], [ SparqlRelationOperator.LessThanOrEqualToRelation, - new SolutionInterval([ Number.NEGATIVE_INFINITY, value ]), + new SolutionInterval([Number.NEGATIVE_INFINITY, value]), ], ]; - for (const [ operator, expectedRange ] of testTable) { + for (const [operator, expectedRange] of testTable) { expect(getSolutionInterval(value, operator)).toStrictEqual(expectedRange); } }); @@ -526,30 +527,30 @@ describe('solver function', () => { it(`given an algebra expression with a litteral containing an invalid datatype than should return an unsupported datatype error`, - () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; + () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; - expect(resolveAFilterTerm(expression, operator, variable)) - .toBeInstanceOf(UnsupportedDataTypeError); - }); + expect(resolveAFilterTerm(expression, operator, variable)) + .toBeInstanceOf(UnsupportedDataTypeError); + }); it(`given an algebra expression with a litteral containing a literal that cannot be converted into number should return an unsupported datatype error`, () => { @@ -588,12 +589,11 @@ describe('solver function', () => { const resp = recursifResolve( expression, new SolutionDomain(), - undefined, + new Or(), 'x', - false, ); - const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionInterval([ 2, 2 ])); + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -608,14 +608,12 @@ describe('solver function', () => { const resp = recursifResolve( expression, new SolutionDomain(), - undefined, + new Or(), 'x', - false, ); - const expectedDomain = new SolutionDomain(); - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + expect(resp.isDomainEmpty()).toBe(true) }); it(`given an algebra expression with two logicals operators that are negated @@ -628,12 +626,14 @@ describe('solver function', () => { const resp = recursifResolve( expression, new SolutionDomain(), - undefined, + new Or(), 'x', - false, ); - const expectedDomain = SolutionDomain.newWithInitialValue(new SolutionInterval([ 5, Number.POSITIVE_INFINITY ])); + const expectedDomain = SolutionDomain.newWithInitialIntervals( + [new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(2)]), + new SolutionInterval([nextUp(2), Number.POSITIVE_INFINITY])] + ); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -642,21 +642,20 @@ describe('solver function', () => { should return the valid solution domain`, () => { const expression = translate(` SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2 && ?x>2 || !(?x=3)) + FILTER( ?x=2 && ?x>=1 || !(?x=3)) }`).input.expression; const resp = recursifResolve( expression, new SolutionDomain(), - undefined, + new Or(), 'x', - false, ); - let expectedDomain = SolutionDomain.newWithInitialValue(new SolutionInterval( - [ Number.NEGATIVE_INFINITY, nextDown(3) ], - )); - expectedDomain = expectedDomain.addWithOrOperator(new SolutionInterval([ nextUp(3), Number.POSITIVE_INFINITY ])); + const expectedDomain = SolutionDomain.newWithInitialIntervals([ + new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(3)]), + new SolutionInterval([nextUp(3), Number.POSITIVE_INFINITY]) + ]); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); }); @@ -1041,11 +1040,11 @@ describe('solver function', () => { }; const filterExpression: Algebra.Expression = - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable('x'), - }; + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable('x'), + }; const variable = 'x'; From 447a3a32ac1721e17630272cf29ceae9e4b08d91 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 25 May 2023 15:11:24 +0200 Subject: [PATCH 156/189] useless comment deleted --- .../actor-extract-links-extract-tree/lib/LogicOperator.ts | 1 - packages/actor-extract-links-extract-tree/lib/solver.ts | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index 4bbd772fb..936cc1d7e 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -3,7 +3,6 @@ import { SolutionInterval } from "./SolutionInterval"; import { LogicOperatorSymbol } from './solverInterfaces'; export interface LogicOperator { apply({ interval, domain }: { interval?: SolutionInterval, domain: SolutionDomain }): SolutionDomain, - //merge(domain1: SolutionDomain, domain2: SolutionDomain): SolutionDomain, operatorName(): LogicOperatorSymbol } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 1394c8163..0b46b115c 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -174,9 +174,9 @@ export function recursifResolve( // add the associated interval into the domain in relation to // the logic operator. if (filterExpression.term.value === 'false') { - domain = logicOperator.apply({ interval: A_FALSE_EXPRESSION, domain }); + domain = logicOperator.apply({ subject: A_FALSE_EXPRESSION, domain }); } else if (filterExpression.term.value === 'true') { - domain = logicOperator.apply({ interval: A_TRUE_EXPRESSION, domain }); + domain = logicOperator.apply({ subject: A_TRUE_EXPRESSION, domain }); } else { throw new MisformatedFilterTermError(`The term sent is not a boolean but is this value {${filterExpression.term.value}}`); } @@ -199,7 +199,7 @@ export function recursifResolve( } else { solutionInterval = getSolutionInterval(solverExpression.valueAsNumber, solverExpression.operator)!; } - domain = logicOperator.apply({ interval: solutionInterval, domain }); + domain = logicOperator.apply({ subject: solutionInterval, domain }); } } else if ( filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && @@ -216,7 +216,7 @@ export function recursifResolve( const logicOperator = operatorFactory(logicOperatorSymbol); for (const arg of filterExpression.args) { domain = recursifResolve(arg, domain, logicOperator, variable); - + } if (logicOperator.operatorName() === LogicOperatorSymbol.Not) { domain = logicOperator.apply({ domain: domain }); From 3586f480d45e362deb6f48fbadae03f5e6011494 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 25 May 2023 18:44:24 +0200 Subject: [PATCH 157/189] unable to simply rewrite the query, will try merging domain approach --- .../lib/LogicOperator.ts | 37 +++----- .../lib/TreeMetadata.ts | 5 ++ .../lib/solver.ts | 88 +++++++++++++++---- .../lib/solverInterfaces.ts | 1 + .../test/LogicOperator-test.ts | 44 +--------- .../test/solver-test.ts | 2 +- 6 files changed, 91 insertions(+), 86 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index 936cc1d7e..5694ce7fc 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -2,13 +2,17 @@ import { SolutionDomain } from "./SolutionDomain"; import { SolutionInterval } from "./SolutionInterval"; import { LogicOperatorSymbol } from './solverInterfaces'; export interface LogicOperator { - apply({ interval, domain }: { interval?: SolutionInterval, domain: SolutionDomain }): SolutionDomain, + apply({ interval, domain }: { interval?: SolutionInterval|[SolutionInterval, SolutionInterval], domain: SolutionDomain }): SolutionDomain, operatorName(): LogicOperatorSymbol } export class Or implements LogicOperator { - public apply({ interval, domain }: { interval?: SolutionInterval | undefined; domain: SolutionDomain; }): SolutionDomain { + public apply({ interval, domain }: { interval?: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain; }): SolutionDomain { + if(Array.isArray(interval)){ + domain = this.apply({interval:interval[0],domain}); + return this.apply({interval:interval[1],domain}); + } let newDomain: SolutionInterval[] = []; if (!interval) { return domain; @@ -40,27 +44,12 @@ export class Or implements LogicOperator { } } -export class Not implements LogicOperator { - private readonly orOperator: Or = new Or() - apply({ domain }: { interval?: SolutionInterval | undefined; domain: SolutionDomain; }): SolutionDomain { - let newDomain = new SolutionDomain(); - for (const domainElement of domain.getDomain()) { - // Inverse the domain and add it with care for the overlap - // wich is similar to apply an or operator - for (const el of domainElement.inverse()) { - newDomain = this.orOperator.apply({ interval: el, domain:newDomain }); - } - } - return newDomain; - } - - public operatorName(): LogicOperatorSymbol { - return LogicOperatorSymbol.Not; - } -} - export class And implements LogicOperator { - apply({ interval, domain }: { interval?: SolutionInterval | undefined; domain: SolutionDomain; }): SolutionDomain { + apply({ interval, domain }: { interval?: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain; }): SolutionDomain { + if(Array.isArray(interval)){ + domain = this.apply({interval:interval[0],domain}); + return this.apply({interval:interval[1],domain}); + } let newDomain: SolutionInterval[] = []; if (!interval) { return domain; @@ -94,9 +83,7 @@ export class And implements LogicOperator { const OPERATOR_MAP = new Map( [ [new Or().operatorName(), new Or()], - [new And().operatorName(), new And()], - [new Not().operatorName(), new Not()], - + [new And().operatorName(), new And()], ] ); diff --git a/packages/actor-extract-links-extract-tree/lib/TreeMetadata.ts b/packages/actor-extract-links-extract-tree/lib/TreeMetadata.ts index 22164e713..0bf4cc572 100644 --- a/packages/actor-extract-links-extract-tree/lib/TreeMetadata.ts +++ b/packages/actor-extract-links-extract-tree/lib/TreeMetadata.ts @@ -41,6 +41,11 @@ export enum SparqlRelationOperator { * Similar to GreaterThanRelation. */ EqualThanRelation = 'https://w3id.org/tree#EqualThanRelation', + /** + * Similar to GreaterThanRelation. + * But do not exist yet in the specification + */ + NotEqualThanRelation = '', /** * A contains b iff no points of b lie in the exterior of a, and at least one point * of the interior of b lies in the interior of a. diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 0b46b115c..65c0365e1 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -17,7 +17,7 @@ import type { } from './solverInterfaces'; import { SparqlRelationOperator } from './TreeMetadata'; import type { ITreeRelation } from './TreeMetadata'; -import { And, LogicOperator, Not, Or, operatorFactory } from './LogicOperator'; +import { And, LogicOperator, Or, operatorFactory } from './LogicOperator'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; @@ -102,7 +102,8 @@ export function isBooleanExpressionTreeRelationFilterSolvable({ relation, filter */ export function resolveAFilterTerm(expression: Algebra.Expression, operator: SparqlRelationOperator, - variable: Variable): + variable: Variable, +): ISolverExpression | Error { let rawValue: string | undefined; let valueType: SparqlOperandDataTypes | undefined; @@ -164,8 +165,9 @@ export function resolveAFilterTerm(expression: Algebra.Expression, export function recursifResolve( filterExpression: Algebra.Expression, domain: SolutionDomain, - logicOperator: LogicOperator, + logicOperator: LogicOperator = new Or(), variable: Variable, + negativeExpression: boolean = false, ): SolutionDomain { if (filterExpression.expressionType === Algebra.expressionTypes.TERM @@ -174,9 +176,9 @@ export function recursifResolve( // add the associated interval into the domain in relation to // the logic operator. if (filterExpression.term.value === 'false') { - domain = logicOperator.apply({ subject: A_FALSE_EXPRESSION, domain }); + domain = logicOperator.apply({ interval: A_FALSE_EXPRESSION, domain }); } else if (filterExpression.term.value === 'true') { - domain = logicOperator.apply({ subject: A_TRUE_EXPRESSION, domain }); + domain = logicOperator.apply({ interval: A_TRUE_EXPRESSION, domain }); } else { throw new MisformatedFilterTermError(`The term sent is not a boolean but is this value {${filterExpression.term.value}}`); } @@ -188,18 +190,32 @@ export function recursifResolve( filterExpression.args.length === 2 ) { const rawOperator = filterExpression.operator; - const operator = filterOperatorToSparqlRelationOperator(rawOperator); - if (operator) { + let operator = filterOperatorToSparqlRelationOperator(rawOperator); + if (operator && logicOperator.operatorName()!= LogicOperatorSymbol.Not) { + if (negativeExpression) { + operator = reverseOperator(operator) + if (operator=== undefined) { + throw TypeError('The operator cannot be reversed') + } + const newLogicOperator = reverseLogicOperator(logicOperator) + if (!newLogicOperator) { + throw TypeError('The logic operator cannot be reversed') + } + logicOperator = newLogicOperator; + } const solverExpression = resolveAFilterTerm(filterExpression, operator, variable); - let solutionInterval: SolutionInterval | undefined; + let solutionInterval: SolutionInterval | [SolutionInterval, SolutionInterval]|undefined; if (solverExpression instanceof MissMatchVariableError) { solutionInterval = A_TRUE_EXPRESSION; } else if (solverExpression instanceof Error) { throw solverExpression; } else { - solutionInterval = getSolutionInterval(solverExpression.valueAsNumber, solverExpression.operator)!; + solutionInterval = getSolutionInterval(solverExpression.valueAsNumber, solverExpression.operator); + if (!solutionInterval){ + throw TypeError('The operator is not supported'); + } } - domain = logicOperator.apply({ subject: solutionInterval, domain }); + domain = logicOperator.apply({ interval: solutionInterval, domain }); } } else if ( filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && @@ -213,14 +229,15 @@ export function recursifResolve( // We prepare the next recursion and we compute the accumulation of results. const logicOperatorSymbol = LogicOperatorReversed.get(filterExpression.operator); if (logicOperatorSymbol) { - const logicOperator = operatorFactory(logicOperatorSymbol); for (const arg of filterExpression.args) { - domain = recursifResolve(arg, domain, logicOperator, variable); - - } - if (logicOperator.operatorName() === LogicOperatorSymbol.Not) { - domain = logicOperator.apply({ domain: domain }); + if (logicOperatorSymbol === LogicOperatorSymbol.Not) { + domain = recursifResolve(arg, domain, logicOperator, variable, !negativeExpression); + }else{ + const logicOperator = operatorFactory(logicOperatorSymbol); + domain = recursifResolve(arg, domain, logicOperator, variable, negativeExpression); + } } + } } return domain; @@ -280,7 +297,7 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { * @param {SparqlRelationOperator} operator * @returns {SolutionInterval | undefined} The solution range associated with the value and the operator. */ -export function getSolutionInterval(value: number, operator: SparqlRelationOperator): SolutionInterval | undefined { +export function getSolutionInterval(value: number, operator: SparqlRelationOperator): SolutionInterval | [SolutionInterval, SolutionInterval]| undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: return new SolutionInterval([nextUp(value), Number.POSITIVE_INFINITY]); @@ -292,6 +309,11 @@ export function getSolutionInterval(value: number, operator: SparqlRelationOpera return new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(value)]); case SparqlRelationOperator.LessThanOrEqualToRelation: return new SolutionInterval([Number.NEGATIVE_INFINITY, value]); + case SparqlRelationOperator.NotEqualThanRelation: + return [ + new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(value)]), + new SolutionInterval([nextUp(value),Number.POSITIVE_INFINITY]) + ]; default: // Not an operator that is compatible with number. break; @@ -383,3 +405,35 @@ export function filterOperatorToSparqlRelationOperator(filterOperator: string): return undefined; } } + +export function reverseOperator(operator: SparqlRelationOperator): SparqlRelationOperator | undefined { + switch (operator) { + case SparqlRelationOperator.LessThanRelation: + return SparqlRelationOperator.GreaterThanOrEqualToRelation + case SparqlRelationOperator.LessThanOrEqualToRelation: + return SparqlRelationOperator.GreaterThanRelation + case SparqlRelationOperator.GreaterThanRelation: + return SparqlRelationOperator.LessThanOrEqualToRelation + case SparqlRelationOperator.GreaterThanOrEqualToRelation: + return SparqlRelationOperator.LessThanRelation + case SparqlRelationOperator.EqualThanRelation: + return SparqlRelationOperator.NotEqualThanRelation + case SparqlRelationOperator.NotEqualThanRelation: + return SparqlRelationOperator.EqualThanRelation + + default: + return undefined + } +} + +export function reverseLogicOperator(operator: LogicOperator): LogicOperator | undefined { + switch (operator.operatorName()) { + case LogicOperatorSymbol.And: + return new Or() + case LogicOperatorSymbol.Or: + return new And() + // the reverse is making an assetion and this operator is useless + case LogicOperatorSymbol.Not: + return undefined + } +} \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index 62e39bd48..1d7de7c7d 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -39,6 +39,7 @@ export enum LogicOperatorSymbol { And = '&&', Or = '||', Not = '!', + Assert='' } /** diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index e0647a42b..8d0b6de1e 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -1,4 +1,4 @@ -import { Or, Not, And } from '../lib/LogicOperator'; +import { Or, And } from '../lib/LogicOperator'; import { SolutionInterval } from '../lib/SolutionInterval'; import { LogicOperatorSymbol } from '../lib/solverInterfaces'; import { SolutionDomain } from '../lib/SolutionDomain'; @@ -89,48 +89,6 @@ describe('LogicOperator', () => { }); }); - describe('Not', () => { - const not = new Not(); - describe('operatorName', () => { - it('Should return the associated operator name', () => { - expect(not.operatorName()).toBe(LogicOperatorSymbol.Not); - }); - }); - describe('apply', () => { - it('given a domain with one range should return the inverse of the domain', () => { - const solutionInterval = new SolutionInterval([0, 1]); - const solutionDomain = SolutionDomain.newWithInitialIntervals(solutionInterval); - - const expectedDomain = [ - new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(0)]), - new SolutionInterval([nextUp(1), Number.POSITIVE_INFINITY]), - ]; - const newDomain = not.apply({ domain: solutionDomain }); - - expect(newDomain.getDomain().length).toBe(2); - expect(newDomain.getDomain()).toStrictEqual(expectedDomain); - }); - - it('given a domain with multiple range should return the inverted domain', () => { - let domain = new SolutionDomain(); - const intervals = [ - new SolutionInterval([0, 1]), - new SolutionInterval([2, 2]), - new SolutionInterval([44, 55]), - ]; - const expectedDomain = [ - new SolutionInterval([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]), - ]; - - for (const interval of intervals) { - domain = new Or().apply({ interval: interval, domain }) - } - domain = not.apply({ domain }); - - expect(domain.getDomain()).toStrictEqual(expectedDomain); - }); - }); - }); describe('And', () => { const and = new And(); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 8f2db363e..8c4e68524 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -847,7 +847,7 @@ describe('solver function', () => { const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z - FILTER( !(?x=2 && ?x>5) || ?x < 88.3) + FILTER( !(?x=100 && ?x>5) || ?x < 88.3) }`).input.expression; const variable = 'x'; From 8964299462736fce11c1441378683de90e865f22 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 26 May 2023 16:34:44 +0200 Subject: [PATCH 158/189] equal operator and multiple apply And. --- .../lib/LogicOperator.ts | 17 +++- .../lib/SolutionDomain.ts | 27 +++++- .../lib/TreeMetadata.ts | 2 +- .../lib/solver.ts | 1 + .../test/SolutionDomain-test.ts | 95 ++++++++++++++++--- 5 files changed, 123 insertions(+), 19 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index 5694ce7fc..493102ba5 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -47,8 +47,21 @@ export class Or implements LogicOperator { export class And implements LogicOperator { apply({ interval, domain }: { interval?: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain; }): SolutionDomain { if(Array.isArray(interval)){ - domain = this.apply({interval:interval[0],domain}); - return this.apply({interval:interval[1],domain}); + const testDomain1 = this.apply({interval:interval[0],domain}); + const testDomain2 = this.apply({interval:interval[1],domain}); + + const cannotAddDomain1 = domain.equal(testDomain1) + const cannotAddDomain2 = domain.equal(testDomain2) + + if(cannotAddDomain1 && cannotAddDomain2){ + return domain + }else if(!cannotAddDomain1 && cannotAddDomain2){ + return testDomain1 + } else if(cannotAddDomain1 && !cannotAddDomain2){ + return testDomain2 + } else { + return new Or().apply({interval:interval[1], domain:testDomain1}) + } } let newDomain: SolutionInterval[] = []; if (!interval) { diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index ae61b7beb..573845afc 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -18,6 +18,23 @@ export class SolutionDomain { return this.domain; } + public equal(other: SolutionDomain): boolean { + if (this.domain.length !== other.domain.length) { + return false + } + + for (const i in this.domain) { + if (!(this.domain[i].lower === other.domain[i].lower + && + this.domain[i].upper === other.domain[i].upper + )) { + return false; + } + } + + return true + } + /** * Check whether the domain is empty * @returns {boolean} Return true if the domain is empty @@ -43,12 +60,12 @@ export class SolutionDomain { // We keep the domain sorted initialIntervals.sort(SolutionDomain.sortDomainRangeByLowerBound); newSolutionDomain.domain = initialIntervals; - if(newSolutionDomain.isThereOverlapInsideDomain()){ + if (newSolutionDomain.isThereOverlapInsideDomain()) { throw new RangeError('There is overlap inside the domain.') } } else { if (!initialIntervals.isEmpty) { - newSolutionDomain.domain = [initialIntervals]; + newSolutionDomain.domain = [initialIntervals]; } } Object.freeze(newSolutionDomain); @@ -69,9 +86,9 @@ export class SolutionDomain { return 1; } - private isThereOverlapInsideDomain(): boolean{ - for (let i=0;i { expect(solutionDomain.getDomain().length).toBe(0); }); }); + describe('equal', () => { + it('should return equal when two domains have the same interval', () => { + const domain1 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([0, 1])); + const domain2 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([0, 1])); + expect(domain1.equal(domain2)).toBe(true); + }); + + it('should return not equal when two domains have the a different interval', () => { + const domain1 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([0, 1])); + const domain2 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([0, 2])); + expect(domain1.equal(domain2)).toBe(false); + }); + + it('should return equal when two domains are empty', () => { + const domain1 = new SolutionDomain(); + const domain2 = new SolutionDomain(); + expect(domain1.equal(domain2)).toBe(true); + }); + + it('should return not equal when one domain is empty and the other have one interval', () => { + const domain1 = new SolutionDomain(); + const domain2 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([0, 2])); + expect(domain1.equal(domain2)).toBe(false); + }); + + it('should return not equal when one domain is empty and the other have multiple intervals', () => { + const domain1 = new SolutionDomain(); + const domain2 = SolutionDomain.newWithInitialIntervals( + [ + new SolutionInterval([0, 1]), + new SolutionInterval([2, 3]), + new SolutionInterval([4, 5]) + ] + ); + expect(domain1.equal(domain2)).toBe(false); + }); + + it('should return equal when two domains have the same intervals', () => { + const domain1 = SolutionDomain.newWithInitialIntervals( + [ + new SolutionInterval([0, 1]), + new SolutionInterval([2, 3]), + new SolutionInterval([4, 5]) + ] + ); + const domain2 = SolutionDomain.newWithInitialIntervals( + [ + new SolutionInterval([0, 1]), + new SolutionInterval([2, 3]), + new SolutionInterval([4, 5]) + ] + ); + expect(domain1.equal(domain2)).toBe(true); + }); + + it('should return not equal when two domains have the different intervals', () => { + const domain1 = SolutionDomain.newWithInitialIntervals( + [ + new SolutionInterval([0, 1]), + new SolutionInterval([2, 3]), + new SolutionInterval([4, 5]) + ] + ); + const domain2 = SolutionDomain.newWithInitialIntervals( + [ + new SolutionInterval([0, 1]), + new SolutionInterval([6, 7]), + new SolutionInterval([4, 5]) + ] + ); + expect(domain1.equal(domain2)).toBe(false); + }); + }); describe('newWithInitialIntervals', () => { it('should create a solution domain with an initial value', () => { - const solutionRange = new SolutionInterval([ 0, 1 ]); + const solutionRange = new SolutionInterval([0, 1]); const solutionDomain = SolutionDomain.newWithInitialIntervals(solutionRange); expect(solutionDomain.getDomain().length).toBe(1); @@ -21,10 +94,10 @@ describe('SolutionDomain', () => { it('should create a solution domain with multiple initial value', () => { const solutionIntervals = [ - new SolutionInterval([ 0, 1 ]), - new SolutionInterval([ 2, 3 ]), - new SolutionInterval([ -33, -3 ]), - new SolutionInterval([ 100, 3000 ]) + new SolutionInterval([0, 1]), + new SolutionInterval([2, 3]), + new SolutionInterval([-33, -3]), + new SolutionInterval([100, 3000]) ]; const solutionDomain = SolutionDomain.newWithInitialIntervals(solutionIntervals); @@ -34,15 +107,15 @@ describe('SolutionDomain', () => { it('should throw an error when creating a solution domain with multiple intervals overlaping', () => { const solutionIntervals = [ - new SolutionInterval([ 0, 1 ]), - new SolutionInterval([ 2, 3 ]), - new SolutionInterval([ 2, 10 ]), + new SolutionInterval([0, 1]), + new SolutionInterval([2, 3]), + new SolutionInterval([2, 10]), ]; - expect(()=>{ + expect(() => { SolutionDomain.newWithInitialIntervals(solutionIntervals); }).toThrow(RangeError); }); - + }); describe('isDomainEmpty', () => { @@ -53,7 +126,7 @@ describe('SolutionDomain', () => { }); it('should return false when the domain is not empty', () => { - const domain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 0, 1 ])); + const domain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([0, 1])); expect(domain.isDomainEmpty()).toBe(false); }); From 267d963390322a8fc92ad5d3a2f2cfecb196c702 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 26 May 2023 17:03:46 +0200 Subject: [PATCH 159/189] filter rewritting function implemented --- .../lib/solver.ts | 80 +++++++++++++++---- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 97a85cb76..0d24aa516 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -167,7 +167,6 @@ export function recursifResolve( domain: SolutionDomain, logicOperator: LogicOperator = new Or(), variable: Variable, - negativeExpression: boolean = false, ): SolutionDomain { if (filterExpression.expressionType === Algebra.expressionTypes.TERM @@ -192,17 +191,6 @@ export function recursifResolve( const rawOperator = filterExpression.operator; let operator = filterOperatorToSparqlRelationOperator(rawOperator); if (operator && logicOperator.operatorName()!= LogicOperatorSymbol.Not) { - if (negativeExpression) { - operator = reverseOperator(operator) - if (operator=== undefined) { - throw TypeError('The operator cannot be reversed') - } - const newLogicOperator = reverseLogicOperator(logicOperator) - if (!newLogicOperator) { - throw TypeError('The logic operator cannot be reversed') - } - logicOperator = newLogicOperator; - } const solverExpression = resolveAFilterTerm(filterExpression, operator, variable); let solutionInterval: SolutionInterval | [SolutionInterval, SolutionInterval]|undefined; if (solverExpression instanceof MissMatchVariableError) { @@ -232,10 +220,11 @@ export function recursifResolve( for (const arg of filterExpression.args) { console.log(arg) if (logicOperatorSymbol === LogicOperatorSymbol.Not) { - domain = recursifResolve(arg, domain, logicOperator, variable, !negativeExpression); + inverseFilter(arg); + domain = recursifResolve(arg, domain, logicOperator, variable); }else{ const logicOperator = operatorFactory(logicOperatorSymbol); - domain = recursifResolve(arg, domain, logicOperator, variable, negativeExpression); + domain = recursifResolve(arg, domain, logicOperator, variable); } } @@ -402,6 +391,38 @@ export function filterOperatorToSparqlRelationOperator(filterOperator: string): return SparqlRelationOperator.GreaterThanRelation; case '>=': return SparqlRelationOperator.GreaterThanOrEqualToRelation; + case '!=': + return SparqlRelationOperator.NotEqualThanRelation; + default: + return undefined; + } +} + +export function reverseRawLogicOperator(logicOperator: string) :string|undefined{ + switch(logicOperator){ + case '&&': + return '||' + case '||': + return '&&' + case '!': + return undefined; + default: + return undefined + } +} + +export function reverseRawOperator(filterOperator: string): string | undefined { + switch (filterOperator) { + case '=': + return '!=' + case '<': + return '>=' + case '<=': + return '>' + case '>': + return '<=' + case '>=': + return '<' default: return undefined; } @@ -437,4 +458,35 @@ export function reverseLogicOperator(operator: LogicOperator): LogicOperator | u case LogicOperatorSymbol.Not: return undefined } +} + +export function inverseFilter(filterExpression: Algebra.Expression){ + if (filterExpression.expressionType === Algebra.expressionTypes.TERM + ) { + if (filterExpression.term.value === 'false') { + filterExpression.term.value = 'true'; + } else if (filterExpression.term.value === 'true') { + filterExpression.term.value = 'false'; + } else { + throw new MisformatedFilterTermError(`The term sent is not a boolean but is this value {${filterExpression.term.value}}`); + } + }else if ( + // If it's an array of terms then we should be able to create a solver expression. + // Given the resulting solver expression we can calculate a solution interval + // that we will add to the domain with regards to the logic operator. + filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && + filterExpression.args.length === 2 + ) { + filterExpression.operator = reverseRawOperator(filterExpression.operator) + + }else{ + for (const arg of filterExpression.args) { + const reversedOperator = reverseRawLogicOperator(filterExpression.operator); + if(reversedOperator){ + filterExpression.operator = reversedOperator; + } + inverseFilter(arg); + } + } + } \ No newline at end of file From db9c27034c3508875aa4c5811c8b2c100f92dff2 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Sat, 27 May 2023 06:49:47 +0200 Subject: [PATCH 160/189] fix test with not operator --- .../lib/solver.ts | 4 ++ .../test/ActorExtractLinksTree-test.ts | 56 +++---------------- .../test/solver-test.ts | 2 +- 3 files changed, 14 insertions(+), 48 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 0d24aa516..0314d08f5 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -480,6 +480,10 @@ export function inverseFilter(filterExpression: Algebra.Expression){ filterExpression.operator = reverseRawOperator(filterExpression.operator) }else{ + const reversedOperator = reverseRawLogicOperator(filterExpression.operator); + if(reversedOperator){ + filterExpression.operator = reversedOperator; + } for (const arg of filterExpression.args) { const reversedOperator = reverseRawLogicOperator(filterExpression.operator); if(reversedOperator){ diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index dba9f0d1c..fe5d51dbd 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -423,54 +423,16 @@ describe('ActorExtractLinksExtractLinksTree', () => { ]); - const bgp = [ - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:path'), DF.variable('o')), - DF.quad(DF.namedNode('ex:foo'), DF.namedNode('ex:p'), DF.namedNode('ex:o')), - DF.quad(DF.namedNode('ex:bar'), DF.namedNode('ex:p2'), DF.namedNode('ex:o2')), - DF.quad(DF.namedNode('ex:too'), DF.namedNode('ex:p3'), DF.namedNode('ex:o3')), - ]; - const filterExpression = { - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - type: Algebra.types.EXPRESSION, - args: [ - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Variable', - value: 'o', - }, - }, - { - expressionType: Algebra.expressionTypes.TERM, - type: Algebra.types.EXPRESSION, - term: { - termType: 'Literal', - langugage: '', - value: '5', - datatype: { - termType: 'namedNode', - value: 'http://www.w3.org/2001/XMLSchema#integer', - }, - }, - }, - ], - }; + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o . + ex:foo ex:p ex:o . + ex:bar ex:p2 ex:o2 . + ex:too ex:p3 ex:o3 . + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' }}); - const query = { - type: Algebra.types.PROJECT, - input: { - type: Algebra.types.FILTER, - expression: filterExpression, - input: { - input: { - type: Algebra.types.JOIN, - input: bgp, - }, - }, - }, - }; const contextWithQuery = new ActionContext({ [KeysRdfResolveQuadPattern.source.name]: treeUrl, [KeysInitQuery.query.name]: query, diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 8c4e68524..4a3f3d251 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -835,7 +835,7 @@ describe('solver function', () => { it(`should return false when the filter has a possible solution but the addition of the relation produce no possible solution`, () => { const relation: ITreeRelation = { - type: SparqlRelationOperator.GreaterThanOrEqualToRelation, + type: SparqlRelationOperator.EqualThanRelation, remainingItems: 10, path: 'ex:path', value: { From 8f12850bae1232385a60821b9291fd186599bea6 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Sat, 27 May 2023 06:55:14 +0200 Subject: [PATCH 161/189] fix current unit tests. --- packages/actor-extract-links-extract-tree/lib/solver.ts | 1 - .../test/SolutionInterval-test.ts | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 0314d08f5..1ba26d418 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -218,7 +218,6 @@ export function recursifResolve( const logicOperatorSymbol = LogicOperatorReversed.get(filterExpression.operator); if (logicOperatorSymbol) { for (const arg of filterExpression.args) { - console.log(arg) if (logicOperatorSymbol === LogicOperatorSymbol.Not) { inverseFilter(arg); domain = recursifResolve(arg, domain, logicOperator, variable); diff --git a/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts index 50b2a7a8b..c59a04bed 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts @@ -401,7 +401,11 @@ describe('SolutionRange', () => { const aSolutionInterval = new SolutionInterval([ 0, 20 ]); const aSecondSolutionInterval = new SolutionInterval([ 30, 40 ]); - expect(SolutionInterval.getIntersection(aSolutionInterval, aSecondSolutionInterval)).toBeUndefined(); + const nonOverlappingInterval = SolutionInterval.getIntersection(aSolutionInterval, aSecondSolutionInterval); + + expect(nonOverlappingInterval.isEmpty).toBe(true); + expect(nonOverlappingInterval.lower).toBe(Number.NaN); + expect(nonOverlappingInterval.upper).toBe(Number.NaN); }); it('given two range when one is inside the other should return the range at the inside', () => { From 73e2e36b864c210372a046fb2f8b6b48682f5a07 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Sat, 27 May 2023 07:27:45 +0200 Subject: [PATCH 162/189] fix simple double equal assertion --- .../lib/solver.ts | 15 +++++++++++++-- .../lib/solverInterfaces.ts | 2 +- .../test/solver-test.ts | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 1ba26d418..7a3409c3b 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -218,10 +218,17 @@ export function recursifResolve( const logicOperatorSymbol = LogicOperatorReversed.get(filterExpression.operator); if (logicOperatorSymbol) { for (const arg of filterExpression.args) { + // To solve the not operation we rewrite the path of filter expression to reverse every operation + // e.g, = : != ; > : <= if (logicOperatorSymbol === LogicOperatorSymbol.Not) { inverseFilter(arg); domain = recursifResolve(arg, domain, logicOperator, variable); - }else{ + }else if( logicOperatorSymbol === LogicOperatorSymbol.Assert){ + domain = recursifResolve(arg, domain, logicOperator, variable); + + } + + else{ const logicOperator = operatorFactory(logicOperatorSymbol); domain = recursifResolve(arg, domain, logicOperator, variable); } @@ -404,7 +411,9 @@ export function reverseRawLogicOperator(logicOperator: string) :string|undefined case '||': return '&&' case '!': - return undefined; + return 'E'; + case 'E': + return '!'; default: return undefined } @@ -414,6 +423,8 @@ export function reverseRawOperator(filterOperator: string): string | undefined { switch (filterOperator) { case '=': return '!=' + case '!=': + return '=' case '<': return '>=' case '<=': diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index 1d7de7c7d..26a3675b7 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -39,7 +39,7 @@ export enum LogicOperatorSymbol { And = '&&', Or = '||', Not = '!', - Assert='' + Assert='E' } /** diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 4a3f3d251..174796b27 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -598,6 +598,24 @@ describe('solver function', () => { expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); + it('given an algebra expression with a double negation it should cancel it', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(!(!(?x=2 && ?x<5))) + }`).input.expression; + + const resp = recursifResolve( + expression, + new SolutionDomain(), + new Or(), + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + it(`given an algebra expression with two logicals operators that cannot be satified should return an empty domain`, () => { const expression = translate(` From d95bb502b26067c96f79e89d55911c4ff22cce67 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Sat, 27 May 2023 09:41:21 +0200 Subject: [PATCH 163/189] unit test added for multiple negation --- .../lib/solver.ts | 2 +- .../test/solver-test.ts | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 7a3409c3b..88a302417 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -164,7 +164,7 @@ export function resolveAFilterTerm(expression: Algebra.Expression, */ export function recursifResolve( filterExpression: Algebra.Expression, - domain: SolutionDomain, + domain: SolutionDomain = new SolutionDomain(), logicOperator: LogicOperator = new Or(), variable: Variable, ): SolutionDomain { diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 174796b27..6d6671a35 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -598,6 +598,24 @@ describe('solver function', () => { expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); + it('given an algebra expression with two logicals operators with a double negation should return the valid solution domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(!(?x=2)) && ?x<5) + }`).input.expression; + + const resp = recursifResolve( + expression, + new SolutionDomain(), + new Or(), + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + it('given an algebra expression with a double negation it should cancel it', () => { const expression = translate(` SELECT * WHERE { ?x ?y ?z @@ -656,6 +674,28 @@ describe('solver function', () => { expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); + it(`given an algebra expression with two logicals operators that are triple negated + should return the valid solution domain`, () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(!(!(?x=2 && ?x<5)))) + }`).input.expression; + + const resp = recursifResolve( + expression, + new SolutionDomain(), + new Or(), + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals( + [new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(2)]), + new SolutionInterval([nextUp(2), Number.POSITIVE_INFINITY])] + ); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + it(`given an algebra expression with three logicals operators where the priority of operation should start with the not operator than should return the valid solution domain`, () => { const expression = translate(` From 83e68583454b2e686fd2cdf5859cb49eb7cb8a84 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 30 May 2023 09:30:27 +0200 Subject: [PATCH 164/189] lint-fix partial --- .../lib/FilterNode.ts | 2 +- .../lib/LogicOperator.ts | 176 +++++---- .../lib/SolutionDomain.ts | 26 +- .../lib/TreeMetadata.ts | 4 +- .../lib/solver.ts | 165 +++++---- .../lib/solverInterfaces.ts | 2 +- .../test/LogicOperator-test.ts | 333 +++++++++--------- .../test/SolutionDomain-test.ts | 70 ++-- .../test/solver-test.ts | 167 +++++---- 9 files changed, 462 insertions(+), 483 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts index 367c24a0f..4163752ab 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/FilterNode.ts @@ -83,7 +83,7 @@ export class FilterNode { // For all the variable check if one is has a possible solutions. for (const variable of variables) { filtered = filtered || isBooleanExpressionTreeRelationFilterSolvable( - { relation, filterExpression: filterOperation, variable }, + { relation, filterExpression: structuredClone(filterOperation), variable }, ); } diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index 493102ba5..cd55467de 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -1,109 +1,107 @@ -import { SolutionDomain } from "./SolutionDomain"; -import { SolutionInterval } from "./SolutionInterval"; +import { SolutionDomain } from './SolutionDomain'; +import { SolutionInterval } from './SolutionInterval'; import { LogicOperatorSymbol } from './solverInterfaces'; export interface LogicOperator { - apply({ interval, domain }: { interval?: SolutionInterval|[SolutionInterval, SolutionInterval], domain: SolutionDomain }): SolutionDomain, - operatorName(): LogicOperatorSymbol + apply: ({ interval, domain }: { interval?: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }) => SolutionDomain; + operatorName: () => LogicOperatorSymbol; } - export class Or implements LogicOperator { - public apply({ interval, domain }: { interval?: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain; }): SolutionDomain { - if(Array.isArray(interval)){ - domain = this.apply({interval:interval[0],domain}); - return this.apply({interval:interval[1],domain}); - } - let newDomain: SolutionInterval[] = []; - if (!interval) { - return domain; - } - let currentInterval = interval; + public apply({ interval, domain }: { interval?: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { + if (Array.isArray(interval)) { + domain = this.apply({ interval: interval[0], domain }); + return this.apply({ interval: interval[1], domain }); + } + let newDomain: SolutionInterval[] = []; + if (!interval) { + return domain; + } + let currentInterval = interval; - // We iterate over all the domain - newDomain = domain.getDomain().filter(el => { - // We check if we can fuse the new range with the current range - // let's not forget that the domain is sorted by the lowest bound - // of the SolutionRange - const resp = SolutionInterval.fuseRange(el, currentInterval); - if (resp.length === 1) { - // If we fuse the range and consider this new range as our current range - // and we delete the old range from the domain as we now have a new range that contained the old - currentInterval = resp[0]; - return false; - } - return true; - }); - // We add the potentialy fused range. - newDomain.push(currentInterval); + // We iterate over all the domain + newDomain = domain.getDomain().filter(el => { + // We check if we can fuse the new range with the current range + // let's not forget that the domain is sorted by the lowest bound + // of the SolutionRange + const resp = SolutionInterval.fuseRange(el, currentInterval); + if (resp.length === 1) { + // If we fuse the range and consider this new range as our current range + // and we delete the old range from the domain as we now have a new range that contained the old + currentInterval = resp[0]; + return false; + } + return true; + }); + // We add the potentialy fused range. + newDomain.push(currentInterval); - return SolutionDomain.newWithInitialIntervals(newDomain); - } + return SolutionDomain.newWithInitialIntervals(newDomain); + } - public operatorName(): LogicOperatorSymbol { - return LogicOperatorSymbol.Or; - } + public operatorName(): LogicOperatorSymbol { + return LogicOperatorSymbol.Or; + } } export class And implements LogicOperator { - apply({ interval, domain }: { interval?: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain; }): SolutionDomain { - if(Array.isArray(interval)){ - const testDomain1 = this.apply({interval:interval[0],domain}); - const testDomain2 = this.apply({interval:interval[1],domain}); - - const cannotAddDomain1 = domain.equal(testDomain1) - const cannotAddDomain2 = domain.equal(testDomain2) + apply({ interval, domain }: { interval?: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { + if (Array.isArray(interval)) { + const testDomain1 = this.apply({ interval: interval[0], domain }); + const testDomain2 = this.apply({ interval: interval[1], domain }); - if(cannotAddDomain1 && cannotAddDomain2){ - return domain - }else if(!cannotAddDomain1 && cannotAddDomain2){ - return testDomain1 - } else if(cannotAddDomain1 && !cannotAddDomain2){ - return testDomain2 - } else { - return new Or().apply({interval:interval[1], domain:testDomain1}) - } - } - let newDomain: SolutionInterval[] = []; - if (!interval) { - return domain; - } - // If the domain is empty then simply add the new range - if (domain.getDomain().length === 0) { - newDomain.push(interval); - return SolutionDomain.newWithInitialIntervals(interval); - } - // Considering the current domain if there is an intersection - // add the intersection to the new domain - domain.getDomain().forEach(el => { - const intersection = SolutionInterval.getIntersection(el, interval); - if (!intersection.isEmpty) { - newDomain.push(intersection); - } - }); + const cannotAddDomain1 = domain.equal(testDomain1); + const cannotAddDomain2 = domain.equal(testDomain2); - if (newDomain.length===0){ - newDomain.push(new SolutionInterval([])); - } - - return SolutionDomain.newWithInitialIntervals(newDomain); + if (cannotAddDomain1 && cannotAddDomain2) { + return domain; + } if (!cannotAddDomain1 && cannotAddDomain2) { + return testDomain1; + } if (cannotAddDomain1 && !cannotAddDomain2) { + return testDomain2; + } + return new Or().apply({ interval: interval[1], domain: testDomain1 }); } - public operatorName(): LogicOperatorSymbol { - return LogicOperatorSymbol.And; + const newDomain: SolutionInterval[] = []; + if (!interval) { + return domain; + } + // If the domain is empty then simply add the new range + if (domain.getDomain().length === 0) { + newDomain.push(interval); + return SolutionDomain.newWithInitialIntervals(interval); + } + // Considering the current domain if there is an intersection + // add the intersection to the new domain + domain.getDomain().forEach(el => { + const intersection = SolutionInterval.getIntersection(el, interval); + if (!intersection.isEmpty) { + newDomain.push(intersection); + } + }); + + if (newDomain.length === 0) { + newDomain.push(new SolutionInterval([])); } -} + return SolutionDomain.newWithInitialIntervals(newDomain); + } + + public operatorName(): LogicOperatorSymbol { + return LogicOperatorSymbol.And; + } +} const OPERATOR_MAP = new Map( - [ - [new Or().operatorName(), new Or()], - [new And().operatorName(), new And()], - ] + [ + [ new Or().operatorName(), new Or() ], + [ new And().operatorName(), new And() ], + ], ); -export function operatorFactory(operatorSymbol: LogicOperatorSymbol): LogicOperator{ - const operator = OPERATOR_MAP.get(operatorSymbol); - if (!operator){ - throw new RangeError("The operator doesn't exist or is not supported.") - } - return operator; -} \ No newline at end of file +export function operatorFactory(operatorSymbol: LogicOperatorSymbol): LogicOperator { + const operator = OPERATOR_MAP.get(operatorSymbol); + if (!operator) { + throw new RangeError('The operator doesn\'t exist or is not supported.'); + } + return operator; +} diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 573845afc..5ac005afe 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -1,4 +1,4 @@ -import { SolutionInterval } from './SolutionInterval'; +import type { SolutionInterval } from './SolutionInterval'; /** * A class representing the domain of a solution of system of boolean equation. @@ -20,19 +20,18 @@ export class SolutionDomain { public equal(other: SolutionDomain): boolean { if (this.domain.length !== other.domain.length) { - return false + return false; } for (const i in this.domain) { - if (!(this.domain[i].lower === other.domain[i].lower - && + if (!(this.domain[i].lower === other.domain[i].lower && this.domain[i].upper === other.domain[i].upper )) { return false; } } - return true + return true; } /** @@ -41,12 +40,11 @@ export class SolutionDomain { */ public isDomainEmpty(): boolean { if (this.domain.length === 0) { - return true - } else if (this.domain.length === 1 && this.domain[0].isEmpty) { - return true - } else { - return false + return true; + } if (this.domain.length === 1 && this.domain[0].isEmpty) { + return true; } + return false; } /** @@ -61,12 +59,10 @@ export class SolutionDomain { initialIntervals.sort(SolutionDomain.sortDomainRangeByLowerBound); newSolutionDomain.domain = initialIntervals; if (newSolutionDomain.isThereOverlapInsideDomain()) { - throw new RangeError('There is overlap inside the domain.') - } - } else { - if (!initialIntervals.isEmpty) { - newSolutionDomain.domain = [initialIntervals]; + throw new RangeError('There is overlap inside the domain.'); } + } else if (!initialIntervals.isEmpty) { + newSolutionDomain.domain = [ initialIntervals ]; } Object.freeze(newSolutionDomain); Object.freeze(newSolutionDomain.domain); diff --git a/packages/actor-extract-links-extract-tree/lib/TreeMetadata.ts b/packages/actor-extract-links-extract-tree/lib/TreeMetadata.ts index 7298d504a..be35d2e93 100644 --- a/packages/actor-extract-links-extract-tree/lib/TreeMetadata.ts +++ b/packages/actor-extract-links-extract-tree/lib/TreeMetadata.ts @@ -41,11 +41,11 @@ export enum SparqlRelationOperator { * Similar to GreaterThanRelation. */ EqualThanRelation = 'https://w3id.org/tree#EqualThanRelation', - /** + /** * Similar to GreaterThanRelation. * But do not exist yet in the specification */ - NotEqualThanRelation = '!=', + NotEqualThanRelation = '!=', /** * A contains b iff no points of b lie in the exterior of a, and at least one point * of the interior of b lies in the interior of a. diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 88a302417..6fa0d0d6a 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -5,6 +5,8 @@ import { MisformatedFilterTermError, UnsupportedDataTypeError, } from './error'; +import type { LogicOperator } from './LogicOperator'; +import { And, Or, operatorFactory } from './LogicOperator'; import { SolutionDomain } from './SolutionDomain'; import { SolutionInterval } from './SolutionInterval'; import { @@ -17,13 +19,12 @@ import type { } from './solverInterfaces'; import { SparqlRelationOperator } from './TreeMetadata'; import type { ITreeRelation } from './TreeMetadata'; -import { And, LogicOperator, Or, operatorFactory } from './LogicOperator'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; const A_TRUE_EXPRESSION: SolutionInterval = new SolutionInterval( - [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY], + [ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ], ); const A_FALSE_EXPRESSION: SolutionInterval = new SolutionInterval([]); @@ -87,7 +88,7 @@ export function isBooleanExpressionTreeRelationFilterSolvable({ relation, filter } // Evaluate the solution domain when adding the relation - solutionDomain = new And().apply({ interval: relationSolutionInterval, domain: solutionDomain }) + solutionDomain = new And().apply({ interval: relationSolutionInterval, domain: solutionDomain }); // If there is a possible solution we don't filter the link return !solutionDomain.isDomainEmpty(); @@ -102,8 +103,7 @@ export function isBooleanExpressionTreeRelationFilterSolvable({ relation, filter */ export function resolveAFilterTerm(expression: Algebra.Expression, operator: SparqlRelationOperator, - variable: Variable, -): + variable: Variable): ISolverExpression | Error { let rawValue: string | undefined; let valueType: SparqlOperandDataTypes | undefined; @@ -168,7 +168,6 @@ export function recursifResolve( logicOperator: LogicOperator = new Or(), variable: Variable, ): SolutionDomain { - if (filterExpression.expressionType === Algebra.expressionTypes.TERM ) { // In that case we are confronted with a boolean expression @@ -183,24 +182,24 @@ export function recursifResolve( } } else if ( // If it's an array of terms then we should be able to create a solver expression. - // Given the resulting solver expression we can calculate a solution interval + // Given the resulting solver expression we can calculate a solution interval // that we will add to the domain with regards to the logic operator. filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && filterExpression.args.length === 2 ) { const rawOperator = filterExpression.operator; - let operator = filterOperatorToSparqlRelationOperator(rawOperator); - if (operator && logicOperator.operatorName()!= LogicOperatorSymbol.Not) { + const operator = filterOperatorToSparqlRelationOperator(rawOperator); + if (operator && logicOperator.operatorName() != LogicOperatorSymbol.Not) { const solverExpression = resolveAFilterTerm(filterExpression, operator, variable); - let solutionInterval: SolutionInterval | [SolutionInterval, SolutionInterval]|undefined; + let solutionInterval: SolutionInterval | [SolutionInterval, SolutionInterval] | undefined; if (solverExpression instanceof MissMatchVariableError) { solutionInterval = A_TRUE_EXPRESSION; } else if (solverExpression instanceof Error) { throw solverExpression; } else { solutionInterval = getSolutionInterval(solverExpression.valueAsNumber, solverExpression.operator); - if (!solutionInterval){ - throw TypeError('The operator is not supported'); + if (!solutionInterval) { + throw new TypeError('The operator is not supported'); } } domain = logicOperator.apply({ interval: solutionInterval, domain }); @@ -223,17 +222,13 @@ export function recursifResolve( if (logicOperatorSymbol === LogicOperatorSymbol.Not) { inverseFilter(arg); domain = recursifResolve(arg, domain, logicOperator, variable); - }else if( logicOperatorSymbol === LogicOperatorSymbol.Assert){ + } else if (logicOperatorSymbol === LogicOperatorSymbol.Exist) { domain = recursifResolve(arg, domain, logicOperator, variable); - - } - - else{ + } else { const logicOperator = operatorFactory(logicOperatorSymbol); domain = recursifResolve(arg, domain, logicOperator, variable); } } - } } return domain; @@ -293,23 +288,23 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { * @param {SparqlRelationOperator} operator * @returns {SolutionInterval | undefined} The solution range associated with the value and the operator. */ -export function getSolutionInterval(value: number, operator: SparqlRelationOperator): SolutionInterval | [SolutionInterval, SolutionInterval]| undefined { +export function getSolutionInterval(value: number, operator: SparqlRelationOperator): SolutionInterval | [SolutionInterval, SolutionInterval] | undefined { switch (operator) { case SparqlRelationOperator.GreaterThanRelation: - return new SolutionInterval([nextUp(value), Number.POSITIVE_INFINITY]); + return new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]); case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return new SolutionInterval([value, Number.POSITIVE_INFINITY]); + return new SolutionInterval([ value, Number.POSITIVE_INFINITY ]); case SparqlRelationOperator.EqualThanRelation: - return new SolutionInterval([value, value]); + return new SolutionInterval([ value, value ]); case SparqlRelationOperator.LessThanRelation: - return new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(value)]); + return new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]); case SparqlRelationOperator.LessThanOrEqualToRelation: - return new SolutionInterval([Number.NEGATIVE_INFINITY, value]); + return new SolutionInterval([ Number.NEGATIVE_INFINITY, value ]); case SparqlRelationOperator.NotEqualThanRelation: - return [ - new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(value)]), - new SolutionInterval([nextUp(value),Number.POSITIVE_INFINITY]) - ]; + return [ + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]), + new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]), + ]; default: // Not an operator that is compatible with number. break; @@ -398,41 +393,41 @@ export function filterOperatorToSparqlRelationOperator(filterOperator: string): case '>=': return SparqlRelationOperator.GreaterThanOrEqualToRelation; case '!=': - return SparqlRelationOperator.NotEqualThanRelation; + return SparqlRelationOperator.NotEqualThanRelation; default: return undefined; } } -export function reverseRawLogicOperator(logicOperator: string) :string|undefined{ - switch(logicOperator){ - case '&&': - return '||' - case '||': - return '&&' - case '!': - return 'E'; - case 'E': - return '!'; +export function reverseRawLogicOperator(logicOperator: string): string | undefined { + switch (logicOperator) { + case LogicOperatorSymbol.And: + return LogicOperatorSymbol.Or; + case LogicOperatorSymbol.Or: + return LogicOperatorSymbol.And; + case LogicOperatorSymbol.Not: + return LogicOperatorSymbol.Exist; + case LogicOperatorSymbol.Exist: + return LogicOperatorSymbol.Not; default: - return undefined + return undefined; } } export function reverseRawOperator(filterOperator: string): string | undefined { switch (filterOperator) { case '=': - return '!=' + return '!='; case '!=': - return '=' + return '='; case '<': - return '>=' + return '>='; case '<=': - return '>' + return '>'; case '>': - return '<=' + return '<='; case '>=': - return '<' + return '<'; default: return undefined; } @@ -441,66 +436,64 @@ export function reverseRawOperator(filterOperator: string): string | undefined { export function reverseOperator(operator: SparqlRelationOperator): SparqlRelationOperator | undefined { switch (operator) { case SparqlRelationOperator.LessThanRelation: - return SparqlRelationOperator.GreaterThanOrEqualToRelation + return SparqlRelationOperator.GreaterThanOrEqualToRelation; case SparqlRelationOperator.LessThanOrEqualToRelation: - return SparqlRelationOperator.GreaterThanRelation + return SparqlRelationOperator.GreaterThanRelation; case SparqlRelationOperator.GreaterThanRelation: - return SparqlRelationOperator.LessThanOrEqualToRelation + return SparqlRelationOperator.LessThanOrEqualToRelation; case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return SparqlRelationOperator.LessThanRelation + return SparqlRelationOperator.LessThanRelation; case SparqlRelationOperator.EqualThanRelation: - return SparqlRelationOperator.NotEqualThanRelation + return SparqlRelationOperator.NotEqualThanRelation; case SparqlRelationOperator.NotEqualThanRelation: - return SparqlRelationOperator.EqualThanRelation + return SparqlRelationOperator.EqualThanRelation; default: - return undefined + return undefined; } } export function reverseLogicOperator(operator: LogicOperator): LogicOperator | undefined { switch (operator.operatorName()) { case LogicOperatorSymbol.And: - return new Or() + return new Or(); case LogicOperatorSymbol.Or: - return new And() - // the reverse is making an assetion and this operator is useless + return new And(); + // The reverse is making an assetion and this operator is useless case LogicOperatorSymbol.Not: - return undefined + return undefined; } } -export function inverseFilter(filterExpression: Algebra.Expression){ +export function inverseFilter(filterExpression: Algebra.Expression) { if (filterExpression.expressionType === Algebra.expressionTypes.TERM - ) { - if (filterExpression.term.value === 'false') { - filterExpression.term.value = 'true'; - } else if (filterExpression.term.value === 'true') { - filterExpression.term.value = 'false'; - } else { - throw new MisformatedFilterTermError(`The term sent is not a boolean but is this value {${filterExpression.term.value}}`); - } - }else if ( - // If it's an array of terms then we should be able to create a solver expression. - // Given the resulting solver expression we can calculate a solution interval - // that we will add to the domain with regards to the logic operator. - filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && - filterExpression.args.length === 2 - ) { - filterExpression.operator = reverseRawOperator(filterExpression.operator) - - }else{ + ) { + if (filterExpression.term.value === 'false') { + filterExpression.term.value = 'true'; + } else if (filterExpression.term.value === 'true') { + filterExpression.term.value = 'false'; + } else { + throw new MisformatedFilterTermError(`The term sent is not a boolean but is this value {${filterExpression.term.value}}`); + } + } else if ( + // If it's an array of terms then we should be able to create a solver expression. + // Given the resulting solver expression we can calculate a solution interval + // that we will add to the domain with regards to the logic operator. + filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && + filterExpression.args.length === 2 + ) { + filterExpression.operator = reverseRawOperator(filterExpression.operator); + } else { + const reversedOperator = reverseRawLogicOperator(filterExpression.operator); + if (reversedOperator) { + filterExpression.operator = reversedOperator; + } + for (const arg of filterExpression.args) { const reversedOperator = reverseRawLogicOperator(filterExpression.operator); - if(reversedOperator){ + if (reversedOperator) { filterExpression.operator = reversedOperator; } - for (const arg of filterExpression.args) { - const reversedOperator = reverseRawLogicOperator(filterExpression.operator); - if(reversedOperator){ - filterExpression.operator = reversedOperator; - } - inverseFilter(arg); - } + inverseFilter(arg); } - -} \ No newline at end of file + } +} diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index 26a3675b7..4db9dc021 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -39,7 +39,7 @@ export enum LogicOperatorSymbol { And = '&&', Or = '||', Not = '!', - Assert='E' + Exist = 'E' } /** diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index 8d0b6de1e..48cc608e2 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -1,185 +1,180 @@ import { Or, And } from '../lib/LogicOperator'; +import { SolutionDomain } from '../lib/SolutionDomain'; import { SolutionInterval } from '../lib/SolutionInterval'; import { LogicOperatorSymbol } from '../lib/solverInterfaces'; -import { SolutionDomain } from '../lib/SolutionDomain'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; describe('LogicOperator', () => { - describe('Or', () => { - const or = new Or(); - describe('operatorName', () => { - it('Should return the associated operator name', () => { - expect(or.operatorName()).toBe(LogicOperatorSymbol.Or); - }); + describe('Or', () => { + const or = new Or(); + describe('operatorName', () => { + it('Should return the associated operator name', () => { + expect(or.operatorName()).toBe(LogicOperatorSymbol.Or); + }); + }); + describe('apply', () => { + const intervals = [ + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 33 ]), + new SolutionInterval([ 60, 70 ]), + ]; + const aDomain = SolutionDomain.newWithInitialIntervals(intervals); + it('given an empty domain should be able to add an interval', () => { + const interval = new SolutionInterval([ 0, 1 ]); + const solutionDomain = new SolutionDomain(); + const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals(interval); + expect(or.apply({ domain: solutionDomain, interval })).toStrictEqual(expectedSolutionDomain); + }); + + it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { + const intervals = [ + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 1, 2 ]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 60, 70 ]), + ]; + + let solutionDomain = new SolutionDomain(); + + intervals.forEach((interval, idx) => { + solutionDomain = or.apply({ domain: solutionDomain, interval }); + expect(solutionDomain.getDomain().length).toBe(idx + 1); }); - describe('apply', () => { - const intervals = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 5]), - new SolutionInterval([10, 10]), - new SolutionInterval([21, 33]), - new SolutionInterval([60, 70]), - ]; - const aDomain = SolutionDomain.newWithInitialIntervals(intervals); - it('given an empty domain should be able to add an interval', () => { - const interval = new SolutionInterval([0, 1]); - const solutionDomain = new SolutionDomain(); - const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals(interval); - expect(or.apply({ domain: solutionDomain, interval: interval })).toStrictEqual(expectedSolutionDomain); - - }); - - it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { - const intervals = [ - new SolutionInterval([10, 10]), - new SolutionInterval([1, 2]), - new SolutionInterval([-1, 0]), - new SolutionInterval([60, 70]), - ]; - - let solutionDomain = new SolutionDomain(); - - intervals.forEach((interval, idx) => { - solutionDomain = or.apply({ domain: solutionDomain, interval: interval }); - expect(solutionDomain.getDomain().length).toBe(idx + 1); - }); - - const expectedDomain = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 2]), - new SolutionInterval([10, 10]), - new SolutionInterval([60, 70]), - ]; - - expect(solutionDomain.getDomain()).toStrictEqual(expectedDomain); - }); - - it('given a domain should not add a range that is inside another', () => { - const anOverlappingInterval = new SolutionInterval([22, 23]); - const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); - - expect(newDomain.getDomain().length).toBe(aDomain.getDomain().length); - expect(newDomain.getDomain()).toStrictEqual(intervals); - }); - - it('given a domain should create a single domain if all the domain segment are contain into the new range', () => { - const anOverlappingInterval = new SolutionInterval([-100, 100]); - const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); - - expect(newDomain.getDomain().length).toBe(1); - expect(newDomain.getDomain()).toStrictEqual([anOverlappingInterval]); - }); - - it('given a domain should fuse multiple domain segment if the new range overlap with them', () => { - const aNewInterval = new SolutionInterval([1, 23]); - const newDomain = or.apply({ domain: aDomain, interval: aNewInterval }); - - const expectedResultingDomainInterval = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 33]), - new SolutionInterval([60, 70]), - ]; - - expect(newDomain.getDomain().length).toBe(3); - expect(newDomain.getDomain()).toStrictEqual(expectedResultingDomainInterval); - }); - }); + const expectedDomain = [ + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 2 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 60, 70 ]), + ]; + + expect(solutionDomain.getDomain()).toStrictEqual(expectedDomain); + }); + + it('given a domain should not add a range that is inside another', () => { + const anOverlappingInterval = new SolutionInterval([ 22, 23 ]); + const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); + + expect(newDomain.getDomain().length).toBe(aDomain.getDomain().length); + expect(newDomain.getDomain()).toStrictEqual(intervals); + }); + + it('given a domain should create a single domain if all the domain segment are contain into the new range', () => { + const anOverlappingInterval = new SolutionInterval([ -100, 100 ]); + const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); + + expect(newDomain.getDomain().length).toBe(1); + expect(newDomain.getDomain()).toStrictEqual([ anOverlappingInterval ]); + }); + + it('given a domain should fuse multiple domain segment if the new range overlap with them', () => { + const aNewInterval = new SolutionInterval([ 1, 23 ]); + const newDomain = or.apply({ domain: aDomain, interval: aNewInterval }); + + const expectedResultingDomainInterval = [ + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 33 ]), + new SolutionInterval([ 60, 70 ]), + ]; + + expect(newDomain.getDomain().length).toBe(3); + expect(newDomain.getDomain()).toStrictEqual(expectedResultingDomainInterval); + }); }); + }); + + describe('And', () => { + const and = new And(); + describe('operatorName', () => { + it('Should return the associated operator name', () => { + expect(and.operatorName()).toBe(LogicOperatorSymbol.And); + }); + }); + describe('apply', () => { + let aDomain: SolutionDomain = new SolutionDomain(); + const intervals = [ + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 33 ]), + new SolutionInterval([ 60, 70 ]), + ]; + beforeEach(() => { + aDomain = new SolutionDomain(); + for (const interval of intervals) { + aDomain = new Or().apply({ interval, domain: aDomain }); + } + }); + it('should add a range when the domain is empty', () => { + const domain = new SolutionDomain(); + const interval = new SolutionInterval([ 0, 1 ]); - describe('And', () => { - const and = new And(); - describe('operatorName', () => { - it('Should return the associated operator name', () => { - expect(and.operatorName()).toBe(LogicOperatorSymbol.And); - }); - }); - describe('apply', () => { - let aDomain: SolutionDomain = new SolutionDomain(); - const intervals = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 5]), - new SolutionInterval([10, 10]), - new SolutionInterval([21, 33]), - new SolutionInterval([60, 70]), - ]; - beforeEach(() => { - aDomain = new SolutionDomain(); - for (const interval of intervals) { - aDomain = new Or().apply({ interval, domain: aDomain }); - } - }); - - it('should add a range when the domain is empty', () => { - const domain = new SolutionDomain(); - const interval = new SolutionInterval([0, 1]); - - const newDomain = and.apply({ interval, domain }); - - expect(newDomain.getDomain()).toStrictEqual([interval]); - }); - - it('should return an empty domain if there is no intersection with the new range', () => { - const interval = new SolutionInterval([ -200, -100 ]); - - const newDomain = and.apply({ interval, domain: aDomain }); - - expect(newDomain.isDomainEmpty()).toBe(true); - }); - - it('given a new range that is inside a part of the domain should only return the intersection', () => { - const interval = new SolutionInterval([ 22, 30 ]); - - const newDomain = and.apply({ interval, domain: aDomain }); - - expect(newDomain.getDomain()).toStrictEqual([ interval ]); - }); - - it('given a new range that intersect a part of the domain should only return the intersection', () => { - const interval = new SolutionInterval([ 19, 25 ]); - - const expectedDomain = [ - new SolutionInterval([ 21, 25 ]), - ]; - - const newDomain = and.apply({ interval, domain: aDomain }); - - expect(newDomain.getDomain()).toStrictEqual(expectedDomain); - }); - - it('given a new range that intersect multiple part of the domain should only return the intersections', () => { - const interval = new SolutionInterval([ -2, 25 ]); - - const expectedDomain = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 5 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 21, 25 ]), - ]; - - const newDomain = and.apply({ interval, domain: aDomain }); - - expect(newDomain.getDomain()).toStrictEqual(expectedDomain); - }); - - it('given an empty domain and a last operator and should return an empty domain', () => { - const interval = new SolutionInterval([ -2, 25 ]); - const anotherIntervalNonOverlapping = new SolutionInterval([ 2_000, 3_000 ]); - - let newDomain = and.apply({ interval, domain: aDomain }); - newDomain = and.apply({ interval:anotherIntervalNonOverlapping, domain: newDomain }); - - expect(newDomain.isDomainEmpty()).toBe(true); - - newDomain = and.apply({ interval, domain: newDomain }); - - expect(newDomain.isDomainEmpty()).toBe(true); - }); + const newDomain = and.apply({ interval, domain }); + expect(newDomain.getDomain()).toStrictEqual([ interval ]); + }); - }); + it('should return an empty domain if there is no intersection with the new range', () => { + const interval = new SolutionInterval([ -200, -100 ]); + + const newDomain = and.apply({ interval, domain: aDomain }); + + expect(newDomain.isDomainEmpty()).toBe(true); + }); + + it('given a new range that is inside a part of the domain should only return the intersection', () => { + const interval = new SolutionInterval([ 22, 30 ]); + + const newDomain = and.apply({ interval, domain: aDomain }); + + expect(newDomain.getDomain()).toStrictEqual([ interval ]); + }); + + it('given a new range that intersect a part of the domain should only return the intersection', () => { + const interval = new SolutionInterval([ 19, 25 ]); + + const expectedDomain = [ + new SolutionInterval([ 21, 25 ]), + ]; + + const newDomain = and.apply({ interval, domain: aDomain }); + + expect(newDomain.getDomain()).toStrictEqual(expectedDomain); + }); + + it('given a new range that intersect multiple part of the domain should only return the intersections', () => { + const interval = new SolutionInterval([ -2, 25 ]); + + const expectedDomain = [ + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 25 ]), + ]; + + const newDomain = and.apply({ interval, domain: aDomain }); + + expect(newDomain.getDomain()).toStrictEqual(expectedDomain); + }); + + it('given an empty domain and a last operator and should return an empty domain', () => { + const interval = new SolutionInterval([ -2, 25 ]); + const anotherIntervalNonOverlapping = new SolutionInterval([ 2_000, 3_000 ]); + + let newDomain = and.apply({ interval, domain: aDomain }); + newDomain = and.apply({ interval: anotherIntervalNonOverlapping, domain: newDomain }); + + expect(newDomain.isDomainEmpty()).toBe(true); + + newDomain = and.apply({ interval, domain: newDomain }); + + expect(newDomain.isDomainEmpty()).toBe(true); + }); }); -}); \ No newline at end of file + }); +}); diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index 07a6d347c..5d4526043 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -11,14 +11,14 @@ describe('SolutionDomain', () => { }); describe('equal', () => { it('should return equal when two domains have the same interval', () => { - const domain1 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([0, 1])); - const domain2 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([0, 1])); + const domain1 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 0, 1 ])); + const domain2 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 0, 1 ])); expect(domain1.equal(domain2)).toBe(true); }); it('should return not equal when two domains have the a different interval', () => { - const domain1 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([0, 1])); - const domain2 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([0, 2])); + const domain1 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 0, 1 ])); + const domain2 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 0, 2 ])); expect(domain1.equal(domain2)).toBe(false); }); @@ -30,7 +30,7 @@ describe('SolutionDomain', () => { it('should return not equal when one domain is empty and the other have one interval', () => { const domain1 = new SolutionDomain(); - const domain2 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([0, 2])); + const domain2 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 0, 2 ])); expect(domain1.equal(domain2)).toBe(false); }); @@ -38,10 +38,10 @@ describe('SolutionDomain', () => { const domain1 = new SolutionDomain(); const domain2 = SolutionDomain.newWithInitialIntervals( [ - new SolutionInterval([0, 1]), - new SolutionInterval([2, 3]), - new SolutionInterval([4, 5]) - ] + new SolutionInterval([ 0, 1 ]), + new SolutionInterval([ 2, 3 ]), + new SolutionInterval([ 4, 5 ]), + ], ); expect(domain1.equal(domain2)).toBe(false); }); @@ -49,17 +49,17 @@ describe('SolutionDomain', () => { it('should return equal when two domains have the same intervals', () => { const domain1 = SolutionDomain.newWithInitialIntervals( [ - new SolutionInterval([0, 1]), - new SolutionInterval([2, 3]), - new SolutionInterval([4, 5]) - ] + new SolutionInterval([ 0, 1 ]), + new SolutionInterval([ 2, 3 ]), + new SolutionInterval([ 4, 5 ]), + ], ); const domain2 = SolutionDomain.newWithInitialIntervals( [ - new SolutionInterval([0, 1]), - new SolutionInterval([2, 3]), - new SolutionInterval([4, 5]) - ] + new SolutionInterval([ 0, 1 ]), + new SolutionInterval([ 2, 3 ]), + new SolutionInterval([ 4, 5 ]), + ], ); expect(domain1.equal(domain2)).toBe(true); }); @@ -67,17 +67,17 @@ describe('SolutionDomain', () => { it('should return not equal when two domains have the different intervals', () => { const domain1 = SolutionDomain.newWithInitialIntervals( [ - new SolutionInterval([0, 1]), - new SolutionInterval([2, 3]), - new SolutionInterval([4, 5]) - ] + new SolutionInterval([ 0, 1 ]), + new SolutionInterval([ 2, 3 ]), + new SolutionInterval([ 4, 5 ]), + ], ); const domain2 = SolutionDomain.newWithInitialIntervals( [ - new SolutionInterval([0, 1]), - new SolutionInterval([6, 7]), - new SolutionInterval([4, 5]) - ] + new SolutionInterval([ 0, 1 ]), + new SolutionInterval([ 6, 7 ]), + new SolutionInterval([ 4, 5 ]), + ], ); expect(domain1.equal(domain2)).toBe(false); }); @@ -85,7 +85,7 @@ describe('SolutionDomain', () => { describe('newWithInitialIntervals', () => { it('should create a solution domain with an initial value', () => { - const solutionRange = new SolutionInterval([0, 1]); + const solutionRange = new SolutionInterval([ 0, 1 ]); const solutionDomain = SolutionDomain.newWithInitialIntervals(solutionRange); expect(solutionDomain.getDomain().length).toBe(1); @@ -94,10 +94,10 @@ describe('SolutionDomain', () => { it('should create a solution domain with multiple initial value', () => { const solutionIntervals = [ - new SolutionInterval([0, 1]), - new SolutionInterval([2, 3]), - new SolutionInterval([-33, -3]), - new SolutionInterval([100, 3000]) + new SolutionInterval([ 0, 1 ]), + new SolutionInterval([ 2, 3 ]), + new SolutionInterval([ -33, -3 ]), + new SolutionInterval([ 100, 3_000 ]), ]; const solutionDomain = SolutionDomain.newWithInitialIntervals(solutionIntervals); @@ -107,15 +107,14 @@ describe('SolutionDomain', () => { it('should throw an error when creating a solution domain with multiple intervals overlaping', () => { const solutionIntervals = [ - new SolutionInterval([0, 1]), - new SolutionInterval([2, 3]), - new SolutionInterval([2, 10]), + new SolutionInterval([ 0, 1 ]), + new SolutionInterval([ 2, 3 ]), + new SolutionInterval([ 2, 10 ]), ]; expect(() => { SolutionDomain.newWithInitialIntervals(solutionIntervals); }).toThrow(RangeError); }); - }); describe('isDomainEmpty', () => { @@ -126,10 +125,9 @@ describe('SolutionDomain', () => { }); it('should return false when the domain is not empty', () => { - const domain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([0, 1])); + const domain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 0, 1 ])); expect(domain.isDomainEmpty()).toBe(false); }); }); - }); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 6d6671a35..f7db0582c 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -5,6 +5,7 @@ import { MisformatedFilterTermError, UnsupportedDataTypeError, } from '../lib/error'; +import { Or } from '../lib/LogicOperator'; import { SolutionDomain } from '../lib/SolutionDomain'; import { SolutionInterval } from '../lib/SolutionInterval'; import { @@ -24,7 +25,6 @@ import type { } from '../lib/solverInterfaces'; import { SparqlRelationOperator } from '../lib/TreeMetadata'; import type { ITreeRelation } from '../lib/TreeMetadata'; -import { Or } from '../lib/LogicOperator'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; @@ -35,14 +35,14 @@ describe('solver function', () => { describe('filterOperatorToSparqlRelationOperator', () => { it('should return the RelationOperator given a string representation', () => { const testTable: [string, SparqlRelationOperator][] = [ - ['=', SparqlRelationOperator.EqualThanRelation], - ['<', SparqlRelationOperator.LessThanRelation], - ['<=', SparqlRelationOperator.LessThanOrEqualToRelation], - ['>', SparqlRelationOperator.GreaterThanRelation], - ['>=', SparqlRelationOperator.GreaterThanOrEqualToRelation], + [ '=', SparqlRelationOperator.EqualThanRelation ], + [ '<', SparqlRelationOperator.LessThanRelation ], + [ '<=', SparqlRelationOperator.LessThanOrEqualToRelation ], + [ '>', SparqlRelationOperator.GreaterThanRelation ], + [ '>=', SparqlRelationOperator.GreaterThanOrEqualToRelation ], ]; - for (const [value, expectedAnswer] of testTable) { + for (const [ value, expectedAnswer ] of testTable) { expect(filterOperatorToSparqlRelationOperator(value)).toBe(expectedAnswer); } }); @@ -91,64 +91,64 @@ describe('solver function', () => { describe('castSparqlRdfTermIntoNumber', () => { it('should return the expected number when given an integer', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - ['19273', SparqlOperandDataTypes.Integer, 19_273], - ['0', SparqlOperandDataTypes.NonPositiveInteger, 0], - ['-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459], - ['121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321], - ['1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213], - ['283', SparqlOperandDataTypes.NonNegativeInteger, 283], - ['-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831], - ['-1234', SparqlOperandDataTypes.UnsignedInt, -1_234], - ['-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234], - ['1234', SparqlOperandDataTypes.PositiveInteger, 1_234], + [ '19273', SparqlOperandDataTypes.Integer, 19_273 ], + [ '0', SparqlOperandDataTypes.NonPositiveInteger, 0 ], + [ '-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459 ], + [ '121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321 ], + [ '1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213 ], + [ '283', SparqlOperandDataTypes.NonNegativeInteger, 283 ], + [ '-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831 ], + [ '-1234', SparqlOperandDataTypes.UnsignedInt, -1_234 ], + [ '-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234 ], + [ '1234', SparqlOperandDataTypes.PositiveInteger, 1_234 ], ]; - for (const [value, valueType, expectedNumber] of testTable) { + for (const [ value, valueType, expectedNumber ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return undefined if a non integer is pass with SparqlOperandDataTypes integer compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - ['asbd', SparqlOperandDataTypes.PositiveInteger], - ['', SparqlOperandDataTypes.NegativeInteger], + [ 'asbd', SparqlOperandDataTypes.PositiveInteger ], + [ '', SparqlOperandDataTypes.NegativeInteger ], ]; - for (const [value, valueType] of testTable) { + for (const [ value, valueType ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return undefined if a non fraction is pass with SparqlOperandDataTypes fraction compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - ['asbd', SparqlOperandDataTypes.Double], - ['', SparqlOperandDataTypes.Float], + [ 'asbd', SparqlOperandDataTypes.Double ], + [ '', SparqlOperandDataTypes.Float ], ]; - for (const [value, valueType] of testTable) { + for (const [ value, valueType ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return the expected number when given an decimal', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - ['1.1', SparqlOperandDataTypes.Decimal, 1.1], - ['2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321], - ['1234.123', SparqlOperandDataTypes.Double, 1_234.123], + [ '1.1', SparqlOperandDataTypes.Decimal, 1.1 ], + [ '2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321 ], + [ '1234.123', SparqlOperandDataTypes.Double, 1_234.123 ], ]; - for (const [value, valueType, expectedNumber] of testTable) { + for (const [ value, valueType, expectedNumber ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return the expected number given a boolean', () => { const testTable: [string, number][] = [ - ['true', 1], - ['false', 0], + [ 'true', 1 ], + [ 'false', 0 ], ]; - for (const [value, expectedNumber] of testTable) { + for (const [ value, expectedNumber ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, SparqlOperandDataTypes.Boolean)).toBe(expectedNumber); } }); @@ -183,27 +183,27 @@ describe('solver function', () => { const testTable: [SparqlRelationOperator, SolutionInterval][] = [ [ SparqlRelationOperator.GreaterThanRelation, - new SolutionInterval([nextUp(value), Number.POSITIVE_INFINITY]), + new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]), ], [ SparqlRelationOperator.GreaterThanOrEqualToRelation, - new SolutionInterval([value, Number.POSITIVE_INFINITY]), + new SolutionInterval([ value, Number.POSITIVE_INFINITY ]), ], [ SparqlRelationOperator.EqualThanRelation, - new SolutionInterval([value, value]), + new SolutionInterval([ value, value ]), ], [ SparqlRelationOperator.LessThanRelation, - new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(value)]), + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]), ], [ SparqlRelationOperator.LessThanOrEqualToRelation, - new SolutionInterval([Number.NEGATIVE_INFINITY, value]), + new SolutionInterval([ Number.NEGATIVE_INFINITY, value ]), ], ]; - for (const [operator, expectedRange] of testTable) { + for (const [ operator, expectedRange ] of testTable) { expect(getSolutionInterval(value, operator)).toStrictEqual(expectedRange); } }); @@ -527,30 +527,30 @@ describe('solver function', () => { it(`given an algebra expression with a litteral containing an invalid datatype than should return an unsupported datatype error`, - () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; + () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; - expect(resolveAFilterTerm(expression, operator, variable)) - .toBeInstanceOf(UnsupportedDataTypeError); - }); + expect(resolveAFilterTerm(expression, operator, variable)) + .toBeInstanceOf(UnsupportedDataTypeError); + }); it(`given an algebra expression with a litteral containing a literal that cannot be converted into number should return an unsupported datatype error`, () => { @@ -593,28 +593,28 @@ describe('solver function', () => { 'x', ); - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); - it('given an algebra expression with two logicals operators with a double negation should return the valid solution domain', () => { - const expression = translate(` + it('given an algebra expression with two logicals operators with a double negation should return the valid solution domain', () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(!(?x=2)) && ?x<5) }`).input.expression; - - const resp = recursifResolve( - expression, - new SolutionDomain(), - new Or(), - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); + + const resp = recursifResolve( + expression, + new SolutionDomain(), + new Or(), + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); it('given an algebra expression with a double negation it should cancel it', () => { const expression = translate(` @@ -629,7 +629,7 @@ describe('solver function', () => { 'x', ); - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -648,8 +648,7 @@ describe('solver function', () => { 'x', ); - - expect(resp.isDomainEmpty()).toBe(true) + expect(resp.isDomainEmpty()).toBe(true); }); it(`given an algebra expression with two logicals operators that are negated @@ -667,8 +666,8 @@ describe('solver function', () => { ); const expectedDomain = SolutionDomain.newWithInitialIntervals( - [new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(2)]), - new SolutionInterval([nextUp(2), Number.POSITIVE_INFINITY])] + [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), + new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], ); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); @@ -689,8 +688,8 @@ describe('solver function', () => { ); const expectedDomain = SolutionDomain.newWithInitialIntervals( - [new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(2)]), - new SolutionInterval([nextUp(2), Number.POSITIVE_INFINITY])] + [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), + new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], ); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); @@ -711,8 +710,8 @@ describe('solver function', () => { ); const expectedDomain = SolutionDomain.newWithInitialIntervals([ - new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(3)]), - new SolutionInterval([nextUp(3), Number.POSITIVE_INFINITY]) + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(3) ]), + new SolutionInterval([ nextUp(3), Number.POSITIVE_INFINITY ]), ]); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); From 825e308539f63ab33518ea701b9349060829c60e Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 30 May 2023 10:54:13 +0200 Subject: [PATCH 165/189] logic operator unit test done --- .../lib/LogicOperator.ts | 42 ++-- .../test/LogicOperator-test.ts | 185 +++++++++++++----- 2 files changed, 168 insertions(+), 59 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index cd55467de..f6923d470 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -2,20 +2,18 @@ import { SolutionDomain } from './SolutionDomain'; import { SolutionInterval } from './SolutionInterval'; import { LogicOperatorSymbol } from './solverInterfaces'; export interface LogicOperator { - apply: ({ interval, domain }: { interval?: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }) => SolutionDomain; + apply: ({ interval, domain }: { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }) => SolutionDomain; operatorName: () => LogicOperatorSymbol; } export class Or implements LogicOperator { - public apply({ interval, domain }: { interval?: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { + public apply({ interval, domain }: { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { if (Array.isArray(interval)) { domain = this.apply({ interval: interval[0], domain }); return this.apply({ interval: interval[1], domain }); } let newDomain: SolutionInterval[] = []; - if (!interval) { - return domain; - } + let currentInterval = interval; // We iterate over all the domain @@ -44,13 +42,18 @@ export class Or implements LogicOperator { } export class And implements LogicOperator { - apply({ interval, domain }: { interval?: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { + apply({ interval, domain }: { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { if (Array.isArray(interval)) { + + if (interval[0].isOverlapping(interval[1])) { + return domain; + } + const testDomain1 = this.apply({ interval: interval[0], domain }); const testDomain2 = this.apply({ interval: interval[1], domain }); - const cannotAddDomain1 = domain.equal(testDomain1); - const cannotAddDomain2 = domain.equal(testDomain2); + const cannotAddDomain1 = testDomain1.isDomainEmpty(); + const cannotAddDomain2 = testDomain2.isDomainEmpty(); if (cannotAddDomain1 && cannotAddDomain2) { return domain; @@ -59,12 +62,23 @@ export class And implements LogicOperator { } if (cannotAddDomain1 && !cannotAddDomain2) { return testDomain2; } - return new Or().apply({ interval: interval[1], domain: testDomain1 }); + + let intervalRes: SolutionInterval; + let newDomain: SolutionDomain; + if(testDomain1.getDomain().length > testDomain2.getDomain().length){ + intervalRes = interval[1]; + newDomain = testDomain1; + }else{ + intervalRes = interval[0]; + newDomain = testDomain2; + } + + return new Or().apply({ + interval: intervalRes, domain: newDomain + }); } const newDomain: SolutionInterval[] = []; - if (!interval) { - return domain; - } + // If the domain is empty then simply add the new range if (domain.getDomain().length === 0) { newDomain.push(interval); @@ -93,8 +107,8 @@ export class And implements LogicOperator { const OPERATOR_MAP = new Map( [ - [ new Or().operatorName(), new Or() ], - [ new And().operatorName(), new And() ], + [new Or().operatorName(), new Or()], + [new And().operatorName(), new And()], ], ); diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index 48cc608e2..77415048e 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -1,4 +1,4 @@ -import { Or, And } from '../lib/LogicOperator'; +import { Or, And, operatorFactory } from '../lib/LogicOperator'; import { SolutionDomain } from '../lib/SolutionDomain'; import { SolutionInterval } from '../lib/SolutionInterval'; import { LogicOperatorSymbol } from '../lib/solverInterfaces'; @@ -16,15 +16,15 @@ describe('LogicOperator', () => { }); describe('apply', () => { const intervals = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 5 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 21, 33 ]), - new SolutionInterval([ 60, 70 ]), + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 5]), + new SolutionInterval([10, 10]), + new SolutionInterval([21, 33]), + new SolutionInterval([60, 70]), ]; const aDomain = SolutionDomain.newWithInitialIntervals(intervals); it('given an empty domain should be able to add an interval', () => { - const interval = new SolutionInterval([ 0, 1 ]); + const interval = new SolutionInterval([0, 1]); const solutionDomain = new SolutionDomain(); const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals(interval); expect(or.apply({ domain: solutionDomain, interval })).toStrictEqual(expectedSolutionDomain); @@ -32,10 +32,10 @@ describe('LogicOperator', () => { it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { const intervals = [ - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 1, 2 ]), - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 60, 70 ]), + new SolutionInterval([10, 10]), + new SolutionInterval([1, 2]), + new SolutionInterval([-1, 0]), + new SolutionInterval([60, 70]), ]; let solutionDomain = new SolutionDomain(); @@ -46,17 +46,17 @@ describe('LogicOperator', () => { }); const expectedDomain = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 2 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 60, 70 ]), + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 2]), + new SolutionInterval([10, 10]), + new SolutionInterval([60, 70]), ]; expect(solutionDomain.getDomain()).toStrictEqual(expectedDomain); }); it('given a domain should not add a range that is inside another', () => { - const anOverlappingInterval = new SolutionInterval([ 22, 23 ]); + const anOverlappingInterval = new SolutionInterval([22, 23]); const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); expect(newDomain.getDomain().length).toBe(aDomain.getDomain().length); @@ -64,21 +64,21 @@ describe('LogicOperator', () => { }); it('given a domain should create a single domain if all the domain segment are contain into the new range', () => { - const anOverlappingInterval = new SolutionInterval([ -100, 100 ]); + const anOverlappingInterval = new SolutionInterval([-100, 100]); const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); expect(newDomain.getDomain().length).toBe(1); - expect(newDomain.getDomain()).toStrictEqual([ anOverlappingInterval ]); + expect(newDomain.getDomain()).toStrictEqual([anOverlappingInterval]); }); it('given a domain should fuse multiple domain segment if the new range overlap with them', () => { - const aNewInterval = new SolutionInterval([ 1, 23 ]); + const aNewInterval = new SolutionInterval([1, 23]); const newDomain = or.apply({ domain: aDomain, interval: aNewInterval }); const expectedResultingDomainInterval = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 33 ]), - new SolutionInterval([ 60, 70 ]), + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 33]), + new SolutionInterval([60, 70]), ]; expect(newDomain.getDomain().length).toBe(3); @@ -97,30 +97,27 @@ describe('LogicOperator', () => { describe('apply', () => { let aDomain: SolutionDomain = new SolutionDomain(); const intervals = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 5 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 21, 33 ]), - new SolutionInterval([ 60, 70 ]), + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 5]), + new SolutionInterval([10, 10]), + new SolutionInterval([21, 33]), + new SolutionInterval([60, 70]), ]; beforeEach(() => { - aDomain = new SolutionDomain(); - for (const interval of intervals) { - aDomain = new Or().apply({ interval, domain: aDomain }); - } + aDomain = SolutionDomain.newWithInitialIntervals(new Array(...intervals)); }); it('should add a range when the domain is empty', () => { const domain = new SolutionDomain(); - const interval = new SolutionInterval([ 0, 1 ]); + const interval = new SolutionInterval([0, 1]); const newDomain = and.apply({ interval, domain }); - expect(newDomain.getDomain()).toStrictEqual([ interval ]); + expect(newDomain.getDomain()).toStrictEqual([interval]); }); it('should return an empty domain if there is no intersection with the new range', () => { - const interval = new SolutionInterval([ -200, -100 ]); + const interval = new SolutionInterval([-200, -100]); const newDomain = and.apply({ interval, domain: aDomain }); @@ -128,18 +125,18 @@ describe('LogicOperator', () => { }); it('given a new range that is inside a part of the domain should only return the intersection', () => { - const interval = new SolutionInterval([ 22, 30 ]); + const interval = new SolutionInterval([22, 30]); const newDomain = and.apply({ interval, domain: aDomain }); - expect(newDomain.getDomain()).toStrictEqual([ interval ]); + expect(newDomain.getDomain()).toStrictEqual([interval]); }); it('given a new range that intersect a part of the domain should only return the intersection', () => { - const interval = new SolutionInterval([ 19, 25 ]); + const interval = new SolutionInterval([19, 25]); const expectedDomain = [ - new SolutionInterval([ 21, 25 ]), + new SolutionInterval([21, 25]), ]; const newDomain = and.apply({ interval, domain: aDomain }); @@ -148,13 +145,13 @@ describe('LogicOperator', () => { }); it('given a new range that intersect multiple part of the domain should only return the intersections', () => { - const interval = new SolutionInterval([ -2, 25 ]); + const interval = new SolutionInterval([-2, 25]); const expectedDomain = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 5 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 21, 25 ]), + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 5]), + new SolutionInterval([10, 10]), + new SolutionInterval([21, 25]), ]; const newDomain = and.apply({ interval, domain: aDomain }); @@ -163,8 +160,8 @@ describe('LogicOperator', () => { }); it('given an empty domain and a last operator and should return an empty domain', () => { - const interval = new SolutionInterval([ -2, 25 ]); - const anotherIntervalNonOverlapping = new SolutionInterval([ 2_000, 3_000 ]); + const interval = new SolutionInterval([-2, 25]); + const anotherIntervalNonOverlapping = new SolutionInterval([2_000, 3_000]); let newDomain = and.apply({ interval, domain: aDomain }); newDomain = and.apply({ interval: anotherIntervalNonOverlapping, domain: newDomain }); @@ -175,6 +172,104 @@ describe('LogicOperator', () => { expect(newDomain.isDomainEmpty()).toBe(true); }); + + it('Given an empty domain and two ranges to add that are overlapping the domain should remain empty', () => { + const domain = new SolutionDomain(); + const interval1 = new SolutionInterval([0, 2]); + const interval2 = new SolutionInterval([1, 2]); + + const newDomain = and.apply({ interval: [interval1, interval2], domain }); + + expect(newDomain.isDomainEmpty()).toBe(true); + }); + + it('Given a domain and two ranges to add that are overlapping the domain should remain empty', () => { + const interval1 = new SolutionInterval([0, 2]); + const interval2 = new SolutionInterval([1, 2]); + + const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + + expect(newDomain.equal(aDomain)).toBe(true); + }); + + it('Given a domain and two ranges to add that are overlapping the domain should remain empty', () => { + const interval1 = new SolutionInterval([0, 2]); + const interval2 = new SolutionInterval([1, 2]); + + const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + + expect(newDomain.equal(aDomain)).toBe(true); + }); + + it('Given a domain and two ranges to add that are not overlapping and where those two interval don\'t overlap with the domain should return the initial domain', () => { + const interval1 = new SolutionInterval([-100, -50]); + const interval2 = new SolutionInterval([-25, -23]); + + const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + + expect(newDomain.getDomain()).toStrictEqual(aDomain.getDomain()); + }); + + it('Given a domain and two ranges to add that not overlapping and where the first one is overlap with the domain then should return a new valid domain', () => { + const interval1 = new SolutionInterval([1, 3]); + const interval2 = new SolutionInterval([-25, -23]); + + const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + const expectedDomain = SolutionDomain.newWithInitialIntervals(interval1); + expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('Given a domain and two ranges to add that not overlapping and where the second one is overlap with the domain then should return a new valid domain', () => { + const interval1 = new SolutionInterval([-25, -23]); + const interval2 = new SolutionInterval([1, 3]); + + const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + const expectedDomain = SolutionDomain.newWithInitialIntervals(interval2); + expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('Given a domain and two ranges to add that not overlapping and where the both are overlap and the first one is more overlapping than the second with the domain then should return a new valid domain', () => { + const interval1 = new SolutionInterval([2, 70]); + const interval2 = new SolutionInterval([1, 1]); + + const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + const expectedDomain = SolutionDomain.newWithInitialIntervals([ + interval2, + new SolutionInterval([2, 5]), + new SolutionInterval([10, 10]), + new SolutionInterval([21, 33]), + new SolutionInterval([60, 70]),]); + expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('Given a domain and two ranges to add that not overlapping and where the both are overlap and the first one is more overlapping than the second with the domain then should return a new valid domain', () => { + const interval1 = new SolutionInterval([1, 1]); + + const interval2 = new SolutionInterval([2, 70]); + + const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + const expectedDomain = SolutionDomain.newWithInitialIntervals([ + interval1, + new SolutionInterval([2, 5]), + new SolutionInterval([10, 10]), + new SolutionInterval([21, 33]), + new SolutionInterval([60, 70]),]); + expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + }); + }); + + describe('operatorFactory', ()=>{ + it('given an unsupported logicOperator should throw an error', ()=>{ + expect(()=>operatorFactory(LogicOperatorSymbol.Exist)).toThrow(); + expect(()=>operatorFactory(LogicOperatorSymbol.Not)).toThrow(); + }); + + it('Given an Or and an And operator should return an LogicOperator', ()=>{ + for (const operator of [LogicOperatorSymbol.And, LogicOperatorSymbol.Or]){ + expect(operatorFactory(operator).operatorName()).toBe(operator); + } }); }); }); From c71c71091b7247e3d22703d0fddfb176605ca4de Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 30 May 2023 13:47:03 +0200 Subject: [PATCH 166/189] test coverage back to 100% --- .../lib/solver.ts | 293 +------------- .../lib/util-solver.ts | 291 ++++++++++++++ .../test/solver-test.ts | 373 ++++++++++++------ 3 files changed, 555 insertions(+), 402 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/lib/util-solver.ts diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 6fa0d0d6a..148717f8d 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -19,6 +19,12 @@ import type { } from './solverInterfaces'; import { SparqlRelationOperator } from './TreeMetadata'; import type { ITreeRelation } from './TreeMetadata'; +import {convertTreeRelationToSolverExpression, + castSparqlRdfTermIntoNumber, + filterOperatorToSparqlRelationOperator, + getSolutionInterval, + inverseFilter + } from './util-solver'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; @@ -60,8 +66,6 @@ export function isBooleanExpressionTreeRelationFilterSolvable({ relation, filter try { solutionDomain = recursifResolve( filterExpression, - solutionDomain, - new Or(), variable, ); } catch (error: unknown) { @@ -164,9 +168,9 @@ export function resolveAFilterTerm(expression: Algebra.Expression, */ export function recursifResolve( filterExpression: Algebra.Expression, + variable: Variable, domain: SolutionDomain = new SolutionDomain(), logicOperator: LogicOperator = new Or(), - variable: Variable, ): SolutionDomain { if (filterExpression.expressionType === Algebra.expressionTypes.TERM ) { @@ -175,11 +179,9 @@ export function recursifResolve( // the logic operator. if (filterExpression.term.value === 'false') { domain = logicOperator.apply({ interval: A_FALSE_EXPRESSION, domain }); - } else if (filterExpression.term.value === 'true') { - domain = logicOperator.apply({ interval: A_TRUE_EXPRESSION, domain }); } else { - throw new MisformatedFilterTermError(`The term sent is not a boolean but is this value {${filterExpression.term.value}}`); - } + domain = logicOperator.apply({ interval: A_TRUE_EXPRESSION, domain }); + } } else if ( // If it's an array of terms then we should be able to create a solver expression. // Given the resulting solver expression we can calculate a solution interval @@ -204,13 +206,6 @@ export function recursifResolve( } domain = logicOperator.apply({ interval: solutionInterval, domain }); } - } else if ( - filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && - filterExpression.args.length === 1 - ) { - // We consider that if we only have one term in an array then it is a malformed expression, because - // we don't support functions - throw new MisformatedFilterTermError(`The expression should have a variable and a value, but is {${filterExpression.args}}`); } else { // In that case we are traversing the filter expression TREE. // We prepare the next recursion and we compute the accumulation of results. @@ -221,279 +216,13 @@ export function recursifResolve( // e.g, = : != ; > : <= if (logicOperatorSymbol === LogicOperatorSymbol.Not) { inverseFilter(arg); - domain = recursifResolve(arg, domain, logicOperator, variable); - } else if (logicOperatorSymbol === LogicOperatorSymbol.Exist) { - domain = recursifResolve(arg, domain, logicOperator, variable); + domain = recursifResolve(arg, variable, domain, logicOperator); } else { const logicOperator = operatorFactory(logicOperatorSymbol); - domain = recursifResolve(arg, domain, logicOperator, variable); + domain = recursifResolve(arg, variable, domain, logicOperator); } } } } return domain; } -/** - * Convert a TREE relation into a solver expression. - * @param {ITreeRelation} relation - TREE relation. - * @param {Variable} variable - variable of the SPARQL query associated with the tree:path of the relation. - * @returns {ISolverExpression | undefined} Resulting solver expression if the data type is supported by SPARQL - * and the value can be cast into a number. - */ -export function convertTreeRelationToSolverExpression(relation: ITreeRelation, - variable: Variable): - ISolverExpression | undefined { - if (relation.value && relation.type) { - const valueType = SparqlOperandDataTypesReversed.get((relation.value.term).datatype.value); - if (!valueType) { - return undefined; - } - const valueNumber = castSparqlRdfTermIntoNumber(relation.value.value, valueType); - if (!valueNumber && valueNumber !== 0) { - return undefined; - } - - return { - variable, - rawValue: relation.value.value, - valueType, - valueAsNumber: valueNumber, - - operator: relation.type, - }; - } -} -/** - * Check if all the expression provided have a SparqlOperandDataTypes compatible type - * it is considered that all number types are compatible between them. - * @param {ISolverExpression[]} expressions - The subject expression. - * @returns {boolean} Return true if the type are compatible. - */ -export function areTypesCompatible(expressions: ISolverExpression[]): boolean { - const firstType = expressions[0].valueType; - for (const expression of expressions) { - const areIdentical = expression.valueType === firstType; - const areNumbers = isSparqlOperandNumberType(firstType) && - isSparqlOperandNumberType(expression.valueType); - - if (!(areIdentical || areNumbers)) { - return false; - } - } - return true; -} -/** - * Find the solution range of a value and operator which is analogue to an expression. - * @param {number} value - * @param {SparqlRelationOperator} operator - * @returns {SolutionInterval | undefined} The solution range associated with the value and the operator. - */ -export function getSolutionInterval(value: number, operator: SparqlRelationOperator): SolutionInterval | [SolutionInterval, SolutionInterval] | undefined { - switch (operator) { - case SparqlRelationOperator.GreaterThanRelation: - return new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]); - case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return new SolutionInterval([ value, Number.POSITIVE_INFINITY ]); - case SparqlRelationOperator.EqualThanRelation: - return new SolutionInterval([ value, value ]); - case SparqlRelationOperator.LessThanRelation: - return new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]); - case SparqlRelationOperator.LessThanOrEqualToRelation: - return new SolutionInterval([ Number.NEGATIVE_INFINITY, value ]); - case SparqlRelationOperator.NotEqualThanRelation: - return [ - new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]), - new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]), - ]; - default: - // Not an operator that is compatible with number. - break; - } -} -/** - * Convert a RDF value into a number. - * @param {string} rdfTermValue - The raw value - * @param {SparqlOperandDataTypes} rdfTermType - The type of the value - * @returns {number | undefined} The resulting number or undefined if the convertion is not possible. - */ -export function castSparqlRdfTermIntoNumber(rdfTermValue: string, - rdfTermType: SparqlOperandDataTypes): - number | undefined { - if ( - rdfTermType === SparqlOperandDataTypes.Decimal || - rdfTermType === SparqlOperandDataTypes.Float || - rdfTermType === SparqlOperandDataTypes.Double - ) { - const val = Number.parseFloat(rdfTermValue); - return Number.isNaN(val) ? undefined : val; - } - - if (rdfTermType === SparqlOperandDataTypes.Boolean) { - if (rdfTermValue === 'true') { - return 1; - } - - if (rdfTermValue === 'false') { - return 0; - } - - return undefined; - } - - if ( - isSparqlOperandNumberType(rdfTermType) - ) { - const val = Number.parseInt(rdfTermValue, 10); - return Number.isNaN(val) ? undefined : val; - } - - if (rdfTermType === SparqlOperandDataTypes.DateTime) { - const val = new Date(rdfTermValue).getTime(); - return Number.isNaN(val) ? undefined : val; - } - - return undefined; -} -/** - * Determine if the type is a number. - * @param {SparqlOperandDataTypes} rdfTermType - The subject type - * @returns {boolean} Return true if the type is a number. - */ -export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean { - return rdfTermType === SparqlOperandDataTypes.Integer || - rdfTermType === SparqlOperandDataTypes.NonPositiveInteger || - rdfTermType === SparqlOperandDataTypes.NegativeInteger || - rdfTermType === SparqlOperandDataTypes.Long || - rdfTermType === SparqlOperandDataTypes.Short || - rdfTermType === SparqlOperandDataTypes.NonNegativeInteger || - rdfTermType === SparqlOperandDataTypes.UnsignedLong || - rdfTermType === SparqlOperandDataTypes.UnsignedInt || - rdfTermType === SparqlOperandDataTypes.UnsignedShort || - rdfTermType === SparqlOperandDataTypes.PositiveInteger || - rdfTermType === SparqlOperandDataTypes.Float || - rdfTermType === SparqlOperandDataTypes.Double || - rdfTermType === SparqlOperandDataTypes.Decimal || - rdfTermType === SparqlOperandDataTypes.Int; -} -/** - * Convert a filter operator to SparqlRelationOperator. - * @param {string} filterOperator - The filter operator. - * @returns {SparqlRelationOperator | undefined} The SparqlRelationOperator corresponding to the filter operator - */ -export function filterOperatorToSparqlRelationOperator(filterOperator: string): SparqlRelationOperator | undefined { - switch (filterOperator) { - case '=': - return SparqlRelationOperator.EqualThanRelation; - case '<': - return SparqlRelationOperator.LessThanRelation; - case '<=': - return SparqlRelationOperator.LessThanOrEqualToRelation; - case '>': - return SparqlRelationOperator.GreaterThanRelation; - case '>=': - return SparqlRelationOperator.GreaterThanOrEqualToRelation; - case '!=': - return SparqlRelationOperator.NotEqualThanRelation; - default: - return undefined; - } -} - -export function reverseRawLogicOperator(logicOperator: string): string | undefined { - switch (logicOperator) { - case LogicOperatorSymbol.And: - return LogicOperatorSymbol.Or; - case LogicOperatorSymbol.Or: - return LogicOperatorSymbol.And; - case LogicOperatorSymbol.Not: - return LogicOperatorSymbol.Exist; - case LogicOperatorSymbol.Exist: - return LogicOperatorSymbol.Not; - default: - return undefined; - } -} - -export function reverseRawOperator(filterOperator: string): string | undefined { - switch (filterOperator) { - case '=': - return '!='; - case '!=': - return '='; - case '<': - return '>='; - case '<=': - return '>'; - case '>': - return '<='; - case '>=': - return '<'; - default: - return undefined; - } -} - -export function reverseOperator(operator: SparqlRelationOperator): SparqlRelationOperator | undefined { - switch (operator) { - case SparqlRelationOperator.LessThanRelation: - return SparqlRelationOperator.GreaterThanOrEqualToRelation; - case SparqlRelationOperator.LessThanOrEqualToRelation: - return SparqlRelationOperator.GreaterThanRelation; - case SparqlRelationOperator.GreaterThanRelation: - return SparqlRelationOperator.LessThanOrEqualToRelation; - case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return SparqlRelationOperator.LessThanRelation; - case SparqlRelationOperator.EqualThanRelation: - return SparqlRelationOperator.NotEqualThanRelation; - case SparqlRelationOperator.NotEqualThanRelation: - return SparqlRelationOperator.EqualThanRelation; - - default: - return undefined; - } -} - -export function reverseLogicOperator(operator: LogicOperator): LogicOperator | undefined { - switch (operator.operatorName()) { - case LogicOperatorSymbol.And: - return new Or(); - case LogicOperatorSymbol.Or: - return new And(); - // The reverse is making an assetion and this operator is useless - case LogicOperatorSymbol.Not: - return undefined; - } -} - -export function inverseFilter(filterExpression: Algebra.Expression) { - if (filterExpression.expressionType === Algebra.expressionTypes.TERM - ) { - if (filterExpression.term.value === 'false') { - filterExpression.term.value = 'true'; - } else if (filterExpression.term.value === 'true') { - filterExpression.term.value = 'false'; - } else { - throw new MisformatedFilterTermError(`The term sent is not a boolean but is this value {${filterExpression.term.value}}`); - } - } else if ( - // If it's an array of terms then we should be able to create a solver expression. - // Given the resulting solver expression we can calculate a solution interval - // that we will add to the domain with regards to the logic operator. - filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && - filterExpression.args.length === 2 - ) { - filterExpression.operator = reverseRawOperator(filterExpression.operator); - } else { - const reversedOperator = reverseRawLogicOperator(filterExpression.operator); - if (reversedOperator) { - filterExpression.operator = reversedOperator; - } - for (const arg of filterExpression.args) { - const reversedOperator = reverseRawLogicOperator(filterExpression.operator); - if (reversedOperator) { - filterExpression.operator = reversedOperator; - } - inverseFilter(arg); - } - } -} diff --git a/packages/actor-extract-links-extract-tree/lib/util-solver.ts b/packages/actor-extract-links-extract-tree/lib/util-solver.ts new file mode 100644 index 000000000..893012006 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/util-solver.ts @@ -0,0 +1,291 @@ +import type * as RDF from 'rdf-js'; +import { Algebra } from 'sparqlalgebrajs'; +import { + MissMatchVariableError, + MisformatedFilterTermError, + UnsupportedDataTypeError, +} from './error'; +import type { LogicOperator } from './LogicOperator'; +import { And, Or, operatorFactory } from './LogicOperator'; +import { SolutionDomain } from './SolutionDomain'; +import { SolutionInterval } from './SolutionInterval'; +import { + SparqlOperandDataTypes, + LogicOperatorReversed, LogicOperatorSymbol, SparqlOperandDataTypesReversed, +} from './solverInterfaces'; +import type { + ISolverExpression, + Variable, +} from './solverInterfaces'; +import { SparqlRelationOperator } from './TreeMetadata'; +import type { ITreeRelation } from './TreeMetadata'; + +const nextUp = require('ulp').nextUp; +const nextDown = require('ulp').nextDown; + +/** + * Convert a TREE relation into a solver expression. + * @param {ITreeRelation} relation - TREE relation. + * @param {Variable} variable - variable of the SPARQL query associated with the tree:path of the relation. + * @returns {ISolverExpression | undefined} Resulting solver expression if the data type is supported by SPARQL + * and the value can be cast into a number. + */ + export function convertTreeRelationToSolverExpression(relation: ITreeRelation, + variable: Variable): + ISolverExpression | undefined { + if (relation.value && relation.type) { + const valueType = SparqlOperandDataTypesReversed.get((relation.value.term).datatype.value); + if (!valueType) { + return undefined; + } + const valueNumber = castSparqlRdfTermIntoNumber(relation.value.value, valueType); + if (!valueNumber && valueNumber !== 0) { + return undefined; + } + + return { + variable, + rawValue: relation.value.value, + valueType, + valueAsNumber: valueNumber, + + operator: relation.type, + }; + } + } + /** + * Check if all the expression provided have a SparqlOperandDataTypes compatible type + * it is considered that all number types are compatible between them. + * @param {ISolverExpression[]} expressions - The subject expression. + * @returns {boolean} Return true if the type are compatible. + */ + export function areTypesCompatible(expressions: ISolverExpression[]): boolean { + const firstType = expressions[0].valueType; + for (const expression of expressions) { + const areIdentical = expression.valueType === firstType; + const areNumbers = isSparqlOperandNumberType(firstType) && + isSparqlOperandNumberType(expression.valueType); + + if (!(areIdentical || areNumbers)) { + return false; + } + } + return true; + } + /** + * Find the solution range of a value and operator which is analogue to an expression. + * @param {number} value + * @param {SparqlRelationOperator} operator + * @returns {SolutionInterval | undefined} The solution range associated with the value and the operator. + */ + export function getSolutionInterval(value: number, operator: SparqlRelationOperator): SolutionInterval | [SolutionInterval, SolutionInterval] | undefined { + switch (operator) { + case SparqlRelationOperator.GreaterThanRelation: + return new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]); + case SparqlRelationOperator.GreaterThanOrEqualToRelation: + return new SolutionInterval([ value, Number.POSITIVE_INFINITY ]); + case SparqlRelationOperator.EqualThanRelation: + return new SolutionInterval([ value, value ]); + case SparqlRelationOperator.LessThanRelation: + return new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]); + case SparqlRelationOperator.LessThanOrEqualToRelation: + return new SolutionInterval([ Number.NEGATIVE_INFINITY, value ]); + case SparqlRelationOperator.NotEqualThanRelation: + return [ + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]), + new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]), + ]; + default: + // Not an operator that is compatible with number. + break; + } + } + /** + * Convert a RDF value into a number. + * @param {string} rdfTermValue - The raw value + * @param {SparqlOperandDataTypes} rdfTermType - The type of the value + * @returns {number | undefined} The resulting number or undefined if the convertion is not possible. + */ + export function castSparqlRdfTermIntoNumber(rdfTermValue: string, + rdfTermType: SparqlOperandDataTypes): + number | undefined { + if ( + rdfTermType === SparqlOperandDataTypes.Decimal || + rdfTermType === SparqlOperandDataTypes.Float || + rdfTermType === SparqlOperandDataTypes.Double + ) { + const val = Number.parseFloat(rdfTermValue); + return Number.isNaN(val) ? undefined : val; + } + + if (rdfTermType === SparqlOperandDataTypes.Boolean) { + if (rdfTermValue === 'true') { + return 1; + } + + if (rdfTermValue === 'false') { + return 0; + } + + return undefined; + } + + if ( + isSparqlOperandNumberType(rdfTermType) + ) { + const val = Number.parseInt(rdfTermValue, 10); + return Number.isNaN(val) ? undefined : val; + } + + if (rdfTermType === SparqlOperandDataTypes.DateTime) { + const val = new Date(rdfTermValue).getTime(); + return Number.isNaN(val) ? undefined : val; + } + + return undefined; + } + /** + * Determine if the type is a number. + * @param {SparqlOperandDataTypes} rdfTermType - The subject type + * @returns {boolean} Return true if the type is a number. + */ + export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean { + return rdfTermType === SparqlOperandDataTypes.Integer || + rdfTermType === SparqlOperandDataTypes.NonPositiveInteger || + rdfTermType === SparqlOperandDataTypes.NegativeInteger || + rdfTermType === SparqlOperandDataTypes.Long || + rdfTermType === SparqlOperandDataTypes.Short || + rdfTermType === SparqlOperandDataTypes.NonNegativeInteger || + rdfTermType === SparqlOperandDataTypes.UnsignedLong || + rdfTermType === SparqlOperandDataTypes.UnsignedInt || + rdfTermType === SparqlOperandDataTypes.UnsignedShort || + rdfTermType === SparqlOperandDataTypes.PositiveInteger || + rdfTermType === SparqlOperandDataTypes.Float || + rdfTermType === SparqlOperandDataTypes.Double || + rdfTermType === SparqlOperandDataTypes.Decimal || + rdfTermType === SparqlOperandDataTypes.Int; + } + /** + * Convert a filter operator to SparqlRelationOperator. + * @param {string} filterOperator - The filter operator. + * @returns {SparqlRelationOperator | undefined} The SparqlRelationOperator corresponding to the filter operator + */ + export function filterOperatorToSparqlRelationOperator(filterOperator: string): SparqlRelationOperator | undefined { + switch (filterOperator) { + case '=': + return SparqlRelationOperator.EqualThanRelation; + case '<': + return SparqlRelationOperator.LessThanRelation; + case '<=': + return SparqlRelationOperator.LessThanOrEqualToRelation; + case '>': + return SparqlRelationOperator.GreaterThanRelation; + case '>=': + return SparqlRelationOperator.GreaterThanOrEqualToRelation; + case '!=': + return SparqlRelationOperator.NotEqualThanRelation; + default: + return undefined; + } + } + /** + * Reverse a logic operator. + * @param {string} logicOperator - A string representation of a logic operator + * @returns {string | undefined} The reversed logic operator or undefined if the input is not a valid operator + */ + export function reverseRawLogicOperator(logicOperator: string): string | undefined { + switch (logicOperator) { + case LogicOperatorSymbol.And: + return LogicOperatorSymbol.Or; + case LogicOperatorSymbol.Or: + return LogicOperatorSymbol.And; + case LogicOperatorSymbol.Not: + return LogicOperatorSymbol.Exist; + case LogicOperatorSymbol.Exist: + return LogicOperatorSymbol.Not; + default: + return undefined; + } + } + + /** + * Reverse a string operator. + * @param {string} filterOperator - A string representation of an operator + * @returns {string | undefined} The reverse operator or undefined if the input is not a valid operator + */ + export function reverseRawOperator(filterOperator: string): string | undefined { + switch (filterOperator) { + case '=': + return '!='; + case '!=': + return '='; + case '<': + return '>='; + case '<=': + return '>'; + case '>': + return '<='; + case '>=': + return '<'; + default: + return undefined; + } + } + + /** + * Reverse a sparqlOperator. + * @param {SparqlRelationOperator} operator - A Sparql operator + * @returns {SparqlRelationOperator | undefined} The reverse operator or undefined if the input is not a supported operator + */ + export function reverseSparqlOperator(operator: SparqlRelationOperator): SparqlRelationOperator | undefined { + switch (operator) { + case SparqlRelationOperator.LessThanRelation: + return SparqlRelationOperator.GreaterThanOrEqualToRelation; + case SparqlRelationOperator.LessThanOrEqualToRelation: + return SparqlRelationOperator.GreaterThanRelation; + case SparqlRelationOperator.GreaterThanRelation: + return SparqlRelationOperator.LessThanOrEqualToRelation; + case SparqlRelationOperator.GreaterThanOrEqualToRelation: + return SparqlRelationOperator.LessThanRelation; + case SparqlRelationOperator.EqualThanRelation: + return SparqlRelationOperator.NotEqualThanRelation; + case SparqlRelationOperator.NotEqualThanRelation: + return SparqlRelationOperator.EqualThanRelation; + + default: + return undefined; + } + } + + + export function inverseFilter(filterExpression: Algebra.Expression) { + if (filterExpression.expressionType === Algebra.expressionTypes.TERM + ) { + if (filterExpression.term.value === 'false') { + filterExpression.term.value = 'true'; + } else { + filterExpression.term.value = 'false'; + } + } else if ( + // If it's an array of terms then we should be able to create a solver expression. + // Given the resulting solver expression we can calculate a solution interval + // that we will add to the domain with regards to the logic operator. + filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && + filterExpression.args.length === 2 + ) { + filterExpression.operator = reverseRawOperator(filterExpression.operator); + } else { + const reversedOperator = reverseRawLogicOperator(filterExpression.operator); + if (reversedOperator) { + filterExpression.operator = reversedOperator; + } + for (const arg of filterExpression.args) { + const reversedOperator = reverseRawLogicOperator(filterExpression.operator); + if (reversedOperator) { + filterExpression.operator = reversedOperator; + } + inverseFilter(arg); + } + } + } + \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index f7db0582c..dc31e2799 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -5,27 +5,36 @@ import { MisformatedFilterTermError, UnsupportedDataTypeError, } from '../lib/error'; -import { Or } from '../lib/LogicOperator'; import { SolutionDomain } from '../lib/SolutionDomain'; import { SolutionInterval } from '../lib/SolutionInterval'; import { - filterOperatorToSparqlRelationOperator, - isSparqlOperandNumberType, - castSparqlRdfTermIntoNumber, - getSolutionInterval, - areTypesCompatible, - convertTreeRelationToSolverExpression, resolveAFilterTerm, isBooleanExpressionTreeRelationFilterSolvable, recursifResolve, } from '../lib/solver'; -import { SparqlOperandDataTypes } from '../lib/solverInterfaces'; +import * as solverFunction from '../lib/solver'; +import { LogicOperatorSymbol, SparqlOperandDataTypes } from '../lib/solverInterfaces'; import type { ISolverExpression, } from '../lib/solverInterfaces'; import { SparqlRelationOperator } from '../lib/TreeMetadata'; import type { ITreeRelation } from '../lib/TreeMetadata'; +import { + convertTreeRelationToSolverExpression, + areTypesCompatible, + castSparqlRdfTermIntoNumber, + filterOperatorToSparqlRelationOperator, + getSolutionInterval, + isSparqlOperandNumberType, + reverseRawLogicOperator, + reverseRawOperator, + reverseSparqlOperator, + inverseFilter +} from '../lib/util-solver'; + +import * as UtilSolver from '../lib/util-solver'; + const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; @@ -35,14 +44,14 @@ describe('solver function', () => { describe('filterOperatorToSparqlRelationOperator', () => { it('should return the RelationOperator given a string representation', () => { const testTable: [string, SparqlRelationOperator][] = [ - [ '=', SparqlRelationOperator.EqualThanRelation ], - [ '<', SparqlRelationOperator.LessThanRelation ], - [ '<=', SparqlRelationOperator.LessThanOrEqualToRelation ], - [ '>', SparqlRelationOperator.GreaterThanRelation ], - [ '>=', SparqlRelationOperator.GreaterThanOrEqualToRelation ], + ['=', SparqlRelationOperator.EqualThanRelation], + ['<', SparqlRelationOperator.LessThanRelation], + ['<=', SparqlRelationOperator.LessThanOrEqualToRelation], + ['>', SparqlRelationOperator.GreaterThanRelation], + ['>=', SparqlRelationOperator.GreaterThanOrEqualToRelation], ]; - for (const [ value, expectedAnswer ] of testTable) { + for (const [value, expectedAnswer] of testTable) { expect(filterOperatorToSparqlRelationOperator(value)).toBe(expectedAnswer); } }); @@ -91,64 +100,64 @@ describe('solver function', () => { describe('castSparqlRdfTermIntoNumber', () => { it('should return the expected number when given an integer', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - [ '19273', SparqlOperandDataTypes.Integer, 19_273 ], - [ '0', SparqlOperandDataTypes.NonPositiveInteger, 0 ], - [ '-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459 ], - [ '121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321 ], - [ '1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213 ], - [ '283', SparqlOperandDataTypes.NonNegativeInteger, 283 ], - [ '-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831 ], - [ '-1234', SparqlOperandDataTypes.UnsignedInt, -1_234 ], - [ '-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234 ], - [ '1234', SparqlOperandDataTypes.PositiveInteger, 1_234 ], + ['19273', SparqlOperandDataTypes.Integer, 19_273], + ['0', SparqlOperandDataTypes.NonPositiveInteger, 0], + ['-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459], + ['121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321], + ['1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213], + ['283', SparqlOperandDataTypes.NonNegativeInteger, 283], + ['-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831], + ['-1234', SparqlOperandDataTypes.UnsignedInt, -1_234], + ['-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234], + ['1234', SparqlOperandDataTypes.PositiveInteger, 1_234], ]; - for (const [ value, valueType, expectedNumber ] of testTable) { + for (const [value, valueType, expectedNumber] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return undefined if a non integer is pass with SparqlOperandDataTypes integer compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - [ 'asbd', SparqlOperandDataTypes.PositiveInteger ], - [ '', SparqlOperandDataTypes.NegativeInteger ], + ['asbd', SparqlOperandDataTypes.PositiveInteger], + ['', SparqlOperandDataTypes.NegativeInteger], ]; - for (const [ value, valueType ] of testTable) { + for (const [value, valueType] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return undefined if a non fraction is pass with SparqlOperandDataTypes fraction compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - [ 'asbd', SparqlOperandDataTypes.Double ], - [ '', SparqlOperandDataTypes.Float ], + ['asbd', SparqlOperandDataTypes.Double], + ['', SparqlOperandDataTypes.Float], ]; - for (const [ value, valueType ] of testTable) { + for (const [value, valueType] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return the expected number when given an decimal', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - [ '1.1', SparqlOperandDataTypes.Decimal, 1.1 ], - [ '2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321 ], - [ '1234.123', SparqlOperandDataTypes.Double, 1_234.123 ], + ['1.1', SparqlOperandDataTypes.Decimal, 1.1], + ['2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321], + ['1234.123', SparqlOperandDataTypes.Double, 1_234.123], ]; - for (const [ value, valueType, expectedNumber ] of testTable) { + for (const [value, valueType, expectedNumber] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return the expected number given a boolean', () => { const testTable: [string, number][] = [ - [ 'true', 1 ], - [ 'false', 0 ], + ['true', 1], + ['false', 0], ]; - for (const [ value, expectedNumber ] of testTable) { + for (const [value, expectedNumber] of testTable) { expect(castSparqlRdfTermIntoNumber(value, SparqlOperandDataTypes.Boolean)).toBe(expectedNumber); } }); @@ -183,27 +192,27 @@ describe('solver function', () => { const testTable: [SparqlRelationOperator, SolutionInterval][] = [ [ SparqlRelationOperator.GreaterThanRelation, - new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]), + new SolutionInterval([nextUp(value), Number.POSITIVE_INFINITY]), ], [ SparqlRelationOperator.GreaterThanOrEqualToRelation, - new SolutionInterval([ value, Number.POSITIVE_INFINITY ]), + new SolutionInterval([value, Number.POSITIVE_INFINITY]), ], [ SparqlRelationOperator.EqualThanRelation, - new SolutionInterval([ value, value ]), + new SolutionInterval([value, value]), ], [ SparqlRelationOperator.LessThanRelation, - new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]), + new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(value)]), ], [ SparqlRelationOperator.LessThanOrEqualToRelation, - new SolutionInterval([ Number.NEGATIVE_INFINITY, value ]), + new SolutionInterval([Number.NEGATIVE_INFINITY, value]), ], ]; - for (const [ operator, expectedRange ] of testTable) { + for (const [operator, expectedRange] of testTable) { expect(getSolutionInterval(value, operator)).toStrictEqual(expectedRange); } }); @@ -527,30 +536,30 @@ describe('solver function', () => { it(`given an algebra expression with a litteral containing an invalid datatype than should return an unsupported datatype error`, - () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; + () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; - expect(resolveAFilterTerm(expression, operator, variable)) - .toBeInstanceOf(UnsupportedDataTypeError); - }); + expect(resolveAFilterTerm(expression, operator, variable)) + .toBeInstanceOf(UnsupportedDataTypeError); + }); it(`given an algebra expression with a litteral containing a literal that cannot be converted into number should return an unsupported datatype error`, () => { @@ -580,6 +589,22 @@ describe('solver function', () => { }); describe('recursifResolve', () => { + + it('given an algebra expression with an unsupported logic operator should throw an error', () => { + const mock = jest.spyOn(UtilSolver, 'getSolutionInterval'); + mock.mockImplementation(() => undefined); + + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2) + }`).input.expression; + + expect(() => recursifResolve( + expression, + 'x', + )).toThrow(); + mock.mockRestore(); + }); it('given an algebra expression with two logicals operators should return the valid solution domain', () => { const expression = translate(` SELECT * WHERE { ?x ?y ?z @@ -588,16 +613,111 @@ describe('solver function', () => { const resp = recursifResolve( expression, - new SolutionDomain(), - new Or(), 'x', ); - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra expression with one logical operators should return the valid solution domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x=2) + }`).input.expression; + + const resp = recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra with a true statement should return an infinite domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(true) + }`).input.expression; + + const resp = recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY])); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); + it('given an algebra with a false statement should return an empty domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(false) + }`).input.expression; + + const resp = recursifResolve( + expression, + 'x', + ); + + expect(resp.isDomainEmpty()).toBe(true); + }); + + it('given an algebra with a not true statement should return an empty domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(!true) + }`).input.expression; + + const resp = recursifResolve( + expression, + 'x', + ); + + expect(resp.isDomainEmpty()).toBe(true); + }); + + + it('given an algebra with a not false statement should return an infinite domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(!false) + }`).input.expression; + + const resp = recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra expression with one not equal logical operators should return the valid solution domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x!=2) + }`).input.expression; + + const resp = recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals([ + new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(2)]), + new SolutionInterval([nextUp(2), Number.POSITIVE_INFINITY]) + ]); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra expression with two logicals operators with a double negation should return the valid solution domain', () => { const expression = translate(` SELECT * WHERE { ?x ?y ?z @@ -606,12 +726,11 @@ describe('solver function', () => { const resp = recursifResolve( expression, - new SolutionDomain(), - new Or(), + 'x', ); - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -624,12 +743,11 @@ describe('solver function', () => { const resp = recursifResolve( expression, - new SolutionDomain(), - new Or(), + 'x', ); - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -643,8 +761,7 @@ describe('solver function', () => { const resp = recursifResolve( expression, - new SolutionDomain(), - new Or(), + 'x', ); @@ -660,14 +777,13 @@ describe('solver function', () => { const resp = recursifResolve( expression, - new SolutionDomain(), - new Or(), + 'x', ); const expectedDomain = SolutionDomain.newWithInitialIntervals( - [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), - new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], + [new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(2)]), + new SolutionInterval([nextUp(2), Number.POSITIVE_INFINITY])], ); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); @@ -682,14 +798,13 @@ describe('solver function', () => { const resp = recursifResolve( expression, - new SolutionDomain(), - new Or(), + 'x', ); const expectedDomain = SolutionDomain.newWithInitialIntervals( - [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), - new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], + [new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(2)]), + new SolutionInterval([nextUp(2), Number.POSITIVE_INFINITY])], ); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); @@ -704,14 +819,12 @@ describe('solver function', () => { const resp = recursifResolve( expression, - new SolutionDomain(), - new Or(), 'x', ); const expectedDomain = SolutionDomain.newWithInitialIntervals([ - new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(3) ]), - new SolutionInterval([ nextUp(3), Number.POSITIVE_INFINITY ]), + new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(3)]), + new SolutionInterval([nextUp(3), Number.POSITIVE_INFINITY]), ]); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -1054,36 +1167,6 @@ describe('solver function', () => { expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); - it('should accept the link if a filter term is malformated', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable('x'), - }, - ], - }; - - const variable = 'x'; - - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); - }); - it('should accept the link if a filter term with no args is not a boolean', () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, @@ -1313,4 +1396,54 @@ describe('solver function', () => { expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); }); }); + + describe('reverseRawLogicOperator', () => { + it('given an non existing operator should return undefined', () => { + expect(reverseRawLogicOperator('foo')).toBeUndefined(); + }); + + it('given an non existing operator should return undefined', () => { + for (const operator in LogicOperatorSymbol) { + expect(reverseRawLogicOperator(LogicOperatorSymbol[operator])).toBeDefined(); + } + }); + }); + + describe('reverseRawOperator', () => { + it('given an invalid operator should return undefined', () => { + expect(reverseRawOperator('')).toBeUndefined(); + }); + + it('given a valid operator should return an operator', () => { + for (const operator of ['=', '!=', '<', '<=', '>', '>=']) { + expect(reverseRawOperator(operator)).toBeDefined(); + } + }); + }); + + describe('reverseSparqlOperator',()=>{ + it('given an unsupported operator should return undefined',()=>{ + for(const operator of [ + SparqlRelationOperator.GeospatiallyContainsRelation, + SparqlRelationOperator.SubstringRelation, + SparqlRelationOperator.PrefixRelation + ]){ + expect(reverseSparqlOperator(operator)).toBeUndefined(); + } + }); + + it('given a supported operator should return an operator',()=>{ + for(const operator of [ + SparqlRelationOperator.LessThanRelation, + SparqlRelationOperator.LessThanOrEqualToRelation, + SparqlRelationOperator.GreaterThanRelation, + SparqlRelationOperator.GreaterThanOrEqualToRelation, + SparqlRelationOperator.EqualThanRelation, + SparqlRelationOperator.NotEqualThanRelation + ]){ + expect(reverseSparqlOperator(operator)).toBeDefined(); + } + }); + + }); }); From ff09f0676802842868c8d305927f5a840d45ff3a Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 30 May 2023 14:13:58 +0200 Subject: [PATCH 167/189] lint-fix --- .../lib/LogicOperator.ts | 39 ++- .../lib/SolutionDomain.ts | 6 +- .../lib/solver.ts | 40 ++- .../lib/solverUtil.ts | 287 +++++++++++++++++ .../lib/util-solver.ts | 291 ------------------ .../test/LogicOperator-test.ts | 188 ++++++----- .../test/solver-test.ts | 214 +++++++------ 7 files changed, 531 insertions(+), 534 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/lib/solverUtil.ts delete mode 100644 packages/actor-extract-links-extract-tree/lib/util-solver.ts diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index f6923d470..0e48326ce 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -1,13 +1,16 @@ import { SolutionDomain } from './SolutionDomain'; import { SolutionInterval } from './SolutionInterval'; import { LogicOperatorSymbol } from './solverInterfaces'; -export interface LogicOperator { - apply: ({ interval, domain }: { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }) => SolutionDomain; + +export interface ILogicOperator { + apply: ({ interval, domain }: + { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }) => SolutionDomain; operatorName: () => LogicOperatorSymbol; } -export class Or implements LogicOperator { - public apply({ interval, domain }: { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { +export class Or implements ILogicOperator { + public apply({ interval, domain }: + { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { if (Array.isArray(interval)) { domain = this.apply({ interval: interval[0], domain }); return this.apply({ interval: interval[1], domain }); @@ -41,10 +44,10 @@ export class Or implements LogicOperator { } } -export class And implements LogicOperator { - apply({ interval, domain }: { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { +export class And implements ILogicOperator { + public apply({ interval, domain }: + { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { if (Array.isArray(interval)) { - if (interval[0].isOverlapping(interval[1])) { return domain; } @@ -57,24 +60,28 @@ export class And implements LogicOperator { if (cannotAddDomain1 && cannotAddDomain2) { return domain; - } if (!cannotAddDomain1 && cannotAddDomain2) { + } + + if (!cannotAddDomain1 && cannotAddDomain2) { return testDomain1; - } if (cannotAddDomain1 && !cannotAddDomain2) { + } + + if (cannotAddDomain1 && !cannotAddDomain2) { return testDomain2; } let intervalRes: SolutionInterval; let newDomain: SolutionDomain; - if(testDomain1.getDomain().length > testDomain2.getDomain().length){ + if (testDomain1.getDomain().length > testDomain2.getDomain().length) { intervalRes = interval[1]; newDomain = testDomain1; - }else{ + } else { intervalRes = interval[0]; newDomain = testDomain2; } return new Or().apply({ - interval: intervalRes, domain: newDomain + interval: intervalRes, domain: newDomain, }); } const newDomain: SolutionInterval[] = []; @@ -105,14 +112,14 @@ export class And implements LogicOperator { } } -const OPERATOR_MAP = new Map( +const OPERATOR_MAP = new Map( [ - [new Or().operatorName(), new Or()], - [new And().operatorName(), new And()], + [ new Or().operatorName(), new Or() ], + [ new And().operatorName(), new And() ], ], ); -export function operatorFactory(operatorSymbol: LogicOperatorSymbol): LogicOperator { +export function operatorFactory(operatorSymbol: LogicOperatorSymbol): ILogicOperator { const operator = OPERATOR_MAP.get(operatorSymbol); if (!operator) { throw new RangeError('The operator doesn\'t exist or is not supported.'); diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 5ac005afe..0243e84c6 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -23,7 +23,7 @@ export class SolutionDomain { return false; } - for (const i in this.domain) { + for (let i = 0; i !== this.domain.length; i++) { if (!(this.domain[i].lower === other.domain[i].lower && this.domain[i].upper === other.domain[i].upper )) { @@ -41,7 +41,9 @@ export class SolutionDomain { public isDomainEmpty(): boolean { if (this.domain.length === 0) { return true; - } if (this.domain.length === 1 && this.domain[0].isEmpty) { + } + + if (this.domain.length === 1 && this.domain[0].isEmpty) { return true; } return false; diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 148717f8d..de6edb816 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -1,30 +1,26 @@ -import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; import { MissMatchVariableError, MisformatedFilterTermError, UnsupportedDataTypeError, } from './error'; -import type { LogicOperator } from './LogicOperator'; +import type { ILogicOperator } from './LogicOperator'; import { And, Or, operatorFactory } from './LogicOperator'; import { SolutionDomain } from './SolutionDomain'; import { SolutionInterval } from './SolutionInterval'; import { - SparqlOperandDataTypes, LogicOperatorReversed, LogicOperatorSymbol, SparqlOperandDataTypesReversed, } from './solverInterfaces'; -import type { - ISolverExpression, +import type { ISolverExpression, Variable, -} from './solverInterfaces'; -import { SparqlRelationOperator } from './TreeMetadata'; -import type { ITreeRelation } from './TreeMetadata'; -import {convertTreeRelationToSolverExpression, - castSparqlRdfTermIntoNumber, - filterOperatorToSparqlRelationOperator, - getSolutionInterval, - inverseFilter - } from './util-solver'; + + SparqlOperandDataTypes } from './solverInterfaces'; +import { convertTreeRelationToSolverExpression, + castSparqlRdfTermIntoNumber, + filterOperatorToSparqlRelationOperator, + getSolutionInterval, + inverseFilter } from './solverUtil'; +import type { SparqlRelationOperator, ITreeRelation } from './TreeMetadata'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; @@ -159,9 +155,11 @@ export function resolveAFilterTerm(expression: Algebra.Expression, /** * Recursively traverse the filter expression and calculate the domain until it get to the current expression. * It will thrown an error if the expression is badly formated or if it's impossible to get the solution range. - * @param {Algebra.Expression} filterExpression - The current filter expression that we are traversing + * @param {Algebra.Expression} filterExpression - + * The current filter expression that we are traversing * @param {SolutionDomain} domain - The current resultant solution domain - * @param {LogicOperatorSymbol} logicOperator - The current logic operator that we have to apply to the boolean expression + * @param {LogicOperatorSymbol} logicOperator + * - The current logic operator that we have to apply to the boolean expression * @param {Variable} variable - The variable targeted inside the filter expression * @param {boolean} notExpression * @returns {SolutionDomain} The solution domain of the whole expression @@ -170,7 +168,7 @@ export function recursifResolve( filterExpression: Algebra.Expression, variable: Variable, domain: SolutionDomain = new SolutionDomain(), - logicOperator: LogicOperator = new Or(), + logicOperator: ILogicOperator = new Or(), ): SolutionDomain { if (filterExpression.expressionType === Algebra.expressionTypes.TERM ) { @@ -181,7 +179,7 @@ export function recursifResolve( domain = logicOperator.apply({ interval: A_FALSE_EXPRESSION, domain }); } else { domain = logicOperator.apply({ interval: A_TRUE_EXPRESSION, domain }); - } + } } else if ( // If it's an array of terms then we should be able to create a solver expression. // Given the resulting solver expression we can calculate a solution interval @@ -191,7 +189,7 @@ export function recursifResolve( ) { const rawOperator = filterExpression.operator; const operator = filterOperatorToSparqlRelationOperator(rawOperator); - if (operator && logicOperator.operatorName() != LogicOperatorSymbol.Not) { + if (operator && logicOperator.operatorName() !== LogicOperatorSymbol.Not) { const solverExpression = resolveAFilterTerm(filterExpression, operator, variable); let solutionInterval: SolutionInterval | [SolutionInterval, SolutionInterval] | undefined; if (solverExpression instanceof MissMatchVariableError) { @@ -218,8 +216,8 @@ export function recursifResolve( inverseFilter(arg); domain = recursifResolve(arg, variable, domain, logicOperator); } else { - const logicOperator = operatorFactory(logicOperatorSymbol); - domain = recursifResolve(arg, variable, domain, logicOperator); + const newLogicOperator = operatorFactory(logicOperatorSymbol); + domain = recursifResolve(arg, variable, domain, newLogicOperator); } } } diff --git a/packages/actor-extract-links-extract-tree/lib/solverUtil.ts b/packages/actor-extract-links-extract-tree/lib/solverUtil.ts new file mode 100644 index 000000000..5d1e1d715 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/solverUtil.ts @@ -0,0 +1,287 @@ +import type * as RDF from 'rdf-js'; +import { Algebra } from 'sparqlalgebrajs'; + +import { SolutionInterval } from './SolutionInterval'; +import { + SparqlOperandDataTypes, LogicOperatorSymbol, SparqlOperandDataTypesReversed, +} from './solverInterfaces'; +import type { + ISolverExpression, + Variable, +} from './solverInterfaces'; +import { SparqlRelationOperator } from './TreeMetadata'; +import type { ITreeRelation } from './TreeMetadata'; + +const nextUp = require('ulp').nextUp; +const nextDown = require('ulp').nextDown; + +/** + * Convert a TREE relation into a solver expression. + * @param {ITreeRelation} relation - TREE relation. + * @param {Variable} variable - variable of the SPARQL query associated with the tree:path of the relation. + * @returns {ISolverExpression | undefined} Resulting solver expression if the data type is supported by SPARQL + * and the value can be cast into a number. + */ +export function convertTreeRelationToSolverExpression(relation: ITreeRelation, + variable: Variable): + ISolverExpression | undefined { + if (relation.value && relation.type) { + const valueType = SparqlOperandDataTypesReversed.get((relation.value.term).datatype.value); + if (!valueType) { + return undefined; + } + const valueNumber = castSparqlRdfTermIntoNumber(relation.value.value, valueType); + if (!valueNumber && valueNumber !== 0) { + return undefined; + } + + return { + variable, + rawValue: relation.value.value, + valueType, + valueAsNumber: valueNumber, + + operator: relation.type, + }; + } +} +/** + * Check if all the expression provided have a SparqlOperandDataTypes compatible type + * it is considered that all number types are compatible between them. + * @param {ISolverExpression[]} expressions - The subject expression. + * @returns {boolean} Return true if the type are compatible. + */ +export function areTypesCompatible(expressions: ISolverExpression[]): boolean { + const firstType = expressions[0].valueType; + for (const expression of expressions) { + const areIdentical = expression.valueType === firstType; + const areNumbers = isSparqlOperandNumberType(firstType) && + isSparqlOperandNumberType(expression.valueType); + + if (!(areIdentical || areNumbers)) { + return false; + } + } + return true; +} +/** + * Find the solution range of a value and operator which is analogue to an expression. + * @param {number} value + * @param {SparqlRelationOperator} operator + * @returns {SolutionInterval | undefined} The solution range associated with the value and the operator. + */ +export function getSolutionInterval(value: number, operator: SparqlRelationOperator): +SolutionInterval | [SolutionInterval, SolutionInterval] | undefined { + switch (operator) { + case SparqlRelationOperator.GreaterThanRelation: + return new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]); + case SparqlRelationOperator.GreaterThanOrEqualToRelation: + return new SolutionInterval([ value, Number.POSITIVE_INFINITY ]); + case SparqlRelationOperator.EqualThanRelation: + return new SolutionInterval([ value, value ]); + case SparqlRelationOperator.LessThanRelation: + return new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]); + case SparqlRelationOperator.LessThanOrEqualToRelation: + return new SolutionInterval([ Number.NEGATIVE_INFINITY, value ]); + case SparqlRelationOperator.NotEqualThanRelation: + return [ + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]), + new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]), + ]; + default: + // Not an operator that is compatible with number. + break; + } +} +/** + * Convert a RDF value into a number. + * @param {string} rdfTermValue - The raw value + * @param {SparqlOperandDataTypes} rdfTermType - The type of the value + * @returns {number | undefined} The resulting number or undefined if the convertion is not possible. + */ +export function castSparqlRdfTermIntoNumber(rdfTermValue: string, + rdfTermType: SparqlOperandDataTypes): + number | undefined { + if ( + rdfTermType === SparqlOperandDataTypes.Decimal || + rdfTermType === SparqlOperandDataTypes.Float || + rdfTermType === SparqlOperandDataTypes.Double + ) { + const val = Number.parseFloat(rdfTermValue); + return Number.isNaN(val) ? undefined : val; + } + + if (rdfTermType === SparqlOperandDataTypes.Boolean) { + if (rdfTermValue === 'true') { + return 1; + } + + if (rdfTermValue === 'false') { + return 0; + } + + return undefined; + } + + if ( + isSparqlOperandNumberType(rdfTermType) + ) { + const val = Number.parseInt(rdfTermValue, 10); + return Number.isNaN(val) ? undefined : val; + } + + if (rdfTermType === SparqlOperandDataTypes.DateTime) { + const val = new Date(rdfTermValue).getTime(); + return Number.isNaN(val) ? undefined : val; + } + + return undefined; +} +/** + * Determine if the type is a number. + * @param {SparqlOperandDataTypes} rdfTermType - The subject type + * @returns {boolean} Return true if the type is a number. + */ +export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean { + return rdfTermType === SparqlOperandDataTypes.Integer || + rdfTermType === SparqlOperandDataTypes.NonPositiveInteger || + rdfTermType === SparqlOperandDataTypes.NegativeInteger || + rdfTermType === SparqlOperandDataTypes.Long || + rdfTermType === SparqlOperandDataTypes.Short || + rdfTermType === SparqlOperandDataTypes.NonNegativeInteger || + rdfTermType === SparqlOperandDataTypes.UnsignedLong || + rdfTermType === SparqlOperandDataTypes.UnsignedInt || + rdfTermType === SparqlOperandDataTypes.UnsignedShort || + rdfTermType === SparqlOperandDataTypes.PositiveInteger || + rdfTermType === SparqlOperandDataTypes.Float || + rdfTermType === SparqlOperandDataTypes.Double || + rdfTermType === SparqlOperandDataTypes.Decimal || + rdfTermType === SparqlOperandDataTypes.Int; +} +/** + * Convert a filter operator to SparqlRelationOperator. + * @param {string} filterOperator - The filter operator. + * @returns {SparqlRelationOperator | undefined} The SparqlRelationOperator corresponding to the filter operator + */ +export function filterOperatorToSparqlRelationOperator(filterOperator: string): SparqlRelationOperator | undefined { + switch (filterOperator) { + case '=': + return SparqlRelationOperator.EqualThanRelation; + case '<': + return SparqlRelationOperator.LessThanRelation; + case '<=': + return SparqlRelationOperator.LessThanOrEqualToRelation; + case '>': + return SparqlRelationOperator.GreaterThanRelation; + case '>=': + return SparqlRelationOperator.GreaterThanOrEqualToRelation; + case '!=': + return SparqlRelationOperator.NotEqualThanRelation; + default: + return undefined; + } +} +/** + * Reverse a logic operator. + * @param {string} logicOperator - A string representation of a logic operator + * @returns {string | undefined} The reversed logic operator or undefined if the input is not a valid operator + */ +export function reverseRawLogicOperator(logicOperator: string): string | undefined { + switch (logicOperator) { + case LogicOperatorSymbol.And: + return LogicOperatorSymbol.Or; + case LogicOperatorSymbol.Or: + return LogicOperatorSymbol.And; + case LogicOperatorSymbol.Not: + return LogicOperatorSymbol.Exist; + case LogicOperatorSymbol.Exist: + return LogicOperatorSymbol.Not; + default: + return undefined; + } +} + +/** + * Reverse a string operator. + * @param {string} filterOperator - A string representation of an operator + * @returns {string | undefined} The reverse operator or undefined if the input is not a valid operator + */ +export function reverseRawOperator(filterOperator: string): string | undefined { + switch (filterOperator) { + case '=': + return '!='; + case '!=': + return '='; + case '<': + return '>='; + case '<=': + return '>'; + case '>': + return '<='; + case '>=': + return '<'; + default: + return undefined; + } +} + +/** + * Reverse a sparqlOperator. + * @param {SparqlRelationOperator} operator - A Sparql operator + * @returns {SparqlRelationOperator | undefined} + * The reverse operator or undefined if the input is not a supported operator + */ +export function reverseSparqlOperator(operator: SparqlRelationOperator): SparqlRelationOperator | undefined { + switch (operator) { + case SparqlRelationOperator.LessThanRelation: + return SparqlRelationOperator.GreaterThanOrEqualToRelation; + case SparqlRelationOperator.LessThanOrEqualToRelation: + return SparqlRelationOperator.GreaterThanRelation; + case SparqlRelationOperator.GreaterThanRelation: + return SparqlRelationOperator.LessThanOrEqualToRelation; + case SparqlRelationOperator.GreaterThanOrEqualToRelation: + return SparqlRelationOperator.LessThanRelation; + case SparqlRelationOperator.EqualThanRelation: + return SparqlRelationOperator.NotEqualThanRelation; + case SparqlRelationOperator.NotEqualThanRelation: + return SparqlRelationOperator.EqualThanRelation; + + default: + return undefined; + } +} + +/** + * Traverse the filter expression tree and reverse each expression. + * @param {Algebra.Expression} filterExpression - Filter expression + */ +export function inverseFilter(filterExpression: Algebra.Expression): void { + if (filterExpression.expressionType === Algebra.expressionTypes.TERM + ) { + if (filterExpression.term.value === 'false') { + filterExpression.term.value = 'true'; + } else { + filterExpression.term.value = 'false'; + } + } else if ( + // If it's an array of terms then we should be able to create a solver expression. + // Given the resulting solver expression we can calculate a solution interval + // that we will add to the domain with regards to the logic operator. + filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && + filterExpression.args.length === 2 + ) { + filterExpression.operator = reverseRawOperator(filterExpression.operator); + } else { + const reversedOperator = reverseRawLogicOperator(filterExpression.operator); + if (reversedOperator) { + filterExpression.operator = reversedOperator; + } + for (const arg of filterExpression.args) { + const newReversedOperator = reverseRawLogicOperator(filterExpression.operator); + if (newReversedOperator) { + filterExpression.operator = newReversedOperator; + } + inverseFilter(arg); + } + } +} diff --git a/packages/actor-extract-links-extract-tree/lib/util-solver.ts b/packages/actor-extract-links-extract-tree/lib/util-solver.ts deleted file mode 100644 index 893012006..000000000 --- a/packages/actor-extract-links-extract-tree/lib/util-solver.ts +++ /dev/null @@ -1,291 +0,0 @@ -import type * as RDF from 'rdf-js'; -import { Algebra } from 'sparqlalgebrajs'; -import { - MissMatchVariableError, - MisformatedFilterTermError, - UnsupportedDataTypeError, -} from './error'; -import type { LogicOperator } from './LogicOperator'; -import { And, Or, operatorFactory } from './LogicOperator'; -import { SolutionDomain } from './SolutionDomain'; -import { SolutionInterval } from './SolutionInterval'; -import { - SparqlOperandDataTypes, - LogicOperatorReversed, LogicOperatorSymbol, SparqlOperandDataTypesReversed, -} from './solverInterfaces'; -import type { - ISolverExpression, - Variable, -} from './solverInterfaces'; -import { SparqlRelationOperator } from './TreeMetadata'; -import type { ITreeRelation } from './TreeMetadata'; - -const nextUp = require('ulp').nextUp; -const nextDown = require('ulp').nextDown; - -/** - * Convert a TREE relation into a solver expression. - * @param {ITreeRelation} relation - TREE relation. - * @param {Variable} variable - variable of the SPARQL query associated with the tree:path of the relation. - * @returns {ISolverExpression | undefined} Resulting solver expression if the data type is supported by SPARQL - * and the value can be cast into a number. - */ - export function convertTreeRelationToSolverExpression(relation: ITreeRelation, - variable: Variable): - ISolverExpression | undefined { - if (relation.value && relation.type) { - const valueType = SparqlOperandDataTypesReversed.get((relation.value.term).datatype.value); - if (!valueType) { - return undefined; - } - const valueNumber = castSparqlRdfTermIntoNumber(relation.value.value, valueType); - if (!valueNumber && valueNumber !== 0) { - return undefined; - } - - return { - variable, - rawValue: relation.value.value, - valueType, - valueAsNumber: valueNumber, - - operator: relation.type, - }; - } - } - /** - * Check if all the expression provided have a SparqlOperandDataTypes compatible type - * it is considered that all number types are compatible between them. - * @param {ISolverExpression[]} expressions - The subject expression. - * @returns {boolean} Return true if the type are compatible. - */ - export function areTypesCompatible(expressions: ISolverExpression[]): boolean { - const firstType = expressions[0].valueType; - for (const expression of expressions) { - const areIdentical = expression.valueType === firstType; - const areNumbers = isSparqlOperandNumberType(firstType) && - isSparqlOperandNumberType(expression.valueType); - - if (!(areIdentical || areNumbers)) { - return false; - } - } - return true; - } - /** - * Find the solution range of a value and operator which is analogue to an expression. - * @param {number} value - * @param {SparqlRelationOperator} operator - * @returns {SolutionInterval | undefined} The solution range associated with the value and the operator. - */ - export function getSolutionInterval(value: number, operator: SparqlRelationOperator): SolutionInterval | [SolutionInterval, SolutionInterval] | undefined { - switch (operator) { - case SparqlRelationOperator.GreaterThanRelation: - return new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]); - case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return new SolutionInterval([ value, Number.POSITIVE_INFINITY ]); - case SparqlRelationOperator.EqualThanRelation: - return new SolutionInterval([ value, value ]); - case SparqlRelationOperator.LessThanRelation: - return new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]); - case SparqlRelationOperator.LessThanOrEqualToRelation: - return new SolutionInterval([ Number.NEGATIVE_INFINITY, value ]); - case SparqlRelationOperator.NotEqualThanRelation: - return [ - new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]), - new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]), - ]; - default: - // Not an operator that is compatible with number. - break; - } - } - /** - * Convert a RDF value into a number. - * @param {string} rdfTermValue - The raw value - * @param {SparqlOperandDataTypes} rdfTermType - The type of the value - * @returns {number | undefined} The resulting number or undefined if the convertion is not possible. - */ - export function castSparqlRdfTermIntoNumber(rdfTermValue: string, - rdfTermType: SparqlOperandDataTypes): - number | undefined { - if ( - rdfTermType === SparqlOperandDataTypes.Decimal || - rdfTermType === SparqlOperandDataTypes.Float || - rdfTermType === SparqlOperandDataTypes.Double - ) { - const val = Number.parseFloat(rdfTermValue); - return Number.isNaN(val) ? undefined : val; - } - - if (rdfTermType === SparqlOperandDataTypes.Boolean) { - if (rdfTermValue === 'true') { - return 1; - } - - if (rdfTermValue === 'false') { - return 0; - } - - return undefined; - } - - if ( - isSparqlOperandNumberType(rdfTermType) - ) { - const val = Number.parseInt(rdfTermValue, 10); - return Number.isNaN(val) ? undefined : val; - } - - if (rdfTermType === SparqlOperandDataTypes.DateTime) { - const val = new Date(rdfTermValue).getTime(); - return Number.isNaN(val) ? undefined : val; - } - - return undefined; - } - /** - * Determine if the type is a number. - * @param {SparqlOperandDataTypes} rdfTermType - The subject type - * @returns {boolean} Return true if the type is a number. - */ - export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean { - return rdfTermType === SparqlOperandDataTypes.Integer || - rdfTermType === SparqlOperandDataTypes.NonPositiveInteger || - rdfTermType === SparqlOperandDataTypes.NegativeInteger || - rdfTermType === SparqlOperandDataTypes.Long || - rdfTermType === SparqlOperandDataTypes.Short || - rdfTermType === SparqlOperandDataTypes.NonNegativeInteger || - rdfTermType === SparqlOperandDataTypes.UnsignedLong || - rdfTermType === SparqlOperandDataTypes.UnsignedInt || - rdfTermType === SparqlOperandDataTypes.UnsignedShort || - rdfTermType === SparqlOperandDataTypes.PositiveInteger || - rdfTermType === SparqlOperandDataTypes.Float || - rdfTermType === SparqlOperandDataTypes.Double || - rdfTermType === SparqlOperandDataTypes.Decimal || - rdfTermType === SparqlOperandDataTypes.Int; - } - /** - * Convert a filter operator to SparqlRelationOperator. - * @param {string} filterOperator - The filter operator. - * @returns {SparqlRelationOperator | undefined} The SparqlRelationOperator corresponding to the filter operator - */ - export function filterOperatorToSparqlRelationOperator(filterOperator: string): SparqlRelationOperator | undefined { - switch (filterOperator) { - case '=': - return SparqlRelationOperator.EqualThanRelation; - case '<': - return SparqlRelationOperator.LessThanRelation; - case '<=': - return SparqlRelationOperator.LessThanOrEqualToRelation; - case '>': - return SparqlRelationOperator.GreaterThanRelation; - case '>=': - return SparqlRelationOperator.GreaterThanOrEqualToRelation; - case '!=': - return SparqlRelationOperator.NotEqualThanRelation; - default: - return undefined; - } - } - /** - * Reverse a logic operator. - * @param {string} logicOperator - A string representation of a logic operator - * @returns {string | undefined} The reversed logic operator or undefined if the input is not a valid operator - */ - export function reverseRawLogicOperator(logicOperator: string): string | undefined { - switch (logicOperator) { - case LogicOperatorSymbol.And: - return LogicOperatorSymbol.Or; - case LogicOperatorSymbol.Or: - return LogicOperatorSymbol.And; - case LogicOperatorSymbol.Not: - return LogicOperatorSymbol.Exist; - case LogicOperatorSymbol.Exist: - return LogicOperatorSymbol.Not; - default: - return undefined; - } - } - - /** - * Reverse a string operator. - * @param {string} filterOperator - A string representation of an operator - * @returns {string | undefined} The reverse operator or undefined if the input is not a valid operator - */ - export function reverseRawOperator(filterOperator: string): string | undefined { - switch (filterOperator) { - case '=': - return '!='; - case '!=': - return '='; - case '<': - return '>='; - case '<=': - return '>'; - case '>': - return '<='; - case '>=': - return '<'; - default: - return undefined; - } - } - - /** - * Reverse a sparqlOperator. - * @param {SparqlRelationOperator} operator - A Sparql operator - * @returns {SparqlRelationOperator | undefined} The reverse operator or undefined if the input is not a supported operator - */ - export function reverseSparqlOperator(operator: SparqlRelationOperator): SparqlRelationOperator | undefined { - switch (operator) { - case SparqlRelationOperator.LessThanRelation: - return SparqlRelationOperator.GreaterThanOrEqualToRelation; - case SparqlRelationOperator.LessThanOrEqualToRelation: - return SparqlRelationOperator.GreaterThanRelation; - case SparqlRelationOperator.GreaterThanRelation: - return SparqlRelationOperator.LessThanOrEqualToRelation; - case SparqlRelationOperator.GreaterThanOrEqualToRelation: - return SparqlRelationOperator.LessThanRelation; - case SparqlRelationOperator.EqualThanRelation: - return SparqlRelationOperator.NotEqualThanRelation; - case SparqlRelationOperator.NotEqualThanRelation: - return SparqlRelationOperator.EqualThanRelation; - - default: - return undefined; - } - } - - - export function inverseFilter(filterExpression: Algebra.Expression) { - if (filterExpression.expressionType === Algebra.expressionTypes.TERM - ) { - if (filterExpression.term.value === 'false') { - filterExpression.term.value = 'true'; - } else { - filterExpression.term.value = 'false'; - } - } else if ( - // If it's an array of terms then we should be able to create a solver expression. - // Given the resulting solver expression we can calculate a solution interval - // that we will add to the domain with regards to the logic operator. - filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && - filterExpression.args.length === 2 - ) { - filterExpression.operator = reverseRawOperator(filterExpression.operator); - } else { - const reversedOperator = reverseRawLogicOperator(filterExpression.operator); - if (reversedOperator) { - filterExpression.operator = reversedOperator; - } - for (const arg of filterExpression.args) { - const reversedOperator = reverseRawLogicOperator(filterExpression.operator); - if (reversedOperator) { - filterExpression.operator = reversedOperator; - } - inverseFilter(arg); - } - } - } - \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index 77415048e..895819dbf 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -16,69 +16,70 @@ describe('LogicOperator', () => { }); describe('apply', () => { const intervals = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 5]), - new SolutionInterval([10, 10]), - new SolutionInterval([21, 33]), - new SolutionInterval([60, 70]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 33 ]), + new SolutionInterval([ 60, 70 ]), ]; const aDomain = SolutionDomain.newWithInitialIntervals(intervals); it('given an empty domain should be able to add an interval', () => { - const interval = new SolutionInterval([0, 1]); + const interval = new SolutionInterval([ 0, 1 ]); const solutionDomain = new SolutionDomain(); const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals(interval); expect(or.apply({ domain: solutionDomain, interval })).toStrictEqual(expectedSolutionDomain); }); it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { - const intervals = [ - new SolutionInterval([10, 10]), - new SolutionInterval([1, 2]), - new SolutionInterval([-1, 0]), - new SolutionInterval([60, 70]), + const givenIntervals = [ + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 1, 2 ]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 60, 70 ]), ]; let solutionDomain = new SolutionDomain(); - intervals.forEach((interval, idx) => { + givenIntervals.forEach((interval, idx) => { solutionDomain = or.apply({ domain: solutionDomain, interval }); expect(solutionDomain.getDomain().length).toBe(idx + 1); }); const expectedDomain = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 2]), - new SolutionInterval([10, 10]), - new SolutionInterval([60, 70]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 2 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 60, 70 ]), ]; expect(solutionDomain.getDomain()).toStrictEqual(expectedDomain); }); it('given a domain should not add a range that is inside another', () => { - const anOverlappingInterval = new SolutionInterval([22, 23]); + const anOverlappingInterval = new SolutionInterval([ 22, 23 ]); const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); expect(newDomain.getDomain().length).toBe(aDomain.getDomain().length); expect(newDomain.getDomain()).toStrictEqual(intervals); }); - it('given a domain should create a single domain if all the domain segment are contain into the new range', () => { - const anOverlappingInterval = new SolutionInterval([-100, 100]); - const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); + it('given a domain should create a single domain if all the domain segment are contain into the new range', + () => { + const anOverlappingInterval = new SolutionInterval([ -100, 100 ]); + const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); - expect(newDomain.getDomain().length).toBe(1); - expect(newDomain.getDomain()).toStrictEqual([anOverlappingInterval]); - }); + expect(newDomain.getDomain().length).toBe(1); + expect(newDomain.getDomain()).toStrictEqual([ anOverlappingInterval ]); + }); it('given a domain should fuse multiple domain segment if the new range overlap with them', () => { - const aNewInterval = new SolutionInterval([1, 23]); + const aNewInterval = new SolutionInterval([ 1, 23 ]); const newDomain = or.apply({ domain: aDomain, interval: aNewInterval }); const expectedResultingDomainInterval = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 33]), - new SolutionInterval([60, 70]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 33 ]), + new SolutionInterval([ 60, 70 ]), ]; expect(newDomain.getDomain().length).toBe(3); @@ -97,11 +98,11 @@ describe('LogicOperator', () => { describe('apply', () => { let aDomain: SolutionDomain = new SolutionDomain(); const intervals = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 5]), - new SolutionInterval([10, 10]), - new SolutionInterval([21, 33]), - new SolutionInterval([60, 70]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 33 ]), + new SolutionInterval([ 60, 70 ]), ]; beforeEach(() => { aDomain = SolutionDomain.newWithInitialIntervals(new Array(...intervals)); @@ -109,15 +110,15 @@ describe('LogicOperator', () => { it('should add a range when the domain is empty', () => { const domain = new SolutionDomain(); - const interval = new SolutionInterval([0, 1]); + const interval = new SolutionInterval([ 0, 1 ]); const newDomain = and.apply({ interval, domain }); - expect(newDomain.getDomain()).toStrictEqual([interval]); + expect(newDomain.getDomain()).toStrictEqual([ interval ]); }); it('should return an empty domain if there is no intersection with the new range', () => { - const interval = new SolutionInterval([-200, -100]); + const interval = new SolutionInterval([ -200, -100 ]); const newDomain = and.apply({ interval, domain: aDomain }); @@ -125,18 +126,18 @@ describe('LogicOperator', () => { }); it('given a new range that is inside a part of the domain should only return the intersection', () => { - const interval = new SolutionInterval([22, 30]); + const interval = new SolutionInterval([ 22, 30 ]); const newDomain = and.apply({ interval, domain: aDomain }); - expect(newDomain.getDomain()).toStrictEqual([interval]); + expect(newDomain.getDomain()).toStrictEqual([ interval ]); }); it('given a new range that intersect a part of the domain should only return the intersection', () => { - const interval = new SolutionInterval([19, 25]); + const interval = new SolutionInterval([ 19, 25 ]); const expectedDomain = [ - new SolutionInterval([21, 25]), + new SolutionInterval([ 21, 25 ]), ]; const newDomain = and.apply({ interval, domain: aDomain }); @@ -145,13 +146,13 @@ describe('LogicOperator', () => { }); it('given a new range that intersect multiple part of the domain should only return the intersections', () => { - const interval = new SolutionInterval([-2, 25]); + const interval = new SolutionInterval([ -2, 25 ]); const expectedDomain = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 5]), - new SolutionInterval([10, 10]), - new SolutionInterval([21, 25]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 25 ]), ]; const newDomain = and.apply({ interval, domain: aDomain }); @@ -160,8 +161,8 @@ describe('LogicOperator', () => { }); it('given an empty domain and a last operator and should return an empty domain', () => { - const interval = new SolutionInterval([-2, 25]); - const anotherIntervalNonOverlapping = new SolutionInterval([2_000, 3_000]); + const interval = new SolutionInterval([ -2, 25 ]); + const anotherIntervalNonOverlapping = new SolutionInterval([ 2_000, 3_000 ]); let newDomain = and.apply({ interval, domain: aDomain }); newDomain = and.apply({ interval: anotherIntervalNonOverlapping, domain: newDomain }); @@ -175,99 +176,94 @@ describe('LogicOperator', () => { it('Given an empty domain and two ranges to add that are overlapping the domain should remain empty', () => { const domain = new SolutionDomain(); - const interval1 = new SolutionInterval([0, 2]); - const interval2 = new SolutionInterval([1, 2]); + const interval1 = new SolutionInterval([ 0, 2 ]); + const interval2 = new SolutionInterval([ 1, 2 ]); - const newDomain = and.apply({ interval: [interval1, interval2], domain }); + const newDomain = and.apply({ interval: [ interval1, interval2 ], domain }); expect(newDomain.isDomainEmpty()).toBe(true); }); it('Given a domain and two ranges to add that are overlapping the domain should remain empty', () => { - const interval1 = new SolutionInterval([0, 2]); - const interval2 = new SolutionInterval([1, 2]); - - const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); - - expect(newDomain.equal(aDomain)).toBe(true); - }); - - it('Given a domain and two ranges to add that are overlapping the domain should remain empty', () => { - const interval1 = new SolutionInterval([0, 2]); - const interval2 = new SolutionInterval([1, 2]); + const interval1 = new SolutionInterval([ 0, 2 ]); + const interval2 = new SolutionInterval([ 1, 2 ]); - const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + const newDomain = and.apply({ interval: [ interval1, interval2 ], domain: aDomain }); expect(newDomain.equal(aDomain)).toBe(true); }); - it('Given a domain and two ranges to add that are not overlapping and where those two interval don\'t overlap with the domain should return the initial domain', () => { - const interval1 = new SolutionInterval([-100, -50]); - const interval2 = new SolutionInterval([-25, -23]); + it(`Given a domain and two ranges to add that are not overlapping and where those + two interval don't overlap with the domain should return the initial domain`, () => { + const interval1 = new SolutionInterval([ -100, -50 ]); + const interval2 = new SolutionInterval([ -25, -23 ]); - const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + const newDomain = and.apply({ interval: [ interval1, interval2 ], domain: aDomain }); expect(newDomain.getDomain()).toStrictEqual(aDomain.getDomain()); }); - it('Given a domain and two ranges to add that not overlapping and where the first one is overlap with the domain then should return a new valid domain', () => { - const interval1 = new SolutionInterval([1, 3]); - const interval2 = new SolutionInterval([-25, -23]); + it(`Given a domain and two ranges to add that not overlapping and where the first + one is overlap with the domain then should return a new valid domain`, () => { + const interval1 = new SolutionInterval([ 1, 3 ]); + const interval2 = new SolutionInterval([ -25, -23 ]); - const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + const newDomain = and.apply({ interval: [ interval1, interval2 ], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals(interval1); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); - it('Given a domain and two ranges to add that not overlapping and where the second one is overlap with the domain then should return a new valid domain', () => { - const interval1 = new SolutionInterval([-25, -23]); - const interval2 = new SolutionInterval([1, 3]); + it(`Given a domain and two ranges to add that not overlapping and where the second + one is overlap with the domain then should return a new valid domain`, () => { + const interval1 = new SolutionInterval([ -25, -23 ]); + const interval2 = new SolutionInterval([ 1, 3 ]); - const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + const newDomain = and.apply({ interval: [ interval1, interval2 ], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals(interval2); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); - it('Given a domain and two ranges to add that not overlapping and where the both are overlap and the first one is more overlapping than the second with the domain then should return a new valid domain', () => { - const interval1 = new SolutionInterval([2, 70]); - const interval2 = new SolutionInterval([1, 1]); + it(`Given a domain and two ranges to add that not overlapping and where the both are + overlap and the first one is more overlapping than the second with the domain then should return a new valid domain`, () => { + const interval1 = new SolutionInterval([ 2, 70 ]); + const interval2 = new SolutionInterval([ 1, 1 ]); - const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + const newDomain = and.apply({ interval: [ interval1, interval2 ], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals([ interval2, - new SolutionInterval([2, 5]), - new SolutionInterval([10, 10]), - new SolutionInterval([21, 33]), - new SolutionInterval([60, 70]),]); + new SolutionInterval([ 2, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 33 ]), + new SolutionInterval([ 60, 70 ]) ]); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); - it('Given a domain and two ranges to add that not overlapping and where the both are overlap and the first one is more overlapping than the second with the domain then should return a new valid domain', () => { - const interval1 = new SolutionInterval([1, 1]); + it(`Given a domain and two ranges to add that not overlapping and where the both are overlap and the second + one is more overlapping than the second with the domain then should return a new valid domain`, () => { + const interval1 = new SolutionInterval([ 1, 1 ]); - const interval2 = new SolutionInterval([2, 70]); + const interval2 = new SolutionInterval([ 2, 70 ]); - const newDomain = and.apply({ interval: [interval1, interval2], domain: aDomain }); + const newDomain = and.apply({ interval: [ interval1, interval2 ], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals([ interval1, - new SolutionInterval([2, 5]), - new SolutionInterval([10, 10]), - new SolutionInterval([21, 33]), - new SolutionInterval([60, 70]),]); + new SolutionInterval([ 2, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 33 ]), + new SolutionInterval([ 60, 70 ]) ]); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); - }); }); - describe('operatorFactory', ()=>{ - it('given an unsupported logicOperator should throw an error', ()=>{ - expect(()=>operatorFactory(LogicOperatorSymbol.Exist)).toThrow(); - expect(()=>operatorFactory(LogicOperatorSymbol.Not)).toThrow(); + describe('operatorFactory', () => { + it('given an unsupported logicOperator should throw an error', () => { + expect(() => operatorFactory(LogicOperatorSymbol.Exist)).toThrow(); + expect(() => operatorFactory(LogicOperatorSymbol.Not)).toThrow(); }); - it('Given an Or and an And operator should return an LogicOperator', ()=>{ - for (const operator of [LogicOperatorSymbol.And, LogicOperatorSymbol.Or]){ + it('Given an Or and an And operator should return an LogicOperator', () => { + for (const operator of [ LogicOperatorSymbol.And, LogicOperatorSymbol.Or ]) { expect(operatorFactory(operator).operatorName()).toBe(operator); } }); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index dc31e2799..fff2b04fa 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -12,14 +12,10 @@ import { isBooleanExpressionTreeRelationFilterSolvable, recursifResolve, } from '../lib/solver'; -import * as solverFunction from '../lib/solver'; import { LogicOperatorSymbol, SparqlOperandDataTypes } from '../lib/solverInterfaces'; import type { ISolverExpression, } from '../lib/solverInterfaces'; -import { SparqlRelationOperator } from '../lib/TreeMetadata'; -import type { ITreeRelation } from '../lib/TreeMetadata'; - import { convertTreeRelationToSolverExpression, areTypesCompatible, @@ -30,10 +26,10 @@ import { reverseRawLogicOperator, reverseRawOperator, reverseSparqlOperator, - inverseFilter -} from '../lib/util-solver'; - -import * as UtilSolver from '../lib/util-solver'; +} from '../lib/solverUtil'; +import * as UtilSolver from '../lib/solverUtil'; +import { SparqlRelationOperator } from '../lib/TreeMetadata'; +import type { ITreeRelation } from '../lib/TreeMetadata'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; @@ -44,14 +40,14 @@ describe('solver function', () => { describe('filterOperatorToSparqlRelationOperator', () => { it('should return the RelationOperator given a string representation', () => { const testTable: [string, SparqlRelationOperator][] = [ - ['=', SparqlRelationOperator.EqualThanRelation], - ['<', SparqlRelationOperator.LessThanRelation], - ['<=', SparqlRelationOperator.LessThanOrEqualToRelation], - ['>', SparqlRelationOperator.GreaterThanRelation], - ['>=', SparqlRelationOperator.GreaterThanOrEqualToRelation], + [ '=', SparqlRelationOperator.EqualThanRelation ], + [ '<', SparqlRelationOperator.LessThanRelation ], + [ '<=', SparqlRelationOperator.LessThanOrEqualToRelation ], + [ '>', SparqlRelationOperator.GreaterThanRelation ], + [ '>=', SparqlRelationOperator.GreaterThanOrEqualToRelation ], ]; - for (const [value, expectedAnswer] of testTable) { + for (const [ value, expectedAnswer ] of testTable) { expect(filterOperatorToSparqlRelationOperator(value)).toBe(expectedAnswer); } }); @@ -100,64 +96,64 @@ describe('solver function', () => { describe('castSparqlRdfTermIntoNumber', () => { it('should return the expected number when given an integer', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - ['19273', SparqlOperandDataTypes.Integer, 19_273], - ['0', SparqlOperandDataTypes.NonPositiveInteger, 0], - ['-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459], - ['121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321], - ['1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213], - ['283', SparqlOperandDataTypes.NonNegativeInteger, 283], - ['-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831], - ['-1234', SparqlOperandDataTypes.UnsignedInt, -1_234], - ['-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234], - ['1234', SparqlOperandDataTypes.PositiveInteger, 1_234], + [ '19273', SparqlOperandDataTypes.Integer, 19_273 ], + [ '0', SparqlOperandDataTypes.NonPositiveInteger, 0 ], + [ '-12313459', SparqlOperandDataTypes.NegativeInteger, -12_313_459 ], + [ '121312321321321', SparqlOperandDataTypes.Long, 121_312_321_321_321 ], + [ '1213123213213213', SparqlOperandDataTypes.Short, 1_213_123_213_213_213 ], + [ '283', SparqlOperandDataTypes.NonNegativeInteger, 283 ], + [ '-12131293912831', SparqlOperandDataTypes.UnsignedLong, -12_131_293_912_831 ], + [ '-1234', SparqlOperandDataTypes.UnsignedInt, -1_234 ], + [ '-123341231231234', SparqlOperandDataTypes.UnsignedShort, -123_341_231_231_234 ], + [ '1234', SparqlOperandDataTypes.PositiveInteger, 1_234 ], ]; - for (const [value, valueType, expectedNumber] of testTable) { + for (const [ value, valueType, expectedNumber ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return undefined if a non integer is pass with SparqlOperandDataTypes integer compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - ['asbd', SparqlOperandDataTypes.PositiveInteger], - ['', SparqlOperandDataTypes.NegativeInteger], + [ 'asbd', SparqlOperandDataTypes.PositiveInteger ], + [ '', SparqlOperandDataTypes.NegativeInteger ], ]; - for (const [value, valueType] of testTable) { + for (const [ value, valueType ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return undefined if a non fraction is pass with SparqlOperandDataTypes fraction compatible type', () => { const testTable: [string, SparqlOperandDataTypes][] = [ - ['asbd', SparqlOperandDataTypes.Double], - ['', SparqlOperandDataTypes.Float], + [ 'asbd', SparqlOperandDataTypes.Double ], + [ '', SparqlOperandDataTypes.Float ], ]; - for (const [value, valueType] of testTable) { + for (const [ value, valueType ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBeUndefined(); } }); it('should return the expected number when given an decimal', () => { const testTable: [string, SparqlOperandDataTypes, number][] = [ - ['1.1', SparqlOperandDataTypes.Decimal, 1.1], - ['2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321], - ['1234.123', SparqlOperandDataTypes.Double, 1_234.123], + [ '1.1', SparqlOperandDataTypes.Decimal, 1.1 ], + [ '2132131.121321321', SparqlOperandDataTypes.Float, 2_132_131.121_321_321 ], + [ '1234.123', SparqlOperandDataTypes.Double, 1_234.123 ], ]; - for (const [value, valueType, expectedNumber] of testTable) { + for (const [ value, valueType, expectedNumber ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, valueType)).toBe(expectedNumber); } }); it('should return the expected number given a boolean', () => { const testTable: [string, number][] = [ - ['true', 1], - ['false', 0], + [ 'true', 1 ], + [ 'false', 0 ], ]; - for (const [value, expectedNumber] of testTable) { + for (const [ value, expectedNumber ] of testTable) { expect(castSparqlRdfTermIntoNumber(value, SparqlOperandDataTypes.Boolean)).toBe(expectedNumber); } }); @@ -192,27 +188,27 @@ describe('solver function', () => { const testTable: [SparqlRelationOperator, SolutionInterval][] = [ [ SparqlRelationOperator.GreaterThanRelation, - new SolutionInterval([nextUp(value), Number.POSITIVE_INFINITY]), + new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]), ], [ SparqlRelationOperator.GreaterThanOrEqualToRelation, - new SolutionInterval([value, Number.POSITIVE_INFINITY]), + new SolutionInterval([ value, Number.POSITIVE_INFINITY ]), ], [ SparqlRelationOperator.EqualThanRelation, - new SolutionInterval([value, value]), + new SolutionInterval([ value, value ]), ], [ SparqlRelationOperator.LessThanRelation, - new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(value)]), + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(value) ]), ], [ SparqlRelationOperator.LessThanOrEqualToRelation, - new SolutionInterval([Number.NEGATIVE_INFINITY, value]), + new SolutionInterval([ Number.NEGATIVE_INFINITY, value ]), ], ]; - for (const [operator, expectedRange] of testTable) { + for (const [ operator, expectedRange ] of testTable) { expect(getSolutionInterval(value, operator)).toStrictEqual(expectedRange); } }); @@ -536,30 +532,30 @@ describe('solver function', () => { it(`given an algebra expression with a litteral containing an invalid datatype than should return an unsupported datatype error`, - () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; + () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; - expect(resolveAFilterTerm(expression, operator, variable)) - .toBeInstanceOf(UnsupportedDataTypeError); - }); + expect(resolveAFilterTerm(expression, operator, variable)) + .toBeInstanceOf(UnsupportedDataTypeError); + }); it(`given an algebra expression with a litteral containing a literal that cannot be converted into number should return an unsupported datatype error`, () => { @@ -589,10 +585,9 @@ describe('solver function', () => { }); describe('recursifResolve', () => { - it('given an algebra expression with an unsupported logic operator should throw an error', () => { const mock = jest.spyOn(UtilSolver, 'getSolutionInterval'); - mock.mockImplementation(() => undefined); + mock.mockImplementation((): undefined => { return undefined; }); const expression = translate(` SELECT * WHERE { ?x ?y ?z @@ -616,7 +611,7 @@ describe('solver function', () => { 'x', ); - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -632,7 +627,7 @@ describe('solver function', () => { 'x', ); - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -648,7 +643,9 @@ describe('solver function', () => { 'x', ); - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY])); + const expectedDomain = SolutionDomain.newWithInitialIntervals( + new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), + ); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -681,7 +678,6 @@ describe('solver function', () => { expect(resp.isDomainEmpty()).toBe(true); }); - it('given an algebra with a not false statement should return an infinite domain', () => { const expression = translate(` SELECT * WHERE { ?x ?y ?z @@ -693,32 +689,35 @@ describe('solver function', () => { 'x', ); - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY])); + const expectedDomain = SolutionDomain.newWithInitialIntervals( + new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), + ); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); - it('given an algebra expression with one not equal logical operators should return the valid solution domain', () => { - const expression = translate(` + it('given an algebra expression with one not equal logical operators should return the valid solution domain', + () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(?x!=2) }`).input.expression; - const resp = recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals([ - new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(2)]), - new SolutionInterval([nextUp(2), Number.POSITIVE_INFINITY]) - ]); + const resp = recursifResolve( + expression, + 'x', + ); - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); + const expectedDomain = SolutionDomain.newWithInitialIntervals([ + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), + new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]), + ]); + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); - it('given an algebra expression with two logicals operators with a double negation should return the valid solution domain', () => { + it(`given an algebra expression with two logicals operators with a double negation should + return the valid solution domain`, () => { const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(!(?x=2)) && ?x<5) @@ -730,7 +729,7 @@ describe('solver function', () => { 'x', ); - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -747,7 +746,7 @@ describe('solver function', () => { 'x', ); - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([2, 2])); + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -782,8 +781,8 @@ describe('solver function', () => { ); const expectedDomain = SolutionDomain.newWithInitialIntervals( - [new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(2)]), - new SolutionInterval([nextUp(2), Number.POSITIVE_INFINITY])], + [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), + new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], ); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); @@ -803,8 +802,8 @@ describe('solver function', () => { ); const expectedDomain = SolutionDomain.newWithInitialIntervals( - [new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(2)]), - new SolutionInterval([nextUp(2), Number.POSITIVE_INFINITY])], + [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), + new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], ); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); @@ -823,8 +822,8 @@ describe('solver function', () => { ); const expectedDomain = SolutionDomain.newWithInitialIntervals([ - new SolutionInterval([Number.NEGATIVE_INFINITY, nextDown(3)]), - new SolutionInterval([nextUp(3), Number.POSITIVE_INFINITY]), + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(3) ]), + new SolutionInterval([ nextUp(3), Number.POSITIVE_INFINITY ]), ]); expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -1402,7 +1401,7 @@ describe('solver function', () => { expect(reverseRawLogicOperator('foo')).toBeUndefined(); }); - it('given an non existing operator should return undefined', () => { + it('given an existing operator should return an operator', () => { for (const operator in LogicOperatorSymbol) { expect(reverseRawLogicOperator(LogicOperatorSymbol[operator])).toBeDefined(); } @@ -1415,35 +1414,34 @@ describe('solver function', () => { }); it('given a valid operator should return an operator', () => { - for (const operator of ['=', '!=', '<', '<=', '>', '>=']) { + for (const operator of [ '=', '!=', '<', '<=', '>', '>=' ]) { expect(reverseRawOperator(operator)).toBeDefined(); } }); }); - describe('reverseSparqlOperator',()=>{ - it('given an unsupported operator should return undefined',()=>{ - for(const operator of [ + describe('reverseSparqlOperator', () => { + it('given an unsupported operator should return undefined', () => { + for (const operator of [ SparqlRelationOperator.GeospatiallyContainsRelation, SparqlRelationOperator.SubstringRelation, - SparqlRelationOperator.PrefixRelation - ]){ + SparqlRelationOperator.PrefixRelation, + ]) { expect(reverseSparqlOperator(operator)).toBeUndefined(); } }); - it('given a supported operator should return an operator',()=>{ - for(const operator of [ + it('given a supported operator should return an operator', () => { + for (const operator of [ SparqlRelationOperator.LessThanRelation, SparqlRelationOperator.LessThanOrEqualToRelation, SparqlRelationOperator.GreaterThanRelation, SparqlRelationOperator.GreaterThanOrEqualToRelation, SparqlRelationOperator.EqualThanRelation, - SparqlRelationOperator.NotEqualThanRelation - ]){ + SparqlRelationOperator.NotEqualThanRelation, + ]) { expect(reverseSparqlOperator(operator)).toBeDefined(); } }); - }); }); From 64cc5c25053560605d772ea22ec04f771ccd6509 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 30 May 2023 14:18:16 +0200 Subject: [PATCH 168/189] useless variable deleted --- packages/actor-extract-links-extract-tree/lib/solver.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index de6edb816..f376c599b 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -22,9 +22,6 @@ import { convertTreeRelationToSolverExpression, inverseFilter } from './solverUtil'; import type { SparqlRelationOperator, ITreeRelation } from './TreeMetadata'; -const nextUp = require('ulp').nextUp; -const nextDown = require('ulp').nextDown; - const A_TRUE_EXPRESSION: SolutionInterval = new SolutionInterval( [ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ], ); From 55405c021fee9f5e07ab8d89bc0af87cc262e436 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 30 May 2023 17:00:02 +0200 Subject: [PATCH 169/189] Decoupling solver. --- .../lib/SolverInput.ts | 277 ++++++++++++++++++ .../lib/error.ts | 4 +- .../lib/solver.ts | 214 +------------- .../lib/solverInterfaces.ts | 17 ++ .../lib/solverUtil.ts | 30 -- 5 files changed, 303 insertions(+), 239 deletions(-) create mode 100644 packages/actor-extract-links-extract-tree/lib/SolverInput.ts diff --git a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts new file mode 100644 index 000000000..32c60edb7 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts @@ -0,0 +1,277 @@ +import type * as RDF from 'rdf-js'; +import { Algebra } from "sparqlalgebrajs"; +import { SolutionDomain } from "./SolutionDomain"; +import { ISolverInput, ResolvedType } from "./solverInterfaces"; +import { + MissMatchVariableError, + MisformatedExpressionError, + UnsupportedDataTypeError +} from './error'; +import type { ILogicOperator } from './LogicOperator'; +import { And, Or, operatorFactory } from './LogicOperator'; +import { SolutionInterval } from './SolutionInterval'; +import { + LogicOperatorReversed, + LogicOperatorSymbol, + SparqlOperandDataTypesReversed, + SparqlOperandDataTypes +} from './solverInterfaces'; +import type { + ISolverExpression, + Variable, + +} from './solverInterfaces'; +import { + castSparqlRdfTermIntoNumber, + filterOperatorToSparqlRelationOperator, + getSolutionInterval, + inverseFilter +} from './solverUtil'; +import type { ITreeRelation, SparqlRelationOperator } from './TreeMetadata'; + +const A_TRUE_EXPRESSION: SolutionInterval = new SolutionInterval( + [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY], +); +const A_FALSE_EXPRESSION: SolutionInterval = new SolutionInterval([]); + +export class SparlFilterExpressionSolverInput implements ISolverInput { + public readonly filterExpression: Algebra.Expression; + public readonly variable: Variable; + public readonly domain: SolutionDomain; + + public constructor(filterExpression: Algebra.Expression, variable: Variable) { + this.filterExpression = filterExpression; + try { + this.domain = this.recursifResolve(this.filterExpression, variable); + + } catch (error: unknown) { + // A filter term was missformed we let the query engine return an error to the user and by precaution + // we accept the link in case the error is from the solver and not the filter expression + if (error instanceof MisformatedExpressionError) { + this.domain = SolutionDomain.newWithInitialIntervals(A_TRUE_EXPRESSION); + } + + // We don't support the data type so let need to explore that link to not diminush the completness of the result + if (error instanceof UnsupportedDataTypeError) { + this.domain = SolutionDomain.newWithInitialIntervals(A_TRUE_EXPRESSION); + } + + /* istanbul ignore next */ + // If it's unexpected error we throw it + throw error; + } finally { + Object.freeze(this); + Object.freeze(this.domain); + Object.freeze(this.variable); + } + } + + /** + * Recursively traverse the filter expression and calculate the domain until it get to the current expression. + * It will thrown an error if the expression is badly formated or if it's impossible to get the solution range. + * @param {Algebra.Expression} filterExpression - + * The current filter expression that we are traversing + * @param {SolutionDomain} domain - The current resultant solution domain + * @param {LogicOperatorSymbol} logicOperator + * - The current logic operator that we have to apply to the boolean expression + * @param {Variable} variable - The variable targeted inside the filter expression + * @param {boolean} notExpression + * @returns {SolutionDomain} The solution domain of the whole expression + */ + public recursifResolve( + filterExpression: Algebra.Expression, + variable: Variable, + domain: SolutionDomain = new SolutionDomain(), + logicOperator: ILogicOperator = new Or(), + ): SolutionDomain { + if (filterExpression.expressionType === Algebra.expressionTypes.TERM + ) { + // In that case we are confronted with a boolean expression + // add the associated interval into the domain in relation to + // the logic operator. + if (filterExpression.term.value === 'false') { + domain = logicOperator.apply({ interval: A_FALSE_EXPRESSION, domain }); + } else { + domain = logicOperator.apply({ interval: A_TRUE_EXPRESSION, domain }); + } + } else if ( + // If it's an array of terms then we should be able to create a solver expression. + // Given the resulting solver expression we can calculate a solution interval + // that we will add to the domain with regards to the logic operator. + filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && + filterExpression.args.length === 2 + ) { + const rawOperator = filterExpression.operator; + const operator = filterOperatorToSparqlRelationOperator(rawOperator); + if (operator && logicOperator.operatorName() !== LogicOperatorSymbol.Not) { + const solverExpression = this.resolveAFilterTerm(filterExpression, operator, variable); + let solutionInterval: SolutionInterval | [SolutionInterval, SolutionInterval] | undefined; + if (solverExpression instanceof MissMatchVariableError) { + solutionInterval = A_TRUE_EXPRESSION; + } else if (solverExpression instanceof Error) { + throw solverExpression; + } else { + solutionInterval = getSolutionInterval(solverExpression.valueAsNumber, solverExpression.operator); + if (!solutionInterval) { + throw new UnsupportedDataTypeError('The operator is not supported'); + } + } + domain = logicOperator.apply({ interval: solutionInterval, domain }); + } + } else { + // In that case we are traversing the filter expression TREE. + // We prepare the next recursion and we compute the accumulation of results. + const logicOperatorSymbol = LogicOperatorReversed.get(filterExpression.operator); + if (logicOperatorSymbol) { + for (const arg of filterExpression.args) { + // To solve the not operation we rewrite the path of filter expression to reverse every operation + // e.g, = : != ; > : <= + if (logicOperatorSymbol === LogicOperatorSymbol.Not) { + inverseFilter(arg); + domain = this.recursifResolve(arg, variable, domain, logicOperator); + } else { + const newLogicOperator = operatorFactory(logicOperatorSymbol); + domain = this.recursifResolve(arg, variable, domain, newLogicOperator); + } + } + } + } + return domain; + } + + /** + * From an Algebra expression return an solver expression if possible + * @param {Algebra.Expression} expression - Algebra expression containing the a variable and a litteral. + * @param {SparqlRelationOperator} operator - The SPARQL operator defining the expression. + * @param {Variable} variable - The variable the expression should have to be part of a system of equation. + * @returns {ISolverExpression | undefined} Return a solver expression if possible + */ + public resolveAFilterTerm(expression: Algebra.Expression, + operator: SparqlRelationOperator, + variable: Variable): + ISolverExpression | Error { + let rawValue: string | undefined; + let valueType: SparqlOperandDataTypes | undefined; + let valueAsNumber: number | undefined; + let hasVariable = false; + + // Find the constituant element of the solver expression + for (const arg of expression.args) { + if ('term' in arg && arg.term.termType === 'Variable') { + // Check if the expression has the same variable as the one the solver try to resolved + if (arg.term.value !== variable) { + return new MissMatchVariableError(`the variable ${arg.term.value} is in the filter whereas we are looking for the varibale ${variable}`); + } + hasVariable = true; + } else if ('term' in arg && arg.term.termType === 'Literal') { + rawValue = arg.term.value; + valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); + if (valueType) { + valueAsNumber = castSparqlRdfTermIntoNumber(rawValue!, valueType); + if (!valueAsNumber) { + return new UnsupportedDataTypeError(`we do not support the datatype "${valueType}" in the solver for the moment`); + } + } else { + return new UnsupportedDataTypeError(`The datatype "${valueType}" is not supported by the SPARQL 1.1 Query Language W3C recommandation`); + } + } + } + // Return if a fully form solver expression can be created + if (hasVariable && rawValue && valueType && valueAsNumber) { + return { + variable, + rawValue, + valueType, + valueAsNumber, + operator, + }; + } + const missingTerm = []; + if (!hasVariable) { + missingTerm.push('Variable'); + } + if (!rawValue) { + missingTerm.push('Litteral'); + } + + return new MisformatedExpressionError(`the filter expressions doesn't have the term ${missingTerm.toString()}`); + } + + + public resolvedType(): ResolvedType { + return ResolvedType.Domain + } +} + +export class TreeRelationSolverInput implements ISolverInput { + public readonly domain: SolutionInterval | [SolutionInterval, SolutionInterval]; + public readonly treeRelation: ITreeRelation; + + public constructor(relation: ITreeRelation, variable: Variable) { + this.treeRelation = relation; + + const relationsolverExpressions = this.convertTreeRelationToSolverExpression(relation, variable); + // The relation doesn't have a value or a type, so we accept it + if (!relationsolverExpressions) { + this.domain = A_TRUE_EXPRESSION; + Object.freeze(this); + Object.freeze(this.domain); + Object.freeze(this.treeRelation); + return; + } + + const relationSolutionInterval = getSolutionInterval( + relationsolverExpressions.valueAsNumber, + relationsolverExpressions.operator, + ); + // We don't prune the relation because we do not implement yet the solution range for this expression + if (!relationSolutionInterval) { + this.domain = A_TRUE_EXPRESSION; + Object.freeze(this); + Object.freeze(this.domain); + Object.freeze(this.treeRelation); + return; + } + this.domain = relationSolutionInterval; + + Object.freeze(this); + Object.freeze(this.domain); + Object.freeze(this.treeRelation); + } + + + + /** + * Convert a TREE relation into a solver expression. + * @param {ITreeRelation} relation - TREE relation. + * @param {Variable} variable - variable of the SPARQL query associated with the tree:path of the relation. + * @returns {ISolverExpression | undefined} Resulting solver expression if the data type is supported by SPARQL + * and the value can be cast into a number. + */ + public convertTreeRelationToSolverExpression(relation: ITreeRelation, + variable: Variable): + ISolverExpression | undefined { + if (relation.value && relation.type) { + const valueType = SparqlOperandDataTypesReversed.get((relation.value.term).datatype.value); + if (!valueType) { + return undefined; + } + const valueNumber = castSparqlRdfTermIntoNumber(relation.value.value, valueType); + if (!valueNumber && valueNumber !== 0) { + return undefined; + } + + return { + variable, + rawValue: relation.value.value, + valueType, + valueAsNumber: valueNumber, + + operator: relation.type, + }; + } + } + public resolvedType(): ResolvedType { + return ResolvedType.Interval; + } +} diff --git a/packages/actor-extract-links-extract-tree/lib/error.ts b/packages/actor-extract-links-extract-tree/lib/error.ts index 40c275cdd..84db0e03f 100644 --- a/packages/actor-extract-links-extract-tree/lib/error.ts +++ b/packages/actor-extract-links-extract-tree/lib/error.ts @@ -5,10 +5,10 @@ export class MissMatchVariableError extends Error { } } -export class MisformatedFilterTermError extends Error { +export class MisformatedExpressionError extends Error { public constructor(message: string) { super(message); - this.name = 'MisformatedFilterTermError'; + this.name = 'MisformatedExpressionError'; } } diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index f376c599b..c7e00a889 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -1,32 +1,11 @@ import { Algebra } from 'sparqlalgebrajs'; -import { - MissMatchVariableError, - MisformatedFilterTermError, - UnsupportedDataTypeError, -} from './error'; -import type { ILogicOperator } from './LogicOperator'; -import { And, Or, operatorFactory } from './LogicOperator'; +import { And,} from './LogicOperator'; import { SolutionDomain } from './SolutionDomain'; -import { SolutionInterval } from './SolutionInterval'; -import { - LogicOperatorReversed, LogicOperatorSymbol, SparqlOperandDataTypesReversed, -} from './solverInterfaces'; -import type { ISolverExpression, - Variable, - - SparqlOperandDataTypes } from './solverInterfaces'; -import { convertTreeRelationToSolverExpression, - castSparqlRdfTermIntoNumber, - filterOperatorToSparqlRelationOperator, - getSolutionInterval, - inverseFilter } from './solverUtil'; -import type { SparqlRelationOperator, ITreeRelation } from './TreeMetadata'; - -const A_TRUE_EXPRESSION: SolutionInterval = new SolutionInterval( - [ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ], -); -const A_FALSE_EXPRESSION: SolutionInterval = new SolutionInterval([]); +import type { Variable } from './solverInterfaces'; +import type { ITreeRelation } from './TreeMetadata'; +import type {ISolverInput} from './solverInterfaces'; +export type SatisfactionChecker = (inputs: ISolverInput[]) => boolean; /** * Check if the solution domain of a system of equation compose of the expressions of the filter * expression and the relation is not empty. @@ -36,188 +15,9 @@ const A_FALSE_EXPRESSION: SolutionInterval = new SolutionInterval([]); * @param {variable} variable - The variable to be resolved. * @returns {boolean} Return true if the domain */ -export function isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable }: { - relation: ITreeRelation; - filterExpression: Algebra.Expression; - variable: Variable; -}): boolean { - const relationsolverExpressions = convertTreeRelationToSolverExpression(relation, variable); - // The relation doesn't have a value or a type, so we accept it - if (!relationsolverExpressions) { - return true; - } - - const relationSolutionInterval = getSolutionInterval( - relationsolverExpressions.valueAsNumber, - relationsolverExpressions.operator, - ); - // We don't prune the relation because we do not implement yet the solution range for this expression - if (!relationSolutionInterval) { - return true; - } - let solutionDomain: SolutionDomain = new SolutionDomain(); - try { - solutionDomain = recursifResolve( - filterExpression, - variable, - ); - } catch (error: unknown) { - // A filter term was missformed we let the query engine return an error to the user and by precaution - // we accept the link in case the error is from the solver and not the filter expression - if (error instanceof MisformatedFilterTermError) { - return true; - } - - // We don't support the data type so let need to explore that link to not diminush the completness of the result - if (error instanceof UnsupportedDataTypeError) { - return true; - } - - /* istanbul ignore next */ - // If it's unexpected error we throw it - throw error; - } - - // If the filter expression is false on it's own then it's impossible to find anything. - // POSSIBLE OPTIMIZATION: reused solution domain of the filter when appropriate. - if (solutionDomain.isDomainEmpty()) { - return false; - } - - // Evaluate the solution domain when adding the relation - solutionDomain = new And().apply({ interval: relationSolutionInterval, domain: solutionDomain }); - - // If there is a possible solution we don't filter the link - return !solutionDomain.isDomainEmpty(); +export function isBooleanExpressionTreeRelationFilterSolvable(inputs: ISolverInput[]): boolean { + return true; } -/** - * From an Algebra expression return an solver expression if possible - * @param {Algebra.Expression} expression - Algebra expression containing the a variable and a litteral. - * @param {SparqlRelationOperator} operator - The SPARQL operator defining the expression. - * @param {Variable} variable - The variable the expression should have to be part of a system of equation. - * @returns {ISolverExpression | undefined} Return a solver expression if possible - */ -export function resolveAFilterTerm(expression: Algebra.Expression, - operator: SparqlRelationOperator, - variable: Variable): - ISolverExpression | Error { - let rawValue: string | undefined; - let valueType: SparqlOperandDataTypes | undefined; - let valueAsNumber: number | undefined; - let hasVariable = false; - // Find the constituant element of the solver expression - for (const arg of expression.args) { - if ('term' in arg && arg.term.termType === 'Variable') { - // Check if the expression has the same variable as the one the solver try to resolved - if (arg.term.value !== variable) { - return new MissMatchVariableError(`the variable ${arg.term.value} is in the filter whereas we are looking for the varibale ${variable}`); - } - hasVariable = true; - } else if ('term' in arg && arg.term.termType === 'Literal') { - rawValue = arg.term.value; - valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); - if (valueType) { - valueAsNumber = castSparqlRdfTermIntoNumber(rawValue!, valueType); - if (!valueAsNumber) { - return new UnsupportedDataTypeError(`we do not support the datatype "${valueType}" in the solver for the moment`); - } - } else { - return new UnsupportedDataTypeError(`The datatype "${valueType}" is not supported by the SPARQL 1.1 Query Language W3C recommandation`); - } - } - } - // Return if a fully form solver expression can be created - if (hasVariable && rawValue && valueType && valueAsNumber) { - return { - variable, - rawValue, - valueType, - valueAsNumber, - operator, - }; - } - const missingTerm = []; - if (!hasVariable) { - missingTerm.push('Variable'); - } - if (!rawValue) { - missingTerm.push('Litteral'); - } - - return new MisformatedFilterTermError(`the filter expressions doesn't have the term ${missingTerm.toString()}`); -} -/** - * Recursively traverse the filter expression and calculate the domain until it get to the current expression. - * It will thrown an error if the expression is badly formated or if it's impossible to get the solution range. - * @param {Algebra.Expression} filterExpression - - * The current filter expression that we are traversing - * @param {SolutionDomain} domain - The current resultant solution domain - * @param {LogicOperatorSymbol} logicOperator - * - The current logic operator that we have to apply to the boolean expression - * @param {Variable} variable - The variable targeted inside the filter expression - * @param {boolean} notExpression - * @returns {SolutionDomain} The solution domain of the whole expression - */ -export function recursifResolve( - filterExpression: Algebra.Expression, - variable: Variable, - domain: SolutionDomain = new SolutionDomain(), - logicOperator: ILogicOperator = new Or(), -): SolutionDomain { - if (filterExpression.expressionType === Algebra.expressionTypes.TERM - ) { - // In that case we are confronted with a boolean expression - // add the associated interval into the domain in relation to - // the logic operator. - if (filterExpression.term.value === 'false') { - domain = logicOperator.apply({ interval: A_FALSE_EXPRESSION, domain }); - } else { - domain = logicOperator.apply({ interval: A_TRUE_EXPRESSION, domain }); - } - } else if ( - // If it's an array of terms then we should be able to create a solver expression. - // Given the resulting solver expression we can calculate a solution interval - // that we will add to the domain with regards to the logic operator. - filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && - filterExpression.args.length === 2 - ) { - const rawOperator = filterExpression.operator; - const operator = filterOperatorToSparqlRelationOperator(rawOperator); - if (operator && logicOperator.operatorName() !== LogicOperatorSymbol.Not) { - const solverExpression = resolveAFilterTerm(filterExpression, operator, variable); - let solutionInterval: SolutionInterval | [SolutionInterval, SolutionInterval] | undefined; - if (solverExpression instanceof MissMatchVariableError) { - solutionInterval = A_TRUE_EXPRESSION; - } else if (solverExpression instanceof Error) { - throw solverExpression; - } else { - solutionInterval = getSolutionInterval(solverExpression.valueAsNumber, solverExpression.operator); - if (!solutionInterval) { - throw new TypeError('The operator is not supported'); - } - } - domain = logicOperator.apply({ interval: solutionInterval, domain }); - } - } else { - // In that case we are traversing the filter expression TREE. - // We prepare the next recursion and we compute the accumulation of results. - const logicOperatorSymbol = LogicOperatorReversed.get(filterExpression.operator); - if (logicOperatorSymbol) { - for (const arg of filterExpression.args) { - // To solve the not operation we rewrite the path of filter expression to reverse every operation - // e.g, = : != ; > : <= - if (logicOperatorSymbol === LogicOperatorSymbol.Not) { - inverseFilter(arg); - domain = recursifResolve(arg, variable, domain, logicOperator); - } else { - const newLogicOperator = operatorFactory(logicOperatorSymbol); - domain = recursifResolve(arg, variable, domain, newLogicOperator); - } - } - } - } - return domain; -} diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index 4db9dc021..87ce0c2f8 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -1,3 +1,5 @@ +import { SolutionDomain } from './SolutionDomain'; +import { SolutionInterval } from './SolutionInterval'; import type { SparqlRelationOperator } from './TreeMetadata'; /** * Valid SPARQL data type for operation. @@ -78,4 +80,19 @@ export interface ISolverExpression { */ operator: SparqlRelationOperator; } +/** + * An input for the solve. + * It must be able to be resolved into a domain or an interval + */ +export interface ISolverInput { + domain: SolutionInterval|[SolutionInterval, SolutionInterval]|SolutionDomain, + resolvedType:()=>ResolvedType +} +/** + * The type of resolution of a solver input + */ +export enum ResolvedType{ + Domain, + Interval +} \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/lib/solverUtil.ts b/packages/actor-extract-links-extract-tree/lib/solverUtil.ts index 5d1e1d715..c2055fd08 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverUtil.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverUtil.ts @@ -1,4 +1,3 @@ -import type * as RDF from 'rdf-js'; import { Algebra } from 'sparqlalgebrajs'; import { SolutionInterval } from './SolutionInterval'; @@ -15,36 +14,7 @@ import type { ITreeRelation } from './TreeMetadata'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; -/** - * Convert a TREE relation into a solver expression. - * @param {ITreeRelation} relation - TREE relation. - * @param {Variable} variable - variable of the SPARQL query associated with the tree:path of the relation. - * @returns {ISolverExpression | undefined} Resulting solver expression if the data type is supported by SPARQL - * and the value can be cast into a number. - */ -export function convertTreeRelationToSolverExpression(relation: ITreeRelation, - variable: Variable): - ISolverExpression | undefined { - if (relation.value && relation.type) { - const valueType = SparqlOperandDataTypesReversed.get((relation.value.term).datatype.value); - if (!valueType) { - return undefined; - } - const valueNumber = castSparqlRdfTermIntoNumber(relation.value.value, valueType); - if (!valueNumber && valueNumber !== 0) { - return undefined; - } - - return { - variable, - rawValue: relation.value.value, - valueType, - valueAsNumber: valueNumber, - operator: relation.type, - }; - } -} /** * Check if all the expression provided have a SparqlOperandDataTypes compatible type * it is considered that all number types are compatible between them. From 9f5f803a77e7afcc73413c1eaf24d0be59fd1310 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 31 May 2023 06:59:42 +0200 Subject: [PATCH 170/189] method to resolved refactored --- .../lib/SolverInput.ts | 12 ++--- .../lib/error.ts | 7 +++ .../lib/solver.ts | 44 ++++++++++++++----- .../lib/solverInterfaces.ts | 8 ---- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts index 32c60edb7..c9bf8bce9 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts @@ -1,14 +1,14 @@ import type * as RDF from 'rdf-js'; import { Algebra } from "sparqlalgebrajs"; import { SolutionDomain } from "./SolutionDomain"; -import { ISolverInput, ResolvedType } from "./solverInterfaces"; +import { ISolverInput } from "./solverInterfaces"; import { MissMatchVariableError, MisformatedExpressionError, UnsupportedDataTypeError } from './error'; import type { ILogicOperator } from './LogicOperator'; -import { And, Or, operatorFactory } from './LogicOperator'; +import { Or, operatorFactory } from './LogicOperator'; import { SolutionInterval } from './SolutionInterval'; import { LogicOperatorReversed, @@ -197,10 +197,6 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { return new MisformatedExpressionError(`the filter expressions doesn't have the term ${missingTerm.toString()}`); } - - public resolvedType(): ResolvedType { - return ResolvedType.Domain - } } export class TreeRelationSolverInput implements ISolverInput { @@ -271,7 +267,5 @@ export class TreeRelationSolverInput implements ISolverInput { }; } } - public resolvedType(): ResolvedType { - return ResolvedType.Interval; - } + } diff --git a/packages/actor-extract-links-extract-tree/lib/error.ts b/packages/actor-extract-links-extract-tree/lib/error.ts index 84db0e03f..3315ce54a 100644 --- a/packages/actor-extract-links-extract-tree/lib/error.ts +++ b/packages/actor-extract-links-extract-tree/lib/error.ts @@ -18,3 +18,10 @@ export class UnsupportedDataTypeError extends Error { this.name = 'UnsupportedDataTypeError'; } } + +export class InvalidExpressionSystem extends Error { + public constructor(message: string) { + super(message); + this.name = 'InvalidExpressionSystem'; + } +} diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index c7e00a889..3feb4e468 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -1,23 +1,43 @@ -import { Algebra } from 'sparqlalgebrajs'; import { And,} from './LogicOperator'; import { SolutionDomain } from './SolutionDomain'; -import type { Variable } from './solverInterfaces'; -import type { ITreeRelation } from './TreeMetadata'; +import { InvalidExpressionSystem } from './error'; import type {ISolverInput} from './solverInterfaces'; +import { SolutionInterval } from './SolutionInterval'; +const AND = new And(); export type SatisfactionChecker = (inputs: ISolverInput[]) => boolean; /** - * Check if the solution domain of a system of equation compose of the expressions of the filter - * expression and the relation is not empty. - * @param {ITreeRelation} relation - The tree:relation that we wish to determine if there is a possible solution - * when we combine it with a filter expression. - * @param {Algebra.Expression} filterExpression - The Algebra expression of the filter. - * @param {variable} variable - The variable to be resolved. - * @returns {boolean} Return true if the domain + * Check if it is possible to satify the combination of the boolean expression of the TREE relations + * and the SPARQL filter. Will Throw if there is no or more than one SPARQL filter which is identify + * with the ResolvedType Domain + * @param {ISolverInput[]} inputs - The solver input, must countain one ResolvedType Domain + * @returns {boolean} Whether the expression can be satify */ export function isBooleanExpressionTreeRelationFilterSolvable(inputs: ISolverInput[]): boolean { - return true; -} + let domain: SolutionDomain|undefined = undefined; + let intervals: Array = []; + + for(const input of inputs){ + if ("getDomain" in input.domain && domain!== undefined){ + throw new InvalidExpressionSystem('there is more than one filter expression'); + } else if ("getDomain" in input.domain){ + domain = input.domain; + }else{ + intervals.push(input.domain); + } + } + if(!domain){ + throw new InvalidExpressionSystem('there should be a filter expression'); + } + if(intervals.length<1){ + throw new InvalidExpressionSystem('there should be at least one TREE relation to resolved'); + } + for(const interval of intervals){ + domain = AND.apply({interval, domain}); + } + + return !domain.isDomainEmpty() +} diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index 87ce0c2f8..2416552f1 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -86,13 +86,5 @@ export interface ISolverExpression { */ export interface ISolverInput { domain: SolutionInterval|[SolutionInterval, SolutionInterval]|SolutionDomain, - resolvedType:()=>ResolvedType } -/** - * The type of resolution of a solver input - */ -export enum ResolvedType{ - Domain, - Interval -} \ No newline at end of file From ed1138bcfd1cacb4744bab6d2812555709c93ca0 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Wed, 31 May 2023 12:57:49 +0200 Subject: [PATCH 171/189] FilterNode class deleted and transformed into a module of function --- .../lib/ActorExtractLinksTree.ts | 6 +- .../lib/SolverInput.ts | 30 +- .../lib/{FilterNode.ts => filterNode.ts} | 89 ++- .../lib/solver.ts | 5 +- .../lib/solverInterfaces.ts | 1 + .../test/SolverInput-test.ts | 528 ++++++++++++++ ...{FilterNode-test.ts => filterNode-test.ts} | 372 +++++----- .../test/solver-test.ts | 648 ++++-------------- 8 files changed, 955 insertions(+), 724 deletions(-) rename packages/actor-extract-links-extract-tree/lib/{FilterNode.ts => filterNode.ts} (56%) create mode 100644 packages/actor-extract-links-extract-tree/test/SolverInput-test.ts rename packages/actor-extract-links-extract-tree/test/{FilterNode-test.ts => filterNode-test.ts} (57%) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 0f502afa6..63e827fb7 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -8,11 +8,11 @@ import type { IActorArgs, IActorTest } from '@comunica/core'; import type { IActionContext } from '@comunica/types'; import type * as RDF from 'rdf-js'; import { termToString } from 'rdf-string'; -import { FilterNode } from './FilterNode'; +import { filterNode } from './filterNode'; import { TreeNodes } from './TreeMetadata'; import type { ITreeRelationRaw, ITreeRelation, ITreeNode } from './TreeMetadata'; import { buildRelationElement, materializeTreeRelation, addRelationDescription } from './treeMetadataExtraction'; - +import { isBooleanExpressionTreeRelationFilterSolvable } from './solver'; /** * A comunica Extract Links Tree Extract Links Actor. */ @@ -90,7 +90,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { * @returns {Promise>} a map containing the filter */ public async applyFilter(node: ITreeNode, context: IActionContext): Promise> { - return await new FilterNode().run(node, context); + return await filterNode(node, context,isBooleanExpressionTreeRelationFilterSolvable); } /** diff --git a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts index c9bf8bce9..789a57812 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts @@ -42,23 +42,24 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { public constructor(filterExpression: Algebra.Expression, variable: Variable) { this.filterExpression = filterExpression; try { - this.domain = this.recursifResolve(this.filterExpression, variable); + this.domain = SparlFilterExpressionSolverInput.recursifResolve(this.filterExpression, variable); } catch (error: unknown) { // A filter term was missformed we let the query engine return an error to the user and by precaution // we accept the link in case the error is from the solver and not the filter expression if (error instanceof MisformatedExpressionError) { this.domain = SolutionDomain.newWithInitialIntervals(A_TRUE_EXPRESSION); - } + } else if (error instanceof UnsupportedDataTypeError) { + // We don't support the data type so let need to explore that link to not diminush the completness of the result - // We don't support the data type so let need to explore that link to not diminush the completness of the result - if (error instanceof UnsupportedDataTypeError) { this.domain = SolutionDomain.newWithInitialIntervals(A_TRUE_EXPRESSION); + } else { + /* istanbul ignore next */ + // If it's unexpected error we throw it + throw error; } - /* istanbul ignore next */ - // If it's unexpected error we throw it - throw error; + } finally { Object.freeze(this); Object.freeze(this.domain); @@ -78,7 +79,7 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { * @param {boolean} notExpression * @returns {SolutionDomain} The solution domain of the whole expression */ - public recursifResolve( + public static recursifResolve( filterExpression: Algebra.Expression, variable: Variable, domain: SolutionDomain = new SolutionDomain(), @@ -104,7 +105,7 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { const rawOperator = filterExpression.operator; const operator = filterOperatorToSparqlRelationOperator(rawOperator); if (operator && logicOperator.operatorName() !== LogicOperatorSymbol.Not) { - const solverExpression = this.resolveAFilterTerm(filterExpression, operator, variable); + const solverExpression = SparlFilterExpressionSolverInput.resolveAFilterTerm(filterExpression, operator, variable); let solutionInterval: SolutionInterval | [SolutionInterval, SolutionInterval] | undefined; if (solverExpression instanceof MissMatchVariableError) { solutionInterval = A_TRUE_EXPRESSION; @@ -146,7 +147,7 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { * @param {Variable} variable - The variable the expression should have to be part of a system of equation. * @returns {ISolverExpression | undefined} Return a solver expression if possible */ - public resolveAFilterTerm(expression: Algebra.Expression, + public static resolveAFilterTerm(expression: Algebra.Expression, operator: SparqlRelationOperator, variable: Variable): ISolverExpression | Error { @@ -202,16 +203,19 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { export class TreeRelationSolverInput implements ISolverInput { public readonly domain: SolutionInterval | [SolutionInterval, SolutionInterval]; public readonly treeRelation: ITreeRelation; + public readonly variable: Variable; public constructor(relation: ITreeRelation, variable: Variable) { this.treeRelation = relation; + this.variable = variable; - const relationsolverExpressions = this.convertTreeRelationToSolverExpression(relation, variable); + const relationsolverExpressions = TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable); // The relation doesn't have a value or a type, so we accept it if (!relationsolverExpressions) { this.domain = A_TRUE_EXPRESSION; Object.freeze(this); Object.freeze(this.domain); + Object.freeze(this.variable); Object.freeze(this.treeRelation); return; } @@ -225,6 +229,7 @@ export class TreeRelationSolverInput implements ISolverInput { this.domain = A_TRUE_EXPRESSION; Object.freeze(this); Object.freeze(this.domain); + Object.freeze(this.variable); Object.freeze(this.treeRelation); return; } @@ -232,6 +237,7 @@ export class TreeRelationSolverInput implements ISolverInput { Object.freeze(this); Object.freeze(this.domain); + Object.freeze(this.variable); Object.freeze(this.treeRelation); } @@ -244,7 +250,7 @@ export class TreeRelationSolverInput implements ISolverInput { * @returns {ISolverExpression | undefined} Resulting solver expression if the data type is supported by SPARQL * and the value can be cast into a number. */ - public convertTreeRelationToSolverExpression(relation: ITreeRelation, + public static convertTreeRelationToSolverExpression(relation: ITreeRelation, variable: Variable): ISolverExpression | undefined { if (relation.value && relation.type) { diff --git a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts b/packages/actor-extract-links-extract-tree/lib/filterNode.ts similarity index 56% rename from packages/actor-extract-links-extract-tree/lib/FilterNode.ts rename to packages/actor-extract-links-extract-tree/lib/filterNode.ts index 4163752ab..1bc48a397 100644 --- a/packages/actor-extract-links-extract-tree/lib/FilterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/filterNode.ts @@ -2,27 +2,24 @@ import { BindingsFactory } from '@comunica/bindings-factory'; import { KeysInitQuery } from '@comunica/context-entries'; import type { IActionContext } from '@comunica/types'; import { Algebra, Factory as AlgebraFactory, Util } from 'sparqlalgebrajs'; -import { isBooleanExpressionTreeRelationFilterSolvable } from './solver'; -import type { Variable } from './solverInterfaces'; +import { SatisfactionChecker } from './solver'; +import type { ISolverInput, Variable } from './solverInterfaces'; import type { ITreeRelation, ITreeNode } from './TreeMetadata'; +import { SparlFilterExpressionSolverInput, TreeRelationSolverInput } from './SolverInput'; const AF = new AlgebraFactory(); const BF = new BindingsFactory(); -/** - * A class to apply [SPAQL filters](https://www.w3.org/TR/sparql11-query/#evaluation) - * to the [TREE specification](https://treecg.github.io/specification/). - * It use [sparqlee](https://github.com/comunica/sparqlee) to evaluate the filter where - * the binding are remplace by the [value of TREE relation](https://treecg.github.io/specification/#traversing). - */ -export class FilterNode { + + + /** * Return the filter expression if the TREE node has relations * @param {ITreeNode} node - The current TREE node * @param {IActionContext} context - The context * @returns {Algebra.Expression | undefined} The filter expression or undefined if the TREE node has no relations */ - public getFilterExpressionIfTreeNodeHasConstraint(node: ITreeNode, + export function getFilterExpressionIfTreeNodeHasConstraint(node: ITreeNode, context: IActionContext): Algebra.Expression | undefined { if (!node.relation) { return undefined; @@ -33,7 +30,7 @@ export class FilterNode { } const query: Algebra.Operation = context.get(KeysInitQuery.query)!; - const filterExpression = FilterNode.findNode(query, Algebra.types.FILTER); + const filterExpression = findNode(query, Algebra.types.FILTER); if (!filterExpression) { return undefined; } @@ -49,11 +46,11 @@ export class FilterNode { * @param {IActionContext} context - The context * @returns {Promise>} A map of the indicating if a tree:relation should be follow */ - public async run(node: ITreeNode, context: IActionContext): Promise> { + export async function filterNode(node: ITreeNode, context: IActionContext, satisfactionChecker:SatisfactionChecker ): Promise> { const filterMap: Map = new Map(); const filterOperation: Algebra.Expression | undefined = - this.getFilterExpressionIfTreeNodeHasConstraint(node, context); + getFilterExpressionIfTreeNodeHasConstraint(node, context); if (!filterOperation) { return new Map(); @@ -63,31 +60,45 @@ export class FilterNode { const queryBody: Algebra.Operation = context.get(KeysInitQuery.query)!; // Capture the relation from the function argument. - const relations: ITreeRelation[] = node.relation!; + const groupedRelations = groupRelationByNode(node.relation!); + + // - for (const relation of relations) { + const calculatedFilterExpressions: Map = new Map(); + for (const relations of groupedRelations) { // Accept the relation if the relation does't specify a condition. - if (!relation.path || !relation.value) { - filterMap.set(relation.node, true); + if (!relations[0].path || !relations[0].value) { + filterMap.set(relations[0].node, true); continue; } // Find the quad from the bgp that are related to the TREE relation. - const variables = FilterNode.findRelevantVariableFromBgp(queryBody, relation.path); + const variables = findRelevantVariableFromBgp(queryBody, relations[0].path); // Accept the relation if no variable are linked with the relation. if (variables.length === 0) { - filterMap.set(relation.node, true); + filterMap.set(relations[0].node, true); continue; } let filtered = false; // For all the variable check if one is has a possible solutions. for (const variable of variables) { - filtered = filtered || isBooleanExpressionTreeRelationFilterSolvable( - { relation, filterExpression: structuredClone(filterOperation), variable }, - ); + let inputFilterExpression = calculatedFilterExpressions.get(variable); + if(!inputFilterExpression){ + inputFilterExpression = new SparlFilterExpressionSolverInput( + structuredClone(filterOperation), + variable); + } + const inputs:ISolverInput[] = relations.map((relation)=> new TreeRelationSolverInput(relation, variable)); + inputs.push(inputFilterExpression); + filtered = filtered || satisfactionChecker(inputs) } + let previousFilterValue = filterMap.get(relations[0].node); + if(!previousFilterValue){ + filterMap.set(relations[0].node, filtered); + }else{ + filterMap.set(relations[0].node, filtered || previousFilterValue); - filterMap.set(relation.node, filtered); + } } return filterMap; } @@ -99,7 +110,7 @@ export class FilterNode { * @param {string} path - TREE path * @returns {Variable[]} the variables of the Quad objects that contain the TREE path as predicate */ - private static findRelevantVariableFromBgp(queryBody: Algebra.Operation, path: string): Variable[] { + export function findRelevantVariableFromBgp(queryBody: Algebra.Operation, path: string): Variable[] { const resp: Variable[] = []; const addVariable = (quad: any): boolean => { if (quad.predicate.value === path && quad.object.termType === 'Variable') { @@ -116,6 +127,32 @@ export class FilterNode { return resp; } + function groupRelationByNode(relations: ITreeRelation[]): ITreeRelation[][]{ + const collector:Map> = new Map(); + const resp:ITreeRelation[][] = []; + for (const relation of relations){ + const path = relation.path!== undefined?relation.path:''; + const nodeGroup = collector.get(relation.node); + if(nodeGroup){ + const pathGroup = nodeGroup.get(path); + if(pathGroup){ + pathGroup.push(relation); + }else{ + nodeGroup.set(path, [relation]); + } + }else{ + collector.set(relation.node, new Map([[path, [relation]]])); + } + } + + for (const [_, pathGroup] of collector){ + for(const [_, relations] of pathGroup){ + resp.push(relations); + } + } + return resp; + } + /** * Find the first node of type `nodeType`, if it doesn't exist * it return undefined. @@ -123,7 +160,7 @@ export class FilterNode { * @param {string} nodeType - the type of node requested * @returns {any} */ - private static findNode(query: Algebra.Operation, nodeType: string): any { + function findNode(query: Algebra.Operation, nodeType: string): any { let currentNode = query; do { if (currentNode.type === nodeType) { @@ -135,5 +172,5 @@ export class FilterNode { } while ('input' in currentNode); return undefined; } -} + diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 3feb4e468..6e71fb90e 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -1,6 +1,6 @@ import { And,} from './LogicOperator'; import { SolutionDomain } from './SolutionDomain'; -import { InvalidExpressionSystem } from './error'; +import { InvalidExpressionSystem, MissMatchVariableError } from './error'; import type {ISolverInput} from './solverInterfaces'; import { SolutionInterval } from './SolutionInterval'; @@ -16,12 +16,15 @@ export type SatisfactionChecker = (inputs: ISolverInput[]) => boolean; export function isBooleanExpressionTreeRelationFilterSolvable(inputs: ISolverInput[]): boolean { let domain: SolutionDomain|undefined = undefined; let intervals: Array = []; + let variable = inputs[0].variable; for(const input of inputs){ if ("getDomain" in input.domain && domain!== undefined){ throw new InvalidExpressionSystem('there is more than one filter expression'); } else if ("getDomain" in input.domain){ domain = input.domain; + } else if (variable != input.variable){ + throw new MissMatchVariableError('the variable are not matching') }else{ intervals.push(input.domain); } diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index 2416552f1..ae56c9b01 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -86,5 +86,6 @@ export interface ISolverExpression { */ export interface ISolverInput { domain: SolutionInterval|[SolutionInterval, SolutionInterval]|SolutionDomain, + variable: Variable } diff --git a/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts b/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts new file mode 100644 index 000000000..1e0ed93f8 --- /dev/null +++ b/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts @@ -0,0 +1,528 @@ +import type * as RDF from 'rdf-js'; +import { Algebra, translate } from 'sparqlalgebrajs'; +import { DataFactory } from 'rdf-data-factory'; +import type { ITreeRelation } from '../lib/TreeMetadata'; +import { SparqlRelationOperator } from '../lib/TreeMetadata'; +import { LogicOperatorSymbol, + SparqlOperandDataTypes, + ISolverExpression } from '../lib/solverInterfaces'; +import { + TreeRelationSolverInput, + SparlFilterExpressionSolverInput +} from '../lib/SolverInput'; +import { + MisformatedExpressionError, + UnsupportedDataTypeError, +} from '../lib/error'; +import * as UtilSolver from '../lib/solverUtil'; +import { SolutionInterval } from '../lib/SolutionInterval'; +import { SolutionDomain } from '../lib/SolutionDomain'; + +const DF = new DataFactory(); + +const nextUp = require('ulp').nextUp; +const nextDown = require('ulp').nextDown; + + +describe('SolverInput',()=>{ + describe('SparlFilterExpressionSolverInput',()=>{ + + }); + + describe('TreeRelationSolverInput',()=>{ + describe('convertTreeRelationToSolverExpression',()=>{ + it('given a TREE relation with all the parameters should return a valid expression', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + const expectedExpression: ISolverExpression = { + variable, + rawValue: '5', + valueType: SparqlOperandDataTypes.Integer, + valueAsNumber: 5, + + operator: SparqlRelationOperator.EqualThanRelation, + }; + + expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toStrictEqual(expectedExpression); + }); + + it('should return undefined given a relation witn a value term containing an unknowed value type', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it(`should return undefined given a relation with + a term containing an incompatible value in relation to its value type`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: 'a', + term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it('should return undefined given a relation without a value and a type', () => { + const relation: ITreeRelation = { + remainingItems: 10, + path: 'ex:path', + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it('should return undefined given a relation without a value', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it('should return undefined given a relation without a type', () => { + const relation: ITreeRelation = { + remainingItems: 10, + path: 'ex:path', + value: { + value: 'a', + term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + }); + }); + + describe('SparlFilterExpressionSolverInput', ()=>{ + describe('resolveAFilterTerm', ()=>{ + it('given an algebra expression with all the solver expression parameters should return a valid expression', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable('x'), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const variable = 'x'; + const expectedSolverExpression: ISolverExpression = { + variable, + rawValue: '6', + valueType: SparqlOperandDataTypes.Integer, + valueAsNumber: 6, + operator, + }; + + const resp = SparlFilterExpressionSolverInput. + resolveAFilterTerm(expression, operator, variable); + + if (resp) { + expect(resp).toStrictEqual(expectedSolverExpression); + } else { + expect(resp).toBeDefined(); + } + }); + + it('given an algebra expression without a variable than should return an misformated error', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + + expect(SparlFilterExpressionSolverInput.resolveAFilterTerm(expression, operator, 'x')).toBeInstanceOf(MisformatedExpressionError); + }); + + it('given an algebra expression without a litteral than should return an misformated error', () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + + expect(SparlFilterExpressionSolverInput. + resolveAFilterTerm(expression, operator, variable)). + toBeInstanceOf(MisformatedExpressionError); + }); + + it('given an algebra expression without args than should return a misformated error', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + + expect(SparlFilterExpressionSolverInput. + resolveAFilterTerm(expression, operator, 'x')). + toBeInstanceOf(MisformatedExpressionError); + }); + + it(`given an algebra expression with a litteral containing an invalid datatype than + should return an unsupported datatype error`, + () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + + expect(SparlFilterExpressionSolverInput. + resolveAFilterTerm(expression, operator, variable)). + toBeInstanceOf(UnsupportedDataTypeError); + }); + + it(`given an algebra expression with a litteral containing a + literal that cannot be converted into number should return an unsupported datatype error`, () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + + expect(SparlFilterExpressionSolverInput. + resolveAFilterTerm(expression, operator, variable)). + toBeInstanceOf(UnsupportedDataTypeError); + }); + }); + + describe('recursifResolve', ()=>{ + it('given an algebra expression with an unsupported logic operator should throw an error', () => { + const mock = jest.spyOn(UtilSolver, 'getSolutionInterval'); + mock.mockImplementation((): undefined => { return undefined; }); + + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2) + }`).input.expression; + + expect(() => SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + )).toThrow(); + mock.mockRestore(); + }); + it('given an algebra expression with two logicals operators should return the valid solution domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x<5) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra expression with one logical operators should return the valid solution domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x=2) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra with a true statement should return an infinite domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(true) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals( + new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), + ); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra with a false statement should return an empty domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(false) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + expect(resp.isDomainEmpty()).toBe(true); + }); + + it('given an algebra with a not true statement should return an empty domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(!true) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + expect(resp.isDomainEmpty()).toBe(true); + }); + + it('given an algebra with a not false statement should return an infinite domain', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(!false) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals( + new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), + ); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra expression with one not equal logical operators should return the valid solution domain', + () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x!=2) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals([ + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), + new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]), + ]); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it(`given an algebra expression with two logicals operators with a double negation should + return the valid solution domain`, () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(!(?x=2)) && ?x<5) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra expression with a double negation it should cancel it', () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(!(!(?x=2 && ?x<5))) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it(`given an algebra expression with two logicals operators + that cannot be satified should return an empty domain`, () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x>5) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + + 'x', + ); + + expect(resp.isDomainEmpty()).toBe(true); + }); + + it(`given an algebra expression with two logicals operators that are negated + should return the valid solution domain`, () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(?x=2 && ?x<5)) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals( + [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), + new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], + ); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it(`given an algebra expression with two logicals operators that are triple negated + should return the valid solution domain`, () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( !(!(!(?x=2 && ?x<5)))) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals( + [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), + new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], + ); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it(`given an algebra expression with three logicals operators where the priority of operation should start with the not operator than + should return the valid solution domain`, () => { + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2 && ?x>=1 || !(?x=3)) + }`).input.expression; + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals([ + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(3) ]), + new SolutionInterval([ nextUp(3), Number.POSITIVE_INFINITY ]), + ]); + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + }); + + + }); +}); \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts b/packages/actor-extract-links-extract-tree/test/filterNode-test.ts similarity index 57% rename from packages/actor-extract-links-extract-tree/test/FilterNode-test.ts rename to packages/actor-extract-links-extract-tree/test/filterNode-test.ts index d2a2aec8e..3960072d8 100644 --- a/packages/actor-extract-links-extract-tree/test/FilterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/filterNode-test.ts @@ -4,84 +4,83 @@ import { ActionContext } from '@comunica/core'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; -import { FilterNode } from '../lib/FilterNode'; +import { filterNode, getFilterExpressionIfTreeNodeHasConstraint } from '../lib/FilterNode'; import { SparqlRelationOperator } from '../lib/TreeMetadata'; import type { ITreeNode } from '../lib/TreeMetadata'; +import { ISolverInput } from '../lib/solverInterfaces'; +import { isBooleanExpressionTreeRelationFilterSolvable } from + '../lib/solver'; const DF = new DataFactory(); -describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { - describe('An ActorOptimizeLinkTraversalFilterTreeLinks instance', () => { - let filterNode: FilterNode; +describe('filterNode Module', () => { - beforeEach(() => { - filterNode = new FilterNode(); - }); - describe('getFilterExpressionIfTreeNodeHasConstraint method', () => { - const treeSubject = 'tree'; - it('should test when there are relations and a filter operation in the query', () => { - const context = new ActionContext({ - [KeysInitQuery.query.name]: translate(` + describe('getFilterExpressionIfTreeNodeHasConstraint ', () => { + const treeSubject = 'tree'; + it('should test when there are relations and a filter operation in the query', () => { + const context = new ActionContext({ + [KeysInitQuery.query.name]: translate(` SELECT * WHERE { ?x ?y ?z FILTER(?x = 5 || true) }`), - }); - const node: ITreeNode = { - identifier: treeSubject, - relation: [ - { - node: 'http://bar.com', - }, - ], - }; - - const response = filterNode.getFilterExpressionIfTreeNodeHasConstraint(node, context); - expect(response).toBeDefined(); }); + const node: ITreeNode = { + identifier: treeSubject, + relation: [ + { + node: 'http://bar.com', + }, + ], + }; - it('should no test when the TREE relation are undefined', async() => { - const context = new ActionContext({ - [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, - }); - const node: ITreeNode = { - identifier: treeSubject, - }; + const response = getFilterExpressionIfTreeNodeHasConstraint(node, context); + expect(response).toBeDefined(); + }); - const response = filterNode.getFilterExpressionIfTreeNodeHasConstraint(node, context); - expect(response).toBeUndefined(); + it('should no test when the TREE relation are undefined', async () => { + const context = new ActionContext({ + [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); + const node: ITreeNode = { + identifier: treeSubject, + }; - it('should not test when there is a filter operation in the query but no TREE relations', async() => { - const context = new ActionContext({ - [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, - }); - const node: ITreeNode = { - identifier: treeSubject, - relation: [], - }; - const response = filterNode.getFilterExpressionIfTreeNodeHasConstraint(node, context); - expect(response).toBeUndefined(); + const response = getFilterExpressionIfTreeNodeHasConstraint(node, context); + expect(response).toBeUndefined(); + }); + + it('should not test when there is a filter operation in the query but no TREE relations', async () => { + const context = new ActionContext({ + [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); + const node: ITreeNode = { + identifier: treeSubject, + relation: [], + }; + const response = getFilterExpressionIfTreeNodeHasConstraint(node, context); + expect(response).toBeUndefined(); + }); - it('should no test when there are no filter operation in the query but a TREE relation', async() => { - const context = new ActionContext({ - [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, - }); - const node: ITreeNode = { - identifier: treeSubject, - relation: [ - { - node: 'http://bar.com', - }, - ], - }; - const response = filterNode.getFilterExpressionIfTreeNodeHasConstraint(node, context); - expect(response).toBeUndefined(); + it('should no test when there are no filter operation in the query but a TREE relation', async () => { + const context = new ActionContext({ + [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); + const node: ITreeNode = { + identifier: treeSubject, + relation: [ + { + node: 'http://bar.com', + }, + ], + }; + const response = getFilterExpressionIfTreeNodeHasConstraint(node, context); + expect(response).toBeUndefined(); }); + }); - describe('run method', () => { - it('should accept the relation when the filter respect the relation', async() => { + describe('filternNode', () => { + describe('with isBooleanExpressionTreeRelationFilterSolvable', () => { + it('should accept the relation when the filter respect the relation', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -100,25 +99,29 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }; const query = translate(` - SELECT ?o WHERE { - ex:foo ex:path ?o. - ex:foo ex:p ex:o. - FILTER(?o=5) - } - `, { prefixes: { ex: 'http://example.com#' }}); + SELECT ?o WHERE { + ex:foo ex:path ?o. + ex:foo ex:p ex:o. + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const result = await filterNode.run(node, context); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable + ); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should not accept the relation when the filter is not respected by the relation', async() => { + it('should not accept the relation when the filter is not respected by the relation', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -137,23 +140,27 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }; const query = translate(` - SELECT ?o WHERE { - ex:foo ex:path ?o. - FILTER(?o=88) - } - `, { prefixes: { ex: 'http://example.com#' }}); + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER(?o=88) + } + `, { prefixes: { ex: 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const result = await filterNode.run(node, context); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable + ); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', false ]]), + new Map([['http://bar.com', false]]), ); }); - it('should accept the relation when the query don\'t invoke the right path', async() => { + it('should accept the relation when the query don\'t invoke the right path', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -171,34 +178,42 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ], }; const query = translate(` - SELECT ?o WHERE { - ex:foo ex:superPath ?o. - FILTER(?o=5) - } - `, { prefixes: { ex: 'http://example.com#' }}); + SELECT ?o WHERE { + ex:foo ex:superPath ?o. + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const result = await filterNode.run(node, context); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable + ); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should return an empty map when there is no relation', async() => { + it('should return an empty map when there is no relation', async () => { const query = translate(` - SELECT ?o WHERE { - ex:foo ex:path ?o. - FILTER(?o=88) - } - `, { prefixes: { ex: 'http://example.com#' }}); + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER(?o=88) + } + `, { prefixes: { ex: 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); const node: ITreeNode = { identifier: 'foo' }; - const result = await filterNode.run(node, context); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable + ); expect(result).toStrictEqual( new Map(), @@ -206,7 +221,7 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }); it('should accept the relation when there is multiple filters and the query path don\'t match the relation', - async() => { + async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -228,25 +243,29 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ]; const query = translate(` - SELECT ?o WHERE { - ex:foo ex:path ?o. - FILTER(?o=5 && ?o<=5 ) - } - `, { prefixes: { ex: 'http://example.com#' }}); + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER(?o=5 && ?o<=5 ) + } + `, { prefixes: { ex: 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const result = await filterNode.run(node, context); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable + ); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); it('should accept the relations when one respect the filter and another has no path and value defined', - async() => { + async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -269,23 +288,27 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ], }; const query = translate(` - SELECT ?o WHERE { - ex:foo ex:path ?o. - FILTER(?o=5) - } - `, { prefixes: { ex: 'http://example.com#' }}); + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const result = await filterNode.run(node, context); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable + ); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ], [ 'http://foo.com', true ]]), + new Map([['http://bar.com', true], ['http://foo.com', true]]), ); }); - it('should accept the relation when the filter argument are not related to the query', async() => { + it('should accept the relation when the filter argument are not related to the query', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -303,23 +326,27 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ], }; const query = translate(` - SELECT ?o WHERE { - ex:foo ex:path ?o. - FILTER(?p=88) - } - `, { prefixes: { ex: 'http://example.com#' }}); + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER(?p=88) + } + `, { prefixes: { ex: 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const result = await filterNode.run(node, context); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable + ); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should accept the relation when there is multiples filters and one is not relevant', async() => { + it('should accept the relation when there is multiples filters and one is not relevant', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -337,23 +364,27 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ], }; const query = translate(` - SELECT ?o WHERE { - ex:foo ex:path ?o. - FILTER((?p=88 && ?p>3) || true) - } - `, { prefixes: { ex: 'http://example.com#' }}); + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER((?p=88 && ?p>3) || true) + } + `, { prefixes: { ex: 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const result = await filterNode.run(node, context); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable + ); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should accept the relation when the filter compare two constants', async() => { + it('should accept the relation when the filter compare two constants', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -371,23 +402,27 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { ], }; const query = translate(` - SELECT ?o WHERE { - ex:foo ex:path ?o. - FILTER(5=5) - } - `, { prefixes: { ex: 'http://example.com#' }}); + SELECT ?o WHERE { + ex:foo ex:path ?o. + FILTER(5=5) + } + `, { prefixes: { ex: 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const result = await filterNode.run(node, context); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable + ); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should accept the relation when the filter respect the relation with a construct query', async() => { + it('should accept the relation when the filter respect the relation with a construct query', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -406,26 +441,30 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }; const query = translate(` - CONSTRUCT { - - } WHERE{ - ex:foo ex:path ?o. - FILTER(?o=5) - } - `, { prefixes: { ex: 'http://example.com#' }}); + CONSTRUCT { + + } WHERE{ + ex:foo ex:path ?o. + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const result = await filterNode.run(node, context); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable + ); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should accept the relation when the filter respect the relation with a nested query', async() => { + it('should accept the relation when the filter respect the relation with a nested query', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -444,29 +483,33 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }; const query = translate(` - SELECT * WHERE { - ?o ex:resp ?v . - { - SELECT ?o WHERE { - ex:foo ex:path ?o. + SELECT * WHERE { + ?o ex:resp ?v . + { + SELECT ?o WHERE { + ex:foo ex:path ?o. + } } - } - FILTER(?o=5) - } - `, { prefixes: { ex: 'http://example.com#' }}); + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const result = await filterNode.run(node, context); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable + ); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); - it('should accept the relation when a complex filter respect the relation', async() => { + it('should accept the relation when a complex filter respect the relation', async () => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -485,24 +528,29 @@ describe('ActorOptimizeLinkTraversalFilterTreeLinks', () => { }; const query = translate(` - SELECT ?o ?x WHERE { - ex:foo ex:path ?o. - ex:foo ex:p ex:o. - ex:foo ex:p2 ?x. - FILTER(?o>2 || ?x=4 || (?x<3 && ?o<6) ) - } - `, { prefixes: { ex: 'http://example.com#' }}); + SELECT ?o ?x WHERE { + ex:foo ex:path ?o. + ex:foo ex:p ex:o. + ex:foo ex:p2 ?x. + FILTER(?o>2 || ?x=4 || (?x<3 && ?o<6) ) + } + `, { prefixes: { ex: 'http://example.com#' } }); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); - const result = await filterNode.run(node, context); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable + ); expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), + new Map([['http://bar.com', true]]), ); }); }); + }); }); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index fff2b04fa..b3565a4d1 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1,23 +1,15 @@ import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; -import { - MisformatedFilterTermError, - UnsupportedDataTypeError, -} from '../lib/error'; -import { SolutionDomain } from '../lib/SolutionDomain'; import { SolutionInterval } from '../lib/SolutionInterval'; import { - resolveAFilterTerm, isBooleanExpressionTreeRelationFilterSolvable, - recursifResolve, } from '../lib/solver'; import { LogicOperatorSymbol, SparqlOperandDataTypes } from '../lib/solverInterfaces'; import type { ISolverExpression, } from '../lib/solverInterfaces'; import { - convertTreeRelationToSolverExpression, areTypesCompatible, castSparqlRdfTermIntoNumber, filterOperatorToSparqlRelationOperator, @@ -27,10 +19,12 @@ import { reverseRawOperator, reverseSparqlOperator, } from '../lib/solverUtil'; -import * as UtilSolver from '../lib/solverUtil'; import { SparqlRelationOperator } from '../lib/TreeMetadata'; import type { ITreeRelation } from '../lib/TreeMetadata'; - +import { + TreeRelationSolverInput, + SparlFilterExpressionSolverInput +} from '../lib/SolverInput'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; @@ -344,490 +338,6 @@ describe('solver function', () => { }); }); - describe('convertTreeRelationToSolverExpression', () => { - it('given a TREE relation with all the parameters should return a valid expression', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - const variable = 'x'; - - const expectedExpression: ISolverExpression = { - variable, - rawValue: '5', - valueType: SparqlOperandDataTypes.Integer, - valueAsNumber: 5, - - operator: SparqlRelationOperator.EqualThanRelation, - }; - - expect(convertTreeRelationToSolverExpression(relation, variable)).toStrictEqual(expectedExpression); - }); - - it('should return undefined given a relation witn a value term containing an unknowed value type', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), - }, - node: 'https://www.example.be', - }; - const variable = 'x'; - - expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - - it(`should return undefined given a relation with - a term containing an incompatible value in relation to its value type`, () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: 'a', - term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - const variable = 'x'; - - expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - - it('should return undefined given a relation without a value and a type', () => { - const relation: ITreeRelation = { - remainingItems: 10, - path: 'ex:path', - node: 'https://www.example.be', - }; - const variable = 'x'; - - expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - - it('should return undefined given a relation without a value', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - node: 'https://www.example.be', - }; - const variable = 'x'; - - expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - - it('should return undefined given a relation without a type', () => { - const relation: ITreeRelation = { - remainingItems: 10, - path: 'ex:path', - value: { - value: 'a', - term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - const variable = 'x'; - - expect(convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - }); - - describe('resolveAFilterTerm', () => { - it('given an algebra expression with all the solver expression parameters should return a valid expression', () => { - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable('x'), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - const variable = 'x'; - const expectedSolverExpression: ISolverExpression = { - variable, - rawValue: '6', - valueType: SparqlOperandDataTypes.Integer, - valueAsNumber: 6, - operator, - }; - - const resp = resolveAFilterTerm(expression, operator, variable); - - if (resp) { - expect(resp).toStrictEqual(expectedSolverExpression); - } else { - expect(resp).toBeDefined(); - } - }); - - it('given an algebra expression without a variable than should return an misformated error', () => { - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - - expect(resolveAFilterTerm(expression, operator, 'x')).toBeInstanceOf(MisformatedFilterTermError); - }); - - it('given an algebra expression without a litteral than should return an misformated error', () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - - expect(resolveAFilterTerm(expression, operator, variable)) - .toBeInstanceOf(MisformatedFilterTermError); - }); - - it('given an algebra expression without args than should return a misformated error', () => { - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - - expect(resolveAFilterTerm(expression, operator, 'x')).toBeInstanceOf(MisformatedFilterTermError); - }); - - it(`given an algebra expression with a litteral containing an invalid datatype than - should return an unsupported datatype error`, - () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - - expect(resolveAFilterTerm(expression, operator, variable)) - .toBeInstanceOf(UnsupportedDataTypeError); - }); - - it(`given an algebra expression with a litteral containing a - literal that cannot be converted into number should return an unsupported datatype error`, () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - - expect(resolveAFilterTerm(expression, operator, variable)) - .toBeInstanceOf(UnsupportedDataTypeError); - }); - }); - - describe('recursifResolve', () => { - it('given an algebra expression with an unsupported logic operator should throw an error', () => { - const mock = jest.spyOn(UtilSolver, 'getSolutionInterval'); - mock.mockImplementation((): undefined => { return undefined; }); - - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2) - }`).input.expression; - - expect(() => recursifResolve( - expression, - 'x', - )).toThrow(); - mock.mockRestore(); - }); - it('given an algebra expression with two logicals operators should return the valid solution domain', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2 && ?x<5) - }`).input.expression; - - const resp = recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it('given an algebra expression with one logical operators should return the valid solution domain', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(?x=2) - }`).input.expression; - - const resp = recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it('given an algebra with a true statement should return an infinite domain', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(true) - }`).input.expression; - - const resp = recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals( - new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), - ); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it('given an algebra with a false statement should return an empty domain', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(false) - }`).input.expression; - - const resp = recursifResolve( - expression, - 'x', - ); - - expect(resp.isDomainEmpty()).toBe(true); - }); - - it('given an algebra with a not true statement should return an empty domain', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(!true) - }`).input.expression; - - const resp = recursifResolve( - expression, - 'x', - ); - - expect(resp.isDomainEmpty()).toBe(true); - }); - - it('given an algebra with a not false statement should return an infinite domain', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(!false) - }`).input.expression; - - const resp = recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals( - new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), - ); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it('given an algebra expression with one not equal logical operators should return the valid solution domain', - () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(?x!=2) - }`).input.expression; - - const resp = recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals([ - new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), - new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]), - ]); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it(`given an algebra expression with two logicals operators with a double negation should - return the valid solution domain`, () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( !(!(?x=2)) && ?x<5) - }`).input.expression; - - const resp = recursifResolve( - expression, - - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it('given an algebra expression with a double negation it should cancel it', () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER(!(!(?x=2 && ?x<5))) - }`).input.expression; - - const resp = recursifResolve( - expression, - - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it(`given an algebra expression with two logicals operators - that cannot be satified should return an empty domain`, () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2 && ?x>5) - }`).input.expression; - - const resp = recursifResolve( - expression, - - 'x', - ); - - expect(resp.isDomainEmpty()).toBe(true); - }); - - it(`given an algebra expression with two logicals operators that are negated - should return the valid solution domain`, () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( !(?x=2 && ?x<5)) - }`).input.expression; - - const resp = recursifResolve( - expression, - - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals( - [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), - new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], - ); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it(`given an algebra expression with two logicals operators that are triple negated - should return the valid solution domain`, () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( !(!(!(?x=2 && ?x<5)))) - }`).input.expression; - - const resp = recursifResolve( - expression, - - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals( - [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), - new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], - ); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it(`given an algebra expression with three logicals operators where the priority of operation should start with the not operator than - should return the valid solution domain`, () => { - const expression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( ?x=2 && ?x>=1 || !(?x=3)) - }`).input.expression; - - const resp = recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals([ - new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(3) ]), - new SolutionInterval([ nextUp(3), Number.POSITIVE_INFINITY ]), - ]); - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - }); describe('isBooleanExpressionTreeRelationFilterSolvable', () => { it('given a relation that is not able to be converted into a solverExpression should return true', () => { @@ -845,8 +355,11 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; - - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it('should return true given a relation and a filter operation where types are not compatible', () => { @@ -868,7 +381,12 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; + + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it('should return false given a relation and a filter operation where types are not compatible', () => { @@ -890,7 +408,12 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; + + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it('should return true when the solution range is not valid of the relation', () => { @@ -912,7 +435,12 @@ describe('solver function', () => { const variable = 'x'; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; + + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it('should return true when the equation system is not valid', () => { @@ -953,8 +481,12 @@ describe('solver function', () => { }; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it('should return true when there is a solution for the filter expression and the relation', () => { @@ -975,8 +507,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it('should return false when the filter expression has no solution ', () => { @@ -997,8 +533,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); }); it(`should return false when the filter has a possible @@ -1020,8 +560,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); }); it('should return true when there is a solution for the filter expression with one expression and the relation', @@ -1043,9 +587,13 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; expect(isBooleanExpressionTreeRelationFilterSolvable( - { relation, filterExpression, variable }, + inputs, )) .toBe(true); }); @@ -1069,9 +617,13 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; expect(isBooleanExpressionTreeRelationFilterSolvable( - { relation, filterExpression, variable }, + inputs, )) .toBe(false); }); @@ -1095,8 +647,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); }); it(`should return true when there is a solution for @@ -1118,8 +674,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it('should accept the link given that recursifResolve return a SyntaxError', () => { @@ -1140,8 +700,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); }); it('should accept the link if the data type of the filter is unsupported', () => { @@ -1162,8 +726,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it('should accept the link if a filter term with no args is not a boolean', () => { @@ -1186,8 +754,12 @@ describe('solver function', () => { }; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it('should accept the link with a solvable simple filter', @@ -1209,9 +781,13 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; expect(isBooleanExpressionTreeRelationFilterSolvable( - { relation, filterExpression, variable }, + inputs, )) .toBe(false); }); @@ -1235,8 +811,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; expect(isBooleanExpressionTreeRelationFilterSolvable( - { relation, filterExpression, variable }, + inputs, )) .toBe(false); }); @@ -1259,8 +839,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it(`should accept the link with a solvable boolean expression with a false boolean statement`, () => { @@ -1281,8 +865,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it(`should accept the link with a solvable simple boolean expression with a true boolean statement`, () => { @@ -1303,8 +891,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it(`Should ignore the SPARQL function when prunning links`, () => { @@ -1325,8 +917,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); }); it(`Should ignore the SPARQL function when prunning links with complex filter expression`, () => { @@ -1347,8 +943,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); it(`should prune a false filter expression`, () => { @@ -1369,8 +969,12 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(false); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); }); it(`should keep a true filter expression`, () => { @@ -1391,10 +995,14 @@ describe('solver function', () => { }`).input.expression; const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable) + ]; - expect(isBooleanExpressionTreeRelationFilterSolvable({ relation, filterExpression, variable })).toBe(true); + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); - }); +}) describe('reverseRawLogicOperator', () => { it('given an non existing operator should return undefined', () => { From 54e6750a94805c7ee540e61605544d0321f66838 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 1 Jun 2023 11:40:30 +0200 Subject: [PATCH 172/189] test case brought back to 100% --- .../lib/ActorExtractLinksTree.ts | 4 +- .../lib/SolverInput.ts | 415 +++---- .../lib/filterNode.ts | 254 ++-- .../lib/solver.ts | 41 +- .../lib/solverInterfaces.ts | 8 +- .../lib/solverUtil.ts | 5 +- .../package.json | 1 + .../test/LogicOperator-test.ts | 3 - .../test/SolverInput-test.ts | 1087 ++++++++++------- .../test/filterNode-test.ts | 412 ++++++- .../test/solver-test.ts | 197 ++- yarn.lock | 187 ++- 12 files changed, 1703 insertions(+), 911 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 63e827fb7..1c65f0a1b 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -9,10 +9,10 @@ import type { IActionContext } from '@comunica/types'; import type * as RDF from 'rdf-js'; import { termToString } from 'rdf-string'; import { filterNode } from './filterNode'; +import { isBooleanExpressionTreeRelationFilterSolvable } from './solver'; import { TreeNodes } from './TreeMetadata'; import type { ITreeRelationRaw, ITreeRelation, ITreeNode } from './TreeMetadata'; import { buildRelationElement, materializeTreeRelation, addRelationDescription } from './treeMetadataExtraction'; -import { isBooleanExpressionTreeRelationFilterSolvable } from './solver'; /** * A comunica Extract Links Tree Extract Links Actor. */ @@ -90,7 +90,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { * @returns {Promise>} a map containing the filter */ public async applyFilter(node: ITreeNode, context: IActionContext): Promise> { - return await filterNode(node, context,isBooleanExpressionTreeRelationFilterSolvable); + return await filterNode(node, context, isBooleanExpressionTreeRelationFilterSolvable); } /** diff --git a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts index 789a57812..011c1a7fa 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts @@ -1,73 +1,69 @@ import type * as RDF from 'rdf-js'; -import { Algebra } from "sparqlalgebrajs"; -import { SolutionDomain } from "./SolutionDomain"; -import { ISolverInput } from "./solverInterfaces"; +import { Algebra } from 'sparqlalgebrajs'; import { - MissMatchVariableError, - MisformatedExpressionError, - UnsupportedDataTypeError + MissMatchVariableError, + MisformatedExpressionError, + UnsupportedDataTypeError, } from './error'; import type { ILogicOperator } from './LogicOperator'; import { Or, operatorFactory } from './LogicOperator'; +import { SolutionDomain } from './SolutionDomain'; import { SolutionInterval } from './SolutionInterval'; +import { LogicOperatorReversed, + LogicOperatorSymbol, + SparqlOperandDataTypesReversed } from './solverInterfaces'; +import type { ISolverInput, + ISolverExpression, + Variable, + SparqlOperandDataTypes } from './solverInterfaces'; import { - LogicOperatorReversed, - LogicOperatorSymbol, - SparqlOperandDataTypesReversed, - SparqlOperandDataTypes -} from './solverInterfaces'; -import type { - ISolverExpression, - Variable, - -} from './solverInterfaces'; -import { - castSparqlRdfTermIntoNumber, - filterOperatorToSparqlRelationOperator, - getSolutionInterval, - inverseFilter + castSparqlRdfTermIntoNumber, + filterOperatorToSparqlRelationOperator, + getSolutionInterval, + inverseFilter, } from './solverUtil'; import type { ITreeRelation, SparqlRelationOperator } from './TreeMetadata'; const A_TRUE_EXPRESSION: SolutionInterval = new SolutionInterval( - [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY], + [ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ], ); const A_FALSE_EXPRESSION: SolutionInterval = new SolutionInterval([]); export class SparlFilterExpressionSolverInput implements ISolverInput { - public readonly filterExpression: Algebra.Expression; - public readonly variable: Variable; - public readonly domain: SolutionDomain; - - public constructor(filterExpression: Algebra.Expression, variable: Variable) { - this.filterExpression = filterExpression; - try { - this.domain = SparlFilterExpressionSolverInput.recursifResolve(this.filterExpression, variable); - - } catch (error: unknown) { - // A filter term was missformed we let the query engine return an error to the user and by precaution - // we accept the link in case the error is from the solver and not the filter expression - if (error instanceof MisformatedExpressionError) { - this.domain = SolutionDomain.newWithInitialIntervals(A_TRUE_EXPRESSION); - } else if (error instanceof UnsupportedDataTypeError) { - // We don't support the data type so let need to explore that link to not diminush the completness of the result - - this.domain = SolutionDomain.newWithInitialIntervals(A_TRUE_EXPRESSION); - } else { - /* istanbul ignore next */ - // If it's unexpected error we throw it - throw error; - } - + public readonly filterExpression: Algebra.Expression; + public readonly variable: Variable; + public readonly domain: SolutionDomain; - } finally { - Object.freeze(this); - Object.freeze(this.domain); - Object.freeze(this.variable); - } + public constructor(filterExpression: Algebra.Expression, variable: Variable) { + this.filterExpression = filterExpression; + this.variable = variable; + try { + this.domain = SparlFilterExpressionSolverInput.recursifResolve(this.filterExpression, variable); + } catch (error: unknown) { + // A filter term was missformed we let the query engine return an error to the user and by precaution + // we accept the link in case the error is from the solver and not the filter expression + if (error instanceof MisformatedExpressionError) { + this.domain = SolutionDomain.newWithInitialIntervals(A_TRUE_EXPRESSION); + } else if (error instanceof UnsupportedDataTypeError) { + // We don't support the data type so let need to explore that link to not diminush the completness of the result + this.domain = SolutionDomain.newWithInitialIntervals(A_TRUE_EXPRESSION); + } else { + /* istanbul ignore next */ + // If it's unexpected error we throw it + throw error; + } + } finally { + this.freeze(); } + } + + private freeze(): void { + Object.freeze(this); + Object.freeze(this.domain); + Object.freeze(this.variable); + } - /** + /** * Recursively traverse the filter expression and calculate the domain until it get to the current expression. * It will thrown an error if the expression is badly formated or if it's impossible to get the solution range. * @param {Algebra.Expression} filterExpression - @@ -79,199 +75,194 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { * @param {boolean} notExpression * @returns {SolutionDomain} The solution domain of the whole expression */ - public static recursifResolve( - filterExpression: Algebra.Expression, - variable: Variable, - domain: SolutionDomain = new SolutionDomain(), - logicOperator: ILogicOperator = new Or(), - ): SolutionDomain { - if (filterExpression.expressionType === Algebra.expressionTypes.TERM - ) { - // In that case we are confronted with a boolean expression - // add the associated interval into the domain in relation to - // the logic operator. - if (filterExpression.term.value === 'false') { - domain = logicOperator.apply({ interval: A_FALSE_EXPRESSION, domain }); - } else { - domain = logicOperator.apply({ interval: A_TRUE_EXPRESSION, domain }); - } - } else if ( - // If it's an array of terms then we should be able to create a solver expression. - // Given the resulting solver expression we can calculate a solution interval - // that we will add to the domain with regards to the logic operator. - filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && + public static recursifResolve( + filterExpression: Algebra.Expression, + variable: Variable, + domain: SolutionDomain = new SolutionDomain(), + logicOperator: ILogicOperator = new Or(), + ): SolutionDomain { + if (filterExpression.expressionType === Algebra.expressionTypes.TERM + ) { + // In that case we are confronted with a boolean expression + // add the associated interval into the domain in relation to + // the logic operator. + if (filterExpression.term.value === 'false') { + domain = logicOperator.apply({ interval: A_FALSE_EXPRESSION, domain }); + } else { + domain = logicOperator.apply({ interval: A_TRUE_EXPRESSION, domain }); + } + } else if ( + // If it's an array of terms then we should be able to create a solver expression. + // Given the resulting solver expression we can calculate a solution interval + // that we will add to the domain with regards to the logic operator. + filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && filterExpression.args.length === 2 - ) { - const rawOperator = filterExpression.operator; - const operator = filterOperatorToSparqlRelationOperator(rawOperator); - if (operator && logicOperator.operatorName() !== LogicOperatorSymbol.Not) { - const solverExpression = SparlFilterExpressionSolverInput.resolveAFilterTerm(filterExpression, operator, variable); - let solutionInterval: SolutionInterval | [SolutionInterval, SolutionInterval] | undefined; - if (solverExpression instanceof MissMatchVariableError) { - solutionInterval = A_TRUE_EXPRESSION; - } else if (solverExpression instanceof Error) { - throw solverExpression; - } else { - solutionInterval = getSolutionInterval(solverExpression.valueAsNumber, solverExpression.operator); - if (!solutionInterval) { - throw new UnsupportedDataTypeError('The operator is not supported'); - } - } - domain = logicOperator.apply({ interval: solutionInterval, domain }); - } + ) { + const rawOperator = filterExpression.operator; + const operator = filterOperatorToSparqlRelationOperator(rawOperator); + if (operator && logicOperator.operatorName() !== LogicOperatorSymbol.Not) { + const solverExpression = SparlFilterExpressionSolverInput + .resolveAFilterTerm(filterExpression, operator, variable); + let solutionInterval: SolutionInterval | [SolutionInterval, SolutionInterval] | undefined; + if (solverExpression instanceof MissMatchVariableError) { + solutionInterval = A_TRUE_EXPRESSION; + } else if (solverExpression instanceof Error) { + throw solverExpression; } else { - // In that case we are traversing the filter expression TREE. - // We prepare the next recursion and we compute the accumulation of results. - const logicOperatorSymbol = LogicOperatorReversed.get(filterExpression.operator); - if (logicOperatorSymbol) { - for (const arg of filterExpression.args) { - // To solve the not operation we rewrite the path of filter expression to reverse every operation - // e.g, = : != ; > : <= - if (logicOperatorSymbol === LogicOperatorSymbol.Not) { - inverseFilter(arg); - domain = this.recursifResolve(arg, variable, domain, logicOperator); - } else { - const newLogicOperator = operatorFactory(logicOperatorSymbol); - domain = this.recursifResolve(arg, variable, domain, newLogicOperator); - } - } - } + solutionInterval = getSolutionInterval(solverExpression.valueAsNumber, solverExpression.operator); + if (!solutionInterval) { + throw new UnsupportedDataTypeError('The operator is not supported'); + } + } + domain = logicOperator.apply({ interval: solutionInterval, domain }); + } + } else { + // In that case we are traversing the filter expression TREE. + // We prepare the next recursion and we compute the accumulation of results. + const logicOperatorSymbol = LogicOperatorReversed.get(filterExpression.operator); + if (logicOperatorSymbol) { + for (const arg of filterExpression.args) { + // To solve the not operation we rewrite the path of filter expression to reverse every operation + // e.g, = : != ; > : <= + if (logicOperatorSymbol === LogicOperatorSymbol.Not) { + inverseFilter(arg); + domain = this.recursifResolve(arg, variable, domain, logicOperator); + } else { + const newLogicOperator = operatorFactory(logicOperatorSymbol); + domain = this.recursifResolve(arg, variable, domain, newLogicOperator); + } } - return domain; + } } + return domain; + } - /** + /** * From an Algebra expression return an solver expression if possible * @param {Algebra.Expression} expression - Algebra expression containing the a variable and a litteral. * @param {SparqlRelationOperator} operator - The SPARQL operator defining the expression. * @param {Variable} variable - The variable the expression should have to be part of a system of equation. * @returns {ISolverExpression | undefined} Return a solver expression if possible */ - public static resolveAFilterTerm(expression: Algebra.Expression, - operator: SparqlRelationOperator, - variable: Variable): - ISolverExpression | Error { - let rawValue: string | undefined; - let valueType: SparqlOperandDataTypes | undefined; - let valueAsNumber: number | undefined; - let hasVariable = false; + public static resolveAFilterTerm(expression: Algebra.Expression, + operator: SparqlRelationOperator, + variable: Variable): + ISolverExpression | Error { + let rawValue: string | undefined; + let valueType: SparqlOperandDataTypes | undefined; + let valueAsNumber: number | undefined; + let hasVariable = false; - // Find the constituant element of the solver expression - for (const arg of expression.args) { - if ('term' in arg && arg.term.termType === 'Variable') { - // Check if the expression has the same variable as the one the solver try to resolved - if (arg.term.value !== variable) { - return new MissMatchVariableError(`the variable ${arg.term.value} is in the filter whereas we are looking for the varibale ${variable}`); - } - hasVariable = true; - } else if ('term' in arg && arg.term.termType === 'Literal') { - rawValue = arg.term.value; - valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); - if (valueType) { - valueAsNumber = castSparqlRdfTermIntoNumber(rawValue!, valueType); - if (!valueAsNumber) { - return new UnsupportedDataTypeError(`we do not support the datatype "${valueType}" in the solver for the moment`); - } - } else { - return new UnsupportedDataTypeError(`The datatype "${valueType}" is not supported by the SPARQL 1.1 Query Language W3C recommandation`); - } - } - } - // Return if a fully form solver expression can be created - if (hasVariable && rawValue && valueType && valueAsNumber) { - return { - variable, - rawValue, - valueType, - valueAsNumber, - operator, - }; - } - const missingTerm = []; - if (!hasVariable) { - missingTerm.push('Variable'); + // Find the constituant element of the solver expression + for (const arg of expression.args) { + if ('term' in arg && arg.term.termType === 'Variable') { + // Check if the expression has the same variable as the one the solver try to resolved + if (arg.term.value !== variable) { + return new MissMatchVariableError(`the variable ${arg.term.value} is in the filter whereas we are looking for the varibale ${variable}`); } - if (!rawValue) { - missingTerm.push('Litteral'); + hasVariable = true; + } else if ('term' in arg && arg.term.termType === 'Literal') { + rawValue = arg.term.value; + valueType = SparqlOperandDataTypesReversed.get(arg.term.datatype.value); + if (valueType) { + valueAsNumber = castSparqlRdfTermIntoNumber(rawValue!, valueType); + if (!valueAsNumber) { + return new UnsupportedDataTypeError(`we do not support the datatype "${valueType}" in the solver for the moment`); + } + } else { + return new UnsupportedDataTypeError(`The datatype "${valueType}" is not supported by the SPARQL 1.1 Query Language W3C recommandation`); } - - return new MisformatedExpressionError(`the filter expressions doesn't have the term ${missingTerm.toString()}`); + } + } + // Return if a fully form solver expression can be created + if (hasVariable && rawValue && valueType && valueAsNumber) { + return { + variable, + rawValue, + valueType, + valueAsNumber, + operator, + }; + } + const missingTerm = []; + if (!hasVariable) { + missingTerm.push('Variable'); + } + if (!rawValue) { + missingTerm.push('Litteral'); } + return new MisformatedExpressionError(`the filter expressions doesn't have the term ${missingTerm.toString()}`); + } } export class TreeRelationSolverInput implements ISolverInput { - public readonly domain: SolutionInterval | [SolutionInterval, SolutionInterval]; - public readonly treeRelation: ITreeRelation; - public readonly variable: Variable; + public readonly domain: SolutionInterval | [SolutionInterval, SolutionInterval]; + public readonly treeRelation: ITreeRelation; + public readonly variable: Variable; - public constructor(relation: ITreeRelation, variable: Variable) { - this.treeRelation = relation; - this.variable = variable; + public constructor(relation: ITreeRelation, variable: Variable) { + this.treeRelation = relation; + this.variable = variable; - const relationsolverExpressions = TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable); - // The relation doesn't have a value or a type, so we accept it - if (!relationsolverExpressions) { - this.domain = A_TRUE_EXPRESSION; - Object.freeze(this); - Object.freeze(this.domain); - Object.freeze(this.variable); - Object.freeze(this.treeRelation); - return; - } - - const relationSolutionInterval = getSolutionInterval( - relationsolverExpressions.valueAsNumber, - relationsolverExpressions.operator, - ); - // We don't prune the relation because we do not implement yet the solution range for this expression - if (!relationSolutionInterval) { - this.domain = A_TRUE_EXPRESSION; - Object.freeze(this); - Object.freeze(this.domain); - Object.freeze(this.variable); - Object.freeze(this.treeRelation); - return; - } - this.domain = relationSolutionInterval; + const relationsolverExpressions = TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable); + // The relation doesn't have a value or a type, so we accept it + if (!relationsolverExpressions) { + this.domain = A_TRUE_EXPRESSION; + this.freeze(); + return; + } - Object.freeze(this); - Object.freeze(this.domain); - Object.freeze(this.variable); - Object.freeze(this.treeRelation); + const relationSolutionInterval = getSolutionInterval( + relationsolverExpressions.valueAsNumber, + relationsolverExpressions.operator, + ); + // We don't prune the relation because we do not implement yet the solution range for this expression + if (!relationSolutionInterval) { + this.domain = A_TRUE_EXPRESSION; + this.freeze(); + return; } + this.domain = relationSolutionInterval; + this.freeze(); + } + private freeze(): void { + Object.freeze(this); + Object.freeze(this.domain); + Object.freeze(this.variable); + Object.freeze(this.treeRelation); + } - /** + /** * Convert a TREE relation into a solver expression. * @param {ITreeRelation} relation - TREE relation. * @param {Variable} variable - variable of the SPARQL query associated with the tree:path of the relation. * @returns {ISolverExpression | undefined} Resulting solver expression if the data type is supported by SPARQL * and the value can be cast into a number. */ - public static convertTreeRelationToSolverExpression(relation: ITreeRelation, - variable: Variable): - ISolverExpression | undefined { - if (relation.value && relation.type) { - const valueType = SparqlOperandDataTypesReversed.get((relation.value.term).datatype.value); - if (!valueType) { - return undefined; - } - const valueNumber = castSparqlRdfTermIntoNumber(relation.value.value, valueType); - if (!valueNumber && valueNumber !== 0) { - return undefined; - } + public static convertTreeRelationToSolverExpression(relation: ITreeRelation, + variable: Variable): + ISolverExpression | undefined { + if (relation.value && relation.type) { + const valueType = SparqlOperandDataTypesReversed.get((relation.value.term).datatype.value); + if (!valueType) { + return undefined; + } + const valueNumber = castSparqlRdfTermIntoNumber(relation.value.value, valueType); + if (!valueNumber && valueNumber !== 0) { + return undefined; + } - return { - variable, - rawValue: relation.value.value, - valueType, - valueAsNumber: valueNumber, + return { + variable, + rawValue: relation.value.value, + valueType, + valueAsNumber: valueNumber, - operator: relation.type, - }; - } + operator: relation.type, + }; } - + } } diff --git a/packages/actor-extract-links-extract-tree/lib/filterNode.ts b/packages/actor-extract-links-extract-tree/lib/filterNode.ts index 1bc48a397..2a4f28f0e 100644 --- a/packages/actor-extract-links-extract-tree/lib/filterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/filterNode.ts @@ -2,43 +2,40 @@ import { BindingsFactory } from '@comunica/bindings-factory'; import { KeysInitQuery } from '@comunica/context-entries'; import type { IActionContext } from '@comunica/types'; import { Algebra, Factory as AlgebraFactory, Util } from 'sparqlalgebrajs'; -import { SatisfactionChecker } from './solver'; +import type { SatisfactionChecker } from './solver'; +import { SparlFilterExpressionSolverInput, TreeRelationSolverInput } from './SolverInput'; import type { ISolverInput, Variable } from './solverInterfaces'; import type { ITreeRelation, ITreeNode } from './TreeMetadata'; -import { SparlFilterExpressionSolverInput, TreeRelationSolverInput } from './SolverInput'; const AF = new AlgebraFactory(); const BF = new BindingsFactory(); - - - - /** +/** * Return the filter expression if the TREE node has relations * @param {ITreeNode} node - The current TREE node * @param {IActionContext} context - The context * @returns {Algebra.Expression | undefined} The filter expression or undefined if the TREE node has no relations */ - export function getFilterExpressionIfTreeNodeHasConstraint(node: ITreeNode, - context: IActionContext): Algebra.Expression | undefined { - if (!node.relation) { - return undefined; - } - - if (node.relation.length === 0) { - return undefined; - } +export function getFilterExpressionIfTreeNodeHasConstraint(node: ITreeNode, + context: IActionContext): Algebra.Expression | undefined { + if (!node.relation) { + return undefined; + } - const query: Algebra.Operation = context.get(KeysInitQuery.query)!; - const filterExpression = findNode(query, Algebra.types.FILTER); - if (!filterExpression) { - return undefined; - } + if (node.relation.length === 0) { + return undefined; + } - return filterExpression.expression; + const query: Algebra.Operation = context.get(KeysInitQuery.query)!; + const filterExpression = findNode(query, Algebra.types.FILTER); + if (!filterExpression) { + return undefined; } - /** + return filterExpression.expression; +} + +/** * Analyze if the tree:relation(s) of a tree:Node should be followed and return a map * where if the value of the key representing the URL to follow is true than the link must be followed * if it is false then it should be ignored. @@ -46,131 +43,148 @@ const BF = new BindingsFactory(); * @param {IActionContext} context - The context * @returns {Promise>} A map of the indicating if a tree:relation should be follow */ - export async function filterNode(node: ITreeNode, context: IActionContext, satisfactionChecker:SatisfactionChecker ): Promise> { - const filterMap: Map = new Map(); - - const filterOperation: Algebra.Expression | undefined = - getFilterExpressionIfTreeNodeHasConstraint(node, context); - - if (!filterOperation) { - return new Map(); - } - - // Extract the bgp of the query. - const queryBody: Algebra.Operation = context.get(KeysInitQuery.query)!; - - // Capture the relation from the function argument. - const groupedRelations = groupRelationByNode(node.relation!); +export async function filterNode( + node: ITreeNode, + context: IActionContext, + satisfactionChecker: SatisfactionChecker, +): Promise> { + const filterMap: Map = new Map(); + + const filterOperation: Algebra.Expression | undefined = + getFilterExpressionIfTreeNodeHasConstraint(node, context); + + if (!filterOperation) { + return new Map(); + } - // + // Extract the bgp of the query. + const queryBody: Algebra.Operation = context.get(KeysInitQuery.query)!; - const calculatedFilterExpressions: Map = new Map(); - for (const relations of groupedRelations) { - // Accept the relation if the relation does't specify a condition. - if (!relations[0].path || !relations[0].value) { - filterMap.set(relations[0].node, true); - continue; - } - // Find the quad from the bgp that are related to the TREE relation. - const variables = findRelevantVariableFromBgp(queryBody, relations[0].path); + // Capture the relation from the function argument. + const groupedRelations = groupRelations(node.relation!); - // Accept the relation if no variable are linked with the relation. - if (variables.length === 0) { - filterMap.set(relations[0].node, true); - continue; - } - let filtered = false; - // For all the variable check if one is has a possible solutions. - for (const variable of variables) { - let inputFilterExpression = calculatedFilterExpressions.get(variable); - if(!inputFilterExpression){ - inputFilterExpression = new SparlFilterExpressionSolverInput( - structuredClone(filterOperation), - variable); - } - const inputs:ISolverInput[] = relations.map((relation)=> new TreeRelationSolverInput(relation, variable)); - inputs.push(inputFilterExpression); - filtered = filtered || satisfactionChecker(inputs) - } - let previousFilterValue = filterMap.get(relations[0].node); - if(!previousFilterValue){ - filterMap.set(relations[0].node, filtered); - }else{ - filterMap.set(relations[0].node, filtered || previousFilterValue); + const calculatedFilterExpressions: Map = new Map(); + for (const relations of groupedRelations) { + // Accept the relation if the relation does't specify a condition. + if (!relations[0].path || !relations[0].value) { + addToFilterMap(filterMap, true, relations[0].node); + continue; + } + // Find the quad from the bgp that are related to the TREE relation. + const variables = findRelevantVariableFromBgp(queryBody, relations[0].path); + // Accept the relation if no variable are linked with the relation. + if (variables.length === 0) { + addToFilterMap(filterMap, true, relations[0].node); + continue; + } + let filtered = false; + // For all the variable check if one is has a possible solutions. + for (const variable of variables) { + let inputFilterExpression = calculatedFilterExpressions.get(variable); + if (!inputFilterExpression) { + inputFilterExpression = new SparlFilterExpressionSolverInput( + structuredClone(filterOperation), + variable, + ); } + const inputs: ISolverInput[] = relations.map(relation => new TreeRelationSolverInput(relation, variable)); + inputs.push(inputFilterExpression); + filtered = filtered || satisfactionChecker(inputs); } - return filterMap; + addToFilterMap(filterMap, filtered, relations[0].node); + } + return filterMap; +} + +/** + * Helper function to add to the filter map while considering that a previous relation group might + * have permit the access to the node. If the a group gives the access previously we should keep the access. + * @param {Map} filterMap - The current filter map + * @param {boolean} filtered - The current access flag + * @param {string} node - The target node + */ +function addToFilterMap(filterMap: Map, filtered: boolean, node: string): void { + const previousFilterValue = filterMap.get(node); + if (!previousFilterValue) { + filterMap.set(node, filtered); + } else { + filterMap.set(node, filtered || previousFilterValue); } +} - /** +/** * Find the variables from the BGP that match the predicate defined by the TREE:path from a TREE relation. * The subject can be anyting. * @param {Algebra.Operation} queryBody - the body of the query * @param {string} path - TREE path * @returns {Variable[]} the variables of the Quad objects that contain the TREE path as predicate */ - export function findRelevantVariableFromBgp(queryBody: Algebra.Operation, path: string): Variable[] { - const resp: Variable[] = []; - const addVariable = (quad: any): boolean => { - if (quad.predicate.value === path && quad.object.termType === 'Variable') { - resp.push(quad.object.value); - } - return true; - }; +export function findRelevantVariableFromBgp(queryBody: Algebra.Operation, path: string): Variable[] { + const resp: Variable[] = []; + const addVariable = (quad: any): boolean => { + if (quad.predicate.value === path && quad.object.termType === 'Variable') { + resp.push(quad.object.value); + } + return true; + }; - Util.recurseOperation(queryBody, { - [Algebra.types.PATH]: addVariable, - [Algebra.types.PATTERN]: addVariable, + Util.recurseOperation(queryBody, { + [Algebra.types.PATH]: addVariable, + [Algebra.types.PATTERN]: addVariable, - }); - return resp; - } + }); + return resp; +} - function groupRelationByNode(relations: ITreeRelation[]): ITreeRelation[][]{ - const collector:Map> = new Map(); - const resp:ITreeRelation[][] = []; - for (const relation of relations){ - const path = relation.path!== undefined?relation.path:''; - const nodeGroup = collector.get(relation.node); - if(nodeGroup){ - const pathGroup = nodeGroup.get(path); - if(pathGroup){ - pathGroup.push(relation); - }else{ - nodeGroup.set(path, [relation]); - } - }else{ - collector.set(relation.node, new Map([[path, [relation]]])); +/** + * Group the relations by their target node and path + * @param {ITreeRelation[]} relations - relations + * @returns {ITreeRelation[][]} relations grouped + */ +export function groupRelations(relations: ITreeRelation[]): ITreeRelation[][] { + const collector: Map> = new Map(); + const resp: ITreeRelation[][] = []; + for (const relation of relations) { + const path = relation.path !== undefined ? relation.path : ''; + const nodeGroup = collector.get(relation.node); + if (nodeGroup) { + const pathGroup = nodeGroup.get(path); + if (pathGroup) { + pathGroup.push(relation); + } else { + nodeGroup.set(path, [ relation ]); } + } else { + collector.set(relation.node, new Map([[ path, [ relation ]]])); } + } - for (const [_, pathGroup] of collector){ - for(const [_, relations] of pathGroup){ - resp.push(relations); - } + for (const [ _path, pathGroup ] of collector) { + for (const [ _group, relationsGroup ] of pathGroup) { + resp.push(relationsGroup); } - return resp; } + return resp; +} - /** +/** * Find the first node of type `nodeType`, if it doesn't exist * it return undefined. * @param {Algebra.Operation} query - the original query * @param {string} nodeType - the type of node requested * @returns {any} */ - function findNode(query: Algebra.Operation, nodeType: string): any { - let currentNode = query; - do { - if (currentNode.type === nodeType) { - return currentNode; - } - if ('input' in currentNode) { - currentNode = currentNode.input; - } - } while ('input' in currentNode); - return undefined; - } - +function findNode(query: Algebra.Operation, nodeType: string): any { + let currentNode = query; + do { + if (currentNode.type === nodeType) { + return currentNode; + } + if ('input' in currentNode) { + currentNode = currentNode.input; + } + } while ('input' in currentNode); + return undefined; +} diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 6e71fb90e..9050c3fb3 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -1,8 +1,8 @@ -import { And,} from './LogicOperator'; -import { SolutionDomain } from './SolutionDomain'; import { InvalidExpressionSystem, MissMatchVariableError } from './error'; -import type {ISolverInput} from './solverInterfaces'; -import { SolutionInterval } from './SolutionInterval'; +import { And } from './LogicOperator'; +import type { SolutionDomain } from './SolutionDomain'; +import type { SolutionInterval } from './SolutionInterval'; +import type { ISolverInput } from './solverInterfaces'; const AND = new And(); export type SatisfactionChecker = (inputs: ISolverInput[]) => boolean; @@ -14,33 +14,38 @@ export type SatisfactionChecker = (inputs: ISolverInput[]) => boolean; * @returns {boolean} Whether the expression can be satify */ export function isBooleanExpressionTreeRelationFilterSolvable(inputs: ISolverInput[]): boolean { - let domain: SolutionDomain|undefined = undefined; - let intervals: Array = []; - let variable = inputs[0].variable; + let domain: SolutionDomain | undefined; + const intervals: (SolutionInterval | [SolutionInterval, SolutionInterval])[] = []; + if (inputs.length === 0) { + return true; + } + const variable = inputs[0].variable; + + for (const input of inputs) { + if (variable !== input.variable) { + throw new MissMatchVariableError('the variable are not matching'); + } - for(const input of inputs){ - if ("getDomain" in input.domain && domain!== undefined){ + if ('getDomain' in input.domain && domain !== undefined) { throw new InvalidExpressionSystem('there is more than one filter expression'); - } else if ("getDomain" in input.domain){ + } else if ('getDomain' in input.domain) { domain = input.domain; - } else if (variable != input.variable){ - throw new MissMatchVariableError('the variable are not matching') - }else{ + } else { intervals.push(input.domain); } } - if(!domain){ + if (!domain) { throw new InvalidExpressionSystem('there should be a filter expression'); } - if(intervals.length<1){ + if (intervals.length === 0) { throw new InvalidExpressionSystem('there should be at least one TREE relation to resolved'); } - for(const interval of intervals){ - domain = AND.apply({interval, domain}); + for (const interval of intervals) { + domain = AND.apply({ interval, domain }); } - return !domain.isDomainEmpty() + return !domain.isDomainEmpty(); } diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index ae56c9b01..efc4d5289 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -1,5 +1,5 @@ -import { SolutionDomain } from './SolutionDomain'; -import { SolutionInterval } from './SolutionInterval'; +import type { SolutionDomain } from './SolutionDomain'; +import type { SolutionInterval } from './SolutionInterval'; import type { SparqlRelationOperator } from './TreeMetadata'; /** * Valid SPARQL data type for operation. @@ -85,7 +85,7 @@ export interface ISolverExpression { * It must be able to be resolved into a domain or an interval */ export interface ISolverInput { - domain: SolutionInterval|[SolutionInterval, SolutionInterval]|SolutionDomain, - variable: Variable + domain: SolutionInterval | [SolutionInterval, SolutionInterval] | SolutionDomain; + variable: Variable; } diff --git a/packages/actor-extract-links-extract-tree/lib/solverUtil.ts b/packages/actor-extract-links-extract-tree/lib/solverUtil.ts index c2055fd08..d756ebf24 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverUtil.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverUtil.ts @@ -2,19 +2,16 @@ import { Algebra } from 'sparqlalgebrajs'; import { SolutionInterval } from './SolutionInterval'; import { - SparqlOperandDataTypes, LogicOperatorSymbol, SparqlOperandDataTypesReversed, + SparqlOperandDataTypes, LogicOperatorSymbol, } from './solverInterfaces'; import type { ISolverExpression, - Variable, } from './solverInterfaces'; import { SparqlRelationOperator } from './TreeMetadata'; -import type { ITreeRelation } from './TreeMetadata'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; - /** * Check if all the expression provided have a SparqlOperandDataTypes compatible type * it is considered that all number types are compatible between them. diff --git a/packages/actor-extract-links-extract-tree/package.json b/packages/actor-extract-links-extract-tree/package.json index 482917432..e7405e547 100644 --- a/packages/actor-extract-links-extract-tree/package.json +++ b/packages/actor-extract-links-extract-tree/package.json @@ -37,6 +37,7 @@ "@comunica/context-entries-link-traversal": "^0.1.1", "@comunica/core": "^2.6.8", "@comunica/types-link-traversal": "^0.1.0", + "@types/node": "17.0.29", "rdf-data-factory": "^1.1.1", "rdf-string": "^1.6.1", "sparqlalgebrajs": "^4.0.0", diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index 895819dbf..58a239d8f 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -3,9 +3,6 @@ import { SolutionDomain } from '../lib/SolutionDomain'; import { SolutionInterval } from '../lib/SolutionInterval'; import { LogicOperatorSymbol } from '../lib/solverInterfaces'; -const nextUp = require('ulp').nextUp; -const nextDown = require('ulp').nextDown; - describe('LogicOperator', () => { describe('Or', () => { const or = new Or(); diff --git a/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts b/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts index 1e0ed93f8..4abfe900e 100644 --- a/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts @@ -1,528 +1,699 @@ +import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; -import { DataFactory } from 'rdf-data-factory'; -import type { ITreeRelation } from '../lib/TreeMetadata'; -import { SparqlRelationOperator } from '../lib/TreeMetadata'; -import { LogicOperatorSymbol, - SparqlOperandDataTypes, - ISolverExpression } from '../lib/solverInterfaces'; import { - TreeRelationSolverInput, - SparlFilterExpressionSolverInput + MisformatedExpressionError, + UnsupportedDataTypeError, +} from '../lib/error'; +import { SolutionDomain } from '../lib/SolutionDomain'; +import { SolutionInterval } from '../lib/SolutionInterval'; +import { + TreeRelationSolverInput, + SparlFilterExpressionSolverInput, } from '../lib/SolverInput'; +import type { + ISolverExpression, +} from '../lib/solverInterfaces'; import { - MisformatedExpressionError, - UnsupportedDataTypeError, -} from '../lib/error'; + SparqlOperandDataTypes, +} from '../lib/solverInterfaces'; import * as UtilSolver from '../lib/solverUtil'; -import { SolutionInterval } from '../lib/SolutionInterval'; -import { SolutionDomain } from '../lib/SolutionDomain'; +import type { ITreeRelation } from '../lib/TreeMetadata'; +import { SparqlRelationOperator } from '../lib/TreeMetadata'; const DF = new DataFactory(); const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; +describe('SolverInput', () => { + describe('TreeRelationSolverInput', () => { + describe('constructor', () => { + afterEach(() => { + jest.spyOn(TreeRelationSolverInput, 'convertTreeRelationToSolverExpression') + .mockRestore(); + jest.spyOn(UtilSolver, 'getSolutionInterval') + .mockRestore(); + }); + + it('should return an infinite domain when we cannot convert the TREE into a solver expression', () => { + const expectedSolutionDomain = + new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]); + + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + jest.spyOn(TreeRelationSolverInput, 'convertTreeRelationToSolverExpression') + .mockImplementation((): undefined => { return undefined; }); + const input = new TreeRelationSolverInput(relation, variable); + + expect(input.domain).toStrictEqual(expectedSolutionDomain); + expect(input.variable).toBe('x'); + expect(input.treeRelation).toStrictEqual(relation); + }); -describe('SolverInput',()=>{ - describe('SparlFilterExpressionSolverInput',()=>{ + it('should return an infinite domain when we cannot get the interval of the solver expression', () => { + const expectedSolutionDomain = + new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]); + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + jest.spyOn(UtilSolver, 'getSolutionInterval') + .mockImplementation((): undefined => { return undefined; }); + const input = new TreeRelationSolverInput(relation, variable); + + expect(input.domain).toStrictEqual(expectedSolutionDomain); + expect(input.variable).toBe('x'); + expect(input.treeRelation).toStrictEqual(relation); + }); + + it(`should return the right solver expression given a + well formated TREE relation with a supported type and operator`, () => { + const expectedSolutionDomain = + new SolutionInterval([ 5, 5 ]); + + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + const input = new TreeRelationSolverInput(relation, variable); + + expect(input.domain).toStrictEqual(expectedSolutionDomain); + expect(input.variable).toBe('x'); + expect(input.treeRelation).toStrictEqual(relation); + }); }); - describe('TreeRelationSolverInput',()=>{ - describe('convertTreeRelationToSolverExpression',()=>{ - it('given a TREE relation with all the parameters should return a valid expression', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - const variable = 'x'; - - const expectedExpression: ISolverExpression = { - variable, - rawValue: '5', - valueType: SparqlOperandDataTypes.Integer, - valueAsNumber: 5, - - operator: SparqlRelationOperator.EqualThanRelation, - }; - - expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toStrictEqual(expectedExpression); - }); - - it('should return undefined given a relation witn a value term containing an unknowed value type', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), - }, - node: 'https://www.example.be', - }; - const variable = 'x'; - - expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - - it(`should return undefined given a relation with + describe('convertTreeRelationToSolverExpression', () => { + it('given a TREE relation with all the parameters should return a valid expression', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + const expectedExpression: ISolverExpression = { + variable, + rawValue: '5', + valueType: SparqlOperandDataTypes.Integer, + valueAsNumber: 5, + + operator: SparqlRelationOperator.EqualThanRelation, + }; + + expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)) + .toStrictEqual(expectedExpression); + }); + + it('should return undefined given a relation witn a value term containing an unknowed value type', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it(`should return undefined given a relation with a term containing an incompatible value in relation to its value type`, () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: 'a', - term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - const variable = 'x'; - - expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - - it('should return undefined given a relation without a value and a type', () => { - const relation: ITreeRelation = { - remainingItems: 10, - path: 'ex:path', - node: 'https://www.example.be', - }; - const variable = 'x'; - - expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - - it('should return undefined given a relation without a value', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - node: 'https://www.example.be', - }; - const variable = 'x'; - - expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - - it('should return undefined given a relation without a type', () => { - const relation: ITreeRelation = { - remainingItems: 10, - path: 'ex:path', - value: { - value: 'a', - term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - const variable = 'x'; - - expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); - }); - }); + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: 'a', + term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it('should return undefined given a relation without a value and a type', () => { + const relation: ITreeRelation = { + remainingItems: 10, + path: 'ex:path', + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it('should return undefined given a relation without a value', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + + it('should return undefined given a relation without a type', () => { + const relation: ITreeRelation = { + remainingItems: 10, + path: 'ex:path', + value: { + value: 'a', + term: DF.literal('a', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + const variable = 'x'; + + expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); + }); + }); + }); + + describe('SparlFilterExpressionSolverInput', () => { + describe('constructor', () => { + afterEach(() => { + jest.spyOn(SparlFilterExpressionSolverInput, 'recursifResolve') + .mockRestore(); + }); + it('should return the solution domain if it is valid', () => { + const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals( + new SolutionInterval([ 0, 1 ]), + ); + jest.spyOn(SparlFilterExpressionSolverInput, 'recursifResolve') + .mockReturnValue(expectedSolutionDomain); + + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2) + }`).input.expression; + + const input = new SparlFilterExpressionSolverInput(expression, 'x'); + + expect(input.domain).toStrictEqual(expectedSolutionDomain); + expect(input.variable).toBe('x'); + expect(input.filterExpression).toStrictEqual(expression); + }); + + it('should return an infinite domain if there is a misformated expression', () => { + const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals( + new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), + ); + + jest.spyOn(SparlFilterExpressionSolverInput, 'recursifResolve') + .mockImplementation(() => { + throw new MisformatedExpressionError('foo'); + }); + + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2) + }`).input.expression; + + const input = new SparlFilterExpressionSolverInput(expression, 'x'); + + expect(input.domain).toStrictEqual(expectedSolutionDomain); + expect(input.variable).toBe('x'); + expect(input.filterExpression).toStrictEqual(expression); + }); + + it('should return an infinite domain if there is a unsupported data type', () => { + const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals( + new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), + ); + + jest.spyOn(SparlFilterExpressionSolverInput, 'recursifResolve') + .mockImplementation(() => { + throw new UnsupportedDataTypeError('foo'); + }); + + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2) + }`).input.expression; + + const input = new SparlFilterExpressionSolverInput(expression, 'x'); + + expect(input.domain).toStrictEqual(expectedSolutionDomain); + expect(input.variable).toBe('x'); + expect(input.filterExpression).toStrictEqual(expression); + }); + + it(`should throw an error if there is an error that is not + cause by an unsupported datatype or a misformated expression`, () => { + const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals( + new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), + ); + + jest.spyOn(SparlFilterExpressionSolverInput, 'recursifResolve') + .mockImplementation(() => { + throw new TypeError('foo'); + }); + + const expression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER( ?x=2) + }`).input.expression; + + expect(() => new SparlFilterExpressionSolverInput(expression, 'x')).toThrow(); + }); }); - describe('SparlFilterExpressionSolverInput', ()=>{ - describe('resolveAFilterTerm', ()=>{ - it('given an algebra expression with all the solver expression parameters should return a valid expression', () => { - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable('x'), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - const variable = 'x'; - const expectedSolverExpression: ISolverExpression = { - variable, - rawValue: '6', - valueType: SparqlOperandDataTypes.Integer, - valueAsNumber: 6, - operator, - }; - - const resp = SparlFilterExpressionSolverInput. - resolveAFilterTerm(expression, operator, variable); - - if (resp) { - expect(resp).toStrictEqual(expectedSolverExpression); - } else { - expect(resp).toBeDefined(); - } - }); - - it('given an algebra expression without a variable than should return an misformated error', () => { - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - - expect(SparlFilterExpressionSolverInput.resolveAFilterTerm(expression, operator, 'x')).toBeInstanceOf(MisformatedExpressionError); - }); - - it('given an algebra expression without a litteral than should return an misformated error', () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - - expect(SparlFilterExpressionSolverInput. - resolveAFilterTerm(expression, operator, variable)). - toBeInstanceOf(MisformatedExpressionError); - }); - - it('given an algebra expression without args than should return a misformated error', () => { - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - - expect(SparlFilterExpressionSolverInput. - resolveAFilterTerm(expression, operator, 'x')). - toBeInstanceOf(MisformatedExpressionError); - }); - - it(`given an algebra expression with a litteral containing an invalid datatype than + describe('resolveAFilterTerm', () => { + it('given an algebra expression with all the solver expression parameters should return a valid expression', + () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable('x'), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + const variable = 'x'; + const expectedSolverExpression: ISolverExpression = { + variable, + rawValue: '6', + valueType: SparqlOperandDataTypes.Integer, + valueAsNumber: 6, + operator, + }; + + const resp = SparlFilterExpressionSolverInput + .resolveAFilterTerm(expression, operator, variable); + + if (resp) { + expect(resp).toStrictEqual(expectedSolverExpression); + } else { + expect(resp).toBeDefined(); + } + }); + + it('given an algebra expression without a variable than should return an misformated error', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + + expect(SparlFilterExpressionSolverInput.resolveAFilterTerm(expression, operator, 'x')) + .toBeInstanceOf(MisformatedExpressionError); + }); + + it('given an algebra expression without a litteral than should return an misformated error', () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + + expect(SparlFilterExpressionSolverInput + .resolveAFilterTerm(expression, operator, variable)) + .toBeInstanceOf(MisformatedExpressionError); + }); + + it('given an algebra expression without args than should return a misformated error', () => { + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + + expect(SparlFilterExpressionSolverInput + .resolveAFilterTerm(expression, operator, 'x')) + .toBeInstanceOf(MisformatedExpressionError); + }); + + it(`given an algebra expression with a litteral containing an invalid datatype than should return an unsupported datatype error`, - () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - - expect(SparlFilterExpressionSolverInput. - resolveAFilterTerm(expression, operator, variable)). - toBeInstanceOf(UnsupportedDataTypeError); - }); - - it(`given an algebra expression with a litteral containing a + () => { + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#foo')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; + + expect(SparlFilterExpressionSolverInput + .resolveAFilterTerm(expression, operator, variable)) + .toBeInstanceOf(UnsupportedDataTypeError); + }); + + it(`given an algebra expression with a litteral containing a literal that cannot be converted into number should return an unsupported datatype error`, () => { - const variable = 'x'; - const expression: Algebra.Expression = { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.OPERATOR, - operator: '=', - args: [ - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.variable(variable), - }, - { - type: Algebra.types.EXPRESSION, - expressionType: Algebra.expressionTypes.TERM, - term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), - }, - ], - }; - const operator = SparqlRelationOperator.EqualThanRelation; - - expect(SparlFilterExpressionSolverInput. - resolveAFilterTerm(expression, operator, variable)). - toBeInstanceOf(UnsupportedDataTypeError); - }); - }); + const variable = 'x'; + const expression: Algebra.Expression = { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.OPERATOR, + operator: '=', + args: [ + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.variable(variable), + }, + { + type: Algebra.types.EXPRESSION, + expressionType: Algebra.expressionTypes.TERM, + term: DF.literal('6', DF.namedNode('http://www.w3.org/2001/XMLSchema#string')), + }, + ], + }; + const operator = SparqlRelationOperator.EqualThanRelation; - describe('recursifResolve', ()=>{ - it('given an algebra expression with an unsupported logic operator should throw an error', () => { - const mock = jest.spyOn(UtilSolver, 'getSolutionInterval'); - mock.mockImplementation((): undefined => { return undefined; }); - - const expression = translate(` + expect(SparlFilterExpressionSolverInput + .resolveAFilterTerm(expression, operator, variable)) + .toBeInstanceOf(UnsupportedDataTypeError); + }); + }); + + describe('recursifResolve', () => { + it('given an algebra expression with an unsupported logic operator should throw an error', () => { + const mock = jest.spyOn(UtilSolver, 'getSolutionInterval'); + mock.mockImplementation((): undefined => { return undefined; }); + + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( ?x=2) }`).input.expression; - - expect(() => SparlFilterExpressionSolverInput.recursifResolve( - expression, - 'x', - )).toThrow(); - mock.mockRestore(); - }); - it('given an algebra expression with two logicals operators should return the valid solution domain', () => { - const expression = translate(` + + expect(() => SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + )).toThrow(); + mock.mockRestore(); + }); + + it('given an algebra expression with two logicals operators should return the valid solution domain', () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( ?x=2 && ?x<5) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it('given an algebra expression with one logical operators should return the valid solution domain', () => { - const expression = translate(` + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra expression with one logical operators should return the valid solution domain', () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(?x=2) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it('given an algebra with a true statement should return an infinite domain', () => { - const expression = translate(` + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra with a true statement should return an infinite domain', () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(true) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals( - new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), - ); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it('given an algebra with a false statement should return an empty domain', () => { - const expression = translate(` + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals( + new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), + ); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra with a false statement should return an empty domain', () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(false) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - 'x', - ); - - expect(resp.isDomainEmpty()).toBe(true); - }); - - it('given an algebra with a not true statement should return an empty domain', () => { - const expression = translate(` + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + expect(resp.isDomainEmpty()).toBe(true); + }); + + it('given an algebra with a not true statement should return an empty domain', () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(!true) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - 'x', - ); - - expect(resp.isDomainEmpty()).toBe(true); - }); - - it('given an algebra with a not false statement should return an infinite domain', () => { - const expression = translate(` + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + expect(resp.isDomainEmpty()).toBe(true); + }); + + it('given an algebra with a not false statement should return an infinite domain', () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(!false) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals( - new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), - ); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it('given an algebra expression with one not equal logical operators should return the valid solution domain', - () => { - const expression = translate(` + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals( + new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY ]), + ); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra expression with one not equal logical operators should return the valid solution domain', + () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(?x!=2) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals([ - new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), - new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]), - ]); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it(`given an algebra expression with two logicals operators with a double negation should + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals([ + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), + new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]), + ]); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it(`given an algebra expression with two logicals operators with a double negation should return the valid solution domain`, () => { - const expression = translate(` + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(!(?x=2)) && ?x<5) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it('given an algebra expression with a double negation it should cancel it', () => { - const expression = translate(` + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it('given an algebra expression with a double negation it should cancel it', () => { + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(!(!(?x=2 && ?x<5))) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it(`given an algebra expression with two logicals operators + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 2, 2 ])); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it(`given an algebra expression with two logicals operators that cannot be satified should return an empty domain`, () => { - const expression = translate(` + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( ?x=2 && ?x>5) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - - 'x', - ); - - expect(resp.isDomainEmpty()).toBe(true); - }); - - it(`given an algebra expression with two logicals operators that are negated + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + + 'x', + ); + + expect(resp.isDomainEmpty()).toBe(true); + }); + + it(`given an algebra expression with two logicals operators that are negated should return the valid solution domain`, () => { - const expression = translate(` + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(?x=2 && ?x<5)) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals( - [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), - new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], - ); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it(`given an algebra expression with two logicals operators that are triple negated + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals( + [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), + new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], + ); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it(`given an algebra expression with two logicals operators that are triple negated should return the valid solution domain`, () => { - const expression = translate(` + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(!(!(?x=2 && ?x<5)))) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals( - [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), - new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], - ); - - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - - it(`given an algebra expression with three logicals operators where the priority of operation should start with the not operator than + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals( + [ new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(2) ]), + new SolutionInterval([ nextUp(2), Number.POSITIVE_INFINITY ]) ], + ); + + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it(`given an algebra expression with three logicals operators where the priority of operation should start with the not operator than should return the valid solution domain`, () => { - const expression = translate(` + const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( ?x=2 && ?x>=1 || !(?x=3)) }`).input.expression; - - const resp = SparlFilterExpressionSolverInput.recursifResolve( - expression, - 'x', - ); - - const expectedDomain = SolutionDomain.newWithInitialIntervals([ - new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(3) ]), - new SolutionInterval([ nextUp(3), Number.POSITIVE_INFINITY ]), - ]); - expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); - }); - }); - - + + const resp = SparlFilterExpressionSolverInput.recursifResolve( + expression, + 'x', + ); + + const expectedDomain = SolutionDomain.newWithInitialIntervals([ + new SolutionInterval([ Number.NEGATIVE_INFINITY, nextDown(3) ]), + new SolutionInterval([ nextUp(3), Number.POSITIVE_INFINITY ]), + ]); + expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); }); -}); \ No newline at end of file + }); +}); diff --git a/packages/actor-extract-links-extract-tree/test/filterNode-test.ts b/packages/actor-extract-links-extract-tree/test/filterNode-test.ts index 3960072d8..dfc3a435d 100644 --- a/packages/actor-extract-links-extract-tree/test/filterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/filterNode-test.ts @@ -4,17 +4,15 @@ import { ActionContext } from '@comunica/core'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; -import { filterNode, getFilterExpressionIfTreeNodeHasConstraint } from '../lib/FilterNode'; -import { SparqlRelationOperator } from '../lib/TreeMetadata'; -import type { ITreeNode } from '../lib/TreeMetadata'; -import { ISolverInput } from '../lib/solverInterfaces'; +import { filterNode, groupRelations, getFilterExpressionIfTreeNodeHasConstraint } from '../lib/filterNode'; import { isBooleanExpressionTreeRelationFilterSolvable } from '../lib/solver'; +import { SparqlRelationOperator } from '../lib/TreeMetadata'; +import type { ITreeNode, ITreeRelation } from '../lib/TreeMetadata'; const DF = new DataFactory(); describe('filterNode Module', () => { - describe('getFilterExpressionIfTreeNodeHasConstraint ', () => { const treeSubject = 'tree'; it('should test when there are relations and a filter operation in the query', () => { @@ -37,7 +35,7 @@ describe('filterNode Module', () => { expect(response).toBeDefined(); }); - it('should no test when the TREE relation are undefined', async () => { + it('should no test when the TREE relation are undefined', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -49,7 +47,7 @@ describe('filterNode Module', () => { expect(response).toBeUndefined(); }); - it('should not test when there is a filter operation in the query but no TREE relations', async () => { + it('should not test when there is a filter operation in the query but no TREE relations', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, }); @@ -61,7 +59,7 @@ describe('filterNode Module', () => { expect(response).toBeUndefined(); }); - it('should no test when there are no filter operation in the query but a TREE relation', async () => { + it('should no test when there are no filter operation in the query but a TREE relation', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); @@ -80,7 +78,7 @@ describe('filterNode Module', () => { describe('filternNode', () => { describe('with isBooleanExpressionTreeRelationFilterSolvable', () => { - it('should accept the relation when the filter respect the relation', async () => { + it('should accept the relation when the filter respect the relation', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -104,7 +102,7 @@ describe('filterNode Module', () => { ex:foo ex:p ex:o. FILTER(?o=5) } - `, { prefixes: { ex: 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, @@ -113,15 +111,15 @@ describe('filterNode Module', () => { const result = await filterNode( node, context, - isBooleanExpressionTreeRelationFilterSolvable + isBooleanExpressionTreeRelationFilterSolvable, ); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should not accept the relation when the filter is not respected by the relation', async () => { + it('should not accept the relation when the filter is not respected by the relation', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -144,7 +142,7 @@ describe('filterNode Module', () => { ex:foo ex:path ?o. FILTER(?o=88) } - `, { prefixes: { ex: 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -152,15 +150,15 @@ describe('filterNode Module', () => { const result = await filterNode( node, context, - isBooleanExpressionTreeRelationFilterSolvable + isBooleanExpressionTreeRelationFilterSolvable, ); expect(result).toStrictEqual( - new Map([['http://bar.com', false]]), + new Map([[ 'http://bar.com', false ]]), ); }); - it('should accept the relation when the query don\'t invoke the right path', async () => { + it('should accept the relation when the query don\'t invoke the right path', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -182,7 +180,7 @@ describe('filterNode Module', () => { ex:foo ex:superPath ?o. FILTER(?o=5) } - `, { prefixes: { ex: 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -190,21 +188,21 @@ describe('filterNode Module', () => { const result = await filterNode( node, context, - isBooleanExpressionTreeRelationFilterSolvable + isBooleanExpressionTreeRelationFilterSolvable, ); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should return an empty map when there is no relation', async () => { + it('should return an empty map when there is no relation', async() => { const query = translate(` SELECT ?o WHERE { ex:foo ex:path ?o. FILTER(?o=88) } - `, { prefixes: { ex: 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -212,7 +210,7 @@ describe('filterNode Module', () => { const result = await filterNode( node, context, - isBooleanExpressionTreeRelationFilterSolvable + isBooleanExpressionTreeRelationFilterSolvable, ); expect(result).toStrictEqual( @@ -221,7 +219,7 @@ describe('filterNode Module', () => { }); it('should accept the relation when there is multiple filters and the query path don\'t match the relation', - async () => { + async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -247,7 +245,7 @@ describe('filterNode Module', () => { ex:foo ex:path ?o. FILTER(?o=5 && ?o<=5 ) } - `, { prefixes: { ex: 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, @@ -256,16 +254,16 @@ describe('filterNode Module', () => { const result = await filterNode( node, context, - isBooleanExpressionTreeRelationFilterSolvable + isBooleanExpressionTreeRelationFilterSolvable, ); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); it('should accept the relations when one respect the filter and another has no path and value defined', - async () => { + async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -292,7 +290,7 @@ describe('filterNode Module', () => { ex:foo ex:path ?o. FILTER(?o=5) } - `, { prefixes: { ex: 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -300,15 +298,15 @@ describe('filterNode Module', () => { const result = await filterNode( node, context, - isBooleanExpressionTreeRelationFilterSolvable + isBooleanExpressionTreeRelationFilterSolvable, ); expect(result).toStrictEqual( - new Map([['http://bar.com', true], ['http://foo.com', true]]), + new Map([[ 'http://bar.com', true ], [ 'http://foo.com', true ]]), ); }); - it('should accept the relation when the filter argument are not related to the query', async () => { + it('should accept the relation when the filter argument are not related to the query', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -330,7 +328,7 @@ describe('filterNode Module', () => { ex:foo ex:path ?o. FILTER(?p=88) } - `, { prefixes: { ex: 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -338,15 +336,15 @@ describe('filterNode Module', () => { const result = await filterNode( node, context, - isBooleanExpressionTreeRelationFilterSolvable + isBooleanExpressionTreeRelationFilterSolvable, ); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should accept the relation when there is multiples filters and one is not relevant', async () => { + it('should accept the relation when there is multiples filters and one is not relevant', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -368,7 +366,7 @@ describe('filterNode Module', () => { ex:foo ex:path ?o. FILTER((?p=88 && ?p>3) || true) } - `, { prefixes: { ex: 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -376,15 +374,15 @@ describe('filterNode Module', () => { const result = await filterNode( node, context, - isBooleanExpressionTreeRelationFilterSolvable + isBooleanExpressionTreeRelationFilterSolvable, ); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should accept the relation when the filter compare two constants', async () => { + it('should accept the relation when the filter compare two constants', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -406,7 +404,7 @@ describe('filterNode Module', () => { ex:foo ex:path ?o. FILTER(5=5) } - `, { prefixes: { ex: 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, }); @@ -414,15 +412,15 @@ describe('filterNode Module', () => { const result = await filterNode( node, context, - isBooleanExpressionTreeRelationFilterSolvable + isBooleanExpressionTreeRelationFilterSolvable, ); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should accept the relation when the filter respect the relation with a construct query', async () => { + it('should accept the relation when the filter respect the relation with a construct query', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -447,7 +445,7 @@ describe('filterNode Module', () => { ex:foo ex:path ?o. FILTER(?o=5) } - `, { prefixes: { ex: 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, @@ -456,15 +454,15 @@ describe('filterNode Module', () => { const result = await filterNode( node, context, - isBooleanExpressionTreeRelationFilterSolvable + isBooleanExpressionTreeRelationFilterSolvable, ); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should accept the relation when the filter respect the relation with a nested query', async () => { + it('should accept the relation when the filter respect the relation with a nested query', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -492,7 +490,7 @@ describe('filterNode Module', () => { } FILTER(?o=5) } - `, { prefixes: { ex: 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, @@ -501,15 +499,15 @@ describe('filterNode Module', () => { const result = await filterNode( node, context, - isBooleanExpressionTreeRelationFilterSolvable + isBooleanExpressionTreeRelationFilterSolvable, ); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); - it('should accept the relation when a complex filter respect the relation', async () => { + it('should accept the relation when a complex filter respect the relation', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -534,7 +532,7 @@ describe('filterNode Module', () => { ex:foo ex:p2 ?x. FILTER(?o>2 || ?x=4 || (?x<3 && ?o<6) ) } - `, { prefixes: { ex: 'http://example.com#' } }); + `, { prefixes: { ex: 'http://example.com#' }}); const context = new ActionContext({ [KeysInitQuery.query.name]: query, @@ -543,14 +541,318 @@ describe('filterNode Module', () => { const result = await filterNode( node, context, - isBooleanExpressionTreeRelationFilterSolvable + isBooleanExpressionTreeRelationFilterSolvable, ); expect(result).toStrictEqual( - new Map([['http://bar.com', true]]), + new Map([[ 'http://bar.com', true ]]), ); }); + + it(`should accept a relation if a first relation is not + satisfiable and a second is because it does not have countraint`, async() => { + const treeSubject = 'tree'; + + const node: ITreeNode = { + identifier: treeSubject, + relation: [ + { + node: 'http://bar.com', + }, + { + node: 'http://bar.com', + path: 'http://example.com#path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + type: SparqlRelationOperator.GreaterThanRelation, + }, + ], + }; + + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + ex:foo ex:p ex:o. + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' }}); + + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable, + ); + + expect(result).toStrictEqual( + new Map([[ 'http://bar.com', true ]]), + ); + }); + + it(`should accept a relation if a first relation is not satisfiable + and a second has a path no present into the filter expression`, async() => { + const treeSubject = 'tree'; + + const node: ITreeNode = { + identifier: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: 'http://example.com#path2', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + type: SparqlRelationOperator.GreaterThanRelation, + }, + { + node: 'http://bar.com', + path: 'http://example.com#path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + type: SparqlRelationOperator.GreaterThanRelation, + }, + ], + }; + + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + ex:foo ex:p ex:o. + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' }}); + + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable, + ); + + expect(result).toStrictEqual( + new Map([[ 'http://bar.com', true ]]), + ); + }); + }); + }); + + describe('groupRelations', () => { + it('should return an empty group given no relation', () => { + expect(groupRelations([])).toStrictEqual([]); + }); + + it('given relations with the same nodes and path should return one group', () => { + const relations: ITreeRelation[] = [ + { + path: 'foo', + node: 'foo', + }, + { + path: 'foo', + node: 'foo', + }, + { + path: 'foo', + node: 'foo', + }, + ]; + const group = groupRelations(relations); + + expect(group).toStrictEqual([ relations ]); + }); + + it('given relations with the same node and path execpt one without path should return two groups', () => { + const relations: ITreeRelation[] = [ + { + path: 'foo', + node: 'foo', + }, + { + node: 'foo', + }, + { + path: 'foo', + node: 'foo', + }, + ]; + + const expectedGroup = [ + [ + { + path: 'foo', + node: 'foo', + }, + { + path: 'foo', + node: 'foo', + }, + ], + [ + { + node: 'foo', + }, + ], + ]; + const group = groupRelations(relations); + + expect(group).toStrictEqual(expectedGroup); + }); + + it('given relations with no path should return one group', () => { + const relations: ITreeRelation[] = [ + { + node: 'foo', + }, + { + node: 'foo', + }, + { + node: 'foo', + }, + ]; + const group = groupRelations(relations); + + expect(group).toStrictEqual([ relations ]); }); + it('given relations with multiple node should return different group', () => { + const relations: ITreeRelation[] = [ + { + path: 'foo', + node: 'foo', + }, + { + path: 'foo', + node: 'bar', + }, + { + path: 'foo', + node: 'foo', + }, + ]; + + const expectedGroup = [ + [ + { + path: 'foo', + node: 'foo', + }, + { + path: 'foo', + node: 'foo', + }, + ], + [ + { + path: 'foo', + node: 'bar', + }, + ], + ]; + const group = groupRelations(relations); + // eslint-disable-next-line @typescript-eslint/require-array-sort-compare + expect(group.sort()).toStrictEqual(expectedGroup.sort()); + }); + + it('given relations with multiple path should different group', () => { + const relations: ITreeRelation[] = [ + { + path: 'bar', + node: 'foo', + }, + { + path: 'foo', + node: 'foo', + }, + { + path: 'foo', + node: 'foo', + }, + ]; + + const expectedGroup = [ + [ + { + path: 'foo', + node: 'foo', + }, + { + path: 'foo', + node: 'foo', + }, + ], + [ + { + path: 'bar', + node: 'foo', + }, + ], + ]; + const group = groupRelations(relations); + + // eslint-disable-next-line @typescript-eslint/require-array-sort-compare + expect(group.sort()).toStrictEqual(expectedGroup.sort()); + }); + + it('given relations with multiple path and group should return different group', () => { + const relations: ITreeRelation[] = [ + { + path: 'bar', + node: 'foo', + }, + { + path: 'foo', + node: 'foo', + }, + { + path: 'foo', + node: 'foo', + }, + { + path: 'tor', + node: 'tor', + }, + ]; + + const expectedGroup = [ + [ + { + path: 'foo', + node: 'foo', + }, + { + path: 'foo', + node: 'foo', + }, + ], + [ + { + path: 'bar', + node: 'foo', + }, + ], + [ + { + path: 'tor', + node: 'tor', + }, + ], + ]; + const group = groupRelations(relations); + + // eslint-disable-next-line @typescript-eslint/require-array-sort-compare + expect(group.sort()).toStrictEqual(expectedGroup.sort()); + }); }); }); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index b3565a4d1..c7a1cf50d 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -1,10 +1,15 @@ import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; +import { InvalidExpressionSystem, MissMatchVariableError } from '../lib/error'; import { SolutionInterval } from '../lib/SolutionInterval'; import { isBooleanExpressionTreeRelationFilterSolvable, } from '../lib/solver'; +import { + TreeRelationSolverInput, + SparlFilterExpressionSolverInput, +} from '../lib/SolverInput'; import { LogicOperatorSymbol, SparqlOperandDataTypes } from '../lib/solverInterfaces'; import type { ISolverExpression, @@ -21,10 +26,6 @@ import { } from '../lib/solverUtil'; import { SparqlRelationOperator } from '../lib/TreeMetadata'; import type { ITreeRelation } from '../lib/TreeMetadata'; -import { - TreeRelationSolverInput, - SparlFilterExpressionSolverInput -} from '../lib/SolverInput'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; @@ -338,7 +339,6 @@ describe('solver function', () => { }); }); - describe('isBooleanExpressionTreeRelationFilterSolvable', () => { it('given a relation that is not able to be converted into a solverExpression should return true', () => { const relation: ITreeRelation = { @@ -357,7 +357,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); @@ -383,7 +383,7 @@ describe('solver function', () => { const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); @@ -410,7 +410,7 @@ describe('solver function', () => { const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); @@ -437,7 +437,7 @@ describe('solver function', () => { const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); @@ -483,7 +483,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); @@ -509,7 +509,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); @@ -535,7 +535,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); @@ -562,7 +562,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); @@ -589,11 +589,11 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable( - inputs, + inputs, )) .toBe(true); }); @@ -619,7 +619,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable( @@ -649,7 +649,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); @@ -676,7 +676,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); @@ -702,7 +702,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); @@ -728,7 +728,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); @@ -756,7 +756,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); @@ -783,7 +783,49 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), + ]; + + expect(isBooleanExpressionTreeRelationFilterSolvable( + inputs, + )) + .toBe(false); + }); + + it('should refuse the link with a a bounded TREE node', + () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.GreaterThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '10', + term: DF.literal('10', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const relationBound: ITreeRelation = { + type: SparqlRelationOperator.LessThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '20', + term: DF.literal('20', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x>30) + }`).input.expression; + + const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new TreeRelationSolverInput(relationBound, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable( @@ -813,7 +855,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable( inputs, @@ -841,7 +883,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); @@ -867,7 +909,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); @@ -893,7 +935,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); @@ -919,7 +961,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); @@ -945,7 +987,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); @@ -971,7 +1013,7 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); @@ -997,12 +1039,105 @@ describe('solver function', () => { const variable = 'x'; const inputs = [ new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable) + new SparlFilterExpressionSolverInput(filterExpression, variable), ]; expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); -}) + + it('should throw an error when there is two filter expressions', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(true) + }`).input.expression; + + const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable), + ]; + + expect(() => isBooleanExpressionTreeRelationFilterSolvable(inputs)).toThrow(InvalidExpressionSystem); + }); + + it('should throw an error if there is no filter expression', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + ]; + + expect(() => isBooleanExpressionTreeRelationFilterSolvable(inputs)).toThrow(InvalidExpressionSystem); + }); + + it('should throw an error if there is no tree relation', () => { + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(true) + }`).input.expression; + + const variable = 'x'; + const inputs = [ + new SparlFilterExpressionSolverInput(filterExpression, variable), + ]; + + expect(() => isBooleanExpressionTreeRelationFilterSolvable(inputs)).toThrow(InvalidExpressionSystem); + }); + + it('should return true if there is no inputs', () => { + const inputs = []; + + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); + }); + + it('should throw an error when there are multiple variables', () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(true) + }`).input.expression; + + const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, 'y'), + ]; + + expect(() => isBooleanExpressionTreeRelationFilterSolvable(inputs)).toThrow(MissMatchVariableError); + }); + }); describe('reverseRawLogicOperator', () => { it('given an non existing operator should return undefined', () => { diff --git a/yarn.lock b/yarn.lock index 241cf631a..0410ceeb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1779,7 +1779,7 @@ "@comunica/types" "^2.6.8" asyncjoin "^1.1.1" -"@comunica/actor-rdf-join-inner-multi-bind@2.6.7", "@comunica/actor-rdf-join-inner-multi-bind@^2.5.1", "@comunica/actor-rdf-join-inner-multi-bind@^2.6.8": +"@comunica/actor-rdf-join-inner-multi-bind@^2.5.1": version "2.6.7" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-join-inner-multi-bind/-/actor-rdf-join-inner-multi-bind-2.6.7.tgz#ea599f47e9809d8b4d38b10a64959fcf3f6295e9" integrity sha512-DzqMRO7s7uTwBxXiuQBvHximW9wNofqRrwJhn61LEboFdUMpW8Hfy3PhjT71yfWlB9zrRO3Sn/i1ScxLg5tMUA== @@ -1793,6 +1793,20 @@ asynciterator "^3.8.0" sparqlalgebrajs "^4.0.5" +"@comunica/actor-rdf-join-inner-multi-bind@^2.6.8": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-join-inner-multi-bind/-/actor-rdf-join-inner-multi-bind-2.7.1.tgz#bd24e1d0dc5ca12b41c55d6847e1d4493f7f1c40" + integrity sha512-lix7hYxnNOflWWYV8rhVG30rAl1Cg+vlcJs8LnqB7qxfRwspYquC4wfKOiuARosR8GziGtnfLSAdY8GqqgfRew== + dependencies: + "@comunica/bus-query-operation" "^2.7.1" + "@comunica/bus-rdf-join" "^2.7.1" + "@comunica/bus-rdf-join-entries-sort" "^2.7.0" + "@comunica/context-entries" "^2.7.0" + "@comunica/mediatortype-join-coefficients" "^2.7.0" + "@comunica/types" "^2.7.0" + asynciterator "^3.8.0" + sparqlalgebrajs "^4.0.5" + "@comunica/actor-rdf-join-inner-multi-empty@^2.5.1", "@comunica/actor-rdf-join-inner-multi-empty@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-join-inner-multi-empty/-/actor-rdf-join-inner-multi-empty-2.6.8.tgz#06c3b4ff98e4bc52c3334427b71049b5325294a3" @@ -2156,7 +2170,7 @@ rdf-terms "^1.9.1" sparqlalgebrajs "^4.0.5" -"@comunica/actor-rdf-resolve-quad-pattern-federated@2.6.7", "@comunica/actor-rdf-resolve-quad-pattern-federated@^2.5.1", "@comunica/actor-rdf-resolve-quad-pattern-federated@^2.6.8": +"@comunica/actor-rdf-resolve-quad-pattern-federated@^2.5.1": version "2.6.7" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-resolve-quad-pattern-federated/-/actor-rdf-resolve-quad-pattern-federated-2.6.7.tgz#763a2180b11aefa9055cc712f395301c4391e455" integrity sha512-rldWtvuQpg1yEwaSqKA6SGgt5mNDS90lVL2TMSZVDg4Gk8DnQiqQwiZWGk09KkiJshCZVXnL8TeSlebtlJR2OQ== @@ -2173,6 +2187,25 @@ rdf-terms "^1.9.1" sparqlalgebrajs "^4.0.5" +"@comunica/actor-rdf-resolve-quad-pattern-federated@^2.6.8": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-resolve-quad-pattern-federated/-/actor-rdf-resolve-quad-pattern-federated-2.7.1.tgz#b730da46ca7ca616c4bf367ab5abe6882e4f32e9" + integrity sha512-b6gowLBrDBs2diYR7Vkpuc5NqkNl/mONtZ7rKuQ/BuIizsTTRrdHTh+NsUssYYHje129kdm9Vva+URm+RwdhYw== + dependencies: + "@comunica/bus-query-operation" "^2.7.1" + "@comunica/bus-rdf-metadata-accumulate" "^2.7.0" + "@comunica/bus-rdf-resolve-quad-pattern" "^2.7.0" + "@comunica/context-entries" "^2.7.0" + "@comunica/core" "^2.7.0" + "@comunica/data-factory" "^2.7.0" + "@comunica/metadata" "^2.7.0" + "@comunica/types" "^2.7.0" + "@rdfjs/types" "*" + asynciterator "^3.8.0" + rdf-data-factory "^1.1.1" + rdf-terms "^1.9.1" + sparqlalgebrajs "^4.0.5" + "@comunica/actor-rdf-resolve-quad-pattern-hypermedia@^2.5.1", "@comunica/actor-rdf-resolve-quad-pattern-hypermedia@^2.6.9": version "2.6.9" resolved "https://registry.yarnpkg.com/@comunica/actor-rdf-resolve-quad-pattern-hypermedia/-/actor-rdf-resolve-quad-pattern-hypermedia-2.6.9.tgz#5e11382912a7db948b16709968f9cd628cc972bd" @@ -2339,6 +2372,16 @@ rdf-data-factory "^1.1.1" rdf-string "^1.6.1" +"@comunica/bindings-factory@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@comunica/bindings-factory/-/bindings-factory-2.7.0.tgz#b35810178beed08803ba1891faf50bcd017f52c8" + integrity sha512-NeLbBmqiNhyUCZSfqZfwZD50dQ5+DABgPbzfuZnbHq9uSfhxAzuGCzgrK0hmuwRHWOPBtmeRnyZTJorePuxTzQ== + dependencies: + "@rdfjs/types" "*" + immutable "^4.1.0" + rdf-data-factory "^1.1.1" + rdf-string "^1.6.1" + "@comunica/bus-context-preprocess@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/bus-context-preprocess/-/bus-context-preprocess-2.6.8.tgz#a7fc608f752b474b60b6854ef09e5e52e934e983" @@ -2427,6 +2470,21 @@ rdf-string "^1.6.1" sparqlalgebrajs "^4.0.5" +"@comunica/bus-query-operation@^2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@comunica/bus-query-operation/-/bus-query-operation-2.7.1.tgz#4ec631c1e038348952086c918e95abed58ee8382" + integrity sha512-LuCx2VNEGIfWGiWI8WA+BsIlbDzYfcEJVL3F/5XuUN5vYv50a/9tOWfEIpUAfmbCVtuHPEmBrRpF9DUaxOOtxg== + dependencies: + "@comunica/bindings-factory" "^2.7.0" + "@comunica/context-entries" "^2.7.0" + "@comunica/core" "^2.7.0" + "@comunica/data-factory" "^2.7.0" + "@comunica/types" "^2.7.0" + "@rdfjs/types" "*" + asynciterator "^3.8.0" + rdf-string "^1.6.1" + sparqlalgebrajs "^4.0.5" + "@comunica/bus-query-parse@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/bus-query-parse/-/bus-query-parse-2.6.8.tgz#4e852afa21fba6fcd91867baae52aa3fe9c755b7" @@ -2453,6 +2511,14 @@ "@comunica/core" "^2.6.8" "@comunica/types" "^2.6.8" +"@comunica/bus-rdf-join-entries-sort@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@comunica/bus-rdf-join-entries-sort/-/bus-rdf-join-entries-sort-2.7.0.tgz#c46c478dca01451c241b1af9e7001873411c3b36" + integrity sha512-Hdf1N1K2UxCpJyOxkaG4rC8wtM4EevcXIZA5Eq0DTBOUnLxfF0/4xt4soFjEnagDM38flwurvw6UvfoRnjANVA== + dependencies: + "@comunica/core" "^2.7.0" + "@comunica/types" "^2.7.0" + "@comunica/bus-rdf-join-selectivity@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/bus-rdf-join-selectivity/-/bus-rdf-join-selectivity-2.6.8.tgz#cde8dd341cdc75e0d85506b12d234d813476c3b4" @@ -2462,6 +2528,15 @@ "@comunica/mediatortype-accuracy" "^2.6.8" "@comunica/types" "^2.6.8" +"@comunica/bus-rdf-join-selectivity@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@comunica/bus-rdf-join-selectivity/-/bus-rdf-join-selectivity-2.7.0.tgz#8c779b364a7d42e4ba6fa5b3247f0f9320bb7800" + integrity sha512-M0C+43cdTHuKM6zgTiWhA1Lz7qGjP4bXiuyKYMXwMjfEIc3o0Q4HaMvTHDj5aUlXvMrPsxj1ad4AAljoB0Z5eg== + dependencies: + "@comunica/core" "^2.7.0" + "@comunica/mediatortype-accuracy" "^2.7.0" + "@comunica/types" "^2.7.0" + "@comunica/bus-rdf-join@^2.6.7", "@comunica/bus-rdf-join@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/bus-rdf-join/-/bus-rdf-join-2.6.8.tgz#2f05fdf2211abc8943f787ca647a98b272efcb55" @@ -2477,6 +2552,30 @@ rdf-data-factory "^1.1.1" rdf-string "^1.6.1" +"@comunica/bus-rdf-join@^2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@comunica/bus-rdf-join/-/bus-rdf-join-2.7.1.tgz#8c735afa22baecdc7ac849d431f6c9b4f65526aa" + integrity sha512-y2H7vm1EonBLtbz/Ji74M3FdvIuBHuUG4uiMU5hKmr12zyqlV00BYMfxD4oGGpHM6hKhHnGYraULIraxVPm7+w== + dependencies: + "@comunica/bus-query-operation" "^2.7.1" + "@comunica/bus-rdf-join-selectivity" "^2.7.0" + "@comunica/context-entries" "^2.7.0" + "@comunica/core" "^2.7.0" + "@comunica/mediatortype-join-coefficients" "^2.7.0" + "@comunica/metadata" "^2.7.0" + "@comunica/types" "^2.7.0" + "@rdfjs/types" "*" + rdf-data-factory "^1.1.1" + rdf-string "^1.6.1" + +"@comunica/bus-rdf-metadata-accumulate@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@comunica/bus-rdf-metadata-accumulate/-/bus-rdf-metadata-accumulate-2.7.0.tgz#20c62f879649324040d27cba34e8418ae21c416c" + integrity sha512-3yUVaGbRjTyqZWDhTK5qsjf5NpmeRO2TNRESFQUuPHKwSE6/e8eEyMQYnQseG39RGaz6xJOQ3edIxVPOHagNvQ== + dependencies: + "@comunica/core" "^2.7.0" + "@comunica/types" "^2.7.0" + "@comunica/bus-rdf-metadata-extract@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/bus-rdf-metadata-extract/-/bus-rdf-metadata-extract-2.6.8.tgz#62ef6280301c16f58f4991f488bb6feb931e2a7b" @@ -2549,6 +2648,18 @@ asynciterator "^3.8.0" sparqlalgebrajs "^4.0.5" +"@comunica/bus-rdf-resolve-quad-pattern@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@comunica/bus-rdf-resolve-quad-pattern/-/bus-rdf-resolve-quad-pattern-2.7.0.tgz#20a76e005bda7981403dfc45a22210a0d1fba2a9" + integrity sha512-ypF7kKJrEetXH9qz+Zht49iMw1ShnOVCzvWkFj4ZacrneBHkllCJViaTH08r9HAHUIh2x3ZCXueAIiANsmegxw== + dependencies: + "@comunica/context-entries" "^2.7.0" + "@comunica/core" "^2.7.0" + "@comunica/types" "^2.7.0" + "@rdfjs/types" "*" + asynciterator "^3.8.0" + sparqlalgebrajs "^4.0.5" + "@comunica/bus-rdf-serialize@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/bus-rdf-serialize/-/bus-rdf-serialize-2.6.8.tgz#0bde30e28ba38100bbcdcf676278831497292e1f" @@ -2603,6 +2714,17 @@ jsonld-context-parser "^2.2.2" sparqlalgebrajs "^4.0.5" +"@comunica/context-entries@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@comunica/context-entries/-/context-entries-2.7.0.tgz#ab62718546ab1d3ee09ca74150d597f6a55449df" + integrity sha512-TTRuhJxD+PmbgtdPh3LyS6bAKCxAW7jZnk28mEJqMpE2Ljt5OEuZaZHfVfWCapnWRUDWsh+kxsSGbu9En/FyUQ== + dependencies: + "@comunica/core" "^2.7.0" + "@comunica/types" "^2.7.0" + "@rdfjs/types" "*" + jsonld-context-parser "^2.2.2" + sparqlalgebrajs "^4.0.5" + "@comunica/core@^2.0.1", "@comunica/core@^2.4.0", "@comunica/core@^2.5.1", "@comunica/core@^2.6.7", "@comunica/core@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/core/-/core-2.6.8.tgz#719a9d2f5176bc0715e9503ba06bbe905c5a59e6" @@ -2611,6 +2733,14 @@ "@comunica/types" "^2.6.8" immutable "^4.1.0" +"@comunica/core@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@comunica/core/-/core-2.7.0.tgz#6450203adb551bfe571d7c7ef2db63383941f98f" + integrity sha512-HyOrP4+WgmclUYUb5ik1LHpDjaVP8nf7td5VXrfdg/3/7yj2q21JRwuAnaNmr8gHBmvliUcFAtHvv1y6ChDdvQ== + dependencies: + "@comunica/types" "^2.7.0" + immutable "^4.1.0" + "@comunica/data-factory@^2.5.1": version "2.5.1" resolved "https://registry.yarnpkg.com/@comunica/data-factory/-/data-factory-2.5.1.tgz#c4b44d3124219a305aff85437c121bf456bb93a1" @@ -2618,6 +2748,13 @@ dependencies: "@rdfjs/types" "*" +"@comunica/data-factory@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@comunica/data-factory/-/data-factory-2.7.0.tgz#616f9abe7804f3627aadc0baec2632a9c68ec62b" + integrity sha512-dSTzrR1w9SzAWx70ZXKXHUC8f0leUolLZ9TOhGjFhhsBMJ9Pbo0g6vHV8txX5FViShngrg9QNKhsHeQnMk5z6Q== + dependencies: + "@rdfjs/types" "*" + "@comunica/logger-pretty@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/logger-pretty/-/logger-pretty-2.6.8.tgz#f88fc798563e73c7df4a33ad53f4edd02cec907f" @@ -2687,6 +2824,13 @@ dependencies: "@comunica/core" "^2.6.8" +"@comunica/mediatortype-accuracy@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@comunica/mediatortype-accuracy/-/mediatortype-accuracy-2.7.0.tgz#7a2e26857f48ad83eff931e5edf8564c422770fb" + integrity sha512-M+dcfRH6/9URE9Rgl2/zdKk+AzuG8CXja2kGtKb36WKi9A+u88FoxPCbGw4yO/SPHvuRkPCAYeDsFK5shiR7gg== + dependencies: + "@comunica/core" "^2.7.0" + "@comunica/mediatortype-httprequests@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/mediatortype-httprequests/-/mediatortype-httprequests-2.6.8.tgz#bf37c846da711ce01c89c9474285643756c99e17" @@ -2702,6 +2846,14 @@ "@comunica/core" "^2.6.8" "@rdfjs/types" "*" +"@comunica/mediatortype-join-coefficients@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@comunica/mediatortype-join-coefficients/-/mediatortype-join-coefficients-2.7.0.tgz#c2e676ca88cb672f7982f3c7164045c42f74ea57" + integrity sha512-QEgQDbLnrWftrI9UycI+sYRAwhhsYCqu8Cl00XX4moV5eUk/4zrWLx6kwTpABH9k8bUnZCcbv0VcRl91qT5JPA== + dependencies: + "@comunica/core" "^2.7.0" + "@rdfjs/types" "*" + "@comunica/mediatortype-time@^2.6.8": version "2.6.8" resolved "https://registry.yarnpkg.com/@comunica/mediatortype-time/-/mediatortype-time-2.6.8.tgz#313817bc54fbdf16ded5b208fcb08cf1a36378e5" @@ -2709,6 +2861,13 @@ dependencies: "@comunica/core" "^2.6.8" +"@comunica/metadata@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@comunica/metadata/-/metadata-2.7.0.tgz#0d679f8326aa85341a6b15dea53cd22edcc44704" + integrity sha512-YlUxDJJ2C28BcOwimLzhRZ9v68KY79kcdnZxln3lmJcTLnF/j0q/5Nayb3NkZaQdcVhJw6Ah7Za1jGkmF/Ur3Q== + dependencies: + "@comunica/types" "^2.7.0" + "@comunica/query-sparql-solid@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@comunica/query-sparql-solid/-/query-sparql-solid-2.4.0.tgz#13b5172de77d4253bdce390105fe9d86b33a23e7" @@ -3002,6 +3161,16 @@ asynciterator "^3.8.0" sparqlalgebrajs "^4.0.5" +"@comunica/types@^2.7.0": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@comunica/types/-/types-2.7.0.tgz#4b224aea85e7356f38150312c69a1a24c869164c" + integrity sha512-P/aD3W9wqTtZx9SOWPldihmyrf02PK3lDUUP7QG76+1MlDxBna3UrXaIWS6VdV4dWzEULsnvZUIq+23o5A3WCg== + dependencies: + "@rdfjs/types" "*" + "@types/yargs" "^17.0.13" + asynciterator "^3.8.0" + sparqlalgebrajs "^4.0.5" + "@dabh/diagnostics@^2.0.2": version "2.0.3" resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" @@ -4119,11 +4288,21 @@ "@types/node" "*" rdf-js "^4.0.2" -"@types/node@*", "@types/node@^14.14.7", "@types/node@^18.0.0", "@types/node@^18.0.3": +"@types/node@*", "@types/node@^18.0.0", "@types/node@^18.0.3": version "18.14.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.6.tgz#ae1973dd2b1eeb1825695bb11ebfb746d27e3e93" integrity sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA== +"@types/node@17.0.29": + version "17.0.29" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.29.tgz#7f2e1159231d4a077bb660edab0fde373e375a3d" + integrity sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA== + +"@types/node@^14.14.7": + version "14.18.48" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.48.tgz#ee5c7ac6e38fd2a9e6885f15c003464cf2da343c" + integrity sha512-iL0PIMwejpmuVHgfibHpfDwOdsbmB50wr21X71VnF5d7SsBF7WK+ZvP/SCcFm7Iwb9iiYSap9rlrdhToNAWdxg== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -4199,7 +4378,7 @@ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== -"@types/yargs@17.0.13", "@types/yargs@^17.0.13", "@types/yargs@^17.0.8": +"@types/yargs@^17.0.13", "@types/yargs@^17.0.8": version "17.0.13" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== From 4f7ac2de948c0edd546a56d24b87e1efb407413b Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 1 Jun 2023 15:10:28 +0200 Subject: [PATCH 173/189] metadata extractor moved inside the extrac link class and reachability criteria flag renamed. --- .../extract-links/actors/tree-not-guided.json | 3 +- .../config/extract-links/actors/tree.json | 3 +- .../lib/ActorExtractLinksTree.ts | 107 +++++++- .../lib/filterNode.ts | 25 +- .../lib/treeMetadataExtraction.ts | 86 ------- .../test/ActorExtractLinksTree-test.ts | 229 +++++++++++++++++- .../test/filterNode-test.ts | 154 ++++++++---- .../test/treeMetadataExtraction-test.ts | 213 ---------------- 8 files changed, 441 insertions(+), 379 deletions(-) delete mode 100644 packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts delete mode 100644 packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts diff --git a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree-not-guided.json b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree-not-guided.json index e965fefe1..b7a3f2407 100644 --- a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree-not-guided.json +++ b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree-not-guided.json @@ -10,8 +10,7 @@ { "@id": "urn:comunica:default:extract-links/actors#extract-links-tree", "@type": "ActorExtractLinksTree", - "reachabilityCriterionUseSPARQLFilter": false + "filterPruning": false } ] } - \ No newline at end of file diff --git a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json index f0696d398..7812d9223 100644 --- a/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json +++ b/engines/config-query-sparql-link-traversal/config/extract-links/actors/tree.json @@ -10,8 +10,7 @@ { "@id": "urn:comunica:default:extract-links/actors#extract-links-tree", "@type": "ActorExtractLinksTree", - "reachabilityCriterionUseSPARQLFilter": true + "filterPruning": true } ] } - \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 6cecdfdab..df39ef02e 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -11,8 +11,8 @@ import type * as RDF from 'rdf-js'; import { termToString } from 'rdf-string'; import { filterNode } from './filterNode'; import { isBooleanExpressionTreeRelationFilterSolvable } from './solver'; -import type { ITreeRelationRaw, ITreeRelation, ITreeNode } from './TreeMetadata'; -import { buildRelationElement, materializeTreeRelation, addRelationDescription } from './treeMetadataExtraction'; +import type { ITreeRelationRaw, ITreeRelation, SparqlRelationOperator, ITreeNode } from './TreeMetadata'; +import { TreeNodes, RelationOperatorReversed } from './TreeMetadata'; const DF = new DataFactory(); @@ -20,19 +20,18 @@ const DF = new DataFactory(); * A comunica Extract Links Tree Extract Links Actor. */ export class ActorExtractLinksTree extends ActorExtractLinks { - private readonly reachabilityCriterionUseSPARQLFilter: boolean = true; + private readonly filterPruning: boolean = true; public static readonly aNodeType = DF.namedNode('https://w3id.org/tree#node'); public static readonly aRelation = DF.namedNode('https://w3id.org/tree#relation'); - private static readonly rdfTypeNode = DF.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'); public static readonly aView = DF.namedNode('https://w3id.org/tree#view'); public static readonly aSubset = DF.namedNode('http://rdfs.org/ns/void#subset'); public static readonly isPartOf = DF.namedNode('http://purl.org/dc/terms/isPartOf'); public constructor(args: IActorExtractLinksTreeArgs) { super(args); - this.reachabilityCriterionUseSPARQLFilter = args.reachabilityCriterionUseSPARQLFilter === undefined ? + this.filterPruning = args.filterPruning === undefined ? true : - args.reachabilityCriterionUseSPARQLFilter; + args.filterPruning; } public async test(action: IActionExtractLinks): Promise { @@ -40,7 +39,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } public isUsingReachabilitySPARQLFilter(): boolean { - return this.reachabilityCriterionUseSPARQLFilter; + return this.filterPruning; } public async run(action: IActionExtractLinks): Promise { @@ -89,14 +88,16 @@ export class ActorExtractLinksTree extends ActorExtractLinks { ) { const relationDescription = relationDescriptions.get(relationId); // Add the relation to the relation array - relations.push(materializeTreeRelation(relationDescription || {}, link)); + relations.push( + ActorExtractLinksTree.materializeTreeRelation(relationDescription || {}, link), + ); } } // Create a ITreeNode object const node: ITreeNode = { relation: relations, identifier: currentNodeUrl }; let acceptedRelation = relations; - if (this.reachabilityCriterionUseSPARQLFilter) { + if (this.filterPruning) { // Filter the relation based on the query const filters = await this.applyFilter(node, action.context); acceptedRelation = this.handleFilter(filters, acceptedRelation); @@ -169,12 +170,94 @@ export class ActorExtractLinksTree extends ActorExtractLinks { nodeLinks.push([ termToString(quad.subject), quad.object.value ]); } - const descriptionElement = buildRelationElement(quad); + const descriptionElement = ActorExtractLinksTree.buildRelationElement(quad); if (descriptionElement) { const [ value, key ] = descriptionElement; - addRelationDescription(relationDescriptions, quad, value, key); + ActorExtractLinksTree.addRelationDescription(relationDescriptions, quad, value, key); } } + + /** + * Materialize a raw tree relation using the captured values. + * @param relationRaw Raw representation of a tree relation. + * @param nextLink Link to the next page. + * @returns ITreeRelation + */ + public static materializeTreeRelation( + relationRaw: ITreeRelationRaw, + nextLink: string, + ): ITreeRelation { + const relation: ITreeRelation = { node: nextLink }; + if (relationRaw?.operator) { + relation.type = relationRaw.operator[0]; + } + + if (relationRaw?.remainingItems) { + relation.remainingItems = relationRaw.remainingItems[0]; + } + + if (relationRaw?.subject) { + relation.path = relationRaw.subject[0]; + } + + if (relationRaw?.value) { + relation.value = { + value: relationRaw.value[0], + term: relationRaw.value[1].object, + }; + } + + return relation; + } + + /** + * From a quad stream return a relation element if it exist + * @param {RDF.Quad} quad - Current quad of the stream. + * @returns {[SparqlRelationOperator | number | string, keyof ITreeRelationRaw] | undefined} The relation element + * and the key associated with it. + */ + public static buildRelationElement( + quad: RDF.Quad, + ): [SparqlRelationOperator | number | string, keyof ITreeRelationRaw] | undefined { + if (quad.predicate.value === TreeNodes.RDFTypeNode) { + // Set the operator of the relation + const operator: SparqlRelationOperator | undefined = RelationOperatorReversed.get(quad.object.value); + if (typeof operator !== 'undefined') { + return [ operator, 'operator' ]; + } + } else if (quad.predicate.value === TreeNodes.Path) { + // Set the subject of the relation condition + return [ quad.object.value, 'subject' ]; + } else if (quad.predicate.value === TreeNodes.Value) { + // Set the value of the relation condition + return [ quad.object.value, 'value' ]; + } else if (quad.predicate.value === TreeNodes.RemainingItems) { + const remainingItems = Number.parseInt(quad.object.value, 10); + if (!Number.isNaN(remainingItems)) { + return [ remainingItems, 'remainingItems' ]; + } + } + return undefined; + } + + /** + * Update the relationDescriptions with the new quad value + * @param {Map} relationDescriptions - Maps relationship identifiers to their description. + * @param {RDF.Quad} quad - Current quad of the steam. + * @param {SparqlRelationOperator | number | string} value - Current description value fetch + * @param {keyof ITreeRelationRaw} key - Key associated with the value. + */ + public static addRelationDescription( + relationDescriptions: Map, + quad: RDF.Quad, + value: SparqlRelationOperator | number | string, + key: keyof ITreeRelationRaw, + ): void { + const rawRelation: ITreeRelationRaw = relationDescriptions?.get(termToString(quad.subject)) || {}; + rawRelation[key] = [ value, quad ]; + + relationDescriptions.set(termToString(quad.subject), rawRelation); + } } export interface IActorExtractLinksTreeArgs @@ -184,5 +267,5 @@ export interface IActorExtractLinksTreeArgs * SPARQL filter * @default {true} */ - reachabilityCriterionUseSPARQLFilter: boolean; + filterPruning: boolean; } diff --git a/packages/actor-extract-links-extract-tree/lib/filterNode.ts b/packages/actor-extract-links-extract-tree/lib/filterNode.ts index 2a4f28f0e..1463c893a 100644 --- a/packages/actor-extract-links-extract-tree/lib/filterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/filterNode.ts @@ -16,16 +16,7 @@ const BF = new BindingsFactory(); * @param {IActionContext} context - The context * @returns {Algebra.Expression | undefined} The filter expression or undefined if the TREE node has no relations */ -export function getFilterExpressionIfTreeNodeHasConstraint(node: ITreeNode, - context: IActionContext): Algebra.Expression | undefined { - if (!node.relation) { - return undefined; - } - - if (node.relation.length === 0) { - return undefined; - } - +export function getFilterExpression(context: IActionContext): Algebra.Expression | undefined { const query: Algebra.Operation = context.get(KeysInitQuery.query)!; const filterExpression = findNode(query, Algebra.types.FILTER); if (!filterExpression) { @@ -48,20 +39,28 @@ export async function filterNode( context: IActionContext, satisfactionChecker: SatisfactionChecker, ): Promise> { - const filterMap: Map = new Map(); + if (!node.relation) { + return new Map(); + } + + if (node.relation.length === 0) { + return new Map(); + } const filterOperation: Algebra.Expression | undefined = - getFilterExpressionIfTreeNodeHasConstraint(node, context); + getFilterExpression(context); if (!filterOperation) { return new Map(); } + const filterMap: Map = new Map(); + // Extract the bgp of the query. const queryBody: Algebra.Operation = context.get(KeysInitQuery.query)!; // Capture the relation from the function argument. - const groupedRelations = groupRelations(node.relation!); + const groupedRelations = groupRelations(node.relation); const calculatedFilterExpressions: Map = new Map(); for (const relations of groupedRelations) { diff --git a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts b/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts deleted file mode 100644 index 52b1cafe4..000000000 --- a/packages/actor-extract-links-extract-tree/lib/treeMetadataExtraction.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type * as RDF from 'rdf-js'; -import { termToString } from 'rdf-string'; -import { TreeNodes, RelationOperatorReversed } from './TreeMetadata'; -import type { ITreeRelation, ITreeRelationRaw, SparqlRelationOperator } from './TreeMetadata'; - -/** - * Materialize a raw tree relation using the captured values. - * @param relationRaw Raw representation of a tree relation. - * @param nextLink Link to the next page. - * @returns ITreeRelation - */ -export function materializeTreeRelation( - relationRaw: ITreeRelationRaw, - nextLink: string, -): ITreeRelation { - const relation: ITreeRelation = { node: nextLink }; - if (relationRaw?.operator) { - relation.type = relationRaw.operator[0]; - } - - if (relationRaw?.remainingItems) { - relation.remainingItems = relationRaw.remainingItems[0]; - } - - if (relationRaw?.subject) { - relation.path = relationRaw.subject[0]; - } - - if (relationRaw?.value) { - relation.value = { - value: relationRaw.value[0], - term: relationRaw.value[1].object, - }; - } - - return relation; -} - -/** - * From a quad stream return a relation element if it exist - * @param {RDF.Quad} quad - Current quad of the stream. - * @returns {[SparqlRelationOperator | number | string, keyof ITreeRelationRaw] | undefined} The relation element - * and the key associated with it. - */ -export function buildRelationElement( - quad: RDF.Quad, -): [SparqlRelationOperator | number | string, keyof ITreeRelationRaw] | undefined { - if (quad.predicate.value === TreeNodes.RDFTypeNode) { - // Set the operator of the relation - const operator: SparqlRelationOperator | undefined = RelationOperatorReversed.get(quad.object.value); - if (typeof operator !== 'undefined') { - return [ operator, 'operator' ]; - } - } else if (quad.predicate.value === TreeNodes.Path) { - // Set the subject of the relation condition - return [ quad.object.value, 'subject' ]; - } else if (quad.predicate.value === TreeNodes.Value) { - // Set the value of the relation condition - return [ quad.object.value, 'value' ]; - } else if (quad.predicate.value === TreeNodes.RemainingItems) { - const remainingItems = Number.parseInt(quad.object.value, 10); - if (!Number.isNaN(remainingItems)) { - return [ remainingItems, 'remainingItems' ]; - } - } - return undefined; -} -/** - * Update the relationDescriptions with the new quad value - * @param {Map} relationDescriptions - Maps relationship identifiers to their description. - * @param {RDF.Quad} quad - Current quad of the steam. - * @param {SparqlRelationOperator | number | string} value - Current description value fetch - * @param {keyof ITreeRelationRaw} key - Key associated with the value. - */ -export function addRelationDescription( - relationDescriptions: Map, - quad: RDF.Quad, - value: SparqlRelationOperator | number | string, - key: keyof ITreeRelationRaw, -): void { - const rawRelation: ITreeRelationRaw = relationDescriptions?.get(termToString(quad.subject)) || {}; - rawRelation[key] = [ value, quad ]; - - relationDescriptions.set(termToString(quad.subject), rawRelation); -} - diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 94f2784e9..b2a05ca8b 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -6,7 +6,7 @@ import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; import { ActorExtractLinksTree } from '../lib/ActorExtractLinksTree'; import { SparqlRelationOperator, TreeNodes } from '../lib/TreeMetadata'; -import type { ITreeRelation } from '../lib/TreeMetadata'; +import type { ITreeRelation, ITreeRelationRaw } from '../lib/TreeMetadata'; const stream = require('streamify-array'); @@ -36,12 +36,12 @@ describe('ActorExtractLinksExtractLinksTree', () => { }); it('should apply the activate the reachability criterion based on the constructor parameter', () => { - let reachabilityCriterionUseSPARQLFilter = true; - let actor = new ActorExtractLinksTree({ name: 'actor', bus, reachabilityCriterionUseSPARQLFilter }); + let filterPruning = true; + let actor = new ActorExtractLinksTree({ name: 'actor', bus, filterPruning }); expect(actor.isUsingReachabilitySPARQLFilter()).toBe(true); - reachabilityCriterionUseSPARQLFilter = false; - actor = new ActorExtractLinksTree({ name: 'actor', bus, reachabilityCriterionUseSPARQLFilter }); + filterPruning = false; + actor = new ActorExtractLinksTree({ name: 'actor', bus, filterPruning }); expect(actor.isUsingReachabilitySPARQLFilter()).toBe(false); }); @@ -782,4 +782,223 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toBe(true); }); }); + + describe('addRelationDescription', () => { + it('should add relation to the map when an operator is provided and the relation map is empty', + () => { + const quad: RDF.Quad = DF.quad( + DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RDFTypeNode), + DF.namedNode(SparqlRelationOperator.EqualThanRelation), + ); + const relationDescriptions: Map = new Map(); + ActorExtractLinksTree.addRelationDescription( + relationDescriptions, + quad, + SparqlRelationOperator.EqualThanRelation, + 'operator', + ); + + expect(relationDescriptions.size).toBe(1); + }); + + it(`should add relation to the map when an operator is provided and + the relation map at the current key is not empty`, + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RDFTypeNode), + DF.namedNode(SparqlRelationOperator.EqualThanRelation)); + const relationDescriptions: Map = new Map([[ 'ex:s', { value: 22 }]]); + ActorExtractLinksTree.addRelationDescription( + relationDescriptions, + quad, + SparqlRelationOperator.EqualThanRelation, + 'operator', + ); + expect(relationDescriptions.size).toBe(1); + }); + + it('should add relation to the map when an operator is provided and the relation map is not empty', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RDFTypeNode), + DF.namedNode(SparqlRelationOperator.EqualThanRelation)); + const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); + ActorExtractLinksTree.addRelationDescription( + relationDescriptions, + quad, + SparqlRelationOperator.EqualThanRelation, + 'operator', + ); + expect(relationDescriptions.size).toBe(2); + }); + + it('should add relation to the map when a value is provided and the relation map is empty', () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); + const relationDescriptions: Map = new Map(); + ActorExtractLinksTree.addRelationDescription(relationDescriptions, quad, '5', 'value'); + expect(relationDescriptions.size).toBe(1); + }); + + it('should add relation to the map when a value is provided and the relation map at the current key is not empty', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); + const relationDescriptions: Map = + new Map([[ 'ex:s', { subject: [ 'ex:s', quad ]}]]); + ActorExtractLinksTree.addRelationDescription(relationDescriptions, quad, '5', 'value'); + expect(relationDescriptions.size).toBe(1); + }); + + it('should add relation to the map when a value is provided and the relation map is not empty', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); + const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); + ActorExtractLinksTree.addRelationDescription(relationDescriptions, quad, '5', 'value'); + expect(relationDescriptions.size).toBe(2); + }); + + it('should add relation to the map when a subject is provided and the relation map is empty', () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Path), DF.namedNode('ex:path')); + const relationDescriptions: Map = new Map(); + ActorExtractLinksTree.addRelationDescription(relationDescriptions, quad, 'ex:path', 'subject'); + expect(relationDescriptions.size).toBe(1); + }); + + it('should add relation to the map when a subject is provided and the relation map at the current key is not empty', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Path), DF.namedNode('ex:path')); + const relationDescriptions: Map = + new Map([[ 'ex:s', { subject: [ 'ex:s', quad ]}]]); + ActorExtractLinksTree.addRelationDescription(relationDescriptions, quad, 'ex:path', 'subject'); + expect(relationDescriptions.size).toBe(1); + }); + + it('should add relation to the map when a subject is provided and the relation map is not empty', + () => { + const quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); + const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); + ActorExtractLinksTree.addRelationDescription(relationDescriptions, quad, 'ex:path', 'subject'); + expect(relationDescriptions.size).toBe(2); + }); + }); + + describe('buildRelations', () => { + it('should return undefined when the quad don\'t respect any relation', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode('ex:p'), DF.namedNode('ex:o')); + expect(ActorExtractLinksTree.buildRelationElement(quad)).toBeUndefined(); + }); + + it('should return the relation element when the quad respect a relation semantic', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RDFTypeNode), + DF.namedNode(SparqlRelationOperator.EqualThanRelation)); + + const res = ActorExtractLinksTree.buildRelationElement(quad); + expect(res).toBeDefined(); + const [ value, key ] = res; + expect(key).toBe( 'operator'); + expect(value).toBe(SparqlRelationOperator.EqualThanRelation); + }); + + it('should return undefined when the type does not exist', + () => { + const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), + DF.namedNode(TreeNodes.RDFTypeNode), + DF.namedNode('foo')); + + const res = ActorExtractLinksTree.buildRelationElement(quad); + expect(res).toBeUndefined(); + }); + }); + + describe('materializeTreeRelation', () => { + it('should materialize a tree Relation when all the raw relation are provided', () => { + const aSubject = 'foo'; + const aValue = '0'; + const anOperator = SparqlRelationOperator.PrefixRelation; + const aRemainingItemDefinition = 44; + const aQuad = DF.quad( + DF.blankNode(''), + DF.namedNode(''), + DF.blankNode(''), + ); + const aNode = 'test'; + const relationRaw: ITreeRelationRaw = { + subject: [ aSubject, aQuad ], + value: [ aValue, aQuad ], + operator: [ anOperator, aQuad ], + remainingItems: [ aRemainingItemDefinition, aQuad ], + }; + const expectedTreeRelation: ITreeRelation = { + type: anOperator, + remainingItems: aRemainingItemDefinition, + path: aSubject, + value: { + value: aValue, + term: aQuad.object, + }, + node: aNode, + }; + + const res = ActorExtractLinksTree.materializeTreeRelation(relationRaw, aNode); + + expect(res).toStrictEqual(expectedTreeRelation); + }); + + it('should materialize a tree Relation when the remaining item is missing', () => { + const aSubject = 'foo'; + const aValue = '0'; + const anOperator = SparqlRelationOperator.PrefixRelation; + const aQuad = DF.quad( + DF.blankNode(''), + DF.namedNode(''), + DF.blankNode(''), + ); + const aNode = 'test'; + const relationRaw: ITreeRelationRaw = { + subject: [ aSubject, aQuad ], + value: [ aValue, aQuad ], + operator: [ anOperator, aQuad ], + }; + const expectedTreeRelation: ITreeRelation = { + type: anOperator, + path: aSubject, + value: { + value: aValue, + term: aQuad.object, + }, + node: aNode, + }; + + const res = ActorExtractLinksTree.materializeTreeRelation(relationRaw, aNode); + + expect(res).toStrictEqual(expectedTreeRelation); + }); + + it('should materialize a tree Relation when the value is missing', () => { + const aSubject = 'foo'; + const anOperator = SparqlRelationOperator.PrefixRelation; + const aQuad = DF.quad( + DF.blankNode(''), + DF.namedNode(''), + DF.blankNode(''), + ); + const aNode = 'test'; + const relationRaw: ITreeRelationRaw = { + subject: [ aSubject, aQuad ], + operator: [ anOperator, aQuad ], + }; + const expectedTreeRelation: ITreeRelation = { + type: anOperator, + path: aSubject, + node: aNode, + }; + + const res = ActorExtractLinksTree.materializeTreeRelation(relationRaw, aNode); + + expect(res).toStrictEqual(expectedTreeRelation); + }); + }); }); diff --git a/packages/actor-extract-links-extract-tree/test/filterNode-test.ts b/packages/actor-extract-links-extract-tree/test/filterNode-test.ts index dfc3a435d..6e9c701f4 100644 --- a/packages/actor-extract-links-extract-tree/test/filterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/filterNode-test.ts @@ -4,7 +4,7 @@ import { ActionContext } from '@comunica/core'; import { DataFactory } from 'rdf-data-factory'; import type * as RDF from 'rdf-js'; import { Algebra, translate } from 'sparqlalgebrajs'; -import { filterNode, groupRelations, getFilterExpressionIfTreeNodeHasConstraint } from '../lib/filterNode'; +import { filterNode, groupRelations, getFilterExpression } from '../lib/filterNode'; import { isBooleanExpressionTreeRelationFilterSolvable } from '../lib/solver'; import { SparqlRelationOperator } from '../lib/TreeMetadata'; @@ -13,65 +13,26 @@ import type { ITreeNode, ITreeRelation } from '../lib/TreeMetadata'; const DF = new DataFactory(); describe('filterNode Module', () => { - describe('getFilterExpressionIfTreeNodeHasConstraint ', () => { + describe('getFilterExpression ', () => { const treeSubject = 'tree'; - it('should test when there are relations and a filter operation in the query', () => { + it('should return the filter operation if the query has a filter expression', () => { const context = new ActionContext({ [KeysInitQuery.query.name]: translate(` SELECT * WHERE { ?x ?y ?z FILTER(?x = 5 || true) }`), }); - const node: ITreeNode = { - identifier: treeSubject, - relation: [ - { - node: 'http://bar.com', - }, - ], - }; - const response = getFilterExpressionIfTreeNodeHasConstraint(node, context); + const response = getFilterExpression(context); expect(response).toBeDefined(); }); - it('should no test when the TREE relation are undefined', async() => { - const context = new ActionContext({ - [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, - }); - const node: ITreeNode = { - identifier: treeSubject, - }; - - const response = getFilterExpressionIfTreeNodeHasConstraint(node, context); - expect(response).toBeUndefined(); - }); - - it('should not test when there is a filter operation in the query but no TREE relations', async() => { - const context = new ActionContext({ - [KeysInitQuery.query.name]: { type: Algebra.types.FILTER }, - }); - const node: ITreeNode = { - identifier: treeSubject, - relation: [], - }; - const response = getFilterExpressionIfTreeNodeHasConstraint(node, context); - expect(response).toBeUndefined(); - }); - - it('should no test when there are no filter operation in the query but a TREE relation', async() => { + it('should return undefined if the query does not have a filter expression', async() => { const context = new ActionContext({ [KeysInitQuery.query.name]: { type: Algebra.types.ASK }, }); - const node: ITreeNode = { - identifier: treeSubject, - relation: [ - { - node: 'http://bar.com', - }, - ], - }; - const response = getFilterExpressionIfTreeNodeHasConstraint(node, context); + + const response = getFilterExpression(context); expect(response).toBeUndefined(); }); }); @@ -119,6 +80,107 @@ describe('filterNode Module', () => { ); }); + it('should return an empty filter if the query has not filter', async() => { + const treeSubject = 'tree'; + + const node: ITreeNode = { + identifier: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: 'http://example.com#path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + type: SparqlRelationOperator.EqualThanRelation, + }, + ], + }; + + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + ex:foo ex:p ex:o. + } + `, { prefixes: { ex: 'http://example.com#' }}); + + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable, + ); + + expect(result).toStrictEqual( + new Map(), + ); + }); + + it('should return an empty filter if the TREE Node has no relation', async() => { + const treeSubject = 'tree'; + + const node: ITreeNode = { + identifier: treeSubject, + relation: [], + }; + + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + ex:foo ex:p ex:o. + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' }}); + + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable, + ); + + expect(result).toStrictEqual( + new Map(), + ); + }); + + it('should return an empty filter if the TREE Node has no relation field', async() => { + const treeSubject = 'tree'; + + const node: ITreeNode = { + identifier: treeSubject, + }; + + const query = translate(` + SELECT ?o WHERE { + ex:foo ex:path ?o. + ex:foo ex:p ex:o. + FILTER(?o=5) + } + `, { prefixes: { ex: 'http://example.com#' }}); + + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); + + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable, + ); + + expect(result).toStrictEqual( + new Map(), + ); + }); + it('should not accept the relation when the filter is not respected by the relation', async() => { const treeSubject = 'tree'; diff --git a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts b/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts deleted file mode 100644 index 969820169..000000000 --- a/packages/actor-extract-links-extract-tree/test/treeMetadataExtraction-test.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { DataFactory } from 'rdf-data-factory'; -import type * as RDF from 'rdf-js'; -import type { ITreeRelationRaw, ITreeRelation } from '../lib/TreeMetadata'; -import { TreeNodes, SparqlRelationOperator } from '../lib/TreeMetadata'; -import { buildRelationElement, addRelationDescription, materializeTreeRelation } from '../lib/treeMetadataExtraction'; - -const DF = new DataFactory(); - -describe('treeMetadataExtraction', () => { - describe('addRelationDescription', () => { - it('should add relation to the map when an operator is provided and the relation map is empty', - () => { - const quad: RDF.Quad = DF.quad( - DF.namedNode('ex:s'), - DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode(SparqlRelationOperator.EqualThanRelation), - ); - const relationDescriptions: Map = new Map(); - addRelationDescription(relationDescriptions, quad, SparqlRelationOperator.EqualThanRelation, 'operator'); - - expect(relationDescriptions.size).toBe(1); - }); - - it(`should add relation to the map when an operator is provided and - the relation map at the current key is not empty`, - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), - DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode(SparqlRelationOperator.EqualThanRelation)); - const relationDescriptions: Map = new Map([[ 'ex:s', { value: 22 }]]); - addRelationDescription(relationDescriptions, quad, SparqlRelationOperator.EqualThanRelation, 'operator'); - expect(relationDescriptions.size).toBe(1); - }); - - it('should add relation to the map when an operator is provided and the relation map is not empty', - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), - DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode(SparqlRelationOperator.EqualThanRelation)); - const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); - addRelationDescription(relationDescriptions, quad, SparqlRelationOperator.EqualThanRelation, 'operator'); - expect(relationDescriptions.size).toBe(2); - }); - - it('should add relation to the map when a value is provided and the relation map is empty', () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); - const relationDescriptions: Map = new Map(); - addRelationDescription(relationDescriptions, quad, '5', 'value'); - expect(relationDescriptions.size).toBe(1); - }); - - it('should add relation to the map when a value is provided and the relation map at the current key is not empty', - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); - const relationDescriptions: Map = - new Map([[ 'ex:s', { subject: [ 'ex:s', quad ]}]]); - addRelationDescription(relationDescriptions, quad, '5', 'value'); - expect(relationDescriptions.size).toBe(1); - }); - - it('should add relation to the map when a value is provided and the relation map is not empty', - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); - const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); - addRelationDescription(relationDescriptions, quad, '5', 'value'); - expect(relationDescriptions.size).toBe(2); - }); - - it('should add relation to the map when a subject is provided and the relation map is empty', () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Path), DF.namedNode('ex:path')); - const relationDescriptions: Map = new Map(); - addRelationDescription(relationDescriptions, quad, 'ex:path', 'subject'); - expect(relationDescriptions.size).toBe(1); - }); - - it('should add relation to the map when a subject is provided and the relation map at the current key is not empty', - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Path), DF.namedNode('ex:path')); - const relationDescriptions: Map = - new Map([[ 'ex:s', { subject: [ 'ex:s', quad ]}]]); - addRelationDescription(relationDescriptions, quad, 'ex:path', 'subject'); - expect(relationDescriptions.size).toBe(1); - }); - - it('should add relation to the map when a subject is provided and the relation map is not empty', - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode(TreeNodes.Value), DF.namedNode('5')); - const relationDescriptions: Map = new Map([[ 'ex:s2', { value: 22 }]]); - addRelationDescription(relationDescriptions, quad, 'ex:path', 'subject'); - expect(relationDescriptions.size).toBe(2); - }); - }); - - describe('buildRelations', () => { - it('should return undefined when the quad don\'t respect any relation', - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), DF.namedNode('ex:p'), DF.namedNode('ex:o')); - expect(buildRelationElement(quad)).toBeUndefined(); - }); - - it('should return the relation element when the quad respect a relation semantic', - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), - DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode(SparqlRelationOperator.EqualThanRelation)); - - const res = buildRelationElement(quad); - expect(res).toBeDefined(); - const [ value, key ] = res; - expect(key).toBe( 'operator'); - expect(value).toBe(SparqlRelationOperator.EqualThanRelation); - }); - - it('should return undefined when the type does not exist', - () => { - const quad: RDF.Quad = DF.quad(DF.namedNode('ex:s'), - DF.namedNode(TreeNodes.RDFTypeNode), - DF.namedNode('foo')); - - const res = buildRelationElement(quad); - expect(res).toBeUndefined(); - }); - }); - - describe('materializeTreeRelation', () => { - it('should materialize a tree Relation when all the raw relation are provided', () => { - const aSubject = 'foo'; - const aValue = '0'; - const anOperator = SparqlRelationOperator.PrefixRelation; - const aRemainingItemDefinition = 44; - const aQuad = DF.quad( - DF.blankNode(''), - DF.namedNode(''), - DF.blankNode(''), - ); - const aNode = 'test'; - const relationRaw: ITreeRelationRaw = { - subject: [ aSubject, aQuad ], - value: [ aValue, aQuad ], - operator: [ anOperator, aQuad ], - remainingItems: [ aRemainingItemDefinition, aQuad ], - }; - const expectedTreeRelation: ITreeRelation = { - type: anOperator, - remainingItems: aRemainingItemDefinition, - path: aSubject, - value: { - value: aValue, - term: aQuad.object, - }, - node: aNode, - }; - - const res = materializeTreeRelation(relationRaw, aNode); - - expect(res).toStrictEqual(expectedTreeRelation); - }); - - it('should materialize a tree Relation when the remaining item is missing', () => { - const aSubject = 'foo'; - const aValue = '0'; - const anOperator = SparqlRelationOperator.PrefixRelation; - const aQuad = DF.quad( - DF.blankNode(''), - DF.namedNode(''), - DF.blankNode(''), - ); - const aNode = 'test'; - const relationRaw: ITreeRelationRaw = { - subject: [ aSubject, aQuad ], - value: [ aValue, aQuad ], - operator: [ anOperator, aQuad ], - }; - const expectedTreeRelation: ITreeRelation = { - type: anOperator, - path: aSubject, - value: { - value: aValue, - term: aQuad.object, - }, - node: aNode, - }; - - const res = materializeTreeRelation(relationRaw, aNode); - - expect(res).toStrictEqual(expectedTreeRelation); - }); - - it('should materialize a tree Relation when the value is missing', () => { - const aSubject = 'foo'; - const anOperator = SparqlRelationOperator.PrefixRelation; - const aQuad = DF.quad( - DF.blankNode(''), - DF.namedNode(''), - DF.blankNode(''), - ); - const aNode = 'test'; - const relationRaw: ITreeRelationRaw = { - subject: [ aSubject, aQuad ], - operator: [ anOperator, aQuad ], - }; - const expectedTreeRelation: ITreeRelation = { - type: anOperator, - path: aSubject, - node: aNode, - }; - - const res = materializeTreeRelation(relationRaw, aNode); - - expect(res).toStrictEqual(expectedTreeRelation); - }); - }); -}); From 7660b83662d85c7e4e75fb2308e341ebf82effc7 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 1 Jun 2023 15:21:08 +0200 Subject: [PATCH 174/189] An interface for the buildRelationElement method has been created. --- .../lib/ActorExtractLinksTree.ts | 25 +++++++++++++------ .../test/ActorExtractLinksTree-test.ts | 2 +- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index df39ef02e..253e4692d 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -172,7 +172,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { const descriptionElement = ActorExtractLinksTree.buildRelationElement(quad); if (descriptionElement) { - const [ value, key ] = descriptionElement; + const {value, key } = descriptionElement; ActorExtractLinksTree.addRelationDescription(relationDescriptions, quad, value, key); } } @@ -211,30 +211,32 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } /** - * From a quad stream return a relation element if it exist + * From a quad stream return a relation element if it exist. + * For example if the quad is the operator the function will return + * the value of the operator and * @param {RDF.Quad} quad - Current quad of the stream. - * @returns {[SparqlRelationOperator | number | string, keyof ITreeRelationRaw] | undefined} The relation element + * @returns {ITreeRelationElement | undefined} The relation element * and the key associated with it. */ public static buildRelationElement( quad: RDF.Quad, - ): [SparqlRelationOperator | number | string, keyof ITreeRelationRaw] | undefined { + ): ITreeRelationElement | undefined { if (quad.predicate.value === TreeNodes.RDFTypeNode) { // Set the operator of the relation const operator: SparqlRelationOperator | undefined = RelationOperatorReversed.get(quad.object.value); if (typeof operator !== 'undefined') { - return [ operator, 'operator' ]; + return {value:operator, key:'operator'}; } } else if (quad.predicate.value === TreeNodes.Path) { // Set the subject of the relation condition - return [ quad.object.value, 'subject' ]; + return {value:quad.object.value,key: 'subject'}; } else if (quad.predicate.value === TreeNodes.Value) { // Set the value of the relation condition - return [ quad.object.value, 'value' ]; + return {value:quad.object.value, key:'value' }; } else if (quad.predicate.value === TreeNodes.RemainingItems) { const remainingItems = Number.parseInt(quad.object.value, 10); if (!Number.isNaN(remainingItems)) { - return [ remainingItems, 'remainingItems' ]; + return { value: remainingItems, key:'remainingItems' }; } } return undefined; @@ -269,3 +271,10 @@ export interface IActorExtractLinksTreeArgs */ filterPruning: boolean; } +/** + * An element of a TREE relation + */ +interface ITreeRelationElement{ + key:keyof ITreeRelationRaw, + value: SparqlRelationOperator | number | string +} \ No newline at end of file diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index b2a05ca8b..fd33ff467 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -897,7 +897,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const res = ActorExtractLinksTree.buildRelationElement(quad); expect(res).toBeDefined(); - const [ value, key ] = res; + const {value, key } = res; expect(key).toBe( 'operator'); expect(value).toBe(SparqlRelationOperator.EqualThanRelation); }); From 36d957bfa3c5f72e753d143aea98c08ac9ade443 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 1 Jun 2023 15:24:04 +0200 Subject: [PATCH 175/189] Apply suggestions from code review typo corrected Co-authored-by: Ruben Taelman --- packages/actor-extract-links-extract-tree/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/README.md b/packages/actor-extract-links-extract-tree/README.md index 8327e16ed..d06052d08 100644 --- a/packages/actor-extract-links-extract-tree/README.md +++ b/packages/actor-extract-links-extract-tree/README.md @@ -2,7 +2,7 @@ [![npm version](https://badge.fury.io/js/%40comunica%2Factor-extract-links-tree.svg)](https://www.npmjs.com/package/@comunica/actor-extract-links-tree) -A comunica [Extract Links Actor](https://github.com/comunica/comunica-feature-link-traversal/tree/master/packages/bus-extract-links) for the [TREE](https://treecg.github.io/specification/). +A comunica [Extract Links Actor](https://github.com/comunica/comunica-feature-link-traversal/tree/master/packages/bus-extract-links) for the [TREE specification](https://treecg.github.io/specification/). The [Guided Linked Traversal Query Processing](https://arxiv.org/abs/2005.02239) option that can be enabled using the `reachabilityCriterionUseSPARQLFilte` flag. The traversal algorithm will consider the solvability of the query filter expression From 7af9161da7862526a3fa976f6662d2e46a11d465 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 1 Jun 2023 15:32:11 +0200 Subject: [PATCH 176/189] readme updated --- .../actor-extract-links-extract-tree/README.md | 11 ++++++----- .../lib/ActorExtractLinksTree.ts | 18 +++++++++--------- .../test/ActorExtractLinksTree-test.ts | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/README.md b/packages/actor-extract-links-extract-tree/README.md index 8327e16ed..7b58fc9e7 100644 --- a/packages/actor-extract-links-extract-tree/README.md +++ b/packages/actor-extract-links-extract-tree/README.md @@ -5,10 +5,11 @@ A comunica [Extract Links Actor](https://github.com/comunica/comunica-feature-link-traversal/tree/master/packages/bus-extract-links) for the [TREE](https://treecg.github.io/specification/). The [Guided Linked Traversal Query Processing](https://arxiv.org/abs/2005.02239) -option that can be enabled using the `reachabilityCriterionUseSPARQLFilte` flag. The traversal algorithm will consider the solvability of the query filter expression -combined with the [`tree:relation`](https://treecg.github.io/specification/#Relation) of each data source encountered. -A more thorough explanation is available in the poster article -["How TREE hypermedia can speed up Link Traversal-based Query Processing for SPARQL queries with filters"](https://constraintautomaton.github.io/How-TREE-hypermedia-can-speed-up-Link-Traversal-based-Query-Processing-queries/) +option can be enabled using the `filterPruning` flag. The traversal algorithm will +prune the links that cannot satisfy the combination of the +[`tree:relation`](https://treecg.github.io/specification/#Relation) and the SPARQL filter of the query. +A more thorough explanation is available in this article +["How TREE hypermedia can speed up Link Traversal-based Query Processing for SPARQL queries with filters"](https://constraintautomaton.github.io/How-TREE-hypermedia-can-speed-up-Link-Traversal-based-Query-Processing-queries/). This module is part of the [Comunica framework](https://github.com/comunica/comunica), and should only be used by [developers that want to build their own query engine](https://comunica.dev/docs/modify/). @@ -35,7 +36,7 @@ After installing, this package can be added to your engine's configuration as fo { "@id": "urn:comunica:default:extract-links/actors#extract-links-tree", "@type": "ActorExtractLinksTree", - "reachabilityCriterionUseSPARQLFilter": true + "filterPruning": true } ] diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 253e4692d..8482480ae 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -172,7 +172,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { const descriptionElement = ActorExtractLinksTree.buildRelationElement(quad); if (descriptionElement) { - const {value, key } = descriptionElement; + const { value, key } = descriptionElement; ActorExtractLinksTree.addRelationDescription(relationDescriptions, quad, value, key); } } @@ -213,7 +213,7 @@ export class ActorExtractLinksTree extends ActorExtractLinks { /** * From a quad stream return a relation element if it exist. * For example if the quad is the operator the function will return - * the value of the operator and + * the value of the operator and * @param {RDF.Quad} quad - Current quad of the stream. * @returns {ITreeRelationElement | undefined} The relation element * and the key associated with it. @@ -225,18 +225,18 @@ export class ActorExtractLinksTree extends ActorExtractLinks { // Set the operator of the relation const operator: SparqlRelationOperator | undefined = RelationOperatorReversed.get(quad.object.value); if (typeof operator !== 'undefined') { - return {value:operator, key:'operator'}; + return { value: operator, key: 'operator' }; } } else if (quad.predicate.value === TreeNodes.Path) { // Set the subject of the relation condition - return {value:quad.object.value,key: 'subject'}; + return { value: quad.object.value, key: 'subject' }; } else if (quad.predicate.value === TreeNodes.Value) { // Set the value of the relation condition - return {value:quad.object.value, key:'value' }; + return { value: quad.object.value, key: 'value' }; } else if (quad.predicate.value === TreeNodes.RemainingItems) { const remainingItems = Number.parseInt(quad.object.value, 10); if (!Number.isNaN(remainingItems)) { - return { value: remainingItems, key:'remainingItems' }; + return { value: remainingItems, key: 'remainingItems' }; } } return undefined; @@ -275,6 +275,6 @@ export interface IActorExtractLinksTreeArgs * An element of a TREE relation */ interface ITreeRelationElement{ - key:keyof ITreeRelationRaw, - value: SparqlRelationOperator | number | string -} \ No newline at end of file + key: keyof ITreeRelationRaw; + value: SparqlRelationOperator | number | string; +} diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index fd33ff467..3d7476c0a 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -897,7 +897,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const res = ActorExtractLinksTree.buildRelationElement(quad); expect(res).toBeDefined(); - const {value, key } = res; + const { value, key } = res; expect(key).toBe( 'operator'); expect(value).toBe(SparqlRelationOperator.EqualThanRelation); }); From baf4b0400f08c778673bf2c279c976495546627c Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Thu, 1 Jun 2023 15:34:53 +0200 Subject: [PATCH 177/189] typo corrected --- .../lib/ActorExtractLinksTree.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 8482480ae..fec7aef87 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -94,15 +94,14 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } } - // Create a ITreeNode object const node: ITreeNode = { relation: relations, identifier: currentNodeUrl }; - let acceptedRelation = relations; + let acceptedRelations = relations; if (this.filterPruning) { // Filter the relation based on the query const filters = await this.applyFilter(node, action.context); - acceptedRelation = this.handleFilter(filters, acceptedRelation); + acceptedRelations = this.handleFilter(filters, acceptedRelations); } - resolve({ links: acceptedRelation.map(el => ({ url: el.node })) }); + resolve({ links: acceptedRelations.map(el => ({ url: el.node })) }); }); }); } From a33c7dc4cd671a9823a36ea1dbb03384906030ca Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 2 Jun 2023 13:13:06 +0200 Subject: [PATCH 178/189] typo corrected. --- .../lib/ActorExtractLinksTree.ts | 96 ++++++++-------- .../lib/LogicOperator.ts | 32 ++++-- .../lib/SolutionDomain.ts | 29 +++-- .../lib/SolutionInterval.ts | 107 +++++++++--------- .../lib/SolverInput.ts | 47 ++++---- .../lib/filterNode.ts | 34 +++--- .../lib/solver.ts | 10 +- .../lib/solverInterfaces.ts | 2 +- .../lib/solverUtil.ts | 35 +++--- .../package.json | 12 +- .../test/ActorExtractLinksTree-test.ts | 26 ++--- .../test/LogicOperator-test.ts | 9 +- .../test/SolutionInterval-test.ts | 22 ++-- .../test/filterNode-test.ts | 29 ++--- yarn.lock | 26 ++++- 15 files changed, 284 insertions(+), 232 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index fec7aef87..63c00224b 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -17,7 +17,7 @@ import { TreeNodes, RelationOperatorReversed } from './TreeMetadata'; const DF = new DataFactory(); /** - * A comunica Extract Links Tree Extract Links Actor. + * A comunica Extract Links Tree Actor. */ export class ActorExtractLinksTree extends ActorExtractLinks { private readonly filterPruning: boolean = true; @@ -34,11 +34,11 @@ export class ActorExtractLinksTree extends ActorExtractLinks { args.filterPruning; } - public async test(action: IActionExtractLinks): Promise { + public async test(_: IActionExtractLinks): Promise { return true; } - public isUsingReachabilitySPARQLFilter(): boolean { + public isUsingfilterPruning(): boolean { return this.filterPruning; } @@ -49,20 +49,21 @@ export class ActorExtractLinksTree extends ActorExtractLinks { const strictMode = strictModeFlag === undefined ? true : strictModeFlag; const metadata = action.metadata; - // Maps relationship identifiers to their description. - // At this point, there's no guarantee yet that these relationships are linked to the current TREE document. + // Maps tree:Relation id to their description. const relationDescriptions: Map = new Map(); const relations: ITreeRelation[] = []; const currentNodeUrl = action.url; - // The relation node value and the subject of the relation are the values of the map + // The tree:Relation id and the subject of the node. const relationNodeSubject: Map = new Map(); + // The subject of the node and the next link. const nodeLinks: [string, string][] = []; + // The subjects of the node that is linked to tree:relation considering the type of node. const effectiveTreeDocumentSubject: Set = new Set(); // Forward errors metadata.on('error', reject); - // Collect information about relationships spread over quads, so that we can accumulate them afterwards. + // Collect information about relationships spread over quads, so that we can materialized them afterwards. metadata.on('data', (quad: RDF.Quad) => this.interpretQuad( quad, @@ -74,32 +75,31 @@ export class ActorExtractLinksTree extends ActorExtractLinks { strictMode, )); - // Resolve to discovered links + // Materialized the tree:Relation, prune them if necessary and fill the link queue. metadata.on('end', async() => { - // If we are not in the loose mode then the subject of the page is the URL + // If we are not in the un-strict then the subject of the page is the URL. if (effectiveTreeDocumentSubject.size === 0) { effectiveTreeDocumentSubject.add(currentNodeUrl); } - // Validate if the nodes forward have the current node has implicit subject + // Materialize the tree:Relation by considering if they are attached to the subject(s) of the node for (const [ relationId, link ] of nodeLinks) { const subjectOfRelation = relationNodeSubject.get(relationId); - if (subjectOfRelation && effectiveTreeDocumentSubject.has(subjectOfRelation) - ) { + if (subjectOfRelation && effectiveTreeDocumentSubject.has(subjectOfRelation)) { const relationDescription = relationDescriptions.get(relationId); - // Add the relation to the relation array relations.push( ActorExtractLinksTree.materializeTreeRelation(relationDescription || {}, link), ); } } + // Prune the link based on satisfiability of the combination of the SPARQL filter and the tree:Relation + // equation. const node: ITreeNode = { relation: relations, identifier: currentNodeUrl }; let acceptedRelations = relations; if (this.filterPruning) { - // Filter the relation based on the query - const filters = await this.applyFilter(node, action.context); - acceptedRelations = this.handleFilter(filters, acceptedRelations); + const filters = await this.createFilter(node, action.context); + acceptedRelations = this.applyFilter(filters, acceptedRelations); } resolve({ links: acceptedRelations.map(el => ({ url: el.node })) }); }); @@ -107,43 +107,45 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } /** + * Create the filter to prune links. * @param {ITreeNode} node - TREE metadata * @param {IActionContext} context - context of the action; containing the query - * @returns {Promise>} a map containing the filter + * @returns {Promise>} a map containing representing the filter */ - public async applyFilter(node: ITreeNode, context: IActionContext): Promise> { + public async createFilter(node: ITreeNode, context: IActionContext): Promise> { return await filterNode(node, context, isBooleanExpressionTreeRelationFilterSolvable); } /** + * Apply the filter to prune the relations. * @param { Map} filters - * @param {ITreeRelation[]} acceptedRelation - the current accepted relation - * @returns {ITreeRelation[]} the relation when the nodes has been filtered + * @param {ITreeRelation[]} acceptedRelations - the current accepted relation + * @returns {ITreeRelation[]} the resulting relations */ - private handleFilter(filters: Map, acceptedRelation: ITreeRelation[]): ITreeRelation[] { + private applyFilter(filters: Map, acceptedRelations: ITreeRelation[]): ITreeRelation[] { return filters.size > 0 ? - acceptedRelation.filter(relation => filters?.get(relation.node)) : - acceptedRelation; + acceptedRelations.filter(relation => filters?.get(relation.node)) : + acceptedRelations; } /** * A helper function to find all the relations of a TREE document and the possible next nodes to visit. * The next nodes are not guaranteed to have as subject the URL of the current page, * so filtering is necessary afterward. - * @param {RDF.Quad} quad - The current quad. - * @param {string} currentPageUrl - The url of the page. - * @param {Set} relationIdentifiers - Identifiers of the relationships defined by the TREE document, - * represented as stringified RDF terms. - * @param {[string, string][]} nodeLinks - An array of pairs of relationship identifiers and next page link to another - * TREE document, represented as stringified RDF terms. - * @param {Map} relationDescriptions - Maps relationship identifiers to their description. + * @param {RDF.Quad} quad - current quad + * @param {string} url - url of the current node + * @param {Map} relationNodeSubject - the tree:Relation id and the subject of the node + * @param {[string, string][]} nodeLinks - the subject of the node and the next link + * @param {Set} effectiveTreeDocumentSubject - the subjects of the node that is linked to tree:relation considering the type of node + * @param {Map} relationDescriptions - the tree:Relation id associated with the relation description + * @param {boolean} strictMode - define whether we the subject of the node should match the page URL */ private interpretQuad( quad: RDF.Quad, url: string, relationNodeSubject: Map, nodeLinks: [string, string][], - rootNodeEffectiveSubject: Set, + effectiveTreeDocumentSubject: Set, relationDescriptions: Map, strictMode: boolean, ): void { @@ -155,16 +157,15 @@ export class ActorExtractLinksTree extends ActorExtractLinks { (!strictMode || quad.subject.value === url) && (quad.predicate.equals(ActorExtractLinksTree.aView) || quad.predicate.equals(ActorExtractLinksTree.aSubset))) { - rootNodeEffectiveSubject.add(termToString(quad.object)); + effectiveTreeDocumentSubject.add(termToString(quad.object)); } if ( (!strictMode || quad.object.value === url) && quad.predicate.equals(ActorExtractLinksTree.isPartOf)) { - rootNodeEffectiveSubject.add(termToString(quad.subject)); + effectiveTreeDocumentSubject.add(termToString(quad.subject)); } - // If it's a node forward if (quad.predicate.equals(ActorExtractLinksTree.aNodeType)) { nodeLinks.push([ termToString(quad.subject), quad.object.value ]); } @@ -177,10 +178,10 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } /** - * Materialize a raw tree relation using the captured values. - * @param relationRaw Raw representation of a tree relation. - * @param nextLink Link to the next page. - * @returns ITreeRelation + * Materialize a raw tree Relation using the captured values. + * @param {ITreeRelationRaw} relationRaw - Raw representation of a tree relation. + * @param {string} nextLink - Link to the next page. + * @returns {ITreeRelation} The tree:Relation javascript object */ public static materializeTreeRelation( relationRaw: ITreeRelationRaw, @@ -210,12 +211,11 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } /** - * From a quad stream return a relation element if it exist. - * For example if the quad is the operator the function will return - * the value of the operator and + * From a quad stream return a {@link ITreeRelationElement} if the quad refer to one. + * For example if the quad describe the operator the tree:relation it will return + * the value of the operator and the key operator * @param {RDF.Quad} quad - Current quad of the stream. - * @returns {ITreeRelationElement | undefined} The relation element - * and the key associated with it. + * @returns {ITreeRelationElement | undefined} The {@link ITreeRelationElement} */ public static buildRelationElement( quad: RDF.Quad, @@ -242,11 +242,11 @@ export class ActorExtractLinksTree extends ActorExtractLinks { } /** - * Update the relationDescriptions with the new quad value - * @param {Map} relationDescriptions - Maps relationship identifiers to their description. - * @param {RDF.Quad} quad - Current quad of the steam. - * @param {SparqlRelationOperator | number | string} value - Current description value fetch - * @param {keyof ITreeRelationRaw} key - Key associated with the value. + * Update the relationDescriptions with the new relevant quad value. + * @param {Map} relationDescriptions - maps relationship identifiers to their description. + * @param {RDF.Quad} quad - current quad of the steam + * @param {SparqlRelationOperator | number | string} value - current description value fetch + * @param {keyof ITreeRelationRaw} key - key associated with the value. */ public static addRelationDescription( relationDescriptions: Map, diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index 0e48326ce..8789ab4f6 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -2,9 +2,18 @@ import { SolutionDomain } from './SolutionDomain'; import { SolutionInterval } from './SolutionInterval'; import { LogicOperatorSymbol } from './solverInterfaces'; +/** + * A logic operator + */ export interface ILogicOperator { + /** + * Apply the operation on a domain given an interval + */ apply: ({ interval, domain }: { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }) => SolutionDomain; + /** + * The name of the operator + */ operatorName: () => LogicOperatorSymbol; } @@ -23,17 +32,18 @@ export class Or implements ILogicOperator { newDomain = domain.getDomain().filter(el => { // We check if we can fuse the new range with the current range // let's not forget that the domain is sorted by the lowest bound - // of the SolutionRange - const resp = SolutionInterval.fuseRange(el, currentInterval); + // of the SolutionInterval. + const resp = SolutionInterval.fuseinterval(el, currentInterval); if (resp.length === 1) { - // If we fuse the range and consider this new range as our current range - // and we delete the old range from the domain as we now have a new range that contained the old + // If we fuse the interval we consider this new interval as our current interval + // and we delete the old interval from the domain as we now have a new interval that contained the old + // one currentInterval = resp[0]; return false; } return true; }); - // We add the potentialy fused range. + // We add the potentialy fused interval. newDomain.push(currentInterval); return SolutionDomain.newWithInitialIntervals(newDomain); @@ -48,13 +58,14 @@ export class And implements ILogicOperator { public apply({ interval, domain }: { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { if (Array.isArray(interval)) { + // we check if it is a step interval if (interval[0].isOverlapping(interval[1])) { return domain; } - + const testDomain1 = this.apply({ interval: interval[0], domain }); const testDomain2 = this.apply({ interval: interval[1], domain }); - + // We check which part of the interval can be added to domain. const cannotAddDomain1 = testDomain1.isDomainEmpty(); const cannotAddDomain2 = testDomain2.isDomainEmpty(); @@ -70,6 +81,8 @@ export class And implements ILogicOperator { return testDomain2; } + // if both can be added we consider the larger domain and use an OR operation + // to add the other part. let intervalRes: SolutionInterval; let newDomain: SolutionDomain; if (testDomain1.getDomain().length > testDomain2.getDomain().length) { @@ -119,6 +132,11 @@ const OPERATOR_MAP = new Map( ], ); +/** + * A factory to get a {@link ILogicOperator} from a {@link LogicOperatorSymbol} + * @param {LogicOperatorSymbol} operatorSymbol - the symbol + * @returns {ILogicOperator} the operator + */ export function operatorFactory(operatorSymbol: LogicOperatorSymbol): ILogicOperator { const operator = OPERATOR_MAP.get(operatorSymbol); if (!operator) { diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 0243e84c6..a5cb3162d 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -1,12 +1,12 @@ import type { SolutionInterval } from './SolutionInterval'; /** - * A class representing the domain of a solution of system of boolean equation. + * A class representing the domain of a solution of a system of boolean equations. */ export class SolutionDomain { /** * The multiple segment of the domain, it is always order by the lower bound - * of the SolutionRange. + * of the SolutionInterval. */ private domain: SolutionInterval[] = []; @@ -17,7 +17,11 @@ export class SolutionDomain { public getDomain(): SolutionInterval[] { return this.domain; } - + /** + * Check if two {@link SolutionDomain} are equals. + * @param {SolutionDomain} other - the other domain + * @returns {boolean} whether two domains are equals + */ public equal(other: SolutionDomain): boolean { if (this.domain.length !== other.domain.length) { return false; @@ -50,8 +54,8 @@ export class SolutionDomain { } /** - * Create a new SolutionDomain with an inititial value. - * @param {SolutionInterval} initialIntervals + * Create a new {@link SolutionDomain} with inititial values. + * @param {SolutionInterval | SolutionInterval[]} initialIntervals * @returns {SolutionDomain} */ public static newWithInitialIntervals(initialIntervals: SolutionInterval | SolutionInterval[]): SolutionDomain { @@ -73,17 +77,22 @@ export class SolutionDomain { /** * Simple sort function to order the domain by the lower bound of SolutionRange. - * @param {SolutionInterval} firstRange - * @param {SolutionInterval} secondRange + * @param {SolutionInterval} firstinterval + * @param {SolutionInterval} secondInterval * @returns {number} see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort */ - public static sortDomainRangeByLowerBound(firstRange: SolutionInterval, secondRange: SolutionInterval): number { - if (firstRange.lower < secondRange.lower) { + public static sortDomainRangeByLowerBound(firstinterval: SolutionInterval, secondInterval: SolutionInterval): number { + if (firstinterval.lower < secondInterval.lower) { return -1; } return 1; } - + + /** + * The invariant contract of the {@link SolutionDomain}. + * There should be no overlapping domain segment. + * @returns {boolean} whether or not the domain is overlapping + */ private isThereOverlapInsideDomain(): boolean { for (let i = 0; i < this.domain.length - 1; i++) { if (this.domain[i].isOverlapping(this.domain[i + 1])) { diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionInterval.ts b/packages/actor-extract-links-extract-tree/lib/SolutionInterval.ts index c31c3d32b..517765d41 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionInterval.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionInterval.ts @@ -1,35 +1,35 @@ const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; /** - * A class representing the range of a solution it contain method to + * A class representing the interval of a solution it contain method to * facilitate operation between subdomain. */ export class SolutionInterval { /** - * The upper bound of the range. + * The upper bound of the interval. */ public readonly upper: number; /** - * The lower bound of the range. + * The lower bound of the interval. */ public readonly lower: number; /** - * Weither the range is empty + * Wether the interval is empty */ public readonly isEmpty: boolean; /** - * Constructor of a solution range, will throw an error if the lower range is greater than the upper range. - * @param {[number, number]} range - An array where the first memeber is the lower bound of the range + * Constructor of a solution interval, will throw an error if the lower interval is greater than the upper interval. + * @param {[number, number]} interval - An array where the first memeber is the lower bound of the interval * and the second the upper bound */ - public constructor(range: [number, number] | []) { - if (range.length === 2) { - if (range[0] > range[1]) { - throw new RangeError('the first element of the range should lower or equal to the second'); + public constructor(interval: [number, number] | []) { + if (interval.length === 2) { + if (interval[0] > interval[1]) { + throw new RangeError('the first element of the interval should lower or equal to the second'); } - this.lower = range[0]; - this.upper = range[1]; + this.lower = interval[0]; + this.upper = interval[1]; this.isEmpty = false; } else { this.isEmpty = true; @@ -43,77 +43,76 @@ export class SolutionInterval { } /** - * Check if the two ranges overlap. - * @param {SolutionInterval} otherRange - * @returns {boolean} Return true if the two range overlap. + * Check if the two intervals overlap. + * @param {SolutionInterval} otherinterval + * @returns {boolean} Return true if the two interval overlap. */ - public isOverlapping(otherRange: SolutionInterval): boolean { - if (this.isEmpty || otherRange.isEmpty) { + public isOverlapping(otherinterval: SolutionInterval): boolean { + if (this.isEmpty || otherinterval.isEmpty) { return false; } - if (this.upper === otherRange.upper && this.lower === otherRange.lower) { + if (this.upper === otherinterval.upper && this.lower === otherinterval.lower) { return true; } - if (this.upper >= otherRange.lower && this.upper <= otherRange.upper) { + if (this.upper >= otherinterval.lower && this.upper <= otherinterval.upper) { return true; } - if (this.lower >= otherRange.lower && this.lower <= otherRange.upper) { + if (this.lower >= otherinterval.lower && this.lower <= otherinterval.upper) { return true; } - if (otherRange.lower >= this.lower && otherRange.upper <= this.upper) { + if (otherinterval.lower >= this.lower && otherinterval.upper <= this.upper) { return true; } return false; } /** - * Check whether the other range is inside the subject range. - * @param {SolutionInterval} otherRange - * @returns {boolean} Return true if the other range is inside this range. + * Check whether the other interval is inside the subject interval. + * @param {SolutionInterval} otherinterval + * @returns {boolean} Return true if the other interval is inside the subject interval. */ - public isInside(otherRange: SolutionInterval): boolean { - if (this.isEmpty || otherRange.isEmpty) { + public isInside(otherinterval: SolutionInterval): boolean { + if (this.isEmpty || otherinterval.isEmpty) { return false; } - return otherRange.lower >= this.lower && otherRange.upper <= this.upper; + return otherinterval.lower >= this.lower && otherinterval.upper <= this.upper; } /** - * Fuse two ranges if they overlap. - * @param {SolutionInterval} subjectRange - * @param {SolutionInterval} otherRange - * @returns {SolutionInterval[]} Return the fused range if they overlap else return the input ranges. - * It also take into consideration if the range is empty. + * Fuse two intervals if they overlap. + * @param {SolutionInterval} subjectinterval + * @param {SolutionInterval} otherinterval + * @returns {SolutionInterval[]} Return the fused interval if they overlap else return the input intervals. + * It also take into consideration if the interval is empty. */ - public static fuseRange(subjectRange: SolutionInterval, otherRange: SolutionInterval): SolutionInterval[] { - if (subjectRange.isEmpty && otherRange.isEmpty) { + public static fuseinterval(subjectinterval: SolutionInterval, otherinterval: SolutionInterval): SolutionInterval[] { + if (subjectinterval.isEmpty && otherinterval.isEmpty) { return [ new SolutionInterval([]) ]; } - if (subjectRange.isEmpty && !otherRange.isEmpty) { - return [ otherRange ]; + if (subjectinterval.isEmpty && !otherinterval.isEmpty) { + return [ otherinterval ]; } - if (!subjectRange.isEmpty && otherRange.isEmpty) { - return [ subjectRange ]; + if (!subjectinterval.isEmpty && otherinterval.isEmpty) { + return [ subjectinterval ]; } - if (subjectRange.isOverlapping(otherRange)) { - const lowest = subjectRange.lower < otherRange.lower ? subjectRange.lower : otherRange.lower; - const uppest = subjectRange.upper > otherRange.upper ? subjectRange.upper : otherRange.upper; + if (subjectinterval.isOverlapping(otherinterval)) { + const lowest = subjectinterval.lower < otherinterval.lower ? subjectinterval.lower : otherinterval.lower; + const uppest = subjectinterval.upper > otherinterval.upper ? subjectinterval.upper : otherinterval.upper; return [ new SolutionInterval([ lowest, uppest ]) ]; } - return [ subjectRange, otherRange ]; + return [ subjectinterval, otherinterval ]; } /** - * Inverse the range, in a way that the range become everything that it excluded. Might - * 0 or return multiple ranges. - * @returns {SolutionInterval[]} The resulting ranges. + * Inverse the interval, in a way that the interval become everything that was excluded. + * @returns {SolutionInterval[]} The resulting intervals. */ public inverse(): SolutionInterval[] { if (this.isEmpty) { @@ -137,18 +136,18 @@ export class SolutionInterval { } /** - * Get the range that intersect the other range and the subject range. - * @param {SolutionInterval} subjectRange - * @param {SolutionInterval} otherRange - * @returns {SolutionInterval | undefined} Return the intersection if the range overlap otherwise return undefined + * Get the interval that intersect the other interval and the subject interval. + * @param {SolutionInterval} subjectinterval + * @param {SolutionInterval} otherinterval + * @returns {SolutionInterval | undefined} Return the intersection if the interval overlap otherwise return undefined */ - public static getIntersection(subjectRange: SolutionInterval, - otherRange: SolutionInterval): SolutionInterval { - if (!subjectRange.isOverlapping(otherRange) || subjectRange.isEmpty || otherRange.isEmpty) { + public static getIntersection(subjectinterval: SolutionInterval, + otherinterval: SolutionInterval): SolutionInterval { + if (!subjectinterval.isOverlapping(otherinterval) || subjectinterval.isEmpty || otherinterval.isEmpty) { return new SolutionInterval([]); } - const lower = subjectRange.lower > otherRange.lower ? subjectRange.lower : otherRange.lower; - const upper = subjectRange.upper < otherRange.upper ? subjectRange.upper : otherRange.upper; + const lower = subjectinterval.lower > otherinterval.lower ? subjectinterval.lower : otherinterval.lower; + const upper = subjectinterval.upper < otherinterval.upper ? subjectinterval.upper : otherinterval.upper; return new SolutionInterval([ lower, upper ]); } diff --git a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts index 011c1a7fa..0bdea5977 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts @@ -29,6 +29,9 @@ const A_TRUE_EXPRESSION: SolutionInterval = new SolutionInterval( ); const A_FALSE_EXPRESSION: SolutionInterval = new SolutionInterval([]); +/** + * The representation in the solver domain of a SPARQL filter expression + */ export class SparlFilterExpressionSolverInput implements ISolverInput { public readonly filterExpression: Algebra.Expression; public readonly variable: Variable; @@ -45,7 +48,7 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { if (error instanceof MisformatedExpressionError) { this.domain = SolutionDomain.newWithInitialIntervals(A_TRUE_EXPRESSION); } else if (error instanceof UnsupportedDataTypeError) { - // We don't support the data type so let need to explore that link to not diminush the completness of the result + // We don't support the data type so we will let the node be explored this.domain = SolutionDomain.newWithInitialIntervals(A_TRUE_EXPRESSION); } else { /* istanbul ignore next */ @@ -65,14 +68,13 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { /** * Recursively traverse the filter expression and calculate the domain until it get to the current expression. - * It will thrown an error if the expression is badly formated or if it's impossible to get the solution range. + * It will thrown an error if the expression is badly formated or if it's impossible to get the {@link SolutionInterval}. * @param {Algebra.Expression} filterExpression - * The current filter expression that we are traversing + * @param {Variable} variable - The variable targeted inside the filter expression * @param {SolutionDomain} domain - The current resultant solution domain * @param {LogicOperatorSymbol} logicOperator * - The current logic operator that we have to apply to the boolean expression - * @param {Variable} variable - The variable targeted inside the filter expression - * @param {boolean} notExpression * @returns {SolutionDomain} The solution domain of the whole expression */ public static recursifResolve( @@ -84,8 +86,6 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { if (filterExpression.expressionType === Algebra.expressionTypes.TERM ) { // In that case we are confronted with a boolean expression - // add the associated interval into the domain in relation to - // the logic operator. if (filterExpression.term.value === 'false') { domain = logicOperator.apply({ interval: A_FALSE_EXPRESSION, domain }); } else { @@ -93,8 +93,6 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { } } else if ( // If it's an array of terms then we should be able to create a solver expression. - // Given the resulting solver expression we can calculate a solution interval - // that we will add to the domain with regards to the logic operator. filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && filterExpression.args.length === 2 ) { @@ -122,7 +120,7 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { const logicOperatorSymbol = LogicOperatorReversed.get(filterExpression.operator); if (logicOperatorSymbol) { for (const arg of filterExpression.args) { - // To solve the not operation we rewrite the path of filter expression to reverse every operation + // To solve the not operation we rewrite the path of the filter expression to reverse every operation // e.g, = : != ; > : <= if (logicOperatorSymbol === LogicOperatorSymbol.Not) { inverseFilter(arg); @@ -138,11 +136,11 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { } /** - * From an Algebra expression return an solver expression if possible - * @param {Algebra.Expression} expression - Algebra expression containing the a variable and a litteral. - * @param {SparqlRelationOperator} operator - The SPARQL operator defining the expression. - * @param {Variable} variable - The variable the expression should have to be part of a system of equation. - * @returns {ISolverExpression | undefined} Return a solver expression if possible + * From an Algebra expression return an solver expression if possible. + * @param {Algebra.Expression} expression - {@link Algebra} expression containing the a variable and a litteral + * @param {SparqlRelationOperator} operator - the SPARQL operator defining the expression. + * @param {Variable} variable - the variable the expression should have to be part of a system of equation + * @returns {ISolverExpression | undefined} Return a {@link ISolverExpression} if it can be satisfy */ public static resolveAFilterTerm(expression: Algebra.Expression, operator: SparqlRelationOperator, @@ -153,10 +151,10 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { let valueAsNumber: number | undefined; let hasVariable = false; - // Find the constituant element of the solver expression + // Find the constituent element of the solver expression for (const arg of expression.args) { if ('term' in arg && arg.term.termType === 'Variable') { - // Check if the expression has the same variable as the one the solver try to resolved + // Check if the expression has the same variable as the one of the solver if (arg.term.value !== variable) { return new MissMatchVariableError(`the variable ${arg.term.value} is in the filter whereas we are looking for the varibale ${variable}`); } @@ -174,7 +172,7 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { } } } - // Return if a fully form solver expression can be created + // Return if a fully form solver expression if (hasVariable && rawValue && valueType && valueAsNumber) { return { variable, @@ -196,6 +194,9 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { } } +/** + * The representation in the solver domain of a tree:Relation + */ export class TreeRelationSolverInput implements ISolverInput { public readonly domain: SolutionInterval | [SolutionInterval, SolutionInterval]; public readonly treeRelation: ITreeRelation; @@ -206,7 +207,7 @@ export class TreeRelationSolverInput implements ISolverInput { this.variable = variable; const relationsolverExpressions = TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable); - // The relation doesn't have a value or a type, so we accept it + // The relation doesn't have a value or a type, so we follow the link if (!relationsolverExpressions) { this.domain = A_TRUE_EXPRESSION; this.freeze(); @@ -236,11 +237,11 @@ export class TreeRelationSolverInput implements ISolverInput { } /** - * Convert a TREE relation into a solver expression. - * @param {ITreeRelation} relation - TREE relation. - * @param {Variable} variable - variable of the SPARQL query associated with the tree:path of the relation. - * @returns {ISolverExpression | undefined} Resulting solver expression if the data type is supported by SPARQL - * and the value can be cast into a number. + * Convert a TREE relation into a {@link ISolverExpression}. + * @param {ITreeRelation} relation - TREE relation + * @param {Variable} variable - variable of the SPARQL query associated with the tree:path of the relation + * @returns {ISolverExpression | undefined} Resulting {@link ISolverExpression} if the data type is supported by SPARQL + * and the value can be cast into a number */ public static convertTreeRelationToSolverExpression(relation: ITreeRelation, variable: Variable): diff --git a/packages/actor-extract-links-extract-tree/lib/filterNode.ts b/packages/actor-extract-links-extract-tree/lib/filterNode.ts index 1463c893a..45f2b122a 100644 --- a/packages/actor-extract-links-extract-tree/lib/filterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/filterNode.ts @@ -11,10 +11,9 @@ const AF = new AlgebraFactory(); const BF = new BindingsFactory(); /** - * Return the filter expression if the TREE node has relations - * @param {ITreeNode} node - The current TREE node + * Return the filter expression from the context. * @param {IActionContext} context - The context - * @returns {Algebra.Expression | undefined} The filter expression or undefined if the TREE node has no relations + * @returns {Algebra.Expression | undefined} the filter expression or undefined if it is not defined */ export function getFilterExpression(context: IActionContext): Algebra.Expression | undefined { const query: Algebra.Operation = context.get(KeysInitQuery.query)!; @@ -27,12 +26,12 @@ export function getFilterExpression(context: IActionContext): Algebra.Expression } /** - * Analyze if the tree:relation(s) of a tree:Node should be followed and return a map - * where if the value of the key representing the URL to follow is true than the link must be followed - * if it is false then it should be ignored. - * @param {ITreeNode} node - The current TREE node - * @param {IActionContext} context - The context - * @returns {Promise>} A map of the indicating if a tree:relation should be follow + * Analyze if the tree:Relation(s) of a tree:Node should be followed. + * It used a {@link SatisfactionChecker} to evaluate if the link should be filtered. + * @param {ITreeNode} node - the current TREE node + * @param {IActionContext} context - the context + * @param {SatisfactionChecker} satisfactionChecker - a function to check the satisfabillity of boolean expressions + * @returns {Promise>} a map indicating if a tree:Relation should be follow */ export async function filterNode( node: ITreeNode, @@ -59,7 +58,7 @@ export async function filterNode( // Extract the bgp of the query. const queryBody: Algebra.Operation = context.get(KeysInitQuery.query)!; - // Capture the relation from the function argument. + // Capture group relations by tree:path and tree:node to consider bounded node. const groupedRelations = groupRelations(node.relation); const calculatedFilterExpressions: Map = new Map(); @@ -78,12 +77,13 @@ export async function filterNode( continue; } let filtered = false; - // For all the variable check if one is has a possible solutions. + // For all the variable check if one has a possible solutions. for (const variable of variables) { let inputFilterExpression = calculatedFilterExpressions.get(variable); if (!inputFilterExpression) { inputFilterExpression = new SparlFilterExpressionSolverInput( - structuredClone(filterOperation), + // make a deep copy of the filter + Util.mapExpression(filterOperation, {}, undefined), variable, ); } @@ -97,8 +97,8 @@ export async function filterNode( } /** - * Helper function to add to the filter map while considering that a previous relation group might - * have permit the access to the node. If the a group gives the access previously we should keep the access. + * Helper function to add value to the filter map while considering that a previous relation group might + * Have permits access to the node. If a group gives access previously, we should keep the access. * @param {Map} filterMap - The current filter map * @param {boolean} filtered - The current access flag * @param {string} node - The target node @@ -114,7 +114,7 @@ function addToFilterMap(filterMap: Map, filtered: boolean, node /** * Find the variables from the BGP that match the predicate defined by the TREE:path from a TREE relation. - * The subject can be anyting. + * The subject can be anything. * @param {Algebra.Operation} queryBody - the body of the query * @param {string} path - TREE path * @returns {Variable[]} the variables of the Quad objects that contain the TREE path as predicate @@ -168,8 +168,8 @@ export function groupRelations(relations: ITreeRelation[]): ITreeRelation[][] { } /** - * Find the first node of type `nodeType`, if it doesn't exist - * it return undefined. + * Find the first node of the type `nodeType`, if it doesn't exist + * it returns undefined. * @param {Algebra.Operation} query - the original query * @param {string} nodeType - the type of node requested * @returns {any} diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 9050c3fb3..710156a8e 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -5,12 +5,14 @@ import type { SolutionInterval } from './SolutionInterval'; import type { ISolverInput } from './solverInterfaces'; const AND = new And(); +/** + * A function that verifies the satisfiability of multiple ISolverInput combined with an AND operation. + */ export type SatisfactionChecker = (inputs: ISolverInput[]) => boolean; /** - * Check if it is possible to satify the combination of the boolean expression of the TREE relations - * and the SPARQL filter. Will Throw if there is no or more than one SPARQL filter which is identify - * with the ResolvedType Domain - * @param {ISolverInput[]} inputs - The solver input, must countain one ResolvedType Domain + * Check if it is possible to satify the combination of the boolean expression of the tree:relation(s) + * and the SPARQL filter. Will Throw if there is no or more than one SPARQL filter. + * @param {ISolverInput[]} inputs - the solver input, must countain one SPARQL filter * @returns {boolean} Whether the expression can be satify */ export function isBooleanExpressionTreeRelationFilterSolvable(inputs: ISolverInput[]): boolean { diff --git a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts index efc4d5289..806a230dd 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverInterfaces.ts @@ -81,7 +81,7 @@ export interface ISolverExpression { operator: SparqlRelationOperator; } /** - * An input for the solve. + * An input for the solver. * It must be able to be resolved into a domain or an interval */ export interface ISolverInput { diff --git a/packages/actor-extract-links-extract-tree/lib/solverUtil.ts b/packages/actor-extract-links-extract-tree/lib/solverUtil.ts index d756ebf24..4195f151d 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverUtil.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverUtil.ts @@ -13,7 +13,7 @@ const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; /** - * Check if all the expression provided have a SparqlOperandDataTypes compatible type + * Check if all the expression provided have a {@link SparqlOperandDataTypes} compatible type * it is considered that all number types are compatible between them. * @param {ISolverExpression[]} expressions - The subject expression. * @returns {boolean} Return true if the type are compatible. @@ -32,10 +32,10 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { return true; } /** - * Find the solution range of a value and operator which is analogue to an expression. + * Find the {@link SolutionInterval} of a value and operator which is analogue to an expression. * @param {number} value * @param {SparqlRelationOperator} operator - * @returns {SolutionInterval | undefined} The solution range associated with the value and the operator. + * @returns {SolutionInterval | undefined} the {@link SolutionInterval} range associated with the value and the operator. */ export function getSolutionInterval(value: number, operator: SparqlRelationOperator): SolutionInterval | [SolutionInterval, SolutionInterval] | undefined { @@ -56,15 +56,15 @@ SolutionInterval | [SolutionInterval, SolutionInterval] | undefined { new SolutionInterval([ nextUp(value), Number.POSITIVE_INFINITY ]), ]; default: - // Not an operator that is compatible with number. + // Not an operator that is compatible with numbers. break; } } /** - * Convert a RDF value into a number. + * Convert a RDF value into a {@link number}. * @param {string} rdfTermValue - The raw value * @param {SparqlOperandDataTypes} rdfTermType - The type of the value - * @returns {number | undefined} The resulting number or undefined if the convertion is not possible. + * @returns {number | undefined} The resulting {@link number} or {@link undefined} if the convertion is not possible. */ export function castSparqlRdfTermIntoNumber(rdfTermValue: string, rdfTermType: SparqlOperandDataTypes): @@ -106,8 +106,8 @@ export function castSparqlRdfTermIntoNumber(rdfTermValue: string, } /** * Determine if the type is a number. - * @param {SparqlOperandDataTypes} rdfTermType - The subject type - * @returns {boolean} Return true if the type is a number. + * @param {SparqlOperandDataTypes} rdfTermType - the subject type + * @returns {boolean} return true if the type is a number. */ export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean { return rdfTermType === SparqlOperandDataTypes.Integer || @@ -126,9 +126,9 @@ export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): rdfTermType === SparqlOperandDataTypes.Int; } /** - * Convert a filter operator to SparqlRelationOperator. - * @param {string} filterOperator - The filter operator. - * @returns {SparqlRelationOperator | undefined} The SparqlRelationOperator corresponding to the filter operator + * Convert a filter operator to {@link SparqlRelationOperator}. + * @param {string} filterOperator - the filter operator. + * @returns {SparqlRelationOperator | undefined} the SparqlRelationOperator corresponding to the filter operator */ export function filterOperatorToSparqlRelationOperator(filterOperator: string): SparqlRelationOperator | undefined { switch (filterOperator) { @@ -151,7 +151,7 @@ export function filterOperatorToSparqlRelationOperator(filterOperator: string): /** * Reverse a logic operator. * @param {string} logicOperator - A string representation of a logic operator - * @returns {string | undefined} The reversed logic operator or undefined if the input is not a valid operator + * @returns {string | undefined} The reversed logic operator or {@link undefined} if the input is not a valid operator */ export function reverseRawLogicOperator(logicOperator: string): string | undefined { switch (logicOperator) { @@ -171,7 +171,7 @@ export function reverseRawLogicOperator(logicOperator: string): string | undefin /** * Reverse a string operator. * @param {string} filterOperator - A string representation of an operator - * @returns {string | undefined} The reverse operator or undefined if the input is not a valid operator + * @returns {string | undefined} The reverse operator or {@link undefined} if the input is not a valid operator */ export function reverseRawOperator(filterOperator: string): string | undefined { switch (filterOperator) { @@ -193,10 +193,10 @@ export function reverseRawOperator(filterOperator: string): string | undefined { } /** - * Reverse a sparqlOperator. - * @param {SparqlRelationOperator} operator - A Sparql operator + * Reverse a {@link SparqlRelationOperator}. + * @param {SparqlRelationOperator} operator - a Sparql operator * @returns {SparqlRelationOperator | undefined} - * The reverse operator or undefined if the input is not a supported operator + * The reverse operator or {@link undefined} if the input is not a supported operator. */ export function reverseSparqlOperator(operator: SparqlRelationOperator): SparqlRelationOperator | undefined { switch (operator) { @@ -231,9 +231,6 @@ export function inverseFilter(filterExpression: Algebra.Expression): void { filterExpression.term.value = 'false'; } } else if ( - // If it's an array of terms then we should be able to create a solver expression. - // Given the resulting solver expression we can calculate a solution interval - // that we will add to the domain with regards to the logic operator. filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && filterExpression.args.length === 2 ) { diff --git a/packages/actor-extract-links-extract-tree/package.json b/packages/actor-extract-links-extract-tree/package.json index 170578f8a..fcb55d0bd 100644 --- a/packages/actor-extract-links-extract-tree/package.json +++ b/packages/actor-extract-links-extract-tree/package.json @@ -31,21 +31,21 @@ "lib/**/*.js" ], "dependencies": { - - "@comunica/core": "^2.7.0", "@comunica/bindings-factory": "^2.2.0", "@comunica/bus-extract-links": "^0.1.1", "@comunica/context-entries": "^2.6.8", "@comunica/context-entries-link-traversal": "^0.1.1", + "@comunica/core": "^2.7.0", "@comunica/types-link-traversal": "^0.1.0", - "@types/node": "17.0.29", + "@types/ungap__structured-clone": "^0.3.0", + "@ungap/structured-clone": "^1.2.0", "rdf-data-factory": "^1.1.1", + "rdf-store-stream": "^1.3.0", "rdf-string": "^1.6.1", - "sparqlalgebrajs": "^4.0.0", + "sparqlalgebrajs": "^4.1.0", "sparqlee": "^2.1.0", "ulp": "^1.0.1", - "uuid": "^9.0.0", - "rdf-store-stream": "^1.3.0" + "uuid": "^9.0.0" }, "scripts": { "build": "npm run build:ts && npm run build:components", diff --git a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts index 3d7476c0a..df59d8eb2 100644 --- a/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts +++ b/packages/actor-extract-links-extract-tree/test/ActorExtractLinksTree-test.ts @@ -38,16 +38,16 @@ describe('ActorExtractLinksExtractLinksTree', () => { it('should apply the activate the reachability criterion based on the constructor parameter', () => { let filterPruning = true; let actor = new ActorExtractLinksTree({ name: 'actor', bus, filterPruning }); - expect(actor.isUsingReachabilitySPARQLFilter()).toBe(true); + expect(actor.isUsingfilterPruning()).toBe(true); filterPruning = false; actor = new ActorExtractLinksTree({ name: 'actor', bus, filterPruning }); - expect(actor.isUsingReachabilitySPARQLFilter()).toBe(false); + expect(actor.isUsingfilterPruning()).toBe(false); }); it('should apply the activate the reachability when it is not defined in the config', () => { const actor = new ActorExtractLinksTree({ name: 'actor', bus }); - expect(actor.isUsingReachabilitySPARQLFilter()).toBe(true); + expect(actor.isUsingfilterPruning()).toBe(true); }); }); @@ -60,7 +60,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { beforeEach(() => { actor = new ActorExtractLinksTree({ name: 'actor', bus }); - jest.spyOn(actor, 'applyFilter').mockReturnValue(Promise.resolve(new Map())); + jest.spyOn(actor, 'createFilter').mockReturnValue(Promise.resolve(new Map())); }); it('should return the link of a TREE with one relation', async() => { @@ -302,7 +302,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { const actorWithCustomFilter = new ActorExtractLinksTree( { name: 'actor', bus }, ); - jest.spyOn(actorWithCustomFilter, 'applyFilter').mockReturnValue(filterOutput); + jest.spyOn(actorWithCustomFilter, 'createFilter').mockReturnValue(filterOutput); const result = await actorWithCustomFilter.run(action); expect(result).toEqual({ links: [{ url: expectedUrl }]}); }); @@ -448,7 +448,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toEqual({ links: [{ url: expectedUrl }]}); }); - it('should return no link when the relation doesn\'t respect the filter when using the real FilterNode class', + it('should return no link when the relation doesn\'t respects the filter when using the real FilterNode class', async() => { const expectedUrl = 'http://foo.com'; const input = stream([ @@ -535,7 +535,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(result).toEqual({ links: [{ url: expectedUrl }]}); }); - it(`should return the links of a TREE with when there is a root type`, async() => { + it('should return the links of a TREE with when there is a root type', async() => { const expectedUrl = 'http://foo.com'; const url = 'bar'; for (const rootNode of [ @@ -587,7 +587,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { is deactivated and the URL matches the subject of the root node TREE documents`, async() => { const unStrictContext = context.set(KeysExtractLinksTree.strictTraversal, false); actor = new ActorExtractLinksTree({ name: 'actor', bus }); - jest.spyOn(actor, 'applyFilter').mockReturnValue(Promise.resolve(new Map())); + jest.spyOn(actor, 'createFilter').mockReturnValue(Promise.resolve(new Map())); const expectedUrl = 'http://foo.com'; for (const rootNode of [ @@ -632,7 +632,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { is deactivated and the URL doesn't match the subject of the root node TREE documents`, async() => { const unStrictContext = context.set(KeysExtractLinksTree.strictTraversal, false); actor = new ActorExtractLinksTree({ name: 'actor', bus }); - jest.spyOn(actor, 'applyFilter').mockReturnValue(Promise.resolve(new Map())); + jest.spyOn(actor, 'createFilter').mockReturnValue(Promise.resolve(new Map())); const expectedUrl = 'http://foo.com'; for (const rootNode of [ @@ -684,7 +684,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { is deactivated and the URL matches the subject of the TREE document that is not a root node`, async() => { const unStrictContext = context.set(KeysExtractLinksTree.strictTraversal, false); actor = new ActorExtractLinksTree({ name: 'actor', bus }); - jest.spyOn(actor, 'applyFilter').mockReturnValue(Promise.resolve(new Map())); + jest.spyOn(actor, 'createFilter').mockReturnValue(Promise.resolve(new Map())); const expectedUrl = [ 'http://foo.com', 'http://bar.com', 'http://example.com', 'http://example.com' ]; const input = stream([ @@ -914,7 +914,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { }); describe('materializeTreeRelation', () => { - it('should materialize a tree Relation when all the raw relation are provided', () => { + it('should materialize a tree:Relation when all the raw relation are provided', () => { const aSubject = 'foo'; const aValue = '0'; const anOperator = SparqlRelationOperator.PrefixRelation; @@ -947,7 +947,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(res).toStrictEqual(expectedTreeRelation); }); - it('should materialize a tree Relation when the remaining item is missing', () => { + it('should materialize a tree:sRelation when the remaining item is missing', () => { const aSubject = 'foo'; const aValue = '0'; const anOperator = SparqlRelationOperator.PrefixRelation; @@ -977,7 +977,7 @@ describe('ActorExtractLinksExtractLinksTree', () => { expect(res).toStrictEqual(expectedTreeRelation); }); - it('should materialize a tree Relation when the value is missing', () => { + it('should materialize a tree:Relation when the value is missing', () => { const aSubject = 'foo'; const anOperator = SparqlRelationOperator.PrefixRelation; const aQuad = DF.quad( diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index 58a239d8f..8ab1ec1b8 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -27,7 +27,7 @@ describe('LogicOperator', () => { expect(or.apply({ domain: solutionDomain, interval })).toStrictEqual(expectedSolutionDomain); }); - it('given an empty domain should be able to add multiple subject range that doesn\'t overlap', () => { + it('given an empty domain should be able to add multiple intervals that doesn\'t overlap', () => { const givenIntervals = [ new SolutionInterval([ 10, 10 ]), new SolutionInterval([ 1, 2 ]), @@ -52,7 +52,7 @@ describe('LogicOperator', () => { expect(solutionDomain.getDomain()).toStrictEqual(expectedDomain); }); - it('given a domain should not add a range that is inside another', () => { + it('given a domain should not add an interval that is inside another', () => { const anOverlappingInterval = new SolutionInterval([ 22, 23 ]); const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); @@ -60,7 +60,8 @@ describe('LogicOperator', () => { expect(newDomain.getDomain()).toStrictEqual(intervals); }); - it('given a domain should create a single domain if all the domain segment are contain into the new range', + it(`given a domain should be able to create a + single domain if all the domain segments are contained into the new range`, () => { const anOverlappingInterval = new SolutionInterval([ -100, 100 ]); const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); @@ -69,7 +70,7 @@ describe('LogicOperator', () => { expect(newDomain.getDomain()).toStrictEqual([ anOverlappingInterval ]); }); - it('given a domain should fuse multiple domain segment if the new range overlap with them', () => { + it('given a domain should be able to fuse multiple domain segment if the new interval overlaps with them', () => { const aNewInterval = new SolutionInterval([ 1, 23 ]); const newDomain = or.apply({ domain: aDomain, interval: aNewInterval }); diff --git a/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts index c59a04bed..fedfb092e 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts @@ -184,7 +184,7 @@ describe('SolutionRange', () => { }); }); - describe('fuseRange', () => { + describe('fuseinterval', () => { it('given an non overlapping range return both range should return the correct range', () => { const aRange: [number, number] = [ 0, 100 ]; const aSolutionInterval = new SolutionInterval(aRange); @@ -192,7 +192,7 @@ describe('SolutionRange', () => { const aSecondRange: [number, number] = [ 101, 200 ]; const aSecondSolutionInterval = new SolutionInterval(aSecondRange); - const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); expect(resp.length).toBe(2); expect(resp[0]).toStrictEqual(aSolutionInterval); @@ -207,7 +207,7 @@ describe('SolutionRange', () => { const aSecondRange: [number, number] = [ 0, 100 ]; const aSecondSolutionInterval = new SolutionInterval(aSecondRange); - const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); expect(resp.length).toBe(1); expect(resp[0]).toStrictEqual(aSolutionInterval); @@ -222,7 +222,7 @@ describe('SolutionRange', () => { const aSecondRange: [number, number] = [ -1, 99 ]; const aSecondSolutionInterval = new SolutionInterval(aSecondRange); - const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); const expectedInterval = new SolutionInterval([ -1, 100 ]); expect(resp.length).toBe(1); @@ -237,7 +237,7 @@ describe('SolutionRange', () => { const aSecondRange: [number, number] = [ -1, 101 ]; const aSecondSolutionInterval = new SolutionInterval(aSecondRange); - const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); const expectedInterval = new SolutionInterval([ -1, 101 ]); expect(resp.length).toBe(1); @@ -252,7 +252,7 @@ describe('SolutionRange', () => { const aSecondRange: [number, number] = [ 0, 101 ]; const aSecondSolutionInterval = new SolutionInterval(aSecondRange); - const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); const expectedInterval = new SolutionInterval([ 0, 101 ]); expect(resp.length).toBe(1); @@ -267,7 +267,7 @@ describe('SolutionRange', () => { const aSecondRange: [number, number] = [ 1, 50 ]; const aSecondSolutionInterval = new SolutionInterval(aSecondRange); - const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); const expectedInterval = new SolutionInterval([ 0, 100 ]); expect(resp.length).toBe(1); @@ -282,7 +282,7 @@ describe('SolutionRange', () => { const aSecondRange: [number, number] = [ 100, 500 ]; const aSecondSolutionInterval = new SolutionInterval(aSecondRange); - const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); const expectedInterval = new SolutionInterval([ 0, 500 ]); expect(resp.length).toBe(1); @@ -294,7 +294,7 @@ describe('SolutionRange', () => { const aSecondSolutionInterval = new SolutionInterval([]); - const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); expect(resp.length).toBe(1); expect(resp[0]).toStrictEqual(new SolutionInterval([])); @@ -306,7 +306,7 @@ describe('SolutionRange', () => { const aSecondRange: [number, number] = [ 101, 200 ]; const aSecondSolutionInterval = new SolutionInterval(aSecondRange); - const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); expect(resp.length).toBe(1); expect(resp[0]).toStrictEqual(aSecondSolutionInterval); @@ -318,7 +318,7 @@ describe('SolutionRange', () => { const aSecondSolutionInterval = new SolutionInterval([]); - const resp = SolutionInterval.fuseRange(aSolutionInterval, aSecondSolutionInterval); + const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); expect(resp.length).toBe(1); expect(resp[0]).toStrictEqual(aSolutionInterval); diff --git a/packages/actor-extract-links-extract-tree/test/filterNode-test.ts b/packages/actor-extract-links-extract-tree/test/filterNode-test.ts index 6e9c701f4..af7d1792a 100644 --- a/packages/actor-extract-links-extract-tree/test/filterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/filterNode-test.ts @@ -13,7 +13,7 @@ import type { ITreeNode, ITreeRelation } from '../lib/TreeMetadata'; const DF = new DataFactory(); describe('filterNode Module', () => { - describe('getFilterExpression ', () => { + describe('getFilterExpression', () => { const treeSubject = 'tree'; it('should return the filter operation if the query has a filter expression', () => { const context = new ActionContext({ @@ -120,7 +120,7 @@ describe('filterNode Module', () => { ); }); - it('should return an empty filter if the TREE Node has no relation', async() => { + it('should return an empty filter if the tree:Node has no relation', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -151,7 +151,7 @@ describe('filterNode Module', () => { ); }); - it('should return an empty filter if the TREE Node has no relation field', async() => { + it('should return an empty filter if the tree:Node has no relation field', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -181,7 +181,7 @@ describe('filterNode Module', () => { ); }); - it('should not accept the relation when the filter is not respected by the relation', async() => { + it('should not accept the relation when the equation cannot be satified', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -368,7 +368,7 @@ describe('filterNode Module', () => { ); }); - it('should accept the relation when the filter argument are not related to the query', async() => { + it('should accept the relation when the filter expression is not related to the relation', async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -406,7 +406,8 @@ describe('filterNode Module', () => { ); }); - it('should accept the relation when there is multiples filters and one is not relevant', async() => { + it('should accept the relation when there is multiples filters and one is not relevant to the relation', + async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -612,7 +613,7 @@ describe('filterNode Module', () => { }); it(`should accept a relation if a first relation is not - satisfiable and a second is because it does not have countraint`, async() => { + satisfiable and a second one is satisfiable because it does not have constraints`, async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -657,7 +658,7 @@ describe('filterNode Module', () => { }); it(`should accept a relation if a first relation is not satisfiable - and a second has a path no present into the filter expression`, async() => { + and a second has a path not present into the filter expression`, async() => { const treeSubject = 'tree'; const node: ITreeNode = { @@ -714,7 +715,7 @@ describe('filterNode Module', () => { expect(groupRelations([])).toStrictEqual([]); }); - it('given relations with the same nodes and path should return one group', () => { + it('given relations with the same nodes and paths should return one group', () => { const relations: ITreeRelation[] = [ { path: 'foo', @@ -734,7 +735,7 @@ describe('filterNode Module', () => { expect(group).toStrictEqual([ relations ]); }); - it('given relations with the same node and path execpt one without path should return two groups', () => { + it('given relations with the same nodes and paths except one without path should return two groups', () => { const relations: ITreeRelation[] = [ { path: 'foo', @@ -771,7 +772,7 @@ describe('filterNode Module', () => { expect(group).toStrictEqual(expectedGroup); }); - it('given relations with no path should return one group', () => { + it('given relations with no path and the same node should return one group', () => { const relations: ITreeRelation[] = [ { node: 'foo', @@ -788,7 +789,7 @@ describe('filterNode Module', () => { expect(group).toStrictEqual([ relations ]); }); - it('given relations with multiple node should return different group', () => { + it('given relations with multiple nodes should return different groups', () => { const relations: ITreeRelation[] = [ { path: 'foo', @@ -827,7 +828,7 @@ describe('filterNode Module', () => { expect(group.sort()).toStrictEqual(expectedGroup.sort()); }); - it('given relations with multiple path should different group', () => { + it('given relations with multiple paths should return different groups', () => { const relations: ITreeRelation[] = [ { path: 'bar', @@ -867,7 +868,7 @@ describe('filterNode Module', () => { expect(group.sort()).toStrictEqual(expectedGroup.sort()); }); - it('given relations with multiple path and group should return different group', () => { + it('given relations with multiple paths and nodes should return different groups', () => { const relations: ITreeRelation[] = [ { path: 'bar', diff --git a/yarn.lock b/yarn.lock index 0e2fbfc76..79d356aaa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4504,7 +4504,7 @@ "@types/node" "*" rdf-js "^4.0.2" -"@types/node@*", "@types/node@17.0.29", "@types/node@>=10.0.0", "@types/node@^10.0.3", "@types/node@^18.0.0", "@types/node@^20.1.0", "@types/node@^8.0.0": +"@types/node@*", "@types/node@>=10.0.0", "@types/node@^10.0.3", "@types/node@^18.0.0", "@types/node@^20.1.0", "@types/node@^8.0.0": version "18.16.16" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.16.tgz#3b64862856c7874ccf7439e6bab872d245c86d8e" integrity sha512-NpaM49IGQQAUlBhHMF82QH80J08os4ZmyF9MkpCzWAGuOHqE4gTEbhzd7L3l5LmWuZ6E0OiC1FweQ4tsiW35+g== @@ -4595,6 +4595,11 @@ resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.2.tgz#38ecb64f01aa0d02b7c8f4222d7c38af6316fef8" integrity sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g== +"@types/ungap__structured-clone@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@types/ungap__structured-clone/-/ungap__structured-clone-0.3.0.tgz#39ef89de1f04bb1920ed99e549b885331295c47d" + integrity sha512-eBWREUhVUGPze+bUW22AgUr05k8u+vETzuYdLYSvWqGTUe0KOf+zVnOB1qER5wMcw8V6D9Ar4DfJmVvD1yu0kQ== + "@types/uritemplate@^0.3.4": version "0.3.4" resolved "https://registry.yarnpkg.com/@types/uritemplate/-/uritemplate-0.3.4.tgz#c7a7f2b7e16b212baa69623183cde641659a4b97" @@ -4706,6 +4711,11 @@ "@typescript-eslint/types" "5.59.8" eslint-visitor-keys "^3.3.0" +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + "@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": version "1.11.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" @@ -12117,6 +12127,20 @@ sparqlalgebrajs@^4.0.0, sparqlalgebrajs@^4.0.2, sparqlalgebrajs@^4.0.3, sparqlal rdf-string "^1.6.0" sparqljs "^3.6.1" +sparqlalgebrajs@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sparqlalgebrajs/-/sparqlalgebrajs-4.1.0.tgz#fb95e1ab1400772d1bb9246709b2ba062b909282" + integrity sha512-Ir5uE1/25hssTv/hRwl8/S2E2Z/QLPsGkkqkUIUqLXSqPXI3tJTxbwQKtCw0Ki/b3UfxrQQmydP1jpWcTVL+hQ== + dependencies: + "@rdfjs/types" "*" + "@types/sparqljs" "^3.1.3" + fast-deep-equal "^3.1.3" + minimist "^1.2.6" + rdf-data-factory "^1.1.0" + rdf-isomorphic "^1.3.0" + rdf-string "^1.6.0" + sparqljs "^3.6.1" + sparqlee@^2.1.0, sparqlee@^2.3.0: version "2.3.2" resolved "https://registry.yarnpkg.com/sparqlee/-/sparqlee-2.3.2.tgz#84b4836e7de350d519cd5c96e63b1bf1062be939" From 334585d8f812197e160a0df51d32666521b1cd92 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 2 Jun 2023 15:01:28 +0200 Subject: [PATCH 179/189] typo corrected and lint-fix. --- .../lib/ActorExtractLinksTree.ts | 12 +- .../lib/LogicOperator.ts | 6 +- .../lib/SolutionDomain.ts | 3 +- .../lib/SolutionInterval.ts | 3 +- .../lib/SolverInput.ts | 3 +- .../lib/filterNode.ts | 4 +- .../lib/solverUtil.ts | 3 +- .../test/LogicOperator-test.ts | 66 ++--- .../test/SolutionDomain-test.ts | 6 +- .../test/SolutionInterval-test.ts | 278 +++++++++--------- .../test/SolverInput-test.ts | 24 +- .../test/filterNode-test.ts | 56 ++-- .../test/solver-test.ts | 75 +---- 13 files changed, 242 insertions(+), 297 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts index 63c00224b..553626caa 100644 --- a/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts +++ b/packages/actor-extract-links-extract-tree/lib/ActorExtractLinksTree.ts @@ -132,13 +132,15 @@ export class ActorExtractLinksTree extends ActorExtractLinks { * A helper function to find all the relations of a TREE document and the possible next nodes to visit. * The next nodes are not guaranteed to have as subject the URL of the current page, * so filtering is necessary afterward. - * @param {RDF.Quad} quad - current quad - * @param {string} url - url of the current node + * @param {RDF.Quad} quad - current quad + * @param {string} url - url of the current node * @param {Map} relationNodeSubject - the tree:Relation id and the subject of the node * @param {[string, string][]} nodeLinks - the subject of the node and the next link - * @param {Set} effectiveTreeDocumentSubject - the subjects of the node that is linked to tree:relation considering the type of node - * @param {Map} relationDescriptions - the tree:Relation id associated with the relation description - * @param {boolean} strictMode - define whether we the subject of the node should match the page URL + * @param {Set} effectiveTreeDocumentSubject - the subjects of the node that is linked to + * tree:relation considering the type of node + * @param {Map} relationDescriptions - the tree:Relation id associated + * with the relation description + * @param {boolean} strictMode - define whether we the subject of the node should match the page URL */ private interpretQuad( quad: RDF.Quad, diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index 8789ab4f6..9dcc8d807 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -58,11 +58,11 @@ export class And implements ILogicOperator { public apply({ interval, domain }: { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { if (Array.isArray(interval)) { - // we check if it is a step interval + // We check if it is a step interval if (interval[0].isOverlapping(interval[1])) { return domain; } - + const testDomain1 = this.apply({ interval: interval[0], domain }); const testDomain2 = this.apply({ interval: interval[1], domain }); // We check which part of the interval can be added to domain. @@ -81,7 +81,7 @@ export class And implements ILogicOperator { return testDomain2; } - // if both can be added we consider the larger domain and use an OR operation + // If both can be added we consider the larger domain and use an OR operation // to add the other part. let intervalRes: SolutionInterval; let newDomain: SolutionDomain; diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index a5cb3162d..4def6dbf7 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -17,6 +17,7 @@ export class SolutionDomain { public getDomain(): SolutionInterval[] { return this.domain; } + /** * Check if two {@link SolutionDomain} are equals. * @param {SolutionDomain} other - the other domain @@ -87,7 +88,7 @@ export class SolutionDomain { } return 1; } - + /** * The invariant contract of the {@link SolutionDomain}. * There should be no overlapping domain segment. diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionInterval.ts b/packages/actor-extract-links-extract-tree/lib/SolutionInterval.ts index 517765d41..ac939adcb 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionInterval.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionInterval.ts @@ -139,7 +139,8 @@ export class SolutionInterval { * Get the interval that intersect the other interval and the subject interval. * @param {SolutionInterval} subjectinterval * @param {SolutionInterval} otherinterval - * @returns {SolutionInterval | undefined} Return the intersection if the interval overlap otherwise return undefined + * @returns {SolutionInterval | undefined} Return the intersection + * if the interval overlap otherwise return undefined */ public static getIntersection(subjectinterval: SolutionInterval, otherinterval: SolutionInterval): SolutionInterval { diff --git a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts index 0bdea5977..59f22c33b 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts @@ -68,7 +68,8 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { /** * Recursively traverse the filter expression and calculate the domain until it get to the current expression. - * It will thrown an error if the expression is badly formated or if it's impossible to get the {@link SolutionInterval}. + * It will thrown an error if the expression is badly formated or + * if it's impossible to get the {@link SolutionInterval}. * @param {Algebra.Expression} filterExpression - * The current filter expression that we are traversing * @param {Variable} variable - The variable targeted inside the filter expression diff --git a/packages/actor-extract-links-extract-tree/lib/filterNode.ts b/packages/actor-extract-links-extract-tree/lib/filterNode.ts index 45f2b122a..762f9e4a9 100644 --- a/packages/actor-extract-links-extract-tree/lib/filterNode.ts +++ b/packages/actor-extract-links-extract-tree/lib/filterNode.ts @@ -82,8 +82,8 @@ export async function filterNode( let inputFilterExpression = calculatedFilterExpressions.get(variable); if (!inputFilterExpression) { inputFilterExpression = new SparlFilterExpressionSolverInput( - // make a deep copy of the filter - Util.mapExpression(filterOperation, {}, undefined), + // Make a deep copy of the filter + Util.mapExpression(filterOperation, {}), variable, ); } diff --git a/packages/actor-extract-links-extract-tree/lib/solverUtil.ts b/packages/actor-extract-links-extract-tree/lib/solverUtil.ts index 4195f151d..363fe9fea 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverUtil.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverUtil.ts @@ -35,7 +35,8 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { * Find the {@link SolutionInterval} of a value and operator which is analogue to an expression. * @param {number} value * @param {SparqlRelationOperator} operator - * @returns {SolutionInterval | undefined} the {@link SolutionInterval} range associated with the value and the operator. + * @returns {SolutionInterval | undefined} the {@link SolutionInterval} range + * associated with the value and the operator. */ export function getSolutionInterval(value: number, operator: SparqlRelationOperator): SolutionInterval | [SolutionInterval, SolutionInterval] | undefined { diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index 8ab1ec1b8..6cceef370 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -61,14 +61,14 @@ describe('LogicOperator', () => { }); it(`given a domain should be able to create a - single domain if all the domain segments are contained into the new range`, - () => { - const anOverlappingInterval = new SolutionInterval([ -100, 100 ]); - const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); + single domain if all the domain segments are contained into the new interval`, + () => { + const anOverlappingInterval = new SolutionInterval([ -100, 100 ]); + const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); - expect(newDomain.getDomain().length).toBe(1); - expect(newDomain.getDomain()).toStrictEqual([ anOverlappingInterval ]); - }); + expect(newDomain.getDomain().length).toBe(1); + expect(newDomain.getDomain()).toStrictEqual([ anOverlappingInterval ]); + }); it('given a domain should be able to fuse multiple domain segment if the new interval overlaps with them', () => { const aNewInterval = new SolutionInterval([ 1, 23 ]); @@ -106,7 +106,7 @@ describe('LogicOperator', () => { aDomain = SolutionDomain.newWithInitialIntervals(new Array(...intervals)); }); - it('should add a range when the domain is empty', () => { + it('should add an interval when the domain is empty', () => { const domain = new SolutionDomain(); const interval = new SolutionInterval([ 0, 1 ]); @@ -115,7 +115,7 @@ describe('LogicOperator', () => { expect(newDomain.getDomain()).toStrictEqual([ interval ]); }); - it('should return an empty domain if there is no intersection with the new range', () => { + it('should return an empty domain if there is no intersection with the new interval', () => { const interval = new SolutionInterval([ -200, -100 ]); const newDomain = and.apply({ interval, domain: aDomain }); @@ -123,7 +123,7 @@ describe('LogicOperator', () => { expect(newDomain.isDomainEmpty()).toBe(true); }); - it('given a new range that is inside a part of the domain should only return the intersection', () => { + it('given a new interval inside a part of the domain should only return the intersection', () => { const interval = new SolutionInterval([ 22, 30 ]); const newDomain = and.apply({ interval, domain: aDomain }); @@ -131,7 +131,7 @@ describe('LogicOperator', () => { expect(newDomain.getDomain()).toStrictEqual([ interval ]); }); - it('given a new range that intersect a part of the domain should only return the intersection', () => { + it('given a new interval that intersects part of the domain should only return the intersection', () => { const interval = new SolutionInterval([ 19, 25 ]); const expectedDomain = [ @@ -143,7 +143,7 @@ describe('LogicOperator', () => { expect(newDomain.getDomain()).toStrictEqual(expectedDomain); }); - it('given a new range that intersect multiple part of the domain should only return the intersections', () => { + it('given a new interval that intersect multiple part of the domain should only return the intersections', () => { const interval = new SolutionInterval([ -2, 25 ]); const expectedDomain = [ @@ -158,21 +158,7 @@ describe('LogicOperator', () => { expect(newDomain.getDomain()).toStrictEqual(expectedDomain); }); - it('given an empty domain and a last operator and should return an empty domain', () => { - const interval = new SolutionInterval([ -2, 25 ]); - const anotherIntervalNonOverlapping = new SolutionInterval([ 2_000, 3_000 ]); - - let newDomain = and.apply({ interval, domain: aDomain }); - newDomain = and.apply({ interval: anotherIntervalNonOverlapping, domain: newDomain }); - - expect(newDomain.isDomainEmpty()).toBe(true); - - newDomain = and.apply({ interval, domain: newDomain }); - - expect(newDomain.isDomainEmpty()).toBe(true); - }); - - it('Given an empty domain and two ranges to add that are overlapping the domain should remain empty', () => { + it('given an empty domain and two intervals that are overlapping the domain should remain empty', () => { const domain = new SolutionDomain(); const interval1 = new SolutionInterval([ 0, 2 ]); const interval2 = new SolutionInterval([ 1, 2 ]); @@ -182,7 +168,7 @@ describe('LogicOperator', () => { expect(newDomain.isDomainEmpty()).toBe(true); }); - it('Given a domain and two ranges to add that are overlapping the domain should remain empty', () => { + it('given a domain and two intervals that are overlapping the domain should remain empty', () => { const interval1 = new SolutionInterval([ 0, 2 ]); const interval2 = new SolutionInterval([ 1, 2 ]); @@ -191,8 +177,8 @@ describe('LogicOperator', () => { expect(newDomain.equal(aDomain)).toBe(true); }); - it(`Given a domain and two ranges to add that are not overlapping and where those - two interval don't overlap with the domain should return the initial domain`, () => { + it(`given a domain and two intervals that are not overlapping and also + don't overlap with the domain should return the initial domain`, () => { const interval1 = new SolutionInterval([ -100, -50 ]); const interval2 = new SolutionInterval([ -25, -23 ]); @@ -201,8 +187,8 @@ describe('LogicOperator', () => { expect(newDomain.getDomain()).toStrictEqual(aDomain.getDomain()); }); - it(`Given a domain and two ranges to add that not overlapping and where the first - one is overlap with the domain then should return a new valid domain`, () => { + it(`given a domain and two intervals that are not overlapping and where the first + one is overlapping with the domain then should return a valid new domain`, () => { const interval1 = new SolutionInterval([ 1, 3 ]); const interval2 = new SolutionInterval([ -25, -23 ]); @@ -211,8 +197,8 @@ describe('LogicOperator', () => { expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); - it(`Given a domain and two ranges to add that not overlapping and where the second - one is overlap with the domain then should return a new valid domain`, () => { + it(`given a domain and two intervals that are not overlapping and where the second + one is overlapping with the domain then should return a valid new domain`, () => { const interval1 = new SolutionInterval([ -25, -23 ]); const interval2 = new SolutionInterval([ 1, 3 ]); @@ -221,8 +207,9 @@ describe('LogicOperator', () => { expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); - it(`Given a domain and two ranges to add that not overlapping and where the both are - overlap and the first one is more overlapping than the second with the domain then should return a new valid domain`, () => { + it(`given a domain and two intervals that are not overlapping and where both are + overlapping with the domain but the first one is more overlapping than the second in relation to the domain + then should return a valid new domain`, () => { const interval1 = new SolutionInterval([ 2, 70 ]); const interval2 = new SolutionInterval([ 1, 1 ]); @@ -236,8 +223,9 @@ describe('LogicOperator', () => { expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); - it(`Given a domain and two ranges to add that not overlapping and where the both are overlap and the second - one is more overlapping than the second with the domain then should return a new valid domain`, () => { + it(`given a domain and two intervals that are not overlapping and where both are + overlapping with the domain but the second one is more overlapping than the second in relation to the domain + then should return a valid new domain`, () => { const interval1 = new SolutionInterval([ 1, 1 ]); const interval2 = new SolutionInterval([ 2, 70 ]); @@ -260,7 +248,7 @@ describe('LogicOperator', () => { expect(() => operatorFactory(LogicOperatorSymbol.Not)).toThrow(); }); - it('Given an Or and an And operator should return an LogicOperator', () => { + it('Given an Or or an And operator should return an LogicOperator', () => { for (const operator of [ LogicOperatorSymbol.And, LogicOperatorSymbol.Or ]) { expect(operatorFactory(operator).operatorName()).toBe(operator); } diff --git a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts index 5d4526043..935c9b16a 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionDomain-test.ts @@ -16,7 +16,7 @@ describe('SolutionDomain', () => { expect(domain1.equal(domain2)).toBe(true); }); - it('should return not equal when two domains have the a different interval', () => { + it('should return not equal when two domains have a different interval', () => { const domain1 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 0, 1 ])); const domain2 = SolutionDomain.newWithInitialIntervals(new SolutionInterval([ 0, 2 ])); expect(domain1.equal(domain2)).toBe(false); @@ -92,7 +92,7 @@ describe('SolutionDomain', () => { expect(solutionDomain.getDomain()[0]).toStrictEqual(solutionRange); }); - it('should create a solution domain with multiple initial value', () => { + it('should create a solution domain with multiple initial values', () => { const solutionIntervals = [ new SolutionInterval([ 0, 1 ]), new SolutionInterval([ 2, 3 ]), @@ -105,7 +105,7 @@ describe('SolutionDomain', () => { expect(solutionDomain.getDomain()).toStrictEqual(solutionIntervals); }); - it('should throw an error when creating a solution domain with multiple intervals overlaping', () => { + it('should throw an error when creating a solution domain with multiple intervals overlapping', () => { const solutionIntervals = [ new SolutionInterval([ 0, 1 ]), new SolutionInterval([ 2, 3 ]), diff --git a/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts b/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts index fedfb092e..860fdacb2 100644 --- a/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolutionInterval-test.ts @@ -3,132 +3,135 @@ import { SolutionInterval } from '../lib//SolutionInterval'; const nextUp = require('ulp').nextUp; const nextDown = require('ulp').nextDown; -describe('SolutionRange', () => { +describe('Solutioninterval', () => { describe('constructor', () => { it('should have the right parameters when building', () => { - const aRange: [number, number] = [ 0, 1 ]; - const solutionRange = new SolutionInterval(aRange); + const ainterval: [number, number] = [ 0, 1 ]; + const solutioninterval = new SolutionInterval(ainterval); - expect(solutionRange.lower).toBe(aRange[0]); - expect(solutionRange.upper).toBe(aRange[1]); + expect(solutioninterval.lower).toBe(ainterval[0]); + expect(solutioninterval.upper).toBe(ainterval[1]); + expect(solutioninterval.isEmpty).toBe(false); }); it('should not throw an error when the domain is unitary', () => { - const aRange: [number, number] = [ 0, 0 ]; - const solutionRange = new SolutionInterval(aRange); + const ainterval: [number, number] = [ 0, 0 ]; + const solutioninterval = new SolutionInterval(ainterval); - expect(solutionRange.lower).toBe(aRange[0]); - expect(solutionRange.upper).toBe(aRange[1]); + expect(solutioninterval.lower).toBe(ainterval[0]); + expect(solutioninterval.upper).toBe(ainterval[1]); + expect(solutioninterval.isEmpty).toBe(false); }); - it('should have throw an error when the first element of the range is greater than the second', () => { - const aRange: [number, number] = [ 1, 0 ]; - expect(() => { new SolutionInterval(aRange); }).toThrow(RangeError); + it('should throw an error when the first element of the interval is greater than the second', () => { + const ainterval: [number, number] = [ 1, 0 ]; + expect(() => { new SolutionInterval(ainterval); }).toThrow(RangeError); }); }); describe('isOverlaping', () => { - it('should return true when the solution range have the same range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it('should return true when the solution interval has the same interval', () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ 0, 100 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 0, 100 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); }); - it('should return true when the other range start before the subject range and end inside the subject range', - () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it(`should return true when the other interval starts before the subject interval and + the end is inside the subject interval`, () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ -1, 99 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ -1, 99 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); - expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); - }); + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); + }); - it('should return true when the other range start before the subject range and end after the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it(`should return true when the other interval starts before the subject interval and + end after the subject interval`, () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ -1, 101 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ -1, 101 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); }); - it('should return true when the other range start at the subject range and end after the range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it('should return true when the other interval starts on the subject interval and end after the interval', () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ 0, 101 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 0, 101 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); }); - it('should return true when the other range start inside the current range and end inside the current range', - () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it(`should return true when the other interval starts inside the current interval and + end inside the current interval`, () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ 1, 50 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 1, 50 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); - expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); - }); + expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); + }); - it('should return true when the other range start at the end of the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it('should return true when the other interval starts at the end of the subject interval', () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ 100, 500 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 100, 500 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(true); }); - it('should return false when the other range is located before the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it('should return false when the other interval is located before the subject interval', () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ -50, -20 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ -50, -20 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(false); }); - it('should return false when the other range is located after the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it('should return false when the other interval is located after the subject interval', () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ 101, 200 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 101, 200 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(false); }); - it('should return false when the subject range is empty', () => { + it('should return false when the subject interval is empty', () => { const aSolutionInterval = new SolutionInterval([]); - const aSecondRange: [number, number] = [ 101, 200 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 101, 200 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(false); }); - it('should return false when the other range is empty and the subject range is not', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it('should return false when the other interval is empty and the subject interval is not', () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); const aSecondSolutionInterval = new SolutionInterval([]); expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(false); }); - it('should return false when the other range and the subject range are empty', () => { + it('should return false when the other interval and the subject interval are empty', () => { const aSolutionInterval = new SolutionInterval([]); const aSecondSolutionInterval = new SolutionInterval([]); @@ -136,46 +139,47 @@ describe('SolutionRange', () => { expect(aSolutionInterval.isOverlapping(aSecondSolutionInterval)).toBe(false); }); }); + describe('isInside', () => { - it('should return true when the other range is inside the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it('should return true when the other interval is inside the subject interval', () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ 1, 50 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 1, 50 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); expect(aSolutionInterval.isInside(aSecondSolutionInterval)).toBe(true); }); - it('should return false when the other range is not inside the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it('should return false when the other interval is not inside the subject interval', () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ -1, 50 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ -1, 50 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); expect(aSolutionInterval.isInside(aSecondSolutionInterval)).toBe(false); }); - it('should return false when the other range is empty and not the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it('should return false when the other interval is empty and not the subject interval', () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); const aSecondSolutionInterval = new SolutionInterval([]); expect(aSolutionInterval.isInside(aSecondSolutionInterval)).toBe(false); }); - it('should return false when the subject range is empty and not the other range', () => { + it('should return false when the subject interval is empty and not the other interval', () => { const aSolutionInterval = new SolutionInterval([]); - const aSecondRange: [number, number] = [ -1, 50 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ -1, 50 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); expect(aSolutionInterval.isInside(aSecondSolutionInterval)).toBe(false); }); - it('should return false when the subject range and the other range are empty', () => { + it('should return false when the subject interval and the other interval are empty', () => { const aSolutionInterval = new SolutionInterval([]); const aSecondSolutionInterval = new SolutionInterval([]); @@ -185,12 +189,12 @@ describe('SolutionRange', () => { }); describe('fuseinterval', () => { - it('given an non overlapping range return both range should return the correct range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it('given an non overlapping interval should return both input intervals', () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ 101, 200 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 101, 200 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); @@ -199,13 +203,13 @@ describe('SolutionRange', () => { expect(resp[1]).toStrictEqual(aSecondSolutionInterval); }); - it('given an overlapping range where the solution range have the same range should return the correct range', + it('given an overlapping interval where the solution interval have the same interval should return that interval', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ 0, 100 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 0, 100 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); @@ -214,13 +218,13 @@ describe('SolutionRange', () => { expect(resp[0]).toStrictEqual(aSecondSolutionInterval); }); - it(`given an overlapping range where the other range start before the subject range and end - inside the subject range should return the correct range`, () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it(`given an overlapping interval where the other interval starts before the subject interval and ends + inside the subject interval should return the correct interval`, () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ -1, 99 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ -1, 99 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); @@ -229,13 +233,13 @@ describe('SolutionRange', () => { expect(resp[0]).toStrictEqual(expectedInterval); }); - it(`given an overlapping range where the other range start before the subject range - and end after the subject range should return the correct range`, () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it(`given an overlapping interval where the other interval starts before the subject interval + and ends after the subject interval should return the correct interval`, () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ -1, 101 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ -1, 101 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); @@ -244,13 +248,13 @@ describe('SolutionRange', () => { expect(resp[0]).toStrictEqual(expectedInterval); }); - it(`given an overlapping range where the other range start at the subject range and - end after the range should return the correct range`, () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it(`given an overlapping interval where the other interval starts on the subject interval and + ends after the interval should return the correct interval`, () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ 0, 101 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 0, 101 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); @@ -259,13 +263,13 @@ describe('SolutionRange', () => { expect(resp[0]).toStrictEqual(expectedInterval); }); - it(`given an overlapping range where the other range start inside the current range and - end inside the current range should return the correct range`, () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it(`given an overlapping interval where the other interval starts inside the current interval and + ends inside the current interval should return the correct interval`, () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ 1, 50 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 1, 50 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); @@ -274,13 +278,13 @@ describe('SolutionRange', () => { expect(resp[0]).toStrictEqual(expectedInterval); }); - it(`given an overlapping range where the other range start at the end - of the subject range should return the correct range`, () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it(`given an overlapping interval where the other interval starts at the end + of the subject interval should return the correct interval`, () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); - const aSecondRange: [number, number] = [ 100, 500 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 100, 500 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); @@ -289,7 +293,7 @@ describe('SolutionRange', () => { expect(resp[0]).toStrictEqual(expectedInterval); }); - it('given two empty ranges should return an empty range', () => { + it('given two empty intervals should return an empty interval', () => { const aSolutionInterval = new SolutionInterval([]); const aSecondSolutionInterval = new SolutionInterval([]); @@ -300,11 +304,11 @@ describe('SolutionRange', () => { expect(resp[0]).toStrictEqual(new SolutionInterval([])); }); - it('given an empty subject range and an non empty other range should return the second range', () => { + it('given an empty subject interval and a non-empty other interval should return the second interval', () => { const aSolutionInterval = new SolutionInterval([]); - const aSecondRange: [number, number] = [ 101, 200 ]; - const aSecondSolutionInterval = new SolutionInterval(aSecondRange); + const aSecondinterval: [number, number] = [ 101, 200 ]; + const aSecondSolutionInterval = new SolutionInterval(aSecondinterval); const resp = SolutionInterval.fuseinterval(aSolutionInterval, aSecondSolutionInterval); @@ -312,9 +316,9 @@ describe('SolutionRange', () => { expect(resp[0]).toStrictEqual(aSecondSolutionInterval); }); - it('given an empty other range and an non empty subject range should return the subject range', () => { - const aRange: [number, number] = [ 0, 100 ]; - const aSolutionInterval = new SolutionInterval(aRange); + it('given an empty other interval and a non-empty subject interval should returns the subject interval', () => { + const ainterval: [number, number] = [ 0, 100 ]; + const aSolutionInterval = new SolutionInterval(ainterval); const aSecondSolutionInterval = new SolutionInterval([]); @@ -326,7 +330,7 @@ describe('SolutionRange', () => { }); describe('inverse', () => { - it('given an infinite solution range it should return an empty range', () => { + it('given an infinite solution interval it should return an empty interval', () => { const aSolutionInterval = new SolutionInterval([ Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY, @@ -338,7 +342,7 @@ describe('SolutionRange', () => { expect(resp[0]).toStrictEqual(new SolutionInterval([])); }); - it('given a range with an infinite upper bound should return a new range', () => { + it('given an interval with an infinite upper-bound should return a new interval', () => { const aSolutionInterval = new SolutionInterval([ 21, Number.POSITIVE_INFINITY, @@ -352,7 +356,7 @@ describe('SolutionRange', () => { expect(resp[0]).toStrictEqual(expectedInterval); }); - it('given a range with an infinite lower bound should return a new range', () => { + it('given an interval with an infinite lower-bound should return a new interval', () => { const aSolutionInterval = new SolutionInterval([ Number.NEGATIVE_INFINITY, -21, @@ -366,7 +370,7 @@ describe('SolutionRange', () => { expect(resp[0]).toStrictEqual(expectedInterval); }); - it('given a range that is not unitary and doesn\t have infinite bound should return 2 ranges', () => { + it('given an interval that is not unitary and doesn\t have infinite bound should return 2 intervals', () => { const aSolutionInterval = new SolutionInterval([ -33, 21, @@ -383,7 +387,7 @@ describe('SolutionRange', () => { expect(resp).toStrictEqual(expectedInterval); }); - it('given an empty solution range it should return an infinite range', () => { + it('given an empty solution interval should return an infinite interval', () => { const aSolutionInterval = new SolutionInterval([]); const resp = aSolutionInterval.inverse(); @@ -397,7 +401,7 @@ describe('SolutionRange', () => { }); describe('getIntersection', () => { - it('given two range that doesn\'t overlap should return no intersection', () => { + it('given two interval that doesn\'t overlap should return no intersection', () => { const aSolutionInterval = new SolutionInterval([ 0, 20 ]); const aSecondSolutionInterval = new SolutionInterval([ 30, 40 ]); @@ -408,7 +412,7 @@ describe('SolutionRange', () => { expect(nonOverlappingInterval.upper).toBe(Number.NaN); }); - it('given two range when one is inside the other should return the range at the inside', () => { + it('given two interval where one is inside the other should return the interval at the inside', () => { const aSolutionInterval = new SolutionInterval([ 0, 20 ]); const aSecondSolutionInterval = new SolutionInterval([ 5, 10 ]); @@ -416,7 +420,7 @@ describe('SolutionRange', () => { .toStrictEqual(aSecondSolutionInterval); }); - it('given two range when they overlap should return the intersection', () => { + it('given two intervals overlapping should return the intersection', () => { const aSolutionInterval = new SolutionInterval([ 0, 20 ]); const aSecondSolutionInterval = new SolutionInterval([ 5, 80 ]); diff --git a/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts b/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts index 4abfe900e..0ff1f03b0 100644 --- a/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts @@ -87,7 +87,7 @@ describe('SolverInput', () => { }); it(`should return the right solver expression given a - well formated TREE relation with a supported type and operator`, () => { + well-formed TREE relation with a supported type and operator`, () => { const expectedSolutionDomain = new SolutionInterval([ 5, 5 ]); @@ -154,7 +154,7 @@ describe('SolverInput', () => { expect(TreeRelationSolverInput.convertTreeRelationToSolverExpression(relation, variable)).toBeUndefined(); }); - it(`should return undefined given a relation with + it(`should return undefined given a tree:Relation with a term containing an incompatible value in relation to its value type`, () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, @@ -217,7 +217,7 @@ describe('SolverInput', () => { jest.spyOn(SparlFilterExpressionSolverInput, 'recursifResolve') .mockRestore(); }); - it('should return the solution domain if it is valid', () => { + it('should return the solution domain if the filter expression is valid', () => { const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals( new SolutionInterval([ 0, 1 ]), ); @@ -340,7 +340,7 @@ describe('SolverInput', () => { } }); - it('given an algebra expression without a variable than should return an misformated error', () => { + it('given an algebra expression without a variable then should return an misformated error', () => { const expression: Algebra.Expression = { type: Algebra.types.EXPRESSION, expressionType: Algebra.expressionTypes.OPERATOR, @@ -394,7 +394,7 @@ describe('SolverInput', () => { .toBeInstanceOf(MisformatedExpressionError); }); - it(`given an algebra expression with a litteral containing an invalid datatype than + it(`given an algebra expression with a literal containing an invalid datatype than should return an unsupported datatype error`, () => { const variable = 'x'; @@ -422,8 +422,8 @@ describe('SolverInput', () => { .toBeInstanceOf(UnsupportedDataTypeError); }); - it(`given an algebra expression with a litteral containing a - literal that cannot be converted into number should return an unsupported datatype error`, () => { + it(`given an algebra expression with a literal containing a + literal that cannot be converted into a number should return an unsupported datatype error`, () => { const variable = 'x'; const expression: Algebra.Expression = { type: Algebra.types.EXPRESSION, @@ -467,7 +467,7 @@ describe('SolverInput', () => { mock.mockRestore(); }); - it('given an algebra expression with two logicals operators should return the valid solution domain', () => { + it('given an algebra expression with two logical operators should return the valid solution domain', () => { const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( ?x=2 && ?x<5) @@ -583,7 +583,7 @@ describe('SolverInput', () => { expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); - it(`given an algebra expression with two logicals operators with a double negation should + it(`given an algebra expression with two logical operators with a double negation should return the valid solution domain`, () => { const expression = translate(` SELECT * WHERE { ?x ?y ?z @@ -618,7 +618,7 @@ describe('SolverInput', () => { expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); - it(`given an algebra expression with two logicals operators + it(`given an algebra expression with two logical operators that cannot be satified should return an empty domain`, () => { const expression = translate(` SELECT * WHERE { ?x ?y ?z @@ -655,8 +655,8 @@ describe('SolverInput', () => { expect(resp.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); - it(`given an algebra expression with two logicals operators that are triple negated - should return the valid solution domain`, () => { + it(`given an algebra expression with two logical operators that are triple negated + should returns the valid solution domain`, () => { const expression = translate(` SELECT * WHERE { ?x ?y ?z FILTER( !(!(!(?x=2 && ?x<5)))) diff --git a/packages/actor-extract-links-extract-tree/test/filterNode-test.ts b/packages/actor-extract-links-extract-tree/test/filterNode-test.ts index af7d1792a..55f11b7ec 100644 --- a/packages/actor-extract-links-extract-tree/test/filterNode-test.ts +++ b/packages/actor-extract-links-extract-tree/test/filterNode-test.ts @@ -407,43 +407,43 @@ describe('filterNode Module', () => { }); it('should accept the relation when there is multiples filters and one is not relevant to the relation', - async() => { - const treeSubject = 'tree'; + async() => { + const treeSubject = 'tree'; - const node: ITreeNode = { - identifier: treeSubject, - relation: [ - { - node: 'http://bar.com', - path: 'http://example.com#path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + const node: ITreeNode = { + identifier: treeSubject, + relation: [ + { + node: 'http://bar.com', + path: 'http://example.com#path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + type: SparqlRelationOperator.EqualThanRelation, }, - type: SparqlRelationOperator.EqualThanRelation, - }, - ], - }; - const query = translate(` + ], + }; + const query = translate(` SELECT ?o WHERE { ex:foo ex:path ?o. FILTER((?p=88 && ?p>3) || true) } `, { prefixes: { ex: 'http://example.com#' }}); - const context = new ActionContext({ - [KeysInitQuery.query.name]: query, - }); + const context = new ActionContext({ + [KeysInitQuery.query.name]: query, + }); - const result = await filterNode( - node, - context, - isBooleanExpressionTreeRelationFilterSolvable, - ); + const result = await filterNode( + node, + context, + isBooleanExpressionTreeRelationFilterSolvable, + ); - expect(result).toStrictEqual( - new Map([[ 'http://bar.com', true ]]), - ); - }); + expect(result).toStrictEqual( + new Map([[ 'http://bar.com', true ]]), + ); + }); it('should accept the relation when the filter compare two constants', async() => { const treeSubject = 'tree'; diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index c7a1cf50d..85143db25 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -177,8 +177,8 @@ describe('solver function', () => { }); }); - describe('getSolutionRange', () => { - it('given a boolean compatible RelationOperator and a value should return a valid SolutionRange', () => { + describe('getSolutionInterval', () => { + it('given a number compatible RelationOperator and a value should return a valid SolutionInterval', () => { const value = -1; const testTable: [SparqlRelationOperator, SolutionInterval][] = [ [ @@ -203,8 +203,8 @@ describe('solver function', () => { ], ]; - for (const [ operator, expectedRange ] of testTable) { - expect(getSolutionInterval(value, operator)).toStrictEqual(expectedRange); + for (const [ operator, expectedinterval ] of testTable) { + expect(getSolutionInterval(value, operator)).toStrictEqual(expectedinterval); } }); @@ -307,7 +307,7 @@ describe('solver function', () => { expect(areTypesCompatible(expressions)).toBe(false); }); - it('should return false when one type is not identical and the other are number', () => { + it('should return false when one type is not identical and the others are number', () => { const expressions: ISolverExpression[] = [ { variable: 'a', @@ -363,33 +363,6 @@ describe('solver function', () => { }); it('should return true given a relation and a filter operation where types are not compatible', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: 'false', - term: DF.literal('false', DF.namedNode('http://www.w3.org/2001/XMLSchema#boolean')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( !(?x=2 && ?x>5) || ?x < 88.3) - }`).input.expression; - - const variable = 'x'; - - const inputs = [ - new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable), - ]; - - expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); - }); - - it('should return false given a relation and a filter operation where types are not compatible', () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, remainingItems: 10, @@ -416,7 +389,7 @@ describe('solver function', () => { expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); - it('should return true when the solution range is not valid of the relation', () => { + it('should return true when the solution interval of the relation is not valid', () => { const relation: ITreeRelation = { type: SparqlRelationOperator.GeospatiallyContainsRelation, remainingItems: 10, @@ -515,7 +488,7 @@ describe('solver function', () => { expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); - it('should return false when the filter expression has no solution ', () => { + it('should return false when the filter expression has no solution', () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, remainingItems: 10, @@ -629,7 +602,7 @@ describe('solver function', () => { }); it(`should return false when there is no solution for the filter - expression with two expressions and the relation`, () => { + expression with two expressions and a relation`, () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, remainingItems: 10, @@ -682,32 +655,6 @@ describe('solver function', () => { expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); - it('should accept the link given that recursifResolve return a SyntaxError', () => { - const relation: ITreeRelation = { - type: SparqlRelationOperator.EqualThanRelation, - remainingItems: 10, - path: 'ex:path', - value: { - value: '5', - term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), - }, - node: 'https://www.example.be', - }; - - const filterExpression = translate(` - SELECT * WHERE { ?x ?y ?z - FILTER( ?x = 2 && ?x > 5 && ?x > 88.3) - }`).input.expression; - - const variable = 'x'; - const inputs = [ - new TreeRelationSolverInput(relation, variable), - new SparlFilterExpressionSolverInput(filterExpression, variable), - ]; - - expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(false); - }); - it('should accept the link if the data type of the filter is unsupported', () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, @@ -792,7 +739,7 @@ describe('solver function', () => { .toBe(false); }); - it('should refuse the link with a a bounded TREE node', + it('should refuse the link with an unsatisfiable bounded TREE node', () => { const relation: ITreeRelation = { type: SparqlRelationOperator.GreaterThanRelation, @@ -889,7 +836,7 @@ describe('solver function', () => { expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); - it(`should accept the link with a solvable boolean expression with a false boolean statement`, () => { + it(`should accept the link with a solvable boolean expression and a false boolean statement linked with an OR operator`, () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, remainingItems: 10, @@ -1092,7 +1039,7 @@ describe('solver function', () => { expect(() => isBooleanExpressionTreeRelationFilterSolvable(inputs)).toThrow(InvalidExpressionSystem); }); - it('should throw an error if there is no tree relation', () => { + it('should throw an error if there is no tree:Relation', () => { const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z FILTER(true) From 87be5e76c4f6646962d26260db2e9775c5fc4488 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 2 Jun 2023 18:47:03 +0200 Subject: [PATCH 180/189] eslint in relation to node 14 deleted as it not supported anymore. --- .eslintrc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 219681bcf..295d714d8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -76,7 +76,6 @@ module.exports = { 'unicorn/consistent-destructuring': 'off', 'unicorn/no-array-callback-reference': 'off', 'unicorn/no-new-array': 'off', - 'unicorn/prefer-at': 'off', // not compatible with node v14 // TS '@typescript-eslint/lines-between-class-members': ['error', { exceptAfterSingleLine: true }], From a954b2a98073aa49751fde7b7a92f78329664a08 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Sat, 3 Jun 2023 19:19:52 +0200 Subject: [PATCH 181/189] Useless dependencies deleted. --- .../actor-extract-links-extract-tree/package.json | 6 +----- yarn.lock | 12 +----------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/package.json b/packages/actor-extract-links-extract-tree/package.json index fcb55d0bd..e1c3f49d6 100644 --- a/packages/actor-extract-links-extract-tree/package.json +++ b/packages/actor-extract-links-extract-tree/package.json @@ -37,15 +37,11 @@ "@comunica/context-entries-link-traversal": "^0.1.1", "@comunica/core": "^2.7.0", "@comunica/types-link-traversal": "^0.1.0", - "@types/ungap__structured-clone": "^0.3.0", - "@ungap/structured-clone": "^1.2.0", "rdf-data-factory": "^1.1.1", "rdf-store-stream": "^1.3.0", "rdf-string": "^1.6.1", "sparqlalgebrajs": "^4.1.0", - "sparqlee": "^2.1.0", - "ulp": "^1.0.1", - "uuid": "^9.0.0" + "ulp": "^1.0.1" }, "scripts": { "build": "npm run build:ts && npm run build:components", diff --git a/yarn.lock b/yarn.lock index 79d356aaa..14a3f0bef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4595,11 +4595,6 @@ resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.2.tgz#38ecb64f01aa0d02b7c8f4222d7c38af6316fef8" integrity sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g== -"@types/ungap__structured-clone@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@types/ungap__structured-clone/-/ungap__structured-clone-0.3.0.tgz#39ef89de1f04bb1920ed99e549b885331295c47d" - integrity sha512-eBWREUhVUGPze+bUW22AgUr05k8u+vETzuYdLYSvWqGTUe0KOf+zVnOB1qER5wMcw8V6D9Ar4DfJmVvD1yu0kQ== - "@types/uritemplate@^0.3.4": version "0.3.4" resolved "https://registry.yarnpkg.com/@types/uritemplate/-/uritemplate-0.3.4.tgz#c7a7f2b7e16b212baa69623183cde641659a4b97" @@ -4711,11 +4706,6 @@ "@typescript-eslint/types" "5.59.8" eslint-visitor-keys "^3.3.0" -"@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== - "@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": version "1.11.6" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" @@ -12141,7 +12131,7 @@ sparqlalgebrajs@^4.1.0: rdf-string "^1.6.0" sparqljs "^3.6.1" -sparqlee@^2.1.0, sparqlee@^2.3.0: +sparqlee@^2.3.0: version "2.3.2" resolved "https://registry.yarnpkg.com/sparqlee/-/sparqlee-2.3.2.tgz#84b4836e7de350d519cd5c96e63b1bf1062be939" integrity sha512-AKkS3O8Il35nJ7gwP2U4S8yEN2ahrFewI8LG/3KDPBoLBHwMrlcojANSKep4ogKJBR6BCvYZipSjP27ueyBvWA== From 356ad75fe2627b881c5d67a7da60fc06144d2590 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Tue, 6 Jun 2023 17:36:59 +0200 Subject: [PATCH 182/189] index.mjs ignored --- .gitignore | 1 + index.mjs | 32 -------------------------------- 2 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 index.mjs diff --git a/.gitignore b/.gitignore index 706285460..167193d14 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ web-clients/builds componentsjs-error-state.json engines/*/components packages/*/components +index.mjs diff --git a/index.mjs b/index.mjs deleted file mode 100644 index b107e6e8a..000000000 --- a/index.mjs +++ /dev/null @@ -1,32 +0,0 @@ - -import { QueryEngineFactory } from "@comunica/query-sparql-link-traversal"; -import { LoggerPretty } from "@comunica/logger-pretty"; - -const query = ` -PREFIX sosa: -PREFIX xsd: - -SELECT ?s ?t WHERE { - ?s sosa:resultTime ?t. -FILTER(?t<"2020-12-08T22:55:51.000Z"^^xsd:dateTime && ?t>="2020-12-07T04:44:35.000"^^xsd:dateTime) -}`; - -const datasource = "https://tree.linkeddatafragments.org/sytadel/ldes/ais";//"http://localhost:3000/ldes/test"; -const config = "/home/id357/Documents/PhD/coding/comunica-feature-link-traversal/engines/config-query-sparql-link-traversal/config/config-tree.json"; - - -const engine = await new QueryEngineFactory().create({ configPath: config }); -const bindingsStream = await engine.queryBindings(query, - { - sources: [datasource], - lenient: true, - log: new LoggerPretty({ level: 'trace' }) - }); -bindingsStream.on('data', (binding) => { - console.log(binding.toString()); -}); - -bindingsStream.on('error', (error) => { - console.log(error) -}); - From 89cb40b62013ffa605cac0b8e7ad1776997f5342 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 12 Jun 2023 18:47:57 +0200 Subject: [PATCH 183/189] first --- .../lib/LogicOperator.ts | 42 ++++++++++--------- .../lib/SolverInput.ts | 6 +-- .../lib/solver.ts | 2 +- .../test/LogicOperator-test.ts | 34 +++++++-------- 4 files changed, 43 insertions(+), 41 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index 9dcc8d807..4c4e211b5 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -9,8 +9,8 @@ export interface ILogicOperator { /** * Apply the operation on a domain given an interval */ - apply: ({ interval, domain }: - { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }) => SolutionDomain; + apply: ({ intervals, domain }: + { intervals: SolutionInterval | [SolutionInterval, SolutionInterval] | SolutionInterval[]; domain: SolutionDomain }) => SolutionDomain; /** * The name of the operator */ @@ -18,15 +18,17 @@ export interface ILogicOperator { } export class Or implements ILogicOperator { - public apply({ interval, domain }: - { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { - if (Array.isArray(interval)) { - domain = this.apply({ interval: interval[0], domain }); - return this.apply({ interval: interval[1], domain }); + public apply({ intervals: intervals, domain }: + { intervals: SolutionInterval |SolutionInterval[] ; domain: SolutionDomain }): SolutionDomain { + if (Array.isArray(intervals)) { + for (const interval of intervals){ + domain = this.apply({ intervals: interval, domain }); + } + return domain } let newDomain: SolutionInterval[] = []; - let currentInterval = interval; + let currentInterval = intervals; // We iterate over all the domain newDomain = domain.getDomain().filter(el => { @@ -55,16 +57,16 @@ export class Or implements ILogicOperator { } export class And implements ILogicOperator { - public apply({ interval, domain }: - { interval: SolutionInterval | [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { - if (Array.isArray(interval)) { + public apply({ intervals: intervals, domain }: + { intervals: SolutionInterval | SolutionInterval[]| [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { + if (Array.isArray(intervals)) { // We check if it is a step interval - if (interval[0].isOverlapping(interval[1])) { + if (intervals[0].isOverlapping(intervals[1])) { return domain; } - const testDomain1 = this.apply({ interval: interval[0], domain }); - const testDomain2 = this.apply({ interval: interval[1], domain }); + const testDomain1 = this.apply({ intervals: intervals[0], domain }); + const testDomain2 = this.apply({ intervals: intervals[1], domain }); // We check which part of the interval can be added to domain. const cannotAddDomain1 = testDomain1.isDomainEmpty(); const cannotAddDomain2 = testDomain2.isDomainEmpty(); @@ -86,28 +88,28 @@ export class And implements ILogicOperator { let intervalRes: SolutionInterval; let newDomain: SolutionDomain; if (testDomain1.getDomain().length > testDomain2.getDomain().length) { - intervalRes = interval[1]; + intervalRes = intervals[1]; newDomain = testDomain1; } else { - intervalRes = interval[0]; + intervalRes = intervals[0]; newDomain = testDomain2; } return new Or().apply({ - interval: intervalRes, domain: newDomain, + intervals: intervalRes, domain: newDomain, }); } const newDomain: SolutionInterval[] = []; // If the domain is empty then simply add the new range if (domain.getDomain().length === 0) { - newDomain.push(interval); - return SolutionDomain.newWithInitialIntervals(interval); + newDomain.push(intervals); + return SolutionDomain.newWithInitialIntervals(intervals); } // Considering the current domain if there is an intersection // add the intersection to the new domain domain.getDomain().forEach(el => { - const intersection = SolutionInterval.getIntersection(el, interval); + const intersection = SolutionInterval.getIntersection(el, intervals); if (!intersection.isEmpty) { newDomain.push(intersection); } diff --git a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts index 59f22c33b..f8c9dcb3f 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts @@ -88,9 +88,9 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { ) { // In that case we are confronted with a boolean expression if (filterExpression.term.value === 'false') { - domain = logicOperator.apply({ interval: A_FALSE_EXPRESSION, domain }); + domain = logicOperator.apply({ intervals: A_FALSE_EXPRESSION, domain }); } else { - domain = logicOperator.apply({ interval: A_TRUE_EXPRESSION, domain }); + domain = logicOperator.apply({ intervals: A_TRUE_EXPRESSION, domain }); } } else if ( // If it's an array of terms then we should be able to create a solver expression. @@ -113,7 +113,7 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { throw new UnsupportedDataTypeError('The operator is not supported'); } } - domain = logicOperator.apply({ interval: solutionInterval, domain }); + domain = logicOperator.apply({ intervals: solutionInterval, domain }); } } else { // In that case we are traversing the filter expression TREE. diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 710156a8e..4c25ab515 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -46,7 +46,7 @@ export function isBooleanExpressionTreeRelationFilterSolvable(inputs: ISolverInp } for (const interval of intervals) { - domain = AND.apply({ interval, domain }); + domain = AND.apply({ intervals: interval, domain }); } return !domain.isDomainEmpty(); diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index 6cceef370..604eaa429 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -24,7 +24,7 @@ describe('LogicOperator', () => { const interval = new SolutionInterval([ 0, 1 ]); const solutionDomain = new SolutionDomain(); const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals(interval); - expect(or.apply({ domain: solutionDomain, interval })).toStrictEqual(expectedSolutionDomain); + expect(or.apply({ domain: solutionDomain, intervals: interval })).toStrictEqual(expectedSolutionDomain); }); it('given an empty domain should be able to add multiple intervals that doesn\'t overlap', () => { @@ -38,7 +38,7 @@ describe('LogicOperator', () => { let solutionDomain = new SolutionDomain(); givenIntervals.forEach((interval, idx) => { - solutionDomain = or.apply({ domain: solutionDomain, interval }); + solutionDomain = or.apply({ domain: solutionDomain, intervals: interval }); expect(solutionDomain.getDomain().length).toBe(idx + 1); }); @@ -54,7 +54,7 @@ describe('LogicOperator', () => { it('given a domain should not add an interval that is inside another', () => { const anOverlappingInterval = new SolutionInterval([ 22, 23 ]); - const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); + const newDomain = or.apply({ domain: aDomain, intervals: anOverlappingInterval }); expect(newDomain.getDomain().length).toBe(aDomain.getDomain().length); expect(newDomain.getDomain()).toStrictEqual(intervals); @@ -64,7 +64,7 @@ describe('LogicOperator', () => { single domain if all the domain segments are contained into the new interval`, () => { const anOverlappingInterval = new SolutionInterval([ -100, 100 ]); - const newDomain = or.apply({ domain: aDomain, interval: anOverlappingInterval }); + const newDomain = or.apply({ domain: aDomain, intervals: anOverlappingInterval }); expect(newDomain.getDomain().length).toBe(1); expect(newDomain.getDomain()).toStrictEqual([ anOverlappingInterval ]); @@ -72,7 +72,7 @@ describe('LogicOperator', () => { it('given a domain should be able to fuse multiple domain segment if the new interval overlaps with them', () => { const aNewInterval = new SolutionInterval([ 1, 23 ]); - const newDomain = or.apply({ domain: aDomain, interval: aNewInterval }); + const newDomain = or.apply({ domain: aDomain, intervals: aNewInterval }); const expectedResultingDomainInterval = [ new SolutionInterval([ -1, 0 ]), @@ -110,7 +110,7 @@ describe('LogicOperator', () => { const domain = new SolutionDomain(); const interval = new SolutionInterval([ 0, 1 ]); - const newDomain = and.apply({ interval, domain }); + const newDomain = and.apply({ intervals:interval, domain }); expect(newDomain.getDomain()).toStrictEqual([ interval ]); }); @@ -118,7 +118,7 @@ describe('LogicOperator', () => { it('should return an empty domain if there is no intersection with the new interval', () => { const interval = new SolutionInterval([ -200, -100 ]); - const newDomain = and.apply({ interval, domain: aDomain }); + const newDomain = and.apply({ intervals:interval, domain: aDomain }); expect(newDomain.isDomainEmpty()).toBe(true); }); @@ -126,7 +126,7 @@ describe('LogicOperator', () => { it('given a new interval inside a part of the domain should only return the intersection', () => { const interval = new SolutionInterval([ 22, 30 ]); - const newDomain = and.apply({ interval, domain: aDomain }); + const newDomain = and.apply({ intervals: interval, domain: aDomain }); expect(newDomain.getDomain()).toStrictEqual([ interval ]); }); @@ -138,7 +138,7 @@ describe('LogicOperator', () => { new SolutionInterval([ 21, 25 ]), ]; - const newDomain = and.apply({ interval, domain: aDomain }); + const newDomain = and.apply({ intervals: interval, domain: aDomain }); expect(newDomain.getDomain()).toStrictEqual(expectedDomain); }); @@ -153,7 +153,7 @@ describe('LogicOperator', () => { new SolutionInterval([ 21, 25 ]), ]; - const newDomain = and.apply({ interval, domain: aDomain }); + const newDomain = and.apply({ intervals: interval, domain: aDomain }); expect(newDomain.getDomain()).toStrictEqual(expectedDomain); }); @@ -163,7 +163,7 @@ describe('LogicOperator', () => { const interval1 = new SolutionInterval([ 0, 2 ]); const interval2 = new SolutionInterval([ 1, 2 ]); - const newDomain = and.apply({ interval: [ interval1, interval2 ], domain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain }); expect(newDomain.isDomainEmpty()).toBe(true); }); @@ -172,7 +172,7 @@ describe('LogicOperator', () => { const interval1 = new SolutionInterval([ 0, 2 ]); const interval2 = new SolutionInterval([ 1, 2 ]); - const newDomain = and.apply({ interval: [ interval1, interval2 ], domain: aDomain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); expect(newDomain.equal(aDomain)).toBe(true); }); @@ -182,7 +182,7 @@ describe('LogicOperator', () => { const interval1 = new SolutionInterval([ -100, -50 ]); const interval2 = new SolutionInterval([ -25, -23 ]); - const newDomain = and.apply({ interval: [ interval1, interval2 ], domain: aDomain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); expect(newDomain.getDomain()).toStrictEqual(aDomain.getDomain()); }); @@ -192,7 +192,7 @@ describe('LogicOperator', () => { const interval1 = new SolutionInterval([ 1, 3 ]); const interval2 = new SolutionInterval([ -25, -23 ]); - const newDomain = and.apply({ interval: [ interval1, interval2 ], domain: aDomain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals(interval1); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -202,7 +202,7 @@ describe('LogicOperator', () => { const interval1 = new SolutionInterval([ -25, -23 ]); const interval2 = new SolutionInterval([ 1, 3 ]); - const newDomain = and.apply({ interval: [ interval1, interval2 ], domain: aDomain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals(interval2); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -213,7 +213,7 @@ describe('LogicOperator', () => { const interval1 = new SolutionInterval([ 2, 70 ]); const interval2 = new SolutionInterval([ 1, 1 ]); - const newDomain = and.apply({ interval: [ interval1, interval2 ], domain: aDomain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals([ interval2, new SolutionInterval([ 2, 5 ]), @@ -230,7 +230,7 @@ describe('LogicOperator', () => { const interval2 = new SolutionInterval([ 2, 70 ]); - const newDomain = and.apply({ interval: [ interval1, interval2 ], domain: aDomain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals([ interval1, new SolutionInterval([ 2, 5 ]), From 330e79c2e7418498ae9736ab44cc4d32fad560cc Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 12 Jun 2023 19:17:21 +0200 Subject: [PATCH 184/189] more generalized and multi interval function --- .../lib/LogicOperator.ts | 63 +++++++------------ .../lib/SolutionDomain.ts | 8 +-- .../test/LogicOperator-test.ts | 8 +-- 3 files changed, 31 insertions(+), 48 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index 4c4e211b5..7b6329329 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -10,7 +10,7 @@ export interface ILogicOperator { * Apply the operation on a domain given an interval */ apply: ({ intervals, domain }: - { intervals: SolutionInterval | [SolutionInterval, SolutionInterval] | SolutionInterval[]; domain: SolutionDomain }) => SolutionDomain; + { intervals: SolutionInterval | SolutionInterval[]; domain: SolutionDomain }) => SolutionDomain; /** * The name of the operator */ @@ -18,13 +18,13 @@ export interface ILogicOperator { } export class Or implements ILogicOperator { - public apply({ intervals: intervals, domain }: - { intervals: SolutionInterval |SolutionInterval[] ; domain: SolutionDomain }): SolutionDomain { + public apply({ intervals, domain }: + { intervals: SolutionInterval | SolutionInterval[]; domain: SolutionDomain }): SolutionDomain { if (Array.isArray(intervals)) { - for (const interval of intervals){ + for (const interval of intervals) { domain = this.apply({ intervals: interval, domain }); } - return domain + return domain; } let newDomain: SolutionInterval[] = []; @@ -57,47 +57,30 @@ export class Or implements ILogicOperator { } export class And implements ILogicOperator { - public apply({ intervals: intervals, domain }: - { intervals: SolutionInterval | SolutionInterval[]| [SolutionInterval, SolutionInterval]; domain: SolutionDomain }): SolutionDomain { + public apply({ intervals, domain }: + { intervals: SolutionInterval | SolutionInterval[]; domain: SolutionDomain }): SolutionDomain { if (Array.isArray(intervals)) { - // We check if it is a step interval - if (intervals[0].isOverlapping(intervals[1])) { + const domain_intervals = domain.getDomain(); + intervals = intervals.sort(SolutionDomain.sortDomainRangeByLowerBound); + if (SolutionDomain.isThereOverlapInsideDomain(intervals)) { return domain; } - const testDomain1 = this.apply({ intervals: intervals[0], domain }); - const testDomain2 = this.apply({ intervals: intervals[1], domain }); - // We check which part of the interval can be added to domain. - const cannotAddDomain1 = testDomain1.isDomainEmpty(); - const cannotAddDomain2 = testDomain2.isDomainEmpty(); - - if (cannotAddDomain1 && cannotAddDomain2) { - return domain; - } - - if (!cannotAddDomain1 && cannotAddDomain2) { - return testDomain1; + let resulting_interval: SolutionInterval[] = []; + for (const interval of intervals) { + for (const domain_interval of domain_intervals) { + const temp_domain = this.apply({ + intervals: interval, + domain: SolutionDomain.newWithInitialIntervals(domain_interval), + }); + resulting_interval = resulting_interval.concat(temp_domain.getDomain()); + } } - - if (cannotAddDomain1 && !cannotAddDomain2) { - return testDomain2; - } - - // If both can be added we consider the larger domain and use an OR operation - // to add the other part. - let intervalRes: SolutionInterval; - let newDomain: SolutionDomain; - if (testDomain1.getDomain().length > testDomain2.getDomain().length) { - intervalRes = intervals[1]; - newDomain = testDomain1; - } else { - intervalRes = intervals[0]; - newDomain = testDomain2; + let resp_domain = new SolutionDomain(); + for (const interval of resulting_interval) { + resp_domain = new Or().apply({ intervals: interval, domain: resp_domain }); } - - return new Or().apply({ - intervals: intervalRes, domain: newDomain, - }); + return resp_domain; } const newDomain: SolutionInterval[] = []; diff --git a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts index 4def6dbf7..b15582dac 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolutionDomain.ts @@ -65,7 +65,7 @@ export class SolutionDomain { // We keep the domain sorted initialIntervals.sort(SolutionDomain.sortDomainRangeByLowerBound); newSolutionDomain.domain = initialIntervals; - if (newSolutionDomain.isThereOverlapInsideDomain()) { + if (SolutionDomain.isThereOverlapInsideDomain(newSolutionDomain.getDomain())) { throw new RangeError('There is overlap inside the domain.'); } } else if (!initialIntervals.isEmpty) { @@ -94,9 +94,9 @@ export class SolutionDomain { * There should be no overlapping domain segment. * @returns {boolean} whether or not the domain is overlapping */ - private isThereOverlapInsideDomain(): boolean { - for (let i = 0; i < this.domain.length - 1; i++) { - if (this.domain[i].isOverlapping(this.domain[i + 1])) { + public static isThereOverlapInsideDomain(domain: SolutionInterval[]): boolean { + for (let i = 0; i < domain.length - 1; i++) { + if (domain[i].isOverlapping(domain[i + 1])) { return true; } } diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index 604eaa429..cda96fa11 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -110,7 +110,7 @@ describe('LogicOperator', () => { const domain = new SolutionDomain(); const interval = new SolutionInterval([ 0, 1 ]); - const newDomain = and.apply({ intervals:interval, domain }); + const newDomain = and.apply({ intervals: interval, domain }); expect(newDomain.getDomain()).toStrictEqual([ interval ]); }); @@ -118,7 +118,7 @@ describe('LogicOperator', () => { it('should return an empty domain if there is no intersection with the new interval', () => { const interval = new SolutionInterval([ -200, -100 ]); - const newDomain = and.apply({ intervals:interval, domain: aDomain }); + const newDomain = and.apply({ intervals: interval, domain: aDomain }); expect(newDomain.isDomainEmpty()).toBe(true); }); @@ -178,13 +178,13 @@ describe('LogicOperator', () => { }); it(`given a domain and two intervals that are not overlapping and also - don't overlap with the domain should return the initial domain`, () => { + don't overlap with the domain should return an empty domain`, () => { const interval1 = new SolutionInterval([ -100, -50 ]); const interval2 = new SolutionInterval([ -25, -23 ]); const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); - expect(newDomain.getDomain()).toStrictEqual(aDomain.getDomain()); + expect(newDomain.isDomainEmpty()).toBe(true); }); it(`given a domain and two intervals that are not overlapping and where the first From f3b2084ebcc31fd4b32a7301aa61676ac6386528 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 12 Jun 2023 19:22:01 +0200 Subject: [PATCH 185/189] lint-fix --- .../test/LogicOperator-test.ts | 144 +++++++++--------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index cda96fa11..c0ec7dca9 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -13,15 +13,15 @@ describe('LogicOperator', () => { }); describe('apply', () => { const intervals = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 5 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 21, 33 ]), - new SolutionInterval([ 60, 70 ]), + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 5]), + new SolutionInterval([10, 10]), + new SolutionInterval([21, 33]), + new SolutionInterval([60, 70]), ]; const aDomain = SolutionDomain.newWithInitialIntervals(intervals); it('given an empty domain should be able to add an interval', () => { - const interval = new SolutionInterval([ 0, 1 ]); + const interval = new SolutionInterval([0, 1]); const solutionDomain = new SolutionDomain(); const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals(interval); expect(or.apply({ domain: solutionDomain, intervals: interval })).toStrictEqual(expectedSolutionDomain); @@ -29,10 +29,10 @@ describe('LogicOperator', () => { it('given an empty domain should be able to add multiple intervals that doesn\'t overlap', () => { const givenIntervals = [ - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 1, 2 ]), - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 60, 70 ]), + new SolutionInterval([10, 10]), + new SolutionInterval([1, 2]), + new SolutionInterval([-1, 0]), + new SolutionInterval([60, 70]), ]; let solutionDomain = new SolutionDomain(); @@ -43,17 +43,17 @@ describe('LogicOperator', () => { }); const expectedDomain = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 2 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 60, 70 ]), + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 2]), + new SolutionInterval([10, 10]), + new SolutionInterval([60, 70]), ]; expect(solutionDomain.getDomain()).toStrictEqual(expectedDomain); }); it('given a domain should not add an interval that is inside another', () => { - const anOverlappingInterval = new SolutionInterval([ 22, 23 ]); + const anOverlappingInterval = new SolutionInterval([22, 23]); const newDomain = or.apply({ domain: aDomain, intervals: anOverlappingInterval }); expect(newDomain.getDomain().length).toBe(aDomain.getDomain().length); @@ -62,22 +62,22 @@ describe('LogicOperator', () => { it(`given a domain should be able to create a single domain if all the domain segments are contained into the new interval`, - () => { - const anOverlappingInterval = new SolutionInterval([ -100, 100 ]); - const newDomain = or.apply({ domain: aDomain, intervals: anOverlappingInterval }); + () => { + const anOverlappingInterval = new SolutionInterval([-100, 100]); + const newDomain = or.apply({ domain: aDomain, intervals: anOverlappingInterval }); - expect(newDomain.getDomain().length).toBe(1); - expect(newDomain.getDomain()).toStrictEqual([ anOverlappingInterval ]); - }); + expect(newDomain.getDomain().length).toBe(1); + expect(newDomain.getDomain()).toStrictEqual([anOverlappingInterval]); + }); it('given a domain should be able to fuse multiple domain segment if the new interval overlaps with them', () => { - const aNewInterval = new SolutionInterval([ 1, 23 ]); + const aNewInterval = new SolutionInterval([1, 23]); const newDomain = or.apply({ domain: aDomain, intervals: aNewInterval }); const expectedResultingDomainInterval = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 33 ]), - new SolutionInterval([ 60, 70 ]), + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 33]), + new SolutionInterval([60, 70]), ]; expect(newDomain.getDomain().length).toBe(3); @@ -96,11 +96,11 @@ describe('LogicOperator', () => { describe('apply', () => { let aDomain: SolutionDomain = new SolutionDomain(); const intervals = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 5 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 21, 33 ]), - new SolutionInterval([ 60, 70 ]), + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 5]), + new SolutionInterval([10, 10]), + new SolutionInterval([21, 33]), + new SolutionInterval([60, 70]), ]; beforeEach(() => { aDomain = SolutionDomain.newWithInitialIntervals(new Array(...intervals)); @@ -108,15 +108,15 @@ describe('LogicOperator', () => { it('should add an interval when the domain is empty', () => { const domain = new SolutionDomain(); - const interval = new SolutionInterval([ 0, 1 ]); + const interval = new SolutionInterval([0, 1]); const newDomain = and.apply({ intervals: interval, domain }); - expect(newDomain.getDomain()).toStrictEqual([ interval ]); + expect(newDomain.getDomain()).toStrictEqual([interval]); }); it('should return an empty domain if there is no intersection with the new interval', () => { - const interval = new SolutionInterval([ -200, -100 ]); + const interval = new SolutionInterval([-200, -100]); const newDomain = and.apply({ intervals: interval, domain: aDomain }); @@ -124,18 +124,18 @@ describe('LogicOperator', () => { }); it('given a new interval inside a part of the domain should only return the intersection', () => { - const interval = new SolutionInterval([ 22, 30 ]); + const interval = new SolutionInterval([22, 30]); const newDomain = and.apply({ intervals: interval, domain: aDomain }); - expect(newDomain.getDomain()).toStrictEqual([ interval ]); + expect(newDomain.getDomain()).toStrictEqual([interval]); }); it('given a new interval that intersects part of the domain should only return the intersection', () => { - const interval = new SolutionInterval([ 19, 25 ]); + const interval = new SolutionInterval([19, 25]); const expectedDomain = [ - new SolutionInterval([ 21, 25 ]), + new SolutionInterval([21, 25]), ]; const newDomain = and.apply({ intervals: interval, domain: aDomain }); @@ -144,13 +144,13 @@ describe('LogicOperator', () => { }); it('given a new interval that intersect multiple part of the domain should only return the intersections', () => { - const interval = new SolutionInterval([ -2, 25 ]); + const interval = new SolutionInterval([-2, 25]); const expectedDomain = [ - new SolutionInterval([ -1, 0 ]), - new SolutionInterval([ 1, 5 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 21, 25 ]), + new SolutionInterval([-1, 0]), + new SolutionInterval([1, 5]), + new SolutionInterval([10, 10]), + new SolutionInterval([21, 25]), ]; const newDomain = and.apply({ intervals: interval, domain: aDomain }); @@ -160,49 +160,49 @@ describe('LogicOperator', () => { it('given an empty domain and two intervals that are overlapping the domain should remain empty', () => { const domain = new SolutionDomain(); - const interval1 = new SolutionInterval([ 0, 2 ]); - const interval2 = new SolutionInterval([ 1, 2 ]); + const interval1 = new SolutionInterval([0, 2]); + const interval2 = new SolutionInterval([1, 2]); - const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain }); + const newDomain = and.apply({ intervals: [interval1, interval2], domain }); expect(newDomain.isDomainEmpty()).toBe(true); }); it('given a domain and two intervals that are overlapping the domain should remain empty', () => { - const interval1 = new SolutionInterval([ 0, 2 ]); - const interval2 = new SolutionInterval([ 1, 2 ]); + const interval1 = new SolutionInterval([0, 2]); + const interval2 = new SolutionInterval([1, 2]); - const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); + const newDomain = and.apply({ intervals: [interval1, interval2], domain: aDomain }); expect(newDomain.equal(aDomain)).toBe(true); }); it(`given a domain and two intervals that are not overlapping and also don't overlap with the domain should return an empty domain`, () => { - const interval1 = new SolutionInterval([ -100, -50 ]); - const interval2 = new SolutionInterval([ -25, -23 ]); + const interval1 = new SolutionInterval([-100, -50]); + const interval2 = new SolutionInterval([-25, -23]); - const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); + const newDomain = and.apply({ intervals: [interval1, interval2], domain: aDomain }); expect(newDomain.isDomainEmpty()).toBe(true); }); it(`given a domain and two intervals that are not overlapping and where the first one is overlapping with the domain then should return a valid new domain`, () => { - const interval1 = new SolutionInterval([ 1, 3 ]); - const interval2 = new SolutionInterval([ -25, -23 ]); + const interval1 = new SolutionInterval([1, 3]); + const interval2 = new SolutionInterval([-25, -23]); - const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); + const newDomain = and.apply({ intervals: [interval1, interval2], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals(interval1); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); it(`given a domain and two intervals that are not overlapping and where the second one is overlapping with the domain then should return a valid new domain`, () => { - const interval1 = new SolutionInterval([ -25, -23 ]); - const interval2 = new SolutionInterval([ 1, 3 ]); + const interval1 = new SolutionInterval([-25, -23]); + const interval2 = new SolutionInterval([1, 3]); - const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); + const newDomain = and.apply({ intervals: [interval1, interval2], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals(interval2); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); @@ -210,33 +210,33 @@ describe('LogicOperator', () => { it(`given a domain and two intervals that are not overlapping and where both are overlapping with the domain but the first one is more overlapping than the second in relation to the domain then should return a valid new domain`, () => { - const interval1 = new SolutionInterval([ 2, 70 ]); - const interval2 = new SolutionInterval([ 1, 1 ]); + const interval1 = new SolutionInterval([2, 70]); + const interval2 = new SolutionInterval([1, 1]); - const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); + const newDomain = and.apply({ intervals: [interval1, interval2], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals([ interval2, - new SolutionInterval([ 2, 5 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 21, 33 ]), - new SolutionInterval([ 60, 70 ]) ]); + new SolutionInterval([2, 5]), + new SolutionInterval([10, 10]), + new SolutionInterval([21, 33]), + new SolutionInterval([60, 70])]); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); it(`given a domain and two intervals that are not overlapping and where both are overlapping with the domain but the second one is more overlapping than the second in relation to the domain then should return a valid new domain`, () => { - const interval1 = new SolutionInterval([ 1, 1 ]); + const interval1 = new SolutionInterval([1, 1]); - const interval2 = new SolutionInterval([ 2, 70 ]); + const interval2 = new SolutionInterval([2, 70]); - const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); + const newDomain = and.apply({ intervals: [interval1, interval2], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals([ interval1, - new SolutionInterval([ 2, 5 ]), - new SolutionInterval([ 10, 10 ]), - new SolutionInterval([ 21, 33 ]), - new SolutionInterval([ 60, 70 ]) ]); + new SolutionInterval([2, 5]), + new SolutionInterval([10, 10]), + new SolutionInterval([21, 33]), + new SolutionInterval([60, 70])]); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); }); @@ -249,7 +249,7 @@ describe('LogicOperator', () => { }); it('Given an Or or an And operator should return an LogicOperator', () => { - for (const operator of [ LogicOperatorSymbol.And, LogicOperatorSymbol.Or ]) { + for (const operator of [LogicOperatorSymbol.And, LogicOperatorSymbol.Or]) { expect(operatorFactory(operator).operatorName()).toBe(operator); } }); From 3444a870cba22aeb63f51fcfa7b6c19f45d5d5a4 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 12 Jun 2023 19:27:40 +0200 Subject: [PATCH 186/189] more unit test for and operator --- .../test/LogicOperator-test.ts | 177 +++++++++++------- 1 file changed, 105 insertions(+), 72 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index c0ec7dca9..a2567d629 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -13,15 +13,15 @@ describe('LogicOperator', () => { }); describe('apply', () => { const intervals = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 5]), - new SolutionInterval([10, 10]), - new SolutionInterval([21, 33]), - new SolutionInterval([60, 70]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 33 ]), + new SolutionInterval([ 60, 70 ]), ]; const aDomain = SolutionDomain.newWithInitialIntervals(intervals); it('given an empty domain should be able to add an interval', () => { - const interval = new SolutionInterval([0, 1]); + const interval = new SolutionInterval([ 0, 1 ]); const solutionDomain = new SolutionDomain(); const expectedSolutionDomain = SolutionDomain.newWithInitialIntervals(interval); expect(or.apply({ domain: solutionDomain, intervals: interval })).toStrictEqual(expectedSolutionDomain); @@ -29,10 +29,10 @@ describe('LogicOperator', () => { it('given an empty domain should be able to add multiple intervals that doesn\'t overlap', () => { const givenIntervals = [ - new SolutionInterval([10, 10]), - new SolutionInterval([1, 2]), - new SolutionInterval([-1, 0]), - new SolutionInterval([60, 70]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 1, 2 ]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 60, 70 ]), ]; let solutionDomain = new SolutionDomain(); @@ -43,17 +43,17 @@ describe('LogicOperator', () => { }); const expectedDomain = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 2]), - new SolutionInterval([10, 10]), - new SolutionInterval([60, 70]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 2 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 60, 70 ]), ]; expect(solutionDomain.getDomain()).toStrictEqual(expectedDomain); }); it('given a domain should not add an interval that is inside another', () => { - const anOverlappingInterval = new SolutionInterval([22, 23]); + const anOverlappingInterval = new SolutionInterval([ 22, 23 ]); const newDomain = or.apply({ domain: aDomain, intervals: anOverlappingInterval }); expect(newDomain.getDomain().length).toBe(aDomain.getDomain().length); @@ -62,22 +62,22 @@ describe('LogicOperator', () => { it(`given a domain should be able to create a single domain if all the domain segments are contained into the new interval`, - () => { - const anOverlappingInterval = new SolutionInterval([-100, 100]); - const newDomain = or.apply({ domain: aDomain, intervals: anOverlappingInterval }); + () => { + const anOverlappingInterval = new SolutionInterval([ -100, 100 ]); + const newDomain = or.apply({ domain: aDomain, intervals: anOverlappingInterval }); - expect(newDomain.getDomain().length).toBe(1); - expect(newDomain.getDomain()).toStrictEqual([anOverlappingInterval]); - }); + expect(newDomain.getDomain().length).toBe(1); + expect(newDomain.getDomain()).toStrictEqual([ anOverlappingInterval ]); + }); it('given a domain should be able to fuse multiple domain segment if the new interval overlaps with them', () => { - const aNewInterval = new SolutionInterval([1, 23]); + const aNewInterval = new SolutionInterval([ 1, 23 ]); const newDomain = or.apply({ domain: aDomain, intervals: aNewInterval }); const expectedResultingDomainInterval = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 33]), - new SolutionInterval([60, 70]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 33 ]), + new SolutionInterval([ 60, 70 ]), ]; expect(newDomain.getDomain().length).toBe(3); @@ -96,11 +96,11 @@ describe('LogicOperator', () => { describe('apply', () => { let aDomain: SolutionDomain = new SolutionDomain(); const intervals = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 5]), - new SolutionInterval([10, 10]), - new SolutionInterval([21, 33]), - new SolutionInterval([60, 70]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 33 ]), + new SolutionInterval([ 60, 70 ]), ]; beforeEach(() => { aDomain = SolutionDomain.newWithInitialIntervals(new Array(...intervals)); @@ -108,15 +108,15 @@ describe('LogicOperator', () => { it('should add an interval when the domain is empty', () => { const domain = new SolutionDomain(); - const interval = new SolutionInterval([0, 1]); + const interval = new SolutionInterval([ 0, 1 ]); const newDomain = and.apply({ intervals: interval, domain }); - expect(newDomain.getDomain()).toStrictEqual([interval]); + expect(newDomain.getDomain()).toStrictEqual([ interval ]); }); it('should return an empty domain if there is no intersection with the new interval', () => { - const interval = new SolutionInterval([-200, -100]); + const interval = new SolutionInterval([ -200, -100 ]); const newDomain = and.apply({ intervals: interval, domain: aDomain }); @@ -124,18 +124,18 @@ describe('LogicOperator', () => { }); it('given a new interval inside a part of the domain should only return the intersection', () => { - const interval = new SolutionInterval([22, 30]); + const interval = new SolutionInterval([ 22, 30 ]); const newDomain = and.apply({ intervals: interval, domain: aDomain }); - expect(newDomain.getDomain()).toStrictEqual([interval]); + expect(newDomain.getDomain()).toStrictEqual([ interval ]); }); it('given a new interval that intersects part of the domain should only return the intersection', () => { - const interval = new SolutionInterval([19, 25]); + const interval = new SolutionInterval([ 19, 25 ]); const expectedDomain = [ - new SolutionInterval([21, 25]), + new SolutionInterval([ 21, 25 ]), ]; const newDomain = and.apply({ intervals: interval, domain: aDomain }); @@ -144,13 +144,13 @@ describe('LogicOperator', () => { }); it('given a new interval that intersect multiple part of the domain should only return the intersections', () => { - const interval = new SolutionInterval([-2, 25]); + const interval = new SolutionInterval([ -2, 25 ]); const expectedDomain = [ - new SolutionInterval([-1, 0]), - new SolutionInterval([1, 5]), - new SolutionInterval([10, 10]), - new SolutionInterval([21, 25]), + new SolutionInterval([ -1, 0 ]), + new SolutionInterval([ 1, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 25 ]), ]; const newDomain = and.apply({ intervals: interval, domain: aDomain }); @@ -160,83 +160,116 @@ describe('LogicOperator', () => { it('given an empty domain and two intervals that are overlapping the domain should remain empty', () => { const domain = new SolutionDomain(); - const interval1 = new SolutionInterval([0, 2]); - const interval2 = new SolutionInterval([1, 2]); + const interval1 = new SolutionInterval([ 0, 2 ]); + const interval2 = new SolutionInterval([ 1, 2 ]); - const newDomain = and.apply({ intervals: [interval1, interval2], domain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain }); expect(newDomain.isDomainEmpty()).toBe(true); }); it('given a domain and two intervals that are overlapping the domain should remain empty', () => { - const interval1 = new SolutionInterval([0, 2]); - const interval2 = new SolutionInterval([1, 2]); + const interval1 = new SolutionInterval([ 0, 2 ]); + const interval2 = new SolutionInterval([ 1, 2 ]); - const newDomain = and.apply({ intervals: [interval1, interval2], domain: aDomain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); expect(newDomain.equal(aDomain)).toBe(true); }); it(`given a domain and two intervals that are not overlapping and also don't overlap with the domain should return an empty domain`, () => { - const interval1 = new SolutionInterval([-100, -50]); - const interval2 = new SolutionInterval([-25, -23]); + const interval1 = new SolutionInterval([ -100, -50 ]); + const interval2 = new SolutionInterval([ -25, -23 ]); - const newDomain = and.apply({ intervals: [interval1, interval2], domain: aDomain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); expect(newDomain.isDomainEmpty()).toBe(true); }); it(`given a domain and two intervals that are not overlapping and where the first one is overlapping with the domain then should return a valid new domain`, () => { - const interval1 = new SolutionInterval([1, 3]); - const interval2 = new SolutionInterval([-25, -23]); + const interval1 = new SolutionInterval([ 1, 3 ]); + const interval2 = new SolutionInterval([ -25, -23 ]); - const newDomain = and.apply({ intervals: [interval1, interval2], domain: aDomain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals(interval1); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); it(`given a domain and two intervals that are not overlapping and where the second one is overlapping with the domain then should return a valid new domain`, () => { - const interval1 = new SolutionInterval([-25, -23]); - const interval2 = new SolutionInterval([1, 3]); + const interval1 = new SolutionInterval([ -25, -23 ]); + const interval2 = new SolutionInterval([ 1, 3 ]); - const newDomain = and.apply({ intervals: [interval1, interval2], domain: aDomain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals(interval2); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); + it(`given a domain and multiple intervals that are not overlapping and where + one is overlapping with the domain then should return a valid new domain`, () => { + const interval1 = new SolutionInterval([ -26, -25 ]); + const interval2 = new SolutionInterval([ -24, -23 ]); + const interval3 = new SolutionInterval([ 1, 3 ]); + + const newDomain = and.apply({ + intervals: [ interval1, interval2, interval3 ], + domain: aDomain, + }); + const expectedDomain = SolutionDomain.newWithInitialIntervals(interval3); + expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + + it(`given a domain and multiple intervals that are not overlapping and where + multiple is overlapping with the domain then should return a valid new domain`, () => { + const interval1 = new SolutionInterval([ -26, -25 ]); + const interval2 = new SolutionInterval([ -24, -23 ]); + const interval3 = new SolutionInterval([ 1, 2 ]); + const interval4 = new SolutionInterval([ 3, 4 ]); + const interval5 = new SolutionInterval([ 5, 6 ]); + const interval6 = new SolutionInterval([ 22, 24 ]); + + const newDomain = and.apply({ + intervals: [ interval1, interval2, interval3, interval4, interval5, interval6 ], + domain: aDomain, + }); + const expectedDomain = SolutionDomain.newWithInitialIntervals( + [ interval3, interval4, new SolutionInterval([ 5, 5 ]), interval6 ], + ); + expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); + }); + it(`given a domain and two intervals that are not overlapping and where both are overlapping with the domain but the first one is more overlapping than the second in relation to the domain then should return a valid new domain`, () => { - const interval1 = new SolutionInterval([2, 70]); - const interval2 = new SolutionInterval([1, 1]); + const interval1 = new SolutionInterval([ 2, 70 ]); + const interval2 = new SolutionInterval([ 1, 1 ]); - const newDomain = and.apply({ intervals: [interval1, interval2], domain: aDomain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals([ interval2, - new SolutionInterval([2, 5]), - new SolutionInterval([10, 10]), - new SolutionInterval([21, 33]), - new SolutionInterval([60, 70])]); + new SolutionInterval([ 2, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 33 ]), + new SolutionInterval([ 60, 70 ]) ]); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); it(`given a domain and two intervals that are not overlapping and where both are overlapping with the domain but the second one is more overlapping than the second in relation to the domain then should return a valid new domain`, () => { - const interval1 = new SolutionInterval([1, 1]); + const interval1 = new SolutionInterval([ 1, 1 ]); - const interval2 = new SolutionInterval([2, 70]); + const interval2 = new SolutionInterval([ 2, 70 ]); - const newDomain = and.apply({ intervals: [interval1, interval2], domain: aDomain }); + const newDomain = and.apply({ intervals: [ interval1, interval2 ], domain: aDomain }); const expectedDomain = SolutionDomain.newWithInitialIntervals([ interval1, - new SolutionInterval([2, 5]), - new SolutionInterval([10, 10]), - new SolutionInterval([21, 33]), - new SolutionInterval([60, 70])]); + new SolutionInterval([ 2, 5 ]), + new SolutionInterval([ 10, 10 ]), + new SolutionInterval([ 21, 33 ]), + new SolutionInterval([ 60, 70 ]) ]); expect(newDomain.getDomain()).toStrictEqual(expectedDomain.getDomain()); }); }); @@ -249,7 +282,7 @@ describe('LogicOperator', () => { }); it('Given an Or or an And operator should return an LogicOperator', () => { - for (const operator of [LogicOperatorSymbol.And, LogicOperatorSymbol.Or]) { + for (const operator of [ LogicOperatorSymbol.And, LogicOperatorSymbol.Or ]) { expect(operatorFactory(operator).operatorName()).toBe(operator); } }); From 04106bb5b7c9af8f5df68994d3111d41759ebeb5 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Mon, 12 Jun 2023 23:58:56 +0200 Subject: [PATCH 187/189] fix the unit tests --- .../lib/LogicOperator.ts | 2 +- .../lib/SolverInput.ts | 79 +++++++++++++++---- .../lib/solver.ts | 4 + .../lib/solverUtil.ts | 48 +++++------ .../test/SolverInput-test.ts | 2 - 5 files changed, 94 insertions(+), 41 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index 7b6329329..5b16b4f8e 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -92,7 +92,7 @@ export class And implements ILogicOperator { // Considering the current domain if there is an intersection // add the intersection to the new domain domain.getDomain().forEach(el => { - const intersection = SolutionInterval.getIntersection(el, intervals); + const intersection = SolutionInterval.getIntersection(el, intervals); if (!intersection.isEmpty) { newDomain.push(intersection); } diff --git a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts index f8c9dcb3f..bb0d84be4 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts @@ -9,13 +9,17 @@ import type { ILogicOperator } from './LogicOperator'; import { Or, operatorFactory } from './LogicOperator'; import { SolutionDomain } from './SolutionDomain'; import { SolutionInterval } from './SolutionInterval'; -import { LogicOperatorReversed, +import { + LogicOperatorReversed, LogicOperatorSymbol, - SparqlOperandDataTypesReversed } from './solverInterfaces'; -import type { ISolverInput, + SparqlOperandDataTypesReversed, +} from './solverInterfaces'; +import type { + ISolverInput, ISolverExpression, Variable, - SparqlOperandDataTypes } from './solverInterfaces'; + SparqlOperandDataTypes, +} from './solverInterfaces'; import { castSparqlRdfTermIntoNumber, filterOperatorToSparqlRelationOperator, @@ -66,6 +70,49 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { Object.freeze(this.variable); } + /** + * Rewrite the expression to make to make outer not statement inner. + * @param {Algebra.Expression} filterExpression - + * The current filter expression that we are traversing + */ + private static rewriteExpression(filterExpression: Algebra.Expression): void { + if ( + !( + (filterExpression.expressionType === Algebra.expressionTypes.TERM) || + filterExpression.args.length === 0 || + ( + filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && + filterExpression.args.length === 2 + ) + ) + + ) { + const logicOperatorSymbol = filterExpression.operator; + if (logicOperatorSymbol) { + if (logicOperatorSymbol === LogicOperatorSymbol.Not) { + inverseFilter(filterExpression); + } + if (filterExpression.args) { + for (const arg of filterExpression.args) { + SparlFilterExpressionSolverInput.rewriteExpression(arg); + } + if (filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM) { + delete filterExpression.operator; + filterExpression.type = filterExpression.args[0].type; + filterExpression.term = filterExpression.args[0].term; + filterExpression.expressionType = filterExpression.args[0].expressionType; + delete filterExpression.args; + } else if (filterExpression.operator === LogicOperatorSymbol.Not || + filterExpression.operator === LogicOperatorSymbol.Exist) { + filterExpression.operator = filterExpression.args[0].operator; + filterExpression.expressionType = filterExpression.args[0].expressionType; + filterExpression.args = filterExpression.args[0].args; + } + } + } + } + } + /** * Recursively traverse the filter expression and calculate the domain until it get to the current expression. * It will thrown an error if the expression is badly formated or @@ -76,6 +123,7 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { * @param {SolutionDomain} domain - The current resultant solution domain * @param {LogicOperatorSymbol} logicOperator * - The current logic operator that we have to apply to the boolean expression + * @param {boolean} rewrite - rewrite the query to make the negative statement inner * @returns {SolutionDomain} The solution domain of the whole expression */ public static recursifResolve( @@ -83,7 +131,11 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { variable: Variable, domain: SolutionDomain = new SolutionDomain(), logicOperator: ILogicOperator = new Or(), + rewrite = true, ): SolutionDomain { + if (rewrite) { + SparlFilterExpressionSolverInput.rewriteExpression(filterExpression); + } if (filterExpression.expressionType === Algebra.expressionTypes.TERM ) { // In that case we are confronted with a boolean expression @@ -93,9 +145,9 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { domain = logicOperator.apply({ intervals: A_TRUE_EXPRESSION, domain }); } } else if ( - // If it's an array of terms then we should be able to create a solver expression. + // If it's an array of terms then we should be able to create a solver expression. filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && - filterExpression.args.length === 2 + filterExpression.args.length === 2 ) { const rawOperator = filterExpression.operator; const operator = filterOperatorToSparqlRelationOperator(rawOperator); @@ -115,22 +167,19 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { } domain = logicOperator.apply({ intervals: solutionInterval, domain }); } + } else if (filterExpression.operator === '=') { + domain = logicOperator.apply({ intervals: A_TRUE_EXPRESSION, domain }); } else { // In that case we are traversing the filter expression TREE. // We prepare the next recursion and we compute the accumulation of results. const logicOperatorSymbol = LogicOperatorReversed.get(filterExpression.operator); if (logicOperatorSymbol) { + let subdomain = new SolutionDomain(); for (const arg of filterExpression.args) { - // To solve the not operation we rewrite the path of the filter expression to reverse every operation - // e.g, = : != ; > : <= - if (logicOperatorSymbol === LogicOperatorSymbol.Not) { - inverseFilter(arg); - domain = this.recursifResolve(arg, variable, domain, logicOperator); - } else { - const newLogicOperator = operatorFactory(logicOperatorSymbol); - domain = this.recursifResolve(arg, variable, domain, newLogicOperator); - } + const newLogicOperator = operatorFactory(logicOperatorSymbol); + subdomain = this.recursifResolve(arg, variable, subdomain, newLogicOperator, false); } + domain = logicOperator.apply({ intervals: subdomain.getDomain(), domain }); } } return domain; diff --git a/packages/actor-extract-links-extract-tree/lib/solver.ts b/packages/actor-extract-links-extract-tree/lib/solver.ts index 4c25ab515..fb922abbb 100644 --- a/packages/actor-extract-links-extract-tree/lib/solver.ts +++ b/packages/actor-extract-links-extract-tree/lib/solver.ts @@ -45,6 +45,10 @@ export function isBooleanExpressionTreeRelationFilterSolvable(inputs: ISolverInp throw new InvalidExpressionSystem('there should be at least one TREE relation to resolved'); } + if (domain.isDomainEmpty()) { + return false; + } + for (const interval of intervals) { domain = AND.apply({ intervals: interval, domain }); } diff --git a/packages/actor-extract-links-extract-tree/lib/solverUtil.ts b/packages/actor-extract-links-extract-tree/lib/solverUtil.ts index 363fe9fea..49be3a907 100644 --- a/packages/actor-extract-links-extract-tree/lib/solverUtil.ts +++ b/packages/actor-extract-links-extract-tree/lib/solverUtil.ts @@ -23,7 +23,7 @@ export function areTypesCompatible(expressions: ISolverExpression[]): boolean { for (const expression of expressions) { const areIdentical = expression.valueType === firstType; const areNumbers = isSparqlOperandNumberType(firstType) && - isSparqlOperandNumberType(expression.valueType); + isSparqlOperandNumberType(expression.valueType); if (!(areIdentical || areNumbers)) { return false; @@ -72,8 +72,8 @@ export function castSparqlRdfTermIntoNumber(rdfTermValue: string, number | undefined { if ( rdfTermType === SparqlOperandDataTypes.Decimal || - rdfTermType === SparqlOperandDataTypes.Float || - rdfTermType === SparqlOperandDataTypes.Double + rdfTermType === SparqlOperandDataTypes.Float || + rdfTermType === SparqlOperandDataTypes.Double ) { const val = Number.parseFloat(rdfTermValue); return Number.isNaN(val) ? undefined : val; @@ -112,19 +112,19 @@ export function castSparqlRdfTermIntoNumber(rdfTermValue: string, */ export function isSparqlOperandNumberType(rdfTermType: SparqlOperandDataTypes): boolean { return rdfTermType === SparqlOperandDataTypes.Integer || - rdfTermType === SparqlOperandDataTypes.NonPositiveInteger || - rdfTermType === SparqlOperandDataTypes.NegativeInteger || - rdfTermType === SparqlOperandDataTypes.Long || - rdfTermType === SparqlOperandDataTypes.Short || - rdfTermType === SparqlOperandDataTypes.NonNegativeInteger || - rdfTermType === SparqlOperandDataTypes.UnsignedLong || - rdfTermType === SparqlOperandDataTypes.UnsignedInt || - rdfTermType === SparqlOperandDataTypes.UnsignedShort || - rdfTermType === SparqlOperandDataTypes.PositiveInteger || - rdfTermType === SparqlOperandDataTypes.Float || - rdfTermType === SparqlOperandDataTypes.Double || - rdfTermType === SparqlOperandDataTypes.Decimal || - rdfTermType === SparqlOperandDataTypes.Int; + rdfTermType === SparqlOperandDataTypes.NonPositiveInteger || + rdfTermType === SparqlOperandDataTypes.NegativeInteger || + rdfTermType === SparqlOperandDataTypes.Long || + rdfTermType === SparqlOperandDataTypes.Short || + rdfTermType === SparqlOperandDataTypes.NonNegativeInteger || + rdfTermType === SparqlOperandDataTypes.UnsignedLong || + rdfTermType === SparqlOperandDataTypes.UnsignedInt || + rdfTermType === SparqlOperandDataTypes.UnsignedShort || + rdfTermType === SparqlOperandDataTypes.PositiveInteger || + rdfTermType === SparqlOperandDataTypes.Float || + rdfTermType === SparqlOperandDataTypes.Double || + rdfTermType === SparqlOperandDataTypes.Decimal || + rdfTermType === SparqlOperandDataTypes.Int; } /** * Convert a filter operator to {@link SparqlRelationOperator}. @@ -228,12 +228,12 @@ export function inverseFilter(filterExpression: Algebra.Expression): void { ) { if (filterExpression.term.value === 'false') { filterExpression.term.value = 'true'; - } else { + } else if (filterExpression.term.value === 'true') { filterExpression.term.value = 'false'; } } else if ( filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && - filterExpression.args.length === 2 + filterExpression.args.length === 2 ) { filterExpression.operator = reverseRawOperator(filterExpression.operator); } else { @@ -241,12 +241,14 @@ export function inverseFilter(filterExpression: Algebra.Expression): void { if (reversedOperator) { filterExpression.operator = reversedOperator; } - for (const arg of filterExpression.args) { - const newReversedOperator = reverseRawLogicOperator(filterExpression.operator); - if (newReversedOperator) { - filterExpression.operator = newReversedOperator; + if (filterExpression.args) { + for (const arg of filterExpression.args) { + const newReversedOperator = reverseRawLogicOperator(filterExpression.operator); + if (newReversedOperator) { + filterExpression.operator = newReversedOperator; + } + inverseFilter(arg); } - inverseFilter(arg); } } } diff --git a/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts b/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts index 0ff1f03b0..8e780d30b 100644 --- a/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts +++ b/packages/actor-extract-links-extract-tree/test/SolverInput-test.ts @@ -589,10 +589,8 @@ describe('SolverInput', () => { SELECT * WHERE { ?x ?y ?z FILTER( !(!(?x=2)) && ?x<5) }`).input.expression; - const resp = SparlFilterExpressionSolverInput.recursifResolve( expression, - 'x', ); From 5b7cf45eea8d363478eac4f9b3897f9a86408584 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 7 Jul 2023 10:57:38 +0200 Subject: [PATCH 188/189] fix bug when multiple and operation the array sorting modify frozen value --- .../lib/LogicOperator.ts | 2 +- .../test/solver-test.ts | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index 5b16b4f8e..e5e730470 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -61,7 +61,7 @@ export class And implements ILogicOperator { { intervals: SolutionInterval | SolutionInterval[]; domain: SolutionDomain }): SolutionDomain { if (Array.isArray(intervals)) { const domain_intervals = domain.getDomain(); - intervals = intervals.sort(SolutionDomain.sortDomainRangeByLowerBound); + intervals = intervals.slice().sort(SolutionDomain.sortDomainRangeByLowerBound); if (SolutionDomain.isThereOverlapInsideDomain(intervals)) { return domain; } diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 85143db25..0bb457333 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -888,6 +888,32 @@ describe('solver function', () => { expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); }); + it(`should accept the link with a solvable simple boolean and an and operator linked with a not expression inside a parenthesis`, () => { + const relation: ITreeRelation = { + type: SparqlRelationOperator.EqualThanRelation, + remainingItems: 10, + path: 'ex:path', + value: { + value: '5', + term: DF.literal('5', DF.namedNode('http://www.w3.org/2001/XMLSchema#integer')), + }, + node: 'https://www.example.be', + }; + + const filterExpression = translate(` + SELECT * WHERE { ?x ?y ?z + FILTER(?x = 5 && !(false && true)) + }`).input.expression; + + const variable = 'x'; + const inputs = [ + new TreeRelationSolverInput(relation, variable), + new SparlFilterExpressionSolverInput(filterExpression, variable), + ]; + + expect(isBooleanExpressionTreeRelationFilterSolvable(inputs)).toBe(true); + }); + it(`Should ignore the SPARQL function when prunning links`, () => { const relation: ITreeRelation = { type: SparqlRelationOperator.EqualThanRelation, From c5309e7eaf5d79b8ded0654b62c14db3d2066ef6 Mon Sep 17 00:00:00 2001 From: Bryan-Elliott Tam Date: Fri, 7 Jul 2023 14:30:57 +0200 Subject: [PATCH 189/189] fix problem where the and statement had as an input an array of interval with one value, now it is process as if it was a single value --- .../lib/LogicOperator.ts | 43 +++++++++++-------- .../lib/SolverInput.ts | 15 ++++--- .../test/LogicOperator-test.ts | 6 +++ .../test/solver-test.ts | 2 +- 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts index e5e730470..f07db7362 100644 --- a/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts +++ b/packages/actor-extract-links-extract-tree/lib/LogicOperator.ts @@ -60,27 +60,34 @@ export class And implements ILogicOperator { public apply({ intervals, domain }: { intervals: SolutionInterval | SolutionInterval[]; domain: SolutionDomain }): SolutionDomain { if (Array.isArray(intervals)) { - const domain_intervals = domain.getDomain(); - intervals = intervals.slice().sort(SolutionDomain.sortDomainRangeByLowerBound); - if (SolutionDomain.isThereOverlapInsideDomain(intervals)) { - return domain; - } + if (intervals.length > 1) { + const domain_intervals = domain.getDomain(); + const new_interval = intervals.slice().sort(SolutionDomain.sortDomainRangeByLowerBound); + if (SolutionDomain.isThereOverlapInsideDomain(new_interval)) { + return domain; + } - let resulting_interval: SolutionInterval[] = []; - for (const interval of intervals) { - for (const domain_interval of domain_intervals) { - const temp_domain = this.apply({ - intervals: interval, - domain: SolutionDomain.newWithInitialIntervals(domain_interval), - }); - resulting_interval = resulting_interval.concat(temp_domain.getDomain()); + let resulting_interval: SolutionInterval[] = []; + for (const interval of new_interval) { + for (const domain_interval of domain_intervals) { + const temp_domain = this.apply({ + intervals: interval, + domain: SolutionDomain.newWithInitialIntervals(domain_interval), + }); + resulting_interval = resulting_interval.concat(temp_domain.getDomain()); + } } + let resp_domain = new SolutionDomain(); + for (const interval of resulting_interval) { + resp_domain = new Or().apply({ intervals: interval, domain: resp_domain }); + } + return resp_domain; } - let resp_domain = new SolutionDomain(); - for (const interval of resulting_interval) { - resp_domain = new Or().apply({ intervals: interval, domain: resp_domain }); + + if (intervals.length === 0) { + return domain; } - return resp_domain; + intervals = intervals[0]; } const newDomain: SolutionInterval[] = []; @@ -92,7 +99,7 @@ export class And implements ILogicOperator { // Considering the current domain if there is an intersection // add the intersection to the new domain domain.getDomain().forEach(el => { - const intersection = SolutionInterval.getIntersection(el, intervals); + const intersection = SolutionInterval.getIntersection(el, intervals); if (!intersection.isEmpty) { newDomain.push(intersection); } diff --git a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts index bb0d84be4..d002c55ff 100644 --- a/packages/actor-extract-links-extract-tree/lib/SolverInput.ts +++ b/packages/actor-extract-links-extract-tree/lib/SolverInput.ts @@ -140,11 +140,12 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { ) { // In that case we are confronted with a boolean expression if (filterExpression.term.value === 'false') { - domain = logicOperator.apply({ intervals: A_FALSE_EXPRESSION, domain }); - } else { - domain = logicOperator.apply({ intervals: A_TRUE_EXPRESSION, domain }); + return logicOperator.apply({ intervals: A_FALSE_EXPRESSION, domain }); } - } else if ( + return logicOperator.apply({ intervals: A_TRUE_EXPRESSION, domain }); + } + + if ( // If it's an array of terms then we should be able to create a solver expression. filterExpression.args[0].expressionType === Algebra.expressionTypes.TERM && filterExpression.args.length === 2 @@ -165,10 +166,10 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { throw new UnsupportedDataTypeError('The operator is not supported'); } } - domain = logicOperator.apply({ intervals: solutionInterval, domain }); + return logicOperator.apply({ intervals: solutionInterval, domain }); } } else if (filterExpression.operator === '=') { - domain = logicOperator.apply({ intervals: A_TRUE_EXPRESSION, domain }); + return logicOperator.apply({ intervals: A_TRUE_EXPRESSION, domain }); } else { // In that case we are traversing the filter expression TREE. // We prepare the next recursion and we compute the accumulation of results. @@ -179,7 +180,7 @@ export class SparlFilterExpressionSolverInput implements ISolverInput { const newLogicOperator = operatorFactory(logicOperatorSymbol); subdomain = this.recursifResolve(arg, variable, subdomain, newLogicOperator, false); } - domain = logicOperator.apply({ intervals: subdomain.getDomain(), domain }); + return logicOperator.apply({ intervals: subdomain.getDomain(), domain }); } } return domain; diff --git a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts index a2567d629..a2ed4f777 100644 --- a/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts +++ b/packages/actor-extract-links-extract-tree/test/LogicOperator-test.ts @@ -168,6 +168,12 @@ describe('LogicOperator', () => { expect(newDomain.isDomainEmpty()).toBe(true); }); + it('given an a domain and no interval in an array should return the domain', () => { + const newDomain = and.apply({ intervals: [ ], domain: aDomain }); + + expect(newDomain).toStrictEqual(aDomain); + }); + it('given a domain and two intervals that are overlapping the domain should remain empty', () => { const interval1 = new SolutionInterval([ 0, 2 ]); const interval2 = new SolutionInterval([ 1, 2 ]); diff --git a/packages/actor-extract-links-extract-tree/test/solver-test.ts b/packages/actor-extract-links-extract-tree/test/solver-test.ts index 0bb457333..c8cac4ed1 100644 --- a/packages/actor-extract-links-extract-tree/test/solver-test.ts +++ b/packages/actor-extract-links-extract-tree/test/solver-test.ts @@ -902,7 +902,7 @@ describe('solver function', () => { const filterExpression = translate(` SELECT * WHERE { ?x ?y ?z - FILTER(?x = 5 && !(false && true)) + FILTER((?x > 2 && ?x<10) && !(?x>3 && ?x<4)) }`).input.expression; const variable = 'x';