Skip to content

Extract catalog API to separate crate, change TableProvider::scan to take a trait rather than SessionState - #11516

Merged
alamb merged 1 commit into
apache:mainfrom
findepi:findepi/catalog-api
Jul 26, 2024
Merged

Extract catalog API to separate crate, change TableProvider::scan to take a trait rather than SessionState#11516
alamb merged 1 commit into
apache:mainfrom
findepi:findepi/catalog-api

Conversation

@findepi

@findepifindepi commented Jul 17, 2024

Copy link
Copy Markdown
Member

This moves CatalogProvider, TableProvider, SchemaProvider to a new datafusion-catalog crate. The circular dependency between core SessionState and implementations is broken up by introducing CatalogSession dyn trait. Implementations of TableProvider that reside under core current have access to CatalogSession by downcasting. This is supposed to be an intermediate step.

Part of #10782
Relates to #11420

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation core Core DataFusion crate sqllogictest SQL Logic Tests (.slt) labels Jul 17, 2024
@findepi

Copy link
Copy Markdown
MemberAuthor

This tries to implement the idea expressed in #10782 (comment)

@findepi
findepi marked this pull request as draft July 17, 2024 14:19
@findepi
findepiforce-pushed the findepi/catalog-api branch 4 times, most recently from d5f5a77 to 5bbf5d7CompareJuly 17, 2024 14:26
@findepi
findepi marked this pull request as ready for review July 17, 2024 14:28
@findepi
findepiforce-pushed the findepi/catalog-api branch 2 times, most recently from 8ad5111 to 0430691CompareJuly 17, 2024 16:50
Comment threaddatafusion/catalog/src/table.rs Outdated
@findepi
findepiforce-pushed the findepi/catalog-api branch from 0430691 to 6c399c2CompareJuly 17, 2024 17:18
@findepi

Copy link
Copy Markdown
MemberAuthor

Eventually all green.

@jayzhan211@alamb@comphead you may want to take a look

@jayzhan211

Copy link
Copy Markdown
Contributor

I think CatalogSession is not the minimum dependency, what is the reason to replace all the SessionState with CatalogSession

@findepi

Copy link
Copy Markdown
MemberAuthor

@jayzhan211 admittedly i made some choices about what to put into CatalogSession (or however we want to call this) and what to keep outside requiring a downcast to SessionState.
my line of thought was to let CatalogSession contain things that are separate modules and not optional dependencies -- so e.g. logical and physical expressions and plans were allowed base on that.

Of course, these choices remain pretty arbitrary. there may be things that CatalogSession should have but doesn't have yet, and also things that it got, but should be left out.
I will need your help, and others, to improve those decisions (provided this is the way to go at all).

@jayzhan211

jayzhan211 commented Jul 18, 2024

Copy link
Copy Markdown
Contributor

let CatalogSession contain things that are separate modules and not optional dependencies

I think this is similar to minimum dependencies what I'm thinking. The only difference is that I propose struct TableProviderContext, but you have trait CatalogSession

IMO struct is more sutiable for storing data, unlike trait that mainly used for sharing same behavior

Given the dependency of TableProvider I found, we might need to find out the smaller context for FileFormatFactory and QueryPlanner.

traitTableProvider{asyncfnscan(&self,context:&TableProviderContext,projection:Option<&Vec<usize>>,filters:&[Expr],limit:Option<usize>,) -> Result<Arc<dynExecutionPlan>>;}structTableProviderContext{FileFormatFactorySessionConfigExectuionPropsRuntimeEnvQueryPlanner}pubtraitQueryPlanner{/// Given a `LogicalPlan`, create an [`ExecutionPlan`] suitable for executionasyncfncreate_physical_plan(&self,logical_plan:&LogicalPlan,context:&QueryPlannerContext,) -> Result<Arc<dynExecutionPlan>>;}pubtraitFileFormatFactory:Sync + Send + GetExt{/// Initialize a [FileFormat] and configure based on session and command level optionsfncreate(&self,context:&FileFormatContext,format_options:&HashMap<String,String>,) -> Result<Arc<dynFileFormat>>;/// Initialize a [FileFormat] with all options set to default valuesfndefault(&self) -> Arc<dynFileFormat>;}

But before that, we need to ensure those contexts won't end up the as the same thing in the long term

@findepi

Copy link
Copy Markdown
MemberAuthor

I think this is similar to minimum dependencies what I'm thinking. The only difference is that I propose struct TableProviderContext, but you have trait CatalogSession

IMO struct is more sutiable for storing data, unlike trait that mainly used for sharing same behavior

A publicly constructible struct is indeed different than a trait. Not sure we want that though, as it would limit evolvability.
I thought about a trait, because of it resemblance to interfaces in other languages. To me seems suited for defining contracts -- and here we're defining a contract between core and the table providers.
I don't feel strongly though, even less so given my little experience

IMO a bigger question is what functionality does this new being provide.
If we go into composability, I would want to think about different table providers as add-ons or plugins.
Then, maybe FileFormatFactory could be moved our of core, as not inherently part of SQL execution?
This question would imply whether FileFormatFactory is exposed in TableProviderContext/CatalogSession.

cc @alamb it would be good to hear your opinion, especially that you created the #10782 issue, which this PR is in response to.

@jayzhan211

jayzhan211 commented Jul 19, 2024

Copy link
Copy Markdown
Contributor

Given the current FileFormatFactory implementation in datafusion, we only need TableOptions, we can change SessionState to FileFormatContext with TableOptions, and it can move out from the crate which seat Sessionstate.

QueryPlanner is much complex, there is schema_for_ref that returns SchemaProvider so there is circular dependency on TableProvider.

TableProvider -> SessionState -> QueryPlanner -> SchemaProvider -> TableProvider

@findepi

Copy link
Copy Markdown
MemberAuthor

At runtime there are circular dependencies, unless we further limit amount of functionality available to the TableProvider.
However, I think we actually solve the circular dependencies with indirection. CatalogSession is supposed to be a POC for that indirection.

@jayzhan211

jayzhan211 commented Jul 19, 2024

Copy link
Copy Markdown
Contributor

I doubt that CatalogSession solves all the dependencies issue. CatalogSession is one higher level trait above the core. The actual implementations are still leave inside core. It doesn't seem problematic since the actual implementation is inside core. If we want to move TableProvider implementation out of core into datasource, since we still need to downcast CatalogSession to SessionState, thus SessionState should be in the same as datasource, then we ends up bringing them all into the same crate. The same issue applied to other actual implementation like Catalog. Without CatalogSession, we are already able to bring TableProvider+CatalogProvider+SessionState+Others related things out of core into it's own crate, but the dependencies between TableProvider, CatalogProvider and SessionState are what left to be decoupled. Therefore, I don't think CatalogSession is enough 🤔

@jayzhan211

jayzhan211 commented Jul 19, 2024

Copy link
Copy Markdown
Contributor

I think there is clear dependency something close to
Catalog -> Schema -> Table -> FileFormat -> QueryPlanner.
They all have trait and trait implementation and we can easily downcast the trait to actual struct in the lower crate.

// queryplanner cratetraitQueryPlanner{}implQueryPlannerforDefaultPlanner{}// fileformat cratetraitFileFormat{}implFileFormatforDefaultFile{// FileFormatContext that has all the higher level concept things above FileFormatfnscan(&self,FileFormatContext) -> Arc<QueryPlanner>{// able to downcast to planner }}// Table cratetraitTableProvider{}implTableProviderforDefaultTable{// arguments are all higher level trait without circular dependencyfn get_file(&self,plan) -> Arc<QueryPlanner>{// able to downcast to actual planner }fnget_planner(&self,fileformat) -> Arc<FileFormat>{// able to downcast to actual format}// TableProviderContext that has all the higher level concept things above TableProviderfnscan(&self,TableProviderContext)}

Here is one example that I think we should fixed. We find the specific table from SessionState and insert query into the table, which violate the dependencies from what I thought of.

LogicalPlan::Dml(DmlStatement{
table_name,op:WriteOp::InsertInto,
..
}) => {let name = table_name.table();let schema = session_state.schema_for_ref(table_name.clone())?;ifletSome(provider) = schema.table(name).await? {let input_exec = children.one()?;
provider
.insert_into(session_state, input_exec,false).await?
}else{returnexec_err!("Table '{table_name}' does not exist");}}

Instead, I think we should provider insert_plan_to_table for SchemaProvider. And avoid insert plan inside QueryPlan but in SchemaProvider. Split the logic into two step with

  1. Create plan to insert from QueryPlan.
  2. Call insert_plan_to_table from SchemaProvider

Not sure if this idea works over all the places 🤔

@alamb

Copy link
Copy Markdown
Contributor

I plan to check this PR out carefully tomorrow

@alambalamb changed the title Extract catalog API to separate crateExtract catalog API to separate crate, change TableProvider::scan to take a trait rather than SessionStateJul 20, 2024

@alambalamb 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 @findepi and @jayzhan211

I think this API is quite clever and solves a long standing problem (circular dependency between Catalog -> TableProvider -> SessionState). Like many elegant solutions, once you see it the design is obvious but it was not before seeing.

I also think this API makes the usecase described in #11193 from @cisaacson (providing the same SessionState information via more APIs) straightforward.

I think using a trait (rather than a struct) to break the dependency follows the same pattern used elsewhere in DataFusion (e.g. ScalarUdfImpl)

Next Steps

First I would like to hear what @jayzhan211 thinks

If he thinks we should proceed, since this PR proposes to change a very widely used API in DataFusion I think some extra communication is warranted before we merge:

  • mark this PR as api-change and change the title to higlight the change to TableProvider
  • communicate this proposal over the mailing list / slack / discord to maximize the chance anyone with feedback to share has a chance to
  • Make sure it is left open for several days for feedback
  • Test this change with some downstream users (e.g. I can test this with InfluxDB 3.0 and see if the migration to the new API goes smoothly

Comment threaddatafusion/catalog/src/session.rs Outdated
Comment threaddatafusion/catalog/src/table.rs Outdated
/// to return as a final result.
async fn scan(
&self,
state: &dyn CatalogSession,

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 one line I think is the biggest potential change in this PR as it will require changing all existing TableProviders which is one of the oldest and most widely used APIs in DataFusion

If we are ok with this change, I think the rest of this PR is wonderful

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 checked in the Spice AI codebase, and this will be a relatively minor refactoring for us. I am ok with this change 👍

Comment threaddatafusion/catalog/src/session.rs
Comment threaddatafusion/core/src/lib.rs Outdated
@alamb

alamb commented Jul 20, 2024

Copy link
Copy Markdown
Contributor

At runtime there are circular dependencies, unless we further limit amount of functionality available to the TableProvider. However, I think we actually solve the circular dependencies with indirection. CatalogSession is supposed to be a POC for that indirection.

Yes I agree with this

Instead, I think we should provider insert_plan_to_table for SchemaProvider. And avoid insert plan inside QueryPlan but in SchemaProvider. Split the logic into two step with

I also think this sounds like a reasonable plan - and I think it becomes easier as we split the logic out and defined the API via traits

@alamb

Copy link
Copy Markdown
Contributor

BTW I agree with most of @jayzhan211 's ideas above, but in my mind they are follow ons / orthogonal to moving the catalog traits out of the core (as in we can do both!)

Comment threaddatafusion/core/src/datasource/listing/table.rs
@alamb

Copy link
Copy Markdown
Contributor

I am feeling even better about this PR. I propose we plan to merge it in 2 days time (Friday) unless there are objections or other comments to address. Thank you @findepi@Omega359@cisaacson@jayzhan211 and @phillipleblanc for your help so far

@alamb

Copy link
Copy Markdown
Contributor

@findepi also mentioned Session as an option which seems reasonable to me too

I slightly prefer SessionProvider as I think it is more consistent with the naming conventions of other traits in DataFusion such as TableProvider, CatalogProvider, SchemaProvider etc, (however, there are plenty of counter examples like ExprSchema)

However, I don't feel super strongly and would be interested in opinions from others if we want to have a Provider on the end of the (now named) Session trait

@findepi

Copy link
Copy Markdown
MemberAuthor

re: SessionProvider
the trait describes something that doesn't provide session, it describes something that is session.
However, same think can be said about TableProvider or CatalogProvider. I don't have an answer to the consistency argument...

i can change, this is not a big deal. @alamb@jayzhan211@cisaacson@phillipleblanc@Omega359 let's just make sure we have agreement to avoid unnecessary back and forth.

@alamb

Copy link
Copy Markdown
Contributor

Oh no, I broke the doc test -- sorry @findepi

@alamb

Copy link
Copy Markdown
Contributor

Oh no, I broke the doc test -- sorry @findepi

Here is a proposed fix: findepi#5

@findepi
findepiforce-pushed the findepi/catalog-api branch from 64e6c20 to 9210b48CompareJuly 25, 2024 12:29
@findepi

Copy link
Copy Markdown
MemberAuthor

rebased on current main, no other changes
i couldn't locally reproduce the build problems observed on CI

@alamb

Copy link
Copy Markdown
Contributor

rebased on current main, no other changes i couldn't locally reproduce the build problems observed on CI

I think the failure is due to new rust version (if you run rustup update it would likely start failing too).

#11651

Not related to the changes in this PR

@findepi

Copy link
Copy Markdown
MemberAuthor

I think the failure is due to new rust version (if you run rustup update it would likely start failing too).

#11651

it's now fixed and the build is green, but there is a new conflict. will rebase

This moves `CatalogProvider`, `TableProvider`, `SchemaProvider` to a new
`datafusion-catalog` crate. The circular dependency between core
`SessionState` and implementations is broken up by introducing
`CatalogSession` dyn trait. Implementations of `TableProvider` that
reside under core current have access to `CatalogSession` by
downcasting. This is supposed to be an intermediate step.
@findepi
findepiforce-pushed the findepi/catalog-api branch from a4d282a to dbb8905CompareJuly 25, 2024 21:33
@findepi

Copy link
Copy Markdown
MemberAuthor

( squashed and rebased, no other changes )

@alamb

Copy link
Copy Markdown
Contributor

Let's do it. go go go 🚀

Thanks again everryone for your comments and help. I think this PR finally breaks open the path to separate out the last monolithic knot

@alamb
alamb merged commit 2eaf1ea into apache:mainJul 26, 2024
@findepi
findepi deleted the findepi/catalog-api branch July 26, 2024 14:59
@findepi

Copy link
Copy Markdown
MemberAuthor

🎉 thanks for all the review feedback and the merge!

@berkaysynnada

Copy link
Copy Markdown
Contributor

Are we tracking this TODO?

// TODO remove downcast_ref from here. Should file format factory be an extension to session state?

@findepi

Copy link
Copy Markdown
MemberAuthor

@berkaysynnada yes, #11600

@alamb

alamb commented Aug 2, 2024

Copy link
Copy Markdown
Contributor

@berkaysynnada yes, #11600

I suggest adding a comment in the code with the link to the ticket to help others find this ticket

@findepi

Copy link
Copy Markdown
MemberAuthor

good idea! #11784

Michael-J-Ward added a commit to Michael-J-Ward/datafusion-python that referenced this pull request Aug 10, 2024
Michael-J-Ward added a commit to Michael-J-Ward/datafusion-python that referenced this pull request Aug 10, 2024
Catlog API was extracted to a separate crate.
Ref: apache/datafusion#11516
@phillipleblanc

Copy link
Copy Markdown
Contributor

Looks like this PR didn't get tagged with api-change, so it got put under the "Documentation Updates" section in the v41 Changelog: https://github.com/apache/datafusion/blob/main/dev/changelog/41.0.0.md?plain=1#L94

@alamb

Copy link
Copy Markdown
Contributor

Sorry -- thanks for the heads up @phillipleblanc

Michael-J-Ward added a commit to Michael-J-Ward/datafusion-python that referenced this pull request Aug 20, 2024
Michael-J-Ward added a commit to Michael-J-Ward/datafusion-python that referenced this pull request Aug 20, 2024
Catlog API was extracted to a separate crate.
Ref: apache/datafusion#11516
andygrove pushed a commit to apache/datafusion-python that referenced this pull request Aug 23, 2024
* update datafusion deps to point to githuc.com/apache/datafusion
Datafusion 41 is not yet released on crates.io.
* update TableProvider::scan
Ref: apache/datafusion#11516
* use SessionStateBuilder
The old constructor is deprecated.
Ref: apache/datafusion#11403
* update AggregateFunction
Upstream Changes:
- The field name was switched from `func_name` to func.
- AggregateFunctionDefinition was removed
Ref: apache/datafusion#11803
* update imports in catalog
Catlog API was extracted to a separate crate.
Ref: apache/datafusion#11516
* use appropriate path for approx_distinct
Ref: apache/datafusion#11644
* migrate AggregateExt to ExprFunctionExt
Also removed `sqlparser` dependency since it's re-exported upstream.
Ref: apache/datafusion#11550
* update regr_count tests for new return type
Ref: apache/datafusion#11731
* migrate from function-array to functions-nested
The package was renamed upstream.
Ref: apache/datafusion#11602
* cargo fmt
* lock datafusion deps to 41
* remove todo from cargo.toml
All the datafusion dependencies are re-exported, but I still need to figure out *why*.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

coreCore DataFusion cratedocumentationImprovements or additions to documentationsqllogictestSQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@findepi@jayzhan211@alamb@cisaacson@berkaysynnada@phillipleblanc@Omega359