diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 4d8b3b5bf..68c3a71d2 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -377,6 +377,9 @@ pub enum ColumnOption { DialectSpecific(Vec), CharacterSet(ObjectName), Comment(String), + DeferrableInitiallyDeferred, + DeferrableInitiallyImmediate, + NotDeferrable, } impl fmt::Display for ColumnOption { @@ -411,6 +414,9 @@ impl fmt::Display for ColumnOption { DialectSpecific(val) => write!(f, "{}", display_separated(val, " ")), CharacterSet(n) => write!(f, "CHARACTER SET {}", n), Comment(v) => write!(f, "COMMENT '{}'", escape_single_quote_string(v)), + DeferrableInitiallyDeferred => write!(f, "DEFERRABLE INITIALLY DEFERRED"), + DeferrableInitiallyImmediate => write!(f, "DEFERRABLE INITIALLY IMMEDIATE"), + NotDeferrable => write!(f, "NOT DEFERRABLE"), } } } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 3fe1945b1..89e135756 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -291,6 +291,11 @@ pub enum Expr { field: DateTimeField, expr: Box, }, + /// POSITION( in ) + Position { + expr: Box, + r#in: Box, + }, /// SUBSTRING( [FROM ] [FOR ]) Substring { expr: Box, @@ -438,6 +443,7 @@ impl fmt::Display for Expr { Expr::Cast { expr, data_type } => write!(f, "CAST({} AS {})", expr, data_type), Expr::TryCast { expr, data_type } => write!(f, "TRY_CAST({} AS {})", expr, data_type), Expr::Extract { field, expr } => write!(f, "EXTRACT({} FROM {})", field, expr), + Expr::Position { expr, r#in } => write!(f, "POSITION({} IN {})", expr, r#in), Expr::Collate { expr, collation } => write!(f, "{} COLLATE {}", expr, collation), Expr::Nested(ast) => write!(f, "({})", ast), Expr::Value(v) => write!(f, "{}", v), diff --git a/src/keywords.rs b/src/keywords.rs index ad31bda5c..2c8011eea 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -174,6 +174,8 @@ define_keywords!( DECIMAL, DECLARE, DEFAULT, + DEFERRABLE, + DEFERRED, DELETE, DELIMITED, DELIMITER, @@ -257,9 +259,11 @@ define_keywords!( IF, IGNORE, ILIKE, + IMMEDIATE, IN, INDEX, INDICATOR, + INITIALLY, INNER, INOUT, INPUTFORMAT, diff --git a/src/parser.rs b/src/parser.rs index b93680077..1711821ed 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -423,6 +423,7 @@ impl<'a> Parser<'a> { Keyword::TRY_CAST => self.parse_try_cast_expr(), Keyword::EXISTS => self.parse_exists_expr(), Keyword::EXTRACT => self.parse_extract_expr(), + Keyword::POSITION => self.parse_position_expr(), Keyword::SUBSTRING => self.parse_substring_expr(), Keyword::TRIM => self.parse_trim_expr(), Keyword::INTERVAL => self.parse_literal_interval(), @@ -779,6 +780,24 @@ impl<'a> Parser<'a> { }) } + pub fn parse_position_expr(&mut self) -> Result { + // PARSE SELECT POSITION('@' in field) + self.expect_token(&Token::LParen)?; + + // Parse the subexpr till the IN keyword + let expr = self.parse_subexpr(Self::BETWEEN_PREC)?; + if self.parse_keyword(Keyword::IN) { + let from = self.parse_expr()?; + self.expect_token(&Token::RParen)?; + Ok(Expr::Position { + expr: Box::new(expr), + r#in: Box::new(from), + }) + } else { + return parser_err!("Position function must include IN keyword".to_string()); + } + } + pub fn parse_substring_expr(&mut self) -> Result { // PARSE SUBSTRING (EXPR [FROM 1] [FOR 3]) self.expect_token(&Token::LParen)?; @@ -1975,6 +1994,17 @@ impl<'a> Parser<'a> { Ok(Some(ColumnOption::Null)) } else if self.parse_keyword(Keyword::DEFAULT) { Ok(Some(ColumnOption::Default(self.parse_expr()?))) + } else if self.parse_keywords(&[Keyword::NOT, Keyword::DEFERRABLE]) { + Ok(Some(ColumnOption::NotDeferrable)) + } else if self.parse_keywords(&[Keyword::DEFERRABLE, Keyword::INITIALLY, Keyword::DEFERRED]) + { + Ok(Some(ColumnOption::DeferrableInitiallyDeferred)) + } else if self.parse_keywords(&[ + Keyword::DEFERRABLE, + Keyword::INITIALLY, + Keyword::IMMEDIATE, + ]) { + Ok(Some(ColumnOption::DeferrableInitiallyImmediate)) } else if self.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) { Ok(Some(ColumnOption::Unique { is_primary: true })) } else if self.parse_keyword(Keyword::UNIQUE) { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 3d7b20e94..27d35ff49 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -951,6 +951,17 @@ fn parse_in_unnest() { chk(true); } +#[test] +fn parse_in_error() { + // IN is no valid + let sql = "SELECT * FROM customers WHERE segment in segment"; + let res = parse_sql_statements(sql); + assert_eq!( + ParserError::ParserError("Expected (, found: segment".to_string()), + res.unwrap_err() + ); +} + #[test] fn parse_string_agg() { let sql = "SELECT a || b"; @@ -1764,6 +1775,48 @@ fn parse_drop_schema() { } } +#[test] +fn parse_deferrable_column_options() { + let sql_deferrable_deferred = r#" + CREATE TABLE test_table (fkey_id INT NOT NULL REFERENCES other_table (id) DEFERRABLE INITIALLY DEFERRED)"#; + + match verified_stmt(sql_deferrable_deferred.trim()) { + Statement::CreateTable { columns: c, .. } => { + assert_eq!( + vec![ColumnDef { + name: Ident { + value: "fkey_id".into(), + quote_style: None, + }, + data_type: DataType::Int(None), + collation: None, + options: vec![ + ColumnOptionDef { + name: None, + option: ColumnOption::NotNull + }, + ColumnOptionDef { + name: None, + option: ColumnOption::ForeignKey { + foreign_table: ObjectName(vec!["other_table".into()]), + referred_columns: vec!["id".into()], + on_delete: None, + on_update: None, + } + }, + ColumnOptionDef { + name: None, + option: ColumnOption::DeferrableInitiallyDeferred + } + ] + }], + c, + ); + } + _ => unreachable!(), + } +} + #[test] fn parse_create_table_as() { let sql = "CREATE TABLE t AS SELECT * FROM a"; @@ -4577,3 +4630,33 @@ fn parse_time_functions() { // Validating Parenthesis one_statement_parses_to("SELECT CURRENT_DATE", sql); } + +#[test] +fn parse_position() { + let sql = "SELECT POSITION('@' IN field)"; + let select = verified_only_select(sql); + assert_eq!( + &Expr::Position { + expr: Box::new(Expr::Value(Value::SingleQuotedString("@".to_string()))), + r#in: Box::new(Expr::Identifier(Ident::new("field"))), + }, + expr_from_projection(only(&select.projection)) + ); +} + +#[test] +fn parse_position_negative() { + let sql = "SELECT POSITION(foo) from bar"; + let res = parse_sql_statements(sql); + assert_eq!( + ParserError::ParserError("Position function must include IN keyword".to_string()), + res.unwrap_err() + ); + + let sql = "SELECT POSITION(foo IN) from bar"; + let res = parse_sql_statements(sql); + assert_eq!( + ParserError::ParserError("Expected an expression:, found: )".to_string()), + res.unwrap_err() + ); +}