Skip to content

Commit

Permalink
feat: delete query
Browse files Browse the repository at this point in the history
  • Loading branch information
tpluscode committed Jan 10, 2023
1 parent 82d4e4f commit 7eb7ae4
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/gold-tigers-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hydrofoil/shape-to-query": patch
---

Support for `sh:oneOrMorePath` and `sh:zeroOrMorePath`
39 changes: 29 additions & 10 deletions packages/shape-to-query/lib/shapeToPatterns.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { NamedNode, Quad } from 'rdf-js'
import type { GraphPointer } from 'clownface'
import { BaseQuad, NamedNode } from 'rdf-js'
import type { AnyPointer, GraphPointer } from 'clownface'
import { sparql, SparqlTemplateResult } from '@tpluscode/rdf-string'
import { rdf, sh, xsd } from '@tpluscode/rdf-ns-builders'
import $rdf from '@rdfjs/data-model'
import { IN } from '@tpluscode/sparql-builder/expressions'
import TermSet from '@rdfjs/term-set'
import { isNamedNode } from 'is-graph-pointer'
import { isBlankNode, isNamedNode } from 'is-graph-pointer'
import { create, PrefixedVariable } from './variableFactory'

export interface Options {
Expand Down Expand Up @@ -38,6 +38,7 @@ export function shapeToPatterns(shape: GraphPointer, options: Options): ShapePat

const flatPatterns = () => [targetClassPattern, ...resourcePatterns
.flat()
.filter((quad): quad is BaseQuad => 'subject' in quad)
.reduce((set, quad) => set.add(quad), new TermSet()),
].map(quad => sparql`${quad}`)

Expand Down Expand Up @@ -80,7 +81,9 @@ function targetClass(shape: GraphPointer, focusNode: PrefixedVariable) {
}
}

function toUnion(propertyPatterns: Quad[][]) {
type Pattern = BaseQuad | SparqlTemplateResult

function toUnion(propertyPatterns: Pattern[][]) {
if (propertyPatterns.length > 1) {
return propertyPatterns.reduce((union, next, index) => {
if (index === 0) {
Expand All @@ -100,10 +103,10 @@ interface PropertyShapePatterns {
shape: GraphPointer
focusNode: PrefixedVariable
options: PropertyShapeOptions
parentPatterns?: Quad[]
parentPatterns?: Pattern[]
}

function * deepPropertyShapePatterns({ shape, focusNode, options, parentPatterns = [] }: PropertyShapePatterns): Generator<Quad[]> {
function * deepPropertyShapePatterns({ shape, focusNode, options, parentPatterns = [] }: PropertyShapePatterns): Generator<Pattern[]> {
const activeProperties = shape.out(sh.property)
.filter(propShape => !propShape.has(sh.deactivated, TRUE).term)
.toArray()
Expand All @@ -114,25 +117,41 @@ function * deepPropertyShapePatterns({ shape, focusNode, options, parentPatterns
: focusNode.extend(index)

const path = propShape.out(sh.path)
if (!isNamedNode(path)) {

let selfPatterns: Pattern[] = []
if (isNamedNode(path)) {
selfPatterns = [$rdf.quad(focusNode(), path.term, variable())]
} else if (isDeepPathPattern(path)) {
const property = <NamedNode>path.out([sh.zeroOrMorePath, sh.oneOrMorePath]).term
const intermediateNode = variable.extend('i')()

selfPatterns = [
sparql`${focusNode()} ${property}* ${intermediateNode} .`,
$rdf.quad(intermediateNode, property, variable()),
]
} else {
continue
}

const selfPatterns = [...parentPatterns, $rdf.quad(focusNode(), path.term, variable())]
const combinedPatterns = [...parentPatterns, ...selfPatterns]

yield selfPatterns
yield combinedPatterns

const shNodes = propShape.out(sh.node).toArray()
for (const shNode of shNodes) {
const deepPatterns = deepPropertyShapePatterns({
shape: shNode,
focusNode: variable,
options,
parentPatterns: selfPatterns,
parentPatterns: combinedPatterns,
})
for (const deepPattern of deepPatterns) {
yield deepPattern
}
}
}
}

function isDeepPathPattern(pointer: AnyPointer) {
return isBlankNode(pointer) && isNamedNode(pointer.out([sh.zeroOrMorePath, sh.oneOrMorePath]))
}
151 changes: 151 additions & 0 deletions packages/shape-to-query/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,4 +424,155 @@ describe('@hydrofoil/shape-to-query', () => {
})
})
})

context('shapes with complex paths and sh:node', () => {
context('sh:zeroOrMorePath', () => {
it('generates a deep pattern', async () => {
// given
const shape = await parse`
<>
a ${sh.NodeShape} ;
${sh.property}
[
${sh.path} [ ${sh.zeroOrMorePath} ${foaf.knows} ] ;
${sh.node}
[
${sh.property}
[
${sh.path} ${foaf.name} ;
] ;
] ;
] ;
.
`

// when
const patterns = shapeToPatterns(shape, { subjectVariable: 'node' })
const query = SELECT.ALL.WHERE`${patterns.whereClause()}`.build()

// then
expect(query).to.be.a.query(sparql`
SELECT * WHERE {
{
?node ${foaf.knows}* ?node_0_i .
?node_0_i ${foaf.knows} ?node_0 .
}
UNION
{
?node ${foaf.knows}* ?node_0_i .
?node_0_i ${foaf.knows} ?node_0 .
?node_0 ${foaf.name} ?node_0_0 .
}
}
`)
})

it('produces correct CONSTRUCT clause', async () => {
// given
const shape = await parse`
<>
${sh.property}
[
${sh.path} [ ${sh.zeroOrMorePath} ${ex.nextInHierarchy} ] ;
${sh.node}
[
${sh.property}
[
${sh.path} ${schema.name} ;
],
[
${sh.path} ${sh.path} ;
${sh.node}
[
${sh.property}
[
${sh.path} ${sh.inversePath} ;
] ;
] ;
] ;
] ;
] .
`

// when
const query = constructQuery(shape, { subjectVariable: 'node' })

// then
expect(query).to.be.a.query(sparql`
CONSTRUCT {
?node_0_i ${ex.nextInHierarchy} ?node_0 .
?node_0 ${schema.name} ?node_0_0 .
?node_0 ${sh.path} ?node_0_1 .
?node_0_1 ${sh.inversePath} ?node_0_1_0 .
} WHERE {
{
?node ${ex.nextInHierarchy}* ?node_0_i .
?node_0_i ${ex.nextInHierarchy} ?node_0 .
}
UNION
{
?node ${ex.nextInHierarchy}* ?node_0_i .
?node_0_i ${ex.nextInHierarchy} ?node_0 .
?node_0 ${schema.name} ?node_0_0 .
}
UNION
{
?node ${ex.nextInHierarchy}* ?node_0_i .
?node_0_i ${ex.nextInHierarchy} ?node_0 .
?node_0 ${sh.path} ?node_0_1 .
}
UNION
{
?node ${ex.nextInHierarchy}* ?node_0_i .
?node_0_i ${ex.nextInHierarchy} ?node_0 .
?node_0 ${sh.path} ?node_0_1 .
?node_0_1 ${sh.inversePath} ?node_0_1_0 .
}
}
`)
})
})

context('sh:oneOrMorePath', () => {
it('generates a deep pattern', async () => {
// given
const shape = await parse`
<>
a ${sh.NodeShape} ;
${sh.property}
[
${sh.path} [ ${sh.oneOrMorePath} ${foaf.knows} ] ;
${sh.node}
[
${sh.property}
[
${sh.path} ${foaf.name} ;
] ;
] ;
] ;
.
`

// when
const patterns = shapeToPatterns(shape, { subjectVariable: 'node' })
const query = SELECT.ALL.WHERE`${patterns.whereClause()}`.build()

// then
expect(query).to.be.a.query(sparql`
SELECT * WHERE {
{
?node ${foaf.knows}* ?node_0_i .
?node_0_i ${foaf.knows} ?node_0 .
}
UNION
{
?node ${foaf.knows}* ?node_0_i .
?node_0_i ${foaf.knows} ?node_0 .
?node_0 ${foaf.name} ?node_0_0 .
}
}
`)
})
})
})
})

0 comments on commit 7eb7ae4

Please sign in to comment.