Skip to content

5.14 sparql — a SPARQL endpoint as a keyed source

A sparql producer joins any endpoint speaking the SPARQL 1.1 Protocol — a public knowledge graph (Wikidata, UniProt, EU Cellar) or an organization’s own triplestore. The realm authors the SELECT query ONCE; consumers only ever see the virtual join, and no query author writes SPARQL.

- name: orgByRegistryId
kind: sparql
endpoint: "https://query.example.org/sparql"
query: |
SELECT ?registryId ?org ?orgLabel ?parentLabel WHERE {
VALUES ?registryId { {{keys}} }
?org <http://example.org/prop/registryId> ?registryId .
OPTIONAL { ?org <http://example.org/prop/parent> ?parent . }
}
keyVar: registryId
keyForm: literal # literal (default) | iri | number
fetch:
maxKeysPerCall: 100 # keys per request; larger batches are split
timeoutSeconds: 60
maxRows: 10000
userAgent: "MyRealm/1.0 (contact url)" # public endpoints often require one
bearerTokenEnv: MY_TRIPLESTORE_TOKEN # env var; never a credential in YAML
cache: { kind: ttl, seconds: 86400 }

The contract:

  • One batch, one query. The whole key batch is serialized into the query at the {{keys}} token — the natural spot is a VALUES clause — so a key set is one request (chunked only by fetch.maxKeysPerCall). A query without the token is rejected at fetch time with a loud error, never an empty result.
  • Keys are serialized safely, never spliced raw. keyForm: literal emits escaped quoted strings; iri emits <key> and rejects keys that cannot be an IRI; number admits only numerics. A key the form cannot represent is SKIPPED and reported in warnings — it can never inject query text, and it can never silently read as “no such record”.
  • Rows are the SELECT bindings. Each solution becomes one record, result variable → value. Common XSD numerics and booleans arrive as numbers/booleans; IRIs arrive as their full string; a variable absent from a solution (an unmatched OPTIONAL) is absent from the record.
  • keyVar is the identity echo. The declared variable must be selected and must carry the anchor key back on every row — that is what links a row to its anchor. A keyVar the query does not select is reported as a warning (the join would otherwise silently never form).
  • Honesty. A failed fetch (network, HTTP error, unparseable response) is a warnings entry plus an empty result — never a silent 0. Exceeding fetch.maxRows truncates and reports a TRUNCATED warning. A retryable refusal — a throttle, a gateway failure, a timeout — is attempted once more, honouring the endpoint’s Retry-After where it sends one: the same law every producer kind gets (§9, what a flaky source costs), not a sparql speciality.
  • Credentials stay out of YAML. An authenticated endpoint names an environment variable (fetch.bearerTokenEnv); public endpoints need nothing.

Cost every consumer should know: each key batch is one live query against an endpoint the realm does not control. Public endpoints enforce their own timeouts and rate limits; a realm should bound its batches, declare a TTL cache, and expect the occasional refused query at peak — which surfaces as a reported fetch failure, not as missing data.