Add all missing table options to be handled in any order (#1747)

Co-authored-by: Tomer Shani <tomer.shani@satoricyber.com>
This commit is contained in:
benrsatori 2025-05-02 16:16:59 +03:00 committed by GitHub
parent a464f8e8d7
commit 728645fb31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 767 additions and 382 deletions

View file

@ -848,9 +848,23 @@ fn parse_create_table_comment() {
for sql in [without_equal, with_equal] {
match mysql().verified_stmt(sql) {
Statement::CreateTable(CreateTable { name, comment, .. }) => {
Statement::CreateTable(CreateTable {
name,
table_options,
..
}) => {
assert_eq!(name.to_string(), "foo");
assert_eq!(comment.expect("Should exist").to_string(), "baz");
let plain_options = match table_options {
CreateTableOptions::Plain(options) => options,
_ => unreachable!(),
};
let comment = match plain_options.first().unwrap() {
SqlOption::Comment(CommentDef::WithEq(c))
| SqlOption::Comment(CommentDef::WithoutEq(c)) => c,
_ => unreachable!(),
};
assert_eq!(comment, "baz");
}
_ => unreachable!(),
}
@ -859,29 +873,226 @@ fn parse_create_table_comment() {
#[test]
fn parse_create_table_auto_increment_offset() {
let canonical =
"CREATE TABLE foo (bar INT NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT 123";
let with_equal =
"CREATE TABLE foo (bar INT NOT NULL AUTO_INCREMENT) ENGINE=InnoDB AUTO_INCREMENT=123";
let sql =
"CREATE TABLE foo (bar INT NOT NULL AUTO_INCREMENT) ENGINE = InnoDB AUTO_INCREMENT = 123";
for sql in [canonical, with_equal] {
match mysql().one_statement_parses_to(sql, canonical) {
match mysql().verified_stmt(sql) {
Statement::CreateTable(CreateTable {
name,
table_options,
..
}) => {
assert_eq!(name.to_string(), "foo");
let plain_options = match table_options {
CreateTableOptions::Plain(options) => options,
_ => unreachable!(),
};
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("AUTO_INCREMENT"),
value: Expr::Value(test_utils::number("123").with_empty_span())
}));
}
_ => unreachable!(),
}
}
#[test]
fn parse_create_table_multiple_options_order_independent() {
let sql1 = "CREATE TABLE mytable (id INT) ENGINE=InnoDB ROW_FORMAT=DYNAMIC KEY_BLOCK_SIZE=8 COMMENT='abc'";
let sql2 = "CREATE TABLE mytable (id INT) KEY_BLOCK_SIZE=8 COMMENT='abc' ENGINE=InnoDB ROW_FORMAT=DYNAMIC";
let sql3 = "CREATE TABLE mytable (id INT) ROW_FORMAT=DYNAMIC KEY_BLOCK_SIZE=8 COMMENT='abc' ENGINE=InnoDB";
for sql in [sql1, sql2, sql3] {
match mysql().parse_sql_statements(sql).unwrap().pop().unwrap() {
Statement::CreateTable(CreateTable {
name,
auto_increment_offset,
table_options,
..
}) => {
assert_eq!(name.to_string(), "foo");
assert_eq!(
auto_increment_offset.expect("Should exist").to_string(),
"123"
);
assert_eq!(name.to_string(), "mytable");
let plain_options = match table_options {
CreateTableOptions::Plain(options) => options,
_ => unreachable!(),
};
assert!(plain_options.contains(&SqlOption::NamedParenthesizedList(
NamedParenthesizedList {
key: Ident::new("ENGINE"),
name: Some(Ident::new("InnoDB")),
values: vec![]
}
)));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("KEY_BLOCK_SIZE"),
value: Expr::Value(test_utils::number("8").with_empty_span())
}));
assert!(plain_options
.contains(&SqlOption::Comment(CommentDef::WithEq("abc".to_owned()))));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("ROW_FORMAT"),
value: Expr::Identifier(Ident::new("DYNAMIC".to_owned()))
}));
}
_ => unreachable!(),
}
}
}
#[test]
fn parse_create_table_with_all_table_options() {
let sql =
"CREATE TABLE foo (bar INT NOT NULL AUTO_INCREMENT) ENGINE = InnoDB AUTO_INCREMENT = 123 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci INSERT_METHOD = FIRST KEY_BLOCK_SIZE = 8 ROW_FORMAT = DYNAMIC DATA DIRECTORY = '/var/lib/mysql/data' INDEX DIRECTORY = '/var/lib/mysql/index' PACK_KEYS = 1 STATS_AUTO_RECALC = 1 STATS_PERSISTENT = 0 STATS_SAMPLE_PAGES = 128 DELAY_KEY_WRITE = 1 COMPRESSION = 'ZLIB' ENCRYPTION = 'Y' MAX_ROWS = 10000 MIN_ROWS = 10 AUTOEXTEND_SIZE = 64 AVG_ROW_LENGTH = 128 CHECKSUM = 1 CONNECTION = 'mysql://localhost' ENGINE_ATTRIBUTE = 'primary' PASSWORD = 'secure_password' SECONDARY_ENGINE_ATTRIBUTE = 'secondary_attr' START TRANSACTION TABLESPACE my_tablespace STORAGE DISK UNION = (table1, table2, table3)";
match mysql().verified_stmt(sql) {
Statement::CreateTable(CreateTable {
name,
table_options,
..
}) => {
assert_eq!(name, vec![Ident::new("foo".to_owned())].into());
let plain_options = match table_options {
CreateTableOptions::Plain(options) => options,
_ => unreachable!(),
};
assert!(plain_options.contains(&SqlOption::NamedParenthesizedList(
NamedParenthesizedList {
key: Ident::new("ENGINE"),
name: Some(Ident::new("InnoDB")),
values: vec![]
}
)));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("COLLATE"),
value: Expr::Identifier(Ident::new("utf8mb4_0900_ai_ci".to_owned()))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("DEFAULT CHARSET"),
value: Expr::Identifier(Ident::new("utf8mb4".to_owned()))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("AUTO_INCREMENT"),
value: Expr::value(test_utils::number("123"))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("KEY_BLOCK_SIZE"),
value: Expr::value(test_utils::number("8"))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("ROW_FORMAT"),
value: Expr::Identifier(Ident::new("DYNAMIC".to_owned()))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("PACK_KEYS"),
value: Expr::value(test_utils::number("1"))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("STATS_AUTO_RECALC"),
value: Expr::value(test_utils::number("1"))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("STATS_PERSISTENT"),
value: Expr::value(test_utils::number("0"))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("STATS_SAMPLE_PAGES"),
value: Expr::value(test_utils::number("128"))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("STATS_SAMPLE_PAGES"),
value: Expr::value(test_utils::number("128"))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("INSERT_METHOD"),
value: Expr::Identifier(Ident::new("FIRST".to_owned()))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("COMPRESSION"),
value: Expr::value(Value::SingleQuotedString("ZLIB".to_owned()))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("ENCRYPTION"),
value: Expr::value(Value::SingleQuotedString("Y".to_owned()))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("MAX_ROWS"),
value: Expr::value(test_utils::number("10000"))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("MIN_ROWS"),
value: Expr::value(test_utils::number("10"))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("AUTOEXTEND_SIZE"),
value: Expr::value(test_utils::number("64"))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("AVG_ROW_LENGTH"),
value: Expr::value(test_utils::number("128"))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("CHECKSUM"),
value: Expr::value(test_utils::number("1"))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("CONNECTION"),
value: Expr::value(Value::SingleQuotedString("mysql://localhost".to_owned()))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("ENGINE_ATTRIBUTE"),
value: Expr::value(Value::SingleQuotedString("primary".to_owned()))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("PASSWORD"),
value: Expr::value(Value::SingleQuotedString("secure_password".to_owned()))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("SECONDARY_ENGINE_ATTRIBUTE"),
value: Expr::value(Value::SingleQuotedString("secondary_attr".to_owned()))
}));
assert!(plain_options.contains(&SqlOption::Ident(Ident::new(
"START TRANSACTION".to_owned()
))));
assert!(
plain_options.contains(&SqlOption::TableSpace(TablespaceOption {
name: "my_tablespace".to_string(),
storage: Some(StorageType::Disk),
}))
);
assert!(plain_options.contains(&SqlOption::NamedParenthesizedList(
NamedParenthesizedList {
key: Ident::new("UNION"),
name: None,
values: vec![
Ident::new("table1".to_string()),
Ident::new("table2".to_string()),
Ident::new("table3".to_string())
]
}
)));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("DATA DIRECTORY"),
value: Expr::value(Value::SingleQuotedString("/var/lib/mysql/data".to_owned()))
}));
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("INDEX DIRECTORY"),
value: Expr::value(Value::SingleQuotedString("/var/lib/mysql/index".to_owned()))
}));
}
_ => unreachable!(),
}
}
#[test]
fn parse_create_table_set_enum() {
let sql = "CREATE TABLE foo (bar SET('a', 'b'), baz ENUM('a', 'b'))";
@ -916,13 +1127,12 @@ fn parse_create_table_set_enum() {
#[test]
fn parse_create_table_engine_default_charset() {
let sql = "CREATE TABLE foo (id INT(11)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3";
let sql = "CREATE TABLE foo (id INT(11)) ENGINE = InnoDB DEFAULT CHARSET = utf8mb3";
match mysql().verified_stmt(sql) {
Statement::CreateTable(CreateTable {
name,
columns,
engine,
default_charset,
table_options,
..
}) => {
assert_eq!(name.to_string(), "foo");
@ -934,14 +1144,24 @@ fn parse_create_table_engine_default_charset() {
},],
columns
);
assert_eq!(
engine,
Some(TableEngine {
name: "InnoDB".to_string(),
parameters: None
})
);
assert_eq!(default_charset, Some("utf8mb3".to_string()));
let plain_options = match table_options {
CreateTableOptions::Plain(options) => options,
_ => unreachable!(),
};
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("DEFAULT CHARSET"),
value: Expr::Identifier(Ident::new("utf8mb3".to_owned()))
}));
assert!(plain_options.contains(&SqlOption::NamedParenthesizedList(
NamedParenthesizedList {
key: Ident::new("ENGINE"),
name: Some(Ident::new("InnoDB")),
values: vec![]
}
)));
}
_ => unreachable!(),
}
@ -949,12 +1169,12 @@ fn parse_create_table_engine_default_charset() {
#[test]
fn parse_create_table_collate() {
let sql = "CREATE TABLE foo (id INT(11)) COLLATE=utf8mb4_0900_ai_ci";
let sql = "CREATE TABLE foo (id INT(11)) COLLATE = utf8mb4_0900_ai_ci";
match mysql().verified_stmt(sql) {
Statement::CreateTable(CreateTable {
name,
columns,
collation,
table_options,
..
}) => {
assert_eq!(name.to_string(), "foo");
@ -966,7 +1186,16 @@ fn parse_create_table_collate() {
},],
columns
);
assert_eq!(collation, Some("utf8mb4_0900_ai_ci".to_string()));
let plain_options = match table_options {
CreateTableOptions::Plain(options) => options,
_ => unreachable!(),
};
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("COLLATE"),
value: Expr::Identifier(Ident::new("utf8mb4_0900_ai_ci".to_owned()))
}));
}
_ => unreachable!(),
}
@ -974,16 +1203,26 @@ fn parse_create_table_collate() {
#[test]
fn parse_create_table_both_options_and_as_query() {
let sql = "CREATE TABLE foo (id INT(11)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb4_0900_ai_ci AS SELECT 1";
let sql = "CREATE TABLE foo (id INT(11)) ENGINE = InnoDB DEFAULT CHARSET = utf8mb3 COLLATE = utf8mb4_0900_ai_ci AS SELECT 1";
match mysql_and_generic().verified_stmt(sql) {
Statement::CreateTable(CreateTable {
name,
collation,
query,
table_options,
..
}) => {
assert_eq!(name.to_string(), "foo");
assert_eq!(collation, Some("utf8mb4_0900_ai_ci".to_string()));
let plain_options = match table_options {
CreateTableOptions::Plain(options) => options,
_ => unreachable!(),
};
assert!(plain_options.contains(&SqlOption::KeyValue {
key: Ident::new("COLLATE"),
value: Expr::Identifier(Ident::new("utf8mb4_0900_ai_ci".to_owned()))
}));
assert_eq!(
query.unwrap().body.as_select().unwrap().projection,
vec![SelectItem::UnnamedExpr(Expr::Value(
@ -994,7 +1233,8 @@ fn parse_create_table_both_options_and_as_query() {
_ => unreachable!(),
}
let sql = r"CREATE TABLE foo (id INT(11)) ENGINE=InnoDB AS SELECT 1 DEFAULT CHARSET=utf8mb3";
let sql =
r"CREATE TABLE foo (id INT(11)) ENGINE = InnoDB AS SELECT 1 DEFAULT CHARSET = utf8mb3";
assert!(matches!(
mysql_and_generic().parse_sql_statements(sql),
Err(ParserError::ParserError(_))