risingwave_frontend/binder/relation/
table_function.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::str::FromStr;
16
17use itertools::Itertools;
18use risingwave_common::bail_not_implemented;
19use risingwave_common::catalog::{Field, RW_INTERNAL_TABLE_FUNCTION_NAME, Schema};
20use risingwave_common::types::DataType;
21use risingwave_sqlparser::ast::{Function, FunctionArg, FunctionArgList, ObjectName, TableAlias};
22
23use super::watermark::is_watermark_func;
24use super::{Binder, Relation, Result, WindowTableFunctionKind};
25use crate::binder::bind_context::Clause;
26use crate::error::ErrorCode;
27use crate::expr::{Expr, ExprImpl};
28
29impl Binder {
30    /// Binds a table function AST, which is a function call in a relation position.
31    ///
32    /// Besides [`crate::expr::TableFunction`] expr, it can also be other things like window table
33    /// functions, or scalar functions.
34    ///
35    /// `with_ordinality` is only supported for the `TableFunction` case now.
36    pub(super) fn bind_table_function(
37        &mut self,
38        name: &ObjectName,
39        alias: Option<&TableAlias>,
40        args: &[FunctionArg],
41        with_ordinality: bool,
42    ) -> Result<Relation> {
43        let func_name = &name.0[0].real_value();
44        // internal/system table functions
45        {
46            if func_name.eq_ignore_ascii_case(RW_INTERNAL_TABLE_FUNCTION_NAME) {
47                if with_ordinality {
48                    bail_not_implemented!(
49                        "WITH ORDINALITY for internal/system table function {}",
50                        func_name
51                    );
52                }
53                return self.bind_internal_table(args, alias);
54            }
55        }
56        // window table functions (tumble/hop)
57        if let Ok(kind) = WindowTableFunctionKind::from_str(func_name) {
58            if with_ordinality {
59                return Err(ErrorCode::InvalidInputSyntax(format!(
60                    "WITH ORDINALITY for window table function {}",
61                    func_name
62                ))
63                .into());
64            }
65            return Ok(Relation::WindowTableFunction(Box::new(
66                self.bind_window_table_function(alias, kind, args)?,
67            )));
68        }
69        // gap_fill
70        if func_name.eq_ignore_ascii_case("gap_fill") {
71            if with_ordinality {
72                return Err(ErrorCode::InvalidInputSyntax(
73                    "WITH ORDINALITY for gap_fill".to_owned(),
74                )
75                .into());
76            }
77            return Ok(Relation::GapFill(Box::new(
78                self.bind_gap_fill(alias, args)?,
79            )));
80        }
81        // watermark
82        if is_watermark_func(func_name) {
83            if with_ordinality {
84                return Err(ErrorCode::InvalidInputSyntax(
85                    "WITH ORDINALITY for watermark".to_owned(),
86                )
87                .into());
88            }
89            return Ok(Relation::Watermark(Box::new(
90                self.bind_watermark(alias, args)?,
91            )));
92        };
93
94        self.push_context();
95        let mut clause = Some(Clause::From);
96        std::mem::swap(&mut self.context.clause, &mut clause);
97        let func = self.bind_function(&Function {
98            scalar_as_agg: false,
99            name: name.clone(),
100            arg_list: FunctionArgList::args_only(args.to_vec()),
101            over: None,
102            filter: None,
103            within_group: None,
104        });
105        self.context.clause = clause;
106        self.pop_context()?;
107        let func = func?;
108
109        if let ExprImpl::TableFunction(func) = &func
110            && func.args.iter().any(|arg| arg.has_subquery())
111        {
112            // Same error reports as DuckDB.
113            return Err(ErrorCode::InvalidInputSyntax(
114                    format!("Only table-in-out functions can have subquery parameters. The table function has subquery parameters is {}", func.name()),
115                )
116                    .into());
117        }
118
119        // bool indicates if the field is hidden
120        let mut columns = if let DataType::Struct(s) = func.return_type() {
121            // If the table function returns a struct, it will be flattened into multiple columns.
122            let schema = Schema::from(&s);
123            schema.fields.into_iter().map(|f| (false, f)).collect_vec()
124        } else {
125            // If there is an table alias (and it doesn't return a struct),
126            // we should use the alias as the table function's
127            // column name. If column aliases are also provided, they
128            // are handled in bind_table_to_context.
129            //
130            // Note: named return value should take precedence over table alias.
131            // But we don't support it yet.
132            // e.g.,
133            // ```sql
134            // > create function foo(ret out int) language sql as 'select 1';
135            // > select t.ret from foo() as t;
136            // ```
137            let col_name = if let Some(alias) = &alias {
138                alias.name.real_value()
139            } else {
140                func_name.clone()
141            };
142            vec![(false, Field::with_name(func.return_type(), col_name))]
143        };
144        if with_ordinality {
145            columns.push((false, Field::with_name(DataType::Int64, "ordinality")));
146        }
147
148        self.bind_table_to_context(columns, func_name.clone(), alias)?;
149
150        Ok(Relation::TableFunction {
151            expr: func,
152            with_ordinality,
153        })
154    }
155}