From 8ad392ff370aaed5b1606a3b49a1f55b31a23886 Mon Sep 17 00:00:00 2001 From: jagzmz Date: Tue, 3 Sep 2024 17:51:23 +0530 Subject: [PATCH] feat: Implement initial subquery implementation --- src/storage/FindOptionsTypes.ts | 9 ++++ .../sparql/SparqlQueryBuilder.ts | 33 +++++++++++++++ .../sparql/SparqlQueryAdapter.test.ts | 42 ++++++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/storage/FindOptionsTypes.ts b/src/storage/FindOptionsTypes.ts index 43ac687..00da288 100644 --- a/src/storage/FindOptionsTypes.ts +++ b/src/storage/FindOptionsTypes.ts @@ -75,9 +75,18 @@ export interface FindOptionsWhere { [k: string]: FindOptionsWhereField | undefined; } +// Add these new types +export interface SubQuery { + select: Variable[]; + where: FindOptionsWhere; + groupBy?: string[]; + having?: FindOptionsWhere; +} + export interface FindAllOptions extends FindOneOptions { offset?: number; limit?: number; + subQueries?: SubQuery[]; } export interface FindExistsOptions { diff --git a/src/storage/query-adapter/sparql/SparqlQueryBuilder.ts b/src/storage/query-adapter/sparql/SparqlQueryBuilder.ts index d5a51aa..a04b4ad 100644 --- a/src/storage/query-adapter/sparql/SparqlQueryBuilder.ts +++ b/src/storage/query-adapter/sparql/SparqlQueryBuilder.ts @@ -1,3 +1,7 @@ +/* eslint-disable max-len */ +/* eslint-disable id-length */ +/* eslint-disable arrow-parens */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import DataFactory from '@rdfjs/data-model'; import type { Variable, NamedNode, Term, Literal } from '@rdfjs/types'; import type { @@ -13,6 +17,8 @@ import type { Pattern, ConstructQuery, GraphPattern, + Grouping, + SelectQuery, } from 'sparqljs'; import { allTypesAndSuperTypesPath, @@ -59,6 +65,7 @@ import type { FindOptionsWhere, FindOptionsWhereField, IdFindOptionsWhereField, + SubQuery, TypeFindOptionsWhereField, ValueWhereFieldObject, } from '../../FindOptionsTypes'; @@ -104,6 +111,7 @@ export interface SparqlQueryBuilderOptions { select?: FindOptionsSelect; order?: FindOptionsOrder; relations?: FindOptionsRelations; + subQueries?: SubQuery[]; } export class SparqlQueryBuilder { @@ -121,6 +129,11 @@ export class SparqlQueryBuilder { const whereQueryData = this.createWhereQueryData(subject, options?.where, true); const orderQueryData = this.createOrderQueryData(subject, options?.order); const relationsQueryData = this.createRelationsQueryData(subject, relations); + // Handle subqueries + if (options?.subQueries && options.subQueries.length > 0) { + const subQueryPatterns = this.createSubQueryPatterns(options.subQueries); + whereQueryData.values.unshift(...subQueryPatterns as ValuesPattern[]); + } const patterns: Pattern[] = whereQueryData.values; if (whereQueryData.triples.length === 0 && ( whereQueryData.filters.length > 0 || @@ -163,6 +176,26 @@ export class SparqlQueryBuilder { }; } + private createSubQueryPatterns(subQueries: SubQuery[]): Pattern[] { + return subQueries.map((subQuery: SubQuery): Pattern => { + const subQueryWhere = this.createWhereQueryData(entityVariable, subQuery.where); + const selectQuery: SelectQuery = { + type: 'query', + queryType: 'SELECT', + variables: subQuery.select, + where: this.createWherePatternsFromQueryData( + subQueryWhere.values, + subQueryWhere.triples, + subQueryWhere.filters, + ), + group: subQuery.groupBy ? subQuery.groupBy.map((g) => ({ expression: DataFactory.variable(g) } as Grouping)) : undefined, + having: subQuery.having ? this.createWhereQueryData(entityVariable, subQuery.having).filters : undefined, + prefixes: {}, + }; + return createSparqlSelectGroup([ selectQuery ]); + }); + } + private createEntityGraphFilterPattern(subject: Variable): GraphPattern { const entityFilterTriple = { subject, predicate: this.createVariable(), object: this.createVariable() }; return createSparqlGraphPattern( diff --git a/test/unit/storage/query-adapter/sparql/SparqlQueryAdapter.test.ts b/test/unit/storage/query-adapter/sparql/SparqlQueryAdapter.test.ts index 6d5e549..ae563f9 100644 --- a/test/unit/storage/query-adapter/sparql/SparqlQueryAdapter.test.ts +++ b/test/unit/storage/query-adapter/sparql/SparqlQueryAdapter.test.ts @@ -706,7 +706,7 @@ describe('a SparqlQueryAdapter', (): void => { await adapter.findAll({ where: { type: 'https://schema.org/Place', - 'https://standardknowledge.com/ontologies/core/deduplicationGroup': '?deduplicationGroup' + 'https://standardknowledge.com/ontologies/core/deduplicationGroup': '?deduplicationGroup', }, group: DataFactory.variable('deduplicationGroup'), entitySelectVariable: { @@ -736,6 +736,46 @@ describe('a SparqlQueryAdapter', (): void => { }); }); + it('executes a subquery.', async(): Promise => { + await adapter.findAll({ + where: { + type: 'https://schema.org/Place', + }, + subQueries: [ + { + select: [ DataFactory.variable('deduplicationGroup'), { + variable: DataFactory.variable('entity'), + expression: { + type: 'aggregate', + aggregation: 'MIN', + expression: DataFactory.variable('entity'), + }, + }], + where: { + 'https://standardknowledge.com/ontologies/core/deduplicationGroup': '?deduplicationGroup', + }, + groupBy: [ 'deduplicationGroup' ], + }, + ], + }); + expect(select.mock.calls[0][0].split('\n')).toEqual([ + 'CONSTRUCT { ?subject ?predicate ?object. }', + 'WHERE {', + ' {', + ' SELECT DISTINCT ?entity WHERE {', + ' {', + ' SELECT ?deduplicationGroup (MIN(?entity) AS ?entity) WHERE { ?entity ?deduplicationGroup. }', + ' GROUP BY ?deduplicationGroup', + ' }', + ' ?entity (/(*)) .', + ' FILTER(EXISTS { GRAPH ?entity { ?entity ?c1 ?c2. } })', + ' }', + ' }', + ' GRAPH ?entity { ?subject ?predicate ?object. }', + '}', + ]); + }); + describe('findAllBy', (): void => { it('queries for entities and returns an empty array if there are no results.', async(): Promise => {