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 Iterator for Fields<'_> {
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}