diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index bb0d4f16b..4cfaddceb 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -393,7 +393,7 @@ impl Dialect for SnowflakeDialect { fn is_column_alias(&self, kw: &Keyword, parser: &mut Parser) -> bool { match kw { - // The following keywords can be considered an alias as long as + // The following keywords can be considered an alias as long as // they are not followed by other tokens that may change their meaning // e.g. `SELECT * EXCEPT (col1) FROM tbl` Keyword::EXCEPT @@ -408,7 +408,7 @@ impl Dialect for SnowflakeDialect { Keyword::LIMIT | Keyword::OFFSET if peek_for_limit_options(parser) => false, // `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT` - // which would give it a different meanings, for example: + // which would give it a different meanings, for example: // `SELECT 1 FETCH FIRST 10 ROWS` - not an alias // `SELECT 1 FETCH 10` - not an alias Keyword::FETCH if parser.peek_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]).is_some() @@ -417,8 +417,8 @@ impl Dialect for SnowflakeDialect { false } - // Reserved keywords by the Snowflake dialect, which seem to be less strictive - // than what is listed in `keywords::RESERVED_FOR_COLUMN_ALIAS`. The following + // Reserved keywords by the Snowflake dialect, which seem to be less strictive + // than what is listed in `keywords::RESERVED_FOR_COLUMN_ALIAS`. The following // keywords were tested with the this statement: `SELECT 1 `. Keyword::FROM | Keyword::GROUP @@ -688,7 +688,7 @@ pub fn parse_create_table( .iceberg(iceberg) .global(global) .dynamic(dynamic) - .hive_formats(Some(Default::default())); + .hive_formats(None); // Snowflake does not enforce order of the parameters in the statement. The parser needs to // parse the statement in a loop. diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 57d94bee1..b137c515a 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5831,15 +5831,19 @@ impl<'a> Parser<'a> { let hive_distribution = self.parse_hive_distribution()?; let hive_formats = self.parse_hive_formats()?; - let file_format = if let Some(ff) = &hive_formats.storage { - match ff { - HiveIOFormat::FileFormat { format } => Some(*format), - _ => None, + let file_format = if let Some(ref hf) = hive_formats { + if let Some(ref ff) = hf.storage { + match ff { + HiveIOFormat::FileFormat { format } => Some(*format), + _ => None, + } + } else { + None } } else { None }; - let location = hive_formats.location.clone(); + let location = hive_formats.as_ref().and_then(|hf| hf.location.clone()); let table_properties = self.parse_options(Keyword::TBLPROPERTIES)?; let table_options = if !table_properties.is_empty() { CreateTableOptions::TableProperties(table_properties) @@ -5850,7 +5854,7 @@ impl<'a> Parser<'a> { .columns(columns) .constraints(constraints) .hive_distribution(hive_distribution) - .hive_formats(Some(hive_formats)) + .hive_formats(hive_formats) .table_options(table_options) .or_replace(or_replace) .if_not_exists(if_not_exists) @@ -7537,8 +7541,8 @@ impl<'a> Parser<'a> { } } - pub fn parse_hive_formats(&mut self) -> Result { - let mut hive_format = HiveFormat::default(); + pub fn parse_hive_formats(&mut self) -> Result, ParserError> { + let mut hive_format: Option = None; loop { match self.parse_one_of_keywords(&[ Keyword::ROW, @@ -7547,7 +7551,9 @@ impl<'a> Parser<'a> { Keyword::WITH, ]) { Some(Keyword::ROW) => { - hive_format.row_format = Some(self.parse_row_format()?); + hive_format + .get_or_insert_with(HiveFormat::default) + .row_format = Some(self.parse_row_format()?); } Some(Keyword::STORED) => { self.expect_keyword_is(Keyword::AS)?; @@ -7555,24 +7561,29 @@ impl<'a> Parser<'a> { let input_format = self.parse_expr()?; self.expect_keyword_is(Keyword::OUTPUTFORMAT)?; let output_format = self.parse_expr()?; - hive_format.storage = Some(HiveIOFormat::IOF { - input_format, - output_format, - }); + hive_format.get_or_insert_with(HiveFormat::default).storage = + Some(HiveIOFormat::IOF { + input_format, + output_format, + }); } else { let format = self.parse_file_format()?; - hive_format.storage = Some(HiveIOFormat::FileFormat { format }); + hive_format.get_or_insert_with(HiveFormat::default).storage = + Some(HiveIOFormat::FileFormat { format }); } } Some(Keyword::LOCATION) => { - hive_format.location = Some(self.parse_literal_string()?); + hive_format.get_or_insert_with(HiveFormat::default).location = + Some(self.parse_literal_string()?); } Some(Keyword::WITH) => { self.prev_token(); let properties = self .parse_options_with_keywords(&[Keyword::WITH, Keyword::SERDEPROPERTIES])?; if !properties.is_empty() { - hive_format.serde_properties = Some(properties); + hive_format + .get_or_insert_with(HiveFormat::default) + .serde_properties = Some(properties); } else { break; } @@ -7787,7 +7798,7 @@ impl<'a> Parser<'a> { .if_not_exists(if_not_exists) .transient(transient) .hive_distribution(hive_distribution) - .hive_formats(Some(hive_formats)) + .hive_formats(hive_formats) .global(global) .query(query) .without_rowid(without_rowid) diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 3649e8d3f..91952b8c0 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -4724,6 +4724,17 @@ fn parse_create_external_table_lowercase() { assert_matches!(ast, Statement::CreateTable(CreateTable { .. })); } +#[test] +fn parse_create_table_hive_formats_none_when_no_options() { + let sql = "CREATE TABLE simple_table (id INT, name VARCHAR(100))"; + match verified_stmt(sql) { + Statement::CreateTable(CreateTable { hive_formats, .. }) => { + assert_eq!(hive_formats, None); + } + _ => unreachable!(), + } +} + #[test] fn parse_alter_table() { let add_column = "ALTER TABLE tab ADD COLUMN foo TEXT;"; diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 0f8051955..73a1afe26 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -739,12 +739,7 @@ fn test_duckdb_union_datatype() { ], constraints: Default::default(), hive_distribution: HiveDistributionStyle::NONE, - hive_formats: Some(HiveFormat { - row_format: Default::default(), - serde_properties: Default::default(), - storage: Default::default(), - location: Default::default() - }), + hive_formats: None, file_format: Default::default(), location: Default::default(), query: Default::default(), diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 99a298f89..37e8e962f 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1881,12 +1881,7 @@ fn parse_create_table_with_valid_options() { ], constraints: vec![], hive_distribution: HiveDistributionStyle::NONE, - hive_formats: Some(HiveFormat { - row_format: None, - serde_properties: None, - storage: None, - location: None, - },), + hive_formats: None, file_format: None, location: None, query: None, @@ -2053,12 +2048,7 @@ fn parse_create_table_with_identity_column() { },], constraints: vec![], hive_distribution: HiveDistributionStyle::NONE, - hive_formats: Some(HiveFormat { - row_format: None, - serde_properties: None, - storage: None, - location: None, - },), + hive_formats: None, file_format: None, location: None, query: None, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index a14ff5ecb..f32b5c401 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -6015,12 +6015,7 @@ fn parse_trigger_related_functions() { ], constraints: vec![], hive_distribution: HiveDistributionStyle::NONE, - hive_formats: Some(HiveFormat { - row_format: None, - serde_properties: None, - storage: None, - location: None - }), + hive_formats: None, file_format: None, location: None, query: None,