Skip to main content

rupg_types/
typmod.rs

1//! The typmod of each type that has one, as `RowDescription` and `pg_attribute.atttypmod` give it.
2//!
3//! Clients decode the typmod, so the encoding must be exact. A typmod of -1 means that the type has no modifier.
4//!
5//! Lifted from `crates/rudb-pgtypes/src/typmod.rs` of tamnd/rudb at f5f7065a (spec/04 section 4.9).
6
7/// `VARHDRSZ`, which the typmod of the string types and of `numeric` adds for old reasons.
8pub const VARHDRSZ: i32 = 4;
9
10/// `MAX_INTERVAL_PRECISION` and `MAX_TIMESTAMP_PRECISION`.
11pub const MAX_TIME_PRECISION: i32 = 6;
12
13/// The typmod of `varchar(n)` and `bpchar(n)`.
14pub const fn char_typmod(n: i32) -> i32 {
15    n + VARHDRSZ
16}
17
18/// The `n` of `varchar(n)` or `bpchar(n)`, if the typmod has one.
19pub const fn char_length(typmod: i32) -> Option<i32> {
20    if typmod > VARHDRSZ { Some(typmod - VARHDRSZ) } else { None }
21}
22
23/// The typmod of `numeric(precision, scale)`, `make_numeric_typmod` in `numeric.c`. The scale can be negative.
24pub const fn numeric_typmod(precision: i32, scale: i32) -> i32 {
25    ((precision << 16) | (scale & 0x7ff)) + VARHDRSZ
26}
27
28/// The precision and the scale of a `numeric` typmod. The scale is 11 bits with a sign.
29pub const fn numeric_precision_scale(typmod: i32) -> Option<(i32, i32)> {
30    if typmod < VARHDRSZ {
31        return None;
32    }
33    let packed = typmod - VARHDRSZ;
34    Some(((packed >> 16) & 0xffff, ((packed & 0x7ff) ^ 1024) - 1024))
35}
36
37/// A field of an interval qualifier such as `DAY TO SECOND`. The value is the bit of the field in the range mask, from `src/include/utils/datetime.h`.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[repr(u8)]
40pub enum IntervalField {
41    Month = 1,
42    Year = 2,
43    Day = 3,
44    Hour = 10,
45    Minute = 11,
46    Second = 12,
47}
48
49/// `INTERVAL_FULL_RANGE`, the range mask of an interval with no qualifier.
50pub const INTERVAL_FULL_RANGE: i32 = 0x7fff;
51
52/// `INTERVAL_FULL_PRECISION`, the precision of an interval with no precision.
53pub const INTERVAL_FULL_PRECISION: i32 = 0xffff;
54
55/// The range mask of a list of fields: `YEAR TO MONTH` is `[Year, Month]`.
56pub fn interval_range(fields: &[IntervalField]) -> i32 {
57    fields.iter().fold(0, |mask, &field| mask | 1 << field as u8)
58}
59
60/// `INTERVAL_TYPMOD`: the range mask in the upper half and the precision in the lower half.
61pub const fn interval_typmod(precision: i32, range: i32) -> i32 {
62    ((range & 0x7fff) << 16) | (precision & 0xffff)
63}
64
65/// The precision and the range mask of an interval typmod.
66pub const fn interval_precision_range(typmod: i32) -> Option<(i32, i32)> {
67    if typmod < 0 {
68        return None;
69    }
70    Some((typmod & 0xffff, (typmod >> 16) & 0x7fff))
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn the_typmods_match_the_table_in_the_notes() {
79        assert_eq!(char_typmod(255), 259);
80        assert_eq!(char_length(259), Some(255));
81        assert_eq!(char_length(-1), None);
82        assert_eq!(numeric_typmod(10, 2), 655366);
83        assert_eq!(numeric_precision_scale(655366), Some((10, 2)));
84        assert_eq!(numeric_precision_scale(numeric_typmod(5, -3)), Some((5, -3)));
85        assert_eq!(numeric_precision_scale(numeric_typmod(1000, 1000)), Some((1000, 1000)));
86        assert_eq!(numeric_precision_scale(-1), None);
87        // `interval day to second(3)`, as `pg_attribute` stores it on the oracle.
88        let range = interval_range(&[
89            IntervalField::Day,
90            IntervalField::Hour,
91            IntervalField::Minute,
92            IntervalField::Second,
93        ]);
94        assert_eq!(interval_typmod(3, range), 470286339);
95        assert_eq!(interval_precision_range(470286339), Some((3, range)));
96        assert_eq!(interval_typmod(INTERVAL_FULL_PRECISION, INTERVAL_FULL_RANGE), 0x7fff_ffff);
97    }
98}