Guard against non-explainable statements in the Query Plan panel - #385
Open
dpage wants to merge 3 commits into
Open
Guard against non-explainable statements in the Query Plan panel#385dpage wants to merge 3 commits into
dpage wants to merge 3 commits into
Conversation
pg_stat_statements records utility statements alongside SELECT and DML, so a Top Queries row can legitimately hold text such as a bare VACUUM from a scheduled maintenance job. useQueryPlan built its request by concatenating EXPLAIN with that captured text, and since PostgreSQL's EXPLAIN accepts only SELECT, INSERT, UPDATE, DELETE, MERGE, VALUES, EXECUTE, DECLARE, CREATE TABLE AS and CREATE MATERIALIZED VIEW, the server rejected the result as invalid SQL and the panel surfaced the raw syntax error verbatim. Decide up front whether EXPLAIN can accept the statement, and if it cannot, set an explanatory message without issuing the request at all. The check is an allowlist of the leading keywords EXPLAIN supports rather than a denylist of utility commands, so it fails safe: anything unrecognised yields the friendly message instead of a raw server error, and there is no list of every utility command to keep up to date as PostgreSQL grows new ones. Leading whitespace, comments and parentheses are stripped before the first keyword is inspected, because captured text is often prefixed with a framework tag comment. The Query Plan panel already renders an error with no accompanying plan as an informational alert, so the message reads as "not available" rather than as a failure, alongside the existing friendly handling of parameterised queries. Closes #368
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (1)
Walkthrough
ChangesQuery plan explainability
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant useQueryPlan
participant classifyExplainSupport
participant PostgreSQL
useQueryPlan->>classifyExplainSupport: classify SQL query
alt planless or unsupported
classifyExplainSupport-->>useQueryPlan: return classification
useQueryPlan->>useQueryPlan: clear plans and set error
else plan
classifyExplainSupport-->>useQueryPlan: return plan
useQueryPlan->>PostgreSQL: execute text and JSON EXPLAIN
PostgreSQL-->>useQueryPlan: return plan responses
end
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Record the #368 fix under Unreleased/Fixed, describing what users now see in place of the raw database syntax error.
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 30 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
PostgreSQL's ExplainableStmt covers two forms the first version of the guard turned away. A bare TABLE foo is a SELECT in disguise and plans exactly like one, so suppressing it lost the user a plan they could have had. REFRESH MATERIALIZED VIEW is accepted by EXPLAIN too, although it runs as a utility command, so the server answers with its internal "Utility statements have no plan structure" notice rather than a plan, and in FORMAT JSON it answers with the bare string array [ "Utility Statement" ]. Both behaviours were confirmed against PostgreSQL 18. Classify statements three ways rather than two: EXPLAIN returns a plan, EXPLAIN is accepted but returns no plan, or EXPLAIN rejects the statement outright. TABLE joins the leading keywords that yield a plan, whilst the planless case gets a message of its own explaining that PostgreSQL accepts the statement but produces no plan for it. Deciding this from the statement text keeps the property the fix rests on, that no request is issued which cannot usefully succeed, and avoids inferring statically known facts by pattern-matching server prose that is not a stable contract across versions. Shape-check the FORMAT JSON response as well, so that a planless reply reaching the parser reports no plan structure instead of assigning an array of bare strings into the plan state, where the truthy value would have sent the panel to its plan tabs and asked the tree renderer to draw a string as a plan node. Closes #368
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
useQueryPlanbuilt its request by concatenatingEXPLAINwith thecaptured statement text, so a Top Queries row holding a utility
statement (a bare
VACUUMfrom a scheduled maintenance job, astandalone
ANALYZE,REINDEX, and so on) produced invalid SQL andthe panel surfaced PostgreSQL's raw
syntax error at or near "VACUUM"verbatim.
EXPLAINcan accept thestatement and, when it cannot, sets an explanatory message without
issuing the request at all. The check is an allowlist of the leading
keywords
EXPLAINsupports (SELECT,INSERT,UPDATE,DELETE,MERGE,VALUES,EXECUTE,DECLARE,WITH,CREATE TABLE ... ASand
CREATE MATERIALIZED VIEW) rather than a denylist of utilitycommands, so it fails safe: anything unrecognised yields the friendly
message instead of a raw server error, and there is no catalogue of
utility commands to maintain as PostgreSQL grows new ones.
--and/* */comments, and opening parenthesesare stripped before the first keyword is inspected, since captured
pg_stat_statementstext is often prefixed with a framework tagcomment.
CREATE TABLE ... AS SELECTstays explainable whilst a plainCREATE TABLEwith aGENERATED ALWAYS AScolumn does not.No component changes were needed:
QueryPlanPanelalready renders anerror with no accompanying plan as an
infoalert, so the message readsas "not available" rather than as a failure, sitting alongside the
existing friendly handling of parameterised queries.
Test plan
npx vitest run src/hooks/__tests__/useQueryPlan.test.ts— 79passed (11 pre-existing plus 68 new). The new cases cover the helper
directly across case, whitespace, comment, parenthesis, CTAS and
materialised-view forms plus the utility statements from the issue,
and cover the hook asserting that a non-explainable statement produces
the message and that the API mock is never called.
npx vitest run --coverageon the modified hook — 97.91% lines(94/96), 97.82% branches; the two uncovered lines are pre-existing and
every new line is covered, comfortably over the project's 90% floor.
npm run lint— 0 errors, 40 warnings, all pre-existingnon-null-assertion warnings across the test tree (confirmed by blame).
npm run buildsucceeds.Closes #368
Summary by CodeRabbit
#368).