Skip to content

Commit 3b52428

Browse files
authored
Redshift: Add support for IAM_ROLE and IGNOREHEADER COPY options (#1968)
1 parent 12c0878 commit 3b52428

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
@@ -8772,6 +8772,10 @@ pub enum CopyLegacyOption {
87728772
Null(String),
87738773
/// CSV ...
87748774
Csv(Vec<CopyLegacyCsvOption>),
8775+
/// IAM_ROLE { DEFAULT | 'arn:aws:iam::123456789:role/role1' }
8776+
IamRole(IamRoleKind),
8777+
/// IGNOREHEADER \[ AS \] number_rows
8778+
IgnoreHeader(u64),
87758779
}
87768780

87778781
impl fmt::Display for CopyLegacyOption {
@@ -8781,7 +8785,37 @@ impl fmt::Display for CopyLegacyOption {
87818785
Binary => write!(f, "BINARY"),
87828786
Delimiter(char) => write!(f, "DELIMITER '{char}'"),
87838787
Null(string) => write!(f, "NULL '{}'", value::escape_single_quote_string(string)),
8784-
Csv(opts) => write!(f, "CSV {}", display_separated(opts, " ")),
8788+
Csv(opts) => {
8789+
write!(f, "CSV")?;
8790+
if !opts.is_empty() {
8791+
write!(f, " {}", display_separated(opts, " "))?;
8792+
}
8793+
Ok(())
8794+
}
8795+
IamRole(role) => write!(f, "IAM_ROLE {role}"),
8796+
IgnoreHeader(num_rows) => write!(f, "IGNOREHEADER {num_rows}"),
8797+
}
8798+
}
8799+
}
8800+
8801+
/// An `IAM_ROLE` option in the AWS ecosystem
8802+
///
8803+
/// [Redshift COPY](https://docs.aws.amazon.com/redshift/latest/dg/copy-parameters-authorization.html#copy-iam-role)
8804+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
8805+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8806+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
8807+
pub enum IamRoleKind {
8808+
/// Default role
8809+
Default,
8810+
/// Specific role ARN, for example: `arn:aws:iam::123456789:role/role1`
8811+
Arn(String),
8812+
}
8813+
8814+
impl fmt::Display for IamRoleKind {
8815+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
8816+
match self {
8817+
IamRoleKind::Default => write!(f, "DEFAULT"),
8818+
IamRoleKind::Arn(arn) => write!(f, "'{arn}'"),
87858819
}
87868820
}
87878821
}

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
@@ -9548,6 +9548,8 @@ impl<'a> Parser<'a> {
95489548
Keyword::DELIMITER,
95499549
Keyword::NULL,
95509550
Keyword::CSV,
9551+
Keyword::IAM_ROLE,
9552+
Keyword::IGNOREHEADER,
95519553
]) {
95529554
Some(Keyword::BINARY) => CopyLegacyOption::Binary,
95539555
Some(Keyword::DELIMITER) => {
@@ -9567,11 +9569,26 @@ impl<'a> Parser<'a> {
95679569
}
95689570
opts
95699571
}),
9572+
Some(Keyword::IAM_ROLE) => CopyLegacyOption::IamRole(self.parse_iam_role_kind()?),
9573+
Some(Keyword::IGNOREHEADER) => {
9574+
let _ = self.parse_keyword(Keyword::AS);
9575+
let num_rows = self.parse_literal_uint()?;
9576+
CopyLegacyOption::IgnoreHeader(num_rows)
9577+
}
95709578
_ => self.expected("option", self.peek_token())?,
95719579
};
95729580
Ok(ret)
95739581
}
95749582

9583+
fn parse_iam_role_kind(&mut self) -> Result<IamRoleKind, ParserError> {
9584+
if self.parse_keyword(Keyword::DEFAULT) {
9585+
Ok(IamRoleKind::Default)
9586+
} else {
9587+
let arn = self.parse_literal_string()?;
9588+
Ok(IamRoleKind::Arn(arn))
9589+
}
9590+
}
9591+
95759592
fn parse_copy_legacy_csv_option(&mut self) -> Result<CopyLegacyCsvOption, ParserError> {
95769593
let ret = match self.parse_one_of_keywords(&[
95779594
Keyword::HEADER,

tests/sqlparser_common.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16729,3 +16729,43 @@ fn parse_create_table_like() {
1672916729
_ => unreachable!(),
1673016730
}
1673116731
}
16732+
16733+
#[test]
16734+
fn parse_copy_options() {
16735+
let copy = verified_stmt(
16736+
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"#,
16737+
);
16738+
match copy {
16739+
Statement::Copy { legacy_options, .. } => {
16740+
assert_eq!(
16741+
legacy_options,
16742+
vec![
16743+
CopyLegacyOption::IamRole(IamRoleKind::Arn(
16744+
"arn:aws:iam::123456789:role/role1".to_string()
16745+
)),
16746+
CopyLegacyOption::Csv(vec![]),
16747+
CopyLegacyOption::IgnoreHeader(1),
16748+
]
16749+
);
16750+
}
16751+
_ => unreachable!(),
16752+
}
16753+
16754+
let copy = one_statement_parses_to(
16755+
r#"COPY dst (c1, c2, c3) FROM 's3://redshift-downloads/tickit/category_pipe.txt' IAM_ROLE DEFAULT CSV IGNOREHEADER AS 1"#,
16756+
r#"COPY dst (c1, c2, c3) FROM 's3://redshift-downloads/tickit/category_pipe.txt' IAM_ROLE DEFAULT CSV IGNOREHEADER 1"#,
16757+
);
16758+
match copy {
16759+
Statement::Copy { legacy_options, .. } => {
16760+
assert_eq!(
16761+
legacy_options,
16762+
vec![
16763+
CopyLegacyOption::IamRole(IamRoleKind::Default),
16764+
CopyLegacyOption::Csv(vec![]),
16765+
CopyLegacyOption::IgnoreHeader(1),
16766+
]
16767+
);
16768+
}
16769+
_ => unreachable!(),
16770+
}
16771+
}

0 commit comments

Comments
 (0)