sqlx_core_guts/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])
138 .unwrap()
139 .try_into()
140 .ok();
141 }
142
143 b'V' => {
144 severity_v = Some(
147 from_utf8(&buf[v.0 as usize..v.1 as usize])
148 .unwrap()
149 .try_into()?,
150 );
151 }
152
153 b'M' => {
154 message = v;
155 }
156
157 b'C' => {
158 code = v;
159 }
160
161 _ => {}
162 }
163 }
164
165 Ok(Self {
166 severity: severity_v.or(severity_s).unwrap_or(DEFAULT_SEVERITY),
167 message,
168 code,
169 storage: buf,
170 })
171 }
172}
173
174struct Fields<'a> {
176 storage: &'a [u8],
177 offset: u16,
178}
179
180impl<'a> Iterator for Fields<'a> {
181 type Item = (u8, (u16, u16));
182
183 fn next(&mut self) -> Option<Self::Item> {
184 let ty = self.storage[self.offset as usize];
188
189 if ty == 0 {
190 return None;
191 }
192
193 let nul = memchr(b'\0', &self.storage[(self.offset + 1) as usize..])? as u16;
194 let offset = self.offset;
195
196 self.offset += nul + 2;
197
198 Some((ty, (offset + 1, offset + nul + 1)))
199 }
200}
201
202#[test]
203fn test_decode_error_response() {
204 const DATA: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
205
206 let m = Notice::decode(Bytes::from_static(DATA)).unwrap();
207
208 assert_eq!(
209 m.message(),
210 "extension \"uuid-ossp\" already exists, skipping"
211 );
212
213 assert!(matches!(m.severity(), PgSeverity::Notice));
214 assert_eq!(m.code(), "42710");
215}
216
217#[cfg(all(test, not(debug_assertions)))]
218#[bench]
219fn bench_error_response_get_message(b: &mut test::Bencher) {
220 const DATA: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
221
222 let res = Notice::decode(test::black_box(Bytes::from_static(DATA))).unwrap();
223
224 b.iter(|| {
225 let _ = test::black_box(&res).message();
226 });
227}
228
229#[cfg(all(test, not(debug_assertions)))]
230#[bench]
231fn bench_decode_error_response(b: &mut test::Bencher) {
232 const DATA: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0";
233
234 b.iter(|| {
235 let _ = Notice::decode(test::black_box(Bytes::from_static(DATA)));
236 });
237}