Skip to main content

rupg_types/
range.rs

1//! The text and binary forms of the range types, from `range_parse`, `range_deparse`, `range_recv` and `range_send` of `rangetypes.c`. A range keeps its bounds as values of the subtype, so these functions give and take the text or the bytes of each bound. The caller reads and writes the bounds with the functions of the subtype.
2
3use rupg_common::SqlState;
4
5use crate::binary::Recv;
6use crate::error::TypeError;
7use crate::value::Value;
8
9/// `RANGE_EMPTY`: the range has no points.
10pub const RANGE_EMPTY: u8 = 0x01;
11/// `RANGE_LB_INC`: the lower bound is part of the range.
12pub const RANGE_LB_INC: u8 = 0x02;
13/// `RANGE_UB_INC`: the upper bound is part of the range.
14pub const RANGE_UB_INC: u8 = 0x04;
15/// `RANGE_LB_INF`: the range has no lower bound.
16pub const RANGE_LB_INF: u8 = 0x08;
17/// `RANGE_UB_INF`: the range has no upper bound.
18pub const RANGE_UB_INF: u8 = 0x10;
19
20/// The text of an empty range.
21const EMPTY_LITERAL: &str = "empty";
22
23/// A value of a range type: the flags of `rangetypes.h` and the two bounds. A bound that is infinite is `Value::Null`, and so are both bounds of an empty range.
24#[derive(Clone, Debug, PartialEq)]
25pub struct Range {
26    pub flags: u8,
27    pub lower: Value,
28    pub upper: Value,
29}
30
31impl Range {
32    /// The empty range.
33    pub fn empty() -> Range {
34        Range { flags: RANGE_EMPTY, lower: Value::Null, upper: Value::Null }
35    }
36
37    /// True for an empty range.
38    pub fn is_empty(&self) -> bool {
39        self.flags & RANGE_EMPTY != 0
40    }
41
42    /// `RANGE_HAS_LBOUND`: the range has a lower bound value.
43    pub fn has_lower(&self) -> bool {
44        self.flags & (RANGE_EMPTY | RANGE_LB_INF) == 0
45    }
46
47    /// `RANGE_HAS_UBOUND`: the range has an upper bound value.
48    pub fn has_upper(&self) -> bool {
49        self.flags & (RANGE_EMPTY | RANGE_UB_INF) == 0
50    }
51}
52
53/// A range as `range_parse` reads it: the flags, and the text of each bound that is not infinite.
54#[derive(Clone, Debug, PartialEq, Eq)]
55pub struct RangeText {
56    pub flags: u8,
57    pub lower: Option<String>,
58    pub upper: Option<String>,
59}
60
61/// `isspace` of C in the C locale.
62fn is_space(b: u8) -> bool {
63    matches!(b, b' ' | b'\t' | b'\n' | b'\r' | 0x0B | 0x0C)
64}
65
66/// The error of a range literal that does not parse.
67fn malformed(text: &str, detail: &str) -> TypeError {
68    let mut error = TypeError::new(
69        SqlState::INVALID_TEXT_REPRESENTATION,
70        format!("malformed range literal: \"{text}\""),
71    );
72    error.detail = Some(detail.to_string());
73    error
74}
75
76/// `range_parse`: the key word `empty`, or a bracket or a parenthesis, the lower bound, a comma, the upper bound and a bracket or a parenthesis. White space before and after the range is not part of it. A bound with no characters is infinite. A bound can have characters in double quotes, and a backslash takes the next character as it is.
77///
78/// # Errors
79///
80/// `22P02` "malformed range literal" with the detail of PostgreSQL.
81pub fn range_parse(text: &str) -> Result<RangeText, TypeError> {
82    let s = text.as_bytes();
83    let mut at = 0;
84    while at < s.len() && is_space(s[at]) {
85        at += 1;
86    }
87    if s.len() - at >= EMPTY_LITERAL.len()
88        && s[at..at + EMPTY_LITERAL.len()].eq_ignore_ascii_case(EMPTY_LITERAL.as_bytes())
89    {
90        at += EMPTY_LITERAL.len();
91        while at < s.len() && is_space(s[at]) {
92            at += 1;
93        }
94        if at < s.len() {
95            return Err(malformed(text, "Junk after \"empty\" key word."));
96        }
97        return Ok(RangeText { flags: RANGE_EMPTY, lower: None, upper: None });
98    }
99    let mut flags = 0;
100    match s.get(at) {
101        Some(b'[') => flags |= RANGE_LB_INC,
102        Some(b'(') => {}
103        _ => return Err(malformed(text, "Missing left parenthesis or bracket.")),
104    }
105    at += 1;
106    let lower = parse_bound(text, &mut at)?;
107    if lower.is_none() {
108        flags |= RANGE_LB_INF;
109    }
110    if s.get(at) != Some(&b',') {
111        return Err(malformed(text, "Missing comma after lower bound."));
112    }
113    at += 1;
114    let upper = parse_bound(text, &mut at)?;
115    if upper.is_none() {
116        flags |= RANGE_UB_INF;
117    }
118    match s.get(at) {
119        Some(b']') => flags |= RANGE_UB_INC,
120        Some(b')') => {}
121        _ => return Err(malformed(text, "Too many commas.")),
122    }
123    at += 1;
124    while at < s.len() && is_space(s[at]) {
125        at += 1;
126    }
127    if at < s.len() {
128        return Err(malformed(text, "Junk after right parenthesis or bracket."));
129    }
130    Ok(RangeText { flags, lower, upper })
131}
132
133/// `range_parse_bound`: the text of one bound up to a comma, a parenthesis or a bracket that is not in quotes, or `None` for a bound with no characters. In quotes, two double quotes are one double quote.
134fn parse_bound(text: &str, at: &mut usize) -> Result<Option<String>, TypeError> {
135    let s = text.as_bytes();
136    let ends = |b: Option<&u8>| matches!(b, Some(b',' | b')' | b']'));
137    if ends(s.get(*at)) {
138        return Ok(None);
139    }
140    let mut out = Vec::new();
141    let mut quoted = false;
142    while quoted || !ends(s.get(*at)) {
143        let Some(&c) = s.get(*at) else {
144            return Err(malformed(text, "Unexpected end of input."));
145        };
146        *at += 1;
147        match c {
148            b'\\' => {
149                let Some(&next) = s.get(*at) else {
150                    return Err(malformed(text, "Unexpected end of input."));
151                };
152                out.push(next);
153                *at += 1;
154            }
155            b'"' if !quoted => quoted = true,
156            b'"' if s.get(*at) == Some(&b'"') => {
157                out.push(b'"');
158                *at += 1;
159            }
160            b'"' => quoted = false,
161            _ => out.push(c),
162        }
163    }
164    Ok(Some(String::from_utf8_lossy(&out).into_owned()))
165}
166
167/// `range_deparse`: the text of a range from the flags and the output of each bound that the range has.
168pub fn range_deparse(flags: u8, lower: Option<&[u8]>, upper: Option<&[u8]>, out: &mut Vec<u8>) {
169    if flags & RANGE_EMPTY != 0 {
170        out.extend_from_slice(EMPTY_LITERAL.as_bytes());
171        return;
172    }
173    out.push(if flags & RANGE_LB_INC != 0 { b'[' } else { b'(' });
174    if let Some(lower) = lower {
175        bound_escape(lower, out);
176    }
177    out.push(b',');
178    if let Some(upper) = upper {
179        bound_escape(upper, out);
180    }
181    out.push(if flags & RANGE_UB_INC != 0 { b']' } else { b')' });
182}
183
184/// `range_bound_escape`: a bound that is empty or has a quote, a backslash, a parenthesis, a bracket, a comma or white space is in double quotes, with each quote and each backslash doubled.
185fn bound_escape(value: &[u8], out: &mut Vec<u8>) {
186    let quote = value.is_empty()
187        || value
188            .iter()
189            .any(|&b| matches!(b, b'"' | b'\\' | b'(' | b')' | b'[' | b']' | b',') || is_space(b));
190    if !quote {
191        out.extend_from_slice(value);
192        return;
193    }
194    out.push(b'"');
195    for &b in value {
196        if matches!(b, b'"' | b'\\') {
197            out.push(b);
198        }
199        out.push(b);
200    }
201    out.push(b'"');
202}
203
204/// The flags and the bytes of each bound of a range in the binary form.
205pub type RangeBytes<'a> = (u8, Option<&'a [u8]>, Option<&'a [u8]>);
206
207/// `range_recv`: the flags, then the length and the bytes of each bound that the flags give. The flags lose the bits that are not flags of a range.
208///
209/// # Errors
210///
211/// `08P01` for a value that is too short or too long.
212pub fn range_recv<'a>(recv: &mut Recv<'a>) -> Result<RangeBytes<'a>, TypeError> {
213    let flags =
214        recv.byte()? & (RANGE_EMPTY | RANGE_LB_INC | RANGE_LB_INF | RANGE_UB_INC | RANGE_UB_INF);
215    let mut bound = |inf: u8| -> Result<Option<&'a [u8]>, TypeError> {
216        if flags & (RANGE_EMPTY | inf) != 0 {
217            return Ok(None);
218        }
219        let len = recv.u32()?;
220        recv.bytes(usize::try_from(len).unwrap_or(usize::MAX)).map(Some)
221    };
222    let lower = bound(RANGE_LB_INF)?;
223    let upper = bound(RANGE_UB_INF)?;
224    if recv.remaining() != 0 {
225        return Err(TypeError::new(
226            SqlState::PROTOCOL_VIOLATION,
227            "invalid message format".to_string(),
228        ));
229    }
230    Ok((flags, lower, upper))
231}
232
233/// `range_send`: the flags, then the length and the bytes of each bound that the range has.
234pub fn range_send(flags: u8, lower: Option<&[u8]>, upper: Option<&[u8]>, out: &mut Vec<u8>) {
235    out.push(flags);
236    for bound in [lower, upper].into_iter().flatten() {
237        let len = u32::try_from(bound.len()).unwrap_or(u32::MAX);
238        out.extend_from_slice(&len.to_be_bytes());
239        out.extend_from_slice(bound);
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    fn parse(text: &str) -> (u8, Option<String>, Option<String>) {
248        let r = range_parse(text).unwrap();
249        (r.flags, r.lower, r.upper)
250    }
251
252    fn detail(text: &str) -> String {
253        range_parse(text).unwrap_err().detail.unwrap()
254    }
255
256    #[test]
257    fn text_forms() {
258        assert_eq!(parse("[1,10)"), (RANGE_LB_INC, Some("1".into()), Some("10".into())));
259        assert_eq!(parse(" (,] "), (RANGE_LB_INF | RANGE_UB_INF | RANGE_UB_INC, None, None));
260        assert_eq!(parse(" EMPTY "), (RANGE_EMPTY, None, None));
261        assert_eq!(parse(r#"["a ""b"" \c",x\,y]"#).1.as_deref(), Some(r#"a "b" c"#));
262        assert_eq!(parse(r#"["a ""b"" \c",x\,y]"#).2.as_deref(), Some("x,y"));
263        assert_eq!(parse(r#"["",)"#).1.as_deref(), Some(""));
264        assert_eq!(detail("emptyx"), "Junk after \"empty\" key word.");
265        assert_eq!(detail(""), "Missing left parenthesis or bracket.");
266        assert_eq!(detail("[1 10)"), "Missing comma after lower bound.");
267        assert_eq!(detail("[1,10"), "Unexpected end of input.");
268        assert_eq!(detail("[1,10,11)"), "Too many commas.");
269        assert_eq!(detail("[1,10)x"), "Junk after right parenthesis or bracket.");
270        assert_eq!(detail("[1)"), "Missing comma after lower bound.");
271        assert_eq!(detail("[1,2\\"), "Unexpected end of input.");
272        let mut out = Vec::new();
273        range_deparse(RANGE_LB_INC, Some(b"a b"), Some(b"x\"y"), &mut out);
274        assert_eq!(out, br#"["a b","x""y")"#);
275        out.clear();
276        range_deparse(RANGE_LB_INF | RANGE_UB_INC, None, Some(b""), &mut out);
277        assert_eq!(out, br#"(,""]"#);
278    }
279
280    #[test]
281    fn binary_forms() {
282        let mut out = Vec::new();
283        range_send(RANGE_LB_INC, Some(&[0, 0, 0, 1]), Some(&[0, 0, 0, 10]), &mut out);
284        assert_eq!(out, [2, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 10]);
285        let mut recv = Recv::new(&out);
286        let (flags, lower, upper) = range_recv(&mut recv).unwrap();
287        assert_eq!(
288            (flags, lower, upper),
289            (RANGE_LB_INC, Some(&[0, 0, 0, 1][..]), Some(&[0, 0, 0, 10][..]))
290        );
291        let mut recv = Recv::new(&[0x81]);
292        assert_eq!(range_recv(&mut recv).unwrap(), (RANGE_EMPTY, None, None));
293        let mut recv = Recv::new(&[RANGE_EMPTY, 0]);
294        assert_eq!(range_recv(&mut recv).unwrap_err().message, "invalid message format");
295    }
296}