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