Skip to main content

rupg_types/
scalar.rs

1//! `bool`, `"char"`, `name`, `bytea` and `uuid` in the text format.
2//!
3//! Each function is a port of the input or output function of the type in `src/backend/utils/adt`: `boolin` and `parse_bool_with_len` in `bool.c`, `charin` and `charout` in `char.c`, `namein` in `name.c`, `byteain` and `byteaout` in `bytea.c` with `hex_decode` in `encode.c`, and `string_to_uuid` and `uuid_out` in `uuid.c`. The string types with a length and `json` are in their own modules.
4//!
5//! Lifted from `crates/rudb-pgtypes/src/scalar.rs` of tamnd/rudb at f5f7065a (spec/04 section 4.9).
6
7use rupg_common::SqlState;
8
9use crate::error::TypeError;
10use crate::number::is_space;
11
12/// The longest `name` in bytes, which is `NAMEDATALEN` less the terminator.
13pub const NAME_MAX_BYTES: usize = 63;
14
15/// The setting `bytea_output`.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum ByteaOutput {
18    /// `\x` and two lowercase hex digits for each byte. The default.
19    #[default]
20    Hex,
21    /// Printable ASCII as itself, a backslash as two, and any other byte as a backslash and three octal digits.
22    Escape,
23}
24
25/// The text input of `bool`. It takes `true`, `yes`, `on` and `1` and the matching words for false, in any case, with white space around them, and any prefix of a word that is not ambiguous. `o` is ambiguous, so `on` and `off` need two letters.
26pub fn bool_in(s: &str) -> Result<bool, TypeError> {
27    let b = s.as_bytes();
28    let start = b.iter().position(|&c| !is_space(c)).unwrap_or(b.len());
29    let end = b.iter().rposition(|&c| !is_space(c)).map_or(start, |i| i + 1);
30    let word = &b[start..end];
31    let prefix = |of: &[u8], min: usize| {
32        word.len() >= min && word.len() <= of.len() && word.eq_ignore_ascii_case(&of[..word.len()])
33    };
34    let value = match word.first() {
35        Some(b't' | b'T') if prefix(b"true", 1) => Some(true),
36        Some(b'f' | b'F') if prefix(b"false", 1) => Some(false),
37        Some(b'y' | b'Y') if prefix(b"yes", 1) => Some(true),
38        Some(b'n' | b'N') if prefix(b"no", 1) => Some(false),
39        Some(b'o' | b'O') if prefix(b"on", 2) => Some(true),
40        Some(b'o' | b'O') if prefix(b"off", 2) => Some(false),
41        Some(b'1') if word.len() == 1 => Some(true),
42        Some(b'0') if word.len() == 1 => Some(false),
43        _ => None,
44    };
45    value.ok_or_else(|| TypeError::syntax("boolean", s))
46}
47
48/// The text output of `bool`.
49pub fn bool_out(v: bool, out: &mut Vec<u8>) {
50    out.push(if v { b't' } else { b'f' });
51}
52
53/// The text input of `"char"`: a backslash and three octal digits is that byte, else the first byte, and an empty string is 0. A character of more than one byte gives its first byte.
54pub fn char_in(s: &str) -> u8 {
55    match s.as_bytes() {
56        [b'\\', a @ b'0'..=b'7', b @ b'0'..=b'7', c @ b'0'..=b'7'] => {
57            ((a - b'0') << 6).wrapping_add((b - b'0') << 3).wrapping_add(c - b'0')
58        }
59        [first, ..] => *first,
60        [] => 0,
61    }
62}
63
64/// The text output of `"char"`: a byte above 127 is a backslash and three octal digits, and 0 is the empty string.
65pub fn char_out(v: u8, out: &mut Vec<u8>) {
66    if v >= 0x80 {
67        out.extend_from_slice(&[b'\\', b'0' + (v >> 6), b'0' + ((v >> 3) & 7), b'0' + (v & 7)]);
68    } else if v != 0 {
69        out.push(v);
70    }
71}
72
73/// The text input of `name`: the string cut to 63 bytes at the end of a character, with no error.
74pub fn name_in(s: &str) -> &str {
75    if s.len() <= NAME_MAX_BYTES {
76        return s;
77    }
78    let mut end = NAME_MAX_BYTES;
79    while !s.is_char_boundary(end) {
80        end -= 1;
81    }
82    &s[..end]
83}
84
85/// The text input of `bytea`. A string that starts with `\x` is hex, with white space allowed between the pairs of digits. Any other string is the escape format.
86pub fn bytea_in(s: &str) -> Result<Vec<u8>, TypeError> {
87    let b = s.as_bytes();
88    if let Some(hex) = b.strip_prefix(b"\\x") {
89        let mut out = Vec::with_capacity(hex.len() / 2);
90        let digit = |i: usize| {
91            (hex[i] as char).to_digit(16).map(|d| d as u8).ok_or_else(|| {
92                // The text has the whole character at the bad byte.
93                let rest = &s[s.len() - (hex.len() - i)..];
94                let c = rest.chars().next().unwrap_or_default();
95                TypeError::new(
96                    SqlState::INVALID_PARAMETER_VALUE,
97                    format!("invalid hexadecimal digit: \"{c}\""),
98                )
99            })
100        };
101        let mut i = 0;
102        while i < hex.len() {
103            if matches!(hex[i], b' ' | b'\n' | b'\t' | b'\r') {
104                i += 1;
105                continue;
106            }
107            let high = digit(i)?;
108            if i + 1 >= hex.len() {
109                return Err(TypeError::new(
110                    SqlState::INVALID_PARAMETER_VALUE,
111                    "invalid hexadecimal data: odd number of digits".to_string(),
112                ));
113            }
114            out.push(high << 4 | digit(i + 1)?);
115            i += 2;
116        }
117        return Ok(out);
118    }
119
120    let mut out = Vec::with_capacity(b.len());
121    let mut i = 0;
122    while i < b.len() {
123        match b[i..] {
124            [b'\\', a @ b'0'..=b'3', c @ b'0'..=b'7', d @ b'0'..=b'7', ..] => {
125                out.push((a - b'0') << 6 | (c - b'0') << 3 | (d - b'0'));
126                i += 4;
127            }
128            [b'\\', b'\\', ..] => {
129                out.push(b'\\');
130                i += 2;
131            }
132            [b'\\', ..] => {
133                return Err(TypeError::new(
134                    SqlState::INVALID_TEXT_REPRESENTATION,
135                    "invalid input syntax for type bytea".to_string(),
136                ));
137            }
138            [c, ..] => {
139                out.push(c);
140                i += 1;
141            }
142            [] => break,
143        }
144    }
145    Ok(out)
146}
147
148const HEX: &[u8; 16] = b"0123456789abcdef";
149
150/// The text output of `bytea`.
151pub fn bytea_out(v: &[u8], style: ByteaOutput, out: &mut Vec<u8>) {
152    match style {
153        ByteaOutput::Hex => {
154            out.reserve(2 + v.len() * 2);
155            out.extend_from_slice(b"\\x");
156            for &byte in v {
157                out.extend_from_slice(&[HEX[usize::from(byte >> 4)], HEX[usize::from(byte & 15)]]);
158            }
159        }
160        ByteaOutput::Escape => {
161            for &byte in v {
162                match byte {
163                    b'\\' => out.extend_from_slice(b"\\\\"),
164                    0x20..=0x7e => out.push(byte),
165                    _ => out.extend_from_slice(&[
166                        b'\\',
167                        b'0' + (byte >> 6),
168                        b'0' + ((byte >> 3) & 7),
169                        b'0' + (byte & 7),
170                    ]),
171                }
172            }
173        }
174    }
175}
176
177/// The text input of `uuid`: 32 hex digits in any case, with an optional pair of braces around them and an optional hyphen after any group of four digits.
178pub fn uuid_in(s: &str) -> Result<[u8; 16], TypeError> {
179    let syntax = || TypeError::syntax("uuid", s);
180    let b = s.as_bytes();
181    let braces = b.first() == Some(&b'{');
182    let mut i = usize::from(braces);
183    let mut uuid = [0u8; 16];
184    for (n, byte) in uuid.iter_mut().enumerate() {
185        let pair = b.get(i..i + 2).ok_or_else(syntax)?;
186        let high = (pair[0] as char).to_digit(16).ok_or_else(syntax)?;
187        let low = (pair[1] as char).to_digit(16).ok_or_else(syntax)?;
188        *byte = (high << 4 | low) as u8;
189        i += 2;
190        if b.get(i) == Some(&b'-') && n % 2 == 1 && n < 15 {
191            i += 1;
192        }
193    }
194    if braces {
195        if b.get(i) != Some(&b'}') {
196            return Err(syntax());
197        }
198        i += 1;
199    }
200    if i != b.len() {
201        return Err(syntax());
202    }
203    Ok(uuid)
204}
205
206/// The text output of `uuid`: lowercase, in groups of 8, 4, 4, 4 and 12 digits.
207pub fn uuid_out(v: &[u8; 16], out: &mut Vec<u8>) {
208    for (i, &byte) in v.iter().enumerate() {
209        if matches!(i, 4 | 6 | 8 | 10) {
210            out.push(b'-');
211        }
212        out.extend_from_slice(&[HEX[usize::from(byte >> 4)], HEX[usize::from(byte & 15)]]);
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    fn text(f: impl Fn(&mut Vec<u8>)) -> Vec<u8> {
221        let mut out = Vec::new();
222        f(&mut out);
223        out
224    }
225
226    #[test]
227    fn a_boolean_takes_a_prefix_that_is_not_ambiguous() {
228        for input in ["t", "TRUE", " tr ", "y", "yes", "on", "ON", "1", "\ttrue\n"] {
229            assert_eq!(bool_in(input), Ok(true), "{input:?}");
230        }
231        for input in ["f", "fAlSe", "n", "no", "of", "off", "0"] {
232            assert_eq!(bool_in(input), Ok(false), "{input:?}");
233        }
234        for input in ["", " ", "o", "truex", "yess", "onn", "01", "2", "t r"] {
235            assert_eq!(
236                bool_in(input).unwrap_err().message,
237                format!("invalid input syntax for type boolean: \"{input}\""),
238                "{input:?}"
239            );
240        }
241        assert_eq!(text(|out| bool_out(true, out)), b"t");
242    }
243
244    #[test]
245    fn a_char_is_one_byte_or_an_octal_escape() {
246        assert_eq!(char_in("a"), b'a');
247        assert_eq!(char_in("abc"), b'a');
248        assert_eq!(char_in(""), 0);
249        assert_eq!(char_in("\\101"), b'A');
250        assert_eq!(char_in("\\377"), 0xff);
251        assert_eq!(char_in("\\777"), 0xff);
252        assert_eq!(char_in("\\18"), b'\\');
253        assert_eq!(char_in("é"), 0xc3);
254        assert_eq!(text(|out| char_out(0xc3, out)), b"\\303");
255        assert_eq!(text(|out| char_out(b'r', out)), b"r");
256        assert_eq!(text(|out| char_out(0, out)), b"");
257    }
258
259    #[test]
260    fn a_name_is_cut_at_a_character() {
261        let long = "a".repeat(70);
262        assert_eq!(name_in(&long).len(), 63);
263        let wide = format!("{}é", "a".repeat(62));
264        assert_eq!(name_in(&wide), "a".repeat(62));
265        assert_eq!(name_in("pg_class"), "pg_class");
266    }
267
268    #[test]
269    fn bytea_reads_and_writes_both_formats() {
270        assert_eq!(bytea_in("\\x0aFF"), Ok(vec![0x0a, 0xff]));
271        assert_eq!(bytea_in("\\x 0a\n ff "), Ok(vec![0x0a, 0xff]));
272        assert_eq!(bytea_in("\\x"), Ok(vec![]));
273        assert_eq!(bytea_in("ab\\\\c\\001\\377"), Ok(b"ab\\c\x01\xff".to_vec()));
274        // Only a lowercase `x` starts the hex format, so this is the escape format.
275        assert!(bytea_in("\\X0a").is_err());
276        let odd = bytea_in("\\x0").unwrap_err();
277        assert_eq!(
278            (odd.sqlstate.as_str(), odd.message.as_str()),
279            ("22023", "invalid hexadecimal data: odd number of digits")
280        );
281        assert_eq!(bytea_in("\\x0g").unwrap_err().message, "invalid hexadecimal digit: \"g\"");
282        assert_eq!(bytea_in("\\xé0").unwrap_err().message, "invalid hexadecimal digit: \"é\"");
283        assert_eq!(bytea_in("\\x0 1").unwrap_err().message, "invalid hexadecimal digit: \" \"");
284        for input in ["\\", "a\\4", "\\400", "\\12"] {
285            let error = bytea_in(input).unwrap_err();
286            assert_eq!(
287                (error.sqlstate.as_str(), error.message.as_str()),
288                ("22P02", "invalid input syntax for type bytea"),
289                "{input:?}"
290            );
291        }
292        let value = b"a\\\x00\x7f\xff ~";
293        assert_eq!(text(|out| bytea_out(value, ByteaOutput::Hex, out)), b"\\x615c007fff207e");
294        assert_eq!(
295            text(|out| bytea_out(value, ByteaOutput::Escape, out)),
296            b"a\\\\\\000\\177\\377 ~"
297        );
298    }
299
300    #[test]
301    fn a_uuid_takes_braces_and_hyphens_after_groups_of_four() {
302        let uuid = [
303            0xa0, 0xee, 0xbc, 0x99, 0x9c, 0x0b, 0x4e, 0xf8, 0xbb, 0x6d, 0x6b, 0xb9, 0xbd, 0x38,
304            0x0a, 0x11,
305        ];
306        for input in [
307            "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
308            "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11",
309            "{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}",
310            "a0eebc999c0b4ef8bb6d6bb9bd380a11",
311            "a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11",
312            "{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}",
313        ] {
314            assert_eq!(uuid_in(input), Ok(uuid), "{input:?}");
315        }
316        for input in [
317            "",
318            "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a1",
319            "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a111",
320            "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11-",
321            "{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
322            "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}",
323            "a0e-ebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
324            "a0eebc99--9c0b-4ef8-bb6d-6bb9bd380a11",
325            " a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
326        ] {
327            assert_eq!(
328                uuid_in(input).unwrap_err().message,
329                format!("invalid input syntax for type uuid: \"{input}\""),
330                "{input:?}"
331            );
332        }
333        assert_eq!(text(|out| uuid_out(&uuid, out)), b"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11");
334    }
335}