Skip to main content

rupg_types/
number.rs

1//! Integers and OIDs in the text format.
2//!
3//! The input functions are ports of `pg_strtoint16_safe`, `pg_strtoint32_safe` and `pg_strtoint64_safe` in `src/backend/utils/adt/numutils.c`, and of `uint32in_subr` for `oid`. The rules are not the same for the two: an integer takes `0x`, `0o` and `0b` and underscores between digits, and an `oid` takes what `strtoul` with base 0 takes, which reads a leading `0` as octal and takes a minus sign.
4//!
5//! Lifted from `crates/rudb-pgtypes/src/number.rs` of tamnd/rudb at f5f7065a (spec/04 section 4.9).
6
7use crate::error::TypeError;
8
9/// `isspace` in the C locale.
10pub(crate) fn is_space(b: u8) -> bool {
11    matches!(b, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r')
12}
13
14/// The value of a digit in `base`, if it is one.
15pub(crate) fn digit(b: u8, base: u64) -> Option<u64> {
16    let value = match b {
17        b'0'..=b'9' => b - b'0',
18        b'a'..=b'f' => b - b'a' + 10,
19        b'A'..=b'F' => b - b'A' + 10,
20        _ => return None,
21    };
22    (u64::from(value) < base).then_some(u64::from(value))
23}
24
25/// Reads an integer whose minimum is `-limit`. PostgreSQL first tries plain decimal digits with an optional `-`, and an overflow there is an error at once, even when other bytes follow. Any other input goes to the slow path, which reads white space, a sign, a base prefix and underscores.
26fn parse_int(s: &str, limit: u64, type_name: &str) -> Result<i128, TypeError> {
27    let b = s.as_bytes();
28    let at = |i: usize| b.get(i).copied().unwrap_or(0);
29    let range = || TypeError::range(s, type_name);
30    let syntax = || TypeError::syntax(type_name, s);
31    let finish = |tmp: u64, neg: bool| match neg {
32        true if tmp > limit => Err(range()),
33        true => Ok(-i128::from(tmp)),
34        false if tmp >= limit => Err(range()),
35        false => Ok(i128::from(tmp)),
36    };
37
38    let mut i = usize::from(at(0) == b'-');
39    if at(i).is_ascii_digit() {
40        let mut tmp = 0u64;
41        while at(i).is_ascii_digit() {
42            if tmp > limit / 10 {
43                return Err(range());
44            }
45            tmp = tmp * 10 + u64::from(at(i) - b'0');
46            i += 1;
47        }
48        if i == b.len() {
49            return finish(tmp, at(0) == b'-');
50        }
51    }
52
53    i = 0;
54    while is_space(at(i)) {
55        i += 1;
56    }
57    let neg = at(i) == b'-';
58    if neg || at(i) == b'+' {
59        i += 1;
60    }
61    let base = match (at(i), at(i + 1)) {
62        (b'0', b'x' | b'X') => 16,
63        (b'0', b'o' | b'O') => 8,
64        (b'0', b'b' | b'B') => 2,
65        _ => 10,
66    };
67    if base != 10 {
68        i += 2;
69    }
70    let first = i;
71    let mut tmp = 0u64;
72    loop {
73        if let Some(d) = digit(at(i), base) {
74            if tmp > limit / base {
75                return Err(range());
76            }
77            tmp = tmp * base + d;
78            i += 1;
79        } else if at(i) == b'_' {
80            // Only a decimal number cannot start with an underscore. An underscore must have a digit after it.
81            if base == 10 && i == first {
82                return Err(syntax());
83            }
84            i += 1;
85            if digit(at(i), base).is_none() {
86                return Err(syntax());
87            }
88        } else {
89            break;
90        }
91    }
92    if i == first {
93        return Err(syntax());
94    }
95    while is_space(at(i)) {
96        i += 1;
97    }
98    if i != b.len() {
99        return Err(syntax());
100    }
101    finish(tmp, neg)
102}
103
104/// The text input of `int2`.
105pub fn int2_in(s: &str) -> Result<i16, TypeError> {
106    parse_int(s, 1 << 15, "smallint").map(|v| v as i16)
107}
108
109/// The text input of `int4`.
110pub fn int4_in(s: &str) -> Result<i32, TypeError> {
111    parse_int(s, 1 << 31, "integer").map(|v| v as i32)
112}
113
114/// The text input of `int8`.
115pub fn int8_in(s: &str) -> Result<i64, TypeError> {
116    parse_int(s, 1 << 63, "bigint").map(|v| v as i64)
117}
118
119/// What `strtoul(s, &end, 0)` gives with a 64-bit `unsigned long`: the value, the end of the number, which is 0 when there is no number, and whether the value overflowed.
120fn strtoul(b: &[u8]) -> (u64, usize, bool) {
121    let at = |i: usize| b.get(i).copied().unwrap_or(0);
122    let mut i = 0;
123    while is_space(at(i)) {
124        i += 1;
125    }
126    let neg = at(i) == b'-';
127    if neg || at(i) == b'+' {
128        i += 1;
129    }
130    // `0x` is a prefix only when a hex digit follows it. Else the `0` is an octal number.
131    let base = match (at(i), at(i + 1)) {
132        (b'0', b'x' | b'X') if digit(at(i + 2), 16).is_some() => {
133            i += 2;
134            16
135        }
136        (b'0', _) => 8,
137        _ => 10,
138    };
139    let first = i;
140    let mut value = 0u64;
141    let mut overflow = false;
142    while let Some(d) = digit(at(i), base) {
143        match value.checked_mul(base).and_then(|v| v.checked_add(d)) {
144            Some(v) => value = v,
145            None => overflow = true,
146        }
147        i += 1;
148    }
149    if i == first {
150        return (0, 0, false);
151    }
152    match (overflow, neg) {
153        (true, _) => (u64::MAX, i, true),
154        (false, true) => (value.wrapping_neg(), i, false),
155        (false, false) => (value, i, false),
156    }
157}
158
159/// The text input of `oid`. A negative number from -2147483648 to -1 is the OID with the same bits, so `-1` is 4294967295.
160pub fn oid_in(s: &str) -> Result<u32, TypeError> {
161    uint32_in(s, "oid")
162}
163
164/// `uint32in_subr` for all of `s`: the text input of `oid`, `xid` and `cid`. The errors show `type_name`.
165pub fn uint32_in(s: &str, type_name: &str) -> Result<u32, TypeError> {
166    let b = s.as_bytes();
167    let (value, mut end, overflow) = strtoul(b);
168    if end == 0 {
169        return Err(TypeError::syntax(type_name, s));
170    }
171    if overflow {
172        return Err(TypeError::range(s, type_name));
173    }
174    while end < b.len() && is_space(b[end]) {
175        end += 1;
176    }
177    if end != b.len() {
178        return Err(TypeError::syntax(type_name, s));
179    }
180    let result = value as u32;
181    if value != u64::from(result) && value != i64::from(result as i32) as u64 {
182        return Err(TypeError::range(s, type_name));
183    }
184    Ok(result)
185}
186
187/// `uint64in_subr` for all of `s`: the text input of `xid8` and `oid8`. The errors show `type_name`.
188///
189/// # Errors
190///
191/// `22P02` for text that is not a number, and `22003` for a number out of the range of `u64`.
192pub fn uint64_in(s: &str, type_name: &str) -> Result<u64, TypeError> {
193    let b = s.as_bytes();
194    let (value, mut end, overflow) = strtoul(b);
195    if end == 0 {
196        return Err(TypeError::syntax(type_name, s));
197    }
198    if overflow {
199        return Err(TypeError::range(s, type_name));
200    }
201    while end < b.len() && is_space(b[end]) {
202        end += 1;
203    }
204    if end != b.len() {
205        return Err(TypeError::syntax(type_name, s));
206    }
207    Ok(value)
208}
209
210/// `uint32in_subr` with an end pointer: a number as `oid_in` reads it, at the start of `s`, and the length of its text. The caller reads what comes after it. An error shows all of `s`.
211pub(crate) fn uint32_in_subr(s: &str, type_name: &str) -> Result<(u32, usize), TypeError> {
212    let (value, end, overflow) = strtoul(s.as_bytes());
213    if end == 0 {
214        return Err(TypeError::syntax(type_name, s));
215    }
216    let result = value as u32;
217    if overflow || (value != u64::from(result) && value != i64::from(result as i32) as u64) {
218        return Err(TypeError::range(s, type_name));
219    }
220    Ok((result, end))
221}
222
223const PAIRS: &[u8; 200] = b"00010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899";
224
225/// Writes the decimal digits of `n`.
226pub fn u64_out(mut n: u64, out: &mut Vec<u8>) {
227    let mut buf = [0u8; 20];
228    let mut at = buf.len();
229    while n >= 100 {
230        let pair = (n % 100) as usize * 2;
231        n /= 100;
232        at -= 2;
233        buf[at..at + 2].copy_from_slice(&PAIRS[pair..pair + 2]);
234    }
235    if n >= 10 {
236        let pair = n as usize * 2;
237        at -= 2;
238        buf[at..at + 2].copy_from_slice(&PAIRS[pair..pair + 2]);
239    } else {
240        at -= 1;
241        buf[at] = b'0' + n as u8;
242    }
243    out.extend_from_slice(&buf[at..]);
244}
245
246/// The text output of `int2`, `int4` and `int8`: the digits with a `-` for a negative value.
247pub fn int_out(n: i64, out: &mut Vec<u8>) {
248    if n < 0 {
249        out.push(b'-');
250    }
251    u64_out(n.unsigned_abs(), out);
252}
253
254/// The text output of `oid`, which is unsigned.
255pub fn oid_out(n: u32, out: &mut Vec<u8>) {
256    u64_out(u64::from(n), out);
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    fn text(f: impl Fn(&mut Vec<u8>)) -> String {
264        let mut out = Vec::new();
265        f(&mut out);
266        String::from_utf8(out).unwrap()
267    }
268
269    #[test]
270    fn integers_take_the_forms_of_numutils() {
271        for (input, value) in [
272            ("0", 0),
273            ("-0", 0),
274            (" 12 ", 12),
275            ("+7", 7),
276            ("0x1F", 31),
277            ("-0X1f", -31),
278            ("0o17", 15),
279            ("0b101", 5),
280            ("1_000", 1000),
281            ("0x_1F", 31),
282            ("2147483647", i32::MAX),
283            ("-2147483648", i32::MIN),
284            ("-0x80000000", i32::MIN),
285            ("\t\n42\r", 42),
286        ] {
287            assert_eq!(int4_in(input), Ok(value), "{input:?}");
288        }
289        for input in ["", " ", "1e3", "1.5", "_1", "1_", "1__0", "0x", "0b2", "- 1", "1 2", "0o8"] {
290            let error = int4_in(input).unwrap_err();
291            assert_eq!(error.sqlstate.as_str(), "22P02", "{input:?}");
292            assert_eq!(
293                error.message,
294                format!("invalid input syntax for type integer: \"{input}\"")
295            );
296        }
297        for input in ["2147483648", "-2147483649", "0x80000000", " 99999999999 ", "99999999999x"] {
298            let error = int4_in(input).unwrap_err();
299            assert_eq!(error.sqlstate.as_str(), "22003", "{input:?}");
300            assert_eq!(
301                error.message,
302                format!("value \"{input}\" is out of range for type integer")
303            );
304        }
305        assert_eq!(int2_in("-32768"), Ok(i16::MIN));
306        assert_eq!(
307            int2_in("32768").unwrap_err().message,
308            "value \"32768\" is out of range for type smallint"
309        );
310        assert_eq!(int8_in("-9223372036854775808"), Ok(i64::MIN));
311        assert_eq!(int8_in("0x7FFF_FFFF_FFFF_FFFF"), Ok(i64::MAX));
312        assert_eq!(
313            int8_in("x").unwrap_err().message,
314            "invalid input syntax for type bigint: \"x\""
315        );
316    }
317
318    #[test]
319    fn an_oid_takes_what_strtoul_takes() {
320        for (input, value) in [
321            ("0", 0),
322            ("16384", 16384),
323            (" 010 ", 8),
324            ("0x10", 16),
325            ("-1", u32::MAX),
326            ("-2147483648", 1 << 31),
327            ("4294967295", u32::MAX),
328            ("+5", 5),
329        ] {
330            assert_eq!(oid_in(input), Ok(value), "{input:?}");
331        }
332        for input in ["", "x", "0x", "08", "1_0", "1.0", "-"] {
333            assert_eq!(oid_in(input).unwrap_err().sqlstate.as_str(), "22P02", "{input:?}");
334        }
335        for input in ["4294967296", "-2147483649", "99999999999999999999999"] {
336            assert_eq!(
337                oid_in(input).unwrap_err().message,
338                format!("value \"{input}\" is out of range for type oid")
339            );
340        }
341    }
342
343    #[test]
344    fn integers_print_as_digits() {
345        for n in [0, 7, -7, 10, 99, 100, 12345, i64::MAX, i64::MIN, -1_000_000] {
346            assert_eq!(text(|out| int_out(n, out)), n.to_string());
347        }
348        assert_eq!(text(|out| oid_out(u32::MAX, out)), "4294967295");
349    }
350}