This document defines SPARQL-RL, a Datalog-style rules language for RDF.

SPARQL-RL generates new RDF data by evaluating a set of declarative rules against an input RDF graph. Rules are written in a SPARQL-like text syntax.

This specification is published by the W3C Data Shapes Working Group.

Introduction

This document introduces SPARQL-RL. It is a mechanism for deriving new RDF triples from existing RDF data through declarative rules. The document defines the syntax and semantics of rule-based inference.

Implementations of SPARQL-RL provide one or both of the operations infer and query. The infer operation applies the rules to a given base graph and produces an inference graph containing the RDF triples derived by rule evaluation that do not appear in the base graph. Combining the inference graph with the base graph is optional and left to users. The query operation determines whether and how a given goal pattern can be derived from the base graph using the rules.

SPARQL-RL allows the creation of new RDF terms, including blank nodes, that can be used in triple templates in the head of rules.

SPARQL-RL also supports negation as failure, which could lead to different inference graphs depending on the order in which rules are evaluated. To avoid this, rules are evaluated using the technique of stratification, which establishes an ordering among rules, ensuring that the same inference graph is always produced.

SPARQL-RL can be referred to as SRL when the context is clear.

Terminology

The following specifications provide fundamental terminology used in this document:

Document Conventions

Examples of RDF data in this document use [[[RDF12-TURTLE]]] [[RDF12-TURTLE]].

Within this document, the following namespace prefix bindings are used:

Prefix Namespace
rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#
xsd: http://www.w3.org/2001/XMLSchema#
: http://example/

Throughout the document, color-coded boxes contain rules in SPARQL-RL syntax or contain RDF graphs in Turtle syntax.

        # This box represents rules
        # This box represents input data
        # This box represents inferred data

This specification defines conformance criteria for:

A conforming [=SPARQL-RL document=] is an [=RDF string=] that conforms to the grammar starting with the RuleSet production as defined in .

This specification does not define how a [=SPARQL-RL processor=] handles non-conforming [=SPARQL-RL documents=].

SPARQL-RL Overview

SPARQL-RL infers new triples given a [=base graph=] and a [=rule set=]. The output of evaluation is an [=inference graph=] containing the derived triples that do not appear in the base graph.

Each [=rule=] has a pattern, called the [=body=], and a result template, called the [=head=]. A rule is executed by finding the values for variables in the body so that the body matches the combined base graph and any inferred triples from the execution up to this point. These values are then used to instantiate the triple templates in the rule head to produce new inferred triples.

The rules are executed until no more triples are inferred, and rules may be executed more than once as new inferred triples become available.

SPARQL-RL execution is defined so that the order of rule execution does not lead to different outcomes when creating new RDF terms, including new blank nodes, or when testing for the absence of a pattern. In other words, the same inference graph is produced regardless of the order of rule execution.

SPARQL-RL has a human-friendly syntax inspired by [[[SPARQL12-QUERY]]]. Rule set evaluation contains elements similar to SPARQL, with differences in the details to ensure that the same inference graph is produced regardless of the order of rule execution.

The examples in this section describe software components and their dependencies: a frontend calls an application server, and the application server queries a database that has a known vulnerability.

Triple Patterns

A [=triple pattern=] is matched against triple data to give values to variables. The pattern has three elements, each of which is an RDF term, a variable, or is a pattern for a [=triple term=] containing variables.

In this first example, we have the following data in the base graph and rule set:

The above rules, applied to the data, will conclude that `:frontend` depends on `:app`, and that `:app` depends on `:db`, whatever the kind of dependency.

We can then derive `:exposedTo` relationships by adding a rule that depends on `:dependsOn` triples produced by the other rules (see also ) below:

Recursion

The `:exposedTo` rule of the previous section only reaches direct dependencies: `:frontend` does not depend directly on `:db`, so no `:exposedTo` triple is inferred for `:frontend`. To propagate exposure along dependency chains of any length, we replace that rule with two rules: a component is exposed to a vulnerability it has, and a component is exposed to any vulnerability that its direct dependencies are exposed to:

Compared with the previous example, this adds `:db :exposedTo :vuln1` — the database is exposed to its own vulnerability — and `:frontend :exposedTo :vuln1`: the exposure reaches `:frontend` through the chain of dependencies, however long the chain.

This last rule is a recursive rule: the body of the rule depends on the head of the rule.

Filtering

We can use expressions in the body of rules to restrict the values of variables in the matching of the body. For example, given the severity of each vulnerability, we can give a status to components that are exposed to a severe vulnerability:

`FILTER` evaluates an expression and keeps the current set of variable bindings if the expression evaluates to true, and it discards the current set of variable bindings if the expression evaluates to false. This is the same as the `FILTER` operation of SPARQL and SPARQL-RL provides many of the same functions and operators as SPARQL.

Negation

Negation is used to specify a pattern that must not match. This is called "negation as failure".

In order to evaluate a negation element, the rule set evaluation algorithm ensures that all the rules that could produce triples matching the pattern in the negation element have been completed. This is called [=stratification=] and ensures that the negation is based on all the relevant possible triples, whether from the data or from other rules. The rule containing the negation is said to depend on the rules generating these triples (see below).

Assignment and Creating RDF Terms

Assignment allows you to assign the result of an expression to a variable in the body of a rule. This can be used to create new RDF terms based on the data.

Blank nodes can be used in the rule head, and each generates a fresh blank node each time rule evaluation generates triples.

Rules involving [=assignments=], rules that create blank nodes from evaluation of the [=rule head=] and rules with a template that involves a [=triple term=] containing [=variables=] are [=run-once rules=]. Such rules are run after all the rules that could produce data that they depend on, and before any rules that depend on the data they produce.

This condition ensures that such rules do not loop back to themselves and cause the creation of an unbounded number of RDF terms.

A constant [=RDF term=] in a [=rule head=] does not make a rule a [=run-once rule=], even when the term does not occur in the [=base graph=]: the rule contributes the same term each time it is evaluated, so it can only add a bounded number of terms.

If evaluating the expression in an [=assignment=] causes an error, then the current solution mapping is rejected by the [=assignment=].

Importing Rule Sets

A SPARQL-RL [=rule set=] can incorporate other rule sets by including their URLs in the [=rule set imports=] of the rule set. This allows rules to be structured into libraries shared between rule sets.

The `IMPORTS` statements of a rule set are processed before any of the rules in the rule set are evaluated. During the importing step, if an imported rule set has its own imports, those are also processed recursively. Traversing `IMPORTS` statements during the processing of rule sets may encounter cyclic imports. A rule set is imported only once; cycles in the import statements graph do not lead to infinite loops.

Support for importing rule sets is optional for [=SPARQL-RL processors=]. See for further details.

Data Blocks

Data blocks allow concisely providing RDF triples directly to the rule set evaluation. Triples in data blocks are added to the inference graph and are available for matching in the body of rules.

For example, a rule set can ship with known facts asserted directly, rather than derived from data:

A data block is equivalent to a rule with an empty body: its triples are part of the inference graph without any rule being evaluated.

Evaluation of a Rule Set

Rules are organized into [=rule sets=]. A rule set and a data graph (the [=base graph=]) are inputs to evaluation. The output is a graph, called the [=inference graph=], which is the set of triples produced by the evaluation process that do not appear in the [=base graph=].

During evaluation, triples inferred based on one rule are available for matching in other rules. Rule set evaluation proceeds until the [=inference graph=] contains all the possible triples from the inputs of the [=rule set=] and the [=base graph=].

Evaluation starts with steps to prepare a rule set before the rules themselves are evaluated:

  1. Where supported, the imports of the input rule set are resolved to form a rule set that includes all the rules of all imported rule sets. A processor signals an error if any import is not acceptable to the processor ().
  2. Check that each rule is a [=well-formed rule=].
  3. Calculate the [=dependency graph=].
  4. A [=stratification=] is calculated () so that [=negation elements=] and [=run-once rules=] produce consistent, predictable outcomes. [=Stratification=] involves inspecting the dependencies between rules ().

Once a [=rule set=] has been prepared, evaluation proceeds by taking each layer from the stratification, in order, evaluating the rules in that layer to completion, and then moving on to the next layer.

Matching Base Data

During rule set evaluation it may be necessary to pattern match only on the original [=base graph=], not on any inferred triples.

An example of this is setting a default value. The rule must test whether the base graph already contains a value and, if not, the rule can then calculate a default value.

Using `NOT DATA` allows other parts of the rule body to match inferred triples. It is also possible to perform all [=rule body=] matching against the [=base graph=] by using `WHERE DATA`. Such rules then only rely on matching from the base graph.

The matching in this section is on the [=base graph=]. It does not include triples added by rule set evaluation from the [=rule set=] `DATA` blocks ().

SPARQL-RL Abstract Syntax

The SPARQL-RL Abstract Syntax is the logical structure of SPARQL-RL. It is used to define the execution algorithm of SPARQL-RL.

Elements of the Abstract Syntax

Variable
A [=variable=] represents a possible [=RDF term=] in a triple pattern. Variables are also used in expressions.
Expression
An [=expression=] is an [=RDF term=], a [=variable=], or the application of a function or a functional form to arguments, where each argument is itself an [=expression=]. An expression is evaluated with respect to a [=solution mapping=], giving an [=RDF term=] as the result, or evaluation results in an error. Expressions are compatible with SPARQL expressions.
Data block
A [=data block=] is a set of triples. These triples are added to the inference graph as additional facts and are included in the inference process.
Triple pattern
A [=triple pattern=] is a 3-tuple where each element is a [=variable=], an [=RDF term=], or a pattern for a [=triple term=] with [=variables=]. The second element of the tuple must be an [=IRI=] or a [=variable=]. [=Triple patterns=] appear in the [=body=] of a [=rule=] and are used to match [=RDF triples=].
Triple template

A [=triple template=] is a 3-tuple where each element is a [=variable=], an [=RDF term=], or a template for a [=triple term=] with [=variables=]. The second element of the tuple must be an [=IRI=] or a [=variable=]. [=Triple templates=] appear in the [=head=] of a [=rule=] and are used to generate [=RDF triples=].

A pattern or template for a [=triple term=] in which no [=variable=] occurs is a [=triple term=], and therefore also an [=RDF term=].

Filter element
A [=filter element=] is an [=expression=] that appears as a [=rule element=]. It is used to restrict the values of variables in pattern matching.
Triple pattern element
A [=triple pattern element=] is a [=triple pattern=] that appears as a [=rule element=].
Negation element
A [=negation element=] is a [=rule element=]. It has a negation element body composed of a sequence of [=triple pattern elements=] and [=filter elements=]. A [=negation element=] has a flag, .evaldata, to indicate which graph to use for matching the [=negation element body=].
Assignment element
An [=assignment element=] is a [=rule element=] that is a pair consisting of a variable, called the assignment variable, and an expression, called the assignment expression.
Rule element
A [=rule element=] is any one of [=triple pattern element=], [=filter element=], [=negation element=], or [=assignment element=].
Rule head
A [=rule head=] is a sequence of [=triple templates=].
Rule body
A [=rule body=] is a sequence of [=rule elements=]; that is, each sequence element is one of a [=triple pattern element=], a [=filter element=], a [=negation element=], or an [=assignment element=].
Rule set imports
[=Rule set imports=] (often just "imports") are the collection of URLs from the `IMPORTS` statements for other rule sets that will be included during evaluation.
Rule
A [=rule=] contains a [=rule head=] (often just "head"), a [=rule body=] (often just "body"), and a flag .evaldata to indicate which graph to use for matching [=triple patterns=] in the body. A rule can be given a URI or a blank node to help identify it.
Run-once rule

A [=run-once rule=] is a rule that is run exactly once at a particular point in the evaluation of a rule set. A [=rule=] is a [=run-once rule=] if any of the following hold:

  • the [=rule body=] has an [=assignment element=];
  • a [=triple template=] of the [=rule head=] has a [=blank node=];
  • a [=triple template=] of the [=rule head=] has a template for a [=triple term=] in which a [=variable=] occurs.
General rule
A [=general rule=] is a rule that is not a [=run-once rule=]. General rules may run more than once during rule set evaluation.
Rule set
A [=rule set=] is a collection of zero or more [=rules=], a collection of zero or more [=data blocks=], and a collection of zero or more [=rule set imports=]. A resolved rule set is a [=rule set=] that has no imports. A [=resolved rule set=] is created from another [=rule set=] by applying the imports process.
Base graph
A [=base graph=] is the [=RDF graph=] given as input to the evaluation process.
Inference graph
An [=inference graph=] is an [=RDF graph=] produced by a rule set evaluation. It contains all inferred triples not present in the [=base graph=] that are inferred by applying the [=rule set=] to the [=base graph=].
Rule set evaluation
A [=rule set evaluation=] is the process of applying a [=rule set=] to a [=base graph=] to produce an [=inference graph=]. A rule evaluation is the process of evaluating a rule once. Evaluating a rule produces all the triples given by the [=rule head=] by the evaluation of the [=rule body=], regardless of whether a triple is in the [=base graph=], already inferred during [=rule set evaluation=], or is a new inferred triple. During [=rule set evaluation=], a rule may be evaluated more than once.

SPARQL-RL provides two operations, [=infer=] and [=query=].

Infer
[=Infer=] is the operation that applies a [=rule set=] to a given [=base graph=] and produces an [=inference graph=] containing inferred triples. It applies the full [=rule set evaluation=].
Query

[=Query=] is the operation that determines whether a given pattern can be derived from a [=base graph=] using the [=rule set=] and returns a [=solution sequence=] in which each [=solution mapping=] gives RDF terms for variables.

[=Query=] has the effect of performing an [=infer=] operation, followed by matching the goal pattern to the combined [=base graph=] and [=inference graph=].

The [=query=] operation may not evaluate all rules; instead, it may only evaluate rules that are necessary to answer the query goal.

SPARQL-RL processor
A [=SPARQL-RL processor=] is a system that implements the SPARQL-RL specification, can evaluate [=rule sets=], and provides one or both of the operations [=infer=] and [=query=].

In a [=triple pattern=] or a [=triple template=], position 1 of the tuple is informally called the subject, position 2 is informally called the predicate, and position 3 is informally called the object.

The elements of a sequence of [=rule elements=] are labeled starting at 1.

The following notation is used for the various components of rule sets and rules.

Component Notation
Component Usage
ruleset.rules The rules of a [=rule set=].
ruleset.data The [=RDF graph=] formed by the union of the [=data blocks=] in the rule set.
ruleset.imports The set of [=imports=] of a [=rule set=].
rule.head The [=triple templates=] of the [=rule head=].
rule.body The [=rule elements=] of the [=rule=].
rule.evaldata A boolean flag; if false, the [=rule body=] is matched against the [=base graph=].
rule.id An identifier for the rule, which is a [=blank node=] or [=IRI=].
filter.expr The [=expression=] of a [=filter element=].
assign.var The [=assignment variable=] of the [=assignment element=].
assign.expr The [=assignment expression=].
negation.inner The [=negation element body=] of a [=negation element=].
negation.evaldata A boolean flag; if false, the `negation.inner` is matched against the [=base graph=].

Well-formedness Conditions

Well-formedness is a set of conditions on the abstract syntax of a [=rule set=]. Together, these conditions ensure that a [=variable=] in the [=head=] of a rule has a value defined in the [=body=] of the rule; that each variable in a [=filter element=] or [=assignment expression=] has a value at the point of evaluation; and that each assignment in a rule introduces a new variable, one that has not been used earlier in the rule body.

Define well-formedness for a sequence of [=rule elements=] given an initial set of variables.

Let elti be the i-th element of a sequence of [=rule elements=].

Let varsi be the set of variables defined by elti as follows:

  • If elti is a [=triple pattern element=] then
    • varsi is the set of [=variables=] that occur in the [=triple pattern element=].
  • If elti is an [=assignment element=] then
    • varsi is the set of the [=assignment variable=].
  • Otherwise,
    • varsi is the empty set.

Let V0 be the initial variables of the sequence.

Let Vi be the union of V0 and all varsj, for j from 1 to i.

Let Vall be the value of VN, where N is the length of the sequence.

A well-formed sequence is a sequence of [=rule elements=], given a set of variables V0, if the following conditions are met for each i, from 1 to N:

  • If elti is a [=filter element=] then
    • every [=variable=] mentioned in the [=filter element=] is an element of Vi-1.
  • If elti is an [=assignment element=] then
    • every variable mentioned in the [=assignment expression=] is an element of Vi-1
    • the [=assignment variable=] is not an element of Vi-1.
  • If elti is a [=negation element=] then
    • the sequence of [=rule elements=] in the [=negation element body=] is a [=well-formed sequence=], setting V0 to the value of Vi-1 of the [=rule element=].

A [=rule=] is a well-formed rule if the sequence of [=rule elements=] of the [=rule body=] is a [=well-formed sequence=], starting with V0 being the empty set, and if each variable in a [=triple template=] of the [=rule head=] is an element of Vall.

A [=rule set=] is a well-formed rule set if and only if all rules of the rule set are [=well-formed rules=].

Rule Dependency

A rule `R1` depends on a rule `R2` if the output of the second rule affects the evaluation of the body of the first rule. That is, the head of `R2` has a [=triple template=] that can generate a triple that matches a [=triple pattern=] in the body of `R1`, either as a [=triple pattern element=] or inside a [=negation element=].

There are two kinds of dependency: [=closed dependencies=] and [=open dependencies=]. A closed dependency ensures that rule `R2` has generated all its possible output before rule `R1` is executed. If a rule dependency is not closed, it is an open dependency which allows the first rule `R1` to be executed while the rule `R2` might be run again to generate further triples which can then cause `R1` to be reevaluated with the new triples from `R2`.

A rule with flag `.evaldata` set to `false` matches using the [=base graph=] and the rule has no dependencies.

In this first example, the first rule has an open dependency on the second rule.

In this second example, the first rule has a closed dependency on the second rule: the triple pattern `?x :status :criticallyExposed` occurs inside a [=negation element=] of the first rule and matches the [=triple template=] in the head of the second rule. The first rule cannot be evaluated until the second rule has generated all its possible output.

Triple pattern matching

A [=triple pattern=] matches a [=triple template=] if the triple template can [=generate=] a triple that [=matches=] the triple pattern.

Triple pattern dependency

A [=triple pattern=] depends on a [=triple template=] if the [=triple pattern=] can match the [=triple template=].

Rule dependency

Rule `R1` depends on rule `R2` if `R1.evaldata` is `true` and any [=triple pattern=] in the body of `R1`, whether as a [=triple pattern element=] or inside a [=negation element=] `neg` where `neg.evaldata` is `true`, depends on a triple template in the head of `R2`.

Closed dependency

A [=rule dependency=] of rule `R1` on rule `R2` is a [=closed dependency=] if any of the following conditions hold:

  • A [=triple pattern=] occurring inside a [=negation element=] of `R1` where the [=negation element=] flag `.evaldata` is `true` matches a [=triple template=] in the [=rule head=] of `R2`.
  • Rule `R1` depends on rule `R2` and `R1` is a [=run-once rule=].
Open dependency

A [=rule dependency=] of rule `R1` on rule `R2` is an [=open dependency=] if the dependency is not a [=closed dependency=].

A [=triple template=] can generate an [=RDF triple=] `T1` if there are values for the variables of the [=triple template=] such that replacing variables by values in the template gives a triple `T2` where `T2` equals `T1`.

Similarly, a [=triple pattern=] matches a triple `T1` if there are values for the variables of the [=triple pattern=] such that replacing variables in the pattern by the values gives a triple `T2` where `T2` equals `T1`.

If a variable is used more than once in a [=triple template=] or [=triple pattern=], then the same RDF term is used as the replacement.

Replacing variables by RDF terms in a triple pattern includes replacing variables inside [=triple terms=].

Dependency Graph

The dependencies between rules are represented as a directed graph, called the [=dependency graph=]. The vertices of the graph are the rules of the rule set, and the edges are labeled either open or closed according to whether the dependency is an [=open dependency=] or a [=closed dependency=].

Dependency graph
A [=dependency graph=] of a [=rule set=] is a directed graph where each vertex is a [=rule=] in the rule set, and an edge exists from rule `R1` to rule `R2` if `R1` depends on `R2`. The edge is labeled either open or closed according to whether the dependency is an [=open dependency=] or a [=closed dependency=].
Transitive rule dependency
A rule `R1` has a [=transitive dependency=] on rule `R2` if there is a path in the [=dependency graph=] from `R1` to `R2`.
Recursive rule dependency

A rule `R` has a [=recursive dependency=] if there is a cyclic path in the [=dependency graph=] involving `R`.

The dependency graph is not affected by the [=base graph=].

Dependency Graph Algorithm

The following algorithm gives one possible method for constructing the [=dependency graph=] from a [=rule set=]. Conformance depends on producing a dependency graph that meets the definitions of a dependency graph, not on the use of this procedure.


define mergeLabel(oldLabel, newLabel):
    # Closed dependency overrides open dependency.
    if oldLabel == "open" and newLabel == "open":
        the result is "open"
    else:
        the result is "closed"
    endif
enddefine

# output -- Dependency graph with rule vertices and labeled edges.
define buildDependencyGraph(ruleSet):
    # edgeLabelMap maps (R1, R2) to "open" or "closed"
    let edgeLabelMap be a map from pair (rule, rule) to label

    foreach rule R1 in ruleSet.rules:
        let bodyDependencies = {}

        if R1.evaldata then:
            # Classify each triple pattern TP in the rule as requiring "open" or "closed"
            # depending on whether it is in a negation element or not.
            foreach rule element RBE in R1.body:
                if RBE is a negation element:
                    if RBE.evaldata then:
                        foreach triple pattern TP in RBE.inner:
                            let item be a pair (TP, "closed")
                            add item to bodyDependencies
                        endfor
                    endif
                else if RBE is a triple pattern element of triple pattern TP:
                    let item be a pair (TP, "open")
                    add item to bodyDependencies
                else if RBE is a filter element:
                    # Do nothing
                else if RBE is an assignment element:
                    # Do nothing
                endif
            endfor
        endif

        foreach pair (triple pattern TP, depLabel) in bodyDependencies:
            if R1 is a run-once rule:
              set depLabel to "closed"
            endif
            # Find dependencies for this triple pattern element or negation element.
            foreach rule R2 in ruleSet.rules:
                foreach triple template TT in R2.head:
                    if triple pattern TP matches triple template TT:
                        let key = (R1, R2)
                        if edgeLabelMap contains key:
                            let oldLabel = edgeLabelMap.get(key)
                            let merged = mergeLabel(oldLabel, depLabel)
                            edgeLabelMap.set(key, merged)
                        else:
                            edgeLabelMap.set(key, depLabel)
                        endif
                    endif
                endfor
            endfor
        endfor
    endfor

    let DP = { }
    foreach entry ((R1, R2), label) in edgeLabelMap:
        add an edge (R1 -> R2) labeled with label to DP
    endfor

    the result is DP
enddefine
            

Stratification

[=Stratification=] is the process of partitioning a [=rule set=] into an ordered sequence of [=stratification layers=] (also known as "strata", singular "stratum"). Rules in lower [=strata=] are evaluated before rules in higher [=strata=].

[=Stratification=] imposes constraints on dependencies between [=rules=] to ensure that [=negation elements=] and [=run-once rules=] depend only on results computed using earlier (lower) [=strata=] and the [=base graph=]. This guarantees a single, well-defined, and finite outcome from the evaluation of a [=rule set=] over a given [=base graph=].

A stratification process may also be used to make other evaluation decisions. This document describes the necessary conditions for consistent evaluation and gives one possible way to form a stratification. Implementations need to meet the conditions described here in order to get compatible behavior, but they are not required to implement the algorithm as presented.

Stratification layer

A [=stratification layer=] `SL` is a pair of disjoint sets of rules (`SL.once`, `SL.general`). `SL.once` is exactly the [=run-once rules=] of the layer; these rules are each evaluated exactly once at the start of evaluation of the [=stratification layer=]. `SL.general` is the set of remaining rules of the layer, which are evaluated repeatedly until no new triples are inferred.

Stratification

A [=stratification=] of a [=rule set=] is a sequence of [=stratification layers=]. Each rule in a [=rule set=] appears in exactly one of the sets of one of the [=stratification layers=].

The [=stratification layers=] of a [=stratification=] are numbered from zero. The stratum number of a [=rule=] is the number of the [=stratification layer=] that contains the rule. A rule with a smaller [=stratum number=] is in a lower stratum; one with a larger [=stratum number=] is in a higher stratum.

For every edge from rule `R1` to rule `R2` in the [=dependency graph=] of the [=rule set=]:

  • if the edge is labeled closed, the [=stratum number=] of `R1` is strictly greater than the [=stratum number=] of `R2`.
  • if the edge is labeled open, the [=stratum number=] of `R1` is greater than or equal to the [=stratum number=] of `R2`.

A [=rule set=] can have more than one [=stratification=]. An implementation can use any [=stratification=] of the [=rule set=].

Stratification Condition

[=Stratification=] is only defined when the following condition is satisfied. If a [=rule set=] does not meet this condition, then this specification does not define an outcome for the evaluation of such a [=rule set=].

Stratification Condition
The [=stratification condition=] requires that there is no [=recursive dependency=] involving a [=closed dependency=] in the [=dependency graph=] for a [=rule set=].

The [=stratification condition=] is exactly the condition for a [=stratification=] to exist: a [=stratification=] of a [=rule set=] can be formed if and only if no cyclic path in the [=dependency graph=] of the rule set contains a [=closed dependency=].

Stratification Algorithm

The following algorithm gives one possible stratification based solely on the rule set.

define stratification(ruleSet):

    let DP = Dependency graph for the rule set.
    let stratumMap be a map from rule to integer

    # The dependency graph should satisfy the stratification condition.
    # The check for unbounded stratification is a guard
    # due to a violation of the stratification condition.
    let limit = num rules + 1
    let maxStratum = 0

    # initialize stratumMap
    foreach rule in ruleSet.rules:
        stratumMap.set(rule, 0)
    endfor

    boolean changed = true
    while changed:
        changed = false
        foreach edge E in DP:
            # Edge from pRule to qRule with a label
            let pRule = source of edge
            let qRule = destination of the edge
            let label = edge label

            if label == "open":
                if stratumMap.get(pRule) < stratumMap.get(qRule):
                    stratumMap.set(pRule, stratumMap.get(qRule))
                    changed = true
                endif
            endif
            if label == "closed":
                if stratumMap.get(pRule) <= stratumMap.get(qRule):
                    let xStratum = 1 + stratumMap.get(qRule)
                    if xStratum > limit:
                        # Stratification requirement violated
                        error "Stratification error"
                    endif
                    stratumMap.set(pRule, xStratum)
                    maxStratum = max(maxStratum, xStratum)
                    changed = true
                endif
            endif
        endfor
    endwhile

    # Initialize the result map.
    let stratumRules be a map from integer to rules.
    foreach i = 0 to maxStratum
        stratumRules.set(i, {})
    endfor

    # Gather rules in stratumMap with the same level number
    foreach rule R in map stratumMap:
        let stratumNum = stratumMap.get(R)
        add R to stratumRules.get(stratumNum)
    endfor

    # Partition each level into once and general
    let stratumLevels be a sequence of pairs of sets of rules.
    for i = 0 to maxStratum:
        let rules = stratumRules.get(i)
        let once = { R in rules | R is a run-once rule }
        let general = rules \ once
        stratumLevels.set(i, pair(once, general))
    endfor

    the result is stratumLevels
enddefine
            

A consequence of the [=stratification condition=] is that once a [=run-once rule=] is evaluated, the data used to determine the outcome of the rule will not change during further evaluation.

A step-by-step application of dependency analysis and stratification to a complete rule set, followed through to evaluation, is given in .

Processing Imports

Reading documents from the web has security implications. Support for importing rule sets is optional for [=SPARQL-RL processors=]. Further, implementations MAY provide partial support, such as supporting imports of certain rule sets and not others, and possibly from a verified copy.

The following conditions apply to imports processing:

A [=resolved rule set=] is produced from another rule set by recursively reading all rule sets mentioned in the imports of that other [=rule set=].

An example algorithm is provided in .

Relationship between SPARQL-RL and SPARQL

SRL and SPARQL have a close relationship. SRL is designed to be compatible with SPARQL, and many of the constructs in SRL are taken from, or inspired by, SPARQL pattern matching. However, there are some differences.

In SRL, `RULE` variables are always bound before use, whether used in an expression of `FILTER` or `SET`, or used in [=triple templates=] of the [=rule head=]. SPARQL `CONSTRUCT` queries and `INSERT` updates will produce partial results if a variable occurs in the `CONSTRUCT` or `INSERT` template but is not given a value in the `WHERE` clause.

Other differences include:

Rule Set Evaluation

This section defines the outcome of evaluating a rule set on given data. It does not prescribe the algorithm as the method of implementation. An implementation can use any algorithm that generates the same outcome.

Inputs: graph G, called the base graph, and a rule set RS.
Output: an RDF graph GI of inferred triples

The inferred triples do not include triples present in the set of triples of the [=base graph=].

Evaluation Definitions

Solution mapping
A [=solution mapping=], μ, is a partial function μ : V → T, where V is the set of all variables and T is the set of all [=RDF terms=]. The domain of μ is denoted by dom(μ), and it is the subset of V for which μ is defined. The term [=solution=] can be used when it is clear that a [=solution mapping=] is meant. Write μ0 for the solution mapping, such that dom(μ0) is the empty set.
Substitution function

The substitution function, or just a substitution, is a function subst(μ, [=triple pattern=]) that returns a [=triple pattern=] where each occurrence in the [=triple pattern=] of a variable var that is in the dom(μ) is replaced by the [=RDF term=] given by the [=solution mapping=] for var. If the triple pattern result has no variables, then it is an [=RDF triple=].

The function subst(μ, [=triple template=]) is similarly defined. Each occurrence of a variable in the [=triple template=] is replaced by the [=RDF term=] given by the [=solution mapping=] for var.

Template instance function
The template instance function is a function replaceBlankNodes(list of [=triple templates=]) that returns a list of [=triple templates=] where each [=blank node=] in the argument is replaced by a fresh blank node. Multiple occurrences of the same blank node are replaced by the same fresh blank node.
Evaluation graph
An [=evaluation graph=] is an [=RDF graph=] that combines the [=base graph=] and all triples produced during the evaluation of a rule set.
Graph match
A [=graph match=] finds the ways to map a triple pattern onto triples in an [=RDF graph=].

Let G be an [=RDF graph=] and TP be a [=triple pattern=]. The function `graphMatch(G, TP)` returns a set of all possible solutions that, when applied to the triple pattern, produce a triple that is in graph `G`.

Let G be an [=RDF graph=], TP be a [=triple pattern=], and V be the set of variables occurring in TP.

graphMatch(G, TP) = { μ | dom(μ) = V and subst(μ, TP) is a triple in G }
Solution compatible
Two solutions S1 and S2 are [=compatible=] if they agree on the variables in common.

Let S1 and S2 be solutions.

compatible(μ1, μ2) = true
                      if forall v in dom(μ1) intersection dom(μ2)
                          μ1(v) = μ2(v)
compatible(μ1, μ2) = false otherwise
              
Solution sequence
A [=solution sequence=] is a multi-set of solutions. There is no defined order to the sequence. It is equivalent to an unordered list and it can contain duplicates.
Solution merge
If two solutions are compatible, the merge of two solutions is the solution that maps variables of each solution to the [=RDF term=] from one or other of the solutions.
Let μ1, μ2 be solution mappings, and S1 and S2 be solution sequences.
merge(μ1, μ2) = μ such that
                    μ(v) = μ1(v) if v in dom(μ1)
                    μ(v) = μ2(v) otherwise
merge(S1, S2) = { μ |
                    μ1 in S1, μ2 in S2
                    and compatible(μ1, μ2)
                    μ = merge(μ1, μ2) }

The domain of merge(μ1, μ2) is domain(μ1) ∪ domain(μ2).

If the two solutions have no variables in common, then they are compatible, and the merge of the two solutions is the union of the μ1 and μ2.

Effective boolean value
The function `EBV(x)` returns the effective boolean value for an [=RDF term=].

Preparation for Evaluation

Evaluation of a rule set involves collecting all imported rule sets, building a single, combined rule set as described in .

Next, the combined rule set is prepared for evaluation with the following steps.

  1. Check that each rule is a [=well-formed rule=], as described in .
  2. Calculate the [=dependency graph=], as described in .
  3. Calculate a [=stratification=] of the combined rule set, as described in . gives one way to calculate a [=stratification=].

Evaluation of an Expression

An expression, whether used in a [=filter element=] or an [=assignment element=], is evaluated with respect to a [=solution mapping=]. The solution mapping provides the RDF term values for each variable in the expression. The well-formedness requirements of ensure that all variables in the expression appear in the solution mapping.

define evalFunction(F, μ):
    # F is an expression: an RDF term, a variable, or op(expr1, ..., exprN)
    # where op is a function or a functional form.
    # μ is a solution mapping
    if F is an RDF term:
        return F
    if F is a variable:
        # By well-formedness, F ∈ dom(μ).
        return μ(F)
    # F is of the form F = op(expr1, ..., exprN)
    if op is a functional form (e.g., IF, logical-or):
        # Evaluated specifically for op; op may evaluate only some arguments.
        # For example, IF(c, t, f) evaluates c, then exactly one of t or f.
        return the value defined for op over expr1, ..., exprN under μ
    # op is an ordinary function: evaluate all arguments first.
    return op(evalFunction(expr1, μ), ..., evalFunction(exprN, μ))
enddefine

Evaluation of a Rule

A [=rule=] is evaluated by calculating a [=solution sequence=] from the [=rule body=] and then using each [=solution mapping=] of the [=solution sequence=] to generate triples using the [=rule head=].

Blank nodes within [=triple patterns=] behave like variables. This is compatible with SPARQL graph pattern matching.

# Evaluate rule body
# This function returns a sequence of solutions

define evalRuleElements(B, SEQ, G, GD):
    where
        B is a sequence of rule elements
        SEQ is a solution sequence
        G and GD are RDF graphs

    foreach rule element rElt in B:

        if rElt is a triple pattern element TP:
            X = graphMatch(G, TP)
            SEQ1 = {}
            foreach μ1 in X:
                foreach μ2 in SEQ:
                    if compatible(μ1, μ2):
                      μ3 = merge(μ1, μ2)
                      add μ3 to SEQ1
                    endif
                endfor
            endfor
        endif

        if rElt is a filter element F:
            SEQ1 = {}
            foreach solution μ in SEQ:
                let x = evalFunction(F.expr, μ)
                if EBV(x) is true:
                    add μ to SEQ1
                endif
            endfor
        endif

        if rElt is a negation element N:
            SEQ1 = {}
            foreach solution μ in SEQ:
                S = sequence{ μ }
                if N.evaldata:
                    NEG = evalRuleElements(N.inner, S, G, GD)
                else:
                    NEG = evalRuleElements(N.inner, S, GD, GD)
                endif
                if NEG is empty:
                    add μ to SEQ1
                endif
            endfor
        endif

        if rElt is an assignment A:
            SEQ1 = {}
            foreach solution μ in SEQ:
                let x = evalFunction(A.expr, μ)
                if x is not an error:
                    # Add mapping A.var -> x to solution μ
                    let μ2 be a solution mapping μ ∪ { (A.var, x) }
                    add μ2 to SEQ1
                else:
                    # Error: drop solution μ
                endif
            endfor
        endif

        SEQ = SEQ1
    endfor

    return SEQ
enddefine

define evalRule(R, G, GD):
    where
        R is a well-formed rule
        G and GD are RDF graphs
    let B be R.body
        where each blank node in a triple pattern in R.body
        is replaced by a variable which is not used in the rule.
        The same variable is used for each occurrence of the
        same blank node, and a different variable is used for
        each different blank node.

    # Solution sequence of one solution that does not map any variables.
    let SEQ0: Solution sequence = { μ0 }

    if R.evaldata:
        let SEQ = evalRuleElements(B, SEQ0, G, GD)
    else:
        let SEQ = evalRuleElements(B, SEQ0, GD, GD)
    endif

    # Evaluate rule head
    let OUT = empty set
    foreach μ in SEQ:
        let S = {}
        # Rule head with each blank node replaced by a fresh blank node
        let HT = replaceBlankNodes(R.head)
        foreach triple template TT in HT
            let triple = subst(μ, TT)
            Add triple to S
        endfor
        OUT = OUT ∪ S
    endfor
    return OUT
enddefine

`OUT` may contain triples that are also in the [=base graph=].

Evaluation of a Rule Set

Evaluation of a [=rule set=] is defined as the execution of each [=stratum=] of a [=stratification=] of the rule set, where each stratum is executed completely and in order before moving on to the next [=stratum=]. A [=stratum=] is evaluated by first evaluating each of the [=run-once rules=] of that stratum, and then evaluating [=general rules=] of the stratum repeatedly until no new triples are produced.

let G0 be the input base graph
let RS be the rule set
let D be the graph of all DATA triples in RS

Apply stratification to RS

let LS be the sequence of layers after stratification

# Inference graph
let GI = { t ∈ D | t ∉ G0 }

# Evaluation graph and data blocks
let GE = G0 ∪ D

foreach stratum ST in LS:
    foreach rule R in ST.once:
        let X = evalRule(R, GE, G0)
        let Y = { t ∈ X | t ∉ GE }
        GI = GI ∪ Y
        GE = GE ∪ Y
    endfor

    let finished = false
    while ! finished:
        finished = true
        foreach rule R in ST.general:
            let X = evalRule(R, GE, G0)
            let Y = { t ∈ X | t ∉ GE }
            if Y is not empty:
                finished = false
                GI = GI ∪ Y
                GE = GE ∪ Y
            endif
        endfor
    endwhile
endfor
the result is GI

Worked Example

This section steps through the evaluation of a complete [=rule set=]: determining the rule dependencies (), calculating a stratification (), and evaluating each stratum in turn to produce the [=inference graph=] ().

The example describes software components and their dependencies: a frontend depends on an application server, which depends on a database and on a logging library. The database has a known vulnerability. The rules propagate exposure to vulnerabilities along the dependency chain, give components exposed to a severe vulnerability the status `:criticallyExposed`, give components that are not critical the status `:safeToDeploy`, and create a notification for each critically exposed component. (In practice, whether a vulnerability affects a component depends on the deployed version; the example elides versions and states the vulnerability directly.) For brevity, the base graph states `:dependsOn` triples directly, rather than deriving them from more specific relations as in .

The rules are labeled `R1` to `R5` in comments and are referred to by these labels in the rest of this section. They are the rules introduced in through . The rule set has no `IMPORTS`, so import processing leaves it unchanged, and no `DATA` blocks, so evaluation starts from the base graph alone.

Determining the Dependencies

Each triple pattern in each rule body is compared with the triple templates in every rule head, to determine which rules depend on which ().

  • The body of `R1` is the single triple pattern `?x :hasVulnerability ?v`. No rule head has a triple template that can generate a triple with predicate `:hasVulnerability`, so this pattern can only match the data. `R1` has no dependencies.
  • The body of `R2` has two triple patterns. `?x :dependsOn ?y` cannot match any rule head, so it matches only the data. `?y :exposedTo ?v` matches the head template `?x :exposedTo ?v` of `R1`, and also matches the head of `R2` itself. Both occurrences are [=triple pattern elements=] (they do not occur inside a [=negation element=], and `R2` is not a [=run-once rule=]), so `R2` has an open dependency on `R1` and an open dependency on itself. The dependency of `R2` on itself makes `R2` a recursive rule.
  • In the body of `R3`, the pattern `?x :exposedTo ?v` matches the heads of `R1` and `R2`, giving `R3` an open dependency on each. The pattern `?v :severity ?s` matches no rule head.
  • In the body of `R4`, the pattern `?x rdf:type :Component` matches no rule head: the only head template with predicate `rdf:type` is in `R5`, and its object `:Notification` is a different RDF term from `:Component`, so no generated triple can match. The pattern `?x :status :criticallyExposed` occurs inside the [=negation element=] `NOT { ?x :status :criticallyExposed }` and matches the head template of `R3`. Because the pattern occurs in a negation element, `R4` has a closed dependency on `R3`.
  • In the body of `R5`, the pattern `?x :status :criticallyExposed` matches the head of `R3`. The head of `R5` contains a blank node, so its dependency on `R3` is a closed dependency, even though the pattern is an ordinary [=triple pattern element=]. No pattern in any rule body matches the head templates of `R5`, so no rule depends on `R5`.

The [=dependency graph=] therefore has five vertices and six edges:

  • `R2 → R1` (open)
  • `R2 → R2` (open)
  • `R3 → R1` (open)
  • `R3 → R2` (open)
  • `R4 → R3` (closed)
  • `R5 → R3` (closed)

Although `R4` and `R5` have no direct dependency on `R1` or `R2`, each has a [=transitive dependency=] on both, through `R3`.

Dependency graph of the worked example rule set Five vertices labeled R1, R2, R3, R4, and R5. Dashed arrows mark open dependencies: from R2 to R1, from R2 to itself, from R3 to R1, and from R3 to R2. Solid arrows mark closed dependencies: from R4 to R3 and from R5 to R3. R2 R1 R3 R4 R5 open dependency closed dependency
The dependency graph of the worked example rule set. Open dependencies are drawn as dashed arrows and closed dependencies as solid arrows. Each edge points from the rule whose body contains the matching triple pattern to the rule whose head could generate the matching triples.

The only cycle in the graph is the self-edge `R2 → R2`, which is an open dependency. No cycle involves a closed dependency, so the [=stratification condition=] is satisfied and the rule set has a well-defined outcome.

Calculating the Stratification

Following the stratification algorithm, every rule starts in stratum 0. The edges are then inspected repeatedly until nothing changes:

  • An open edge requires the source rule to be in the same stratum as its target or higher. All four open edges already satisfy this with every rule at stratum 0, so they cause no changes.
  • A closed edge requires the source rule to be in a strictly higher stratum than its target, because the target must run to completion first. The edge `R4 → R3` (closed) finds `R4` and `R3` both at stratum 0, so `R4` is moved to stratum 1. Likewise the edge `R5 → R3` (closed) moves `R5` to stratum 1.

A further pass over the edges makes no changes, so the strata are final. Each stratum is then partitioned into [=run-once rules=] and [=general rules=]. `R5` has a blank node in its head, so it is a [=run-once rule=]; no other rule of the rule set is a [=run-once rule=]. The stratification is:

  • Stratum 0: general `R1`, `R2`, `R3`; no run-once rules
  • Stratum 1: run-once `R5`; general `R4`

This ordering captures the intent of the rules in the higher stratum. Whether a component is safe to deploy, and which components need a notification, can only be decided after all `:status :criticallyExposed` triples have been derived — and the rule that derives them, `R3`, completes in the stratum below, together with the rules `R1` and `R2` that feed it.

Evaluating the Strata

Evaluation follows the algorithm of . The evaluation graph `GE` starts as the base graph and the inference graph `GI` starts empty. Each stratum is evaluated to completion in order: the rules of the stratum are evaluated in passes, and passes repeat until a pass produces no new triples. The algorithm does not fix the order in which the rules of a stratum are evaluated within a pass; this trace uses the order `R1`, `R2`, `R3`. A different order can spread the same inferences across a different number of passes but, because stratum 0 runs to completion, it produces the same final graph.

Because this rule set has no `DATA` blocks, `GE` is `G0` plus `GI` at every point in the evaluation. The trace therefore tracks only `GI`, showing its state as evaluation proceeds and marking each new triple with the rule that produced it.

Stratum 0, first pass.

  • `R1`: the body matches `:db :hasVulnerability :vuln1`, binding `?x = :db` and `?v = :vuln1`. The head produces `:db :exposedTo :vuln1`. The triple is not in `GE`, so it is added to `GE` and `GI`.
  • `R2`: the body joins `?x :dependsOn ?y` with `?y :exposedTo ?v`. `GE` now contains `:db :exposedTo :vuln1`, so the join yields one solution: `?x = :app`, `?y = :db`, and `?v = :vuln1` (from `:app :dependsOn :db`). The head produces `:app :exposedTo :vuln1`, which is new and is added. There is no solution for `:frontend` yet, because `:app :exposedTo :vuln1` was not in `GE` when this evaluation of `R2` started.
  • `R3`: `?x :exposedTo ?v` now matches for `:db` and `:app`; joining with `:vuln1 :severity 9.1` and applying `FILTER(?s >= 9.0)` keeps both solutions. The head produces `:db :status :criticallyExposed` and `:app :status :criticallyExposed`, both new.
            # GI after stratum 0, first pass
            :db  :exposedTo :vuln1 .                    # new (R1)
            :app :exposedTo :vuln1 .                    # new (R2)
            :db  :status :criticallyExposed .           # new (R3)
            :app :status :criticallyExposed .           # new (R3)
          

New triples were produced, so evaluation of stratum 0 continues with another pass.

Stratum 0, second pass.

  • `R1`: matches as before; `:db :exposedTo :vuln1` is already in `GE`, so nothing new is produced.
  • `R2`: the join now also yields `?x = :frontend`, `?y = :app`, and `?v = :vuln1`, producing the new triple `:frontend :exposedTo :vuln1`. The exposure has taken two passes to travel two steps along the dependency chain.
  • `R3`: produces the new triple `:frontend :status :criticallyExposed`.
            # GI after stratum 0, second pass
            :db       :exposedTo :vuln1 .
            :app      :exposedTo :vuln1 .
            :frontend :exposedTo :vuln1 .               # new (R2)
            :db       :status :criticallyExposed .
            :app      :status :criticallyExposed .
            :frontend :status :criticallyExposed .      # new (R3)
          

Stratum 0, third pass. Every solution of every rule now produces only triples already in `GE`. `GI` is unchanged. No new triples means stratum 0 is complete: every `:exposedTo` triple and every `:status :criticallyExposed` triple that can ever be derived has been derived.

Stratum 1, run-once rules. The [=run-once rules=] of the stratum are evaluated first, each exactly once.

  • `R5`: the body matches the three `:status :criticallyExposed` triples, giving the solutions `?x = :db`, `?x = :app`, and `?x = :frontend`. For each solution, instantiating the head creates a fresh blank node, producing two triples per solution which are notifications for each critical component. `R5` is not evaluated again, even though the general rules of the stratum will now be evaluated repeatedly.

Stratum 1, first pass of the general rules.

  • `R4`: the pattern `?x rdf:type :Component` matches `:frontend`, `:app`, `:db`, and `:logger`. (It does not match the `rdf:type :Notification` triples just created by `R5`.) The negation element `NOT { ?x :status :criticallyExposed }` rejects the solutions for `:frontend`, `:app`, and `:db`, because `GE` contains a `:status :criticallyExposed` triple for each of them. Only the solution `?x = :logger` survives, producing the new triple `:logger :status :safeToDeploy`.
            # GI after stratum 1
            :db       :exposedTo :vuln1 .
            :app      :exposedTo :vuln1 .
            :frontend :exposedTo :vuln1 .
            :db       :status :criticallyExposed .
            :app      :status :criticallyExposed .
            :frontend :status :criticallyExposed .
            _:n1      rdf:type :Notification .          # new (R5)
            _:n1      :concerns :db .                   # new (R5)
            _:n2      rdf:type :Notification .          # new (R5)
            _:n2      :concerns :app .                  # new (R5)
            _:n3      rdf:type :Notification .          # new (R5)
            _:n3      :concerns :frontend .             # new (R5)
            :logger   :status :safeToDeploy .           # new (R4)
          

Stratum 1, second pass of the general rules. No new triples. Evaluation is complete, and `GI` as shown above is the resulting [=inference graph=].

The stratification is what makes this outcome reliable. Evaluated during the first pass of stratum 0, before `:frontend :status :criticallyExposed` was derived, `R4` would have incorrectly concluded `:frontend :status :safeToDeploy`, and `R5` would have created notifications for `:db` and `:app` but none for `:frontend`. Deferring both to stratum 1 means they see the complete set of `:status :criticallyExposed` triples, so the order in which rules are evaluated within each stratum does not affect the final outcome.

SPARQL-RL Grammar

A SPARQL-RL document is an [=RDF string=] encoded in UTF-8 [[!RFC3629]] and starting with the RuleSet production and conforming to the additional constraints defined in . Only Unicode scalar values, in the ranges U+0000 to U+D7FF and U+E000 to U+10FFFF, are allowed. This excludes surrogate code points, range U+D800 to U+DFFF.

Version Announcement

A version label is a string that identifies the syntax and semantics conformance for SPARQL-RL.

Version Labels
Version Label
"1.2"

The version announcement SHOULD be made early in the document.

Multiple VERSION directives may appear in a [=SPARQL-RL document=]. Each directive applies to the part of the document following the directive, until another directive is encountered or the end of the document is reached.

[=Version labels=] can also be given by the `version` parameter of the Media Type. In the absence of a current VERSION directive, the version specified as part of the Media Type is considered.

White Space

White space (production WS) is used to separate two terminals that would otherwise be (mis-)recognized as one terminal. Rule names below in capitals indicate where white space is significant; these form a possible choice of terminals for constructing a SPARQL-RL parser.

White space is significant in the production String.

Comments

Comments start with a # outside an IRIREF, STRING_LITERAL1, STRING_LITERAL2, STRING_LITERAL_LONG1, or STRING_LITERAL_LONG2, and continue to the end of line (marked by LF, or CR), or end of file if there is no end of line after the comment marker. Comments are treated as white space.

IRI References

Relative IRI references are resolved with base IRIs as per [[[RFC3986]]] [[RFC3986]] using only the basic algorithm in section 5.2. Neither Syntax-Based Normalization nor Scheme-Based Normalization (described in sections 6.2.2 and 6.2.3 of RFC3986) is performed. Characters additionally allowed in IRI references are treated in the same way that unreserved characters are treated in URI references, per section 6.5 of [[[RFC3987]]] [[RFC3987]].

The BASE directive defines the Base IRI used to resolve relative IRI references per [[RFC3986]] section 5.1.1, "Base URI Embedded in Content". section 5.1.2, "Base URI from the Encapsulating Entity" defines how the In-Scope Base IRI may come from an encapsulating document, such as a SOAP envelope with an `xml:base` directive or a MIME multipart document with a `Content-Location` header. The "Retrieval URI" identified in 5.1.3, Base "URI from the Retrieval URI", is the URL from which a particular [=SPARQL-RL document=] was retrieved. If none of the above specifies the Base URI, the default Base URI (section 5.1.4, "Default Base URI") is used. Each BASE directive sets a new In-Scope Base URI, relative to the previous one.

Escape Sequences

There are three forms of escapes used in [=SPARQL-RL documents=]:

Context where each kind of escape sequence can be used
numeric
escapes
string
escapes
reserved character
escapes
IRIs, used as [=RDF terms=], PREFIX, or BASE declarations yes no no
local names no no yes
Strings yes yes no

%-encoded sequences are in the character range for IRIs and are explicitly allowed in local names. These appear as a % followed by two hex characters and represent that same sequence of three characters. These sequences are not decoded during processing. A term written as <http://a.example/%66oo-bar> designates the IRI http://a.example/%66oo-bar and not IRI http://a.example/foo-bar. A term written as ex:%66oo-bar with a prefix PREFIX ex: <http://a.example/> also designates the IRI http://a.example/%66oo-bar.

Grammar

The EBNF used here is defined in XML 1.0 [[!EBNF-NOTATION]].

Notes:

  1. The entry point of the grammar is RuleSet.
  2. Keywords are case-insensitive except for 'a' which is case-sensitive.
  3. Escape sequences UCHAR and ECHAR are case sensitive.
  4. Variables are not allowed in a `DATA` block.
  5. When tokenizing the input and choosing grammar rules, the longest match is chosen.
  6. The SPARQL-RL grammar is LL(1) and LALR(1) when the rules with uppercased names are used as terminals.

A text version of this grammar is available here.

Selected Terminal Literal Strings

This document uses some specific terminal literal strings [[EBNF-NOTATION]]. To clarify the Unicode code points used for these terminal literal strings, the following table describes specific characters used in this section.

CodeGlyphDescription
U+000A LF Line feed
U+000D CR Carriage return
U+0023 # Number sign
U+0025 % Percent sign
U+005C \ Backslash

Example Imports Algorithm

The following algorithm shows one way to resolve `IMPORTS` statements by visiting all the referenced documents recursively.

The rule set merge of two rule sets, `RS1` and `RS2`, produces a rule set, `MR`, defined as follows:

let RS be a rule set
let V = {}
if RS has a location:
    V = { location of RS }
endif

define ruleSetMerge(rule set RS1, rule set RS2):
    let MR be a rule set
    MR.rules = RS1.rules ∪ RS2.rules
    MR.data = rdf_merge(RS1.data, RS2.data)
    MR.imports = {}
    the result is MR
enddefine

define imports(rule set RS):
    let RS2 be a rule set formed from RS.rules and RS.data
    foreach URL x in RS.imports:
        if x ∉ V:
            V = V ∪ { x }
            read rule set RS3 from URL x
            RS2 = ruleSetMerge(RS2, imports(RS3))
        endif
    endfor
    the result is RS2
enddefine

result is imports(RS)
        

where `rdf_merge` is the RDF merge operation.

Internet Media Type and File Extension

The Internet Media Type (formerly known as MIME Type) for SPARQL-RL is "application/sparql-rl".

The information that follows has been submitted to the Internet Engineering Steering Group (IESG) for review, approval, and registration with IANA.

Type name:
application
Subtype name:
sparql-rl
Required parameters:
None
Optional parameters:
version
This parameter is optional. If present, acceptable values of version are defined in Version Labels.
profile
This parameter is optional and is used to include additional information. It does not change the semantics of the resource representation when processed without knowledge of the profile. The value of a profile parameter is a non-empty list of space-separated URIs. For more information and background, please refer to [[RFC6906]].
Encoding considerations:
The syntax of SPARQL-RL is expressed over code points in Unicode [[UNICODE]]. The encoding is always UTF-8 [[RFC3629]].
Unicode code points may also be expressed using an \uXXXX (U+0 to U+FFFF) or \UXXXXXXXX syntax (for U+10000 onward) where X is a hexadecimal digit [0-9A-Fa-f], excluding U+D800 to U+DFFF, the surrogate code points.
Security considerations:
See as well as [[[RFC3629]]] [[RFC3629]] section 7, Security Considerations.
Interoperability considerations:
There are no known interoperability issues.
Published specification:
This specification.
Additional information:
Magic number(s):
A SPARQL-RL file may have the string 'PREFIX' (case independent) near the beginning of the document.
File extension(s):
".srl"
Base URI:
The SPARQL-RL 'BASE <IRIref>' term can change the current base URI for relative IRIrefs in the language that are used later in the document.
Email address to contact for further information:
Data Shapes Working Group <public-shacl@w3.org>
Intended usage:
COMMON
Restrictions on usage:
None
Author/Change controller:
The SPARQL-RL specification is a work product of the World Wide Web Consortium's Data Shapes Working Group. The W3C has change control over this specification.

Security Considerations

SPARQL-RL documents may contain `IMPORTS` statements that reference other [=SPARQL-RL documents=] and import their contents into the current document. If an imported document itself contains its own `IMPORTS` statements, those documents are also imported.

While useful to modularize [=rule sets=], the use of `IMPORTS` statements can introduce security risks. Risks include, but are not limited to, [=rule sets=] causing excessive computation, whether maliciously or accidentally, while being evaluated; and HTTP requests being intercepted and a different document being returned, including out-of-date copies.

Applying a SPARQL-RL rule set to an RDF graph can result in significant computation and memory usage, which may be exploited to cause denial of service. Applications should take care to limit the amount of computation and memory usage that can be caused by applying a SPARQL-RL rule set.

The SPARQL-RL syntax is encoded in UTF-8 [[!RFC3629]] and allows the use of unescaped control characters in string data. Although this specification does not directly expose this content to an end user, it might be presented through a user agent, which may cause the presented text to be misleading due to such characters.

SPARQL-RL can be used to process and create arbitrary application data; security considerations will vary by domain of use. Security tools and protocols applicable to text (for example, PGP encryption, checksum validation, password-protected compression) may also be used on [=SPARQL-RL documents=]. Security/privacy protocols must be imposed to reflect the sensitivity of the information in the outcome of SPARQL-RL rule set evaluation.

The security considerations of SPARQL-RL include those of RDF data and formats such as RDF Turtle.

Privacy Considerations

A SPARQL-RL document can contain additional application data which may include the expression of personally identifiable information (PII) or other information which could be considered sensitive. Authors publishing rule sets with such information are advised to carefully consider the needs and use of publishing such information, as well as the applicable regulations for the regions where the data is expected to be consumed and potentially revealed (e.g., GDPR, CCPA, others), particularly whether authorization measures are needed for access to the data.

Acknowledgments

The following people contributed to the development of SPARQL-RL in the rules task force of the Data Shapes Working Group: Robert David, David Habgood, Livio Robaldo, Ognjen Savkovic, Simon Steyskal, Ted Thibodeau Jr, and Andy Seaborne.

Members of the Data Shapes Working Group included @@.