1use std::sync::Arc;
16
17use common_time::Timezone;
18use common_time::timestamp::{TimeUnit, Timestamp};
19use datafusion::config::ConfigOptions;
20use datafusion_common::tree_node::{Transformed, TreeNode, TreeNodeRewriter};
21use datafusion_common::{DFSchemaRef, DataFusionError, Result, ScalarValue};
22use datafusion_expr::expr::InList;
23use datafusion_expr::{
24 Between, BinaryExpr, Expr, ExprSchemable, Filter, LogicalPlan, Operator, TableScan, WriteOp,
25};
26use datatypes::arrow::compute;
27use datatypes::arrow::datatypes::DataType;
28use session::context::QueryContextRef;
29
30use crate::QueryEngineContext;
31use crate::optimizer::ExtensionAnalyzerRule;
32use crate::optimizer::insert_assignment::rewrite_insert_assignments;
33use crate::plan::ExtractExpr;
34
35pub struct TypeConversionRule;
41
42impl ExtensionAnalyzerRule for TypeConversionRule {
43 fn analyze(
44 &self,
45 plan: LogicalPlan,
46 ctx: &QueryEngineContext,
47 config: &ConfigOptions,
48 ) -> Result<LogicalPlan> {
49 plan.transform_up_with_subqueries(|plan| match plan {
50 LogicalPlan::Filter(filter) => {
51 let mut converter =
52 TypeConverter::new(filter.input.schema().clone(), ctx.query_ctx());
53 let rewritten = filter.predicate.clone().rewrite(&mut converter)?.data;
54 Ok(Transformed::yes(LogicalPlan::Filter(Filter::try_new(
55 rewritten,
56 filter.input,
57 )?)))
58 }
59 LogicalPlan::TableScan(TableScan {
60 table_name,
61 source,
62 projection,
63 projected_schema,
64 filters,
65 fetch,
66 }) => {
67 let mut converter = TypeConverter::new(projected_schema.clone(), ctx.query_ctx());
68 let rewrite_filters = filters
69 .into_iter()
70 .map(|e| e.rewrite(&mut converter).map(|x| x.data))
71 .collect::<Result<Vec<_>>>()?;
72 Ok(Transformed::yes(LogicalPlan::TableScan(TableScan {
73 table_name: table_name.clone(),
74 source: source.clone(),
75 projection,
76 projected_schema,
77 filters: rewrite_filters,
78 fetch,
79 })))
80 }
81 LogicalPlan::Projection { .. } => {
82 let mut converter = TypeConverter::new(plan.schema().clone(), ctx.query_ctx());
83 let inputs = plan.inputs().into_iter().cloned().collect::<Vec<_>>();
84 let expr = plan
85 .expressions_consider_join()
86 .into_iter()
87 .map(|e| e.rewrite(&mut converter).map(|x| x.data))
88 .collect::<Result<Vec<_>>>()?;
89
90 plan.with_new_exprs(expr, inputs).map(Transformed::yes)
91 }
92 LogicalPlan::Window { .. }
93 | LogicalPlan::Aggregate { .. }
94 | LogicalPlan::Repartition { .. }
95 | LogicalPlan::Extension { .. }
96 | LogicalPlan::Sort { .. }
97 | LogicalPlan::Union { .. }
98 | LogicalPlan::Values { .. }
99 | LogicalPlan::Analyze { .. } => {
100 let mut converter = TypeConverter::new(plan.schema().clone(), ctx.query_ctx());
101 let inputs = plan.inputs().into_iter().cloned().collect::<Vec<_>>();
102 let expr = plan
103 .expressions_consider_join()
104 .into_iter()
105 .map(|e| e.rewrite(&mut converter).map(|x| x.data))
106 .collect::<Result<Vec<_>>>()?;
107
108 plan.with_new_exprs(expr, inputs).map(Transformed::yes)
109 }
110
111 LogicalPlan::Join(join) => {
112 let Ok(schema) = join.left.schema().join(join.right.schema()) else {
113 return Ok(Transformed::no(LogicalPlan::Join(join)));
114 };
115 let mut converter = TypeConverter::new(Arc::new(schema), ctx.query_ctx());
116 let plan = LogicalPlan::Join(join);
117 let inputs = plan.inputs().into_iter().cloned().collect::<Vec<_>>();
118 let expr = plan
119 .expressions_consider_join()
120 .into_iter()
121 .map(|e| e.rewrite(&mut converter).map(|x| x.data))
122 .collect::<Result<Vec<_>>>()?;
123
124 plan.with_new_exprs(expr, inputs).map(Transformed::yes)
125 }
126
127 LogicalPlan::Dml(mut dml) if matches!(dml.op, WriteOp::Insert(_)) => {
128 dml.input = Arc::new(rewrite_insert_assignments(
129 dml.input.as_ref().clone(),
130 &ctx.query_ctx(),
131 config,
132 )?);
133 Ok(Transformed::yes(LogicalPlan::Dml(dml)))
134 }
135
136 LogicalPlan::Distinct { .. }
137 | LogicalPlan::Limit { .. }
138 | LogicalPlan::Subquery { .. }
139 | LogicalPlan::Explain { .. }
140 | LogicalPlan::SubqueryAlias { .. }
141 | LogicalPlan::EmptyRelation(_)
142 | LogicalPlan::Dml(_)
143 | LogicalPlan::DescribeTable(_)
144 | LogicalPlan::Unnest(_)
145 | LogicalPlan::Statement(_)
146 | LogicalPlan::Ddl(_)
147 | LogicalPlan::Copy(_)
148 | LogicalPlan::RecursiveQuery(_) => Ok(Transformed::no(plan)),
149 })
150 .map(|x| x.data)
151 }
152}
153
154struct TypeConverter {
155 query_ctx: QueryContextRef,
156 schema: DFSchemaRef,
157}
158
159impl TypeConverter {
160 fn new(schema: DFSchemaRef, query_ctx: QueryContextRef) -> Self {
161 Self { query_ctx, schema }
162 }
163
164 fn column_type(&self, expr: &Expr) -> Option<DataType> {
165 if let Expr::Column(_) = expr
166 && let Ok(v) = expr.get_type(&self.schema)
167 {
168 return Some(v);
169 }
170 None
171 }
172
173 fn cast_scalar_value(
174 &self,
175 value: &ScalarValue,
176 target_type: &DataType,
177 ) -> Result<ScalarValue> {
178 match (target_type, value) {
179 (DataType::Timestamp(_, _), ScalarValue::Utf8(Some(v))) => {
180 parse_string_to_timestamp(v, Some(&self.query_ctx.timezone()))
181 }
182 (DataType::Boolean, ScalarValue::Utf8(Some(v))) => match v.to_lowercase().as_str() {
183 "true" => Ok(ScalarValue::Boolean(Some(true))),
184 "false" => Ok(ScalarValue::Boolean(Some(false))),
185 _ => Ok(ScalarValue::Boolean(None)),
186 },
187 (target_type, value) => {
188 let value_arr = value.to_array()?;
189 let arr = compute::cast(&value_arr, target_type)
190 .map_err(|e| DataFusionError::ArrowError(Box::new(e), None))?;
191
192 ScalarValue::try_from_array(
193 &arr,
194 0, )
196 }
197 }
198 }
199
200 fn convert_type<'b>(&self, left: &'b Expr, right: &'b Expr) -> Result<(Expr, Expr)> {
201 let left_type = self.column_type(left);
202 let right_type = self.column_type(right);
203
204 let target_type = match (&left_type, &right_type) {
205 (Some(v), None) => v,
206 (None, Some(v)) => v,
207 _ => return Ok((left.clone(), right.clone())),
208 };
209
210 if !matches!(target_type, DataType::Timestamp(_, _) | DataType::Boolean) {
212 return Ok((left.clone(), right.clone()));
213 }
214
215 match (left, right) {
216 (Expr::Column(col), Expr::Literal(value, _)) => {
217 let casted_right = self.cast_scalar_value(value, target_type)?;
218 if casted_right.is_null() {
219 return Err(DataFusionError::Plan(format!(
220 "column:{col:?}. Casting value:{value:?} to {target_type:?} is invalid",
221 )));
222 }
223 Ok((left.clone(), Expr::Literal(casted_right, None)))
224 }
225 (Expr::Literal(value, _), Expr::Column(col)) => {
226 let casted_left = self.cast_scalar_value(value, target_type)?;
227 if casted_left.is_null() {
228 return Err(DataFusionError::Plan(format!(
229 "column:{col:?}. Casting value:{value:?} to {target_type:?} is invalid",
230 )));
231 }
232 Ok((Expr::Literal(casted_left, None), right.clone()))
233 }
234 _ => Ok((left.clone(), right.clone())),
235 }
236 }
237}
238
239impl TreeNodeRewriter for TypeConverter {
240 type Node = Expr;
241
242 fn f_up(&mut self, expr: Expr) -> Result<Transformed<Expr>> {
243 let new_expr = match expr {
244 Expr::BinaryExpr(BinaryExpr { left, op, right }) => match op {
245 Operator::Eq
246 | Operator::NotEq
247 | Operator::Lt
248 | Operator::LtEq
249 | Operator::Gt
250 | Operator::GtEq => {
251 let (left, right) = self.convert_type(&left, &right)?;
252 Expr::BinaryExpr(BinaryExpr {
253 left: Box::new(left),
254 op,
255 right: Box::new(right),
256 })
257 }
258 _ => Expr::BinaryExpr(BinaryExpr { left, op, right }),
259 },
260 Expr::Between(Between {
261 expr,
262 negated,
263 low,
264 high,
265 }) => {
266 let (expr, low) = self.convert_type(&expr, &low)?;
267 let (expr, high) = self.convert_type(&expr, &high)?;
268 Expr::Between(Between {
269 expr: Box::new(expr),
270 negated,
271 low: Box::new(low),
272 high: Box::new(high),
273 })
274 }
275 Expr::InList(InList {
276 expr,
277 list,
278 negated,
279 }) => {
280 let mut list_expr = Vec::with_capacity(list.len());
281 for e in list {
282 let (_, expr_conversion) = self.convert_type(&expr, &e)?;
283 list_expr.push(expr_conversion);
284 }
285 Expr::InList(InList {
286 expr,
287 list: list_expr,
288 negated,
289 })
290 }
291 Expr::Literal(value, _) => match value {
292 ScalarValue::TimestampSecond(Some(i), _) => {
293 timestamp_to_timestamp_ms_expr(i, TimeUnit::Second)
294 }
295 ScalarValue::TimestampMillisecond(Some(i), _) => {
296 timestamp_to_timestamp_ms_expr(i, TimeUnit::Millisecond)
297 }
298 ScalarValue::TimestampMicrosecond(Some(i), _) => {
299 timestamp_to_timestamp_ms_expr(i, TimeUnit::Microsecond)
300 }
301 ScalarValue::TimestampNanosecond(Some(i), _) => {
302 timestamp_to_timestamp_ms_expr(i, TimeUnit::Nanosecond)
303 }
304 _ => Expr::Literal(value, None),
305 },
306 expr => expr,
307 };
308 Ok(Transformed::yes(new_expr))
309 }
310}
311
312fn timestamp_to_timestamp_ms_expr(val: i64, unit: TimeUnit) -> Expr {
313 let timestamp = match unit {
314 TimeUnit::Second => val * 1_000,
315 TimeUnit::Millisecond => val,
316 TimeUnit::Microsecond => val / 1_000,
317 TimeUnit::Nanosecond => val / 1_000 / 1_000,
318 };
319
320 Expr::Literal(
321 ScalarValue::TimestampMillisecond(Some(timestamp), None),
322 None,
323 )
324}
325
326pub(crate) fn cast_string_to_timestamp(
327 string: &str,
328 target_type: &DataType,
329 timezone: Option<&Timezone>,
330) -> Result<ScalarValue> {
331 let parsed = parse_string_to_timestamp(string, timezone)?;
332 cast_timestamp(parsed, target_type)
333}
334
335fn parse_string_to_timestamp(string: &str, timezone: Option<&Timezone>) -> Result<ScalarValue> {
336 let ts = Timestamp::from_str(string, timezone)
337 .map_err(|e| DataFusionError::External(Box::new(e)))?;
338
339 let value = Some(ts.value());
340 Ok(match ts.unit() {
341 TimeUnit::Second => ScalarValue::TimestampSecond(value, None),
342 TimeUnit::Millisecond => ScalarValue::TimestampMillisecond(value, None),
343 TimeUnit::Microsecond => ScalarValue::TimestampMicrosecond(value, None),
344 TimeUnit::Nanosecond => ScalarValue::TimestampNanosecond(value, None),
345 })
346}
347
348fn cast_timestamp(parsed: ScalarValue, target_type: &DataType) -> Result<ScalarValue> {
349 let parsed = parsed.to_array()?;
350 let casted = compute::cast(&parsed, target_type)
351 .map_err(|e| DataFusionError::ArrowError(Box::new(e), None))?;
352
353 ScalarValue::try_from_array(&casted, 0)
354}
355
356#[cfg(test)]
357mod tests {
358 use std::collections::HashMap;
359 use std::sync::Arc;
360
361 use datafusion_common::arrow::datatypes::Field;
362 use datafusion_common::{Column, DFSchema, NullEquality};
363 use datafusion_expr::expr::{Cast, Exists};
364 use datafusion_expr::{Join, JoinConstraint, JoinType, Literal, LogicalPlanBuilder, Subquery};
365 use datafusion_sql::TableReference;
366 use session::context::QueryContext;
367
368 use super::*;
369
370 #[test]
371 fn test_cast_string_to_timestamp() {
372 use datafusion_common::arrow::datatypes::TimeUnit as ArrowTimeUnit;
373
374 let target_type = DataType::Timestamp(ArrowTimeUnit::Second, None);
375 assert_eq!(
376 cast_string_to_timestamp("2022-02-02 19:00:00+08:00", &target_type, None).unwrap(),
377 ScalarValue::TimestampSecond(Some(1643799600), None)
378 );
379 assert_eq!(
380 cast_string_to_timestamp("2009-02-13 23:31:30Z", &target_type, None).unwrap(),
381 ScalarValue::TimestampSecond(Some(1234567890), None)
382 );
383
384 assert_eq!(
385 cast_string_to_timestamp(
386 "2009-02-13 23:31:30",
387 &target_type,
388 Some(&Timezone::from_tz_string("Asia/Shanghai").unwrap())
389 )
390 .unwrap(),
391 ScalarValue::TimestampSecond(Some(1234567890 - 8 * 3600), None)
392 );
393
394 assert_eq!(
395 cast_string_to_timestamp(
396 "2009-02-13 23:31:30",
397 &target_type,
398 Some(&Timezone::from_tz_string("-8:00").unwrap())
399 )
400 .unwrap(),
401 ScalarValue::TimestampSecond(Some(1234567890 + 8 * 3600), None)
402 );
403
404 assert_eq!(
405 cast_string_to_timestamp(
406 "2009-02-13 23:31:30.123456789Z",
407 &DataType::Timestamp(ArrowTimeUnit::Nanosecond, None),
408 None,
409 )
410 .unwrap(),
411 ScalarValue::TimestampNanosecond(Some(1_234_567_890_123_456_789), None)
412 );
413 }
414
415 #[test]
416 fn test_timestamp_to_timestamp_ms_expr() {
417 assert_eq!(
418 timestamp_to_timestamp_ms_expr(123, TimeUnit::Second),
419 ScalarValue::TimestampMillisecond(Some(123000), None).lit()
420 );
421
422 assert_eq!(
423 timestamp_to_timestamp_ms_expr(123, TimeUnit::Millisecond),
424 ScalarValue::TimestampMillisecond(Some(123), None).lit()
425 );
426
427 assert_eq!(
428 timestamp_to_timestamp_ms_expr(123, TimeUnit::Microsecond),
429 ScalarValue::TimestampMillisecond(Some(0), None).lit()
430 );
431
432 assert_eq!(
433 timestamp_to_timestamp_ms_expr(1230, TimeUnit::Microsecond),
434 ScalarValue::TimestampMillisecond(Some(1), None).lit()
435 );
436
437 assert_eq!(
438 timestamp_to_timestamp_ms_expr(123000, TimeUnit::Microsecond),
439 ScalarValue::TimestampMillisecond(Some(123), None).lit()
440 );
441
442 assert_eq!(
443 timestamp_to_timestamp_ms_expr(1230, TimeUnit::Nanosecond),
444 ScalarValue::TimestampMillisecond(Some(0), None).lit()
445 );
446 assert_eq!(
447 timestamp_to_timestamp_ms_expr(123_000_000, TimeUnit::Nanosecond),
448 ScalarValue::TimestampMillisecond(Some(123), None).lit()
449 );
450 }
451
452 #[test]
453 fn test_convert_timestamp_str() {
454 use datatypes::arrow::datatypes::TimeUnit as ArrowTimeUnit;
455
456 let schema = Arc::new(
457 DFSchema::new_with_metadata(
458 vec![(
459 None::<TableReference>,
460 Arc::new(Field::new(
461 "ts",
462 DataType::Timestamp(ArrowTimeUnit::Millisecond, None),
463 true,
464 )),
465 )],
466 HashMap::new(),
467 )
468 .unwrap(),
469 );
470 let mut converter = TypeConverter::new(schema, QueryContext::arc());
471
472 assert_eq!(
473 Expr::Column(Column::from_name("ts")).gt(ScalarValue::TimestampSecond(
474 Some(1_599_514_949),
475 None
476 )
477 .lit()),
478 converter
479 .f_up(Expr::Column(Column::from_name("ts")).gt("2020-09-08T05:42:29+08:00".lit()))
480 .unwrap()
481 .data
482 );
483 }
484
485 #[test]
486 fn test_type_converter_leaves_explicit_timestamp_cast_unchanged() {
487 use datafusion_common::arrow::datatypes::TimeUnit as ArrowTimeUnit;
488
489 let mut converter = TypeConverter::new(Arc::new(DFSchema::empty()), QueryContext::arc());
490 let expr = Expr::Cast(Cast {
491 expr: Box::new("2009-02-13 23:31:30".lit()),
492 data_type: DataType::Timestamp(ArrowTimeUnit::Millisecond, None),
493 });
494
495 assert_eq!(converter.f_up(expr.clone()).unwrap().data, expr);
496 }
497
498 #[test]
499 fn test_convert_bool() {
500 let col_name = "is_valid";
501 let schema = Arc::new(
502 DFSchema::new_with_metadata(
503 vec![(
504 None::<TableReference>,
505 Arc::new(Field::new(col_name, DataType::Boolean, false)),
506 )],
507 HashMap::new(),
508 )
509 .unwrap(),
510 );
511 let mut converter = TypeConverter::new(schema, QueryContext::arc());
512
513 assert_eq!(
514 Expr::Column(Column::from_name(col_name)).eq(true.lit()),
515 converter
516 .f_up(Expr::Column(Column::from_name(col_name)).eq("true".lit()))
517 .unwrap()
518 .data
519 );
520 }
521
522 #[test]
523 fn test_retrieve_type_from_aggr_plan() {
524 let plan = LogicalPlanBuilder::values(vec![vec![
525 ScalarValue::Int64(Some(1)).lit(),
526 ScalarValue::Float64(Some(1.0)).lit(),
527 ScalarValue::TimestampMillisecond(Some(1), None).lit(),
528 ]])
529 .unwrap()
530 .filter(Expr::Column(Column::from_name("column3")).gt("1970-01-01 00:00:00+08:00".lit()))
531 .unwrap()
532 .filter(
533 "1970-01-01 00:00:00+08:00"
534 .lit()
535 .lt_eq(Expr::Column(Column::from_name("column3"))),
536 )
537 .unwrap()
538 .aggregate(
539 Vec::<Expr>::new(),
540 vec![Expr::AggregateFunction(
541 datafusion_expr::expr::AggregateFunction::new_udf(
542 datafusion::functions_aggregate::count::count_udaf(),
543 vec![Expr::Column(Column::from_name("column1"))],
544 false,
545 None,
546 vec![],
547 None,
548 ),
549 )],
550 )
551 .unwrap()
552 .build()
553 .unwrap();
554 let context = QueryEngineContext::mock();
555
556 let transformed_plan = TypeConversionRule
557 .analyze(plan, &context, &ConfigOptions::default())
558 .unwrap();
559 let expected = String::from(
560 "Aggregate: groupBy=[[]], aggr=[[count(column1)]]\
561 \n Filter: TimestampSecond(-28800, None) <= column3\
562 \n Filter: column3 > TimestampSecond(-28800, None)\
563 \n Values: (Int64(1), Float64(1), TimestampMillisecond(1, None))",
564 );
565 assert_eq!(format!("{}", transformed_plan.display_indent()), expected);
566 }
567
568 #[test]
569 fn test_reverse_non_ts_type() {
570 let context = QueryEngineContext::mock();
571
572 let plan = LogicalPlanBuilder::values(vec![vec![1.0f64.lit()]])
573 .unwrap()
574 .filter(Expr::Column(Column::from_name("column1")).gt_eq("1.2345".lit()))
575 .unwrap()
576 .filter(
577 "1.2345"
578 .lit()
579 .lt(Expr::Column(Column::from_name("column1"))),
580 )
581 .unwrap()
582 .build()
583 .unwrap();
584 let transformed_plan = TypeConversionRule
585 .analyze(plan, &context, &ConfigOptions::default())
586 .unwrap();
587 let expected = String::from(
588 "Filter: Utf8(\"1.2345\") < column1\
589 \n Filter: column1 >= Utf8(\"1.2345\")\
590 \n Values: (Float64(1))",
591 );
592 assert_eq!(format!("{}", transformed_plan.display_indent()), expected);
593 }
594
595 #[test]
596 fn test_convert_join_filter_uses_input_schemas() {
597 let left = LogicalPlanBuilder::values(vec![vec![
598 ScalarValue::Int64(Some(1)).lit(),
599 ScalarValue::TimestampMillisecond(Some(1), None).lit(),
600 ]])
601 .unwrap()
602 .alias("left")
603 .unwrap()
604 .build()
605 .unwrap();
606 let right = LogicalPlanBuilder::values(vec![vec![ScalarValue::Int64(Some(1)).lit()]])
607 .unwrap()
608 .alias("right")
609 .unwrap()
610 .build()
611 .unwrap();
612 let left_key = Column::new(Some("left"), "column1");
613 let right_key = Column::new(Some("right"), "column1");
614 let timestamp_column = Column::new(Some("left"), "column2");
615 let plan = LogicalPlanBuilder::from(left)
616 .join(
617 right,
618 JoinType::RightSemi,
619 (vec![left_key.clone()], vec![right_key.clone()]),
620 Some(Expr::Column(timestamp_column.clone()).gt("2009-02-13 23:31:30".lit())),
621 )
622 .unwrap()
623 .build()
624 .unwrap();
625 let context = QueryEngineContext::mock();
626 context
627 .query_ctx()
628 .set_timezone(Timezone::from_tz_string("Asia/Shanghai").unwrap());
629
630 let transformed = TypeConversionRule
631 .analyze(plan, &context, &ConfigOptions::default())
632 .unwrap();
633 let LogicalPlan::Join(join) = transformed else {
634 panic!("expected join plan");
635 };
636
637 assert_eq!(
638 join.on,
639 vec![(Expr::Column(left_key), Expr::Column(right_key))]
640 );
641 assert_eq!(
642 join.filter,
643 Some(
644 Expr::Column(timestamp_column).gt(ScalarValue::TimestampSecond(
645 Some(1_234_539_090),
646 None
647 )
648 .lit())
649 )
650 );
651 }
652
653 #[test]
654 fn test_semi_anti_join_with_duplicate_unqualified_fields() {
655 let left = Arc::new(
656 LogicalPlanBuilder::values(vec![vec![1_i64.lit()]])
657 .unwrap()
658 .build()
659 .unwrap(),
660 );
661 let right = Arc::new(
662 LogicalPlanBuilder::values(vec![vec![2_i64.lit()]])
663 .unwrap()
664 .build()
665 .unwrap(),
666 );
667 let context = QueryEngineContext::mock();
668
669 for join_type in [JoinType::LeftSemi, JoinType::LeftAnti] {
670 let join = Join::try_new(
671 Arc::clone(&left),
672 Arc::clone(&right),
673 vec![(
674 Expr::Column(Column::from_name("column1")),
675 Expr::Column(Column::from_name("column1")),
676 )],
677 None,
678 join_type,
679 JoinConstraint::On,
680 NullEquality::NullEqualsNothing,
681 false,
682 )
683 .unwrap();
684
685 let result = TypeConversionRule.analyze(
686 LogicalPlan::Join(join),
687 &context,
688 &ConfigOptions::default(),
689 );
690 assert!(result.is_ok(), "{join_type:?}: {result:?}");
691 }
692 }
693
694 #[test]
695 fn test_convert_exists_subquery_filter() {
696 let inner = LogicalPlanBuilder::values(vec![vec![
697 ScalarValue::TimestampMillisecond(Some(1), None).lit(),
698 false.lit(),
699 ]])
700 .unwrap()
701 .filter(
702 Expr::Column(Column::from_name("column1"))
703 .gt("2009-02-13 23:31:30+08:00".lit())
704 .and(Expr::Column(Column::from_name("column2")).eq("true".lit())),
705 )
706 .unwrap()
707 .build()
708 .unwrap();
709 let outer = LogicalPlanBuilder::values(vec![vec![1_i64.lit()]])
710 .unwrap()
711 .filter(Expr::Exists(Exists {
712 subquery: Subquery {
713 subquery: Arc::new(inner),
714 outer_ref_columns: Default::default(),
715 spans: Default::default(),
716 },
717 negated: false,
718 }))
719 .unwrap()
720 .build()
721 .unwrap();
722 let context = QueryEngineContext::mock();
723
724 let transformed = TypeConversionRule
725 .analyze(outer, &context, &ConfigOptions::default())
726 .unwrap();
727 let LogicalPlan::Filter(outer_filter) = transformed else {
728 panic!("expected outer filter");
729 };
730 let Expr::Exists(exists) = outer_filter.predicate else {
731 panic!("expected exists predicate");
732 };
733 let LogicalPlan::Filter(inner_filter) = exists.subquery.subquery.as_ref() else {
734 panic!("expected inner filter");
735 };
736
737 assert_eq!(
738 inner_filter.predicate,
739 Expr::Column(Column::from_name("column1"))
740 .gt(ScalarValue::TimestampSecond(Some(1_234_539_090), None).lit())
741 .and(Expr::Column(Column::from_name("column2")).eq(true.lit()))
742 );
743 }
744}