risingwave_frontend/binder/
mod.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::collections::{HashMap, HashSet};
16use std::sync::Arc;
17
18use itertools::Itertools;
19use parking_lot::RwLock;
20use risingwave_common::catalog::FunctionId;
21use risingwave_common::session_config::{SearchPath, SessionConfig};
22use risingwave_common::types::DataType;
23use risingwave_common::util::iter_util::ZipEqDebug;
24use risingwave_sqlparser::ast::Statement;
25
26use crate::error::Result;
27
28mod bind_context;
29mod bind_param;
30mod create;
31mod create_view;
32mod declare_cursor;
33mod delete;
34mod expr;
35pub mod fetch_cursor;
36mod for_system;
37mod insert;
38mod query;
39mod relation;
40mod select;
41mod set_expr;
42mod statement;
43mod struct_field;
44mod update;
45mod values;
46
47pub use bind_context::{BindContext, Clause, LateralBindContext};
48pub use create_view::BoundCreateView;
49pub use delete::BoundDelete;
50pub use expr::bind_data_type;
51pub use insert::BoundInsert;
52use pgwire::pg_server::{Session, SessionId};
53pub use query::BoundQuery;
54pub use relation::{
55    BoundBackCteRef, BoundBaseTable, BoundJoin, BoundShare, BoundShareInput, BoundSource,
56    BoundSystemTable, BoundWatermark, BoundWindowTableFunction, Relation,
57    ResolveQualifiedNameError, WindowTableFunctionKind,
58};
59pub use select::{BoundDistinct, BoundSelect};
60pub use set_expr::*;
61pub use statement::BoundStatement;
62pub use update::{BoundUpdate, UpdateProject};
63pub use values::BoundValues;
64
65use crate::catalog::catalog_service::CatalogReadGuard;
66use crate::catalog::root_catalog::SchemaPath;
67use crate::catalog::schema_catalog::SchemaCatalog;
68use crate::catalog::{CatalogResult, DatabaseId, TableId, ViewId};
69use crate::error::ErrorCode;
70use crate::session::{AuthContext, SessionImpl, TemporarySourceManager};
71use crate::user::user_service::UserInfoReadGuard;
72
73pub type ShareId = usize;
74
75/// The type of binding statement.
76enum BindFor {
77    /// Binding MV/SINK
78    Stream,
79    /// Binding a batch query
80    Batch,
81    /// Binding a DDL (e.g. CREATE TABLE/SOURCE)
82    Ddl,
83    /// Binding a system query (e.g. SHOW)
84    System,
85}
86
87/// `Binder` binds the identifiers in AST to columns in relations
88pub struct Binder {
89    // TODO: maybe we can only lock the database, but not the whole catalog.
90    catalog: CatalogReadGuard,
91    user: UserInfoReadGuard,
92    db_name: String,
93    database_id: DatabaseId,
94    session_id: SessionId,
95    context: BindContext,
96    auth_context: Arc<AuthContext>,
97    /// A stack holding contexts of outer queries when binding a subquery.
98    /// It also holds all of the lateral contexts for each respective
99    /// subquery.
100    ///
101    /// See [`Binder::bind_subquery_expr`] for details.
102    upper_subquery_contexts: Vec<(BindContext, Vec<LateralBindContext>)>,
103
104    /// A stack holding contexts of left-lateral `TableFactor`s.
105    ///
106    /// We need a separate stack as `CorrelatedInputRef` depth is
107    /// determined by the upper subquery context depth, not the lateral context stack depth.
108    lateral_contexts: Vec<LateralBindContext>,
109
110    next_subquery_id: usize,
111    next_values_id: usize,
112    /// The `ShareId` is used to identify the share relation which could be a CTE, a source, a view
113    /// and so on.
114    next_share_id: ShareId,
115
116    session_config: Arc<RwLock<SessionConfig>>,
117
118    search_path: SearchPath,
119    /// The type of binding statement.
120    bind_for: BindFor,
121
122    /// `ShareId`s identifying shared views.
123    shared_views: HashMap<ViewId, ShareId>,
124
125    /// The included relations while binding a query.
126    included_relations: HashSet<TableId>,
127
128    /// The included user-defined functions while binding a query.
129    included_udfs: HashSet<FunctionId>,
130
131    param_types: ParameterTypes,
132
133    /// The temporary sources that will be used during binding phase
134    temporary_source_manager: TemporarySourceManager,
135
136    /// Information for `secure_compare` function. It's ONLY available when binding the
137    /// `VALIDATE` clause of Webhook source i.e. `VALIDATE SECRET ... AS SECURE_COMPARE(...)`.
138    secure_compare_context: Option<SecureCompareContext>,
139}
140
141// There's one more hidden name, `HEADERS`, which is a reserved identifier for HTTP headers. Its type is `JSONB`.
142#[derive(Default, Clone, Debug)]
143pub struct SecureCompareContext {
144    /// The column name to store the whole payload in `JSONB`, but during validation it will be used as `bytea`
145    pub column_name: String,
146    /// The secret (usually a token provided by the webhook source user) to validate the calls
147    pub secret_name: Option<String>,
148}
149
150/// `ParameterTypes` is used to record the types of the parameters during binding prepared stataments.
151/// It works by following the rules:
152/// 1. At the beginning, it contains the user specified parameters type.
153/// 2. When the binder encounters a parameter, it will record it as unknown(call `record_new_param`)
154///    if it didn't exist in `ParameterTypes`.
155/// 3. When the binder encounters a cast on parameter, if it's a unknown type, the cast function
156///    will record the target type as infer type for that parameter(call `record_infer_type`). If the
157///    parameter has been inferred, the cast function will act as a normal cast.
158/// 4. After bind finished:
159///    (a) parameter not in `ParameterTypes` means that the user didn't specify it and it didn't
160///    occur in the query. `export` will return error if there is a kind of
161///    parameter. This rule is compatible with PostgreSQL
162///    (b) parameter is None means that it's a unknown type. The user didn't specify it
163///    and we can't infer it in the query. We will treat it as VARCHAR type finally. This rule is
164///    compatible with PostgreSQL.
165///    (c) parameter is Some means that it's a known type.
166#[derive(Clone, Debug)]
167pub struct ParameterTypes(Arc<RwLock<HashMap<u64, Option<DataType>>>>);
168
169impl ParameterTypes {
170    pub fn new(specified_param_types: Vec<Option<DataType>>) -> Self {
171        let map = specified_param_types
172            .into_iter()
173            .enumerate()
174            .map(|(index, data_type)| ((index + 1) as u64, data_type))
175            .collect::<HashMap<u64, Option<DataType>>>();
176        Self(Arc::new(RwLock::new(map)))
177    }
178
179    pub fn has_infer(&self, index: u64) -> bool {
180        self.0.read().get(&index).unwrap().is_some()
181    }
182
183    pub fn read_type(&self, index: u64) -> Option<DataType> {
184        self.0.read().get(&index).unwrap().clone()
185    }
186
187    pub fn record_new_param(&mut self, index: u64) {
188        self.0.write().entry(index).or_insert(None);
189    }
190
191    pub fn record_infer_type(&mut self, index: u64, data_type: &DataType) {
192        assert!(
193            !self.has_infer(index),
194            "The parameter has been inferred, should not be inferred again."
195        );
196        self.0
197            .write()
198            .get_mut(&index)
199            .unwrap()
200            .replace(data_type.clone());
201    }
202
203    pub fn export(&self) -> Result<Vec<DataType>> {
204        let types = self
205            .0
206            .read()
207            .clone()
208            .into_iter()
209            .sorted_by_key(|(index, _)| *index)
210            .collect::<Vec<_>>();
211
212        // Check if all the parameters have been inferred.
213        for ((index, _), expect_index) in types.iter().zip_eq_debug(1_u64..=types.len() as u64) {
214            if *index != expect_index {
215                return Err(ErrorCode::InvalidInputSyntax(format!(
216                    "Cannot infer the type of the parameter {}.",
217                    expect_index
218                ))
219                .into());
220            }
221        }
222
223        Ok(types
224            .into_iter()
225            .map(|(_, data_type)| data_type.unwrap_or(DataType::Varchar))
226            .collect::<Vec<_>>())
227    }
228}
229
230impl Binder {
231    fn new_inner(
232        session: &SessionImpl,
233        bind_for: BindFor,
234        param_types: Vec<Option<DataType>>,
235    ) -> Binder {
236        Binder {
237            catalog: session.env().catalog_reader().read_guard(),
238            user: session.env().user_info_reader().read_guard(),
239            db_name: session.database(),
240            database_id: session.database_id(),
241            session_id: session.id(),
242            context: BindContext::new(),
243            auth_context: session.auth_context(),
244            upper_subquery_contexts: vec![],
245            lateral_contexts: vec![],
246            next_subquery_id: 0,
247            next_values_id: 0,
248            next_share_id: 0,
249            session_config: session.shared_config(),
250            search_path: session.config().search_path(),
251            bind_for,
252            shared_views: HashMap::new(),
253            included_relations: HashSet::new(),
254            included_udfs: HashSet::new(),
255            param_types: ParameterTypes::new(param_types),
256            temporary_source_manager: session.temporary_source_manager(),
257            secure_compare_context: None,
258        }
259    }
260
261    pub fn new(session: &SessionImpl) -> Binder {
262        Self::new_inner(session, BindFor::Batch, vec![])
263    }
264
265    pub fn new_with_param_types(
266        session: &SessionImpl,
267        param_types: Vec<Option<DataType>>,
268    ) -> Binder {
269        Self::new_inner(session, BindFor::Batch, param_types)
270    }
271
272    pub fn new_for_stream(session: &SessionImpl) -> Binder {
273        Self::new_inner(session, BindFor::Stream, vec![])
274    }
275
276    pub fn new_for_ddl(session: &SessionImpl) -> Binder {
277        Self::new_inner(session, BindFor::Ddl, vec![])
278    }
279
280    pub fn new_for_ddl_with_secure_compare(
281        session: &SessionImpl,
282        ctx: SecureCompareContext,
283    ) -> Binder {
284        let mut binder = Self::new_inner(session, BindFor::Ddl, vec![]);
285        binder.secure_compare_context = Some(ctx);
286        binder
287    }
288
289    pub fn new_for_system(session: &SessionImpl) -> Binder {
290        Self::new_inner(session, BindFor::System, vec![])
291    }
292
293    fn is_for_stream(&self) -> bool {
294        matches!(self.bind_for, BindFor::Stream)
295    }
296
297    #[allow(dead_code)]
298    fn is_for_batch(&self) -> bool {
299        matches!(self.bind_for, BindFor::Batch)
300    }
301
302    fn is_for_ddl(&self) -> bool {
303        matches!(self.bind_for, BindFor::Ddl)
304    }
305
306    /// Bind a [`Statement`].
307    pub fn bind(&mut self, stmt: Statement) -> Result<BoundStatement> {
308        self.bind_statement(stmt)
309    }
310
311    pub fn export_param_types(&self) -> Result<Vec<DataType>> {
312        self.param_types.export()
313    }
314
315    /// Get included relations in the query after binding. This is used for resolving relation
316    /// dependencies. Note that it only contains referenced relations discovered during binding.
317    /// After the plan is built, the referenced relations may be changed. We cannot rely on the
318    /// collection result of plan, because we still need to record the dependencies that have been
319    /// optimised away.
320    pub fn included_relations(&self) -> &HashSet<TableId> {
321        &self.included_relations
322    }
323
324    /// Get included user-defined functions in the query after binding.
325    pub fn included_udfs(&self) -> &HashSet<FunctionId> {
326        &self.included_udfs
327    }
328
329    fn push_context(&mut self) {
330        let new_context = std::mem::take(&mut self.context);
331        self.context
332            .cte_to_relation
333            .clone_from(&new_context.cte_to_relation);
334        self.context.disable_security_invoker = new_context.disable_security_invoker;
335        let new_lateral_contexts = std::mem::take(&mut self.lateral_contexts);
336        self.upper_subquery_contexts
337            .push((new_context, new_lateral_contexts));
338    }
339
340    fn pop_context(&mut self) -> Result<()> {
341        let (old_context, old_lateral_contexts) = self
342            .upper_subquery_contexts
343            .pop()
344            .ok_or_else(|| ErrorCode::InternalError("Popping non-existent context".to_owned()))?;
345        self.context = old_context;
346        self.lateral_contexts = old_lateral_contexts;
347        Ok(())
348    }
349
350    fn push_lateral_context(&mut self) {
351        let new_context = std::mem::take(&mut self.context);
352        self.context
353            .cte_to_relation
354            .clone_from(&new_context.cte_to_relation);
355        self.context.disable_security_invoker = new_context.disable_security_invoker;
356        self.lateral_contexts.push(LateralBindContext {
357            is_visible: false,
358            context: new_context,
359        });
360    }
361
362    fn pop_and_merge_lateral_context(&mut self) -> Result<()> {
363        let mut old_context = self
364            .lateral_contexts
365            .pop()
366            .ok_or_else(|| ErrorCode::InternalError("Popping non-existent context".to_owned()))?
367            .context;
368        old_context.merge_context(self.context.clone())?;
369        self.context = old_context;
370        Ok(())
371    }
372
373    fn try_mark_lateral_as_visible(&mut self) {
374        if let Some(mut ctx) = self.lateral_contexts.pop() {
375            ctx.is_visible = true;
376            self.lateral_contexts.push(ctx);
377        }
378    }
379
380    fn try_mark_lateral_as_invisible(&mut self) {
381        if let Some(mut ctx) = self.lateral_contexts.pop() {
382            ctx.is_visible = false;
383            self.lateral_contexts.push(ctx);
384        }
385    }
386
387    /// Returns a reverse iterator over the upper subquery contexts that are visible to the current
388    /// context. Not to be confused with `is_visible` in [`LateralBindContext`].
389    ///
390    /// In most cases, this should include all the upper subquery contexts. However, when binding
391    /// SQL UDFs, we should avoid resolving the context outside the UDF for hygiene.
392    fn visible_upper_subquery_contexts_rev(
393        &self,
394    ) -> impl Iterator<Item = &(BindContext, Vec<LateralBindContext>)> + '_ {
395        self.upper_subquery_contexts
396            .iter()
397            .rev()
398            .take_while(|(context, _)| context.sql_udf_arguments.is_none())
399    }
400
401    fn next_subquery_id(&mut self) -> usize {
402        let id = self.next_subquery_id;
403        self.next_subquery_id += 1;
404        id
405    }
406
407    fn next_values_id(&mut self) -> usize {
408        let id = self.next_values_id;
409        self.next_values_id += 1;
410        id
411    }
412
413    fn next_share_id(&mut self) -> ShareId {
414        let id = self.next_share_id;
415        self.next_share_id += 1;
416        id
417    }
418
419    fn first_valid_schema(&self) -> CatalogResult<&SchemaCatalog> {
420        self.catalog.first_valid_schema(
421            &self.db_name,
422            &self.search_path,
423            &self.auth_context.user_name,
424        )
425    }
426
427    fn bind_schema_path<'a>(&'a self, schema_name: Option<&'a str>) -> SchemaPath<'a> {
428        SchemaPath::new(schema_name, &self.search_path, &self.auth_context.user_name)
429    }
430
431    pub fn set_clause(&mut self, clause: Option<Clause>) {
432        self.context.clause = clause;
433    }
434}
435
436/// The column name stored in [`BindContext`] for a column without an alias.
437pub const UNNAMED_COLUMN: &str = "?column?";
438/// The table name stored in [`BindContext`] for a subquery without an alias.
439const UNNAMED_SUBQUERY: &str = "?subquery?";
440/// The table name stored in [`BindContext`] for a column group.
441const COLUMN_GROUP_PREFIX: &str = "?column_group_id?";
442
443#[cfg(test)]
444pub mod test_utils {
445    use risingwave_common::types::DataType;
446
447    use super::Binder;
448    use crate::session::SessionImpl;
449
450    #[cfg(test)]
451    pub fn mock_binder() -> Binder {
452        Binder::new(&SessionImpl::mock())
453    }
454
455    #[cfg(test)]
456    pub fn mock_binder_with_param_types(param_types: Vec<Option<DataType>>) -> Binder {
457        Binder::new_with_param_types(&SessionImpl::mock(), param_types)
458    }
459}
460
461#[cfg(test)]
462mod tests {
463    use expect_test::expect;
464
465    use super::test_utils::*;
466
467    #[tokio::test]
468    async fn test_rcte() {
469        let stmt = risingwave_sqlparser::parser::Parser::parse_sql(
470            "WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT * FROM t1",
471        ).unwrap().into_iter().next().unwrap();
472        let mut binder = mock_binder();
473        let bound = binder.bind(stmt).unwrap();
474
475        let expected = expect![[r#"
476            Query(
477                BoundQuery {
478                    body: Select(
479                        BoundSelect {
480                            distinct: All,
481                            select_items: [
482                                InputRef(
483                                    InputRef {
484                                        index: 0,
485                                        data_type: Int32,
486                                    },
487                                ),
488                            ],
489                            aliases: [
490                                Some(
491                                    "a",
492                                ),
493                            ],
494                            from: Some(
495                                Share(
496                                    BoundShare {
497                                        share_id: 0,
498                                        input: Query(
499                                            Right(
500                                                RecursiveUnion {
501                                                    all: true,
502                                                    base: Select(
503                                                        BoundSelect {
504                                                            distinct: All,
505                                                            select_items: [
506                                                                Literal(
507                                                                    Literal {
508                                                                        data: Some(
509                                                                            Int32(
510                                                                                1,
511                                                                            ),
512                                                                        ),
513                                                                        data_type: Some(
514                                                                            Int32,
515                                                                        ),
516                                                                    },
517                                                                ),
518                                                            ],
519                                                            aliases: [
520                                                                Some(
521                                                                    "a",
522                                                                ),
523                                                            ],
524                                                            from: None,
525                                                            where_clause: None,
526                                                            group_by: GroupKey(
527                                                                [],
528                                                            ),
529                                                            having: None,
530                                                            window: {},
531                                                            schema: Schema {
532                                                                fields: [
533                                                                    a:Int32,
534                                                                ],
535                                                            },
536                                                        },
537                                                    ),
538                                                    recursive: Select(
539                                                        BoundSelect {
540                                                            distinct: All,
541                                                            select_items: [
542                                                                FunctionCall(
543                                                                    FunctionCall {
544                                                                        func_type: Add,
545                                                                        return_type: Int32,
546                                                                        inputs: [
547                                                                            InputRef(
548                                                                                InputRef {
549                                                                                    index: 0,
550                                                                                    data_type: Int32,
551                                                                                },
552                                                                            ),
553                                                                            Literal(
554                                                                                Literal {
555                                                                                    data: Some(
556                                                                                        Int32(
557                                                                                            1,
558                                                                                        ),
559                                                                                    ),
560                                                                                    data_type: Some(
561                                                                                        Int32,
562                                                                                    ),
563                                                                                },
564                                                                            ),
565                                                                        ],
566                                                                    },
567                                                                ),
568                                                            ],
569                                                            aliases: [
570                                                                None,
571                                                            ],
572                                                            from: Some(
573                                                                BackCteRef(
574                                                                    BoundBackCteRef {
575                                                                        share_id: 0,
576                                                                        base: Select(
577                                                                            BoundSelect {
578                                                                                distinct: All,
579                                                                                select_items: [
580                                                                                    Literal(
581                                                                                        Literal {
582                                                                                            data: Some(
583                                                                                                Int32(
584                                                                                                    1,
585                                                                                                ),
586                                                                                            ),
587                                                                                            data_type: Some(
588                                                                                                Int32,
589                                                                                            ),
590                                                                                        },
591                                                                                    ),
592                                                                                ],
593                                                                                aliases: [
594                                                                                    Some(
595                                                                                        "a",
596                                                                                    ),
597                                                                                ],
598                                                                                from: None,
599                                                                                where_clause: None,
600                                                                                group_by: GroupKey(
601                                                                                    [],
602                                                                                ),
603                                                                                having: None,
604                                                                                window: {},
605                                                                                schema: Schema {
606                                                                                    fields: [
607                                                                                        a:Int32,
608                                                                                    ],
609                                                                                },
610                                                                            },
611                                                                        ),
612                                                                    },
613                                                                ),
614                                                            ),
615                                                            where_clause: Some(
616                                                                FunctionCall(
617                                                                    FunctionCall {
618                                                                        func_type: LessThan,
619                                                                        return_type: Boolean,
620                                                                        inputs: [
621                                                                            InputRef(
622                                                                                InputRef {
623                                                                                    index: 0,
624                                                                                    data_type: Int32,
625                                                                                },
626                                                                            ),
627                                                                            Literal(
628                                                                                Literal {
629                                                                                    data: Some(
630                                                                                        Int32(
631                                                                                            10,
632                                                                                        ),
633                                                                                    ),
634                                                                                    data_type: Some(
635                                                                                        Int32,
636                                                                                    ),
637                                                                                },
638                                                                            ),
639                                                                        ],
640                                                                    },
641                                                                ),
642                                                            ),
643                                                            group_by: GroupKey(
644                                                                [],
645                                                            ),
646                                                            having: None,
647                                                            window: {},
648                                                            schema: Schema {
649                                                                fields: [
650                                                                    ?column?:Int32,
651                                                                ],
652                                                            },
653                                                        },
654                                                    ),
655                                                    schema: Schema {
656                                                        fields: [
657                                                            a:Int32,
658                                                        ],
659                                                    },
660                                                },
661                                            ),
662                                        ),
663                                    },
664                                ),
665                            ),
666                            where_clause: None,
667                            group_by: GroupKey(
668                                [],
669                            ),
670                            having: None,
671                            window: {},
672                            schema: Schema {
673                                fields: [
674                                    a:Int32,
675                                ],
676                            },
677                        },
678                    ),
679                    order: [],
680                    limit: None,
681                    offset: None,
682                    with_ties: false,
683                    extra_order_exprs: [],
684                },
685            )"#]];
686
687        expected.assert_eq(&format!("{:#?}", bound));
688    }
689
690    #[tokio::test]
691    async fn test_bind_approx_percentile() {
692        let stmt = risingwave_sqlparser::parser::Parser::parse_sql(
693            "SELECT approx_percentile(0.5, 0.01) WITHIN GROUP (ORDER BY generate_series) FROM generate_series(1, 100)",
694        ).unwrap().into_iter().next().unwrap();
695        let parse_expected = expect![[r#"
696            Query(
697                Query {
698                    with: None,
699                    body: Select(
700                        Select {
701                            distinct: All,
702                            projection: [
703                                UnnamedExpr(
704                                    Function(
705                                        Function {
706                                            scalar_as_agg: false,
707                                            name: ObjectName(
708                                                [
709                                                    Ident {
710                                                        value: "approx_percentile",
711                                                        quote_style: None,
712                                                    },
713                                                ],
714                                            ),
715                                            arg_list: FunctionArgList {
716                                                distinct: false,
717                                                args: [
718                                                    Unnamed(
719                                                        Expr(
720                                                            Value(
721                                                                Number(
722                                                                    "0.5",
723                                                                ),
724                                                            ),
725                                                        ),
726                                                    ),
727                                                    Unnamed(
728                                                        Expr(
729                                                            Value(
730                                                                Number(
731                                                                    "0.01",
732                                                                ),
733                                                            ),
734                                                        ),
735                                                    ),
736                                                ],
737                                                variadic: false,
738                                                order_by: [],
739                                                ignore_nulls: false,
740                                            },
741                                            within_group: Some(
742                                                OrderByExpr {
743                                                    expr: Identifier(
744                                                        Ident {
745                                                            value: "generate_series",
746                                                            quote_style: None,
747                                                        },
748                                                    ),
749                                                    asc: None,
750                                                    nulls_first: None,
751                                                },
752                                            ),
753                                            filter: None,
754                                            over: None,
755                                        },
756                                    ),
757                                ),
758                            ],
759                            from: [
760                                TableWithJoins {
761                                    relation: TableFunction {
762                                        name: ObjectName(
763                                            [
764                                                Ident {
765                                                    value: "generate_series",
766                                                    quote_style: None,
767                                                },
768                                            ],
769                                        ),
770                                        alias: None,
771                                        args: [
772                                            Unnamed(
773                                                Expr(
774                                                    Value(
775                                                        Number(
776                                                            "1",
777                                                        ),
778                                                    ),
779                                                ),
780                                            ),
781                                            Unnamed(
782                                                Expr(
783                                                    Value(
784                                                        Number(
785                                                            "100",
786                                                        ),
787                                                    ),
788                                                ),
789                                            ),
790                                        ],
791                                        with_ordinality: false,
792                                    },
793                                    joins: [],
794                                },
795                            ],
796                            lateral_views: [],
797                            selection: None,
798                            group_by: [],
799                            having: None,
800                            window: [],
801                        },
802                    ),
803                    order_by: [],
804                    limit: None,
805                    offset: None,
806                    fetch: None,
807                },
808            )"#]];
809        parse_expected.assert_eq(&format!("{:#?}", stmt));
810
811        let mut binder = mock_binder();
812        let bound = binder.bind(stmt).unwrap();
813
814        let expected = expect![[r#"
815            Query(
816                BoundQuery {
817                    body: Select(
818                        BoundSelect {
819                            distinct: All,
820                            select_items: [
821                                AggCall(
822                                    AggCall {
823                                        agg_type: Builtin(
824                                            ApproxPercentile,
825                                        ),
826                                        return_type: Float64,
827                                        args: [
828                                            FunctionCall(
829                                                FunctionCall {
830                                                    func_type: Cast,
831                                                    return_type: Float64,
832                                                    inputs: [
833                                                        InputRef(
834                                                            InputRef {
835                                                                index: 0,
836                                                                data_type: Int32,
837                                                            },
838                                                        ),
839                                                    ],
840                                                },
841                                            ),
842                                        ],
843                                        filter: Condition {
844                                            conjunctions: [],
845                                        },
846                                        distinct: false,
847                                        order_by: OrderBy {
848                                            sort_exprs: [
849                                                OrderByExpr {
850                                                    expr: InputRef(
851                                                        InputRef {
852                                                            index: 0,
853                                                            data_type: Int32,
854                                                        },
855                                                    ),
856                                                    order_type: OrderType {
857                                                        direction: Ascending,
858                                                        nulls_are: Largest,
859                                                    },
860                                                },
861                                            ],
862                                        },
863                                        direct_args: [
864                                            Literal {
865                                                data: Some(
866                                                    Float64(
867                                                        OrderedFloat(
868                                                            0.5,
869                                                        ),
870                                                    ),
871                                                ),
872                                                data_type: Some(
873                                                    Float64,
874                                                ),
875                                            },
876                                            Literal {
877                                                data: Some(
878                                                    Float64(
879                                                        OrderedFloat(
880                                                            0.01,
881                                                        ),
882                                                    ),
883                                                ),
884                                                data_type: Some(
885                                                    Float64,
886                                                ),
887                                            },
888                                        ],
889                                    },
890                                ),
891                            ],
892                            aliases: [
893                                Some(
894                                    "approx_percentile",
895                                ),
896                            ],
897                            from: Some(
898                                TableFunction {
899                                    expr: TableFunction(
900                                        FunctionCall {
901                                            function_type: GenerateSeries,
902                                            return_type: Int32,
903                                            args: [
904                                                Literal(
905                                                    Literal {
906                                                        data: Some(
907                                                            Int32(
908                                                                1,
909                                                            ),
910                                                        ),
911                                                        data_type: Some(
912                                                            Int32,
913                                                        ),
914                                                    },
915                                                ),
916                                                Literal(
917                                                    Literal {
918                                                        data: Some(
919                                                            Int32(
920                                                                100,
921                                                            ),
922                                                        ),
923                                                        data_type: Some(
924                                                            Int32,
925                                                        ),
926                                                    },
927                                                ),
928                                            ],
929                                        },
930                                    ),
931                                    with_ordinality: false,
932                                },
933                            ),
934                            where_clause: None,
935                            group_by: GroupKey(
936                                [],
937                            ),
938                            having: None,
939                            window: {},
940                            schema: Schema {
941                                fields: [
942                                    approx_percentile:Float64,
943                                ],
944                            },
945                        },
946                    ),
947                    order: [],
948                    limit: None,
949                    offset: None,
950                    with_ties: false,
951                    extra_order_exprs: [],
952                },
953            )"#]];
954
955        expected.assert_eq(&format!("{:#?}", bound));
956    }
957}