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¶
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 }istrue, andSELECT ?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_CONCATnow returnsxsd: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:
This is syntactic sugar equivalent to:
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 inORDER BY(previously comparing twoxsd:datevalues was an evaluation error); YEAR(),MONTH()andDAY()work onxsd:date(previously they were unbound);- subtracting two
xsd:dateTime,xsd:dateorxsd:timevalues yields anxsd:dayTimeDuration, and durations can be added to or subtracted from them; ADJUST(value, timezone)moves anxsd:dateTime,xsd:dateorxsd:timeto 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 aGROUP BYexpression, or nested inside another aggregate (SUM(COUNT(*))). Aggregates remain valid in theSELECTprojection,HAVINGandORDER 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>?yis 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
OPTIONALorUNION). 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 inVALUESkeep the exact RDF term — a literal such as"7"^^xsd:nonNegativeIntegeris no longer rewritten toxsd:integer, so a triple term rebuilt in an update template still matches the original statement. - Language tags are compared case-insensitively:
"a"@enand"a"@ENare the same term. - Fast counts.
SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }, itsGRAPH ?g { … }andGRAPH <g> { … }variants, andSELECT ?g (COUNT(*) AS ?n) WHERE { GRAPH ?g { ?s ?p ?o } } GROUP BY ?g(optionally withORDER BYandLIMIT/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
DISTINCTis removed.SELECT DISTINCT ?k (COUNT(*) AS ?c) … GROUP BY ?kskips 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>'