Skip to content

Commit 663959d

Browse files
authored
Add iceberg create table (#1)
* Create Iceberg Table snowflake statement parser * Cargo fmt * Extend iceberg all options test * Add invalid base_location test * Revert import order change
1 parent c808c4e commit 663959d

File tree

5 files changed

+566
-122
lines changed

5 files changed

+566
-122
lines changed

src/ast/mod.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2516,6 +2516,35 @@ pub enum Statement {
25162516
/// CREATE TABLE
25172517
/// ```
25182518
CreateTable(CreateTable),
2519+
/// ``` sql
2520+
/// CREATE ICEBERG TABLE
2521+
/// Snowflake-specific statement
2522+
/// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
2523+
/// ```
2524+
CreateIcebergTable {
2525+
or_replace: bool,
2526+
if_not_exists: bool,
2527+
/// Table name
2528+
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
2529+
name: ObjectName,
2530+
columns: Vec<ColumnDef>,
2531+
constraints: Vec<TableConstraint>,
2532+
with_options: Vec<SqlOption>,
2533+
comment: Option<CommentDef>,
2534+
cluster_by: Option<WrappedCollection<Vec<Ident>>>,
2535+
external_volume: Option<String>,
2536+
catalog: Option<String>,
2537+
base_location: String,
2538+
catalog_sync: Option<String>,
2539+
storage_serialization_policy: Option<StorageSerializationPolicy>,
2540+
data_retention_time_in_days: Option<u64>,
2541+
max_data_extension_time_in_days: Option<u64>,
2542+
change_tracking: Option<bool>,
2543+
copy_grants: bool,
2544+
with_aggregation_policy: Option<ObjectName>,
2545+
with_row_access_policy: Option<RowAccessPolicy>,
2546+
with_tags: Option<Vec<Tag>>,
2547+
},
25192548
/// ```sql
25202549
/// CREATE VIRTUAL TABLE .. USING <module_name> (<module_args>)`
25212550
/// ```
@@ -4030,6 +4059,120 @@ impl fmt::Display for Statement {
40304059
}
40314060
Ok(())
40324061
}
4062+
Statement::CreateIcebergTable {
4063+
or_replace,
4064+
if_not_exists,
4065+
name,
4066+
columns,
4067+
constraints,
4068+
with_options,
4069+
comment,
4070+
cluster_by,
4071+
external_volume,
4072+
catalog,
4073+
base_location,
4074+
catalog_sync,
4075+
storage_serialization_policy,
4076+
data_retention_time_in_days,
4077+
max_data_extension_time_in_days,
4078+
change_tracking,
4079+
copy_grants,
4080+
with_row_access_policy,
4081+
with_aggregation_policy,
4082+
with_tags,
4083+
} => {
4084+
write!(
4085+
f,
4086+
"CREATE {or_replace}ICEBERG TABLE {if_not_exists}{name}",
4087+
if_not_exists = if *if_not_exists { "IF NOT EXISTS" } else { "" },
4088+
or_replace = if *or_replace { "OR REPLACE " } else { "" }
4089+
)?;
4090+
if !columns.is_empty() || !constraints.is_empty() {
4091+
write!(f, " ({}", display_comma_separated(columns))?;
4092+
if !columns.is_empty() && !constraints.is_empty() {
4093+
write!(f, ", ")?;
4094+
}
4095+
write!(f, "{})", display_comma_separated(&constraints))?;
4096+
}
4097+
if !with_options.is_empty() {
4098+
write!(f, " WITH ({})", display_comma_separated(&with_options))?;
4099+
}
4100+
if let Some(comment_def) = &comment {
4101+
match comment_def {
4102+
CommentDef::WithEq(comment) => {
4103+
write!(f, " COMMENT = '{comment}'")?;
4104+
}
4105+
CommentDef::WithoutEq(comment) => {
4106+
write!(f, " COMMENT '{comment}'")?;
4107+
}
4108+
// For CommentDef::AfterColumnDefsWithoutEq will be displayed after column definition
4109+
CommentDef::AfterColumnDefsWithoutEq(_) => (),
4110+
}
4111+
}
4112+
if let Some(cluster_by) = cluster_by {
4113+
write!(f, " CLUSTER BY {cluster_by}")?;
4114+
}
4115+
if let Some(external_volume) = external_volume {
4116+
write!(f, " EXTERNAL_VOLUME = '{external_volume}'")?;
4117+
}
4118+
4119+
if let Some(catalog) = catalog {
4120+
write!(f, " CATALOG = '{catalog}'")?;
4121+
}
4122+
4123+
write!(f, " BASE_LOCATION = '{base_location}'")?;
4124+
4125+
if let Some(catalog_sync) = catalog_sync {
4126+
write!(f, " CATALOG_SYNC = '{catalog_sync}'")?;
4127+
}
4128+
4129+
if let Some(storage_serialization_policy) = storage_serialization_policy {
4130+
write!(
4131+
f,
4132+
" STORAGE_SERIALIZATION_POLICY = {storage_serialization_policy}"
4133+
)?;
4134+
}
4135+
4136+
if *copy_grants {
4137+
write!(f, " COPY GRANTS")?;
4138+
}
4139+
4140+
if let Some(is_enabled) = change_tracking {
4141+
write!(
4142+
f,
4143+
" CHANGE_TRACKING = {}",
4144+
if *is_enabled { "TRUE" } else { "FALSE" }
4145+
)?;
4146+
}
4147+
4148+
if let Some(data_retention_time_in_days) = data_retention_time_in_days {
4149+
write!(
4150+
f,
4151+
" DATA_RETENTION_TIME_IN_DAYS = {data_retention_time_in_days}",
4152+
)?;
4153+
}
4154+
4155+
if let Some(max_data_extension_time_in_days) = max_data_extension_time_in_days {
4156+
write!(
4157+
f,
4158+
" MAX_DATA_EXTENSION_TIME_IN_DAYS = {max_data_extension_time_in_days}",
4159+
)?;
4160+
}
4161+
4162+
if let Some(with_aggregation_policy) = with_aggregation_policy {
4163+
write!(f, " WITH AGGREGATION POLICY {with_aggregation_policy}",)?;
4164+
}
4165+
4166+
if let Some(row_access_policy) = with_row_access_policy {
4167+
write!(f, " {row_access_policy}",)?;
4168+
}
4169+
4170+
if let Some(tag) = with_tags {
4171+
write!(f, " WITH TAG ({})", display_comma_separated(tag.as_slice()))?;
4172+
}
4173+
4174+
Ok(())
4175+
}
40334176
Statement::CreateVirtualTable {
40344177
name,
40354178
if_not_exists,
@@ -8064,6 +8207,29 @@ impl fmt::Display for SessionParamValue {
80648207
}
80658208
}
80668209

8210+
/// Snowflake StorageSerializationPolicy for Iceberg Tables
8211+
/// ```sql
8212+
/// [ STORAGE_SERIALIZATION_POLICY = { COMPATIBLE | OPTIMIZED } ]
8213+
/// ```
8214+
///
8215+
/// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
8216+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
8217+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8218+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
8219+
pub enum StorageSerializationPolicy {
8220+
Compatible,
8221+
Optimized,
8222+
}
8223+
8224+
impl Display for StorageSerializationPolicy {
8225+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8226+
match self {
8227+
StorageSerializationPolicy::Compatible => write!(f, "COMPATIBLE"),
8228+
StorageSerializationPolicy::Optimized => write!(f, "OPTIMIZED"),
8229+
}
8230+
}
8231+
}
8232+
80678233
#[cfg(test)]
80688234
mod tests {
80698235
use super::*;

src/ast/spans.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use core::iter;
19-
2018
use crate::tokenizer::Span;
19+
use core::iter;
2120

2221
use super::{
2322
dcl::SecondaryRoles, AccessExpr, AlterColumnOperation, AlterIndexOperation,
@@ -384,6 +383,33 @@ impl Spanned for Statement {
384383
.chain(to.iter().map(|i| i.span())),
385384
),
386385
Statement::CreateTable(create_table) => create_table.span(),
386+
Statement::CreateIcebergTable {
387+
or_replace: _,
388+
if_not_exists: _,
389+
name,
390+
columns,
391+
constraints,
392+
with_options,
393+
comment: _,
394+
cluster_by: _,
395+
external_volume: _,
396+
catalog: _,
397+
base_location: _,
398+
catalog_sync: _,
399+
storage_serialization_policy: _,
400+
data_retention_time_in_days: _,
401+
max_data_extension_time_in_days: _,
402+
change_tracking: _,
403+
copy_grants: _,
404+
with_row_access_policy: _,
405+
with_aggregation_policy: _,
406+
with_tags: _,
407+
} => union_spans(
408+
core::iter::once(name.span())
409+
.chain(columns.iter().map(|i| i.span()))
410+
.chain(constraints.iter().map(|i| i.span()))
411+
.chain(with_options.iter().map(|i| i.span())),
412+
),
387413
Statement::CreateVirtualTable {
388414
name,
389415
if_not_exists: _,

0 commit comments

Comments
 (0)