Skip to content

explain_query returns raw JSON instead of the Raw { engine, format, payload, original_query } wrapper the builtin emits #89

Description

@aesslinger

Summary

explain_query returns the raw EXPLAIN JSON value directly. The built-in driver wraps it in ExplainQueryOutput::Raw { raw: RawExplainOutput { engine, format, payload, original_query } }. The host's plugin adapter inspects the response for engine/format/payload fields; since the plugin omits them, the host classifies the result as ExplainQueryOutput::Plan { plan: res } (the parsed-plan path) instead of Raw (the raw-payload path the builtin uses).

Builtin behavior (upstream main)

src-tauri/src/drivers/postgres/explain.rs:

Ok(ExplainQueryOutput::Raw {
    raw: RawExplainOutput {
        engine: "postgres".to_string(),
        format: "postgres-json".to_string(),
        payload: plan_json_str,   // value.to_string() — stringified JSON
        original_query: query.to_string(),
    },
})

payload is the JSON serialized to a string (so the frontend can hand it to a runtime-registered EXPLAIN parser), and engine/format/original_query carry the metadata the parser needs.

Plugin behavior (this repo)

src/handlers/query.rs:116 — returns the raw EXPLAIN JSON value (plan_json.clone()) with an acknowledging comment:

// The host wraps this in ExplainQueryOutput::Plan { plan: res }
// We just return the raw explain JSON from the first row/col
if let Some(plan_json) = first_row.first() {
    return ok_response(id, plan_json.clone());
}

How the host interprets it

tabularis src-tauri/src/plugins/driver.rs::explain_query checks the plugin response for engine/format/payload/original_query fields. The plugin's raw JSON array has none of those, so it falls through to:

Ok(ExplainQueryOutput::Plan { plan: res })

— the parsed-plan path, not the Raw path. Consequences:

  • The result is rendered through the parsed-tree renderer instead of the raw-payload path.
  • engine ("postgres"), format ("postgres-json"), and original_query metadata are absent — runtime-registered EXPLAIN parsers (matched on engine+format) won't select this plugin's output.
  • payload is a live JSON object here, whereas the builtin's contract is a stringified JSON string; a parser expecting a string would receive an object.

Impact

  • Severity: Medium. EXPLAIN still renders (via the Plan path), but with the wrong variant and missing metadata. Visual Explain rendering and any registered postgres-json parser selection behave differently from the builtin. The plugin's own comment shows this was a known shortcut.
  • Not caught by the parity suite (the 2 explain_query parity tests assert the result is non-empty, not the Raw vs Plan variant).

Fix

Wrap the EXPLAIN JSON as the builtin does: return json!({ "engine": "postgres", "format": "postgres-json", "payload": plan_json.to_string(), "original_query": query }) so the host adapter matches it as Raw. (Note payload must be the JSON string, not the object — the adapter reads object.get("payload")?.as_str().) Add a parity test asserting the response shape carries engine/format/payload/original_query.

Related

  • Builtin reference: TabularisDB/tabularis drivers/postgres/explain.rs + plugins/driver.rs::explain_query (the Raw field check).
  • Related upstream: TabularisDB/tabularis#276 ("Explain Plan does not work for Postgres") may share rendering context.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingcapability-gapA builtin-only feature this plugin doesn't yet support

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions