Skip to content

Support AUTO_INCREMENT for MySQL and AUTOINCREMENT for SQLite #234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 28, 2020
6 changes: 6 additions & 0 deletions src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -214,6 +216,9 @@ pub enum ColumnOption {
},
// `CHECK (<expr>)`
Check(Expr),
// Support AUTO_INCREMENT for MySQL
// Support AUTOINCREMENT for SQLite
DialectSpecific(Vec<Token>),
}

impl fmt::Display for ColumnOption {
Expand Down Expand Up @@ -245,6 +250,7 @@ impl fmt::Display for ColumnOption {
Ok(())
}
Check(expr) => write!(f, "CHECK ({})", expr),
DialectSpecific(val) => write!(f, "{}", display_separated(val, " ")),
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/dialect/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ 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)]
#[allow(non_camel_case_types)]
pub enum Keyword {
NoKeyword,
Expand Down Expand Up @@ -84,6 +84,8 @@ define_keywords!(
AT,
ATOMIC,
AUTHORIZATION,
AUTOINCREMENT,
AUTO_INCREMENT,
AVG,
AVRO,
BEGIN,
Expand Down
22 changes: 22 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,28 @@ 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
let word = Word {
value: "AUTO_INCREMENT".to_string(),
quote_style: None,
keyword: Keyword::AUTO_INCREMENT,
};
let token = Token::Word(word);
let mut vec = vec![];
vec.push(token);
ColumnOption::DialectSpecific(vec)
} else if self.parse_keyword(Keyword::AUTOINCREMENT) {
// Support AUTOINCREMENT for SQLite
let word = Word {
value: "AUTOINCREMENT".to_string(),
quote_style: None,
keyword: Keyword::AUTOINCREMENT,
};
let token = Token::Word(word);
let mut vec = vec![];
vec.push(token);
ColumnOption::DialectSpecific(vec)
} else {
return self.expected("column option", self.peek_token());
};
Expand Down
6 changes: 3 additions & 3 deletions src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use super::dialect::Dialect;
use std::fmt;

/// SQL Token enumeration
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Token {
/// An end-of-file marker, not a real token
EOF,
Expand Down Expand Up @@ -160,7 +160,7 @@ impl Token {
}

/// A keyword (like SELECT) or an optionally quoted SQL identifier
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
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)
Expand Down Expand Up @@ -196,7 +196,7 @@ impl Word {
}
}

#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Whitespace {
Space,
Newline,
Expand Down
39 changes: 39 additions & 0 deletions tests/sqlparser_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
//! is also tested (on the inputs it can handle).

use sqlparser::ast::*;
use sqlparser::dialect::keywords::Keyword;
use sqlparser::dialect::{GenericDialect, MySqlDialect};
use sqlparser::test_utils::*;
use sqlparser::tokenizer::{Token, Word};

#[test]
fn parse_identifiers() {
Expand Down Expand Up @@ -97,6 +99,43 @@ fn parse_show_columns() {
}
}

#[test]
fn parse_create_table_auto_increment() {
let sql = "CREATE TABLE foo (bar INT PRIMARY KEY AUTO_INCREMENT)";
let word = Word {
value: "AUTO_INCREMENT".to_string(),
quote_style: None,
keyword: Keyword::AUTO_INCREMENT,
};
let token = Token::Word(word);
let mut vec = vec![];
vec.push(token);
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)
}
],
}],
columns
);
}
_ => unreachable!(),
}
}

fn mysql() -> TestedDialects {
TestedDialects {
dialects: vec![Box::new(MySqlDialect {})],
Expand Down
39 changes: 39 additions & 0 deletions tests/sqlparser_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
//! generic dialect is also tested (on the inputs it can handle).

use sqlparser::ast::*;
use sqlparser::dialect::keywords::Keyword;
use sqlparser::dialect::GenericDialect;
use sqlparser::test_utils::*;
use sqlparser::tokenizer::{Token, Word};

#[test]
fn parse_create_table_without_rowid() {
Expand Down Expand Up @@ -55,6 +57,43 @@ 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)";
let word = Word {
value: "AUTOINCREMENT".to_string(),
quote_style: None,
keyword: Keyword::AUTOINCREMENT,
};
let token = Token::Word(word);
let mut vec = vec![];
vec.push(token);
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)
}
],
}],
columns
);
}
_ => unreachable!(),
}
}

fn sqlite_and_generic() -> TestedDialects {
TestedDialects {
// we don't have a separate SQLite dialect, so test only the generic dialect for now
Expand Down