Skip to main content

risingwave_frontend/binder/expr/function/
mod.rs

1// Copyright 2024 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::collections::{HashMap, HashSet};
16use std::str::FromStr;
17use std::sync::Arc;
18
19use anyhow::Context;
20use itertools::Itertools;
21use risingwave_common::acl::AclMode;
22use risingwave_common::bail_not_implemented;
23use risingwave_common::catalog::INFORMATION_SCHEMA_SCHEMA_NAME;
24use risingwave_common::license::Feature;
25use risingwave_common::secret::LocalSecretManager;
26use risingwave_common::types::{DataType, MapType, StructType};
27use risingwave_common::util::iter_util::ZipEqFast;
28use risingwave_expr::aggregate::AggType;
29use risingwave_expr::window_function::WindowFuncKind;
30use risingwave_sqlparser::ast::{
31    self, Expr as AstExpr, Function, FunctionArg, FunctionArgExpr, FunctionArgList, Ident,
32    OrderByExpr, SecretRefAsType, Statement, Window,
33};
34use risingwave_sqlparser::parser::Parser;
35
36use crate::binder::Binder;
37use crate::binder::bind_context::Clause;
38use crate::catalog::OwnedByUserCatalog;
39use crate::catalog::function_catalog::FunctionCatalog;
40use crate::error::{ErrorCode, Result, RwError};
41use crate::expr::{
42    Expr, ExprImpl, ExprType, FunctionCall, FunctionCallWithLambda, InputRef,
43    MSSQL_QUERY_ARG_COUNT, TableFunction, TableFunctionType, UserDefinedFunction,
44    expr_impl_to_string_fn,
45};
46use crate::handler::privilege::ObjectCheckItem;
47
48mod aggregate;
49mod builtin_scalar;
50mod window;
51
52// Defines system functions that without args, ref: https://www.postgresql.org/docs/current/functions-info.html
53const SYS_FUNCTION_WITHOUT_ARGS: &[&str] = &[
54    "session_user",
55    "user",
56    "current_user",
57    "current_role",
58    "current_catalog",
59    "current_schema",
60    "current_timestamp",
61];
62
63const INLINE_QUERY_ARG_LEN: usize = 6;
64const CDC_SOURCE_QUERY_ARG_LEN: usize = 2;
65
66pub(super) fn is_sys_function_without_args(ident: &Ident) -> bool {
67    SYS_FUNCTION_WITHOUT_ARGS
68        .iter()
69        .any(|e| ident.real_value().as_str() == *e && ident.quote_style().is_none())
70}
71
72macro_rules! reject_syntax {
73    ($pred:expr, $msg:expr) => {
74        if $pred {
75            return Err(ErrorCode::InvalidInputSyntax($msg.to_string()).into());
76        }
77    };
78
79    ($pred:expr, $fmt:expr, $($arg:tt)*) => {
80        if $pred {
81            return Err(ErrorCode::InvalidInputSyntax(
82                format!($fmt, $($arg)*)
83            ).into());
84        }
85    };
86}
87
88impl Binder {
89    /// Validate and resolve the args for the external-database `_query`
90    /// table-valued functions (`postgres_query`, `mysql_query`, and
91    /// `mssql_query`).
92    ///
93    /// Two calling forms are supported:
94    /// * **Inline form** (`inline_arg_len` args): each argument is cast to
95    ///   `varchar` and returned verbatim. The caller picks `inline_arg_len`
96    ///   because the per-connector inline signature differs (`postgres` /
97    ///   `mysql` = 6 args; `sqlserver` = 8 args to carry `encrypt` /
98    ///   `trust_cert`).
99    /// * **Source-reference form** (2 args: `cdc_source_name, query`): the
100    ///   named source's connection parameters and secrets are resolved,
101    ///   expanded into the inline shape, and the per-connector TLS flags
102    ///   are appended. **Crucially, this path enforces `AclMode::Select` on
103    ///   the source before the secrets are expanded.** Without this check,
104    ///   a non-owner caller could use
105    ///   `mssql_query('my_source', 'SELECT ...')` to query the external
106    ///   database using the source's elevated credentials.
107    fn bind_external_db_query_args(
108        &self,
109        schema_name: Option<&str>,
110        args: Vec<ExprImpl>,
111        inline_arg_len: usize,
112        expected_connector_name: &str,
113    ) -> Result<Vec<ExprImpl>> {
114        match args.len() {
115            n if n == inline_arg_len => {
116                let mut cast_args = Vec::with_capacity(n);
117                for arg in args {
118                    cast_args.push(arg.cast_implicit(&DataType::Varchar)?);
119                }
120                Ok(cast_args)
121            }
122            CDC_SOURCE_QUERY_ARG_LEN => {
123                use thiserror_ext::AsReport as _;
124
125                use crate::catalog::root_catalog::SchemaPath;
126
127                let schema_path = self.bind_schema_path(schema_name);
128                let raw_source_name = expr_impl_to_string_fn(&args[0])?;
129                // Accept `schema.source` and quoted identifiers, matching every other SQL surface.
130                // Historical behavior fed the whole string (dot included) into the catalog lookup,
131                // so a qualified name could never match and an unqualified name only searched the
132                // session `search_path` — which broke sources created in non-default schemas.
133                let object_name = Parser::parse_object_name_str(&raw_source_name).map_err(|e| {
134                    ErrorCode::BindError(format!(
135                        "invalid source name `{}`: {}",
136                        raw_source_name,
137                        e.to_report_string(),
138                    ))
139                })?;
140                let (explicit_schema, source_name) =
141                    Binder::resolve_schema_qualified_name(&self.db_name, &object_name)?;
142                let source_schema_path = match explicit_schema.as_deref() {
143                    Some(schema) => SchemaPath::Name(schema),
144                    None => schema_path,
145                };
146                let source_catalog = self
147                    .catalog
148                    .get_source_by_name(&self.db_name, source_schema_path, &source_name)?
149                    .0;
150
151                // Enforce the source's SELECT privilege *before* expanding
152                // secrets or doing schema discovery, so a non-owner caller
153                // cannot read the external database using a source they
154                // have no grant on. Same ordering as the inline-form arg
155                // cast above: refuse early, leak nothing.
156                self.check_privilege(
157                    ObjectCheckItem::new(
158                        source_catalog.owner,
159                        AclMode::Select,
160                        source_catalog.name.clone(),
161                        source_catalog.id,
162                    ),
163                    source_catalog.database_id,
164                )?;
165
166                if source_catalog.is_cdc_table_source() {
167                    return Err(ErrorCode::BindError(
168                        "mysql_query and postgres_query only accept shared CDC sources, not CDC table sources"
169                            .to_owned(),
170                    )
171                    .into());
172                }
173
174                if !source_catalog
175                    .connector_name()
176                    .eq_ignore_ascii_case(expected_connector_name)
177                {
178                    return Err(ErrorCode::BindError(format!(
179                        "TVF function only accepts `mysql-cdc`, `postgres-cdc` and `sqlserver-cdc` source. Expected: {}, but got: {}",
180                        expected_connector_name,
181                        source_catalog.connector_name()
182                    ))
183                    .into());
184                }
185
186                let (props, secret_refs) = source_catalog.with_properties.clone().into_parts();
187                let secret_resolved =
188                    LocalSecretManager::global().fill_secrets(props, secret_refs)?;
189
190                let mut args_vec = vec![
191                    ExprImpl::literal_varchar(secret_resolved["hostname"].clone()),
192                    ExprImpl::literal_varchar(secret_resolved["port"].clone()),
193                    ExprImpl::literal_varchar(secret_resolved["username"].clone()),
194                    ExprImpl::literal_varchar(secret_resolved["password"].clone()),
195                    ExprImpl::literal_varchar(secret_resolved["database.name"].clone()),
196                    args[1].clone().cast_implicit(&DataType::Varchar)?,
197                ];
198
199                if expected_connector_name.eq_ignore_ascii_case("postgres-cdc") {
200                    args_vec.push(ExprImpl::literal_varchar(
201                        secret_resolved.get("ssl.mode").cloned().unwrap_or_default(),
202                    ));
203                    args_vec.push(ExprImpl::literal_varchar(
204                        secret_resolved
205                            .get("ssl.root.cert")
206                            .cloned()
207                            .unwrap_or_default(),
208                    ));
209                } else if expected_connector_name.eq_ignore_ascii_case("sqlserver-cdc") {
210                    // The CDC source is the source of truth for whether the
211                    // SQL Server connection requires TLS. Normalize the
212                    // source property to the public TVF's string argument;
213                    // `TableFunction::new_mssql_query` parses it once and
214                    // carries a boolean through the internal plan.
215                    let encrypt = secret_resolved
216                        .get("database.encrypt")
217                        .map(|v| v.eq_ignore_ascii_case("true").to_string())
218                        .unwrap_or_else(|| "false".to_owned());
219                    args_vec.push(ExprImpl::literal_varchar(encrypt));
220                    // The CDC implementation sets `trust_cert`
221                    // unconditionally (see
222                    // `connector::sink::sqlserver::SqlServerClient::new`),
223                    // so the source-reference form mirrors that.
224                    args_vec.push(ExprImpl::literal_varchar("true".to_owned()));
225                }
226
227                Ok(args_vec)
228            }
229            _ => {
230                let func_name = match expected_connector_name.to_ascii_lowercase().as_str() {
231                    "postgres-cdc" => "postgres_query",
232                    "mysql-cdc" => "mysql_query",
233                    "sqlserver-cdc" => "mssql_query",
234                    _ => "external _query",
235                };
236                Err(ErrorCode::BindError(format!(
237                    "{func_name} function accepts either 2 arguments: \
238                     (cdc_source_name varchar, query varchar) or {inline_arg_len} arguments \
239                     for the inline form (see mssql_query / postgres_query / mysql_query \
240                     documentation)"
241                ))
242                .into())
243            }
244        }
245    }
246
247    pub(in crate::binder) fn bind_function(
248        &mut self,
249        Function {
250            scalar_as_agg,
251            name,
252            arg_list,
253            within_group,
254            filter,
255            over,
256        }: &Function,
257    ) -> Result<ExprImpl> {
258        let (schema_name, func_name) = match name.0.as_slice() {
259            [name] => (None, name.real_value()),
260            [schema, name] => {
261                let schema_name = schema.real_value();
262                let func_name = if schema_name == INFORMATION_SCHEMA_SCHEMA_NAME {
263                    // definition of information_schema: https://github.com/postgres/postgres/blob/e0b2eed047df9045664da6f724cb42c10f8b12f0/src/backend/catalog/information_schema.sql
264                    //
265                    // FIXME: handle schema correctly, so that the functions are hidden if the schema is not in the search path.
266                    let function_name = name.real_value();
267                    if function_name != "_pg_expandarray" {
268                        bail_not_implemented!(
269                            issue = 12422,
270                            "Unsupported function name under schema: {}",
271                            schema_name
272                        );
273                    }
274                    function_name
275                } else {
276                    name.real_value()
277                };
278                (Some(schema_name), func_name)
279            }
280            [database, schema, name] => {
281                // Support database.schema.function qualified names when database matches current database
282                let database_name = database.real_value();
283                if database_name != self.db_name {
284                    return Err(ErrorCode::BindError(format!(
285                        "Cross-database function call is not supported: {}",
286                        name
287                    ))
288                    .into());
289                }
290                let schema_name = schema.real_value();
291                let func_name = name.real_value();
292                (Some(schema_name), func_name)
293            }
294            _ => bail_not_implemented!(issue = 112, "qualified function {}", name),
295        };
296
297        // FIXME: This is a hack to support [Bytebase queries](https://github.com/TennyZhuang/bytebase/blob/4a26f7c62b80e86e58ad2f77063138dc2f420623/backend/plugin/db/pg/sync.go#L549).
298        // Bytebase widely used the pattern like `obj_description(format('%s.%s',
299        // quote_ident(idx.schemaname), quote_ident(idx.indexname))::regclass) AS comment` to
300        // retrieve object comment, however we don't support casting a non-literal expression to
301        // regclass. We just hack the `obj_description` and `col_description` here, to disable it to
302        // bind its arguments.
303        if func_name == "obj_description" || func_name == "col_description" {
304            return Ok(ExprImpl::literal_varchar("".to_owned()));
305        }
306
307        // special binding logic for `array_transform` and `map_filter`
308        if func_name == "array_transform" || func_name == "map_filter" {
309            return self.validate_and_bind_special_function_params(
310                &func_name,
311                *scalar_as_agg,
312                arg_list,
313                within_group.as_deref(),
314                filter.as_deref(),
315                over.as_ref(),
316            );
317        }
318
319        // Bind function arguments. Secret references are bound as ExprImpl::SecretRef first,
320        // and then restricted to UDF calls below.
321        let has_secret_ref_arg = arg_list.args.iter().any(|arg| {
322            matches!(
323                arg,
324                FunctionArg::Unnamed(FunctionArgExpr::SecretRef(_))
325                    | FunctionArg::Named {
326                        arg: FunctionArgExpr::SecretRef(_),
327                        ..
328                    }
329            )
330        });
331
332        // Return a clear usage error before secret lookup when there is no UDF candidate by name.
333        // This avoids leaking "not found" for secret refs used in clearly invalid contexts.
334        if has_secret_ref_arg {
335            let has_udf_candidate = self
336                .catalog
337                .get_functions_by_name(
338                    &self.db_name,
339                    self.bind_schema_path(schema_name.as_deref()),
340                    &func_name,
341                )
342                .map(|(funcs, _)| !funcs.is_empty())
343                .unwrap_or(false);
344            if !has_udf_candidate {
345                return Err(ErrorCode::InvalidInputSyntax(
346                    "secret reference is only allowed in user-defined function arguments"
347                        .to_owned(),
348                )
349                .into());
350            }
351        }
352
353        let bind_arg = if func_name.eq_ignore_ascii_case("jsonb_agg") {
354            Self::bind_jsonb_agg_arg
355        } else {
356            Self::bind_function_arg
357        };
358        let mut args: Vec<ExprImpl> = arg_list
359            .args
360            .iter()
361            .map(|arg| bind_arg(self, arg))
362            .flatten_ok()
363            .try_collect()?;
364
365        let mut referred_udfs = HashSet::new();
366        let mut is_udf_call = false;
367
368        let wrapped_agg_type = if *scalar_as_agg {
369            // Let's firstly try to apply the `AGGREGATE:` prefix.
370            // We will reject functions that are not able to be wrapped as aggregate function.
371            let mut array_args = args
372                .iter()
373                .enumerate()
374                .map(|(i, expr)| InputRef::new(i, DataType::list(expr.return_type())).into())
375                .collect_vec();
376            let schema_path = self.bind_schema_path(schema_name.as_deref());
377            let scalar_func_expr = if let Ok((func, _)) = self.catalog.get_function_by_name_inputs(
378                &self.db_name,
379                schema_path,
380                &func_name,
381                &mut array_args,
382            ) {
383                // record the dependency upon the UDF
384                referred_udfs.insert(func.id);
385                is_udf_call = true;
386                self.check_privilege(
387                    ObjectCheckItem::new(func.owner, AclMode::Execute, func.name.clone(), func.id),
388                    self.database_id,
389                )?;
390
391                if !func.kind.is_scalar() {
392                    return Err(ErrorCode::InvalidInputSyntax(
393                        "expect a scalar function after `AGGREGATE:`".to_owned(),
394                    )
395                    .into());
396                }
397
398                if func.language == "sql" {
399                    self.bind_sql_udf(func.clone(), array_args)?
400                } else {
401                    UserDefinedFunction::new(func.clone(), array_args).into()
402                }
403            } else {
404                self.bind_builtin_scalar_function(&func_name, array_args, arg_list.variadic)?
405            };
406
407            // `AggType::WrapScalar` requires the inner expression to be convertible to `ExprNode`
408            // and directly executable. If there's any subquery in the expression, this will fail.
409            let expr_node = match scalar_func_expr.try_to_expr_proto() {
410                Ok(expr_node) => expr_node,
411                Err(e) => {
412                    return Err(ErrorCode::InvalidInputSyntax(format!(
413                        "function {func_name} cannot be used after `AGGREGATE:`: {e}",
414                    ))
415                    .into());
416                }
417            };
418
419            // now this is either an aggregate/window function call
420            Some(AggType::WrapScalar(expr_node))
421        } else {
422            None
423        };
424
425        let schema_path = self.bind_schema_path(schema_name.as_deref());
426        let udf = if wrapped_agg_type.is_none()
427            && let Ok((func, _)) = self.catalog.get_function_by_name_inputs(
428                &self.db_name,
429                schema_path,
430                &func_name,
431                &mut args,
432            ) {
433            // record the dependency upon the UDF
434            referred_udfs.insert(func.id);
435            is_udf_call = true;
436            self.check_privilege(
437                ObjectCheckItem::new(func.owner, AclMode::Execute, func.name.clone(), func.id),
438                self.database_id,
439            )?;
440            Some(func.clone())
441        } else {
442            None
443        };
444
445        if has_secret_ref_arg && !is_udf_call {
446            return Err(ErrorCode::InvalidInputSyntax(
447                "secret reference is only allowed in user-defined function arguments".to_owned(),
448            )
449            .into());
450        }
451
452        self.included_udfs.extend(referred_udfs);
453
454        let agg_type = if wrapped_agg_type.is_some() {
455            wrapped_agg_type
456        } else if let Some(ref udf) = udf
457            && udf.kind.is_aggregate()
458        {
459            assert_ne!(udf.language, "sql", "SQL UDAF is not supported yet");
460            Some(AggType::UserDefined(udf.as_ref().into()))
461        } else {
462            AggType::from_str(&func_name).ok()
463        };
464
465        // try to bind it as a window function call
466        if let Some(over) = over {
467            reject_syntax!(
468                arg_list.distinct,
469                "`DISTINCT` is not allowed in window function call"
470            );
471            reject_syntax!(
472                arg_list.variadic,
473                "`VARIADIC` is not allowed in window function call"
474            );
475            reject_syntax!(
476                !arg_list.order_by.is_empty(),
477                "`ORDER BY` is not allowed in window function call argument list"
478            );
479            reject_syntax!(
480                within_group.is_some(),
481                "`WITHIN GROUP` is not allowed in window function call"
482            );
483
484            let kind = if let Some(agg_type) = agg_type {
485                // aggregate as window function
486                WindowFuncKind::Aggregate(agg_type)
487            } else if let Ok(kind) = WindowFuncKind::from_str(&func_name) {
488                kind
489            } else {
490                bail_not_implemented!(issue = 8961, "Unrecognized window function: {}", func_name);
491            };
492            return self.bind_window_function(
493                kind,
494                args,
495                arg_list.ignore_nulls,
496                filter.as_deref(),
497                over,
498            );
499        }
500
501        // now it's an aggregate/scalar/table function call
502        reject_syntax!(
503            arg_list.ignore_nulls,
504            "`IGNORE NULLS` is not allowed in aggregate/scalar/table function call"
505        );
506
507        // try to bind it as an aggregate function call
508        if let Some(agg_type) = agg_type {
509            reject_syntax!(
510                arg_list.variadic,
511                "`VARIADIC` is not allowed in aggregate function call"
512            );
513            return self.bind_aggregate_function(
514                agg_type,
515                arg_list.distinct,
516                args,
517                &arg_list.order_by,
518                within_group.as_deref(),
519                filter.as_deref(),
520            );
521        }
522
523        // now it's a scalar/table function call
524        reject_syntax!(
525            arg_list.distinct,
526            "`DISTINCT` is not allowed in scalar/table function call"
527        );
528        reject_syntax!(
529            !arg_list.order_by.is_empty(),
530            "`ORDER BY` is not allowed in scalar/table function call"
531        );
532        reject_syntax!(
533            within_group.is_some(),
534            "`WITHIN GROUP` is not allowed in scalar/table function call"
535        );
536        reject_syntax!(
537            filter.is_some(),
538            "`FILTER` is not allowed in scalar/table function call"
539        );
540
541        // try to bind it as a table function call
542        {
543            // `file_scan` table function
544            if func_name.eq_ignore_ascii_case("file_scan") {
545                reject_syntax!(
546                    arg_list.variadic,
547                    "`VARIADIC` is not allowed in table function call"
548                );
549                self.ensure_table_function_allowed()?;
550                return Ok(TableFunction::new_file_scan(args)?.into());
551            }
552            // `postgres_query` table function
553            if func_name.eq("postgres_query") {
554                reject_syntax!(
555                    arg_list.variadic,
556                    "`VARIADIC` is not allowed in table function call"
557                );
558                self.ensure_table_function_allowed()?;
559                let args = self
560                    .bind_external_db_query_args(
561                        schema_name.as_deref(),
562                        args,
563                        INLINE_QUERY_ARG_LEN,
564                        "postgres-cdc",
565                    )
566                    .context("postgres_query error")?;
567                return Ok(TableFunction::new_postgres_query(args)
568                    .context("postgres_query error")?
569                    .into());
570            }
571            // `mysql_query` table function
572            if func_name.eq("mysql_query") {
573                reject_syntax!(
574                    arg_list.variadic,
575                    "`VARIADIC` is not allowed in table function call"
576                );
577                self.ensure_table_function_allowed()?;
578                let args = self
579                    .bind_external_db_query_args(
580                        schema_name.as_deref(),
581                        args,
582                        INLINE_QUERY_ARG_LEN,
583                        "mysql-cdc",
584                    )
585                    .context("mysql_query error")?;
586                return Ok(TableFunction::new_mysql_query(args)
587                    .context("mysql_query error")?
588                    .into());
589            }
590            // `mssql_query` table function (enterprise feature)
591            if func_name.eq("mssql_query") {
592                reject_syntax!(
593                    arg_list.variadic,
594                    "`VARIADIC` is not allowed in table function call"
595                );
596                self.ensure_table_function_allowed()?;
597
598                // Reuse the SQL Server CDC source entitlement instead of
599                // introducing a separate license gate for this query surface.
600                Feature::SqlServerCdcSource.check_available()?;
601
602                let args = self
603                    .bind_external_db_query_args(
604                        schema_name.as_deref(),
605                        args,
606                        MSSQL_QUERY_ARG_COUNT,
607                        "sqlserver-cdc",
608                    )
609                    .context("mssql_query error")?;
610                return Ok(TableFunction::new_mssql_query(args)
611                    .context("mssql_query error")?
612                    .into());
613            }
614            // `internal_backfill_progress` table function
615            if func_name.eq("internal_backfill_progress") {
616                reject_syntax!(
617                    arg_list.variadic,
618                    "`VARIADIC` is not allowed in table function call"
619                );
620                self.ensure_table_function_allowed()?;
621                return Ok(TableFunction::new_internal_backfill_progress().into());
622            }
623            // `internal_source_backfill_progress` table function
624            if func_name.eq("internal_source_backfill_progress") {
625                reject_syntax!(
626                    arg_list.variadic,
627                    "`VARIADIC` is not allowed in table function call"
628                );
629                self.ensure_table_function_allowed()?;
630                return Ok(TableFunction::new_internal_source_backfill_progress().into());
631            }
632            // `internal_get_channel_delta_stats` table function
633            if func_name.eq("internal_get_channel_delta_stats") {
634                reject_syntax!(
635                    arg_list.variadic,
636                    "`VARIADIC` is not allowed in table function call"
637                );
638                self.ensure_table_function_allowed()?;
639
640                return Ok(TableFunction::new_internal_get_channel_delta_stats(args).into());
641            }
642            // UDTF
643            if let Some(ref udf) = udf
644                && udf.kind.is_table()
645            {
646                reject_syntax!(
647                    arg_list.variadic,
648                    "`VARIADIC` is not allowed in table function call"
649                );
650                self.ensure_table_function_allowed()?;
651                if udf.language == "sql" {
652                    return self.bind_sql_udf(udf.clone(), args);
653                }
654                return Ok(TableFunction::new_user_defined(udf.clone(), args).into());
655            }
656            // builtin table function
657            if let Ok(function_type) = TableFunctionType::from_str(&func_name) {
658                reject_syntax!(
659                    arg_list.variadic,
660                    "`VARIADIC` is not allowed in table function call"
661                );
662                self.ensure_table_function_allowed()?;
663                return Ok(TableFunction::new(function_type, args)?.into());
664            }
665        }
666
667        // try to bind it as a scalar function call
668        if let Some(ref udf) = udf {
669            assert!(udf.kind.is_scalar());
670            reject_syntax!(
671                arg_list.variadic,
672                "`VARIADIC` is not allowed in user-defined function call"
673            );
674            if udf.language == "sql" {
675                return self.bind_sql_udf(udf.clone(), args);
676            }
677            return Ok(UserDefinedFunction::new(udf.clone(), args).into());
678        }
679
680        self.bind_builtin_scalar_function(&func_name, args, arg_list.variadic)
681    }
682
683    fn validate_and_bind_special_function_params(
684        &mut self,
685        func_name: &str,
686        scalar_as_agg: bool,
687        arg_list: &FunctionArgList,
688        within_group: Option<&OrderByExpr>,
689        filter: Option<&risingwave_sqlparser::ast::Expr>,
690        over: Option<&Window>,
691    ) -> Result<ExprImpl> {
692        assert!(["array_transform", "map_filter"].contains(&func_name));
693
694        reject_syntax!(
695            scalar_as_agg,
696            "`AGGREGATE:` prefix is not allowed for `{}`",
697            func_name
698        );
699        reject_syntax!(
700            !arg_list.is_args_only(),
701            "keywords like `DISTINCT`, `ORDER BY` are not allowed in `{}` argument list",
702            func_name
703        );
704        reject_syntax!(
705            within_group.is_some(),
706            "`WITHIN GROUP` is not allowed in `{}` call",
707            func_name
708        );
709        reject_syntax!(
710            filter.is_some(),
711            "`FILTER` is not allowed in `{}` call",
712            func_name
713        );
714        reject_syntax!(
715            over.is_some(),
716            "`OVER` is not allowed in `{}` call",
717            func_name
718        );
719        if func_name == "array_transform" {
720            self.bind_array_transform(&arg_list.args)
721        } else {
722            self.bind_map_filter(&arg_list.args)
723        }
724    }
725
726    fn bind_array_transform(&mut self, args: &[FunctionArg]) -> Result<ExprImpl> {
727        let [array, lambda] = args else {
728            return Err(ErrorCode::BindError(format!(
729                "`array_transform` expect two inputs `array` and `lambda`, but {} were given",
730                args.len()
731            ))
732            .into());
733        };
734
735        let bound_array = self.bind_function_arg(array)?;
736        let [bound_array] = <[ExprImpl; 1]>::try_from(bound_array).map_err(|bound_array| -> RwError {
737            ErrorCode::BindError(format!("The `array` argument for `array_transform` should be bound to one argument, but {} were got", bound_array.len()))
738                .into()
739        })?;
740
741        let inner_ty = match bound_array.return_type() {
742            DataType::List(ty) => ty.into_elem(),
743            real_type => return Err(ErrorCode::BindError(format!(
744                "The `array` argument for `array_transform` should be an array, but {} were got",
745                real_type
746            ))
747            .into()),
748        };
749
750        let ast::FunctionArgExpr::Expr(ast::Expr::LambdaFunction {
751            args: lambda_args,
752            body: lambda_body,
753        }) = lambda.get_expr()
754        else {
755            return Err(ErrorCode::BindError(
756                "The `lambda` argument for `array_transform` should be a lambda function"
757                    .to_owned(),
758            )
759            .into());
760        };
761
762        let [lambda_arg] = <[Ident; 1]>::try_from(lambda_args).map_err(|args| -> RwError {
763            ErrorCode::BindError(format!(
764                "The `lambda` argument for `array_transform` should be a lambda function with one argument, but {} were given",
765                args.len()
766            ))
767            .into()
768        })?;
769
770        let bound_lambda = self.bind_unary_lambda_function(inner_ty, lambda_arg, *lambda_body)?;
771
772        let lambda_ret_type = bound_lambda.return_type();
773        let transform_ret_type = DataType::list(lambda_ret_type);
774
775        Ok(ExprImpl::FunctionCallWithLambda(Box::new(
776            FunctionCallWithLambda::new_unchecked(
777                ExprType::ArrayTransform,
778                vec![bound_array],
779                bound_lambda,
780                transform_ret_type,
781            ),
782        )))
783    }
784
785    fn bind_unary_lambda_function(
786        &mut self,
787        input_ty: DataType,
788        arg: Ident,
789        body: ast::Expr,
790    ) -> Result<ExprImpl> {
791        let lambda_args = HashMap::from([(arg.real_value(), (0usize, input_ty))]);
792        let orig_lambda_args = self.context.lambda_args.replace(lambda_args);
793        let body = self.bind_expr_inner(&body)?;
794        self.context.lambda_args = orig_lambda_args;
795
796        Ok(body)
797    }
798
799    fn bind_map_filter(&mut self, args: &[FunctionArg]) -> Result<ExprImpl> {
800        let [input, lambda] = args else {
801            return Err(ErrorCode::BindError(format!(
802                "`map_filter` requires two arguments (input_map and lambda), got {}",
803                args.len()
804            ))
805            .into());
806        };
807
808        let bound_input = self.bind_function_arg(input)?;
809        let [bound_input] = <[ExprImpl; 1]>::try_from(bound_input).map_err(|e| {
810            ErrorCode::BindError(format!(
811                "Input argument should resolve to single expression, got {}",
812                e.len()
813            ))
814        })?;
815
816        let (key_type, value_type) = match bound_input.return_type() {
817            DataType::Map(map_type) => (map_type.key().clone(), map_type.value().clone()),
818            t => {
819                return Err(
820                    ErrorCode::BindError(format!("Input must be Map type, got {}", t)).into(),
821                );
822            }
823        };
824
825        let ast::FunctionArgExpr::Expr(ast::Expr::LambdaFunction {
826            args: lambda_args,
827            body: lambda_body,
828        }) = lambda.get_expr()
829        else {
830            return Err(ErrorCode::BindError(
831                "Second argument must be a lambda function".to_owned(),
832            )
833            .into());
834        };
835
836        let [key_arg, value_arg] = <[Ident; 2]>::try_from(lambda_args).map_err(|args| {
837            ErrorCode::BindError(format!(
838                "Lambda must have exactly two parameters (key, value), got {}",
839                args.len()
840            ))
841        })?;
842
843        let bound_lambda = self.bind_binary_lambda_function(
844            key_arg,
845            key_type.clone(),
846            value_arg,
847            value_type.clone(),
848            *lambda_body,
849        )?;
850
851        let lambda_ret_type = bound_lambda.return_type();
852        if lambda_ret_type != DataType::Boolean {
853            return Err(ErrorCode::BindError(format!(
854                "Lambda must return Boolean type, got {}",
855                lambda_ret_type
856            ))
857            .into());
858        }
859
860        let map_type = MapType::from_kv(key_type, value_type);
861        let return_type = DataType::Map(map_type);
862
863        Ok(ExprImpl::FunctionCallWithLambda(Box::new(
864            FunctionCallWithLambda::new_unchecked(
865                ExprType::MapFilter,
866                vec![bound_input],
867                bound_lambda,
868                return_type,
869            ),
870        )))
871    }
872
873    fn bind_binary_lambda_function(
874        &mut self,
875        first_arg: Ident,
876        first_ty: DataType,
877        second_arg: Ident,
878        second_ty: DataType,
879        body: ast::Expr,
880    ) -> Result<ExprImpl> {
881        let lambda_args = HashMap::from([
882            (first_arg.real_value(), (0usize, first_ty)),
883            (second_arg.real_value(), (1usize, second_ty)),
884        ]);
885
886        let orig_ctx = self.context.lambda_args.replace(lambda_args);
887        let bound_body = self.bind_expr_inner(&body)?;
888        self.context.lambda_args = orig_ctx;
889
890        Ok(bound_body)
891    }
892
893    fn ensure_table_function_allowed(&self) -> Result<()> {
894        if let Some(clause) = self.context.clause {
895            match clause {
896                Clause::JoinOn
897                | Clause::Where
898                | Clause::Having
899                | Clause::Filter
900                | Clause::Values
901                | Clause::Insert
902                | Clause::GeneratedColumn => {
903                    return Err(ErrorCode::InvalidInputSyntax(format!(
904                        "table functions are not allowed in {}",
905                        clause
906                    ))
907                    .into());
908                }
909                Clause::GroupBy | Clause::From => {}
910            }
911        }
912        Ok(())
913    }
914
915    /// A common utility function to extract sql udf expression out from the input `ast`.
916    pub(crate) fn extract_udf_expr(ast: Vec<Statement>) -> Result<AstExpr> {
917        if ast.len() != 1 {
918            return Err(ErrorCode::InvalidInputSyntax(
919                "the query for sql udf should contain only one statement".to_owned(),
920            )
921            .into());
922        }
923
924        // Extract the expression out
925        let Statement::Query(query) = ast.into_iter().next().unwrap() else {
926            return Err(ErrorCode::InvalidInputSyntax(
927                "invalid function definition, please recheck the syntax".to_owned(),
928            )
929            .into());
930        };
931
932        if let Some(expr) = query.as_single_select_item() {
933            // Inline SQL UDF.
934            Ok(expr.clone())
935        } else {
936            // Subquery SQL UDF.
937            Ok(AstExpr::Subquery(query))
938        }
939    }
940
941    pub fn bind_sql_udf_inner(
942        &mut self,
943        body: &str,
944        arg_names: &[String],
945        args: Vec<ExprImpl>,
946    ) -> Result<ExprImpl> {
947        // This represents the current user defined function is `language sql`
948        let ast = Parser::parse_sql(body)?;
949
950        // Stash the current arguments.
951        // For subquery SQL UDF, as we always push a new context, there should be no arguments to stash.
952        // For inline SQL UDF, we need to stash the arguments in case of nesting.
953        let stashed_arguments = self.context.sql_udf_arguments.take();
954
955        // The actual inline logic for sql udf.
956        let mut arguments = HashMap::new();
957        for (i, arg) in args.into_iter().enumerate() {
958            if arg_names[i].is_empty() {
959                // unnamed argument, use `$1`, `$2` as the name
960                arguments.insert(format!("${}", i + 1), arg);
961            } else {
962                // named argument
963                arguments.insert(arg_names[i].clone(), arg);
964            }
965        }
966        self.context.sql_udf_arguments = Some(arguments);
967
968        let Ok(expr) = Self::extract_udf_expr(ast) else {
969            return Err(ErrorCode::InvalidInputSyntax(
970                "failed to parse the input query and extract the udf expression, \
971                please recheck the syntax"
972                    .to_owned(),
973            )
974            .into());
975        };
976
977        let bind_result = self.bind_expr(&expr);
978        // Restore arguments information for subsequent binding.
979        self.context.sql_udf_arguments = stashed_arguments;
980
981        bind_result
982    }
983
984    fn bind_sql_udf(
985        &mut self,
986        func: Arc<FunctionCatalog>,
987        args: Vec<ExprImpl>,
988    ) -> Result<ExprImpl> {
989        let Some(body) = &func.body else {
990            return Err(
991                ErrorCode::InvalidInputSyntax("`body` must exist for sql udf".to_owned()).into(),
992            );
993        };
994
995        self.bind_sql_udf_inner(body, &func.arg_names, args)
996    }
997
998    pub(in crate::binder) fn bind_function_expr_arg(
999        &mut self,
1000        arg_expr: &FunctionArgExpr,
1001    ) -> Result<Vec<ExprImpl>> {
1002        match arg_expr {
1003            FunctionArgExpr::Expr(expr) => Ok(vec![self.bind_expr_inner(expr)?]),
1004            FunctionArgExpr::QualifiedWildcard(_, _)
1005            | FunctionArgExpr::ExprQualifiedWildcard(_, _) => Err(ErrorCode::InvalidInputSyntax(
1006                format!("unexpected wildcard {}", arg_expr),
1007            )
1008            .into()),
1009            FunctionArgExpr::Wildcard(None) => Ok(vec![]),
1010            FunctionArgExpr::Wildcard(Some(_)) => unreachable!(),
1011            FunctionArgExpr::SecretRef(secret_ref_value) => {
1012                let (schema_name, secret_name) = Binder::resolve_schema_qualified_name(
1013                    &self.db_name,
1014                    &secret_ref_value.secret_name,
1015                )?;
1016                let schema_path = self.bind_schema_path(schema_name.as_deref());
1017                let (secret_catalog, _) =
1018                    self.catalog
1019                        .get_secret_by_name(&self.db_name, schema_path, &secret_name)?;
1020
1021                self.check_privilege(
1022                    ObjectCheckItem::new(
1023                        secret_catalog.owner(),
1024                        AclMode::Usage,
1025                        secret_catalog.name.clone(),
1026                        secret_catalog.id,
1027                    ),
1028                    self.database_id,
1029                )?;
1030
1031                self.included_secrets.insert(secret_catalog.id);
1032
1033                let ref_as = match secret_ref_value.ref_as {
1034                    SecretRefAsType::Text => risingwave_pb::secret::secret_ref::RefAsType::Text,
1035                    SecretRefAsType::File => risingwave_pb::secret::secret_ref::RefAsType::File,
1036                };
1037
1038                Ok(vec![
1039                    crate::expr::SecretRef {
1040                        secret_id: secret_catalog.id,
1041                        ref_as,
1042                        secret_name: secret_catalog.name.clone(),
1043                    }
1044                    .into(),
1045                ])
1046            }
1047        }
1048    }
1049
1050    pub(in crate::binder) fn bind_function_arg(
1051        &mut self,
1052        arg: &FunctionArg,
1053    ) -> Result<Vec<ExprImpl>> {
1054        match arg {
1055            FunctionArg::Unnamed(expr) => self.bind_function_expr_arg(expr),
1056            FunctionArg::Named { .. } => Err(ErrorCode::InvalidInputSyntax(
1057                "named function arguments are not supported yet".to_owned(),
1058            )
1059            .into()),
1060        }
1061    }
1062
1063    fn bind_jsonb_agg_arg(&mut self, arg: &FunctionArg) -> Result<Vec<ExprImpl>> {
1064        match arg {
1065            FunctionArg::Unnamed(FunctionArgExpr::QualifiedWildcard(prefix, except)) => {
1066                let (schema_name, table_name) =
1067                    Binder::resolve_schema_qualified_name(&self.db_name, prefix)?;
1068                let except_indices = self.generate_except_indices(except.as_deref())?;
1069                let (begin, end) = self
1070                    .context
1071                    .resolve_relation_range(&table_name, &schema_name)?;
1072                let (exprs, names) = Self::iter_bound_columns(
1073                    self.context.columns[begin..end]
1074                        .iter()
1075                        .filter(|c| !c.is_hidden && !except_indices.contains(&c.index)),
1076                );
1077                self.wrap_wildcard_exprs_as_named_row(exprs, names)
1078            }
1079            FunctionArg::Unnamed(FunctionArgExpr::ExprQualifiedWildcard(expr, prefix)) => {
1080                let (exprs, names) = self.bind_wildcard_field_column(expr, prefix)?;
1081                self.wrap_wildcard_exprs_as_named_row(exprs, names)
1082            }
1083            _ => self.bind_function_arg(arg),
1084        }
1085    }
1086
1087    fn wrap_wildcard_exprs_as_named_row(
1088        &self,
1089        exprs: Vec<ExprImpl>,
1090        names: Vec<Option<String>>,
1091    ) -> Result<Vec<ExprImpl>> {
1092        let return_type =
1093            DataType::Struct(StructType::new(
1094                names.into_iter().zip_eq_fast(exprs.iter()).enumerate().map(
1095                    |(idx, (name, expr))| {
1096                        (
1097                            name.unwrap_or_else(|| format!("f{}", idx + 1)),
1098                            expr.return_type(),
1099                        )
1100                    },
1101                ),
1102            ));
1103        Ok(vec![
1104            FunctionCall::new_unchecked(ExprType::Row, exprs, return_type).into(),
1105        ])
1106    }
1107}