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