Skip to content

Unsupported table function arguments are silently dropped #21125

@RyanMarcus

Description

@RyanMarcus

Describe the bug

When a table function is called with a tuple argument, the SQL parser preserves the argument, but DataFusion drops it during planning and ContextProvider::get_table_function_source receives an empty args vector.

For example:

SELECT * FROM my_func(('a', 'b', 'c'));

... will lead to a call equivalent to get_table_function_source("my_func", vec![])

To Reproduce

With datafusion-sql 53.0.0:

use std::sync::Arc;

use datafusion_common::{arrow::datatypes::DataType, config::ConfigOptions, DataFusionError};
use datafusion_expr::{AggregateUDF, Expr, ScalarUDF, TableSource, WindowUDF};
use datafusion_sql::{
    planner::{ContextProvider, SqlToRel},
    sqlparser::{dialect::GenericDialect, parser::Parser},
    TableReference,
};

struct Dummy;

impl ContextProvider for Dummy {
    fn get_table_source(
        &self,
        _name: TableReference,
    ) -> Result<Arc<dyn TableSource>, DataFusionError> {
        unimplemented!()
    }

    fn get_table_function_source(
        &self,
        name: &str,
        args: Vec<Expr>,
    ) -> Result<Arc<dyn TableSource>, DataFusionError> {
        println!("table function name={name:?} args={args:#?}");
        unimplemented!()
    }

    fn get_function_meta(&self, _name: &str) -> Option<Arc<ScalarUDF>> { None }
    fn get_aggregate_meta(&self, _name: &str) -> Option<Arc<AggregateUDF>> { None }
    fn get_window_meta(&self, _name: &str) -> Option<Arc<WindowUDF>> { None }
    fn get_variable_type(&self, _variable_names: &[String]) -> Option<DataType> { None }

    fn options(&self) -> &ConfigOptions {
        static OPTIONS: std::sync::OnceLock<ConfigOptions> = std::sync::OnceLock::new();
        OPTIONS.get_or_init(ConfigOptions::default)
    }

    fn udf_names(&self) -> Vec<String> { vec![] }
    fn udaf_names(&self) -> Vec<String> { vec![] }
    fn udwf_names(&self) -> Vec<String> { vec![] }
}

fn main() {
    let sql = "select * from my_func(('a', 'b', 'c'))";
    let dialect = GenericDialect {};
    let ast = Parser::parse_sql(&dialect, sql).unwrap();

    println!("ast = {:#?}", ast[0]);

    let provider = Dummy;
    let sql_to_rel = SqlToRel::new(&provider);
    let _ = sql_to_rel.sql_statement_to_plan(ast[0].clone());
}

Cargo.toml:

[dependencies]
datafusion-common = "53.0.0"
datafusion-expr = "53.0.0"
datafusion-sql = "53.0.0"

This prints: table function name="my_func" args=[]

Expected behavior

I would expect get_table_function_source to either get a non-empty argument list, or for planning to return an error.

Additional context

It looks like the issue might be here:

The flat_map will happily turn the error result into a iterable, ignoring the error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions