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
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,12 @@ pub struct DropDatabase {
/// ClickHouse: SYNC modifier
#[serde(default)]
pub sync: bool,
/// Snowflake CASCADE, left out of the tree when absent
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub cascade: bool,
/// Snowflake RESTRICT, left out of the tree when absent
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub restrict: bool,
}

impl DropDatabase {
Expand All @@ -405,6 +411,8 @@ impl DropDatabase {
name: Identifier::new(name),
if_exists: false,
sync: false,
cascade: false,
restrict: false,
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions crates/sqlquack-syntax/src/parser/_helpers/ddl/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ impl Parser {
/// Parses CALL procedure(args, ...).
pub(crate) fn parse_call(&mut self) -> Result<Expression> {
let call_token = self.advance()?;
if let Some(call) = self.parse_snowflake_method_call()? {
return Ok(call);
}

let mut tokens = vec![("CALL".to_string(), call_token.token_type)];
while !self.is_at_end() && !self.check(TokenType::Semicolon) {
Expand Down Expand Up @@ -732,6 +735,7 @@ impl Parser {

Expression::Identifier(Identifier::new(name_str))
} else {
self.check_snowflake_comment_name()?;
self.parse_qualified_name()?
};

Expand All @@ -740,6 +744,13 @@ impl Parser {
} else {
return Err(self.parse_error("Expected IS in COMMENT ON statement"));
}
if self.is_snowflake()
&& !self.check(TokenType::String)
&& !self.check(TokenType::DollarString)
&& !self.check(TokenType::NationalString)
{
return Err(self.parse_error("Expected a string after IS"));
}

let expression = self.parse_primary()?;

Expand Down Expand Up @@ -1457,11 +1468,15 @@ impl Parser {
let name = self.expect_identifier_or_safe_keyword_with_quoted()?;

let sync = { false };
let cascade = self.match_token(TokenType::Cascade);
let restrict = !cascade && self.match_token(TokenType::Restrict);

Ok(Expression::DropDatabase(Box::new(DropDatabase {
name,
if_exists,
sync,
cascade,
restrict,
})))
}

Expand Down
37 changes: 10 additions & 27 deletions crates/sqlquack-syntax/src/parser/_helpers/ddl/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1419,33 +1419,6 @@ impl Parser {
{
return self.parse_drop_snowflake_object(kind.join(" "));
}
let text_upper = self.peek_text().to_ascii_uppercase();
let is_snowflake_drop =
matches!(text_upper.as_str(), "INTEGRATION" | "NETWORK" | "SHARE");
if is_snowflake_drop {
self.skip();
let mut tokens: Vec<(String, TokenType)> = vec![
("DROP".to_string(), TokenType::Var),
(
self.previous_text().to_ascii_uppercase(),
self.previous().token_type,
),
];
while !self.is_at_end() && !self.check(TokenType::Semicolon) {
let token = self.advance()?;
let text = if token.token_type == TokenType::QuotedIdentifier {
format!("\"{}\"", token.text)
} else if token.token_type == TokenType::String {
format!("'{}'", token.text)
} else {
token.text.to_string()
};
tokens.push((text, token.token_type));
}
return Ok(Expression::Command(Box::new(Command {
this: self.join_command_tokens(tokens),
})));
}
}
Err(self.parse_error(format!(
"Expected TABLE, VIEW, INDEX, SCHEMA, DATABASE, FUNCTION, PROCEDURE, SEQUENCE, TRIGGER, TYPE, or NAMESPACE after DROP, got {:?}",
Expand Down Expand Up @@ -1581,6 +1554,16 @@ impl Parser {
} else {
None
};
if matches!(
self.config.dialect,
Some(crate::tokens::models::DialectType::Snowflake)
) {
if let Some(expr) =
self.parse_alter_snowflake_table_action(alter_table_modifier.is_some())?
{
return Ok(expr);
}
}

match self.peek().token_type {
TokenType::Table => self.parse_alter_table(alter_table_modifier),
Expand Down
2 changes: 2 additions & 0 deletions crates/sqlquack-syntax/src/parser/_helpers/ddl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub(crate) mod snowflake_alter;
pub(crate) mod snowflake_alter_account;
pub(crate) mod snowflake_alter_objects;
pub(crate) mod snowflake_alter_resources;
pub(crate) mod snowflake_alter_tables;
pub(crate) mod snowflake_commands;
pub(crate) mod snowflake_show;
pub(crate) mod snowflake_statements;
pub(crate) mod table_options;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ impl Parser {
}

/// Parse a parenthesized list of string literals.
fn parse_string_list(&mut self) -> Result<Expression> {
pub(super) fn parse_string_list(&mut self) -> Result<Expression> {
self.expect_token(TokenType::LParen)?;
let mut items = vec![self.parse_string_value()?];
while self.match_token(TokenType::Comma) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ impl Parser {
(vec![manifest], self.parse_resource_properties()?)
}
(_, "FROM SPECIFICATION") => (vec![self.parse_object_value()?], vec![]),
(_, "ADD FILES" | "REMOVE FILES") => (vec![self.parse_value_list()?], vec![]),
(_, "ADD FILES" | "REMOVE FILES") => (vec![self.parse_string_list()?], vec![]),
(_, "ADD TARGETS" | "REMOVE TARGETS") => (vec![self.parse_resource_value()?], vec![]),
("STAGE" | "PIPE", "REFRESH") => (vec![], self.parse_resource_properties()?),
("NETWORK POLICY" | "DATA MOVEMENT POLICY", "ADD" | "REMOVE") => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
//! Snowflake ALTER actions of TABLE, ICEBERG TABLE, SEQUENCE, VIEW and SHARE that the generic ALTER parsers lack.

use crate::expressions::names::models::Identifier;
use crate::expressions::schema_objects::models::ObjectProperty;
use crate::expressions::tree::models::Expression;
use crate::expressions::types::models::Tuple;
use crate::parser::_helpers::ddl::snowflake_alter::alter_object;
use crate::parser::_helpers::ddl::snowflake_alter::property;
use crate::parser::models::Parser;
use crate::tokens::models::TokenType;
use crate::tokens::types::Result;

/// The actions only an ICEBERG TABLE takes, as their leading words.
const ICEBERG_ACTIONS: &[&[&str]] = &[
&["CREATE", "OR", "REPLACE", "VERSION_TAG"],
&["CREATE", "VERSION_TAG"],
&["REPLACE", "VERSION_TAG"],
&["ALTER", "VERSION_TAG"],
&["DROP", "VERSION_TAG"],
&["REPLACE", "PARTITION", "BY"],
&["UPGRADE", "TO", "ICEBERG", "VERSION"],
&["OPTIMIZE"],
];

/// The storage lifecycle policy actions of an ALTER TABLE.
const LIFECYCLE_ACTIONS: &[&[&str]] = &[
&["ADD", "STORAGE", "LIFECYCLE", "POLICY"],
&["DROP", "STORAGE", "LIFECYCLE", "POLICY"],
];

/// The retention units of a version tag's RETAIN.
const RETAIN_UNITS: &[&[&str]] = &[&["DAYS"], &["HOURS"], &["MINUTES"]];

/// The operands and properties of one ALTER action.
type ActionParts = (Vec<Expression>, Vec<ObjectProperty>);

impl Parser {
/// Parse a Snowflake ALTER action this module owns, or None (position unchanged) for any other.
pub(crate) fn parse_alter_snowflake_table_action(
&mut self,
iceberg: bool,
) -> Result<Option<Expression>> {
if self.check_identifier("USER")
&& self
.peek_nth(1)
.is_some_and(|token| token.token_type != TokenType::QuotedIdentifier)
&& self.text_at(1).as_deref() == Some("GROUP")
{
return Err(self.parse_error("Snowflake has ALTER ORGANIZATION USER GROUP"));
}
let start = self.current;
let found = if self.match_token(TokenType::Table) {
self.parse_alter_table_extension(iceberg)
} else if iceberg {
Ok(None)
} else if self.match_token(TokenType::Sequence) {
self.parse_alter_kind_extension("SEQUENCE", &[&["RENAME", "TO"], &["UNSET"]])
} else if self.match_token(TokenType::View) {
self.parse_alter_kind_extension("VIEW", &[&["RENAME", "TO"]])
} else if self.match_identifier("SHARE") {
return self.parse_alter_share();
} else {
Ok(None)
};
let found = found?;
if found.is_none() {
self.current = start;
}
Ok(found)
}

/// Parse the head and an ICEBERG or lifecycle action of an ALTER [ ICEBERG ] TABLE, or None.
fn parse_alter_table_extension(&mut self, iceberg: bool) -> Result<Option<Expression>> {
let head = self.parse_alter_object_head()?;
if !iceberg && self.match_text_seq(&["RENAME"]) && !self.check(TokenType::Column) {
self.expect_token(TokenType::To)?;
self.parse_object_name()?;
return Ok(None);
}
if !iceberg && self.match_text_seq(&["SWAP", "WITH"]) {
self.parse_object_name()?;
return Ok(None);
}
if !iceberg && self.check_identifier("OPTIMIZE") {
return Err(self.parse_error("OPTIMIZE is an action of ALTER ICEBERG TABLE"));
}
if !iceberg && self.check(TokenType::Set) && self.check_next(TokenType::Semicolon) {
return Err(self.parse_error("SET names at least one property"));
}
let choices = if iceberg {
ICEBERG_ACTIONS
} else {
LIFECYCLE_ACTIONS
};
let Some(words) = choices.iter().find(|words| self.match_text_seq(words)) else {
return Ok(None);
};
let kind = if iceberg { "ICEBERG TABLE" } else { "TABLE" };
let (action, parts) = self.parse_table_extension_action(words.join(" "))?;
Ok(Some(alter_object(kind, head, action, parts)))
}

/// Parse what follows the words of a table extension action.
fn parse_table_extension_action(
&mut self,
mut action: String,
) -> Result<(String, ActionParts)> {
let parts = match action.as_str() {
"CREATE VERSION_TAG" | "CREATE OR REPLACE VERSION_TAG" => {
if self.match_keywords(&[TokenType::If, TokenType::Not, TokenType::Exists]) {
action.push_str(" IF NOT EXISTS");
}
let tag = self.parse_tag_operand()?;
let mut properties: Vec<ObjectProperty> = Vec::new();
if self.match_text_seq(&["AS", "OF"]) {
properties = self.parse_version_point()?;
}
(vec![tag], properties)
}
"REPLACE VERSION_TAG" | "ALTER VERSION_TAG" => {
let tag = self.parse_tag_operand()?;
self.expect_alter_words(&[&["AS", "OF"]])?;
(vec![tag], self.parse_version_point()?)
}
"DROP VERSION_TAG" => {
if self.match_keywords(&[TokenType::If, TokenType::Exists]) {
action.push_str(" IF EXISTS");
}
(vec![self.parse_tag_operand()?], vec![])
}
"REPLACE PARTITION BY" => {
let old = self.parse_partition_expression()?;
self.expect_token(TokenType::With)?;
(vec![old, self.parse_partition_expression()?], vec![])
}
"UPGRADE TO ICEBERG VERSION" => (vec![self.parse_number_operand()?], vec![]),
"OPTIMIZE" => {
self.expect_token(TokenType::LParen)?;
self.expect_alter_words(&[&["REMOVE_POSITION_DELETES"]])?;
self.expect_token(TokenType::RParen)?;
(
vec![],
vec![property("REMOVE_POSITION_DELETES", None, None)],
)
}
"ADD STORAGE LIFECYCLE POLICY" => {
let policy = self.parse_tag_operand()?;
self.expect_token(TokenType::On)?;
(vec![policy, self.parse_column_names()?], vec![])
}
_ => (vec![], vec![]),
};
Ok((action, parts))
}

/// Parse the head and one of `actions` of an ALTER `kind`, or None for another action.
fn parse_alter_kind_extension(
&mut self,
kind: &str,
actions: &[&[&str]],
) -> Result<Option<Expression>> {
let head = self.parse_alter_object_head()?;
let Some(words) = actions.iter().find(|words| self.match_text_seq(words)) else {
return Ok(None);
};
let action = words.join(" ");
let parts: ActionParts = match action.as_str() {
"UNSET" => {
self.expect_alter_words(&[&["COMMENT"]])?;
(vec![], vec![property("COMMENT", None, None)])
}
_ => (vec![self.parse_tag_operand()?], vec![]),
};
Ok(Some(alter_object(kind, head, action, parts)))
}

/// Parse ALTER SHARE: UNSET COMMENT typed; ADD, REMOVE, SET and UNSET TAG are left to the fallback.
fn parse_alter_share(&mut self) -> Result<Option<Expression>> {
let start = self.current - 1;
let head = self.parse_alter_object_head()?;
if self.match_text_seq(&["UNSET", "COMMENT"]) {
let parts = (vec![], vec![property("COMMENT", None, None)]);
return Ok(Some(alter_object("SHARE", head, "UNSET".to_owned(), parts)));
}
match (
self.text_at(0).unwrap_or_default().as_str(),
self.text_at(1).as_deref(),
) {
("UNSET", Some("TAG")) | ("ADD" | "REMOVE" | "SET", _) => {}
("UNSET", _) => return Err(self.parse_error("Expected COMMENT or TAG after UNSET")),
_ => return Err(self.parse_error("Expected ADD, REMOVE, SET or UNSET")),
}
self.current = start;
Ok(None)
}

/// Parse a name operand: an identifier, never a string or an unquoted reserved keyword.
fn parse_tag_operand(&mut self) -> Result<Expression> {
Ok(Expression::Table(Box::new(self.parse_object_name()?)))
}

/// Parse `{ VERSION <snapshot_id> | TIMESTAMP <timestamp_ms> } [ RETAIN <num> { DAYS | HOURS | MINUTES } ]`.
fn parse_version_point(&mut self) -> Result<Vec<ObjectProperty>> {
let point = self.expect_alter_words(&[&["VERSION"], &["TIMESTAMP"]])?;
let mut properties = vec![property(&point, None, Some(self.parse_number_operand()?))];
if self.match_identifier("RETAIN") {
let count = self.parse_number_operand()?;
let unit = self.expect_alter_words(RETAIN_UNITS)?;
properties.push(property("RETAIN", Some(Identifier::new(unit)), Some(count)));
}
Ok(properties)
}

/// Parse an integer literal.
fn parse_number_operand(&mut self) -> Result<Expression> {
if !self.check(TokenType::Number) {
return Err(self.parse_error("Expected a number"));
}
self.parse_primary()
}

/// Parse `( <expr> )`, one partition expression.
fn parse_partition_expression(&mut self) -> Result<Expression> {
self.expect_token(TokenType::LParen)?;
let expression = self.parse_expression()?;
self.expect_token(TokenType::RParen)?;
Ok(expression)
}

/// Parse `( <col_name> [ , ... ] )`.
fn parse_column_names(&mut self) -> Result<Expression> {
self.expect_token(TokenType::LParen)?;
let mut expressions = vec![self.parse_tag_operand()?];
while self.match_token(TokenType::Comma) {
expressions.push(self.parse_tag_operand()?);
}
self.expect_token(TokenType::RParen)?;
Ok(Expression::Tuple(Box::new(Tuple { expressions })))
}
}
Loading