internal/parser/planparserv2/rewriter/README.md
This module performs rule-based logical rewrites on parsed planpb.Expr trees right after template value filling and before planning/execution.
RewriteExpr(*planpb.Expr) *planpb.Expr (in entry.go)
paramtable.Get().CommonCfg.EnabledOptimizeExprRewriteExprWithConfig(*planpb.Expr, bool) *planpb.Expr (in entry.go)
RewriteExpr but allows custom configuration for testing or special cases.The rewriter can be configured via the following parameter (refreshable at runtime):
| Parameter | Default | Description |
|---|---|---|
common.enabledOptimizeExpr | true | Enable query expression optimization including range simplification, IN/NOT IN merge, TEXT_MATCH merge, and all other optimizations |
IMPORTANT: IN/NOT IN value list sorting and deduplication always runs regardless of this configuration setting, because the execution engine depends on sorted value lists.
term_in.go)a == v1 OR a == v2 ... → a IN (v1, v2, ...)a != v1 AND a != v2 ... → NOT (a IN (v1, v2, ...))(a ∈ S) AND (a = v):
v ∈ S → a = vv ∉ S → contradiction → constant false(a ∈ S) OR (a = v) → a ∈ (S ∪ {v}) (always union)(a ∈ S1) OR (a ∈ S2) → a ∈ (S1 ∪ S2) with sorting/dedup(a ∈ S1) AND (a ∈ S2) → a ∈ (S1 ∩ S2); empty intersection → constant falseIN / NOT IN value lists (supported types: bool, int64, float64, string).text_match.go)TEXT_MATCH(field, "literal") on the same column (no options):
TEXT_MATCH(f, "A C") OR TEXT_MATCH(f, "B D") → TEXT_MATCH(f, "A C B D")TEXT_MATCH in the group has options (e.g., minimum_should_match), this optimization is skipped for that group.range.go)a > 10 AND a > 20 → a > 20 (pick strongest lower)a < 50 AND a < 60 → a < 50 (pick strongest upper)a > 10 AND a < 50 → 10 < a < 50 (BinaryRangeExpr)a > 10 OR a > 20 → a > 10 (pick weakest lower)a < 10 OR a < 20 → a < 20 (pick weakest upper)a ≥ x AND a > x → a > x; a ≤ y AND a < y → a < ya ≥ x OR a > x → a ≥ x; a ≤ y OR a < y → a ≤ ya > 10 AND a ≥ 10 → a > 10; a < 5 OR a ≤ 5 → a ≤ 5(a ∈ {…}) AND (range) → keep only values in the set that satisfy the range
{1,3,5} AND a > 3 → {5}ArrayInt[0]), the element type above appliesJSONField["price"], $meta["age"]) are range-optimized
json["a"] > 10 and json["a"] > "hello" remain separate)Int64Field > 10)FloatField > 10 or > 10.5)ColumnInfo (including nested path and element index). For example, ArrayInt[0] and ArrayInt[1] are different columns and are not merged with each other.BinaryRangeExpr nodes on the same column to compute intersection (max lower, min upper)
(10 < x < 50) AND (20 < x < 40) → (20 < x < 40)falseBinaryRangeExpr
(10 < x < 50) AND (x > 30) → (30 < x < 50)BinaryRangeExpr nodes into wider interval
(10 < x < 25) OR (20 < x < 40) → (10 < x < 40) (overlapping)(10 < x <= 20) OR (20 <= x < 30) → (10 < x < 30) (adjacent with inclusive)(10 < x < 20) OR (30 < x < 40) → remains as ORColumnInfo, including nested path/element type).util.go (defaultConvertOrToInNumericLimit, default 150).== → IN!= short-circuiting!= filtering!= → NOT INEach construction of IN will be normalized (sorted and deduplicated). TEXT_MATCH OR merge concatenates literals with a single space; no tokenization, deduplication, or sorting is performed.
entry.go — rewrite entry and visitor orchestrationutil.go — shared helpers (column keying, value classification, sorting/dedup, constructors)term_in.go — IN/NOT IN normalization and conversionstext_match.go — TEXT_MATCH OR merge (no options)range.go — range tightening/weakening and interval constructionIN vs exact equality propagation across subtrees).(a == 1) AND (a == 2) → false; (a > 10) AND (a == 5) → false(a > 10) OR (a <= 10) → true (for non-NULL values)(a > 10) OR ((a > 10) AND (b > 20)) → a > 10(10 < x < 20) OR (15 < x < 25) OR (22 < x < 30) → (10 < x < 30).(x > 10) OR (5 < x < 15) → x > 5.