This ticket is one of the findings my Claude/ChatGPT review found; the text below is written by Claude. If you'd prefer reports as PRs I'd be happy to resubmit. -- Demitri
Summary
The two selectivity-estimation callbacks assume the radius argument folds to a Const and cast to it unconditionally, and they never release the variable-statistics they acquire. This is planner-only (bad row estimates, a leaked stats-cache reference, and a possible bad read on an exotic query) — no data corruption. Found alongside #50.
Details
In both pgq3c_sel (q3c.c:127) and pgq3c_seljoin (q3c.c:173):
other = estimate_expression_value(root, vardata.var);
radDatum = ((Const *) other)->constvalue; /* q3c.c:129 / :175 */
isnull = ((Const *) other)->constisnull;
estimate_expression_value returns a Const only when the expression can be folded; a non-foldable argument (e.g. an unresolved Param, or a non-constant radius) comes back as some other node type. Reading constvalue/constisnull off that node reinterprets unrelated fields — a garbage selectivity estimate, and potentially an invalid read.
Separately, examine_variable(...) acquires statistics into vardata, but neither function calls ReleaseVariableStats(&vardata) before returning, leaking the reference.
Suggested fix
Guard the cast with IsA(other, Const) and fall back to a sensible default selectivity otherwise; and always ReleaseVariableStats(&vardata) before returning (including on the early/isnull paths).
This ticket is one of the findings my Claude/ChatGPT review found; the text below is written by Claude. If you'd prefer reports as PRs I'd be happy to resubmit. -- Demitri
Summary
The two selectivity-estimation callbacks assume the radius argument folds to a
Constand cast to it unconditionally, and they never release the variable-statistics they acquire. This is planner-only (bad row estimates, a leaked stats-cache reference, and a possible bad read on an exotic query) — no data corruption. Found alongside #50.Details
In both
pgq3c_sel(q3c.c:127) andpgq3c_seljoin(q3c.c:173):estimate_expression_valuereturns aConstonly when the expression can be folded; a non-foldable argument (e.g. an unresolvedParam, or a non-constant radius) comes back as some other node type. Readingconstvalue/constisnulloff that node reinterprets unrelated fields — a garbage selectivity estimate, and potentially an invalid read.Separately,
examine_variable(...)acquires statistics intovardata, but neither function callsReleaseVariableStats(&vardata)before returning, leaking the reference.Suggested fix
Guard the cast with
IsA(other, Const)and fall back to a sensible default selectivity otherwise; and alwaysReleaseVariableStats(&vardata)before returning (including on the early/isnullpaths).