Skip to content

SPARQL 1.2 Reference

IndentiaDB implements the SPARQL 1.1 specification plus the SPARQL 1.2 Working Draft (checked against the 27 August 2026 draft), aligned with the RDF 1.2 Candidate Recommendation (7 April 2026). This reference covers every supported feature with complete working examples.


Endpoints

Operation Method Endpoint Content-Type
SELECT / ASK / CONSTRUCT / DESCRIBE (GET) GET /sparql?query=<encoded> —
SELECT / ASK / CONSTRUCT / DESCRIBE (POST) POST /sparql application/sparql-query
INSERT DATA / DELETE DATA / DELETE…INSERT…WHERE POST /update application/sparql-update
Graph Store: PUT graph PUT /data?graph=<iri> text/turtle
Graph Store: GET graph GET /data?graph=<iri> —
Graph Store: DELETE graph DELETE /data?graph=<iri> —

The Graph Store Protocol is also served at /graph-store. See SPARQL & REST Endpoints for request details, update limits and export endpoints.


Response Formats

Accept Header Format Applicable To
application/sparql-results+json SPARQL JSON SELECT, ASK (CONSTRUCT/DESCRIBE as a JSON array of N-Triples strings)
application/sparql-results+xml SPARQL XML SELECT, ASK
text/csv CSV SELECT
text/tab-separated-values TSV SELECT
application/x-ndjson NDJSON SELECT (streaming)
text/turtle Turtle CONSTRUCT, DESCRIBE
application/n-triples N-Triples CONSTRUCT, DESCRIBE
application/n-triples;profile="http://www.w3.org/ns/rdf-canon#c14n" N-Triples Canonical (RDF 1.2 C14N) CONSTRUCT, DESCRIBE

Canonical N-Triples output (ntriples-c14n) applies the escaping and language-tag normalization rules from the RDF 1.2 Candidate Recommendation (Section 3 and the related RDF Dataset Canonicalization spec). Select it via the profile parameter on the Accept header, or by passing ?format=ntriples-c14n as a query parameter.

TSV and CSV columns follow the SELECT projection order. TSV cells are complete RDF terms (<iri>, "literal"^^<datatype>, "text"@lang, _:b0, <<( s p o )>>) under a ?-prefixed header, while CSV cells carry bare values — see TSV and CSV.

Without an Accept header or format parameter the response is application/sparql-results+json. An Accept header that names no supported format (and no */*) returns 406.


Namespace Prefixes

All examples use these common prefixes:

PREFIX rdf:   <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs:  <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl:   <http://www.w3.org/2002/07/owl#>
PREFIX xsd:   <http://www.w3.org/2001/XMLSchema#>
PREFIX foaf:  <http://xmlns.com/foaf/0.1/>
PREFIX ex:    <http://example.org/>
PREFIX skos:  <http://www.w3.org/2004/02/skos/core#>
PREFIX dbo:   <http://dbpedia.org/ontology/>
PREFIX geo:   <http://www.opengis.net/ont/geosparql#>

Example 1: Basic SELECT

PREFIX foaf: <http://xmlns.com/foaf/0.1/>

SELECT ?person ?name WHERE {
    ?person a foaf:Person ;
            foaf:name ?name .
}
ORDER BY ?name
LIMIT 100
curl -X POST 'http://localhost:7001/sparql' \
  -H 'Content-Type: application/sparql-query' \
  -H 'Accept: application/sparql-results+json' \
  -d 'PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?person ?name WHERE {
    ?person a foaf:Person ; foaf:name ?name .
}
ORDER BY ?name LIMIT 100'

Example 2: SELECT with FILTER and OPTIONAL

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>

SELECT ?name ?age ?email WHERE {
    ?person a foaf:Person ;
            foaf:name ?name ;
            foaf:age  ?age .
    OPTIONAL { ?person foaf:mbox ?email }
    FILTER (?age >= 25)
    FILTER (!BOUND(?email) || CONTAINS(STR(?email), "@example.org"))
}
ORDER BY DESC(?age)
LIMIT 50

The OPTIONAL clause includes ?email as null if the triple does not exist. FILTER (!BOUND(?email) || ...) accepts rows where email is absent or matches a condition.


Example 3: INSERT DATA with Named Graph

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>
PREFIX xsd:  <http://www.w3.org/2001/XMLSchema#>

INSERT DATA {
    GRAPH <http://example.org/social> {
        ex:alice a foaf:Person ;
            foaf:name "Alice van den Berg" ;
            foaf:age 30 ;
            foaf:mbox <mailto:alice@example.org> ;
            foaf:knows ex:bob , ex:carol .

        ex:bob a foaf:Person ;
            foaf:name "Bob de Vries" ;
            foaf:age 28 .

        ex:carol a foaf:Person ;
            foaf:name "Carol Jansen" ;
            foaf:age 35 .
    }
    GRAPH <http://example.org/hr> {
        ex:alice ex:worksAt ex:acme ;
                 ex:salary  "85000"^^xsd:integer .
        ex:bob   ex:worksAt ex:acme ;
                 ex:salary  "92000"^^xsd:integer .
    }
}

Example 4: DELETE/INSERT UPDATE WHERE

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>

DELETE {
    GRAPH <http://example.org/hr> {
        ex:alice ex:salary ?old_salary .
    }
}
INSERT {
    GRAPH <http://example.org/hr> {
        ex:alice ex:salary "90000"^^<http://www.w3.org/2001/XMLSchema#integer> .
        ex:alice ex:salary_updated_at "2026-03-21"^^<http://www.w3.org/2001/XMLSchema#date> .
    }
}
WHERE {
    GRAPH <http://example.org/hr> {
        ex:alice ex:salary ?old_salary .
    }
}

The DELETE/INSERT … WHERE form is atomic. The WHERE clause binds variables that are used in both the DELETE and INSERT templates.


Example 5: CONSTRUCT

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>

CONSTRUCT {
    ?person foaf:name ?name ;
            foaf:age  ?age ;
            ex:ageGroup ?group .
}
WHERE {
    GRAPH <http://example.org/social> {
        ?person a foaf:Person ;
                foaf:name ?name ;
                foaf:age  ?age .
    }
    BIND(
        IF(?age < 30, "young",
        IF(?age < 50, "middle",
        "senior")) AS ?group
    )
}

CONSTRUCT returns an RDF graph (triples) rather than a result set. Use Accept: text/turtle to receive Turtle syntax.


Example 6: ASK

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>

ASK {
    GRAPH <http://example.org/social> {
        ex:alice foaf:knows ex:bob .
    }
}

Returns {"boolean": true} or {"boolean": false} in JSON format.


Example 7: DESCRIBE

PREFIX ex: <http://example.org/>

DESCRIBE ex:alice

Returns all known triples about ex:alice across all named graphs. The exact triples returned depend on the DESCRIBE algorithm (CBD — Concise Bounded Description — by default).


Example 8: Property Paths (All Operators)

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>

# Sequence: p1 / p2 — follows two consecutive predicates
SELECT ?org WHERE {
    ex:alice foaf:knows / ex:worksAt ?org .
}

# Alternative: p1 | p2 — matches either predicate
SELECT ?connection WHERE {
    ex:alice (foaf:knows | foaf:member) ?connection .
}

# Zero or more (transitive): p* — any number of hops including zero
SELECT ?ancestor WHERE {
    ex:GradStudent rdfs:subClassOf* ?ancestor .
}

# One or more (transitive): p+ — requires at least one hop
SELECT ?reachable WHERE {
    ex:alice foaf:knows+ ?reachable .
    FILTER (?reachable != ex:alice)
}

# Zero or one: p? — optional single hop
SELECT ?contact WHERE {
    ex:alice foaf:knows? ?contact .
}

# Inverse: ^p — traverse in reverse direction
SELECT ?follower WHERE {
    ex:alice ^foaf:knows ?follower .
    # equivalent to: ?follower foaf:knows ex:alice
}

# Negated property set: !(p1 | p2) — any predicate except listed
SELECT ?s ?p ?o WHERE {
    ?s ?p ?o .
    FILTER (?p != rdf:type)
    # DSL equivalent:
    # ?s !(rdf:type) ?o .
}

# Compound: (p1 / p2)+ — sequence, one or more times
SELECT ?deep WHERE {
    ex:alice (foaf:knows / foaf:knows)+ ?deep .
}

BFS Cycle Detection: IndentiaDB evaluates transitive property paths using BFS with cycle detection (SPARQL 1.2 Issues #266 and #267). Cyclic graphs terminate correctly without infinite loops.

Zero-length paths with a fixed endpoint: a path that can match zero steps — p*, p?, or combinations such as ^(p*) and (p?/q*) — matches a fixed term to itself, following the W3C algebra, even when that term does not occur in the data. ASK { ex:nobody foaf:knows* ex:nobody } is true, and SELECT ?x WHERE { ex:nobody foaf:knows* ?x } returns ?x = ex:nobody. Only a pattern with variables on both ends (?x foaf:knows* ?y) enumerates the nodes of the graph for its zero-length matches.


Example 9: Named Graphs (FROM, FROM NAMED, GRAPH)

PREFIX foaf: <http://xmlns.com/foaf/0.1/>

# Query a specific named graph
SELECT ?name WHERE {
    GRAPH <http://example.org/social> {
        ?p a foaf:Person ; foaf:name ?name .
    }
}

# Query multiple named graphs using FROM NAMED + GRAPH variable
SELECT ?graph ?name WHERE {
    GRAPH ?graph {
        ?p a foaf:Person ; foaf:name ?name .
    }
}

# Restrict default dataset with FROM
SELECT ?name
FROM <http://example.org/social>
WHERE {
    ?p a foaf:Person ; foaf:name ?name .
}

# Combine default graph and named graphs
SELECT ?name ?employer
FROM          <http://example.org/social>
FROM NAMED    <http://example.org/hr>
WHERE {
    ?p foaf:name ?name .
    GRAPH <http://example.org/hr> {
        ?p <http://example.org/worksAt> ?employer .
    }
}

Example 10: RDF-star — Quoted Triples (Insert + Query)

Why RDF-star matters: reification vs quoted triples

In RDF 1.1, adding metadata to a triple — a confidence score, a source, a timestamp — required reification: creating a blank node that indirectly represents the triple, then attaching properties to that blank node. To express "Alice knows Bob with 90% confidence, according to source X", you needed 6 extra triples:

# RDF 1.1 — 6 triples to annotate one fact
INSERT DATA {
    _:stmt1 a               rdf:Statement ;
            rdf:subject     ex:alice ;
            rdf:predicate   foaf:knows ;
            rdf:object      ex:bob .
    _:stmt1 ex:confidence   "0.9"^^xsd:decimal .
    _:stmt1 ex:source       ex:SourceX .
}

This is verbose, fragile (blank nodes cannot be referenced across graphs), and query-hostile: finding all high-confidence facts requires joining through the reification structure.

RDF 1.2 (RDF-star) solves this with one line:

# RDF 1.2 — 1 quoted triple with inline annotation
INSERT DATA {
    << ex:alice foaf:knows ex:bob >>
        ex:confidence "0.9"^^xsd:decimal ;
        ex:source     ex:SourceX .
}

<< ex:alice foaf:knows ex:bob >> is a quoted triple — the triple itself becomes a first-class value that you can attach properties to directly. No blank nodes, no joins, no fragility.

The practical impact

A knowledge graph with 10 million annotated facts needs 60 million triples under RDF 1.1 reification. Under RDF-star, it needs 10 million — the base facts — plus the annotation triples directly attached. Queries are simpler, storage is smaller, and the intent is immediately readable.

See the RDF-star Guide for a deeper treatment of use cases, patterns, and the comparison with other approaches.

PREFIX foaf:  <http://xmlns.com/foaf/0.1/>
PREFIX ex:    <http://example.org/>
PREFIX xsd:   <http://www.w3.org/2001/XMLSchema#>

# Insert quoted triples with provenance metadata
INSERT DATA {
    << ex:alice foaf:knows ex:bob >>
        ex:since      "2020-01-15"^^xsd:date ;
        ex:confidence "0.95"^^xsd:decimal ;
        ex:source     <http://example.org/linkedin_import> .

    << ex:bob foaf:knows ex:carol >>
        ex:since      "2022-06-01"^^xsd:date ;
        ex:confidence "0.80"^^xsd:decimal ;
        ex:source     <http://example.org/manual_entry> .
}
# Query: find all knows relationships with metadata
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>

SELECT ?subject ?object ?since ?confidence ?source WHERE {
    << ?subject foaf:knows ?object >>
        ex:since      ?since ;
        ex:confidence ?confidence ;
        ex:source     ?source .
}
ORDER BY DESC(?confidence)

Example 11: Provenance Query with RDF-star

PREFIX foaf:  <http://xmlns.com/foaf/0.1/>
PREFIX ex:    <http://example.org/>
PREFIX xsd:   <http://www.w3.org/2001/XMLSchema#>

SELECT ?subject ?predicate ?object ?source ?confidence
WHERE {
    << ?subject ?predicate ?object >>
        ex:source     ?source ;
        ex:confidence ?confidence .
    FILTER (?confidence > 0.8)
}
ORDER BY DESC(?confidence)

This pattern is the foundation of triple-level provenance tracking. Every fact in the knowledge graph can carry its own evidence score, data source, ingestion timestamp, and authoring agent — without creating auxiliary reification nodes.


Example 12: SPARQL 1.2 TRIPLE() Function Family

SPARQL 1.2 introduces the TRIPLE(), SUBJECT(), PREDICATE(), OBJECT(), and isTRIPLE() functions for constructing and decomposing triple terms programmatically.

PREFIX ex: <http://example.org/>

# TRIPLE(s, p, o) constructs a triple term from components
SELECT ?t WHERE {
    BIND(TRIPLE(ex:alice, <http://xmlns.com/foaf/0.1/knows>, ex:bob) AS ?t)
}

# SUBJECT(), PREDICATE(), OBJECT() decompose a quoted triple
SELECT ?s ?p ?o WHERE {
    ?annotation rdf:subject   ?quoted_triple .
    BIND(SUBJECT(?quoted_triple)   AS ?s)
    BIND(PREDICATE(?quoted_triple) AS ?p)
    BIND(OBJECT(?quoted_triple)    AS ?o)
}

# isTRIPLE() tests whether a value is a triple term
SELECT ?val WHERE {
    ?s ex:hasAnnotation ?val .
    FILTER (isTRIPLE(?val))
}

# Combined: construct quoted triple term and use in INSERT
INSERT {
    TRIPLE(?s, ?p, ?o) ex:assertedBy ex:system1 .
}
WHERE {
    GRAPH ex:staging {
        ?s ?p ?o .
    }
}

Example 13: Federated SERVICE Query

PREFIX foaf:    <http://xmlns.com/foaf/0.1/>
PREFIX dbo:     <http://dbpedia.org/ontology/>
PREFIX ex:      <http://example.org/>
PREFIX company: <http://example.org/company/>

SELECT ?employee ?name ?birthPlace WHERE {
    # Local graph: find employees
    GRAPH <http://example.org/hr> {
        ?employee a company:Employee .
    }

    # Federated: enrich from DBpedia
    SERVICE <https://dbpedia.org/sparql> {
        ?employee foaf:name      ?name ;
                  dbo:birthPlace ?birthPlace .
    }
}
ORDER BY ?name

The SERVICE clause sends a sub-query to a remote SPARQL endpoint and joins the results with the local dataset. When the local pattern shares variables with the SERVICE pattern, its distinct bindings are pushed to the remote endpoint as a VALUES block, so only joinable rows come back.

Federation must be enabled

SERVICE is disabled by default: without IG_SPARQL_FEDERATION=true on the server, this query returns 501. Endpoints must also pass the federation allowlist and address policy. See Federated Queries — Configuration & Security.


Example 14: SERVICE SILENT (Fault Tolerance)

PREFIX foaf: <http://xmlns.com/foaf/0.1/>

SELECT ?name ?label WHERE {
    ?person a foaf:Person ; foaf:name ?name .

    # SERVICE SILENT: if the remote endpoint is unavailable, skip rather than fail
    SERVICE SILENT <https://external-kb.example.com/sparql> {
        ?person rdfs:label ?label .
    }
}

With SERVICE SILENT, if the remote endpoint returns an error or is unreachable, the query continues with the local results — the ?label variable simply has no binding for those rows.


Example 15: Aggregate Functions

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>
PREFIX xsd:  <http://www.w3.org/2001/XMLSchema#>

SELECT
    (COUNT(?person)            AS ?count)
    (COUNT(DISTINCT ?person)   AS ?unique_people)
    (SUM(?salary)              AS ?total_payroll)
    (AVG(?salary)              AS ?average_salary)
    (MIN(?salary)              AS ?min_salary)
    (MAX(?salary)              AS ?max_salary)
    (SAMPLE(?name)             AS ?example_name)
WHERE {
    GRAPH <http://example.org/hr> {
        ?person a foaf:Person ;
                foaf:name ?name ;
                ex:salary ?salary .
    }
}

SPARQL 1.2 Change: GROUP_CONCAT now returns xsd:string (per Feb 3, 2026 Editor's Draft), consistent with other aggregate functions that return typed literals.


Example 16: GROUP BY / HAVING

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>

SELECT ?department (COUNT(?person) AS ?headcount) (AVG(?salary) AS ?avg_salary)
WHERE {
    GRAPH <http://example.org/hr> {
        ?person a foaf:Person ;
                ex:department ?department ;
                ex:salary     ?salary .
    }
}
GROUP BY ?department
HAVING (COUNT(?person) >= 2 && AVG(?salary) > 70000)
ORDER BY DESC(?avg_salary)

Example 17: GROUP_CONCAT

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>

SELECT ?department (GROUP_CONCAT(?name; separator=", ") AS ?members)
WHERE {
    GRAPH <http://example.org/hr> {
        ?person a foaf:Person ;
                foaf:name   ?name ;
                ex:department ?department .
    }
}
GROUP BY ?department
ORDER BY ?department

Returns one row per department with all member names concatenated. Per SPARQL 1.2, the return type is xsd:string.


Example 18: BIND and Expression Variables

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>
PREFIX xsd:  <http://www.w3.org/2001/XMLSchema#>

SELECT ?name ?salary ?tax_bracket ?net_salary WHERE {
    GRAPH <http://example.org/hr> {
        ?person foaf:name ?name ;
                ex:salary ?salary .
    }

    BIND(
        IF(?salary > 100000, "high",
        IF(?salary > 60000,  "medium",
        "low")) AS ?tax_bracket
    )

    BIND(
        IF(?salary > 100000,
            ?salary * 0.45,
        IF(?salary > 60000,
            ?salary * 0.35,
            ?salary * 0.25)) AS ?net_salary
    )
}
ORDER BY DESC(?salary)

Example 19: Sub-Queries (SELECT inside SELECT)

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>

SELECT ?name ?salary WHERE {
    # Outer query: people with above-average salary
    ?person foaf:name ?name ;
            ex:salary ?salary .

    # Sub-query: compute the average
    {
        SELECT (AVG(?s) AS ?avg_salary) WHERE {
            ?p ex:salary ?s .
        }
    }

    FILTER (?salary > ?avg_salary)
}
ORDER BY DESC(?salary)

Sub-queries are enclosed in { SELECT … } and their result variables are in scope in the outer query.


Example 20: VALUES Clause

PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>

SELECT ?person ?name WHERE {
    VALUES ?person {
        ex:alice
        ex:bob
        ex:carol
    }
    ?person foaf:name ?name .
}

The VALUES clause provides an inline set of bindings — equivalent to a SQL IN clause or a parameterized query. Also useful for injecting IDs from application code:

SELECT ?name ?salary WHERE {
    VALUES (?person ?min_salary) {
        (ex:alice 80000)
        (ex:bob   85000)
    }
    ?person foaf:name ?name ;
            ex:salary ?salary .
    FILTER (?salary >= ?min_salary)
}

Example 21: GeoSPARQL Distance Query

IndentiaDB supports GeoSPARQL for spatial queries over RDF geometry data.

PREFIX geo:   <http://www.opengis.net/ont/geosparql#>
PREFIX geof:  <http://www.opengis.net/def/function/geosparql/>
PREFIX ex:    <http://example.org/>
PREFIX xsd:   <http://www.w3.org/2001/XMLSchema#>
PREFIX uom:   <http://www.opengis.net/def/uom/OGC/1.0/>

# Insert location data (WKT point geometry)
INSERT DATA {
    ex:amsterdam ex:name "Amsterdam" ;
                 geo:hasGeometry [
                     geo:asWKT "POINT(4.9041 52.3676)"^^geo:wktLiteral
                 ] .
    ex:berlin ex:name "Berlin" ;
              geo:hasGeometry [
                  geo:asWKT "POINT(13.4050 52.5200)"^^geo:wktLiteral
              ] .
    ex:paris ex:name "Paris" ;
             geo:hasGeometry [
                 geo:asWKT "POINT(2.3522 48.8566)"^^geo:wktLiteral
             ] .
}

# Query: find cities within 700 km of Amsterdam
SELECT ?city ?name ?distance WHERE {
    ex:amsterdam geo:hasGeometry ?ref_geom .
    ?ref_geom geo:asWKT ?ref_wkt .

    ?city ex:name ?name ;
          geo:hasGeometry ?geom .
    ?geom geo:asWKT ?city_wkt .

    BIND(geof:distance(?ref_wkt, ?city_wkt, uom:metre) AS ?distance_m)
    BIND(?distance_m / 1000 AS ?distance)

    FILTER (?city != ex:amsterdam)
    FILTER (?distance < 700)
}
ORDER BY ?distance

RDF 1.2 Features

Triple Terms (Quoted Triples)

SPARQL 1.2 introduces triple terms as first-class values. A quoted triple << s p o >> can appear as the subject or object of another triple, or be bound to a variable.

# A quoted triple as subject
INSERT DATA {
    << ex:alice foaf:knows ex:bob >> ex:confidence 0.9 .
}

# A quoted triple as object
SELECT ?annotation WHERE {
    ex:evidence1 ex:supports ?annotation .
    FILTER (isTRIPLE(?annotation))
}

Reified Triples with ~ Operator

The ~ operator creates a reified triple:

INSERT DATA {
    ex:alice foaf:knows ex:bob ~ ex:confidence 0.9 .
}

This is syntactic sugar equivalent to:

INSERT DATA {
    << ex:alice foaf:knows ex:bob >> ex:confidence 0.9 .
}

Base Direction Tags

SPARQL 1.2 adds support for base direction in language-tagged literals using the --ltr and --rtl suffixes:

# English (left-to-right)
INSERT DATA {
    ex:doc1 rdfs:label "Hello World"@en--ltr .
}

# Arabic (right-to-left)
INSERT DATA {
    ex:doc2 rdfs:label "مرحبا بالعالم"@ar--rtl .
}

# LANGDIR() function returns the base direction
SELECT ?label (LANGDIR(?label) AS ?direction) WHERE {
    ?doc rdfs:label ?label .
    FILTER (LANG(?label) = "ar")
}

rdf:JSON Literals

RDF 1.2 introduces rdf:JSON as a first-class datatype for embedding arbitrary JSON values in RDF literals:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ex:  <http://example.org/>

INSERT DATA {
    ex:sensor42 ex:latestReading
        "{\"temperature\": 21.3, \"unit\": \"celsius\", \"ts\": 1711620000}"^^rdf:JSON .
}

Query and filter on the JSON literal value:

SELECT ?sensor ?reading WHERE {
    ?sensor ex:latestReading ?reading .
    FILTER (DATATYPE(?reading) = rdf:JSON)
}

Elasticsearch indexing

When IndentiaDB auto-maps RDF triples to Elasticsearch indices, fields backed by rdf:JSON literals are indexed as keyword (exact match) rather than text (full-text analyzed). This preserves the raw JSON string for filtering and aggregation without tokenization artifacts.

SEMIJOIN and ANTIJOIN Operators

SPARQL 1.2 formalizes SEMIJOIN and ANTIJOIN as explicit algebraic operators. In query syntax they are expressed via FILTER EXISTS and FILTER NOT EXISTS:

# SEMIJOIN: people who have at least one known contact
SELECT ?name WHERE {
    ?person foaf:name ?name .
    FILTER EXISTS { ?person foaf:knows ?anyone }
}

# ANTIJOIN: people with no known contacts
SELECT ?name WHERE {
    ?person foaf:name ?name .
    FILTER NOT EXISTS { ?person foaf:knows ?anyone }
}

sameValue Three-Valued Comparison (SPARQL 1.2 Issue #187)

The sameValue operator implements three-valued comparison semantics consistent with the RDF data model. Two values are sameValue if and only if they are the same RDF term (or error in both). This differs from =, which raises a type error for incompatible datatypes.


Date, Time and Duration Values

IndentiaDB enables the SPARQL community extension for dates, times and durations (SEP-0002), as Apache Jena and QLever do. Values of type xsd:date, xsd:time, xsd:duration, xsd:yearMonthDuration and xsd:dayTimeDuration are first-class:

  • they compare with =, !=, <, <=, >, >= and sort in ORDER BY (previously comparing two xsd:date values was an evaluation error);
  • YEAR(), MONTH() and DAY() work on xsd:date (previously they were unbound);
  • subtracting two xsd:dateTime, xsd:date or xsd:time values yields an xsd:dayTimeDuration, and durations can be added to or subtracted from them;
  • ADJUST(value, timezone) moves an xsd:dateTime, xsd:date or xsd:time to the timezone given as a duration.
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX ex:  <http://example.org/>

SELECT ?contract ?end (YEAR(?end) AS ?year) WHERE {
    ?contract ex:endDate ?end .
    FILTER (?end >= "2026-01-01"^^xsd:date && ?end < "2027-01-01"^^xsd:date)
}
ORDER BY ?end

A literal with an invalid lexical form (for example "2020-02-30"^^xsd:date) keeps its datatype but stays an opaque value that does not compare as a date.


Parser Strictness

The parser follows the SPARQL 1.2 grammar closely. Queries that older releases accepted by mistake now fail with 400 (syntax_error):

  • Aggregates outside a grouping scope — an aggregate in WHERE (FILTER(COUNT(*) > 0), BIND(COUNT(*) AS ?n)), in a GROUP BY expression, or nested inside another aggregate (SUM(COUNT(*))). Aggregates remain valid in the SELECT projection, HAVING and ORDER BY, and each sub-query has its own aggregate scope.
  • Triple-term expressions — the subject of a <<( s p o )>> expression must be an IRI or a variable; a literal or a nested triple term is rejected there. Triple patterns keep their broader subject grammar.
  • IRI tokens next to comparisons — ?x<?a&&?b>?y is read as containing the IRI token <?a&&?b>, which is invalid, per the longest-token rule. Separate comparison operators with whitespace: ?x < ?a && ?b > ?y.
  • Blank-node labels across graph patterns — one blank-node label cannot be reused in a different basic graph pattern (for example on both sides of OPTIONAL or UNION).
  • SELECT * with implicit grouping — SELECT * … HAVING (COUNT(*) > 0) is a syntax error; project explicit variables instead.

Evaluation Notes

  • Exact terms in triple-term patterns. Matching <<( ?s ?p ?o )>>, rebuilding a triple term from bound variables, and ground triple terms in VALUES keep the exact RDF term — a literal such as "7"^^xsd:nonNegativeInteger is no longer rewritten to xsd:integer, so a triple term rebuilt in an update template still matches the original statement.
  • Language tags are compared case-insensitively: "a"@en and "a"@EN are the same term.
  • Fast counts. SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }, its GRAPH ?g { … } and GRAPH <g> { … } variants, and SELECT ?g (COUNT(*) AS ?n) WHERE { GRAPH ?g { ?s ?p ?o } } GROUP BY ?g (optionally with ORDER BY and LIMIT/OFFSET) are answered exactly from maintained per-graph counters instead of a scan — when all data lives in the live update overlay (no bulk-built index loaded) and no triple-level ACL restricts the caller. Otherwise the regular evaluator runs; results are identical either way.
  • Redundant DISTINCT is removed. SELECT DISTINCT ?k (COUNT(*) AS ?c) … GROUP BY ?k skips the de-duplication step, because the grouping already makes every row unique.
  • Long reads do not block writes. Pattern scans take the data lock per chunk rather than for the whole scan, so a long analytical query no longer stalls concurrent updates. As a consequence a single scan is read-committed rather than a point-in-time snapshot: an update that commits while the scan runs may or may not be visible to it.

SPARQL 1.2 Working Draft Changes (through 27 August 2026)

The following changes from the SPARQL 1.2 Working Draft are implemented:

Issue Change Status
Issue #187 sameValue three-valued comparison operator Implemented
Feb 3 ED GROUP_CONCAT returns xsd:string Implemented
Issue #266 Property path evaluation: BFS with cycle detection Implemented
Issue #267 Property path cardinality semantics fixes Implemented
Issue #290 Extend cardinality fix for OPTIONAL patterns Implemented
RDF 1.2 CR N-Triples Canonical (C14N) serialization — language tag lowercasing, canonical escaping Implemented (2026-04-11)
RDF 1.2 §3.3.1 RDF Reference IRI validation (strict mode) Implemented (2026-04-11)
Grammar §19.7 Aggregates rejected outside grouping scopes Implemented
Grammar §19.3 Longest-token rule for IRIs next to comparison operators Implemented
Grammar Expression triple-term subject restricted to IRI or variable Implemented
WD 2026-08-27 App. A SELECT * with implicit grouping is a syntax error Implemented
W3C algebra Zero-length property paths with fixed endpoints Implemented
SEP-0002 xsd:date / xsd:time / durations: comparison, arithmetic, YEAR/MONTH/DAY on dates, ADJUST Implemented

Error Codes

HTTP Status Meaning Common Causes
200 OK Query succeeded —
400 Bad Request Malformed SPARQL or limit exceeded Syntax error, unresolved prefix, invalid IRI, aggregate in an invalid scope; result_limit_exceeded
401 Unauthorized Authentication required Missing or invalid credentials
403 Forbidden Access denied Caller lacks read permission for the requested graph; SERVICE endpoint refused by the federation policy
406 Not Acceptable Unsupported response format Accept header requests unsupported serialization
408 Request Timeout Query timeout Evaluation exceeded [limits] query_timeout_secs (query_timeout)
413 Payload Too Large Query body or memory budget too large max_body_size exceeded (default 10 MB); per-query memory budget exceeded (memory_limit_exceeded)
429 Too Many Requests Admission queue full Too many concurrent queries
500 Internal Server Error Server-side execution error Storage backend error
501 Not Implemented Feature disabled SERVICE used while federation is disabled
502 Bad Gateway Federated endpoint failed Remote SERVICE endpoint error, unparseable or oversized response
503 Service Unavailable Overloaded or starting up Raft leader election in progress, storage backend offline, memory pressure
504 Gateway Timeout Federated endpoint timed out Remote SERVICE endpoint did not answer in time

Complete curl Examples

# SELECT via GET
curl -G 'http://localhost:7001/sparql' \
  --data-urlencode 'query=SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 10'

# SELECT via POST with JSON response
curl -X POST 'http://localhost:7001/sparql' \
  -H 'Content-Type: application/sparql-query' \
  -H 'Accept: application/sparql-results+json' \
  -d 'PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?name WHERE { ?p a foaf:Person ; foaf:name ?name }'

# CONSTRUCT returning Turtle
curl -X POST 'http://localhost:7001/sparql' \
  -H 'Content-Type: application/sparql-query' \
  -H 'Accept: text/turtle' \
  -d 'CONSTRUCT { ?s ?p ?o } WHERE { ?s a <http://xmlns.com/foaf/0.1/Person> ; ?p ?o }'

# ASK
curl -X POST 'http://localhost:7001/sparql' \
  -H 'Content-Type: application/sparql-query' \
  -d 'ASK { <http://example.org/alice> a <http://xmlns.com/foaf/0.1/Person> }'

# INSERT DATA
curl -X POST 'http://localhost:7001/update' \
  -H 'Content-Type: application/sparql-update' \
  -d 'PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>
INSERT DATA {
    GRAPH <http://example.org/people> {
        ex:alice a foaf:Person ; foaf:name "Alice" ; foaf:age 30 .
    }
}'

# DELETE DATA
curl -X POST 'http://localhost:7001/update' \
  -H 'Content-Type: application/sparql-update' \
  -d 'PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX ex:   <http://example.org/>
DELETE DATA {
    GRAPH <http://example.org/people> {
        ex:alice foaf:age 30 .
    }
}'

# DESCRIBE
curl -G 'http://localhost:7001/sparql' \
  -H 'Accept: text/turtle' \
  --data-urlencode 'query=DESCRIBE <http://example.org/alice>'