mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-10-03 15:55:45 +00:00
Rename NoticeResponse to Response; clean up tests
This commit is contained in:
parent
3fa880f5f3
commit
d3121c60cf
@ -3,24 +3,15 @@ extern crate criterion;
|
||||
|
||||
use bytes::Bytes;
|
||||
use criterion::{black_box, Criterion};
|
||||
use mason_postgres_protocol::{Decode, NoticeResponse};
|
||||
use mason_postgres_protocol::{Decode, Response};
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
// 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";
|
||||
|
||||
c.bench_function("decode NoticeResponse", |b| {
|
||||
c.bench_function("decode Response", |b| {
|
||||
b.iter(|| {
|
||||
let _ = NoticeResponse::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();
|
||||
let _ = Response::decode(black_box(Bytes::from_static(NOTICE_RESPONSE))).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -1,28 +1,25 @@
|
||||
#[macro_use]
|
||||
extern crate criterion;
|
||||
|
||||
use criterion::{Criterion};
|
||||
use mason_postgres_protocol::{Encode, NoticeResponse, Severity};
|
||||
use criterion::Criterion;
|
||||
use mason_postgres_protocol::{Encode, Response, Severity};
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("encode NoticeResponse",
|
||||
|b| {
|
||||
let mut dst = Vec::new();
|
||||
b.iter(|| {
|
||||
let message = NoticeResponse::builder()
|
||||
.severity(Severity::Notice)
|
||||
.code("42710")
|
||||
.message("extension \"uuid-ossp\" already exists, skipping")
|
||||
.file("extension.c")
|
||||
.line(1656)
|
||||
.routine("CreateExtension")
|
||||
.build();
|
||||
|
||||
dst.truncate(0);
|
||||
message.encode(&mut dst).unwrap();
|
||||
})
|
||||
}
|
||||
);
|
||||
c.bench_function("encode Response(Builder)", |b| {
|
||||
let mut dst = Vec::new();
|
||||
b.iter(|| {
|
||||
dst.truncate(0);
|
||||
Response::builder()
|
||||
.severity(Severity::Notice)
|
||||
.code("42710")
|
||||
.message("extension \"uuid-ossp\" already exists, skipping")
|
||||
.file("extension.c")
|
||||
.line(1656)
|
||||
.routine("CreateExtension")
|
||||
.encode(&mut dst)
|
||||
.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
|
@ -5,13 +5,13 @@ mod backend_key_data;
|
||||
mod decode;
|
||||
mod encode;
|
||||
mod message;
|
||||
mod notice_response;
|
||||
mod ready_for_query;
|
||||
mod response;
|
||||
|
||||
pub use self::{
|
||||
decode::Decode,
|
||||
encode::Encode,
|
||||
message::Message,
|
||||
notice_response::{NoticeResponse, NoticeResponseBuilder, NoticeResponseFields, Severity},
|
||||
ready_for_query::{ReadyForQuery, TransactionStatus},
|
||||
response::{Response, ResponseBuilder, Severity},
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{Decode, Encode, NoticeResponse, ReadyForQuery};
|
||||
use crate::{Decode, Encode, ReadyForQuery, Response};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use bytes::Bytes;
|
||||
use std::io::{self, Cursor};
|
||||
@ -6,21 +6,21 @@ use std::io::{self, Cursor};
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
ReadyForQuery(ReadyForQuery),
|
||||
NoticeResponse(NoticeResponse),
|
||||
Response(Response),
|
||||
}
|
||||
|
||||
impl Encode for Message {
|
||||
fn size_hint(&self) -> usize {
|
||||
match self {
|
||||
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<()> {
|
||||
match self {
|
||||
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 {
|
||||
// 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)?),
|
||||
|
||||
_ => unimplemented!("decode not implemented for token: {}", token as char),
|
||||
|
@ -59,32 +59,30 @@ impl Decode for ReadyForQuery {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ReadyForQuery, TransactionStatus};
|
||||
use crate::{Decode, Encode, Message};
|
||||
use crate::{Decode, Encode};
|
||||
use bytes::Bytes;
|
||||
use std::io;
|
||||
|
||||
const READY_FOR_QUERY: &[u8] = b"E";
|
||||
|
||||
#[test]
|
||||
fn it_encodes_ready_for_query() -> io::Result<()> {
|
||||
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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_decodes_ready_for_query() -> io::Result<()> {
|
||||
// FIXME: A test-utils type thing could be useful here as these 7 lines are quite..
|
||||
// duplicated
|
||||
let src = Bytes::from_static(READY_FOR_QUERY);
|
||||
let message = ReadyForQuery::decode(src)?;
|
||||
|
||||
let b = Bytes::from_static(b"Z\0\0\0\x05E");
|
||||
let message = Message::decode(b)?;
|
||||
let body = if let Message::ReadyForQuery(body) = message {
|
||||
body
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
assert_eq!(body.status, TransactionStatus::Error);
|
||||
assert_eq!(message.status, TransactionStatus::Error);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,14 +2,13 @@ use crate::{decode::get_str, Decode, Encode};
|
||||
use byteorder::{BigEndian, WriteBytesExt};
|
||||
use bytes::Bytes;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt,
|
||||
io::{self, Write},
|
||||
ops::Range,
|
||||
pin::Pin,
|
||||
ptr::NonNull,
|
||||
str::{self, FromStr},
|
||||
};
|
||||
use core::fmt::Debug;
|
||||
|
||||
#[derive(Debug, PartialEq, PartialOrd, Copy, Clone)]
|
||||
pub enum Severity {
|
||||
@ -24,6 +23,25 @@ pub enum 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 {
|
||||
match self {
|
||||
Severity::Panic => "PANIC",
|
||||
@ -59,57 +77,8 @@ impl FromStr for Severity {
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: `NoticeResponse` is lazily decoded on access to `.fields()`
|
||||
#[derive(Clone)]
|
||||
pub struct NoticeResponse(Bytes);
|
||||
|
||||
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 {
|
||||
pub struct Response {
|
||||
#[used]
|
||||
storage: Pin<Bytes>,
|
||||
severity: Severity,
|
||||
@ -132,10 +101,15 @@ pub struct NoticeResponseFields {
|
||||
}
|
||||
|
||||
// SAFE: Raw pointers point to pinned memory inside the struct
|
||||
unsafe impl Send for NoticeResponseFields {}
|
||||
unsafe impl Sync for NoticeResponseFields {}
|
||||
unsafe impl Send for Response {}
|
||||
unsafe impl Sync for Response {}
|
||||
|
||||
impl Response {
|
||||
#[inline]
|
||||
pub fn builder() -> ResponseBuilder {
|
||||
ResponseBuilder::new()
|
||||
}
|
||||
|
||||
impl NoticeResponseFields {
|
||||
#[inline]
|
||||
pub fn 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 {
|
||||
f.debug_struct("NoticeResponseFields")
|
||||
f.debug_struct("Response")
|
||||
.field("severity", &self.severity)
|
||||
.field("code", &self.code())
|
||||
.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> {
|
||||
let storage = Pin::new(src);
|
||||
|
||||
@ -371,7 +365,7 @@ impl Decode for NoticeResponseFields {
|
||||
|
||||
_ => {
|
||||
unimplemented!(
|
||||
"error/notice message field {:?} not implemented",
|
||||
"response message field {:?} not implemented",
|
||||
field_type as char
|
||||
);
|
||||
}
|
||||
@ -380,10 +374,10 @@ impl Decode for NoticeResponseFields {
|
||||
|
||||
let severity = severity_non_local
|
||||
.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 message = NonNull::from(message.expect("required by protocol"));
|
||||
let code = NonNull::from(code.expect("`code` required by protocol"));
|
||||
let message = NonNull::from(message.expect("`message` required by protocol"));
|
||||
let detail = detail.map(NonNull::from);
|
||||
let hint = hint.map(NonNull::from);
|
||||
let internal_query = internal_query.map(NonNull::from);
|
||||
@ -419,32 +413,35 @@ impl Decode for NoticeResponseFields {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NoticeResponseBuilder<'a> {
|
||||
severity: Severity,
|
||||
code: Cow<'a, str>,
|
||||
message: Cow<'a, str>,
|
||||
detail: Option<Cow<'a, str>>,
|
||||
hint: Option<Cow<'a, str>>,
|
||||
pub struct ResponseBuilder {
|
||||
storage: Vec<u8>,
|
||||
severity: Option<Severity>,
|
||||
code: Option<Range<usize>>,
|
||||
message: Option<Range<usize>>,
|
||||
detail: Option<Range<usize>>,
|
||||
hint: Option<Range<usize>>,
|
||||
position: Option<usize>,
|
||||
internal_position: Option<usize>,
|
||||
internal_query: Option<Cow<'a, str>>,
|
||||
where_: Option<Cow<'a, str>>,
|
||||
schema: Option<Cow<'a, str>>,
|
||||
table: Option<Cow<'a, str>>,
|
||||
column: Option<Cow<'a, str>>,
|
||||
data_type: Option<Cow<'a, str>>,
|
||||
constraint: Option<Cow<'a, str>>,
|
||||
file: Option<Cow<'a, str>>,
|
||||
internal_query: Option<Range<usize>>,
|
||||
where_: Option<Range<usize>>,
|
||||
schema: Option<Range<usize>>,
|
||||
table: Option<Range<usize>>,
|
||||
column: Option<Range<usize>>,
|
||||
data_type: Option<Range<usize>>,
|
||||
constraint: Option<Range<usize>>,
|
||||
file: Option<Range<usize>>,
|
||||
line: Option<usize>,
|
||||
routine: Option<Cow<'a, str>>,
|
||||
routine: Option<Range<usize>>,
|
||||
}
|
||||
|
||||
impl Default for NoticeResponseBuilder<'_> {
|
||||
impl Default for ResponseBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
severity: Severity::Notice,
|
||||
message: Cow::Borrowed(""),
|
||||
code: Cow::Borrowed("XX000"), // internal_error
|
||||
// FIXME: Remove this allocation (on the quest for zero-allocation)
|
||||
storage: Vec::with_capacity(128),
|
||||
severity: None,
|
||||
message: None,
|
||||
code: None,
|
||||
detail: None,
|
||||
hint: 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]
|
||||
pub fn new() -> NoticeResponseBuilder<'a> {
|
||||
pub fn new() -> ResponseBuilder {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn severity(&mut self, severity: Severity) -> &mut Self {
|
||||
self.severity = severity;
|
||||
pub fn severity(mut self, severity: Severity) -> Self {
|
||||
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
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn message(&mut self, message: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.message = message.into();
|
||||
pub fn message(mut self, message: impl AsRef<str>) -> Self {
|
||||
self.message = Some(put_str(&mut self.storage, b'M', message));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn code(&mut self, code: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.code = code.into();
|
||||
pub fn code(mut self, code: impl AsRef<str>) -> Self {
|
||||
self.code = Some(put_str(&mut self.storage, b'C', code));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn detail(&mut self, detail: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.detail = Some(detail.into());
|
||||
pub fn detail(mut self, detail: impl AsRef<str>) -> Self {
|
||||
self.detail = Some(put_str(&mut self.storage, b'D', detail));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hint(&mut self, hint: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.hint = Some(hint.into());
|
||||
pub fn hint(mut self, hint: impl AsRef<str>) -> Self {
|
||||
self.hint = Some(put_str(&mut self.storage, b'H', hint));
|
||||
self
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn internal_query(&mut self, query: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.internal_query = Some(query.into());
|
||||
pub fn internal_query(mut self, query: impl AsRef<str>) -> Self {
|
||||
self.internal_query = Some(put_str(&mut self.storage, b'q', query));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn where_(&mut self, where_: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.where_ = Some(where_.into());
|
||||
pub fn where_(mut self, where_: impl AsRef<str>) -> Self {
|
||||
self.where_ = Some(put_str(&mut self.storage, b'w', where_));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn schema(&mut self, schema: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.schema = Some(schema.into());
|
||||
pub fn schema(mut self, schema: impl AsRef<str>) -> Self {
|
||||
self.schema = Some(put_str(&mut self.storage, b's', schema));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn table(&mut self, table: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.table = Some(table.into());
|
||||
pub fn table(mut self, table: impl AsRef<str>) -> Self {
|
||||
self.table = Some(put_str(&mut self.storage, b't', table));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn column(&mut self, column: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.column = Some(column.into());
|
||||
pub fn column(mut self, column: impl AsRef<str>) -> Self {
|
||||
self.column = Some(put_str(&mut self.storage, b'c', column));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn data_type(&mut self, data_type: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.data_type = Some(data_type.into());
|
||||
pub fn data_type(mut self, data_type: impl AsRef<str>) -> Self {
|
||||
self.data_type = Some(put_str(&mut self.storage, b'd', data_type));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn constraint(&mut self, constraint: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.constraint = Some(constraint.into());
|
||||
pub fn constraint(mut self, constraint: impl AsRef<str>) -> Self {
|
||||
self.constraint = Some(put_str(&mut self.storage, b'n', constraint));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn file(&mut self, file: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.file = Some(file.into());
|
||||
pub fn file(mut self, file: impl AsRef<str>) -> Self {
|
||||
self.file = Some(put_str(&mut self.storage, b'F', file));
|
||||
self
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn routine(&mut self, routine: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.routine = Some(routine.into());
|
||||
pub fn routine(mut self, routine: impl AsRef<str>) -> Self {
|
||||
self.routine = Some(put_str(&mut self.storage, b'R', routine));
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn build(&self) -> NoticeResponse {
|
||||
let mut buf = Vec::new();
|
||||
pub fn build(mut self) -> Response {
|
||||
// Add a \0 terminator
|
||||
self.storage.push(0);
|
||||
|
||||
// FIXME: Should Encode even be fallible?
|
||||
// PANIC: Cannot fail
|
||||
self.encode(&mut buf).unwrap();
|
||||
// Freeze the storage and Pin so we can self-reference it
|
||||
let storage = Pin::new(Bytes::from(self.storage));
|
||||
|
||||
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 {
|
||||
// Too variable to measure efficiently
|
||||
0
|
||||
self.storage.len() + 6
|
||||
}
|
||||
|
||||
fn encode(&self, buf: &mut Vec<u8>) -> io::Result<()> {
|
||||
// Severity and Localized Severity (required)
|
||||
let sev = self.severity.to_str().as_bytes();
|
||||
buf.push(b'S');
|
||||
buf.write_all(sev)?;
|
||||
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 self.severity.as_ref().map_or(false, |s| s.is_error()) {
|
||||
buf.push(b'E');
|
||||
} else {
|
||||
buf.push(b'N');
|
||||
}
|
||||
|
||||
if let Some(hint) = &self.hint {
|
||||
buf.push(b'H');
|
||||
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.write_u32::<BigEndian>((5 + self.storage.len()) as u32)?;
|
||||
buf.write_all(&self.storage)?;
|
||||
buf.push(0);
|
||||
|
||||
Ok(())
|
||||
@ -705,18 +669,17 @@ impl<'a> Encode for NoticeResponseBuilder<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{NoticeResponse, Severity};
|
||||
use super::{Response, Severity};
|
||||
use crate::{Decode, Encode};
|
||||
use bytes::Bytes;
|
||||
use std::io;
|
||||
|
||||
const NOTICE_RESPONSE: &[u8] =
|
||||
b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, \
|
||||
const RESPONSE: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, \
|
||||
skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
|
||||
|
||||
#[test]
|
||||
fn it_encodes_notice_response() -> io::Result<()> {
|
||||
let message = NoticeResponse::builder()
|
||||
fn it_encodes_response() -> io::Result<()> {
|
||||
let message = Response::builder()
|
||||
.severity(Severity::Notice)
|
||||
.code("42710")
|
||||
.message("extension \"uuid-ossp\" already exists, skipping")
|
||||
@ -728,23 +691,40 @@ mod tests {
|
||||
let mut dst = Vec::with_capacity(message.size_hint());
|
||||
message.encode(&mut dst)?;
|
||||
|
||||
assert_eq!(&dst[5..], NOTICE_RESPONSE);
|
||||
assert_eq!(&dst[5..], RESPONSE);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_decodes_notice_response() -> io::Result<()> {
|
||||
let src = Bytes::from_static(NOTICE_RESPONSE);
|
||||
let message = NoticeResponse::decode(src)?;
|
||||
let fields = message.fields()?;
|
||||
fn it_encodes_response_builder() -> io::Result<()> {
|
||||
let message = Response::builder()
|
||||
.severity(Severity::Notice)
|
||||
.code("42710")
|
||||
.message("extension \"uuid-ossp\" already exists, skipping")
|
||||
.file("extension.c")
|
||||
.line(1656)
|
||||
.routine("CreateExtension");
|
||||
|
||||
assert_eq!(fields.severity(), Severity::Notice);
|
||||
assert_eq!(fields.message(), "extension \"uuid-ossp\" already exists, skipping");
|
||||
assert_eq!(fields.code(), "42710");
|
||||
assert_eq!(fields.file(), Some("extension.c"));
|
||||
assert_eq!(fields.line(), Some(1656));
|
||||
assert_eq!(fields.routine(), Some("CreateExtension"));
|
||||
let mut dst = Vec::with_capacity(message.size_hint());
|
||||
message.encode(&mut dst)?;
|
||||
|
||||
assert_eq!(&dst[5..], RESPONSE);
|
||||
|
||||
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(())
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user