1use crate::protocol::{BackendMessage, FrontendMessage, TransactionStatus};
23use fallible_iterator::FallibleIterator;
24
25use crate::connection::{Connection, ConnectionState};
26use crate::error::{Error, PgError, PgServerError, Result};
27
28#[cfg(feature = "tracing")]
29use crate::tracing_ext::{truncate_str, TARGET_COPY};
30
31mod binary;
32
33pub use binary::BinaryCopyWriter;
34
35fn parse_csv_line(line: &str, delimiter: char, quote: char) -> Vec<String> {
44 let mut fields = Vec::new();
45 let mut current = String::new();
46 let mut in_quotes = false;
47 let mut chars = line.chars().peekable();
48
49 while let Some(ch) = chars.next() {
50 if in_quotes {
51 if ch == quote {
52 if chars.peek() == Some("e) {
54 chars.next(); current.push(quote);
56 } else {
57 in_quotes = false;
59 }
60 } else {
61 current.push(ch);
62 }
63 } else if ch == delimiter {
64 fields.push(std::mem::take(&mut current));
65 } else if ch == quote {
66 in_quotes = true;
67 } else {
68 current.push(ch);
69 }
70 }
71
72 fields.push(current);
73 fields
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Default)]
82#[non_exhaustive]
83pub enum CopyFormat {
84 #[default]
86 Text,
87 Csv {
89 delimiter: char,
91 null: String,
93 header: bool,
95 quote: char,
97 escape: char,
99 },
100 Binary,
102}
103
104impl CopyFormat {
105 pub fn to_sql_options(&self) -> String {
107 match self {
108 CopyFormat::Text => String::new(),
109 CopyFormat::Csv {
110 delimiter,
111 null,
112 header,
113 quote,
114 escape,
115 } => {
116 format!(
117 "WITH (FORMAT csv, DELIMITER '{}', NULL '{}', HEADER {}, QUOTE '{}', ESCAPE '{}')",
118 delimiter, null, header, quote, escape
119 )
120 }
121 CopyFormat::Binary => "WITH (FORMAT binary)".to_string(),
122 }
123 }
124}
125
126#[non_exhaustive]
136pub struct CopyIn<'a> {
137 conn: &'a mut Connection,
138 format: u8,
139 column_formats: Vec<u16>,
140 done: bool,
141}
142
143impl<'a> CopyIn<'a> {
144 pub fn format(&self) -> u8 {
148 self.format
149 }
150
151 pub fn column_formats(&self) -> &[u16] {
153 &self.column_formats
154 }
155
156 #[must_use = "copy write errors should be checked"]
158 pub async fn write(&mut self, data: &[u8]) -> Result<()> {
159 #[cfg(feature = "tracing")]
160 tracing::trace!(target: TARGET_COPY, chunk_len = data.len(), "COPY IN: writing data chunk");
161 self.conn
162 .codec
163 .send(
164 &mut self.conn.transport,
165 &FrontendMessage::CopyData {
166 data: data.to_vec(),
167 },
168 )
169 .await
170 .map_err(Error::from)
171 }
172
173 #[must_use = "copy write errors should be checked"]
177 pub async fn write_row(&mut self, columns: &[&str]) -> Result<()> {
178 let line = columns.join("\t") + "\n";
179 self.write(line.as_bytes()).await
180 }
181
182 #[must_use = "copy write errors should be checked"]
192 pub async fn write_csv_row(
193 &mut self,
194 columns: &[&str],
195 delimiter: char,
196 quote: char,
197 ) -> Result<()> {
198 let mut line = String::new();
199 for (i, col) in columns.iter().enumerate() {
200 if i > 0 {
201 line.push(delimiter);
202 }
203 let needs_quote = col.contains(delimiter)
204 || col.contains(quote)
205 || col.contains('\n')
206 || col.contains('\r');
207 if needs_quote {
208 line.push(quote);
209 for ch in col.chars() {
210 if ch == quote {
211 line.push(quote);
212 }
213 line.push(ch);
214 }
215 line.push(quote);
216 } else {
217 line.push_str(col);
218 }
219 }
220 line.push('\n');
221 self.write(line.as_bytes()).await
222 }
223
224 #[must_use = "copy write errors should be checked"]
240 pub async fn write_csv_row_with_null(
241 &mut self,
242 columns: &[Option<&str>],
243 delimiter: char,
244 quote: char,
245 null_string: &str,
246 ) -> Result<()> {
247 let mut line = String::new();
248 for (i, col) in columns.iter().enumerate() {
249 if i > 0 {
250 line.push(delimiter);
251 }
252 match col {
253 Some(val) => {
254 let needs_quote = val.contains(delimiter)
255 || val.contains(quote)
256 || val.contains('\n')
257 || val.contains('\r');
258 if needs_quote {
259 line.push(quote);
260 for ch in val.chars() {
261 if ch == quote {
262 line.push(quote);
263 }
264 line.push(ch);
265 }
266 line.push(quote);
267 } else {
268 line.push_str(val);
269 }
270 }
271 None => {
272 line.push_str(null_string);
274 }
275 }
276 }
277 line.push('\n');
278 self.write(line.as_bytes()).await
279 }
280
281 #[must_use = "copy finish errors should be checked"]
286 pub async fn finish(mut self) -> Result<u64> {
287 self.conn
288 .codec
289 .send(&mut self.conn.transport, &FrontendMessage::CopyDone)
290 .await
291 .map_err(Error::from)?;
292
293 let mut rows = 0u64;
294 loop {
295 let msg = self
296 .conn
297 .codec
298 .read_message(&mut self.conn.transport)
299 .await?;
300 if self.conn.handle_async_message(&msg) {
301 continue;
302 }
303 match msg {
304 BackendMessage::CommandComplete(body) => {
305 let tag = body.tag().unwrap_or("");
306 rows = crate::query::result::CommandTag::new(tag.to_string())
307 .rows_affected()
308 .unwrap_or(0);
309 }
310 BackendMessage::ReadyForQuery(body) => {
311 self.conn.transaction_status = TransactionStatus::from_u8(body.status())
312 .unwrap_or(TransactionStatus::Idle);
313 self.done = true;
314 self.conn.state = ConnectionState::Idle;
315 break;
316 }
317 BackendMessage::ErrorResponse(body) => {
318 let server_err = PgServerError::from_error_body(&body).map_err(PgError::Io)?;
319 self.conn.read_until_ready().await?;
320 self.done = true;
321 self.conn.state = ConnectionState::Idle;
322 return Err(PgError::Server(Box::new(server_err)));
323 }
324 _ => {}
325 }
326 }
327
328 #[cfg(feature = "tracing")]
329 tracing::info!(target: TARGET_COPY, rows = rows, "COPY IN: finished");
330 Ok(rows)
331 }
332
333 #[must_use = "copy cancel errors should be checked"]
338 pub async fn cancel(mut self, reason: &str) -> Result<()> {
339 self.conn
340 .codec
341 .send(
342 &mut self.conn.transport,
343 &FrontendMessage::CopyFail {
344 message: reason.to_string(),
345 },
346 )
347 .await
348 .map_err(Error::from)?;
349
350 self.conn.read_until_ready().await?;
351 self.done = true;
352 self.conn.state = ConnectionState::Idle;
353 Ok(())
354 }
355}
356
357impl<'a> Drop for CopyIn<'a> {
358 fn drop(&mut self) {
359 if self.done || std::thread::panicking() {
360 return;
361 }
362 #[cfg(feature = "tracing")]
363 tracing::warn!(target: TARGET_COPY, "CopyIn dropped without finish; connection may need recovery");
364 self.conn
378 .cancel_copy_in_sync("CopyIn dropped without finish");
379 }
380}
381
382#[non_exhaustive]
391pub struct CopyOut<'a> {
392 conn: &'a mut Connection,
393 format: u8,
394 column_formats: Vec<u16>,
395 done: bool,
396}
397
398impl<'a> CopyOut<'a> {
399 pub fn format(&self) -> u8 {
403 self.format
404 }
405
406 pub fn column_formats(&self) -> &[u16] {
408 &self.column_formats
409 }
410
411 #[must_use = "copy read errors should be checked"]
415 pub async fn read_next(&mut self) -> Result<Option<Vec<u8>>> {
416 loop {
417 let msg = self
418 .conn
419 .codec
420 .read_message(&mut self.conn.transport)
421 .await?;
422 if self.conn.handle_async_message(&msg) {
423 continue;
424 }
425 match msg {
426 BackendMessage::CopyData(body) => {
427 return Ok(Some(body.data().to_vec()));
428 }
429 BackendMessage::CopyDone => {
430 }
432 BackendMessage::CommandComplete(_) => {
433 }
435 BackendMessage::ReadyForQuery(body) => {
436 self.conn.transaction_status = TransactionStatus::from_u8(body.status())
437 .unwrap_or(TransactionStatus::Idle);
438 self.done = true;
439 self.conn.state = ConnectionState::Idle;
440 return Ok(None);
441 }
442 BackendMessage::ErrorResponse(body) => {
443 let server_err = PgServerError::from_error_body(&body).map_err(PgError::Io)?;
444 self.conn.read_until_ready().await?;
445 self.done = true;
446 self.conn.state = ConnectionState::Idle;
447 return Err(PgError::Server(Box::new(server_err)));
448 }
449 _ => {}
450 }
451 }
452 }
453
454 #[must_use = "copy read errors should be checked"]
456 pub async fn read_all(&mut self) -> Result<Vec<u8>> {
457 let mut result = Vec::new();
458 while let Some(chunk) = self.read_next().await? {
459 result.extend_from_slice(&chunk);
460 }
461 Ok(result)
462 }
463
464 #[must_use = "copy read errors should be checked"]
466 pub async fn for_each<F>(&mut self, mut f: F) -> Result<()>
467 where
468 F: FnMut(&[u8]) -> Result<()>,
469 {
470 while let Some(chunk) = self.read_next().await? {
471 f(&chunk)?;
472 }
473 Ok(())
474 }
475
476 #[must_use = "copy read errors should be checked"]
491 pub async fn for_each_row<F>(&mut self, mut f: F) -> Result<()>
492 where
493 F: FnMut(&[&str]) -> Result<()>,
494 {
495 let mut buffer = String::new();
496 while let Some(chunk) = self.read_next().await? {
497 buffer.push_str(&String::from_utf8_lossy(&chunk));
498
499 while let Some(newline_pos) = buffer.find('\n') {
501 let line = buffer[..newline_pos].to_string();
502 buffer = buffer[newline_pos + 1..].to_string();
503
504 if line.is_empty() {
506 continue;
507 }
508
509 let fields: Vec<&str> = line.split('\t').collect();
510 f(&fields)?;
511 }
512 }
513
514 let remaining = buffer.trim_end();
516 if !remaining.is_empty() {
517 let fields: Vec<&str> = remaining.split('\t').collect();
518 f(&fields)?;
519 }
520
521 Ok(())
522 }
523
524 #[must_use = "copy read errors should be checked"]
540 pub async fn for_each_csv_row<F>(
541 &mut self,
542 delimiter: char,
543 quote: char,
544 mut f: F,
545 ) -> Result<()>
546 where
547 F: FnMut(&[String]) -> Result<()>,
548 {
549 let mut buffer = String::new();
550 while let Some(chunk) = self.read_next().await? {
551 buffer.push_str(&String::from_utf8_lossy(&chunk));
552
553 while let Some(newline_pos) = buffer.find('\n') {
555 let line = buffer[..newline_pos].to_string();
556 buffer = buffer[newline_pos + 1..].to_string();
557
558 if line.is_empty() {
560 continue;
561 }
562
563 let fields = parse_csv_line(&line, delimiter, quote);
564 f(&fields)?;
565 }
566 }
567
568 let remaining = buffer.trim_end();
570 if !remaining.is_empty() {
571 let fields = parse_csv_line(remaining, delimiter, quote);
572 f(&fields)?;
573 }
574
575 Ok(())
576 }
577}
578
579impl<'a> Drop for CopyOut<'a> {
580 fn drop(&mut self) {
581 if self.done || std::thread::panicking() {
582 return;
583 }
584 #[cfg(feature = "tracing")]
585 tracing::warn!(target: TARGET_COPY, "CopyOut dropped without finish; connection may need recovery");
586 self.conn.drain_copy_out_sync();
596 }
597}
598
599impl Connection {
604 #[must_use = "copy errors should be checked"]
616 pub async fn copy_in(&mut self, sql: &str) -> Result<CopyIn<'_>> {
617 #[cfg(feature = "tracing")]
618 tracing::info!(target: TARGET_COPY, direction = "in", sql_truncated = %truncate_str(sql, 200), "Starting COPY IN operation");
619 self.transition(ConnectionState::CopyIn)?;
620
621 self.codec
622 .send(
623 &mut self.transport,
624 &FrontendMessage::Query { sql: sql.into() },
625 )
626 .await?;
627
628 loop {
629 let msg = self.codec.read_message(&mut self.transport).await?;
630 if self.handle_async_message(&msg) {
631 continue;
632 }
633 match msg {
634 BackendMessage::CopyInResponse(body) => {
635 let format = body.format();
636 let mut column_formats = Vec::new();
637 let mut iter = body.column_formats();
638 while let Some(fmt) = iter.next()? {
639 column_formats.push(fmt);
640 }
641 break Ok(CopyIn {
642 conn: self,
643 format,
644 column_formats,
645 done: false,
646 });
647 }
648 BackendMessage::ErrorResponse(body) => {
649 let server_err = PgServerError::from_error_body(&body).map_err(PgError::Io)?;
650 self.read_until_ready().await?;
651 self.state = ConnectionState::Idle;
652 break Err(PgError::Server(Box::new(server_err)));
653 }
654 _ => {
655 self.read_until_ready().await?;
656 self.state = ConnectionState::Idle;
657 break Err(PgError::Protocol(
658 crate::protocol::ProtocolError::ProtocolViolation(
659 "expected CopyInResponse after COPY query".into(),
660 ),
661 ));
662 }
663 }
664 }
665 }
666
667 #[must_use = "copy errors should be checked"]
679 pub async fn copy_out(&mut self, sql: &str) -> Result<CopyOut<'_>> {
680 #[cfg(feature = "tracing")]
681 tracing::info!(target: TARGET_COPY, direction = "out", sql_truncated = %truncate_str(sql, 200), "Starting COPY OUT operation");
682 self.transition(ConnectionState::CopyOut)?;
683
684 self.codec
685 .send(
686 &mut self.transport,
687 &FrontendMessage::Query { sql: sql.into() },
688 )
689 .await?;
690
691 loop {
692 let msg = self.codec.read_message(&mut self.transport).await?;
693 if self.handle_async_message(&msg) {
694 continue;
695 }
696 match msg {
697 BackendMessage::CopyOutResponse(body) => {
698 let format = body.format();
699 let mut column_formats = Vec::new();
700 let mut iter = body.column_formats();
701 while let Some(fmt) = iter.next()? {
702 column_formats.push(fmt);
703 }
704 break Ok(CopyOut {
705 conn: self,
706 format,
707 column_formats,
708 done: false,
709 });
710 }
711 BackendMessage::ErrorResponse(body) => {
712 let server_err = PgServerError::from_error_body(&body).map_err(PgError::Io)?;
713 self.read_until_ready().await?;
714 self.state = ConnectionState::Idle;
715 break Err(PgError::Server(Box::new(server_err)));
716 }
717 _ => {
718 self.read_until_ready().await?;
719 self.state = ConnectionState::Idle;
720 break Err(PgError::Protocol(
721 crate::protocol::ProtocolError::ProtocolViolation(
722 "expected CopyOutResponse after COPY query".into(),
723 ),
724 ));
725 }
726 }
727 }
728 }
729}
730
731#[cfg(test)]
736mod tests {
737 use super::*;
738 use crate::auth::{Codec, ServerParams};
739 use crate::config::Config;
740 use crate::connection::ConnectionState;
741 use crate::protocol::TransactionStatus;
742 use crate::transport::{BufferedTransport, ClientTransport, MockTransport, PgTransport};
743 use std::collections::VecDeque;
744
745 fn make_connection(read_data: Vec<u8>) -> Connection {
746 let transport = PgTransport::Plain(BufferedTransport::new(ClientTransport::Mock(
747 MockTransport::new(read_data),
748 )));
749 Connection {
750 transport,
751 codec: Codec::new(),
752 server_params: ServerParams::default(),
753 state: ConnectionState::Idle,
754 config: Config::new(),
755 transaction_status: TransactionStatus::Idle,
756 notification_queue: VecDeque::new(),
757 notice_handler: None,
758 statement_counter: 0,
759 needs_recovery: false,
760 health: crate::reconnect::session::ConnectionHealth::new(),
761 session_state: crate::reconnect::session::SessionState::new(),
762 }
763 }
764
765 fn build_copy_in_response(format: u8, col_formats: &[u16]) -> Vec<u8> {
766 let mut buf = vec![b'G'];
767 let mut body = Vec::new();
768 body.push(format);
769 body.extend_from_slice(&(col_formats.len() as i16).to_be_bytes());
770 for fmt in col_formats {
771 body.extend_from_slice(&fmt.to_be_bytes());
772 }
773 let len = (body.len() + 4) as i32;
774 buf.extend_from_slice(&len.to_be_bytes());
775 buf.extend_from_slice(&body);
776 buf
777 }
778
779 fn build_copy_out_response(format: u8, col_formats: &[u16]) -> Vec<u8> {
780 let mut buf = vec![b'H'];
781 let mut body = Vec::new();
782 body.push(format);
783 body.extend_from_slice(&(col_formats.len() as i16).to_be_bytes());
784 for fmt in col_formats {
785 body.extend_from_slice(&fmt.to_be_bytes());
786 }
787 let len = (body.len() + 4) as i32;
788 buf.extend_from_slice(&len.to_be_bytes());
789 buf.extend_from_slice(&body);
790 buf
791 }
792
793 fn build_copy_data(data: &[u8]) -> Vec<u8> {
794 let mut buf = vec![b'd'];
795 let len = (data.len() + 4) as i32;
796 buf.extend_from_slice(&len.to_be_bytes());
797 buf.extend_from_slice(data);
798 buf
799 }
800
801 fn build_copy_done() -> Vec<u8> {
802 vec![b'c', 0, 0, 0, 4]
803 }
804
805 fn build_command_complete_msg(tag: &str) -> Vec<u8> {
806 let mut buf = vec![b'C'];
807 let mut body = Vec::new();
808 body.extend_from_slice(tag.as_bytes());
809 body.push(0);
810 let len = (body.len() + 4) as i32;
811 buf.extend_from_slice(&len.to_be_bytes());
812 buf.extend_from_slice(&body);
813 buf
814 }
815
816 fn build_ready_for_query(status: u8) -> Vec<u8> {
817 vec![b'Z', 0, 0, 0, 5, status]
818 }
819
820 fn build_error_response(msg: &str) -> Vec<u8> {
821 let mut buf = vec![b'E'];
822 let mut body = Vec::new();
823 body.push(b'S');
824 body.extend_from_slice(b"ERROR\0");
825 body.push(b'M');
826 body.extend_from_slice(msg.as_bytes());
827 body.push(0);
828 body.push(0);
829 let len = (body.len() + 4) as i32;
830 buf.extend_from_slice(&len.to_be_bytes());
831 buf.extend_from_slice(&body);
832 buf
833 }
834
835 #[test]
840 fn test_copy_format_text() {
841 assert_eq!(CopyFormat::Text.to_sql_options(), "");
842 }
843
844 #[test]
845 fn test_copy_format_csv() {
846 let fmt = CopyFormat::Csv {
847 delimiter: ',',
848 null: "\\N".to_string(),
849 header: true,
850 quote: '"',
851 escape: '"',
852 };
853 let sql = fmt.to_sql_options();
854 assert!(sql.contains("FORMAT csv"));
855 assert!(sql.contains("HEADER true"));
856 }
857
858 #[test]
859 fn test_copy_format_binary() {
860 assert_eq!(CopyFormat::Binary.to_sql_options(), "WITH (FORMAT binary)");
861 }
862
863 #[tokio::test]
868 async fn test_copy_in_success() {
869 let mut data = Vec::new();
870 data.extend_from_slice(&build_copy_in_response(0, &[0, 0]));
871 data.extend_from_slice(&build_command_complete_msg("COPY 2"));
872 data.extend_from_slice(&build_ready_for_query(b'I'));
873
874 let mut conn = make_connection(data);
875 let mut copy = conn.copy_in("COPY users FROM STDIN").await.unwrap();
876 assert_eq!(copy.format(), 0);
877 assert_eq!(copy.column_formats(), &[0, 0]);
878
879 copy.write_row(&["1", "alice"]).await.unwrap();
880 copy.write_row(&["2", "bob"]).await.unwrap();
881
882 let rows = copy.finish().await.unwrap();
883 assert_eq!(rows, 2);
884 assert_eq!(conn.state, ConnectionState::Idle);
885 }
886
887 #[tokio::test]
888 async fn test_copy_in_cancel() {
889 let mut data = Vec::new();
890 data.extend_from_slice(&build_copy_in_response(0, &[]));
891 data.extend_from_slice(&build_command_complete_msg("ROLLBACK"));
892 data.extend_from_slice(&build_ready_for_query(b'I'));
893
894 let mut conn = make_connection(data);
895 let copy = conn.copy_in("COPY users FROM STDIN").await.unwrap();
896 copy.cancel("user requested").await.unwrap();
897 assert_eq!(conn.state, ConnectionState::Idle);
898
899 if let PgTransport::Plain(buf) = &conn.transport {
901 let mock = buf.inner();
902 #[allow(unreachable_patterns)]
903 let m = match mock {
904 ClientTransport::Mock(m) => m,
905 _ => panic!("expected mock transport in unit test"),
906 };
907 assert!(m.written().windows(1).any(|w| w[0] == b'f'));
908 }
909 }
910
911 #[tokio::test]
912 async fn test_copy_in_error_response() {
913 let mut data = Vec::new();
914 data.extend_from_slice(&build_error_response("syntax error"));
915 data.extend_from_slice(&build_ready_for_query(b'I'));
916
917 let mut conn = make_connection(data);
918 let result = conn.copy_in("COPY users FROM STDIN").await;
919 assert!(matches!(result, Err(Error::Server(_))));
920 drop(result);
921 assert_eq!(conn.state, ConnectionState::Idle);
922 }
923
924 #[tokio::test]
929 async fn test_copy_out_success() {
930 let mut data = Vec::new();
931 data.extend_from_slice(&build_copy_out_response(0, &[0, 0]));
932 data.extend_from_slice(&build_copy_data(b"1\talice\n"));
933 data.extend_from_slice(&build_copy_data(b"2\tbob\n"));
934 data.extend_from_slice(&build_copy_done());
935 data.extend_from_slice(&build_command_complete_msg("COPY 2"));
936 data.extend_from_slice(&build_ready_for_query(b'I'));
937
938 let mut conn = make_connection(data);
939 let mut copy = conn.copy_out("COPY users TO STDOUT").await.unwrap();
940 assert_eq!(copy.format(), 0);
941 assert_eq!(copy.column_formats(), &[0, 0]);
942
943 let chunk1 = copy.read_next().await.unwrap().unwrap();
944 assert_eq!(chunk1, b"1\talice\n");
945
946 let chunk2 = copy.read_next().await.unwrap().unwrap();
947 assert_eq!(chunk2, b"2\tbob\n");
948
949 let done = copy.read_next().await.unwrap();
950 assert!(done.is_none());
951 drop(copy);
952 assert_eq!(conn.state, ConnectionState::Idle);
953 }
954
955 #[tokio::test]
956 async fn test_copy_out_read_all() {
957 let mut data = Vec::new();
958 data.extend_from_slice(&build_copy_out_response(0, &[]));
959 data.extend_from_slice(&build_copy_data(b"hello"));
960 data.extend_from_slice(&build_copy_data(b" world"));
961 data.extend_from_slice(&build_copy_done());
962 data.extend_from_slice(&build_command_complete_msg("COPY 2"));
963 data.extend_from_slice(&build_ready_for_query(b'I'));
964
965 let mut conn = make_connection(data);
966 let mut copy = conn.copy_out("COPY users TO STDOUT").await.unwrap();
967 let all = copy.read_all().await.unwrap();
968 assert_eq!(all, b"hello world");
969 }
970
971 #[tokio::test]
972 async fn test_copy_out_for_each() {
973 let mut data = Vec::new();
974 data.extend_from_slice(&build_copy_out_response(0, &[]));
975 data.extend_from_slice(&build_copy_data(b"a"));
976 data.extend_from_slice(&build_copy_data(b"b"));
977 data.extend_from_slice(&build_copy_done());
978 data.extend_from_slice(&build_command_complete_msg("COPY 2"));
979 data.extend_from_slice(&build_ready_for_query(b'I'));
980
981 let mut conn = make_connection(data);
982 let mut copy = conn.copy_out("COPY users TO STDOUT").await.unwrap();
983 let mut acc = Vec::new();
984 copy.for_each(|chunk| {
985 acc.extend_from_slice(chunk);
986 Ok(())
987 })
988 .await
989 .unwrap();
990 assert_eq!(acc, b"ab");
991 }
992
993 #[tokio::test]
994 async fn test_copy_out_error_response() {
995 let mut data = Vec::new();
996 data.extend_from_slice(&build_error_response("relation does not exist"));
997 data.extend_from_slice(&build_ready_for_query(b'I'));
998
999 let mut conn = make_connection(data);
1000 let result = conn.copy_out("COPY missing TO STDOUT").await;
1001 assert!(matches!(result, Err(Error::Server(_))));
1002 drop(result);
1003 assert_eq!(conn.state, ConnectionState::Idle);
1004 }
1005
1006 #[test]
1011 fn test_parse_csv_line_simple() {
1012 let fields = super::parse_csv_line("1,alice,hello", ',', '"');
1013 assert_eq!(fields, vec!["1", "alice", "hello"]);
1014 }
1015
1016 #[test]
1017 fn test_parse_csv_line_quoted() {
1018 let fields = super::parse_csv_line("1,\"alice\",\"hello world\"", ',', '"');
1019 assert_eq!(fields, vec!["1", "alice", "hello world"]);
1020 }
1021
1022 #[test]
1023 fn test_parse_csv_line_escaped_quote() {
1024 let fields = super::parse_csv_line("1,\"says \"\"hi\"\"\",ok", ',', '"');
1025 assert_eq!(fields, vec!["1", "says \"hi\"", "ok"]);
1026 }
1027
1028 #[test]
1029 fn test_parse_csv_line_empty_fields() {
1030 let fields = super::parse_csv_line("1,,3", ',', '"');
1031 assert_eq!(fields, vec!["1", "", "3"]);
1032 }
1033
1034 #[test]
1035 fn test_parse_csv_line_single_field() {
1036 let fields = super::parse_csv_line("hello", ',', '"');
1037 assert_eq!(fields, vec!["hello"]);
1038 }
1039
1040 #[test]
1041 fn test_parse_csv_line_delimiter_in_quotes() {
1042 let fields = super::parse_csv_line("1,\"a, b, c\",3", ',', '"');
1043 assert_eq!(fields, vec!["1", "a, b, c", "3"]);
1044 }
1045
1046 #[test]
1047 fn test_parse_csv_line_tab_delimiter() {
1048 let fields = super::parse_csv_line("1\talice\thello", '\t', '"');
1049 assert_eq!(fields, vec!["1", "alice", "hello"]);
1050 }
1051
1052 #[tokio::test]
1057 async fn test_write_csv_row_with_null() {
1058 let mut data = Vec::new();
1059 data.extend_from_slice(&build_copy_in_response(0, &[]));
1060 data.extend_from_slice(&build_command_complete_msg("COPY 3"));
1061 data.extend_from_slice(&build_ready_for_query(b'I'));
1062
1063 let mut conn = make_connection(data);
1064 let mut copy = conn.copy_in("COPY t FROM STDIN").await.unwrap();
1065
1066 copy.write_csv_row_with_null(&[Some("1"), Some("alice"), Some("hello")], ',', '"', "")
1068 .await
1069 .unwrap();
1070
1071 copy.write_csv_row_with_null(&[Some("2"), Some("bob"), None], ',', '"', "")
1073 .await
1074 .unwrap();
1075
1076 copy.write_csv_row_with_null(&[Some("3"), None, Some("world")], ',', '"', "\\N")
1078 .await
1079 .unwrap();
1080
1081 let rows = copy.finish().await.unwrap();
1082 assert_eq!(rows, 3);
1083
1084 if let PgTransport::Plain(buf) = &conn.transport {
1086 let mock = buf.inner();
1087 #[allow(unreachable_patterns)]
1088 let m = match mock {
1089 ClientTransport::Mock(m) => m,
1090 _ => panic!("expected mock transport in unit test"),
1091 };
1092 let written = m.written();
1093 let mut copy_data_parts: Vec<Vec<u8>> = Vec::new();
1095 let mut i = 0;
1096 while i < written.len() {
1097 if written[i] == b'd' {
1098 let len = i32::from_be_bytes([
1100 written[i + 1],
1101 written[i + 2],
1102 written[i + 3],
1103 written[i + 4],
1104 ]);
1105 let data_len = len as usize - 4;
1106 copy_data_parts.push(written[i + 5..i + 5 + data_len].to_vec());
1107 i += 5 + data_len;
1108 } else {
1109 i += 1;
1110 }
1111 }
1112
1113 assert_eq!(©_data_parts[0], b"1,alice,hello\n");
1115 assert_eq!(©_data_parts[1], b"2,bob,\n");
1117 assert_eq!(©_data_parts[2], b"3,\\N,world\n");
1119 }
1120 }
1121
1122 #[tokio::test]
1127 async fn test_copy_out_for_each_row() {
1128 let mut data = Vec::new();
1129 data.extend_from_slice(&build_copy_out_response(0, &[]));
1130 data.extend_from_slice(&build_copy_data(b"1\talice\n2\tbob\n3\tcharlie\n"));
1131 data.extend_from_slice(&build_copy_done());
1132 data.extend_from_slice(&build_command_complete_msg("COPY 3"));
1133 data.extend_from_slice(&build_ready_for_query(b'I'));
1134
1135 let mut conn = make_connection(data);
1136 let mut copy = conn.copy_out("COPY users TO STDOUT").await.unwrap();
1137
1138 let mut rows: Vec<Vec<String>> = Vec::new();
1139 copy.for_each_row(|fields| {
1140 rows.push(fields.iter().map(|f| f.to_string()).collect());
1141 Ok(())
1142 })
1143 .await
1144 .unwrap();
1145
1146 assert_eq!(rows.len(), 3);
1147 assert_eq!(rows[0], vec!["1", "alice"]);
1148 assert_eq!(rows[1], vec!["2", "bob"]);
1149 assert_eq!(rows[2], vec!["3", "charlie"]);
1150 }
1151
1152 #[tokio::test]
1153 async fn test_copy_out_for_each_csv_row() {
1154 let mut data = Vec::new();
1155 data.extend_from_slice(&build_copy_out_response(0, &[]));
1156 data.extend_from_slice(&build_copy_data(b"1,alice\n2,\"bob\"\n3,charlie\n"));
1157 data.extend_from_slice(&build_copy_done());
1158 data.extend_from_slice(&build_command_complete_msg("COPY 3"));
1159 data.extend_from_slice(&build_ready_for_query(b'I'));
1160
1161 let mut conn = make_connection(data);
1162 let mut copy = conn.copy_out("COPY users TO STDOUT").await.unwrap();
1163
1164 let mut rows: Vec<Vec<String>> = Vec::new();
1165 copy.for_each_csv_row(',', '"', |fields| {
1166 rows.push(fields.to_vec());
1167 Ok(())
1168 })
1169 .await
1170 .unwrap();
1171
1172 assert_eq!(rows.len(), 3);
1173 assert_eq!(rows[0], vec!["1", "alice"]);
1174 assert_eq!(rows[1], vec!["2", "bob"]);
1175 assert_eq!(rows[2], vec!["3", "charlie"]);
1176 }
1177}