Add SQLite "ON CONFLICT" column option in CREATE TABLE statements (#1442)

Co-authored-by: hulk <hulk.website@gmail.com>
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
This commit is contained in:
nucccc 2024-10-07 22:20:55 +02:00 committed by GitHub
parent 84348d483e
commit 8badcdc200
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 62 additions and 0 deletions

View file

@ -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
@ -1186,6 +1187,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<IdentityProperty>),
/// SQLite specific: ON CONFLICT option on column definition
/// <https://www.sqlite.org/lang_conflict.html>
OnConflict(Keyword),
}
impl fmt::Display for ColumnOption {
@ -1294,6 +1298,10 @@ impl fmt::Display for ColumnOption {
}
Ok(())
}
OnConflict(keyword) => {
write!(f, "ON CONFLICT {:?}", keyword)?;
Ok(())
}
}
}
}

View file

@ -6224,6 +6224,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)
}

View file

@ -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)");