#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct BinaryCopyWriter {
buf: Vec<u8>,
column_count: i16,
header_written: bool,
}
impl BinaryCopyWriter {
pub fn new(column_count: i16) -> Self {
Self {
buf: Vec::new(),
column_count,
header_written: false,
}
}
pub fn column_count(&self) -> i16 {
self.column_count
}
pub fn header(&mut self) -> &[u8] {
if self.header_written {
return &[];
}
self.buf.clear();
self.buf.extend_from_slice(b"PGCOPY\n\xff\r\n\0");
self.buf.extend_from_slice(&0i32.to_be_bytes());
self.buf.extend_from_slice(&0i32.to_be_bytes());
self.header_written = true;
&self.buf
}
pub fn write_row(&mut self, values: &[Option<&[u8]>]) -> &[u8] {
assert_eq!(
values.len() as i16,
self.column_count,
"value count must match column count"
);
let start = self.buf.len();
self.buf.extend_from_slice(&self.column_count.to_be_bytes());
for val in values {
match val {
Some(data) => {
self.buf
.extend_from_slice(&(data.len() as i32).to_be_bytes());
self.buf.extend_from_slice(data);
}
None => {
self.buf.extend_from_slice(&(-1i32).to_be_bytes());
}
}
}
&self.buf[start..]
}
pub fn trailer(&mut self) -> &[u8] {
let start = self.buf.len();
self.buf.extend_from_slice(&(-1i16).to_be_bytes());
&self.buf[start..]
}
pub fn into_inner(self) -> Vec<u8> {
self.buf
}
pub fn buffer(&self) -> &[u8] {
&self.buf
}
pub fn clear(&mut self) {
self.buf.clear();
self.header_written = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_binary_header() {
let mut writer = BinaryCopyWriter::new(2);
let header = writer.header();
assert_eq!(header.len(), 11 + 4 + 4); assert_eq!(&header[..11], b"PGCOPY\n\xff\r\n\0");
assert_eq!(&header[11..15], &[0, 0, 0, 0]); assert_eq!(&header[15..19], &[0, 0, 0, 0]); }
#[test]
fn test_binary_row() {
let mut writer = BinaryCopyWriter::new(2);
let _header = writer.header();
let row = writer.write_row(&[Some(b"42"), Some(b"hello")]);
assert_eq!(&row[..2], &[0, 2]);
assert_eq!(&row[2..6], &[0, 0, 0, 2]);
assert_eq!(&row[6..8], b"42");
assert_eq!(&row[8..12], &[0, 0, 0, 5]);
assert_eq!(&row[12..17], b"hello");
}
#[test]
fn test_binary_null() {
let mut writer = BinaryCopyWriter::new(1);
let _header = writer.header();
let row = writer.write_row(&[None]);
assert_eq!(&row[..2], &[0, 1]);
assert_eq!(&row[2..6], &[0xff, 0xff, 0xff, 0xff]);
}
#[test]
fn test_binary_trailer() {
let mut writer = BinaryCopyWriter::new(1);
let trailer = writer.trailer();
assert_eq!(trailer.len(), 2);
assert_eq!(i16::from_be_bytes([trailer[0], trailer[1]]), -1);
}
#[test]
fn test_binary_roundtrip_structure() {
let mut writer = BinaryCopyWriter::new(3);
let header = writer.header().to_vec();
let row1 = writer
.write_row(&[Some(b"1"), Some(b"alice"), None])
.to_vec();
let row2 = writer
.write_row(&[Some(b"2"), Some(b"bob"), Some(b"extra")])
.to_vec();
let trailer = writer.trailer().to_vec();
let mut all = Vec::new();
all.extend_from_slice(&header);
all.extend_from_slice(&row1);
all.extend_from_slice(&row2);
all.extend_from_slice(&trailer);
assert_eq!(&all[..11], b"PGCOPY\n\xff\r\n\0");
let row1_offset = 19;
assert_eq!(
i16::from_be_bytes([all[row1_offset], all[row1_offset + 1]]),
3
);
let last_two = &all[all.len() - 2..];
assert_eq!(i16::from_be_bytes([last_two[0], last_two[1]]), -1);
}
#[test]
#[should_panic(expected = "value count must match column count")]
fn test_wrong_column_count_panics() {
let mut writer = BinaryCopyWriter::new(2);
writer.write_row(&[Some(b"only_one")]);
}
}