Skip to main content

rupg_sql/
error.rs

1//! The errors and the notices of the lexer and the parser, with the fields that PostgreSQL puts in an `ErrorResponse` and a `NoticeResponse`.
2//!
3//! Lifted from `crates/rudb-pgparse/src/error.rs` of tamnd/rudb at f5f7065a (spec/04 section 4.9).
4
5use std::fmt;
6
7use rupg_common::SqlState;
8
9/// An error of the lexer or the parser.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct Error {
12    /// The SQLSTATE, for example `42601`.
13    pub code: SqlState,
14    /// The primary message, as PostgreSQL writes it.
15    pub message: String,
16    /// The detail, or `None`.
17    pub detail: Option<&'static str>,
18    /// The hint, or `None`.
19    pub hint: Option<&'static str>,
20    /// The byte offset in the text that the error points to, or `None`.
21    pub location: Option<usize>,
22}
23
24impl Error {
25    /// An error in the form of `scanner_yyerror`: the message, then `at end of input` when the location is at the end of the text, or `at or near` and the text from `start` to `end`.
26    pub(crate) fn syntax(text: &[u8], message: &str, start: usize, end: usize) -> Error {
27        let message = if start >= text.len() {
28            format!("{message} at end of input")
29        } else {
30            let near = String::from_utf8_lossy(&text[start..end.clamp(start, text.len())]);
31            format!("{message} at or near \"{near}\"")
32        };
33        Error {
34            code: SqlState::SYNTAX_ERROR,
35            message,
36            detail: None,
37            hint: None,
38            location: Some(start),
39        }
40    }
41
42    /// An error with a location and no `at or near` part.
43    pub(crate) fn at(code: SqlState, message: &str, location: usize) -> Error {
44        Error {
45            code,
46            message: message.to_owned(),
47            detail: None,
48            hint: None,
49            location: Some(location),
50        }
51    }
52
53    /// The error for an action of `gram.y` that is not ported yet. `rule` is its name in `productions.txt`, for example `a_expr.17`.
54    pub(crate) fn not_ported(rule: &str) -> Error {
55        Error {
56            code: SqlState::FEATURE_NOT_SUPPORTED,
57            message: format!("the action of the rule {rule} of gram.y is not ported"),
58            detail: None,
59            hint: None,
60            location: None,
61        }
62    }
63
64    /// An error for a fault of the parser itself, with SQLSTATE `XX000`.
65    pub(crate) fn internal(message: &str) -> Error {
66        Error {
67            code: SqlState::INTERNAL_ERROR,
68            message: message.to_owned(),
69            detail: None,
70            hint: None,
71            location: None,
72        }
73    }
74
75    /// The error that `report_invalid_encoding` gives for bytes that are not UTF-8, or that contain a zero byte. `bad` starts at the first bad byte.
76    pub(crate) fn encoding(bad: &[u8]) -> Error {
77        let first = bad.first().copied().unwrap_or(0);
78        let length = match first {
79            b if b & 0x80 == 0 => 1,
80            b if b & 0xe0 == 0xc0 => 2,
81            b if b & 0xf0 == 0xe0 => 3,
82            b if b & 0xf8 == 0xf0 => 4,
83            _ => 1,
84        };
85        let bytes: Vec<String> = bad.iter().take(length).map(|b| format!("0x{b:02x}")).collect();
86        Error {
87            code: SqlState::CHARACTER_NOT_IN_REPERTOIRE,
88            message: format!("invalid byte sequence for encoding \"UTF8\": {}", bytes.join(" ")),
89            detail: None,
90            hint: None,
91            location: None,
92        }
93    }
94
95    /// The position that an `ErrorResponse` gives: the number of the character that the location points to in `text`, where the first character is 1.
96    pub fn position(&self, text: &str) -> Option<usize> {
97        let location = self.location?.min(text.len());
98        let characters = text.as_bytes()[..location].iter().filter(|&&b| b & 0xc0 != 0x80).count();
99        Some(characters + 1)
100    }
101}
102
103impl fmt::Display for Error {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        f.write_str(&self.message)
106    }
107}
108
109impl std::error::Error for Error {}
110
111impl From<Error> for rupg_common::Error {
112    /// The error of the engine with the SQLSTATE, the message, the detail and the hint. The location stays in the parser error: [`Error::position`] gives the position of the `ErrorResponse`.
113    fn from(error: Error) -> Self {
114        let mut common = rupg_common::Error::new(error.code, error.message);
115        if let Some(detail) = error.detail {
116            common = common.with_detail(detail);
117        }
118        if let Some(hint) = error.hint {
119            common = common.with_hint(hint);
120        }
121        common
122    }
123}
124
125/// A notice or a warning of the lexer or the parser, for example for an identifier that is too long.
126#[derive(Clone, Debug, PartialEq, Eq)]
127pub struct Notice {
128    /// The severity, `NOTICE` or `WARNING`.
129    pub severity: Severity,
130    /// The SQLSTATE, for example `42622`.
131    pub code: SqlState,
132    /// The message, as PostgreSQL writes it.
133    pub message: String,
134    /// The byte offset in the text that the notice points to, or `None`.
135    pub location: Option<usize>,
136}
137
138/// The severity of a [`Notice`].
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
140pub enum Severity {
141    /// `NOTICE`.
142    Notice,
143    /// `WARNING`.
144    Warning,
145}