1use std::sync::{Arc, LazyLock};
16
17use arrow_schema::Field;
18use arrow_schema::extension::ExtensionType;
19use common_function::scalars::json::json_get::{JsonGetWithType, parse_json_get_path};
20use common_function::scalars::udf::create_udf;
21use datafusion_common::arrow::datatypes::DataType;
22use datafusion_common::{
23 Column, DFSchema, DataFusionError, ExprSchema, Result, ScalarValue, TableReference,
24 plan_datafusion_err,
25};
26use datafusion_expr::expr::{BinaryExpr, ScalarFunction};
27use datafusion_expr::planner::{
28 ExprPlanner, PlannerResult, RawAggregateExpr, RawBinaryExpr, RawFieldAccessExpr, RawScalarExpr,
29 RawWindowExpr,
30};
31use datafusion_expr::type_coercion::functions::{UDFCoercionExt, fields_with_udf};
32use datafusion_expr::{
33 Expr, ExprSchemable, GetFieldAccess, Operator, ScalarUDF, WindowFunctionDefinition,
34};
35use datatypes::extension::json::{
36 Json2ExtensionType, is_json2_extension_type, parse_legacy_json2_settings,
37};
38use datatypes::types::json_type::JsonNativeType;
39use jsonb::jsonpath::Path;
40use sqlparser::ast::BinaryOperator;
41
42#[derive(Debug)]
58pub(crate) struct JsonExprPlanner;
59
60impl ExprPlanner for JsonExprPlanner {
61 fn plan_binary_op(
62 &self,
63 expr: RawBinaryExpr,
64 schema: &DFSchema,
65 ) -> Result<PlannerResult<RawBinaryExpr>> {
66 let RawBinaryExpr {
67 op,
68 mut left,
69 mut right,
70 } = expr;
71
72 if !is_untyped_json_get(&left) && !is_untyped_json_get(&right) {
73 return Ok(PlannerResult::Original(RawBinaryExpr { op, left, right }));
74 }
75
76 let Some(expr_op) = parse_sql_op(&op) else {
77 return Ok(PlannerResult::Original(RawBinaryExpr { op, left, right }));
78 };
79
80 let left_type = left.get_type(schema)?;
81 let right_type = right.get_type(schema)?;
82 let left_changed = push_json_get_type_arg(&mut left, &right_type)?;
83 let right_changed = push_json_get_type_arg(&mut right, &left_type)?;
84 if left_changed || right_changed {
85 Ok(PlannerResult::Planned(Expr::BinaryExpr(BinaryExpr::new(
86 Box::new(left),
87 expr_op,
88 Box::new(right),
89 ))))
90 } else {
91 Ok(PlannerResult::Original(RawBinaryExpr { op, left, right }))
92 }
93 }
94
95 fn plan_field_access(
102 &self,
103 mut expr: RawFieldAccessExpr,
104 _schema: &DFSchema,
105 ) -> Result<PlannerResult<RawFieldAccessExpr>> {
106 let suffix = match &expr.field_access {
109 GetFieldAccess::ListIndex { key } => {
110 let Expr::Literal(ScalarValue::Int64(Some(index)), _) = key.as_ref() else {
112 return Ok(PlannerResult::Original(expr));
113 };
114 format!("[{index}]")
115 }
116 GetFieldAccess::NamedStructField { name } => {
117 let Some(name) = name.try_as_str().flatten() else {
118 return Ok(PlannerResult::Original(expr));
119 };
120 format!(".{}", json_path_field(name)?)
121 }
122 GetFieldAccess::ListRange { .. } => return Ok(PlannerResult::Original(expr)),
123 };
124 let Some(json_get) = extract_untyped_json_get(&mut expr.expr) else {
125 return Ok(PlannerResult::Original(expr));
126 };
127 let Some(Expr::Literal(ScalarValue::Utf8(Some(path)), _)) = json_get.args.get_mut(1) else {
128 return Ok(PlannerResult::Original(expr));
129 };
130
131 path.push_str(&suffix);
132 Ok(PlannerResult::Planned(expr.expr))
133 }
134
135 fn plan_compound_identifier(
136 &self,
137 field: &Field,
138 qualifier: Option<&TableReference>,
139 nested_names: &[String],
140 ) -> Result<PlannerResult<Vec<Expr>>> {
141 if !is_json2_extension_type(field) {
142 return Ok(PlannerResult::Original(Vec::new()));
143 }
144
145 static JSON_GET_UDF: LazyLock<Arc<ScalarUDF>> =
146 LazyLock::new(|| Arc::new(create_udf(Arc::new(JsonGetWithType::default()))));
147
148 let json_get = JSON_GET_UDF.clone();
149 let mut path = "$".to_string();
150 for name in nested_names {
151 path.push('.');
152 path.push_str(&json_path_field(name)?);
153 }
154
155 let mut args = vec![
156 Expr::Column(Column::from((qualifier, field))),
157 Expr::Literal(ScalarValue::Utf8(Some(path)), None),
158 ];
159 apply_json_type_hint(field, nested_names, &mut args)?;
160
161 Ok(PlannerResult::Planned(Expr::ScalarFunction(
162 ScalarFunction::new_udf(json_get, args),
163 )))
164 }
165
166 fn plan_scalar(
171 &self,
172 mut expr: RawScalarExpr,
173 schema: &DFSchema,
174 ) -> Result<PlannerResult<RawScalarExpr>> {
175 if expr.func.name().eq_ignore_ascii_case(JsonGetWithType::NAME) && expr.args.len() == 2 {
176 let field = match &expr.args[0] {
177 Expr::Column(column) => schema.field_from_column(column).ok().cloned(),
181 Expr::OuterReferenceColumn(field, _) => Some(Arc::clone(field)),
183 _ => None,
184 };
185 if let Some(field) = field
186 && let Some(path) = json_get_object_path(&expr.args[1])
187 {
188 apply_json_type_hint(&field, &path, &mut expr.args)?;
189 }
190 }
191 push_function_arg_types(expr.func.as_ref(), &mut expr.args)?;
192 Ok(PlannerResult::Original(expr))
193 }
194
195 fn plan_aggregate(
197 &self,
198 mut expr: RawAggregateExpr,
199 ) -> Result<PlannerResult<RawAggregateExpr>> {
200 push_function_arg_types(expr.func.as_ref(), &mut expr.args)?;
201 Ok(PlannerResult::Original(expr))
202 }
203
204 fn plan_window(&self, mut expr: RawWindowExpr) -> Result<PlannerResult<RawWindowExpr>> {
206 match &expr.func_def {
207 WindowFunctionDefinition::AggregateUDF(func) => {
208 push_function_arg_types(func.as_ref(), &mut expr.args)?;
209 }
210 WindowFunctionDefinition::WindowUDF(func) => {
211 push_function_arg_types(func.as_ref(), &mut expr.args)?;
212 }
213 }
214 Ok(PlannerResult::Original(expr))
215 }
216}
217
218fn apply_json_type_hint(field: &Field, path: &[String], args: &mut Vec<Expr>) -> Result<()> {
221 if !is_json2_extension_type(field) || args.len() != 2 {
222 return Ok(());
223 }
224 if let Some(json_type) = json_type_hint(field, path)? {
225 args.push(Expr::Literal(
226 ScalarValue::try_new_null(&json_type.as_arrow_type())?,
227 None,
228 ));
229 }
230 Ok(())
231}
232
233fn json_get_object_path(expr: &Expr) -> Option<Vec<String>> {
240 let path = expr.as_literal()?.try_as_str().flatten()?;
241 let json_path = parse_json_get_path(path).ok()?;
244 let mut names = Vec::new();
245 for segment in json_path.paths {
246 match segment {
247 Path::Root => {}
248 Path::DotField(name) | Path::ColonField(name) | Path::ObjectField(name) => {
249 names.push(name.into_owned());
250 }
251 _ => return None,
253 }
254 }
255 Some(names)
256}
257
258fn json_type_hint(field: &Field, path: &[String]) -> Result<Option<JsonNativeType>> {
260 let settings = if field.extension_type_name() == Some(Json2ExtensionType::NAME) {
261 let extension = field
262 .try_extension_type::<Json2ExtensionType>()
263 .map_err(|e| plan_datafusion_err!("invalid JSON2 extension metadata: {e}"))?;
264 Some(extension.metadata().json_settings().clone())
265 } else {
266 parse_legacy_json2_settings(field.metadata())
267 .map_err(|e| plan_datafusion_err!("invalid JSON2 extension metadata: {e}"))?
268 };
269
270 Ok(settings.and_then(|settings| {
271 settings
272 .type_hints()
273 .iter()
274 .find(|hint| hint.path == path)
275 .map(|hint| JsonNativeType::from(&hint.data_type))
276 }))
277}
278
279fn json_path_field(name: &str) -> Result<String> {
281 if !name.is_empty() && name.chars().all(|c| c.is_alphanumeric() || c == '_') {
282 Ok(name.to_string())
283 } else {
284 serde_json::to_string(name).map_err(|e| DataFusionError::External(Box::new(e)))
286 }
287}
288
289enum JsonGetTypeResolution {
290 Fallback,
291 Typed(Vec<(usize, DataType)>),
292}
293
294fn push_function_arg_types<F>(func: &F, args: &mut [Expr]) -> Result<()>
308where
309 F: UDFCoercionExt,
310{
311 if !args.iter().any(is_untyped_json_get) {
312 return Ok(());
313 }
314
315 let fields = args.iter().map(function_arg_field).collect::<Vec<_>>();
316 match infer_json_get_types(func, args, &fields) {
317 JsonGetTypeResolution::Fallback => {
318 let Some(data_type) = fallback_json_get_type(func, args, &fields) else {
319 return Ok(());
320 };
321 for arg in args.iter_mut() {
322 if is_untyped_json_get(arg) {
323 let _ = push_json_get_type_arg(arg, &data_type)?;
324 }
325 }
326 }
327 JsonGetTypeResolution::Typed(types) => {
328 for (index, data_type) in types {
329 let _ = push_json_get_type_arg(&mut args[index], &data_type)?;
330 }
331 }
332 }
333 Ok(())
334}
335
336fn infer_json_get_types<F>(func: &F, args: &[Expr], fields: &[Arc<Field>]) -> JsonGetTypeResolution
337where
338 F: UDFCoercionExt,
339{
340 let Ok(coerced) = fields_with_udf(fields, func) else {
349 return JsonGetTypeResolution::Fallback;
350 };
351
352 let mut inferred_types = Vec::with_capacity(coerced.len());
353 for (index, (arg, field)) in args.iter().zip(coerced).enumerate() {
354 if !is_untyped_json_get(arg) || field.data_type().is_null() {
355 continue;
356 }
357 let Some(data_type) = json_get_output_type(field.data_type()) else {
358 return JsonGetTypeResolution::Fallback;
359 };
360 inferred_types.push((index, data_type));
361 }
362 if inferred_types.is_empty() {
363 JsonGetTypeResolution::Fallback
364 } else {
365 JsonGetTypeResolution::Typed(inferred_types)
366 }
367}
368
369fn fallback_json_get_type<F>(func: &F, args: &[Expr], fields: &[Arc<Field>]) -> Option<DataType>
370where
371 F: UDFCoercionExt,
372{
373 let mut candidate_fields = fields.to_vec();
376 for data_type in [
377 DataType::Utf8View,
378 DataType::Float64,
379 DataType::Int64,
380 DataType::Boolean,
381 ] {
382 for (index, arg) in args.iter().enumerate() {
383 if is_untyped_json_get(arg) {
384 candidate_fields[index] = Arc::new(
385 fields[index]
386 .as_ref()
387 .clone()
388 .with_data_type(data_type.clone()),
389 );
390 }
391 }
392 if fields_with_udf(&candidate_fields, func).is_ok() {
393 return Some(data_type);
394 }
395 }
396 None
397}
398
399fn function_arg_field(expr: &Expr) -> Arc<Field> {
400 let data_type = if is_untyped_json_get(expr) {
401 DataType::Null
402 } else if let Some(data_type) = extract_json_get_type(expr) {
403 data_type
404 } else {
405 expr.get_type(&DFSchema::empty()).unwrap_or(DataType::Null)
411 };
412 Arc::new(Field::new("", data_type, true))
413}
414
415fn json_get_output_type(data_type: &DataType) -> Option<DataType> {
416 let output_type = match data_type {
417 DataType::Boolean => DataType::Boolean,
418 data_type if data_type.is_integer() => DataType::Int64,
419 data_type if data_type.is_floating() => DataType::Float64,
420 DataType::Decimal128(_, _) | DataType::Decimal256(_, _) => DataType::Float64,
421 data_type if data_type.is_string() => DataType::Utf8View,
422 _ => return None,
423 };
424 Some(output_type)
425}
426
427macro_rules! is_untyped_json_get_func {
428 ($func:expr) => {
429 $func
430 .func
431 .name()
432 .eq_ignore_ascii_case(JsonGetWithType::NAME)
433 && $func.args.len() == 2
434 };
435}
436
437macro_rules! is_typed_json_get_func {
438 ($func:expr) => {
439 $func
440 .func
441 .name()
442 .eq_ignore_ascii_case(JsonGetWithType::NAME)
443 && $func.args.len() == 3
444 };
445}
446
447fn extract_untyped_json_get(expr: &mut Expr) -> Option<&mut ScalarFunction> {
448 match expr {
449 Expr::ScalarFunction(f) if is_untyped_json_get_func!(f) => Some(f),
450 _ => None,
451 }
452}
453
454fn extract_json_get_type(expr: &Expr) -> Option<DataType> {
455 match expr {
456 Expr::ScalarFunction(f) if is_typed_json_get_func!(f) => f
457 .args
458 .get(2)
459 .and_then(|x| x.as_literal())
460 .map(|x| x.data_type()),
461 _ => None,
462 }
463}
464
465fn is_untyped_json_get(expr: &Expr) -> bool {
466 matches!(
467 expr,
468 Expr::ScalarFunction(f) if is_untyped_json_get_func!(f)
469 )
470}
471
472fn push_json_get_type_arg(expr: &mut Expr, data_type: &DataType) -> Result<bool> {
473 let Some(json_get) = extract_untyped_json_get(expr) else {
474 return Ok(false);
475 };
476
477 if data_type.is_string() {
480 return Ok(false);
481 }
482 let with_type = ScalarValue::try_new_null(data_type).map(|x| Expr::Literal(x, None))?;
483 json_get.args.push(with_type);
484 Ok(true)
485}
486
487fn parse_sql_op(op: &BinaryOperator) -> Option<Operator> {
488 match *op {
489 BinaryOperator::Plus => Some(Operator::Plus),
490 BinaryOperator::Minus => Some(Operator::Minus),
491 BinaryOperator::Multiply => Some(Operator::Multiply),
492 BinaryOperator::Divide => Some(Operator::Divide),
493 BinaryOperator::Modulo => Some(Operator::Modulo),
494 BinaryOperator::Gt => Some(Operator::Gt),
495 BinaryOperator::GtEq => Some(Operator::GtEq),
496 BinaryOperator::Lt => Some(Operator::Lt),
497 BinaryOperator::LtEq => Some(Operator::LtEq),
498 BinaryOperator::Eq => Some(Operator::Eq),
499 BinaryOperator::NotEq => Some(Operator::NotEq),
500 BinaryOperator::And => Some(Operator::And),
501 BinaryOperator::Or => Some(Operator::Or),
502 BinaryOperator::BitwiseAnd => Some(Operator::BitwiseAnd),
503 BinaryOperator::BitwiseOr => Some(Operator::BitwiseOr),
504 BinaryOperator::BitwiseXor => Some(Operator::BitwiseXor),
505 _ => None,
506 }
507}
508
509#[cfg(test)]
510mod tests {
511 use arrow_schema::Fields;
512 use datafusion::functions_aggregate::count::count_udaf;
513 use datafusion::functions_aggregate::sum::sum_udaf;
514 use datafusion_expr::WindowFrame;
515 use datafusion_functions::core::coalesce;
516 use datafusion_functions::math::{abs, power};
517 use datatypes::extension::json::{Json2ExtensionType, JsonMetadata};
518 use datatypes::json::{JsonSettings, JsonTypeHint};
519 use datatypes::prelude::ConcreteDataType;
520
521 use super::*;
522
523 fn json_get_expr(base: Expr, path: &str) -> Expr {
524 let json_get = Arc::new(create_udf(Arc::new(JsonGetWithType::default())));
525 Expr::ScalarFunction(ScalarFunction::new_udf(
526 json_get,
527 vec![
528 base,
529 Expr::Literal(ScalarValue::Utf8(Some(path.to_string())), None),
530 ],
531 ))
532 }
533
534 #[test]
535 fn test_plan_binary_op() -> Result<()> {
536 let planner = JsonExprPlanner;
537 let schema = DFSchema::from_unqualified_fields(
538 Fields::from(vec![Field::new("value", DataType::Int64, true)]),
539 Default::default(),
540 )?;
541
542 let planned = planner.plan_binary_op(
543 RawBinaryExpr {
544 op: BinaryOperator::Eq,
545 left: json_get_expr(
546 Expr::Literal(ScalarValue::Binary(Some(b"{\"a\": 1}".to_vec())), None),
547 "a",
548 ),
549 right: Expr::Column(Column::new_unqualified("value")),
550 },
551 &schema,
552 )?;
553
554 match planned {
555 PlannerResult::Planned(Expr::BinaryExpr(expr)) => {
556 assert_eq!(expr.op, Operator::Eq);
557
558 match expr.left.as_ref() {
559 Expr::ScalarFunction(func) => {
560 assert_eq!(func.func.name(), JsonGetWithType::NAME);
561 assert_eq!(func.args.len(), 3);
562 assert_eq!(func.args[2], Expr::Literal(ScalarValue::Int64(None), None));
563 }
564 other => panic!("expected json_get on left side, got {other:?}"),
565 }
566
567 assert_eq!(
568 expr.right.as_ref(),
569 &Expr::Column(Column::new_unqualified("value"))
570 );
571 }
572 other => panic!("expected planned binary expression, got {other:?}"),
573 }
574
575 let original = planner.plan_binary_op(
576 RawBinaryExpr {
577 op: BinaryOperator::StringConcat,
578 left: Expr::Column(Column::new_unqualified("value")),
579 right: Expr::Literal(ScalarValue::Utf8(Some("x".to_string())), None),
580 },
581 &schema,
582 )?;
583
584 match original {
585 PlannerResult::Original(expr) => {
586 assert!(matches!(expr.op, BinaryOperator::StringConcat));
587 assert_eq!(expr.left, Expr::Column(Column::new_unqualified("value")));
588 assert_eq!(
589 expr.right,
590 Expr::Literal(ScalarValue::Utf8(Some("x".to_string())), None)
591 );
592 }
593 other => panic!(
594 "expected original expression for unsupported operator, got {:?}",
595 other,
596 ),
597 }
598
599 Ok(())
600 }
601
602 #[test]
603 fn test_plan_list_index() -> Result<()> {
604 let planner = JsonExprPlanner;
605 let planned = planner.plan_field_access(
606 RawFieldAccessExpr {
607 field_access: GetFieldAccess::ListIndex {
608 key: Box::new(Expr::Literal(ScalarValue::Int64(Some(0)), None)),
609 },
610 expr: json_get_expr(Expr::Column(Column::new_unqualified("j")), "list"),
611 },
612 &DFSchema::empty(),
613 )?;
614 let PlannerResult::Planned(Expr::ScalarFunction(func)) = planned else {
615 unreachable!()
616 };
617 assert_eq!(func.func.name(), JsonGetWithType::NAME);
618 assert_eq!(func.args.len(), 2);
619 assert_eq!(
620 func.args[1],
621 Expr::Literal(ScalarValue::Utf8(Some("list[0]".to_string())), None)
622 );
623 Ok(())
624 }
625
626 #[test]
627 fn test_plan_field_after_list_index() -> Result<()> {
628 let planner = JsonExprPlanner;
629 let planned = planner.plan_field_access(
630 RawFieldAccessExpr {
631 field_access: GetFieldAccess::NamedStructField {
632 name: ScalarValue::Utf8(Some("a.b".to_string())),
633 },
634 expr: json_get_expr(Expr::Column(Column::new_unqualified("j")), "list[0]"),
635 },
636 &DFSchema::empty(),
637 )?;
638 let PlannerResult::Planned(Expr::ScalarFunction(func)) = planned else {
639 unreachable!()
640 };
641 assert_eq!(
642 func.args[1],
643 Expr::Literal(
644 ScalarValue::Utf8(Some(r#"list[0]."a.b""#.to_string())),
645 None
646 )
647 );
648 Ok(())
649 }
650
651 #[test]
652 fn test_plan_compound_identifier() -> Result<()> {
653 let planner = JsonExprPlanner;
654 let qualifier = TableReference::bare("events");
655 let nested_names = vec!["payload".to_string(), "cpu".to_string()];
656
657 let planned = planner.plan_compound_identifier(
658 &Field::new("labels", DataType::Struct(Fields::empty()), true)
659 .with_extension_type(Json2ExtensionType::default()),
660 Some(&qualifier),
661 &nested_names,
662 )?;
663
664 match planned {
665 PlannerResult::Planned(Expr::ScalarFunction(func)) => {
666 assert_eq!(func.func.name(), JsonGetWithType::NAME);
667 assert_eq!(func.args.len(), 2);
668 assert_eq!(
669 func.args[0],
670 Expr::Column(Column::new(Some(qualifier.clone()), "labels"))
671 );
672 assert_eq!(
673 func.args[1],
674 Expr::Literal(ScalarValue::Utf8(Some("$.payload.cpu".to_string())), None)
675 );
676 }
677 other => panic!("expected json_get scalar function, got {other:?}"),
678 }
679
680 for (key, path) in [
681 ("http.status_code", r#"$."http.status_code""#),
682 ("a\"b", r#"$."a\"b""#),
683 ("a\\b", r#"$."a\\b""#),
684 ] {
685 let planned = planner.plan_compound_identifier(
686 &Field::new("labels", DataType::Struct(Fields::empty()), true)
687 .with_extension_type(Json2ExtensionType::default()),
688 Some(&qualifier),
689 &[key.to_string()],
690 )?;
691 let PlannerResult::Planned(Expr::ScalarFunction(func)) = planned else {
692 unreachable!()
693 };
694 assert_eq!(
695 func.args[1],
696 Expr::Literal(ScalarValue::Utf8(Some(path.to_string())), None)
697 );
698 }
699
700 let PlannerResult::Planned(Expr::ScalarFunction(func)) = planner.plan_compound_identifier(
701 &Field::new("labels", DataType::Struct(Fields::empty()), true)
702 .with_extension_type(Json2ExtensionType::default()),
703 Some(&qualifier),
704 &["resource".to_string(), "http.status_code".to_string()],
705 )?
706 else {
707 unreachable!()
708 };
709 assert_eq!(
710 func.args[1],
711 Expr::Literal(
712 ScalarValue::Utf8(Some(r#"$.resource."http.status_code""#.to_string())),
713 None
714 )
715 );
716
717 let original = planner.plan_compound_identifier(
718 &Field::new("plain", DataType::Utf8, true),
719 Some(&qualifier),
720 &nested_names,
721 )?;
722
723 match original {
724 PlannerResult::Original(exprs) => assert!(exprs.is_empty()),
725 other => panic!(
726 "expected original empty result for non-json field, got {:?}",
727 other,
728 ),
729 }
730
731 Ok(())
732 }
733
734 #[test]
735 fn test_plan_compound_identifier_applies_json2_type_hint() -> Result<()> {
736 let settings = JsonSettings::try_new(
737 vec![JsonTypeHint {
738 path: vec!["payload".to_string(), "cpu".to_string()],
739 data_type: ConcreteDataType::int64_datatype(),
740 inverted_index: false,
741 }],
742 None,
743 )
744 .unwrap();
745 let field = Field::new("labels", DataType::Struct(Fields::empty()), true)
746 .with_extension_type(Json2ExtensionType::new(Arc::new(JsonMetadata::new(
747 settings,
748 ))));
749
750 let PlannerResult::Planned(Expr::ScalarFunction(func)) = JsonExprPlanner
751 .plan_compound_identifier(
752 &field,
753 Some(&TableReference::bare("events")),
754 &["payload".to_string(), "cpu".to_string()],
755 )?
756 else {
757 unreachable!()
758 };
759
760 assert_eq!(
761 Some(DataType::Int64),
762 extract_json_get_type(&Expr::ScalarFunction(func))
763 );
764 Ok(())
765 }
766
767 #[test]
768 fn test_plan_json_get_applies_type_hint() -> Result<()> {
769 let settings = JsonSettings::try_new(
770 vec![
771 JsonTypeHint {
772 path: vec!["payload".to_string(), "cpu".to_string()],
773 data_type: ConcreteDataType::int64_datatype(),
774 inverted_index: false,
775 },
776 JsonTypeHint {
777 path: vec!["payload.cpu".to_string()],
778 data_type: ConcreteDataType::string_datatype(),
779 inverted_index: false,
780 },
781 ],
782 None,
783 )
784 .unwrap();
785 let field = Field::new("j", DataType::Struct(Fields::empty()), true).with_extension_type(
786 Json2ExtensionType::new(Arc::new(JsonMetadata::new(settings))),
787 );
788 let relation = TableReference::bare("events");
789 let schema = DFSchema::new_with_metadata(
790 vec![
791 (Some(relation.clone()), Arc::new(field.clone())),
792 (
793 Some(TableReference::bare("other")),
794 Arc::new(Field::new("j", DataType::Binary, true)),
795 ),
796 ],
797 Default::default(),
798 )?;
799 let base = Expr::Column(Column::new(Some(relation.clone()), "j"));
800
801 for (path, names, expected) in [
802 ("payload.cpu", vec!["payload", "cpu"], Some(DataType::Int64)),
803 (
804 "$.payload.cpu",
805 vec!["payload", "cpu"],
806 Some(DataType::Int64),
807 ),
808 (
809 r#"$."payload.cpu""#,
810 vec!["payload.cpu"],
811 Some(DataType::Utf8View),
812 ),
813 ("missing", vec!["missing"], None),
814 ] {
815 let Expr::ScalarFunction(function) = json_get_expr(base.clone(), path) else {
816 unreachable!();
817 };
818 let PlannerResult::Original(scalar) = JsonExprPlanner.plan_scalar(
819 RawScalarExpr {
820 func: function.func,
821 args: function.args,
822 },
823 &schema,
824 )?
825 else {
826 unreachable!();
827 };
828 let direct = Expr::ScalarFunction(ScalarFunction::new_udf(scalar.func, scalar.args));
829 let names = names.into_iter().map(String::from).collect::<Vec<_>>();
830 let PlannerResult::Planned(dotted) =
831 JsonExprPlanner.plan_compound_identifier(&field, Some(&relation), &names)?
832 else {
833 unreachable!();
834 };
835 assert_eq!(expected, extract_json_get_type(&direct), "{path}");
836 assert_eq!(
837 extract_json_get_type(&dotted),
838 extract_json_get_type(&direct)
839 );
840
841 if expected.is_some() {
843 let PlannerResult::Original(outer) = JsonExprPlanner.plan_scalar(
844 RawScalarExpr {
845 func: coalesce(),
846 args: vec![
847 direct.clone(),
848 Expr::Literal(ScalarValue::Float64(Some(1.0)), None),
849 ],
850 },
851 &schema,
852 )?
853 else {
854 unreachable!();
855 };
856 assert_eq!(direct, outer.args[0]);
857 }
858 }
859
860 for (base, path) in [
863 (base.clone(), "$"),
864 (base.clone(), "$.payload.*"),
865 (base.clone(), "$.payload.cpu[0]"),
866 (base.clone(), "$.payload["),
867 (Expr::Column(Column::new(Some("other"), "j")), "payload.cpu"),
868 ] {
869 let Expr::ScalarFunction(function) = json_get_expr(base, path) else {
870 unreachable!();
871 };
872 let PlannerResult::Original(scalar) = JsonExprPlanner.plan_scalar(
873 RawScalarExpr {
874 func: function.func,
875 args: function.args,
876 },
877 &schema,
878 )?
879 else {
880 unreachable!();
881 };
882 assert_eq!(2, scalar.args.len(), "{path}");
883 }
884 Ok(())
885 }
886
887 #[test]
888 fn test_plan_functions() -> Result<()> {
889 let planner = JsonExprPlanner;
890 let json_get = || json_get_expr(Expr::Column(Column::new_unqualified("j")), "a.b");
891
892 let PlannerResult::Original(scalar) = planner.plan_scalar(
893 RawScalarExpr {
894 func: abs(),
895 args: vec![json_get()],
896 },
897 &DFSchema::empty(),
898 )?
899 else {
900 unreachable!();
901 };
902 assert_eq!(
903 Some(DataType::Float64),
904 extract_json_get_type(&scalar.args[0])
905 );
906
907 let PlannerResult::Original(scalar) = planner.plan_scalar(
908 RawScalarExpr {
909 func: power(),
910 args: vec![
911 json_get(),
912 Expr::Column(Column::new_unqualified("exponent")),
913 ],
914 },
915 &DFSchema::empty(),
916 )?
917 else {
918 unreachable!();
919 };
920 assert_eq!(
921 Some(DataType::Float64),
922 extract_json_get_type(&scalar.args[0])
923 );
924
925 let PlannerResult::Original(aggregate) = planner.plan_aggregate(RawAggregateExpr {
926 func: sum_udaf(),
927 args: vec![json_get()],
928 distinct: false,
929 filter: None,
930 order_by: vec![],
931 null_treatment: None,
932 })?
933 else {
934 unreachable!();
935 };
936 assert_eq!(
937 Some(DataType::Float64),
938 extract_json_get_type(&aggregate.args[0])
939 );
940
941 let PlannerResult::Original(count) = planner.plan_aggregate(RawAggregateExpr {
942 func: count_udaf(),
943 args: vec![json_get()],
944 distinct: false,
945 filter: None,
946 order_by: vec![],
947 null_treatment: None,
948 })?
949 else {
950 unreachable!();
951 };
952 assert_eq!(None, extract_json_get_type(&count.args[0]));
953
954 let PlannerResult::Original(window) = planner.plan_window(RawWindowExpr {
955 func_def: WindowFunctionDefinition::AggregateUDF(sum_udaf()),
956 args: vec![json_get()],
957 partition_by: vec![],
958 order_by: vec![],
959 window_frame: WindowFrame::new(None),
960 filter: None,
961 null_treatment: None,
962 distinct: false,
963 })?
964 else {
965 unreachable!();
966 };
967 assert_eq!(
968 Some(DataType::Float64),
969 extract_json_get_type(&window.args[0])
970 );
971 Ok(())
972 }
973
974 #[test]
975 fn test_plan_function_with_mixed_json_get_types() -> Result<()> {
976 let planner = JsonExprPlanner;
977 let json_get = || json_get_expr(Expr::Column(Column::new_unqualified("j")), "a.b");
978 let mut typed = json_get();
979 push_json_get_type_arg(&mut typed, &DataType::Float64)?;
980
981 let PlannerResult::Original(scalar) = planner.plan_scalar(
982 RawScalarExpr {
983 func: coalesce(),
984 args: vec![json_get(), typed],
985 },
986 &DFSchema::empty(),
987 )?
988 else {
989 unreachable!();
990 };
991 assert_eq!(
992 Some(DataType::Float64),
993 extract_json_get_type(&scalar.args[0])
994 );
995 assert_eq!(
996 Some(DataType::Float64),
997 extract_json_get_type(&scalar.args[1])
998 );
999 Ok(())
1000 }
1001}