1use crate::types::decode::Decode;
2use crate::types::encode::{Encode, IsNull};
3use crate::value::{PgValue, PgValueFormat};
4use rbdc::Error;
5use rbs::Value;
6use std::collections::HashMap;
7use std::fmt::{Display, Formatter};
8
9#[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.0
41 .iter()
42 .map(|(k, v)| format!("{}=>{}", k, v))
43 .collect();
44 write!(f, "{}", pairs.join(", "))
45 }
46}
47
48impl From<HashMap<String, String>> for Hstore {
49 fn from(map: HashMap<String, String>) -> Self {
50 Self(map)
51 }
52}
53
54impl From<Hstore> for Value {
55 fn from(arg: Hstore) -> Self {
56 let s = format!("{}", arg);
58 Value::Ext("hstore", Box::new(Value::String(s)))
59 }
60}
61
62impl Decode for Hstore {
63 fn decode(value: PgValue) -> Result<Self, Error> {
64 Ok(match value.format() {
65 PgValueFormat::Binary => {
66 let bytes = value.as_bytes()?;
74 if bytes.len() < 4 {
75 return Err(Error::from("HSTORE binary data too short"));
76 }
77
78 let mut buf = &bytes[..];
79 use byteorder::{BigEndian, ReadBytesExt};
80
81 let count = buf.read_i32::<BigEndian>()? as usize;
82 let mut map = HashMap::new();
83
84 for _ in 0..count {
85 if buf.len() < 8 {
86 return Err(Error::from("HSTORE binary entry too short"));
87 }
88
89 let key_len = buf.read_i32::<BigEndian>()? as usize;
90 let val_len = buf.read_i32::<BigEndian>()? as i32;
91
92 if buf.len() < key_len {
93 return Err(Error::from("HSTORE binary key too short"));
94 }
95
96 let key = String::from_utf8(buf[..key_len].to_vec())
97 .map_err(|e| Error::from(format!("Invalid HSTORE key: {}", e)))?;
98 buf = &buf[key_len..];
99
100 if val_len < 0 {
101 map.insert(key, "null".to_string());
103 } else {
104 let val_len = val_len as usize;
105 if buf.len() < val_len {
106 return Err(Error::from("HSTORE binary value too short"));
107 }
108
109 let val = String::from_utf8(buf[..val_len].to_vec())
110 .map_err(|e| Error::from(format!("Invalid HSTORE value: {}", e)))?;
111 buf = &buf[val_len..];
112
113 map.insert(key, val);
114 }
115 }
116
117 Self(map)
118 }
119 PgValueFormat::Text => {
120 let s = value.as_str()?.trim();
122 if s.is_empty() {
123 return Ok(Self(HashMap::new()));
124 }
125
126 let mut map = HashMap::new();
127 for pair in s.split(',') {
129 let pair = pair.trim();
130 if pair.is_empty() {
131 continue;
132 }
133
134 if let Some(pos) = pair.find("=>") {
136 let key = pair[..pos].trim().to_string();
137 let value = pair[pos + 2..].trim().to_string();
138 map.insert(key, value);
139 } else {
140 return Err(Error::from(format!(
141 "Invalid HSTORE format: '{}'. Expected 'key=>value'",
142 pair
143 )));
144 }
145 }
146
147 Self(map)
148 }
149 })
150 }
151}
152
153impl Encode for Hstore {
154 fn encode(self, _buf: &mut crate::arguments::PgArgumentBuffer) -> Result<IsNull, Error> {
155 Err(Error::from(
158 "HStore encoding not supported. Use hstore(text) or hstore(text, text) in your query instead."
159 ))
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use crate::types::decode::Decode;
167 use crate::value::{PgValue, PgValueFormat};
168
169 #[test]
170 fn test_display() {
171 let mut map = HashMap::new();
172 map.insert("name".to_string(), "John".to_string());
173 map.insert("age".to_string(), "30".to_string());
174 let hstore = Hstore(map);
175 let display = format!("{}", hstore);
176 assert!(display.contains("name=>John"));
178 assert!(display.contains("age=>30"));
179 }
180
181 #[test]
182 fn test_default() {
183 let hstore = Hstore::default();
184 assert_eq!(hstore.0.len(), 0);
185 }
186
187 #[test]
188 fn test_from_hashmap() {
189 let mut map = HashMap::new();
190 map.insert("key".to_string(), "value".to_string());
191 let hstore: Hstore = map.into();
192 assert_eq!(hstore.0.get("key"), Some(&"value".to_string()));
193 }
194
195 #[test]
196 fn test_decode_text_empty() {
197 let hstore: Hstore = Decode::decode(PgValue {
198 value: Some(b"".to_vec()),
199 type_info: crate::type_info::PgTypeInfo::HSTORE,
200 format: PgValueFormat::Text,
201 timezone_sec: None,
202 }).unwrap();
203 assert_eq!(hstore.0.len(), 0);
204 }
205
206 #[test]
207 fn test_decode_text_single() {
208 let s = "name=>John";
209 let hstore: Hstore = Decode::decode(PgValue {
210 value: Some(s.as_bytes().to_vec()),
211 type_info: crate::type_info::PgTypeInfo::HSTORE,
212 format: PgValueFormat::Text,
213 timezone_sec: None,
214 }).unwrap();
215 assert_eq!(hstore.0.get("name"), Some(&"John".to_string()));
216 }
217
218 #[test]
219 fn test_decode_text_multiple() {
220 let s = "name=>John, age=>30, city=>NYC";
221 let hstore: Hstore = Decode::decode(PgValue {
222 value: Some(s.as_bytes().to_vec()),
223 type_info: crate::type_info::PgTypeInfo::HSTORE,
224 format: PgValueFormat::Text,
225 timezone_sec: None,
226 }).unwrap();
227 assert_eq!(hstore.0.len(), 3);
228 assert_eq!(hstore.0.get("name"), Some(&"John".to_string()));
229 assert_eq!(hstore.0.get("age"), Some(&"30".to_string()));
230 assert_eq!(hstore.0.get("city"), Some(&"NYC".to_string()));
231 }
232
233 #[test]
234 fn test_from_value() {
235 let mut map = HashMap::new();
236 map.insert("key".to_string(), "value".to_string());
237 let hstore = Hstore(map);
238 let value: Value = hstore.into();
239 match value {
240 Value::Ext(type_name, boxed) => {
241 assert_eq!(type_name, "hstore");
242 if let Value::String(s) = *boxed {
243 assert!(s.contains("key"));
244 assert!(s.contains("value"));
245 } else {
246 panic!("Expected String");
247 }
248 }
249 _ => panic!("Expected Ext variant"),
250 }
251 }
252
253 #[test]
254 fn test_equality() {
255 let mut map1 = HashMap::new();
256 map1.insert("key".to_string(), "value".to_string());
257 let h1 = Hstore(map1.clone());
258
259 let h2 = Hstore(map1);
260
261 assert_eq!(h1, h2);
262 }
263}