sqlx_core_guts/postgres/message/
response.rs

1use 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    // Field descriptions available here:
77    //  https://www.postgresql.org/docs/current/protocol-error-fields.html
78
79    #[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        // unwrap: this cannot fail at this stage
104        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        // In order to support PostgreSQL 9.5 and older we need to parse the localized S field.
111        // Newer versions additionally come with the V field that is guaranteed to be in English.
112        // We thus read both versions and prefer the unlocalized one if available.
113        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        // we cache the three always present fields
120        // this enables to keep the access time down for the fields most likely accessed
121
122        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                // stop iterating when we have the 3 fields we were looking for
130                // we assume V (severity) was the first field as it should be
131                break;
132            }
133
134            match field {
135                b'S' => {
136                    // Discard potential errors, because the message might be localized
137                    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                    // Propagate errors here, because V is not localized and thus we are missing a possible
145                    // variant.
146                    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
174/// An iterator over each field in the Error (or Notice) response.
175struct 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        // The fields in the response body are sequentially stored as [tag][string],
185        // ending in a final, additional [nul]
186
187        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}