diff --git a/crates/polyglot-sql/src/dialects/mod.rs b/crates/polyglot-sql/src/dialects/mod.rs index 8aa74b09..9f70446c 100644 --- a/crates/polyglot-sql/src/dialects/mod.rs +++ b/crates/polyglot-sql/src/dialects/mod.rs @@ -4305,14 +4305,14 @@ impl Dialect { if let Expression::Count(ref c) = e { // Build COUNT_BIG(...) as an AggregateFunction let args = if c.star { - vec![Expression::Star(crate::expressions::Star { + vec![Expression::Star(Box::new(crate::expressions::Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - })] + }))] } else if let Some(ref this) = c.this { vec![this.clone()] } else { @@ -9799,14 +9799,14 @@ impl Dialect { })); // Outer: SELECT ARRAY_AGG(*) FROM (inner_subquery) - let star = Expression::Star(Star { + let star = Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: vec![], span: None, - }); + })); let array_agg = Expression::ArrayAgg(Box::new(AggFunc { this: star, distinct: false, diff --git a/crates/polyglot-sql/src/dialects/normalization/statements.rs b/crates/polyglot-sql/src/dialects/normalization/statements.rs index 1186544e..6a5db758 100644 --- a/crates/polyglot-sql/src/dialects/normalization/statements.rs +++ b/crates/polyglot-sql/src/dialects/normalization/statements.rs @@ -422,14 +422,15 @@ pub(super) fn normalize_root(expression: Expression, context: &NormalizationCont inferred_type: None, })); let mut new_select = crate::expressions::Select::new(); - new_select.expressions = vec![Expression::Star(crate::expressions::Star { - table: None, - except: None, - replace: None, - rename: None, - trailing_comments: Vec::new(), - span: None, - })]; + new_select.expressions = + vec![Expression::Star(Box::new(crate::expressions::Star { + table: None, + except: None, + replace: None, + rename: None, + trailing_comments: Vec::new(), + span: None, + }))]; new_select.from = Some(crate::expressions::From { expressions: vec![values_subquery], }); @@ -476,14 +477,14 @@ pub(super) fn normalize_root(expression: Expression, context: &NormalizationCont Expression::Values(Box::new(values)) }; let mut anchor = Select::new(); - anchor.expressions = vec![Expression::Star(Star { + anchor.expressions = vec![Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - })]; + }))]; anchor.from = Some(From { expressions: vec![source], }); @@ -744,14 +745,16 @@ pub(super) fn rewrite( new_ct.constraints.clear(); // Build: SELECT * FROM b LIMIT 0 let select = Expression::Select(Box::new(crate::expressions::Select { - expressions: vec![Expression::Star(crate::expressions::Star { - table: None, - except: None, - replace: None, - rename: None, - trailing_comments: Vec::new(), - span: None, - })], + expressions: vec![Expression::Star(Box::new( + crate::expressions::Star { + table: None, + except: None, + replace: None, + rename: None, + trailing_comments: Vec::new(), + span: None, + }, + ))], from: Some(crate::expressions::From { expressions: vec![Expression::Table(Box::new(source_table))], }), @@ -789,14 +792,16 @@ pub(super) fn rewrite( aliased_source.alias = Some(Identifier::new("temp")); // Build: SELECT TOP 0 * INTO a FROM b AS temp let select = Expression::Select(Box::new(crate::expressions::Select { - expressions: vec![Expression::Star(crate::expressions::Star { - table: None, - except: None, - replace: None, - rename: None, - trailing_comments: Vec::new(), - span: None, - })], + expressions: vec![Expression::Star(Box::new( + crate::expressions::Star { + table: None, + except: None, + replace: None, + rename: None, + trailing_comments: Vec::new(), + span: None, + }, + ))], from: Some(crate::expressions::From { expressions: vec![Expression::Table(Box::new(aliased_source))], }), diff --git a/crates/polyglot-sql/src/dialects/tsql.rs b/crates/polyglot-sql/src/dialects/tsql.rs index 87c8b3b5..71eafe8d 100644 --- a/crates/polyglot-sql/src/dialects/tsql.rs +++ b/crates/polyglot-sql/src/dialects/tsql.rs @@ -1603,14 +1603,14 @@ impl TSQLDialect { let predicate = Self::rewrite_column_qualifier(predicate, &outer_alias, &inner_alias)?; let mut select = Select::new(); - select.expressions = vec![Expression::Star(Star { + select.expressions = vec![Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - })]; + }))]; select.from = Some(crate::expressions::From { expressions: vec![source], }); diff --git a/crates/polyglot-sql/src/expressions.rs b/crates/polyglot-sql/src/expressions.rs index 921fd456..198b8a3d 100644 --- a/crates/polyglot-sql/src/expressions.rs +++ b/crates/polyglot-sql/src/expressions.rs @@ -89,7 +89,7 @@ pub enum Expression { Identifier(Identifier), Column(Box), Table(Box), - Star(Star), + Star(Box), /// Snowflake braced wildcard syntax: {*}, {tbl.*}, {* EXCLUDE (...)}, {* ILIKE '...'} BracedWildcard(Box), @@ -1829,14 +1829,14 @@ impl Expression { /// Create a wildcard star (`*`) expression with no EXCEPT/REPLACE/RENAME modifiers. pub fn star() -> Self { - Expression::Star(Star { + Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - }) + })) } /// Wrap this expression in an `AS` alias (e.g. `expr AS name`). diff --git a/crates/polyglot-sql/src/generator.rs b/crates/polyglot-sql/src/generator.rs index c30ca806..a6c989f5 100644 --- a/crates/polyglot-sql/src/generator.rs +++ b/crates/polyglot-sql/src/generator.rs @@ -5190,7 +5190,7 @@ impl Generator { }; // Build the outer select: SELECT * EXCLUDE (cols) FROM (inner) - let star = Expression::Star(crate::expressions::Star { + let star = Expression::Star(Box::new(crate::expressions::Star { table: None, except: Some( exclude @@ -5206,7 +5206,7 @@ impl Generator { rename: None, trailing_comments: Vec::new(), span: None, - }); + })); let outer_select = Select { vertica: None, @@ -7661,14 +7661,14 @@ impl Generator { let (limit, fetch) = split_set_limit(limit); let mut outer_select = Select { vertica: None, - expressions: vec![Expression::Star(Star { + expressions: vec![Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - })], + }))], from: Some(From { expressions: vec![Expression::Subquery(Box::new(subquery))], }), diff --git a/crates/polyglot-sql/src/lineage.rs b/crates/polyglot-sql/src/lineage.rs index d8c575fd..b5a14378 100644 --- a/crates/polyglot-sql/src/lineage.rs +++ b/crates/polyglot-sql/src/lineage.rs @@ -1614,14 +1614,14 @@ fn to_node_inner( let mut child = LineageNode::new( format!("{}.*", name), - Expression::Star(crate::expressions::Star { + Expression::Star(Box::new(crate::expressions::Star { table: star.table.clone(), except: None, replace: None, rename: None, trailing_comments: vec![], span: None, - }), + })), source_info.expression.as_ref().clone(), ); apply_source_info_context(&mut child, name, source_info); diff --git a/crates/polyglot-sql/src/optimizer/isolate_table_selects.rs b/crates/polyglot-sql/src/optimizer/isolate_table_selects.rs index 661314f4..96adaebc 100644 --- a/crates/polyglot-sql/src/optimizer/isolate_table_selects.rs +++ b/crates/polyglot-sql/src/optimizer/isolate_table_selects.rs @@ -181,14 +181,14 @@ fn maybe_wrap_table(expression: Expression, schema: Option<&dyn Schema>) -> Expr fn wrap_table_in_subquery(table: TableRef, alias_name: &str) -> Expression { // Build: SELECT * FROM let inner_select = Select::new() - .column(Expression::Star(Star { + .column(Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - })) + }))) .from(Expression::Table(Box::new(table))); // Wrap the SELECT in a Subquery with the original alias diff --git a/crates/polyglot-sql/src/optimizer/qualify_tables.rs b/crates/polyglot-sql/src/optimizer/qualify_tables.rs index 6997a430..439ca187 100644 --- a/crates/polyglot-sql/src/optimizer/qualify_tables.rs +++ b/crates/polyglot-sql/src/optimizer/qualify_tables.rs @@ -572,28 +572,31 @@ fn is_passthrough_star_select(select: &Select) -> bool { } fn is_plain_star(expression: &Expression) -> bool { + let Expression::Star(star) = expression else { + return false; + }; matches!( - expression, - Expression::Star(Star { + **star, + Star { table: None, except: None, replace: None, rename: None, - trailing_comments, + ref trailing_comments, span: None, - }) if trailing_comments.is_empty() + } if trailing_comments.is_empty() ) } fn plain_star_expression() -> Expression { - Expression::Star(Star { + Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - }) + })) } fn is_plain_unaliased_subquery(subquery: &Subquery) -> bool { diff --git a/crates/polyglot-sql/src/parser.rs b/crates/polyglot-sql/src/parser.rs index 0537be7c..bb24b425 100644 --- a/crates/polyglot-sql/src/parser.rs +++ b/crates/polyglot-sql/src/parser.rs @@ -725,6 +725,38 @@ impl Parser { } } + /// Create a parser from the tokens of `sql` that the caller keeps, without copying their + /// text: each token whose text is its own span of `sql` reads it from one shared copy. + /// + /// The parser has no source attached, so it builds the same AST as [`Parser::with_config`]. + pub fn with_config_from_tokens(sql: &str, tokens: &[Token], config: ParserConfig) -> Self { + let end = tokens + .iter() + .position(|token| token.token_type == TokenType::Eof) + .unwrap_or(tokens.len()); + let eof_followed_by = tokens + .get(end + 1) + .map(|token| (token.token_type, token.span)); + let source: Arc = Arc::from(sql); + let tokens = tokens[..end] + .iter() + .map(|token| ParserToken::from_kept(token, &source)) + .collect(); + Self { + tokens, + eof_followed_by, + current: 0, + config, + source: None, + pending_leading_comments: Vec::new(), + guards_checked: false, + token_guard_stats: None, + if_expr_ruled_out: HashSet::new(), + parsing_duckdb_json_path: false, + recursion: Arc::default(), + } + } + /// Create a parser with source SQL attached. /// /// The original SQL text is stored so that `Command` expressions (unparsed @@ -3663,7 +3695,7 @@ impl Parser { let star_trailing_comments = self.previous_trailing_comments().to_vec(); let star = self.parse_star_modifiers_with_comments(None, star_trailing_comments)?; - Expression::Star(star) + Expression::Star(Box::new(star)) }; // ClickHouse: * APPLY(func) or * APPLY func or * APPLY(x -> expr) column transformer if matches!( @@ -4251,14 +4283,14 @@ impl Parser { self.parse_select_expressions()? } else { // No explicit SELECT means SELECT * - vec![Expression::Star(crate::expressions::Star { + vec![Expression::Star(Box::new(crate::expressions::Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - })] + }))] }; // Parse PREWHERE clause (ClickHouse specific) @@ -34432,7 +34464,7 @@ impl Parser { if self.match_token(TokenType::Dot) { if self.match_token(TokenType::Star) { let star = self.parse_star_modifiers(Some(ident))?; - let mut star_expr = Expression::Star(star); + let mut star_expr = Expression::Star(Box::new(star)); if matches!( self.config.dialect, Some(crate::dialects::DialectType::ClickHouse) @@ -34978,14 +35010,14 @@ impl Parser { if let Expression::Count(ref count_expr) = func { if count_expr.distinct { let this_expr = count_expr.this.clone().unwrap_or_else(|| { - Expression::Star(crate::expressions::Star { + Expression::Star(Box::new(crate::expressions::Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - }) + })) }); return Ok(Expression::ApproxDistinct(Box::new( crate::expressions::AggFunc { @@ -35199,14 +35231,14 @@ impl Parser { // Create an ILike expression with Star as left side Expression::ILike(Box::new(LikeOp { negated: false, - left: Expression::Star(Star { + left: Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - }), + })), right: pattern, escape: None, quantifier: None, @@ -35215,7 +35247,7 @@ impl Parser { } else { // {*} or {* EXCLUDE ...} let star = self.parse_star_modifiers(None)?; - Expression::Star(star) + Expression::Star(Box::new(star)) } } else { // {tbl.*} - table qualified wildcard @@ -35223,7 +35255,7 @@ impl Parser { self.expect(TokenType::Dot)?; self.expect(TokenType::Star)?; let star = self.parse_star_modifiers(Some(table_name))?; - Expression::Star(star) + Expression::Star(Box::new(star)) }; self.expect(TokenType::RBrace)?; @@ -35995,14 +36027,14 @@ impl Parser { // Parse the argument: can be *, a regex string, or an array of column names let arg = if self.check(TokenType::Star) { self.skip(); // consume * - Expression::Star(Star { + Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - }) + })) } else if let Some(lambda) = self.parse_function_lambda()? { lambda } else { @@ -36028,7 +36060,7 @@ impl Parser { // Regular star self.skip(); // consume * let star = self.parse_star_modifiers(None)?; - return Ok(Expression::Star(star)); + return Ok(Expression::Star(Box::new(star))); } // Generic type expressions: ARRAY, MAP, STRUCT<...> @@ -36182,7 +36214,7 @@ impl Parser { if self.match_token(TokenType::Star) { // case.* or top.* let star = self.parse_star_modifiers(Some(ident))?; - return Ok(Expression::Star(star)); + return Ok(Expression::Star(Box::new(star))); } // case.column or top.column let col_ident = self.expect_identifier_or_keyword_with_quoted()?; @@ -36661,7 +36693,7 @@ impl Parser { // keyword.* with potential modifiers let ident = Identifier::new(name).with_span(token.span); let star = self.parse_star_modifiers(Some(ident))?; - return Ok(Expression::Star(star)); + return Ok(Expression::Star(Box::new(star))); } // ClickHouse: json.^path — the ^ prefix means "get all nested subcolumns" if matches!( @@ -37045,7 +37077,7 @@ impl Parser { self.expect(TokenType::Dot)?; if self.match_token(TokenType::Star) { let star = self.parse_star_modifiers(Some(ident))?; - return Ok(Expression::Star(star)); + return Ok(Expression::Star(Box::new(star))); } let col_ident = self.expect_identifier_or_keyword_with_quoted()?; let trailing_comments = self.previous_trailing_comments().to_vec(); @@ -39420,14 +39452,14 @@ impl Parser { crate::function_registry::ParserDispatchBehavior::HllAggregate => { let distinct = self.match_token(TokenType::Distinct); let args = if self.match_token(TokenType::Star) { - vec![Expression::Star(Star { + vec![Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - })] + }))] } else if self.check(TokenType::RParen) { Vec::new() } else { @@ -41618,7 +41650,7 @@ impl Parser { // e.g., COLUMNS(* EXCLUDE (empid, dept)) self.skip(); // consume * let star = self.parse_star_modifiers(None)?; - let mut args = vec![Expression::Star(star)]; + let mut args = vec![Expression::Star(Box::new(star))]; // ClickHouse: func(*, col1, col2) — star followed by more args if self.match_token(TokenType::Comma) { let rest = self.parse_function_arguments_for(Some(name))?; @@ -42954,7 +42986,7 @@ impl Parser { } if table_name.is_some() { let star = self.parse_star_modifiers(table_name)?; - expr = Expression::Star(star); + expr = Expression::Star(Box::new(star)); // ClickHouse: a.* APPLY(func) EXCEPT(col) REPLACE(expr AS col) in any order if matches!( self.config.dialect, @@ -53036,7 +53068,7 @@ impl Parser { table.span = result.as_ref().and_then(Self::reference_span); } let star = self.parse_star_modifiers(table_name)?; - result = Some(Expression::Star(star)); + result = Some(Expression::Star(Box::new(star))); break; } // Parse the field identifier - use is_identifier_or_keyword_token to allow keywords @@ -58089,14 +58121,14 @@ impl Parser { // Spark/Databricks: INSERT * (insert all columns) if self.match_token(TokenType::Star) { - elements.push(Expression::Star(crate::expressions::Star { + elements.push(Expression::Star(Box::new(crate::expressions::Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - })); + }))); } else // Parse column list (optional) if self.match_token(TokenType::LParen) { @@ -58178,25 +58210,25 @@ impl Parser { // Spark/Databricks: UPDATE * (update all columns) if self.match_token(TokenType::Star) { - elements.push(Expression::Star(crate::expressions::Star { + elements.push(Expression::Star(Box::new(crate::expressions::Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - })); + }))); } else if self.match_token(TokenType::Set) { // Spark/Databricks: UPDATE SET * (update all columns) if self.match_token(TokenType::Star) { - elements.push(Expression::Star(crate::expressions::Star { + elements.push(Expression::Star(Box::new(crate::expressions::Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - })); + }))); } else { // Parse col = value assignments manually let mut assignments: Vec = Vec::new(); @@ -60277,14 +60309,14 @@ impl Parser { match expressions { Some(expr) => Ok(Some(expr)), - None => Ok(Some(Expression::Star(Star { + None => Ok(Some(Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - }))), + })))), } } @@ -63305,14 +63337,14 @@ impl Parser { // Parse optional RENAME columns let rename = self.parse_star_rename()?; - Ok(Some(Expression::Star(Star { + Ok(Some(Expression::Star(Box::new(Star { table: None, except, replace, rename, trailing_comments: Vec::new(), span: None, - }))) + })))) } /// try_parse_identifier - Try to parse an identifier, returning None if not found @@ -63344,14 +63376,14 @@ impl Parser { let pattern = self.parse_bitwise_or()?; let except = self.parse_star_except()?; - let left = Expression::Star(Star { + let left = Expression::Star(Box::new(Star { table: None, except, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - }); + })); let like = LikeOp { negated: false, left, @@ -63573,14 +63605,14 @@ impl Parser { let _rename_exprs = self.parse_star_op(&["RENAME"])?; let rename: Option> = None; // Complex to extract from expressions - Ok(Some(Expression::Star(Star { + Ok(Some(Expression::Star(Box::new(Star { table: None, except, replace, rename, trailing_comments: Vec::new(), span: None, - }))) + })))) } /// parse_stored - Implemented from Python _parse_stored @@ -66332,14 +66364,14 @@ impl Parser { } else if let Some(from_table) = from_expr { // Create a SELECT * FROM
let mut select_struct = Select::new(); - select_struct.expressions = vec![Expression::Star(Star { + select_struct.expressions = vec![Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - })]; + }))]; select_struct.from = Some(From { expressions: vec![from_table], }); diff --git a/crates/polyglot-sql/src/tokens.rs b/crates/polyglot-sql/src/tokens.rs index 6715791d..2e059802 100644 --- a/crates/polyglot-sql/src/tokens.rs +++ b/crates/polyglot-sql/src/tokens.rs @@ -185,6 +185,29 @@ impl From for ParserToken { } } +impl ParserToken { + /// `token` for the parser, reading its text from `source` when it is the token's span. + pub(crate) fn from_kept(token: &Token, source: &Arc) -> Self { + let (start, end) = (token.span.start, token.span.end); + let text = if source.get(start..end) == Some(token.text.as_str()) { + ParserTokenText::Source { + source: Arc::clone(source), + start, + end, + } + } else { + ParserTokenText::Owned(token.text.clone()) + }; + Self { + token_type: token.token_type, + span: token.span, + comments: token.comments.clone(), + trailing_comments: token.trailing_comments.clone(), + text, + } + } +} + trait TokenOutput: Sized { fn from_source( token_type: TokenType, @@ -1742,9 +1765,53 @@ struct TokenizerState<'a, C, T> { comments: Vec, guard_stats: Option, config: &'a TokenizerConfig, + ascii: AsciiLookups, +} + +/// The single-character token and identifier-quote maps indexed by ASCII byte, so the +/// per-token checks skip hashing. +struct AsciiLookups { + single_tokens: [Option; 128], + identifier_ends: [Option; 128], +} + +impl AsciiLookups { + fn new(config: &TokenizerConfig) -> Self { + let mut lookups = Self { + single_tokens: [None; 128], + identifier_ends: [None; 128], + }; + for (&character, &token_type) in &config.single_tokens { + if character.is_ascii() { + lookups.single_tokens[character as usize] = Some(token_type); + } + } + for (&start, &end) in &config.identifiers { + if start.is_ascii() { + lookups.identifier_ends[start as usize] = Some(end); + } + } + lookups + } } impl<'a, C: TokenizerCursor, T: TokenOutput> TokenizerState<'a, C, T> { + fn single_token(&self, character: char) -> Option { + if character.is_ascii() { + self.ascii.single_tokens[character as usize] + } else { + self.config.single_tokens.get(&character).copied() + } + } + + fn identifier_end(&self, character: char) -> Option { + if character.is_ascii() { + self.ascii.identifier_ends[character as usize] + } else { + self.config.identifiers.get(&character).copied() + } + } + fn new(sql: &'a str, config: &'a TokenizerConfig, cursor: C) -> Self { let size = cursor.len(); Self { @@ -1760,6 +1827,7 @@ impl<'a, C: TokenizerCursor, T: TokenOutput> TokenizerState<'a, C, T> { comments: Vec::new(), guard_stats: None, config, + ascii: AsciiLookups::new(config), } } @@ -1778,6 +1846,7 @@ impl<'a, C: TokenizerCursor, T: TokenOutput> TokenizerState<'a, C, T> { comments: Vec::new(), guard_stats: Some(TokenGuardStats::default()), config, + ascii: AsciiLookups::new(config), } } @@ -2326,15 +2395,12 @@ impl<'a, C: TokenizerCursor, T: TokenOutput> TokenizerState<'a, C, T> { // Check for double-quoted strings when dialect supports them (e.g., BigQuery) // This must come before identifier quotes check - if c == '"' - && self.config.quotes.contains_key("\"") - && !self.config.identifiers.contains_key(&'"') - { + if c == '"' && self.config.quotes.contains_key("\"") && self.identifier_end('"').is_none() { return self.scan_double_quoted_string(); } // Check for identifier quotes - if let Some(&end_quote) = self.config.identifiers.get(&c) { + if let Some(end_quote) = self.identifier_end(c) { return self.scan_quoted_identifier(end_quote); } @@ -2424,7 +2490,7 @@ impl<'a, C: TokenizerCursor, T: TokenOutput> TokenizerState<'a, C, T> { } // Check for single character tokens - if let Some(&token_type) = self.config.single_tokens.get(&c) { + if let Some(token_type) = self.single_token(c) { self.advance(); self.add_token(token_type); return Ok(()); diff --git a/crates/polyglot-sql/src/transforms.rs b/crates/polyglot-sql/src/transforms.rs index d1d4e797..fa2a2543 100644 --- a/crates/polyglot-sql/src/transforms.rs +++ b/crates/polyglot-sql/src/transforms.rs @@ -6046,14 +6046,14 @@ pub fn no_limit_order_by_union(expr: Expression) -> Result { // Build SELECT * FROM (UNION) AS _l_0 ORDER BY ... LIMIT ... let mut select = Select::default(); - select.expressions = vec![Expression::Star(Star { + select.expressions = vec![Expression::Star(Box::new(Star { table: None, except: None, replace: None, rename: None, trailing_comments: Vec::new(), span: None, - })]; + }))]; select.from = Some(From { expressions: vec![Expression::Subquery(Box::new(subquery))], });