Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion datafusion/catalog/src/information_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
//! [Information Schema]: https://en.wikipedia.org/wiki/Information_schema

use crate::streaming::StreamingTable;
use crate::table::TableFunction;
use crate::{CatalogProviderList, SchemaProvider, TableProvider};
use arrow::array::builder::{BooleanBuilder, UInt8Builder};
use arrow::{
Expand Down Expand Up @@ -81,14 +82,28 @@ impl InformationSchemaProvider {
/// Creates a new [`InformationSchemaProvider`] for the provided `catalog_list`
pub fn new(catalog_list: Arc<dyn CatalogProviderList>) -> Self {
Self {
config: InformationSchemaConfig { catalog_list },
config: InformationSchemaConfig {
catalog_list,
table_functions: HashMap::new(),
},
}
}

/// Attach the session's table (UDTF) functions so that they appear in
/// `information_schema.routines` / `SHOW FUNCTIONS`.
pub fn with_table_functions(
mut self,
table_functions: HashMap<String, Arc<TableFunction>>,
) -> Self {
self.config.table_functions = table_functions;
self
}
}

#[derive(Clone, Debug)]
struct InformationSchemaConfig {
catalog_list: Arc<dyn CatalogProviderList>,
table_functions: HashMap<String, Arc<TableFunction>>,
}

impl InformationSchemaConfig {
Expand Down Expand Up @@ -301,6 +316,26 @@ impl InformationSchemaConfig {
)
}
}

// Table functions (UDTFs) don't have scalar signatures; their return
// type is always a table, so emit a single row per UDTF with
// routine_type = "FUNCTION", function_type = "TABLE" and
// data_type = "TABLE".
for name in self.table_functions.keys() {
builder.add_routine(
catalog_name,
schema_name,
name,
"FUNCTION",
// No signature is available for UDTFs; report deterministic
// = false to stay conservative.
false,
Some(&"TABLE"),
"TABLE",
None::<String>,
None::<String>,
)
}
Ok(())
}

Expand Down Expand Up @@ -400,6 +435,14 @@ impl InformationSchemaConfig {
}
}

// UDTFs deliberately do NOT appear in `information_schema.parameters`.
// A same-named scalar UDF (e.g. `generate_series` exists as both a
// scalar UDF in functions-nested and a UDTF in functions-table) would
// cross-join with a UDTF row keyed only by (name, rid) and produce
// spurious `TABLE`-typed variants of every scalar signature in
// SHOW FUNCTIONS. `show_functions_to_plan` sources UDTFs directly
// from `information_schema.routines` via a UNION branch instead.

Ok(())
}

Expand Down Expand Up @@ -1522,6 +1565,7 @@ mod tests {
async fn make_tables_uses_table_type() {
let config = InformationSchemaConfig {
catalog_list: Arc::new(Fixture),
table_functions: HashMap::new(),
};
let mut builder = InformationSchemaTablesBuilder {
catalog_names: StringBuilder::new(),
Expand Down
7 changes: 4 additions & 3 deletions datafusion/core/src/execution/session_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,10 @@ impl SessionState {
let resolved_ref = self.resolve_table_ref(table_ref);
if self.config.information_schema() && *resolved_ref.schema == *INFORMATION_SCHEMA
{
return Ok(Arc::new(InformationSchemaProvider::new(Arc::clone(
&self.catalog_list,
))));
return Ok(Arc::new(
InformationSchemaProvider::new(Arc::clone(&self.catalog_list))
.with_table_functions(self.table_functions.clone()),
));
}

self.catalog_list
Expand Down
64 changes: 49 additions & 15 deletions datafusion/sql/src/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2631,17 +2631,35 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
"".to_string()
};

// Scalar / aggregate / window functions are resolved by joining
// parameters (IN rows aggregated per OUT row) with routines.
// Table functions (UDTFs) don't have parameter rows, so they are
// sourced directly from routines via a UNION branch. Restricting
// the JOIN to non-TABLE routines prevents same-named scalar+UDTF
// pairs (e.g. `generate_series`) from cross-joining.
let where_clause = where_clause.replace("p.function_name", "sc.function_name");
let query = format!(
r#"
SELECT DISTINCT
p.*,
r.function_type function_type,
r.description description,
r.syntax_example syntax_example
FROM
(
sc.function_name,
sc.return_type,
sc.parameters,
sc.parameter_types,
sc.function_type,
sc.description,
sc.syntax_example
FROM (
SELECT
p.function_name,
p.return_type,
p.parameters,
p.parameter_types,
r.function_type function_type,
r.description description,
r.syntax_example syntax_example
FROM (
SELECT
i.specific_name function_name,
o.specific_name function_name,
o.data_type return_type,
array_agg(i.parameter_name ORDER BY i.ordinal_position ASC) parameters,
array_agg(i.data_type ORDER BY i.ordinal_position ASC) parameter_types
Expand All @@ -2657,9 +2675,9 @@ FROM
FROM
information_schema.parameters
WHERE
parameter_mode = 'IN'
) i
JOIN
parameter_mode = 'OUT'
) o
LEFT JOIN
(
SELECT
specific_catalog,
Expand All @@ -2672,16 +2690,32 @@ FROM
FROM
information_schema.parameters
WHERE
parameter_mode = 'OUT'
) o
parameter_mode = 'IN'
) i
ON i.specific_catalog = o.specific_catalog
AND i.specific_schema = o.specific_schema
AND i.specific_name = o.specific_name
AND i.rid = o.rid
GROUP BY 1, 2, i.rid
GROUP BY 1, 2, o.rid
) as p
JOIN information_schema.routines r
ON p.function_name = r.routine_name
JOIN information_schema.routines r
ON p.function_name = r.routine_name
AND r.function_type <> 'TABLE'

UNION ALL

SELECT
routine_name function_name,
data_type return_type,
array_agg(NULL) FILTER (WHERE FALSE) parameters,
array_agg(NULL) FILTER (WHERE FALSE) parameter_types,
function_type,
description,
syntax_example
FROM information_schema.routines
WHERE function_type = 'TABLE'
GROUP BY routine_name, data_type, function_type, description, syntax_example
) sc
{where_clause}
"#
);
Expand Down
11 changes: 11 additions & 0 deletions datafusion/sqllogictest/test_files/information_schema.slt
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,17 @@ date_trunc Time(ns) [precision, expression] [String, Time(ns)] SCALAR Truncates
date_trunc Timestamp(ns) [precision, expression] [String, Timestamp(ns)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression)
date_trunc Timestamp(ns, "+TZ") [precision, expression] [String, Timestamp(ns, "+TZ")] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression)

# Table functions (UDTFs) appear in information_schema.routines with
# function_type = TABLE and data_type = TABLE.
# Note: built-in `generate_series` and `range` are registered as BOTH a
# scalar UDF and a UDTF, so this test filters to the TABLE rows to make
# a stable assertion.
query TTT rowsort
select routine_name, data_type, function_type from information_schema.routines where function_type = 'TABLE' order by routine_name;
----
generate_series TABLE TABLE
range TABLE TABLE

statement ok
show functions

Expand Down
Loading