Skip to content

Commit 924a116

Browse files
authored
Fix STORAGE LIFECYCLE POLICY for snowflake queries (#2264)
1 parent 9d5a171 commit 924a116

File tree

10 files changed

+96
-6
lines changed

10 files changed

+96
-6
lines changed

src/ast/ddl.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ use crate::ast::{
4848
HiveFormat, HiveIOFormat, HiveRowFormat, HiveSetLocation, Ident, InitializeKind,
4949
MySQLColumnPosition, ObjectName, OnCommit, OneOrManyWithParens, OperateFunctionArg,
5050
OrderByExpr, ProjectionSelect, Query, RefreshModeKind, RowAccessPolicy, SequenceOptions,
51-
Spanned, SqlOption, StorageSerializationPolicy, TableVersion, Tag, TriggerEvent,
52-
TriggerExecBody, TriggerObject, TriggerPeriod, TriggerReferencing, Value, ValueWithSpan,
53-
WrappedCollection,
51+
Spanned, SqlOption, StorageLifecyclePolicy, StorageSerializationPolicy, TableVersion, Tag,
52+
TriggerEvent, TriggerExecBody, TriggerObject, TriggerPeriod, TriggerReferencing, Value,
53+
ValueWithSpan, WrappedCollection,
5454
};
5555
use crate::display_utils::{DisplayCommaSeparated, Indent, NewLine, SpaceOrNewline};
5656
use crate::keywords::Keyword;
@@ -3012,6 +3012,9 @@ pub struct CreateTable {
30123012
/// Snowflake "WITH ROW ACCESS POLICY" clause
30133013
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
30143014
pub with_row_access_policy: Option<RowAccessPolicy>,
3015+
/// Snowflake `WITH STORAGE LIFECYCLE POLICY` clause
3016+
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
3017+
pub with_storage_lifecycle_policy: Option<StorageLifecyclePolicy>,
30153018
/// Snowflake "WITH TAG" clause
30163019
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
30173020
pub with_tags: Option<Vec<Tag>>,
@@ -3317,6 +3320,10 @@ impl fmt::Display for CreateTable {
33173320
write!(f, " {row_access_policy}",)?;
33183321
}
33193322

3323+
if let Some(storage_lifecycle_policy) = &self.with_storage_lifecycle_policy {
3324+
write!(f, " {storage_lifecycle_policy}",)?;
3325+
}
3326+
33203327
if let Some(tag) = &self.with_tags {
33213328
write!(f, " WITH TAG ({})", display_comma_separated(tag.as_slice()))?;
33223329
}

src/ast/helpers/stmt_create_table.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ use crate::ast::{
2828
ClusteredBy, ColumnDef, CommentDef, CreateTable, CreateTableLikeKind, CreateTableOptions,
2929
DistStyle, Expr, FileFormat, ForValues, HiveDistributionStyle, HiveFormat, Ident,
3030
InitializeKind, ObjectName, OnCommit, OneOrManyWithParens, Query, RefreshModeKind,
31-
RowAccessPolicy, Statement, StorageSerializationPolicy, TableConstraint, TableVersion, Tag,
32-
WrappedCollection,
31+
RowAccessPolicy, Statement, StorageLifecyclePolicy, StorageSerializationPolicy,
32+
TableConstraint, TableVersion, Tag, WrappedCollection,
3333
};
3434

3535
use crate::parser::ParserError;
@@ -149,6 +149,8 @@ pub struct CreateTableBuilder {
149149
pub with_aggregation_policy: Option<ObjectName>,
150150
/// Optional row access policy applied to the table.
151151
pub with_row_access_policy: Option<RowAccessPolicy>,
152+
/// Optional storage lifecycle policy applied to the table.
153+
pub with_storage_lifecycle_policy: Option<StorageLifecyclePolicy>,
152154
/// Optional tags/labels attached to the table metadata.
153155
pub with_tags: Option<Vec<Tag>>,
154156
/// Optional base location for staged data.
@@ -227,6 +229,7 @@ impl CreateTableBuilder {
227229
default_ddl_collation: None,
228230
with_aggregation_policy: None,
229231
with_row_access_policy: None,
232+
with_storage_lifecycle_policy: None,
230233
with_tags: None,
231234
base_location: None,
232235
external_volume: None,
@@ -459,6 +462,14 @@ impl CreateTableBuilder {
459462
self.with_row_access_policy = with_row_access_policy;
460463
self
461464
}
465+
/// Attach a storage lifecycle policy to the table.
466+
pub fn with_storage_lifecycle_policy(
467+
mut self,
468+
with_storage_lifecycle_policy: Option<StorageLifecyclePolicy>,
469+
) -> Self {
470+
self.with_storage_lifecycle_policy = with_storage_lifecycle_policy;
471+
self
472+
}
462473
/// Attach tags/labels to the table metadata.
463474
pub fn with_tags(mut self, with_tags: Option<Vec<Tag>>) -> Self {
464475
self.with_tags = with_tags;
@@ -582,6 +593,7 @@ impl CreateTableBuilder {
582593
default_ddl_collation: self.default_ddl_collation,
583594
with_aggregation_policy: self.with_aggregation_policy,
584595
with_row_access_policy: self.with_row_access_policy,
596+
with_storage_lifecycle_policy: self.with_storage_lifecycle_policy,
585597
with_tags: self.with_tags,
586598
base_location: self.base_location,
587599
external_volume: self.external_volume,
@@ -661,6 +673,7 @@ impl From<CreateTable> for CreateTableBuilder {
661673
default_ddl_collation: table.default_ddl_collation,
662674
with_aggregation_policy: table.with_aggregation_policy,
663675
with_row_access_policy: table.with_row_access_policy,
676+
with_storage_lifecycle_policy: table.with_storage_lifecycle_policy,
664677
with_tags: table.with_tags,
665678
base_location: table.base_location,
666679
external_volume: table.external_volume,

src/ast/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10472,6 +10472,30 @@ impl Display for RowAccessPolicy {
1047210472
}
1047310473
}
1047410474

10475+
/// Snowflake `[ WITH ] STORAGE LIFECYCLE POLICY <policy_name> ON ( <col_name> [ , ... ] )`
10476+
///
10477+
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
10478+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
10479+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10480+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
10481+
pub struct StorageLifecyclePolicy {
10482+
/// The fully-qualified policy object name.
10483+
pub policy: ObjectName,
10484+
/// Column names the policy applies to.
10485+
pub on: Vec<Ident>,
10486+
}
10487+
10488+
impl Display for StorageLifecyclePolicy {
10489+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
10490+
write!(
10491+
f,
10492+
"WITH STORAGE LIFECYCLE POLICY {} ON ({})",
10493+
self.policy,
10494+
display_comma_separated(self.on.as_slice())
10495+
)
10496+
}
10497+
}
10498+
1047510499
/// Snowflake `WITH TAG ( tag_name = '<tag_value>', ...)`
1047610500
///
1047710501
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ impl Spanned for CreateTable {
572572
default_ddl_collation: _, // string, no span
573573
with_aggregation_policy: _, // todo, Snowflake specific
574574
with_row_access_policy: _, // todo, Snowflake specific
575+
with_storage_lifecycle_policy: _, // todo, Snowflake specific
575576
with_tags: _, // todo, Snowflake specific
576577
external_volume: _, // todo, Snowflake specific
577578
base_location: _, // todo, Snowflake specific

src/dialect/snowflake.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use crate::ast::{
3333
IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, InitializeKind,
3434
Insert, MultiTableInsertIntoClause, MultiTableInsertType, MultiTableInsertValue,
3535
MultiTableInsertValues, MultiTableInsertWhenClause, ObjectName, ObjectNamePart,
36-
RefreshModeKind, RowAccessPolicy, ShowObjects, SqlOption, Statement,
36+
RefreshModeKind, RowAccessPolicy, ShowObjects, SqlOption, Statement, StorageLifecyclePolicy,
3737
StorageSerializationPolicy, TableObject, TagsColumnOption, Value, WrappedCollection,
3838
};
3939
use crate::dialect::{Dialect, Precedence};
@@ -917,6 +917,7 @@ pub fn parse_create_table(
917917
Keyword::WITH => {
918918
parser.expect_one_of_keywords(&[
919919
Keyword::AGGREGATION,
920+
Keyword::STORAGE,
920921
Keyword::TAG,
921922
Keyword::ROW,
922923
])?;
@@ -938,6 +939,19 @@ pub fn parse_create_table(
938939
builder =
939940
builder.with_row_access_policy(Some(RowAccessPolicy::new(policy, columns)))
940941
}
942+
Keyword::STORAGE => {
943+
parser.expect_keywords(&[Keyword::LIFECYCLE, Keyword::POLICY])?;
944+
let policy = parser.parse_object_name(false)?;
945+
parser.expect_keyword_is(Keyword::ON)?;
946+
parser.expect_token(&Token::LParen)?;
947+
let columns = parser.parse_comma_separated(|p| p.parse_identifier())?;
948+
parser.expect_token(&Token::RParen)?;
949+
950+
builder = builder.with_storage_lifecycle_policy(Some(StorageLifecyclePolicy {
951+
policy,
952+
on: columns,
953+
}))
954+
}
941955
Keyword::TAG => {
942956
parser.expect_token(&Token::LParen)?;
943957
let tags = parser.parse_comma_separated(Parser::parse_tag)?;

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ define_keywords!(
573573
LEFT,
574574
LEFTARG,
575575
LEVEL,
576+
LIFECYCLE,
576577
LIKE,
577578
LIKE_REGEX,
578579
LIMIT,

tests/sqlparser_duckdb.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,7 @@ fn test_duckdb_union_datatype() {
776776
default_ddl_collation: Default::default(),
777777
with_aggregation_policy: Default::default(),
778778
with_row_access_policy: Default::default(),
779+
with_storage_lifecycle_policy: Default::default(),
779780
with_tags: Default::default(),
780781
base_location: Default::default(),
781782
external_volume: Default::default(),

tests/sqlparser_mssql.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1994,6 +1994,7 @@ fn parse_create_table_with_valid_options() {
19941994
default_ddl_collation: None,
19951995
with_aggregation_policy: None,
19961996
with_row_access_policy: None,
1997+
with_storage_lifecycle_policy: None,
19971998
with_tags: None,
19981999
base_location: None,
19992000
external_volume: None,
@@ -2166,6 +2167,7 @@ fn parse_create_table_with_identity_column() {
21662167
default_ddl_collation: None,
21672168
with_aggregation_policy: None,
21682169
with_row_access_policy: None,
2170+
with_storage_lifecycle_policy: None,
21692171
with_tags: None,
21702172
base_location: None,
21712173
external_volume: None,

tests/sqlparser_postgres.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6462,6 +6462,7 @@ fn parse_trigger_related_functions() {
64626462
default_ddl_collation: None,
64636463
with_aggregation_policy: None,
64646464
with_row_access_policy: None,
6465+
with_storage_lifecycle_policy: None,
64656466
with_tags: None,
64666467
base_location: None,
64676468
external_volume: None,

tests/sqlparser_snowflake.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,32 @@ fn test_snowflake_create_table_with_row_access_policy() {
286286
}
287287
}
288288

289+
#[test]
290+
fn test_snowflake_create_table_with_storage_lifecycle_policy() {
291+
// WITH keyword
292+
match snowflake().verified_stmt(
293+
"CREATE TABLE IF NOT EXISTS my_table (a NUMBER(38, 0), b VARIANT) WITH STORAGE LIFECYCLE POLICY dba.global_settings.my_policy ON (a)",
294+
) {
295+
Statement::CreateTable(CreateTable {
296+
name,
297+
with_storage_lifecycle_policy,
298+
..
299+
}) => {
300+
assert_eq!("my_table", name.to_string());
301+
let policy = with_storage_lifecycle_policy.unwrap();
302+
assert_eq!("dba.global_settings.my_policy", policy.policy.to_string());
303+
assert_eq!(vec![Ident::new("a")], policy.on);
304+
}
305+
_ => unreachable!(),
306+
}
307+
308+
// Without WITH keyword — canonicalizes to WITH form
309+
snowflake().one_statement_parses_to(
310+
"CREATE TABLE my_table (a NUMBER(38, 0)) STORAGE LIFECYCLE POLICY my_policy ON (a, b)",
311+
"CREATE TABLE my_table (a NUMBER(38, 0)) WITH STORAGE LIFECYCLE POLICY my_policy ON (a, b)",
312+
);
313+
}
314+
289315
#[test]
290316
fn test_snowflake_create_table_with_tag() {
291317
match snowflake()

0 commit comments

Comments
 (0)