mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-10-05 08:45:14 +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 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();
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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},
|
||||||
};
|
};
|
||||||
|
@ -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),
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user