JingsongLi commented on code in PR #499:
URL: https://github.com/apache/paimon-rust/pull/499#discussion_r3563699800


##########
crates/integrations/datafusion/src/sql_context.rs:
##########
@@ -1430,6 +1442,165 @@ impl SQLContext {
         }
     }
 
+    async fn handle_create_function(
+        &self,
+        create_function: &CreateFunction,
+    ) -> DFResult<DataFrame> {
+        validate_persistent_create_function(create_function)?;
+        if create_function
+            .language
+            .as_ref()
+            .is_some_and(|language| 
!language.value.eq_ignore_ascii_case("sql"))
+        {
+            return Err(DataFusionError::Plan(
+                "CREATE FUNCTION only supports LANGUAGE SQL".to_string(),
+            ));
+        }
+        if matches!(
+            create_function.behavior,
+            Some(FunctionBehavior::Stable | FunctionBehavior::Volatile)
+        ) {
+            return Err(DataFusionError::Plan(
+                "CREATE FUNCTION only supports deterministic SQL 
functions".to_string(),
+            ));
+        }
+        let FunctionReturnType::DataType(return_type) = create_function
+            .return_type
+            .as_ref()
+            .ok_or_else(|| DataFusionError::Plan("CREATE FUNCTION requires 
RETURNS".to_string()))?
+        else {
+            return Err(DataFusionError::Plan(
+                "CREATE FUNCTION SETOF return types are not 
supported".to_string(),
+            ));
+        };
+        let CreateFunctionBody::Return(body) = create_function
+            .function_body
+            .as_ref()
+            .ok_or_else(|| DataFusionError::Plan("CREATE FUNCTION requires 
RETURN".to_string()))?
+        else {
+            return Err(DataFusionError::Plan(
+                "CREATE FUNCTION only supports a RETURN 
expression".to_string(),
+            ));
+        };
+
+        let mut parameter_names = HashSet::new();
+        let input_params = create_function
+            .args
+            .as_deref()
+            .unwrap_or_default()
+            .iter()
+            .enumerate()
+            .map(|(id, argument)| {
+                if argument.mode.is_some() || argument.default_expr.is_some() {
+                    return Err(DataFusionError::Plan(
+                        "CREATE FUNCTION argument modes and defaults are not 
supported".to_string(),
+                    ));
+                }
+                let name = argument
+                    .name
+                    .clone()
+                    .map(normalize_create_function_argument_name)
+                    .ok_or_else(|| {
+                        DataFusionError::Plan(
+                            "CREATE FUNCTION arguments must have 
names".to_string(),
+                        )
+                    })?;
+                if !parameter_names.insert(name.clone()) {
+                    return Err(DataFusionError::Plan(format!(
+                        "duplicate function argument name '{name}'"
+                    )));
+                }
+                Ok(PaimonDataField::new(
+                    id as i32,
+                    name,
+                    sql_data_type_to_paimon_type(&argument.data_type, true)?,
+                ))
+            })
+            .collect::<DFResult<Vec<_>>>()?;
+        let return_params = vec![PaimonDataField::new(
+            0,
+            "result".to_string(),
+            sql_data_type_to_paimon_type(return_type, true)?,
+        )];
+        let (catalog, catalog_name, identifier) =
+            self.resolve_catalog_and_function(&create_function.name)?;
+        let function = paimon::catalog::Function::new(
+            identifier,
+            Some(input_params),
+            Some(return_params),
+            true,
+            HashMap::from([(
+                "datafusion".to_string(),
+                paimon::catalog::FunctionDefinition::Sql {
+                    definition: body.to_string(),
+                },
+            )]),
+            None,
+            HashMap::new(),
+        );
+        self.validate_create_function(&function, &catalog_name)
+            .await?;
+        catalog
+            .create_function(&function, create_function.if_not_exists)
+            .await
+            .map_err(to_datafusion_error)?;
+        ok_result(&self.ctx)
+    }
+
+    async fn validate_create_function(
+        &self,
+        function: &paimon::catalog::Function,
+        catalog_name: &str,
+    ) -> DFResult<()> {
+        let arguments = function
+            .input_params()
+            .unwrap_or_default()
+            .iter()
+            .map(|field| {
+                let value = 
serde_json::to_value(field.data_type()).map_err(|error| {
+                    DataFusionError::Plan(format!(
+                        "Invalid CREATE FUNCTION argument type '{:?}': 
{error}",
+                        field.data_type()
+                    ))
+                })?;
+                let sql_type = value.as_str().ok_or_else(|| {

Review Comment:
   Fixed in b0dbc50. CREATE FUNCTION validation placeholders and REST SQL 
function return casts now both reuse the recursive table::data_type_to_sql 
renderer, so ARRAY/MAP/ROW types no longer depend on JSON string serialization. 
Added regression tests for ARRAY input creation and ARRAY return 
creation/execution. Verified 17 persistent CREATE FUNCTION tests, 15 REST SQL 
function tests, fmt/diff checks, and clippy with -D warnings.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to