diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 019622433..7041ed63a 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1430,6 +1430,25 @@ pub enum Statement { // Specifies the actions to perform when values match or do not match. clauses: Vec, }, + /// CACHE [ FLAG ] TABLE [ OPTIONS('K1' = 'V1', 'K2' = V2) ] [ AS ] [ ] + /// Based on Spark SQL,see + Cache { + // Table flag + table_flag: Option, + // Table name + table_name: ObjectName, + has_as: bool, + // Table confs + options: Vec, + // Cache table as a Query + query: Option, + }, + /// UNCACHE TABLE [ IF EXISTS ] + UNCache { + // Table name + table_name: ObjectName, + if_exists: bool, + }, } impl fmt::Display for Statement { @@ -2397,6 +2416,53 @@ impl fmt::Display for Statement { write!(f, "ON {} ", on)?; write!(f, "{}", display_separated(clauses, " ")) } + Statement::Cache { + table_name, + table_flag, + has_as, + options, + query, + } => { + if table_flag.is_some() { + write!( + f, + "CACHE {table_flag} TABLE {table_name}", + table_flag = table_flag.clone().unwrap(), + table_name = table_name, + )?; + } else { + write!(f, "CACHE TABLE {table_name}", table_name = table_name,)?; + } + + if !options.is_empty() { + write!(f, " OPTIONS({})", display_comma_separated(options))?; + } + + let has_query = query.is_some(); + if *has_as && has_query { + write!(f, " AS {query}", query = query.clone().unwrap()) + } else if !has_as && has_query { + write!(f, " {query}", query = query.clone().unwrap()) + } else if *has_as && !has_query { + write!(f, " AS") + } else { + Ok(()) + } + } + Statement::UNCache { + table_name, + if_exists, + } => { + if *if_exists { + write!( + f, + "UNCACHE TABLE IF EXISTS {table_name}", + table_name = table_name + ) + } else { + write!(f, "UNCACHE TABLE {table_name}", table_name = table_name) + } + } } } } diff --git a/src/keywords.rs b/src/keywords.rs index 0b6b06b33..1c2c43854 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -383,6 +383,7 @@ define_keywords!( OPEN, OPERATOR, OPTION, + OPTIONS, OR, ORC, ORDER, @@ -554,6 +555,7 @@ define_keywords!( TYPE, UESCAPE, UNBOUNDED, + UNCACHE, UNCOMMITTED, UNION, UNIQUE, diff --git a/src/parser.rs b/src/parser.rs index 0c0bd9c5a..f8a472f9a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -170,12 +170,14 @@ impl<'a> Parser<'a> { Keyword::TRUNCATE => Ok(self.parse_truncate()?), Keyword::MSCK => Ok(self.parse_msck()?), Keyword::CREATE => Ok(self.parse_create()?), + Keyword::CACHE => Ok(self.parse_cache_table()?), Keyword::DROP => Ok(self.parse_drop()?), Keyword::DISCARD => Ok(self.parse_discard()?), Keyword::DECLARE => Ok(self.parse_declare()?), Keyword::FETCH => Ok(self.parse_fetch_statement()?), Keyword::DELETE => Ok(self.parse_delete()?), Keyword::INSERT => Ok(self.parse_insert()?), + Keyword::UNCACHE => Ok(self.parse_uncache_table()?), Keyword::UPDATE => Ok(self.parse_update()?), Keyword::ALTER => Ok(self.parse_alter()?), Keyword::COPY => Ok(self.parse_copy()?), @@ -1905,6 +1907,115 @@ impl<'a> Parser<'a> { } } + /// Parse a CACHE TABLE statement + pub fn parse_cache_table(&mut self) -> Result { + let (mut table_flag, mut options, mut has_as, mut query) = (None, vec![], false, None); + if self.parse_keyword(Keyword::TABLE) { + let table_name = self.parse_object_name()?; + if self.peek_token() != Token::EOF { + if let Token::Word(word) = self.peek_token() { + if word.keyword == Keyword::OPTIONS { + options = self.parse_options(Keyword::OPTIONS)? + } + }; + + if self.peek_token() != Token::EOF { + let (a, q) = self.parse_as_query()?; + has_as = a; + query = Some(q); + } + + Ok(Statement::Cache { + table_flag, + table_name, + has_as, + options, + query, + }) + } else { + Ok(Statement::Cache { + table_flag, + table_name, + has_as, + options, + query, + }) + } + } else { + table_flag = Some(self.parse_object_name()?); + if self.parse_keyword(Keyword::TABLE) { + let table_name = self.parse_object_name()?; + if self.peek_token() != Token::EOF { + if let Token::Word(word) = self.peek_token() { + if word.keyword == Keyword::OPTIONS { + options = self.parse_options(Keyword::OPTIONS)? + } + }; + + if self.peek_token() != Token::EOF { + let (a, q) = self.parse_as_query()?; + has_as = a; + query = Some(q); + } + + Ok(Statement::Cache { + table_flag, + table_name, + has_as, + options, + query, + }) + } else { + Ok(Statement::Cache { + table_flag, + table_name, + has_as, + options, + query, + }) + } + } else { + if self.peek_token() == Token::EOF { + self.prev_token(); + } + self.expected("a `TABLE` keyword", self.peek_token()) + } + } + } + + /// Parse 'AS' before as query,such as `WITH XXX AS SELECT XXX` oer `CACHE TABLE AS SELECT XXX` + pub fn parse_as_query(&mut self) -> Result<(bool, Query), ParserError> { + match self.peek_token() { + Token::Word(word) => match word.keyword { + Keyword::AS => { + self.next_token(); + Ok((true, self.parse_query()?)) + } + _ => Ok((false, self.parse_query()?)), + }, + _ => self.expected("a QUERY statement", self.peek_token()), + } + } + + /// Parse a UNCACHE TABLE statement + pub fn parse_uncache_table(&mut self) -> Result { + let has_table = self.parse_keyword(Keyword::TABLE); + if has_table { + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let table_name = self.parse_object_name()?; + if self.peek_token() == Token::EOF { + Ok(Statement::UNCache { + table_name, + if_exists, + }) + } else { + self.expected("an `EOF`", self.peek_token()) + } + } else { + self.expected("a `TABLE` keyword", self.peek_token()) + } + } + /// SQLite-specific `CREATE VIRTUAL TABLE` pub fn parse_create_virtual_table(&mut self) -> Result { self.expect_keyword(Keyword::TABLE)?; diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index aa4013394..42121d0a0 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -202,19 +202,19 @@ fn parse_update_with_table_alias() { name: ObjectName(vec![Ident::new("users")]), alias: Some(TableAlias { name: Ident::new("u"), - columns: vec![] + columns: vec![], }), args: None, with_hints: vec![], }, - joins: vec![] + joins: vec![], }, table ); assert_eq!( vec![Assignment { id: vec![Ident::new("u"), Ident::new("username")], - value: Expr::Value(Value::SingleQuotedString("new_user".to_string())) + value: Expr::Value(Value::SingleQuotedString("new_user".to_string())), }], assignments ); @@ -222,12 +222,12 @@ fn parse_update_with_table_alias() { Some(Expr::BinaryOp { left: Box::new(Expr::CompoundIdentifier(vec![ Ident::new("u"), - Ident::new("username") + Ident::new("username"), ])), op: BinaryOperator::Eq, right: Box::new(Expr::Value(Value::SingleQuotedString( "old_user".to_string() - ))) + ))), }), selection ); @@ -259,7 +259,7 @@ fn parse_delete_statement() { name: ObjectName(vec![Ident::with_quote('"', "table")]), alias: None, args: None, - with_hints: vec![] + with_hints: vec![], }, table_name ); @@ -284,7 +284,7 @@ fn parse_where_delete_statement() { name: ObjectName(vec![Ident::new("foo")]), alias: None, args: None, - with_hints: vec![] + with_hints: vec![], }, table_name, ); @@ -319,10 +319,10 @@ fn parse_where_delete_with_alias_statement() { name: ObjectName(vec![Ident::new("basket")]), alias: Some(TableAlias { name: Ident::new("a"), - columns: vec![] + columns: vec![], }), args: None, - with_hints: vec![] + with_hints: vec![], }, table_name, ); @@ -332,10 +332,10 @@ fn parse_where_delete_with_alias_statement() { name: ObjectName(vec![Ident::new("basket")]), alias: Some(TableAlias { name: Ident::new("b"), - columns: vec![] + columns: vec![], }), args: None, - with_hints: vec![] + with_hints: vec![], }), using ); @@ -343,12 +343,12 @@ fn parse_where_delete_with_alias_statement() { Expr::BinaryOp { left: Box::new(Expr::CompoundIdentifier(vec![ Ident::new("a"), - Ident::new("id") + Ident::new("id"), ])), op: Lt, right: Box::new(Expr::CompoundIdentifier(vec![ Ident::new("b"), - Ident::new("id") + Ident::new("id"), ])), }, selection.unwrap(), @@ -458,7 +458,7 @@ fn parse_select_into() { temporary: false, unlogged: false, table: false, - name: ObjectName(vec![Ident::new("table0")]) + name: ObjectName(vec![Ident::new("table0")]), }, only(&select.into) ); @@ -677,7 +677,7 @@ fn parse_select_with_date_column_name() { assert_eq!( &Expr::Identifier(Ident { value: "date".into(), - quote_style: None + quote_style: None, }), expr_from_projection(only(&select.projection)), ); @@ -695,7 +695,7 @@ fn parse_escaped_single_quote_string_predicate() { op: NotEq, right: Box::new(Expr::Value(Value::SingleQuotedString( "Jim's salary".to_string() - ))) + ))), }), ast.selection, ); @@ -727,8 +727,8 @@ fn parse_compound_expr_1() { right: Box::new(BinaryOp { left: Box::new(Identifier(Ident::new("b"))), op: Multiply, - right: Box::new(Identifier(Ident::new("c"))) - }) + right: Box::new(Identifier(Ident::new("c"))), + }), }, verified_expr(sql) ); @@ -744,10 +744,10 @@ fn parse_compound_expr_2() { left: Box::new(BinaryOp { left: Box::new(Identifier(Ident::new("a"))), op: Multiply, - right: Box::new(Identifier(Ident::new("b"))) + right: Box::new(Identifier(Ident::new("b"))), }), op: Plus, - right: Box::new(Identifier(Ident::new("c"))) + right: Box::new(Identifier(Ident::new("c"))), }, verified_expr(sql) ); @@ -800,11 +800,12 @@ fn parse_is_distinct_from() { assert_eq!( IsDistinctFrom( Box::new(Identifier(Ident::new("a"))), - Box::new(Identifier(Ident::new("b"))) + Box::new(Identifier(Ident::new("b"))), ), verified_expr(sql) ); } + #[test] fn parse_is_not_distinct_from() { use self::Expr::*; @@ -812,7 +813,7 @@ fn parse_is_not_distinct_from() { assert_eq!( IsNotDistinctFrom( Box::new(Identifier(Ident::new("a"))), - Box::new(Identifier(Ident::new("b"))) + Box::new(Identifier(Ident::new("b"))), ), verified_expr(sql) ); @@ -865,7 +866,7 @@ fn parse_not_precedence() { expr: Box::new(Expr::Value(Value::SingleQuotedString("a".into()))), negated: true, pattern: Box::new(Expr::Value(Value::SingleQuotedString("b".into()))), - escape_char: None + escape_char: None, }), }, ); @@ -898,7 +899,7 @@ fn parse_like() { expr: Box::new(Expr::Identifier(Ident::new("name"))), negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: None + escape_char: None, }, select.selection.unwrap() ); @@ -914,7 +915,7 @@ fn parse_like() { expr: Box::new(Expr::Identifier(Ident::new("name"))), negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: Some('\\') + escape_char: Some('\\'), }, select.selection.unwrap() ); @@ -931,7 +932,7 @@ fn parse_like() { expr: Box::new(Expr::Identifier(Ident::new("name"))), negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: None + escape_char: None, })), select.selection.unwrap() ); @@ -953,12 +954,12 @@ fn parse_null_like() { expr: Box::new(Expr::Identifier(Ident::new("column1"))), negated: false, pattern: Box::new(Expr::Value(Value::Null)), - escape_char: None + escape_char: None, }, alias: Ident { value: "col_null".to_owned(), - quote_style: None - } + quote_style: None, + }, }, select.projection[0] ); @@ -968,12 +969,12 @@ fn parse_null_like() { expr: Box::new(Expr::Value(Value::Null)), negated: false, pattern: Box::new(Expr::Identifier(Ident::new("column1"))), - escape_char: None + escape_char: None, }, alias: Ident { value: "null_col".to_owned(), - quote_style: None - } + quote_style: None, + }, }, select.projection[1] ); @@ -992,7 +993,7 @@ fn parse_ilike() { expr: Box::new(Expr::Identifier(Ident::new("name"))), negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: None + escape_char: None, }, select.selection.unwrap() ); @@ -1008,7 +1009,7 @@ fn parse_ilike() { expr: Box::new(Expr::Identifier(Ident::new("name"))), negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: Some('^') + escape_char: Some('^'), }, select.selection.unwrap() ); @@ -1025,7 +1026,7 @@ fn parse_ilike() { expr: Box::new(Expr::Identifier(Ident::new("name"))), negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: None + escape_char: None, })), select.selection.unwrap() ); @@ -1047,7 +1048,7 @@ fn parse_similar_to() { expr: Box::new(Expr::Identifier(Ident::new("name"))), negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: None + escape_char: None, }, select.selection.unwrap() ); @@ -1063,7 +1064,7 @@ fn parse_similar_to() { expr: Box::new(Expr::Identifier(Ident::new("name"))), negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: Some('\\') + escape_char: Some('\\'), }, select.selection.unwrap() ); @@ -1079,7 +1080,7 @@ fn parse_similar_to() { expr: Box::new(Expr::Identifier(Ident::new("name"))), negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: Some('\\') + escape_char: Some('\\'), })), select.selection.unwrap() ); @@ -1336,14 +1337,14 @@ fn parse_tuples() { vec![ SelectItem::UnnamedExpr(Expr::Tuple(vec![ Expr::Value(number("1")), - Expr::Value(number("2")) + Expr::Value(number("2")), ])), SelectItem::UnnamedExpr(Expr::Nested(Box::new(Expr::Value(number("1"))))), SelectItem::UnnamedExpr(Expr::Tuple(vec![ Expr::Value(Value::SingleQuotedString("foo".into())), Expr::Value(number("3")), - Expr::Identifier(Ident::new("baz")) - ])) + Expr::Identifier(Ident::new("baz")), + ])), ], select.projection ); @@ -1477,7 +1478,7 @@ fn parse_select_group_by_grouping_sets() { vec![Expr::Identifier(Ident::new("brand"))], vec![Expr::Identifier(Ident::new("size"))], vec![], - ]) + ]), ], select.group_by ); @@ -1496,7 +1497,7 @@ fn parse_select_group_by_rollup() { Expr::Rollup(vec![ vec![Expr::Identifier(Ident::new("brand"))], vec![Expr::Identifier(Ident::new("size"))], - ]) + ]), ], select.group_by ); @@ -1515,7 +1516,7 @@ fn parse_select_group_by_cube() { Expr::Cube(vec![ vec![Expr::Identifier(Ident::new("brand"))], vec![Expr::Identifier(Ident::new("size"))], - ]) + ]), ], select.group_by ); @@ -1535,7 +1536,7 @@ fn parse_select_having() { special: false, })), op: BinaryOperator::Gt, - right: Box::new(Expr::Value(number("1"))) + right: Box::new(Expr::Value(number("1"))), }), select.having ); @@ -1560,15 +1561,15 @@ fn parse_select_qualify() { order_by: vec![OrderByExpr { expr: Expr::Identifier(Ident::new("o")), asc: None, - nulls_first: None + nulls_first: None, }], - window_frame: None + window_frame: None, }), distinct: false, - special: false + special: false, })), op: BinaryOperator::Eq, - right: Box::new(Expr::Value(number("1"))) + right: Box::new(Expr::Value(number("1"))), }), select.qualify ); @@ -1579,7 +1580,7 @@ fn parse_select_qualify() { Some(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("row_num"))), op: BinaryOperator::Eq, - right: Box::new(Expr::Value(number("1"))) + right: Box::new(Expr::Value(number("1"))), }), select.qualify ); @@ -1600,7 +1601,7 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::BigInt(None) + data_type: DataType::BigInt(None), }, expr_from_projection(only(&select.projection)) ); @@ -1610,7 +1611,7 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::TinyInt(None) + data_type: DataType::TinyInt(None), }, expr_from_projection(only(&select.projection)) ); @@ -1642,7 +1643,7 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::Nvarchar(Some(50)) + data_type: DataType::Nvarchar(Some(50)), }, expr_from_projection(only(&select.projection)) ); @@ -1652,7 +1653,7 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::Clob(None) + data_type: DataType::Clob(None), }, expr_from_projection(only(&select.projection)) ); @@ -1662,7 +1663,7 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::Clob(Some(50)) + data_type: DataType::Clob(Some(50)), }, expr_from_projection(only(&select.projection)) ); @@ -1672,7 +1673,7 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::Binary(Some(50)) + data_type: DataType::Binary(Some(50)), }, expr_from_projection(only(&select.projection)) ); @@ -1682,7 +1683,7 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::Varbinary(Some(50)) + data_type: DataType::Varbinary(Some(50)), }, expr_from_projection(only(&select.projection)) ); @@ -1692,7 +1693,7 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::Blob(None) + data_type: DataType::Blob(None), }, expr_from_projection(only(&select.projection)) ); @@ -1702,7 +1703,7 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::Blob(Some(50)) + data_type: DataType::Blob(Some(50)), }, expr_from_projection(only(&select.projection)) ); @@ -1715,7 +1716,7 @@ fn parse_try_cast() { assert_eq!( &Expr::TryCast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::BigInt(None) + data_type: DataType::BigInt(None), }, expr_from_projection(only(&select.projection)) ); @@ -1893,7 +1894,7 @@ fn parse_listagg() { ", ".to_string() )))), on_overflow, - within_group + within_group, }), expr_from_projection(only(&select.projection)) ); @@ -1947,12 +1948,12 @@ fn parse_create_table() { name: "name".into(), data_type: DataType::Varchar(Some(CharacterLength { length: 100, - unit: None + unit: None, })), collation: None, options: vec![ColumnOptionDef { name: None, - option: ColumnOption::NotNull + option: ColumnOption::NotNull, }], }, ColumnDef { @@ -1961,7 +1962,7 @@ fn parse_create_table() { collation: None, options: vec![ColumnOptionDef { name: None, - option: ColumnOption::Null + option: ColumnOption::Null, }], }, ColumnDef { @@ -1977,15 +1978,15 @@ fn parse_create_table() { options: vec![ ColumnOptionDef { name: None, - option: ColumnOption::Null + option: ColumnOption::Null, }, ColumnOptionDef { name: Some("pkey".into()), - option: ColumnOption::Unique { is_primary: true } + option: ColumnOption::Unique { is_primary: true }, }, ColumnOptionDef { name: None, - option: ColumnOption::NotNull + option: ColumnOption::NotNull, }, ColumnOptionDef { name: None, @@ -1994,7 +1995,7 @@ fn parse_create_table() { ColumnOptionDef { name: None, option: ColumnOption::Check(verified_expr("constrained > 0")), - } + }, ], }, ColumnDef { @@ -2005,11 +2006,11 @@ fn parse_create_table() { name: None, option: ColumnOption::ForeignKey { foreign_table: ObjectName(vec!["othertable".into()]), - referred_columns: vec!["a".into(), "b".into(),], + referred_columns: vec!["a".into(), "b".into()], on_delete: None, on_update: None, - } - }] + }, + }], }, ColumnDef { name: "ref2".into(), @@ -2022,9 +2023,9 @@ fn parse_create_table() { referred_columns: vec![], on_delete: Some(ReferentialAction::Cascade), on_update: Some(ReferentialAction::NoAction), - } - },] - } + }, + },], + }, ] ); assert_eq!( @@ -2036,7 +2037,7 @@ fn parse_create_table() { foreign_table: ObjectName(vec!["othertable3".into()]), referred_columns: vec!["lat".into()], on_delete: Some(ReferentialAction::Restrict), - on_update: None + on_update: None, }, TableConstraint::ForeignKey { name: Some("fkey2".into()), @@ -2044,7 +2045,7 @@ fn parse_create_table() { foreign_table: ObjectName(vec!["othertable4".into()]), referred_columns: vec!["lat".into()], on_delete: Some(ReferentialAction::NoAction), - on_update: Some(ReferentialAction::Restrict) + on_update: Some(ReferentialAction::Restrict), }, TableConstraint::ForeignKey { name: None, @@ -2052,7 +2053,7 @@ fn parse_create_table() { foreign_table: ObjectName(vec!["othertable4".into()]), referred_columns: vec!["lat".into()], on_delete: Some(ReferentialAction::Cascade), - on_update: Some(ReferentialAction::SetDefault) + on_update: Some(ReferentialAction::SetDefault), }, TableConstraint::ForeignKey { name: None, @@ -2060,7 +2061,7 @@ fn parse_create_table() { foreign_table: ObjectName(vec!["othertable4".into()]), referred_columns: vec!["longitude".into()], on_delete: None, - on_update: Some(ReferentialAction::SetNull) + on_update: Some(ReferentialAction::SetNull), }, ] ); @@ -2339,11 +2340,11 @@ fn parse_create_table_with_options() { vec![ SqlOption { name: "foo".into(), - value: Value::SingleQuotedString("bar".into()) + value: Value::SingleQuotedString("bar".into()), }, SqlOption { name: "a".into(), - value: number("123") + value: number("123"), }, ], with_options @@ -2406,12 +2407,12 @@ fn parse_create_external_table() { name: "name".into(), data_type: DataType::Varchar(Some(CharacterLength { length: 100, - unit: None + unit: None, })), collation: None, options: vec![ColumnOptionDef { name: None, - option: ColumnOption::NotNull + option: ColumnOption::NotNull, }], }, ColumnDef { @@ -2420,7 +2421,7 @@ fn parse_create_external_table() { collation: None, options: vec![ColumnOptionDef { name: None, - option: ColumnOption::Null + option: ColumnOption::Null, }], }, ColumnDef { @@ -2477,12 +2478,12 @@ fn parse_create_or_replace_external_table() { name: "name".into(), data_type: DataType::Varchar(Some(CharacterLength { length: 100, - unit: None + unit: None, })), collation: None, options: vec![ColumnOptionDef { name: None, - option: ColumnOption::NotNull + option: ColumnOption::NotNull, }], },] ); @@ -3020,7 +3021,7 @@ fn parse_literal_date() { assert_eq!( &Expr::TypedString { data_type: DataType::Date, - value: "1999-01-01".into() + value: "1999-01-01".into(), }, expr_from_projection(only(&select.projection)), ); @@ -3033,7 +3034,7 @@ fn parse_literal_time() { assert_eq!( &Expr::TypedString { data_type: DataType::Time(TimezoneInfo::None), - value: "01:23:34".into() + value: "01:23:34".into(), }, expr_from_projection(only(&select.projection)), ); @@ -3046,7 +3047,7 @@ fn parse_literal_datetime() { assert_eq!( &Expr::TypedString { data_type: DataType::Datetime, - value: "1999-01-01 01:23:34.45".into() + value: "1999-01-01 01:23:34.45".into(), }, expr_from_projection(only(&select.projection)), ); @@ -3059,7 +3060,7 @@ fn parse_literal_timestamp_without_time_zone() { assert_eq!( &Expr::TypedString { data_type: DataType::Timestamp(TimezoneInfo::None), - value: "1999-01-01 01:23:34".into() + value: "1999-01-01 01:23:34".into(), }, expr_from_projection(only(&select.projection)), ); @@ -3074,7 +3075,7 @@ fn parse_literal_timestamp_with_time_zone() { assert_eq!( &Expr::TypedString { data_type: DataType::Timestamp(TimezoneInfo::Tz), - value: "1999-01-01 01:23:34Z".into() + value: "1999-01-01 01:23:34Z".into(), }, expr_from_projection(only(&select.projection)), ); @@ -3158,7 +3159,7 @@ fn parse_interval() { value: Box::new(Expr::BinaryOp { left: Box::new(Expr::Value(number("1"))), op: BinaryOperator::Plus, - right: Box::new(Expr::Value(number("1"))) + right: Box::new(Expr::Value(number("1"))), }), leading_field: Some(DateTimeField::Day), leading_precision: None, @@ -3239,14 +3240,14 @@ fn parse_at_timezone() { timestamp: Box::new(Expr::Function(Function { name: ObjectName(vec![Ident { value: "FROM_UNIXTIME".to_string(), - quote_style: None + quote_style: None, }]), args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero.clone()))], over: None, distinct: false, special: false, })), - time_zone: "UTC-06:00".to_string() + time_zone: "UTC-06:00".to_string(), }, expr_from_projection(only(&select.projection)), ); @@ -3267,24 +3268,24 @@ fn parse_at_timezone() { value: "FROM_UNIXTIME".to_string(), quote_style: None, },],), - args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero,),),], + args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero))], over: None, distinct: false, - special: false + special: false, },)), time_zone: "UTC-06:00".to_string(), },),), FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( - Value::SingleQuotedString("%Y-%m-%dT%H".to_string(),), + Value::SingleQuotedString("%Y-%m-%dT%H".to_string()), ),),), ], over: None, distinct: false, - special: false + special: false, },), alias: Ident { value: "hour".to_string(), - quote_style: Some('"',), + quote_style: Some('"'), }, }, only(&select.projection), @@ -3479,7 +3480,7 @@ fn parse_delimited_identifiers() { assert_eq!( &Expr::CompoundIdentifier(vec![ Ident::with_quote('"', "alias"), - Ident::with_quote('"', "bar baz") + Ident::with_quote('"', "bar baz"), ]), expr_from_projection(&select.projection[0]), ); @@ -3516,14 +3517,14 @@ fn parse_parens() { left: Box::new(Nested(Box::new(BinaryOp { left: Box::new(Identifier(Ident::new("a"))), op: Plus, - right: Box::new(Identifier(Ident::new("b"))) + right: Box::new(Identifier(Ident::new("b"))), }))), op: Minus, right: Box::new(Nested(Box::new(BinaryOp { left: Box::new(Identifier(Ident::new("c"))), op: Plus, - right: Box::new(Identifier(Ident::new("d"))) - }))) + right: Box::new(Identifier(Ident::new("d"))), + }))), }, verified_expr(sql) ); @@ -3543,22 +3544,22 @@ fn parse_searched_case_expr() { BinaryOp { left: Box::new(Identifier(Ident::new("bar"))), op: Eq, - right: Box::new(Expr::Value(number("0"))) + right: Box::new(Expr::Value(number("0"))), }, BinaryOp { left: Box::new(Identifier(Ident::new("bar"))), op: GtEq, - right: Box::new(Expr::Value(number("0"))) - } + right: Box::new(Expr::Value(number("0"))), + }, ], results: vec![ Expr::Value(Value::SingleQuotedString("null".to_string())), Expr::Value(Value::SingleQuotedString("=0".to_string())), - Expr::Value(Value::SingleQuotedString(">=0".to_string())) + Expr::Value(Value::SingleQuotedString(">=0".to_string())), ], else_result: Some(Box::new(Expr::Value(Value::SingleQuotedString( "<0".to_string() - )))) + )))), }, expr_from_projection(only(&select.projection)), ); @@ -3574,10 +3575,10 @@ fn parse_simple_case_expr() { &Case { operand: Some(Box::new(Identifier(Ident::new("foo")))), conditions: vec![Expr::Value(number("1"))], - results: vec![Expr::Value(Value::SingleQuotedString("Y".to_string())),], + results: vec![Expr::Value(Value::SingleQuotedString("Y".to_string()))], else_result: Some(Box::new(Expr::Value(Value::SingleQuotedString( "N".to_string() - )))) + )))), }, expr_from_projection(only(&select.projection)), ); @@ -3618,7 +3619,7 @@ fn parse_implicit_join() { with_hints: vec![], }, joins: vec![], - } + }, ], select.from, ); @@ -3642,7 +3643,7 @@ fn parse_implicit_join() { with_hints: vec![], }, join_operator: JoinOperator::Inner(JoinConstraint::Natural), - }] + }], }, TableWithJoins { relation: TableFactor::Table { @@ -3659,8 +3660,8 @@ fn parse_implicit_join() { with_hints: vec![], }, join_operator: JoinOperator::Inner(JoinConstraint::Natural), - }] - } + }], + }, ], select.from, ); @@ -3678,7 +3679,7 @@ fn parse_cross_join() { args: None, with_hints: vec![], }, - join_operator: JoinOperator::CrossJoin + join_operator: JoinOperator::CrossJoin, }, only(only(select.from).joins), ); @@ -3711,7 +3712,7 @@ fn parse_joins_on() { vec![join_with_constraint( "t2", table_alias("foo"), - JoinOperator::Inner + JoinOperator::Inner, )] ); one_statement_parses_to( @@ -3760,7 +3761,7 @@ fn parse_joins_using() { vec![join_with_constraint( "t2", table_alias("foo"), - JoinOperator::Inner + JoinOperator::Inner, )] ); one_statement_parses_to( @@ -3850,7 +3851,7 @@ fn parse_join_nesting() { only(&verified_only_select(sql).from).joins, vec![ join(nest!(table("b"), nest!(table("c"), table("d"), table("e")))), - join(nest!(table("f"), nest!(table("g"), table("h")))) + join(nest!(table("f"), nest!(table("g"), table("h")))), ], ); @@ -3885,7 +3886,7 @@ fn parse_join_nesting() { relation: table("a"), joins: vec![join(table("b"))], }), - alias: table_alias("c") + alias: table_alias("c"), } ); assert_eq!(from.joins, vec![]); @@ -4054,7 +4055,7 @@ fn parse_derived_tables() { alias: Some(TableAlias { name: "t1".into(), columns: vec![], - }) + }), }, joins: vec![Join { relation: TableFactor::Table { @@ -4066,7 +4067,7 @@ fn parse_derived_tables() { join_operator: JoinOperator::Inner(JoinConstraint::Natural), }], }), - alias: None + alias: None, } ); } @@ -4192,7 +4193,7 @@ fn parse_overlay() { left: Box::new(Expr::Identifier(Ident::new("id"))), op: BinaryOperator::Plus, right: Box::new(Expr::Value(number("1"))), - })) + })), }, expr_from_projection(only(&select.projection)) ); @@ -4239,7 +4240,7 @@ fn parse_exists_subquery() { assert_eq!( Expr::Exists { negated: false, - subquery: Box::new(expected_inner.clone()) + subquery: Box::new(expected_inner.clone()), }, select.selection.unwrap(), ); @@ -4249,7 +4250,7 @@ fn parse_exists_subquery() { assert_eq!( Expr::Exists { negated: true, - subquery: Box::new(expected_inner) + subquery: Box::new(expected_inner), }, select.selection.unwrap(), ); @@ -4344,11 +4345,11 @@ fn parse_create_view_with_options() { vec![ SqlOption { name: "foo".into(), - value: Value::SingleQuotedString("bar".into()) + value: Value::SingleQuotedString("bar".into()), }, SqlOption { name: "a".into(), - value: number("123") + value: number("123"), }, ], with_options @@ -4380,6 +4381,7 @@ fn parse_create_view_with_columns() { _ => unreachable!(), } } + #[test] fn parse_create_or_replace_view() { let sql = "CREATE OR REPLACE VIEW v AS SELECT 1"; @@ -4985,6 +4987,7 @@ fn parse_create_index() { _ => unreachable!(), } } + #[test] fn parse_drop_index() { let sql = "DROP INDEX idx_a"; @@ -5080,12 +5083,12 @@ fn parse_grant() { columns: Some(vec![ Ident { value: "shape".into(), - quote_style: None + quote_style: None, }, Ident { value: "size".into(), - quote_style: None - } + quote_style: None, + }, ]) }, Action::Usage, @@ -5259,10 +5262,10 @@ fn parse_merge() { name: ObjectName(vec![Ident::new("s"), Ident::new("bar")]), alias: Some(TableAlias { name: Ident::new("dest"), - columns: vec![] + columns: vec![], }), args: None, - with_hints: vec![] + with_hints: vec![], } ); assert_eq!(table, table_no_into); @@ -5283,9 +5286,9 @@ fn parse_merge() { name: ObjectName(vec![Ident::new("s"), Ident::new("foo")]), alias: None, args: None, - with_hints: vec![] + with_hints: vec![], }, - joins: vec![] + joins: vec![], }], lateral_views: vec![], selection: None, @@ -5300,15 +5303,15 @@ fn parse_merge() { limit: None, offset: None, fetch: None, - lock: None + lock: None, }), alias: Some(TableAlias { name: Ident { value: "stg".to_string(), - quote_style: None + quote_style: None, }, - columns: vec![] - }) + columns: vec![], + }), } ); assert_eq!(source, source_no_into); @@ -5319,26 +5322,26 @@ fn parse_merge() { left: Box::new(Expr::BinaryOp { left: Box::new(Expr::CompoundIdentifier(vec![ Ident::new("dest"), - Ident::new("D") + Ident::new("D"), ])), op: BinaryOperator::Eq, right: Box::new(Expr::CompoundIdentifier(vec![ Ident::new("stg"), - Ident::new("D") - ])) + Ident::new("D"), + ])), }), op: BinaryOperator::And, right: Box::new(Expr::BinaryOp { left: Box::new(Expr::CompoundIdentifier(vec![ Ident::new("dest"), - Ident::new("E") + Ident::new("E"), ])), op: BinaryOperator::Eq, right: Box::new(Expr::CompoundIdentifier(vec![ Ident::new("stg"), - Ident::new("E") - ])) - }) + Ident::new("E"), + ])), + }), }) ); assert_eq!(on, on_no_into); @@ -5353,37 +5356,37 @@ fn parse_merge() { Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("A")]), Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("B")]), Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("C")]), - ]]) + ]]), }, MergeClause::MatchedUpdate { predicate: Some(Expr::BinaryOp { left: Box::new(Expr::CompoundIdentifier(vec![ Ident::new("dest"), - Ident::new("A") + Ident::new("A"), ])), op: BinaryOperator::Eq, right: Box::new(Expr::Value(Value::SingleQuotedString( "a".to_string() - ))) + ))), }), assignments: vec![ Assignment { id: vec![Ident::new("dest"), Ident::new("F")], value: Expr::CompoundIdentifier(vec![ Ident::new("stg"), - Ident::new("F") - ]) + Ident::new("F"), + ]), }, Assignment { id: vec![Ident::new("dest"), Ident::new("G")], value: Expr::CompoundIdentifier(vec![ Ident::new("stg"), - Ident::new("G") - ]) - } - ] + Ident::new("G"), + ]), + }, + ], }, - MergeClause::MatchedDelete(None) + MergeClause::MatchedDelete(None), ] ); assert_eq!(clauses, clauses_no_into); @@ -5439,7 +5442,7 @@ fn test_placeholder() { Some(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("id"))), op: BinaryOperator::Eq, - right: Box::new(Expr::Value(Value::Placeholder("?".into()))) + right: Box::new(Expr::Value(Value::Placeholder("?".into()))), }) ); @@ -5462,7 +5465,7 @@ fn test_placeholder() { Some(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("id"))), op: BinaryOperator::Eq, - right: Box::new(Expr::Value(Value::Placeholder("$Id1".into()))) + right: Box::new(Expr::Value(Value::Placeholder("$Id1".into()))), }) ); @@ -5810,3 +5813,249 @@ fn parse_show_functions() { } ); } + +#[test] +fn parse_cache_table() { + let sql = "SELECT a, b, c FROM foo"; + let cache_table_name = "cache_table_name"; + let table_flag = "flag"; + let query = all_dialects().verified_query(sql); + + assert_eq!( + verified_stmt( + format!("CACHE TABLE '{table_name}'", table_name = cache_table_name).as_str() + ), + Statement::Cache { + table_flag: None, + table_name: ObjectName(vec![Ident::with_quote('\'', cache_table_name)]), + has_as: false, + options: vec![], + query: None, + } + ); + + assert_eq!( + verified_stmt( + format!( + "CACHE {flag} TABLE '{table_name}'", + flag = table_flag, + table_name = cache_table_name + ) + .as_str() + ), + Statement::Cache { + table_flag: Some(ObjectName(vec![Ident::new(table_flag)])), + table_name: ObjectName(vec![Ident::with_quote('\'', cache_table_name)]), + has_as: false, + options: vec![], + query: None, + } + ); + + assert_eq!( + verified_stmt( + format!( + "CACHE {flag} TABLE '{table_name}' OPTIONS('K1' = 'V1', 'K2' = 0.88)", + flag = table_flag, + table_name = cache_table_name, + ) + .as_str() + ), + Statement::Cache { + table_flag: Some(ObjectName(vec![Ident::new(table_flag)])), + table_name: ObjectName(vec![Ident::with_quote('\'', cache_table_name)]), + has_as: false, + options: vec![ + SqlOption { + name: Ident::with_quote('\'', "K1"), + value: Value::SingleQuotedString("V1".into()), + }, + SqlOption { + name: Ident::with_quote('\'', "K2"), + value: number("0.88"), + }, + ], + query: None, + } + ); + + assert_eq!( + verified_stmt( + format!( + "CACHE {flag} TABLE '{table_name}' OPTIONS('K1' = 'V1', 'K2' = 0.88) {sql}", + flag = table_flag, + table_name = cache_table_name, + sql = sql, + ) + .as_str() + ), + Statement::Cache { + table_flag: Some(ObjectName(vec![Ident::new(table_flag)])), + table_name: ObjectName(vec![Ident::with_quote('\'', cache_table_name)]), + has_as: false, + options: vec![ + SqlOption { + name: Ident::with_quote('\'', "K1"), + value: Value::SingleQuotedString("V1".into()), + }, + SqlOption { + name: Ident::with_quote('\'', "K2"), + value: number("0.88"), + }, + ], + query: Some(query.clone()), + } + ); + + assert_eq!( + verified_stmt( + format!( + "CACHE {flag} TABLE '{table_name}' OPTIONS('K1' = 'V1', 'K2' = 0.88) AS {sql}", + flag = table_flag, + table_name = cache_table_name, + sql = sql, + ) + .as_str() + ), + Statement::Cache { + table_flag: Some(ObjectName(vec![Ident::new(table_flag)])), + table_name: ObjectName(vec![Ident::with_quote('\'', cache_table_name)]), + has_as: true, + options: vec![ + SqlOption { + name: Ident::with_quote('\'', "K1"), + value: Value::SingleQuotedString("V1".into()), + }, + SqlOption { + name: Ident::with_quote('\'', "K2"), + value: number("0.88"), + }, + ], + query: Some(query.clone()), + } + ); + + assert_eq!( + verified_stmt( + format!( + "CACHE {flag} TABLE '{table_name}' {sql}", + flag = table_flag, + table_name = cache_table_name, + sql = sql + ) + .as_str() + ), + Statement::Cache { + table_flag: Some(ObjectName(vec![Ident::new(table_flag)])), + table_name: ObjectName(vec![Ident::with_quote('\'', cache_table_name)]), + has_as: false, + options: vec![], + query: Some(query.clone()), + } + ); + + assert_eq!( + verified_stmt( + format!( + "CACHE {flag} TABLE '{table_name}' AS {sql}", + flag = table_flag, + table_name = cache_table_name + ) + .as_str() + ), + Statement::Cache { + table_flag: Some(ObjectName(vec![Ident::new(table_flag)])), + table_name: ObjectName(vec![Ident::with_quote('\'', cache_table_name)]), + has_as: true, + options: vec![], + query: Some(query), + } + ); + + let res = parse_sql_statements("CACHE TABLE 'table_name' foo"); + assert_eq!( + ParserError::ParserError( + "Expected SELECT, VALUES, or a subquery in the query body, found: foo".to_string() + ), + res.unwrap_err() + ); + + let res = parse_sql_statements("CACHE flag TABLE 'table_name' OPTIONS('K1'='V1') foo"); + assert_eq!( + ParserError::ParserError( + "Expected SELECT, VALUES, or a subquery in the query body, found: foo".to_string() + ), + res.unwrap_err() + ); + + let res = parse_sql_statements("CACHE TABLE 'table_name' AS foo"); + assert_eq!( + ParserError::ParserError( + "Expected SELECT, VALUES, or a subquery in the query body, found: foo".to_string() + ), + res.unwrap_err() + ); + + let res = parse_sql_statements("CACHE flag TABLE 'table_name' OPTIONS('K1'='V1') AS foo"); + assert_eq!( + ParserError::ParserError( + "Expected SELECT, VALUES, or a subquery in the query body, found: foo".to_string() + ), + res.unwrap_err() + ); + + let res = parse_sql_statements("CACHE 'table_name'"); + assert_eq!( + ParserError::ParserError("Expected a `TABLE` keyword, found: 'table_name'".to_string()), + res.unwrap_err() + ); + + let res = parse_sql_statements("CACHE 'table_name' OPTIONS('K1'='V1')"); + assert_eq!( + ParserError::ParserError("Expected a `TABLE` keyword, found: OPTIONS".to_string()), + res.unwrap_err() + ); + + let res = parse_sql_statements("CACHE flag 'table_name' OPTIONS('K1'='V1')"); + assert_eq!( + ParserError::ParserError("Expected a `TABLE` keyword, found: 'table_name'".to_string()), + res.unwrap_err() + ); +} + +#[test] +fn parse_uncache_table() { + assert_eq!( + verified_stmt("UNCACHE TABLE 'table_name'"), + Statement::UNCache { + table_name: ObjectName(vec![Ident::with_quote('\'', "table_name")]), + if_exists: false, + } + ); + + assert_eq!( + verified_stmt("UNCACHE TABLE IF EXISTS 'table_name'"), + Statement::UNCache { + table_name: ObjectName(vec![Ident::with_quote('\'', "table_name")]), + if_exists: true, + } + ); + + let res = parse_sql_statements("UNCACHE TABLE 'table_name' foo"); + assert_eq!( + ParserError::ParserError("Expected an `EOF`, found: foo".to_string()), + res.unwrap_err() + ); + + let res = parse_sql_statements("UNCACHE 'table_name' foo"); + assert_eq!( + ParserError::ParserError("Expected a `TABLE` keyword, found: 'table_name'".to_string()), + res.unwrap_err() + ); + + let res = parse_sql_statements("UNCACHE IF EXISTS 'table_name' foo"); + assert_eq!( + ParserError::ParserError("Expected a `TABLE` keyword, found: IF".to_string()), + res.unwrap_err() + ); +}