Skip to content

Information schema extension - #25887

Open
pepijnve wants to merge 9 commits into
apache:mainfrom
pepijnve:information-schema-extension
Open

pepijnve wants to merge 9 commits into
apache:mainfrom
pepijnve:information-schema-extension

Conversation

@pepijnve

@pepijnve pepijnve commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Catalog-qualified information schema queries currently return metadata from every registered catalog.

For example, my_catalog.information_schema.tables should describe only my_catalog, consistent with PostgreSQL's information schema representing the current database.

What changes are included in this PR?

  • The catalog list provided to InformationSchemaProvider is now constructed dynamically based on the catalog being queried. An optional 'system' catalog provides the existing global catalog behaviour. All other catalogs are restricted to just their own content.
  • Unqualified information_schema queries now use the configured default catalog instead of returning metadata from all catalogs.

What is the testing strategy for this PR?

Added multi-catalog SQL logic coverage for qualified and unqualified information schema queries.

Are there any user-facing changes?

Yes. Information schema results are now scoped to the resolved catalog.

@github-actions github-actions Bot added sql SQL Planner core Core DataFusion crate sqllogictest SQL Logic Tests (.slt) catalog Related to the catalog crate common Related to common crate execution Related to the execution crate labels Sep 29, 2026
@github-actions

github-actions Bot commented Sep 29, 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 [  52.301s] (current)
     Parsing datafusion v55.1.0 (current)
      Parsed [   0.031s] (current)
    Building datafusion v55.1.0 (baseline)
       Built [  52.141s] (baseline)
     Parsing datafusion v55.1.0 (baseline)
      Parsed [   0.032s] (baseline)
    Checking datafusion v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.630s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 107.329s] datafusion
    Building datafusion-catalog v55.1.0 (current)
       Built [  35.987s] (current)
     Parsing datafusion-catalog v55.1.0 (current)
      Parsed [   0.020s] (current)
    Building datafusion-catalog v55.1.0 (baseline)
       Built [  36.119s] (baseline)
     Parsing datafusion-catalog v55.1.0 (baseline)
      Parsed [   0.021s] (baseline)
    Checking datafusion-catalog v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.108s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  73.773s] datafusion-catalog
    Building datafusion-cli v55.1.0 (current)
       Built [  85.531s] (current)
     Parsing datafusion-cli v55.1.0 (current)
      Parsed [   0.026s] (current)
    Building datafusion-cli v55.1.0 (baseline)
       Built [  85.884s] (baseline)
     Parsing datafusion-cli v55.1.0 (baseline)
      Parsed [   0.027s] (baseline)
    Checking datafusion-cli v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.130s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 174.446s] datafusion-cli
    Building datafusion-common v55.1.0 (current)
       Built [  31.429s] (current)
     Parsing datafusion-common v55.1.0 (current)
      Parsed [   0.058s] (current)
    Building datafusion-common v55.1.0 (baseline)
       Built [  31.493s] (baseline)
     Parsing datafusion-common v55.1.0 (baseline)
      Parsed [   0.062s] (baseline)
    Checking datafusion-common v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.767s] 223 checks: 222 pass, 1 fail, 0 warn, 31 skip

--- failure constructible_struct_adds_field: struct exhaustively constructible through public API adds field ---

Description:
A pub struct that could be exhaustively constructed with a literal using only public API has a new pub field, breaking existing exhaustive literals.
        ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/constructible_struct_adds_field.ron

Failed in:
  field CatalogOptions.system_catalog in /home/runner/work/datafusion/datafusion/datafusion/common/src/config.rs:224

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  65.329s] datafusion-common
    Building datafusion-execution v55.1.0 (current)
       Built [  29.156s] (current)
     Parsing datafusion-execution v55.1.0 (current)
      Parsed [   0.024s] (current)
    Building datafusion-execution v55.1.0 (baseline)
       Built [  29.113s] (baseline)
     Parsing datafusion-execution v55.1.0 (baseline)
      Parsed [   0.026s] (baseline)
    Checking datafusion-execution v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.258s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  60.456s] datafusion-execution
    Building datafusion-sql v55.1.0 (current)
       Built [  38.875s] (current)
     Parsing datafusion-sql v55.1.0 (current)
      Parsed [   0.028s] (current)
    Building datafusion-sql v55.1.0 (baseline)
       Built [  38.749s] (baseline)
     Parsing datafusion-sql v55.1.0 (baseline)
      Parsed [   0.031s] (baseline)
    Checking datafusion-sql v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.230s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  79.496s] datafusion-sql
    Building datafusion-sqllogictest v55.1.0 (current)
       Built [  89.781s] (current)
     Parsing datafusion-sqllogictest v55.1.0 (current)
      Parsed [   0.015s] (current)
    Building datafusion-sqllogictest v55.1.0 (baseline)
       Built [  89.764s] (baseline)
     Parsing datafusion-sqllogictest v55.1.0 (baseline)
      Parsed [   0.016s] (baseline)
    Checking datafusion-sqllogictest v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.106s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 182.546s] datafusion-sqllogictest

@github-actions github-actions Bot added auto detected api change Auto detected API change documentation Improvements or additions to documentation labels Sep 29, 2026
@pepijnve
pepijnve force-pushed the information-schema-extension branch from b5c8c7a to 1b1ed04 Compare October 2, 2026 20:19
@pepijnve
pepijnve force-pushed the information-schema-extension branch from 1ae5b90 to 1e5b40e Compare October 3, 2026 13:24
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.26087% with 40 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.63%. Comparing base (b9c8b3d) to head (1e5b40e).
⚠️ Report is 59 commits behind head on main.

Files with missing lines Patch % Lines
datafusion/catalog/src/information_schema.rs 77.90% 13 Missing and 6 partials ⚠️
datafusion/core/src/execution/session_state.rs 79.68% 12 Missing and 1 partial ⚠️
datafusion/sql/src/statement.rs 65.21% 6 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #25887      +/-   ##
==========================================
+ Coverage   82.57%   82.63%   +0.06%     
==========================================
  Files        1142     1147       +5     
  Lines      441215   445567    +4352     
  Branches   441215   445567    +4352     
==========================================
+ Hits       364332   368215    +3883     
- Misses      54841    54993     +152     
- Partials    22042    22359     +317     

☔ 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
pepijnve marked this pull request as ready for review October 3, 2026 13:54
@pepijnve

pepijnve commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

@neilconway this branch is now clean. There's still quite a lot that can be improved in the information schema, but I've restricted it to just the scope change here.

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

Thanks @pepijnve -- I am worried about the behavior change in this PR

I thought postgres had some notion of "default search path" or something that would scope its name resolution rules (and presumably also information_schema visibility). Did you consider something like that for DataFusion?

statement: Statement,
) -> datafusion_common::Result<LogicalPlan> {
let references = self.resolve_table_references(&statement)?;
let mut references = self.resolve_table_references(&statement)?;

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 a nice change to move it here into the session state

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.

Thanks, that gives me the feeling I'm heading in the right direction.

schema: schema.into(),
table: table.into(),
fn resolve_info_table(&self, table: &str) -> Option<TableReference> {
let table_reference = if let Some(system_catalog) =

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.

maybe we can adjust the comments here to explain the scoping rules:

Looks first in the system catalog, if defined, and otherwise treats it as unqualified reference

@pepijnve pepijnve Oct 6, 2026 •

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.

See other comment. Might be best to peek ahead at PR #26057 where this goes away entirely. In a nutshell, what I've done there is to change the planning of show ... statements so that they're not dependent on information_schema at all.

{
TableReference::Full {
catalog: system_catalog.deref().into(),
schema: "information_schema".into(),

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 information_schema reference feels wrong to me -- I thought the design was that the core of Datafusion / the sql parser didn't have any special case for the information_schema and information schema was handled with the same API as the other catalog providers

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.

In this PR and more importantly in main, the references to information_schema, the various tables and their columns all exist here. As an example, see the show_tables_to_plan function on main.

I agree with you that the hardcoding of the information_schema table schema in the sql crate feels wrong. Getting rid of that is something I'm trying in followup PR #26057. This one is smaller in scope and only tries to land the work done in PR #24200 which seems to have stalled.

WHERE table_schema <> 'information_schema';
----
my_other_catalog my_other_schema t3

query TTTT rowsort
SELECT * from information_schema.tables;

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.

why don't all the other tables appear now? That seems ilke people would treat it as a regression 🤔

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.

What I've tried to implement in this PR is what's described at https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-information-schema

In the SYSTEM catalog, the INFORMATION_SCHEMA is a SQL standard schema that provides metadata about objects across all catalogs in the metastore. It does not contain metadata about hive_metastore objects.

Separately, each catalog created in Unity Catalog also automatically includes an information_schema that describes metadata about objects in that catalog only.

The behaviour you're seeing here is that the default catalog was set to my_other_catalog. information_schema.tables then resolves to my_other_catalog.information_schema.tables which only describes the objects in my_other_catalog.

@pepijnve

pepijnve commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

I think you're referring to PostgreSQL's schema search path. I'm not familiar with that, but will look into it.

@pepijnve

pepijnve commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

I spent some time surveying what's out there with AI to get a feel for our options.

We'll need to make a decision on a couple of things:

  1. Should information_schema be global or per catalog?
  2. If global, which catalog is it a child of?
  3. If per catalog, what does an unqualified usage of information_schema resolve to?

Here's what I got from Claude wrt other some other systems out there

System Global? Where it lives? Unqualified?
DuckDB Yes, one view covers all attached databases Once, in the system catalog system.information_schema
SQL Server No; query db.INFORMATION_SCHEMA.TABLES per database, or use sys.databases plus dynamic SQL Each database Current database (USE)
Snowflake No; SNOWFLAKE.ACCOUNT_USAGE views cover the account (with latency) Each database Current database; errors if none is set
Trino No; system.jdbc.* and system.metadata.* cover all catalogs Each catalog (generated per connector) Session catalog; errors if none is set
Databricks (Unity Catalog) Opt-in via system.information_schema Each catalog, plus system.information_schema Current catalog (USE CATALOG)
BigQuery Region-qualified, e.g. `region-us`.INFORMATION_SCHEMA.TABLES Per dataset or region, as a qualifier Generally errors without a qualifier
MySQL / MariaDB Yes, but "databases" are really schemas; table_catalog is always def Once, global The global schema
SQLite No; query aux.sqlite_schema per attachment None; each attached DB has sqlite_schema main.sqlite_schema
StarRocks No; query catalog.information_schema.tables per catalog (external catalogs supported from v3.2) Each catalog (default_catalog and each external catalog) Current catalog (SET CATALOG), default_catalog by default

The current state of DataFusion is somewhere in between all the choices listed above: information_schema lives in each catalog which suggests it's partitioned per catalog, but each 'instance' is identical and is actually global/cross-catalog. This becomes particularly annoying when you're trying to work with remote catalogs. It doesn't seem desirable for a query that looks local to start accessing remote resources (e.g., I try to query the memory catalog via the information schema, and it starts accessing my Iceberg REST catalog over the internet).

Given that a cross-catalog information schema currently exists, I think we at least need to retain that capability. The question then is how we expose that to the outside world.

What I did in this PR is to introduce a new magic catalog named system that holds the global information schema. Because DataFusion only has a single default catalog value at the moment rather than a PostgreSQL style search path, you need to address this explicitly.

An alternative solution could be to add a search path capability for unqualified identifier resolution. By default, we would not expose the per-catalog information schema and add system to the end of the search path by default. This would still introduce a behaviour change though for info schema queries in qualified form (i.e. select * from datafusion.information_schema.tables would not work).

A hybrid solution where we special case the unqualified resolution of information_schema is also an option.

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 common Related to common crate core Core DataFusion crate documentation Improvements or additions to documentation execution Related to the execution crate sql SQL Planner sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

information_schema.tables contains all tables from all catalogs instead of current catalog

4 participants