diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index e0441d07a..6fb6237d9 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -33,6 +33,7 @@ use crate::ast::{ display_comma_separated, display_separated, DataType, Expr, Ident, MySQLColumnPosition, ObjectName, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Value, }; +use crate::keywords::Keyword; use crate::tokenizer::Token; /// An `ALTER TABLE` (`Statement::AlterTable`) operation @@ -1146,6 +1147,9 @@ pub enum ColumnOption { /// ``` /// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property Identity(Option), + /// SQLite specific: ON CONFLICT option on column definition + /// + OnConflict(Keyword), } impl fmt::Display for ColumnOption { @@ -1254,6 +1258,10 @@ impl fmt::Display for ColumnOption { } Ok(()) } + OnConflict(keyword) => { + write!(f, "ON CONFLICT {:?}", keyword)?; + Ok(()) + } } } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 5d57347cf..5ff2cd6bc 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6083,6 +6083,19 @@ impl<'a> Parser<'a> { None }; Ok(Some(ColumnOption::Identity(property))) + } else if dialect_of!(self is SQLiteDialect | GenericDialect) + && self.parse_keywords(&[Keyword::ON, Keyword::CONFLICT]) + { + // Support ON CONFLICT for SQLite + Ok(Some(ColumnOption::OnConflict( + self.expect_one_of_keywords(&[ + Keyword::ROLLBACK, + Keyword::ABORT, + Keyword::FAIL, + Keyword::IGNORE, + Keyword::REPLACE, + ])?, + ))) } else { Ok(None) } diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index d7fd3b896..b5427fb3c 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -22,6 +22,7 @@ #[macro_use] mod test_utils; +use sqlparser::keywords::Keyword; use test_utils::*; use sqlparser::ast::SelectItem::UnnamedExpr; @@ -281,6 +282,46 @@ fn parse_create_table_gencol() { sqlite_and_generic().verified_stmt("CREATE TABLE t1 (a INT, b INT AS (a * 2) STORED)"); } +#[test] +fn parse_create_table_on_conflict_col() { + for keyword in [ + Keyword::ROLLBACK, + Keyword::ABORT, + Keyword::FAIL, + Keyword::IGNORE, + Keyword::REPLACE, + ] { + let sql = format!("CREATE TABLE t1 (a INT, b INT ON CONFLICT {:?})", keyword); + match sqlite_and_generic().verified_stmt(&sql) { + Statement::CreateTable(CreateTable { columns, .. }) => { + assert_eq!( + vec![ColumnOptionDef { + name: None, + option: ColumnOption::OnConflict(keyword), + }], + columns[1].options + ); + } + _ => unreachable!(), + } + } +} + +#[test] +fn test_parse_create_table_on_conflict_col_err() { + let sql_err = "CREATE TABLE t1 (a INT, b INT ON CONFLICT BOH)"; + let err = sqlite_and_generic() + .parse_sql_statements(sql_err) + .unwrap_err(); + assert_eq!( + err, + ParserError::ParserError( + "Expected: one of ROLLBACK or ABORT or FAIL or IGNORE or REPLACE, found: BOH" + .to_string() + ) + ); +} + #[test] fn parse_create_table_untyped() { sqlite().verified_stmt("CREATE TABLE t1 (a, b AS (a * 2), c NOT NULL)");