Skip to content

Commit db7c83f

Browse files
committed
Add ACCOUNT DDL parsing for Snowflake dialect
Add CREATE/ALTER/DROP ACCOUNT and SHOW ACCOUNTS statements: - CreateAccount, AlterAccount, DropAccount, ShowAccounts AST nodes - AccountOption and AlterAccountOperation supporting types - Display round-trips and Visit derive participation - Parser entries from parse_create/parse_alter/parse_drop and the Snowflake SHOW dispatch - New keywords (ACCOUNTS, ADMIN_NAME, ADMIN_PASSWORD, EDITION, EMAIL, FIRST_NAME, LAST_NAME, MUST_CHANGE_PASSWORD, GRACE_PERIOD_IN_DAYS, REGION_GROUP)
1 parent 29160c6 commit db7c83f

6 files changed

Lines changed: 427 additions & 1 deletion

File tree

src/ast/mod.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4780,6 +4780,54 @@ pub enum Statement {
47804780
filter: Option<ShowStatementFilter>,
47814781
},
47824782
/// ```sql
4783+
/// CREATE ACCOUNT <name>
4784+
/// ADMIN_NAME = <id> ADMIN_PASSWORD = '<pw>'
4785+
/// [FIRST_NAME = …] [LAST_NAME = …] EMAIL = '<email>'
4786+
/// [MUST_CHANGE_PASSWORD = <bool>]
4787+
/// EDITION = { STANDARD | ENTERPRISE | BUSINESS_CRITICAL }
4788+
/// [REGION = …] [REGION_GROUP = …] [COMMENT = '<text>']
4789+
/// ```
4790+
/// See <https://docs.snowflake.com/en/sql-reference/sql/create-account>
4791+
CreateAccount {
4792+
/// Account name.
4793+
name: Ident,
4794+
/// Raw `key = value` option pairs (`ADMIN_NAME`, `EDITION`, …) in source
4795+
/// order; not validated against the Snowflake property catalogue.
4796+
options: Vec<AccountOption>,
4797+
},
4798+
/// ```sql
4799+
/// ALTER ACCOUNT [<name>] <operation>
4800+
/// ```
4801+
/// See <https://docs.snowflake.com/en/sql-reference/sql/alter-account>
4802+
AlterAccount {
4803+
/// Optional account name (omitted to target the current account).
4804+
name: Option<Ident>,
4805+
/// The alter operation.
4806+
operation: AlterAccountOperation,
4807+
},
4808+
/// ```sql
4809+
/// DROP ACCOUNT [IF EXISTS] <name> GRACE_PERIOD_IN_DAYS = <int>
4810+
/// ```
4811+
/// See <https://docs.snowflake.com/en/sql-reference/sql/drop-account>
4812+
DropAccount {
4813+
/// `IF EXISTS` flag.
4814+
if_exists: bool,
4815+
/// Account name.
4816+
name: Ident,
4817+
/// `GRACE_PERIOD_IN_DAYS = <int>` value.
4818+
grace_period_in_days: Expr,
4819+
},
4820+
/// ```sql
4821+
/// SHOW ACCOUNTS [HISTORY] [LIKE '<pattern>']
4822+
/// ```
4823+
/// See <https://docs.snowflake.com/en/sql-reference/sql/show-accounts>
4824+
ShowAccounts {
4825+
/// `HISTORY` flag.
4826+
history: bool,
4827+
/// Optional `LIKE` pattern.
4828+
like: Option<String>,
4829+
},
4830+
/// ```sql
47834831
/// CREATE [OR REPLACE] TASK [IF NOT EXISTS] <name>
47844832
/// [WAREHOUSE = <ident>]
47854833
/// [SCHEDULE = '<string>']
@@ -6802,6 +6850,41 @@ impl fmt::Display for Statement {
68026850
}
68036851
Ok(())
68046852
}
6853+
Statement::CreateAccount { name, options } => {
6854+
write!(f, "CREATE ACCOUNT {name}")?;
6855+
for option in options {
6856+
write!(f, " {option}")?;
6857+
}
6858+
Ok(())
6859+
}
6860+
Statement::AlterAccount { name, operation } => {
6861+
write!(f, "ALTER ACCOUNT")?;
6862+
if let Some(name) = name {
6863+
write!(f, " {name}")?;
6864+
}
6865+
write!(f, " {operation}")
6866+
}
6867+
Statement::DropAccount {
6868+
if_exists,
6869+
name,
6870+
grace_period_in_days,
6871+
} => {
6872+
write!(f, "DROP ACCOUNT ")?;
6873+
if *if_exists {
6874+
write!(f, "IF EXISTS ")?;
6875+
}
6876+
write!(f, "{name} GRACE_PERIOD_IN_DAYS = {grace_period_in_days}")
6877+
}
6878+
Statement::ShowAccounts { history, like } => {
6879+
write!(f, "SHOW ACCOUNTS")?;
6880+
if *history {
6881+
write!(f, " HISTORY")?;
6882+
}
6883+
if let Some(like) = like {
6884+
write!(f, " LIKE '{}'", value::escape_single_quote_string(like))?;
6885+
}
6886+
Ok(())
6887+
}
68056888
Statement::CreateTask {
68066889
or_replace,
68076890
if_not_exists,
@@ -12120,6 +12203,63 @@ impl fmt::Display for AlterWarehouseOperation {
1212012203
}
1212112204
}
1212212205

12206+
/// A `<key> = <value>` pair inside `CREATE ACCOUNT` or `ALTER ACCOUNT … SET …`.
12207+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
12208+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12209+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
12210+
pub struct AccountOption {
12211+
/// Property name (e.g. `ADMIN_NAME`, `EDITION`).
12212+
pub name: Ident,
12213+
/// Property value expression (e.g. `'secret'`, `STANDARD`, `TRUE`).
12214+
pub value: Expr,
12215+
}
12216+
12217+
impl fmt::Display for AccountOption {
12218+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
12219+
write!(f, "{} = {}", self.name, self.value)
12220+
}
12221+
}
12222+
12223+
/// Operations for `ALTER ACCOUNT`.
12224+
///
12225+
/// See <https://docs.snowflake.com/en/sql-reference/sql/alter-account>.
12226+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
12227+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12228+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
12229+
pub enum AlterAccountOperation {
12230+
/// `SET <param> = <value> [, ...]`
12231+
Set {
12232+
/// Raw `key = value` pairs; not validated against the Snowflake property
12233+
/// catalogue because the executor doesn't read them.
12234+
params: Vec<AccountOption>,
12235+
},
12236+
/// `RENAME TO <new_name>`
12237+
RenameTo {
12238+
/// New account name.
12239+
new_name: Ident,
12240+
},
12241+
}
12242+
12243+
impl fmt::Display for AlterAccountOperation {
12244+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
12245+
match self {
12246+
AlterAccountOperation::Set { params } => {
12247+
write!(f, "SET ")?;
12248+
for (i, p) in params.iter().enumerate() {
12249+
if i > 0 {
12250+
write!(f, ", ")?;
12251+
}
12252+
write!(f, "{p}")?;
12253+
}
12254+
Ok(())
12255+
}
12256+
AlterAccountOperation::RenameTo { new_name } => {
12257+
write!(f, "RENAME TO {new_name}")
12258+
}
12259+
}
12260+
}
12261+
}
12262+
1212312263
/// Action for [`Statement::AlterTask`].
1212412264
///
1212512265
/// See <https://docs.snowflake.com/en/sql-reference/sql/alter-task>.

src/ast/spans.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,10 @@ impl Spanned for Statement {
527527
Statement::AlterWarehouse { .. } => Span::empty(),
528528
Statement::DescribeWarehouse { .. } => Span::empty(),
529529
Statement::ShowWarehouses { .. } => Span::empty(),
530+
Statement::CreateAccount { .. } => Span::empty(),
531+
Statement::AlterAccount { .. } => Span::empty(),
532+
Statement::DropAccount { .. } => Span::empty(),
533+
Statement::ShowAccounts { .. } => Span::empty(),
530534
Statement::CreateTask { .. } => Span::empty(),
531535
Statement::AlterTask { .. } => Span::empty(),
532536
Statement::ExecuteTask { .. } => Span::empty(),

src/dialect/snowflake.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,9 @@ impl Dialect for SnowflakeDialect {
430430
if parser.parse_keyword(Keyword::WAREHOUSES) {
431431
return Some(parse_show_warehouses(parser));
432432
}
433+
if parser.parse_keyword(Keyword::ACCOUNTS) {
434+
return Some(parse_show_accounts(parser));
435+
}
433436
let terse = parser.parse_keyword(Keyword::TERSE);
434437
if parser.parse_keyword(Keyword::OBJECTS) {
435438
return Some(parse_show_objects(terse, parser));
@@ -2379,6 +2382,17 @@ fn parse_show_warehouses(parser: &mut Parser) -> Result<Statement, ParserError>
23792382
Ok(Statement::ShowWarehouses { filter })
23802383
}
23812384

2385+
/// Parse `SHOW ACCOUNTS [HISTORY] [LIKE '<pattern>']`
2386+
fn parse_show_accounts(parser: &mut Parser) -> Result<Statement, ParserError> {
2387+
let history = parser.parse_keyword(Keyword::HISTORY);
2388+
let like = if parser.parse_keyword(Keyword::LIKE) {
2389+
Some(parser.parse_literal_string()?)
2390+
} else {
2391+
None
2392+
};
2393+
Ok(Statement::ShowAccounts { history, like })
2394+
}
2395+
23822396
/// Parse `CREATE [OR REPLACE] CATALOG INTEGRATION [IF NOT EXISTS] <name> ...`
23832397
fn parse_create_catalog_integration(
23842398
or_replace: bool,

src/keywords.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,13 @@ define_keywords!(
9898
ACCESS,
9999
ACCESS_DELEGATION_MODE,
100100
ACCOUNT,
101+
ACCOUNTS,
101102
ACTION,
102103
ADD,
103104
ADDQUOTES,
104105
ADMIN,
106+
ADMIN_NAME,
107+
ADMIN_PASSWORD,
105108
AFTER,
106109
AGAINST,
107110
AGGREGATE,
@@ -369,10 +372,12 @@ define_keywords!(
369372
DUPLICATE,
370373
DYNAMIC,
371374
EACH,
375+
EDITION,
372376
ELEMENT,
373377
ELEMENTS,
374378
ELSE,
375379
ELSEIF,
380+
EMAIL,
376381
EMPTY,
377382
EMPTYASNULL,
378383
ENABLE,
@@ -440,6 +445,7 @@ define_keywords!(
440445
FILTER,
441446
FINAL,
442447
FIRST,
448+
FIRST_NAME,
443449
FIRST_VALUE,
444450
FIXEDSTRING,
445451
FIXEDWIDTH,
@@ -485,6 +491,7 @@ define_keywords!(
485491
GIST,
486492
GLOBAL,
487493
GLUE,
494+
GRACE_PERIOD_IN_DAYS,
488495
GRANT,
489496
GRANTED,
490497
GRANTS,
@@ -590,6 +597,7 @@ define_keywords!(
590597
LANGUAGE,
591598
LARGE,
592599
LAST,
600+
LAST_NAME,
593601
LAST_VALUE,
594602
LATERAL,
595603
LEAD,
@@ -687,6 +695,7 @@ define_keywords!(
687695
MSCK,
688696
MULTIRANGE_TYPE_NAME,
689697
MULTISET,
698+
MUST_CHANGE_PASSWORD,
690699
MUTATION,
691700
NAME,
692701
NAMES,
@@ -875,6 +884,7 @@ define_keywords!(
875884
REGCLASS,
876885
REGEXP,
877886
REGION,
887+
REGION_GROUP,
878888
REGR_AVGX,
879889
REGR_AVGY,
880890
REGR_COUNT,

src/parser/mod.rs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5403,6 +5403,8 @@ impl<'a> Parser<'a> {
54035403
self.parse_create_user(or_replace).map(Into::into)
54045404
} else if self.parse_keyword(Keyword::WAREHOUSE) {
54055405
self.parse_create_warehouse(or_replace)
5406+
} else if self.parse_keyword(Keyword::ACCOUNT) {
5407+
self.parse_create_account()
54065408
} else if self.parse_keyword(Keyword::TASK) {
54075409
self.parse_create_task(or_replace)
54085410
} else if self.parse_keyword(Keyword::PROCEDURE) {
@@ -5497,6 +5499,31 @@ impl<'a> Parser<'a> {
54975499
})
54985500
}
54995501

5502+
fn parse_create_account(&mut self) -> Result<Statement, ParserError> {
5503+
let name = self.parse_identifier()?;
5504+
let mut options = Vec::new();
5505+
while let Token::Word(_) = self.peek_token_ref().token {
5506+
let name = self.parse_identifier()?;
5507+
self.expect_token(&Token::Eq)?;
5508+
let value = self.parse_expr()?;
5509+
options.push(AccountOption { name, value });
5510+
}
5511+
Ok(Statement::CreateAccount { name, options })
5512+
}
5513+
5514+
fn parse_drop_account(&mut self) -> Result<Statement, ParserError> {
5515+
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
5516+
let name = self.parse_identifier()?;
5517+
self.expect_keyword(Keyword::GRACE_PERIOD_IN_DAYS)?;
5518+
self.expect_token(&Token::Eq)?;
5519+
let grace_period_in_days = self.parse_expr()?;
5520+
Ok(Statement::DropAccount {
5521+
if_exists,
5522+
name,
5523+
grace_period_in_days,
5524+
})
5525+
}
5526+
55005527
/// Parse `CREATE [OR REPLACE] TASK [IF NOT EXISTS] <name> ... AS <statement>`.
55015528
fn parse_create_task(&mut self, or_replace: bool) -> Result<Statement, ParserError> {
55025529
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
@@ -7699,6 +7726,8 @@ impl<'a> Parser<'a> {
76997726
ObjectType::Stream
77007727
} else if self.parse_keyword(Keyword::WAREHOUSE) {
77017728
ObjectType::Warehouse
7729+
} else if self.parse_keyword(Keyword::ACCOUNT) {
7730+
return self.parse_drop_account();
77027731
} else if self.parse_keyword(Keyword::TASK) {
77037732
ObjectType::Task
77047733
} else if self.parse_keyword(Keyword::FUNCTION) {
@@ -11069,6 +11098,7 @@ impl<'a> Parser<'a> {
1106911098
Keyword::USER,
1107011099
Keyword::OPERATOR,
1107111100
Keyword::WAREHOUSE,
11101+
Keyword::ACCOUNT,
1107211102
Keyword::TASK,
1107311103
])?;
1107411104
match object_type {
@@ -11119,10 +11149,11 @@ impl<'a> Parser<'a> {
1111911149
Keyword::CONNECTOR => self.parse_alter_connector(),
1112011150
Keyword::USER => self.parse_alter_user().map(Into::into),
1112111151
Keyword::WAREHOUSE => self.parse_alter_warehouse(),
11152+
Keyword::ACCOUNT => self.parse_alter_account(),
1112211153
Keyword::TASK => self.parse_alter_task(),
1112311154
// unreachable because expect_one_of_keywords used above
1112411155
unexpected_keyword => Err(ParserError::ParserError(
11125-
format!("Internal parser error: expected any of {{VIEW, TYPE, COLLATION, TABLE, INDEX, FUNCTION, AGGREGATE, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR, WAREHOUSE, TASK}}, got {unexpected_keyword:?}"),
11156+
format!("Internal parser error: expected any of {{VIEW, TYPE, COLLATION, TABLE, INDEX, FUNCTION, AGGREGATE, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR, WAREHOUSE, ACCOUNT, TASK}}, got {unexpected_keyword:?}"),
1112611157
)),
1112711158
}
1112811159
}
@@ -11409,6 +11440,41 @@ impl<'a> Parser<'a> {
1140911440
})
1141011441
}
1141111442

11443+
fn parse_alter_account(&mut self) -> Result<Statement, ParserError> {
11444+
// The name is optional — bare `ALTER ACCOUNT SET ...` targets the
11445+
// current account. SET and RENAME are the only operations, so their
11446+
// presence indicates no name was provided.
11447+
let starts_with_op = matches!(
11448+
self.peek_token().token,
11449+
Token::Word(ref w) if matches!(w.keyword, Keyword::SET | Keyword::RENAME)
11450+
);
11451+
let name = if starts_with_op {
11452+
None
11453+
} else {
11454+
Some(self.parse_identifier()?)
11455+
};
11456+
11457+
let operation = if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
11458+
let new_name = self.parse_identifier()?;
11459+
AlterAccountOperation::RenameTo { new_name }
11460+
} else if self.parse_keyword(Keyword::SET) {
11461+
let params = self.parse_comma_separated(|p| {
11462+
let name = p.parse_identifier()?;
11463+
p.expect_token(&Token::Eq)?;
11464+
let value = p.parse_expr()?;
11465+
Ok::<AccountOption, ParserError>(AccountOption { name, value })
11466+
})?;
11467+
AlterAccountOperation::Set { params }
11468+
} else {
11469+
return self.expected(
11470+
"SET or RENAME TO after ALTER ACCOUNT",
11471+
self.peek_token(),
11472+
);
11473+
};
11474+
11475+
Ok(Statement::AlterAccount { name, operation })
11476+
}
11477+
1141211478
/// Parse `ALTER TASK [IF EXISTS] <name> { RESUME | SUSPEND }`.
1141311479
pub fn parse_alter_task(&mut self) -> Result<Statement, ParserError> {
1141411480
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);

0 commit comments

Comments
 (0)