apache / apache/datafusion

Construction of user-defined table functions (UDTFs) should be async to allow for async schemas

Open
#10,889 1 comment 2 reactions 0 assignees View on GitHub
enhancement
Dominant language
Rust
Stars
9.3k
Forks
2.4k
Avg merge
3d 7h
Merged PRs (30d)
344

Description

### Is your feature request related to a problem or challenge?

Accessing a `TableProviders` schema is a sync function call ([see here](https://github.com/apache/datafusion/blob/dfdda7cb04f7f9b640da4f297ce1a16b08f3bf7b/datafusion/core/src/datasource/provider.rs#L44)). This means that the `TableProvider` must know its schema before construction.

DataFusion recently introduced `TableFunctionImpl`, which allows users to define a function to create a `TableProvider.` Unfortunately, this `call` method is sync, meaning that the user-defined table function must know its schema upfront in a non-blocking way. This isn't possible when implementing TableProviders, which might infer their schema async, like an HTTP connector that can connect to arbitrary sources with payloads only known once the response is streaming in.

### Describe the solution you'd like

I propose we make the `call` method async to allow for async schemas and thus async table provider construction.

current code
```rust
use super::TableProvider;

use datafusion_common::Result;
use datafusion_expr::Expr;

use std::sync::Arc;

/// A trait for table function implementations
pub trait TableFunctionImpl: Sync + Send {
/// Create a table provider
fn call(&self, args: &[Expr]) -> Result>;
}

/// A table that uses a function to generate data
pub struct TableFunction {
/// Name of the table function
name: String,
/// Function implementation
fun: Arc,
}

impl TableFunction {
/// Create a new table function
pub fn new(name: String, fun: Arc) -> Self {
Self { name, fun }
}

/// Get the name of the table function
pub fn name(&self) -> &str {
&self.name
}

/// Get the function implementation and generate a table
pub fn create_table_provider(&self, args: &[Expr]) -> Result> {
self.fun.call(args)
}
}
```

where the new trait would looks something like

```rust
/// A trait for table function implementations
pub trait TableFunctionImpl: Sync + Send {
/// Create a table provider
async fn call(&self, args: &[Expr]) -> Result>;
}
```

### Describe alternatives you've considered

I've worked around this by creating the table outside of data fusion, but I would prefer to use the table functions to achieve the same thing.

### Additional context

_No response_

Contributor guide

Open the contributing guide

Research direction

Start with datafusion/core/src/datasource/provider.rs and trace TableFunctionImpl::call and TableFunction::create_table_provider through their call sites. Determine the required async API changes and verify that table providers whose schemas are inferred asynchronously can be constructed from user-defined table functions without leaving synchronous callers unresolved.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend-api-design, databases
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.