1use std::borrow::Cow;
11use std::io;
12
13use bytes::BytesMut;
14use itertools::Itertools;
15use mz_pgrepr::TextEncodeSettings;
16use mz_repr::{
17 Datum, RelationDesc, Row, RowArena, RowRef, SharedRow, SqlColumnType, SqlRelationType,
18 SqlScalarType,
19};
20use proptest::prelude::{Arbitrary, any};
21use proptest::strategy::{BoxedStrategy, Strategy};
22use serde::{Deserialize, Serialize};
23
24static END_OF_COPY_MARKER: &[u8] = b"\\.";
25
26fn encode_copy_row_binary(
27 row: &RowRef,
28 typ: &SqlRelationType,
29 out: &mut Vec<u8>,
30) -> Result<(), io::Error> {
31 const NULL_BYTES: [u8; 4] = (-1i32).to_be_bytes();
32
33 let count = i16::try_from(typ.column_types.len()).map_err(|_| {
35 io::Error::new(
36 io::ErrorKind::InvalidData,
37 "column count does not fit into an i16",
38 )
39 })?;
40
41 out.extend(count.to_be_bytes());
42 let mut buf = BytesMut::new();
43 for (field, typ) in row
44 .iter()
45 .zip_eq(&typ.column_types)
46 .map(|(datum, typ)| (mz_pgrepr::Value::from_datum(datum, &typ.scalar_type), typ))
47 {
48 match field {
49 None => out.extend(NULL_BYTES),
50 Some(field) => {
51 buf.clear();
52 field.encode_binary(&mz_pgrepr::Type::from(&typ.scalar_type), &mut buf)?;
53 out.extend(
54 i32::try_from(buf.len())
55 .map_err(|_| {
56 io::Error::new(
57 io::ErrorKind::InvalidData,
58 "field length does not fit into an i32",
59 )
60 })?
61 .to_be_bytes(),
62 );
63 out.extend(&buf);
64 }
65 }
66 }
67 Ok(())
68}
69
70fn encode_copy_row_text(
71 CopyTextFormatParams { null, delimiter }: &CopyTextFormatParams,
72 row: &RowRef,
73 typ: &SqlRelationType,
74 out: &mut Vec<u8>,
75 settings: TextEncodeSettings,
76) -> Result<(), io::Error> {
77 let null = null.as_bytes();
78 let mut buf = BytesMut::new();
79 for (idx, field) in mz_pgrepr::values_from_row(row, typ).into_iter().enumerate() {
80 if idx > 0 {
81 out.push(*delimiter);
82 }
83 match field {
84 None => out.extend(null),
85 Some(field) => {
86 buf.clear();
87 field.encode_text(&mut buf, settings);
88 for b in &buf {
89 match b {
90 b'\\' => out.extend(b"\\\\"),
91 b'\n' => out.extend(b"\\n"),
92 b'\r' => out.extend(b"\\r"),
93 b'\t' => out.extend(b"\\t"),
94 _ => out.push(*b),
95 }
96 }
97 }
98 }
99 }
100 out.push(b'\n');
101 Ok(())
102}
103
104fn encode_copy_row_csv(
105 CopyCsvFormatParams {
106 delimiter: delim,
107 quote,
108 escape,
109 header: _,
110 null,
111 }: &CopyCsvFormatParams,
112 row: &RowRef,
113 typ: &SqlRelationType,
114 out: &mut Vec<u8>,
115 settings: TextEncodeSettings,
116) -> Result<(), io::Error> {
117 let null = null.as_bytes();
118 let is_special = |c: &u8| *c == *delim || *c == *quote || *c == b'\r' || *c == b'\n';
119 let mut buf = BytesMut::new();
120 for (idx, field) in mz_pgrepr::values_from_row(row, typ).into_iter().enumerate() {
121 if idx > 0 {
122 out.push(*delim);
123 }
124 match field {
125 None => out.extend(null),
126 Some(field) => {
127 buf.clear();
128 field.encode_text(&mut buf, settings);
129 if (typ.column_types.len() == 1 && buf == END_OF_COPY_MARKER)
135 || buf.iter().any(is_special)
136 || &*buf == null
137 {
138 out.push(*quote);
142 for b in &buf {
143 if *b == *quote || *b == *escape {
144 out.push(*escape);
145 }
146 out.push(*b);
147 }
148 out.push(*quote);
149 } else {
150 out.extend(&buf);
153 }
154 }
155 }
156 }
157 out.push(b'\n');
158 Ok(())
159}
160
161pub struct CopyTextFormatParser<'a> {
162 data: &'a [u8],
163 position: usize,
164 column_delimiter: u8,
165 null_string: &'a str,
166 buffer: Vec<u8>,
167}
168
169impl<'a> CopyTextFormatParser<'a> {
170 pub fn new(data: &'a [u8], column_delimiter: u8, null_string: &'a str) -> Self {
171 Self {
172 data,
173 position: 0,
174 column_delimiter,
175 null_string,
176 buffer: Vec::new(),
177 }
178 }
179
180 fn peek(&self) -> Option<u8> {
181 if self.position < self.data.len() {
182 Some(self.data[self.position])
183 } else {
184 None
185 }
186 }
187
188 fn consume_n(&mut self, n: usize) {
189 self.position = std::cmp::min(self.position + n, self.data.len());
190 }
191
192 pub fn is_eof(&self) -> bool {
193 self.peek().is_none() || self.is_end_of_copy_marker()
194 }
195
196 pub fn is_end_of_copy_marker(&self) -> bool {
197 self.check_bytes(END_OF_COPY_MARKER)
198 }
199
200 fn is_end_of_line(&self) -> bool {
201 match self.peek() {
202 Some(b'\n') | None => true,
203 _ => false,
204 }
205 }
206
207 pub fn expect_end_of_line(&mut self) -> Result<(), io::Error> {
208 if self.is_end_of_line() {
209 self.consume_n(1);
210 Ok(())
211 } else {
212 Err(io::Error::new(
213 io::ErrorKind::InvalidData,
214 "extra data after last expected column",
215 ))
216 }
217 }
218
219 fn is_column_delimiter(&self) -> bool {
220 self.check_bytes(&[self.column_delimiter])
221 }
222
223 pub fn expect_column_delimiter(&mut self) -> Result<(), io::Error> {
224 if self.consume_bytes(&[self.column_delimiter]) {
225 Ok(())
226 } else {
227 Err(io::Error::new(
228 io::ErrorKind::InvalidData,
229 "missing data for column",
230 ))
231 }
232 }
233
234 fn check_bytes(&self, bytes: &[u8]) -> bool {
235 self.data
236 .get(self.position..self.position + bytes.len())
237 .map_or(false, |d| d == bytes)
238 }
239
240 fn consume_bytes(&mut self, bytes: &[u8]) -> bool {
241 if self.check_bytes(bytes) {
242 self.consume_n(bytes.len());
243 true
244 } else {
245 false
246 }
247 }
248
249 fn consume_null_string(&mut self) -> bool {
250 if self.null_string.is_empty() {
251 self.is_column_delimiter()
254 || self.is_end_of_line()
255 || self.is_end_of_copy_marker()
256 || self.is_eof()
257 } else {
258 self.consume_bytes(self.null_string.as_bytes())
259 }
260 }
261
262 pub fn consume_raw_value(&mut self) -> Result<Option<&[u8]>, io::Error> {
263 if self.consume_null_string() {
264 return Ok(None);
265 }
266
267 let mut start = self.position;
268
269 self.buffer.clear();
271
272 while !self.is_eof() && !self.is_end_of_copy_marker() {
273 if self.is_end_of_line() || self.is_column_delimiter() {
274 break;
275 }
276 match self.peek() {
277 Some(b'\\') => {
278 self.buffer.extend(&self.data[start..self.position]);
280
281 self.consume_n(1);
282 match self.peek() {
283 Some(b'b') => {
284 self.consume_n(1);
285 self.buffer.push(8);
286 }
287 Some(b'f') => {
288 self.consume_n(1);
289 self.buffer.push(12);
290 }
291 Some(b'n') => {
292 self.consume_n(1);
293 self.buffer.push(b'\n');
294 }
295 Some(b'r') => {
296 self.consume_n(1);
297 self.buffer.push(b'\r');
298 }
299 Some(b't') => {
300 self.consume_n(1);
301 self.buffer.push(b'\t');
302 }
303 Some(b'v') => {
304 self.consume_n(1);
305 self.buffer.push(11);
306 }
307 Some(b'x') => {
308 self.consume_n(1);
309 match self.peek() {
310 Some(_c @ b'0'..=b'9')
311 | Some(_c @ b'A'..=b'F')
312 | Some(_c @ b'a'..=b'f') => {
313 let mut value: u8 = 0;
314 let decode_nibble = |b| match b {
315 Some(c @ b'a'..=b'f') => Some(c - b'a' + 10),
316 Some(c @ b'A'..=b'F') => Some(c - b'A' + 10),
317 Some(c @ b'0'..=b'9') => Some(c - b'0'),
318 _ => None,
319 };
320 for _ in 0..2 {
321 match decode_nibble(self.peek()) {
322 Some(c) => {
323 self.consume_n(1);
324 value = (value << 4) | c;
325 }
326 _ => break,
327 }
328 }
329 self.buffer.push(value);
330 }
331 _ => {
332 self.buffer.push(b'x');
333 }
334 }
335 }
336 Some(_c @ b'0'..=b'7') => {
337 let mut value: u8 = 0;
338 for _ in 0..3 {
339 match self.peek() {
340 Some(c @ b'0'..=b'7') => {
341 self.consume_n(1);
342 value = (value << 3) | (c - b'0');
343 }
344 _ => break,
345 }
346 }
347 self.buffer.push(value);
348 }
349 Some(c) => {
350 self.consume_n(1);
351 self.buffer.push(c);
352 }
353 None => {
354 self.buffer.push(b'\\');
355 }
356 }
357
358 start = self.position;
359 }
360 Some(_) => {
361 self.consume_n(1);
362 }
363 None => {}
364 }
365 }
366
367 if self.buffer.is_empty() {
369 Ok(Some(&self.data[start..self.position]))
370 } else {
371 self.buffer.extend(&self.data[start..self.position]);
374 Ok(Some(&self.buffer[..]))
375 }
376 }
377
378 pub fn iter_raw(self, num_columns: usize) -> RawIterator<'a> {
380 RawIterator {
381 parser: self,
382 current_column: 0,
383 num_columns,
384 truncate: false,
385 }
386 }
387
388 pub fn iter_raw_truncating(self, num_columns: usize) -> RawIterator<'a> {
390 RawIterator {
391 parser: self,
392 current_column: 0,
393 num_columns,
394 truncate: true,
395 }
396 }
397}
398
399pub struct RawIterator<'a> {
400 parser: CopyTextFormatParser<'a>,
401 current_column: usize,
402 num_columns: usize,
403 truncate: bool,
404}
405
406impl<'a> RawIterator<'a> {
407 pub fn next(&mut self) -> Option<Result<Option<&[u8]>, io::Error>> {
408 if self.current_column > self.num_columns {
409 return None;
410 }
411
412 if self.current_column == self.num_columns {
413 if !self.truncate {
414 if let Some(err) = self.parser.expect_end_of_line().err() {
415 return Some(Err(err));
416 }
417 }
418
419 return None;
420 }
421
422 if self.current_column > 0 {
423 if let Some(err) = self.parser.expect_column_delimiter().err() {
424 return Some(Err(err));
425 }
426 }
427
428 self.current_column += 1;
429 Some(self.parser.consume_raw_value())
430 }
431}
432
433#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
434pub enum CopyFormatParams<'a> {
435 Text(CopyTextFormatParams<'a>),
436 Csv(CopyCsvFormatParams<'a>),
437 Binary,
438 Parquet,
439}
440
441impl CopyFormatParams<'static> {
442 pub fn file_extension(&self) -> &str {
443 match self {
444 &CopyFormatParams::Text(_) => "txt",
445 &CopyFormatParams::Csv(_) => "csv",
446 &CopyFormatParams::Binary => "bin",
447 &CopyFormatParams::Parquet => "parquet",
448 }
449 }
450
451 pub fn requires_header(&self) -> bool {
452 match self {
453 CopyFormatParams::Text(_) => false,
454 CopyFormatParams::Csv(params) => params.header,
455 CopyFormatParams::Binary => false,
456 CopyFormatParams::Parquet => false,
457 }
458 }
459}
460
461pub fn decode_copy_format<'a>(
463 data: &[u8],
464 column_types: &[mz_pgrepr::Type],
465 params: CopyFormatParams<'a>,
466) -> Result<Vec<Row>, io::Error> {
467 match params {
468 CopyFormatParams::Text(params) => decode_copy_format_text(data, column_types, params),
469 CopyFormatParams::Csv(params) => decode_copy_format_csv(data, column_types, params),
470 CopyFormatParams::Binary => Err(io::Error::new(
471 io::ErrorKind::Unsupported,
472 "cannot decode as binary format",
473 )),
474 CopyFormatParams::Parquet => {
475 Err(io::Error::new(io::ErrorKind::Unsupported, "parquet format"))
477 }
478 }
479}
480
481pub fn encode_copy_format<'a>(
487 params: &CopyFormatParams<'a>,
488 row: &RowRef,
489 typ: &SqlRelationType,
490 out: &mut Vec<u8>,
491 settings: TextEncodeSettings,
492) -> Result<(), io::Error> {
493 match params {
494 CopyFormatParams::Text(params) => encode_copy_row_text(params, row, typ, out, settings),
495 CopyFormatParams::Csv(params) => encode_copy_row_csv(params, row, typ, out, settings),
496 CopyFormatParams::Binary => encode_copy_row_binary(row, typ, out),
497 CopyFormatParams::Parquet => {
498 Err(io::Error::new(io::ErrorKind::Unsupported, "parquet format"))
500 }
501 }
502}
503
504pub fn encode_copy_format_header<'a>(
505 params: &CopyFormatParams<'a>,
506 desc: &RelationDesc,
507 out: &mut Vec<u8>,
508) -> Result<(), io::Error> {
509 match params {
510 CopyFormatParams::Text(_) => Ok(()),
511 CopyFormatParams::Binary => Ok(()),
512 CopyFormatParams::Csv(params) => {
513 let mut header_row = Row::with_capacity(desc.arity());
514 header_row
515 .packer()
516 .extend(desc.iter_names().map(|s| Datum::from(s.as_str())));
517 let typ = SqlRelationType::new(vec![
518 SqlColumnType {
519 scalar_type: SqlScalarType::String,
520 nullable: false,
521 };
522 desc.arity()
523 ]);
524 encode_copy_row_csv(params, &header_row, &typ, out, TextEncodeSettings::STABLE)
525 }
526 CopyFormatParams::Parquet => {
527 Err(io::Error::new(io::ErrorKind::Unsupported, "parquet format"))
529 }
530 }
531}
532
533#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
534pub struct CopyTextFormatParams<'a> {
535 pub null: Cow<'a, str>,
536 pub delimiter: u8,
537}
538
539impl<'a> Default for CopyTextFormatParams<'a> {
540 fn default() -> Self {
541 CopyTextFormatParams {
542 delimiter: b'\t',
543 null: Cow::from("\\N"),
544 }
545 }
546}
547
548pub fn decode_copy_format_text(
549 data: &[u8],
550 column_types: &[mz_pgrepr::Type],
551 CopyTextFormatParams { null, delimiter }: CopyTextFormatParams,
552) -> Result<Vec<Row>, io::Error> {
553 let mut rows = Vec::new();
554
555 let mut parser = CopyTextFormatParser::new(data, delimiter, &null);
557 while !parser.is_eof() && !parser.is_end_of_copy_marker() {
558 let mut row = Vec::new();
559 let buf = RowArena::new();
560 for (col, typ) in column_types.iter().enumerate() {
561 if col > 0 {
562 parser.expect_column_delimiter()?;
563 }
564 let raw_value = parser.consume_raw_value()?;
565 if let Some(raw_value) = raw_value {
566 match mz_pgrepr::Value::decode_text(typ, raw_value) {
567 Ok(value) => {
568 row.push(
569 value
570 .into_datum_decode_error(&buf, typ, "column")
571 .map_err(|msg| io::Error::new(io::ErrorKind::InvalidData, msg))?,
572 );
573 }
574 Err(err) => {
575 let msg = format!("unable to decode column: {}", err);
576 return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
577 }
578 }
579 } else {
580 row.push(Datum::Null);
581 }
582 }
583 parser.expect_end_of_line()?;
584 rows.push(Row::pack(row));
585 }
586 Ok(rows)
589}
590
591#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
592pub struct CopyCsvFormatParams<'a> {
593 pub delimiter: u8,
594 pub quote: u8,
595 pub escape: u8,
596 pub header: bool,
597 pub null: Cow<'a, str>,
598}
599
600impl<'a> CopyCsvFormatParams<'a> {
601 pub fn to_owned(&self) -> CopyCsvFormatParams<'static> {
602 CopyCsvFormatParams {
603 delimiter: self.delimiter,
604 quote: self.quote,
605 escape: self.escape,
606 header: self.header,
607 null: Cow::Owned(self.null.to_string()),
608 }
609 }
610}
611
612impl Arbitrary for CopyCsvFormatParams<'static> {
613 type Parameters = ();
614 type Strategy = BoxedStrategy<Self>;
615
616 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
617 (
618 any::<u8>(),
619 any::<u8>(),
620 any::<u8>(),
621 any::<bool>(),
622 any::<String>(),
623 )
624 .prop_map(|(delimiter, diff, escape, header, null)| {
625 let diff = diff.saturating_sub(1).max(1);
627 let quote = delimiter.wrapping_add(diff);
628
629 Self::try_new(
630 Some(delimiter),
631 Some(quote),
632 Some(escape),
633 Some(header),
634 Some(null),
635 )
636 .expect("delimiter and quote should be different")
637 })
638 .boxed()
639 }
640}
641
642impl<'a> Default for CopyCsvFormatParams<'a> {
643 fn default() -> Self {
644 CopyCsvFormatParams {
645 delimiter: b',',
646 quote: b'"',
647 escape: b'"',
648 header: false,
649 null: Cow::from(""),
650 }
651 }
652}
653
654impl<'a> CopyCsvFormatParams<'a> {
655 pub fn try_new(
656 delimiter: Option<u8>,
657 quote: Option<u8>,
658 escape: Option<u8>,
659 header: Option<bool>,
660 null: Option<String>,
661 ) -> Result<CopyCsvFormatParams<'a>, String> {
662 let mut params = CopyCsvFormatParams::default();
663
664 if let Some(delimiter) = delimiter {
665 params.delimiter = delimiter;
666 }
667 if let Some(quote) = quote {
668 params.quote = quote;
669 params.escape = quote;
671 }
672 if let Some(escape) = escape {
673 params.escape = escape;
674 }
675 if let Some(header) = header {
676 params.header = header;
677 }
678 if let Some(null) = null {
679 params.null = Cow::from(null);
680 }
681
682 if params.quote == params.delimiter {
683 return Err("COPY delimiter and quote must be different".to_string());
684 }
685 Ok(params)
686 }
687}
688
689struct DecodedField {
697 start: usize,
698 end: usize,
699 quoted: bool,
700}
701
702pub fn decode_copy_format_csv(
703 data: &[u8],
704 column_types: &[mz_pgrepr::Type],
705 CopyCsvFormatParams {
706 delimiter,
707 quote,
708 escape,
709 null,
710 header,
711 }: CopyCsvFormatParams,
712) -> Result<Vec<Row>, io::Error> {
713 let (double_quote, escape) = if quote == escape {
714 (true, None)
715 } else {
716 (false, Some(escape))
717 };
718
719 let mut rdr = csv_core::ReaderBuilder::new()
720 .delimiter(delimiter)
721 .quote(quote)
722 .escape(escape)
723 .double_quote(double_quote)
724 .build();
725
726 let null_as_bytes = null.as_bytes();
727 let mut rows = Vec::new();
728 let mut input = data;
735 let mut output = vec![0u8; data.len().max(1024)];
736 let mut out_pos = 0;
737 let mut fields: Vec<DecodedField> = Vec::new();
738 let mut field_start = 0;
739 let mut field_quoted: Option<bool> = None;
740 let mut skip_header = header;
741 let mut at_record_start = true;
745
746 loop {
747 if field_quoted.is_none() {
748 if at_record_start {
749 while matches!(input.first(), Some(&b'\r') | Some(&b'\n')) {
762 input = &input[1..];
763 }
764 }
765 field_quoted = Some(input.first() == Some("e));
766 field_start = out_pos;
767 }
768
769 let (result, nin, nout) = rdr.read_field(input, &mut output[out_pos..]);
770 input = &input[nin..];
771 out_pos += nout;
772
773 match result {
774 csv_core::ReadFieldResult::Field { record_end } => {
775 fields.push(DecodedField {
776 start: field_start,
777 end: out_pos,
778 quoted: field_quoted.take().unwrap(),
779 });
780 at_record_start = record_end;
783
784 if record_end {
785 if skip_header {
786 skip_header = false;
787 } else if let [
788 DecodedField {
789 start,
790 end,
791 quoted: false,
792 },
793 ] = fields[..]
794 && &output[start..end] == END_OF_COPY_MARKER
795 {
796 return Ok(rows);
800 } else {
801 match fields.len().cmp(&column_types.len()) {
802 std::cmp::Ordering::Less => {
803 return Err(io::Error::new(
804 io::ErrorKind::InvalidData,
805 "missing data for column",
806 ));
807 }
808 std::cmp::Ordering::Greater => {
809 return Err(io::Error::new(
810 io::ErrorKind::InvalidData,
811 "extra data after last expected column",
812 ));
813 }
814 std::cmp::Ordering::Equal => {}
815 }
816
817 let mut row_builder = SharedRow::get();
818 let mut row_packer = row_builder.packer();
819 for (typ, field) in column_types.iter().zip_eq(fields.iter()) {
820 let raw_value = &output[field.start..field.end];
821 if !field.quoted && raw_value == null_as_bytes {
822 row_packer.push(Datum::Null);
823 } else {
824 let s = match std::str::from_utf8(raw_value) {
825 Ok(s) => s,
826 Err(err) => {
827 let msg = format!("invalid utf8 data in column: {}", err);
828 return Err(io::Error::new(
829 io::ErrorKind::InvalidData,
830 msg,
831 ));
832 }
833 };
834 if let Err(err) =
835 mz_pgrepr::Value::decode_text_into_row(typ, s, &mut row_packer)
836 {
837 let msg = format!("unable to decode column: {}", err);
838 return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
839 }
840 }
841 }
842 rows.push(row_builder.clone());
843 }
844 fields.clear();
845 out_pos = 0;
846 }
847 }
848 csv_core::ReadFieldResult::OutputFull => {
849 let new_len = output.len().saturating_mul(2).max(out_pos + 1);
850 output.resize(new_len, 0);
851 }
852 csv_core::ReadFieldResult::InputEmpty => {
853 }
859 csv_core::ReadFieldResult::End => return Ok(rows),
860 }
861 }
862}
863
864#[cfg(test)]
865mod tests {
866 use mz_ore::collections::CollectionExt;
867 use mz_repr::SqlColumnType;
868 use proptest::prelude::*;
869
870 use super::*;
871
872 #[mz_ore::test]
873 fn test_copy_format_text_parser() {
874 let text = "\t\\nt e\t\\N\t\n\\x60\\xA\\x7D\\x4a\n\\44\\044\\123".as_bytes();
875 let mut parser = CopyTextFormatParser::new(text, b'\t', "\\N");
876 assert!(parser.is_column_delimiter());
877 parser
878 .expect_column_delimiter()
879 .expect("expected column delimiter");
880 assert_eq!(
881 parser
882 .consume_raw_value()
883 .expect("unexpected error")
884 .expect("unexpected empty result"),
885 "\nt e".as_bytes()
886 );
887 parser
888 .expect_column_delimiter()
889 .expect("expected column delimiter");
890 assert!(
892 parser
893 .consume_raw_value()
894 .expect("unexpected error")
895 .is_none()
896 );
897 parser
898 .expect_column_delimiter()
899 .expect("expected column delimiter");
900 assert!(parser.is_end_of_line());
901 parser.expect_end_of_line().expect("expected eol");
902 assert_eq!(
904 parser
905 .consume_raw_value()
906 .expect("unexpected error")
907 .expect("unexpected empty result"),
908 "`\n}J".as_bytes()
909 );
910 parser.expect_end_of_line().expect("expected eol");
911 assert_eq!(
913 parser
914 .consume_raw_value()
915 .expect("unexpected error")
916 .expect("unexpected empty result"),
917 "$$S".as_bytes()
918 );
919 assert!(parser.is_eof());
920 }
921
922 #[mz_ore::test]
923 fn test_copy_format_text_empty_null_string() {
924 let text = "\t\n10\t20\n30\t\n40\t".as_bytes();
925 let expect = vec![
926 vec![None, None],
927 vec![Some("10"), Some("20")],
928 vec![Some("30"), None],
929 vec![Some("40"), None],
930 ];
931 let mut parser = CopyTextFormatParser::new(text, b'\t', "");
932 for line in expect {
933 for (i, value) in line.iter().enumerate() {
934 if i > 0 {
935 parser
936 .expect_column_delimiter()
937 .expect("expected column delimiter");
938 }
939 match value {
940 Some(s) => {
941 assert!(!parser.consume_null_string());
942 assert_eq!(
943 parser
944 .consume_raw_value()
945 .expect("unexpected error")
946 .expect("unexpected empty result"),
947 s.as_bytes()
948 );
949 }
950 None => {
951 assert!(parser.consume_null_string());
952 }
953 }
954 }
955 parser.expect_end_of_line().expect("expected eol");
956 }
957 }
958
959 #[mz_ore::test]
960 fn test_copy_format_text_parser_escapes() {
961 struct TestCase {
962 input: &'static str,
963 expect: &'static [u8],
964 }
965 let tests = vec![
966 TestCase {
967 input: "simple",
968 expect: b"simple",
969 },
970 TestCase {
971 input: r#"new\nline"#,
972 expect: b"new\nline",
973 },
974 TestCase {
975 input: r#"\b\f\n\r\t\v\\"#,
976 expect: b"\x08\x0c\n\r\t\x0b\\",
977 },
978 TestCase {
979 input: r#"\0\12\123"#,
980 expect: &[0, 0o12, 0o123],
981 },
982 TestCase {
983 input: r#"\x1\xaf"#,
984 expect: &[0x01, 0xaf],
985 },
986 TestCase {
987 input: r#"T\n\07\xEV\x0fA\xb2C\1"#,
988 expect: b"T\n\x07\x0eV\x0fA\xb2C\x01",
989 },
990 TestCase {
991 input: r#"\\\""#,
992 expect: b"\\\"",
993 },
994 TestCase {
995 input: r#"\x"#,
996 expect: b"x",
997 },
998 TestCase {
999 input: r#"\xg"#,
1000 expect: b"xg",
1001 },
1002 TestCase {
1003 input: r#"\"#,
1004 expect: b"\\",
1005 },
1006 TestCase {
1007 input: r#"\8"#,
1008 expect: b"8",
1009 },
1010 TestCase {
1011 input: r#"\a"#,
1012 expect: b"a",
1013 },
1014 TestCase {
1015 input: r#"\x\xg\8\xH\x32\s\"#,
1016 expect: b"xxg8xH2s\\",
1017 },
1018 ];
1019
1020 for test in tests {
1021 let mut parser = CopyTextFormatParser::new(test.input.as_bytes(), b'\t', "\\N");
1022 assert_eq!(
1023 parser
1024 .consume_raw_value()
1025 .expect("unexpected error")
1026 .expect("unexpected empty result"),
1027 test.expect,
1028 "input: {}, expect: {:?}",
1029 test.input,
1030 std::str::from_utf8(test.expect),
1031 );
1032 assert!(parser.is_eof());
1033 }
1034 }
1035
1036 #[mz_ore::test]
1037 fn test_copy_csv_format_params() {
1038 assert_eq!(
1039 CopyCsvFormatParams::try_new(Some(b't'), Some(b'q'), None, None, None),
1040 Ok(CopyCsvFormatParams {
1041 delimiter: b't',
1042 quote: b'q',
1043 escape: b'q',
1044 header: false,
1045 null: Cow::from(""),
1046 })
1047 );
1048
1049 assert_eq!(
1050 CopyCsvFormatParams::try_new(
1051 Some(b't'),
1052 Some(b'q'),
1053 Some(b'e'),
1054 Some(true),
1055 Some("null".to_string())
1056 ),
1057 Ok(CopyCsvFormatParams {
1058 delimiter: b't',
1059 quote: b'q',
1060 escape: b'e',
1061 header: true,
1062 null: Cow::from("null"),
1063 })
1064 );
1065
1066 assert_eq!(
1067 CopyCsvFormatParams::try_new(
1068 None,
1069 Some(b','),
1070 Some(b'e'),
1071 Some(true),
1072 Some("null".to_string())
1073 ),
1074 Err("COPY delimiter and quote must be different".to_string())
1075 );
1076 }
1077
1078 #[mz_ore::test]
1079 fn test_copy_csv_row() -> Result<(), io::Error> {
1080 let mut row = Row::default();
1081 let mut packer = row.packer();
1082 packer.push(Datum::from("1,2,\"3\""));
1083 packer.push(Datum::Null);
1084 packer.push(Datum::from(1000u64));
1085 packer.push(Datum::from("qe")); packer.push(Datum::from(""));
1087
1088 let typ: SqlRelationType = SqlRelationType::new(vec![
1089 SqlColumnType {
1090 scalar_type: mz_repr::SqlScalarType::String,
1091 nullable: false,
1092 },
1093 SqlColumnType {
1094 scalar_type: mz_repr::SqlScalarType::String,
1095 nullable: true,
1096 },
1097 SqlColumnType {
1098 scalar_type: mz_repr::SqlScalarType::UInt64,
1099 nullable: false,
1100 },
1101 SqlColumnType {
1102 scalar_type: mz_repr::SqlScalarType::String,
1103 nullable: false,
1104 },
1105 SqlColumnType {
1106 scalar_type: mz_repr::SqlScalarType::String,
1107 nullable: false,
1108 },
1109 ]);
1110
1111 let mut out = Vec::new();
1112
1113 struct TestCase<'a> {
1114 params: CopyCsvFormatParams<'a>,
1115 expected: &'static [u8],
1116 }
1117
1118 let tests = [
1119 TestCase {
1120 params: CopyCsvFormatParams::default(),
1121 expected: b"\"1,2,\"\"3\"\"\",,1000,qe,\"\"\n",
1122 },
1123 TestCase {
1124 params: CopyCsvFormatParams {
1125 null: Cow::from("NULL"),
1126 quote: b'q',
1127 escape: b'e',
1128 ..Default::default()
1129 },
1130 expected: b"q1,2,\"3\"q,NULL,1000,qeqeeq,\n",
1131 },
1132 ];
1133
1134 for TestCase { params, expected } in tests {
1135 out.clear();
1136 let params = CopyFormatParams::Csv(params);
1137 let _ = encode_copy_format(¶ms, &row, &typ, &mut out, TextEncodeSettings::STABLE);
1138 let output = std::str::from_utf8(&out);
1139 assert_eq!(output, std::str::from_utf8(expected));
1140 }
1141
1142 Ok(())
1143 }
1144
1145 #[mz_ore::test]
1146 fn test_decode_copy_format_csv_end_marker() {
1147 let column_types = vec![mz_pgrepr::Type::from(&mz_repr::SqlScalarType::String)];
1153
1154 let decode_strings = |input: &[u8]| -> Vec<String> {
1155 decode_copy_format_csv(input, &column_types, CopyCsvFormatParams::default())
1156 .expect("decode should succeed")
1157 .iter()
1158 .map(|r| match r.iter().next().unwrap() {
1159 Datum::String(s) => s.to_owned(),
1160 d => panic!("unexpected datum: {:?}", d),
1161 })
1162 .collect()
1163 };
1164
1165 for eol in [&b"\n"[..], b"\r\n", b"\r"] {
1166 let join = |lines: &[&str]| -> Vec<u8> {
1167 let mut out = Vec::new();
1168 for line in lines {
1169 out.extend_from_slice(line.as_bytes());
1170 out.extend_from_slice(eol);
1171 }
1172 out
1173 };
1174
1175 assert_eq!(
1177 decode_strings(&join(&["before", "\"\\.\"", "after"])),
1178 vec!["before", "\\.", "after"],
1179 "quoted marker, eol={eol:?}"
1180 );
1181
1182 assert_eq!(
1184 decode_strings(&join(&["first", "\\.", "ignored"])),
1185 vec!["first"],
1186 "bare marker, eol={eol:?}"
1187 );
1188 }
1189 }
1190
1191 #[mz_ore::test]
1192 fn test_decode_copy_format_csv_leading_orphan() {
1193 let column_types = vec![
1199 mz_pgrepr::Type::from(&mz_repr::SqlScalarType::String),
1200 mz_pgrepr::Type::from(&mz_repr::SqlScalarType::String),
1201 ];
1202
1203 let rows = decode_copy_format_csv(
1204 b"\n\"\",x\r\ny,z\r\n",
1205 &column_types,
1206 CopyCsvFormatParams::default(),
1207 )
1208 .expect("decode should succeed");
1209 let got: Vec<Vec<Datum>> = rows.iter().map(|r| r.iter().collect()).collect();
1210 assert_eq!(got.len(), 2);
1211 assert_eq!(got[0][0], Datum::String(""));
1215 assert_eq!(got[0][1], Datum::String("x"));
1216 assert_eq!(got[1][0], Datum::String("y"));
1217 assert_eq!(got[1][1], Datum::String("z"));
1218 }
1219
1220 #[mz_ore::test]
1221 fn test_decode_copy_format_csv_quoted_null() {
1222 let column_types = vec![
1225 mz_pgrepr::Type::from(&mz_repr::SqlScalarType::String),
1226 mz_pgrepr::Type::from(&mz_repr::SqlScalarType::String),
1227 ];
1228
1229 let cases: &[(CopyCsvFormatParams, &[(&str, &str)], &[[Option<&str>; 2]])] = &[
1234 (
1236 CopyCsvFormatParams::default(),
1237 &[("a", ""), ("b", "\"\""), ("\"\"", "c")],
1238 &[
1239 [Some("a"), None],
1240 [Some("b"), Some("")],
1241 [Some(""), Some("c")],
1242 ],
1243 ),
1244 (
1246 CopyCsvFormatParams {
1247 null: Cow::from("NULL"),
1248 ..Default::default()
1249 },
1250 &[("a", "NULL"), ("b", "\"NULL\""), ("NULL", "c")],
1251 &[
1252 [Some("a"), None],
1253 [Some("b"), Some("NULL")],
1254 [None, Some("c")],
1255 ],
1256 ),
1257 ];
1258
1259 for eol in [&b"\n"[..], b"\r\n", b"\r"] {
1260 for (params, lines, expected) in cases {
1261 let mut input = Vec::new();
1262 for (a, b) in *lines {
1263 input.extend_from_slice(a.as_bytes());
1264 input.push(b',');
1265 input.extend_from_slice(b.as_bytes());
1266 input.extend_from_slice(eol);
1267 }
1268 let rows = decode_copy_format_csv(&input, &column_types, params.clone())
1269 .expect("decode should succeed");
1270 assert_eq!(rows.len(), expected.len(), "eol={eol:?}");
1271 for (row, want) in rows.iter().zip_eq(expected.iter()) {
1272 let got: Vec<Datum> = row.iter().collect();
1273 assert_eq!(got.len(), 2);
1274 for (g, w) in got.iter().zip_eq(want.iter()) {
1275 match (g, w) {
1276 (Datum::Null, None) => {}
1277 (Datum::String(s), Some(w)) => assert_eq!(s, w, "eol={eol:?}"),
1278 _ => panic!("mismatch: got {g:?}, want {w:?}, eol={eol:?}"),
1279 }
1280 }
1281 }
1282 }
1283 }
1284 }
1285
1286 proptest! {
1287 #[mz_ore::test]
1288 #[cfg_attr(miri, ignore)]
1289 fn proptest_csv_roundtrips(copy_csv_params: CopyCsvFormatParams) {
1290 let try_roundtrip_datum = |scalar_type: &SqlScalarType, datum| {
1292 let row = Row::pack_slice(&[datum]);
1293 let typ = SqlRelationType::new(vec![
1294 SqlColumnType {
1295 scalar_type: scalar_type.clone(),
1296 nullable: true,
1297 }
1298 ]);
1299
1300 let mut buf = Vec::new();
1301 let mut csv_params = copy_csv_params.clone();
1302 csv_params.header = false;
1304 let params = CopyFormatParams::Csv(csv_params);
1305
1306 encode_copy_format(¶ms, &row, &typ, &mut buf, TextEncodeSettings::STABLE)?;
1308 let column_types = typ
1309 .column_types
1310 .iter()
1311 .map(|x| &x.scalar_type)
1312 .map(mz_pgrepr::Type::from)
1313 .collect::<Vec<mz_pgrepr::Type>>();
1314 let result = decode_copy_format(&buf, &column_types, params);
1315
1316 match result {
1317 Ok(rows) => {
1318 let out_str = std::str::from_utf8(&buf[..]);
1319
1320 prop_assert_eq!(
1321 rows.len(),
1322 1,
1323 "unexpected number of rows! {:?}, csv string: {:?}", rows, out_str
1324 );
1325 let output = rows.into_element();
1326
1327 prop_assert_eq!(
1328 row,
1329 output,
1330 "csv string: {:?}, scalar_type: {:?}", out_str, scalar_type
1331 );
1332 }
1333 _ => {
1334 }
1336 }
1337
1338 Ok(())
1339 };
1340
1341 for scalar_type in SqlScalarType::enumerate() {
1343 for datum in scalar_type.interesting_datums() {
1344 if let Some(value) = mz_pgrepr::Value::from_datum(datum, scalar_type) {
1346 let mut buf = bytes::BytesMut::new();
1347 value.encode_text(&mut buf, TextEncodeSettings::STABLE);
1348
1349 if let Ok(datum_str) = std::str::from_utf8(&buf[..]) {
1350 if datum_str == copy_csv_params.null {
1351 continue;
1352 }
1353 }
1354 }
1355
1356 let updated_datum = match datum {
1357 Datum::Timestamp(_) | Datum::TimestampTz(_) | Datum::Null => {
1359 continue;
1360 }
1361 Datum::String(s) => {
1362 if s.trim() == copy_csv_params.null || s.trim().is_empty() {
1364 continue;
1365 } else {
1366 Datum::String(s)
1367 }
1368 }
1369 other => other,
1370 };
1371
1372 let result = try_roundtrip_datum(scalar_type, updated_datum);
1373 prop_assert!(result.is_ok(), "failure: {result:?}");
1374 }
1375 }
1376 }
1377 }
1378}