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