Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<IdentityProperty>),
/// Sqlite specific: ON CONFLICT option on column definition
/// https://www.sqlite.org/lang_conflict.html
OnConflict(Keyword),
}

impl fmt::Display for ColumnOption {
Expand Down Expand Up @@ -1254,6 +1258,10 @@ impl fmt::Display for ColumnOption {
}
Ok(())
}
OnConflict(keyword) => {
write!(f, "ON CONFLICT {:?}", keyword)?;
Ok(())
}
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6083,6 +6083,24 @@ impl<'a> Parser<'a> {
None
};
Ok(Some(ColumnOption::Identity(property)))
} else if dialect_of!(self is SQLiteDialect)
&& self.parse_keywords(&[Keyword::ON, Keyword::CONFLICT])
{
// Support ON CONFLICT for SQLite
let on_confl = self.parse_one_of_keywords(&[
Keyword::ROLLBACK,
Keyword::ABORT,
Keyword::FAIL,
Keyword::IGNORE,
Keyword::REPLACE,
]);
match on_confl {
Some(keyword) => Ok(Some(ColumnOption::OnConflict(keyword))),
_ => self.expected(
"one of ROLLBACK, ABORT, FAIL, IGNORE or REPLACE",
self.peek_token(),
),
}
} else {
Ok(None)
}
Expand Down
98 changes: 98 additions & 0 deletions tests/sqlparser_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,104 @@ 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() {
let sql_rollback = "CREATE TABLE t1 (a INT, b INT ON CONFLICT ROLLBACK)";
match sqlite().verified_stmt(sql_rollback) {
Statement::CreateTable(CreateTable { name, columns, .. }) => {
assert_eq!(name.to_string(), "t1");
assert_eq!(
vec![
ColumnDef {
name: Ident::new("a"),
data_type: DataType::Int(None),
collation: None,
options: vec![],
},
ColumnDef {
name: Ident::new("b"), //::with_quote('[', "BINDEX"),
data_type: DataType::Int(None),
collation: None,
options: vec![ColumnOptionDef {
name: None,
option: ColumnOption::OnConflict(
sqlparser::keywords::Keyword::ROLLBACK
),
}],
},
],
columns
);
}
_ => unreachable!(),
}

let sql_abort = "CREATE TABLE t1 (a INT, b INT ON CONFLICT ABORT)";
match sqlite().verified_stmt(sql_abort) {
Statement::CreateTable(CreateTable { columns, .. }) => {
assert_eq!(
vec![ColumnOptionDef {
name: None,
option: ColumnOption::OnConflict(sqlparser::keywords::Keyword::ABORT),
}],
columns[1].options
);
}
_ => unreachable!(),
}

let sql_fail = "CREATE TABLE t1 (a INT, b INT ON CONFLICT FAIL)";
match sqlite().verified_stmt(sql_fail) {
Statement::CreateTable(CreateTable { columns, .. }) => {
assert_eq!(
vec![ColumnOptionDef {
name: None,
option: ColumnOption::OnConflict(sqlparser::keywords::Keyword::FAIL),
}],
columns[1].options
);
}
_ => unreachable!(),
}

let sql_ignore = "CREATE TABLE t1 (a INT, b INT ON CONFLICT IGNORE)";
match sqlite().verified_stmt(sql_ignore) {
Statement::CreateTable(CreateTable { columns, .. }) => {
assert_eq!(
vec![ColumnOptionDef {
name: None,
option: ColumnOption::OnConflict(sqlparser::keywords::Keyword::IGNORE),
}],
columns[1].options
);
}
_ => unreachable!(),
}

let sql_replace = "CREATE TABLE t1 (a INT, b INT ON CONFLICT REPLACE)";
match sqlite().verified_stmt(sql_replace) {
Statement::CreateTable(CreateTable { columns, .. }) => {
assert_eq!(
vec![ColumnOptionDef {
name: None,
option: ColumnOption::OnConflict(sqlparser::keywords::Keyword::REPLACE),
}],
columns[1].options
);
}
_ => unreachable!(),
}

let sql_err = "CREATE TABLE t1 (a INT, b INT ON CONFLICT BOH)";
assert_eq!(
"sql parser error: Expected: one of ROLLBACK, ABORT, FAIL, IGNORE or REPLACE, found: BOH",
sqlite()
.parse_sql_statements(sql_err)
.unwrap_err()
.to_string()
);
}

#[test]
fn parse_create_table_untyped() {
sqlite().verified_stmt("CREATE TABLE t1 (a, b AS (a * 2), c NOT NULL)");
Expand Down