1use core::marker::PhantomData;
2use std::io::BufRead;
3
4use super::CommonOptions;
5use super::CopyFormat;
6use super::CopyTarget;
7use crate::AppearsOnTable;
8use crate::Connection;
9use crate::Expression;
10use crate::QueryResult;
11use crate::Selectable;
12use crate::deserialize::FromSqlRow;
13use crate::pg::Pg;
14#[cfg(feature = "postgres")]
15use crate::pg::value::TypeOidLookup;
16use crate::query_builder::QueryFragment;
17use crate::query_builder::QueryId;
18use crate::row::Row;
19#[cfg(feature = "postgres")]
20use crate::row::{self, Field, PartialRow, RowIndex, RowSealed};
21
22#[derive(#[automatically_derived]impl ::core::default::Default for CopyToOptions {
#[inline]
fn default() -> Self {
CopyToOptions {
common: ::core::default::Default::default(),
header: ::core::default::Default::default(),
}
}
}Default, #[automatically_derived]impl ::core::fmt::Debug for CopyToOptions {
#[inline]
#[rustc_trivial_field_reads]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f,
"CopyToOptions common header", &self.common, &&self.header)
}
}Debug)]
23pub struct CopyToOptions {
24 common: CommonOptions,
25 header: Option<bool>,
26}
27
28impl CopyToOptions {
29 fn any_set(&self) -> bool {
30 self.common.any_set() || self.header.is_some()
31 }
32}
33
34impl QueryFragment<Pg> for CopyToOptions {
35 fn walk_ast<'b>(
36 &'b self,
37 mut pass: crate::query_builder::AstPass<'_, 'b, Pg>,
38 ) -> crate::QueryResult<()> {
39 if self.any_set() {
40 let mut comma = "";
41 pass.push_sql(" WITH (");
42 self.common.walk_ast(pass.reborrow(), &mut comma);
43 if let Some(header_is_set) = self.header {
44 pass.push_sql(comma);
45 pass.push_sql("HEADER ");
48 pass.push_sql(if header_is_set { "1" } else { "0" });
49 }
50
51 pass.push_sql(")");
52 }
53 Ok(())
54 }
55}
56
57#[derive(#[automatically_derived]impl<S: ::core::fmt::Debug> ::core::fmt::Debug for
CopyToCommand<S> {
#[inline]
#[rustc_trivial_field_reads]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f,
"CopyToCommand options p", &self.options, &&self.p)
}
}Debug)]
58pub struct CopyToCommand<S> {
59 options: CopyToOptions,
60 p: PhantomData<S>,
61}
62
63impl<S> QueryId for CopyToCommand<S>
64where
65 S: CopyTarget,
66{
67 type QueryId = ();
68
69 const HAS_STATIC_QUERY_ID: bool = false;
70}
71
72impl<S> QueryFragment<Pg> for CopyToCommand<S>
73where
74 S: CopyTarget,
75{
76 fn walk_ast<'b>(
77 &'b self,
78 mut pass: crate::query_builder::AstPass<'_, 'b, Pg>,
79 ) -> crate::QueryResult<()> {
80 pass.unsafe_to_cache_prepared();
81 pass.push_sql("COPY ");
82 S::walk_target(pass.reborrow())?;
83 pass.push_sql(" TO STDOUT");
84 self.options.walk_ast(pass.reborrow())?;
85 Ok(())
86 }
87}
88
89#[derive(#[automatically_derived]impl ::core::fmt::Debug for NotSet {
#[inline]
#[rustc_trivial_field_reads]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f, "NotSet")
}
}Debug, #[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
for NotSet {
}
#[automatically_derived]impl ::core::clone::Clone for NotSet {
#[inline]
#[rustc_trivial_field_reads]
fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]impl ::core::marker::Copy for NotSet { }Copy)]
90pub struct NotSet;
91
92pub trait CopyToMarker: Sized {
93 fn setup_options<T>(q: CopyToQuery<T, Self>) -> CopyToQuery<T, CopyToOptions>;
94}
95
96impl CopyToMarker for NotSet {
97 fn setup_options<T>(q: CopyToQuery<T, Self>) -> CopyToQuery<T, CopyToOptions> {
98 CopyToQuery {
99 target: q.target,
100 options: CopyToOptions::default(),
101 }
102 }
103}
104impl CopyToMarker for CopyToOptions {
105 fn setup_options<T>(q: CopyToQuery<T, Self>) -> CopyToQuery<T, CopyToOptions> {
106 q
107 }
108}
109#[derive(#[automatically_derived]impl<T: ::core::fmt::Debug, O: ::core::fmt::Debug>
::core::fmt::Debug for CopyToQuery<T, O> {
#[inline]
#[rustc_trivial_field_reads]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f,
"CopyToQuery target options", &self.target, &&self.options)
}
}Debug)]
122#[must_use = "`COPY TO` statements are only executed when calling `.load()` or `load_raw()`."]
123#[cfg(feature = "postgres_backend")]
124pub struct CopyToQuery<T, O> {
125 target: T,
126 options: O,
127}
128
129#[cfg(feature = "postgres")]
130struct CopyRow<'a> {
131 buffers: Vec<Option<&'a [u8]>>,
132 result: &'a crate::pg::connection::PgResult,
133}
134
135#[cfg(feature = "postgres")]
136struct CopyField<'a> {
137 field: &'a Option<&'a [u8]>,
138 result: &'a crate::pg::connection::PgResult,
139 col_idx: usize,
140}
141
142#[cfg(feature = "postgres")]
143impl<'f> Field<'f, Pg> for CopyField<'f> {
144 fn field_name(&self) -> Option<&str> {
145 None
146 }
147
148 fn value(&self) -> Option<<Pg as crate::backend::Backend>::RawValue<'_>> {
149 let value = self.field.as_deref()?;
150 Some(crate::pg::PgValue::new_internal(value, self))
151 }
152}
153
154#[cfg(feature = "postgres")]
155impl TypeOidLookup for CopyField<'_> {
156 fn lookup(&self) -> core::num::NonZeroU32 {
157 self.result.column_type(self.col_idx)
158 }
159}
160
161#[cfg(feature = "postgres")]
162impl RowSealed for CopyRow<'_> {}
163
164#[cfg(feature = "postgres")]
165impl RowIndex<usize> for CopyRow<'_> {
166 fn idx(&self, idx: usize) -> Option<usize> {
167 if idx < self.field_count() {
168 Some(idx)
169 } else {
170 None
171 }
172 }
173}
174
175#[cfg(feature = "postgres")]
176impl<'a> RowIndex<&'a str> for CopyRow<'_> {
177 fn idx(&self, _idx: &'a str) -> Option<usize> {
178 None
179 }
180}
181
182#[cfg(feature = "postgres")]
183impl<'a> Row<'a, Pg> for CopyRow<'_> {
184 type Field<'f>
185 = CopyField<'f>
186 where
187 'a: 'f,
188 Self: 'f;
189
190 type InnerPartialRow = Self;
191
192 fn field_count(&self) -> usize {
193 self.buffers.len()
194 }
195
196 fn get<'b, I>(&'b self, idx: I) -> Option<Self::Field<'b>>
197 where
198 'a: 'b,
199 Self: RowIndex<I>,
200 {
201 let idx = self.idx(idx)?;
202 let buffer = self.buffers.get(idx)?;
203 Some(CopyField {
204 field: buffer,
205 result: self.result,
206 col_idx: idx,
207 })
208 }
209
210 fn partial_row(
211 &self,
212 range: core::ops::Range<usize>,
213 ) -> row::PartialRow<'_, Self::InnerPartialRow> {
214 PartialRow::new(self, range)
215 }
216}
217
218pub trait ExecuteCopyToConnection: Connection<Backend = Pg> {
219 type CopyToBuffer<'a>: BufRead;
220
221 fn make_row<'a, 'b>(
222 out: &'a Self::CopyToBuffer<'_>,
223 buffers: Vec<Option<&'a [u8]>>,
224 ) -> impl Row<'b, Pg> + 'a;
225
226 fn get_buffer<'a>(out: &'a Self::CopyToBuffer<'_>) -> &'a [u8];
227
228 fn execute<T>(&mut self, command: CopyToCommand<T>) -> QueryResult<Self::CopyToBuffer<'_>>
229 where
230 T: CopyTarget;
231}
232
233#[cfg(feature = "postgres")]
234impl ExecuteCopyToConnection for crate::PgConnection {
235 type CopyToBuffer<'a> = crate::pg::connection::copy::CopyToBuffer<'a>;
236
237 fn make_row<'a, 'b>(
238 out: &'a Self::CopyToBuffer<'_>,
239 buffers: Vec<Option<&'a [u8]>>,
240 ) -> impl Row<'b, Pg> + 'a {
241 CopyRow {
242 buffers,
243 result: out.get_result(),
244 }
245 }
246
247 fn get_buffer<'a>(out: &'a Self::CopyToBuffer<'_>) -> &'a [u8] {
248 out.data_slice()
249 }
250
251 fn execute<T>(&mut self, command: CopyToCommand<T>) -> QueryResult<Self::CopyToBuffer<'_>>
252 where
253 T: CopyTarget,
254 {
255 self.copy_to(command)
256 }
257}
258
259#[cfg(feature = "r2d2")]
260impl<C> ExecuteCopyToConnection for crate::r2d2::PooledConnection<crate::r2d2::ConnectionManager<C>>
261where
262 C: ExecuteCopyToConnection + crate::r2d2::R2D2Connection + 'static,
263{
264 type CopyToBuffer<'a> = C::CopyToBuffer<'a>;
265
266 fn make_row<'a, 'b>(
267 out: &'a Self::CopyToBuffer<'_>,
268 buffers: Vec<Option<&'a [u8]>>,
269 ) -> impl Row<'b, Pg> + 'a {
270 C::make_row(out, buffers)
271 }
272
273 fn get_buffer<'a>(out: &'a Self::CopyToBuffer<'_>) -> &'a [u8] {
274 C::get_buffer(out)
275 }
276
277 fn execute<T>(&mut self, command: CopyToCommand<T>) -> QueryResult<Self::CopyToBuffer<'_>>
278 where
279 T: CopyTarget,
280 {
281 C::execute(&mut **self, command)
282 }
283}
284
285impl<T> CopyToQuery<T, NotSet>
286where
287 T: CopyTarget,
288{
289 pub fn load<U, C>(self, conn: &mut C) -> QueryResult<impl Iterator<Item = QueryResult<U>> + '_>
296 where
297 U: FromSqlRow<<U::SelectExpression as Expression>::SqlType, Pg> + Selectable<Pg>,
298 U::SelectExpression: AppearsOnTable<T::Table> + CopyTarget<Table = T::Table>,
299 C: ExecuteCopyToConnection,
300 {
301 let command = CopyToCommand {
302 p: PhantomData::<U::SelectExpression>,
303 options: CopyToOptions {
304 header: None,
305 common: CommonOptions {
306 format: Some(CopyFormat::Binary),
307 ..Default::default()
308 },
309 },
310 };
311 let out = ExecuteCopyToConnection::execute(conn, command)?;
312 decode_data::<U, C>(out)
313 }
314}
315
316fn decode_data<'a, U, C>(
317 mut out: C::CopyToBuffer<'a>,
318) -> QueryResult<impl Iterator<Item = QueryResult<U>> + 'a>
319where
320 U: FromSqlRow<<U::SelectExpression as Expression>::SqlType, Pg> + Selectable<Pg>,
321 C: ExecuteCopyToConnection + 'a,
322{
323 let io_result_mapper = |e| crate::result::Error::DeserializationError(Box::new(e));
324 let buffer = out.fill_buf().map_err(io_result_mapper)?;
330 expect_buffer_len(buffer, super::COPY_MAGIC_HEADER.len() + 8)?;
331 if buffer[..super::COPY_MAGIC_HEADER.len()] != super::COPY_MAGIC_HEADER {
332 return Err(crate::result::Error::DeserializationError(
333 "Unexpected protocol header".into(),
334 ));
335 }
336 let flags_backward_incompatible = i16::from_be_bytes(
338 (&buffer[super::COPY_MAGIC_HEADER.len() + 2..super::COPY_MAGIC_HEADER.len() + 4])
339 .try_into()
340 .expect("Exactly 2 byte"),
341 );
342 if flags_backward_incompatible != 0 {
343 return Err(crate::result::Error::DeserializationError(
344 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Unexpected flag value: {0:x}",
flags_backward_incompatible))
})format!("Unexpected flag value: {flags_backward_incompatible:x}").into(),
345 ));
346 }
347 let header_size = usize::try_from(i32::from_be_bytes(
348 (&buffer[super::COPY_MAGIC_HEADER.len() + 4..super::COPY_MAGIC_HEADER.len() + 8])
349 .try_into()
350 .expect("Exactly 4 byte"),
351 ))
352 .map_err(|e| crate::result::Error::DeserializationError(Box::new(e)))?;
353 out.consume(super::COPY_MAGIC_HEADER.len() + 8 + header_size);
354 let mut len = None;
355 let mut finished = false;
356 Ok(core::iter::from_fn(move || {
357 if finished {
358 return None;
359 }
360 if let Some(len) = len {
361 out.consume(len);
362 if let Err(e) = out.fill_buf().map_err(io_result_mapper) {
363 finished = true;
364 return Some(Err(e));
365 }
366 }
367 let buffer = C::get_buffer(&out);
368 len = Some(buffer.len());
369 if let Err(e) = expect_buffer_len(buffer, 2) {
370 finished = true;
371 return Some(Err(e));
372 }
373 let tuple_count = i16::from_be_bytes((&buffer[..2]).try_into().expect("Exactly 2 bytes"));
374 if tuple_count > 0 {
375 let tuple_count = match usize::try_from(tuple_count) {
376 Ok(o) => o,
377 Err(e) => {
378 return Some(Err(crate::result::Error::DeserializationError(Box::new(e))));
379 }
380 };
381 let mut buffers = Vec::with_capacity(tuple_count);
382 let mut offset = 2;
383 for _t in 0..tuple_count {
384 if let Err(e) = expect_buffer_len(buffer, offset + 4) {
385 finished = true;
386 return Some(Err(e));
387 }
388 let data_size = i32::from_be_bytes(
389 (&buffer[offset..offset + 4])
390 .try_into()
391 .expect("Exactly 4 bytes"),
392 );
393 if data_size < 0 {
394 offset += 4;
395 buffers.push(None);
396 } else {
397 match usize::try_from(data_size) {
398 Ok(data_size) => {
399 if let Err(e) = expect_buffer_len(buffer, offset + 4 + data_size) {
400 finished = true;
401 return Some(Err(e));
402 }
403 buffers.push(Some(&buffer[offset + 4..offset + 4 + data_size]));
404 offset += data_size + 4;
405 }
406 Err(e) => {
407 return Some(Err(crate::result::Error::DeserializationError(
408 Box::new(e),
409 )));
410 }
411 }
412 }
413 }
414
415 let row = C::make_row(&out, buffers);
416 Some(U::build_from_row(&row).map_err(crate::result::Error::DeserializationError))
417 } else {
418 out.consume(len.expect("It's always set here"));
419 finished = true;
420 if let Err(e) = out.fill_buf().map_err(io_result_mapper) {
421 return Some(Err(e));
422 }
423 None
424 }
425 }))
426}
427
428fn expect_buffer_len(buffer: &[u8], expected: usize) -> QueryResult<()> {
429 if buffer.len() < expected {
430 Err(crate::result::Error::DeserializationError(
431 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Expected at least {1} bytes but got {0} bytes",
buffer.len(), expected))
})format!(
432 "Expected at least {expected} bytes but got {} bytes",
433 buffer.len()
434 )
435 .into(),
436 ))
437 } else {
438 Ok(())
439 }
440}
441
442impl<T, O> CopyToQuery<T, O>
443where
444 O: CopyToMarker,
445 T: CopyTarget,
446{
447 pub fn load_raw<C>(self, conn: &mut C) -> QueryResult<impl BufRead + '_>
453 where
454 C: ExecuteCopyToConnection,
455 {
456 let q = O::setup_options(self);
457 let command = CopyToCommand {
458 p: PhantomData::<T>,
459 options: q.options,
460 };
461 ExecuteCopyToConnection::execute(conn, command)
462 }
463
464 pub fn with_format(self, format: CopyFormat) -> CopyToQuery<T, CopyToOptions> {
469 let mut out = O::setup_options(self);
470 out.options.common.format = Some(format);
471 out
472 }
473
474 pub fn with_freeze(self, freeze: bool) -> CopyToQuery<T, CopyToOptions> {
479 let mut out = O::setup_options(self);
480 out.options.common.freeze = Some(freeze);
481 out
482 }
483
484 pub fn with_delimiter(self, delimiter: char) -> CopyToQuery<T, CopyToOptions> {
493 let mut out = O::setup_options(self);
494 out.options.common.delimiter = Some(delimiter);
495 out
496 }
497
498 pub fn with_null(self, null: impl Into<String>) -> CopyToQuery<T, CopyToOptions> {
508 let mut out = O::setup_options(self);
509 out.options.common.null = Some(null.into());
510 out
511 }
512
513 pub fn with_quote(self, quote: char) -> CopyToQuery<T, CopyToOptions> {
522 let mut out = O::setup_options(self);
523 out.options.common.quote = Some(quote);
524 out
525 }
526
527 pub fn with_escape(self, escape: char) -> CopyToQuery<T, CopyToOptions> {
536 let mut out = O::setup_options(self);
537 out.options.common.escape = Some(escape);
538 out
539 }
540
541 pub fn with_header(self, set: bool) -> CopyToQuery<T, CopyToOptions> {
546 let mut out = O::setup_options(self);
547 out.options.header = Some(set);
548 out
549 }
550}
551
552#[cfg(feature = "postgres_backend")]
649pub fn copy_to<T>(target: T) -> CopyToQuery<T, NotSet>
650where
651 T: CopyTarget,
652{
653 CopyToQuery {
654 target,
655 options: NotSet,
656 }
657}
658
659#[cfg(test)]
660mod tests {
661
662 use super::*;
663 use crate::connection::Instrumentation;
664 use crate::connection::{AnsiTransactionManager, TransactionManager};
665 use crate::pg::query_builder::copy::COPY_MAGIC_HEADER;
666 use std::collections::VecDeque;
667 use std::io::Read;
668
669 struct Wrapper;
670
671 struct Messages {
674 messages: VecDeque<Vec<u8>>,
675 offset: usize,
676 }
677
678 impl Messages {
679 fn new(messages: Vec<Vec<u8>>) -> Self {
680 Self {
681 messages: messages.into(),
682 offset: 0,
683 }
684 }
685
686 fn data_slice(&self) -> &[u8] {
687 self.messages
688 .front()
689 .map(|m| &m[self.offset..])
690 .unwrap_or_default()
691 }
692 }
693
694 impl Read for Messages {
695 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
696 let data = self.fill_buf()?;
697 let len = usize::min(buf.len(), data.len());
698 buf[..len].copy_from_slice(&data[..len]);
699 self.consume(len);
700 Ok(len)
701 }
702 }
703
704 impl BufRead for Messages {
705 fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
706 if self.data_slice().is_empty() && self.messages.pop_front().is_some() {
707 self.offset = 0;
708 }
709 Ok(self.data_slice())
710 }
711
712 fn consume(&mut self, amt: usize) {
713 let len = self.messages.front().map_or(0, Vec::len);
714 self.offset = usize::min(len, self.offset + amt);
715 }
716 }
717
718 impl crate::connection::SimpleConnection for Wrapper {
719 fn batch_execute(&mut self, _query: &str) -> QueryResult<()> {
720 unreachable!()
721 }
722 }
723
724 impl crate::connection::ConnectionSealed for Wrapper {}
725
726 impl crate::connection::Connection for Wrapper {
727 type Backend = crate::pg::Pg;
728
729 type TransactionManager = crate::connection::AnsiTransactionManager;
730
731 fn establish(_database_url: &str) -> crate::prelude::ConnectionResult<Self> {
732 unreachable!()
733 }
734
735 fn execute_returning_count<T>(&mut self, _source: &T) -> QueryResult<usize>
736 where
737 T: QueryFragment<Self::Backend> + QueryId,
738 {
739 unreachable!()
740 }
741
742 fn transaction_state(
743 &mut self,
744 ) -> &mut <AnsiTransactionManager as TransactionManager<Self>>::TransactionStateData
745 {
746 unreachable!()
747 }
748
749 fn instrumentation(&mut self) -> &mut dyn Instrumentation {
750 unreachable!()
751 }
752
753 fn set_instrumentation(
754 &mut self,
755 _instrumentation: impl crate::connection::Instrumentation,
756 ) {
757 unreachable!()
758 }
759
760 fn set_prepared_statement_cache_size(&mut self, _size: crate::connection::CacheSize) {
761 unreachable!()
762 }
763 }
764
765 impl ExecuteCopyToConnection for Wrapper {
766 type CopyToBuffer<'a> = Messages;
767
768 fn make_row<'a, 'b>(
769 _out: &'a Self::CopyToBuffer<'_>,
770 _buffers: Vec<Option<&'a [u8]>>,
771 ) -> impl Row<'b, Pg> + 'a {
772 unreachable!() as CopyRow<'a>
774 }
775
776 fn get_buffer<'a>(out: &'a Self::CopyToBuffer<'_>) -> &'a [u8] {
777 out.data_slice()
778 }
779
780 fn execute<T>(&mut self, _command: CopyToCommand<T>) -> QueryResult<Self::CopyToBuffer<'_>>
781 where
782 T: CopyTarget,
783 {
784 unreachable!()
785 }
786 }
787
788 crate::table! {
789 users {
790 id -> Integer,
791 name -> Text,
792 hair_color -> Nullable<Text>,
793 age -> Nullable<Integer>,
794 }
795 }
796
797 #[derive(diesel::Queryable, diesel::Selectable, Debug)]
798 #[diesel(table_name = users)]
799 #[allow(dead_code, reason = "Only used as structure for the test")]
800 struct Dummy {
801 id: i32,
802 name: String,
803 hair_color: Option<String>,
804 age: Option<i32>,
805 }
806
807 fn decode(messages: Vec<Vec<u8>>) -> QueryResult<impl Iterator<Item = QueryResult<Dummy>>> {
808 decode_data::<Dummy, Wrapper>(Messages::new(messages))
809 }
810
811 fn tuple_count(n: i16) -> [u8; 2] {
812 n.to_be_bytes()
813 }
814
815 fn field_len(n: i32) -> [u8; 4] {
816 n.to_be_bytes()
817 }
818
819 #[track_caller]
820 fn first_row_error(first_message: &[&[u8]]) -> String {
821 let first_message = [
822 COPY_MAGIC_HEADER.as_slice(),
823 [0; 8].as_slice(),
824 &first_message.concat(),
825 ]
826 .concat();
827 let mut rows = decode(vec![first_message]).unwrap();
828 let error = match rows.next() {
829 Some(Err(e)) => e.to_string(),
830 Some(Ok(row)) => panic!("expected an error, got {row:?}"),
831 None => panic!("expected an error, got the end of the data"),
832 };
833 assert!(
834 rows.next().is_none(),
835 "the iterator must end after an error"
836 );
837 error
838 }
839
840 #[track_caller]
841 fn check_decode_error(input: Vec<Vec<u8>>, error: &str) {
842 let r = decode(input);
843 assert!(r.is_err());
844 let Err(e) = r else { unreachable!() };
845 assert_eq!(e.to_string(), error);
846 }
847
848 #[test]
849 fn check_empty_buffer_returns_error() {
850 check_decode_error(
851 vec![Vec::new()],
852 "Expected at least 19 bytes but got 0 bytes",
853 );
854 }
855
856 #[test]
857 fn check_header_without_flags_returns_error() {
858 check_decode_error(
859 vec![COPY_MAGIC_HEADER.to_vec()],
860 "Expected at least 19 bytes but got 11 bytes",
861 );
862 }
863
864 #[test]
865 fn check_header_without_extension_length_returns_error() {
866 check_decode_error(
867 vec![[COPY_MAGIC_HEADER.as_slice(), &[0; 7]].concat().to_vec()],
868 "Expected at least 19 bytes but got 18 bytes",
869 );
870 }
871
872 #[test]
873 fn check_missing_trailer_returns_error() {
874 assert_eq!(
876 first_row_error(&[]),
877 "Expected at least 2 bytes but got 0 bytes",
878 );
879 }
880
881 #[test]
882 fn check_truncated_tuple_count_returns_error() {
883 assert_eq!(
884 first_row_error(&[&[0]]),
885 "Expected at least 2 bytes but got 1 bytes"
886 );
887 }
888
889 #[test]
890 fn check_missing_field_length_returns_error() {
891 assert_eq!(
892 first_row_error(&[&tuple_count(4)]),
893 "Expected at least 6 bytes but got 2 bytes"
894 );
895 }
896
897 #[test]
898 fn check_truncated_field_length_returns_error() {
899 assert_eq!(
900 first_row_error(&[&tuple_count(4), &[0, 0]]),
901 "Expected at least 6 bytes but got 4 bytes"
902 );
903 }
904
905 #[test]
906 fn check_missing_field_length_after_value_returns_error() {
907 assert_eq!(
908 first_row_error(&[&tuple_count(4), &field_len(4), &1_i32.to_be_bytes()]),
909 "Expected at least 14 bytes but got 10 bytes"
910 );
911 }
912
913 #[test]
914 fn check_missing_field_length_after_null_returns_error() {
915 assert_eq!(
916 first_row_error(&[&tuple_count(4), &field_len(-1)]),
917 "Expected at least 10 bytes but got 6 bytes"
918 );
919 }
920
921 #[test]
922 fn check_missing_field_data_returns_error() {
923 assert_eq!(
924 first_row_error(&[&tuple_count(4), &field_len(4)]),
925 "Expected at least 10 bytes but got 6 bytes"
926 );
927 }
928
929 #[test]
930 fn check_truncated_field_data_returns_error() {
931 assert_eq!(
932 first_row_error(&[&tuple_count(4), &field_len(100), b"abc"]),
933 "Expected at least 106 bytes but got 9 bytes"
934 );
935 }
936
937 #[test]
938 fn check_truncated_later_field_data_returns_error() {
939 assert_eq!(
940 first_row_error(&[
941 &tuple_count(4),
942 &field_len(4),
943 &1_i32.to_be_bytes(),
944 &field_len(5),
945 b"Sean",
946 ]),
947 "Expected at least 19 bytes but got 18 bytes"
948 );
949 }
950
951 #[test]
952 fn check_field_length_i32_max_returns_error() {
953 assert_eq!(
954 first_row_error(&[&tuple_count(4), &field_len(i32::MAX), b"abc"]),
955 format!(
956 "Expected at least {} bytes but got 9 bytes",
957 6 + i32::MAX as usize
958 )
959 );
960 }
961}