From d6ccfe64ca6a7cb62e29ab9e73afc5ceae94cbb1 Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Tue, 31 Dec 2024 14:51:16 +0100 Subject: [PATCH] fix: update full text querying and totals --- .../lib/domain/shared-dimensions.ts | 8 +- .../lib/handlers/collection.ts | 9 +- apis/shared-dimensions/lib/shapeToQuery.ts | 87 ++++++++++++++----- apis/shared-dimensions/package.json | 2 +- yarn.lock | 79 ++++++++--------- 5 files changed, 113 insertions(+), 72 deletions(-) diff --git a/apis/shared-dimensions/lib/domain/shared-dimensions.ts b/apis/shared-dimensions/lib/domain/shared-dimensions.ts index 1a29248a1..69dba6b8a 100644 --- a/apis/shared-dimensions/lib/domain/shared-dimensions.ts +++ b/apis/shared-dimensions/lib/domain/shared-dimensions.ts @@ -98,9 +98,11 @@ export async function getSharedTerms({ s } const { constructQuery } = await shapeToQuery() - return client.query.construct(constructQuery(shape), { - operation: 'postDirect', - }) as any + return { + members: await client.query.construct(constructQuery(shape), { + operation: 'postDirect', + }) as any, + } } async function loadShape(shape: string, shapeType: NamedNode = sh.NodeShape) { diff --git a/apis/shared-dimensions/lib/handlers/collection.ts b/apis/shared-dimensions/lib/handlers/collection.ts index 6d1f4e619..a9028e0d2 100644 --- a/apis/shared-dimensions/lib/handlers/collection.ts +++ b/apis/shared-dimensions/lib/handlers/collection.ts @@ -5,7 +5,7 @@ import { hydra, rdf } from '@tpluscode/rdf-ns-builders' export interface CollectionData = Stream | Iterable> { members: M - totalItems: number + totalItems?: number } interface CollectionHandler { @@ -25,7 +25,12 @@ export function getCollection({ collection, view, data: { members: memberQuads, graph.node(collection) .addOut(rdf.type, [hydra.Collection, collectionType]) .addOut(hydra.member, members) - .addOut(hydra.totalItems, totalItems) + + if (totalItems) { + graph.node(collection).addOut(hydra.totalItems, totalItems) + } else { + graph.node(collection).addOut(hydra.totalItems, members.terms.length) + } if (view) { graph.node(view) diff --git a/apis/shared-dimensions/lib/shapeToQuery.ts b/apis/shared-dimensions/lib/shapeToQuery.ts index d63103eee..043be862d 100644 --- a/apis/shared-dimensions/lib/shapeToQuery.ts +++ b/apis/shared-dimensions/lib/shapeToQuery.ts @@ -1,14 +1,14 @@ import onetime from 'onetime' import { md } from '@cube-creator/core/namespace' -import { AnyPointer, GraphPointer } from 'clownface' +import clownface, { AnyPointer, GraphPointer } from 'clownface' import { isGraphPointer } from 'is-graph-pointer' import { hydra, sh } from '@tpluscode/rdf-ns-builders' import { Parameters, PropertyShape } from '@hydrofoil/shape-to-query/model/constraint/ConstraintComponent' import evalTemplateLiteral from 'rdf-loader-code/evalTemplateLiteral.js' import namespace from '@rdfjs/namespace' -import { sparql } from '@tpluscode/sparql-builder' import $rdf from 'rdf-ext' import type { Literal } from '@rdfjs/types' +import type { ServicePattern, GroupPattern } from 'sparqljs' import env from './env' /* @@ -21,7 +21,11 @@ const _importDynamic = new Function('modulePath', 'return import(modulePath)') export default async function shapeToQuery(): Promise> { await setup() - const { constructQuery, deleteQuery, s2q } = await _importDynamic('@hydrofoil/shape-to-query') + const { + constructQuery, + deleteQuery, + s2q, + } = await _importDynamic('@hydrofoil/shape-to-query') as typeof import('@hydrofoil/shape-to-query') return { constructQuery, @@ -76,9 +80,9 @@ const setup = onetime(async () => { }) async function defineConstraintComponents() { - const { default: ConstraintComponent } = await _importDynamic('@hydrofoil/shape-to-query/model/constraint/ConstraintComponent.js') - const { constraintComponents } = await _importDynamic('@hydrofoil/shape-to-query/model/constraint/index.js') - const { PatternConstraintComponent } = await _importDynamic('@hydrofoil/shape-to-query/model/constraint/pattern.js') + const { default: ConstraintComponent } = await _importDynamic('@hydrofoil/shape-to-query/model/constraint/ConstraintComponent.js') as typeof import('@hydrofoil/shape-to-query/model/constraint/ConstraintComponent.js') + const { constraintComponents } = await _importDynamic('@hydrofoil/shape-to-query/model/constraint/index.js') as typeof import('@hydrofoil/shape-to-query/model/constraint/index.js') + const { PatternConstraintComponent } = await _importDynamic('@hydrofoil/shape-to-query/model/constraint/pattern.js') as typeof import('@hydrofoil/shape-to-query/model/constraint/pattern.js') constraintComponents.set(md.FreeTextSearchConstraintComponent, class TextSearch extends ConstraintComponent { static match(pointer: GraphPointer) { @@ -101,7 +105,7 @@ async function defineConstraintComponents() { yield new TextSearch('fuseki', patternElement.pointer.value) break default: - yield new PatternConstraintComponent('^' + patternElement.pointer.value) + yield new PatternConstraintComponent(patternElement.pointer.term as Literal) } } } @@ -110,29 +114,66 @@ async function defineConstraintComponents() { super(md.FreeTextSearchConstraintComponent) } - buildPatterns({ focusNode, valueNode, propertyPath }: Parameters): any { + buildPropertyShapePatterns(args: Parameters) { if (this.vendor === 'stardog') { - const fts = namespace('tag:stardog:api:search:') - return sparql` - service ${fts.textMatch} { - [] ${fts.query} """${this.pattern + '*'}"""; - ${fts.result} ${valueNode} ; - } - ${focusNode} ${propertyPath} ${valueNode} . - ` + return [this.stardogServiceGroup(args)] } if (this.vendor === 'fuseki') { - return sparql` - ${focusNode} (${propertyPath} """${this.pattern + '*'}""") . - - # Second filtering to make sure the word starts with the given query - ${focusNode} ${propertyPath} ${valueNode} . - FILTER (REGEX(${valueNode}, "^${this.pattern}", "i")) - ` + return [this.fusekiPatterns(args)] } throw new Error('Unsupported vendor') } + + stardogServiceGroup({ focusNode, valueNode, propertyPath }: Parameters): ServicePattern { + if (!propertyPath || !('value' in propertyPath)) { + throw new Error('Property path must be a named node') + } + + const fts = namespace('tag:stardog:api:search:') + + const patterns = clownface({ dataset: $rdf.dataset() }) + .blankNode() + .addOut(fts.query, $rdf.literal(this.pattern + '*')) + .addOut(fts.result, valueNode) + .node(focusNode).addOut(propertyPath, valueNode) + + return { + type: 'service', + name: fts.textMatch, + silent: false, + patterns: [{ + type: 'bgp', + triples: [...patterns.dataset], + }], + } + } + + fusekiPatterns({ focusNode, valueNode, propertyPath }: Parameters): GroupPattern { + if (!propertyPath || !('value' in propertyPath)) { + throw new Error('Property path must be a named node') + } + + const patterns = clownface({ dataset: $rdf.dataset() }) + .node(focusNode) + .addList($rdf.namedNode('http://jena.apache.org/text#query'), [propertyPath, $rdf.literal(this.pattern + '*')]) + .addOut(propertyPath, valueNode) // Second filtering to make sure the word starts with the given query + + return { + type: 'group', + patterns: [{ + type: 'bgp', + triples: [...patterns.dataset], + }, { + type: 'filter', + expression: { + type: 'operation', + operator: 'regex', + args: [valueNode, $rdf.literal('^' + this.pattern), $rdf.literal('i')], + }, + }], + } + } }) } diff --git a/apis/shared-dimensions/package.json b/apis/shared-dimensions/package.json index 160f5172e..0db7eb461 100644 --- a/apis/shared-dimensions/package.json +++ b/apis/shared-dimensions/package.json @@ -7,7 +7,7 @@ "@cube-creator/core": "1.0.0", "@cube-creator/express": "0.0.0", "@hydrofoil/labyrinth": "^0.4.2", - "@hydrofoil/shape-to-query": "^0.13.4", + "@hydrofoil/shape-to-query": "^0.13.5", "@rdfine/hydra": "^0.8.2", "@rdfine/rdfs": "^0.6.4", "@rdfine/schema": "^0.6.3", diff --git a/yarn.lock b/yarn.lock index f3dc20a44..ae2848407 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1566,19 +1566,19 @@ rdf-loaders-registry "^0.2.0" sparql-http-client "^2.2.2" -"@hydrofoil/shape-to-query@^0.13.4": - version "0.13.4" - resolved "https://registry.yarnpkg.com/@hydrofoil/shape-to-query/-/shape-to-query-0.13.4.tgz#61ac73a33a1c8a34f6652a284ec7e2fdf5101b12" - integrity sha512-Q9GDAacePZZQkwnOxOD43BlkWBNDKr+VR05l3B8Q99D9gKYZ5AeF8kNDdH3za9LzES9Hc3hs/g0R8sM0Rz8WiQ== +"@hydrofoil/shape-to-query@^0.13.5": + version "0.13.5" + resolved "https://registry.yarnpkg.com/@hydrofoil/shape-to-query/-/shape-to-query-0.13.5.tgz#246ffcaaf51d1ee3f1f62162310c23a2ad28d7d9" + integrity sha512-bwjUzeOLEV48tldt+0pGH8y21MOTO9cw6A78boM9i+bpKR6UJqAZ6Rv8RmeGSY5GTIILfuelqP/kgOgvM0C6Xw== dependencies: - "@hydrofoil/sparql-processor" "^0.1.2" + "@hydrofoil/sparql-processor" "^0.1.3" "@tpluscode/rdf-ns-builders" ">=3.0.2" "@tpluscode/rdf-string" "^1.3.3" "@types/sparqljs" "^3.1.11" "@vocabulary/dash" "^1.0.4" "@vocabulary/dash-sparql" "^1.0.4" "@vocabulary/sh" "^1.1.5" - "@zazuko/env" "^2.2.0" + "@zazuko/env" "^2.4.2" "@zazuko/prefixes" ">=2" clownface-shacl-path "^2.2" is-graph-pointer "^2.0.0" @@ -1667,10 +1667,10 @@ concat-merge "^1.0.3" lit "^2.0.0" -"@hydrofoil/sparql-processor@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@hydrofoil/sparql-processor/-/sparql-processor-0.1.2.tgz#5241204d114c98322a67ffec86fb2398b216586c" - integrity sha512-GjzGkiIA9mjEN8SElcPSfbO43JCpf+tz+r7cErXsb8CtjbP5Lw+/V54jJtu4Y2T0tx5azzXlCwHJC244Svzt5A== +"@hydrofoil/sparql-processor@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@hydrofoil/sparql-processor/-/sparql-processor-0.1.3.tgz#076b1a5ef53fc9ee65143afbad390b7247531689" + integrity sha512-QVgIy+H3038t0r2EtPuknn1I9Cn2foVKk36uU3Wk+npceFu995kTkH1T77AN4KRxsSZBUyENN9XUxaCvUwiN+Q== dependencies: "@types/sparqljs" "^3.1.11" "@zazuko/prefixes" "^2.1.0" @@ -3009,6 +3009,11 @@ dependencies: xmlchars "^2.2.0" +"@sec-ant/readable-stream@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz#60de891bb126abfdc5410fdc6166aca065f10a0c" + integrity sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg== + "@sentry/browser@6.17.9": version "6.17.9" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.17.9.tgz#62eac0cc3c7c788df6b4677fe9882d3974d84027" @@ -4956,10 +4961,10 @@ "@zazuko/env" "^2.1.1" "@zazuko/rdf-utils-fs" "^3.3.0" -"@zazuko/env@^2.1.1", "@zazuko/env@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@zazuko/env/-/env-2.2.0.tgz#090ca10466113428483bfffeedf981b0b411d8e5" - integrity sha512-73KwqrckawQTmoPAizlkHIRpsqFOaR31LA4XEZUnLM6CbGF/DhmbclfhMtg8GBAiZLIVdgXj6GZAW0lUmA4knQ== +"@zazuko/env@^2.1.1", "@zazuko/env@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@zazuko/env/-/env-2.4.2.tgz#a9c3df642e3567f681ad23c3635cafa6bb2fb85d" + integrity sha512-mq3YTIs9dmXh2nyS2Hm94bNRHuItlgrp+5QS1zG6ClSpmgztKhMYNRhssmZvd0kBcpYZSV4+EogI/QxrkCg6ww== dependencies: "@rdfjs/data-model" "^2.0.1" "@rdfjs/dataset" "^2.0.1" @@ -4972,7 +4977,7 @@ "@zazuko/env-core" "^1.1.2" "@zazuko/prefixes" "^2.1.0" clownface "^2.0.2" - get-stream "^8.0.1" + get-stream "^9.0.1" rdf-dataset-ext "^1.1.0" "@zazuko/node-fetch@^2.6.6": @@ -8925,6 +8930,14 @@ get-stream@^8.0.1: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== +get-stream@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-9.0.1.tgz#95157d21df8eb90d1647102b63039b1df60ebd27" + integrity sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA== + dependencies: + "@sec-ant/readable-stream" "^0.4.1" + is-stream "^4.0.1" + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -9873,6 +9886,11 @@ is-stream@^3.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== +is-stream@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-4.0.1.tgz#375cf891e16d2e4baec250b85926cffc14720d9b" + integrity sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A== + is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -14287,7 +14305,7 @@ string-to-stream@^3.0.1: dependencies: readable-stream "^3.4.0" -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -14304,15 +14322,6 @@ string-width@^2.1.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -14382,7 +14391,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -14396,13 +14405,6 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -15577,7 +15579,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -15603,15 +15605,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"