sqlx_postgres/message/
response.rs1use std::ops::Range;
2use std::str::from_utf8;
3
4use memchr::memchr;
5
6use sqlx_core::bytes::Bytes;
7
8use crate::error::Error;
9use crate::io::ProtocolDecode;
10use crate::message::{BackendMessage, BackendMessageFormat};
11
12#[derive(Debug, Copy, Clone, Eq, PartialEq)]
13#[repr(u8)]
14pub enum PgSeverity {
15 Panic,
16 Fatal,
17 Error,
18 Warning,
19 Notice,
20 Debug,
21 Info,
22 Log,
23}
24
25impl PgSeverity {
26 #[inline]
27 pub fn is_error(self) -> bool {
28 matches!(self, Self::Panic | Self::Fatal | Self::Error)
29 }
30}
31
32impl TryFrom<&str> for PgSeverity {
33 type Error = Error;
34
35 fn try_from(s: &str) -> Result<PgSeverity, Error> {
36 let result = match s {
37 "PANIC" => PgSeverity::Panic,
38 "FATAL" => PgSeverity::Fatal,
39 "ERROR" => PgSeverity::Error,
40 "WARNING" => PgSeverity::Warning,
41 "NOTICE" => PgSeverity::Notice,
42 "DEBUG" => PgSeverity::Debug,
43 "INFO" => PgSeverity::Info,
44 "LOG" => PgSeverity::Log,
45
46 severity => {
47 return Err(err_protocol!("unknown severity: {:?}", severity));
48 }
49 };
50
51 Ok(result)
52 }
53}
54
55#[derive(Debug)]
56pub struct Notice {
57 storage: Bytes,
58 severity: PgSeverity,
59 message: Range<usize>,
60 code: Range<usize>,
61}
62
63impl Notice {
64 #[inline]
65 pub fn severity(&self) -> PgSeverity {
66 self.severity
67 }
68
69 #[inline]
70 pub fn code(&self) -> &str {
71 self.get_cached_str(self.code.clone())
72 }
73
74 #[inline]
75 pub fn message(&self) -> &str {
76 self.get_cached_str(self.message.clone())
77 }
78
79 #[inline]
83 pub fn get(&self, ty: u8) -> Option<&str> {
84 self.get_raw(ty).and_then(|v| from_utf8(v).ok())
85 }
86
87 pub fn get_raw(&self, ty: u8) -> Option<&[u8]> {
88 self.fields()
89 .filter(|(field, _)| *field == ty)
90 .map(|(_, range)| &self.storage[range])
91 .next()
92 }
93}
94
95impl Notice {
96 #[inline]
97 fn fields(&self) -> Fields<'_> {
98 Fields {
99 storage: &self.storage,
100 offset: 0,
101 }
102 }
103
104 #[inline]
105 fn get_cached_str(&self, cache: Range<usize>) -> &str {
106 from_utf8(&self.storage[cache]).unwrap()
108 }
109}
110
111impl ProtocolDecode<'_> for Notice {
112 fn decode_with(buf: Bytes, _: ()) -> Result<Self, Error> {
113 const DEFAULT_SEVERITY: PgSeverity = PgSeverity::Log;
117 let mut severity_v = None;
118 let mut severity_s = None;
119 let mut message = 0..0;
120 let mut code = 0..0;
121
122 let fields = Fields {
126 storage: &buf,
127 offset: 0,
128 };
129
130 for (field, v) in fields {
131 if !(message.is_empty() || code.is_empty()) {
132 break;
135 }
136
137 match field {
138 b'S' => {
139 severity_s = from_utf8(&buf[v.clone()])
140 .map_err(|_| notice_protocol_err())?
143 .try_into()
144 .ok();
146 }
147
148 b'V' => {
149 severity_v = Some(
152 from_utf8(&buf[v.clone()])
153 .map_err(|_| notice_protocol_err())?
154 .try_into()?,
155 );
156 }
157
158 b'M' => {
159 _ = from_utf8(&buf[v.clone()]).map_err(|_| notice_protocol_err())?;
160 message = v;
161 }
162
163 b'C' => {
164 _ = from_utf8(&buf[v.clone()]).map_err(|_| notice_protocol_err())?;
165 code = v;
166 }
167
168 _ => {}
171 }
172 }
173
174 Ok(Self {
175 severity: severity_v.or(severity_s).unwrap_or(DEFAULT_SEVERITY),
176 message,
177 code,
178 storage: buf,
179 })
180 }
181}
182
183impl BackendMessage for Notice {
184 const FORMAT: BackendMessageFormat = BackendMessageFormat::NoticeResponse;
185
186 fn decode_body(buf: Bytes) -> Result<Self, Error> {
187 Self::decode_with(buf, ())
189 }
190}
191
192struct Fields<'a> {
194 storage: &'a [u8],
195 offset: usize,
196}
197
198impl<'a> Iterator for Fields<'a> {
199 type Item = (u8, Range<usize>);
200
201 fn next(&mut self) -> Option<Self::Item> {
202 let ty = *self.storage.get(self.offset)?;
206
207 if ty == 0 {
208 return None;
209 }
210
211 self.offset = self.offset.checked_add(1)?;
213
214 let start = self.offset;
215
216 let len = memchr(b'\0', self.storage.get(start..)?)?;
217
218 let end = self.offset + len;
220 self.offset = end + 1;
221
222 Some((ty, start..end))
223 }
224}
225
226fn notice_protocol_err() -> Error {
227 Error::Protocol(
229 "Postgres returned a non-UTF-8 string for its error message. \
230 This is most likely due to an error that occurred during authentication and \
231 the default lc_messages locale is not binary-compatible with UTF-8. \
232 See the server logs for the error details."
233 .into(),
234 )
235}
236
237#[test]
238fn test_decode_error_response() {
239 const DATA: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
240
241 let m = Notice::decode(Bytes::from_static(DATA)).unwrap();
242
243 assert_eq!(
244 m.message(),
245 "extension \"uuid-ossp\" already exists, skipping"
246 );
247
248 assert!(matches!(m.severity(), PgSeverity::Notice));
249 assert_eq!(m.code(), "42710");
250}
251
252#[cfg(all(test, not(debug_assertions)))]
253#[bench]
254fn bench_error_response_get_message(b: &mut test::Bencher) {
255 const DATA: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
256
257 let res = Notice::decode(test::black_box(Bytes::from_static(DATA))).unwrap();
258
259 b.iter(|| {
260 let _ = test::black_box(&res).message();
261 });
262}
263
264#[cfg(all(test, not(debug_assertions)))]
265#[bench]
266fn bench_decode_error_response(b: &mut test::Bencher) {
267 const DATA: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
268
269 b.iter(|| {
270 let _ = Notice::decode(test::black_box(Bytes::from_static(DATA)));
271 });
272}