Skip to content

Commit 47961ec

Browse files
yoavcloudayman-sigma
authored andcommitted
Redshift: Add support for UNLOAD statement (apache#2013)
1 parent 8a19626 commit 47961ec

4 files changed

Lines changed: 420 additions & 18 deletions

File tree

src/ast/mod.rs

Lines changed: 167 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4323,15 +4323,24 @@ pub enum Statement {
43234323
/// ```
43244324
/// Note: this is a MySQL-specific statement. See <https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html>
43254325
UnlockTables,
4326+
/// Unloads the result of a query to file
4327+
///
4328+
/// [Athena](https://docs.aws.amazon.com/athena/latest/ug/unload.html):
43264329
/// ```sql
43274330
/// UNLOAD(statement) TO <destination> [ WITH options ]
43284331
/// ```
4329-
/// See Redshift <https://docs.aws.amazon.com/redshift/latest/dg/r_UNLOAD.html> and
4330-
// Athena <https://docs.aws.amazon.com/athena/latest/ug/unload.html>
4332+
///
4333+
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_UNLOAD.html):
4334+
/// ```sql
4335+
/// UNLOAD('statement') TO <destination> [ OPTIONS ]
4336+
/// ```
43314337
Unload {
4332-
query: Box<Query>,
4338+
query: Option<Box<Query>>,
4339+
query_text: Option<String>,
43334340
to: Ident,
4341+
auth: Option<IamRoleKind>,
43344342
with: Vec<SqlOption>,
4343+
options: Vec<CopyLegacyOption>,
43354344
},
43364345
/// ```sql
43374346
/// OPTIMIZE TABLE [db.]name [ON CLUSTER cluster] [PARTITION partition | PARTITION ID 'partition_id'] [FINAL] [DEDUPLICATE [BY expression]]
@@ -6311,13 +6320,31 @@ impl fmt::Display for Statement {
63116320
Statement::UnlockTables => {
63126321
write!(f, "UNLOCK TABLES")
63136322
}
6314-
Statement::Unload { query, to, with } => {
6315-
write!(f, "UNLOAD({query}) TO {to}")?;
6316-
6323+
Statement::Unload {
6324+
query,
6325+
query_text,
6326+
to,
6327+
auth,
6328+
with,
6329+
options,
6330+
} => {
6331+
write!(f, "UNLOAD(")?;
6332+
if let Some(query) = query {
6333+
write!(f, "{query}")?;
6334+
}
6335+
if let Some(query_text) = query_text {
6336+
write!(f, "'{query_text}'")?;
6337+
}
6338+
write!(f, ") TO {to}")?;
6339+
if let Some(auth) = auth {
6340+
write!(f, " IAM_ROLE {auth}")?;
6341+
}
63176342
if !with.is_empty() {
63186343
write!(f, " WITH ({})", display_comma_separated(with))?;
63196344
}
6320-
6345+
if !options.is_empty() {
6346+
write!(f, " {}", display_separated(options, " "))?;
6347+
}
63216348
Ok(())
63226349
}
63236350
Statement::OptimizeTable {
@@ -8826,10 +8853,18 @@ pub enum CopyLegacyOption {
88268853
AcceptAnyDate,
88278854
/// ACCEPTINVCHARS
88288855
AcceptInvChars(Option<String>),
8856+
/// ADDQUOTES
8857+
AddQuotes,
8858+
/// ALLOWOVERWRITE
8859+
AllowOverwrite,
88298860
/// BINARY
88308861
Binary,
88318862
/// BLANKSASNULL
88328863
BlankAsNull,
8864+
/// BZIP2
8865+
Bzip2,
8866+
/// CLEANPATH
8867+
CleanPath,
88338868
/// CSV ...
88348869
Csv(Vec<CopyLegacyCsvOption>),
88358870
/// DATEFORMAT \[ AS \] {'dateformat_string' | 'auto' }
@@ -8838,16 +8873,46 @@ pub enum CopyLegacyOption {
88388873
Delimiter(char),
88398874
/// EMPTYASNULL
88408875
EmptyAsNull,
8876+
/// ENCRYPTED \[ AUTO \]
8877+
Encrypted { auto: bool },
8878+
/// ESCAPE
8879+
Escape,
8880+
/// EXTENSION 'extension-name'
8881+
Extension(String),
8882+
/// FIXEDWIDTH \[ AS \] 'fixedwidth-spec'
8883+
FixedWidth(String),
8884+
/// GZIP
8885+
Gzip,
8886+
/// HEADER
8887+
Header,
88418888
/// IAM_ROLE { DEFAULT | 'arn:aws:iam::123456789:role/role1' }
88428889
IamRole(IamRoleKind),
88438890
/// IGNOREHEADER \[ AS \] number_rows
88448891
IgnoreHeader(u64),
8892+
/// JSON
8893+
Json,
8894+
/// MANIFEST \[ VERBOSE \]
8895+
Manifest { verbose: bool },
8896+
/// MAXFILESIZE \[ AS \] max-size \[ MB | GB \]
8897+
MaxFileSize(FileSize),
88458898
/// NULL \[ AS \] 'null_string'
88468899
Null(String),
8900+
/// PARALLEL [ { ON | TRUE } | { OFF | FALSE } ]
8901+
Parallel(Option<bool>),
8902+
/// PARQUET
8903+
Parquet,
8904+
/// PARTITION BY ( column_name [, ... ] ) \[ INCLUDE \]
8905+
PartitionBy(UnloadPartitionBy),
8906+
/// REGION \[ AS \] 'aws-region' }
8907+
Region(String),
8908+
/// ROWGROUPSIZE \[ AS \] size \[ MB | GB \]
8909+
RowGroupSize(FileSize),
88478910
/// TIMEFORMAT \[ AS \] {'timeformat_string' | 'auto' | 'epochsecs' | 'epochmillisecs' }
88488911
TimeFormat(Option<String>),
88498912
/// TRUNCATECOLUMNS
88508913
TruncateColumns,
8914+
/// ZSTD
8915+
Zstd,
88518916
}
88528917

88538918
impl fmt::Display for CopyLegacyOption {
@@ -8862,8 +8927,12 @@ impl fmt::Display for CopyLegacyOption {
88628927
}
88638928
Ok(())
88648929
}
8930+
AddQuotes => write!(f, "ADDQUOTES"),
8931+
AllowOverwrite => write!(f, "ALLOWOVERWRITE"),
88658932
Binary => write!(f, "BINARY"),
88668933
BlankAsNull => write!(f, "BLANKSASNULL"),
8934+
Bzip2 => write!(f, "BZIP2"),
8935+
CleanPath => write!(f, "CLEANPATH"),
88678936
Csv(opts) => {
88688937
write!(f, "CSV")?;
88698938
if !opts.is_empty() {
@@ -8880,9 +8949,37 @@ impl fmt::Display for CopyLegacyOption {
88808949
}
88818950
Delimiter(char) => write!(f, "DELIMITER '{char}'"),
88828951
EmptyAsNull => write!(f, "EMPTYASNULL"),
8952+
Encrypted { auto } => write!(f, "ENCRYPTED{}", if *auto { " AUTO" } else { "" }),
8953+
Escape => write!(f, "ESCAPE"),
8954+
Extension(ext) => write!(f, "EXTENSION '{}'", value::escape_single_quote_string(ext)),
8955+
FixedWidth(spec) => write!(
8956+
f,
8957+
"FIXEDWIDTH '{}'",
8958+
value::escape_single_quote_string(spec)
8959+
),
8960+
Gzip => write!(f, "GZIP"),
8961+
Header => write!(f, "HEADER"),
88838962
IamRole(role) => write!(f, "IAM_ROLE {role}"),
88848963
IgnoreHeader(num_rows) => write!(f, "IGNOREHEADER {num_rows}"),
8964+
Json => write!(f, "JSON"),
8965+
Manifest { verbose } => write!(f, "MANIFEST{}", if *verbose { " VERBOSE" } else { "" }),
8966+
MaxFileSize(file_size) => write!(f, "MAXFILESIZE {file_size}"),
88858967
Null(string) => write!(f, "NULL '{}'", value::escape_single_quote_string(string)),
8968+
Parallel(enabled) => {
8969+
write!(
8970+
f,
8971+
"PARALLEL{}",
8972+
match enabled {
8973+
Some(true) => " TRUE",
8974+
Some(false) => " FALSE",
8975+
_ => "",
8976+
}
8977+
)
8978+
}
8979+
Parquet => write!(f, "PARQUET"),
8980+
PartitionBy(p) => write!(f, "{p}"),
8981+
Region(region) => write!(f, "REGION '{}'", value::escape_single_quote_string(region)),
8982+
RowGroupSize(file_size) => write!(f, "ROWGROUPSIZE {file_size}"),
88868983
TimeFormat(fmt) => {
88878984
write!(f, "TIMEFORMAT")?;
88888985
if let Some(fmt) = fmt {
@@ -8891,10 +8988,73 @@ impl fmt::Display for CopyLegacyOption {
88918988
Ok(())
88928989
}
88938990
TruncateColumns => write!(f, "TRUNCATECOLUMNS"),
8991+
Zstd => write!(f, "ZSTD"),
8992+
}
8993+
}
8994+
}
8995+
8996+
/// ```sql
8997+
/// SIZE \[ MB | GB \]
8998+
/// ```
8999+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
9000+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9001+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
9002+
pub struct FileSize {
9003+
pub size: Value,
9004+
pub unit: Option<FileSizeUnit>,
9005+
}
9006+
9007+
impl fmt::Display for FileSize {
9008+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
9009+
write!(f, "{}", self.size)?;
9010+
if let Some(unit) = &self.unit {
9011+
write!(f, " {unit}")?;
9012+
}
9013+
Ok(())
9014+
}
9015+
}
9016+
9017+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
9018+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9019+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
9020+
pub enum FileSizeUnit {
9021+
MB,
9022+
GB,
9023+
}
9024+
9025+
impl fmt::Display for FileSizeUnit {
9026+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
9027+
match self {
9028+
FileSizeUnit::MB => write!(f, "MB"),
9029+
FileSizeUnit::GB => write!(f, "GB"),
88949030
}
88959031
}
88969032
}
88979033

9034+
/// Specifies the partition keys for the unload operation
9035+
///
9036+
/// ```sql
9037+
/// PARTITION BY ( column_name [, ... ] ) [ INCLUDE ]
9038+
/// ```
9039+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
9040+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9041+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
9042+
pub struct UnloadPartitionBy {
9043+
pub columns: Vec<Ident>,
9044+
pub include: bool,
9045+
}
9046+
9047+
impl fmt::Display for UnloadPartitionBy {
9048+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
9049+
write!(
9050+
f,
9051+
"PARTITION BY ({}){}",
9052+
display_comma_separated(&self.columns),
9053+
if self.include { " INCLUDE" } else { "" }
9054+
)
9055+
}
9056+
}
9057+
88989058
/// An `IAM_ROLE` option in the AWS ecosystem
88999059
///
89009060
/// [Redshift COPY](https://docs.aws.amazon.com/redshift/latest/dg/copy-parameters-authorization.html#copy-iam-role)

src/keywords.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ define_keywords!(
8282
ACCOUNT,
8383
ACTION,
8484
ADD,
85+
ADDQUOTES,
8586
ADMIN,
8687
AFTER,
8788
AGAINST,
@@ -92,6 +93,7 @@ define_keywords!(
9293
ALIAS,
9394
ALL,
9495
ALLOCATE,
96+
ALLOWOVERWRITE,
9597
ALTER,
9698
ALWAYS,
9799
ANALYZE,
@@ -159,6 +161,7 @@ define_keywords!(
159161
BYPASSRLS,
160162
BYTEA,
161163
BYTES,
164+
BZIP2,
162165
CACHE,
163166
CALL,
164167
CALLED,
@@ -190,6 +193,7 @@ define_keywords!(
190193
CHECK,
191194
CHECKSUM,
192195
CIRCLE,
196+
CLEANPATH,
193197
CLEAR,
194198
CLOB,
195199
CLONE,
@@ -322,6 +326,7 @@ define_keywords!(
322326
ENABLE,
323327
ENABLE_SCHEMA_EVOLUTION,
324328
ENCODING,
329+
ENCRYPTED,
325330
ENCRYPTION,
326331
END,
327332
END_EXEC = "END-EXEC",
@@ -380,6 +385,7 @@ define_keywords!(
380385
FIRST,
381386
FIRST_VALUE,
382387
FIXEDSTRING,
388+
FIXEDWIDTH,
383389
FLATTEN,
384390
FLOAT,
385391
FLOAT32,
@@ -411,6 +417,7 @@ define_keywords!(
411417
FUNCTIONS,
412418
FUSION,
413419
FUTURE,
420+
GB,
414421
GENERAL,
415422
GENERATE,
416423
GENERATED,
@@ -426,6 +433,7 @@ define_keywords!(
426433
GROUP,
427434
GROUPING,
428435
GROUPS,
436+
GZIP,
429437
HASH,
430438
HAVING,
431439
HEADER,
@@ -550,6 +558,7 @@ define_keywords!(
550558
MANAGE,
551559
MANAGED,
552560
MANAGEDLOCATION,
561+
MANIFEST,
553562
MAP,
554563
MASKING,
555564
MATCH,
@@ -560,9 +569,11 @@ define_keywords!(
560569
MATERIALIZE,
561570
MATERIALIZED,
562571
MAX,
572+
MAXFILESIZE,
563573
MAXVALUE,
564574
MAX_DATA_EXTENSION_TIME_IN_DAYS,
565575
MAX_ROWS,
576+
MB,
566577
MEASURES,
567578
MEDIUMBLOB,
568579
MEDIUMINT,
@@ -761,6 +772,7 @@ define_keywords!(
761772
REFRESH_MODE,
762773
REGCLASS,
763774
REGEXP,
775+
REGION,
764776
REGR_AVGX,
765777
REGR_AVGY,
766778
REGR_COUNT,
@@ -813,6 +825,7 @@ define_keywords!(
813825
ROLLUP,
814826
ROOT,
815827
ROW,
828+
ROWGROUPSIZE,
816829
ROWID,
817830
ROWS,
818831
ROW_FORMAT,
@@ -1062,7 +1075,8 @@ define_keywords!(
10621075
YEAR,
10631076
YEARS,
10641077
ZONE,
1065-
ZORDER
1078+
ZORDER,
1079+
ZSTD
10661080
);
10671081

10681082
/// These keywords can't be used as a table alias, so that `FROM table_name alias`

0 commit comments

Comments
 (0)