mirror of
https://github.com/launchbadge/sqlx
synced 2024-11-10 14:34:19 +00:00
Propagate some decode failures
This commit is contained in:
parent
0d49de2d13
commit
37c22eb55d
17 changed files with 176 additions and 291 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,6 +1,11 @@
|
|||
.idea/
|
||||
# Built artifacts
|
||||
target/
|
||||
|
||||
# Lockfile
|
||||
Cargo.lock
|
||||
|
||||
# Project and editor files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.vim
|
||||
*.vi
|
||||
|
|
|
@ -1 +1 @@
|
|||
nightly-2019-08-15
|
||||
nightly-2019-08-25
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>),
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue