Skip to content

Commit 75ff463

Browse files
Parse COPY FROM STDIN payload as rows
1 parent 2f3b5b8 commit 75ff463

3 files changed

Lines changed: 77 additions & 21 deletions

File tree

src/ast/mod.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3650,8 +3650,9 @@ pub enum Statement {
36503650
options: Vec<CopyOption>,
36513651
/// WITH options (before PostgreSQL version 9.0)
36523652
legacy_options: Vec<CopyLegacyOption>,
3653-
/// VALUES a vector of values to be copied
3654-
values: Vec<Option<String>>,
3653+
/// The inline payload of `COPY ... FROM STDIN`, one inner vector per
3654+
/// row, where `None` is the `\N` null field.
3655+
values: Vec<Vec<Option<String>>>,
36553656
},
36563657
/// ```sql
36573658
/// COPY INTO <table> | <location>
@@ -5349,17 +5350,19 @@ impl fmt::Display for Statement {
53495350
}
53505351
if !values.is_empty() {
53515352
writeln!(f, ";")?;
5352-
let mut delim = "";
5353-
for v in values {
5354-
write!(f, "{delim}")?;
5355-
delim = "\t";
5356-
if let Some(v) = v {
5357-
write!(f, "{v}")?;
5358-
} else {
5359-
write!(f, "\\N")?;
5353+
for row in values {
5354+
let mut delim = "";
5355+
for field in row {
5356+
write!(f, "{delim}")?;
5357+
delim = "\t";
5358+
match field {
5359+
Some(field) => write!(f, "{field}")?,
5360+
None => write!(f, "\\N")?,
5361+
}
53605362
}
5363+
writeln!(f)?;
53615364
}
5362-
write!(f, "\n\\.")?;
5365+
write!(f, "\\.")?;
53635366
}
53645367
Ok(())
53655368
}

src/parser/mod.rs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12353,29 +12353,50 @@ impl<'a> Parser<'a> {
1235312353

1235412354
/// Parse a tab separated values in
1235512355
/// COPY payload
12356-
pub fn parse_tsv(&mut self) -> Vec<Option<String>> {
12356+
pub fn parse_tsv(&mut self) -> Vec<Vec<Option<String>>> {
1235712357
self.parse_tab_value()
1235812358
}
1235912359

12360-
/// Parse a single tab-separated value row used by `COPY` payload parsing.
12361-
pub fn parse_tab_value(&mut self) -> Vec<Option<String>> {
12362-
let mut values = vec![];
12360+
/// Parse the tab separated payload rows of a `COPY ... FROM STDIN`.
12361+
///
12362+
/// A tab ends a field, a newline ends a row, and `\N` is a null field.
12363+
pub fn parse_tab_value(&mut self) -> Vec<Vec<Option<String>>> {
12364+
/// Take the field accumulated so far, `None` when it was `\N`.
12365+
fn take_field(content: &mut String, is_null: &mut bool) -> Option<String> {
12366+
if core::mem::take(is_null) {
12367+
content.clear();
12368+
None
12369+
} else {
12370+
Some(core::mem::take(content))
12371+
}
12372+
}
12373+
12374+
// The newline closing the `COPY ... ;` header opens the payload, so it
12375+
// is not a row terminator. Every later newline is.
12376+
if self.peek_token_no_skip().token == Token::Whitespace(Whitespace::Newline) {
12377+
self.next_token_no_skip();
12378+
}
12379+
12380+
let mut rows = vec![];
12381+
let mut row: Vec<Option<String>> = vec![];
1236312382
let mut content = String::new();
12383+
let mut is_null = false;
1236412384
while let Some(t) = self.next_token_no_skip().map(|t| &t.token) {
1236512385
match t {
1236612386
Token::Whitespace(Whitespace::Tab) => {
12367-
values.push(Some(core::mem::take(&mut content)));
12387+
row.push(take_field(&mut content, &mut is_null));
1236812388
}
1236912389
Token::Whitespace(Whitespace::Newline) => {
12370-
values.push(Some(core::mem::take(&mut content)));
12390+
row.push(take_field(&mut content, &mut is_null));
12391+
rows.push(core::mem::take(&mut row));
1237112392
}
1237212393
Token::Backslash => {
1237312394
if self.consume_token(&Token::Period) {
12374-
return values;
12395+
break;
1237512396
}
1237612397
if let Token::Word(w) = self.next_token().token {
1237712398
if w.value == "N" {
12378-
values.push(None);
12399+
is_null = true;
1237912400
}
1238012401
}
1238112402
}
@@ -12384,7 +12405,11 @@ impl<'a> Parser<'a> {
1238412405
}
1238512406
}
1238612407
}
12387-
values
12408+
if !row.is_empty() || !content.is_empty() || is_null {
12409+
row.push(take_field(&mut content, &mut is_null));
12410+
rows.push(row);
12411+
}
12412+
rows
1238812413
}
1238912414

1239012415
/// Parse a literal value (numbers, strings, date/time, booleans)
@@ -21337,7 +21362,6 @@ mod tests {
2133721362
unit: None
2133821363
}))
2133921364
);
21340-
2134121365
test_parse_data_type!(
2134221366
dialect,
2134321367
"CHAR(20 CHARACTERS)",

tests/sqlparser_postgres.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9663,3 +9663,32 @@ fn parse_right_deep_join_chain() {
96639663
// NATURAL JOIN followed by a constrained join must stay left-associative.
96649664
pg().verified_stmt("SELECT * FROM t0 NATURAL JOIN t1 INNER JOIN t2 ON true");
96659665
}
9666+
9667+
#[test]
9668+
fn parse_copy_from_stdin_payload_rows() {
9669+
fn payload(sql: &str) -> Vec<Vec<Option<String>>> {
9670+
let Statement::Copy { values, .. } = pg().verified_stmt(sql) else {
9671+
panic!("expected a COPY statement");
9672+
};
9673+
values
9674+
}
9675+
9676+
// Rows keep their boundaries, and `\N` is a null field rather than an extra one.
9677+
assert_eq!(
9678+
payload("COPY t (a, b) FROM STDIN;\n1\t\\N\n2\ty\n\\."),
9679+
vec![
9680+
vec![Some("1".to_string()), None],
9681+
vec![Some("2".to_string()), Some("y".to_string())],
9682+
]
9683+
);
9684+
// An empty field is not a null one.
9685+
assert_eq!(
9686+
payload("COPY t (a, b) FROM STDIN;\n\t\\N\n\\."),
9687+
vec![vec![Some(String::new()), None]]
9688+
);
9689+
// A row of empty fields is still a row.
9690+
assert_eq!(
9691+
payload("COPY t (a) FROM STDIN;\n\n\\."),
9692+
vec![vec![Some(String::new())]]
9693+
);
9694+
}

0 commit comments

Comments
 (0)