Skip to content

Add an extension point to allow SQL based registration of external catalogs - #25112

Merged
alamb merged 19 commits into
apache:mainfrom
pepijnve:catalog
Sep 24, 2026
Merged

alamb merged 19 commits into
apache:mainfrom
pepijnve:catalog

Conversation

@pepijnve

@pepijnve pepijnve commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Adding a CatalogProviderFactory trait, similar to TableProviderFactory, makes it possible for extensions to register support for external catalogs that can then be used by consumers without requiring additional code. This is quite convenient when working with open table formats via catalogs.

What changes are included in this PR?

  • Adds a CatalogProviderFactory trait similar to TableProviderFactory
  • Adds CREATE EXTERNAL CATALOG and DROP CATALOG support

What is the testing strategy for this PR?

  • Covered by unit and integration tests

Are there any user-facing changes?

Yes, the supported DDL has been extended. Documentation has been updated to reflect this.

@github-actions github-actions Bot added documentation Improvements or additions to documentation sql SQL Planner logical-expr Logical plan and expressions optimizer Optimizer rules core Core DataFusion crate catalog Related to the catalog crate proto Related to proto crate labels Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

Thank you for opening this pull request!

Reviewer note: cargo-semver-checks reported the current version number is not SemVer-compatible with the changes in this pull request (compared against the base branch).

Details
     Cloning apache/main
    Building datafusion v55.1.0 (current)
       Built [  62.759s] (current)
     Parsing datafusion v55.1.0 (current)
      Parsed [   0.036s] (current)
    Building datafusion v55.1.0 (baseline)
       Built [  63.390s] (baseline)
     Parsing datafusion v55.1.0 (baseline)
      Parsed [   0.038s] (baseline)
    Checking datafusion v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.566s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 128.554s] datafusion
    Building datafusion-catalog v55.1.0 (current)
       Built [  45.545s] (current)
     Parsing datafusion-catalog v55.1.0 (current)
      Parsed [   0.027s] (current)
    Building datafusion-catalog v55.1.0 (baseline)
       Built [  45.631s] (baseline)
     Parsing datafusion-catalog v55.1.0 (baseline)
      Parsed [   0.027s] (baseline)
    Checking datafusion-catalog v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.124s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  92.504s] datafusion-catalog
    Building datafusion-cli v55.1.0 (current)
       Built [ 102.925s] (current)
     Parsing datafusion-cli v55.1.0 (current)
      Parsed [   0.036s] (current)
    Building datafusion-cli v55.1.0 (baseline)
       Built [ 103.475s] (baseline)
     Parsing datafusion-cli v55.1.0 (baseline)
      Parsed [   0.037s] (baseline)
    Checking datafusion-cli v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.123s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 209.515s] datafusion-cli
    Building datafusion-expr v55.1.0 (current)
       Built [  31.757s] (current)
     Parsing datafusion-expr v55.1.0 (current)
      Parsed [   0.083s] (current)
    Building datafusion-expr v55.1.0 (baseline)
       Built [  31.965s] (baseline)
     Parsing datafusion-expr v55.1.0 (baseline)
      Parsed [   0.085s] (baseline)
    Checking datafusion-expr v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   1.346s] 223 checks: 221 pass, 1 fail, 1 warn, 31 skip

--- failure enum_variant_added: enum variant added on exhaustive enum ---

Description:
A publicly-visible enum without #[non_exhaustive] has a new variant.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#enum-variant-new
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/enum_variant_added.ron

Failed in:
  variant DdlStatement:CreateExternalCatalog in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:54
  variant DdlStatement:DropCatalog in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:64
  variant DdlStatement:CreateExternalCatalog in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:54
  variant DdlStatement:DropCatalog in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:64

--- warning partial_ord_enum_variants_reordered: enum variants reordered in #[derive(PartialOrd)] enum ---

Description:
A public enum that derives PartialOrd had its variants reordered. #[derive(PartialOrd)] uses the enum variant order to set the enum's ordering behavior, so this change may break downstream code that relies on the previous order.
        ref: https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html#derivable
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/partial_ord_enum_variants_reordered.ron

Failed in:
  DdlStatement::CreateIndex moved from position 6 to 7, in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:56
  DdlStatement::DropTable moved from position 7 to 8, in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:58
  DdlStatement::DropView moved from position 8 to 9, in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:60
  DdlStatement::DropCatalogSchema moved from position 9 to 10, in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:62
  DdlStatement::CreateFunction moved from position 10 to 12, in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:67
  DdlStatement::DropFunction moved from position 11 to 13, in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:69
  DdlStatement::CreateIndex moved from position 6 to 7, in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:56
  DdlStatement::DropTable moved from position 7 to 8, in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:58
  DdlStatement::DropView moved from position 8 to 9, in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:60
  DdlStatement::DropCatalogSchema moved from position 9 to 10, in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:62
  DdlStatement::CreateFunction moved from position 10 to 12, in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:67
  DdlStatement::DropFunction moved from position 11 to 13, in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/ddl.rs:69

     Summary semver requires new major version: 1 major and 0 minor checks failed
     Warning produced 1 major and 0 minor level warnings
    Finished [  66.440s] datafusion-expr
    Building datafusion-optimizer v55.1.0 (current)
       Built [  30.364s] (current)
     Parsing datafusion-optimizer v55.1.0 (current)
      Parsed [   0.035s] (current)
    Building datafusion-optimizer v55.1.0 (baseline)
       Built [  30.445s] (baseline)
     Parsing datafusion-optimizer v55.1.0 (baseline)
      Parsed [   0.038s] (baseline)
    Checking datafusion-optimizer v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.181s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  62.194s] datafusion-optimizer
    Building datafusion-proto v55.1.0 (current)
       Built [  59.517s] (current)
     Parsing datafusion-proto v55.1.0 (current)
      Parsed [   0.018s] (current)
    Building datafusion-proto v55.1.0 (baseline)
       Built [  59.995s] (baseline)
     Parsing datafusion-proto v55.1.0 (baseline)
      Parsed [   0.019s] (baseline)
    Checking datafusion-proto v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.123s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 121.207s] datafusion-proto
    Building datafusion-session v55.1.0 (current)
       Built [  41.121s] (current)
     Parsing datafusion-session v55.1.0 (current)
      Parsed [   0.012s] (current)
    Building datafusion-session v55.1.0 (baseline)
       Built [  41.074s] (baseline)
     Parsing datafusion-session v55.1.0 (baseline)
      Parsed [   0.012s] (baseline)
    Checking datafusion-session v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.179s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  83.485s] datafusion-session
    Building datafusion-sql v55.1.0 (current)
       Built [  46.407s] (current)
     Parsing datafusion-sql v55.1.0 (current)
      Parsed [   0.037s] (current)
    Building datafusion-sql v55.1.0 (baseline)
       Built [  46.453s] (baseline)
     Parsing datafusion-sql v55.1.0 (baseline)
      Parsed [   0.034s] (baseline)
    Checking datafusion-sql v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.234s] 223 checks: 222 pass, 1 fail, 0 warn, 31 skip

--- failure enum_variant_added: enum variant added on exhaustive enum ---

Description:
A publicly-visible enum without #[non_exhaustive] has a new variant.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#enum-variant-new
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/enum_variant_added.ron

Failed in:
  variant Statement:CreateExternalCatalog in /home/runner/work/datafusion/datafusion/datafusion/sql/src/parser.rs:415

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  94.348s] datafusion-sql
    Building datafusion-sqllogictest v55.1.0 (current)
       Built [ 107.638s] (current)
     Parsing datafusion-sqllogictest v55.1.0 (current)
      Parsed [   0.023s] (current)
    Building datafusion-sqllogictest v55.1.0 (baseline)
       Built [ 109.114s] (baseline)
     Parsing datafusion-sqllogictest v55.1.0 (baseline)
      Parsed [   0.025s] (baseline)
    Checking datafusion-sqllogictest v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.105s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 219.418s] datafusion-sqllogictest

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Sep 9, 2026
@pepijnve
pepijnve marked this pull request as draft September 10, 2026 11:22
@pepijnve

Copy link
Copy Markdown
Contributor Author

Moved back to draft; still some work to do based on the CI results.

@pepijnve

Copy link
Copy Markdown
Contributor Author

@geoffreyclaude @Jefffrey in #19383 an create external catalog example was added which is provided out of the box by this PR. I triggered an examples failure related to this. Any suggestions on how I should adapt the custom SQL parser example if that particular example is no longer actually custom?

@pepijnve
pepijnve marked this pull request as ready for review September 14, 2026 21:21
@codecov-commenter

codecov-commenter commented Sep 14, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.94915% with 156 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.49%. Comparing base (e640ae0) to head (be7356d).

Files with missing lines Patch % Lines
datafusion/sql/src/parser.rs 77.15% 30 Missing and 23 partials ⚠️
datafusion/expr/src/logical_plan/ddl.rs 9.30% 39 Missing ⚠️
datafusion/core/src/execution/session_state.rs 52.63% 18 Missing ⚠️
datafusion/session/src/catalog.rs 0.00% 18 Missing ⚠️
datafusion/core/src/execution/context/mod.rs 80.32% 11 Missing and 1 partial ⚠️
datafusion/sql/src/statement.rs 82.00% 5 Missing and 4 partials ⚠️
datafusion/catalog/src/dynamic_file/catalog.rs 0.00% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #25112      +/-   ##
==========================================
- Coverage   82.51%   82.49%   -0.02%     
==========================================
  Files        1140     1140              
  Lines      438568   439019     +451     
  Branches   438568   439019     +451     
==========================================
+ Hits       361881   362171     +290     
- Misses      54832    54962     +130     
- Partials    21855    21886      +31     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pepijnve

Copy link
Copy Markdown
Contributor Author

@geoffreyclaude @Jefffrey in #19383 an create external catalog example was added which is provided out of the box by this PR. I triggered an examples failure related to this. Any suggestions on how I should adapt the custom SQL parser example if that particular example is no longer actually custom?

For now I've adapted the example to use CREATE FOREIGN CATALOG as syntax, keeping everything else the same.

@geoffreyclaude

Copy link
Copy Markdown
Contributor

@geoffreyclaude @Jefffrey in #19383 an create external catalog example was added which is provided out of the box by this PR. I triggered an examples failure related to this. Any suggestions on how I should adapt the custom SQL parser example if that particular example is no longer actually custom?

For now I've adapted the example to use CREATE FOREIGN CATALOG as syntax, keeping everything else the same.

@pepijnve That's a very reasonable adaptation of the example I think! It keeps the custom sql parser which is the core of the example. Of course it's way less useful as a template since you add native CREATE EXTERNAL CATALOG support, and could probably be rewritten from scratch for some new SQL, but that's clearly out of scope of the PR.
I'd just suggest adding a short comment to the example explaining that CREATE EXTERNAL CATALOG is now natively supported, and this example uses CREATE FOREIGN CATALOG purely as demo, if that makes sense.

@alamb

alamb commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

I plan to review this today

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @pepijnve -- I think this looks good to go to me

I left a bunch of comments. The ones I think are needed before merging:

  1. Remove the submodule pins
  2. add the cascade method to the deregister_catalog trait method and relevant tests.

Thank you for this -- I think it is a great extension point and the PR was easy to read and well tested

Comment thread datafusion-examples/examples/sql_ops/custom_sql_parser.rs
Comment on lines +21 to +24
//! Note: DataFusion supports `CREATE EXTERNAL CATALOG` out-of-the-box making use of
//! [`CatalogProviderFactory`](datafusion::catalog::CatalogProviderFactory). This example
//! is a partial reimplementation of the existing functionality to demonstrate how to extend the
//! SQL parser.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this somewhat confusing -- the comments are referring to CREATE EXTERNAL CATALOG but the example seems to use CREATE FOREIGN CATALOG 🤔

I think the point is that this example is showing how to support CREATE FOREIGN CATALOG which is similar in functionality to the (about to be) built in feature of CREATE EXTERNAL CATALOG)

Suggested change
//! Note: DataFusion supports `CREATE EXTERNAL CATALOG` out-of-the-box making use of
//! [`CatalogProviderFactory`](datafusion::catalog::CatalogProviderFactory). This example
//! is a partial reimplementation of the existing functionality to demonstrate how to extend the
//! SQL parser.
//! Note: DataFusion supports `CREATE EXTERNAL CATALOG` out-of-the-box making use of
//! [`CatalogProviderFactory`](datafusion::catalog::CatalogProviderFactory). This example
//! implements very similar functionality to demonstrate how to extend the SQL parser.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I wasn't sure how to describe this well. The point I was trying to get across is that this example is only a partial reimplementation of the functionality provided by the built-in 'external' support. There's no delegation to the registered factories in handle_create_foreign_catalog, it's a simple hardcoded example only.

I think it would actually be better to replace this example with something else that doesn't overlap with built-in functionality, but I took the easy way out for myself mainly due to lack of inspiration for a different (but still simple to implement) custom syntax alternative.


#[tokio::test]
async fn create_external_catalog_with_factory() -> Result<()> {
let mut state = SessionStateBuilder::new().with_default_features().build();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is consistent with the other code in this test, so it is all good. However, it seems like a lot of ceremony. It would be really nice, perhaps a follow on PR, to use the SessionStateBuilder for this. Something like

    let ctx: SessionContext = SessionStateBuilder::new()
        .with_default_features()
        .with_catalog_factory("TESTCATALOG",  Arc::new(TestCatalogFactory {}))
        .build()
        .into();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone ahead and done this already, and while I was at it changed SessiontStateBuilder::with_table_factory to accept impl Into<String> as well.

Comment thread parquet-testing
Comment thread datafusion/core/src/execution/session_state.rs

#[test]
fn drop_catalog() -> Result<(), DataFusionError> {
let sql = "DROP CATALOG c";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you also please add a test for DROP CATALOG c CASCADE ?

"Catalog should have been dropped!"
);

// dropping again should fail without IF EXISTS

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you also please add a test for DROP CATALOG cat CASCADE ?

I would expect it to error with not supported / not implemented

before a query ever runs. Sometimes it is useful to let users attach a
catalog dynamically from SQL instead — for example, a catalog backed by a
remote catalog service such as an Iceberg REST catalog. This is exactly
analogous to how [`TableProviderFactory`] lets `CREATE EXTERNAL TABLE`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment thread datafusion/sql/src/statement.rs Outdated
}),
)),
_ => not_impl_err!(
"Only `DROP TABLE/VIEW/SCHEMA ...` statement is supported currently"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably update this message to also mention catalog

DdlStatement::DropCatalog(datafusion_expr::DropCatalog {
name: object_name_to_string(&name),
if_exists,
schema: DFSchemaRef::new(DFSchema::empty()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should also have CASCADE here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code path consumes DROP DATABASE which is parsed by the standard SQL parser. Is that wanted or should I remove this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is ok

@pepijnve

Copy link
Copy Markdown
Contributor Author

The remaining open question on this one is what to do with drop catalog ... cascade. drop catalog ... (which is equivalent to drop catalog ... restrict) would prevent non-empty external catalogs from being forgotten. drop catalog ... cascade would delete everything on your remote catalog.

This has me wondering if we should use a different syntax to deal with connecting to and disconnecting from remote catalogs than create external catalog and drop catalog. I went for that in this PR since it seemed the natural extrapolation of create external table.

CREATE DATABASE cat;
```

## CREATE EXTERNAL CATALOG

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation above uses CREATE DATABASE ... for internal catalogs.
Why the external ones use CATALOG instead of DATABASE ?
Maybe we should just mention that they are synonyms ?!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have taken a closer look at the existing documentation. My bad.

The usage of this terminology is kind of inconsistent across the RDBMS ecosystem. The SQL spec talks about a hierarchy of catalogs, schemas, and schema objects (tables, views, etc.). What 'database' maps to seems to be implementation dependent. In Databricks and MySQL for instance, CREATE DATABASE is a synonym for CREATE SCHEMA.

The term catalog on the other hand maps quite nicely to Iceberg catalogs and many others (thank you for the list StarRocks). Looking at Iceberg, a database/schema maps to a namespace there.

But... DataFusion today treats CREATE DATABASE as a synonym for CREATE CATALOG. Not sure how best to proceed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick Claude generated survey that may or may not be useful:

Prompt

Are there any RDBMS implementations where CREATE DATABASE is a synonym for CREATE CATALOG rather than CREATE SCHEMA?

Answer

Not as a literal keyword synonym, as far as I know. I'm not aware of any mainstream RDBMS that accepts CREATE CATALOG as an alias for CREATE DATABASE. But the answer changes if you mean "a database that is semantically a catalog."

Where CREATE DATABASE creates a standard-SQL catalog (conceptually)

In the SQL standard's hierarchy (catalog → schema → object), several major systems make "database" the catalog level. None of them has a CREATE CATALOG statement.

PostgreSQL: a database holds schemas, and information_schema.tables.table_catalog reports the database name. CREATE DATABASE and CREATE SCHEMA are distinct.
SQL Server: same pattern. TABLE_CATALOG is the database, and objects are named database.schema.object.
Snowflake, Redshift, and similar warehouses: database.schema.table, with TABLE_CATALOG holding the database name.
MotherDuck/DuckDB: a MotherDuck CREATE DATABASE produces what DuckDB treats as a catalog (an attached database), and schemas live inside it.

Where CREATE DATABASE is the schema level

MySQL / MariaDB: CREATE SCHEMA is documented as a synonym for CREATE DATABASE.
Spark SQL / Databricks: CREATE DATABASE is an alias for CREATE SCHEMA. Unity Catalog adds a separate CREATE CATALOG above it.
Hive, Impala: DATABASE and SCHEMA are interchangeable.
Flink SQL, StarRocks, Doris: they have both CREATE CATALOG and CREATE DATABASE, with the database sitting inside the catalog (i.e., at the schema level).

A reverse oddity: Firebird/InterBase

Historically, CREATE SCHEMA was a synonym for CREATE DATABASE in these systems, and it created a whole database file. Firebird 6 introduces real schemas, which changes this.

So if you've seen something claim CREATE DATABASE ≡ CREATE CATALOG, it's most likely describing the semantic mapping in PostgreSQL/SQL Server-style systems rather than an actual syntactic alias. If you have a specific engine in mind, I can check its current docs, since some newer engines add syntax quickly.

@alamb

alamb commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

But... DataFusion today treats CREATE DATABASE as a synonym for CREATE CATALOG. Not sure how best to proceed.

I think we should just use CREATE CATALOG consistently in our docs / examples

@alamb

alamb commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

This has me wondering if we should use a different syntax to deal with connecting to and disconnecting from remote catalogs than create external catalog and drop catalog. I went for that in this PR since it seemed the natural extrapolation of create external table.

I agree -- this is fine. We can sort out the cascade syntax / semantics as a follow on PR / work if the usecase arises.

I think this pR is ready to go now -- and we can iterate on it in follow on PRs. Any other thoughts before we merge @pepijnve or @martin-g ?

@martin-g

martin-g commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

I think we should just use CREATE CATALOG consistently in our docs / examples

👍🏻
But CREATE DATABASE ... is already in the wild, so it has to be supported.

I think this pR is ready to go now -- and we can iterate on it in follow on PRs. Any other thoughts before we merge @pepijnve or @martin-g ?

IMO it would be good to add a simple SLT to document/verify that they are synonyms:

statement ok
CREATE EXTERNAL DATABASE abc; -- uses DATABASE

statement ok
DROP CATALOG abc; -- uses CATALOG

@pepijnve

Copy link
Copy Markdown
Contributor Author

I think we should just use CREATE CATALOG consistently in our docs / examples

👍🏻 But CREATE DATABASE ... is already in the wild, so it has to be supported.

We can keep CREATE DATABASE support as is as an alias for CREATE CATALOG. And then use CREATE CATALOG everywhere in the documentation. The added benefit of using catalog.schema.table is that this maps nicely to the equivalent types in DataFusion.

I think this pR is ready to go now -- and we can iterate on it in follow on PRs. Any other thoughts before we merge @pepijnve or @martin-g ?

I have the cascade part you requested ready locally, was just waiting on clarification regarding the direction. I'll finish that and add it to this PR.

IMO it would be good to add a simple SLT to document/verify that they are synonyms:

statement ok
CREATE EXTERNAL DATABASE abc; -- uses DATABASE

I'm not inclined to add CREATE EXTERNAL DATABASE support due to the inconsistent use of DATABASE across systems this is just going to be confusing. We can add a plain CREATE DATABASE test though to cover what you have in mind.

@martin-g

Copy link
Copy Markdown
Member

So, CREATE DATABASE will be supported as an alias/synonym to CREATE CATALOG but CREATE EXTERNAL DATABASE will not be supported ?

@github-actions github-actions Bot added the sqllogictest SQL Logic Tests (.slt) label Sep 24, 2026
@pepijnve

Copy link
Copy Markdown
Contributor Author

So, CREATE DATABASE will be supported as an alias/synonym to CREATE CATALOG but CREATE EXTERNAL DATABASE will not be supported ?

That's what 795f0de does indeed. I've biased everything towards CREATE CATALOG, but retained CREATE DATABASE support and added the test you suggested.

One thing I'm not entirely sure about in that commit is the method name CatalogProvider::prepare_deregister_catalog. I wanted to be able to have a difference in behaviour when dropping an internal catalog vs dropping an external one. DROP CATALOG <internal> will error if the catalog is not empty. DROP CATALOG <external> should simply detach the catalog. Each implementation of CatalogProvider can choose what it does using this callback.

The alternative would be to make a distinction at registration time. In other words, we add an additional method CatalogProviderList::register_external_catalog so that CatalogProviderList::deregister_catalog implementations can decide what to do. I think this would still require a callback on CatalogProvider though since you want to be able to make the DROP CATALOG ... CASCADE operation an atomic operation which is going to be impossible to do externally.

@pepijnve

Copy link
Copy Markdown
Contributor Author

FWIW, encountering things like this in the current code made the best choice seem obvious

image image

@pepijnve

Copy link
Copy Markdown
Contributor Author

Test failure seems to be an unrelated minio problem

@alamb
alamb enabled auto-merge September 24, 2026 18:29
@alamb

alamb commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Thank you very much @pepijnve and @martin-g

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still have some questions but I think we have iterated on this enough in this PR and we can refine the design / implementation as a follow on PR

/// to prevent a catalog from being dropped if it is not empty.
///
/// By default returns a "Not Implemented" error
fn prepare_deregister_catalog(&self, _cascade: bool) -> Result<()> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand why this can't be implemented in deregister_catalog (why couldn't a deregister_catalog call simply return an error if it wasn't empty 🤔 )

@pepijnve

Copy link
Copy Markdown
Contributor Author

I still have some questions but I think we have iterated on this enough in this PR and we can refine the design / implementation as a follow on PR

As you prefer. Happy to keep on tinkering on it.

@alamb
alamb added this pull request to the merge queue Sep 24, 2026
Merged via the queue into apache:main with commit 975c0f0 Sep 24, 2026
42 checks passed
@pepijnve
pepijnve deleted the catalog branch September 24, 2026 20:30
diegoQuinas pushed a commit to diegoQuinas/datafusion that referenced this pull request Sep 24, 2026
…talogs (apache#25112)

## Which issue does this PR close?

- Closes apache#25111.

## Rationale for this change

Adding a `CatalogProviderFactory` trait, similar to
`TableProviderFactory`, makes it possible for extensions to register
support for external catalogs that can then be used by consumers without
requiring additional code. This is quite convenient when working with
open table formats via catalogs.

## What changes are included in this PR?

- Adds a `CatalogProviderFactory` trait similar to
`TableProviderFactory`
- Adds `CREATE EXTERNAL CATALOG` and `DROP CATALOG` support

## What is the testing strategy for this PR?

- Covered by unit and integration tests

## Are there any user-facing changes?

Yes, the supported DDL has been extended. Documentation has been updated
to reflect this.

---------

Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
Omega359 pushed a commit to Omega359/arrow-datafusion that referenced this pull request Oct 11, 2026
…talogs (apache#25112)

## Which issue does this PR close?

- Closes apache#25111.

## Rationale for this change

Adding a `CatalogProviderFactory` trait, similar to
`TableProviderFactory`, makes it possible for extensions to register
support for external catalogs that can then be used by consumers without
requiring additional code. This is quite convenient when working with
open table formats via catalogs.

## What changes are included in this PR?

- Adds a `CatalogProviderFactory` trait similar to
`TableProviderFactory`
- Adds `CREATE EXTERNAL CATALOG` and `DROP CATALOG` support

## What is the testing strategy for this PR?

- Covered by unit and integration tests

## Are there any user-facing changes?

Yes, the supported DDL has been extended. Documentation has been updated
to reflect this.

---------

Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto detected api change Auto detected API change catalog Related to the catalog crate core Core DataFusion crate documentation Improvements or additions to documentation logical-expr Logical plan and expressions optimizer Optimizer rules proto Related to proto crate sql SQL Planner sqllogictest SQL Logic Tests (.slt) v56.0.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for SQL based registration of external catalogs

6 participants