sqlx_postgres/message/
response.rs

1use 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    // Field descriptions available here:
80    //  https://www.postgresql.org/docs/current/protocol-error-fields.html
81
82    #[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        // unwrap: this cannot fail at this stage
107        from_utf8(&self.storage[cache]).unwrap()
108    }
109}
110
111impl ProtocolDecode<'_> for Notice {
112    fn decode_with(buf: Bytes, _: ()) -> Result<Self, Error> {
113        // In order to support PostgreSQL 9.5 and older we need to parse the localized S field.
114        // Newer versions additionally come with the V field that is guaranteed to be in English.
115        // We thus read both versions and prefer the unlocalized one if available.
116        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        // we cache the three always present fields
123        // this enables to keep the access time down for the fields most likely accessed
124
125        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                // stop iterating when we have the 3 fields we were looking for
133                // we assume V (severity) was the first field as it should be
134                break;
135            }
136
137            match field {
138                b'S' => {
139                    severity_s = from_utf8(&buf[v.clone()])
140                        // If the error string is not UTF-8, we have no hope of interpreting it,
141                        // localized or not. The `V` field would likely fail to parse as well.
142                        .map_err(|_| notice_protocol_err())?
143                        .try_into()
144                        // If we couldn't parse the severity here, it might just be localized.
145                        .ok();
146                }
147
148                b'V' => {
149                    // Propagate errors here, because V is not localized and
150                    // thus we are missing a possible variant.
151                    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                // If more fields are added, make sure to check that they are valid UTF-8,
169                // otherwise the get_cached_str method will panic.
170                _ => {}
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        // Keeping both impls for now
188        Self::decode_with(buf, ())
189    }
190}
191
192/// An iterator over each field in the Error (or Notice) response.
193struct 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        // The fields in the response body are sequentially stored as [tag][string],
203        // ending in a final, additional [nul]
204
205        let ty = *self.storage.get(self.offset)?;
206
207        if ty == 0 {
208            return None;
209        }
210
211        // Consume the type byte
212        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        // Neither can overflow as they will always be `<= self.storage.len()`.
219        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    // https://github.com/launchbadge/sqlx/issues/1144
228    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}