@@ -11,10 +11,13 @@ use datafusion::{
1111 Limit , Partitioning , Projection , Repartition , Sort , Subquery , TableScan , TableUDFs ,
1212 Union , Values , Window ,
1313 } ,
14- union_with_alias, Column , DFSchema , ExprSchemable , LogicalPlan , LogicalPlanBuilder ,
15- Operator ,
14+ union_with_alias, Column , DFSchema , ExprRewritable , ExprSchemable , LogicalPlan ,
15+ LogicalPlanBuilder , Operator ,
16+ } ,
17+ optimizer:: {
18+ optimizer:: { OptimizerConfig , OptimizerRule } ,
19+ simplify_expressions:: ConstEvaluator ,
1620 } ,
17- optimizer:: optimizer:: { OptimizerConfig , OptimizerRule } ,
1821 scalar:: ScalarValue ,
1922 sql:: planner:: ContextProvider ,
2023} ;
@@ -27,7 +30,9 @@ use crate::compile::{engine::CubeContext, rewrite::rules::utils::DatePartToken};
2730/// Currently this includes replacing:
2831/// - literal granularities in `DatePart` and `DateTrunc` functions
2932/// with their normalized equivalents
30- /// - replacing `DATE - DATE` expressions with `DATEDIFF` equivalent
33+ /// - `DATE - DATE` expressions with `DATEDIFF` equivalent
34+ /// - binary operations between a literal string and an expression
35+ /// of a different type to a string casted to that type
3136pub struct PlanNormalize < ' a > {
3237 cube_ctx : & ' a CubeContext ,
3338}
@@ -1189,8 +1194,10 @@ fn grouping_set_normalize(
11891194}
11901195
11911196/// Recursively normalizes binary expressions.
1192- /// Currently this includes replacing `DATE - DATE` expressions
1193- /// with respective `DATEDIFF` function calls.
1197+ /// Currently this includes replacing:
1198+ /// - `DATE - DATE` expressions with respective `DATEDIFF` function calls
1199+ /// - binary operations between a literal string and an expression
1200+ /// of a different type to a string casted to that type
11941201fn binary_expr_normalize (
11951202 optimizer : & PlanNormalize ,
11961203 left : & Expr ,
@@ -1221,10 +1228,9 @@ fn binary_expr_normalize(
12211228 // can be rewritten to something else, a binary variation still exists and would be picked
12221229 // for SQL push down generation either way. This creates an issue in dialects
12231230 // other than Postgres that would return INTERVAL on `DATE - DATE` expression.
1224- if left. get_type ( schema) ? == DataType :: Date32
1225- && op == Operator :: Minus
1226- && right. get_type ( schema) ? == DataType :: Date32
1227- {
1231+ let left_type = left. get_type ( schema) ?;
1232+ let right_type = right. get_type ( schema) ?;
1233+ if left_type == DataType :: Date32 && op == Operator :: Minus && right_type == DataType :: Date32 {
12281234 let fun = optimizer
12291235 . cube_ctx
12301236 . get_function_meta ( "datediff" )
@@ -1241,5 +1247,84 @@ fn binary_expr_normalize(
12411247 return Ok ( Expr :: ScalarUDF { fun, args } ) ;
12421248 }
12431249
1244- Ok ( Expr :: BinaryExpr { left, op, right } )
1250+ // Check if one side of the binary expression is a literal string. If that's the case,
1251+ // attempt to cast the string to other type based on the operator and type on the other side.
1252+ // If none of the sides is a literal string, the normalization is complete.
1253+ let ( other_type, literal_on_the_left) = match ( left. as_ref ( ) , right. as_ref ( ) ) {
1254+ ( _, Expr :: Literal ( ScalarValue :: Utf8 ( Some ( _) ) ) ) => ( left_type, false ) ,
1255+ ( Expr :: Literal ( ScalarValue :: Utf8 ( Some ( _) ) ) , _) => ( right_type, true ) ,
1256+ _ => return Ok ( Expr :: BinaryExpr { left, op, right } ) ,
1257+ } ;
1258+ let Some ( cast_type) = binary_expr_cast_literal ( & op, & other_type) else {
1259+ return Ok ( Expr :: BinaryExpr { left, op, right } ) ;
1260+ } ;
1261+ if literal_on_the_left {
1262+ let new_left = evaluate_expr ( optimizer, left. cast_to ( & cast_type, schema) ?) ?;
1263+ Ok ( Expr :: BinaryExpr {
1264+ left : Box :: new ( new_left) ,
1265+ op,
1266+ right,
1267+ } )
1268+ } else {
1269+ let new_right = evaluate_expr ( optimizer, right. cast_to ( & cast_type, schema) ?) ?;
1270+ Ok ( Expr :: BinaryExpr {
1271+ left,
1272+ op,
1273+ right : Box :: new ( new_right) ,
1274+ } )
1275+ }
1276+ }
1277+
1278+ /// Returns the type a literal string should be casted to based on the operator
1279+ /// and the type on the other side of the binary expression.
1280+ /// If no casting is needed, returns `None`.
1281+ fn binary_expr_cast_literal ( op : & Operator , other_type : & DataType ) -> Option < DataType > {
1282+ if other_type == & DataType :: Utf8 {
1283+ // If the other side is a string, casting is never required
1284+ return None ;
1285+ }
1286+
1287+ match op {
1288+ // Comparison operators should cast strings to the other side type
1289+ Operator :: Eq
1290+ | Operator :: NotEq
1291+ | Operator :: Lt
1292+ | Operator :: LtEq
1293+ | Operator :: Gt
1294+ | Operator :: GtEq
1295+ | Operator :: IsDistinctFrom
1296+ | Operator :: IsNotDistinctFrom => Some ( other_type. clone ( ) ) ,
1297+ // Arithmetic operators should cast strings to the other side type
1298+ Operator :: Plus
1299+ | Operator :: Minus
1300+ | Operator :: Multiply
1301+ | Operator :: Divide
1302+ | Operator :: Modulo
1303+ | Operator :: Exponentiate => Some ( other_type. clone ( ) ) ,
1304+ // Logical operators operate only on booleans
1305+ Operator :: And | Operator :: Or => Some ( DataType :: Boolean ) ,
1306+ // LIKE and regexes operate only on strings, no casting needed
1307+ Operator :: Like
1308+ | Operator :: NotLike
1309+ | Operator :: ILike
1310+ | Operator :: NotILike
1311+ | Operator :: RegexMatch
1312+ | Operator :: RegexIMatch
1313+ | Operator :: RegexNotMatch
1314+ | Operator :: RegexNotIMatch => None ,
1315+ // Bitwise oprators should cast strings to the other side type
1316+ Operator :: BitwiseAnd
1317+ | Operator :: BitwiseOr
1318+ | Operator :: BitwiseShiftRight
1319+ | Operator :: BitwiseShiftLeft => Some ( other_type. clone ( ) ) ,
1320+ // String concat allows string on either side, no casting needed
1321+ Operator :: StringConcat => None ,
1322+ }
1323+ }
1324+
1325+ /// Evaluates an expression to a constant if possible.
1326+ fn evaluate_expr ( optimizer : & PlanNormalize , expr : Expr ) -> Result < Expr > {
1327+ let execution_props = & optimizer. cube_ctx . state . execution_props ;
1328+ let mut const_evaluator = ConstEvaluator :: new ( execution_props) ;
1329+ expr. rewrite ( & mut const_evaluator)
12451330}
0 commit comments