Skip to main content

risingwave_frontend/binder/relation/
mod.rs

1// Copyright 2022 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::hash_map::Entry;
16use std::ops::Deref;
17
18use itertools::{EitherOrBoth, Itertools};
19use risingwave_common::bail;
20use risingwave_common::catalog::{Field, TableId};
21use risingwave_sqlparser::ast::{
22    AsOf, Expr as ParserExpr, FunctionArg, FunctionArgExpr, Ident, ObjectName, TableAlias,
23    TableFactor,
24};
25use thiserror::Error;
26use thiserror_ext::AsReport;
27
28use super::bind_context::ColumnBinding;
29use super::statement::RewriteExprsRecursive;
30use crate::binder::Binder;
31use crate::binder::bind_context::{BindingCte, BindingCteState};
32use crate::error::{ErrorCode, Result, RwError};
33use crate::expr::{ExprImpl, InputRef};
34
35mod gap_fill;
36mod join;
37mod match_recognize;
38mod share;
39mod subquery;
40mod table_function;
41mod table_or_source;
42mod watermark;
43mod window_table_function;
44
45pub use gap_fill::BoundGapFill;
46pub use join::BoundJoin;
47pub use match_recognize::{
48    BoundMatchRecognize, BoundMeasure, BoundSymbolDefinition, MeasureSlotKind,
49};
50pub use share::{BoundShare, BoundShareInput};
51pub use subquery::BoundSubquery;
52pub use table_or_source::{
53    BoundBaseTable, BoundIcebergMetadataTable, BoundSource, BoundSystemTable,
54};
55pub use watermark::BoundWatermark;
56pub use window_table_function::{BoundWindowTableFunction, WindowTableFunctionKind};
57
58use crate::expr::{CorrelatedId, ScopeId};
59
60/// A validated item that refers to a table-like entity, including base table, subquery, join, etc.
61/// It is usually part of the `from` clause.
62#[derive(Debug, Clone)]
63pub enum Relation {
64    Source(Box<BoundSource>),
65    BaseTable(Box<BoundBaseTable>),
66    SystemTable(Box<BoundSystemTable>),
67    IcebergMetadataTable(Box<BoundIcebergMetadataTable>),
68    Subquery(Box<BoundSubquery>),
69    Join(Box<BoundJoin>),
70    Apply(Box<BoundJoin>),
71    WindowTableFunction(Box<BoundWindowTableFunction>),
72    /// Table function or scalar function.
73    TableFunction {
74        expr: ExprImpl,
75        with_ordinality: bool,
76    },
77    Watermark(Box<BoundWatermark>),
78    Share(Box<BoundShare>),
79    GapFill(Box<BoundGapFill>),
80    MatchRecognize(Box<BoundMatchRecognize>),
81}
82
83impl RewriteExprsRecursive for Relation {
84    fn rewrite_exprs_recursive(&mut self, rewriter: &mut impl crate::expr::ExprRewriter) {
85        match self {
86            Relation::Subquery(inner) => inner.rewrite_exprs_recursive(rewriter),
87            Relation::Join(inner) => inner.rewrite_exprs_recursive(rewriter),
88            Relation::Apply(inner) => inner.rewrite_exprs_recursive(rewriter),
89            Relation::WindowTableFunction(inner) => inner.rewrite_exprs_recursive(rewriter),
90            Relation::Watermark(inner) => inner.rewrite_exprs_recursive(rewriter),
91            Relation::Share(inner) => inner.rewrite_exprs_recursive(rewriter),
92            Relation::TableFunction { expr: inner, .. } => {
93                *inner = rewriter.rewrite_expr(inner.take())
94            }
95            Relation::MatchRecognize(inner) => {
96                inner.input.rewrite_exprs_recursive(rewriter);
97                for e in inner.exprs_mut() {
98                    *e = rewriter.rewrite_expr(e.take());
99                }
100            }
101            _ => {}
102        }
103    }
104}
105
106impl Relation {
107    pub fn is_correlated_by_scope(&self, scope: ScopeId) -> bool {
108        match self {
109            Relation::Subquery(subquery) => subquery.query.is_correlated_by_scope(scope),
110            Relation::Join(join) | Relation::Apply(join) => {
111                join.cond.has_correlated_input_ref_by_scope(scope)
112                    || join.left.is_correlated_by_scope(scope)
113                    || join.right.is_correlated_by_scope(scope)
114            }
115            Relation::TableFunction {
116                expr: table_function,
117                with_ordinality: _,
118            } => table_function.has_correlated_input_ref_by_scope(scope),
119            Relation::Share(share) => match &share.input {
120                BoundShareInput::Query(query) => query.is_correlated_by_scope(scope),
121                BoundShareInput::ChangeLog { relation, .. } => {
122                    relation.is_correlated_by_scope(scope)
123                }
124            },
125            Relation::MatchRecognize(inner) => {
126                inner.input.is_correlated_by_scope(scope)
127                    || inner
128                        .exprs()
129                        .any(|e| e.has_correlated_input_ref_by_scope(scope))
130            }
131            _ => false,
132        }
133    }
134
135    pub fn is_correlated_by_correlated_id(&self, correlated_id: CorrelatedId) -> bool {
136        match self {
137            Relation::Subquery(subquery) => {
138                subquery.query.is_correlated_by_correlated_id(correlated_id)
139            }
140            Relation::Join(join) | Relation::Apply(join) => {
141                join.cond
142                    .has_correlated_input_ref_by_correlated_id(correlated_id)
143                    || join.left.is_correlated_by_correlated_id(correlated_id)
144                    || join.right.is_correlated_by_correlated_id(correlated_id)
145            }
146            Relation::TableFunction {
147                expr: table_function,
148                with_ordinality: _,
149            } => table_function.has_correlated_input_ref_by_correlated_id(correlated_id),
150            Relation::Share(share) => match &share.input {
151                BoundShareInput::Query(query) => {
152                    query.is_correlated_by_correlated_id(correlated_id)
153                }
154                BoundShareInput::ChangeLog { relation, .. } => {
155                    relation.is_correlated_by_correlated_id(correlated_id)
156                }
157            },
158            Relation::MatchRecognize(inner) => {
159                inner.input.is_correlated_by_correlated_id(correlated_id)
160                    || inner
161                        .exprs()
162                        .any(|e| e.has_correlated_input_ref_by_correlated_id(correlated_id))
163            }
164            _ => false,
165        }
166    }
167
168    pub fn collect_correlated_indices_by_scope_and_assign_id(
169        &mut self,
170        scope: ScopeId,
171        correlated_id: CorrelatedId,
172    ) -> Vec<usize> {
173        match self {
174            Relation::Subquery(subquery) => subquery
175                .query
176                .collect_correlated_indices_by_scope_and_assign_id(scope, correlated_id),
177            Relation::Join(join) | Relation::Apply(join) => {
178                let mut correlated_indices = vec![];
179                correlated_indices.extend(
180                    join.cond
181                        .collect_correlated_indices_by_scope_and_assign_id(scope, correlated_id),
182                );
183                correlated_indices.extend(
184                    join.left
185                        .collect_correlated_indices_by_scope_and_assign_id(scope, correlated_id),
186                );
187                correlated_indices.extend(
188                    join.right
189                        .collect_correlated_indices_by_scope_and_assign_id(scope, correlated_id),
190                );
191                correlated_indices
192            }
193            Relation::TableFunction {
194                expr: table_function,
195                with_ordinality: _,
196            } => table_function
197                .collect_correlated_indices_by_scope_and_assign_id(scope, correlated_id),
198            Relation::Share(share) => {
199                match &mut share.input {
200                    BoundShareInput::Query(query) => query
201                        .collect_correlated_indices_by_scope_and_assign_id(scope, correlated_id),
202                    BoundShareInput::ChangeLog { relation, .. } => relation
203                        .collect_correlated_indices_by_scope_and_assign_id(scope, correlated_id),
204                }
205            }
206            Relation::MatchRecognize(inner) => {
207                let mut indices = inner
208                    .input
209                    .collect_correlated_indices_by_scope_and_assign_id(scope, correlated_id);
210                for e in inner.exprs_mut() {
211                    indices.extend(
212                        e.collect_correlated_indices_by_scope_and_assign_id(scope, correlated_id),
213                    );
214                }
215                indices
216            }
217            _ => vec![],
218        }
219    }
220}
221
222#[derive(Debug)]
223#[non_exhaustive]
224pub enum ResolveQualifiedNameErrorKind {
225    QualifiedNameTooLong,
226    NotCurrentDatabase,
227}
228
229#[derive(Debug, Error)]
230pub struct ResolveQualifiedNameError {
231    qualified: String,
232    kind: ResolveQualifiedNameErrorKind,
233}
234
235impl std::fmt::Display for ResolveQualifiedNameError {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        match self.kind {
238            ResolveQualifiedNameErrorKind::QualifiedNameTooLong => write!(
239                f,
240                "improper qualified name (too many dotted names): {}",
241                self.qualified
242            ),
243            ResolveQualifiedNameErrorKind::NotCurrentDatabase => write!(
244                f,
245                "cross-database references are not implemented: \"{}\"",
246                self.qualified
247            ),
248        }
249    }
250}
251
252impl ResolveQualifiedNameError {
253    pub fn new(qualified: String, kind: ResolveQualifiedNameErrorKind) -> Self {
254        Self { qualified, kind }
255    }
256}
257
258impl From<ResolveQualifiedNameError> for RwError {
259    fn from(e: ResolveQualifiedNameError) -> Self {
260        ErrorCode::InvalidInputSyntax(format!("{}", e.as_report())).into()
261    }
262}
263
264impl Binder {
265    /// return (`schema_name`, `name`)
266    pub fn resolve_schema_qualified_name(
267        db_name: &str,
268        name: &ObjectName,
269    ) -> std::result::Result<(Option<String>, String), ResolveQualifiedNameError> {
270        let formatted_name = name.to_string();
271        let mut identifiers = name.0.clone();
272
273        if identifiers.len() > 3 {
274            return Err(ResolveQualifiedNameError::new(
275                formatted_name,
276                ResolveQualifiedNameErrorKind::QualifiedNameTooLong,
277            ));
278        }
279
280        let name = identifiers.pop().unwrap().real_value();
281
282        let schema_name = identifiers.pop().map(|ident| ident.real_value());
283        let database_name = identifiers.pop().map(|ident| ident.real_value());
284
285        if let Some(database_name) = database_name
286            && database_name != db_name
287        {
288            return Err(ResolveQualifiedNameError::new(
289                formatted_name,
290                ResolveQualifiedNameErrorKind::NotCurrentDatabase,
291            ));
292        }
293
294        Ok((schema_name, name))
295    }
296
297    /// check whether the name is a cross-database reference
298    pub fn validate_cross_db_reference(
299        db_name: &str,
300        name: &ObjectName,
301    ) -> std::result::Result<(), ResolveQualifiedNameError> {
302        let formatted_name = name.to_string();
303        let identifiers = &name.0;
304        if identifiers.len() > 3 {
305            return Err(ResolveQualifiedNameError::new(
306                formatted_name,
307                ResolveQualifiedNameErrorKind::QualifiedNameTooLong,
308            ));
309        }
310
311        if identifiers.len() == 3 && identifiers[0].real_value() != db_name {
312            return Err(ResolveQualifiedNameError::new(
313                formatted_name,
314                ResolveQualifiedNameErrorKind::NotCurrentDatabase,
315            ));
316        }
317
318        Ok(())
319    }
320
321    /// return (`database_name`, `schema_name`, `name`)
322    pub fn resolve_db_schema_qualified_name(
323        name: &ObjectName,
324    ) -> std::result::Result<(Option<String>, Option<String>, String), ResolveQualifiedNameError>
325    {
326        let formatted_name = name.to_string();
327        let mut identifiers = name.0.clone();
328
329        if identifiers.len() > 3 {
330            return Err(ResolveQualifiedNameError::new(
331                formatted_name,
332                ResolveQualifiedNameErrorKind::QualifiedNameTooLong,
333            ));
334        }
335
336        let name = identifiers.pop().unwrap().real_value();
337        let schema_name = identifiers.pop().map(|ident| ident.real_value());
338        let database_name = identifiers.pop().map(|ident| ident.real_value());
339
340        Ok((database_name, schema_name, name))
341    }
342
343    /// return first name in identifiers, must have only one name.
344    fn resolve_single_name(mut identifiers: Vec<Ident>, ident_desc: &str) -> Result<String> {
345        if identifiers.len() > 1 {
346            bail!("{} must contain 1 argument", ident_desc);
347        }
348        let name = identifiers.pop().unwrap().real_value();
349
350        Ok(name)
351    }
352
353    /// return the `database_name`
354    pub fn resolve_database_name(name: ObjectName) -> Result<String> {
355        Self::resolve_single_name(name.0, "database name")
356    }
357
358    /// return the `schema_name`
359    pub fn resolve_schema_name(name: ObjectName) -> Result<String> {
360        Self::resolve_single_name(name.0, "schema name")
361    }
362
363    /// return the `index_name`
364    pub fn resolve_index_name(name: ObjectName) -> Result<String> {
365        Self::resolve_single_name(name.0, "index name")
366    }
367
368    /// return the `view_name`
369    pub fn resolve_view_name(name: ObjectName) -> Result<String> {
370        Self::resolve_single_name(name.0, "view name")
371    }
372
373    /// return the `sink_name`
374    pub fn resolve_sink_name(name: ObjectName) -> Result<String> {
375        Self::resolve_single_name(name.0, "sink name")
376    }
377
378    /// return the `subscription_name`
379    pub fn resolve_subscription_name(name: ObjectName) -> Result<String> {
380        Self::resolve_single_name(name.0, "subscription name")
381    }
382
383    /// return the `table_name`
384    pub fn resolve_table_name(name: ObjectName) -> Result<String> {
385        Self::resolve_single_name(name.0, "table name")
386    }
387
388    /// return the `source_name`
389    pub fn resolve_source_name(name: ObjectName) -> Result<String> {
390        Self::resolve_single_name(name.0, "source name")
391    }
392
393    /// return the `user_name`
394    pub fn resolve_user_name(name: ObjectName) -> Result<String> {
395        Self::resolve_single_name(name.0, "user name")
396    }
397
398    /// Fill the [`BindContext`](super::BindContext) for table.
399    pub(super) fn bind_table_to_context(
400        &mut self,
401        columns: impl IntoIterator<Item = (bool, Field)>, // bool indicates if the field is hidden
402        table_name: String,
403        schema_name: Option<String>,
404        alias: Option<&TableAlias>,
405    ) -> Result<()> {
406        const EMPTY: [Ident; 0] = [];
407        let (resolved_schema_name, table_name, column_aliases, table_alias) = match alias {
408            None => (schema_name.clone(), table_name, &EMPTY[..], None),
409            Some(TableAlias { name, columns }) => (
410                None,
411                name.real_value(),
412                columns.as_slice(),
413                Some(table_name),
414            ),
415        };
416
417        let num_col_aliases = column_aliases.len();
418
419        let begin = self.context.columns.len();
420        // Column aliases can be less than columns, but not more.
421        // It also needs to skip hidden columns.
422        let mut alias_iter = column_aliases.iter().fuse();
423        let mut index = 0;
424        columns.into_iter().for_each(|(is_hidden, mut field)| {
425            let name = match is_hidden {
426                true => field.name.clone(),
427                false => alias_iter
428                    .next()
429                    .map(|t| t.real_value())
430                    .unwrap_or_else(|| field.name.clone()),
431            };
432            field.name.clone_from(&name);
433            self.context.columns.push(ColumnBinding::new(
434                table_name.clone(),
435                schema_name.clone(),
436                table_alias.clone(),
437                begin + index,
438                is_hidden,
439                field,
440            ));
441            self.context
442                .indices_of
443                .entry(name)
444                .or_default()
445                .push(self.context.columns.len() - 1);
446            index += 1;
447        });
448
449        let num_cols = index;
450        if num_cols < num_col_aliases {
451            return Err(ErrorCode::BindError(format!(
452                "table \"{table_name}\" has {num_cols} columns available but {num_col_aliases} column aliases specified",
453            ))
454            .into());
455        }
456
457        match self
458            .context
459            .range_of
460            .entry((resolved_schema_name, table_name.clone()))
461        {
462            Entry::Occupied(_) => Err(ErrorCode::InternalError(format!(
463                "Duplicated table name while binding table to context: {}",
464                table_name
465            ))
466            .into()),
467            Entry::Vacant(entry) => {
468                entry.insert((begin, self.context.columns.len()));
469                Ok(())
470            }
471        }
472    }
473
474    /// Binds a relation, which can be:
475    /// - a table/source/materialized view
476    /// - a reference to a CTE
477    /// - a logical view
478    pub fn bind_relation_by_name(
479        &mut self,
480        name: &ObjectName,
481        alias: Option<&TableAlias>,
482        as_of: Option<&AsOf>,
483        allow_cross_db: bool,
484    ) -> Result<Relation> {
485        let (db_name, schema_name, table_name) = if allow_cross_db {
486            Self::resolve_db_schema_qualified_name(name)?
487        } else {
488            let (schema_name, table_name) =
489                Self::resolve_schema_qualified_name(&self.db_name, name)?;
490            (None, schema_name, table_name)
491        };
492
493        if schema_name.is_none()
494            // the `table_name` here is the name of the currently binding cte.
495            && let Some(item) = self.context.cte_to_relation.get(&table_name)
496        {
497            // Handles CTE
498
499            if as_of.is_some() {
500                return Err(ErrorCode::BindError(
501                    "Right table of a temporal join should not be a CTE. \
502                 It should be a table, index, or materialized view"
503                        .to_owned(),
504                )
505                .into());
506            }
507
508            let BindingCte {
509                share_id,
510                state: cte_state,
511                alias: mut original_alias,
512            } = item.deref().borrow().clone();
513
514            // The original CTE alias ought to be its table name.
515            debug_assert_eq!(original_alias.name.real_value(), table_name);
516
517            if let Some(from_alias) = alias {
518                original_alias.name = from_alias.name.clone();
519                original_alias.columns = original_alias
520                    .columns
521                    .into_iter()
522                    .zip_longest(from_alias.columns.iter().cloned())
523                    .map(EitherOrBoth::into_right)
524                    .collect();
525            }
526
527            let exposed_table_name = original_alias.name.real_value();
528            self.context
529                .check_relation_name_conflict(&exposed_table_name)?;
530
531            match cte_state {
532                BindingCteState::Bound { query } => {
533                    let input = BoundShareInput::Query(query);
534                    self.bind_table_to_context(
535                        input.fields()?,
536                        table_name,
537                        None,
538                        Some(&original_alias),
539                    )?;
540                    self.context.add_cte_name(exposed_table_name);
541                    // we could always share the cte,
542                    // no matter it's recursive or not.
543                    Ok(Relation::Share(Box::new(BoundShare { share_id, input })))
544                }
545                BindingCteState::ChangeLog { table, key_indices } => {
546                    let input = BoundShareInput::ChangeLog {
547                        relation: table,
548                        key_indices,
549                    };
550                    self.bind_table_to_context(
551                        input.fields()?,
552                        table_name,
553                        None,
554                        Some(&original_alias),
555                    )?;
556                    self.context.add_cte_name(exposed_table_name);
557                    Ok(Relation::Share(Box::new(BoundShare { share_id, input })))
558                }
559            }
560        } else {
561            let exposed_table_name = alias
562                .map(|alias| alias.name.real_value())
563                .unwrap_or_else(|| table_name.clone());
564            self.context.check_catalog_name(&exposed_table_name)?;
565            self.bind_catalog_relation_by_name(
566                db_name.as_deref(),
567                schema_name.as_deref(),
568                &table_name,
569                alias,
570                as_of,
571                false,
572            )
573        }
574    }
575
576    // Bind a relation provided a function arg.
577    fn bind_relation_by_function_arg(
578        &mut self,
579        arg: Option<&FunctionArg>,
580        err_msg: &str,
581    ) -> Result<(Relation, ObjectName)> {
582        let Some(FunctionArg::Unnamed(FunctionArgExpr::Expr(expr))) = arg else {
583            return Err(ErrorCode::BindError(err_msg.to_owned()).into());
584        };
585        let table_name = match expr {
586            ParserExpr::Identifier(ident) => Ok::<_, RwError>(ObjectName(vec![ident.clone()])),
587            ParserExpr::CompoundIdentifier(idents) => Ok(ObjectName(idents.clone())),
588            _ => Err(ErrorCode::BindError(err_msg.to_owned()).into()),
589        }?;
590
591        Ok((
592            self.bind_relation_by_name(&table_name, None, None, true)?,
593            table_name,
594        ))
595    }
596
597    // Bind column provided a function arg.
598    fn bind_column_by_function_args(
599        &mut self,
600        arg: Option<&FunctionArg>,
601        err_msg: &str,
602    ) -> Result<Box<InputRef>> {
603        if let Some(time_col_arg) = arg
604            && let Some(ExprImpl::InputRef(time_col)) =
605                self.bind_function_arg(time_col_arg)?.into_iter().next()
606        {
607            Ok(time_col)
608        } else {
609            Err(ErrorCode::BindError(err_msg.to_owned()).into())
610        }
611    }
612
613    /// `rw_table(table_id[,schema_name])` which queries internal table
614    fn bind_internal_table(
615        &mut self,
616        args: &[FunctionArg],
617        alias: Option<&TableAlias>,
618    ) -> Result<Relation> {
619        if args.is_empty() || args.len() > 2 {
620            return Err(
621                ErrorCode::BindError("usage: rw_table(table_id[,schema_name])".to_owned()).into(),
622            );
623        }
624
625        let table_id: TableId = args[0]
626            .to_string()
627            .parse::<u32>()
628            .map_err(|err| {
629                RwError::from(ErrorCode::BindError(format!(
630                    "invalid table id: {}",
631                    err.as_report()
632                )))
633            })?
634            .into();
635
636        let schema = args.get(1).map(|arg| arg.to_string());
637
638        let table_name = self.catalog.get_table_name_by_id(table_id)?;
639        self.bind_catalog_relation_by_name(None, schema.as_deref(), &table_name, alias, None, false)
640    }
641
642    pub(super) fn bind_table_factor(&mut self, table_factor: &TableFactor) -> Result<Relation> {
643        match table_factor {
644            TableFactor::Table { name, alias, as_of } => {
645                self.bind_relation_by_name(name, alias.as_ref(), as_of.as_ref(), true)
646            }
647            TableFactor::TableFunction {
648                name,
649                alias,
650                args,
651                with_ordinality,
652            } => {
653                let visibility = self.mark_lateral_contexts_visible();
654                let result = self.bind_table_function(name, alias.as_ref(), args, *with_ordinality);
655                self.restore_lateral_contexts_visibility(visibility);
656                result
657            }
658            TableFactor::Derived {
659                lateral,
660                subquery,
661                alias,
662            } => {
663                if *lateral {
664                    let visibility = self.mark_lateral_contexts_visible();
665
666                    // Bind lateral subquery here.
667                    let result = self.bind_subquery_relation(subquery, alias.as_ref(), true);
668
669                    self.restore_lateral_contexts_visibility(visibility);
670                    result.map(|subquery| Relation::Subquery(Box::new(subquery)))
671                } else {
672                    // Non-lateral subqueries to not have access to the join-tree context.
673                    self.push_lateral_context();
674                    let bound_subquery =
675                        self.bind_subquery_relation(subquery, alias.as_ref(), false)?;
676                    self.pop_and_merge_lateral_context()?;
677                    Ok(Relation::Subquery(Box::new(bound_subquery)))
678                }
679            }
680            TableFactor::NestedJoin(table_with_joins) => {
681                self.push_lateral_context();
682                let bound_join = self.bind_table_with_joins(table_with_joins)?;
683                self.pop_and_merge_lateral_context()?;
684                Ok(bound_join)
685            }
686            TableFactor::MatchRecognize {
687                table,
688                partition_by,
689                order_by,
690                measures,
691                rows_per_match,
692                after_match_skip,
693                pattern,
694                within,
695                subsets,
696                symbols,
697                alias,
698            } => Ok(Relation::MatchRecognize(Box::new(
699                self.bind_match_recognize(
700                    table,
701                    partition_by,
702                    order_by,
703                    measures,
704                    rows_per_match,
705                    after_match_skip,
706                    pattern,
707                    within,
708                    subsets,
709                    symbols,
710                    alias.as_ref(),
711                )?,
712            ))),
713        }
714    }
715}