WhereEvaluator::evaluate returns true for anything it cannot evaluate — "default to including the row" (src/catalog/where_evaluator.rs:53-56). Under NOT, !true becomes false (:151), so an unevaluable predicate silently excludes every row.
This was the root cause of #87 for pg_class, which no longer uses this code path. The ~20 remaining catalog handlers still do.
Demonstrated with an arbitrary unknown function:
| Query |
Result |
WHERE foobar(c.relname) |
all rows |
WHERE NOT foobar(c.relname) |
0 rows |
Suggested fix: make evaluation tri-state (Option<bool>, None = cannot evaluate). NOT None stays None; None AND x is x; the top level treats None as include. Keep the current evaluate() -> bool as a thin evaluate_opt().unwrap_or(true) wrapper so the 22 call sites are untouched.
A related gap: joined columns do not resolve at all. n.nspname = 'public' returns 0 rows and n.nspname <> 'public' returns everything — both wrong, because the handler synthesizes only its own table's columns and the join is never materialized.
WhereEvaluator::evaluatereturnstruefor anything it cannot evaluate — "default to including the row" (src/catalog/where_evaluator.rs:53-56). UnderNOT,!truebecomesfalse(:151), so an unevaluable predicate silently excludes every row.This was the root cause of #87 for
pg_class, which no longer uses this code path. The ~20 remaining catalog handlers still do.Demonstrated with an arbitrary unknown function:
WHERE foobar(c.relname)WHERE NOT foobar(c.relname)Suggested fix: make evaluation tri-state (
Option<bool>,None= cannot evaluate).NOT NonestaysNone;None AND xisx; the top level treatsNoneas include. Keep the currentevaluate() -> boolas a thinevaluate_opt().unwrap_or(true)wrapper so the 22 call sites are untouched.A related gap: joined columns do not resolve at all.
n.nspname = 'public'returns 0 rows andn.nspname <> 'public'returns everything — both wrong, because the handler synthesizes only its own table's columns and the join is never materialized.