diff --git a/e2e_test/batch/basic/range.slt.part b/e2e_test/batch/basic/range.slt.part index 436a2639593d9..9d404e5f5b97b 100644 --- a/e2e_test/batch/basic/range.slt.part +++ b/e2e_test/batch/basic/range.slt.part @@ -113,3 +113,19 @@ SELECT * FROM range(0.1::numeric, 2.1::numeric, 0.5::numeric) 0.6 1.1 1.6 + +# test table function with aliases +query I +SELECT alias from range(1,2) alias; +---- +1 + +query I +SELECT alias.alias from range(1,2) alias; +---- +1 + +query I +SELECT alias.col from range(1,2) alias(col); +---- +1 diff --git a/src/frontend/src/binder/relation/mod.rs b/src/frontend/src/binder/relation/mod.rs index 0ac9ae9b9c81b..ba43993d969b2 100644 --- a/src/frontend/src/binder/relation/mod.rs +++ b/src/frontend/src/binder/relation/mod.rs @@ -462,10 +462,28 @@ impl Binder { .into()); }; let columns = if let DataType::Struct(s) = tf.return_type() { + // If the table function returns a struct, it's fields can be accessed just + // like a table's columns. let schema = Schema::from(&*s); schema.fields.into_iter().map(|f| (false, f)).collect_vec() } else { - vec![(false, Field::with_name(tf.return_type(), tf.name()))] + // If there is an table alias, we should use the alias as the table function's + // column name. If column aliases are also provided, they + // are handled in bind_table_to_context. + // + // Note: named return value should take precedence over table alias. + // But we don't support it yet. + // e.g., + // ``` + // > create function foo(ret out int) language sql as 'select 1'; + // > select t.ret from foo() as t; + // ``` + let col_name = if let Some(alias) = &alias { + alias.name.real_value() + } else { + tf.name().to_string() + }; + vec![(false, Field::with_name(tf.return_type(), col_name))] }; self.bind_table_to_context(columns, tf.name().to_string(), alias)?;