Skip to content

Commit b862dc7

Browse files
Added handling of CSVs in COPY STDIN
1 parent 1b8d716 commit b862dc7

File tree

6 files changed

+339
-171
lines changed

6 files changed

+339
-171
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ visitor = ["sqlparser_derive"]
4747
[dependencies]
4848
bigdecimal = { version = "0.4.1", features = ["serde"], optional = true }
4949
log = "0.4"
50+
csv = "1.4.0"
5051
recursive = { version = "0.1.1", optional = true}
5152

5253
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"], optional = true }

src/ast/mod.rs

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3227,7 +3227,7 @@ pub enum Statement {
32273227
/// WITH options (before PostgreSQL version 9.0)
32283228
legacy_options: Vec<CopyLegacyOption>,
32293229
/// VALUES a vector of values to be copied
3230-
values: Vec<Option<String>>,
3230+
values: Vec<Vec<Option<String>>>,
32313231
},
32323232
/// ```sql
32333233
/// COPY INTO <table> | <location>
@@ -4579,18 +4579,76 @@ impl fmt::Display for Statement {
45794579
if !legacy_options.is_empty() {
45804580
write!(f, " {}", display_separated(legacy_options, " "))?;
45814581
}
4582+
4583+
let mut null_symbol = "\\N";
4584+
let mut writer_builder = csv::WriterBuilder::new();
4585+
4586+
// Apply options
4587+
for option in options {
4588+
match option {
4589+
CopyOption::Delimiter(c) => {
4590+
writer_builder.delimiter(*c as u8);
4591+
}
4592+
CopyOption::Quote(c) => {
4593+
writer_builder.quote(*c as u8);
4594+
}
4595+
CopyOption::Escape(c) => {
4596+
writer_builder.escape(*c as u8);
4597+
}
4598+
CopyOption::Null(null) => {
4599+
null_symbol = null;
4600+
}
4601+
_ => {}
4602+
}
4603+
}
4604+
4605+
// Apply legacy options
4606+
for option in legacy_options {
4607+
match option {
4608+
CopyLegacyOption::Delimiter(c) => {
4609+
writer_builder.delimiter(*c as u8);
4610+
}
4611+
CopyLegacyOption::Header => {
4612+
writer_builder.has_headers(true);
4613+
}
4614+
CopyLegacyOption::Null(null) => {
4615+
null_symbol = null;
4616+
}
4617+
CopyLegacyOption::Csv(csv_options) => {
4618+
for csv_option in csv_options {
4619+
match csv_option {
4620+
CopyLegacyCsvOption::Header => {
4621+
writer_builder.has_headers(true);
4622+
}
4623+
CopyLegacyCsvOption::Quote(c) => {
4624+
writer_builder.quote(*c as u8);
4625+
}
4626+
CopyLegacyCsvOption::Escape(c) => {
4627+
writer_builder.escape(*c as u8);
4628+
}
4629+
_ => {}
4630+
}
4631+
}
4632+
}
4633+
_ => {}
4634+
}
4635+
}
4636+
45824637
if !values.is_empty() {
45834638
writeln!(f, ";")?;
4584-
let mut delim = "";
4585-
for v in values {
4586-
write!(f, "{delim}")?;
4587-
delim = "\t";
4588-
if let Some(v) = v {
4589-
write!(f, "{v}")?;
4590-
} else {
4591-
write!(f, "\\N")?;
4592-
}
4639+
let mut writer = writer_builder.from_writer(vec![]);
4640+
for row in values {
4641+
writer
4642+
.write_record(
4643+
row.iter()
4644+
.map(|column| column.as_deref().unwrap_or(null_symbol)),
4645+
)
4646+
.map_err(|_| fmt::Error)?
45934647
}
4648+
writer.flush().map_err(|_| fmt::Error)?;
4649+
let data = String::from_utf8(writer.into_inner().map_err(|_| fmt::Error)?)
4650+
.map_err(|_| fmt::Error)?;
4651+
write!(f, "{}", data)?;
45944652
write!(f, "\n\\.")?;
45954653
}
45964654
Ok(())

src/dialect/bigquery.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ impl Dialect for BigQueryDialect {
8383
}
8484

8585
fn is_identifier_part(&self, ch: char) -> bool {
86-
ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_'
86+
ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_' || ch == '-'
8787
}
8888

8989
/// See [doc](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals)

src/dialect/snowflake.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,9 +1049,9 @@ pub fn parse_create_stage(
10491049

10501050
pub fn parse_stage_name_identifier(parser: &mut Parser) -> Result<Ident, ParserError> {
10511051
let mut ident = String::new();
1052-
while let Some(next_token) = parser.next_token_no_skip() {
1053-
match &next_token.token {
1054-
Token::SemiColon => break,
1052+
loop {
1053+
match &parser.next_token().token {
1054+
Token::SemiColon | Token::EOF => break,
10551055
Token::Period => {
10561056
parser.prev_token();
10571057
break;

0 commit comments

Comments
 (0)