diff --git a/package-lock.json b/package-lock.json index bcf2444..df3f4e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@comake/skql-js-engine", - "version": "0.11.4", + "version": "0.13.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@comake/skql-js-engine", - "version": "0.11.4", + "version": "0.13.1", "license": "BSD-4-Clause", "dependencies": { "@comake/openapi-operation-executor": "^0.5.0", diff --git a/package.json b/package.json index bd88ffe..8e5f235 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@comake/skql-js-engine", - "version": "0.13.0", + "version": "0.13.1", "description": "Standard Knowledge Query Language Javascript Engine", "keywords": [ "skl", diff --git a/src/storage/sparql/SparqlQueryAdapter.ts b/src/storage/sparql/SparqlQueryAdapter.ts index bff75e3..039301c 100644 --- a/src/storage/sparql/SparqlQueryAdapter.ts +++ b/src/storage/sparql/SparqlQueryAdapter.ts @@ -8,9 +8,21 @@ import type { SparqlGenerator, AskQuery, SelectQuery, + Pattern, + Variable, + GraphPattern, + IriTerm, + Ordering, + GroupPattern, } from 'sparqljs'; import { Generator } from 'sparqljs'; -import { toJSValueFromDataType, triplesToJsonld, triplesToJsonldWithFrame } from '../../util/TripleUtil'; +import { + countVariable, + entityVariable, + toJSValueFromDataType, + triplesToJsonld, + triplesToJsonldWithFrame, +} from '../../util/TripleUtil'; import type { Entity } from '../../util/Types'; import type { FindOneOptions, FindAllOptions, FindOptionsWhere } from '../FindOptionsTypes'; import type { QueryAdapter, RawQueryResult } from '../QueryAdapter'; @@ -62,7 +74,25 @@ export class SparqlQueryAdapter implements QueryAdapter { public async find(options?: FindOneOptions): Promise { const queryBuilder = new SparqlQueryBuilder(); - const query = queryBuilder.buildEntityQuery({ ...options, limit: 1 }); + const selectQueryData = queryBuilder.buildPatternsFromQueryOptions( + entityVariable, + options?.where, + options?.order, + options?.relations, + ); + const entitySelectQuery = this.sparqlSelect( + [ entityVariable, ...selectQueryData.variables ], + selectQueryData.where, + selectQueryData.orders, + 1, + ); + const entitySelectGroupQuery = this.sparqlSelectGroup([ entitySelectQuery ]); + selectQueryData.graphWhere.unshift(entitySelectGroupQuery); + const query = queryBuilder.buildConstructFromEntitySelectQuery( + selectQueryData.graphWhere, + selectQueryData.variables, + options?.select, + ); const generatedQuery = this.sparqlGenerator.stringify(query); const responseTriples = await this.executeSparqlSelectAndGetData(generatedQuery); if (responseTriples.length === 0) { @@ -72,41 +102,157 @@ export class SparqlQueryAdapter implements QueryAdapter { return jsonld as Entity; } + private sparqlSelect( + variables: Variable[], + where: Pattern[], + order: Ordering[], + limit?: number, + offset?: number, + ): SelectQuery { + return { + type: 'query', + queryType: 'SELECT', + variables, + where, + order: order.length > 0 ? order : undefined, + limit, + offset, + prefixes: {}, + }; + } + public async findBy(where: FindOptionsWhere): Promise { return this.find({ where }); } public async findAll(options?: FindAllOptions): Promise { const queryBuilder = new SparqlQueryBuilder(); - const query = queryBuilder.buildEntityQuery(options); + const selectQueryData = queryBuilder.buildPatternsFromQueryOptions( + entityVariable, + options?.where, + options?.order, + options?.relations, + ); + const entitySelectVariables = [ entityVariable, ...selectQueryData.variables ]; + const entitySelectQuery = this.sparqlSelect( + entitySelectVariables, + selectQueryData.where, + selectQueryData.orders, + options?.limit, + options?.offset, + ); + + let orderedEntityVariableValues: string[] | undefined; + if (selectQueryData.orders.length > 0 && options?.limit !== 1) { + // We need to execute the entity select query here first to get ordered results. + const generatedEntitySelectQuery = this.sparqlGenerator.stringify(entitySelectQuery); + const entitySelectResponse = + await this.executeSparqlSelectAndGetData>(generatedEntitySelectQuery); + const valuesByVariable = this.groupByKey(entitySelectResponse); + orderedEntityVariableValues = (valuesByVariable[entityVariable.value] as NamedNode[]) + .map((namedNode: NamedNode): string => namedNode.value); + const variableValueFilter = queryBuilder.buildInFilterForVariables(valuesByVariable); + selectQueryData.graphWhere.push(variableValueFilter); + } else { + const entitySelectGroupQuery = this.sparqlSelectGroup([ entitySelectQuery ]); + selectQueryData.graphWhere.unshift(entitySelectGroupQuery); + } + const query = queryBuilder.buildConstructFromEntitySelectQuery( + selectQueryData.graphWhere, + selectQueryData.variables, + options?.select, + ); const generatedQuery = this.sparqlGenerator.stringify(query); const responseTriples = await this.executeSparqlSelectAndGetData(generatedQuery); if (responseTriples.length === 0) { return []; } - const jsonld = await triplesToJsonld(responseTriples, options?.relations); + const jsonld = await triplesToJsonld(responseTriples, options?.relations, orderedEntityVariableValues); if (Array.isArray(jsonld)) { return jsonld as Entity[]; } return [ jsonld ] as Entity[]; } + private groupByKey( + entitySelectResponse: SelectVariableQueryResult[], + ): Record { + return entitySelectResponse + .reduce((obj: Record, result): Record => { + for (const [ key, value ] of Object.entries(result)) { + if (!(key in obj)) { + obj[key] = [ value ]; + } else { + obj[key].push(value); + } + } + return obj; + }, {}); + } + + private sparqlSelectGroup(patterns: Pattern[]): GroupPattern { + return { + type: 'group', + patterns, + }; + } + public async findAllBy(where: FindOptionsWhere): Promise { return this.findAll({ where }); } public async exists(where: FindOptionsWhere): Promise { const queryBuilder = new SparqlQueryBuilder(); - const query = queryBuilder.buildEntityExistQuery(where); + const selectQueryData = queryBuilder.buildPatternsFromQueryOptions(entityVariable, where); + const query = this.sparqlAsk(selectQueryData.where); return await this.executeAskQueryAndGetResponse(query); } + private sparqlAsk(where: Pattern[]): AskQuery { + return { + type: 'query', + queryType: 'ASK', + where, + prefixes: {}, + }; + } + public async count(where?: FindOptionsWhere): Promise { const queryBuilder = new SparqlQueryBuilder(); - const query = queryBuilder.buildEntityCountQuery(where); + const selectQueryData = queryBuilder.buildPatternsFromQueryOptions(entityVariable, where); + const query = this.sparqlCountSelect(selectQueryData.where, selectQueryData.graphWhere); return await this.executeSelectCountAndGetResponse(query); } + private sparqlCountSelect(where: Pattern[], graphWhere: Pattern[]): SelectQuery { + return { + type: 'query', + queryType: 'SELECT', + variables: [{ + expression: { + type: 'aggregate', + aggregation: 'count', + distinct: true, + expression: entityVariable, + }, + variable: countVariable, + }], + where: [ + this.sparqlSelectGraph(entityVariable, where), + ...graphWhere, + ], + prefixes: {}, + }; + } + + private sparqlSelectGraph(name: Variable | NamedNode, patterns: Pattern[]): GraphPattern { + return { + type: 'graph', + name: name as IriTerm, + patterns, + }; + } + public async save(entity: Entity): Promise; public async save(entities: Entity[]): Promise; public async save(entityOrEntities: Entity | Entity[]): Promise { diff --git a/src/storage/sparql/SparqlQueryBuilder.ts b/src/storage/sparql/SparqlQueryBuilder.ts index 3908bc6..d4ba024 100644 --- a/src/storage/sparql/SparqlQueryBuilder.ts +++ b/src/storage/sparql/SparqlQueryBuilder.ts @@ -2,7 +2,6 @@ import DataFactory from '@rdfjs/data-model'; import type { Variable, NamedNode, Term, Literal } from '@rdfjs/types'; import type { - SelectQuery, FilterPattern, Pattern, IriTerm, @@ -13,7 +12,6 @@ import type { GroupPattern, GraphPattern, Ordering, - AskQuery, OptionalPattern, BgpPattern, PropertyPath, @@ -22,7 +20,6 @@ import { allTypesAndSuperTypesPath, entityVariable, valueToLiteral, - countVariable, subjectNode, predicateNode, objectNode, @@ -33,7 +30,6 @@ import { RDF } from '../../util/Vocabularies'; import { FindOperator } from '../FindOperator'; import type { FieldPrimitiveValue, - FindAllOptions, FindOptionsOrder, FindOptionsOrderValue, FindOptionsRelations, @@ -89,89 +85,21 @@ export class SparqlQueryBuilder { this.variableGenerator = new VariableGenerator(); } - public buildEntityCountQuery(where?: FindOptionsWhere): SelectQuery { - const selectQueryData = this.buildPatternsFromQueryData(entityVariable, where); - return this.sparqlCountSelect(selectQueryData.where, selectQueryData.graphWhere); - } - - private sparqlCountSelect(where: Pattern[], graphWhere: Pattern[]): SelectQuery { - return { - type: 'query', - queryType: 'SELECT', - variables: [{ - expression: { - type: 'aggregate', - aggregation: 'count', - distinct: true, - expression: entityVariable, - }, - variable: countVariable, - }], - where: [ - this.sparqlSelectGraph(entityVariable, where), - ...graphWhere, - ], - prefixes: {}, - }; - } - - public buildEntityExistQuery(where: FindOptionsWhere): AskQuery { - const selectQueryData = this.buildPatternsFromQueryData(entityVariable, where); - return this.sparqlAsk(selectQueryData.where); - } - - private sparqlAsk(where: Pattern[]): AskQuery { - return { - type: 'query', - queryType: 'ASK', - where, - prefixes: {}, - }; - } - - public buildEntityQuery(options?: FindAllOptions): ConstructQuery { - const selectQueryData = this.buildPatternsFromQueryData( - entityVariable, - options?.where, - options?.order, - options?.relations, - ); - const entitySelectQuery = this.sparqlSelect( - [ entityVariable, ...selectQueryData.variables ], - selectQueryData.where, - selectQueryData.orders, - options?.limit, - options?.offset, - ); - // TODO: If there are orders and not a limit of 1 we should execute the select query first? - return this.sparqlConstruct( - entitySelectQuery, - selectQueryData.graphWhere, - selectQueryData.variables, - options?.select, + public buildInFilterForVariables(valuesByVariable: Record): FilterPattern { + return this.filterPatternFromFilters( + Object.entries(valuesByVariable).map(([ variableName, values ]): OperationExpression => + this.buildInOperation( + DataFactory.variable(variableName), + values, + )), ); } - private sparqlSelect( - variables: Variable[], - where: Pattern[], - order: Ordering[], - limit?: number, - offset?: number, - ): SelectQuery { - return { - type: 'query', - queryType: 'SELECT', - variables, - where, - order: order.length > 0 ? order : undefined, - limit, - offset, - prefixes: {}, - }; + private filterWithExpression(expression: Expression): FilterPattern { + return { type: 'filter', expression }; } - private buildPatternsFromQueryData( + public buildPatternsFromQueryOptions( subject: Variable, where?: FindOptionsWhere, order?: FindOptionsOrder, @@ -216,19 +144,13 @@ export class SparqlQueryBuilder { private filterPatternFromFilters(filters: Expression[]): FilterPattern { if (filters.length > 1) { - return { - type: 'filter', - expression: { - type: 'operation', - operator: '&&', - args: filters, - }, - }; + return this.filterWithExpression({ + type: 'operation', + operator: '&&', + args: filters, + }); } - return { - type: 'filter', - expression: filters[0], - }; + return this.filterWithExpression(filters[0]); } private sparqlOptionalWithTriples(triples: Triple[]): OptionalPattern { @@ -242,8 +164,7 @@ export class SparqlQueryBuilder { return { type: 'bgp', triples }; } - private sparqlConstruct( - graphSelectionQuery: SelectQuery, + public buildConstructFromEntitySelectQuery( graphWhere: Pattern[], graphSelectVariables: Variable[], select?: FindOptionsSelect, @@ -255,18 +176,15 @@ export class SparqlQueryBuilder { triples = this.createSelectPattern(select, entityVariable); where = [ this.sparqlOptionalSelectGraph(entityVariable, triples), - this.sparqlSelectGroup([ graphSelectionQuery ]), ...graphWhere ]; } else { - const graphSelectsAndTriplePatterns = - this.createGraphSelectsAndTriplePatterns(graphSelectVariables); + const graphSelectsAndTriplePatterns = this.createGraphSelectsAndTriplePatterns(graphSelectVariables); const entityGraphTriple = { subject: subjectNode, predicate: predicateNode, object: objectNode }; triples = [ entityGraphTriple, ...graphSelectsAndTriplePatterns.triples, ]; where = [ - this.sparqlSelectGroup([ graphSelectionQuery ]), ...graphWhere, this.sparqlSelectGraph( entityVariable, @@ -351,13 +269,6 @@ export class SparqlQueryBuilder { }; } - private sparqlSelectGroup(patterns: Pattern[]): GroupPattern { - return { - type: 'group', - patterns, - }; - } - private createQueryData( subject: Variable, where?: FindOptionsWhere, @@ -704,12 +615,11 @@ export class SparqlQueryBuilder { } catch { throw new Error(`Unsupported Not sub operator "${rightSide.operator}"`); } - filterExpression = { type: 'filter', expression }; + filterExpression = this.filterWithExpression(expression); } else { - filterExpression = { - type: 'filter', - expression: this.buildEqualOperation(leftSide, rightSide as Expression), - }; + filterExpression = this.filterWithExpression( + this.buildEqualOperation(leftSide, rightSide as Expression), + ); } return { type: 'operation', diff --git a/src/util/TripleUtil.ts b/src/util/TripleUtil.ts index 4b226a1..e811ff3 100644 --- a/src/util/TripleUtil.ts +++ b/src/util/TripleUtil.ts @@ -174,11 +174,12 @@ function sortGraphOfNodeObject(graphObject: GraphObject, nodeIdOrder: string[]): export async function triplesToJsonld( triples: Quad[], relations?: FindOptionsRelations, + orderedNodeIds?: string[], ): Promise> { const { nodeIdOrder, nodesById } = triplesToNodes(triples); const framed = await frameWithRelationsOrNonBlankNodes(nodesById, relations); if ('@graph' in framed) { - return sortNodesByOrder(framed['@graph'] as NodeObject[], nodeIdOrder); + return sortNodesByOrder(framed['@graph'] as NodeObject[], orderedNodeIds ?? nodeIdOrder); } return framed; } diff --git a/test/deploy/package.json b/test/deploy/package.json index c4c0516..4aebf90 100644 --- a/test/deploy/package.json +++ b/test/deploy/package.json @@ -7,7 +7,7 @@ }, "main": "./dist/index.js", "dependencies": { - "@comake/skql-js-engine": "file:./comake-skql-js-engine-0.13.0.tgz", + "@comake/skql-js-engine": "file:./comake-skql-js-engine-0.13.1.tgz", "jsonld": "^6.0.0" }, "devDependencies": { diff --git a/test/unit/storage/sparql/SparqlQueryAdapter.test.ts b/test/unit/storage/sparql/SparqlQueryAdapter.test.ts index 71fe029..0bc3951 100644 --- a/test/unit/storage/sparql/SparqlQueryAdapter.test.ts +++ b/test/unit/storage/sparql/SparqlQueryAdapter.test.ts @@ -424,6 +424,56 @@ describe('a SparqlQueryAdapter', (): void => { '}', ]); }); + + it('executes an entity selection query then queries for entities if there is an order and a limit greater than 1.', + async(): Promise => { + select.mockImplementationOnce( + async(): Promise => streamFrom([{ entity: data1 }, { entity: data2 }]), + ); + response = [ + { + subject: data1, + predicate: rdfTypeNamedNode, + object: file, + }, + { + subject: data2, + predicate: rdfTypeNamedNode, + object: file, + }, + ]; + await expect( + adapter.findAll({ + order: { + 'https://example.com/pred': 'asc', + }, + }), + ).resolves.toEqual([ + { + '@id': 'https://example.com/data/1', + '@type': 'https://skl.standard.storage/File', + }, + { + '@id': 'https://example.com/data/2', + '@type': 'https://skl.standard.storage/File', + }, + ]); + expect(select).toHaveBeenCalledTimes(2); + expect(select.mock.calls[0][0].split('\n')).toEqual([ + 'SELECT ?entity WHERE {', + ' ?entity ?c1 ?c2.', + ' OPTIONAL { ?entity ?c3. }', + '}', + 'ORDER BY (?c3)', + ]); + expect(select.mock.calls[1][0].split('\n')).toEqual([ + 'CONSTRUCT { ?subject ?predicate ?object. }', + 'WHERE {', + ' FILTER(?entity IN(, ))', + ' GRAPH ?entity { ?subject ?predicate ?object. }', + '}', + ]); + }); }); describe('findAllBy', (): void => { diff --git a/test/unit/storage/sparql/SparqlQueryBuilder.test.ts b/test/unit/storage/sparql/SparqlQueryBuilder.test.ts index 923847b..7db4179 100644 --- a/test/unit/storage/sparql/SparqlQueryBuilder.test.ts +++ b/test/unit/storage/sparql/SparqlQueryBuilder.test.ts @@ -11,7 +11,6 @@ import { LessThanOrEqual } from '../../../../src/storage/operator/LessThanOrEqua import { Not } from '../../../../src/storage/operator/Not'; import { SparqlQueryBuilder } from '../../../../src/storage/sparql/SparqlQueryBuilder'; import { - countVariable, entityVariable, objectNode, predicateNode, @@ -25,19 +24,12 @@ const c1 = DataFactory.variable('c1'); const c2 = DataFactory.variable('c2'); const c3 = DataFactory.variable('c3'); const c4 = DataFactory.variable('c4'); -const c5 = DataFactory.variable('c5'); -const c6 = DataFactory.variable('c6'); -const c7 = DataFactory.variable('c7'); -const c8 = DataFactory.variable('c8'); -const c9 = DataFactory.variable('c9'); -const c10 = DataFactory.variable('c10'); const predicate = DataFactory.namedNode('https://example.com/pred'); const predicate2 = DataFactory.namedNode('https://example.com/pred2'); const data1 = DataFactory.namedNode('https://example.com/data/1'); const data2 = DataFactory.namedNode('https://example.com/data/2'); const file = DataFactory.namedNode(SKL.File); const event = DataFactory.namedNode(SKL.Event); -const graphPattern = [{ subject: subjectNode, predicate: predicateNode, object: objectNode }]; describe('A SparqlQueryBuilder', (): void => { let builder: SparqlQueryBuilder; @@ -46,347 +38,185 @@ describe('A SparqlQueryBuilder', (): void => { builder = new SparqlQueryBuilder(); }); - describe('exists query', (): void => { - it('builds an ask query.', (): void => { - const query = { - type: 'query', - queryType: 'ASK', - prefixes: {}, - where: [{ - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate, - object: DataFactory.literal('1', XSD.integer), - }], - }], - }; - expect(builder.buildEntityExistQuery({ - 'https://example.com/pred': 1, - })).toEqual(query); + describe('#buildInFilterForVariables', (): void => { + it('builds an and expression between multiple in expressions.', (): void => { + expect(builder.buildInFilterForVariables({ + entity: [ + DataFactory.namedNode('https://example.com/data/1'), + DataFactory.namedNode('https://example.com/data/2'), + ], + })).toEqual({ + type: 'filter', + expression: { + type: 'operation', + operator: 'in', + args: [ + DataFactory.variable('entity'), + [ + DataFactory.namedNode('https://example.com/data/1'), + DataFactory.namedNode('https://example.com/data/2'), + ], + ], + }, + }); }); }); - describe('count query', (): void => { - it('builds a select count query.', (): void => { - const query = { - type: 'query', - queryType: 'SELECT', - prefixes: {}, - variables: [{ - expression: { - type: 'aggregate', - aggregation: 'count', - distinct: true, - expression: entityVariable, - }, - variable: countVariable, - }], + describe('#buildPatternsFromQueryOptions', (): void => { + it('builds a query without any options.', (): void => { + expect(builder.buildPatternsFromQueryOptions(entityVariable)).toEqual({ + variables: [], where: [{ - type: 'graph', - name: entityVariable, - patterns: [{ - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate, - object: DataFactory.literal('1', XSD.integer), - }], + type: 'bgp', + triples: [{ + subject: entityVariable, + predicate: c1, + object: c2, }], }], - }; - expect(builder.buildEntityCountQuery({ - 'https://example.com/pred': 1, - })).toEqual(query); - }); - }); - - describe('entity query', (): void => { - it('builds a query without any options.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, - where: [ - { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [{ - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate: c1, - object: c2, - }], - }], - limit: undefined, - offset: undefined, - order: undefined, - }], - }, - { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], - }, - ], - }; - expect(builder.buildEntityQuery()).toEqual(query); + orders: [], + graphWhere: [], + }); }); - it('builds a query with where, limit, and offset options.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, - where: [ - { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate: { - type: 'path', - pathType: '/', - items: [ - rdfTypeNamedNode, - { - type: 'path', - pathType: '*', - items: [ rdfsSubClassOfNamedNode ], - }, - ], - }, - object: file, - }, - { - subject: entityVariable, - predicate, - object: DataFactory.literal('1', XSD.integer), - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '=', - args: [ entityVariable, data1 ], - }, - }, - ], - limit: 5, - offset: 5, - order: undefined, - }], - }, - { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], - }, - ], - }; - expect(builder.buildEntityQuery({ - where: { + it('builds a query with where options.', (): void => { + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { id: 'https://example.com/data/1', type: SKL.File, 'https://example.com/pred': 1, }, - limit: 5, - offset: 5, - })).toEqual(query); - }); - - it('builds a query with one filter and no triple patterns.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate: { + type: 'path', + pathType: '/', + items: [ + rdfTypeNamedNode, { - subject: entityVariable, - predicate: c1, - object: c2, + type: 'path', + pathType: '*', + items: [ rdfsSubClassOfNamedNode ], }, ], }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '=', - args: [ entityVariable, data1 ], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + object: file, + }, + { + subject: entityVariable, + predicate, + object: DataFactory.literal('1', XSD.integer), + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: '=', + args: [ entityVariable, data1 ], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - id: 'https://example.com/data/1', - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); - it('builds a query with more than one filter.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + it('builds a query with one where filter and no triple patterns.', (): void => { + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { id: 'https://example.com/data/1' }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate: DataFactory.namedNode('https://example.com/nested'), - object: c1, - }, - { - subject: c1, - predicate: c2, - object: c3, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '&&', - args: [ - { - type: 'operation', - operator: '=', - args: [ entityVariable, data1 ], - }, - { - type: 'operation', - operator: '=', - args: [ c1, data2 ], - }, - ], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate: c1, + object: c2, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: '=', + args: [ entityVariable, data1 ], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { + orders: [], + graphWhere: [], + }); + }); + + it('builds a query with more than one filter.', (): void => { + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { id: 'https://example.com/data/1', 'https://example.com/nested': { id: 'https://example.com/data/2', }, }, - })).toEqual(query); - }); - - it('builds a query with a literal value filter.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate: DataFactory.namedNode('https://example.com/nested'), + object: c1, + }, + { + subject: c1, + predicate: c2, + object: c3, + }, + ], + }, + { + type: 'filter', + expression: { + type: 'operation', + operator: '&&', + args: [ { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate, - object: DataFactory.literal('{"alpha":1}', RDF.JSON), - }, - { - subject: entityVariable, - predicate: predicate2, - object: DataFactory.literal('false', XSD.boolean), - }, - { - subject: entityVariable, - predicate: DataFactory.namedNode('https://example.com/pred3'), - object: DataFactory.literal('hello', 'en'), - }, - ], + type: 'operation', + operator: '=', + args: [ entityVariable, data1 ], + }, + { + type: 'operation', + operator: '=', + args: [ c1, data2 ], }, ], - limit: undefined, - offset: undefined, - order: undefined, - }], - }, - { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { + orders: [], + graphWhere: [], + }); + }); + + it('builds a query with a literal value filter.', (): void => { + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { 'https://example.com/pred': { '@value': { alpha: 1 }, '@type': '@json', @@ -400,1032 +230,712 @@ describe('A SparqlQueryBuilder', (): void => { '@language': 'en', }, }, - })).toEqual(query); + )).toEqual({ + variables: [], + where: [ + { + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate, + object: DataFactory.literal('{"alpha":1}', RDF.JSON), + }, + { + subject: entityVariable, + predicate: predicate2, + object: DataFactory.literal('false', XSD.boolean), + }, + { + subject: entityVariable, + predicate: DataFactory.namedNode('https://example.com/pred3'), + object: DataFactory.literal('hello', 'en'), + }, + ], + }, + ], + orders: [], + graphWhere: [], + }); }); it('builds a query with a NamedNode filter.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + 'https://example.com/pred': 'https://example.com/object', + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate, - object: DataFactory.namedNode('https://example.com/object'), - }, - ], - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], - }, - { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate, + object: DataFactory.namedNode('https://example.com/object'), + }, + ], }, ], - }; - expect(builder.buildEntityQuery({ - where: { - 'https://example.com/pred': 'https://example.com/object', - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with an array valued filter.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + 'https://example.com/pred': [ 1, 2 ], + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate, - object: DataFactory.literal('1', XSD.integer), - }, - { - subject: entityVariable, - predicate, - object: DataFactory.literal('2', XSD.integer), - }, - ], - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], - }, - { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate, + object: DataFactory.literal('1', XSD.integer), + }, + { + subject: entityVariable, + predicate, + object: DataFactory.literal('2', XSD.integer), + }, + ], }, ], - }; - expect(builder.buildEntityQuery({ - where: { - 'https://example.com/pred': [ 1, 2 ], - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with an in operator on the id field.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + id: In([ 'https://example.com/data/1' ]), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate: c1, - object: c2, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: 'in', - args: [ entityVariable, [ data1 ]], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate: c1, + object: c2, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: 'in', + args: [ entityVariable, [ data1 ]], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - id: In([ 'https://example.com/data/1' ]), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with an in operator on the type field.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + type: In([ SKL.File, SKL.Event ]), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate: { + type: 'path', + pathType: '/', + items: [ + rdfTypeNamedNode, { - subject: entityVariable, - predicate: { - type: 'path', - pathType: '/', - items: [ - rdfTypeNamedNode, - { - type: 'path', - pathType: '*', - items: [ rdfsSubClassOfNamedNode ], - }, - ], - }, - object: c1, + type: 'path', + pathType: '*', + items: [ rdfsSubClassOfNamedNode ], }, ], }, - { - type: 'filter', - expression: { - type: 'operation', - operator: 'in', - args: [ c1, [ file, event ]], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + object: c1, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: 'in', + args: [ c1, [ file, event ]], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - type: In([ SKL.File, SKL.Event ]), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with an in operator on a non id field.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + 'https://example.com/pred': In([ 1, 2 ]), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate, - object: c1, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: 'in', - args: [ - c1, - [ - DataFactory.literal('1', XSD.integer), - DataFactory.literal('2', XSD.integer), - ], - ], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate, + object: c1, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: 'in', + args: [ + c1, + [ + DataFactory.literal('1', XSD.integer), + DataFactory.literal('2', XSD.integer), + ], + ], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - 'https://example.com/pred': In([ 1, 2 ]), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with a not operator on the id field.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + id: Not('https://example.com/data/1'), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate: c1, - object: c2, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '!=', - args: [ entityVariable, data1 ], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate: c1, + object: c2, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: '!=', + args: [ entityVariable, data1 ], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - id: Not('https://example.com/data/1'), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with a nested not in operator on the id field.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + id: Not(In([ 'https://example.com/data/1', 'https://example.com/data/2' ])), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate: c1, - object: c2, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: 'notin', - args: [ - entityVariable, - [ data1, data2 ], - ], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate: c1, + object: c2, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: 'notin', + args: [ + entityVariable, + [ data1, data2 ], + ], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - id: Not(In([ 'https://example.com/data/1', 'https://example.com/data/2' ])), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with a nested not equal operator on the id field.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + id: Not(Equal('https://example.com/data/1')), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate: c1, - object: c2, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '!=', - args: [ entityVariable, data1 ], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate: c1, + object: c2, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: '!=', + args: [ entityVariable, data1 ], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - id: Not(Equal('https://example.com/data/1')), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with an equal operator on the id field.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + id: Equal('https://example.com/data/1'), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate: c1, - object: c2, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '=', - args: [ entityVariable, data1 ], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate: c1, + object: c2, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: '=', + args: [ entityVariable, data1 ], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - id: Equal('https://example.com/data/1'), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with a not operator on a non id field.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + 'https://example.com/pred': Not(1), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate, + object: c1, + }, + ], + }, + { + type: 'filter', + expression: { + type: 'operation', + operator: 'notexists', + args: [{ + type: 'group', + patterns: [ + { + type: 'bgp', + triples: [{ subject: entityVariable, predicate, object: c1, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: 'notexists', - args: [{ - type: 'group', - patterns: [ - { - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate, - object: c1, - }], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '=', - args: [ - c1, - DataFactory.literal('1', XSD.integer), - ], - }, - }, - ], }], }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], - }, - { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + { + type: 'filter', + expression: { + type: 'operation', + operator: '=', + args: [ + c1, + DataFactory.literal('1', XSD.integer), + ], + }, + }, + ], + }], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - 'https://example.com/pred': Not(1), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with a nested not in operator on a non id field.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + 'https://example.com/pred': Not(In([ 1, 2 ])), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate, + object: c1, + }, + ], + }, + { + type: 'filter', + expression: { + type: 'operation', + operator: 'notexists', + args: [{ + type: 'group', + patterns: [ + { + type: 'bgp', + triples: [{ subject: entityVariable, predicate, object: c1, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: 'notexists', - args: [{ - type: 'group', - patterns: [ - { - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate, - object: c1, - }], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: 'in', - args: [ - c1, - [ - DataFactory.literal('1', XSD.integer), - DataFactory.literal('2', XSD.integer), - ], - ], - }, - }, - ], }], }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], - }, - { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + { + type: 'filter', + expression: { + type: 'operation', + operator: 'in', + args: [ + c1, + [ + DataFactory.literal('1', XSD.integer), + DataFactory.literal('2', XSD.integer), + ], + ], + }, + }, + ], + }], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - 'https://example.com/pred': Not(In([ 1, 2 ])), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with a nested not equal operator on a non id field.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + 'https://example.com/pred': Not(Equal(1)), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate, + object: c1, + }, + ], + }, + { + type: 'filter', + expression: { + type: 'operation', + operator: 'notexists', + args: [{ + type: 'group', + patterns: [ + { + type: 'bgp', + triples: [{ subject: entityVariable, predicate, object: c1, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: 'notexists', - args: [{ - type: 'group', - patterns: [ - { - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate, - object: c1, - }], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '=', - args: [ - c1, - DataFactory.literal('1', XSD.integer), - ], - }, - }, - ], }], }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], - }, - { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + { + type: 'filter', + expression: { + type: 'operation', + operator: '=', + args: [ + c1, + DataFactory.literal('1', XSD.integer), + ], + }, + }, + ], + }], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - 'https://example.com/pred': Not(Equal(1)), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with an equal operator on a non id field.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + 'https://example.com/pred': Equal(1), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate, - object: c1, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '=', - args: [ - c1, - DataFactory.literal('1', XSD.integer), - ], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate, + object: c1, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: '=', + args: [ + c1, + DataFactory.literal('1', XSD.integer), + ], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - 'https://example.com/pred': Equal(1), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with a gt operator.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + 'https://example.com/pred': GreaterThan(1), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate, - object: c1, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '>', - args: [ - c1, - DataFactory.literal('1', XSD.integer), - ], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate, + object: c1, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: '>', + args: [ + c1, + DataFactory.literal('1', XSD.integer), + ], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - 'https://example.com/pred': GreaterThan(1), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with a gte operator.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + 'https://example.com/pred': GreaterThanOrEqual(1), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate, - object: c1, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '>=', - args: [ - c1, - DataFactory.literal('1', XSD.integer), - ], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate, + object: c1, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: '>=', + args: [ + c1, + DataFactory.literal('1', XSD.integer), + ], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - 'https://example.com/pred': GreaterThanOrEqual(1), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with a lt operator.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + 'https://example.com/pred': LessThan(1), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate, - object: c1, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '<', - args: [ - c1, - DataFactory.literal('1', XSD.integer), - ], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate, + object: c1, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: '<', + args: [ + c1, + DataFactory.literal('1', XSD.integer), + ], + }, }, - ], - }; - expect(builder.buildEntityQuery({ - where: { - 'https://example.com/pred': LessThan(1), - }, - })).toEqual(query); + ], + orders: [], + graphWhere: [], + }); }); it('builds a query with a lte operator.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + 'https://example.com/pred': LessThanOrEqual(1), + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate, - object: c1, - }, - ], - }, - { - type: 'filter', - expression: { - type: 'operation', - operator: '<=', - args: [ - c1, - DataFactory.literal('1', XSD.integer), - ], - }, - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate, + object: c1, + }, + ], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'filter', + expression: { + type: 'operation', + operator: '<=', + args: [ + c1, + DataFactory.literal('1', XSD.integer), + ], + }, }, ], - }; - expect(builder.buildEntityQuery({ - where: { - 'https://example.com/pred': LessThanOrEqual(1), - }, - })).toEqual(query); + orders: [], + graphWhere: [], + }); }); it('builds a query with an inverse operator.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, - where: [ - { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [{ - type: 'bgp', - triples: [ - { - subject: entityVariable, - predicate: c1, - object: c2, - }, - ], - }], - }], - }, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + { + 'https://example.com/pred': Inverse(1), + }, + )).toEqual({ + variables: [], + where: [{ + type: 'bgp', + triples: [ + { + subject: entityVariable, + predicate: c1, + object: c2, + }, + ], + }], + orders: [], + graphWhere: [ { type: 'bgp', triples: [ @@ -1440,233 +950,245 @@ describe('A SparqlQueryBuilder', (): void => { }, ], }, - { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], - }, ], - }; - expect(builder.buildEntityQuery({ - where: { - 'https://example.com/pred': Inverse(1), - }, - })).toEqual(query); + }); }); it('throws an error if there is an unsupported operation on a non id field.', (): void => { expect((): void => { - builder.buildEntityQuery({ - where: { + builder.buildPatternsFromQueryOptions( + entityVariable, + { 'https://example.com/pred': { type: 'operator', operator: 'and', }, }, - }); + ); }).toThrow('Unsupported operator "and"'); }); it('throws an error if there is an unsupported operation on the id field.', (): void => { expect((): void => { - builder.buildEntityQuery({ - where: { + builder.buildPatternsFromQueryOptions( + entityVariable, + { id: { type: 'operator', operator: 'and' as any, } as FindOperator, }, - }); + ); }).toThrow('Unsupported operator "and"'); }); it('throws an error if there is an unsupported operation as an argument to a Not operator.', (): void => { expect((): void => { - builder.buildEntityQuery({ - where: { + builder.buildPatternsFromQueryOptions( + entityVariable, + { 'https://example.com/pred': Not({ type: 'operator', operator: 'and', }), }, - }); + ); }).toThrow('Unsupported Not sub operator "and"'); }); it('throws an error if there is an unsupported operation as an argument to a Not operator on the id field.', (): void => { expect((): void => { - builder.buildEntityQuery({ - where: { + builder.buildPatternsFromQueryOptions( + entityVariable, + { id: Not({ type: 'operator', operator: 'and', }) as unknown as FindOperator, }, - }); + ); }).toThrow('Unsupported Not sub operator "and"'); }); it('builds a query with a with an order.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + undefined, + { + 'https://example.com/pred': 'desc', + }, + )).toEqual({ + variables: [], where: [ { - type: 'group', + type: 'bgp', + triples: [{ + subject: entityVariable, + predicate: c1, + object: c2, + }], + }, + { + type: 'optional', patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate: c1, - object: c2, - }], - }, - { - type: 'optional', - patterns: [{ - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate, - object: c3, - }], - }], - }, - ], - limit: undefined, - offset: undefined, - order: [{ - expression: c3, - descending: true, + type: 'bgp', + triples: [{ + subject: entityVariable, + predicate, + object: c3, }], }], }, + ], + orders: [{ + expression: c3, + descending: true, + }], + graphWhere: [], + }); + }); + + it('builds a query with a with an order on id.', (): void => { + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + undefined, + { id: 'desc' }, + )).toEqual({ + variables: [], + where: [ { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'bgp', + triples: [{ + subject: entityVariable, + predicate: c1, + object: c2, + }], }, ], - }; - expect(builder.buildEntityQuery({ - order: { - 'https://example.com/pred': 'desc', - }, - })).toEqual(query); + orders: [{ + expression: entityVariable, + descending: true, + }], + graphWhere: [], + }); }); - it('builds a query with a with an order on id.', (): void => { - const query = { - type: 'query', - queryType: 'CONSTRUCT', - prefixes: {}, - template: graphPattern, + it('builds a query with selected relations.', (): void => { + expect(builder.buildPatternsFromQueryOptions( + entityVariable, + undefined, + undefined, + { + 'https://example.com/pred': { + 'https://example.com/pred2': true, + }, + }, + )).toEqual({ + variables: [ c3, c4 ], where: [ { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [ - { - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate: c1, - object: c2, - }], - }, - ], - limit: undefined, - offset: undefined, - order: [{ - expression: entityVariable, - descending: true, - }], + type: 'bgp', + triples: [{ + subject: entityVariable, + predicate: c1, + object: c2, }], }, { - type: 'graph', - name: entityVariable, - patterns: [{ type: 'bgp', triples: graphPattern }], + type: 'optional', + patterns: [ + { + triples: [ + { + subject: entityVariable, + predicate, + object: c3, + }, + ], + type: 'bgp', + }, + ], + }, + ], + orders: [], + graphWhere: [ + { + patterns: [ + { + triples: [ + { + object: c4, + predicate: predicate2, + subject: c3, + }, + ], + type: 'bgp', + }, + ], + type: 'optional', }, ], - }; - expect(builder.buildEntityQuery({ - order: { id: 'desc' }, - })).toEqual(query); + }); }); + }); - it('builds a query with a nested select clause.', (): void => { + describe('#buildConstructFromEntitySelectQuery', (): void => { + it('builds a construct query without a select clause.', (): void => { + const foo = DataFactory.variable('foo'); const selectPattern = [ - { subject: entityVariable, predicate, object: c3 }, - { subject: c3, predicate: predicate2, object: c4 }, + { subject: subjectNode, predicate: predicateNode, object: objectNode }, + { subject: c1, predicate: c2, object: c3 }, ]; - const query = { + expect(builder.buildConstructFromEntitySelectQuery( + [], + [ foo ], + )).toEqual({ type: 'query', - queryType: 'CONSTRUCT', prefixes: {}, + queryType: 'CONSTRUCT', template: selectPattern, where: [ { type: 'graph', name: entityVariable, patterns: [{ - type: 'optional', - patterns: [{ type: 'bgp', triples: selectPattern }], + type: 'bgp', + triples: [{ subject: subjectNode, predicate: predicateNode, object: objectNode }], }], }, { - type: 'group', + type: 'optional', patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [{ + type: 'graph', + name: foo, + patterns: [{ type: 'bgp', - triples: [{ - subject: entityVariable, - predicate: c1, - object: c2, - }], + triples: [{ subject: c1, predicate: c2, object: c3 }], }], - limit: undefined, - offset: undefined, - order: undefined, }], }, ], - }; - expect(builder.buildEntityQuery({ - select: { - 'https://example.com/pred': { - 'https://example.com/pred2': true, - }, - }, - })).toEqual(query); + }); }); - it('builds a query with an array of selections.', (): void => { + it('builds a construct query with a select clause.', (): void => { const selectPattern = [ - { subject: entityVariable, predicate, object: c3 }, - { subject: entityVariable, predicate: predicate2, object: c4 }, + { subject: entityVariable, predicate, object: c1 }, + { subject: c1, predicate: predicate2, object: c2 }, ]; - const query = { + expect(builder.buildConstructFromEntitySelectQuery( + [], + [], + { + 'https://example.com/pred': { + 'https://example.com/pred2': true, + }, + }, + )).toEqual({ type: 'query', - queryType: 'CONSTRUCT', prefixes: {}, + queryType: 'CONSTRUCT', template: selectPattern, where: [ { @@ -1677,160 +1199,38 @@ describe('A SparqlQueryBuilder', (): void => { patterns: [{ type: 'bgp', triples: selectPattern }], }], }, - { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable ], - where: [{ - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate: c1, - object: c2, - }], - }], - limit: undefined, - offset: undefined, - order: undefined, - }], - }, ], - }; - expect(builder.buildEntityQuery({ - select: [ + }); + }); + + it('builds a query with an array of selections.', (): void => { + const selectPattern = [ + { subject: entityVariable, predicate, object: c1 }, + { subject: entityVariable, predicate: predicate2, object: c2 }, + ]; + expect(builder.buildConstructFromEntitySelectQuery( + [], + [], + [ 'https://example.com/pred', 'https://example.com/pred2', ], - })).toEqual(query); - }); - - it('builds a query with selected relations.', (): void => { - const query = { + )).toEqual({ type: 'query', - queryType: 'CONSTRUCT', prefixes: {}, - template: [ - { subject: subjectNode, predicate: predicateNode, object: objectNode }, - { subject: c5, predicate: c6, object: c7 }, - { subject: c8, predicate: c9, object: c10 }, - ], + queryType: 'CONSTRUCT', + template: selectPattern, where: [ - { - type: 'group', - patterns: [{ - type: 'query', - prefixes: {}, - queryType: 'SELECT', - variables: [ entityVariable, c3, c4 ], - where: [ - { - type: 'bgp', - triples: [{ - subject: entityVariable, - predicate: c1, - object: c2, - }], - }, - { - type: 'optional', - patterns: [ - { - triples: [ - { - subject: entityVariable, - predicate, - object: c3, - }, - ], - type: 'bgp', - }, - ], - }, - ], - limit: undefined, - offset: undefined, - order: undefined, - }], - }, - { - patterns: [ - { - triples: [ - { - object: c4, - predicate: predicate2, - subject: c3, - }, - ], - type: 'bgp', - }, - ], - type: 'optional', - }, { type: 'graph', name: entityVariable, patterns: [{ - type: 'bgp', - triples: [ - { subject: subjectNode, predicate: predicateNode, object: objectNode }, - ], + type: 'optional', + patterns: [{ type: 'bgp', triples: selectPattern }], }], }, - { - type: 'optional', - patterns: [ - { - type: 'graph', - name: c3, - patterns: [ - { - type: 'bgp', - triples: [ - { - subject: c5, - predicate: c6, - object: c7, - }, - ], - }, - ], - }, - ], - }, - { - type: 'optional', - patterns: [ - { - type: 'graph', - name: c4, - patterns: [ - { - type: 'bgp', - triples: [ - { - subject: c8, - predicate: c9, - object: c10, - }, - ], - }, - ], - }, - ], - }, ], - }; - expect(builder.buildEntityQuery({ - relations: { - 'https://example.com/pred': { - 'https://example.com/pred2': true, - }, - }, - })).toEqual(query); + }); }); }); });