<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Pruning on Opensource internals</title><link>https://articles.9j.nz/tags/pruning/</link><description>Posts filed under Pruning on Opensource internals.</description><generator>Hugo</generator><language>en-US</language><managingEditor>Jay Hubbert</managingEditor><lastBuildDate>Wed, 21 Oct 2026 04:37:00 +0000</lastBuildDate><atom:link href="https://articles.9j.nz/tags/pruning/index.xml" rel="self" type="application/rss+xml"/><item><title>Interval Arithmetic in DataFusion: Proving That a Predicate Can Only Match Certain Values</title><link>https://articles.9j.nz/posts/how-datafusion-proves-a-predicate-can-only-match-certain-values/</link><pubDate>Wed, 21 Oct 2026 04:37:00 +0000</pubDate><dc:creator>Jay Hubbert</dc:creator><guid isPermaLink="true">https://articles.9j.nz/posts/how-datafusion-proves-a-predicate-can-only-match-certain-values/</guid><category>datafusion</category><category>pruning</category><category>intervals</category><category>performance</category><category>internals</category><description>DataFusion builds a DAG of expression intervals and propagates constraints both ways, so a filter on x*x + y = 4 can prove that x is at least sqrt(3).</description><content:encoded>&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; A filter like &lt;code&gt;x * x + y = 4&lt;/code&gt; tells you something about &lt;code&gt;x&lt;/code&gt; and&#10;&lt;code&gt;y&lt;/code&gt; even if you only know their rough ranges. DataFusion builds a graph&#10;of the filter&amp;rsquo;s expression, computes each node&amp;rsquo;s &lt;em&gt;value interval&lt;/em&gt;&#10;bottom-up, then propagates the filter&amp;rsquo;s equality constraint back down&#10;through &lt;em&gt;inverse&lt;/em&gt; arithmetic (&lt;code&gt;y = 4 - x*x&lt;/code&gt; means &lt;code&gt;y ∈ [0, 1]&lt;/code&gt; once &lt;code&gt;x&lt;/code&gt;&#10;is known), tightening the input intervals. Those tightened intervals are&#10;what let a file scan skip data it can prove cannot match.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;The pruning articles in this series are about &lt;em&gt;statistics&lt;/em&gt;: a Parquet&#10;row group knows the min and max of its columns, and a predicate is&#10;evaluated against those to decide whether the row group can be skipped.&lt;/p&gt;&#10;&lt;p&gt;Statistics give you the range of a &lt;em&gt;column&lt;/em&gt;. Interval arithmetic gives you&#10;the range of an &lt;em&gt;expression&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;Consider a filter on a computed column:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;sql&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;A Parquet file has statistics for &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; separately. It does not have&#10;statistics for &lt;code&gt;a * a + b&lt;/code&gt;. But if the file says &lt;code&gt;a ∈ [0, 10]&lt;/code&gt; and&#10;&lt;code&gt;b ∈ [0, 10]&lt;/code&gt;, then &lt;code&gt;a*a + b ∈ [0, 110]&lt;/code&gt;, and the filter &lt;code&gt;&amp;gt; 100&lt;/code&gt; might&#10;match. But if &lt;code&gt;a ∈ [0, 3]&lt;/code&gt;, then &lt;code&gt;a*a + b ∈ [0, 19]&lt;/code&gt;, and the filter&#10;&lt;strong&gt;cannot&lt;/strong&gt; match — so the file can be skipped entirely.&lt;/p&gt;&#10;&lt;p&gt;DataFusion can do better than that, and the machinery is&#10;&lt;code&gt;datafusion/physical-expr/src/intervals/cp_solver.rs&lt;/code&gt; (1,959 lines) plus&#10;&lt;code&gt;datafusion/expr-common/src/interval_arithmetic.rs&lt;/code&gt; (5,362 lines).&lt;/p&gt;&#10;&lt;h2 id="interval-arithmetic"&gt;Interval arithmetic&lt;/h2&gt;&#10;&lt;p&gt;An &lt;code&gt;Interval&lt;/code&gt; is a range of possible values:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;rust&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Interval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;: &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ScalarValue&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;: &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ScalarValue&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;with &lt;code&gt;None&lt;/code&gt; meaning unbounded on that side. The arithmetic is&#10;&lt;em&gt;interval arithmetic&lt;/em&gt; in the classical sense: the sum of two intervals is&#10;the sum of their bounds, the product of two intervals is the min and max&#10;of the four products, and so on.&lt;/p&gt;&#10;&lt;p&gt;The crucial property is that interval arithmetic &lt;em&gt;over-approximates&lt;/em&gt;: the&#10;computed interval always contains every value the expression can actually&#10;take. That is what makes it safe to use for pruning — you might fail to&#10;prune, but you will never prune a row that should have been kept.&lt;/p&gt;&#10;&lt;p&gt;The references in the module doc are the classics:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Kabak, Mehmet Ozan. Analog Circuit Start-Up Behavior Analysis: An&#10;Interval Arithmetic Based Approach, Chapter 4. Stanford University, 2015.&lt;/li&gt;&#10;&lt;li&gt;Moore, Ramon E. Interval analysis. Vol. 4. Englewood Cliffs:&#10;Prentice-Hall, 1966.&lt;/li&gt;&#10;&lt;li&gt;F. Messine, &amp;ldquo;Deterministic global optimization using interval&#10;constraint propagation techniques,&amp;rdquo; RAIRO-Operations Research, 2004.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;The first reference is telling: &lt;em&gt;analog circuit start-up analysis&lt;/em&gt;. The&#10;same mathematics that bounds a circuit&amp;rsquo;s operating range bounds a&#10;query&amp;rsquo;s filter selectivity. That is a genuinely delightful piece of&#10;computer science: interval arithmetic from circuit design, applied to&#10;query planning.&lt;/p&gt;&#10;&lt;h2 id="the-expression-graph"&gt;The expression graph&lt;/h2&gt;&#10;&lt;p&gt;The solver does not work on a tree. It works on a &lt;strong&gt;directed acyclic&#10;expression graph&lt;/strong&gt; (DAEG):&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;rust&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;/// This object implements a directed acyclic expression graph (DAEG) that&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;/// is used to compute ranges for expressions through interval arithmetic.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#[derive(Clone, Debug)]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;ExprIntervalGraph&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;: &lt;span class="nc"&gt;StableGraph&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ExprIntervalGraphNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;: &lt;span class="nc"&gt;NodeIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;A graph, not a tree, because a column can appear many times in an&#10;expression (&lt;code&gt;a + a&lt;/code&gt;, or &lt;code&gt;a * b - a * b&lt;/code&gt;) and you want to compute its&#10;interval &lt;em&gt;once&lt;/em&gt; and share it.&lt;/p&gt;&#10;&lt;p&gt;Each node carries the expression and its current interval:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;rust&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;ExprIntervalGraphNode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;: &lt;span class="nc"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PhysicalExpr&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;: &lt;span class="nc"&gt;Interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;and the graph is built with &lt;code&gt;petgraph&lt;/code&gt; — a general-purpose graph library&#10;— using a &lt;code&gt;StableGraph&lt;/code&gt; so that node indices stay valid as the graph is&#10;mutated.&lt;/p&gt;&#10;&lt;p&gt;Literals start as singleton intervals; everything else starts unbounded:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;rust&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;/// Constructs a new DAEG node from DataFusion&amp;#39;s [`ExprTreeNode`]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;/// object. Literals are created with definite, singleton intervals while&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;/// any other expression starts with an indefinite interval (`[-∞, ∞]`).&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;The &lt;code&gt;[-∞, ∞]&lt;/code&gt; start is the honest one: before analysis, you know nothing&#10;about &lt;code&gt;a&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="bottom-up-compute-the-ranges"&gt;Bottom-up: compute the ranges&lt;/h2&gt;&#10;&lt;p&gt;The first pass computes each node&amp;rsquo;s interval from its children&amp;rsquo;s:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;text&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-----+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +----| + |----+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | +-----+ |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-----+ +-----+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | 2 | | y |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |[.] | | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-----+ +-----+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +---+ +---+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | x | [1, 2] | x | [1, 2]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +---+ +---+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;code&gt;x ∈ [1, 2]&lt;/code&gt;, so &lt;code&gt;x * x ∈ [1, 4]&lt;/code&gt;. &lt;code&gt;y ∈ [-3, 1]&lt;/code&gt;. Then &lt;code&gt;x*x + y ∈ [1-3, 4+1] = [-2, 5]&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;This is a &lt;em&gt;breadth-first&lt;/em&gt; traversal of the graph (the code imports&#10;&lt;code&gt;petgraph::visit::Bfs&lt;/code&gt;), computing each node once its children are done.&lt;/p&gt;&#10;&lt;h2 id="top-down-propagate-the-constraint"&gt;Top-down: propagate the constraint&lt;/h2&gt;&#10;&lt;p&gt;The interesting part is the second pass. The filter says the expression&#10;&lt;em&gt;equals&lt;/em&gt; 4. That is a constraint on the root, and it can be pushed &lt;em&gt;back&#10;down&lt;/em&gt; through inverse arithmetic:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;text&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [-2, 5] ∩ [4, 4] = [4, 4]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-----+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +----| + |----+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | +-----+ |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-----+ +-----+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | 2 | [1, 4] | y | [0, 1]*&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |[.] | | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +-----+ +-----+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | [-3, 1]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +---+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | x | [1, 2]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; +---+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;with the arithmetic spelled out in the doc:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;[-3, 1] ∩ ([4, 4] - [1, 4]) = [0, 1]&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;[1, 4] ∩ ([4, 4] - [0, 1]) = [3, 4]&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;[1, 2] ∩ [sqrt(3), sqrt(4)] = [sqrt(3), 2]&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Read that as a chain:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;The root&amp;rsquo;s interval &lt;code&gt;[-2, 5]&lt;/code&gt; is intersected with the constraint&#10;&lt;code&gt;[4, 4]&lt;/code&gt; → the root is exactly 4.&lt;/li&gt;&#10;&lt;li&gt;Since &lt;code&gt;root = x*x + y = 4&lt;/code&gt;, we get &lt;code&gt;y = 4 - x*x&lt;/code&gt;. With &lt;code&gt;x*x ∈ [1, 4]&lt;/code&gt;,&#10;&lt;code&gt;y ∈ [4-4, 4-1] = [0, 3]&lt;/code&gt;, intersected with the original &lt;code&gt;y ∈ [-3, 1]&lt;/code&gt;&#10;→ &lt;code&gt;y ∈ [0, 1]&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Now &lt;code&gt;x*x = 4 - y ∈ [4-1, 4-0] = [3, 4]&lt;/code&gt;, intersected with &lt;code&gt;[1, 4]&lt;/code&gt; →&#10;&lt;code&gt;x*x ∈ [3, 4]&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;x ∈ [1, 2]&lt;/code&gt; and &lt;code&gt;x*x ∈ [3, 4]&lt;/code&gt; → &lt;code&gt;x ∈ [sqrt(3), 2]&lt;/code&gt;.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;So the solver has proved that &lt;strong&gt;any row satisfying &lt;code&gt;x*x + y = 4&lt;/code&gt; must have&#10;&lt;code&gt;x ∈ [sqrt(3), 2]&lt;/code&gt;&lt;/strong&gt; — a fact that is not visible anywhere in the original&#10;expression, and which a file-level statistics check on &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; alone&#10;could never derive.&lt;/p&gt;&#10;&lt;p&gt;That is the payoff. The tightened intervals are what the pruning&#10;machinery then uses to skip files and row groups.&lt;/p&gt;&#10;&lt;h2 id="the-inverse-operators"&gt;The inverse operators&lt;/h2&gt;&#10;&lt;p&gt;Step 2 needs the &lt;em&gt;inverse&lt;/em&gt; of each operator: given &lt;code&gt;x + y = 4&lt;/code&gt; and&#10;&lt;code&gt;x ∈ [1, 4]&lt;/code&gt;, what is &lt;code&gt;y&lt;/code&gt;? Answer: &lt;code&gt;y ∈ [4-4, 4-1] = [0, 3]&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;The code has a helper for exactly this:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;rust&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;::&lt;span class="n"&gt;utils&lt;/span&gt;::&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;convert_duration_type_to_interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;convert_interval_type_to_duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get_inverse_op&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;code&gt;get_inverse_op&lt;/code&gt; maps an operator to its inverse — &lt;code&gt;+&lt;/code&gt; becomes &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;&#10;becomes &lt;code&gt;/&lt;/code&gt;, and so on. For the operators where no inverse exists (&lt;code&gt;%&lt;/code&gt;,&#10;bitwise ops), the propagation simply stops, and the interval stays as&#10;computed bottom-up. A partial answer is still useful.&lt;/p&gt;&#10;&lt;p&gt;The result type makes the three outcomes explicit:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;rust&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;/// This object encapsulates all possible constraint propagation results.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#[derive(PartialEq, Debug)]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;PropagationResult&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CannotPropagate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Infeasible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;&lt;code&gt;Infeasible&lt;/code&gt; is the interesting one: it means the constraint &lt;em&gt;contradicts&lt;/em&gt;&#10;itself — the filter is &lt;code&gt;x &amp;gt; 5 AND x &amp;lt; 3&lt;/code&gt;, or &lt;code&gt;x*x + y = 4&lt;/code&gt; with &lt;code&gt;y ∈ [100, 200]&lt;/code&gt;. When the solver reaches that state, the whole expression is&#10;provably empty, and the filter can be replaced with &lt;code&gt;false&lt;/code&gt; and the entire&#10;subtree dropped from the plan.&lt;/p&gt;&#10;&lt;h2 id="the-catch"&gt;The catch&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Interval arithmetic over-approximates, and it over-approximates&#10;badly.&lt;/strong&gt; &lt;code&gt;x * x&lt;/code&gt; with &lt;code&gt;x ∈ [-10, 10]&lt;/code&gt; gives &lt;code&gt;[-100, 100]&lt;/code&gt;, not&#10;&lt;code&gt;[0, 100]&lt;/code&gt; — because the interval arithmetic does not know that &lt;code&gt;x&lt;/code&gt;&#10;appears twice. The &lt;em&gt;top-down&lt;/em&gt; pass recovers some of that (it knows the&#10;two occurrences are the same variable), but the bottom-up pass does&#10;not. This is the classic weakness of interval arithmetic, and it is why&#10;the constraint propagation step exists.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;The propagation is not always possible.&lt;/strong&gt; Some operators have no&#10;invertible counterpart, and some expressions have no useful structure.&#10;&lt;code&gt;CannotPropagate&lt;/code&gt; is the honest answer.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;The result is only as good as the input statistics.&lt;/strong&gt; If the file&amp;rsquo;s&#10;statistics for &lt;code&gt;a&lt;/code&gt; are wrong, the derived intervals are wrong and the&#10;pruning is wrong. (Which is why the &lt;em&gt;pruning&lt;/em&gt; machinery, not the&#10;interval solver, is where the correctness guarantees live.)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;The graph can be large.&lt;/strong&gt; A wide expression tree becomes a graph with&#10;one node per distinct subexpression, and the propagation is a fixed-point&#10;iteration over it. For a very large expression this is not free — and&#10;it is not something you want to do for every predicate in every query.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="why-it-matters"&gt;Why it matters&lt;/h2&gt;&#10;&lt;p&gt;Interval arithmetic is one of those techniques that sounds academic and&#10;turns out to be quietly load-bearing. It is the difference between &amp;ldquo;I&#10;know the range of column &lt;code&gt;a&lt;/code&gt;&amp;rdquo; and &amp;ldquo;I know the range of &lt;code&gt;a * a + b&lt;/code&gt;&amp;rdquo;, and&#10;the second is a much stronger statement — strong enough, in the right&#10;data layout, to skip a whole Parquet file.&lt;/p&gt;&#10;&lt;p&gt;And the top-down propagation is the part that makes it more than an&#10;arithmetic curiosity: it turns a &lt;em&gt;filter&lt;/em&gt; into a &lt;em&gt;constraint system&lt;/em&gt;, and&#10;solves it in both directions. The &lt;code&gt;x ∈ [sqrt(3), 2]&lt;/code&gt; result is not a&#10;property of the data — it is a property of the &lt;em&gt;query&lt;/em&gt;, deduced from the&#10;shape of the expression and the semantics of the operators.&lt;/p&gt;&#10;&lt;p&gt;Which is, in the end, the oldest trick in the book: read the question&#10;carefully and the answer often falls out.&lt;/p&gt;&#10;</content:encoded></item></channel></rss>