Skip to content

Commit 09ca14f

Browse files
authored
Support dialect-specific auto-increment column options for MySQL and SQLite (#234)
In MySQL it's AUTO_INCREMENT (see https://dev.mysql.com/doc/refman/8.0/en/create-table.html) and in SQLite it's AUTOINCREMENT. We use `ColumnOption::DialectSpecific(Vec<Token>)` to avoid adding a new variant for each vendor-specific column option.
1 parent 8020b2e commit 09ca14f

File tree

7 files changed

+95
-5
lines changed

7 files changed

+95
-5
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ Check https://github.com/ballista-compute/sqlparser-rs/commits/main for undocume
1111
### Changed
1212

1313
### Added
14+
- Support `CREATE OR REPLACE VIEW`/`TABLE` (#239) - thanks @Dandandan!
15+
- Support PostgreSQL `PREPARE`, `EXECUTE`, and `DEALLOCATE` (#243) - thanks @silathdiir!
16+
- Support SQLite `AUTOINCREMENT` and MySQL `AUTO_INCREMENT` column option in `CREATE TABLE` - thanks @mashuai!
1417

1518
### Fixed
1619

src/ast/ddl.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
//! AST types specific to CREATE/ALTER variants of [Statement]
1414
//! (commonly referred to as Data Definition Language, or DDL)
1515
use super::{display_comma_separated, DataType, Expr, Ident, ObjectName};
16+
use crate::ast::display_separated;
17+
use crate::tokenizer::Token;
1618
#[cfg(feature = "serde")]
1719
use serde::{Deserialize, Serialize};
1820
use std::fmt;
@@ -212,8 +214,12 @@ pub enum ColumnOption {
212214
on_delete: Option<ReferentialAction>,
213215
on_update: Option<ReferentialAction>,
214216
},
215-
// `CHECK (<expr>)`
217+
/// `CHECK (<expr>)`
216218
Check(Expr),
219+
/// Dialect-specific options, such as:
220+
/// - MySQL's `AUTO_INCREMENT` or SQLite's `AUTOINCREMENT`
221+
/// - ...
222+
DialectSpecific(Vec<Token>),
217223
}
218224

219225
impl fmt::Display for ColumnOption {
@@ -245,6 +251,7 @@ impl fmt::Display for ColumnOption {
245251
Ok(())
246252
}
247253
Check(expr) => write!(f, "CHECK ({})", expr),
254+
DialectSpecific(val) => write!(f, "{}", display_separated(val, " ")),
248255
}
249256
}
250257
}

src/dialect/keywords.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
/// and could be removed.
2424
/// 3) a `RESERVED_FOR_TABLE_ALIAS` array with keywords reserved in a
2525
/// "table alias" context.
26+
#[cfg(feature = "serde")]
27+
use serde::{Deserialize, Serialize};
2628

2729
/// Defines a string constant for a single keyword: `kw_def!(SELECT);`
2830
/// expands to `pub const SELECT = "SELECT";`
@@ -41,7 +43,8 @@ macro_rules! define_keywords {
4143
($(
4244
$ident:ident $(= $string_keyword:expr)?
4345
),*) => {
44-
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
46+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
47+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
4548
#[allow(non_camel_case_types)]
4649
pub enum Keyword {
4750
NoKeyword,
@@ -84,6 +87,8 @@ define_keywords!(
8487
AT,
8588
ATOMIC,
8689
AUTHORIZATION,
90+
AUTOINCREMENT,
91+
AUTO_INCREMENT,
8792
AVG,
8893
AVRO,
8994
BEGIN,

src/parser.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,12 @@ impl Parser {
12831283
let expr = self.parse_expr()?;
12841284
self.expect_token(&Token::RParen)?;
12851285
ColumnOption::Check(expr)
1286+
} else if self.parse_keyword(Keyword::AUTO_INCREMENT) {
1287+
// Support AUTO_INCREMENT for MySQL
1288+
ColumnOption::DialectSpecific(vec![Token::make_keyword("AUTO_INCREMENT")])
1289+
} else if self.parse_keyword(Keyword::AUTOINCREMENT) {
1290+
// Support AUTOINCREMENT for SQLite
1291+
ColumnOption::DialectSpecific(vec![Token::make_keyword("AUTOINCREMENT")])
12861292
} else {
12871293
return self.expected("column option", self.peek_token());
12881294
};

src/tokenizer.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ use std::str::Chars;
2121

2222
use super::dialect::keywords::{Keyword, ALL_KEYWORDS, ALL_KEYWORDS_INDEX};
2323
use super::dialect::Dialect;
24+
#[cfg(feature = "serde")]
25+
use serde::{Deserialize, Serialize};
2426
use std::fmt;
2527

2628
/// SQL Token enumeration
27-
#[derive(Debug, Clone, PartialEq)]
29+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2831
pub enum Token {
2932
/// An end-of-file marker, not a real token
3033
EOF,
@@ -160,7 +163,8 @@ impl Token {
160163
}
161164

162165
/// A keyword (like SELECT) or an optionally quoted SQL identifier
163-
#[derive(Debug, Clone, PartialEq)]
166+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
167+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
164168
pub struct Word {
165169
/// The value of the token, without the enclosing quotes, and with the
166170
/// escape sequences (if any) processed (TODO: escapes are not handled)
@@ -196,7 +200,8 @@ impl Word {
196200
}
197201
}
198202

199-
#[derive(Debug, Clone, PartialEq)]
203+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
204+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
200205
pub enum Whitespace {
201206
Space,
202207
Newline,

tests/sqlparser_mysql.rs

+32
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use sqlparser::ast::*;
1919
use sqlparser::dialect::{GenericDialect, MySqlDialect};
2020
use sqlparser::test_utils::*;
21+
use sqlparser::tokenizer::Token;
2122

2223
#[test]
2324
fn parse_identifiers() {
@@ -97,6 +98,37 @@ fn parse_show_columns() {
9798
}
9899
}
99100

101+
#[test]
102+
fn parse_create_table_auto_increment() {
103+
let sql = "CREATE TABLE foo (bar INT PRIMARY KEY AUTO_INCREMENT)";
104+
match mysql().verified_stmt(sql) {
105+
Statement::CreateTable { name, columns, .. } => {
106+
assert_eq!(name.to_string(), "foo");
107+
assert_eq!(
108+
vec![ColumnDef {
109+
name: "bar".into(),
110+
data_type: DataType::Int,
111+
collation: None,
112+
options: vec![
113+
ColumnOptionDef {
114+
name: None,
115+
option: ColumnOption::Unique { is_primary: true }
116+
},
117+
ColumnOptionDef {
118+
name: None,
119+
option: ColumnOption::DialectSpecific(vec![Token::make_keyword(
120+
"AUTO_INCREMENT"
121+
)])
122+
}
123+
],
124+
}],
125+
columns
126+
);
127+
}
128+
_ => unreachable!(),
129+
}
130+
}
131+
100132
fn mysql() -> TestedDialects {
101133
TestedDialects {
102134
dialects: vec![Box::new(MySqlDialect {})],

tests/sqlparser_sqlite.rs

+32
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use sqlparser::ast::*;
1818
use sqlparser::dialect::GenericDialect;
1919
use sqlparser::test_utils::*;
20+
use sqlparser::tokenizer::Token;
2021

2122
#[test]
2223
fn parse_create_table_without_rowid() {
@@ -55,6 +56,37 @@ fn parse_create_virtual_table() {
5556
sqlite_and_generic().verified_stmt(sql);
5657
}
5758

59+
#[test]
60+
fn parse_create_table_auto_increment() {
61+
let sql = "CREATE TABLE foo (bar INT PRIMARY KEY AUTOINCREMENT)";
62+
match sqlite_and_generic().verified_stmt(sql) {
63+
Statement::CreateTable { name, columns, .. } => {
64+
assert_eq!(name.to_string(), "foo");
65+
assert_eq!(
66+
vec![ColumnDef {
67+
name: "bar".into(),
68+
data_type: DataType::Int,
69+
collation: None,
70+
options: vec![
71+
ColumnOptionDef {
72+
name: None,
73+
option: ColumnOption::Unique { is_primary: true }
74+
},
75+
ColumnOptionDef {
76+
name: None,
77+
option: ColumnOption::DialectSpecific(vec![Token::make_keyword(
78+
"AUTOINCREMENT"
79+
)])
80+
}
81+
],
82+
}],
83+
columns
84+
);
85+
}
86+
_ => unreachable!(),
87+
}
88+
}
89+
5890
fn sqlite_and_generic() -> TestedDialects {
5991
TestedDialects {
6092
// we don't have a separate SQLite dialect, so test only the generic dialect for now

0 commit comments

Comments
 (0)