Propagate some decode failures

This commit is contained in:
Ryan Leckey 2019-08-25 14:52:13 -07:00
parent 0d49de2d13
commit 37c22eb55d
17 changed files with 176 additions and 291 deletions

7
.gitignore vendored
View file

@ -1,6 +1,11 @@
.idea/
# Built artifacts
target/
# Lockfile
Cargo.lock
# Project and editor files
.vscode/
.idea/
*.vim
*.vi

View file

@ -1 +1 @@
nightly-2019-08-15
nightly-2019-08-25

View file

@ -1,4 +1,4 @@
#![feature(async_await)]
#![cfg_attr(test, feature(test))]
#![allow(clippy::needless_lifetimes)]
#![allow(unused)]
@ -8,6 +8,9 @@
// #[macro_use]
// extern crate enum_tryfrom_derive;
#[cfg(test)]
extern crate test;
#[macro_use]
mod macros;

View file

@ -69,24 +69,25 @@ impl PostgresRawConnection {
loop {
// Read the message header (id + len)
let mut header = ret_if_none!(self.stream.peek(5).await?);
let id = header.get_int_1()?;
let len = (header.get_int_4()? - 4) as usize;
let id = header.get_u8()?;
let len = (header.get_u32()? - 4) as usize;
// Read the message body
self.stream.consume(5);
let body = ret_if_none!(self.stream.peek(len).await?);
let message = match id {
b'N' | b'E' => Message::Response(Box::new(protocol::Response::decode(body))),
b'D' => Message::DataRow(Box::new(protocol::DataRow::decode(body))),
b'S' => Message::ParameterStatus(Box::new(protocol::ParameterStatus::decode(body))),
b'Z' => Message::ReadyForQuery(protocol::ReadyForQuery::decode(body)),
b'R' => Message::Authentication(Box::new(protocol::Authentication::decode(body))),
b'K' => Message::BackendKeyData(protocol::BackendKeyData::decode(body)),
b'T' => Message::RowDescription(Box::new(protocol::RowDescription::decode(body))),
b'C' => Message::CommandComplete(protocol::CommandComplete::decode(body)),
b'N' | b'E' => Message::Response(Box::new(protocol::Response::decode(body)?)),
b'D' => Message::DataRow(Box::new(protocol::DataRow::decode(body)?)),
b'S' => {
Message::ParameterStatus(Box::new(protocol::ParameterStatus::decode(body)?))
}
b'Z' => Message::ReadyForQuery(protocol::ReadyForQuery::decode(body)?),
b'R' => Message::Authentication(Box::new(protocol::Authentication::decode(body)?)),
b'K' => Message::BackendKeyData(protocol::BackendKeyData::decode(body)?),
b'C' => Message::CommandComplete(protocol::CommandComplete::decode(body)?),
b'A' => Message::NotificationResponse(Box::new(
protocol::NotificationResponse::decode(body),
protocol::NotificationResponse::decode(body)?,
)),
b'1' => Message::ParseComplete,
b'2' => Message::BindComplete,
@ -94,7 +95,7 @@ impl PostgresRawConnection {
b'n' => Message::NoData,
b's' => Message::PortalSuspended,
b't' => Message::ParameterDescription(Box::new(
protocol::ParameterDescription::decode(body),
protocol::ParameterDescription::decode(body)?,
)),
_ => unimplemented!("unknown message id: {}", id as char),

View file

@ -1,4 +1,5 @@
use super::Decode;
use std::io;
#[derive(Debug)]
pub enum Authentication {
@ -38,8 +39,8 @@ pub enum Authentication {
}
impl Decode for Authentication {
fn decode(src: &[u8]) -> Self {
match src[0] {
fn decode(src: &[u8]) -> io::Result<Self> {
Ok(match src[0] {
0 => Authentication::Ok,
2 => Authentication::KerberosV5,
3 => Authentication::CleartextPassword,
@ -56,6 +57,6 @@ impl Decode for Authentication {
9 => Authentication::Sspi,
token => unimplemented!("decode not implemented for token: {}", token),
}
})
}
}

View file

@ -1,5 +1,5 @@
use super::Decode;
use std::convert::TryInto;
use super::{Buf, Decode};
use std::io;
#[derive(Debug)]
pub struct BackendKeyData {
@ -23,16 +23,16 @@ impl BackendKeyData {
}
impl Decode for BackendKeyData {
fn decode(src: &[u8]) -> Self {
fn decode(mut src: &[u8]) -> io::Result<Self> {
debug_assert_eq!(src.len(), 8);
let process_id = u32::from_be_bytes(src[0..4].try_into().unwrap());
let secret_key = u32::from_be_bytes(src[4..8].try_into().unwrap());
let process_id = src.get_u32()?;
let secret_key = src.get_u32()?;
Self {
Ok(Self {
process_id,
secret_key,
}
})
}
}
@ -45,7 +45,7 @@ mod tests {
#[test]
fn it_decodes_backend_key_data() {
let message = BackendKeyData::decode(BACKEND_KEY_DATA);
let message = BackendKeyData::decode(BACKEND_KEY_DATA).unwrap();
assert_eq!(message.process_id(), 10182);
assert_eq!(message.secret_key(), 2303903019);

View file

@ -1,6 +1,6 @@
use super::Decode;
use memchr::memrchr;
use std::str;
use std::{io, str};
#[derive(Debug)]
pub struct CommandComplete {
@ -8,17 +8,21 @@ pub struct CommandComplete {
}
impl Decode for CommandComplete {
fn decode(src: &[u8]) -> Self {
fn decode(src: &[u8]) -> io::Result<Self> {
// Attempt to parse the last word in the command tag as an integer
// If it can't be parased, the tag is probably "CREATE TABLE" or something
// and we should return 0 rows
let rows_start = memrchr(b' ', src).unwrap_or(0);
let rows = unsafe { str::from_utf8_unchecked(&src[(rows_start + 1)..(src.len() - 1)]) };
// TODO: Use [atoi] or similar to parse an integer directly from the bytes
Self {
let rows_start = memrchr(b' ', src).unwrap_or(0);
let mut buf = &src[(rows_start + 1)..(src.len() - 1)];
let rows = unsafe { str::from_utf8_unchecked(buf) };
Ok(Self {
rows: rows.parse().unwrap_or(0),
}
})
}
}
@ -33,28 +37,28 @@ mod tests {
#[test]
fn it_decodes_command_complete_for_insert() {
let message = CommandComplete::decode(COMMAND_COMPLETE_INSERT);
let message = CommandComplete::decode(COMMAND_COMPLETE_INSERT).unwrap();
assert_eq!(message.rows, 1);
}
#[test]
fn it_decodes_command_complete_for_update() {
let message = CommandComplete::decode(COMMAND_COMPLETE_UPDATE);
let message = CommandComplete::decode(COMMAND_COMPLETE_UPDATE).unwrap();
assert_eq!(message.rows, 512);
}
#[test]
fn it_decodes_command_complete_for_begin() {
let message = CommandComplete::decode(COMMAND_COMPLETE_BEGIN);
let message = CommandComplete::decode(COMMAND_COMPLETE_BEGIN).unwrap();
assert_eq!(message.rows, 0);
}
#[test]
fn it_decodes_command_complete_for_create_table() {
let message = CommandComplete::decode(COMMAND_COMPLETE_CREATE_TABLE);
let message = CommandComplete::decode(COMMAND_COMPLETE_CREATE_TABLE).unwrap();
assert_eq!(message.rows, 0);
}

View file

@ -1,7 +1,8 @@
use super::Decode;
use super::{Decode, Buf};
use std::{
convert::TryInto,
fmt::{self, Debug},
io,
pin::Pin,
ptr::NonNull,
};
@ -17,39 +18,30 @@ unsafe impl Send for DataRow {}
unsafe impl Sync for DataRow {}
impl Decode for DataRow {
fn decode(buf: &[u8]) -> Self {
let len: [u8; 2] = buf[..2].try_into().unwrap();
let len = u16::from_be_bytes(len) as usize;
let buffer: Pin<Box<[u8]>> = Pin::new(buf[2..].into());
fn decode(mut buf: &[u8]) -> io::Result<Self> {
let len = buf.get_u16()? as usize;
let buffer: Pin<Box<[u8]>> = Pin::new(buf.into());
let mut buf = &*buffer;
let mut values = Vec::with_capacity(len);
let mut index = 0;
while values.len() < len {
// The length of the column value, in bytes (this count does not include itself).
// Can be zero. As a special case, -1 indicates a NULL column value.
// No value bytes follow in the NULL case.
// TODO: Handle unwrap
let value_len: [u8; 4] = buffer[index..(index + 4)].try_into().unwrap();
let value_len = i32::from_be_bytes(value_len);
index += 4;
let value_len = buf.get_i32()?;
if value_len == -1 {
values.push(None);
} else {
let value_len = value_len as usize;
let value = &buffer[index..(index + value_len)];
index += value_len as usize;
values.push(Some(value.into()));
values.push(Some(buf[..(value_len as usize)].into()));
buf.advance(value_len as usize);
}
}
Self {
Ok(Self {
values: values.into_boxed_slice(),
buffer,
}
})
}
}
@ -87,7 +79,7 @@ mod tests {
#[test]
fn it_decodes_data_row() {
let message = DataRow::decode(DATA_ROW);
let message = DataRow::decode(DATA_ROW).unwrap();
assert_eq!(message.len(), 3);
@ -95,4 +87,9 @@ mod tests {
assert_eq!(message.get(1), Some(&b"2"[..]));
assert_eq!(message.get(2), Some(&b"3"[..]));
}
#[bench]
fn bench_decode_data_row(b: &mut test::Bencher) {
b.iter(|| DataRow::decode(DATA_ROW).unwrap());
}
}

View file

@ -2,7 +2,7 @@ use memchr::memchr;
use std::{convert::TryInto, io, str};
pub trait Decode {
fn decode(src: &[u8]) -> Self
fn decode(src: &[u8]) -> io::Result<Self>
where
Self: Sized;
}
@ -18,22 +18,22 @@ pub(crate) fn get_str(src: &[u8]) -> &str {
pub trait Buf {
fn advance(&mut self, cnt: usize);
// An n-bit integer in network byte order
fn get_int_1(&mut self) -> io::Result<u8>;
fn get_int_4(&mut self) -> io::Result<u32>;
// An n-bit integer in network byte order (IntN)
fn get_u8(&mut self) -> io::Result<u8>;
fn get_u16(&mut self) -> io::Result<u16>;
fn get_i32(&mut self) -> io::Result<i32>;
fn get_u32(&mut self) -> io::Result<u32>;
// A null-terminated string
fn get_str(&mut self) -> io::Result<&str>;
fn get_str_null(&mut self) -> io::Result<&str>;
}
impl<'a> Buf for &'a [u8] {
#[inline]
fn advance(&mut self, cnt: usize) {
*self = &self[cnt..];
}
#[inline]
fn get_int_1(&mut self) -> io::Result<u8> {
fn get_u8(&mut self) -> io::Result<u8> {
let val = self[0];
self.advance(1);
@ -41,9 +41,28 @@ impl<'a> Buf for &'a [u8] {
Ok(val)
}
#[inline]
fn get_int_4(&mut self) -> io::Result<u32> {
let val: [u8; 4] = (*self)
fn get_u16(&mut self) -> io::Result<u16> {
let val: [u8; 2] = (&self[..2])
.try_into()
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
self.advance(2);
Ok(u16::from_be_bytes(val))
}
fn get_i32(&mut self) -> io::Result<i32> {
let val: [u8; 4] = (&self[..4])
.try_into()
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
self.advance(4);
Ok(i32::from_be_bytes(val))
}
fn get_u32(&mut self) -> io::Result<u32> {
let val: [u8; 4] = (&self[..4])
.try_into()
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
@ -52,12 +71,16 @@ impl<'a> Buf for &'a [u8] {
Ok(u32::from_be_bytes(val))
}
fn get_str(&mut self) -> io::Result<&str> {
fn get_str_null(&mut self) -> io::Result<&str> {
let end = memchr(b'\0', &*self).ok_or(io::ErrorKind::InvalidData)?;
let buf = &self[..end];
self.advance(end);
self.advance(end + 1);
str::from_utf8(buf).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
if cfg!(debug_asserts) {
str::from_utf8(buf).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
} else {
Ok(unsafe { str::from_utf8_unchecked(buf) })
}
}
}

View file

@ -1,6 +1,6 @@
use super::{
Authentication, BackendKeyData, CommandComplete, DataRow, Decode, NotificationResponse,
ParameterDescription, ParameterStatus, ReadyForQuery, Response, RowDescription,
ParameterDescription, ParameterStatus, ReadyForQuery, Response,
};
use byteorder::{BigEndian, ByteOrder};
use bytes::BytesMut;
@ -14,7 +14,6 @@ pub enum Message {
BackendKeyData(BackendKeyData),
ReadyForQuery(ReadyForQuery),
CommandComplete(CommandComplete),
RowDescription(Box<RowDescription>),
DataRow(Box<DataRow>),
Response(Box<Response>),
NotificationResponse(Box<NotificationResponse>),

View file

@ -56,7 +56,6 @@ mod parameter_description;
mod parameter_status;
mod ready_for_query;
mod response;
mod row_description;
pub use self::{
authentication::Authentication,
@ -70,5 +69,4 @@ pub use self::{
parameter_status::ParameterStatus,
ready_for_query::ReadyForQuery,
response::Response,
row_description::RowDescription,
};

View file

@ -1,6 +1,6 @@
use super::{decode::get_str, Decode};
use super::{Buf, Decode};
use byteorder::{BigEndian, ByteOrder};
use std::{fmt, pin::Pin, ptr::NonNull};
use std::{fmt, io, pin::Pin, ptr::NonNull};
pub struct NotificationResponse {
#[used]
@ -44,26 +44,22 @@ impl fmt::Debug for NotificationResponse {
}
impl Decode for NotificationResponse {
fn decode(src: &[u8]) -> Self {
let pid = BigEndian::read_u32(&src);
fn decode(mut src: &[u8]) -> io::Result<Self> {
let pid = src.get_u32()?;
// offset from pid=4
let storage = Pin::new(Vec::from(&src[4..]));
let storage = Pin::new(src.into());
let mut src: &[u8] = &*storage;
let channel_name = get_str(&storage);
let channel_name = src.get_str_null()?.into();
let message = src.get_str_null()?.into();
// offset = channel_name.len() + \0
let message = get_str(&storage[(channel_name.len() + 1)..]);
let channel_name = NonNull::from(channel_name);
let message = NonNull::from(message);
Self {
Ok(Self {
storage,
pid,
channel_name,
message,
}
})
}
}
@ -76,7 +72,7 @@ mod tests {
#[test]
fn it_decodes_notification_response() {
let message = NotificationResponse::decode(NOTIFICATION_RESPONSE);
let message = NotificationResponse::decode(NOTIFICATION_RESPONSE).unwrap();
assert_eq!(message.pid(), 0x34201002);
assert_eq!(message.channel_name(), "TEST-CHANNEL");

View file

@ -1,6 +1,6 @@
use super::Decode;
use super::{Buf, Decode};
use byteorder::{BigEndian, ByteOrder};
use std::mem::size_of;
use std::{io, mem::size_of};
type ObjectId = u32;
@ -10,18 +10,17 @@ pub struct ParameterDescription {
}
impl Decode for ParameterDescription {
fn decode(src: &[u8]) -> Self {
let count = BigEndian::read_u16(&*src) as usize;
fn decode(mut src: &[u8]) -> io::Result<Self> {
let count = src.get_u16()?;
let mut ids = Vec::with_capacity(count as usize);
let mut ids = Vec::with_capacity(count);
for i in 0..count {
let offset = i * size_of::<u32>() + size_of::<u16>();
ids.push(BigEndian::read_u32(&src[offset..]));
ids.push(src.get_u32()?);
}
ParameterDescription {
Ok(ParameterDescription {
ids: ids.into_boxed_slice(),
}
})
}
}
@ -33,7 +32,7 @@ mod test {
#[test]
fn it_decodes_parameter_description() {
let src = b"\x00\x02\x00\x00\x00\x00\x00\x00\x05\x00";
let desc = ParameterDescription::decode(src);
let desc = ParameterDescription::decode(src).unwrap();
assert_eq!(desc.ids.len(), 2);
assert_eq!(desc.ids[0], 0x0000_0000);
@ -43,7 +42,7 @@ mod test {
#[test]
fn it_decodes_empty_parameter_description() {
let src = b"\x00\x00";
let desc = ParameterDescription::decode(src);
let desc = ParameterDescription::decode(src).unwrap();
assert_eq!(desc.ids.len(), 0);
}

View file

@ -1,11 +1,11 @@
use super::decode::{get_str, Decode};
use std::{pin::Pin, ptr::NonNull, str};
use super::decode::{Buf, Decode};
use std::{io, pin::Pin, ptr::NonNull, str};
// FIXME: Use &str functions for a custom Debug
#[derive(Debug)]
pub struct ParameterStatus {
#[used]
storage: Pin<Vec<u8>>,
storage: Pin<Box<[u8]>>,
name: NonNull<str>,
value: NonNull<str>,
}
@ -29,20 +29,18 @@ impl ParameterStatus {
}
impl Decode for ParameterStatus {
fn decode(src: &[u8]) -> Self {
let storage = Pin::new(Vec::from(src));
fn decode(src: &[u8]) -> io::Result<Self> {
let storage = Pin::new(src.into());
let mut src: &[u8] = &*storage;
let name = get_str(&storage);
let value = get_str(&storage[name.len() + 1..]);
let name = NonNull::from(src.get_str_null()?);
let value = NonNull::from(src.get_str_null()?);
let name = NonNull::from(name);
let value = NonNull::from(value);
Self {
Ok(Self {
storage,
name,
value,
}
})
}
}
@ -54,9 +52,14 @@ mod tests {
#[test]
fn it_decodes_param_status() {
let message = ParameterStatus::decode(PARAM_STATUS);
let message = ParameterStatus::decode(PARAM_STATUS).unwrap();
assert_eq!(message.name(), "session_authorization");
assert_eq!(message.value(), "postgres");
}
#[bench]
fn bench_decode_param_status(b: &mut test::Bencher) {
b.iter(|| ParameterStatus::decode(PARAM_STATUS).unwrap());
}
}

View file

@ -1,4 +1,5 @@
use super::Decode;
use std::io;
#[derive(Debug, PartialEq, Clone, Copy)]
#[repr(u8)]
@ -27,17 +28,25 @@ impl ReadyForQuery {
}
impl Decode for ReadyForQuery {
fn decode(src: &[u8]) -> Self {
Self {
fn decode(src: &[u8]) -> io::Result<Self> {
Ok(Self {
status: match src[0] {
// FIXME: Variant value are duplicated with declaration
b'I' => TransactionStatus::Idle,
b'T' => TransactionStatus::Transaction,
b'E' => TransactionStatus::Error,
status => panic!("received {:?} for TransactionStatus", status),
status => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"received {:?} for TransactionStatus in ReadyForQuery",
status
),
));
}
},
}
})
}
}
@ -49,7 +58,7 @@ mod tests {
#[test]
fn it_decodes_ready_for_query() {
let message = ReadyForQuery::decode(READY_FOR_QUERY);
let message = ReadyForQuery::decode(READY_FOR_QUERY).unwrap();
assert_eq!(message.status, TransactionStatus::Error);
}

View file

@ -225,7 +225,7 @@ impl fmt::Debug for Response {
}
impl Decode for Response {
fn decode(src: &[u8]) -> Self {
fn decode(src: &[u8]) -> io::Result<Self> {
let storage: Pin<Box<[u8]>> = Pin::new(src.into());
let mut code = None::<&str>;
@ -266,7 +266,7 @@ impl Decode for Response {
}
b'V' => {
severity_non_local = Some(field_value.parse().unwrap());
severity_non_local = Some(field_value.parse()?);
}
b'C' => {
@ -286,11 +286,19 @@ impl Decode for Response {
}
b'P' => {
position = Some(field_value.parse().unwrap());
position = Some(
field_value
.parse()
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?,
);
}
b'p' => {
internal_position = Some(field_value.parse().unwrap());
internal_position = Some(
field_value
.parse()
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?,
);
}
b'q' => {
@ -326,7 +334,11 @@ impl Decode for Response {
}
b'L' => {
line = Some(field_value.parse().unwrap());
line = Some(
field_value
.parse()
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?,
);
}
b'R' => {
@ -360,7 +372,7 @@ impl Decode for Response {
let file = file.map(NonNull::from);
let routine = routine.map(NonNull::from);
Self {
Ok(Self {
storage,
severity,
code,
@ -379,7 +391,7 @@ impl Decode for Response {
line,
position,
internal_position,
}
})
}
}
@ -392,7 +404,7 @@ mod tests {
#[test]
fn it_decodes_response() {
let message = Response::decode(RESPONSE);
let message = Response::decode(RESPONSE).unwrap();
assert_eq!(message.severity(), Severity::Notice);
assert_eq!(message.code(), "42710");

View file

@ -1,165 +0,0 @@
use super::Decode;
use byteorder::{BigEndian, ByteOrder};
use memchr::memchr;
use std::{
mem::size_of_val,
num::{NonZeroI16, NonZeroU32},
str,
};
// TODO: Custom Debug for RowDescription and FieldDescription
/// A descriptive record on a single field received from PostgreSQL.
#[derive(Debug)]
pub struct FieldDescription<'a> {
name: &'a str,
table_oid: Option<NonZeroU32>,
column_attribute_num: Option<NonZeroI16>,
type_oid: u32,
type_size: i16,
type_modifier: i32,
format: i16,
}
impl<'a> FieldDescription<'a> {
#[inline]
pub fn name(&self) -> &'a str {
self.name
}
#[inline]
pub fn table_oid(&self) -> Option<u32> {
self.table_oid.map(Into::into)
}
#[inline]
pub fn column_attribute_num(&self) -> Option<i16> {
self.column_attribute_num.map(Into::into)
}
#[inline]
pub fn type_oid(&self) -> u32 {
self.type_oid
}
#[inline]
pub fn type_size(&self) -> i16 {
self.type_size
}
#[inline]
pub fn type_modifier(&self) -> i32 {
self.type_modifier
}
#[inline]
pub fn format(&self) -> i16 {
self.format
}
}
#[derive(Debug)]
pub struct RowDescription {
// The number of fields in a row (can be zero).
len: u16,
data: Box<[u8]>,
}
impl RowDescription {
pub fn fields(&self) -> FieldDescriptions<'_> {
FieldDescriptions {
rem: self.len,
buf: &self.data,
}
}
}
impl Decode for RowDescription {
fn decode(src: &[u8]) -> Self {
let len = BigEndian::read_u16(&src[..2]);
Self {
len,
data: src[2..].into(),
}
}
}
pub struct FieldDescriptions<'a> {
rem: u16,
buf: &'a [u8],
}
impl<'a> Iterator for FieldDescriptions<'a> {
type Item = FieldDescription<'a>;
fn size_hint(&self) -> (usize, Option<usize>) {
(self.rem as usize, Some(self.rem as usize))
}
fn next(&mut self) -> Option<Self::Item> {
if self.rem == 0 {
return None;
}
let name_end = memchr(0, &self.buf).unwrap();
let mut idx = name_end + 1;
let name = unsafe { str::from_utf8_unchecked(&self.buf[..name_end]) };
let table_oid = BigEndian::read_u32(&self.buf[idx..]);
idx += size_of_val(&table_oid);
let column_attribute_num = BigEndian::read_i16(&self.buf[idx..]);
idx += size_of_val(&column_attribute_num);
let type_oid = BigEndian::read_u32(&self.buf[idx..]);
idx += size_of_val(&type_oid);
let type_size = BigEndian::read_i16(&self.buf[idx..]);
idx += size_of_val(&type_size);
let type_modifier = BigEndian::read_i32(&self.buf[idx..]);
idx += size_of_val(&type_modifier);
let format = BigEndian::read_i16(&self.buf[idx..]);
idx += size_of_val(&format);
self.rem -= 1;
self.buf = &self.buf[idx..];
Some(FieldDescription {
name,
table_oid: NonZeroU32::new(table_oid),
column_attribute_num: NonZeroI16::new(column_attribute_num),
type_oid,
type_size,
type_modifier,
format,
})
}
}
impl<'a> ExactSizeIterator for FieldDescriptions<'a> {}
#[cfg(test)]
mod tests {
use super::{Decode, RowDescription};
const ROW_DESC: &[u8] = b"\0\x03?column?\0\0\0\0\0\0\0\0\0\0\x17\0\x04\xff\xff\xff\xff\0\0?column?\0\0\0\0\0\0\0\0\0\0\x17\0\x04\xff\xff\xff\xff\0\0?column?\0\0\0\0\0\0\0\0\0\0\x17\0\x04\xff\xff\xff\xff\0\0";
#[test]
fn it_decodes_row_description() {
let message = RowDescription::decode(ROW_DESC);
assert_eq!(message.fields().len(), 3);
for field in message.fields() {
assert_eq!(field.name(), "?column?");
assert_eq!(field.table_oid(), None);
assert_eq!(field.column_attribute_num(), None);
assert_eq!(field.type_oid(), 23);
assert_eq!(field.type_size(), 4);
assert_eq!(field.type_modifier(), -1);
assert_eq!(field.format(), 0);
}
}
}