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 gap_fill_binder;
38mod insert;
39mod query;
40mod relation;
41mod select;
42mod set_expr;
43mod statement;
44mod struct_field;
45mod update;
46mod values;
47
48pub use bind_context::{BindContext, Clause, LateralBindContext};
49pub use create_view::BoundCreateView;
50pub use delete::BoundDelete;
51pub use expr::bind_data_type;
52pub use gap_fill_binder::BoundFillStrategy;
53pub use insert::BoundInsert;
54use pgwire::pg_server::{Session, SessionId};
55pub use query::BoundQuery;
56pub use relation::{
57    BoundBackCteRef, BoundBaseTable, BoundGapFill, BoundJoin, BoundShare, BoundShareInput,
58    BoundSource, BoundSystemTable, BoundWatermark, BoundWindowTableFunction, Relation,
59    ResolveQualifiedNameError, WindowTableFunctionKind,
60};
61// Re-export common types
62pub use risingwave_common::gap_fill_types::FillStrategy;
63pub use select::{BoundDistinct, BoundSelect};
64pub use set_expr::*;
65pub use statement::BoundStatement;
66pub use update::{BoundUpdate, UpdateProject};
67pub use values::BoundValues;
68
69use crate::catalog::catalog_service::CatalogReadGuard;
70use crate::catalog::root_catalog::SchemaPath;
71use crate::catalog::schema_catalog::SchemaCatalog;
72use crate::catalog::{CatalogResult, DatabaseId, TableId, ViewId};
73use crate::error::ErrorCode;
74use crate::session::{AuthContext, SessionImpl, StagingCatalogManager, TemporarySourceManager};
75use crate::user::user_service::UserInfoReadGuard;
76
77pub type ShareId = usize;
78
79/// The type of binding statement.
80enum BindFor {
81    /// Binding MV/SINK
82    Stream,
83    /// Binding a batch query
84    Batch,
85    /// Binding a DDL (e.g. CREATE TABLE/SOURCE)
86    Ddl,
87    /// Binding a system query (e.g. SHOW)
88    System,
89}
90
91/// `Binder` binds the identifiers in AST to columns in relations
92pub struct Binder {
93    // TODO: maybe we can only lock the database, but not the whole catalog.
94    catalog: CatalogReadGuard,
95    user: UserInfoReadGuard,
96    db_name: String,
97    database_id: DatabaseId,
98    session_id: SessionId,
99    context: BindContext,
100    auth_context: Arc<AuthContext>,
101    /// A stack holding contexts of outer queries when binding a subquery.
102    /// It also holds all of the lateral contexts for each respective
103    /// subquery.
104    ///
105    /// See [`Binder::bind_subquery_expr`] for details.
106    upper_subquery_contexts: Vec<(BindContext, Vec<LateralBindContext>)>,
107
108    /// A stack holding contexts of left-lateral `TableFactor`s.
109    ///
110    /// We need a separate stack as `CorrelatedInputRef` depth is
111    /// determined by the upper subquery context depth, not the lateral context stack depth.
112    lateral_contexts: Vec<LateralBindContext>,
113
114    next_subquery_id: usize,
115    next_values_id: usize,
116    /// The `ShareId` is used to identify the share relation which could be a CTE, a source, a view
117    /// and so on.
118    next_share_id: ShareId,
119
120    session_config: Arc<RwLock<SessionConfig>>,
121
122    search_path: SearchPath,
123    /// The type of binding statement.
124    bind_for: BindFor,
125
126    /// `ShareId`s identifying shared views.
127    shared_views: HashMap<ViewId, ShareId>,
128
129    /// The included relations while binding a query.
130    included_relations: HashSet<TableId>,
131
132    /// The included user-defined functions while binding a query.
133    included_udfs: HashSet<FunctionId>,
134
135    param_types: ParameterTypes,
136
137    /// The temporary sources that will be used during binding phase
138    temporary_source_manager: TemporarySourceManager,
139
140    /// The staging catalogs that will be used during binding phase
141    staging_catalog_manager: StagingCatalogManager,
142
143    /// Information for `secure_compare` function. It's ONLY available when binding the
144    /// `VALIDATE` clause of Webhook source i.e. `VALIDATE SECRET ... AS SECURE_COMPARE(...)`.
145    secure_compare_context: Option<SecureCompareContext>,
146}
147
148// There's one more hidden name, `HEADERS`, which is a reserved identifier for HTTP headers. Its type is `JSONB`.
149#[derive(Default, Clone, Debug)]
150pub struct SecureCompareContext {
151    /// The column name to store the whole payload in `JSONB`, but during validation it will be used as `bytea`
152    pub column_name: String,
153    /// The secret (usually a token provided by the webhook source user) to validate the calls
154    pub secret_name: Option<String>,
155}
156
157/// `ParameterTypes` is used to record the types of the parameters during binding prepared stataments.
158/// It works by following the rules:
159/// 1. At the beginning, it contains the user specified parameters type.
160/// 2. When the binder encounters a parameter, it will record it as unknown(call `record_new_param`)
161///    if it didn't exist in `ParameterTypes`.
162/// 3. When the binder encounters a cast on parameter, if it's a unknown type, the cast function
163///    will record the target type as infer type for that parameter(call `record_infer_type`). If the
164///    parameter has been inferred, the cast function will act as a normal cast.
165/// 4. After bind finished:
166///    (a) parameter not in `ParameterTypes` means that the user didn't specify it and it didn't
167///    occur in the query. `export` will return error if there is a kind of
168///    parameter. This rule is compatible with PostgreSQL
169///    (b) parameter is None means that it's a unknown type. The user didn't specify it
170///    and we can't infer it in the query. We will treat it as VARCHAR type finally. This rule is
171///    compatible with PostgreSQL.
172///    (c) parameter is Some means that it's a known type.
173#[derive(Clone, Debug)]
174pub struct ParameterTypes(Arc<RwLock<HashMap<u64, Option<DataType>>>>);
175
176impl ParameterTypes {
177    pub fn new(specified_param_types: Vec<Option<DataType>>) -> Self {
178        let map = specified_param_types
179            .into_iter()
180            .enumerate()
181            .map(|(index, data_type)| ((index + 1) as u64, data_type))
182            .collect::<HashMap<u64, Option<DataType>>>();
183        Self(Arc::new(RwLock::new(map)))
184    }
185
186    pub fn has_infer(&self, index: u64) -> bool {
187        self.0.read().get(&index).unwrap().is_some()
188    }
189
190    pub fn read_type(&self, index: u64) -> Option<DataType> {
191        self.0.read().get(&index).unwrap().clone()
192    }
193
194    pub fn record_new_param(&mut self, index: u64) {
195        self.0.write().entry(index).or_insert(None);
196    }
197
198    pub fn record_infer_type(&mut self, index: u64, data_type: &DataType) {
199        assert!(
200            !self.has_infer(index),
201            "The parameter has been inferred, should not be inferred again."
202        );
203        self.0
204            .write()
205            .get_mut(&index)
206            .unwrap()
207            .replace(data_type.clone());
208    }
209
210    pub fn export(&self) -> Result<Vec<DataType>> {
211        let types = self
212            .0
213            .read()
214            .clone()
215            .into_iter()
216            .sorted_by_key(|(index, _)| *index)
217            .collect::<Vec<_>>();
218
219        // Check if all the parameters have been inferred.
220        for ((index, _), expect_index) in types.iter().zip_eq_debug(1_u64..=types.len() as u64) {
221            if *index != expect_index {
222                return Err(ErrorCode::InvalidInputSyntax(format!(
223                    "Cannot infer the type of the parameter {}.",
224                    expect_index
225                ))
226                .into());
227            }
228        }
229
230        Ok(types
231            .into_iter()
232            .map(|(_, data_type)| data_type.unwrap_or(DataType::Varchar))
233            .collect::<Vec<_>>())
234    }
235}
236
237impl Binder {
238    fn new(session: &SessionImpl, bind_for: BindFor) -> Binder {
239        Binder {
240            catalog: session.env().catalog_reader().read_guard(),
241            user: session.env().user_info_reader().read_guard(),
242            db_name: session.database(),
243            database_id: session.database_id(),
244            session_id: session.id(),
245            context: BindContext::new(),
246            auth_context: session.auth_context(),
247            upper_subquery_contexts: vec![],
248            lateral_contexts: vec![],
249            next_subquery_id: 0,
250            next_values_id: 0,
251            next_share_id: 0,
252            session_config: session.shared_config(),
253            search_path: session.config().search_path(),
254            bind_for,
255            shared_views: HashMap::new(),
256            included_relations: HashSet::new(),
257            included_udfs: HashSet::new(),
258            param_types: ParameterTypes::new(vec![]),
259            temporary_source_manager: session.temporary_source_manager(),
260            staging_catalog_manager: session.staging_catalog_manager(),
261            secure_compare_context: None,
262        }
263    }
264
265    pub fn new_for_batch(session: &SessionImpl) -> Binder {
266        Self::new(session, BindFor::Batch)
267    }
268
269    pub fn new_for_stream(session: &SessionImpl) -> Binder {
270        Self::new(session, BindFor::Stream)
271    }
272
273    pub fn new_for_ddl(session: &SessionImpl) -> Binder {
274        Self::new(session, BindFor::Ddl)
275    }
276
277    pub fn new_for_system(session: &SessionImpl) -> Binder {
278        Self::new(session, BindFor::System)
279    }
280
281    /// Set the specified parameter types.
282    pub fn with_specified_params_types(mut self, param_types: Vec<Option<DataType>>) -> Self {
283        self.param_types = ParameterTypes::new(param_types);
284        self
285    }
286
287    /// Set the secure compare context.
288    pub fn with_secure_compare(mut self, ctx: SecureCompareContext) -> Self {
289        self.secure_compare_context = Some(ctx);
290        self
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    pub fn mock_binder() -> Binder {
451        mock_binder_with_param_types(vec![])
452    }
453
454    pub fn mock_binder_with_param_types(param_types: Vec<Option<DataType>>) -> Binder {
455        Binder::new_for_batch(&SessionImpl::mock()).with_specified_params_types(param_types)
456    }
457}
458
459#[cfg(test)]
460mod tests {
461    use expect_test::expect;
462
463    use super::test_utils::*;
464
465    #[tokio::test]
466    async fn test_rcte() {
467        let stmt = risingwave_sqlparser::parser::Parser::parse_sql(
468            "WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT * FROM t1",
469        ).unwrap().into_iter().next().unwrap();
470        let mut binder = mock_binder();
471        let bound = binder.bind(stmt).unwrap();
472
473        let expected = expect![[r#"
474            Query(
475                BoundQuery {
476                    body: Select(
477                        BoundSelect {
478                            distinct: All,
479                            select_items: [
480                                InputRef(
481                                    InputRef {
482                                        index: 0,
483                                        data_type: Int32,
484                                    },
485                                ),
486                            ],
487                            aliases: [
488                                Some(
489                                    "a",
490                                ),
491                            ],
492                            from: Some(
493                                Share(
494                                    BoundShare {
495                                        share_id: 0,
496                                        input: Query(
497                                            Right(
498                                                RecursiveUnion {
499                                                    all: true,
500                                                    base: Select(
501                                                        BoundSelect {
502                                                            distinct: All,
503                                                            select_items: [
504                                                                Literal(
505                                                                    Literal {
506                                                                        data: Some(
507                                                                            Int32(
508                                                                                1,
509                                                                            ),
510                                                                        ),
511                                                                        data_type: Some(
512                                                                            Int32,
513                                                                        ),
514                                                                    },
515                                                                ),
516                                                            ],
517                                                            aliases: [
518                                                                Some(
519                                                                    "a",
520                                                                ),
521                                                            ],
522                                                            from: None,
523                                                            where_clause: None,
524                                                            group_by: GroupKey(
525                                                                [],
526                                                            ),
527                                                            having: None,
528                                                            window: {},
529                                                            schema: Schema {
530                                                                fields: [
531                                                                    a:Int32,
532                                                                ],
533                                                            },
534                                                        },
535                                                    ),
536                                                    recursive: Select(
537                                                        BoundSelect {
538                                                            distinct: All,
539                                                            select_items: [
540                                                                FunctionCall(
541                                                                    FunctionCall {
542                                                                        func_type: Add,
543                                                                        return_type: Int32,
544                                                                        inputs: [
545                                                                            InputRef(
546                                                                                InputRef {
547                                                                                    index: 0,
548                                                                                    data_type: Int32,
549                                                                                },
550                                                                            ),
551                                                                            Literal(
552                                                                                Literal {
553                                                                                    data: Some(
554                                                                                        Int32(
555                                                                                            1,
556                                                                                        ),
557                                                                                    ),
558                                                                                    data_type: Some(
559                                                                                        Int32,
560                                                                                    ),
561                                                                                },
562                                                                            ),
563                                                                        ],
564                                                                    },
565                                                                ),
566                                                            ],
567                                                            aliases: [
568                                                                None,
569                                                            ],
570                                                            from: Some(
571                                                                BackCteRef(
572                                                                    BoundBackCteRef {
573                                                                        share_id: 0,
574                                                                        base: Select(
575                                                                            BoundSelect {
576                                                                                distinct: All,
577                                                                                select_items: [
578                                                                                    Literal(
579                                                                                        Literal {
580                                                                                            data: Some(
581                                                                                                Int32(
582                                                                                                    1,
583                                                                                                ),
584                                                                                            ),
585                                                                                            data_type: Some(
586                                                                                                Int32,
587                                                                                            ),
588                                                                                        },
589                                                                                    ),
590                                                                                ],
591                                                                                aliases: [
592                                                                                    Some(
593                                                                                        "a",
594                                                                                    ),
595                                                                                ],
596                                                                                from: None,
597                                                                                where_clause: None,
598                                                                                group_by: GroupKey(
599                                                                                    [],
600                                                                                ),
601                                                                                having: None,
602                                                                                window: {},
603                                                                                schema: Schema {
604                                                                                    fields: [
605                                                                                        a:Int32,
606                                                                                    ],
607                                                                                },
608                                                                            },
609                                                                        ),
610                                                                    },
611                                                                ),
612                                                            ),
613                                                            where_clause: Some(
614                                                                FunctionCall(
615                                                                    FunctionCall {
616                                                                        func_type: LessThan,
617                                                                        return_type: Boolean,
618                                                                        inputs: [
619                                                                            InputRef(
620                                                                                InputRef {
621                                                                                    index: 0,
622                                                                                    data_type: Int32,
623                                                                                },
624                                                                            ),
625                                                                            Literal(
626                                                                                Literal {
627                                                                                    data: Some(
628                                                                                        Int32(
629                                                                                            10,
630                                                                                        ),
631                                                                                    ),
632                                                                                    data_type: Some(
633                                                                                        Int32,
634                                                                                    ),
635                                                                                },
636                                                                            ),
637                                                                        ],
638                                                                    },
639                                                                ),
640                                                            ),
641                                                            group_by: GroupKey(
642                                                                [],
643                                                            ),
644                                                            having: None,
645                                                            window: {},
646                                                            schema: Schema {
647                                                                fields: [
648                                                                    ?column?:Int32,
649                                                                ],
650                                                            },
651                                                        },
652                                                    ),
653                                                    schema: Schema {
654                                                        fields: [
655                                                            a:Int32,
656                                                        ],
657                                                    },
658                                                },
659                                            ),
660                                        ),
661                                    },
662                                ),
663                            ),
664                            where_clause: None,
665                            group_by: GroupKey(
666                                [],
667                            ),
668                            having: None,
669                            window: {},
670                            schema: Schema {
671                                fields: [
672                                    a:Int32,
673                                ],
674                            },
675                        },
676                    ),
677                    order: [],
678                    limit: None,
679                    offset: None,
680                    with_ties: false,
681                    extra_order_exprs: [],
682                },
683            )"#]];
684
685        expected.assert_eq(&format!("{:#?}", bound));
686    }
687
688    #[tokio::test]
689    async fn test_bind_approx_percentile() {
690        let stmt = risingwave_sqlparser::parser::Parser::parse_sql(
691            "SELECT approx_percentile(0.5, 0.01) WITHIN GROUP (ORDER BY generate_series) FROM generate_series(1, 100)",
692        ).unwrap().into_iter().next().unwrap();
693        let parse_expected = expect![[r#"
694            Query(
695                Query {
696                    with: None,
697                    body: Select(
698                        Select {
699                            distinct: All,
700                            projection: [
701                                UnnamedExpr(
702                                    Function(
703                                        Function {
704                                            scalar_as_agg: false,
705                                            name: ObjectName(
706                                                [
707                                                    Ident {
708                                                        value: "approx_percentile",
709                                                        quote_style: None,
710                                                    },
711                                                ],
712                                            ),
713                                            arg_list: FunctionArgList {
714                                                distinct: false,
715                                                args: [
716                                                    Unnamed(
717                                                        Expr(
718                                                            Value(
719                                                                Number(
720                                                                    "0.5",
721                                                                ),
722                                                            ),
723                                                        ),
724                                                    ),
725                                                    Unnamed(
726                                                        Expr(
727                                                            Value(
728                                                                Number(
729                                                                    "0.01",
730                                                                ),
731                                                            ),
732                                                        ),
733                                                    ),
734                                                ],
735                                                variadic: false,
736                                                order_by: [],
737                                                ignore_nulls: false,
738                                            },
739                                            within_group: Some(
740                                                OrderByExpr {
741                                                    expr: Identifier(
742                                                        Ident {
743                                                            value: "generate_series",
744                                                            quote_style: None,
745                                                        },
746                                                    ),
747                                                    asc: None,
748                                                    nulls_first: None,
749                                                },
750                                            ),
751                                            filter: None,
752                                            over: None,
753                                        },
754                                    ),
755                                ),
756                            ],
757                            from: [
758                                TableWithJoins {
759                                    relation: TableFunction {
760                                        name: ObjectName(
761                                            [
762                                                Ident {
763                                                    value: "generate_series",
764                                                    quote_style: None,
765                                                },
766                                            ],
767                                        ),
768                                        alias: None,
769                                        args: [
770                                            Unnamed(
771                                                Expr(
772                                                    Value(
773                                                        Number(
774                                                            "1",
775                                                        ),
776                                                    ),
777                                                ),
778                                            ),
779                                            Unnamed(
780                                                Expr(
781                                                    Value(
782                                                        Number(
783                                                            "100",
784                                                        ),
785                                                    ),
786                                                ),
787                                            ),
788                                        ],
789                                        with_ordinality: false,
790                                    },
791                                    joins: [],
792                                },
793                            ],
794                            lateral_views: [],
795                            selection: None,
796                            group_by: [],
797                            having: None,
798                            window: [],
799                        },
800                    ),
801                    order_by: [],
802                    limit: None,
803                    offset: None,
804                    fetch: None,
805                },
806            )"#]];
807        parse_expected.assert_eq(&format!("{:#?}", stmt));
808
809        let mut binder = mock_binder();
810        let bound = binder.bind(stmt).unwrap();
811
812        let expected = expect![[r#"
813            Query(
814                BoundQuery {
815                    body: Select(
816                        BoundSelect {
817                            distinct: All,
818                            select_items: [
819                                AggCall(
820                                    AggCall {
821                                        agg_type: Builtin(
822                                            ApproxPercentile,
823                                        ),
824                                        return_type: Float64,
825                                        args: [
826                                            FunctionCall(
827                                                FunctionCall {
828                                                    func_type: Cast,
829                                                    return_type: Float64,
830                                                    inputs: [
831                                                        InputRef(
832                                                            InputRef {
833                                                                index: 0,
834                                                                data_type: Int32,
835                                                            },
836                                                        ),
837                                                    ],
838                                                },
839                                            ),
840                                        ],
841                                        filter: Condition {
842                                            conjunctions: [],
843                                        },
844                                        distinct: false,
845                                        order_by: OrderBy {
846                                            sort_exprs: [
847                                                OrderByExpr {
848                                                    expr: InputRef(
849                                                        InputRef {
850                                                            index: 0,
851                                                            data_type: Int32,
852                                                        },
853                                                    ),
854                                                    order_type: OrderType {
855                                                        direction: Ascending,
856                                                        nulls_are: Largest,
857                                                    },
858                                                },
859                                            ],
860                                        },
861                                        direct_args: [
862                                            Literal {
863                                                data: Some(
864                                                    Float64(
865                                                        0.5,
866                                                    ),
867                                                ),
868                                                data_type: Some(
869                                                    Float64,
870                                                ),
871                                            },
872                                            Literal {
873                                                data: Some(
874                                                    Float64(
875                                                        0.01,
876                                                    ),
877                                                ),
878                                                data_type: Some(
879                                                    Float64,
880                                                ),
881                                            },
882                                        ],
883                                    },
884                                ),
885                            ],
886                            aliases: [
887                                Some(
888                                    "approx_percentile",
889                                ),
890                            ],
891                            from: Some(
892                                TableFunction {
893                                    expr: TableFunction(
894                                        FunctionCall {
895                                            function_type: GenerateSeries,
896                                            return_type: Int32,
897                                            args: [
898                                                Literal(
899                                                    Literal {
900                                                        data: Some(
901                                                            Int32(
902                                                                1,
903                                                            ),
904                                                        ),
905                                                        data_type: Some(
906                                                            Int32,
907                                                        ),
908                                                    },
909                                                ),
910                                                Literal(
911                                                    Literal {
912                                                        data: Some(
913                                                            Int32(
914                                                                100,
915                                                            ),
916                                                        ),
917                                                        data_type: Some(
918                                                            Int32,
919                                                        ),
920                                                    },
921                                                ),
922                                            ],
923                                        },
924                                    ),
925                                    with_ordinality: false,
926                                },
927                            ),
928                            where_clause: None,
929                            group_by: GroupKey(
930                                [],
931                            ),
932                            having: None,
933                            window: {},
934                            schema: Schema {
935                                fields: [
936                                    approx_percentile:Float64,
937                                ],
938                            },
939                        },
940                    ),
941                    order: [],
942                    limit: None,
943                    offset: None,
944                    with_ties: false,
945                    extra_order_exprs: [],
946                },
947            )"#]];
948
949        expected.assert_eq(&format!("{:#?}", bound));
950    }
951}