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.
This commit is contained in:
mz 2020-07-29 04:34:21 +08:00 committed by GitHub
parent 8020b2e5f0
commit 09ca14fe8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 95 additions and 5 deletions

View file

@ -11,6 +11,9 @@ Check https://github.com/ballista-compute/sqlparser-rs/commits/main for undocume
### Changed ### Changed
### Added ### Added
- Support `CREATE OR REPLACE VIEW`/`TABLE` (#239) - thanks @Dandandan!
- Support PostgreSQL `PREPARE`, `EXECUTE`, and `DEALLOCATE` (#243) - thanks @silathdiir!
- Support SQLite `AUTOINCREMENT` and MySQL `AUTO_INCREMENT` column option in `CREATE TABLE` - thanks @mashuai!
### Fixed ### Fixed

View file

@ -13,6 +13,8 @@
//! AST types specific to CREATE/ALTER variants of [Statement] //! AST types specific to CREATE/ALTER variants of [Statement]
//! (commonly referred to as Data Definition Language, or DDL) //! (commonly referred to as Data Definition Language, or DDL)
use super::{display_comma_separated, DataType, Expr, Ident, ObjectName}; use super::{display_comma_separated, DataType, Expr, Ident, ObjectName};
use crate::ast::display_separated;
use crate::tokenizer::Token;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
@ -212,8 +214,12 @@ pub enum ColumnOption {
on_delete: Option<ReferentialAction>, on_delete: Option<ReferentialAction>,
on_update: Option<ReferentialAction>, on_update: Option<ReferentialAction>,
}, },
// `CHECK (<expr>)` /// `CHECK (<expr>)`
Check(Expr), Check(Expr),
/// Dialect-specific options, such as:
/// - MySQL's `AUTO_INCREMENT` or SQLite's `AUTOINCREMENT`
/// - ...
DialectSpecific(Vec<Token>),
} }
impl fmt::Display for ColumnOption { impl fmt::Display for ColumnOption {
@ -245,6 +251,7 @@ impl fmt::Display for ColumnOption {
Ok(()) Ok(())
} }
Check(expr) => write!(f, "CHECK ({})", expr), Check(expr) => write!(f, "CHECK ({})", expr),
DialectSpecific(val) => write!(f, "{}", display_separated(val, " ")),
} }
} }
} }

View file

@ -23,6 +23,8 @@
/// and could be removed. /// and could be removed.
/// 3) a `RESERVED_FOR_TABLE_ALIAS` array with keywords reserved in a /// 3) a `RESERVED_FOR_TABLE_ALIAS` array with keywords reserved in a
/// "table alias" context. /// "table alias" context.
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Defines a string constant for a single keyword: `kw_def!(SELECT);` /// Defines a string constant for a single keyword: `kw_def!(SELECT);`
/// expands to `pub const SELECT = "SELECT";` /// expands to `pub const SELECT = "SELECT";`
@ -41,7 +43,8 @@ macro_rules! define_keywords {
($( ($(
$ident:ident $(= $string_keyword:expr)? $ident:ident $(= $string_keyword:expr)?
),*) => { ),*) => {
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub enum Keyword { pub enum Keyword {
NoKeyword, NoKeyword,
@ -84,6 +87,8 @@ define_keywords!(
AT, AT,
ATOMIC, ATOMIC,
AUTHORIZATION, AUTHORIZATION,
AUTOINCREMENT,
AUTO_INCREMENT,
AVG, AVG,
AVRO, AVRO,
BEGIN, BEGIN,

View file

@ -1283,6 +1283,12 @@ impl Parser {
let expr = self.parse_expr()?; let expr = self.parse_expr()?;
self.expect_token(&Token::RParen)?; self.expect_token(&Token::RParen)?;
ColumnOption::Check(expr) ColumnOption::Check(expr)
} else if self.parse_keyword(Keyword::AUTO_INCREMENT) {
// Support AUTO_INCREMENT for MySQL
ColumnOption::DialectSpecific(vec![Token::make_keyword("AUTO_INCREMENT")])
} else if self.parse_keyword(Keyword::AUTOINCREMENT) {
// Support AUTOINCREMENT for SQLite
ColumnOption::DialectSpecific(vec![Token::make_keyword("AUTOINCREMENT")])
} else { } else {
return self.expected("column option", self.peek_token()); return self.expected("column option", self.peek_token());
}; };

View file

@ -21,10 +21,13 @@ use std::str::Chars;
use super::dialect::keywords::{Keyword, ALL_KEYWORDS, ALL_KEYWORDS_INDEX}; use super::dialect::keywords::{Keyword, ALL_KEYWORDS, ALL_KEYWORDS_INDEX};
use super::dialect::Dialect; use super::dialect::Dialect;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
/// SQL Token enumeration /// SQL Token enumeration
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Token { pub enum Token {
/// An end-of-file marker, not a real token /// An end-of-file marker, not a real token
EOF, EOF,
@ -160,7 +163,8 @@ impl Token {
} }
/// A keyword (like SELECT) or an optionally quoted SQL identifier /// A keyword (like SELECT) or an optionally quoted SQL identifier
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Word { pub struct Word {
/// The value of the token, without the enclosing quotes, and with the /// The value of the token, without the enclosing quotes, and with the
/// escape sequences (if any) processed (TODO: escapes are not handled) /// escape sequences (if any) processed (TODO: escapes are not handled)
@ -196,7 +200,8 @@ impl Word {
} }
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Whitespace { pub enum Whitespace {
Space, Space,
Newline, Newline,

View file

@ -18,6 +18,7 @@
use sqlparser::ast::*; use sqlparser::ast::*;
use sqlparser::dialect::{GenericDialect, MySqlDialect}; use sqlparser::dialect::{GenericDialect, MySqlDialect};
use sqlparser::test_utils::*; use sqlparser::test_utils::*;
use sqlparser::tokenizer::Token;
#[test] #[test]
fn parse_identifiers() { fn parse_identifiers() {
@ -97,6 +98,37 @@ fn parse_show_columns() {
} }
} }
#[test]
fn parse_create_table_auto_increment() {
let sql = "CREATE TABLE foo (bar INT PRIMARY KEY AUTO_INCREMENT)";
match mysql().verified_stmt(sql) {
Statement::CreateTable { name, columns, .. } => {
assert_eq!(name.to_string(), "foo");
assert_eq!(
vec![ColumnDef {
name: "bar".into(),
data_type: DataType::Int,
collation: None,
options: vec![
ColumnOptionDef {
name: None,
option: ColumnOption::Unique { is_primary: true }
},
ColumnOptionDef {
name: None,
option: ColumnOption::DialectSpecific(vec![Token::make_keyword(
"AUTO_INCREMENT"
)])
}
],
}],
columns
);
}
_ => unreachable!(),
}
}
fn mysql() -> TestedDialects { fn mysql() -> TestedDialects {
TestedDialects { TestedDialects {
dialects: vec![Box::new(MySqlDialect {})], dialects: vec![Box::new(MySqlDialect {})],

View file

@ -17,6 +17,7 @@
use sqlparser::ast::*; use sqlparser::ast::*;
use sqlparser::dialect::GenericDialect; use sqlparser::dialect::GenericDialect;
use sqlparser::test_utils::*; use sqlparser::test_utils::*;
use sqlparser::tokenizer::Token;
#[test] #[test]
fn parse_create_table_without_rowid() { fn parse_create_table_without_rowid() {
@ -55,6 +56,37 @@ fn parse_create_virtual_table() {
sqlite_and_generic().verified_stmt(sql); sqlite_and_generic().verified_stmt(sql);
} }
#[test]
fn parse_create_table_auto_increment() {
let sql = "CREATE TABLE foo (bar INT PRIMARY KEY AUTOINCREMENT)";
match sqlite_and_generic().verified_stmt(sql) {
Statement::CreateTable { name, columns, .. } => {
assert_eq!(name.to_string(), "foo");
assert_eq!(
vec![ColumnDef {
name: "bar".into(),
data_type: DataType::Int,
collation: None,
options: vec![
ColumnOptionDef {
name: None,
option: ColumnOption::Unique { is_primary: true }
},
ColumnOptionDef {
name: None,
option: ColumnOption::DialectSpecific(vec![Token::make_keyword(
"AUTOINCREMENT"
)])
}
],
}],
columns
);
}
_ => unreachable!(),
}
}
fn sqlite_and_generic() -> TestedDialects { fn sqlite_and_generic() -> TestedDialects {
TestedDialects { TestedDialects {
// we don't have a separate SQLite dialect, so test only the generic dialect for now // we don't have a separate SQLite dialect, so test only the generic dialect for now