Skip to content

Commit 4b81224

Browse files
committed
feat: optimise triple rules
1 parent d100e89 commit 4b81224

File tree

8 files changed

+95
-73
lines changed

8 files changed

+95
-73
lines changed

.changeset/khaki-actors-shop.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hydrofoil/shape-to-query": patch
3+
---
4+
5+
Node Expressions which only produce a constructed terms are inlined into the `CONSTRUCT` clause

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/demo/public/docs/example/rules/triple-rule-unrelated.ttl.rq

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ CONSTRUCT {
55
?resource1 rdf:type schema:Person.
66
?resource1 schema:givenName ?resource2.
77
?resource1 schema:familyName ?resource3.
8-
?resource4 ?resource5 ?resource6.
8+
<http://example.org/JobTitles> hydra:member ?resource4.
99
}
1010
WHERE {
1111
?resource1 rdf:type schema:Person.
@@ -16,9 +16,7 @@ WHERE {
1616
}
1717
UNION
1818
{
19-
?resource1 rdf:type schema:Person.
20-
BIND(<http://example.org/JobTitles> AS ?resource4)
21-
BIND(hydra:member AS ?resource5)
22-
?resource1 schema:jobTitle ?resource6.
19+
?resource1 rdf:type schema:Person;
20+
schema:jobTitle ?resource4.
2321
}
2422
}

packages/demo/public/docs/example/rules/triple-rule.ttl.rq

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,12 @@ PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
22
PREFIX schema: <http://schema.org/>
33
CONSTRUCT {
44
?resource1 rdf:type schema:Person.
5-
?resource2 ?resource3 ?resource1.
6-
?resource1 ?resource6 ?resource7.
5+
?resource2 schema:relatedTo ?resource1.
6+
?resource1 schema:relatedTo ?resource3.
77
}
88
WHERE {
99
?resource1 rdf:type schema:Person.
10-
{
11-
?resource1 (schema:spouse|schema:children|schema:parent) ?resource2.
12-
BIND(schema:relatedTo AS ?resource3)
13-
}
10+
{ ?resource1 (schema:spouse|schema:children|schema:parent) ?resource2. }
1411
UNION
15-
{
16-
BIND(schema:relatedTo AS ?resource6)
17-
?resource1 (schema:spouse|schema:children|schema:parent) ?resource7.
18-
}
12+
{ ?resource1 (schema:spouse|schema:children|schema:parent) ?resource3. }
1913
}
Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
/* eslint-disable camelcase */
12
import $rdf from '@zazuko/env/web.js'
23
import type { GraphPointer } from 'clownface'
34
import { sh } from '@tpluscode/rdf-ns-builders/loose'
45
import { isGraphPointer } from 'is-graph-pointer'
56
import type { Pattern } from 'sparqljs'
7+
import type { Term, Variable } from '@rdfjs/types'
68
import type { NodeExpression } from '../nodeExpression/NodeExpression.js'
79
import { PatternBuilder } from '../nodeExpression/NodeExpression.js'
810
import type { ShapePatterns } from '../../lib/shapePatterns.js'
911
import type { ModelFactory } from '../ModelFactory.js'
10-
import type { Rule, Parameters } from './Rule.js'
12+
import type { Rule, Parameters as BuildParameters } from './Rule.js'
1113

1214
export default class TripleRule implements Rule {
1315
constructor(public subject: NodeExpression, public predicate: NodeExpression, public object: NodeExpression) {
@@ -31,32 +33,59 @@ export default class TripleRule implements Rule {
3133
)
3234
}
3335

34-
buildPatterns({ focusNode, variable, rootPatterns }: Parameters): ShapePatterns {
36+
buildPatterns({ focusNode, variable, rootPatterns }: BuildParameters): ShapePatterns {
37+
const args = { subject: focusNode, variable, rootPatterns }
3538
const builder = new PatternBuilder()
3639

37-
const subject = builder.build(this.subject, { subject: focusNode, variable, rootPatterns })
38-
const predicate = builder.build(this.predicate, { subject: focusNode, variable, rootPatterns })
39-
const object = builder.build(this.object, { subject: focusNode, variable, rootPatterns })
40-
41-
const whereClause: Pattern[] = [
42-
...rootPatterns,
43-
...subject.patterns,
44-
...predicate.patterns,
45-
...object.patterns,
46-
].map(pattern => {
47-
if (pattern.type === 'query') {
48-
return {
49-
type: 'group',
50-
patterns: [pattern],
51-
}
52-
}
40+
let constructSubject = getInlineTerm(this.subject, [args, builder], 'NamedNode', 'BlankNode')
41+
let constructPredicate = getInlineTerm(this.predicate, [args, builder], 'NamedNode')
42+
let constructObject = getInlineTerm(this.object, [args, builder], 'NamedNode', 'BlankNode', 'Literal')
43+
44+
const whereClause: Pattern[] = []
45+
if (!constructSubject || !constructPredicate || !constructObject) {
46+
whereClause.push(...rootPatterns)
47+
}
48+
49+
function buildExpression(expression: NodeExpression): Variable {
50+
const { patterns, object } = builder.build(expression, args)
51+
whereClause.push(...patterns)
52+
return object
53+
}
5354

54-
return pattern
55-
})
55+
constructSubject = constructSubject || buildExpression(this.subject)
56+
constructPredicate = constructPredicate || buildExpression(this.predicate)
57+
constructObject = constructObject || buildExpression(this.object)
5658

5759
return {
58-
constructClause: [$rdf.quad(subject.object, predicate.object, object.object)],
59-
whereClause,
60+
constructClause: [$rdf.quad(constructSubject, constructPredicate, constructObject)],
61+
whereClause: whereClause.map(pattern => {
62+
if (pattern.type === 'query') {
63+
return {
64+
type: 'group',
65+
patterns: [pattern],
66+
}
67+
}
68+
69+
return pattern
70+
}),
6071
}
6172
}
6273
}
74+
75+
type ArrayToUnion<T> = T extends (infer U)[] ? U : never
76+
type LimitedTerm<T extends Term['termType']> = Term & { termType: ArrayToUnion<T> }
77+
78+
function getInlineTerm<T extends Term['termType']>(expression: NodeExpression, args: Parameters<NodeExpression['buildInlineExpression']>, ...termType: T[]): Variable | LimitedTerm<T> | undefined {
79+
const result = expression.buildInlineExpression?.(...args)
80+
if (result && !result.patterns && 'termType' in result.inline) {
81+
if (result.inline.termType === 'Variable') {
82+
return result.inline
83+
}
84+
85+
if ((termType as string[]).includes(result.inline.termType)) {
86+
return result.inline as LimitedTerm<T>
87+
}
88+
}
89+
90+
return undefined
91+
}

packages/shape-to-query/test/__snapshots__/index.test.ts.snap

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`@hydrofoil/shape-to-query shape with count rule generates correct query 1`] = `
4-
"CONSTRUCT { ?q1 ?q2 ?q3. }
4+
"CONSTRUCT { <http://example.org/collection> <http://example.org/numberOfArticles> ?q1. }
55
WHERE {
6-
BIND(<http://example.org/collection> AS ?q1)
7-
BIND(<http://example.org/numberOfArticles> AS ?q2)
86
{
9-
SELECT (COUNT(DISTINCT ?q4) AS ?q3) WHERE {
10-
?q4 schema:name ?q5.
11-
FILTER(REGEX(?q5, \\"Tech\\", \\"i\\"))
12-
?q4 rdf:type ?q6.
13-
FILTER(EXISTS { ?q4 rdf:type schema:DefinedTermSet, <https://cube.link/meta/SharedDimension>. })
7+
SELECT (COUNT(DISTINCT ?q2) AS ?q1) WHERE {
8+
?q2 schema:name ?q3.
9+
FILTER(REGEX(?q3, \\"Tech\\", \\"i\\"))
10+
?q2 rdf:type ?q4.
11+
FILTER(EXISTS { ?q2 rdf:type schema:DefinedTermSet, <https://cube.link/meta/SharedDimension>. })
1412
}
1513
}
1614
}"

packages/shape-to-query/test/__snapshots__/store.test.ts.snap

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -483,8 +483,8 @@ exports[`@hydrofoil/shape-to-query executing queries union of triple rules 1`] =
483483
PREFIX hydra: <http://www.w3.org/ns/hydra/core#>
484484
CONSTRUCT {
485485
?resource1 rdf:type ?resource2.
486-
?resource1 ?resource4 ?resource5.
487-
?resource6 ?resource7 ?resource8.
486+
?resource1 hydra:view ?resource3.
487+
?resource4 rdf:type <http://example.org/AlphabeticallyPagedView>.
488488
}
489489
WHERE {
490490
VALUES ?resource1 {
@@ -497,17 +497,14 @@ WHERE {
497497
VALUES ?resource1 {
498498
<http://example.org/people>
499499
}
500-
BIND(hydra:view AS ?resource4)
501-
BIND(IRI(CONCAT(STR(?resource1), \\"#index\\")) AS ?resource5)
500+
BIND(IRI(CONCAT(STR(?resource1), \\"#index\\")) AS ?resource3)
502501
}
503502
UNION
504503
{
505504
VALUES ?resource1 {
506505
<http://example.org/people>
507506
}
508-
BIND(IRI(CONCAT(STR(?resource1), \\"#index\\")) AS ?resource6)
509-
BIND(rdf:type AS ?resource7)
510-
BIND(<http://example.org/AlphabeticallyPagedView> AS ?resource8)
507+
BIND(IRI(CONCAT(STR(?resource1), \\"#index\\")) AS ?resource4)
511508
}
512509
}
513510
}"

packages/shape-to-query/test/index.test.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -374,34 +374,35 @@ describe('@hydrofoil/shape-to-query', () => {
374374
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
375375
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
376376
CONSTRUCT {
377-
?resource1 schema:mainEntity ?resource2.
378-
?resource3 ?resource4 ?resource5.
377+
?q1 schema:mainEntity ?q2.
378+
?q3 rdfs:label ?q4.
379379
}
380380
WHERE {
381381
{
382-
SELECT ?resource1 ?resource2 {
383-
VALUES (?resource1) {
384-
(<https://new.wikibus.org/page/brands>)
382+
SELECT ?q1 ?q2 WHERE {
383+
VALUES ?q1 {
384+
<https://new.wikibus.org/page/brands>
385385
}
386-
?resource1 schema:mainEntity ?resource2 .
386+
?q1 schema:mainEntity ?q2.
387387
}
388388
}
389389
UNION
390390
{
391-
SELECT ?resource3 ?resource4 ?resource5 {
392-
VALUES (?resource1) {
391+
SELECT ?q3 ?q4 WHERE {
392+
VALUES ?q1 {
393393
(<https://new.wikibus.org/page/brands>)
394394
}
395-
?resource1 schema:mainEntity ?resource2.
396-
?resource2 rdf:type*/hydra:memberAssertion ?resource9.
397-
?resource9 hydra:property ?resource11.
398-
VALUES ?resource11 { rdf:type }
399-
?resource9 hydra:object ?resource8.
400-
?resource8 ^rdf:type ?resource7.
401-
?resource7 skos:prefLabel ?resource6.
402-
BIND(IRI((CONCAT((str(?resource2)), "?i=", (ENCODE_FOR_URI((LCASE((SUBSTR(?resource6, 1, 1))))))))) as ?resource3)
403-
BIND(rdfs:label as ?resource4)
404-
BIND(UCASE((SUBSTR(?resource6, 1 , 1 ))) as ?resource5)
395+
?q1 schema:mainEntity ?q2.
396+
?q2 ((rdf:type*)/hydra:memberAssertion) ?q5.
397+
?q5 hydra:property ?q6.
398+
VALUES ?q6 {
399+
rdf:type
400+
}
401+
?q5 hydra:object ?q7.
402+
?q7 ^rdf:type ?q8.
403+
?q8 skos:prefLabel ?q9.
404+
BIND(IRI(CONCAT(STR(?q2), "?q10=", ENCODE_FOR_URI(LCASE(SUBSTR(?q9, 1 , 1 ))))) AS ?q3)
405+
BIND(UCASE(SUBSTR(?q9, 1 , 1 )) AS ?q4)
405406
}
406407
}
407408
}`)

0 commit comments

Comments
 (0)