Skip to content

Commit e2ce324

Browse files
jonathanlehtoalamb
andauthored
Support Unload statement (#1150)
Co-authored-by: Andrew Lamb <[email protected]>
1 parent 4d1eecd commit e2ce324

File tree

4 files changed

+96
-1
lines changed

4 files changed

+96
-1
lines changed

src/ast/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2547,6 +2547,16 @@ pub enum Statement {
25472547
/// ```
25482548
/// Note: this is a MySQL-specific statement. See <https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html>
25492549
UnlockTables,
2550+
/// ```sql
2551+
/// UNLOAD(statement) TO <destination> [ WITH options ]
2552+
/// ```
2553+
/// See Redshift <https://docs.aws.amazon.com/redshift/latest/dg/r_UNLOAD.html> and
2554+
// Athena <https://docs.aws.amazon.com/athena/latest/ug/unload.html>
2555+
Unload {
2556+
query: Box<Query>,
2557+
to: Ident,
2558+
with: Vec<SqlOption>,
2559+
},
25502560
}
25512561

25522562
impl fmt::Display for Statement {
@@ -4060,6 +4070,15 @@ impl fmt::Display for Statement {
40604070
Statement::UnlockTables => {
40614071
write!(f, "UNLOCK TABLES")
40624072
}
4073+
Statement::Unload { query, to, with } => {
4074+
write!(f, "UNLOAD({query}) TO {to}")?;
4075+
4076+
if !with.is_empty() {
4077+
write!(f, " WITH ({})", display_comma_separated(with))?;
4078+
}
4079+
4080+
Ok(())
4081+
}
40634082
}
40644083
}
40654084
}

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,7 @@ define_keywords!(
689689
UNION,
690690
UNIQUE,
691691
UNKNOWN,
692+
UNLOAD,
692693
UNLOCK,
693694
UNLOGGED,
694695
UNNEST,

src/parser/mod.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ impl<'a> Parser<'a> {
516516
Keyword::MERGE => Ok(self.parse_merge()?),
517517
// `PRAGMA` is sqlite specific https://www.sqlite.org/pragma.html
518518
Keyword::PRAGMA => Ok(self.parse_pragma()?),
519+
Keyword::UNLOAD => Ok(self.parse_unload()?),
519520
// `INSTALL` is duckdb specific https://duckdb.org/docs/extensions/overview
520521
Keyword::INSTALL if dialect_of!(self is DuckDbDialect | GenericDialect) => {
521522
Ok(self.parse_install()?)
@@ -524,7 +525,6 @@ impl<'a> Parser<'a> {
524525
Keyword::LOAD if dialect_of!(self is DuckDbDialect | GenericDialect) => {
525526
Ok(self.parse_load()?)
526527
}
527-
528528
_ => self.expected("an SQL statement", next_token),
529529
},
530530
Token::LParen => {
@@ -8947,6 +8947,23 @@ impl<'a> Parser<'a> {
89478947
})
89488948
}
89498949

8950+
pub fn parse_unload(&mut self) -> Result<Statement, ParserError> {
8951+
self.expect_token(&Token::LParen)?;
8952+
let query = self.parse_query()?;
8953+
self.expect_token(&Token::RParen)?;
8954+
8955+
self.expect_keyword(Keyword::TO)?;
8956+
let to = self.parse_identifier(false)?;
8957+
8958+
let with_options = self.parse_options(Keyword::WITH)?;
8959+
8960+
Ok(Statement::Unload {
8961+
query: Box::new(query),
8962+
to,
8963+
with: with_options,
8964+
})
8965+
}
8966+
89508967
pub fn parse_merge_clauses(&mut self) -> Result<Vec<MergeClause>, ParserError> {
89518968
let mut clauses: Vec<MergeClause> = vec![];
89528969
loop {

tests/sqlparser_common.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8434,6 +8434,64 @@ fn parse_binary_operators_without_whitespace() {
84348434
);
84358435
}
84368436

8437+
#[test]
8438+
fn parse_unload() {
8439+
let unload = verified_stmt("UNLOAD(SELECT cola FROM tab) TO 's3://...' WITH (format = 'AVRO')");
8440+
assert_eq!(
8441+
unload,
8442+
Statement::Unload {
8443+
query: Box::new(Query {
8444+
body: Box::new(SetExpr::Select(Box::new(Select {
8445+
distinct: None,
8446+
top: None,
8447+
projection: vec![UnnamedExpr(Expr::Identifier(Ident::new("cola"))),],
8448+
into: None,
8449+
from: vec![TableWithJoins {
8450+
relation: TableFactor::Table {
8451+
name: ObjectName(vec![Ident::new("tab")]),
8452+
alias: None,
8453+
args: None,
8454+
with_hints: vec![],
8455+
version: None,
8456+
partitions: vec![],
8457+
},
8458+
joins: vec![],
8459+
}],
8460+
lateral_views: vec![],
8461+
selection: None,
8462+
group_by: GroupByExpr::Expressions(vec![]),
8463+
cluster_by: vec![],
8464+
distribute_by: vec![],
8465+
sort_by: vec![],
8466+
having: None,
8467+
named_window: vec![],
8468+
qualify: None,
8469+
value_table_mode: None,
8470+
}))),
8471+
with: None,
8472+
limit: None,
8473+
limit_by: vec![],
8474+
offset: None,
8475+
fetch: None,
8476+
locks: vec![],
8477+
for_clause: None,
8478+
order_by: vec![],
8479+
}),
8480+
to: Ident {
8481+
value: "s3://...".to_string(),
8482+
quote_style: Some('\'')
8483+
},
8484+
with: vec![SqlOption {
8485+
name: Ident {
8486+
value: "format".to_string(),
8487+
quote_style: None
8488+
},
8489+
value: Expr::Value(Value::SingleQuotedString("AVRO".to_string()))
8490+
}]
8491+
}
8492+
);
8493+
}
8494+
84378495
#[test]
84388496
fn test_savepoint() {
84398497
match verified_stmt("SAVEPOINT test1") {

0 commit comments

Comments
 (0)