From f8663a4345c8f895be8be98d4e21556ea58e8ce3 Mon Sep 17 00:00:00 2001 From: osipovartem Date: Mon, 12 Oct 2026 03:11:43 +0300 Subject: [PATCH] Parse Snowflake Iceberg version and optional base location --- src/ast/ddl.rs | 6 ++++++ src/ast/helpers/stmt_create_table.rs | 5 +++++ src/ast/spans.rs | 1 + src/dialect/snowflake.rs | 10 ++++------ src/keywords.rs | 1 + tests/sqlparser_duckdb.rs | 1 + tests/sqlparser_mssql.rs | 2 ++ tests/sqlparser_postgres.rs | 1 + tests/sqlparser_snowflake.rs | 28 +++++++++++++++++++++++----- 9 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 67aefb3928..e2a3676555 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -3024,6 +3024,9 @@ pub struct CreateTable { /// Snowflake "BASE_LOCATION" clause for Iceberg tables /// pub base_location: Option, + /// Snowflake "ICEBERG_VERSION" clause for Iceberg tables. + /// + pub iceberg_version: Option, /// Snowflake "CATALOG" clause for Iceberg tables /// pub catalog: Option, @@ -3265,6 +3268,9 @@ impl fmt::Display for CreateTable { if let Some(base_location) = self.base_location.as_ref() { write!(f, " BASE_LOCATION='{base_location}'")?; } + if let Some(iceberg_version) = self.iceberg_version { + write!(f, " ICEBERG_VERSION={iceberg_version}")?; + } } if let Some(catalog_sync) = self.catalog_sync.as_ref() { diff --git a/src/ast/helpers/stmt_create_table.rs b/src/ast/helpers/stmt_create_table.rs index ab2feb6930..92b343f974 100644 --- a/src/ast/helpers/stmt_create_table.rs +++ b/src/ast/helpers/stmt_create_table.rs @@ -155,6 +155,8 @@ pub struct CreateTableBuilder { pub with_tags: Option>, /// Optional base location for staged data. pub base_location: Option, + /// Optional Iceberg format version. + pub iceberg_version: Option, /// Optional external volume identifier. pub external_volume: Option, /// Optional catalog name. @@ -234,6 +236,7 @@ impl CreateTableBuilder { with_storage_lifecycle_policy: None, with_tags: None, base_location: None, + iceberg_version: None, external_volume: None, catalog: None, catalog_sync: None, @@ -604,6 +607,7 @@ impl CreateTableBuilder { with_storage_lifecycle_policy: self.with_storage_lifecycle_policy, with_tags: self.with_tags, base_location: self.base_location, + iceberg_version: self.iceberg_version, external_volume: self.external_volume, catalog: self.catalog, catalog_sync: self.catalog_sync, @@ -685,6 +689,7 @@ impl From for CreateTableBuilder { with_storage_lifecycle_policy: table.with_storage_lifecycle_policy, with_tags: table.with_tags, base_location: table.base_location, + iceberg_version: table.iceberg_version, external_volume: table.external_volume, catalog: table.catalog, catalog_sync: table.catalog_sync, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 91f547053a..c0a4499d92 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -609,6 +609,7 @@ impl Spanned for CreateTable { with_tags: _, // todo, Snowflake specific external_volume: _, // todo, Snowflake specific base_location: _, // todo, Snowflake specific + iceberg_version: _, // u64, no span catalog: _, // todo, Snowflake specific catalog_sync: _, // todo, Snowflake specific storage_serialization_policy: _, diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 754bbee640..23fad2a398 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -1046,6 +1046,10 @@ pub fn parse_create_table( parser.expect_token(&Token::Eq)?; builder.base_location = Some(parser.parse_literal_string()?); } + Keyword::ICEBERG_VERSION if iceberg => { + parser.expect_token(&Token::Eq)?; + builder.iceberg_version = Some(parser.parse_literal_uint()?); + } Keyword::CATALOG_SYNC => { parser.expect_token(&Token::Eq)?; builder.catalog_sync = Some(parser.parse_literal_string()?); @@ -1131,12 +1135,6 @@ pub fn parse_create_table( builder = builder.table_options(table_options); - if iceberg && builder.base_location.is_none() { - return Err(ParserError::ParserError( - "BASE_LOCATION is required for ICEBERG tables".to_string(), - )); - } - Ok(builder.build()) } diff --git a/src/keywords.rs b/src/keywords.rs index c10530af1c..e51b839e31 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -495,6 +495,7 @@ define_keywords!( HUGEINT, IAM_ROLE, ICEBERG, + ICEBERG_VERSION, ID, IDENTIFIED, IDENTITY, diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index df62685808..d36e907305 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -779,6 +779,7 @@ fn test_duckdb_union_datatype() { with_storage_lifecycle_policy: Default::default(), with_tags: Default::default(), base_location: Default::default(), + iceberg_version: Default::default(), external_volume: Default::default(), catalog: Default::default(), catalog_sync: Default::default(), diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index ceac1b60a9..53b96e8657 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -2003,6 +2003,7 @@ fn parse_create_table_with_valid_options() { with_storage_lifecycle_policy: None, with_tags: None, base_location: None, + iceberg_version: None, external_volume: None, catalog: None, catalog_sync: None, @@ -2177,6 +2178,7 @@ fn parse_create_table_with_identity_column() { with_storage_lifecycle_policy: None, with_tags: None, base_location: None, + iceberg_version: None, external_volume: None, catalog: None, catalog_sync: None, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 9a51264e0f..eeba53b566 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -6702,6 +6702,7 @@ fn parse_trigger_related_functions() { with_storage_lifecycle_policy: None, with_tags: None, base_location: None, + iceberg_version: None, external_volume: None, catalog: None, catalog_sync: None, diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index b3cc8b6335..f5dfa3f5d9 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -1070,11 +1070,29 @@ fn test_snowflake_create_iceberg_table() { #[test] fn test_snowflake_create_iceberg_table_without_location() { - let res = snowflake().parse_sql_statements("CREATE ICEBERG TABLE my_table (a INT)"); - assert_eq!( - ParserError::ParserError("BASE_LOCATION is required for ICEBERG tables".to_string()), - res.unwrap_err() - ); + match snowflake().verified_stmt("CREATE ICEBERG TABLE my_table (a INT)") { + Statement::CreateTable(table) => assert_eq!(table.base_location, None), + _ => unreachable!(), + } +} + +#[test] +fn test_snowflake_create_iceberg_table_version() { + for version in [2, 3] { + let sql = format!( + "CREATE ICEBERG TABLE my_table (a INT) EXTERNAL_VOLUME='SNOWFLAKE_MANAGED' CATALOG='SNOWFLAKE' ICEBERG_VERSION={version}" + ); + match snowflake().verified_stmt(&sql) { + Statement::CreateTable(table) => { + assert_eq!(table.iceberg_version, Some(version)); + assert_eq!(table.base_location, None); + } + _ => unreachable!(), + } + } + assert!(snowflake() + .parse_sql_statements("CREATE TABLE my_table (a INT) ICEBERG_VERSION=3") + .is_err()); } #[test]