Rename NoticeResponse to Response; clean up tests

This commit is contained in:
Ryan Leckey 2019-06-21 22:47:15 -07:00
parent 3fa880f5f3
commit d3121c60cf
6 changed files with 263 additions and 297 deletions

View File

@ -3,24 +3,15 @@ extern crate criterion;
use bytes::Bytes; use bytes::Bytes;
use criterion::{black_box, Criterion}; use criterion::{black_box, Criterion};
use mason_postgres_protocol::{Decode, NoticeResponse}; use mason_postgres_protocol::{Decode, Response};
fn criterion_benchmark(c: &mut Criterion) { fn criterion_benchmark(c: &mut Criterion) {
// NOTE: This is sans header (for direct decoding) // NOTE: This is sans header (for direct decoding)
const NOTICE_RESPONSE: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0"; const NOTICE_RESPONSE: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
c.bench_function("decode NoticeResponse", |b| { c.bench_function("decode Response", |b| {
b.iter(|| { b.iter(|| {
let _ = NoticeResponse::decode(black_box(Bytes::from_static(NOTICE_RESPONSE))).unwrap(); let _ = Response::decode(black_box(Bytes::from_static(NOTICE_RESPONSE))).unwrap();
})
});
c.bench_function("decode NoticeResponse and NoticeResponseFields", |b| {
b.iter(|| {
let _ = NoticeResponse::decode(black_box(Bytes::from_static(NOTICE_RESPONSE)))
.unwrap()
.fields()
.unwrap();
}) })
}); });
} }

View File

@ -1,28 +1,25 @@
#[macro_use] #[macro_use]
extern crate criterion; extern crate criterion;
use criterion::{Criterion}; use criterion::Criterion;
use mason_postgres_protocol::{Encode, NoticeResponse, Severity}; use mason_postgres_protocol::{Encode, Response, Severity};
fn criterion_benchmark(c: &mut Criterion) { fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("encode NoticeResponse", c.bench_function("encode Response(Builder)", |b| {
|b| { let mut dst = Vec::new();
let mut dst = Vec::new(); b.iter(|| {
b.iter(|| { dst.truncate(0);
let message = NoticeResponse::builder() Response::builder()
.severity(Severity::Notice) .severity(Severity::Notice)
.code("42710") .code("42710")
.message("extension \"uuid-ossp\" already exists, skipping") .message("extension \"uuid-ossp\" already exists, skipping")
.file("extension.c") .file("extension.c")
.line(1656) .line(1656)
.routine("CreateExtension") .routine("CreateExtension")
.build(); .encode(&mut dst)
.unwrap();
dst.truncate(0); })
message.encode(&mut dst).unwrap(); });
})
}
);
} }
criterion_group!(benches, criterion_benchmark); criterion_group!(benches, criterion_benchmark);

View File

@ -5,13 +5,13 @@ mod backend_key_data;
mod decode; mod decode;
mod encode; mod encode;
mod message; mod message;
mod notice_response;
mod ready_for_query; mod ready_for_query;
mod response;
pub use self::{ pub use self::{
decode::Decode, decode::Decode,
encode::Encode, encode::Encode,
message::Message, message::Message,
notice_response::{NoticeResponse, NoticeResponseBuilder, NoticeResponseFields, Severity},
ready_for_query::{ReadyForQuery, TransactionStatus}, ready_for_query::{ReadyForQuery, TransactionStatus},
response::{Response, ResponseBuilder, Severity},
}; };

View File

@ -1,4 +1,4 @@
use crate::{Decode, Encode, NoticeResponse, ReadyForQuery}; use crate::{Decode, Encode, ReadyForQuery, Response};
use byteorder::{BigEndian, ReadBytesExt}; use byteorder::{BigEndian, ReadBytesExt};
use bytes::Bytes; use bytes::Bytes;
use std::io::{self, Cursor}; use std::io::{self, Cursor};
@ -6,21 +6,21 @@ use std::io::{self, Cursor};
#[derive(Debug)] #[derive(Debug)]
pub enum Message { pub enum Message {
ReadyForQuery(ReadyForQuery), ReadyForQuery(ReadyForQuery),
NoticeResponse(NoticeResponse), Response(Response),
} }
impl Encode for Message { impl Encode for Message {
fn size_hint(&self) -> usize { fn size_hint(&self) -> usize {
match self { match self {
Message::ReadyForQuery(body) => body.size_hint(), Message::ReadyForQuery(body) => body.size_hint(),
Message::NoticeResponse(body) => body.size_hint(), Message::Response(body) => body.size_hint(),
} }
} }
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> { fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> {
match self { match self {
Message::ReadyForQuery(body) => body.encode(buf), Message::ReadyForQuery(body) => body.encode(buf),
Message::NoticeResponse(body) => body.encode(buf), Message::Response(body) => body.encode(buf),
} }
} }
} }
@ -41,7 +41,7 @@ impl Decode for Message {
Ok(match token { Ok(match token {
// FIXME: These tokens are duplicated here and in the respective encode functions // FIXME: These tokens are duplicated here and in the respective encode functions
b'N' => Message::NoticeResponse(NoticeResponse::decode(src)?), b'N' | b'E' => Message::Response(Response::decode(src)?),
b'Z' => Message::ReadyForQuery(ReadyForQuery::decode(src)?), b'Z' => Message::ReadyForQuery(ReadyForQuery::decode(src)?),
_ => unimplemented!("decode not implemented for token: {}", token as char), _ => unimplemented!("decode not implemented for token: {}", token as char),

View File

@ -59,32 +59,30 @@ impl Decode for ReadyForQuery {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{ReadyForQuery, TransactionStatus}; use super::{ReadyForQuery, TransactionStatus};
use crate::{Decode, Encode, Message}; use crate::{Decode, Encode};
use bytes::Bytes; use bytes::Bytes;
use std::io; use std::io;
const READY_FOR_QUERY: &[u8] = b"E";
#[test] #[test]
fn it_encodes_ready_for_query() -> io::Result<()> { fn it_encodes_ready_for_query() -> io::Result<()> {
let message = ReadyForQuery { status: TransactionStatus::Error }; let message = ReadyForQuery { status: TransactionStatus::Error };
assert_eq!(&*message.to_bytes()?, &b"Z\0\0\0\x05E"[..]);
let mut dst = Vec::with_capacity(message.size_hint());
message.encode(&mut dst)?;
assert_eq!(&dst[5..], READY_FOR_QUERY);
Ok(()) Ok(())
} }
#[test] #[test]
fn it_decodes_ready_for_query() -> io::Result<()> { fn it_decodes_ready_for_query() -> io::Result<()> {
// FIXME: A test-utils type thing could be useful here as these 7 lines are quite.. let src = Bytes::from_static(READY_FOR_QUERY);
// duplicated let message = ReadyForQuery::decode(src)?;
let b = Bytes::from_static(b"Z\0\0\0\x05E"); assert_eq!(message.status, TransactionStatus::Error);
let message = Message::decode(b)?;
let body = if let Message::ReadyForQuery(body) = message {
body
} else {
unreachable!();
};
assert_eq!(body.status, TransactionStatus::Error);
Ok(()) Ok(())
} }

View File

@ -2,14 +2,13 @@ use crate::{decode::get_str, Decode, Encode};
use byteorder::{BigEndian, WriteBytesExt}; use byteorder::{BigEndian, WriteBytesExt};
use bytes::Bytes; use bytes::Bytes;
use std::{ use std::{
borrow::Cow,
fmt, fmt,
io::{self, Write}, io::{self, Write},
ops::Range,
pin::Pin, pin::Pin,
ptr::NonNull, ptr::NonNull,
str::{self, FromStr}, str::{self, FromStr},
}; };
use core::fmt::Debug;
#[derive(Debug, PartialEq, PartialOrd, Copy, Clone)] #[derive(Debug, PartialEq, PartialOrd, Copy, Clone)]
pub enum Severity { pub enum Severity {
@ -24,6 +23,25 @@ pub enum Severity {
} }
impl Severity { impl Severity {
pub fn is_error(&self) -> bool {
match self {
Severity::Panic | Severity::Fatal | Severity::Error => true,
_ => false,
}
}
pub fn is_notice(&self) -> bool {
match self {
Severity::Warning
| Severity::Notice
| Severity::Debug
| Severity::Info
| Severity::Log => true,
_ => false,
}
}
pub fn to_str(&self) -> &'static str { pub fn to_str(&self) -> &'static str {
match self { match self {
Severity::Panic => "PANIC", Severity::Panic => "PANIC",
@ -59,57 +77,8 @@ impl FromStr for Severity {
} }
} }
// NOTE: `NoticeResponse` is lazily decoded on access to `.fields()`
#[derive(Clone)] #[derive(Clone)]
pub struct NoticeResponse(Bytes); pub struct Response {
impl NoticeResponse {
#[inline]
pub fn builder() -> NoticeResponseBuilder<'static> {
NoticeResponseBuilder::new()
}
#[inline]
pub fn fields(self) -> io::Result<NoticeResponseFields> {
NoticeResponseFields::decode(self.0)
}
}
impl fmt::Debug for NoticeResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Proxy format to the results of decoding the fields
self.clone().fields().fmt(f)
}
}
impl Encode for NoticeResponse {
#[inline]
fn size_hint(&self) -> usize {
self.0.len() + 5
}
#[inline]
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> {
buf.write_u8(b'Z')?;
buf.write_u32::<BigEndian>((4 + self.0.len()) as u32)?;
buf.write_all(&self.0)?;
Ok(())
}
}
impl Decode for NoticeResponse {
#[inline]
fn decode(src: Bytes) -> io::Result<Self>
where
Self: Sized,
{
// NOTE: Further decoding is delayed until `.fields()`
Ok(Self(src))
}
}
pub struct NoticeResponseFields {
#[used] #[used]
storage: Pin<Bytes>, storage: Pin<Bytes>,
severity: Severity, severity: Severity,
@ -132,10 +101,15 @@ pub struct NoticeResponseFields {
} }
// SAFE: Raw pointers point to pinned memory inside the struct // SAFE: Raw pointers point to pinned memory inside the struct
unsafe impl Send for NoticeResponseFields {} unsafe impl Send for Response {}
unsafe impl Sync for NoticeResponseFields {} unsafe impl Sync for Response {}
impl Response {
#[inline]
pub fn builder() -> ResponseBuilder {
ResponseBuilder::new()
}
impl NoticeResponseFields {
#[inline] #[inline]
pub fn severity(&self) -> Severity { pub fn severity(&self) -> Severity {
self.severity self.severity
@ -235,9 +209,9 @@ impl NoticeResponseFields {
} }
} }
impl fmt::Debug for NoticeResponseFields { impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("NoticeResponseFields") f.debug_struct("Response")
.field("severity", &self.severity) .field("severity", &self.severity)
.field("code", &self.code()) .field("code", &self.code())
.field("message", &self.message()) .field("message", &self.message())
@ -259,7 +233,27 @@ impl fmt::Debug for NoticeResponseFields {
} }
} }
impl Decode for NoticeResponseFields { impl Encode for Response {
#[inline]
fn size_hint(&self) -> usize {
self.storage.len() + 5
}
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> {
if self.severity.is_error() {
buf.push(b'E');
} else {
buf.push(b'N');
}
buf.write_u32::<BigEndian>((4 + self.storage.len()) as u32)?;
buf.write_all(&self.storage)?;
Ok(())
}
}
impl Decode for Response {
fn decode(src: Bytes) -> io::Result<Self> { fn decode(src: Bytes) -> io::Result<Self> {
let storage = Pin::new(src); let storage = Pin::new(src);
@ -371,7 +365,7 @@ impl Decode for NoticeResponseFields {
_ => { _ => {
unimplemented!( unimplemented!(
"error/notice message field {:?} not implemented", "response message field {:?} not implemented",
field_type as char field_type as char
); );
} }
@ -380,10 +374,10 @@ impl Decode for NoticeResponseFields {
let severity = severity_non_local let severity = severity_non_local
.or_else(move || severity?.parse().ok()) .or_else(move || severity?.parse().ok())
.expect("required by protocol"); .expect("`severity` required by protocol");
let code = NonNull::from(code.expect("required by protocol")); let code = NonNull::from(code.expect("`code` required by protocol"));
let message = NonNull::from(message.expect("required by protocol")); let message = NonNull::from(message.expect("`message` required by protocol"));
let detail = detail.map(NonNull::from); let detail = detail.map(NonNull::from);
let hint = hint.map(NonNull::from); let hint = hint.map(NonNull::from);
let internal_query = internal_query.map(NonNull::from); let internal_query = internal_query.map(NonNull::from);
@ -419,32 +413,35 @@ impl Decode for NoticeResponseFields {
} }
} }
pub struct NoticeResponseBuilder<'a> { pub struct ResponseBuilder {
severity: Severity, storage: Vec<u8>,
code: Cow<'a, str>, severity: Option<Severity>,
message: Cow<'a, str>, code: Option<Range<usize>>,
detail: Option<Cow<'a, str>>, message: Option<Range<usize>>,
hint: Option<Cow<'a, str>>, detail: Option<Range<usize>>,
hint: Option<Range<usize>>,
position: Option<usize>, position: Option<usize>,
internal_position: Option<usize>, internal_position: Option<usize>,
internal_query: Option<Cow<'a, str>>, internal_query: Option<Range<usize>>,
where_: Option<Cow<'a, str>>, where_: Option<Range<usize>>,
schema: Option<Cow<'a, str>>, schema: Option<Range<usize>>,
table: Option<Cow<'a, str>>, table: Option<Range<usize>>,
column: Option<Cow<'a, str>>, column: Option<Range<usize>>,
data_type: Option<Cow<'a, str>>, data_type: Option<Range<usize>>,
constraint: Option<Cow<'a, str>>, constraint: Option<Range<usize>>,
file: Option<Cow<'a, str>>, file: Option<Range<usize>>,
line: Option<usize>, line: Option<usize>,
routine: Option<Cow<'a, str>>, routine: Option<Range<usize>>,
} }
impl Default for NoticeResponseBuilder<'_> { impl Default for ResponseBuilder {
fn default() -> Self { fn default() -> Self {
Self { Self {
severity: Severity::Notice, // FIXME: Remove this allocation (on the quest for zero-allocation)
message: Cow::Borrowed(""), storage: Vec::with_capacity(128),
code: Cow::Borrowed("XX000"), // internal_error severity: None,
message: None,
code: None,
detail: None, detail: None,
hint: None, hint: None,
position: None, position: None,
@ -463,240 +460,207 @@ impl Default for NoticeResponseBuilder<'_> {
} }
} }
impl<'a> NoticeResponseBuilder<'a> { fn put_str(buf: &mut Vec<u8>, tag: u8, value: impl AsRef<str>) -> Range<usize> {
buf.push(tag);
let beg = buf.len();
buf.extend_from_slice(value.as_ref().as_bytes());
let end = buf.len();
buf.push(0);
beg..end
}
impl ResponseBuilder {
#[inline] #[inline]
pub fn new() -> NoticeResponseBuilder<'a> { pub fn new() -> ResponseBuilder {
Self::default() Self::default()
} }
#[inline] #[inline]
pub fn severity(&mut self, severity: Severity) -> &mut Self { pub fn severity(mut self, severity: Severity) -> Self {
self.severity = severity; let sev = severity.to_str();
let _ = put_str(&mut self.storage, b'S', sev);
let _ = put_str(&mut self.storage, b'V', sev);
self.severity = Some(severity);
self self
} }
#[inline] #[inline]
pub fn message(&mut self, message: impl Into<Cow<'a, str>>) -> &mut Self { pub fn message(mut self, message: impl AsRef<str>) -> Self {
self.message = message.into(); self.message = Some(put_str(&mut self.storage, b'M', message));
self self
} }
#[inline] #[inline]
pub fn code(&mut self, code: impl Into<Cow<'a, str>>) -> &mut Self { pub fn code(mut self, code: impl AsRef<str>) -> Self {
self.code = code.into(); self.code = Some(put_str(&mut self.storage, b'C', code));
self self
} }
#[inline] #[inline]
pub fn detail(&mut self, detail: impl Into<Cow<'a, str>>) -> &mut Self { pub fn detail(mut self, detail: impl AsRef<str>) -> Self {
self.detail = Some(detail.into()); self.detail = Some(put_str(&mut self.storage, b'D', detail));
self self
} }
#[inline] #[inline]
pub fn hint(&mut self, hint: impl Into<Cow<'a, str>>) -> &mut Self { pub fn hint(mut self, hint: impl AsRef<str>) -> Self {
self.hint = Some(hint.into()); self.hint = Some(put_str(&mut self.storage, b'H', hint));
self self
} }
#[inline] #[inline]
pub fn position(&mut self, position: usize) -> &mut Self { pub fn position(mut self, position: usize) -> Self {
self.storage.push(b'P');
// PANIC: Write to Vec<u8> is infallible
itoa::write(&mut self.storage, position).unwrap();
self.storage.push(0);
self.position = Some(position); self.position = Some(position);
self self
} }
#[inline] #[inline]
pub fn internal_position(&mut self, position: usize) -> &mut Self { pub fn internal_position(mut self, position: usize) -> Self {
self.storage.push(b'p');
// PANIC: Write to Vec<u8> is infallible
itoa::write(&mut self.storage, position).unwrap();
self.storage.push(0);
self.internal_position = Some(position); self.internal_position = Some(position);
self self
} }
#[inline] #[inline]
pub fn internal_query(&mut self, query: impl Into<Cow<'a, str>>) -> &mut Self { pub fn internal_query(mut self, query: impl AsRef<str>) -> Self {
self.internal_query = Some(query.into()); self.internal_query = Some(put_str(&mut self.storage, b'q', query));
self self
} }
#[inline] #[inline]
pub fn where_(&mut self, where_: impl Into<Cow<'a, str>>) -> &mut Self { pub fn where_(mut self, where_: impl AsRef<str>) -> Self {
self.where_ = Some(where_.into()); self.where_ = Some(put_str(&mut self.storage, b'w', where_));
self self
} }
#[inline] #[inline]
pub fn schema(&mut self, schema: impl Into<Cow<'a, str>>) -> &mut Self { pub fn schema(mut self, schema: impl AsRef<str>) -> Self {
self.schema = Some(schema.into()); self.schema = Some(put_str(&mut self.storage, b's', schema));
self self
} }
#[inline] #[inline]
pub fn table(&mut self, table: impl Into<Cow<'a, str>>) -> &mut Self { pub fn table(mut self, table: impl AsRef<str>) -> Self {
self.table = Some(table.into()); self.table = Some(put_str(&mut self.storage, b't', table));
self self
} }
#[inline] #[inline]
pub fn column(&mut self, column: impl Into<Cow<'a, str>>) -> &mut Self { pub fn column(mut self, column: impl AsRef<str>) -> Self {
self.column = Some(column.into()); self.column = Some(put_str(&mut self.storage, b'c', column));
self self
} }
#[inline] #[inline]
pub fn data_type(&mut self, data_type: impl Into<Cow<'a, str>>) -> &mut Self { pub fn data_type(mut self, data_type: impl AsRef<str>) -> Self {
self.data_type = Some(data_type.into()); self.data_type = Some(put_str(&mut self.storage, b'd', data_type));
self self
} }
#[inline] #[inline]
pub fn constraint(&mut self, constraint: impl Into<Cow<'a, str>>) -> &mut Self { pub fn constraint(mut self, constraint: impl AsRef<str>) -> Self {
self.constraint = Some(constraint.into()); self.constraint = Some(put_str(&mut self.storage, b'n', constraint));
self self
} }
#[inline] #[inline]
pub fn file(&mut self, file: impl Into<Cow<'a, str>>) -> &mut Self { pub fn file(mut self, file: impl AsRef<str>) -> Self {
self.file = Some(file.into()); self.file = Some(put_str(&mut self.storage, b'F', file));
self self
} }
#[inline] #[inline]
pub fn line(&mut self, line: usize) -> &mut Self { pub fn line(mut self, line: usize) -> Self {
self.storage.push(b'L');
// PANIC: Write to Vec<u8> is infallible
itoa::write(&mut self.storage, line).unwrap();
self.storage.push(0);
self.line = Some(line); self.line = Some(line);
self self
} }
#[inline] #[inline]
pub fn routine(&mut self, routine: impl Into<Cow<'a, str>>) -> &mut Self { pub fn routine(mut self, routine: impl AsRef<str>) -> Self {
self.routine = Some(routine.into()); self.routine = Some(put_str(&mut self.storage, b'R', routine));
self self
} }
#[inline] pub fn build(mut self) -> Response {
pub fn build(&self) -> NoticeResponse { // Add a \0 terminator
let mut buf = Vec::new(); self.storage.push(0);
// FIXME: Should Encode even be fallible? // Freeze the storage and Pin so we can self-reference it
// PANIC: Cannot fail let storage = Pin::new(Bytes::from(self.storage));
self.encode(&mut buf).unwrap();
NoticeResponse(Bytes::from(buf)) let make_str_ref = |val: Option<Range<usize>>| unsafe {
val.map(|r| NonNull::from(str::from_utf8_unchecked(&storage[r])))
};
let code = make_str_ref(self.code);
let message = make_str_ref(self.message);
let detail = make_str_ref(self.detail);
let hint = make_str_ref(self.hint);
let internal_query = make_str_ref(self.internal_query);
let where_ = make_str_ref(self.where_);
let schema = make_str_ref(self.schema);
let table = make_str_ref(self.table);
let column = make_str_ref(self.column);
let data_type = make_str_ref(self.data_type);
let constraint = make_str_ref(self.constraint);
let file = make_str_ref(self.file);
let routine = make_str_ref(self.routine);
Response {
storage,
// FIXME: Default and don't panic here
severity: self.severity.expect("`severity` required by protocol"),
code: code.expect("`code` required by protocol"),
message: message.expect("`message` required by protocol"),
detail,
hint,
internal_query,
where_,
schema,
table,
column,
data_type,
constraint,
file,
routine,
line: self.line,
position: self.position,
internal_position: self.internal_position,
}
} }
} }
impl<'a> Encode for NoticeResponseBuilder<'a> { impl Encode for ResponseBuilder {
#[inline]
fn size_hint(&self) -> usize { fn size_hint(&self) -> usize {
// Too variable to measure efficiently self.storage.len() + 6
0
} }
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> { fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> {
// Severity and Localized Severity (required) if self.severity.as_ref().map_or(false, |s| s.is_error()) {
let sev = self.severity.to_str().as_bytes(); buf.push(b'E');
buf.push(b'S'); } else {
buf.write_all(sev)?; buf.push(b'N');
buf.push(0);
buf.push(b'V');
buf.write_all(sev)?;
buf.push(0);
// Code (required)
buf.push(b'C');
buf.write_all(self.code.as_bytes())?;
buf.push(0);
// Message (required)
buf.push(b'M');
buf.write_all(self.message.as_bytes())?;
buf.push(0);
// All remaining fields are optional and
// should be encoded if present
if let Some(detail) = &self.detail {
buf.push(b'D');
buf.write_all(detail.as_bytes())?;
buf.push(0);
} }
if let Some(hint) = &self.hint { buf.write_u32::<BigEndian>((5 + self.storage.len()) as u32)?;
buf.push(b'H'); buf.write_all(&self.storage)?;
buf.write_all(hint.as_bytes())?;
buf.push(0);
}
if let Some(position) = &self.position {
buf.push(b'P');
itoa::write(&mut *buf, *position)?;
buf.push(0);
}
if let Some(internal_position) = &self.internal_position {
buf.push(b'p');
itoa::write(&mut *buf, *internal_position)?;
buf.push(0);
}
if let Some(internal_query) = &self.internal_query {
buf.push(b'q');
buf.write_all(internal_query.as_bytes())?;
buf.push(0);
}
if let Some(where_) = &self.where_ {
buf.push(b'w');
buf.write_all(where_.as_bytes())?;
buf.push(0);
}
if let Some(schema) = &self.schema {
buf.push(b's');
buf.write_all(schema.as_bytes())?;
buf.push(0);
}
if let Some(table) = &self.table {
buf.push(b't');
buf.write_all(table.as_bytes())?;
buf.push(0);
}
if let Some(column) = &self.column {
buf.push(b'c');
buf.write_all(column.as_bytes())?;
buf.push(0);
}
if let Some(data_type) = &self.data_type {
buf.push(b'd');
buf.write_all(data_type.as_bytes())?;
buf.push(0);
}
if let Some(constraint) = &self.constraint {
buf.push(b'n');
buf.write_all(constraint.as_bytes())?;
buf.push(0);
}
if let Some(file) = &self.file {
buf.push(b'F');
buf.write_all(file.as_bytes())?;
buf.push(0);
}
if let Some(line) = &self.line {
buf.push(b'L');
itoa::write(&mut *buf, *line)?;
buf.push(0);
}
if let Some(routine) = &self.routine {
buf.push(b'R');
buf.write_all(routine.as_bytes())?;
buf.push(0);
}
// After the final field, there is a nul terminator
buf.push(0); buf.push(0);
Ok(()) Ok(())
@ -705,18 +669,17 @@ impl<'a> Encode for NoticeResponseBuilder<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{NoticeResponse, Severity}; use super::{Response, Severity};
use crate::{Decode, Encode}; use crate::{Decode, Encode};
use bytes::Bytes; use bytes::Bytes;
use std::io; use std::io;
const NOTICE_RESPONSE: &[u8] = const RESPONSE: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, \
b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, \
skipping\0Fextension.c\0L1656\0RCreateExtension\0\0"; skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
#[test] #[test]
fn it_encodes_notice_response() -> io::Result<()> { fn it_encodes_response() -> io::Result<()> {
let message = NoticeResponse::builder() let message = Response::builder()
.severity(Severity::Notice) .severity(Severity::Notice)
.code("42710") .code("42710")
.message("extension \"uuid-ossp\" already exists, skipping") .message("extension \"uuid-ossp\" already exists, skipping")
@ -728,23 +691,40 @@ mod tests {
let mut dst = Vec::with_capacity(message.size_hint()); let mut dst = Vec::with_capacity(message.size_hint());
message.encode(&mut dst)?; message.encode(&mut dst)?;
assert_eq!(&dst[5..], NOTICE_RESPONSE); assert_eq!(&dst[5..], RESPONSE);
Ok(()) Ok(())
} }
#[test] #[test]
fn it_decodes_notice_response() -> io::Result<()> { fn it_encodes_response_builder() -> io::Result<()> {
let src = Bytes::from_static(NOTICE_RESPONSE); let message = Response::builder()
let message = NoticeResponse::decode(src)?; .severity(Severity::Notice)
let fields = message.fields()?; .code("42710")
.message("extension \"uuid-ossp\" already exists, skipping")
.file("extension.c")
.line(1656)
.routine("CreateExtension");
assert_eq!(fields.severity(), Severity::Notice); let mut dst = Vec::with_capacity(message.size_hint());
assert_eq!(fields.message(), "extension \"uuid-ossp\" already exists, skipping"); message.encode(&mut dst)?;
assert_eq!(fields.code(), "42710");
assert_eq!(fields.file(), Some("extension.c")); assert_eq!(&dst[5..], RESPONSE);
assert_eq!(fields.line(), Some(1656));
assert_eq!(fields.routine(), Some("CreateExtension")); Ok(())
}
#[test]
fn it_decodes_response() -> io::Result<()> {
let src = Bytes::from_static(RESPONSE);
let message = Response::decode(src)?;
assert_eq!(message.severity(), Severity::Notice);
assert_eq!(message.message(), "extension \"uuid-ossp\" already exists, skipping");
assert_eq!(message.code(), "42710");
assert_eq!(message.file(), Some("extension.c"));
assert_eq!(message.line(), Some(1656));
assert_eq!(message.routine(), Some("CreateExtension"));
Ok(()) Ok(())
} }