mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-03 04:37:21 +00:00
Redshift: Add more copy options (#2008)
Some checks are pending
license / Release Audit Tool (RAT) (push) Waiting to run
Rust / codestyle (push) Waiting to run
Rust / lint (push) Waiting to run
Rust / benchmark-lint (push) Waiting to run
Rust / compile (push) Waiting to run
Rust / docs (push) Waiting to run
Rust / compile-no-std (push) Waiting to run
Rust / test (beta) (push) Waiting to run
Rust / test (stable) (push) Waiting to run
Rust / test (nightly) (push) Waiting to run
Some checks are pending
license / Release Audit Tool (RAT) (push) Waiting to run
Rust / codestyle (push) Waiting to run
Rust / lint (push) Waiting to run
Rust / benchmark-lint (push) Waiting to run
Rust / compile (push) Waiting to run
Rust / docs (push) Waiting to run
Rust / compile-no-std (push) Waiting to run
Rust / test (beta) (push) Waiting to run
Rust / test (stable) (push) Waiting to run
Rust / test (nightly) (push) Waiting to run
This commit is contained in:
parent
6e80e5c237
commit
5d5c90c77f
4 changed files with 139 additions and 17 deletions
|
@ -8774,32 +8774,54 @@ impl fmt::Display for CopyOption {
|
||||||
|
|
||||||
/// An option in `COPY` statement before PostgreSQL version 9.0.
|
/// An option in `COPY` statement before PostgreSQL version 9.0.
|
||||||
///
|
///
|
||||||
/// <https://www.postgresql.org/docs/8.4/sql-copy.html>
|
/// [PostgreSQL](https://www.postgresql.org/docs/8.4/sql-copy.html)
|
||||||
|
/// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_COPY-alphabetical-parm-list.html)
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
pub enum CopyLegacyOption {
|
pub enum CopyLegacyOption {
|
||||||
|
/// ACCEPTANYDATE
|
||||||
|
AcceptAnyDate,
|
||||||
|
/// ACCEPTINVCHARS
|
||||||
|
AcceptInvChars(Option<String>),
|
||||||
/// BINARY
|
/// BINARY
|
||||||
Binary,
|
Binary,
|
||||||
/// DELIMITER \[ AS \] 'delimiter_character'
|
/// BLANKSASNULL
|
||||||
Delimiter(char),
|
BlankAsNull,
|
||||||
/// NULL \[ AS \] 'null_string'
|
|
||||||
Null(String),
|
|
||||||
/// CSV ...
|
/// CSV ...
|
||||||
Csv(Vec<CopyLegacyCsvOption>),
|
Csv(Vec<CopyLegacyCsvOption>),
|
||||||
|
/// DATEFORMAT \[ AS \] {'dateformat_string' | 'auto' }
|
||||||
|
DateFormat(Option<String>),
|
||||||
|
/// DELIMITER \[ AS \] 'delimiter_character'
|
||||||
|
Delimiter(char),
|
||||||
|
/// EMPTYASNULL
|
||||||
|
EmptyAsNull,
|
||||||
/// IAM_ROLE { DEFAULT | 'arn:aws:iam::123456789:role/role1' }
|
/// IAM_ROLE { DEFAULT | 'arn:aws:iam::123456789:role/role1' }
|
||||||
IamRole(IamRoleKind),
|
IamRole(IamRoleKind),
|
||||||
/// IGNOREHEADER \[ AS \] number_rows
|
/// IGNOREHEADER \[ AS \] number_rows
|
||||||
IgnoreHeader(u64),
|
IgnoreHeader(u64),
|
||||||
|
/// NULL \[ AS \] 'null_string'
|
||||||
|
Null(String),
|
||||||
|
/// TIMEFORMAT \[ AS \] {'timeformat_string' | 'auto' | 'epochsecs' | 'epochmillisecs' }
|
||||||
|
TimeFormat(Option<String>),
|
||||||
|
/// TRUNCATECOLUMNS
|
||||||
|
TruncateColumns,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for CopyLegacyOption {
|
impl fmt::Display for CopyLegacyOption {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
use CopyLegacyOption::*;
|
use CopyLegacyOption::*;
|
||||||
match self {
|
match self {
|
||||||
|
AcceptAnyDate => write!(f, "ACCEPTANYDATE"),
|
||||||
|
AcceptInvChars(ch) => {
|
||||||
|
write!(f, "ACCEPTINVCHARS")?;
|
||||||
|
if let Some(ch) = ch {
|
||||||
|
write!(f, " '{}'", value::escape_single_quote_string(ch))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Binary => write!(f, "BINARY"),
|
Binary => write!(f, "BINARY"),
|
||||||
Delimiter(char) => write!(f, "DELIMITER '{char}'"),
|
BlankAsNull => write!(f, "BLANKSASNULL"),
|
||||||
Null(string) => write!(f, "NULL '{}'", value::escape_single_quote_string(string)),
|
|
||||||
Csv(opts) => {
|
Csv(opts) => {
|
||||||
write!(f, "CSV")?;
|
write!(f, "CSV")?;
|
||||||
if !opts.is_empty() {
|
if !opts.is_empty() {
|
||||||
|
@ -8807,8 +8829,26 @@ impl fmt::Display for CopyLegacyOption {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
DateFormat(fmt) => {
|
||||||
|
write!(f, "DATEFORMAT")?;
|
||||||
|
if let Some(fmt) = fmt {
|
||||||
|
write!(f, " '{}'", value::escape_single_quote_string(fmt))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Delimiter(char) => write!(f, "DELIMITER '{char}'"),
|
||||||
|
EmptyAsNull => write!(f, "EMPTYASNULL"),
|
||||||
IamRole(role) => write!(f, "IAM_ROLE {role}"),
|
IamRole(role) => write!(f, "IAM_ROLE {role}"),
|
||||||
IgnoreHeader(num_rows) => write!(f, "IGNOREHEADER {num_rows}"),
|
IgnoreHeader(num_rows) => write!(f, "IGNOREHEADER {num_rows}"),
|
||||||
|
Null(string) => write!(f, "NULL '{}'", value::escape_single_quote_string(string)),
|
||||||
|
TimeFormat(fmt) => {
|
||||||
|
write!(f, "TIMEFORMAT")?;
|
||||||
|
if let Some(fmt) = fmt {
|
||||||
|
write!(f, " '{}'", value::escape_single_quote_string(fmt))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
TruncateColumns => write!(f, "TRUNCATECOLUMNS"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,8 @@ define_keywords!(
|
||||||
ABS,
|
ABS,
|
||||||
ABSENT,
|
ABSENT,
|
||||||
ABSOLUTE,
|
ABSOLUTE,
|
||||||
|
ACCEPTANYDATE,
|
||||||
|
ACCEPTINVCHARS,
|
||||||
ACCESS,
|
ACCESS,
|
||||||
ACCOUNT,
|
ACCOUNT,
|
||||||
ACTION,
|
ACTION,
|
||||||
|
@ -138,6 +140,7 @@ define_keywords!(
|
||||||
BIND,
|
BIND,
|
||||||
BINDING,
|
BINDING,
|
||||||
BIT,
|
BIT,
|
||||||
|
BLANKSASNULL,
|
||||||
BLOB,
|
BLOB,
|
||||||
BLOCK,
|
BLOCK,
|
||||||
BLOOM,
|
BLOOM,
|
||||||
|
@ -255,6 +258,7 @@ define_keywords!(
|
||||||
DATA_RETENTION_TIME_IN_DAYS,
|
DATA_RETENTION_TIME_IN_DAYS,
|
||||||
DATE,
|
DATE,
|
||||||
DATE32,
|
DATE32,
|
||||||
|
DATEFORMAT,
|
||||||
DATETIME,
|
DATETIME,
|
||||||
DATETIME64,
|
DATETIME64,
|
||||||
DAY,
|
DAY,
|
||||||
|
@ -314,6 +318,7 @@ define_keywords!(
|
||||||
ELSE,
|
ELSE,
|
||||||
ELSEIF,
|
ELSEIF,
|
||||||
EMPTY,
|
EMPTY,
|
||||||
|
EMPTYASNULL,
|
||||||
ENABLE,
|
ENABLE,
|
||||||
ENABLE_SCHEMA_EVOLUTION,
|
ENABLE_SCHEMA_EVOLUTION,
|
||||||
ENCODING,
|
ENCODING,
|
||||||
|
@ -933,6 +938,7 @@ define_keywords!(
|
||||||
THEN,
|
THEN,
|
||||||
TIES,
|
TIES,
|
||||||
TIME,
|
TIME,
|
||||||
|
TIMEFORMAT,
|
||||||
TIMESTAMP,
|
TIMESTAMP,
|
||||||
TIMESTAMPTZ,
|
TIMESTAMPTZ,
|
||||||
TIMESTAMP_NTZ,
|
TIMESTAMP_NTZ,
|
||||||
|
@ -961,6 +967,7 @@ define_keywords!(
|
||||||
TRIM_ARRAY,
|
TRIM_ARRAY,
|
||||||
TRUE,
|
TRUE,
|
||||||
TRUNCATE,
|
TRUNCATE,
|
||||||
|
TRUNCATECOLUMNS,
|
||||||
TRY,
|
TRY,
|
||||||
TRY_CAST,
|
TRY_CAST,
|
||||||
TRY_CONVERT,
|
TRY_CONVERT,
|
||||||
|
|
|
@ -9602,23 +9602,38 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_copy_legacy_option(&mut self) -> Result<CopyLegacyOption, ParserError> {
|
fn parse_copy_legacy_option(&mut self) -> Result<CopyLegacyOption, ParserError> {
|
||||||
|
// FORMAT \[ AS \] is optional
|
||||||
|
if self.parse_keyword(Keyword::FORMAT) {
|
||||||
|
let _ = self.parse_keyword(Keyword::AS);
|
||||||
|
}
|
||||||
|
|
||||||
let ret = match self.parse_one_of_keywords(&[
|
let ret = match self.parse_one_of_keywords(&[
|
||||||
|
Keyword::ACCEPTANYDATE,
|
||||||
|
Keyword::ACCEPTINVCHARS,
|
||||||
Keyword::BINARY,
|
Keyword::BINARY,
|
||||||
Keyword::DELIMITER,
|
Keyword::BLANKSASNULL,
|
||||||
Keyword::NULL,
|
|
||||||
Keyword::CSV,
|
Keyword::CSV,
|
||||||
|
Keyword::DATEFORMAT,
|
||||||
|
Keyword::DELIMITER,
|
||||||
|
Keyword::EMPTYASNULL,
|
||||||
Keyword::IAM_ROLE,
|
Keyword::IAM_ROLE,
|
||||||
Keyword::IGNOREHEADER,
|
Keyword::IGNOREHEADER,
|
||||||
|
Keyword::NULL,
|
||||||
|
Keyword::TIMEFORMAT,
|
||||||
|
Keyword::TRUNCATECOLUMNS,
|
||||||
]) {
|
]) {
|
||||||
|
Some(Keyword::ACCEPTANYDATE) => CopyLegacyOption::AcceptAnyDate,
|
||||||
|
Some(Keyword::ACCEPTINVCHARS) => {
|
||||||
|
let _ = self.parse_keyword(Keyword::AS); // [ AS ]
|
||||||
|
let ch = if matches!(self.peek_token().token, Token::SingleQuotedString(_)) {
|
||||||
|
Some(self.parse_literal_string()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
CopyLegacyOption::AcceptInvChars(ch)
|
||||||
|
}
|
||||||
Some(Keyword::BINARY) => CopyLegacyOption::Binary,
|
Some(Keyword::BINARY) => CopyLegacyOption::Binary,
|
||||||
Some(Keyword::DELIMITER) => {
|
Some(Keyword::BLANKSASNULL) => CopyLegacyOption::BlankAsNull,
|
||||||
let _ = self.parse_keyword(Keyword::AS); // [ AS ]
|
|
||||||
CopyLegacyOption::Delimiter(self.parse_literal_char()?)
|
|
||||||
}
|
|
||||||
Some(Keyword::NULL) => {
|
|
||||||
let _ = self.parse_keyword(Keyword::AS); // [ AS ]
|
|
||||||
CopyLegacyOption::Null(self.parse_literal_string()?)
|
|
||||||
}
|
|
||||||
Some(Keyword::CSV) => CopyLegacyOption::Csv({
|
Some(Keyword::CSV) => CopyLegacyOption::Csv({
|
||||||
let mut opts = vec![];
|
let mut opts = vec![];
|
||||||
while let Some(opt) =
|
while let Some(opt) =
|
||||||
|
@ -9628,12 +9643,40 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
opts
|
opts
|
||||||
}),
|
}),
|
||||||
|
Some(Keyword::DATEFORMAT) => {
|
||||||
|
let _ = self.parse_keyword(Keyword::AS);
|
||||||
|
let fmt = if matches!(self.peek_token().token, Token::SingleQuotedString(_)) {
|
||||||
|
Some(self.parse_literal_string()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
CopyLegacyOption::DateFormat(fmt)
|
||||||
|
}
|
||||||
|
Some(Keyword::DELIMITER) => {
|
||||||
|
let _ = self.parse_keyword(Keyword::AS);
|
||||||
|
CopyLegacyOption::Delimiter(self.parse_literal_char()?)
|
||||||
|
}
|
||||||
|
Some(Keyword::EMPTYASNULL) => CopyLegacyOption::EmptyAsNull,
|
||||||
Some(Keyword::IAM_ROLE) => CopyLegacyOption::IamRole(self.parse_iam_role_kind()?),
|
Some(Keyword::IAM_ROLE) => CopyLegacyOption::IamRole(self.parse_iam_role_kind()?),
|
||||||
Some(Keyword::IGNOREHEADER) => {
|
Some(Keyword::IGNOREHEADER) => {
|
||||||
let _ = self.parse_keyword(Keyword::AS);
|
let _ = self.parse_keyword(Keyword::AS);
|
||||||
let num_rows = self.parse_literal_uint()?;
|
let num_rows = self.parse_literal_uint()?;
|
||||||
CopyLegacyOption::IgnoreHeader(num_rows)
|
CopyLegacyOption::IgnoreHeader(num_rows)
|
||||||
}
|
}
|
||||||
|
Some(Keyword::NULL) => {
|
||||||
|
let _ = self.parse_keyword(Keyword::AS);
|
||||||
|
CopyLegacyOption::Null(self.parse_literal_string()?)
|
||||||
|
}
|
||||||
|
Some(Keyword::TIMEFORMAT) => {
|
||||||
|
let _ = self.parse_keyword(Keyword::AS);
|
||||||
|
let fmt = if matches!(self.peek_token().token, Token::SingleQuotedString(_)) {
|
||||||
|
Some(self.parse_literal_string()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
CopyLegacyOption::TimeFormat(fmt)
|
||||||
|
}
|
||||||
|
Some(Keyword::TRUNCATECOLUMNS) => CopyLegacyOption::TruncateColumns,
|
||||||
_ => self.expected("option", self.peek_token())?,
|
_ => self.expected("option", self.peek_token())?,
|
||||||
};
|
};
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
|
|
|
@ -16840,6 +16840,38 @@ fn parse_copy_options() {
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
one_statement_parses_to(
|
||||||
|
concat!(
|
||||||
|
"COPY dst (c1, c2, c3) FROM 's3://redshift-downloads/tickit/category_pipe.txt' ",
|
||||||
|
"ACCEPTANYDATE ",
|
||||||
|
"ACCEPTINVCHARS AS '*' ",
|
||||||
|
"BLANKSASNULL ",
|
||||||
|
"CSV ",
|
||||||
|
"DATEFORMAT AS 'DD-MM-YYYY' ",
|
||||||
|
"EMPTYASNULL ",
|
||||||
|
"IAM_ROLE DEFAULT ",
|
||||||
|
"IGNOREHEADER AS 1 ",
|
||||||
|
"TIMEFORMAT AS 'auto' ",
|
||||||
|
"TRUNCATECOLUMNS",
|
||||||
|
),
|
||||||
|
concat!(
|
||||||
|
"COPY dst (c1, c2, c3) FROM 's3://redshift-downloads/tickit/category_pipe.txt' ",
|
||||||
|
"ACCEPTANYDATE ",
|
||||||
|
"ACCEPTINVCHARS '*' ",
|
||||||
|
"BLANKSASNULL ",
|
||||||
|
"CSV ",
|
||||||
|
"DATEFORMAT 'DD-MM-YYYY' ",
|
||||||
|
"EMPTYASNULL ",
|
||||||
|
"IAM_ROLE DEFAULT ",
|
||||||
|
"IGNOREHEADER 1 ",
|
||||||
|
"TIMEFORMAT 'auto' ",
|
||||||
|
"TRUNCATECOLUMNS",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
one_statement_parses_to(
|
||||||
|
"COPY dst (c1, c2, c3) FROM 's3://redshift-downloads/tickit/category_pipe.txt' FORMAT AS CSV",
|
||||||
|
"COPY dst (c1, c2, c3) FROM 's3://redshift-downloads/tickit/category_pipe.txt' CSV",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue