Skip to content

Commit e739dd1

Browse files
Snowflake: parse TRANSIENT DYNAMIC, bare TARGET_LAG=DOWNSTREAM, INITIALIZATION_WAREHOUSE, SCHEDULER, IMMUTABLE WHERE
1 parent 81db47f commit e739dd1

6 files changed

Lines changed: 153 additions & 10 deletions

File tree

src/ast/ddl.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3127,9 +3127,23 @@ pub struct CreateTable {
31273127
/// Snowflake "TARGET_LAG" clause for dybamic tables
31283128
/// <https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table>
31293129
pub target_lag: Option<String>,
3130-
/// Snowflake "WAREHOUSE" clause for dybamic tables
3130+
/// Snowflake "WAREHOUSE" clause for dybamic tables. Stored verbatim (not
3131+
/// as an `Ident`) so the user's original casing survives — SHOW / GET_DDL
3132+
/// echo the warehouse exactly as written.
31313133
/// <https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table>
3132-
pub warehouse: Option<Ident>,
3134+
pub warehouse: Option<String>,
3135+
/// Snowflake "INITIALIZATION_WAREHOUSE" clause for dynamic tables. Stored
3136+
/// verbatim for the same case-fidelity reason as `warehouse`.
3137+
/// <https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table>
3138+
pub initialization_warehouse: Option<String>,
3139+
/// Snowflake "SCHEDULER" clause for dynamic tables (a quoted string or a
3140+
/// bare keyword such as `DISABLE`); the value is stored verbatim.
3141+
/// <https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table>
3142+
pub scheduler: Option<String>,
3143+
/// Snowflake "IMMUTABLE WHERE (<predicate>)" clause for dynamic tables.
3144+
/// Stored as the serialized predicate text so its casing survives.
3145+
/// <https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table>
3146+
pub immutable_where: Option<String>,
31333147
/// Snowflake "REFRESH_MODE" clause for dybamic tables
31343148
/// <https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table>
31353149
pub refresh_mode: Option<RefreshModeKind>,
@@ -3445,6 +3459,18 @@ impl fmt::Display for CreateTable {
34453459
write!(f, " WAREHOUSE={warehouse}")?;
34463460
}
34473461

3462+
if let Some(initialization_warehouse) = &self.initialization_warehouse {
3463+
write!(f, " INITIALIZATION_WAREHOUSE={initialization_warehouse}")?;
3464+
}
3465+
3466+
if let Some(scheduler) = &self.scheduler {
3467+
write!(f, " SCHEDULER='{scheduler}'")?;
3468+
}
3469+
3470+
if let Some(immutable_where) = &self.immutable_where {
3471+
write!(f, " IMMUTABLE WHERE ({immutable_where})")?;
3472+
}
3473+
34483474
if let Some(refresh_mode) = &self.refresh_mode {
34493475
write!(f, " REFRESH_MODE={refresh_mode}")?;
34503476
}

src/ast/helpers/stmt_create_table.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,14 @@ pub struct CreateTableBuilder {
171171
pub table_options: CreateTableOptions,
172172
/// Optional target lag configuration.
173173
pub target_lag: Option<String>,
174-
/// Optional warehouse identifier.
175-
pub warehouse: Option<Ident>,
174+
/// Optional warehouse name, stored verbatim.
175+
pub warehouse: Option<String>,
176+
/// Optional initialization warehouse name, stored verbatim.
177+
pub initialization_warehouse: Option<String>,
178+
/// Optional scheduler value (quoted string or bare keyword).
179+
pub scheduler: Option<String>,
180+
/// Optional `IMMUTABLE WHERE` predicate text.
181+
pub immutable_where: Option<String>,
176182
/// Optional refresh mode for materialized tables.
177183
pub refresh_mode: Option<RefreshModeKind>,
178184
/// Optional initialization kind for the table.
@@ -247,6 +253,9 @@ impl CreateTableBuilder {
247253
table_options: CreateTableOptions::None,
248254
target_lag: None,
249255
warehouse: None,
256+
initialization_warehouse: None,
257+
scheduler: None,
258+
immutable_where: None,
250259
refresh_mode: None,
251260
initialize: None,
252261
require_user: false,
@@ -522,11 +531,26 @@ impl CreateTableBuilder {
522531
self.target_lag = target_lag;
523532
self
524533
}
525-
/// Associate the table with a warehouse identifier.
526-
pub fn warehouse(mut self, warehouse: Option<Ident>) -> Self {
534+
/// Associate the table with a warehouse name (stored verbatim).
535+
pub fn warehouse(mut self, warehouse: Option<String>) -> Self {
527536
self.warehouse = warehouse;
528537
self
529538
}
539+
/// Set the initialization warehouse name (stored verbatim).
540+
pub fn initialization_warehouse(mut self, initialization_warehouse: Option<String>) -> Self {
541+
self.initialization_warehouse = initialization_warehouse;
542+
self
543+
}
544+
/// Set the scheduler value (quoted string or bare keyword).
545+
pub fn scheduler(mut self, scheduler: Option<String>) -> Self {
546+
self.scheduler = scheduler;
547+
self
548+
}
549+
/// Set the `IMMUTABLE WHERE` predicate text.
550+
pub fn immutable_where(mut self, immutable_where: Option<String>) -> Self {
551+
self.immutable_where = immutable_where;
552+
self
553+
}
530554
/// Set refresh mode for materialized/managed tables.
531555
pub fn refresh_mode(mut self, refresh_mode: Option<RefreshModeKind>) -> Self {
532556
self.refresh_mode = refresh_mode;
@@ -619,6 +643,9 @@ impl CreateTableBuilder {
619643
table_options: self.table_options,
620644
target_lag: self.target_lag,
621645
warehouse: self.warehouse,
646+
initialization_warehouse: self.initialization_warehouse,
647+
scheduler: self.scheduler,
648+
immutable_where: self.immutable_where,
622649
refresh_mode: self.refresh_mode,
623650
initialize: self.initialize,
624651
require_user: self.require_user,
@@ -702,6 +729,9 @@ impl From<CreateTable> for CreateTableBuilder {
702729
table_options: table.table_options,
703730
target_lag: table.target_lag,
704731
warehouse: table.warehouse,
732+
initialization_warehouse: table.initialization_warehouse,
733+
scheduler: table.scheduler,
734+
immutable_where: table.immutable_where,
705735
refresh_mode: table.refresh_mode,
706736
initialize: table.initialize,
707737
require_user: table.require_user,

src/ast/spans.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,9 @@ impl Spanned for CreateTable {
656656
table_options,
657657
target_lag: _,
658658
warehouse: _,
659+
initialization_warehouse: _,
660+
scheduler: _,
661+
immutable_where: _,
659662
version: _,
660663
refresh_mode: _,
661664
initialize: _,

src/dialect/snowflake.rs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -431,13 +431,24 @@ impl Dialect for SnowflakeDialect {
431431
_ => None,
432432
};
433433

434-
let dynamic = parser.parse_keyword(Keyword::DYNAMIC);
435-
436434
let mut temporary = false;
437435
let mut volatile = false;
438436
let mut transient = false;
439437
let mut iceberg = false;
440438

439+
// Snowflake allows a leading `TRANSIENT` before `DYNAMIC`
440+
// (`CREATE [OR REPLACE] [TRANSIENT] DYNAMIC [ICEBERG] TABLE`); the
441+
// standalone `TRANSIENT` modifier for plain tables is still handled
442+
// by the modifier group below.
443+
let dynamic = if parser.parse_keyword(Keyword::DYNAMIC) {
444+
true
445+
} else if parser.parse_keywords(&[Keyword::TRANSIENT, Keyword::DYNAMIC]) {
446+
transient = true;
447+
true
448+
} else {
449+
false
450+
};
451+
441452
match parser.parse_one_of_keywords(&[
442453
Keyword::TEMP,
443454
Keyword::TEMPORARY,
@@ -1221,13 +1232,43 @@ pub fn parse_create_table(
12211232
}
12221233
Keyword::TARGET_LAG => {
12231234
parser.expect_token(&Token::Eq)?;
1224-
let target_lag = parser.parse_literal_string()?;
1235+
// TARGET_LAG accepts a quoted duration ('1 minute') or the
1236+
// bare keyword DOWNSTREAM.
1237+
let target_lag = if parser.parse_keyword(Keyword::DOWNSTREAM) {
1238+
"DOWNSTREAM".to_string()
1239+
} else {
1240+
parser.parse_literal_string()?
1241+
};
12251242
builder = builder.target_lag(Some(target_lag));
12261243
}
12271244
Keyword::WAREHOUSE => {
12281245
parser.expect_token(&Token::Eq)?;
12291246
let warehouse = parser.parse_identifier()?;
1230-
builder = builder.warehouse(Some(warehouse));
1247+
builder = builder.warehouse(Some(warehouse.value));
1248+
}
1249+
Keyword::INITIALIZATION_WAREHOUSE => {
1250+
parser.expect_token(&Token::Eq)?;
1251+
let warehouse = parser.parse_identifier()?;
1252+
builder = builder.initialization_warehouse(Some(warehouse.value));
1253+
}
1254+
Keyword::SCHEDULER => {
1255+
parser.expect_token(&Token::Eq)?;
1256+
// SCHEDULER accepts a quoted string ('DISABLE') or a bare
1257+
// keyword (the GET_DDL spelling, e.g. DISABLE).
1258+
let value_token = parser.next_token();
1259+
let scheduler = match &value_token.token {
1260+
Token::SingleQuotedString(s) => s.clone(),
1261+
Token::Word(w) => w.value.clone(),
1262+
_ => return parser.expected("a scheduler value", value_token),
1263+
};
1264+
builder = builder.scheduler(Some(scheduler));
1265+
}
1266+
Keyword::IMMUTABLE => {
1267+
parser.expect_keyword_is(Keyword::WHERE)?;
1268+
parser.expect_token(&Token::LParen)?;
1269+
let predicate = parser.parse_expr()?;
1270+
parser.expect_token(&Token::RParen)?;
1271+
builder = builder.immutable_where(Some(predicate.to_string()));
12311272
}
12321273
Keyword::AT | Keyword::BEFORE => {
12331274
parser.prev_token();

src/keywords.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,7 @@ define_keywords!(
545545
INDICATOR,
546546
INHERIT,
547547
INHERITS,
548+
INITIALIZATION_WAREHOUSE,
548549
INITIALIZE,
549550
INITIALLY,
550551
INNER,
@@ -965,6 +966,7 @@ define_keywords!(
965966
SAMPLE,
966967
SAVEPOINT,
967968
SCHEDULE,
969+
SCHEDULER,
968970
SCHEMA,
969971
SCHEMAS,
970972
SCOPE,

tests/sqlparser_snowflake.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,47 @@ fn parse_create_dynamic_table() {
13661366
));
13671367
}
13681368

1369+
#[test]
1370+
fn parse_create_dynamic_table_snowflake_clauses() {
1371+
// Leading TRANSIENT, bare TARGET_LAG = DOWNSTREAM, INITIALIZATION_WAREHOUSE,
1372+
// SCHEDULER (quoted + bare), and IMMUTABLE WHERE all carry their values in
1373+
// the AST.
1374+
let sql = concat!(
1375+
"CREATE OR REPLACE TRANSIENT DYNAMIC TABLE t",
1376+
" TARGET_LAG='1 minute'",
1377+
" WAREHOUSE=my_wh",
1378+
" INITIALIZATION_WAREHOUSE=init_wh",
1379+
" SCHEDULER='DISABLE'",
1380+
" IMMUTABLE WHERE (id > 5)",
1381+
" AS SELECT id FROM staging_table"
1382+
);
1383+
match snowflake().verified_stmt(sql) {
1384+
Statement::CreateTable(ct) => {
1385+
assert!(ct.transient);
1386+
assert!(ct.dynamic);
1387+
assert!(ct.or_replace);
1388+
assert_eq!(Some("1 minute".to_string()), ct.target_lag);
1389+
assert_eq!(Some("my_wh".to_string()), ct.warehouse);
1390+
assert_eq!(Some("init_wh".to_string()), ct.initialization_warehouse);
1391+
assert_eq!(Some("DISABLE".to_string()), ct.scheduler);
1392+
assert_eq!(Some("id > 5".to_string()), ct.immutable_where);
1393+
}
1394+
other => panic!("expected CreateTable, got {other:?}"),
1395+
}
1396+
1397+
// Bare TARGET_LAG = DOWNSTREAM and bare SCHEDULER value.
1398+
match snowflake().one_statement_parses_to(
1399+
"CREATE DYNAMIC TABLE t TARGET_LAG=DOWNSTREAM WAREHOUSE=wh SCHEDULER=disable AS SELECT id FROM s",
1400+
"CREATE DYNAMIC TABLE t TARGET_LAG='DOWNSTREAM' WAREHOUSE=wh SCHEDULER='disable' AS SELECT id FROM s",
1401+
) {
1402+
Statement::CreateTable(ct) => {
1403+
assert_eq!(Some("DOWNSTREAM".to_string()), ct.target_lag);
1404+
assert_eq!(Some("disable".to_string()), ct.scheduler);
1405+
}
1406+
other => panic!("expected CreateTable, got {other:?}"),
1407+
}
1408+
}
1409+
13691410
#[test]
13701411
fn test_sf_derived_table_in_parenthesis() {
13711412
// Nesting a subquery in an extra set of parentheses is non-standard,

0 commit comments

Comments
 (0)