1use std::vec;
19
20use datafusion::arrow::datatypes::DataType;
21use datafusion::logical_expr::{Expr, Like, Operator};
22use datafusion::scalar::ScalarValue;
23use iceberg::expr::{BinaryExpression, Predicate, PredicateOperator, Reference, UnaryExpression};
24use iceberg::spec::{Datum, PrimitiveLiteral};
25
26enum TransformedResult {
28 Predicate(Predicate),
29 Column(Reference),
30 Literal(Datum),
31 NotTransformed,
32}
33
34enum OpTransformedResult {
35 Operator(PredicateOperator),
36 And,
37 Or,
38 NotTransformed,
39}
40
41pub fn convert_filters_to_predicate(filters: &[Expr]) -> Option<Predicate> {
45 filters
46 .iter()
47 .filter_map(convert_filter_to_predicate)
48 .reduce(Predicate::and)
49}
50
51fn convert_filter_to_predicate(expr: &Expr) -> Option<Predicate> {
52 match to_iceberg_predicate(expr) {
53 TransformedResult::Predicate(predicate) => Some(predicate),
54 TransformedResult::Column(column) => {
55 Some(Predicate::Binary(BinaryExpression::new(
58 PredicateOperator::Eq,
59 column,
60 Datum::bool(true),
61 )))
62 }
63 TransformedResult::Literal(_) => {
64 None
66 }
67 _ => None,
68 }
69}
70
71fn to_iceberg_predicate(expr: &Expr) -> TransformedResult {
72 match expr {
73 Expr::BinaryExpr(binary) => {
74 let left = to_iceberg_predicate(&binary.left);
75 let right = to_iceberg_predicate(&binary.right);
76 let op = to_iceberg_operation(binary.op);
77 match op {
78 OpTransformedResult::Operator(op) => to_iceberg_binary_predicate(left, right, op),
79 OpTransformedResult::And => to_iceberg_and_predicate(left, right),
80 OpTransformedResult::Or => to_iceberg_or_predicate(left, right),
81 OpTransformedResult::NotTransformed => TransformedResult::NotTransformed,
82 }
83 }
84 Expr::Not(exp) => {
85 let expr = to_iceberg_predicate(exp);
86 match expr {
87 TransformedResult::Predicate(p) => TransformedResult::Predicate(!p),
88 TransformedResult::Column(column) => {
89 TransformedResult::Predicate(Predicate::Binary(BinaryExpression::new(
91 PredicateOperator::Eq,
92 column,
93 Datum::bool(false),
94 )))
95 }
96 _ => TransformedResult::NotTransformed,
97 }
98 }
99 Expr::Column(column) => TransformedResult::Column(Reference::new(column.name())),
100 Expr::Literal(literal, _) => match scalar_value_to_datum(literal) {
101 Some(data) => TransformedResult::Literal(data),
102 None => TransformedResult::NotTransformed,
103 },
104 Expr::InList(inlist) => {
105 let mut datums = vec![];
106 for expr in &inlist.list {
107 let p = to_iceberg_predicate(expr);
108 match p {
109 TransformedResult::Literal(l) => datums.push(l),
110 _ => return TransformedResult::NotTransformed,
111 }
112 }
113
114 let expr = to_iceberg_predicate(&inlist.expr);
115 match expr {
116 TransformedResult::Column(r) => match inlist.negated {
117 false => TransformedResult::Predicate(r.is_in(datums)),
118 true => TransformedResult::Predicate(r.is_not_in(datums)),
119 },
120 _ => TransformedResult::NotTransformed,
121 }
122 }
123 Expr::IsNull(expr) => {
124 let p = to_iceberg_predicate(expr);
125 match p {
126 TransformedResult::Column(r) => TransformedResult::Predicate(Predicate::Unary(
127 UnaryExpression::new(PredicateOperator::IsNull, r),
128 )),
129 _ => TransformedResult::NotTransformed,
130 }
131 }
132 Expr::IsNotNull(expr) => {
133 let p = to_iceberg_predicate(expr);
134 match p {
135 TransformedResult::Column(r) => TransformedResult::Predicate(Predicate::Unary(
136 UnaryExpression::new(PredicateOperator::NotNull, r),
137 )),
138 _ => TransformedResult::NotTransformed,
139 }
140 }
141 Expr::Cast(c) => {
142 if c.data_type == DataType::Date32 || c.data_type == DataType::Date64 {
143 return TransformedResult::NotTransformed;
146 }
147 to_iceberg_predicate(&c.expr)
148 }
149 Expr::Like(Like {
150 negated,
151 expr,
152 pattern,
153 escape_char,
154 case_insensitive,
155 }) => {
156 if escape_char.is_some() || *case_insensitive {
161 return TransformedResult::NotTransformed;
162 }
163
164 let pattern_str = match to_iceberg_predicate(pattern) {
166 TransformedResult::Literal(d) => match d.literal() {
167 PrimitiveLiteral::String(s) => s.clone(),
168 _ => return TransformedResult::NotTransformed,
169 },
170 _ => return TransformedResult::NotTransformed,
171 };
172
173 if pattern_str.ends_with('%')
175 && !pattern_str[..pattern_str.len() - 1].contains(['%', '_'])
176 {
177 let prefix = pattern_str[..pattern_str.len() - 1].to_string();
179
180 let column = match to_iceberg_predicate(expr) {
182 TransformedResult::Column(r) => r,
183 _ => return TransformedResult::NotTransformed,
184 };
185
186 let predicate = if *negated {
188 column.not_starts_with(Datum::string(prefix))
189 } else {
190 column.starts_with(Datum::string(prefix))
191 };
192
193 TransformedResult::Predicate(predicate)
194 } else {
195 TransformedResult::NotTransformed
197 }
198 }
199 _ => TransformedResult::NotTransformed,
200 }
201}
202
203fn to_iceberg_operation(op: Operator) -> OpTransformedResult {
204 match op {
205 Operator::Eq => OpTransformedResult::Operator(PredicateOperator::Eq),
206 Operator::NotEq => OpTransformedResult::Operator(PredicateOperator::NotEq),
207 Operator::Lt => OpTransformedResult::Operator(PredicateOperator::LessThan),
208 Operator::LtEq => OpTransformedResult::Operator(PredicateOperator::LessThanOrEq),
209 Operator::Gt => OpTransformedResult::Operator(PredicateOperator::GreaterThan),
210 Operator::GtEq => OpTransformedResult::Operator(PredicateOperator::GreaterThanOrEq),
211 Operator::And => OpTransformedResult::And,
213 Operator::Or => OpTransformedResult::Or,
214 _ => OpTransformedResult::NotTransformed,
216 }
217}
218
219fn to_iceberg_and_predicate(
220 left: TransformedResult,
221 right: TransformedResult,
222) -> TransformedResult {
223 match (left, right) {
224 (TransformedResult::Predicate(left), TransformedResult::Predicate(right)) => {
225 TransformedResult::Predicate(left.and(right))
226 }
227 (TransformedResult::Predicate(left), _) => TransformedResult::Predicate(left),
228 (_, TransformedResult::Predicate(right)) => TransformedResult::Predicate(right),
229 _ => TransformedResult::NotTransformed,
230 }
231}
232
233fn to_iceberg_or_predicate(left: TransformedResult, right: TransformedResult) -> TransformedResult {
234 match (left, right) {
235 (TransformedResult::Predicate(left), TransformedResult::Predicate(right)) => {
236 TransformedResult::Predicate(left.or(right))
237 }
238 _ => TransformedResult::NotTransformed,
239 }
240}
241
242fn to_iceberg_binary_predicate(
243 left: TransformedResult,
244 right: TransformedResult,
245 op: PredicateOperator,
246) -> TransformedResult {
247 let (r, d, op) = match (left, right) {
248 (TransformedResult::NotTransformed, _) => return TransformedResult::NotTransformed,
249 (_, TransformedResult::NotTransformed) => return TransformedResult::NotTransformed,
250 (TransformedResult::Column(r), TransformedResult::Literal(d)) => (r, d, op),
251 (TransformedResult::Literal(d), TransformedResult::Column(r)) => {
252 (r, d, reverse_predicate_operator(op))
253 }
254 _ => return TransformedResult::NotTransformed,
255 };
256 TransformedResult::Predicate(Predicate::Binary(BinaryExpression::new(op, r, d)))
257}
258
259fn reverse_predicate_operator(op: PredicateOperator) -> PredicateOperator {
260 match op {
261 PredicateOperator::Eq => PredicateOperator::Eq,
262 PredicateOperator::NotEq => PredicateOperator::NotEq,
263 PredicateOperator::GreaterThan => PredicateOperator::LessThan,
264 PredicateOperator::GreaterThanOrEq => PredicateOperator::LessThanOrEq,
265 PredicateOperator::LessThan => PredicateOperator::GreaterThan,
266 PredicateOperator::LessThanOrEq => PredicateOperator::GreaterThanOrEq,
267 _ => unreachable!("Reverse {}", op),
268 }
269}
270
271const MILLIS_PER_DAY: i64 = 24 * 60 * 60 * 1000;
272
273fn scalar_value_to_datum(value: &ScalarValue) -> Option<Datum> {
275 match value {
276 ScalarValue::Boolean(Some(v)) => Some(Datum::bool(*v)),
277 ScalarValue::Int8(Some(v)) => Some(Datum::int(*v as i32)),
278 ScalarValue::Int16(Some(v)) => Some(Datum::int(*v as i32)),
279 ScalarValue::Int32(Some(v)) => Some(Datum::int(*v)),
280 ScalarValue::Int64(Some(v)) => Some(Datum::long(*v)),
281 ScalarValue::Float32(Some(v)) => Some(Datum::double(*v as f64)),
282 ScalarValue::Float64(Some(v)) => Some(Datum::double(*v)),
283 ScalarValue::Utf8(Some(v)) => Some(Datum::string(v.clone())),
284 ScalarValue::LargeUtf8(Some(v)) => Some(Datum::string(v.clone())),
285 ScalarValue::Binary(Some(v)) => Some(Datum::binary(v.clone())),
286 ScalarValue::LargeBinary(Some(v)) => Some(Datum::binary(v.clone())),
287 ScalarValue::Date32(Some(v)) => Some(Datum::date(*v)),
288 ScalarValue::Date64(Some(v)) => Some(Datum::date((*v / MILLIS_PER_DAY) as i32)),
289 ScalarValue::TimestampMicrosecond(Some(v), _) => Some(Datum::timestamp_micros(*v)),
295 ScalarValue::TimestampNanosecond(Some(v), _) => Some(Datum::timestamp_nanos(*v)),
296 _ => None,
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use std::collections::HashMap;
303
304 use datafusion::arrow::datatypes::{DataType, Field, Schema, TimeUnit};
305 use datafusion::common::DFSchema;
306 use datafusion::logical_expr::utils::split_conjunction;
307 use datafusion::prelude::{Expr, SessionContext};
308 use iceberg::expr::{Predicate, Reference};
309 use iceberg::spec::Datum;
310 use parquet::arrow::PARQUET_FIELD_ID_META_KEY;
311
312 use super::convert_filters_to_predicate;
313
314 fn create_test_schema() -> DFSchema {
315 let arrow_schema = Schema::new(vec![
316 Field::new("foo", DataType::Int32, true).with_metadata(HashMap::from([(
317 PARQUET_FIELD_ID_META_KEY.to_string(),
318 "1".to_string(),
319 )])),
320 Field::new("bar", DataType::Utf8, true).with_metadata(HashMap::from([(
321 PARQUET_FIELD_ID_META_KEY.to_string(),
322 "2".to_string(),
323 )])),
324 Field::new("ts", DataType::Timestamp(TimeUnit::Second, None), true).with_metadata(
325 HashMap::from([(PARQUET_FIELD_ID_META_KEY.to_string(), "3".to_string())]),
326 ),
327 ]);
328 DFSchema::try_from_qualified_schema("my_table", &arrow_schema).unwrap()
329 }
330
331 fn convert_to_iceberg_predicate(sql: &str) -> Option<Predicate> {
332 let df_schema = create_test_schema();
333 let expr = SessionContext::new()
334 .parse_sql_expr(sql, &df_schema)
335 .unwrap();
336 let exprs: Vec<Expr> = split_conjunction(&expr).into_iter().cloned().collect();
337 convert_filters_to_predicate(&exprs[..])
338 }
339
340 #[test]
341 fn test_predicate_conversion_with_single_condition() {
342 let predicate = convert_to_iceberg_predicate("foo = 1").unwrap();
343 assert_eq!(predicate, Reference::new("foo").equal_to(Datum::long(1)));
344
345 let predicate = convert_to_iceberg_predicate("foo != 1").unwrap();
346 assert_eq!(
347 predicate,
348 Reference::new("foo").not_equal_to(Datum::long(1))
349 );
350
351 let predicate = convert_to_iceberg_predicate("foo > 1").unwrap();
352 assert_eq!(
353 predicate,
354 Reference::new("foo").greater_than(Datum::long(1))
355 );
356
357 let predicate = convert_to_iceberg_predicate("foo >= 1").unwrap();
358 assert_eq!(
359 predicate,
360 Reference::new("foo").greater_than_or_equal_to(Datum::long(1))
361 );
362
363 let predicate = convert_to_iceberg_predicate("foo < 1").unwrap();
364 assert_eq!(predicate, Reference::new("foo").less_than(Datum::long(1)));
365
366 let predicate = convert_to_iceberg_predicate("foo <= 1").unwrap();
367 assert_eq!(
368 predicate,
369 Reference::new("foo").less_than_or_equal_to(Datum::long(1))
370 );
371
372 let predicate = convert_to_iceberg_predicate("foo is null").unwrap();
373 assert_eq!(predicate, Reference::new("foo").is_null());
374
375 let predicate = convert_to_iceberg_predicate("foo is not null").unwrap();
376 assert_eq!(predicate, Reference::new("foo").is_not_null());
377
378 let predicate = convert_to_iceberg_predicate("foo in (5, 6)").unwrap();
379 assert_eq!(
380 predicate,
381 Reference::new("foo").is_in([Datum::long(5), Datum::long(6)])
382 );
383
384 let predicate = convert_to_iceberg_predicate("foo not in (5, 6)").unwrap();
385 assert_eq!(
386 predicate,
387 Reference::new("foo").is_not_in([Datum::long(5), Datum::long(6)])
388 );
389
390 let predicate = convert_to_iceberg_predicate("not foo = 1").unwrap();
391 assert_eq!(predicate, !Reference::new("foo").equal_to(Datum::long(1)));
392 }
393
394 #[test]
395 fn test_predicate_conversion_with_single_unsupported_condition() {
396 let predicate = convert_to_iceberg_predicate("foo + 1 = 1");
397 assert_eq!(predicate, None);
398
399 let predicate = convert_to_iceberg_predicate("length(bar) = 1");
400 assert_eq!(predicate, None);
401
402 let predicate = convert_to_iceberg_predicate("foo in (1, 2, foo)");
403 assert_eq!(predicate, None);
404 }
405
406 #[test]
407 fn test_predicate_conversion_with_single_condition_rev() {
408 let predicate = convert_to_iceberg_predicate("1 < foo").unwrap();
409 assert_eq!(
410 predicate,
411 Reference::new("foo").greater_than(Datum::long(1))
412 );
413 }
414
415 #[test]
416 fn test_predicate_conversion_with_and_condition() {
417 let sql = "foo > 1 and bar = 'test'";
418 let predicate = convert_to_iceberg_predicate(sql).unwrap();
419 let expected_predicate = Predicate::and(
420 Reference::new("foo").greater_than(Datum::long(1)),
421 Reference::new("bar").equal_to(Datum::string("test")),
422 );
423 assert_eq!(predicate, expected_predicate);
424 }
425
426 #[test]
427 fn test_predicate_conversion_with_and_condition_unsupported() {
428 let sql = "foo > 1 and length(bar) = 1";
429 let predicate = convert_to_iceberg_predicate(sql).unwrap();
430 let expected_predicate = Reference::new("foo").greater_than(Datum::long(1));
431 assert_eq!(predicate, expected_predicate);
432 }
433
434 #[test]
435 fn test_predicate_conversion_with_and_condition_both_unsupported() {
436 let sql = "foo in (1, 2, foo) and length(bar) = 1";
437 let predicate = convert_to_iceberg_predicate(sql);
438 assert_eq!(predicate, None);
439 }
440
441 #[test]
442 fn test_predicate_conversion_with_or_condition_unsupported() {
443 let sql = "foo > 1 or length(bar) = 1";
444 let predicate = convert_to_iceberg_predicate(sql);
445 assert_eq!(predicate, None);
446 }
447
448 #[test]
449 fn test_predicate_conversion_with_or_condition_supported() {
450 let sql = "foo > 1 or bar = 'test'";
451 let predicate = convert_to_iceberg_predicate(sql).unwrap();
452 let expected_predicate = Predicate::or(
453 Reference::new("foo").greater_than(Datum::long(1)),
454 Reference::new("bar").equal_to(Datum::string("test")),
455 );
456 assert_eq!(predicate, expected_predicate);
457 }
458
459 #[test]
460 fn test_predicate_conversion_with_complex_binary_expr() {
461 let sql = "(foo > 1 and bar = 'test') or foo < 0 ";
462 let predicate = convert_to_iceberg_predicate(sql).unwrap();
463
464 let inner_predicate = Predicate::and(
465 Reference::new("foo").greater_than(Datum::long(1)),
466 Reference::new("bar").equal_to(Datum::string("test")),
467 );
468 let expected_predicate = Predicate::or(
469 inner_predicate,
470 Reference::new("foo").less_than(Datum::long(0)),
471 );
472 assert_eq!(predicate, expected_predicate);
473 }
474
475 #[test]
476 fn test_predicate_conversion_with_one_and_expr_supported() {
477 let sql = "(foo > 1 and length(bar) = 1 ) or foo < 0 ";
478 let predicate = convert_to_iceberg_predicate(sql).unwrap();
479
480 let inner_predicate = Reference::new("foo").greater_than(Datum::long(1));
481 let expected_predicate = Predicate::or(
482 inner_predicate,
483 Reference::new("foo").less_than(Datum::long(0)),
484 );
485 assert_eq!(predicate, expected_predicate);
486 }
487
488 #[test]
489 fn test_predicate_conversion_with_complex_binary_expr_unsupported() {
490 let sql = "(foo > 1 or length(bar) = 1 ) and foo < 0 ";
491 let predicate = convert_to_iceberg_predicate(sql).unwrap();
492 let expected_predicate = Reference::new("foo").less_than(Datum::long(0));
493 assert_eq!(predicate, expected_predicate);
494 }
495
496 #[test]
497 fn test_predicate_conversion_with_cast() {
498 let sql = "ts >= timestamp '2023-01-05T00:00:00'";
499 let predicate = convert_to_iceberg_predicate(sql).unwrap();
500 let expected_predicate =
501 Reference::new("ts").greater_than_or_equal_to(Datum::string("2023-01-05T00:00:00"));
502 assert_eq!(predicate, expected_predicate);
503 }
504
505 #[test]
506 fn test_predicate_conversion_with_date_cast() {
507 let sql = "ts >= date '2023-01-05T11:00:00'";
508 let predicate = convert_to_iceberg_predicate(sql);
509 assert_eq!(predicate, None);
510 }
511
512 #[test]
513 fn test_scalar_value_to_datum_timestamp() {
514 use datafusion::common::ScalarValue;
515
516 let ts_micros = 1672876800000000i64; let datum =
519 super::scalar_value_to_datum(&ScalarValue::TimestampMicrosecond(Some(ts_micros), None));
520 assert_eq!(datum, Some(Datum::timestamp_micros(ts_micros)));
521
522 let ts_nanos = 1672876800000000500i64; let datum =
525 super::scalar_value_to_datum(&ScalarValue::TimestampNanosecond(Some(ts_nanos), None));
526 assert_eq!(datum, Some(Datum::timestamp_nanos(ts_nanos)));
527
528 let datum = super::scalar_value_to_datum(&ScalarValue::TimestampMicrosecond(None, None));
530 assert_eq!(datum, None);
531
532 let ts_seconds = 1672876800i64; let datum =
539 super::scalar_value_to_datum(&ScalarValue::TimestampSecond(Some(ts_seconds), None));
540 assert_eq!(datum, None);
541
542 let ts_millis = 1672876800000i64; let datum =
544 super::scalar_value_to_datum(&ScalarValue::TimestampMillisecond(Some(ts_millis), None));
545 assert_eq!(datum, None);
546 }
547
548 #[test]
549 fn test_scalar_value_to_datum_binary() {
550 use datafusion::common::ScalarValue;
551
552 let bytes = vec![1u8, 2u8, 3u8];
553 let datum = super::scalar_value_to_datum(&ScalarValue::Binary(Some(bytes.clone())));
554 assert_eq!(datum, Some(Datum::binary(bytes.clone())));
555
556 let datum = super::scalar_value_to_datum(&ScalarValue::LargeBinary(Some(bytes.clone())));
557 assert_eq!(datum, Some(Datum::binary(bytes)));
558
559 let datum = super::scalar_value_to_datum(&ScalarValue::Binary(None));
560 assert_eq!(datum, None);
561 }
562
563 #[test]
564 fn test_predicate_conversion_with_binary() {
565 let sql = "foo = 1 and bar = X'0102'";
566 let predicate = convert_to_iceberg_predicate(sql).unwrap();
567 let expected_predicate = Reference::new("foo")
570 .equal_to(Datum::long(1))
571 .and(Reference::new("bar").equal_to(Datum::binary(vec![1u8, 2u8])));
572 assert_eq!(predicate, expected_predicate);
573 }
574
575 #[test]
576 fn test_scalar_value_to_datum_boolean() {
577 use datafusion::common::ScalarValue;
578
579 let datum = super::scalar_value_to_datum(&ScalarValue::Boolean(Some(true)));
581 assert_eq!(datum, Some(Datum::bool(true)));
582
583 let datum = super::scalar_value_to_datum(&ScalarValue::Boolean(Some(false)));
585 assert_eq!(datum, Some(Datum::bool(false)));
586
587 let datum = super::scalar_value_to_datum(&ScalarValue::Boolean(None));
589 assert_eq!(datum, None);
590 }
591
592 #[test]
593 fn test_predicate_conversion_with_like_starts_with() {
594 let sql = "bar LIKE 'test%'";
595 let predicate = convert_to_iceberg_predicate(sql).unwrap();
596 assert_eq!(
597 predicate,
598 Reference::new("bar").starts_with(Datum::string("test"))
599 );
600 }
601
602 #[test]
603 fn test_predicate_conversion_with_not_like_starts_with() {
604 let sql = "bar NOT LIKE 'test%'";
605 let predicate = convert_to_iceberg_predicate(sql).unwrap();
606 assert_eq!(
607 predicate,
608 Reference::new("bar").not_starts_with(Datum::string("test"))
609 );
610 }
611
612 #[test]
613 fn test_predicate_conversion_with_like_empty_prefix() {
614 let sql = "bar LIKE '%'";
615 let predicate = convert_to_iceberg_predicate(sql).unwrap();
616 assert_eq!(
617 predicate,
618 Reference::new("bar").starts_with(Datum::string(""))
619 );
620 }
621
622 #[test]
623 fn test_predicate_conversion_with_like_complex_pattern() {
624 let sql = "bar LIKE 'te%st'";
626 let predicate = convert_to_iceberg_predicate(sql);
627 assert_eq!(predicate, None);
628 }
629
630 #[test]
631 fn test_predicate_conversion_with_like_underscore_wildcard() {
632 let sql = "bar LIKE 'test_'";
634 let predicate = convert_to_iceberg_predicate(sql);
635 assert_eq!(predicate, None);
636 }
637
638 #[test]
639 fn test_predicate_conversion_with_like_no_wildcard() {
640 let sql = "bar LIKE 'test'";
642 let predicate = convert_to_iceberg_predicate(sql);
643 assert_eq!(predicate, None);
644 }
645
646 #[test]
647 fn test_predicate_conversion_with_ilike() {
648 let sql = "bar ILIKE 'test%'";
650 let predicate = convert_to_iceberg_predicate(sql);
651 assert_eq!(predicate, None);
652 }
653
654 #[test]
655 fn test_predicate_conversion_with_like_and_other_conditions() {
656 let sql = "bar LIKE 'test%' AND foo > 1";
657 let predicate = convert_to_iceberg_predicate(sql).unwrap();
658 let expected_predicate = Predicate::and(
659 Reference::new("bar").starts_with(Datum::string("test")),
660 Reference::new("foo").greater_than(Datum::long(1)),
661 );
662 assert_eq!(predicate, expected_predicate);
663 }
664
665 #[test]
666 fn test_predicate_conversion_with_like_special_characters() {
667 let sql = "bar LIKE 'test-abc_123%'";
669 let predicate = convert_to_iceberg_predicate(sql);
670 assert_eq!(predicate, None);
672 }
673
674 #[test]
675 fn test_predicate_conversion_with_like_unicode() {
676 let sql = "bar LIKE '测试%'";
678 let predicate = convert_to_iceberg_predicate(sql).unwrap();
679 assert_eq!(
680 predicate,
681 Reference::new("bar").starts_with(Datum::string("测试"))
682 );
683 }
684}