Skip to content

Commit 2e2d668

Browse files
yoavcloudayman-sigma
authored andcommitted
Redshift: Add support for IAM_ROLE and IGNOREHEADER COPY options (apache#1968)
1 parent 02604ed commit 2e2d668

File tree

4 files changed

+94
-1
lines changed

4 files changed

+94
-1
lines changed

src/ast/mod.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8801,6 +8801,10 @@ pub enum CopyLegacyOption {
88018801
Null(String),
88028802
/// CSV ...
88038803
Csv(Vec<CopyLegacyCsvOption>),
8804+
/// IAM_ROLE { DEFAULT | 'arn:aws:iam::123456789:role/role1' }
8805+
IamRole(IamRoleKind),
8806+
/// IGNOREHEADER \[ AS \] number_rows
8807+
IgnoreHeader(u64),
88048808
}
88058809

88068810
impl fmt::Display for CopyLegacyOption {
@@ -8810,7 +8814,37 @@ impl fmt::Display for CopyLegacyOption {
88108814
Binary => write!(f, "BINARY"),
88118815
Delimiter(char) => write!(f, "DELIMITER '{char}'"),
88128816
Null(string) => write!(f, "NULL '{}'", value::escape_single_quote_string(string)),
8813-
Csv(opts) => write!(f, "CSV {}", display_separated(opts, " ")),
8817+
Csv(opts) => {
8818+
write!(f, "CSV")?;
8819+
if !opts.is_empty() {
8820+
write!(f, " {}", display_separated(opts, " "))?;
8821+
}
8822+
Ok(())
8823+
}
8824+
IamRole(role) => write!(f, "IAM_ROLE {role}"),
8825+
IgnoreHeader(num_rows) => write!(f, "IGNOREHEADER {num_rows}"),
8826+
}
8827+
}
8828+
}
8829+
8830+
/// An `IAM_ROLE` option in the AWS ecosystem
8831+
///
8832+
/// [Redshift COPY](https://docs.aws.amazon.com/redshift/latest/dg/copy-parameters-authorization.html#copy-iam-role)
8833+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
8834+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8835+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
8836+
pub enum IamRoleKind {
8837+
/// Default role
8838+
Default,
8839+
/// Specific role ARN, for example: `arn:aws:iam::123456789:role/role1`
8840+
Arn(String),
8841+
}
8842+
8843+
impl fmt::Display for IamRoleKind {
8844+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
8845+
match self {
8846+
IamRoleKind::Default => write!(f, "DEFAULT"),
8847+
IamRoleKind::Arn(arn) => write!(f, "'{arn}'"),
88148848
}
88158849
}
88168850
}

src/keywords.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,12 +429,14 @@ define_keywords!(
429429
HOUR,
430430
HOURS,
431431
HUGEINT,
432+
IAM_ROLE,
432433
ICEBERG,
433434
ID,
434435
IDENTITY,
435436
IDENTITY_INSERT,
436437
IF,
437438
IGNORE,
439+
IGNOREHEADER,
438440
ILIKE,
439441
IMMEDIATE,
440442
IMMUTABLE,

src/parser/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9579,6 +9579,8 @@ impl<'a> Parser<'a> {
95799579
Keyword::DELIMITER,
95809580
Keyword::NULL,
95819581
Keyword::CSV,
9582+
Keyword::IAM_ROLE,
9583+
Keyword::IGNOREHEADER,
95829584
]) {
95839585
Some(Keyword::BINARY) => CopyLegacyOption::Binary,
95849586
Some(Keyword::DELIMITER) => {
@@ -9598,11 +9600,26 @@ impl<'a> Parser<'a> {
95989600
}
95999601
opts
96009602
}),
9603+
Some(Keyword::IAM_ROLE) => CopyLegacyOption::IamRole(self.parse_iam_role_kind()?),
9604+
Some(Keyword::IGNOREHEADER) => {
9605+
let _ = self.parse_keyword(Keyword::AS);
9606+
let num_rows = self.parse_literal_uint()?;
9607+
CopyLegacyOption::IgnoreHeader(num_rows)
9608+
}
96019609
_ => self.expected("option", self.peek_token())?,
96029610
};
96039611
Ok(ret)
96049612
}
96059613

9614+
fn parse_iam_role_kind(&mut self) -> Result<IamRoleKind, ParserError> {
9615+
if self.parse_keyword(Keyword::DEFAULT) {
9616+
Ok(IamRoleKind::Default)
9617+
} else {
9618+
let arn = self.parse_literal_string()?;
9619+
Ok(IamRoleKind::Arn(arn))
9620+
}
9621+
}
9622+
96069623
fn parse_copy_legacy_csv_option(&mut self) -> Result<CopyLegacyCsvOption, ParserError> {
96079624
let ret = match self.parse_one_of_keywords(&[
96089625
Keyword::HEADER,

tests/sqlparser_common.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16755,3 +16755,43 @@ fn parse_create_table_like() {
1675516755
_ => unreachable!(),
1675616756
}
1675716757
}
16758+
16759+
#[test]
16760+
fn parse_copy_options() {
16761+
let copy = verified_stmt(
16762+
r#"COPY dst (c1, c2, c3) FROM 's3://redshift-downloads/tickit/category_pipe.txt' IAM_ROLE 'arn:aws:iam::123456789:role/role1' CSV IGNOREHEADER 1"#,
16763+
);
16764+
match copy {
16765+
Statement::Copy { legacy_options, .. } => {
16766+
assert_eq!(
16767+
legacy_options,
16768+
vec![
16769+
CopyLegacyOption::IamRole(IamRoleKind::Arn(
16770+
"arn:aws:iam::123456789:role/role1".to_string()
16771+
)),
16772+
CopyLegacyOption::Csv(vec![]),
16773+
CopyLegacyOption::IgnoreHeader(1),
16774+
]
16775+
);
16776+
}
16777+
_ => unreachable!(),
16778+
}
16779+
16780+
let copy = one_statement_parses_to(
16781+
r#"COPY dst (c1, c2, c3) FROM 's3://redshift-downloads/tickit/category_pipe.txt' IAM_ROLE DEFAULT CSV IGNOREHEADER AS 1"#,
16782+
r#"COPY dst (c1, c2, c3) FROM 's3://redshift-downloads/tickit/category_pipe.txt' IAM_ROLE DEFAULT CSV IGNOREHEADER 1"#,
16783+
);
16784+
match copy {
16785+
Statement::Copy { legacy_options, .. } => {
16786+
assert_eq!(
16787+
legacy_options,
16788+
vec![
16789+
CopyLegacyOption::IamRole(IamRoleKind::Default),
16790+
CopyLegacyOption::Csv(vec![]),
16791+
CopyLegacyOption::IgnoreHeader(1),
16792+
]
16793+
);
16794+
}
16795+
_ => unreachable!(),
16796+
}
16797+
}

0 commit comments

Comments
 (0)