Skip to content

Commit 4c6af0a

Browse files
authored
Add support for Snowflake LIST and REMOVE (apache#1639)
1 parent 17e22f0 commit 4c6af0a

File tree

6 files changed

+97
-5
lines changed

6 files changed

+97
-5
lines changed

src/ast/helpers/stmt_data_loading.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use core::fmt::Formatter;
2929
#[cfg(feature = "serde")]
3030
use serde::{Deserialize, Serialize};
3131

32-
use crate::ast::Ident;
32+
use crate::ast::{Ident, ObjectName};
3333
#[cfg(feature = "visitor")]
3434
use sqlparser_derive::{Visit, VisitMut};
3535

@@ -156,3 +156,22 @@ impl fmt::Display for StageLoadSelectItem {
156156
Ok(())
157157
}
158158
}
159+
160+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
161+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
162+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
163+
pub struct FileStagingCommand {
164+
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
165+
pub stage: ObjectName,
166+
pub pattern: Option<String>,
167+
}
168+
169+
impl fmt::Display for FileStagingCommand {
170+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171+
write!(f, "{}", self.stage)?;
172+
if let Some(pattern) = self.pattern.as_ref() {
173+
write!(f, " PATTERN='{pattern}'")?;
174+
}
175+
Ok(())
176+
}
177+
}

src/ast/mod.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use alloc::{
2323
string::{String, ToString},
2424
vec::Vec,
2525
};
26-
use helpers::attached_token::AttachedToken;
26+
use helpers::{attached_token::AttachedToken, stmt_data_loading::FileStagingCommand};
2727

2828
use core::ops::Deref;
2929
use core::{
@@ -3420,6 +3420,12 @@ pub enum Statement {
34203420
///
34213421
/// See Mysql <https://dev.mysql.com/doc/refman/9.1/en/rename-table.html>
34223422
RenameTable(Vec<RenameTable>),
3423+
/// Snowflake `LIST`
3424+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/list>
3425+
List(FileStagingCommand),
3426+
/// Snowflake `REMOVE`
3427+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/remove>
3428+
Remove(FileStagingCommand),
34233429
}
34243430

34253431
impl fmt::Display for Statement {
@@ -4980,6 +4986,8 @@ impl fmt::Display for Statement {
49804986
Statement::RenameTable(rename_tables) => {
49814987
write!(f, "RENAME TABLE {}", display_comma_separated(rename_tables))
49824988
}
4989+
Statement::List(command) => write!(f, "LIST {command}"),
4990+
Statement::Remove(command) => write!(f, "REMOVE {command}"),
49834991
}
49844992
}
49854993
}

src/ast/spans.rs

+1
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ impl Spanned for Statement {
493493
Statement::LoadData { .. } => Span::empty(),
494494
Statement::UNLISTEN { .. } => Span::empty(),
495495
Statement::RenameTable { .. } => Span::empty(),
496+
Statement::List(..) | Statement::Remove(..) => Span::empty(),
496497
}
497498
}
498499
}

src/dialect/snowflake.rs

+32-3
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
use crate::alloc::string::ToString;
2020
use crate::ast::helpers::stmt_create_table::CreateTableBuilder;
2121
use crate::ast::helpers::stmt_data_loading::{
22-
DataLoadingOption, DataLoadingOptionType, DataLoadingOptions, StageLoadSelectItem,
23-
StageParamsObject,
22+
DataLoadingOption, DataLoadingOptionType, DataLoadingOptions, FileStagingCommand,
23+
StageLoadSelectItem, StageParamsObject,
2424
};
2525
use crate::ast::{
2626
ColumnOption, ColumnPolicy, ColumnPolicyProperty, Ident, IdentityParameters, IdentityProperty,
@@ -165,6 +165,15 @@ impl Dialect for SnowflakeDialect {
165165
return Some(parse_copy_into(parser));
166166
}
167167

168+
if let Some(kw) = parser.parse_one_of_keywords(&[
169+
Keyword::LIST,
170+
Keyword::LS,
171+
Keyword::REMOVE,
172+
Keyword::RM,
173+
]) {
174+
return Some(parse_file_staging_command(kw, parser));
175+
}
176+
168177
None
169178
}
170179

@@ -240,6 +249,26 @@ impl Dialect for SnowflakeDialect {
240249
}
241250
}
242251

252+
fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statement, ParserError> {
253+
let stage = parse_snowflake_stage_name(parser)?;
254+
let pattern = if parser.parse_keyword(Keyword::PATTERN) {
255+
parser.expect_token(&Token::Eq)?;
256+
Some(parser.parse_literal_string()?)
257+
} else {
258+
None
259+
};
260+
261+
match kw {
262+
Keyword::LIST | Keyword::LS => Ok(Statement::List(FileStagingCommand { stage, pattern })),
263+
Keyword::REMOVE | Keyword::RM => {
264+
Ok(Statement::Remove(FileStagingCommand { stage, pattern }))
265+
}
266+
_ => Err(ParserError::ParserError(
267+
"unexpected stage command, expecting LIST, LS, REMOVE or RM".to_string(),
268+
)),
269+
}
270+
}
271+
243272
/// Parse snowflake create table statement.
244273
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
245274
pub fn parse_create_table(
@@ -501,7 +530,7 @@ pub fn parse_stage_name_identifier(parser: &mut Parser) -> Result<Ident, ParserE
501530
Token::Tilde => ident.push('~'),
502531
Token::Mod => ident.push('%'),
503532
Token::Div => ident.push('/'),
504-
Token::Word(w) => ident.push_str(&w.value),
533+
Token::Word(w) => ident.push_str(&w.to_string()),
505534
_ => return parser.expected("stage name identifier", parser.peek_token()),
506535
}
507536
}

src/keywords.rs

+4
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ define_keywords!(
451451
LIKE_REGEX,
452452
LIMIT,
453453
LINES,
454+
LIST,
454455
LISTEN,
455456
LN,
456457
LOAD,
@@ -467,6 +468,7 @@ define_keywords!(
467468
LOWCARDINALITY,
468469
LOWER,
469470
LOW_PRIORITY,
471+
LS,
470472
MACRO,
471473
MANAGEDLOCATION,
472474
MAP,
@@ -649,6 +651,7 @@ define_keywords!(
649651
RELAY,
650652
RELEASE,
651653
REMOTE,
654+
REMOVE,
652655
RENAME,
653656
REORG,
654657
REPAIR,
@@ -672,6 +675,7 @@ define_keywords!(
672675
REVOKE,
673676
RIGHT,
674677
RLIKE,
678+
RM,
675679
ROLE,
676680
ROLES,
677681
ROLLBACK,

tests/sqlparser_snowflake.rs

+31
Original file line numberDiff line numberDiff line change
@@ -2991,3 +2991,34 @@ fn test_table_sample() {
29912991
snowflake_and_generic().verified_stmt("SELECT id FROM mytable TABLESAMPLE (10) REPEATABLE (1)");
29922992
snowflake_and_generic().verified_stmt("SELECT id FROM mytable TABLESAMPLE (10) SEED (1)");
29932993
}
2994+
2995+
#[test]
2996+
fn parse_ls_and_rm() {
2997+
snowflake().one_statement_parses_to("LS @~", "LIST @~");
2998+
snowflake().one_statement_parses_to("RM @~", "REMOVE @~");
2999+
3000+
let statement = snowflake()
3001+
.verified_stmt("LIST @SNOWFLAKE_KAFKA_CONNECTOR_externalDataLakeSnowflakeConnector_STAGE_call_tracker_stream/");
3002+
match statement {
3003+
Statement::List(command) => {
3004+
assert_eq!(command.stage, ObjectName(vec!["@SNOWFLAKE_KAFKA_CONNECTOR_externalDataLakeSnowflakeConnector_STAGE_call_tracker_stream/".into()]));
3005+
assert!(command.pattern.is_none());
3006+
}
3007+
_ => unreachable!(),
3008+
};
3009+
3010+
let statement =
3011+
snowflake().verified_stmt("REMOVE @my_csv_stage/analysis/ PATTERN='.*data_0.*'");
3012+
match statement {
3013+
Statement::Remove(command) => {
3014+
assert_eq!(
3015+
command.stage,
3016+
ObjectName(vec!["@my_csv_stage/analysis/".into()])
3017+
);
3018+
assert_eq!(command.pattern, Some(".*data_0.*".to_string()));
3019+
}
3020+
_ => unreachable!(),
3021+
};
3022+
3023+
snowflake().verified_stmt(r#"LIST @"STAGE_WITH_QUOTES""#);
3024+
}

0 commit comments

Comments
 (0)