From 646f88f822103506e58a3d39e5993c638d9e2708 Mon Sep 17 00:00:00 2001 From: Sarah Yurick Date: Tue, 8 Nov 2022 15:42:46 -0800 Subject: [PATCH 01/12] first commit --- src/parser.rs | 180 ++++++++++++++++++++++++++++++++++++++ tests/sqlparser_common.rs | 13 +++ 2 files changed, 193 insertions(+) diff --git a/src/parser.rs b/src/parser.rs index 0753d263e..47d224531 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4180,6 +4180,26 @@ impl<'a> Parser<'a> { SetExpr::Query(Box::new(subquery)) } else if self.parse_keyword(Keyword::VALUES) { SetExpr::Values(self.parse_values()?) + } else if self.parse_keyword(Keyword::TABLE) { + self.next_token(); + match &self.tokens[0] { + Token::Word(w) => { + if w.keyword == Keyword::CREATE { + SetExpr::Select(Box::new(self.parse_as_table()?)) + } else { + return self.expected( // SARAH + "CREATE statement", + Token::SingleQuotedString(w.value.clone()), + ) + } + } + unexpected => { + return self.expected( + "CREATE TABLE ... AS TABLE ...", + unexpected.clone(), + ) + } + } } else { return self.expected( "SELECT, VALUES, or a subquery in the query body", @@ -4378,6 +4398,166 @@ impl<'a> Parser<'a> { }) } + /// Parse `CREATE TABLE x AS TABLE y` + /// the same as `CREATE TABLE x AS SELECT * FROM y` + /// Mostly the same logic as parse_select, + /// except for the "projection" and "from" variables + pub fn parse_as_table(&mut self) -> Result { + let distinct = self.parse_all_or_distinct()?; + + let top = if self.parse_keyword(Keyword::TOP) { + Some(self.parse_top()?) + } else { + None + }; + + let projection = vec![SelectItem::Wildcard]; + + let into = if self.parse_keyword(Keyword::INTO) { + let temporary = self + .parse_one_of_keywords(&[Keyword::TEMP, Keyword::TEMPORARY]) + .is_some(); + let unlogged = self.parse_keyword(Keyword::UNLOGGED); + let table = self.parse_keyword(Keyword::TABLE); + let name = self.parse_object_name()?; + Some(SelectInto { + temporary, + unlogged, + table, + name, + }) + } else { + None + }; + + let mut as_flag = false; + let mut table_flag = false; + let mut table_name = ""; + // Grab the table name after `AS TABLE ...` + for token in self.tokens.iter() { + match token { + Token::Word(w) => { + if w.keyword == Keyword::AS { + as_flag = true; + } else if w.keyword == Keyword::TABLE && as_flag { + table_flag = true; + } else if as_flag && table_flag { + table_name = &w.value; + as_flag = false; + table_flag = false; + } else if w.keyword != Keyword::TABLE && as_flag { + as_flag = false; + } + } + _ => () + } + } + + let from = vec![ + TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident { + value: table_name.to_string(), + quote_style: None, + }]), + alias: None, + args: None, + with_hints: vec![], + }, + joins: vec![], + } + ]; + + let mut lateral_views = vec![]; + loop { + if self.parse_keywords(&[Keyword::LATERAL, Keyword::VIEW]) { + let outer = self.parse_keyword(Keyword::OUTER); + let lateral_view = self.parse_expr()?; + let lateral_view_name = self.parse_object_name()?; + let lateral_col_alias = self + .parse_comma_separated(|parser| { + parser.parse_optional_alias(&[ + Keyword::WHERE, + Keyword::GROUP, + Keyword::CLUSTER, + Keyword::HAVING, + Keyword::LATERAL, + ]) + })? + .into_iter() + .flatten() + .collect(); + + lateral_views.push(LateralView { + lateral_view, + lateral_view_name, + lateral_col_alias, + outer, + }); + } else { + break; + } + } + + let selection = if self.parse_keyword(Keyword::WHERE) { + Some(self.parse_expr()?) + } else { + None + }; + + let group_by = if self.parse_keywords(&[Keyword::GROUP, Keyword::BY]) { + self.parse_comma_separated(Parser::parse_group_by_expr)? + } else { + vec![] + }; + + let cluster_by = if self.parse_keywords(&[Keyword::CLUSTER, Keyword::BY]) { + self.parse_comma_separated(Parser::parse_expr)? + } else { + vec![] + }; + + let distribute_by = if self.parse_keywords(&[Keyword::DISTRIBUTE, Keyword::BY]) { + self.parse_comma_separated(Parser::parse_expr)? + } else { + vec![] + }; + + let sort_by = if self.parse_keywords(&[Keyword::SORT, Keyword::BY]) { + self.parse_comma_separated(Parser::parse_expr)? + } else { + vec![] + }; + + let having = if self.parse_keyword(Keyword::HAVING) { + Some(self.parse_expr()?) + } else { + None + }; + + let qualify = if self.parse_keyword(Keyword::QUALIFY) { + Some(self.parse_expr()?) + } else { + None + }; + + Ok(Select { + distinct, + top, + projection, + into, + from, + lateral_views, + selection, + group_by, + cluster_by, + distribute_by, + sort_by, + having, + qualify, + }) + } + pub fn parse_set(&mut self) -> Result { let modifier = self.parse_one_of_keywords(&[Keyword::SESSION, Keyword::LOCAL, Keyword::HIVEVAR]); diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 4efb8cc7c..f3bf3cd2e 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2153,6 +2153,19 @@ fn parse_create_table_as() { } } +#[test] +fn parse_create_table_as_table() { + let sql = "CREATE TABLE new_table AS TABLE old_table"; + + match verified_stmt(sql) { + Statement::CreateTable { name, query, .. } => { + assert_eq!(name.to_string(), "new_table".to_string()); + assert_eq!(query, Some(Box::new(verified_query("SELECT * FROM old_table")))); + } + _ => unreachable!(), + } +} + #[test] fn parse_create_table_on_cluster() { // Using single-quote literal to define current cluster From 9e5d05e382e50e22c1f875dfa15d40106872aeb0 Mon Sep 17 00:00:00 2001 From: Sarah Yurick Date: Tue, 8 Nov 2022 16:03:36 -0800 Subject: [PATCH 02/12] fix style and edit test --- src/parser.rs | 60 +++++++++++++++++---------------------- tests/sqlparser_common.rs | 7 ++++- 2 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 47d224531..e4d9bfa62 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4187,17 +4187,14 @@ impl<'a> Parser<'a> { if w.keyword == Keyword::CREATE { SetExpr::Select(Box::new(self.parse_as_table()?)) } else { - return self.expected( // SARAH + return self.expected( "CREATE statement", Token::SingleQuotedString(w.value.clone()), - ) + ); } } unexpected => { - return self.expected( - "CREATE TABLE ... AS TABLE ...", - unexpected.clone(), - ) + return self.expected("CREATE TABLE ... AS TABLE ...", unexpected.clone()) } } } else { @@ -4435,38 +4432,33 @@ impl<'a> Parser<'a> { let mut table_name = ""; // Grab the table name after `AS TABLE ...` for token in self.tokens.iter() { - match token { - Token::Word(w) => { - if w.keyword == Keyword::AS { - as_flag = true; - } else if w.keyword == Keyword::TABLE && as_flag { - table_flag = true; - } else if as_flag && table_flag { - table_name = &w.value; - as_flag = false; - table_flag = false; - } else if w.keyword != Keyword::TABLE && as_flag { - as_flag = false; - } + if let Token::Word(w) = token { + if w.keyword == Keyword::AS { + as_flag = true; + } else if w.keyword == Keyword::TABLE && as_flag { + table_flag = true; + } else if as_flag && table_flag { + table_name = &w.value; + as_flag = false; + table_flag = false; + } else if w.keyword != Keyword::TABLE && as_flag { + as_flag = false; } - _ => () } } - let from = vec![ - TableWithJoins { - relation: TableFactor::Table { - name: ObjectName(vec![Ident { - value: table_name.to_string(), - quote_style: None, - }]), - alias: None, - args: None, - with_hints: vec![], - }, - joins: vec![], - } - ]; + let from = vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident { + value: table_name.to_string(), + quote_style: None, + }]), + alias: None, + args: None, + with_hints: vec![], + }, + joins: vec![], + }]; let mut lateral_views = vec![]; loop { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index f3bf3cd2e..1ae5d00ed 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2160,10 +2160,15 @@ fn parse_create_table_as_table() { match verified_stmt(sql) { Statement::CreateTable { name, query, .. } => { assert_eq!(name.to_string(), "new_table".to_string()); - assert_eq!(query, Some(Box::new(verified_query("SELECT * FROM old_table")))); + assert_eq!(query, Some(Box::new(verified_query("TABLE old_table")))); } _ => unreachable!(), } + + let actual_ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); + let sql2 = "CREATE TABLE new_table AS SELECT * FROM old_table"; + let expected_ast = Parser::parse_sql(&GenericDialect {}, sql2).unwrap(); + assert_eq!(actual_ast, expected_ast); } #[test] From 02421a5744959f4a938146f54e5db19f796bfeac Mon Sep 17 00:00:00 2001 From: Sarah Yurick Date: Tue, 8 Nov 2022 16:26:22 -0800 Subject: [PATCH 03/12] fix test? --- tests/sqlparser_common.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 1ae5d00ed..35a4dec3e 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2155,19 +2155,12 @@ fn parse_create_table_as() { #[test] fn parse_create_table_as_table() { - let sql = "CREATE TABLE new_table AS TABLE old_table"; + let sql1 = "CREATE TABLE new_table AS TABLE old_table"; + let actual_ast = Parser::parse_sql(&GenericDialect {}, sql1).unwrap(); - match verified_stmt(sql) { - Statement::CreateTable { name, query, .. } => { - assert_eq!(name.to_string(), "new_table".to_string()); - assert_eq!(query, Some(Box::new(verified_query("TABLE old_table")))); - } - _ => unreachable!(), - } - - let actual_ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); let sql2 = "CREATE TABLE new_table AS SELECT * FROM old_table"; let expected_ast = Parser::parse_sql(&GenericDialect {}, sql2).unwrap(); + assert_eq!(actual_ast, expected_ast); } From 5e066d853b8ec4b04f19ef70dcafac01e7865ae2 Mon Sep 17 00:00:00 2001 From: Sarah Yurick Date: Wed, 9 Nov 2022 11:21:40 -0800 Subject: [PATCH 04/12] remove unnecessary logic --- src/parser.rs | 109 ++++++-------------------------------------------- 1 file changed, 12 insertions(+), 97 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index e4d9bfa62..ac01fdf7b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4397,35 +4397,14 @@ impl<'a> Parser<'a> { /// Parse `CREATE TABLE x AS TABLE y` /// the same as `CREATE TABLE x AS SELECT * FROM y` - /// Mostly the same logic as parse_select, - /// except for the "projection" and "from" variables + /// Note logic for the "projection" and "from" variables pub fn parse_as_table(&mut self) -> Result { - let distinct = self.parse_all_or_distinct()?; - - let top = if self.parse_keyword(Keyword::TOP) { - Some(self.parse_top()?) - } else { - None - }; + let distinct = false; + let top = None; let projection = vec![SelectItem::Wildcard]; - let into = if self.parse_keyword(Keyword::INTO) { - let temporary = self - .parse_one_of_keywords(&[Keyword::TEMP, Keyword::TEMPORARY]) - .is_some(); - let unlogged = self.parse_keyword(Keyword::UNLOGGED); - let table = self.parse_keyword(Keyword::TABLE); - let name = self.parse_object_name()?; - Some(SelectInto { - temporary, - unlogged, - table, - name, - }) - } else { - None - }; + let into = None; let mut as_flag = false; let mut table_flag = false; @@ -4460,78 +4439,14 @@ impl<'a> Parser<'a> { joins: vec![], }]; - let mut lateral_views = vec![]; - loop { - if self.parse_keywords(&[Keyword::LATERAL, Keyword::VIEW]) { - let outer = self.parse_keyword(Keyword::OUTER); - let lateral_view = self.parse_expr()?; - let lateral_view_name = self.parse_object_name()?; - let lateral_col_alias = self - .parse_comma_separated(|parser| { - parser.parse_optional_alias(&[ - Keyword::WHERE, - Keyword::GROUP, - Keyword::CLUSTER, - Keyword::HAVING, - Keyword::LATERAL, - ]) - })? - .into_iter() - .flatten() - .collect(); - - lateral_views.push(LateralView { - lateral_view, - lateral_view_name, - lateral_col_alias, - outer, - }); - } else { - break; - } - } - - let selection = if self.parse_keyword(Keyword::WHERE) { - Some(self.parse_expr()?) - } else { - None - }; - - let group_by = if self.parse_keywords(&[Keyword::GROUP, Keyword::BY]) { - self.parse_comma_separated(Parser::parse_group_by_expr)? - } else { - vec![] - }; - - let cluster_by = if self.parse_keywords(&[Keyword::CLUSTER, Keyword::BY]) { - self.parse_comma_separated(Parser::parse_expr)? - } else { - vec![] - }; - - let distribute_by = if self.parse_keywords(&[Keyword::DISTRIBUTE, Keyword::BY]) { - self.parse_comma_separated(Parser::parse_expr)? - } else { - vec![] - }; - - let sort_by = if self.parse_keywords(&[Keyword::SORT, Keyword::BY]) { - self.parse_comma_separated(Parser::parse_expr)? - } else { - vec![] - }; - - let having = if self.parse_keyword(Keyword::HAVING) { - Some(self.parse_expr()?) - } else { - None - }; - - let qualify = if self.parse_keyword(Keyword::QUALIFY) { - Some(self.parse_expr()?) - } else { - None - }; + let lateral_views = vec![]; + let selection = None; + let group_by = vec![]; + let cluster_by = vec![]; + let distribute_by = vec![]; + let sort_by = vec![]; + let having = None; + let qualify = None; Ok(Select { distinct, From 051f3f3b3a9c8f47b934d291791dc18160847ae4 Mon Sep 17 00:00:00 2001 From: Sarah Yurick Date: Thu, 17 Nov 2022 15:41:31 -0800 Subject: [PATCH 05/12] refactor implementation --- src/ast/mod.rs | 2 +- src/ast/query.rs | 22 ++++++++++++++++- src/parser.rs | 51 +++------------------------------------ tests/sqlparser_common.rs | 23 +++++++++++++----- 4 files changed, 42 insertions(+), 56 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2b81f0b39..6b687bff1 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -33,7 +33,7 @@ pub use self::operator::{BinaryOperator, UnaryOperator}; pub use self::query::{ Cte, Fetch, Join, JoinConstraint, JoinOperator, LateralView, LockType, Offset, OffsetRows, OrderByExpr, Query, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, - TableAlias, TableFactor, TableWithJoins, Top, Values, With, + Table, TableAlias, TableFactor, TableWithJoins, Top, Values, With, }; pub use self::value::{escape_quoted_string, DateTimeField, TrimWhereField, Value}; diff --git a/src/ast/query.rs b/src/ast/query.rs index 4f3d79cdf..712e59cc9 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -84,7 +84,7 @@ pub enum SetExpr { }, Values(Values), Insert(Statement), - // TODO: ANSI SQL supports `TABLE` here. + Table(Box), } impl fmt::Display for SetExpr { @@ -94,6 +94,7 @@ impl fmt::Display for SetExpr { SetExpr::Query(q) => write!(f, "({})", q), SetExpr::Values(v) => write!(f, "{}", v), SetExpr::Insert(v) => write!(f, "{}", v), + SetExpr::Table(t) => write!(f, "{}", t), SetExpr::SetOperation { left, right, @@ -152,6 +153,25 @@ impl fmt::Display for SetQuantifier { } } } + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Table { + pub table_name: String, + // pub schema_name: String, +} + +impl fmt::Display for Table { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "TABLE {}", + self.table_name, + )?; + Ok(()) + } +} + /// A restricted variant of `SELECT` (without CTEs/`ORDER BY`), which may /// appear either as the only body item of a `Query`, or as an operand /// to a set operation like `UNION`. diff --git a/src/parser.rs b/src/parser.rs index ac01fdf7b..d07e62ae7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4185,7 +4185,7 @@ impl<'a> Parser<'a> { match &self.tokens[0] { Token::Word(w) => { if w.keyword == Keyword::CREATE { - SetExpr::Select(Box::new(self.parse_as_table()?)) + SetExpr::Table(Box::new(self.parse_as_table()?)) } else { return self.expected( "CREATE statement", @@ -4396,16 +4396,7 @@ impl<'a> Parser<'a> { } /// Parse `CREATE TABLE x AS TABLE y` - /// the same as `CREATE TABLE x AS SELECT * FROM y` - /// Note logic for the "projection" and "from" variables - pub fn parse_as_table(&mut self) -> Result { - let distinct = false; - let top = None; - - let projection = vec![SelectItem::Wildcard]; - - let into = None; - + pub fn parse_as_table(&mut self) -> Result { let mut as_flag = false; let mut table_flag = false; let mut table_name = ""; @@ -4426,43 +4417,7 @@ impl<'a> Parser<'a> { } } - let from = vec![TableWithJoins { - relation: TableFactor::Table { - name: ObjectName(vec![Ident { - value: table_name.to_string(), - quote_style: None, - }]), - alias: None, - args: None, - with_hints: vec![], - }, - joins: vec![], - }]; - - let lateral_views = vec![]; - let selection = None; - let group_by = vec![]; - let cluster_by = vec![]; - let distribute_by = vec![]; - let sort_by = vec![]; - let having = None; - let qualify = None; - - Ok(Select { - distinct, - top, - projection, - into, - from, - lateral_views, - selection, - group_by, - cluster_by, - distribute_by, - sort_by, - having, - qualify, - }) + Ok(Table { table_name: table_name.to_string() }) } pub fn parse_set(&mut self) -> Result { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 35a4dec3e..aac30020e 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2155,13 +2155,24 @@ fn parse_create_table_as() { #[test] fn parse_create_table_as_table() { - let sql1 = "CREATE TABLE new_table AS TABLE old_table"; - let actual_ast = Parser::parse_sql(&GenericDialect {}, sql1).unwrap(); - - let sql2 = "CREATE TABLE new_table AS SELECT * FROM old_table"; - let expected_ast = Parser::parse_sql(&GenericDialect {}, sql2).unwrap(); + let sql = "CREATE TABLE new_table AS TABLE old_table"; + + let expected_query = Box::new(Query { + with: None, + body: Box::new(SetExpr::Table(Box::new(Table { table_name: "old_table".to_string() }))), + order_by: vec![], + limit: None, + offset: None, + fetch: None, + lock: None, + }); - assert_eq!(actual_ast, expected_ast); + match verified_stmt(sql) { + Statement::CreateTable { query, .. } => { + assert_eq!(query.unwrap(), expected_query); + } + _ => unreachable!(), + } } #[test] From 1221712a29ffc1f03c598c7a772f1c22e27fb525 Mon Sep 17 00:00:00 2001 From: Sarah Yurick Date: Thu, 17 Nov 2022 15:45:11 -0800 Subject: [PATCH 06/12] codestyle --- src/ast/mod.rs | 4 ++-- src/ast/query.rs | 6 +----- src/parser.rs | 4 +++- tests/sqlparser_common.rs | 4 +++- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 6b687bff1..ac6846644 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -32,8 +32,8 @@ pub use self::ddl::{ pub use self::operator::{BinaryOperator, UnaryOperator}; pub use self::query::{ Cte, Fetch, Join, JoinConstraint, JoinOperator, LateralView, LockType, Offset, OffsetRows, - OrderByExpr, Query, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, - Table, TableAlias, TableFactor, TableWithJoins, Top, Values, With, + OrderByExpr, Query, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Table, + TableAlias, TableFactor, TableWithJoins, Top, Values, With, }; pub use self::value::{escape_quoted_string, DateTimeField, TrimWhereField, Value}; diff --git a/src/ast/query.rs b/src/ast/query.rs index 712e59cc9..f6a500e65 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -163,11 +163,7 @@ pub struct Table { impl fmt::Display for Table { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "TABLE {}", - self.table_name, - )?; + write!(f, "TABLE {}", self.table_name,)?; Ok(()) } } diff --git a/src/parser.rs b/src/parser.rs index d07e62ae7..f9e349610 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4417,7 +4417,9 @@ impl<'a> Parser<'a> { } } - Ok(Table { table_name: table_name.to_string() }) + Ok(Table { + table_name: table_name.to_string(), + }) } pub fn parse_set(&mut self) -> Result { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index aac30020e..c5d46905c 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2159,7 +2159,9 @@ fn parse_create_table_as_table() { let expected_query = Box::new(Query { with: None, - body: Box::new(SetExpr::Table(Box::new(Table { table_name: "old_table".to_string() }))), + body: Box::new(SetExpr::Table(Box::new(Table { + table_name: "old_table".to_string(), + }))), order_by: vec![], limit: None, offset: None, From 37235b86df23b559f175aa604d6addb855ebfdf8 Mon Sep 17 00:00:00 2001 From: Sarah Yurick Date: Fri, 18 Nov 2022 14:45:51 -0800 Subject: [PATCH 07/12] add schema support --- src/ast/query.rs | 19 ++++++++++++++++--- src/parser.rs | 35 +++++++++++++++++++++++++++-------- tests/sqlparser_common.rs | 33 ++++++++++++++++++++++++++++----- 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index f6a500e65..4cdb99ed8 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -157,13 +157,26 @@ impl fmt::Display for SetQuantifier { #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Table { - pub table_name: String, - // pub schema_name: String, + pub table_name: Option, + pub schema_name: Option, } impl fmt::Display for Table { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "TABLE {}", self.table_name,)?; + if let Some(ref schema_name) = self.schema_name { + write!( + f, + "TABLE {}.{}", + schema_name, + self.table_name.as_ref().unwrap(), + )?; + } else { + write!( + f, + "TABLE {}", + self.table_name.as_ref().unwrap(), + )?; + } Ok(()) } } diff --git a/src/parser.rs b/src/parser.rs index f9e349610..e72e7aa6e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4181,6 +4181,8 @@ impl<'a> Parser<'a> { } else if self.parse_keyword(Keyword::VALUES) { SetExpr::Values(self.parse_values()?) } else if self.parse_keyword(Keyword::TABLE) { + self.next_token(); + self.next_token(); self.next_token(); match &self.tokens[0] { Token::Word(w) => { @@ -4400,26 +4402,43 @@ impl<'a> Parser<'a> { let mut as_flag = false; let mut table_flag = false; let mut table_name = ""; - // Grab the table name after `AS TABLE ...` - for token in self.tokens.iter() { + let mut schema_name = ""; + + let mut tokens_iter = self.tokens.iter().enumerate().peekable(); + while let Some((_, token)) = tokens_iter.next() { if let Token::Word(w) = token { if w.keyword == Keyword::AS { as_flag = true; } else if w.keyword == Keyword::TABLE && as_flag { table_flag = true; } else if as_flag && table_flag { - table_name = &w.value; - as_flag = false; - table_flag = false; + match tokens_iter.peek() { + Some((_, Token::Period)) => { + schema_name = &w.value; + } + _ => { + table_name = &w.value; + as_flag = false; + table_flag = false; + } + } } else if w.keyword != Keyword::TABLE && as_flag { as_flag = false; } } } - Ok(Table { - table_name: table_name.to_string(), - }) + if schema_name == "" { + Ok(Table { + table_name: Some(table_name.to_string()), + schema_name: None, + }) + } else { + Ok(Table { + table_name: Some(table_name.to_string()), + schema_name: Some(schema_name.to_string()), + }) + } } pub fn parse_set(&mut self) -> Result { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index c5d46905c..25652d053 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2155,12 +2155,13 @@ fn parse_create_table_as() { #[test] fn parse_create_table_as_table() { - let sql = "CREATE TABLE new_table AS TABLE old_table"; + let sql1 = "CREATE TABLE new_table AS TABLE old_table"; - let expected_query = Box::new(Query { + let expected_query1 = Box::new(Query { with: None, body: Box::new(SetExpr::Table(Box::new(Table { - table_name: "old_table".to_string(), + table_name: Some("old_table".to_string()), + schema_name: None, }))), order_by: vec![], limit: None, @@ -2169,9 +2170,31 @@ fn parse_create_table_as_table() { lock: None, }); - match verified_stmt(sql) { + match verified_stmt(sql1) { + Statement::CreateTable { query, .. } => { + assert_eq!(query.unwrap(), expected_query1); + } + _ => unreachable!(), + } + + let sql2 = "CREATE TABLE new_table AS TABLE schema_name.old_table"; + + let expected_query2 = Box::new(Query { + with: None, + body: Box::new(SetExpr::Table(Box::new(Table { + table_name: Some("old_table".to_string()), + schema_name: Some("schema_name".to_string()), + }))), + order_by: vec![], + limit: None, + offset: None, + fetch: None, + lock: None, + }); + + match verified_stmt(sql2) { Statement::CreateTable { query, .. } => { - assert_eq!(query.unwrap(), expected_query); + assert_eq!(query.unwrap(), expected_query2); } _ => unreachable!(), } From d0264c63427409063f17bf0bd84674faa96b9a47 Mon Sep 17 00:00:00 2001 From: Sarah Yurick Date: Fri, 18 Nov 2022 14:50:20 -0800 Subject: [PATCH 08/12] codestyle and lint --- src/ast/query.rs | 6 +----- src/parser.rs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index 4cdb99ed8..467c264a5 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -171,11 +171,7 @@ impl fmt::Display for Table { self.table_name.as_ref().unwrap(), )?; } else { - write!( - f, - "TABLE {}", - self.table_name.as_ref().unwrap(), - )?; + write!(f, "TABLE {}", self.table_name.as_ref().unwrap(),)?; } Ok(()) } diff --git a/src/parser.rs b/src/parser.rs index e72e7aa6e..d264fa2c8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4428,7 +4428,7 @@ impl<'a> Parser<'a> { } } - if schema_name == "" { + if schema_name.is_empty() { Ok(Table { table_name: Some(table_name.to_string()), schema_name: None, From 43d57ea0a8b638cbae2a93ddb472f53a74e75a4a Mon Sep 17 00:00:00 2001 From: Sarah Yurick <53962159+sarahyurick@users.noreply.github.com> Date: Wed, 30 Nov 2022 09:58:30 -0800 Subject: [PATCH 09/12] Apply suggestions from code review Co-authored-by: Andrew Lamb --- src/ast/query.rs | 1 + tests/sqlparser_common.rs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index 1de599093..46ce1b34a 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -156,6 +156,7 @@ impl fmt::Display for SetQuantifier { #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +/// A [`TABLE` command]( https://www.postgresql.org/docs/current/sql-select.html#SQL-TABLE) pub struct Table { pub table_name: Option, pub schema_name: Option, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index ef031cebc..6f5feb636 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2288,7 +2288,8 @@ fn parse_create_table_as_table() { }); match verified_stmt(sql1) { - Statement::CreateTable { query, .. } => { + Statement::CreateTable { query, name, .. } => { + assert_eq!(name, ObjectName(vec![Ident::new("new_table")])); assert_eq!(query.unwrap(), expected_query1); } _ => unreachable!(), @@ -2310,7 +2311,8 @@ fn parse_create_table_as_table() { }); match verified_stmt(sql2) { - Statement::CreateTable { query, .. } => { + Statement::CreateTable { query, name, .. } => { + assert_eq!(name, ObjectName(vec![Ident::new("new_table")])); assert_eq!(query.unwrap(), expected_query2); } _ => unreachable!(), From 74d606fcabfc7b25e928e5b7f9464bfd7138a3e4 Mon Sep 17 00:00:00 2001 From: Sarah Yurick Date: Wed, 30 Nov 2022 10:08:39 -0800 Subject: [PATCH 10/12] PartialOrd and Ord --- src/ast/query.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index 46ce1b34a..59fb201b6 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -154,7 +154,7 @@ impl fmt::Display for SetQuantifier { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] /// A [`TABLE` command]( https://www.postgresql.org/docs/current/sql-select.html#SQL-TABLE) pub struct Table { From ba4c0cad3d22bba40e67b397a971541ed54c14d2 Mon Sep 17 00:00:00 2001 From: Sarah Yurick Date: Wed, 30 Nov 2022 14:09:29 -0800 Subject: [PATCH 11/12] clean up parser logic --- src/parser.rs | 78 +++++++++++++++++++++------------------------------ 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 18015b112..2dc8fcd3e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4369,24 +4369,13 @@ impl<'a> Parser<'a> { } else if self.parse_keyword(Keyword::VALUES) { SetExpr::Values(self.parse_values()?) } else if self.parse_keyword(Keyword::TABLE) { + let token1 = self.peek_token(); + let token2 = self.peek_nth_token(1); + let token3 = self.peek_nth_token(2); self.next_token(); self.next_token(); self.next_token(); - match &self.tokens[0] { - Token::Word(w) => { - if w.keyword == Keyword::CREATE { - SetExpr::Table(Box::new(self.parse_as_table()?)) - } else { - return self.expected( - "CREATE statement", - Token::SingleQuotedString(w.value.clone()), - ); - } - } - unexpected => { - return self.expected("CREATE TABLE ... AS TABLE ...", unexpected.clone()) - } - } + SetExpr::Table(Box::new(self.parse_as_table(token1, token2, token3)?)) } else { return self.expected( "SELECT, VALUES, or a subquery in the query body", @@ -4586,45 +4575,42 @@ impl<'a> Parser<'a> { } /// Parse `CREATE TABLE x AS TABLE y` - pub fn parse_as_table(&mut self) -> Result { - let mut as_flag = false; - let mut table_flag = false; - let mut table_name = ""; - let mut schema_name = ""; - - let mut tokens_iter = self.tokens.iter().enumerate().peekable(); - while let Some((_, token)) = tokens_iter.next() { - if let Token::Word(w) = token { - if w.keyword == Keyword::AS { - as_flag = true; - } else if w.keyword == Keyword::TABLE && as_flag { - table_flag = true; - } else if as_flag && table_flag { - match tokens_iter.peek() { - Some((_, Token::Period)) => { - schema_name = &w.value; - } - _ => { - table_name = &w.value; - as_flag = false; - table_flag = false; - } - } - } else if w.keyword != Keyword::TABLE && as_flag { - as_flag = false; + pub fn parse_as_table(&self, token1: Token, token2: Token, token3: Token) -> Result { + let table_name; + let schema_name; + if token2 == Token::Period { + match token1 { + Token::Word(w) => { + schema_name = w.value; + } + _ => { + return self.expected("Schema name", token1); + } + } + match token3 { + Token::Word(w) => { + table_name = w.value; + } + _ => { + return self.expected("Table name", token3); } } - } - - if schema_name.is_empty() { Ok(Table { table_name: Some(table_name.to_string()), - schema_name: None, + schema_name: Some(schema_name.to_string()), }) } else { + match token1 { + Token::Word(w) => { + table_name = w.value; + } + _ => { + return self.expected("Table name", token1); + } + } Ok(Table { table_name: Some(table_name.to_string()), - schema_name: Some(schema_name.to_string()), + schema_name: None, }) } } From d93502c7a5078ef89fbe5fa8b53d1f73f2dbd4d3 Mon Sep 17 00:00:00 2001 From: Sarah Yurick Date: Wed, 30 Nov 2022 14:14:30 -0800 Subject: [PATCH 12/12] codestyle and lint --- src/parser.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 2dc8fcd3e..b23cc297d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4575,7 +4575,12 @@ impl<'a> Parser<'a> { } /// Parse `CREATE TABLE x AS TABLE y` - pub fn parse_as_table(&self, token1: Token, token2: Token, token3: Token) -> Result { + pub fn parse_as_table( + &self, + token1: Token, + token2: Token, + token3: Token, + ) -> Result { let table_name; let schema_name; if token2 == Token::Period { @@ -4596,8 +4601,8 @@ impl<'a> Parser<'a> { } } Ok(Table { - table_name: Some(table_name.to_string()), - schema_name: Some(schema_name.to_string()), + table_name: Some(table_name), + schema_name: Some(schema_name), }) } else { match token1 { @@ -4609,7 +4614,7 @@ impl<'a> Parser<'a> { } } Ok(Table { - table_name: Some(table_name.to_string()), + table_name: Some(table_name), schema_name: None, }) }