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