Skip to content

Commit 1b69820

Browse files
zzylolclaude
andcommitted
fix(sql): fold numeric casts of literals as DataFusion does
Type coercion turns a literal compared with a column of another type into a cast: `latency < 100` on a DOUBLE column became `latency < CAST(100 AS DOUBLE)`, which summary coverage cannot read as a constant. Fold a cast of a literal to an integer or float type with DataFusion's `ScalarValue::cast_to`, the evaluation its constant folding uses; the predicate lowers as `latency < 100.0`. A cast that fails to evaluate is kept. DataFusion's whole SimplifyExpressions rule is not run: it also folds timestamp casts into timestamp literals, which the IR cannot write yet, and rewrites REGEXP_LIKE into the `~` operator (#650). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
1 parent 32e4dad commit 1b69820

2 files changed

Lines changed: 37 additions & 0 deletions

File tree

‎crates/frontend-sql/src/unified/sql/expr.rs‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,25 @@ impl SqlLowerer<'_> {
146146

147147
Expr::IsNotNull(inner) => Ok(Unresolved::IsNotNull(bx(inner)?)),
148148

149+
// Fold a numeric cast of a literal, as DataFusion's constant folding
150+
// does: type coercion turns `latency < 100` on a DOUBLE column into
151+
// `latency < CAST(100 AS DOUBLE)`, which lowers as `latency < 100.0`.
152+
Expr::Cast(c)
153+
if matches!(c.expr.as_ref(), Expr::Literal(..))
154+
&& (c.field.data_type().is_integer() || c.field.data_type().is_floating()) =>
155+
{
156+
let Expr::Literal(sv, _) = c.expr.as_ref() else {
157+
unreachable!()
158+
};
159+
match sv.cast_to(c.field.data_type()) {
160+
Ok(folded) => scalar_value_to_asap(&folded).map(Unresolved::Literal),
161+
Err(_) => Ok(Unresolved::Cast {
162+
expr: bx(&c.expr)?,
163+
to: arrow_to_dtype(c.field.data_type())?,
164+
try_cast: false,
165+
}),
166+
}
167+
}
149168
Expr::Cast(c) => Ok(Unresolved::Cast {
150169
expr: bx(&c.expr)?,
151170
to: arrow_to_dtype(c.field.data_type())?,

‎crates/frontend-sql/tests/unified_sql_lowering.rs‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,6 +2217,24 @@ async fn now_in_predicate_lowers_to_current_timestamp() {
22172217
);
22182218
}
22192219

2220+
/// The cast that type coercion puts on `100` against a `DOUBLE` column is
2221+
/// folded into the literal, as DataFusion's constant folding does.
2222+
#[tokio::test]
2223+
async fn coerced_literal_is_folded() {
2224+
let qe = lower("SELECT * FROM metrics WHERE latency < 100").await;
2225+
let NonASAPOp::Project { child, .. } = op(&qe) else {
2226+
panic!("expected Project at root, got {qe:?}");
2227+
};
2228+
let NonASAPOp::Scan { predicates, .. } = op(child) else {
2229+
panic!("expected Scan under the projection, got {child:?}");
2230+
};
2231+
assert!(
2232+
matches!(&predicates[..], [Predicate(ScalarExpr::Compare { right, .. })]
2233+
if matches!(right.as_ref(), ScalarExpr::Literal(ScalarValue::Float64(v)) if *v == 100.0)),
2234+
"{predicates:?}"
2235+
);
2236+
}
2237+
22202238
/// Same for ClickHouse's `now()`, since #184 was raised specifically against
22212239
/// the ClickHouse dialect.
22222240
#[tokio::test]

0 commit comments

Comments
 (0)