This is an automated email from the ASF dual-hosted git repository.

jayzhan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new fe66daaa81 Support DuckDB style struct syntax (#11214)
fe66daaa81 is described below

commit fe66daaa81738dc60afc297a79897faaaa127724
Author: Jay Zhan <[email protected]>
AuthorDate: Thu Jul 4 07:29:41 2024 +0800

    Support DuckDB style struct syntax (#11214)
    
    * struct literal
    
    Signed-off-by: jayzhan211 <[email protected]>
    
    * add nested
    
    Signed-off-by: jayzhan211 <[email protected]>
    
    * fmt
    
    Signed-off-by: jayzhan211 <[email protected]>
    
    * rm useless comment
    
    Signed-off-by: jayzhan211 <[email protected]>
    
    * switch to NYI error, derive debug/clone
    
    * improve documentation strings
    
    * Avoid stack overflow by putting code in a new function
    
    ---------
    
    Signed-off-by: jayzhan211 <[email protected]>
    Co-authored-by: Andrew Lamb <[email protected]>
---
 datafusion/core/src/execution/session_state.rs     | 30 ++++++------------
 datafusion/expr/src/planner.rs                     | 37 +++++++++++++++++++---
 datafusion/functions/src/core/mod.rs               |  1 +
 .../functions/src/{datetime => core}/planner.rs    | 30 ++++++++++--------
 datafusion/functions/src/datetime/planner.rs       |  2 +-
 datafusion/sql/src/expr/mod.rs                     | 37 +++++++++++++++++++++-
 datafusion/sqllogictest/test_files/struct.slt      | 25 +++++++++++++++
 7 files changed, 122 insertions(+), 40 deletions(-)

diff --git a/datafusion/core/src/execution/session_state.rs 
b/datafusion/core/src/execution/session_state.rs
index 8e8adc32ab..a831f92def 100644
--- a/datafusion/core/src/execution/session_state.rs
+++ b/datafusion/core/src/execution/session_state.rs
@@ -231,26 +231,16 @@ impl SessionState {
             );
         }
 
-        let mut user_defined_sql_planners = vec![];
-
-        // register crate of array expressions (if enabled)
-        #[cfg(feature = "array_expressions")]
-        {
-            let array_planner =
-                Arc::new(functions_array::planner::ArrayFunctionPlanner) as _;
-
-            let field_access_planner =
-                Arc::new(functions_array::planner::FieldAccessPlanner) as _;
-
-            user_defined_sql_planners.extend(vec![array_planner, 
field_access_planner]);
-        }
-        #[cfg(feature = "datetime_expressions")]
-        {
-            let extract_planner =
-                
Arc::new(functions::datetime::planner::ExtractPlanner::default()) as _;
-
-            user_defined_sql_planners.push(extract_planner);
-        }
+        let user_defined_sql_planners: Vec<Arc<dyn UserDefinedSQLPlanner>> = 
vec![
+            Arc::new(functions::core::planner::CoreFunctionPlanner::default()),
+            // register crate of array expressions (if enabled)
+            #[cfg(feature = "array_expressions")]
+            Arc::new(functions_array::planner::ArrayFunctionPlanner),
+            #[cfg(feature = "array_expressions")]
+            Arc::new(functions_array::planner::FieldAccessPlanner),
+            #[cfg(feature = "datetime_expressions")]
+            Arc::new(functions::datetime::planner::ExtractPlanner),
+        ];
 
         let mut new_self = SessionState {
             session_id,
diff --git a/datafusion/expr/src/planner.rs b/datafusion/expr/src/planner.rs
index 873085c585..bba0228ae0 100644
--- a/datafusion/expr/src/planner.rs
+++ b/datafusion/expr/src/planner.rs
@@ -84,7 +84,8 @@ pub trait ContextProvider {
 
 /// This trait allows users to customize the behavior of the SQL planner
 pub trait UserDefinedSQLPlanner: Send + Sync {
-    /// Plan the binary operation between two expressions, returns 
OriginalBinaryExpr if not possible
+    /// Plan the binary operation between two expressions, returns original
+    /// BinaryExpr if not possible
     fn plan_binary_op(
         &self,
         expr: RawBinaryExpr,
@@ -93,7 +94,9 @@ pub trait UserDefinedSQLPlanner: Send + Sync {
         Ok(PlannerResult::Original(expr))
     }
 
-    /// Plan the field access expression, returns OriginalFieldAccessExpr if 
not possible
+    /// Plan the field access expression
+    ///
+    /// returns original FieldAccessExpr if not possible
     fn plan_field_access(
         &self,
         expr: RawFieldAccessExpr,
@@ -102,7 +105,9 @@ pub trait UserDefinedSQLPlanner: Send + Sync {
         Ok(PlannerResult::Original(expr))
     }
 
-    // Plan the array literal, returns OriginalArray if not possible
+    /// Plan the array literal, returns OriginalArray if not possible
+    ///
+    /// Returns origin expression arguments if not possible
     fn plan_array_literal(
         &self,
         exprs: Vec<Expr>,
@@ -111,8 +116,20 @@ pub trait UserDefinedSQLPlanner: Send + Sync {
         Ok(PlannerResult::Original(exprs))
     }
 
-    // Plan the Extract expression, e.g., EXTRACT(month FROM foo)
-    // returns origin expression arguments if not possible
+    /// Plan the dictionary literal `{ key: value, ...}`
+    ///
+    /// Returns origin expression arguments if not possible
+    fn plan_dictionary_literal(
+        &self,
+        expr: RawDictionaryExpr,
+        _schema: &DFSchema,
+    ) -> Result<PlannerResult<RawDictionaryExpr>> {
+        Ok(PlannerResult::Original(expr))
+    }
+
+    /// Plan an extract expression, e.g., `EXTRACT(month FROM foo)`
+    ///
+    /// Returns origin expression arguments if not possible
     fn plan_extract(&self, args: Vec<Expr>) -> 
Result<PlannerResult<Vec<Expr>>> {
         Ok(PlannerResult::Original(args))
     }
@@ -142,6 +159,16 @@ pub struct RawFieldAccessExpr {
     pub expr: Expr,
 }
 
+/// A Dictionary literal expression `{ key: value, ...}`
+///
+/// This structure is used by [`UserDefinedSQLPlanner`] to plan operators with
+/// custom expressions.
+#[derive(Debug, Clone)]
+pub struct RawDictionaryExpr {
+    pub keys: Vec<Expr>,
+    pub values: Vec<Expr>,
+}
+
 /// Result of planning a raw expr with [`UserDefinedSQLPlanner`]
 #[derive(Debug, Clone)]
 pub enum PlannerResult<T> {
diff --git a/datafusion/functions/src/core/mod.rs 
b/datafusion/functions/src/core/mod.rs
index a2742220f3..5761a4bd39 100644
--- a/datafusion/functions/src/core/mod.rs
+++ b/datafusion/functions/src/core/mod.rs
@@ -29,6 +29,7 @@ pub mod named_struct;
 pub mod nullif;
 pub mod nvl;
 pub mod nvl2;
+pub mod planner;
 pub mod r#struct;
 
 // create UDFs
diff --git a/datafusion/functions/src/datetime/planner.rs 
b/datafusion/functions/src/core/planner.rs
similarity index 58%
copy from datafusion/functions/src/datetime/planner.rs
copy to datafusion/functions/src/core/planner.rs
index 5f2a5d71d7..71f6c7f8ac 100644
--- a/datafusion/functions/src/datetime/planner.rs
+++ b/datafusion/functions/src/core/planner.rs
@@ -15,22 +15,26 @@
 // specific language governing permissions and limitations
 // under the License.
 
-//! SQL planning extensions like [`ExtractPlanner`]
-
+use datafusion_common::DFSchema;
 use datafusion_common::Result;
-use datafusion_expr::{
-    expr::ScalarFunction,
-    planner::{PlannerResult, UserDefinedSQLPlanner},
-    Expr,
-};
+use datafusion_expr::planner::{PlannerResult, RawDictionaryExpr, 
UserDefinedSQLPlanner};
+
+use super::named_struct;
 
 #[derive(Default)]
-pub struct ExtractPlanner {}
+pub struct CoreFunctionPlanner {}
 
-impl UserDefinedSQLPlanner for ExtractPlanner {
-    fn plan_extract(&self, args: Vec<Expr>) -> 
Result<PlannerResult<Vec<Expr>>> {
-        Ok(PlannerResult::Planned(Expr::ScalarFunction(
-            ScalarFunction::new_udf(crate::datetime::date_part(), args),
-        )))
+impl UserDefinedSQLPlanner for CoreFunctionPlanner {
+    fn plan_dictionary_literal(
+        &self,
+        expr: RawDictionaryExpr,
+        _schema: &DFSchema,
+    ) -> Result<PlannerResult<RawDictionaryExpr>> {
+        let mut args = vec![];
+        for (k, v) in expr.keys.into_iter().zip(expr.values.into_iter()) {
+            args.push(k);
+            args.push(v);
+        }
+        Ok(PlannerResult::Planned(named_struct().call(args)))
     }
 }
diff --git a/datafusion/functions/src/datetime/planner.rs 
b/datafusion/functions/src/datetime/planner.rs
index 5f2a5d71d7..4265ce42a5 100644
--- a/datafusion/functions/src/datetime/planner.rs
+++ b/datafusion/functions/src/datetime/planner.rs
@@ -25,7 +25,7 @@ use datafusion_expr::{
 };
 
 #[derive(Default)]
-pub struct ExtractPlanner {}
+pub struct ExtractPlanner;
 
 impl UserDefinedSQLPlanner for ExtractPlanner {
     fn plan_extract(&self, args: Vec<Expr>) -> 
Result<PlannerResult<Vec<Expr>>> {
diff --git a/datafusion/sql/src/expr/mod.rs b/datafusion/sql/src/expr/mod.rs
index 24c62a2ea6..2ddd2d22c0 100644
--- a/datafusion/sql/src/expr/mod.rs
+++ b/datafusion/sql/src/expr/mod.rs
@@ -18,8 +18,11 @@
 use arrow_schema::DataType;
 use arrow_schema::TimeUnit;
 use datafusion_expr::planner::PlannerResult;
+use datafusion_expr::planner::RawDictionaryExpr;
 use datafusion_expr::planner::RawFieldAccessExpr;
-use sqlparser::ast::{CastKind, Expr as SQLExpr, Subscript, TrimWhereField, 
Value};
+use sqlparser::ast::{
+    CastKind, DictionaryField, Expr as SQLExpr, Subscript, TrimWhereField, 
Value,
+};
 
 use datafusion_common::{
     internal_datafusion_err, internal_err, not_impl_err, plan_err, DFSchema, 
Result,
@@ -619,10 +622,42 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
                     }
                 },
             ))),
+            SQLExpr::Dictionary(fields) => {
+                self.try_plan_dictionary_literal(fields, schema, 
planner_context)
+            }
             _ => not_impl_err!("Unsupported ast node in sqltorel: {sql:?}"),
         }
     }
 
+    fn try_plan_dictionary_literal(
+        &self,
+        fields: Vec<DictionaryField>,
+        schema: &DFSchema,
+        planner_context: &mut PlannerContext,
+    ) -> Result<Expr> {
+        let mut keys = vec![];
+        let mut values = vec![];
+        for field in fields {
+            let key = lit(field.key.value);
+            let value =
+                self.sql_expr_to_logical_expr(*field.value, schema, 
planner_context)?;
+            keys.push(key);
+            values.push(value);
+        }
+
+        let mut raw_expr = RawDictionaryExpr { keys, values };
+
+        for planner in self.planners.iter() {
+            match planner.plan_dictionary_literal(raw_expr, schema)? {
+                PlannerResult::Planned(expr) => {
+                    return Ok(expr);
+                }
+                PlannerResult::Original(expr) => raw_expr = expr,
+            }
+        }
+        not_impl_err!("Unsupported dictionary literal: {raw_expr:?}")
+    }
+
     /// Parses a struct(..) expression
     fn parse_struct(
         &self,
diff --git a/datafusion/sqllogictest/test_files/struct.slt 
b/datafusion/sqllogictest/test_files/struct.slt
index 749daa7e20..fd6e25ea74 100644
--- a/datafusion/sqllogictest/test_files/struct.slt
+++ b/datafusion/sqllogictest/test_files/struct.slt
@@ -162,6 +162,13 @@ select named_struct('scalar', 27, 'array', values.a, 
'null', NULL) from values;
 {scalar: 27, array: 2, null: }
 {scalar: 27, array: 3, null: }
 
+query ?
+select {'scalar': 27, 'array': values.a, 'null': NULL} from values;
+----
+{scalar: 27, array: 1, null: }
+{scalar: 27, array: 2, null: }
+{scalar: 27, array: 3, null: }
+
 # named_struct with mixed scalar and array values #2
 query ?
 select named_struct('array', values.a, 'scalar', 27, 'null', NULL) from values;
@@ -170,6 +177,13 @@ select named_struct('array', values.a, 'scalar', 27, 
'null', NULL) from values;
 {array: 2, scalar: 27, null: }
 {array: 3, scalar: 27, null: }
 
+query ?
+select {'array': values.a, 'scalar': 27, 'null': NULL} from values;
+----
+{array: 1, scalar: 27, null: }
+{array: 2, scalar: 27, null: }
+{array: 3, scalar: 27, null: }
+
 # named_struct with mixed scalar and array values #3
 query ?
 select named_struct('null', NULL, 'array', values.a, 'scalar', 27) from values;
@@ -207,3 +221,14 @@ query T
 select arrow_typeof(named_struct('first', 1, 'second', 2, 'third', 3));
 ----
 Struct([Field { name: "first", data_type: Int64, nullable: true, dict_id: 0, 
dict_is_ordered: false, metadata: {} }, Field { name: "second", data_type: 
Int64, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: {} }, 
Field { name: "third", data_type: Int64, nullable: true, dict_id: 0, 
dict_is_ordered: false, metadata: {} }])
+
+query T
+select arrow_typeof({'first': 1, 'second': 2, 'third': 3});
+----
+Struct([Field { name: "first", data_type: Int64, nullable: true, dict_id: 0, 
dict_is_ordered: false, metadata: {} }, Field { name: "second", data_type: 
Int64, nullable: true, dict_id: 0, dict_is_ordered: false, metadata: {} }, 
Field { name: "third", data_type: Int64, nullable: true, dict_id: 0, 
dict_is_ordered: false, metadata: {} }])
+
+# test nested struct literal
+query ?
+select {'animal': {'cat': 1, 'dog': 2, 'bird': {'parrot': 3, 'canary': 1}}, 
'genre': {'fiction': ['mystery', 'sci-fi', 'fantasy'], 'non-fiction': 
{'biography': 5, 'history': 7, 'science': {'physics': 2, 'biology': 3}}}, 
'vehicle': {'car': {'sedan': 4, 'suv': 2}, 'bicycle': 3, 'boat': ['sailboat', 
'motorboat']}, 'weather': {'sunny': True, 'temperature': 25.5, 'wind': 
{'speed': 10, 'direction': 'NW'}}};
+----
+{animal: {cat: 1, dog: 2, bird: {parrot: 3, canary: 1}}, genre: {fiction: 
[mystery, sci-fi, fantasy], non-fiction: {biography: 5, history: 7, science: 
{physics: 2, biology: 3}}}, vehicle: {car: {sedan: 4, suv: 2}, bicycle: 3, 
boat: [sailboat, motorboat]}, weather: {sunny: true, temperature: 25.5, wind: 
{speed: 10, direction: NW}}}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to