How to Execute SPARQL Queries over RDF Graphs in Semantica: A Complete Backend Guide

Semantica provides three interchangeable backends—RDF4J, Oxigraph, and Jena—that implement a common execute_sparql API for querying RDF triplets with SPARQL.

The semantica-agi/semantica repository offers a flexible triplet store abstraction that lets you execute SPARQL queries against remote servers, embedded databases, or in-memory session graphs without changing your application code. All backends return results in a standardized JSON structure containing bindings, variables, triples, or metadata depending on the query type.

Choosing Your SPARQL Backend

Semantica's store backend registry (semantica/vector_store/registry.py) automatically instantiates the appropriate class based on your configuration. Here is how each backend compares:

  • RDF4JStore — HTTP client for Eclipse RDF4J repositories; ideal for production deployments with existing RDF4J infrastructure.
  • OxigraphStore — Embedded PyOxigraph store running in-process; best for local development or serverless deployments requiring zero external dependencies.
  • JenaStore — Connector to Apache Jena/Fuseki endpoints with optional rdflib.Dataset fallback; suitable for organizations standardized on Jena.

Changing backends requires only a configuration update. The same execute_sparql(query: str, **options) signature works identically across all three.

The Common execute_sparql API

Each backend implements execute_sparql in semantica/triplet_store/[backend]_store.py. The method follows a five-step pipeline:

  1. Query type detection — A regex in sparql_escaping.CONSTRUCT_QUERY_RE determines whether the result should use "construct" or "bindings" format (RDF4JStore._is_construct_query).

  2. Result format validation — Only "construct" or "bindings" are permitted; invalid values raise ValidationError (OxigraphStore.execute_sparql).

  3. Query execution — RDF4J uses requests.post with negotiated Accept headers; Oxigraph runs native queries against the embedded store; Jena delegates to rdflib.Dataset or SPARQLUpdateStore.

  4. Response parsing — CONSTRUCT results become Turtle-parsed graphs or QueryTriples iterators; SELECT/ASK results become variable binding lists.

  5. Progress tracking — All calls are wrapped by ProgressTracker.start_tracking / stop_tracking for observability and error handling.

The standardized return shape is:

{
  "success": true,
  "bindings": [],
  "variables": [],
  "triples": [],
  "metadata": {"query": "...", "endpoint": "...", "result_format": "..."}
}

Executing SPARQL with RDF4JStore

The RDF4J backend (semantica/triplet_store/rdf4j_store.py) connects to external repositories over HTTP. It automatically sets Accept: application/sparql-results+json for SELECT queries and Accept: text/turtle for CONSTRUCT queries.

from semantica.triplet_store import RDF4JStore

store = RDF4JStore(
    endpoint="http://localhost:8080/rdf4j-server",
    repository_id="myrepo"
)

query = """
SELECT ?s ?p ?o
WHERE {
  ?s ?p ?o .
  FILTER(?s = <http://example.org/subject>)
}
"""

result = store.execute_sparql(query)
print(result["bindings"])

The execute_sparql method auto-detects result_format="bindings" for this SELECT query. CONSTRUCT queries return serialized triples in result["triples"] after Turtle parsing (source).

Executing SPARQL with OxigraphStore

The Oxigraph backend (semantica/triplet_store/oxigraph_store.py) runs an embedded store without network dependencies. It supports both in-memory and on-disk persistence.

from semantica.triplet_store import OxigraphStore

store = OxigraphStore()  # in-memory default

# Populate with data...

store.add_triplets([...])

construct_q = """
CONSTRUCT { ?s <http://example.org/hasLabel> ?label }
WHERE {
  ?s <http://example.org/name> ?label .
}
"""

result = store.execute_sparql(construct_q, result_format="construct")
print(result["triples"])  # [(s, p, o, metadata), ...]

Oxigraph handles QuerySolutions and QueryTriples iterators natively, normalizing them to the shared result format (source).

Executing SPARQL with JenaStore

The Jena backend (semantica/triplet_store/jena_store.py) supports remote Fuseki endpoints and named graphs.

from semantica.triplet_store import JenaStore
from semantica.semantic_extract.triplet_extractor import Triplet

store = JenaStore(endpoint="http://localhost:3030/ds")

triplets = [
    Triplet(
        subject="http://example.org/Alice",
        predicate="http://example.org/knows",
        object="http://example.org/Bob"
    )
]

store.add_triplets(triplets, graph="http://example.org/graph1")

For queries, JenaStore uses either a remote SPARQLUpdateStore or an in-memory rdflib.Dataset, then normalizes results to the standard shape (source).

Using the Read-Only SPARQL HTTP Endpoint

Semantica's Explorer API exposes a safe, read-only SPARQL endpoint at /api/sparql (semantica/explorer/routes/sparql.py). This route:

  • Validates queries with _is_read_only_query to block INSERT, DELETE, and other update operations (source).
  • Materializes the current GraphSession into an in-memory rdflib.Graph capped at 50,000 nodes/edges.
  • Truncates results to _SPARQL_MAX_ROWS = 5,000 to prevent response bloat.
curl -X POST http://localhost:8000/api/sparql \
     -H "Content-Type: application/json" \
     -d '{"query":"SELECT ?s WHERE { ?s a <http://semantica.local/entity/Person> } LIMIT 10"}'

Response format:

{
  "columns": ["s"],
  "rows": [{"s": "http://semantica.local/entity/JohnDoe"}],
  "total": 1,
  "truncated": false,
  "error": null
}

This endpoint isideal for debugging and ad-hoc exploration without affecting persistent stores (route source).

Configuration-Driven Backend Selection

Switch backends via YAML configuration without code changes:

store_backend: "rdf4j"
rdf4j:
  endpoint: "http://localhost:8080/rdf4j-server"
  repository_id: "myrepo"

Valid store_backend values are "rdf4j", "oxigraph", or "jena". The registry instantiates the corresponding class from semantica/triplet_store/[backend]_store.py while preserving the identical execute_sparql interface.

Helper Utilities for SPARQL Query Handling

The sparql_escaping module (semantica/triplet_store/sparql_escaping.py) provides shared utilities:

  • CONSTRUCT_QUERY_RE — Regex for detecting CONSTRUCT/DESCRIBE queries.
  • URI validation and literal escaping for safe query construction.

These helpers ensure consistent behavior across all three backends when parsing and validating SPARQL syntax.

Summary

  • Three interchangeable backends (RDF4J, Oxigraph, Jena) implement the same execute_sparql API in semantica/triplet_store/.
  • Standardized result format returns bindings, variables, triples, and metadata regardless of backend.
  • Auto-detection of query type via sparql_escaping.CONSTRUCT_QUERY_RE sets appropriate result handling.
  • Read-only HTTP endpoint at /api/sparql enables safe session exploration without backend configuration.
  • Configuration-driven selection lets you swap remote, embedded, or in-memory stores without code changes.

Frequently Asked Questions

How does Semantica determine whether a SPARQL query returns bindings or triples?

The _is_construct_query method in RDF4JStore uses a regex defined as sparql_escaping.CONSTRUCT_QUERY_RE to detect CONSTRUCT and DESCRIBE queries. When matched, the default result_format becomes "construct"; otherwise "bindings" is used for SELECT and ASK queries (source).

Can I execute SPARQL UPDATE queries through Semantica's backends?

Direct UPDATE support varies by backend. RDF4JStore and JenaStore can perform updates through their respective HTTP protocols, but the /api/sparql Explorer endpoint explicitly blocks all update operations via _is_read_only_query validation. For production updates, use the backend-specific methods rather than the read-only HTTP route (sanitization source).

What is the maximum result size for the Explorer SPARQL endpoint?

The endpoint truncates results to _SPARQL_MAX_ROWS = 5,000 rows and materializes in-memory graphs with a cap of 50,000 nodes/edges. These limits prevent memory exhaustion when querying large session graphs through the lightweight HTTP interface (graph building source).

Does OxigraphStore require an external server process?

No. OxigraphStore runs PyOxigraph embedded in the Python process with no external dependencies. It operates in-memory by default and can optionally persist to disk, making it suitable for serverless deployments and local development where managing separate database processes is undesirable (implementation).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →