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

JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new 69b68a91 fix(datafusion): reject unknown arguments for tag and index 
procedures (#917)
69b68a91 is described below

commit 69b68a91aed0efd201aad14254bc697251151349
Author: jackylee <[email protected]>
AuthorDate: Thu Sep 24 10:22:08 2026 +0800

    fix(datafusion): reject unknown arguments for tag and index procedures 
(#917)
---
 crates/integrations/datafusion/src/procedures.rs | 84 ++++++++++++++++++++----
 1 file changed, 72 insertions(+), 12 deletions(-)

diff --git a/crates/integrations/datafusion/src/procedures.rs 
b/crates/integrations/datafusion/src/procedures.rs
index 3ea1f6c6..369c81d9 100644
--- a/crates/integrations/datafusion/src/procedures.rs
+++ b/crates/integrations/datafusion/src/procedures.rs
@@ -159,9 +159,28 @@ async fn earlier_or_equal_from_all(
     }
 }
 
-/// The parameter names Java declares for each management procedure, in Java's 
order.
-fn management_parameters(proc_name: &str) -> Option<&'static [&'static str]> {
+/// The parameter names each `sys.*` procedure declares, in Java's order. Java 
rejects a
+/// CALL argument that no parameter declares, so a typo cannot be dropped in 
silence: a
+/// misspelled `snapshot_id` on `create_tag` would otherwise tag the latest 
snapshot, and
+/// a misspelled `index_type` on `create_global_index` would build the default 
index.
+fn declared_parameters(proc_name: &str) -> Option<&'static [&'static str]> {
     Some(match proc_name {
+        "create_tag" => &["table", "tag", "snapshot_id"],
+        "delete_tag" => &["table", "tag"],
+        "rollback_to" => &["table", "snapshot_id", "tag"],
+        "rollback_to_timestamp" => &["table", "timestamp"],
+        "create_tag_from_timestamp" => &["table", "tag", "timestamp"],
+        "create_global_index" => &["table", "index_column", "index_type", 
"options"],
+        // `partitions`/`dry_run` are declared but not yet implemented; they 
still reach
+        // their own "not supported yet" error rather than being reported as 
unknown.
+        "drop_global_index" => &[
+            "table",
+            "index_column",
+            "index_type",
+            "partitions",
+            "dry_run",
+        ],
+        "create_lumina_index" => &["table", "index_column", "index_type", 
"options"],
         "grant_permission" => &[
             "resource_type",
             "access",
@@ -224,6 +243,21 @@ fn management_parameters(proc_name: &str) -> 
Option<&'static [&'static str]> {
     })
 }
 
+/// Reject a CALL argument that no parameter of `proc_name` declares, matching 
Java's
+/// `ProcedureBase` argument binding. Applies to every procedure with a 
declared parameter
+/// set; a procedure absent from [`declared_parameters`] is left unchecked.
+fn reject_unknown_args(proc_name: &str, args: &HashMap<String, String>) -> 
DFResult<()> {
+    if let Some(declared) = declared_parameters(proc_name) {
+        if let Some(unknown) = args.keys().find(|key| 
!declared.contains(&key.as_str())) {
+            return Err(DataFusionError::Plan(format!(
+                "Argument {unknown} is unknown. Expected one of [{}].",
+                declared.join(", ")
+            )));
+        }
+    }
+    Ok(())
+}
+
 pub async fn execute_call(
     ctx: &SessionContext,
     catalogs: &HashMap<String, Arc<dyn Catalog>>,
@@ -237,16 +271,7 @@ pub async fn execute_call(
         .ok_or_else(|| DataFusionError::Plan(format!("Unknown catalog 
'{catalog_name}'")))?;
     let args = extract_named_args(&func.args)?;
 
-    // Java rejects an argument name no parameter declares, so a typo cannot 
be dropped in
-    // silence. `expiretime => ...` on a grant would otherwise send a 
permanent one.
-    if let Some(declared) = management_parameters(&proc_name) {
-        if let Some(unknown) = args.keys().find(|key| 
!declared.contains(&key.as_str())) {
-            return Err(DataFusionError::Plan(format!(
-                "Argument {unknown} is unknown. Expected one of [{}].",
-                declared.join(", ")
-            )));
-        }
-    }
+    reject_unknown_args(&proc_name, &args)?;
 
     match proc_name.as_str() {
         "create_tag" => proc_create_tag(ctx, catalog, catalog_name, 
&args).await,
@@ -1353,4 +1378,39 @@ mod tests {
         // The predicate requires a canonical input; callers normalize first.
         assert!(!is_scalar_global_index_type("BTREE"));
     }
+
+    #[test]
+    fn test_reject_unknown_args_covers_tag_and_index_procedures() {
+        // A typo'd optional arg on a tag/index procedure used to be silently 
dropped:
+        // `create_tag(..., snapshotid => 5)` tagged the latest snapshot, not 
snapshot 5.
+        let typo = HashMap::from([
+            ("table".to_string(), "db.t".to_string()),
+            ("snapshotid".to_string(), "5".to_string()),
+        ]);
+        let err = reject_unknown_args("create_tag", &typo).unwrap_err();
+        assert!(err.to_string().contains("snapshotid"), "{err}");
+
+        // The correctly spelled argument is accepted.
+        let ok = HashMap::from([
+            ("table".to_string(), "db.t".to_string()),
+            ("snapshot_id".to_string(), "5".to_string()),
+        ]);
+        reject_unknown_args("create_tag", &ok).unwrap();
+
+        // `create_global_index` index_type typo is rejected (was: silent 
default btree).
+        let idx_typo = HashMap::from([
+            ("table".to_string(), "db.t".to_string()),
+            ("index_column".to_string(), "id".to_string()),
+            ("index_typ".to_string(), "bitmap".to_string()),
+        ]);
+        assert!(reject_unknown_args("create_global_index", 
&idx_typo).is_err());
+
+        // A management procedure is still checked, unchanged.
+        let mgmt_typo = HashMap::from([("resourcetype".to_string(), 
"TABLE".to_string())]);
+        assert!(reject_unknown_args("grant_permission", &mgmt_typo).is_err());
+
+        // A name with no declared parameter set is left unchecked -- no false 
positive.
+        let other = HashMap::from([("anything".to_string(), "x".to_string())]);
+        reject_unknown_args("not_a_procedure", &other).unwrap();
+    }
 }

Reply via email to