Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions crates/sqlquack-syntax/src/parser/_helpers/ddl/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,13 @@ impl Parser {
return self.parse_drop_table_with_iceberg(leading_comments, true);
}

let snowflake = matches!(
self.config.dialect,
Some(crate::tokens::models::DialectType::Snowflake)
);
if snowflake && self.match_text_seq(&["DATABASE", "ROLE"]) {
return self.parse_drop_snowflake_object("DATABASE ROLE".to_owned());
}
match self.peek().token_type {
TokenType::Table => self.parse_drop_table_with_iceberg(leading_comments, false),
TokenType::View => self.parse_drop_view(false),
Expand Down Expand Up @@ -1420,6 +1427,14 @@ impl Parser {
.iter()
.find(|kind| self.match_text_seq(kind))
{
let shorter = &kind[..kind.len() - 1];
let kind_word_named = kind.len() > 1
&& self.is_statement_end_at(0)
&& SNOWFLAKE_DROP_KINDS.contains(&shorter);
if kind_word_named {
self.current -= 1;
return self.parse_drop_snowflake_object(shorter.join(" "));
}
return self.parse_drop_snowflake_object(kind.join(" "));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,16 +384,14 @@ impl Parser {
"SET" => (vec![], self.parse_integration_set()?),
"UNSET" => (vec![], self.parse_group_unset()?),
_ => {
while !self.is_at_end() && !self.check(TokenType::Semicolon) {
let key = self.expect_alter_words(&[
&["OAUTH_CLIENT_SECRET_2"],
&["OAUTH_CLIENT_SECRET"],
&["SAML2_SNOWFLAKE_PRIVATE_KEY"],
&["METADATA_URL"],
])?;
action.push(' ');
action.push_str(&key);
}
let key = self.expect_alter_words(&[
&["OAUTH_CLIENT_SECRET_2"],
&["OAUTH_CLIENT_SECRET"],
&["SAML2_SNOWFLAKE_PRIVATE_KEY"],
&["METADATA_URL"],
])?;
action.push(' ');
action.push_str(&key);
(vec![], vec![])
}
};
Expand Down Expand Up @@ -498,6 +496,16 @@ impl Parser {
(vec![], properties),
));
}
let renames = self
.tokens
.get(self.current + 1)
.is_some_and(|token| token.text.eq_ignore_ascii_case("RENAME"));
if renames {
let head = self.parse_alter_object_head()?;
let action = self.expect_alter_words(&[&["RENAME", "TO"]])?;
let parts: ActionParts = (vec![self.parse_group_name()?], vec![]);
return Ok(alter_object("ORGANIZATION", head, action, parts));
}
if self.match_identifier("PROFILE") {
return self.parse_alter_organization_profile();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Parser ddl: Snowflake ALTER of schemas, stages, file formats, routines, services and project objects.

use crate::expressions::names::models::Literal;
use crate::expressions::names::models::TableRef;
use crate::expressions::schema_objects::models::ObjectProperty;
use crate::expressions::tree::models::Expression;
use crate::expressions::types::models::Tuple;
Expand All @@ -14,6 +15,7 @@ use crate::tokens::types::Result;
/// The object kinds this module parses, as the words that follow ALTER.
const RESOURCE_KINDS: &[&[&str]] = &[
&["MULTI", "PARTY", "APPROVAL", "POLICY"],
&["ROW", "ACCESS", "POLICY"],
&["STORAGE", "LIFECYCLE", "POLICY"],
&["DATA", "MOVEMENT", "POLICY"],
&["AGGREGATION", "POLICY"],
Expand All @@ -28,6 +30,8 @@ const RESOURCE_KINDS: &[&[&str]] = &[
&["NETWORK", "RULE"],
&["PACKAGES", "POLICY"],
&["PASSWORD", "POLICY"],
&["PRIVACY", "POLICY"],
&["PROJECTION", "POLICY"],
&["SESSION", "POLICY"],
&["SNAPSHOT", "POLICY"],
&["RESTRICTED", "SESSION", "SCOPE"],
Expand All @@ -43,6 +47,7 @@ const RESOURCE_KINDS: &[&[&str]] = &[
&["FILE", "FORMAT"],
&["GIT", "REPOSITORY"],
&["MODEL", "MONITOR"],
&["RESOURCE", "MONITOR"],
&["AGENT"],
&["DATASET"],
&["PIPE"],
Expand All @@ -56,6 +61,7 @@ const RESOURCE_KINDS: &[&[&str]] = &[
&["NOTEBOOK"],
&["PROCEDURE"],
&["SCHEMA"],
&["SECRET"],
&["SERVICE"],
&["STAGE"],
];
Expand All @@ -76,6 +82,7 @@ fn resource_actions(kind: &str) -> &'static [&'static [&'static str]] {
],
"DBT PROJECT" => &[&["DEPLOY", "FROM"], &["RENAME", "TO"], &["SET"], &["UNSET"]],
"DCM PROJECT" => &[&["DROP", "DEPLOYMENT"], &["SET"], &["UNSET"]],
"SECRET" => &[&["SET"]],
"EVENT ROUTING TABLE" => &[
&["MODIFY", "RULE"],
&["RENAME", "TO"],
Expand Down Expand Up @@ -207,6 +214,9 @@ impl Parser {
};
let kind = words.join(" ");
let head = self.parse_alter_object_head()?;
if matches!(kind.as_str(), "RESOURCE MONITOR") {
return self.parse_alter_resource_monitor(head).map(Some);
}
let arguments = match kind.as_str() {
"PROCEDURE" => self.parse_routine_signature()?,
_ => Vec::new(),
Expand All @@ -220,6 +230,34 @@ impl Parser {
Ok(Some(Expression::AlterObject(alter)))
}

/// ALTER RESOURCE MONITOR after its name: `[ SET <property> ... ] [ TRIGGERS ON <n> PERCENT DO <action> ... ]`.
fn parse_alter_resource_monitor(&mut self, head: (TableRef, bool)) -> Result<Expression> {
let mut words: Vec<&str> = Vec::new();
let mut properties: Vec<ObjectProperty> = Vec::new();
if self.match_text_seq(&["SET"]) {
words.push("SET");
while !self.is_statement_end_at(0) && !self.check_identifier("TRIGGERS") {
properties.extend(self.parse_create_property_item()?);
}
if properties.is_empty() {
return Err(self.parse_error("Expected a property to set"));
}
}
if self.check_identifier("TRIGGERS") {
words.push("TRIGGERS");
properties.extend(self.parse_create_property_item()?);
}
if !self.is_statement_end_at(0) {
return Err(self.parse_error("Expected SET, TRIGGERS or the end of the statement"));
}
Ok(alter_object(
"RESOURCE MONITOR",
head,
words.join(" "),
(vec![], properties),
))
}

/// The words of an incomplete multi-word kind, such as `COMPUTE` without `POOL`, if the next words are one.
fn partial_resource_kind(&self) -> Option<String> {
let first = self.text_at(0)?;
Expand All @@ -242,7 +280,8 @@ impl Parser {
"COMPUTE" | "DBT" | "DCM" | "GIT" | "FILE" | "EXTERNAL" | "EVENT" | "ARTIFACT"
| "MATERIALIZED" | "RESTRICTED" | "VOLUME" | "AGGREGATION" | "AUTHENTICATION"
| "BACKUP" | "DATA" | "INGRESS" | "JOIN" | "MAINTENANCE" | "MASKING" | "MULTI"
| "NETWORK" | "PACKAGES" | "PASSWORD",
| "NETWORK" | "PACKAGES" | "PASSWORD" | "PRIVACY" | "PROJECTION" | "RESOURCE"
| "ROW",
_,
) => false,
_ => return None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ impl Parser {
}

/// One item: TAG ( <name> = <value> , ... ), TRIGGERS ON <n> PERCENT DO <action> ..., AS <body>, IGNORE EDITION CHECK, or <property> = <value>.
fn parse_create_property_item(&mut self) -> Result<Vec<ObjectProperty>> {
pub(super) fn parse_create_property_item(&mut self) -> Result<Vec<ObjectProperty>> {
if self.check_identifier("TAG") && self.check_next(TokenType::LParen) {
self.skip();
self.skip();
Expand Down
76 changes: 68 additions & 8 deletions crates/sqlquack-syntax/src/parser/_helpers/ddl/snowflake_show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ impl Parser {
}
return self.parse_snowflake_show_grants(future).map(Some);
}
if self.match_text_seq(&["CALLER", "GRANTS"]) {
return self.parse_snowflake_show_caller_grants().map(Some);
}
let class = self.applies_snowflake_context_rules() && self.check_next(TokenType::Dot);
let shared = SHARED_SHOW_KINDS
.iter()
Expand All @@ -45,6 +48,9 @@ impl Parser {
show.target = target;
show.terse = terse;
show.history = self.match_identifier("HISTORY");
if matches!(kind.as_str(), "OPENFLOW DATA PLANE INTEGRATIONS") {
return Ok(Some(Expression::Show(Box::new(show))));
}
if matches!(kind.as_str(), "DELEGATED AUTHORIZATIONS BY USER") {
show.for_target = Some(self.parse_show_name()?);
return Ok(Some(Expression::Show(Box::new(show))));
Expand Down Expand Up @@ -74,11 +80,12 @@ impl Parser {
if self.match_token(TokenType::Like) {
show.like = Some(self.parse_show_string("LIKE")?);
}
let mut show = self.parse_show_on_user(&kind, show)?;
let required = SNOWFLAKE_SHOW_REQUIRED_CONTAINERS
.iter()
.find(|(name, _)| *name == kind)
.map(|(_, containers)| *containers);
if required.is_some() || self.check(TokenType::In) {
if show.scope_kind.is_none() && (required.is_some() || self.check(TokenType::In)) {
self.expect_token(TokenType::In)?;
let (container, name) = self.parse_show_container(&kind)?;
let allowed = |container: &str| match required {
Expand Down Expand Up @@ -114,11 +121,6 @@ impl Parser {
show.from = Some(self.parse_show_string("FROM")?);
}
}
if show.scope_kind.is_none() && show.scope.is_none() && self.match_token(TokenType::In) {
let (container, name) = self.parse_show_container(&kind)?;
show.scope_kind = container;
show.scope = name;
}
if self.match_token(TokenType::With) {
show = self.parse_show_with(&kind, show)?;
}
Expand Down Expand Up @@ -160,6 +162,11 @@ impl Parser {
}
}

/// Whether a LIMIT ends the statement after ACCOUNT, where Snowflake reads it as the account's name.
fn is_limit_name(&self) -> bool {
self.check(TokenType::Limit) && self.is_statement_end_at(1)
}

/// An object name, kept as written.
fn parse_show_name(&mut self) -> Result<Expression> {
Ok(Expression::Table(Box::new(self.parse_object_name()?)))
Expand All @@ -177,9 +184,16 @@ impl Parser {
let groups = matches!(kind, "FAILOVER GROUPS" | "REPLICATION GROUPS");
let optional = match container.as_str() {
"ACCOUNT" => !groups,
"DATABASE" | "SCHEMA" => !matches!(kind, "DATABASE ROLES" | "SCHEMA VARIABLES"),
"DATABASE" | "SCHEMA" => !matches!(kind, "SCHEMA VARIABLES"),
"APPLICATION" => !matches!(
kind,
"PRIVILEGES" | "REFERENCES" | "TELEMETRY EVENT DEFINITIONS"
),
"TABLE" | "VIEW" => true,
"REPLICATION GROUP" => matches!(kind, "DATABASES" | "SHARES"),
"REPLICATION GROUP" | "FAILOVER GROUP" => matches!(kind, "DATABASES" | "SHARES"),
"SERVICE" => matches!(kind, "ROLES" | "ENDPOINTS"),
"CLASS" => matches!(kind, "ROLES" | "FUNCTIONS" | "PROCEDURES"),
"ORGANIZATION PROFILE" => true,
_ => false,
};
let ends = self.is_at_end()
Expand All @@ -188,6 +202,7 @@ impl Parser {
|| self.check(TokenType::Limit)
|| self.check_identifier("STARTS");
let name = match (container.as_str(), optional && ends) {
("ACCOUNT", _) if self.is_limit_name() => Some(self.parse_show_name()?),
(_, true) => None,
("ACCOUNT", _) if !groups => None,
_ => Some(self.parse_show_name()?),
Expand Down Expand Up @@ -243,6 +258,48 @@ impl Parser {
}
}

/// `[ ON ACCOUNT | ON USER <name> ]` after LIKE, for the policy kinds a user can be shown with.
fn parse_show_on_user(&mut self, kind: &str, mut show: Show) -> Result<Show> {
let on_user = matches!(
kind,
"AUTHENTICATION POLICIES"
| "INGRESS POLICIES"
| "PASSWORD POLICIES"
| "SESSION POLICIES"
);
if on_user && self.match_token(TokenType::On) {
let on = self.expect_alter_words(&[&["ACCOUNT"], &["USER"]])?;
if on == USER {
show.scope = Some(self.parse_show_name()?);
}
show.scope_kind = Some(format!("ON {on}"));
}
Ok(show)
}

/// SHOW CALLER GRANTS: ON as for SHOW GRANTS; TO takes ROLE, DATABASE ROLE, DATABASE or a name.
fn parse_snowflake_show_caller_grants(&mut self) -> Result<Expression> {
if !self.match_token(TokenType::To) {
let mut grants = self.parse_snowflake_show_grants(false)?;
if let Expression::Show(show) = &mut grants {
show.this = "CALLER GRANTS".to_owned();
}
return Ok(grants);
}
let mut show = Show::new("CALLER GRANTS".to_owned());
let kinds: &[&[&str]] = &[&["DATABASE", "ROLE"], &["ROLE"], &["DATABASE"]];
let kind = kinds.iter().find(|words| self.match_text_seq(words));
show.scope_kind = Some(match kind {
Some(words) => format!("TO {}", words.join(" ")),
None => "TO".to_owned(),
});
let bare_role = kind.is_some_and(|words| words == &["ROLE"]) && self.is_statement_end_at(0);
if !bare_role {
show.scope = Some(self.parse_show_name()?);
}
Ok(Expression::Show(Box::new(show)))
}

/// SHOW [ FUTURE ] GRANTS after GRANTS: its one ON, TO, OF or IN clause, then LIMIT.
fn parse_snowflake_show_grants(&mut self, future: bool) -> Result<Expression> {
let show = Show::new(if future { "FUTURE GRANTS" } else { "GRANTS" }.to_owned());
Expand Down Expand Up @@ -332,6 +389,9 @@ impl Parser {
/// The clause after SHOW GRANTS ON: ACCOUNT, or an object kind of one or more words and the object's name.
fn parse_show_grants_on(&mut self, mut show: Show) -> Result<(String, Show)> {
if self.match_identifier(ACCOUNT) {
if self.is_limit_name() {
show.scope = Some(self.parse_show_name()?);
}
return Ok((ON_ACCOUNT.to_owned(), show));
}
let mut kind: Vec<String> = Vec::new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,17 @@ impl Parser {
let Some(kind) = SNOWFLAKE_DESCRIBE_KINDS
.iter()
.find(|kind| self.match_text_seq(kind))
.map(|kind| kind.join(" "))
.map(|kind| {
let shorter = &kind[..kind.len() - 1];
let kind_word_named = kind.len() > 1
&& self.is_statement_end_at(0)
&& SNOWFLAKE_DESCRIBE_KINDS.contains(&shorter);
if kind_word_named {
self.current -= 1;
return shorter.join(" ");
}
kind.join(" ")
})
else {
let shared = SHARED_DESCRIBE_KINDS
.iter()
Expand Down
Loading
Loading