Summary
SPARQL queries are parsed multiple times along a single request. The query route parses once for content negotiation, then API execution parses again (once to recover the dataset spec for formatting, and the connection/dataset execution path parses/lowers again). Parsing is cheap relative to execution, so this is not a correctness or hot-path concern — but it is avoidable repeated work and an asymmetry worth recording.
Where it shows up
In fluree-db-server/src/routes/query.rs, the connection-scoped SPARQL JSON path parses for negotiation:
let parsed = fluree_db_sparql::parse_sparql(&sparql);
let (fmt_config, content_type) =
sparql_json_response_format(parsed.ast.as_ref(), &headers);
match state.fluree.query_from().sparql(&sparql).format(fmt_config).execute_formatted().await {
FromQueryBuilder::execute_formatted() then executes via the SPARQL connection path and parses again afterward to recover the dataset spec for formatting. The connection path itself parses to build the DatasetSpec, then calls dataset execution, which parses/lowers again.
This is not unique to the connection-scoped handler — the same parse-for-routing-then-parse-in-builder shape exists across the ~12 call sites that call parse_sparql (for is_graph_query / dataset-clause checks) and then hand the raw string to .sparql(&str).
Why this is a refactor, not a one-liner
The clean fix is not simply adding .sparql_parsed() at one call site. It should thread a parsed/validated SparqlAst plus the original source text through API execution. The source text still matters because lowering uses it for SERVICE body capture, so the AST alone is insufficient.
Proposed approach
- A helper that lowers an existing
SparqlAst to IR, preserving the original &str for source-sensitive lowering.
- Parsed-SPARQL execution paths for connection/dataset/view builders, without adding a new public
QueryInput variant unless we're comfortable with that API surface.
- Reuse the parsed AST for dataset-spec extraction and graph-query / content-negotiation decisions.
- Tests around CONSTRUCT/DESCRIBE content type plus a SPARQL
SERVICE query if there's existing coverage.
Non-goals
- A connection-handler-only patch. It would remove one visible parse but leave the deeper repeated parsing in place and make the code asymmetrical.
Context
Surfaced as a non-blocking review note on #1277 (SPARQL CONSTRUCT/DESCRIBE content negotiation). That PR is intentionally left as-is to avoid adding review surface to already-approved work.
Summary
SPARQL queries are parsed multiple times along a single request. The query route parses once for content negotiation, then API execution parses again (once to recover the dataset spec for formatting, and the connection/dataset execution path parses/lowers again). Parsing is cheap relative to execution, so this is not a correctness or hot-path concern — but it is avoidable repeated work and an asymmetry worth recording.
Where it shows up
In
fluree-db-server/src/routes/query.rs, the connection-scoped SPARQL JSON path parses for negotiation:FromQueryBuilder::execute_formatted()then executes via the SPARQL connection path and parses again afterward to recover the dataset spec for formatting. The connection path itself parses to build theDatasetSpec, then calls dataset execution, which parses/lowers again.This is not unique to the connection-scoped handler — the same parse-for-routing-then-parse-in-builder shape exists across the ~12 call sites that call
parse_sparql(foris_graph_query/ dataset-clause checks) and then hand the raw string to.sparql(&str).Why this is a refactor, not a one-liner
The clean fix is not simply adding
.sparql_parsed()at one call site. It should thread a parsed/validatedSparqlAstplus the original source text through API execution. The source text still matters because lowering uses it forSERVICEbody capture, so the AST alone is insufficient.Proposed approach
SparqlAstto IR, preserving the original&strfor source-sensitive lowering.QueryInputvariant unless we're comfortable with that API surface.SERVICEquery if there's existing coverage.Non-goals
Context
Surfaced as a non-blocking review note on #1277 (SPARQL CONSTRUCT/DESCRIBE content negotiation). That PR is intentionally left as-is to avoid adding review surface to already-approved work.