An overview of the changes in SPARQL 1.2

Ruben Taelman

GOBLIN Hackathon on Metadata and Provenance for Knowledge Graphs, 8 June 2026

An overview of the changes in SPARQL 1.2

Ghent University – imec – IDLab, Belgium

Triple terms use the “<<( … )>>” syntax

Syntax remains aligned with Turtle.

VERSION "1.2"
PREFIX : <http://example.com/ns#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

SELECT ?person ?authority {
  _:r rdf:reifies <<( ?person :jobTitle "Designer" )>> .
  _:r :accordingTo ?authority .
}
 
Triple term

“<< … >>” as shorthand for writing reifying triples

Triple terms are rarely used directly.

SELECT ?person ?authority {
  << ?person :jobTitle "Designer" >>
      :accordingTo ?authority .
}

Expands to:

SELECT ?person ?authority {
  _:r rdf:reifies <<( ?person :jobTitle "Designer" )>> .
  _:r :accordingTo ?authority .
}
 
Reifying triple
 
Reifier

“<< … ~ :reifier >>” sets a specific reifier

Reifier can be IRIs, blank nodes, or variables.

SELECT ?person ?authority {
  << ?person :jobTitle "Designer" ~ :id >>
      :accordingTo ?authority .
}
 
Reifier

“{| … |}” reifies and asserts triples

The annotation syntax.

SELECT ?p ?auth ?date {
  ?p :jobTitle ?title {| :accordingTo ?auth; :recorded ?date |} .
}

Expands to:

SELECT ?p ?auth ?date {
  ?p :jobTitle ?title .
  << ?p :jobTitle ?title >> :accordingTo ?auth ;
                            :recorded ?date .
}
 
Annotation block

Five new functions related to triple terms

To be used within expressions (FILTER, BIND, …)

Dynamically bind triple terms (1)

Using BIND, triple terms can be bound to a variable.

→ Lookup all asserted reifying triples

SELECT ?tt ?date {
  ?s ?p ?o .
  BIND( TRIPLE(?s, ?p, ?o) AS ?tt )
  ?reifier rdf:reifies ?tt .
  ?reifier :tripleAdded ?date .
}

Dynamically bind triple terms (2)

TRIPLE can also be written using the “<<( … )>>” shorthand.

SELECT ?tt ?date {
  ?s ?p ?o .
  BIND( <<( ?s ?p ?o )>> AS ?tt )
  ?reifier rdf:reifies ?tt .
  ?reifier :tripleAdded ?date .
}

Limitation: no sub-expressions allowed within this shorthand.

Retrieve subjects of triple terms

Using SUBJECT, the subject of a triple term is returned.

SELECT ?s {
  :id rdf:reifies ?tt .
  BIND(SUBJECT(?tt) AS ?s)
}

SPARQL depends on different forms of comparison

Introduction of triple terms has an impact on all of them:

sameTerm(term1, term2)

True if terms are the same RDF term.

term1 = term2 (sameValue)

True if terms correspond to the same value.

Difference between sameTerm and sameValue (=)

Expression Result
sameTerm(123, 123) true
sameTerm(123, 123.0) false
sameTerm(<<(:s :p 123)>>, <<(:s :p 123.0)>>) false
123 = 123 true
123 = 123.0 true
<<(:s :p 123)>> = <<(:s :p 123.0)>> true

ORDER BY now also considers triple terms

“<” operator is used for directly comparable terms. Otherwise, incomparable terms are sorted in this order:

SPARQL results formats also support triple terms

SPARQL JSON: “triple” term types

{
  "head": { "vars": [ "x", "name", "triple" ] },
  "results": {
    "bindings": [
      {
        "x":    { "type": "bnode",   "value": "r1" },
        "name": { "type": "literal", "value": "Alice" },
        "triple": {
          "type": "triple",
          "value": {
            "subject":   { "type": "uri",     "value": "http://example.org/alice" },
            "predicate": { "type": "uri",     "value": "http://example.org/name" },
            "object":    { "type": "literal", "value": "Alice", "datatype": "..." }
          }
        }
      }
    ]
  }
}

SPARQL XML: <triple> tag

<?xml version="1.0"?>
<sparql ...>
  <head> ... </head>
  <results>
    <result>
      <binding name="x"><bnode>r2</bnode></binding>
      <binding name="name"><literal xml:lang="en">Bob</literal></binding>
      <binding name="triple">
        <triple>
          <subject><uri>http://example.org/alice</uri></subject>
          <predicate><uri>http://example.org/name</uri></predicate>
          <object><literal
datatype="http://www.w3.org/2001/XMLSchema#string">Alice</literal></object>
        </triple>
      </binding>
    </result>
  </results>
</sparql>

SPARQL CSV: encoding in “<<( … )>>”

xtriple
Alice<<( http://example/alice http://example/knows http://example/bob )>>
Bob<<( http://example/bob http://example/knows http://example/alice )>>
Carol<<( http://example/carol http://example/says "Hello world, my name is Alice." )>>

Warning: SPARQL CSV is lossy! (no data types)

SPARQL TSV: encoding in “<<( … )>>”

?x?triple
"Alice"<<( <http://example/alice> <http://example/knows> <http://example/bob> )>>
"Bob"<<( <http://example/bob> <http://example/knows> <http://example/alice> )>>
"Carol"<<( <http://example/carol> <http://example/says> "Hello world, my name is Alice". )>>

Manage syntax changes without new media types

Also painful for clients to manage during content negotiation
    (+CORS: 128 char header value limit).

Keep media types and introduce versioning

In-band versioning

VERSION "1.2"
PREFIX : <http://example.com/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
:a :name "Alice" ~ :t {| :statedBy :bob |} .

Out-of-band versioning

GET /document.ttl HTTP/1.1
Host: example.com
Accept: text/turtle; version=1.2

HTTP/1.1 200 OK
Content-Type: text/turtle; version=1.2

Media type parameter

Defined version labels

Version label Description
"1.2" RDF 1.2 syntax
"1.2-basic" RDF 1.2 syntax without triple terms
"1.1" RDF 1.1 syntax except for use of a version directive

Languages can (optionally) have different reading directions

SPARQL functions to handle rdf:dirLangString

SPARQL 1.1 introduced the (NOT) EXISTS filter expressions

For testing if some data does (not) exist.

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?person WHERE {
  ?person rdf:type foaf:Person .
  FILTER EXISTS { ?person foaf:name ?name }
}

Several issues were discovered with EXISTS

Outcome of the SPARQL EXISTS W3C community group:

  1. Some uses of EXISTS are not defined during evaluation.
  2. Substitution happens where definitions are only for variables.
  3. Blank nodes substituted into BGPs act as variables.
  4. Substitution can flip MINUS to its disjoint-domain case.
  5. Substitution affects disconnected variables.

Examples: substitution happens where definitions are only for variables

The target term of BIND must always be a variable.

SELECT ?person WHERE {
  ?person rdf:type foaf:Person .
  FILTER EXISTS { BIND ( :p AS ?person ) }
}

Projection with SELECT must always be a variable.

SELECT ?person WHERE {
  ?person rdf:type foaf:Person .
  FILTER EXISTS { SELECT ?person { :a :b ?person } }
}

Different interpretations lead to inconsistent behaviour across SPARQL engines.

Solution: “deep injection”

Many new specification tests

https://github.com/w3c/rdf-tests/

Declarative set of tests to measure conformance of a system.

New tests for:

Next steps

Reference implementations for 1.2