You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/10/01 18:23:44 UTC

(datafusion) branch main updated: feat: add builders for `CreateExternalCatalog` and `DropCatalog` (#25785)

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

github-merge-queue[bot] 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 141663e8c4 feat: add builders for `CreateExternalCatalog` and `DropCatalog` (#25785)
141663e8c4 is described below

commit 141663e8c49a484ae84ef4cf67ede80ab56b85d0
Author: TTyChud <10...@users.noreply.github.com>
AuthorDate: Thu Oct 1 18:13:59 2026 +0000

    feat: add builders for `CreateExternalCatalog` and `DropCatalog` (#25785)
    
    ## Which issue does this PR close?
    
    - Closes #25627.
    
    ## Rationale for this change
    
    These two structs can only be built by setting all seven fields by hand,
    including
    a `schema` field that is always empty. The builders let you set only
    what you
    need, the same way `CreateExternalTable::builder` already does.
    
    ## What changes are included in this PR?
    
    - Adds `CreateExternalCatalog::builder` and `DropCatalog::builder`, with
    `must_use`
      `with_*` setters and `build()`.
    - Uses them in `CREATE EXTERNAL CATALOG`, `DROP CATALOG` and `DROP
    DATABASE`
      (`datafusion/sql/src/statement.rs`).
    
    ## What is the testing strategy for this PR?
    
    - 5 unit tests in `datafusion/expr/src/logical_plan/ddl.rs` covering the
    builder
    defaults, every `with_*` setter, and that `with_location(None)` unsets
    the location.
    - New doc examples on both builders, run by `cargo test -p
    datafusion-expr --doc`.
    - `cargo fmt --all -- --check`, `cargo test -p datafusion-expr --lib
    ddl::test` and
    `cargo clippy -p datafusion-expr -p datafusion-sql --all-targets
    --no-deps -- -D warnings`
      pass locally.
    - Existing catalog tests are unchanged.
    
    ## Are there any user-facing changes?
    
    Two new public builders. Nothing removed or changed.
---
 datafusion/expr/src/logical_plan/ddl.rs | 229 +++++++++++++++++++++++++++++++-
 datafusion/sql/src/statement.rs         |  42 +++---
 2 files changed, 249 insertions(+), 22 deletions(-)

diff --git a/datafusion/expr/src/logical_plan/ddl.rs b/datafusion/expr/src/logical_plan/ddl.rs
index 16c46d31c2..d0f2a1566e 100644
--- a/datafusion/expr/src/logical_plan/ddl.rs
+++ b/datafusion/expr/src/logical_plan/ddl.rs
@@ -30,7 +30,7 @@ use crate::sql::Ident;
 use arrow::datatypes::DataType;
 use datafusion_common::tree_node::{Transformed, TreeNodeContainer, TreeNodeRecursion};
 use datafusion_common::{
-    Constraints, DFSchemaRef, Result, SchemaReference, TableReference,
+    Constraints, DFSchema, DFSchemaRef, Result, SchemaReference, TableReference,
 };
 #[cfg(feature = "sql")]
 use sqlparser::ast::Ident;
@@ -615,6 +615,99 @@ impl PartialOrd for CreateExternalCatalog {
     }
 }
 
+impl CreateExternalCatalog {
+    /// Creates a builder for [`CreateExternalCatalog`].
+    ///
+    /// # Example
+    /// ```
+    /// # use datafusion_expr::CreateExternalCatalog;
+    /// let cmd = CreateExternalCatalog::builder("my_catalog", "memory")
+    ///     .with_if_not_exists(true)
+    ///     .build();
+    /// assert_eq!(cmd.catalog_name, "my_catalog");
+    /// ```
+    #[must_use]
+    pub fn builder(
+        catalog_name: impl Into<String>,
+        catalog_type: impl Into<String>,
+    ) -> CreateExternalCatalogBuilder {
+        CreateExternalCatalogBuilder {
+            catalog_name: catalog_name.into(),
+            catalog_type: catalog_type.into(),
+            location: None,
+            if_not_exists: false,
+            or_replace: false,
+            options: HashMap::new(),
+            schema: Arc::new(DFSchema::empty()),
+        }
+    }
+}
+
+/// Builder for [`CreateExternalCatalog`].
+#[derive(Debug, Clone)]
+pub struct CreateExternalCatalogBuilder {
+    catalog_name: String,
+    catalog_type: String,
+    location: Option<String>,
+    if_not_exists: bool,
+    or_replace: bool,
+    options: HashMap<String, String>,
+    schema: DFSchemaRef,
+}
+
+impl CreateExternalCatalogBuilder {
+    /// Set the physical location of the catalog
+    ///
+    /// Passing `None` will **un**set the location.
+    #[must_use]
+    pub fn with_location(mut self, location: Option<String>) -> Self {
+        self.location = location;
+        self
+    }
+
+    /// Set the if_not_exists flag
+    #[must_use]
+    pub fn with_if_not_exists(mut self, if_not_exists: bool) -> Self {
+        self.if_not_exists = if_not_exists;
+        self
+    }
+
+    /// Set the or_replace flag
+    #[must_use]
+    pub fn with_or_replace(mut self, or_replace: bool) -> Self {
+        self.or_replace = or_replace;
+        self
+    }
+
+    /// Set the catalog options
+    #[must_use]
+    pub fn with_options(mut self, options: HashMap<String, String>) -> Self {
+        self.options = options;
+        self
+    }
+
+    /// Set the schema
+    #[must_use]
+    pub fn with_schema(mut self, schema: DFSchemaRef) -> Self {
+        self.schema = schema;
+        self
+    }
+
+    /// Build the [`CreateExternalCatalog`]
+    #[must_use]
+    pub fn build(self) -> CreateExternalCatalog {
+        CreateExternalCatalog {
+            catalog_name: self.catalog_name,
+            catalog_type: self.catalog_type,
+            location: self.location,
+            if_not_exists: self.if_not_exists,
+            or_replace: self.or_replace,
+            options: self.options,
+            schema: self.schema,
+        }
+    }
+}
+
 /// Creates a schema.
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct CreateCatalogSchema {
@@ -737,6 +830,69 @@ impl PartialOrd for DropCatalog {
     }
 }
 
+impl DropCatalog {
+    /// Creates a builder for [`DropCatalog`].
+    ///
+    /// # Example
+    /// ```
+    /// # use datafusion_expr::DropCatalog;
+    /// let cmd = DropCatalog::builder("my_catalog").with_if_exists(true).build();
+    /// assert_eq!(cmd.name, "my_catalog");
+    /// ```
+    #[must_use]
+    pub fn builder(name: impl Into<String>) -> DropCatalogBuilder {
+        DropCatalogBuilder {
+            name: name.into(),
+            if_exists: false,
+            cascade: false,
+            schema: Arc::new(DFSchema::empty()),
+        }
+    }
+}
+
+/// Builder for [`DropCatalog`].
+#[derive(Debug, Clone)]
+pub struct DropCatalogBuilder {
+    name: String,
+    if_exists: bool,
+    cascade: bool,
+    schema: DFSchemaRef,
+}
+
+impl DropCatalogBuilder {
+    /// Set the if_exists flag
+    #[must_use]
+    pub fn with_if_exists(mut self, if_exists: bool) -> Self {
+        self.if_exists = if_exists;
+        self
+    }
+
+    /// Set whether the drop should cascade
+    #[must_use]
+    pub fn with_cascade(mut self, cascade: bool) -> Self {
+        self.cascade = cascade;
+        self
+    }
+
+    /// Set the schema
+    #[must_use]
+    pub fn with_schema(mut self, schema: DFSchemaRef) -> Self {
+        self.schema = schema;
+        self
+    }
+
+    /// Build the [`DropCatalog`]
+    #[must_use]
+    pub fn build(self) -> DropCatalog {
+        DropCatalog {
+            name: self.name,
+            if_exists: self.if_exists,
+            cascade: self.cascade,
+            schema: self.schema,
+        }
+    }
+}
+
 /// Arguments passed to the `CREATE FUNCTION` statement
 ///
 /// These statements are turned into executable functions using [`FunctionFactory`]
@@ -931,9 +1087,13 @@ impl PartialOrd for CreateIndex {
 
 #[cfg(test)]
 mod test {
-    use crate::{CreateCatalog, DdlStatement, DropView};
+    use crate::{
+        CreateCatalog, CreateExternalCatalog, DdlStatement, DropCatalog, DropView,
+    };
     use datafusion_common::{DFSchema, DFSchemaRef, TableReference};
     use std::cmp::Ordering;
+    use std::collections::HashMap;
+    use std::sync::Arc;
 
     #[test]
     fn test_partial_ord() {
@@ -958,4 +1118,69 @@ mod test {
 
         assert_eq!(drop_view.partial_cmp(&catalog), Some(Ordering::Greater));
     }
+
+    #[test]
+    fn test_create_external_catalog_builder_defaults() {
+        let expected = CreateExternalCatalog {
+            catalog_name: "my_catalog".to_string(),
+            catalog_type: "memory".to_string(),
+            location: None,
+            if_not_exists: false,
+            or_replace: false,
+            options: HashMap::new(),
+            schema: Arc::new(DFSchema::empty()),
+        };
+        let cmd = CreateExternalCatalog::builder("my_catalog", "memory").build();
+        assert_eq!(cmd, expected);
+    }
+
+    #[test]
+    fn test_create_external_catalog_builder_setters() {
+        let cmd = CreateExternalCatalog::builder("my_catalog", "memory")
+            .with_location(Some("/tmp/catalog".to_string()))
+            .with_if_not_exists(true)
+            .with_or_replace(true)
+            .with_options(HashMap::from([("key".to_string(), "value".to_string())]))
+            .with_schema(Arc::new(DFSchema::empty()))
+            .build();
+        assert_eq!(cmd.location, Some("/tmp/catalog".to_string()));
+        assert!(cmd.if_not_exists);
+        assert!(cmd.or_replace);
+        assert_eq!(cmd.options.get("key").map(String::as_str), Some("value"));
+        assert_eq!(cmd.schema, Arc::new(DFSchema::empty()));
+    }
+
+    #[test]
+    fn test_create_external_catalog_builder_unsets_location() {
+        let cmd = CreateExternalCatalog::builder("my_catalog", "memory")
+            .with_location(Some("/tmp/catalog".to_string()))
+            .with_location(None)
+            .build();
+        assert_eq!(cmd.location, None);
+    }
+
+    #[test]
+    fn test_drop_catalog_builder_defaults() {
+        let expected = DropCatalog {
+            name: "my_catalog".to_string(),
+            if_exists: false,
+            cascade: false,
+            schema: Arc::new(DFSchema::empty()),
+        };
+        let cmd = DropCatalog::builder("my_catalog").build();
+        assert_eq!(cmd, expected);
+    }
+
+    #[test]
+    fn test_drop_catalog_builder_setters() {
+        let cmd = DropCatalog::builder("my_catalog")
+            .with_if_exists(true)
+            .with_cascade(true)
+            .with_schema(Arc::new(DFSchema::empty()))
+            .build();
+        assert_eq!(cmd.name, "my_catalog");
+        assert!(cmd.if_exists);
+        assert!(cmd.cascade);
+        assert_eq!(cmd.schema, Arc::new(DFSchema::empty()));
+    }
 }
diff --git a/datafusion/sql/src/statement.rs b/datafusion/sql/src/statement.rs
index fe0b359839..10420b9a61 100644
--- a/datafusion/sql/src/statement.rs
+++ b/datafusion/sql/src/statement.rs
@@ -51,9 +51,9 @@ use datafusion_expr::{
     CreateExternalCatalog as PlanCreateExternalCatalog,
     CreateExternalTable as PlanCreateExternalTable, CreateFunction, CreateFunctionBody,
     CreateIndex as PlanCreateIndex, CreateMemoryTable, CreateView, Deallocate,
-    DescribeTable, DmlStatement, DropCatalogSchema, DropFunction, DropTable, DropView,
-    EmptyRelation, Execute, Explain, ExplainFormat, Expr, ExprSchemable, Filter,
-    LogicalPlan, LogicalPlanBuilder, OperateFunctionArg, PlanType, Prepare,
+    DescribeTable, DmlStatement, DropCatalog, DropCatalogSchema, DropFunction, DropTable,
+    DropView, EmptyRelation, Execute, Explain, ExplainFormat, Expr, ExprSchemable,
+    Filter, LogicalPlan, LogicalPlanBuilder, OperateFunctionArg, PlanType, Prepare,
     ResetVariable, SetVariable, SortExpr, Statement as PlanStatement, ToStringifiedPlan,
     TransactionAccessMode, TransactionConclusion, TransactionEnd,
     TransactionIsolationLevel, TransactionStart, Volatility, WriteOp, cast,
@@ -842,14 +842,14 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
                             },
                         )))
                     }
-                    ObjectType::Database => Ok(LogicalPlan::Ddl(
-                        DdlStatement::DropCatalog(datafusion_expr::DropCatalog {
-                            name: object_name_to_string(&name),
-                            if_exists,
-                            cascade,
-                            schema: DFSchemaRef::new(DFSchema::empty()),
-                        }),
-                    )),
+                    ObjectType::Database => {
+                        Ok(LogicalPlan::Ddl(DdlStatement::DropCatalog(
+                            DropCatalog::builder(object_name_to_string(&name))
+                                .with_if_exists(if_exists)
+                                .with_cascade(cascade)
+                                .build(),
+                        )))
+                    }
                     _ => not_impl_err!(
                         "Only `DROP TABLE/VIEW/SCHEMA/CATALOG/DATABASE  ...` statement is supported currently"
                     ),
@@ -1955,15 +1955,17 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
         }
 
         Ok(LogicalPlan::Ddl(DdlStatement::CreateExternalCatalog(
-            Box::new(PlanCreateExternalCatalog {
-                catalog_name: object_name_to_string(&catalog_name),
-                catalog_type,
-                location,
-                if_not_exists,
-                or_replace,
-                options: options_map,
-                schema: Arc::new(DFSchema::empty()),
-            }),
+            Box::new(
+                PlanCreateExternalCatalog::builder(
+                    object_name_to_string(&catalog_name),
+                    catalog_type,
+                )
+                .with_location(location)
+                .with_if_not_exists(if_not_exists)
+                .with_or_replace(or_replace)
+                .with_options(options_map)
+                .build(),
+            ),
         )))
     }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org