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}