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


##########
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:
   Complex signature types are accepted by the DDL parser and converted into 
Paimon `DataType`s, but this assumes every serialized type is a JSON string. 
`ARRAY`, `MAP`, and `ROW` serialize as structured JSON, so, for example, 
`CREATE FUNCTION array_answer(x ARRAY<BIGINT>) RETURNS BIGINT RETURN 42` fails 
here before the REST request is sent. The same assumption in `sql_function.rs` 
also makes complex return types unusable (for example, `RETURNS 
ARRAY<BIGINT>`). Please reuse/extract the recursive SQL type renderer currently 
in `table/mod.rs` for both validation placeholders and return casts, or 
explicitly reject and document unsupported complex signature types, with 
regression tests for both argument and return types.



-- 
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