@@ -21,10 +21,11 @@ pub use guarantee::{Guarantee, LiteralGuarantee};
2121use std:: borrow:: Borrow ;
2222use std:: sync:: Arc ;
2323
24- use crate :: PhysicalExpr ;
25- use crate :: PhysicalSortExpr ;
26- use crate :: expressions:: { BinaryExpr , Column } ;
24+ use crate :: expressions:: { BinaryExpr , Column , Literal } ;
2725use crate :: tree_node:: ExprContext ;
26+ use crate :: {
27+ AcrossPartitions , ConstExpr , EquivalenceProperties , PhysicalExpr , PhysicalSortExpr ,
28+ } ;
2829
2930use arrow:: datatypes:: Schema ;
3031use datafusion_common:: tree_node:: {
@@ -45,6 +46,66 @@ pub fn split_conjunction(
4546 split_impl ( Operator :: And , predicate, vec ! [ ] )
4647}
4748
49+ impl ConstExpr {
50+ /// Collects predicate-derived constants from equality conjunctions.
51+ ///
52+ /// For each equality predicate of the form `lhs = rhs`, if either side is
53+ /// already known constant according to `input_eqs`, or is a literal, then
54+ /// the other side is also constant and will be returned as a [`ConstExpr`].
55+ ///
56+ /// Literals are treated as uniform constants across partitions, so
57+ /// `col = literal` produces a constant for `col` with the literal value.
58+ ///
59+ /// For example, given predicate `a = 5 AND b = c` where `c` is already
60+ /// known constant, this returns constants for both `a` (Uniform with value
61+ /// 5) and `b` (propagating `c`'s across-partitions value).
62+ pub fn collect_predicate_constants (
63+ input_eqs : & EquivalenceProperties ,
64+ predicate : & Arc < dyn PhysicalExpr > ,
65+ ) -> Vec < ConstExpr > {
66+ /// Returns the `AcrossPartitions` value for `expr` if it is constant:
67+ /// either already known constant in `input_eqs`, or a `Literal`
68+ /// (which is inherently constant across all partitions).
69+ fn expr_constant_or_literal (
70+ expr : & Arc < dyn PhysicalExpr > ,
71+ input_eqs : & EquivalenceProperties ,
72+ ) -> Option < AcrossPartitions > {
73+ input_eqs. is_expr_constant ( expr) . or_else ( || {
74+ expr. as_any ( )
75+ . downcast_ref :: < Literal > ( )
76+ . map ( |l| AcrossPartitions :: Uniform ( Some ( l. value ( ) . clone ( ) ) ) )
77+ } )
78+ }
79+
80+ let mut constants = Vec :: new ( ) ;
81+ for conjunction in split_conjunction ( predicate) {
82+ if let Some ( binary) = conjunction. as_any ( ) . downcast_ref :: < BinaryExpr > ( )
83+ && binary. op ( ) == & Operator :: Eq
84+ {
85+ // Check if either side is constant — either already known
86+ // constant from the input equivalence properties, or a literal
87+ // value (which is inherently constant across all partitions).
88+ let left_const = expr_constant_or_literal ( binary. left ( ) , input_eqs) ;
89+ let right_const = expr_constant_or_literal ( binary. right ( ) , input_eqs) ;
90+
91+ if let Some ( left_across) = left_const {
92+ // LEFT is constant, so RIGHT must also be constant.
93+ // Use RIGHT's known across value if available, otherwise
94+ // propagate LEFT's (e.g. Uniform from a literal).
95+ let across = right_const. unwrap_or ( left_across) ;
96+ constants. push ( ConstExpr :: new ( Arc :: clone ( binary. right ( ) ) , across) ) ;
97+ } else if let Some ( right_across) = right_const {
98+ // RIGHT is constant, so LEFT must also be constant.
99+ constants
100+ . push ( ConstExpr :: new ( Arc :: clone ( binary. left ( ) ) , right_across) ) ;
101+ }
102+ }
103+ }
104+
105+ constants
106+ }
107+ }
108+
48109/// Create a conjunction of the given predicates.
49110/// If the input is empty, return a literal true.
50111/// If the input contains a single predicate, return the predicate.
@@ -562,4 +623,31 @@ pub(crate) mod tests {
562623 assert_eq ! ( collect_columns( & expr3) , expected) ;
563624 Ok ( ( ) )
564625 }
626+
627+ #[ test]
628+ fn test_collect_predicate_constants_propagates_uniform_literal_value ( ) -> Result < ( ) > {
629+ let schema = Arc :: new ( Schema :: new ( vec ! [ Field :: new(
630+ "ticker" ,
631+ DataType :: Utf8 ,
632+ false ,
633+ ) ] ) ) ;
634+ let predicate = binary (
635+ col ( "ticker" , schema. as_ref ( ) ) ?,
636+ Operator :: Eq ,
637+ lit ( ScalarValue :: Utf8 ( Some ( "NGJ26" . to_string ( ) ) ) ) ,
638+ schema. as_ref ( ) ,
639+ ) ?;
640+ let eq_properties = EquivalenceProperties :: new ( schema) ;
641+
642+ let constants =
643+ ConstExpr :: collect_predicate_constants ( & eq_properties, & predicate) ;
644+
645+ assert_eq ! ( constants. len( ) , 1 ) ;
646+ assert_eq ! (
647+ constants[ 0 ] . across_partitions,
648+ AcrossPartitions :: Uniform ( Some ( ScalarValue :: Utf8 ( Some ( "NGJ26" . to_string( ) ) ) ) )
649+ ) ;
650+
651+ Ok ( ( ) )
652+ }
565653}
0 commit comments