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.
Summary
explain_queryreturns the raw EXPLAIN JSON value directly. The built-in driver wraps it inExplainQueryOutput::Raw { raw: RawExplainOutput { engine, format, payload, original_query } }. The host's plugin adapter inspects the response forengine/format/payloadfields; since the plugin omits them, the host classifies the result asExplainQueryOutput::Plan { plan: res }(the parsed-plan path) instead ofRaw(the raw-payload path the builtin uses).Builtin behavior (upstream
main)src-tauri/src/drivers/postgres/explain.rs:payloadis the JSON serialized to a string (so the frontend can hand it to a runtime-registered EXPLAIN parser), andengine/format/original_querycarry 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:How the host interprets it
tabularissrc-tauri/src/plugins/driver.rs::explain_querychecks the plugin response forengine/format/payload/original_queryfields. The plugin's raw JSON array has none of those, so it falls through to:— the parsed-plan path, not the
Rawpath. Consequences:engine("postgres"),format("postgres-json"), andoriginal_querymetadata are absent — runtime-registered EXPLAIN parsers (matched onengine+format) won't select this plugin's output.payloadis 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
Planpath), 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.explain_queryparity tests assert the result is non-empty, not theRawvsPlanvariant).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 asRaw. (Notepayloadmust be the JSON string, not the object — the adapter readsobject.get("payload")?.as_str().) Add a parity test asserting the response shape carriesengine/format/payload/original_query.Related
TabularisDB/tabularisdrivers/postgres/explain.rs+plugins/driver.rs::explain_query(theRawfield check).TabularisDB/tabularis#276("Explain Plan does not work for Postgres") may share rendering context.