Skip to main content

rbdc_pg/types/
hstore.rs

1use crate::types::decode::Decode;
2use crate::types::encode::{Encode, IsNull};
3use crate::value::{PgValueFormat, PgValueRef};
4use rbdc::Error;
5use rbs::Value;
6use std::collections::HashMap;
7use std::fmt::{Display, Formatter};
8
9/// PostgreSQL HStore type for key-value pairs
10///
11/// HStore is a PostgreSQL extension module that implements the hstore data type
12/// for storing sets of key/value pairs within a single PostgreSQL value.
13///
14/// # Examples
15///
16/// ```ignore
17/// // Create an hstore from a HashMap
18/// let mut map = HashMap::new();
19/// map.insert("name".to_string(), "John".to_string());
20/// map.insert("age".to_string(), "30".to_string());
21/// let hstore = Hstore(map);
22///
23/// // Text format representation: "name=>John, age=>30"
24/// ```
25///
26/// This implementation supports both TEXT and BINARY formats:
27/// - TEXT: "key1=>value1, key2=>value2"
28/// - BINARY: 32-bit header + count + entries
29#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
30pub struct Hstore(pub HashMap<String, String>);
31
32impl Default for Hstore {
33    fn default() -> Self {
34        Self(HashMap::new())
35    }
36}
37
38impl Display for Hstore {
39    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
40        let pairs: Vec<String> = self
41            .0
42            .iter()
43            .map(|(k, v)| format!("{}=>{}", k, v))
44            .collect();
45        write!(f, "{}", pairs.join(", "))
46    }
47}
48
49impl From<HashMap<String, String>> for Hstore {
50    fn from(map: HashMap<String, String>) -> Self {
51        Self(map)
52    }
53}
54
55impl From<Hstore> for Value {
56    fn from(arg: Hstore) -> Self {
57        // Store as string representation: "key1=>value1, key2=>value2"
58        let s = format!("{}", arg);
59        Value::Ext("hstore", Box::new(Value::String(s)))
60    }
61}
62
63impl Decode for Hstore {
64    fn decode(value: PgValueRef) -> Result<Self, Error> {
65        Ok(match value.format() {
66            PgValueFormat::Binary => {
67                // Binary format:
68                // 4 bytes: number of entries (int32)
69                // For each entry:
70                //   4 bytes: key length
71                //   key bytes
72                //   4 bytes: value length (-1 for NULL)
73                //   value bytes (if not NULL)
74                let bytes = value.as_bytes()?;
75                if bytes.len() < 4 {
76                    return Err(Error::from("HSTORE binary data too short"));
77                }
78
79                let mut buf = &bytes[..];
80                use byteorder::{BigEndian, ReadBytesExt};
81
82                let count = buf.read_i32::<BigEndian>()? as usize;
83                let mut map = HashMap::new();
84
85                for _ in 0..count {
86                    if buf.len() < 8 {
87                        return Err(Error::from("HSTORE binary entry too short"));
88                    }
89
90                    let key_len = buf.read_i32::<BigEndian>()? as usize;
91                    let val_len = buf.read_i32::<BigEndian>()? as i32;
92
93                    if buf.len() < key_len {
94                        return Err(Error::from("HSTORE binary key too short"));
95                    }
96
97                    let key = String::from_utf8(buf[..key_len].to_vec())
98                        .map_err(|e| Error::from(format!("Invalid HSTORE key: {}", e)))?;
99                    buf = &buf[key_len..];
100
101                    if val_len < 0 {
102                        // NULL value
103                        map.insert(key, "null".to_string());
104                    } else {
105                        let val_len = val_len as usize;
106                        if buf.len() < val_len {
107                            return Err(Error::from("HSTORE binary value too short"));
108                        }
109
110                        let val = String::from_utf8(buf[..val_len].to_vec())
111                            .map_err(|e| Error::from(format!("Invalid HSTORE value: {}", e)))?;
112                        buf = &buf[val_len..];
113
114                        map.insert(key, val);
115                    }
116                }
117
118                Self(map)
119            }
120            PgValueFormat::Text => {
121                // Text format: "key1=>value1, key2=>value2"
122                let s = value.as_str()?.trim();
123                if s.is_empty() {
124                    return Ok(Self(HashMap::new()));
125                }
126
127                let mut map = HashMap::new();
128                // Parse pairs separated by comma
129                for pair in s.split(',') {
130                    let pair = pair.trim();
131                    if pair.is_empty() {
132                        continue;
133                    }
134
135                    // Find the => separator
136                    if let Some(pos) = pair.find("=>") {
137                        let key = pair[..pos].trim().to_string();
138                        let value = pair[pos + 2..].trim().to_string();
139                        map.insert(key, value);
140                    } else {
141                        return Err(Error::from(format!(
142                            "Invalid HSTORE format: '{}'. Expected 'key=>value'",
143                            pair
144                        )));
145                    }
146                }
147
148                Self(map)
149            }
150        })
151    }
152}
153
154impl Encode for Hstore {
155    fn encode(self, _buf: &mut crate::arguments::PgArgumentBuffer) -> Result<IsNull, Error> {
156        // HSTORE encoding is complex
157        // Applications should use hstore(text) or hstore(text, text) in their query
158        Err(Error::from(
159            "HStore encoding not supported. Use hstore(text) or hstore(text, text) in your query instead."
160        ))
161    }
162}