Skip to content

refactor: thread parsed SparqlAst through API execution to avoid repeated SPARQL parsing #1283

@bplatz

Description

@bplatz

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions