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
### 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

View file

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

View file

@ -23,6 +23,8 @@
/// and could be removed.
/// 3) a `RESERVED_FOR_TABLE_ALIAS` array with keywords reserved in a
/// "table alias" context.
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Defines a string constant for a single keyword: `kw_def!(SELECT);`
/// expands to `pub const SELECT = "SELECT";`
@ -41,7 +43,8 @@ macro_rules! define_keywords {
($(
$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)]
pub enum Keyword {
NoKeyword,
@ -84,6 +87,8 @@ define_keywords!(
AT,
ATOMIC,
AUTHORIZATION,
AUTOINCREMENT,
AUTO_INCREMENT,
AVG,
AVRO,
BEGIN,

View file

@ -1283,6 +1283,12 @@ impl Parser {
let expr = self.parse_expr()?;
self.expect_token(&Token::RParen)?;
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 {
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::Dialect;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::fmt;
/// SQL Token enumeration
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Token {
/// An end-of-file marker, not a real token
EOF,
@ -160,7 +163,8 @@ impl Token {
}
/// 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 {
/// The value of the token, without the enclosing quotes, and with the
/// 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 {
Space,
Newline,

View file

@ -18,6 +18,7 @@
use sqlparser::ast::*;
use sqlparser::dialect::{GenericDialect, MySqlDialect};
use sqlparser::test_utils::*;
use sqlparser::tokenizer::Token;
#[test]
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 {
TestedDialects {
dialects: vec![Box::new(MySqlDialect {})],

View file

@ -17,6 +17,7 @@
use sqlparser::ast::*;
use sqlparser::dialect::GenericDialect;
use sqlparser::test_utils::*;
use sqlparser::tokenizer::Token;
#[test]
fn parse_create_table_without_rowid() {
@ -55,6 +56,37 @@ fn parse_create_virtual_table() {
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 {
TestedDialects {
// we don't have a separate SQLite dialect, so test only the generic dialect for now