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