diff --git a/src/ast/dml.rs b/src/ast/dml.rs index 8cfc67414..91ab2d529 100644 --- a/src/ast/dml.rs +++ b/src/ast/dml.rs @@ -29,6 +29,11 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "visitor")] use sqlparser_derive::{Visit, VisitMut}; +use crate::parser::{ + Compression, DelayKeyWrite, DirectoryOption, Encryption, InsertMethod, OptionState, RowFormat, + StorageType, TablespaceOption, +}; + pub use super::ddl::{ColumnDef, TableConstraint}; use super::{ @@ -138,6 +143,30 @@ pub struct CreateTable { pub engine: Option, pub comment: Option, pub auto_increment_offset: Option, + pub key_block_size: Option, + pub max_rows: Option, + pub min_rows: Option, + pub autoextend_size: Option, + pub avg_row_length: Option, + pub checksum: Option, + pub connection: Option, + pub engine_attribute: Option, + pub password: Option, + pub secondary_engine_attribute: Option, + pub tablespace_option: Option, + pub row_format: Option, + pub insert_method: Option, + pub compression: Option, + pub delay_key_write: Option, + pub encryption: Option, + pub pack_keys: Option, + pub stats_auto_recalc: Option, + pub stats_persistent: Option, + pub stats_sample_pages: Option, + pub start_transaction: Option, + pub union_tables: Option>, + pub data_directory: Option, + pub index_directory: Option, pub default_charset: Option, pub collation: Option, pub on_commit: Option, @@ -378,7 +407,7 @@ impl Display for CreateTable { } if let Some(auto_increment_offset) = self.auto_increment_offset { - write!(f, " AUTO_INCREMENT {auto_increment_offset}")?; + write!(f, " AUTO_INCREMENT={auto_increment_offset}")?; } if let Some(primary_key) = &self.primary_key { write!(f, " PRIMARY KEY {}", primary_key)?; @@ -483,6 +512,158 @@ impl Display for CreateTable { write!(f, " COLLATE={collation}")?; } + if let Some(insert_method) = &self.insert_method { + match insert_method { + InsertMethod::No => write!(f, " INSERT_METHOD=NO")?, + InsertMethod::First => write!(f, " INSERT_METHOD=FIRST")?, + InsertMethod::Last => write!(f, " INSERT_METHOD=LAST")?, + } + } + + if let Some(key_block_size) = self.key_block_size { + write!(f, " KEY_BLOCK_SIZE={key_block_size}")?; + } + + if let Some(row_format) = &self.row_format { + match row_format { + RowFormat::Default => write!(f, " ROW_FORMAT=DEFAULT")?, + RowFormat::Dynamic => write!(f, " ROW_FORMAT=DYNAMIC")?, + RowFormat::Fixed => write!(f, " ROW_FORMAT=FIXED")?, + RowFormat::Compressed => write!(f, " ROW_FORMAT=COMPRESSED")?, + RowFormat::Redundant => write!(f, " ROW_FORMAT=REDUNDANT")?, + RowFormat::Compact => write!(f, " ROW_FORMAT=COMPACT")?, + } + } + + if let Some(data_dir) = &self.data_directory { + write!(f, " DATA DIRECTORY='{}'", data_dir.path)?; + } + + if let Some(index_dir) = &self.index_directory { + write!(f, " INDEX DIRECTORY='{}'", index_dir.path)?; + } + + if let Some(pack_keys) = &self.pack_keys { + match pack_keys { + OptionState::Default => write!(f, " PACK_KEYS=DEFAULT")?, + OptionState::One => write!(f, " PACK_KEYS=1")?, + OptionState::Zero => write!(f, " PACK_KEYS=0")?, + } + } + + if let Some(stats_auto_recalc) = &self.stats_auto_recalc { + match stats_auto_recalc { + OptionState::Default => write!(f, " STATS_AUTO_RECALC=DEFAULT")?, + OptionState::One => write!(f, " STATS_AUTO_RECALC=1")?, + OptionState::Zero => write!(f, " STATS_AUTO_RECALC=0")?, + } + } + + if let Some(stats_persistent) = &self.stats_persistent { + match stats_persistent { + OptionState::Default => write!(f, " STATS_PERSISTENT=DEFAULT")?, + OptionState::One => write!(f, " STATS_PERSISTENT=1")?, + OptionState::Zero => write!(f, " STATS_PERSISTENT=0")?, + } + } + + if let Some(stats_sample_pages) = self.stats_sample_pages { + write!(f, " STATS_SAMPLE_PAGES={stats_sample_pages}")?; + } + + if let Some(delay_key_write) = &self.delay_key_write { + match delay_key_write { + DelayKeyWrite::Enabled => write!(f, " DELAY_KEY_WRITE=1")?, + DelayKeyWrite::Disabled => write!(f, " DELAY_KEY_WRITE=0")?, + } + } + + if let Some(compression) = &self.compression { + match compression { + Compression::Lz4 => write!(f, " COMPRESSION=LZ4")?, + Compression::Zlib => write!(f, " COMPRESSION=ZLIB")?, + Compression::None => write!(f, " COMPRESSION=NONE")?, + } + } + + if let Some(encryption) = &self.encryption { + match encryption { + Encryption::Yes => write!(f, " ENCRYPTION='Y'")?, + Encryption::No => write!(f, " ENCRYPTION='N'")?, + } + } + + if let Some(max_rows) = &self.max_rows { + write!(f, " MAX_ROWS={}", max_rows)?; + } + + if let Some(min_rows) = &self.min_rows { + write!(f, " MIN_ROWS={}", min_rows)?; + } + + if let Some(autoextend_size) = &self.autoextend_size { + write!(f, " AUTOEXTEND_SIZE={}", autoextend_size)?; + } + + if let Some(avg_row_length) = &self.avg_row_length { + write!(f, " AVG_ROW_LENGTH={}", avg_row_length)?; + } + + if let Some(checksum) = &self.checksum { + match checksum { + true => write!(f, " CHECKSUM=1")?, + false => write!(f, " CHECKSUM=0")?, + } + } + + if let Some(connection) = &self.connection { + write!(f, " CONNECTION='{}'", connection)?; + } + + if let Some(engine_attribute) = &self.engine_attribute { + write!(f, " ENGINE_ATTRIBUTE='{}'", engine_attribute)?; + } + + if let Some(password) = &self.password { + write!(f, " PASSWORD='{}'", password)?; + } + + if let Some(secondary_engine_attribute) = &self.secondary_engine_attribute { + write!( + f, + " SECONDARY_ENGINE_ATTRIBUTE='{}'", + secondary_engine_attribute + )?; + } + + if self.start_transaction.unwrap_or(false) { + write!(f, " START TRANSACTION")?; + } + + if let Some(tablespace_option) = &self.tablespace_option { + write!(f, " TABLESPACE {}", tablespace_option.name)?; + if let Some(storage) = &tablespace_option.storage { + match storage { + StorageType::Disk => write!(f, " STORAGE DISK")?, + StorageType::Memory => write!(f, " STORAGE MEMORY")?, + } + } + } + + if let Some(union_tables) = &self.union_tables { + if !union_tables.is_empty() { + write!( + f, + " UNION=({})", + union_tables + .iter() + .map(|table| table.to_string()) + .collect::>() + .join(", ") + )?; + } + } + if self.on_commit.is_some() { let on_commit = match self.on_commit { Some(OnCommit::DeleteRows) => "ON COMMIT DELETE ROWS", diff --git a/src/ast/helpers/stmt_create_table.rs b/src/ast/helpers/stmt_create_table.rs index 2a44cef3e..c23234c48 100644 --- a/src/ast/helpers/stmt_create_table.rs +++ b/src/ast/helpers/stmt_create_table.rs @@ -30,7 +30,10 @@ use crate::ast::{ ObjectName, OnCommit, OneOrManyWithParens, Query, RowAccessPolicy, SqlOption, Statement, StorageSerializationPolicy, TableConstraint, TableEngine, Tag, WrappedCollection, }; -use crate::parser::ParserError; +use crate::parser::{ + Compression, DelayKeyWrite, DirectoryOption, Encryption, InsertMethod, OptionState, + ParserError, RowFormat, TablespaceOption, +}; /// Builder for create table statement variant ([1]). /// @@ -90,6 +93,30 @@ pub struct CreateTableBuilder { pub auto_increment_offset: Option, pub default_charset: Option, pub collation: Option, + pub key_block_size: Option, + pub max_rows: Option, + pub min_rows: Option, + pub autoextend_size: Option, + pub avg_row_length: Option, + pub checksum: Option, + pub row_format: Option, + pub compression: Option, + pub encryption: Option, + pub insert_method: Option, + pub pack_keys: Option, + pub stats_auto_recalc: Option, + pub stats_persistent: Option, + pub stats_sample_pages: Option, + pub delay_key_write: Option, + pub connection: Option, + pub engine_attribute: Option, + pub password: Option, + pub secondary_engine_attribute: Option, + pub start_transaction: Option, + pub tablespace_option: Option, + pub union_tables: Option>, + pub data_directory: Option, + pub index_directory: Option, pub on_commit: Option, pub on_cluster: Option, pub primary_key: Option>, @@ -142,6 +169,30 @@ impl CreateTableBuilder { engine: None, comment: None, auto_increment_offset: None, + compression: None, + encryption: None, + insert_method: None, + key_block_size: None, + row_format: None, + data_directory: None, + index_directory: None, + pack_keys: None, + stats_auto_recalc: None, + stats_persistent: None, + stats_sample_pages: None, + delay_key_write: None, + max_rows: None, + min_rows: None, + autoextend_size: None, + avg_row_length: None, + checksum: None, + connection: None, + engine_attribute: None, + password: None, + secondary_engine_attribute: None, + start_transaction: None, + tablespace_option: None, + union_tables: None, default_charset: None, collation: None, on_commit: None, @@ -291,6 +342,118 @@ impl CreateTableBuilder { self.collation = collation; self } + pub fn compression(mut self, compression: Option) -> Self { + self.compression = compression; + self + } + pub fn encryption(mut self, encryption: Option) -> Self { + self.encryption = encryption; + self + } + pub fn delay_key_write(mut self, delay_key_write: Option) -> Self { + self.delay_key_write = delay_key_write; + self + } + pub fn insert_method(mut self, insert_method: Option) -> Self { + self.insert_method = insert_method; + self + } + + pub fn key_block_size(mut self, key_block_size: Option) -> Self { + self.key_block_size = key_block_size; + self + } + pub fn max_rows(mut self, max_rows: Option) -> Self { + self.max_rows = max_rows; + self + } + + pub fn min_rows(mut self, min_rows: Option) -> Self { + self.min_rows = min_rows; + self + } + + pub fn autoextend_size(mut self, autoextend_size: Option) -> Self { + self.autoextend_size = autoextend_size; + self + } + pub fn avg_row_length(mut self, avg_row_length: Option) -> Self { + self.avg_row_length = avg_row_length; + self + } + + pub fn checksum(mut self, checksum: Option) -> Self { + self.checksum = checksum; + self + } + pub fn connection(mut self, connection: Option) -> Self { + self.connection = connection; + self + } + pub fn engine_attribute(mut self, engine_attribute: Option) -> Self { + self.engine_attribute = engine_attribute; + self + } + pub fn password(mut self, password: Option) -> Self { + self.password = password; + self + } + pub fn secondary_engine_attribute( + mut self, + secondary_engine_attribute: Option, + ) -> Self { + self.secondary_engine_attribute = secondary_engine_attribute; + self + } + + pub fn start_transaction(mut self, start_transaction: Option) -> Self { + self.start_transaction = start_transaction; + self + } + + pub fn tablespace_option(mut self, tablespace_option: Option) -> Self { + self.tablespace_option = tablespace_option; + self + } + pub fn union_tables(mut self, union_tables: Option>) -> Self { + self.union_tables = union_tables; + self + } + + pub fn row_format(mut self, row_format: Option) -> Self { + self.row_format = row_format; + self + } + + pub fn data_directory(mut self, directory_option: Option) -> Self { + self.data_directory = directory_option; + self + } + + pub fn index_directory(mut self, directory_option: Option) -> Self { + self.index_directory = directory_option; + self + } + + pub fn pack_keys(mut self, pack_keys: Option) -> Self { + self.pack_keys = pack_keys; + self + } + + pub fn stats_auto_recalc(mut self, stats_auto_recalc: Option) -> Self { + self.stats_auto_recalc = stats_auto_recalc; + self + } + + pub fn stats_persistent(mut self, stats_persistent: Option) -> Self { + self.stats_persistent = stats_persistent; + self + } + + pub fn stats_sample_pages(mut self, stats_sample_pages: Option) -> Self { + self.stats_sample_pages = stats_sample_pages; + self + } pub fn on_commit(mut self, on_commit: Option) -> Self { self.on_commit = on_commit; @@ -444,6 +607,30 @@ impl CreateTableBuilder { auto_increment_offset: self.auto_increment_offset, default_charset: self.default_charset, collation: self.collation, + compression: self.compression, + encryption: self.encryption, + insert_method: self.insert_method, + key_block_size: self.key_block_size, + row_format: self.row_format, + data_directory: self.data_directory, + index_directory: self.index_directory, + pack_keys: self.pack_keys, + stats_auto_recalc: self.stats_auto_recalc, + stats_persistent: self.stats_persistent, + stats_sample_pages: self.stats_sample_pages, + delay_key_write: self.delay_key_write, + max_rows: self.max_rows, + min_rows: self.min_rows, + autoextend_size: self.autoextend_size, + avg_row_length: self.avg_row_length, + checksum: self.checksum, + connection: self.connection, + engine_attribute: self.engine_attribute, + password: self.password, + secondary_engine_attribute: self.secondary_engine_attribute, + start_transaction: self.start_transaction, + tablespace_option: self.tablespace_option, + union_tables: self.union_tables, on_commit: self.on_commit, on_cluster: self.on_cluster, primary_key: self.primary_key, @@ -503,6 +690,30 @@ impl TryFrom for CreateTableBuilder { engine, comment, auto_increment_offset, + compression, + encryption, + insert_method, + key_block_size, + row_format, + data_directory, + index_directory, + pack_keys, + stats_auto_recalc, + stats_persistent, + stats_sample_pages, + delay_key_write, + max_rows, + min_rows, + autoextend_size, + avg_row_length, + checksum, + connection, + engine_attribute, + password, + secondary_engine_attribute, + start_transaction, + tablespace_option, + union_tables, default_charset, collation, on_commit, @@ -551,6 +762,30 @@ impl TryFrom for CreateTableBuilder { engine, comment, auto_increment_offset, + compression, + encryption, + insert_method, + key_block_size, + row_format, + data_directory, + index_directory, + pack_keys, + stats_auto_recalc, + stats_persistent, + stats_sample_pages, + delay_key_write, + max_rows, + min_rows, + autoextend_size, + avg_row_length, + checksum, + connection, + engine_attribute, + password, + secondary_engine_attribute, + start_transaction, + tablespace_option, + union_tables, default_charset, collation, on_commit, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 18f6f6e2f..66e0f0b85 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -559,6 +559,30 @@ impl Spanned for CreateTable { auto_increment_offset: _, // u32, no span default_charset: _, // string, no span collation: _, // string, no span + compression: _, // enum + encryption: _, // enum + key_block_size: _, // u32, no span + insert_method: _, // enum + row_format: _, // enum + data_directory: _, // string, no span + index_directory: _, // string, no span + stats_auto_recalc: _, // enum + stats_persistent: _, // enum + pack_keys: _, // enum + stats_sample_pages: _, // u32, no span + delay_key_write: _, // enum + max_rows: _, // u32, no span + min_rows: _, // u32, no span + avg_row_length: _, // u32, no span + autoextend_size: _, // u32, no span + checksum: _, // bool, no span + connection: _, // string, no span + password: _, // string, no span + engine_attribute: _, // string, no span + secondary_engine_attribute: _, // string, no span + start_transaction: _, // bool + tablespace_option: _, // enum + union_tables: _, // list of strings, no span on_commit: _, // enum on_cluster: _, // todo, clickhouse specific primary_key: _, // todo, clickhouse specific diff --git a/src/keywords.rs b/src/keywords.rs index a67f6bc76..7ac757f3b 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -115,9 +115,11 @@ define_keywords!( AUTHENTICATION, AUTHORIZATION, AUTO, + AUTOEXTEND_SIZE, AUTOINCREMENT, AUTO_INCREMENT, AVG, + AVG_ROW_LENGTH, AVRO, BACKWARD, BASE64, @@ -175,6 +177,7 @@ define_keywords!( CHARSET, CHAR_LENGTH, CHECK, + CHECKSUM, CLEAR, CLOB, CLONE, @@ -262,6 +265,7 @@ define_keywords!( DEFINED, DEFINER, DELAYED, + DELAY_KEY_WRITE, DELETE, DELIMITED, DELIMITER, @@ -304,6 +308,7 @@ define_keywords!( END_PARTITION, ENFORCED, ENGINE, + ENGINE_ATTRIBUTE, ENUM, ENUM16, ENUM8, @@ -429,6 +434,7 @@ define_keywords!( INPUTFORMAT, INSENSITIVE, INSERT, + INSERT_METHOD, INSTALL, INSTEAD, INT, @@ -464,6 +470,7 @@ define_keywords!( JULIAN, KEY, KEYS, + KEY_BLOCK_SIZE, KILL, LAG, LANGUAGE, @@ -515,6 +522,7 @@ define_keywords!( MAX, MAXVALUE, MAX_DATA_EXTENSION_TIME_IN_DAYS, + MAX_ROWS, MEASURES, MEDIUMBLOB, MEDIUMINT, @@ -535,6 +543,7 @@ define_keywords!( MINUTE, MINUTES, MINVALUE, + MIN_ROWS, MOD, MODE, MODIFIES, @@ -630,6 +639,7 @@ define_keywords!( OWNERSHIP, PACKAGE, PACKAGES, + PACK_KEYS, PARALLEL, PARAMETER, PARQUET, @@ -746,6 +756,7 @@ define_keywords!( ROW, ROWID, ROWS, + ROW_FORMAT, ROW_NUMBER, RULE, RUN, @@ -760,6 +771,7 @@ define_keywords!( SEARCH, SECOND, SECONDARY, + SECONDARY_ENGINE_ATTRIBUTE, SECONDS, SECRET, SECURITY, @@ -808,12 +820,16 @@ define_keywords!( STATEMENT, STATIC, STATISTICS, + STATS_AUTO_RECALC, + STATS_PERSISTENT, + STATS_SAMPLE_PAGES, STATUS, STDDEV_POP, STDDEV_SAMP, STDIN, STDOUT, STEP, + STORAGE, STORAGE_INTEGRATION, STORAGE_SERIALIZATION_POLICY, STORED, @@ -838,6 +854,7 @@ define_keywords!( TABLE, TABLES, TABLESAMPLE, + TABLESPACE, TAG, TARGET, TASK, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6d3290d23..acf6a6b8e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -48,6 +48,130 @@ pub enum ParserError { RecursionLimitExceeded, } +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Compression { + Zlib, + Lz4, + None, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum DelayKeyWrite { + Disabled, + Enabled, +} +impl fmt::Display for DelayKeyWrite { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DelayKeyWrite::Disabled => write!(f, "DISABLED"), + DelayKeyWrite::Enabled => write!(f, "ENABLED"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Encryption { + Yes, + No, +} + +impl fmt::Display for Encryption { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Encryption::Yes => write!(f, "Y"), + Encryption::No => write!(f, "N"), + } + } +} + +impl fmt::Display for Compression { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Compression::Zlib => write!(f, "ZLIB"), + Compression::Lz4 => write!(f, "LZ4"), + Compression::None => write!(f, "NONE"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum InsertMethod { + No, + First, + Last, +} +impl fmt::Display for InsertMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + InsertMethod::No => write!(f, "NO"), + InsertMethod::First => write!(f, "FIRST"), + InsertMethod::Last => write!(f, "LAST"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum RowFormat { + Default, + Dynamic, + Fixed, + Compressed, + Redundant, + Compact, +} +impl fmt::Display for RowFormat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RowFormat::Default => write!(f, "DEFAULT"), + RowFormat::Dynamic => write!(f, "DYNAMIC"), + RowFormat::Fixed => write!(f, "FIXED"), + RowFormat::Compressed => write!(f, "COMPRESSED"), + RowFormat::Redundant => write!(f, "REDUNDANT"), + RowFormat::Compact => write!(f, "COMPACT"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum DirectoryType { + Data, + Index, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct DirectoryOption { + pub directory_type: DirectoryType, + pub path: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum OptionState { + Zero, + One, + Default, +} +impl fmt::Display for OptionState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OptionState::Zero => write!(f, "0"), + OptionState::One => write!(f, "1"), + OptionState::Default => write!(f, "DEFAULT"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum StorageType { + Disk, + Memory, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct TablespaceOption { + pub name: String, + pub storage: Option, +} + // Use `Parser::expected` instead, if possible macro_rules! parser_err { ($MSG:expr, $loc:expr) => { @@ -6572,35 +6696,616 @@ impl<'a> Parser<'a> { let with_options = self.parse_options(Keyword::WITH)?; let table_properties = self.parse_options(Keyword::TBLPROPERTIES)?; - let engine = if self.parse_keyword(Keyword::ENGINE) { - self.expect_token(&Token::Eq)?; - let next_token = self.next_token(); - match next_token.token { - Token::Word(w) => { - let name = w.value; - let parameters = if self.peek_token() == Token::LParen { - Some(self.parse_parenthesized_identifiers()?) + let mut engine = None; + let mut auto_increment_offset = None; + let mut default_charset = None; + let mut collation = None; + let mut insert_method = None; + let mut key_block_size = None; + let mut row_format = None; + let mut data_directory = None; + let mut index_directory = None; + let mut pack_keys = None; + let mut stats_auto_recalc = None; + let mut stats_persistent = None; + let mut stats_sample_pages = None; + let mut delay_key_write = None; + let mut compression = None; + let mut encryption = None; + let mut max_rows = None; + let mut min_rows = None; + let mut autoextend_size = None; + let mut avg_row_length = None; + let mut checksum = None; + let mut connection = None; + let mut engine_attribute = None; + let mut password = None; + let mut secondary_engine_attribute = None; + let mut start_transaction = None; + let mut tablespace_option = None; + let mut union_tables = None; + + // Parse table options in any order + while let Some(keyword) = self.parse_one_of_keywords(&[ + Keyword::ENGINE, + Keyword::AUTO_INCREMENT, + Keyword::DEFAULT, + Keyword::CHARSET, + Keyword::COLLATE, + Keyword::INSERT_METHOD, + Keyword::KEY_BLOCK_SIZE, + Keyword::ROW_FORMAT, + Keyword::DATA, + Keyword::INDEX, + Keyword::PACK_KEYS, + Keyword::STATS_AUTO_RECALC, + Keyword::STATS_PERSISTENT, + Keyword::STATS_SAMPLE_PAGES, + Keyword::DELAY_KEY_WRITE, + Keyword::COMPRESSION, + Keyword::ENCRYPTION, + Keyword::MAX_ROWS, + Keyword::MIN_ROWS, + Keyword::AUTOEXTEND_SIZE, + Keyword::AVG_ROW_LENGTH, + Keyword::CHECKSUM, + Keyword::CONNECTION, + Keyword::ENGINE_ATTRIBUTE, + Keyword::PASSWORD, + Keyword::SECONDARY_ENGINE_ATTRIBUTE, + Keyword::START, + Keyword::TABLESPACE, + Keyword::UNION, + ]) { + match keyword { + Keyword::ENGINE => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Word(w) => { + let name = w.value; + let parameters = if self.peek_token() == Token::LParen { + Some(self.parse_parenthesized_identifiers()?) + } else { + None + }; + engine = Some(TableEngine { name, parameters }); + } + _ => self.expected("identifier", next_token)?, + } + } + + Keyword::AUTO_INCREMENT => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Number(s, _) => { + auto_increment_offset = + Some(Self::parse::(s, next_token.span.start)?) + } + _ => self.expected("literal int", next_token)?, + } + } + + Keyword::DEFAULT => { + if self.parse_keyword(Keyword::CHARSET) { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Word(w) => default_charset = Some(w.value), + _ => self.expected("identifier", next_token)?, + } + } else if self.parse_keyword(Keyword::COLLATE) { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Word(w) => collation = Some(w.value), + _ => self.expected("identifier", next_token)?, + } + } else { + return Err(ParserError::ParserError( + "Expected CHARACTER SET or COLLATE after DEFAULT".to_string(), + )); + } + } + + Keyword::CHARSET => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Word(w) => default_charset = Some(w.value), + _ => self.expected("identifier", next_token)?, + } + } + + Keyword::COLLATE => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Word(w) => collation = Some(w.value), + _ => self.expected("identifier", next_token)?, + } + } + + Keyword::INSERT_METHOD => { + let _ = self.consume_token(&Token::Eq); + let token = self.next_token(); + match token.token { + Token::Word(w) => match w.value.to_uppercase().as_str() { + "NO" => insert_method = Some(InsertMethod::No), + "FIRST" => insert_method = Some(InsertMethod::First), + "LAST" => insert_method = Some(InsertMethod::Last), + _ => { + return Err(ParserError::ParserError( + "Invalid INSERT_METHOD value".to_string(), + )) + } + }, + _ => { + return Err(ParserError::ParserError( + "Expected INSERT_METHOD value".to_string(), + )) + } + } + } + + Keyword::KEY_BLOCK_SIZE => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Number(s, _) => { + key_block_size = Some(Self::parse::(s, next_token.span.start)?) + } + _ => { + return Err(ParserError::ParserError( + "Expected KEY_BLOCK_SIZE value".to_string(), + )) + } + } + } + + Keyword::ROW_FORMAT => { + let _ = self.consume_token(&Token::Eq); + let token = self.next_token(); + match token.token { + Token::Word(w) => match w.value.to_uppercase().as_str() { + "DEFAULT" => row_format = Some(RowFormat::Default), + "DYNAMIC" => row_format = Some(RowFormat::Dynamic), + "FIXED" => row_format = Some(RowFormat::Fixed), + "COMPRESSED" => row_format = Some(RowFormat::Compressed), + "REDUNDANT" => row_format = Some(RowFormat::Redundant), + "COMPACT" => row_format = Some(RowFormat::Compact), + _ => { + return Err(ParserError::ParserError( + "Invalid ROW_FORMAT value".to_string(), + )) + } + }, + _ => { + return Err(ParserError::ParserError( + "Expected ROW_FORMAT value".to_string(), + )) + } + } + } + + Keyword::DATA => { + if self.parse_keyword(Keyword::DIRECTORY) { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::SingleQuotedString(path) => { + data_directory = Some(DirectoryOption { + directory_type: DirectoryType::Data, + path, + }) + } + _ => { + return Err(ParserError::ParserError( + "Expected path for DATA DIRECTORY".to_string(), + )); + } + } + } else { + return Err(ParserError::ParserError( + "Expected DIRECTORY after DATA".to_string(), + )); + } + } + + Keyword::INDEX => { + if self.parse_keyword(Keyword::DIRECTORY) { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::SingleQuotedString(path) => { + index_directory = Some(DirectoryOption { + directory_type: DirectoryType::Index, + path, + }) + } + _ => { + return Err(ParserError::ParserError( + "Expected path for INDEX DIRECTORY".to_string(), + )); + } + } + } else { + return Err(ParserError::ParserError( + "Expected DIRECTORY after INDEX".to_string(), + )); + } + } + + Keyword::PACK_KEYS => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Number(s, _) => match s.as_str() { + "0" => pack_keys = Some(OptionState::Zero), + "1" => pack_keys = Some(OptionState::One), + _ => { + return Err(ParserError::ParserError(format!( + "PACK_KEYS can only be 0, 1, or DEFAULT, found: {}", + s + ))) + } + }, + Token::Word(w) if w.value.to_uppercase() == "DEFAULT" => { + pack_keys = Some(OptionState::Default) + } + _ => { + return Err(ParserError::ParserError( + "Expected numeric value for PACK_KEYS".to_string(), + )) + } + } + } + + Keyword::STATS_AUTO_RECALC => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Number(s, _) => match s.as_str() { + "0" => stats_auto_recalc = Some(OptionState::Zero), + "1" => stats_auto_recalc = Some(OptionState::One), + _ => { + return Err(ParserError::ParserError(format!( + "STATS_AUTO_RECALC can only be 0, 1, or DEFAULT, found: {}", + s + ))) + } + }, + Token::Word(w) if w.value.to_uppercase() == "DEFAULT" => { + stats_auto_recalc = Some(OptionState::Default) + } + _ => { + return Err(ParserError::ParserError( + "Expected STATS_AUTO_RECALC value (0, 1, or DEFAULT)".to_string(), + )) + } + } + } + + Keyword::STATS_PERSISTENT => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Number(s, _) => match s.as_str() { + "0" => stats_persistent = Some(OptionState::Zero), + "1" => stats_persistent = Some(OptionState::One), + _ => { + return Err(ParserError::ParserError(format!( + "STATS_PERSISTENT can only be 0, 1, or DEFAULT, found: {}", + s + ))) + } + }, + Token::Word(w) if w.value.to_uppercase() == "DEFAULT" => { + stats_persistent = Some(OptionState::Default) + } + _ => { + return Err(ParserError::ParserError( + "Expected STATS_PERSISTENT value (0, 1, or DEFAULT)".to_string(), + )) + } + } + } + + Keyword::STATS_SAMPLE_PAGES => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Number(s, _) => { + stats_sample_pages = Some(Self::parse::(s, next_token.span.start)?) + } + _ => { + return Err(ParserError::ParserError( + "Expected numeric value for STATS_SAMPLE_PAGES".to_string(), + )) + } + } + } + + Keyword::DELAY_KEY_WRITE => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Number(s, _) => match s.to_uppercase().as_str() { + "0" => delay_key_write = Some(DelayKeyWrite::Disabled), + "1" => delay_key_write = Some(DelayKeyWrite::Enabled), + _ => { + return Err(ParserError::ParserError( + "Invalid DELAY_KEY_WRITE value (expected 0 or 1)".to_string(), + )) + } + }, + _ => { + return Err(ParserError::ParserError( + "Expected DELAY_KEY_WRITE value".to_string(), + )) + } + } + } + + Keyword::COMPRESSION => { + let _ = self.consume_token(&Token::Eq); + let token = self.next_token(); + match token.token { + Token::Word(w) => match w.value.to_uppercase().as_str() { + "ZLIB" => compression = Some(Compression::Zlib), + "LZ4" => compression = Some(Compression::Lz4), + "NONE" => compression = Some(Compression::None), + _ => { + return Err(ParserError::ParserError( + "Invalid COMPRESSION value".to_string(), + )) + } + }, + _ => { + return Err(ParserError::ParserError( + "Expected COMPRESSION value".to_string(), + )) + } + } + } + + Keyword::ENCRYPTION => { + let _ = self.consume_token(&Token::Eq); + let token = self.next_token(); + match token.token { + Token::SingleQuotedString(s) => match s.to_uppercase().as_str() { + "Y" => encryption = Some(Encryption::Yes), + "N" => encryption = Some(Encryption::No), + _ => { + return Err(ParserError::ParserError( + "Invalid ENCRYPTION value (expected 'Y' or 'N')".to_string(), + )) + } + }, + _ => { + return Err(ParserError::ParserError( + "Expected ENCRYPTION value".to_string(), + )) + } + } + } + + Keyword::MAX_ROWS => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Number(s, _) => { + max_rows = Some(Self::parse::(s, next_token.span.start)?) + } + _ => { + return Err(ParserError::ParserError( + "Expected numeric value for MAX_ROWS".to_string(), + )) + } + } + } + + Keyword::MIN_ROWS => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Number(s, _) => { + min_rows = Some(Self::parse::(s, next_token.span.start)?) + } + _ => { + return Err(ParserError::ParserError( + "Expected numeric value for MIN_ROWS".to_string(), + )) + } + } + } + + Keyword::AUTOEXTEND_SIZE => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Number(s, _) => { + autoextend_size = Some(Self::parse::(s, next_token.span.start)?) + } + _ => { + return Err(ParserError::ParserError( + "Expected numeric value for AUTOEXTEND_SIZE".to_string(), + )) + } + } + } + + Keyword::AVG_ROW_LENGTH => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Number(s, _) => { + avg_row_length = Some(Self::parse::(s, next_token.span.start)?) + } + _ => { + return Err(ParserError::ParserError( + "Expected numeric value for AVG_ROW_LENGTH".to_string(), + )) + } + } + } + + Keyword::CHECKSUM => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + match next_token.token { + Token::Number(s, _) => match s.as_str() { + "0" => checksum = Some(false), + "1" => checksum = Some(true), + _ => { + return Err(ParserError::ParserError( + "Invalid CHECKSUM value (expected 0 or 1)".to_string(), + )) + } + }, + _ => { + return Err(ParserError::ParserError( + "Expected a numeric value for CHECKSUM".to_string(), + )) + } + } + } + + Keyword::CONNECTION => { + let _ = self.consume_token(&Token::Eq); + let token = self.next_token(); + match token.token { + Token::SingleQuotedString(s) => connection = Some(s), + _ => { + return Err(ParserError::ParserError( + "Expected CONNECTION value as a single-quoted string".to_string(), + )) + } + } + } + + Keyword::ENGINE_ATTRIBUTE => { + let _ = self.consume_token(&Token::Eq); + let token = self.next_token(); + match token.token { + Token::SingleQuotedString(s) => engine_attribute = Some(s), + _ => { + return Err(ParserError::ParserError( + "Expected ENGINE_ATTRIBUTE value as a single-quoted string" + .to_string(), + )) + } + } + } + + Keyword::PASSWORD => { + let _ = self.consume_token(&Token::Eq); + let token = self.next_token(); + match token.token { + Token::SingleQuotedString(s) => password = Some(s), + _ => { + return Err(ParserError::ParserError( + "Expected PASSWORD value as a single-quoted string".to_string(), + )) + } + } + } + + Keyword::SECONDARY_ENGINE_ATTRIBUTE => { + let _ = self.consume_token(&Token::Eq); + let token = self.next_token(); + match token.token { + Token::SingleQuotedString(s) => secondary_engine_attribute = Some(s), + _ => return Err(ParserError::ParserError( + "Expected SECONDARY_ENGINE_ATTRIBUTE value as a single-quoted string" + .to_string(), + )), + } + } + + Keyword::START => { + if self.parse_keyword(Keyword::TRANSACTION) { + start_transaction = Some(true); + } else { + return Err(ParserError::ParserError( + "Expected TRANSACTION after START".to_string(), + )); + } + } + + Keyword::TABLESPACE => { + let _ = self.consume_token(&Token::Eq); + let next_token = self.next_token(); + + let name = match next_token.token { + Token::Word(w) => w.value.clone(), + Token::SingleQuotedString(s) => s.clone(), + _ => { + return Err(ParserError::ParserError( + "Expected TABLESPACE name".to_string(), + )); + } + }; + + let storage = if self.parse_keyword(Keyword::STORAGE) { + let _ = self.consume_token(&Token::Eq); + let storage_token = self.next_token(); + match storage_token.token { + Token::Word(w) => match w.value.to_uppercase().as_str() { + "DISK" => Some(StorageType::Disk), + "MEMORY" => Some(StorageType::Memory), + _ => { + return Err(ParserError::ParserError( + "Invalid STORAGE type (expected DISK or MEMORY)" + .to_string(), + )) + } + }, + _ => { + return Err(ParserError::ParserError( + "Expected STORAGE type".to_string(), + )) + } + } } else { None }; - Some(TableEngine { name, parameters }) + + tablespace_option = Some(TablespaceOption { name, storage }); } - _ => self.expected("identifier", next_token)?, - } - } else { - None - }; - let auto_increment_offset = if self.parse_keyword(Keyword::AUTO_INCREMENT) { - let _ = self.consume_token(&Token::Eq); - let next_token = self.next_token(); - match next_token.token { - Token::Number(s, _) => Some(Self::parse::(s, next_token.span.start)?), - _ => self.expected("literal int", next_token)?, + Keyword::UNION => { + let _ = self.consume_token(&Token::Eq); + self.expect_token(&Token::LParen)?; + let mut tables = Vec::new(); + + loop { + let next_token = self.next_token(); + match next_token.token { + Token::Word(w) => tables.push(w.value), + Token::Comma => continue, + Token::RParen => break, + _ => { + return Err(ParserError::ParserError( + "Expected table name or ')' in UNION".to_string(), + )); + } + } + } + union_tables = Some(tables); + } + + _ => { + return Err(ParserError::ParserError(format!( + "Unexpected keyword: {:?}", + keyword + ))); + } } - } else { - None - }; + } + + let create_table_config = self.parse_optional_create_table_config()?; // ClickHouse supports `PRIMARY KEY`, before `ORDER BY` // https://clickhouse.com/docs/en/sql-reference/statements/create/table#primary-key @@ -6628,30 +7333,6 @@ impl<'a> Parser<'a> { None }; - let create_table_config = self.parse_optional_create_table_config()?; - - let default_charset = if self.parse_keywords(&[Keyword::DEFAULT, Keyword::CHARSET]) { - self.expect_token(&Token::Eq)?; - let next_token = self.next_token(); - match next_token.token { - Token::Word(w) => Some(w.value), - _ => self.expected("identifier", next_token)?, - } - } else { - None - }; - - let collation = if self.parse_keywords(&[Keyword::COLLATE]) { - self.expect_token(&Token::Eq)?; - let next_token = self.next_token(); - match next_token.token { - Token::Word(w) => Some(w.value), - _ => self.expected("identifier", next_token)?, - } - } else { - None - }; - let on_commit = if self.parse_keywords(&[Keyword::ON, Keyword::COMMIT]) { Some(self.parse_create_table_on_commit()?) } else { @@ -6701,6 +7382,30 @@ impl<'a> Parser<'a> { .order_by(order_by) .default_charset(default_charset) .collation(collation) + .compression(compression) + .insert_method(insert_method) + .encryption(encryption) + .key_block_size(key_block_size) + .row_format(row_format) + .data_directory(data_directory) + .index_directory(index_directory) + .pack_keys(pack_keys) + .stats_auto_recalc(stats_auto_recalc) + .stats_persistent(stats_persistent) + .stats_sample_pages(stats_sample_pages) + .delay_key_write(delay_key_write) + .max_rows(max_rows) + .min_rows(min_rows) + .autoextend_size(autoextend_size) + .avg_row_length(avg_row_length) + .checksum(checksum) + .connection(connection) + .engine_attribute(engine_attribute) + .password(password) + .secondary_engine_attribute(secondary_engine_attribute) + .start_transaction(start_transaction) + .tablespace_option(tablespace_option) + .union_tables(union_tables) .on_commit(on_commit) .on_cluster(on_cluster) .clustered_by(clustered_by) diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 43e12746c..3660fb7ab 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -722,6 +722,30 @@ fn test_duckdb_union_datatype() { comment: Default::default(), auto_increment_offset: Default::default(), default_charset: Default::default(), + insert_method: Default::default(), + data_directory: Default::default(), + key_block_size: Default::default(), + pack_keys: Default::default(), + row_format: Default::default(), + stats_auto_recalc: Default::default(), + stats_persistent: Default::default(), + stats_sample_pages: Default::default(), + max_rows: Default::default(), + min_rows: Default::default(), + autoextend_size: Default::default(), + avg_row_length: Default::default(), + checksum: Default::default(), + connection: Default::default(), + engine_attribute: Default::default(), + password: Default::default(), + secondary_engine_attribute: Default::default(), + tablespace_option: Default::default(), + compression: Default::default(), + delay_key_write: Default::default(), + encryption: Default::default(), + start_transaction: Default::default(), + union_tables: Default::default(), + index_directory: Default::default(), collation: Default::default(), on_commit: Default::default(), on_cluster: Default::default(), diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 6865bdd4e..6a60bf4b7 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1540,6 +1540,30 @@ fn parse_create_table_with_valid_options() { comment: None, auto_increment_offset: None, default_charset: None, + insert_method: None, + data_directory: None, + key_block_size: None, + pack_keys: None, + row_format: None, + stats_auto_recalc: None, + stats_persistent: None, + stats_sample_pages: None, + max_rows: None, + min_rows: None, + autoextend_size: None, + avg_row_length: None, + checksum: None, + connection: None, + engine_attribute: None, + password: None, + secondary_engine_attribute: None, + tablespace_option: None, + compression: None, + delay_key_write: None, + encryption: None, + start_transaction: None, + union_tables: None, + index_directory: None, collation: None, on_commit: None, on_cluster: None, @@ -1694,6 +1718,30 @@ fn parse_create_table_with_identity_column() { comment: None, auto_increment_offset: None, default_charset: None, + insert_method: None, + data_directory: None, + key_block_size: None, + pack_keys: None, + row_format: None, + stats_auto_recalc: None, + stats_persistent: None, + stats_sample_pages: None, + max_rows: None, + min_rows: None, + autoextend_size: None, + avg_row_length: None, + checksum: None, + connection: None, + engine_attribute: None, + password: None, + secondary_engine_attribute: None, + tablespace_option: None, + compression: None, + delay_key_write: None, + encryption: None, + start_transaction: None, + union_tables: None, + index_directory: None, collation: None, on_commit: None, on_cluster: None, diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 48fcbbf9b..d16d978b5 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -25,7 +25,7 @@ use matches::assert_matches; use sqlparser::ast::MysqlInsertPriority::{Delayed, HighPriority, LowPriority}; use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, MySqlDialect}; -use sqlparser::parser::{ParserError, ParserOptions}; +use sqlparser::parser::{ParserError, ParserOptions, RowFormat, StorageType, TablespaceOption}; use sqlparser::tokenizer::Span; use sqlparser::tokenizer::Token; use test_utils::*; @@ -862,29 +862,175 @@ 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, + auto_increment_offset, + .. + }) => { + assert_eq!(name.to_string(), "foo"); + assert_eq!( + auto_increment_offset.expect("Should exist").to_string(), + "123" + ); + } + _ => 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"; + let sql2 = "CREATE TABLE mytable (id INT) KEY_BLOCK_SIZE=8 ENGINE=InnoDB ROW_FORMAT=DYNAMIC"; + let sql3 = "CREATE TABLE mytable (id INT) ROW_FORMAT=DYNAMIC KEY_BLOCK_SIZE=8 ENGINE=InnoDB"; + + for sql in [sql1, sql2, sql3] { + match mysql().parse_sql_statements(sql).unwrap().pop().unwrap() { Statement::CreateTable(CreateTable { name, - auto_increment_offset, + engine, + row_format, + key_block_size, .. }) => { - assert_eq!(name.to_string(), "foo"); + assert_eq!(name.to_string(), "mytable"); assert_eq!( - auto_increment_offset.expect("Should exist").to_string(), - "123" + engine, + Some(TableEngine { + name: "InnoDB".to_string(), + parameters: None + }) ); + assert_eq!(row_format, Some(RowFormat::Dynamic)); + assert_eq!(key_block_size.expect("Should exist").to_string(), "8"); } _ => 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, + engine, + default_charset, + auto_increment_offset, + key_block_size, + row_format, + data_directory, + index_directory, + pack_keys, + stats_auto_recalc, + stats_persistent, + stats_sample_pages, + compression, + insert_method, + encryption, + max_rows, + min_rows, + collation, + autoextend_size, + avg_row_length, + checksum, + connection, + engine_attribute, + password, + secondary_engine_attribute, + start_transaction, + tablespace_option, + union_tables, + .. + }) => { + assert_eq!(name.to_string(), "foo"); + assert_eq!( + engine, + Some(TableEngine { + name: "InnoDB".to_string(), + parameters: None + }) + ); + assert_eq!(default_charset, Some("utf8mb4".to_string())); + assert_eq!(collation, Some("utf8mb4_0900_ai_ci".to_string())); + assert_eq!( + auto_increment_offset.expect("Should exist").to_string(), + "123" + ); + assert_eq!(key_block_size.expect("Should exist").to_string(), "8"); + assert_eq!(row_format.expect("Should exist").to_string(), "DYNAMIC"); + assert_eq!(pack_keys.expect("Should exist").to_string(), "1"); + assert_eq!(stats_auto_recalc.expect("Should exist").to_string(), "1"); + assert_eq!(stats_persistent.expect("Should exist").to_string(), "0"); + assert_eq!(stats_sample_pages.expect("Should exist").to_string(), "128"); + assert_eq!(insert_method.expect("Should exist").to_string(), "FIRST"); + assert_eq!(compression.expect("Should exist").to_string(), "ZLIB"); + assert_eq!(encryption.expect("Should exist").to_string(), "Y"); + assert_eq!(max_rows.expect("Should exist").to_string(), "10000"); + assert_eq!(min_rows.expect("Should exist").to_string(), "10"); + assert_eq!(autoextend_size.expect("Should exist").to_string(), "64"); + assert_eq!(avg_row_length.expect("Should exist").to_string(), "128"); + assert_eq!( + if checksum.expect("Should exist") { + "1" + } else { + "0" + }, + "1" + ); + assert_eq!( + connection.expect("Should exist").to_string(), + "mysql://localhost" + ); + assert_eq!( + engine_attribute.expect("Should exist").to_string(), + "primary" + ); + assert_eq!( + password.expect("Should exist").to_string(), + "secure_password" + ); + assert_eq!( + secondary_engine_attribute + .expect("Should exist") + .to_string(), + "secondary_attr" + ); + + assert_eq!(start_transaction.expect("Should exist"), true); + assert_eq!( + tablespace_option.expect("Should exist"), + TablespaceOption { + name: "my_tablespace".to_string(), + storage: Some(StorageType::Disk), + } + ); + assert_eq!( + union_tables.expect("Should exist"), + vec![ + "table1".to_string(), + "table2".to_string(), + "table3".to_string() + ] + ); + assert_eq!( + data_directory.expect("Should exist").path, + "/var/lib/mysql/data" + ); + assert_eq!( + index_directory.expect("Should exist").path, + "/var/lib/mysql/index" + ); + } + _ => unreachable!(), + } +} + #[test] fn parse_create_table_set_enum() { let sql = "CREATE TABLE foo (bar SET('a', 'b'), baz ENUM('a', 'b'))"; diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 3a4504ebb..d41cee63b 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -5115,6 +5115,30 @@ fn parse_trigger_related_functions() { comment: None, auto_increment_offset: None, default_charset: None, + insert_method: None, + data_directory: None, + key_block_size: None, + pack_keys: None, + row_format: None, + stats_auto_recalc: None, + stats_persistent: None, + stats_sample_pages: None, + max_rows: None, + min_rows: None, + autoextend_size: None, + avg_row_length: None, + checksum: None, + connection: None, + engine_attribute: None, + password: None, + secondary_engine_attribute: None, + tablespace_option: None, + compression: None, + delay_key_write: None, + encryption: None, + start_transaction: None, + union_tables: None, + index_directory: None, collation: None, on_commit: None, on_cluster: None,