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