From 1362e4642c3c0cfd17fcb7ed2db3c7245f46165e Mon Sep 17 00:00:00 2001 From: Nikita-str Date: Tue, 12 Mar 2024 01:41:57 +0300 Subject: [PATCH 1/6] Use additional call to box values(to reserve less stack mem) --- src/parser/mod.rs | 72 ++++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 145e19007..f3323ee83 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -470,7 +470,7 @@ impl<'a> Parser<'a> { Keyword::ANALYZE => Ok(self.parse_analyze()?), Keyword::SELECT | Keyword::WITH | Keyword::VALUES => { self.prev_token(); - Ok(Statement::Query(Box::new(self.parse_query()?))) + Ok(Statement::Query(self.parse_boxed_query()?)) } Keyword::TRUNCATE => Ok(self.parse_truncate()?), Keyword::ATTACH => Ok(self.parse_attach_database()?), @@ -530,7 +530,7 @@ impl<'a> Parser<'a> { }, Token::LParen => { self.prev_token(); - Ok(Statement::Query(Box::new(self.parse_query()?))) + Ok(Statement::Query(self.parse_boxed_query()?)) } _ => self.expected("an SQL statement", next_token), } @@ -1084,7 +1084,7 @@ impl<'a> Parser<'a> { let expr = if self.parse_keyword(Keyword::SELECT) || self.parse_keyword(Keyword::WITH) { self.prev_token(); - Expr::Subquery(Box::new(self.parse_query()?)) + Expr::Subquery(self.parse_boxed_query()?) } else { let exprs = self.parse_comma_separated(Parser::parse_expr)?; match exprs.len() { @@ -1461,7 +1461,7 @@ impl<'a> Parser<'a> { self.expect_token(&Token::LParen)?; let exists_node = Expr::Exists { negated, - subquery: Box::new(self.parse_query()?), + subquery: self.parse_boxed_query()?, }; self.expect_token(&Token::RParen)?; Ok(exists_node) @@ -1670,9 +1670,9 @@ impl<'a> Parser<'a> { // Parses an array constructed from a subquery pub fn parse_array_subquery(&mut self) -> Result { - let query = self.parse_query()?; + let query = self.parse_boxed_query()?; self.expect_token(&Token::RParen)?; - Ok(Expr::ArraySubquery(Box::new(query))) + Ok(Expr::ArraySubquery(query)) } /// Parse a SQL LISTAGG expression, e.g. `LISTAGG(...) WITHIN GROUP (ORDER BY ...)`. @@ -2531,7 +2531,7 @@ impl<'a> Parser<'a> { self.prev_token(); Expr::InSubquery { expr: Box::new(expr), - subquery: Box::new(self.parse_query()?), + subquery: self.parse_boxed_query()?, negated, } } else { @@ -3574,7 +3574,7 @@ impl<'a> Parser<'a> { } self.expect_keyword(Keyword::AS)?; - let query = Box::new(self.parse_query()?); + let query = self.parse_boxed_query()?; // Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here. let with_no_schema_binding = dialect_of!(self is RedshiftSqlDialect | GenericDialect) @@ -3969,7 +3969,7 @@ impl<'a> Parser<'a> { self.expect_keyword(Keyword::FOR)?; - let query = Some(Box::new(self.parse_query()?)); + let query = Some(self.parse_boxed_query()?); Ok(Statement::Declare { stmts: vec![Declare { @@ -4063,7 +4063,7 @@ impl<'a> Parser<'a> { match self.peek_token().token { Token::Word(w) if w.keyword == Keyword::SELECT => ( Some(DeclareType::Cursor), - Some(Box::new(self.parse_query()?)), + Some(self.parse_boxed_query()?), None, None, ), @@ -4587,7 +4587,7 @@ impl<'a> Parser<'a> { // Parse optional `AS ( query )` let query = if self.parse_keyword(Keyword::AS) { - Some(Box::new(self.parse_query()?)) + Some(self.parse_boxed_query()?) } else { None }; @@ -5583,7 +5583,7 @@ impl<'a> Parser<'a> { let with_options = self.parse_options(Keyword::WITH)?; self.expect_keyword(Keyword::AS)?; - let query = Box::new(self.parse_query()?); + let query = self.parse_boxed_query()?; Ok(Statement::AlterView { name, @@ -5623,7 +5623,7 @@ impl<'a> Parser<'a> { pub fn parse_copy(&mut self) -> Result { let source; if self.consume_token(&Token::LParen) { - source = CopySource::Query(Box::new(self.parse_query()?)); + source = CopySource::Query(self.parse_boxed_query()?); self.expect_token(&Token::RParen)?; } else { let table_name = self.parse_object_name(false)?; @@ -6846,10 +6846,20 @@ impl<'a> Parser<'a> { } } + /// Do the same as `parse_query` but return `Box`'ed `Ok(..)` result + /// and therefore caller fn reserves only `poiter size` memory on stack + /// instead of `sizeof(Query)` (if the call isn't optimized by compiler) + pub fn parse_boxed_query(&mut self) -> Result, ParserError> { + self.parse_query().map(Box::new) + } + /// Parse a query expression, i.e. a `SELECT` statement optionally /// preceded with some `WITH` CTE declarations and optionally followed /// by `ORDER BY`. Unlike some other parse_... methods, this one doesn't /// expect the initial keyword to be already consumed + /// + /// If you need `Box` then maybe there is sense to use `parse_boxed_query` + /// due to prevent stack overflow in debug building(to reserve less memory on stack). pub fn parse_query(&mut self) -> Result { let _guard = self.recursion_counter.try_decrease()?; let with = if self.parse_keyword(Keyword::WITH) { @@ -6889,7 +6899,7 @@ impl<'a> Parser<'a> { for_clause: None, }) } else { - let body = Box::new(self.parse_query_body(0)?); + let body = self.parse_boxed_query_body(0)?; let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) { self.parse_comma_separated(Parser::parse_order_by_expr)? @@ -7079,7 +7089,7 @@ impl<'a> Parser<'a> { } } self.expect_token(&Token::LParen)?; - let query = Box::new(self.parse_query()?); + let query = self.parse_boxed_query()?; self.expect_token(&Token::RParen)?; let alias = TableAlias { name, @@ -7103,7 +7113,7 @@ impl<'a> Parser<'a> { } } self.expect_token(&Token::LParen)?; - let query = Box::new(self.parse_query()?); + let query = self.parse_boxed_query()?; self.expect_token(&Token::RParen)?; let alias = TableAlias { name, columns }; Cte { @@ -7119,6 +7129,13 @@ impl<'a> Parser<'a> { Ok(cte) } + /// Do the same as `parse_query_body` but return `Box`'ed `Ok(..)` result + /// and therefore caller fn reserves only `poiter size` memory on stack + /// instead of `sizeof(SetExpr)` (if the call isn't optimized by compiler) + pub fn parse_boxed_query_body(&mut self, precedence: u8) -> Result, ParserError> { + self.parse_query_body(precedence).map(Box::new) + } + /// Parse a "query body", which is an expression with roughly the /// following grammar: /// ```sql @@ -7127,6 +7144,9 @@ impl<'a> Parser<'a> { /// subquery ::= query_body [ order_by_limit ] /// set_operation ::= query_body { 'UNION' | 'EXCEPT' | 'INTERSECT' } [ 'ALL' ] query_body /// ``` + /// + /// If you need `Box` then maybe there is sense to use `parse_boxed_query_body` + /// due to prevent stack overflow in debug building(to reserve less memory on stack). pub fn parse_query_body(&mut self, precedence: u8) -> Result { // We parse the expression using a Pratt parser, as in `parse_expr()`. // Start by parsing a restricted SELECT or a `(subquery)`: @@ -7134,9 +7154,9 @@ impl<'a> Parser<'a> { SetExpr::Select(Box::new(self.parse_select()?)) } else if self.consume_token(&Token::LParen) { // CTEs are not allowed here, but the parser currently accepts them - let subquery = self.parse_query()?; + let subquery = self.parse_boxed_query()?; self.expect_token(&Token::RParen)?; - SetExpr::Query(Box::new(subquery)) + SetExpr::Query(subquery) } else if self.parse_keyword(Keyword::VALUES) { let is_mysql = dialect_of!(self is MySqlDialect); SetExpr::Values(self.parse_values(is_mysql)?) @@ -7169,7 +7189,7 @@ impl<'a> Parser<'a> { left: Box::new(expr), op: op.unwrap(), set_quantifier, - right: Box::new(self.parse_query_body(next_precedence)?), + right: self.parse_boxed_query_body(next_precedence)?, }; } @@ -8083,7 +8103,7 @@ impl<'a> Parser<'a> { &mut self, lateral: IsLateral, ) -> Result { - let subquery = Box::new(self.parse_query()?); + let subquery = self.parse_boxed_query()?; self.expect_token(&Token::RParen)?; let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?; Ok(TableFactor::Derived { @@ -8381,7 +8401,7 @@ impl<'a> Parser<'a> { } else { None }; - let source = Box::new(self.parse_query()?); + let source = self.parse_boxed_query()?; Ok(Statement::Directory { local, path, @@ -8414,7 +8434,7 @@ impl<'a> Parser<'a> { // Hive allows you to specify columns after partitions as well if you want. let after_columns = self.parse_parenthesized_column_list(Optional, false)?; - let source = Some(Box::new(self.parse_query()?)); + let source = Some(self.parse_boxed_query()?); (columns, partitioned, after_columns, source) }; @@ -8607,11 +8627,11 @@ impl<'a> Parser<'a> { .is_some() { self.prev_token(); - let subquery = self.parse_query()?; + let subquery = self.parse_boxed_query()?; self.expect_token(&Token::RParen)?; return Ok(( vec![FunctionArg::Unnamed(FunctionArgExpr::from(Expr::Subquery( - Box::new(subquery), + subquery, )))], vec![], )); @@ -9125,7 +9145,7 @@ impl<'a> Parser<'a> { pub fn parse_unload(&mut self) -> Result { self.expect_token(&Token::LParen)?; - let query = self.parse_query()?; + let query = self.parse_boxed_query()?; self.expect_token(&Token::RParen)?; self.expect_keyword(Keyword::TO)?; @@ -9134,7 +9154,7 @@ impl<'a> Parser<'a> { let with_options = self.parse_options(Keyword::WITH)?; Ok(Statement::Unload { - query: Box::new(query), + query, to, with: with_options, }) From cfc15256eb192351cd40ece60787a451788f3f1c Mon Sep 17 00:00:00 2001 From: Nikita-str Date: Tue, 12 Mar 2024 03:59:52 +0300 Subject: [PATCH 2/6] Make stack size less by moving out big structs into closure --- src/parser/mod.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f3323ee83..3f7600746 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6872,11 +6872,13 @@ impl<'a> Parser<'a> { }; if self.parse_keyword(Keyword::INSERT) { - let insert = self.parse_insert()?; + let body = self + .parse_insert() + .map(|insert| Box::new(SetExpr::Insert(insert)))?; Ok(Query { with, - body: Box::new(SetExpr::Insert(insert)), + body, limit: None, limit_by: vec![], order_by: vec![], @@ -6886,10 +6888,13 @@ impl<'a> Parser<'a> { for_clause: None, }) } else if self.parse_keyword(Keyword::UPDATE) { - let update = self.parse_update()?; + let body = self + .parse_update() + .map(|update| Box::new(SetExpr::Update(update)))?; + Ok(Query { with, - body: Box::new(SetExpr::Update(update)), + body, limit: None, limit_by: vec![], order_by: vec![], @@ -7151,7 +7156,7 @@ impl<'a> Parser<'a> { // We parse the expression using a Pratt parser, as in `parse_expr()`. // Start by parsing a restricted SELECT or a `(subquery)`: let mut expr = if self.parse_keyword(Keyword::SELECT) { - SetExpr::Select(Box::new(self.parse_select()?)) + SetExpr::Select(self.parse_select().map(Box::new)?) } else if self.consume_token(&Token::LParen) { // CTEs are not allowed here, but the parser currently accepts them let subquery = self.parse_boxed_query()?; From 63baa83c2a4b146ec6d04e355815feb9f87867f4 Mon Sep 17 00:00:00 2001 From: Nikita-str Date: Tue, 12 Mar 2024 04:26:39 +0300 Subject: [PATCH 3/6] moving out use of big structs into case-related fns --- src/parser/mod.rs | 72 +++++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 3f7600746..70110ed8e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6861,6 +6861,45 @@ impl<'a> Parser<'a> { /// If you need `Box` then maybe there is sense to use `parse_boxed_query` /// due to prevent stack overflow in debug building(to reserve less memory on stack). pub fn parse_query(&mut self) -> Result { + mod parse_query { + use super::*; + type ReturnTy = Result; + pub fn insert_case(parser: &mut Parser<'_>, with: Option) -> ReturnTy { + let body = parser + .parse_insert() + .map(|insert| Box::new(SetExpr::Insert(insert)))?; + + Ok(Query { + with, + body, + limit: None, + limit_by: vec![], + order_by: vec![], + offset: None, + fetch: None, + locks: vec![], + for_clause: None, + }) + } + pub fn update_case(parser: &mut Parser<'_>, with: Option) -> ReturnTy { + let body = parser + .parse_update() + .map(|update| Box::new(SetExpr::Update(update)))?; + + Ok(Query { + with, + body, + limit: None, + limit_by: vec![], + order_by: vec![], + offset: None, + fetch: None, + locks: vec![], + for_clause: None, + }) + } + } + let _guard = self.recursion_counter.try_decrease()?; let with = if self.parse_keyword(Keyword::WITH) { Some(With { @@ -6870,39 +6909,10 @@ impl<'a> Parser<'a> { } else { None }; - if self.parse_keyword(Keyword::INSERT) { - let body = self - .parse_insert() - .map(|insert| Box::new(SetExpr::Insert(insert)))?; - - Ok(Query { - with, - body, - limit: None, - limit_by: vec![], - order_by: vec![], - offset: None, - fetch: None, - locks: vec![], - for_clause: None, - }) + parse_query::insert_case(self, with) } else if self.parse_keyword(Keyword::UPDATE) { - let body = self - .parse_update() - .map(|update| Box::new(SetExpr::Update(update)))?; - - Ok(Query { - with, - body, - limit: None, - limit_by: vec![], - order_by: vec![], - offset: None, - fetch: None, - locks: vec![], - for_clause: None, - }) + parse_query::update_case(self, with) } else { let body = self.parse_boxed_query_body(0)?; From f9ab24a6ca24517afce1c2bfc05da489599c2004 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Sun, 7 Apr 2024 07:21:33 -0400 Subject: [PATCH 4/6] Make functions non pub --- src/parser/mod.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 70110ed8e..becbb261f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6846,10 +6846,12 @@ impl<'a> Parser<'a> { } } - /// Do the same as `parse_query` but return `Box`'ed `Ok(..)` result - /// and therefore caller fn reserves only `poiter size` memory on stack - /// instead of `sizeof(Query)` (if the call isn't optimized by compiler) - pub fn parse_boxed_query(&mut self) -> Result, ParserError> { + /// Call's [`parse_query`] returning a `Box`'ed result. + /// + /// This function can be used to reduce the stack size required in debug + /// builds. Instead of `sizeof(Query)` only a pointer (`Box`) + /// is used. + fn parse_boxed_query(&mut self) -> Result, ParserError> { self.parse_query().map(Box::new) } @@ -6857,9 +6859,6 @@ impl<'a> Parser<'a> { /// preceded with some `WITH` CTE declarations and optionally followed /// by `ORDER BY`. Unlike some other parse_... methods, this one doesn't /// expect the initial keyword to be already consumed - /// - /// If you need `Box` then maybe there is sense to use `parse_boxed_query` - /// due to prevent stack overflow in debug building(to reserve less memory on stack). pub fn parse_query(&mut self) -> Result { mod parse_query { use super::*; @@ -7144,10 +7143,12 @@ impl<'a> Parser<'a> { Ok(cte) } - /// Do the same as `parse_query_body` but return `Box`'ed `Ok(..)` result - /// and therefore caller fn reserves only `poiter size` memory on stack - /// instead of `sizeof(SetExpr)` (if the call isn't optimized by compiler) - pub fn parse_boxed_query_body(&mut self, precedence: u8) -> Result, ParserError> { + /// Call's [`parse_query_body`] returning a `Box`'ed result. + /// + /// This function can be used to reduce the stack size required in debug + /// builds. Instead of `sizeof(QueryBody)` only a pointer (`Box`) + /// is used. + fn parse_boxed_query_body(&mut self, precedence: u8) -> Result, ParserError> { self.parse_query_body(precedence).map(Box::new) } From 8101f472a4a720bf0237a7800bec6a4cc8886e2d Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Sun, 7 Apr 2024 08:07:00 -0400 Subject: [PATCH 5/6] Remove inner module --- src/parser/mod.rs | 77 ++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b92a1da05..0e6a7d308 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6924,45 +6924,6 @@ impl<'a> Parser<'a> { /// by `ORDER BY`. Unlike some other parse_... methods, this one doesn't /// expect the initial keyword to be already consumed pub fn parse_query(&mut self) -> Result { - mod parse_query { - use super::*; - type ReturnTy = Result; - pub fn insert_case(parser: &mut Parser<'_>, with: Option) -> ReturnTy { - let body = parser - .parse_insert() - .map(|insert| Box::new(SetExpr::Insert(insert)))?; - - Ok(Query { - with, - body, - limit: None, - limit_by: vec![], - order_by: vec![], - offset: None, - fetch: None, - locks: vec![], - for_clause: None, - }) - } - pub fn update_case(parser: &mut Parser<'_>, with: Option) -> ReturnTy { - let body = parser - .parse_update() - .map(|update| Box::new(SetExpr::Update(update)))?; - - Ok(Query { - with, - body, - limit: None, - limit_by: vec![], - order_by: vec![], - offset: None, - fetch: None, - locks: vec![], - for_clause: None, - }) - } - } - let _guard = self.recursion_counter.try_decrease()?; let with = if self.parse_keyword(Keyword::WITH) { Some(With { @@ -6973,9 +6934,29 @@ impl<'a> Parser<'a> { None }; if self.parse_keyword(Keyword::INSERT) { - parse_query::insert_case(self, with) + Ok(Query { + with, + body: self.parse_insert_setexpr_boxed()?, + limit: None, + limit_by: vec![], + order_by: vec![], + offset: None, + fetch: None, + locks: vec![], + for_clause: None, + }) } else if self.parse_keyword(Keyword::UPDATE) { - parse_query::update_case(self, with) + Ok(Query { + with, + body: self.parse_update_setexpr_boxed()?, + limit: None, + limit_by: vec![], + order_by: vec![], + offset: None, + fetch: None, + locks: vec![], + for_clause: None, + }) } else { let body = self.parse_boxed_query_body(0)?; @@ -8431,6 +8412,13 @@ impl<'a> Parser<'a> { Ok(insert.clone()) } + /// Parse an INSERT statement, returning a `Box`ed SetExpr + /// + /// This is used to reduce the size of the stack frames in debug builds + fn parse_insert_setexpr_boxed(&mut self) -> Result, ParserError> { + Ok(Box::new(SetExpr::Insert(self.parse_insert()?))) + } + /// Parse an INSERT statement pub fn parse_insert(&mut self) -> Result { let or = if !dialect_of!(self is SQLiteDialect) { @@ -8617,6 +8605,13 @@ impl<'a> Parser<'a> { } } + /// Parse an UPDATE statement, returning a `Box`ed SetExpr + /// + /// This is used to reduce the size of the stack frames in debug builds + fn parse_update_setexpr_boxed(&mut self) -> Result, ParserError> { + Ok(Box::new(SetExpr::Update(self.parse_update()?))) + } + pub fn parse_update(&mut self) -> Result { let table = self.parse_table_and_joins()?; self.expect_keyword(Keyword::SET)?; From 780a0379e227730a25b613eb150dd02765649325 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Sun, 7 Apr 2024 08:09:33 -0400 Subject: [PATCH 6/6] fix docs --- src/parser/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0e6a7d308..07893d98e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6910,7 +6910,7 @@ impl<'a> Parser<'a> { } } - /// Call's [`parse_query`] returning a `Box`'ed result. + /// Call's [`Self::parse_query`] returning a `Box`'ed result. /// /// This function can be used to reduce the stack size required in debug /// builds. Instead of `sizeof(Query)` only a pointer (`Box`) @@ -7188,7 +7188,7 @@ impl<'a> Parser<'a> { Ok(cte) } - /// Call's [`parse_query_body`] returning a `Box`'ed result. + /// Call's [`Self::parse_query_body`] returning a `Box`'ed result. /// /// This function can be used to reduce the stack size required in debug /// builds. Instead of `sizeof(QueryBody)` only a pointer (`Box`)