@hydrofoil/shape-to-query How-Tos

Living Document,

This version:
https://shape-to-query.hypermedia.app/how-tos/
Issue Tracking:
GitHub
Inline In Spec
Editor:
Tomasz Pluskiewicz

Abstract

Guides for efficiently using SHACL-AF with the @hydrofoil/shape-to-query library

1. How-To Guides

1.1. How to implement a custom Node Expressions

Note: before proceeding you may want to read Conceptual Guides § dsl

1.1.1. How to implement a specialized SPARQL node expression

implementing hydra member assertion query

1.1.2. How to implement a compound node expression

implementing a limit/offset/order shorthand

1.2. How to register a custom function

In this how-to we will add support for a palindrome function as described in this tutorial. Assuming that you’ve already added support of such a function to your SPARQL engine, you should be able to perform a query such as that shown in the mentioned tutorial:

PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX cfn: <http://example.org/custom-function/>
SELECT ?x ?label
WHERE {
   ?x rdfs:label ?label .
   FILTER(cfn:palindrome(str(?label)))
}

In order for shape-to-query to recognize you custom function, the necessary metadata needs to be added to the SPARQL vocabulary. This can be easily done programmatically, at the minimum by adding an rdf:type assertion about the function itself.

import vocabulary, { rdf, sh } from '@hydrofoil/shape-to-query/vocabulary.js'

vocabulary
  .namedNode('http://example.org/custom-function/palindrome')
  .addOut(rdf.type, sh.Function)

Alternatively, you may choose to load the additional metadata by parsing a turtle string or loading an RDF file, or fetching it the web. Use the following code to add all the parsed triples to the vocabulary dataset:

import type { DatasetCore } from '@rdfjs/types'
import vocabulary from '@hydrofoil/shape-to-query/vocabulary.js'
import addAll from 'rdf-dataset-ext/addAll.js'

let customFunctions: DatasetCore = await loadAndParseFunctions()

addAll(vocabulary.dataset, customFunctions)

NOTE: Read Conceptual Guides § function-metadata to learn more about the recognised properties of SHACL functions.

With function metadata in place, you can now use the custom function in any node expression:

Populate a property which will indicate that the name property of a resource is a palindrome
PREFIX ex: <http://example.org/>
PREFIX schema: <http://schema.org/>
PREFIX sh: <http://www.w3.org/ns/shacl#>
PREFIX cfn: <http://example.org/custom-function/>

[
  a sh:NodeShape ;
  sh:target schema:Thing ;
  sh:property
    [
      sh:path ex:nameIsPalindrome ;
      sh:values
        [
          cfn:palindrome ( [ sh:path schema:name ] ) ;
        ] ;
    ] ;
] .
PREFIX schema: <http://schema.org/>
CONSTRUCT { ?resource1 <http://example.org/nameIsPalindrome> ?resource2. }
WHERE {
  ?resource1 schema:name ?resource3.
  BIND(<http://example.org/custom-function/palindrome>(?resource3) AS ?resource2)
}

simplify the example when sh:expressions is implemented to use the result of the palindrome function call as a constraint [Issue #130]

1.3. Optimising joins in Stardog

Deep and broad structures described with sh:node tend to degrade join performance in some versions of Stardog which fail to optimise the joins necessary to combine the results coming from nested base graph patterns.

This can be remedied by introducing group.joins query hint to the combined properties of a sh:node constraint. To do that, it will be necessary to provide a customized instance of PropertyShape class which builds the property constraints. The following example shows how to do that:

import { PropertyShape, constructQuery } from '@hydrofoil/shape-to-query'
import rdf from '@zazuko/env/web.js'
import type { BuildParameters } from '@hydrofoil/shape-to-query/model/Shape.js'
import type sparqljs from 'sparqljs'

/*
 * WARNING: this uses patched sparqljs to add support for comments
 */

// subclass PropertyShape and override `buildConstraints` method
class PropertyShapeEx extends PropertyShape {
  buildConstraints(arg: BuildParameters): sparqljs.Pattern[] {
    return [
      { type: 'comment', text: 'pragma group.joins' },
      ...super.buildConstraints(arg),
    ]
  }
}

// replace the default implementation when creating the query
const pointer = rdf.clownface()
  .blankNode()
  .addOut(rdf.ns.sh.property, p => p.addOut(rdf.ns.sh.path, rdf.ns.schema.name))
const query = constructQuery(pointer, {
  PropertyShape: PropertyShapeEx,
})

console.log(query)

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

References

Normative References

[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119

Informative References

[S2Q-CONCEPTS]
Conceptual Guides. URL: ../concepts

Issues Index

implementing hydra member assertion query
implementing a limit/offset/order shorthand
simplify the example when sh:expressions is implemented to use the result of the palindrome function call as a constraint [Issue #130]