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