Skip to main content

sentinel_driver/types/
multirange.rs

1use bytes::{BufMut, BytesMut};
2
3use crate::error::{Error, Result};
4use crate::types::range::PgRange;
5use crate::types::{FromSql, Oid, ToSql};
6
7/// PostgreSQL multirange type (PG 14+).
8///
9/// A multirange is an ordered list of non-overlapping ranges.
10/// Wire format: count(i32) + [length(i32) + range_bytes] per range.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct PgMultirange<T> {
13    pub ranges: Vec<PgRange<T>>,
14    pub multirange_oid: Oid,
15    pub range_oid: Oid,
16    pub element_oid: Oid,
17}
18
19impl<T: ToSql> ToSql for PgMultirange<T> {
20    fn oid(&self) -> Oid {
21        self.multirange_oid
22    }
23
24    fn to_sql(&self, buf: &mut BytesMut) -> Result<()> {
25        buf.put_i32(self.ranges.len() as i32);
26
27        for range in &self.ranges {
28            let len_pos = buf.len();
29            buf.put_i32(0); // placeholder for range length
30            let data_start = buf.len();
31            range.to_sql(buf)?;
32            let data_len = (buf.len() - data_start) as i32;
33            buf[len_pos..len_pos + 4].copy_from_slice(&data_len.to_be_bytes());
34        }
35
36        Ok(())
37    }
38}
39
40impl<T: FromSql> PgMultirange<T> {
41    /// Decode a multirange from binary format. Requires OIDs since generic
42    /// types cannot determine them.
43    pub fn from_sql_with_oids(
44        buf: &[u8],
45        multirange_oid: Oid,
46        range_oid: Oid,
47        element_oid: Oid,
48    ) -> Result<Self> {
49        if buf.len() < 4 {
50            return Err(Error::Decode("multirange: header too short".into()));
51        }
52
53        let count = i32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize;
54        let mut offset = 4;
55        let mut ranges = Vec::with_capacity(count);
56
57        for _ in 0..count {
58            if offset + 4 > buf.len() {
59                return Err(Error::Decode("multirange: range length truncated".into()));
60            }
61            let range_len = i32::from_be_bytes([
62                buf[offset],
63                buf[offset + 1],
64                buf[offset + 2],
65                buf[offset + 3],
66            ]) as usize;
67            offset += 4;
68
69            if offset + range_len > buf.len() {
70                return Err(Error::Decode("multirange: range data truncated".into()));
71            }
72
73            let range = PgRange::from_sql_with_oids(
74                &buf[offset..offset + range_len],
75                range_oid,
76                element_oid,
77            )?;
78            ranges.push(range);
79            offset += range_len;
80        }
81
82        Ok(PgMultirange {
83            ranges,
84            multirange_oid,
85            range_oid,
86            element_oid,
87        })
88    }
89}