sqlx_postgres/types/
hstore.rs

1use std::{
2    collections::{btree_map, BTreeMap},
3    mem,
4    ops::{Deref, DerefMut},
5    str,
6};
7
8use crate::{
9    decode::Decode,
10    encode::{Encode, IsNull},
11    error::BoxDynError,
12    types::Type,
13    PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres,
14};
15use serde::{Deserialize, Serialize};
16use sqlx_core::bytes::Buf;
17
18/// Key-value support (`hstore`) for Postgres.
19///
20/// SQLx currently maps `hstore` to a `BTreeMap<String, Option<String>>` but this may be expanded in
21/// future to allow for user defined types.
22///
23/// See [the Postgres manual, Appendix F, Section 18][PG.F.18]
24///
25/// [PG.F.18]: https://www.postgresql.org/docs/current/hstore.html
26///
27/// ### Note: Requires Postgres 8.3+
28/// Introduced as a method for storing unstructured data, the `hstore` extension was first added in
29/// Postgres 8.3.
30///
31///
32/// ### Note: Extension Required
33/// The `hstore` extension is not enabled by default in Postgres. You will need to do so explicitly:
34///
35/// ```ignore
36/// CREATE EXTENSION IF NOT EXISTS hstore;
37/// ```
38///
39/// # Examples
40///
41/// ```
42/// # use sqlx_postgres::types::PgHstore;
43/// // Shows basic usage of the PgHstore type.
44/// //
45/// #[derive(Clone, Debug, Default, Eq, PartialEq)]
46/// struct UserCreate<'a> {
47///     username: &'a str,
48///     password: &'a str,
49///     additional_data: PgHstore
50/// }
51///
52/// let mut new_user = UserCreate {
53///     username: "name.surname@email.com",
54///     password: "@super_secret_1",
55///     ..Default::default()
56/// };
57///
58/// new_user.additional_data.insert("department".to_string(), Some("IT".to_string()));
59/// new_user.additional_data.insert("equipment_issued".to_string(), None);
60/// ```
61/// ```ignore
62/// query_scalar::<_, i64>(
63///     "insert into user(username, password, additional_data) values($1, $2, $3) returning id"
64/// )
65/// .bind(new_user.username)
66/// .bind(new_user.password)
67/// .bind(new_user.additional_data)
68/// .fetch_one(pg_conn)
69/// .await?;
70/// ```
71///
72/// ```
73/// # use sqlx_postgres::types::PgHstore;
74/// // PgHstore implements FromIterator to simplify construction.
75/// //
76/// let additional_data = PgHstore::from_iter([
77///     ("department".to_string(), Some("IT".to_string())),
78///     ("equipment_issued".to_string(), None),
79/// ]);
80///
81/// assert_eq!(additional_data["department"], Some("IT".to_string()));
82/// assert_eq!(additional_data["equipment_issued"], None);
83///
84/// // Also IntoIterator for ease of iteration.
85/// //
86/// for (key, value) in additional_data {
87///     println!("{key}: {value:?}");
88/// }
89/// ```
90///
91#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
92pub struct PgHstore(pub BTreeMap<String, Option<String>>);
93
94impl Deref for PgHstore {
95    type Target = BTreeMap<String, Option<String>>;
96
97    fn deref(&self) -> &Self::Target {
98        &self.0
99    }
100}
101
102impl DerefMut for PgHstore {
103    fn deref_mut(&mut self) -> &mut Self::Target {
104        &mut self.0
105    }
106}
107
108impl FromIterator<(String, String)> for PgHstore {
109    fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
110        iter.into_iter().map(|(k, v)| (k, Some(v))).collect()
111    }
112}
113
114impl FromIterator<(String, Option<String>)> for PgHstore {
115    fn from_iter<T: IntoIterator<Item = (String, Option<String>)>>(iter: T) -> Self {
116        let mut result = Self::default();
117
118        for (key, value) in iter {
119            result.0.insert(key, value);
120        }
121
122        result
123    }
124}
125
126impl IntoIterator for PgHstore {
127    type Item = (String, Option<String>);
128    type IntoIter = btree_map::IntoIter<String, Option<String>>;
129
130    fn into_iter(self) -> Self::IntoIter {
131        self.0.into_iter()
132    }
133}
134
135impl Type<Postgres> for PgHstore {
136    fn type_info() -> PgTypeInfo {
137        PgTypeInfo::with_name("hstore")
138    }
139}
140
141impl PgHasArrayType for PgHstore {
142    fn array_type_info() -> PgTypeInfo {
143        PgTypeInfo::array_of("hstore")
144    }
145}
146
147impl<'r> Decode<'r, Postgres> for PgHstore {
148    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
149        let mut buf = <&[u8] as Decode<Postgres>>::decode(value)?;
150        let len = read_length(&mut buf)?;
151
152        let len =
153            usize::try_from(len).map_err(|_| format!("PgHstore: length out of range: {len}"))?;
154
155        let mut result = Self::default();
156
157        for i in 0..len {
158            let key = read_string(&mut buf)
159                .map_err(|e| format!("PgHstore: error reading {i}th key: {e}"))?
160                .ok_or_else(|| format!("PgHstore: expected {i}th key, got nothing"))?;
161
162            let value = read_string(&mut buf)
163                .map_err(|e| format!("PgHstore: error reading value for key {key:?}: {e}"))?;
164
165            result.insert(key, value);
166        }
167
168        if !buf.is_empty() {
169            tracing::warn!("{} unread bytes at the end of HSTORE value", buf.len());
170        }
171
172        Ok(result)
173    }
174}
175
176impl Encode<'_, Postgres> for PgHstore {
177    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
178        buf.extend_from_slice(&i32::to_be_bytes(
179            self.0
180                .len()
181                .try_into()
182                .map_err(|_| format!("PgHstore length out of range: {}", self.0.len()))?,
183        ));
184
185        for (i, (key, val)) in self.0.iter().enumerate() {
186            let key_bytes = key.as_bytes();
187
188            let key_len = i32::try_from(key_bytes.len()).map_err(|_| {
189                // Doesn't make sense to print the key itself: it's more than 2 GiB long!
190                format!(
191                    "PgHstore: length of {i}th key out of range: {} bytes",
192                    key_bytes.len()
193                )
194            })?;
195
196            buf.extend_from_slice(&i32::to_be_bytes(key_len));
197            buf.extend_from_slice(key_bytes);
198
199            match val {
200                Some(val) => {
201                    let val_bytes = val.as_bytes();
202
203                    let val_len = i32::try_from(val_bytes.len()).map_err(|_| {
204                        format!(
205                            "PgHstore: value length for key {key:?} out of range: {} bytes",
206                            val_bytes.len()
207                        )
208                    })?;
209                    buf.extend_from_slice(&i32::to_be_bytes(val_len));
210                    buf.extend_from_slice(val_bytes);
211                }
212                None => {
213                    buf.extend_from_slice(&i32::to_be_bytes(-1));
214                }
215            }
216        }
217
218        Ok(IsNull::No)
219    }
220}
221
222fn read_length(buf: &mut &[u8]) -> Result<i32, String> {
223    if buf.len() < mem::size_of::<i32>() {
224        return Err(format!(
225            "expected {} bytes, got {}",
226            mem::size_of::<i32>(),
227            buf.len()
228        ));
229    }
230
231    Ok(buf.get_i32())
232}
233
234fn read_string(buf: &mut &[u8]) -> Result<Option<String>, String> {
235    let len = read_length(buf)?;
236
237    match len {
238        -1 => Ok(None),
239        len => {
240            let len =
241                usize::try_from(len).map_err(|_| format!("string length out of range: {len}"))?;
242
243            if buf.len() < len {
244                return Err(format!("expected {len} bytes, got {}", buf.len()));
245            }
246
247            let (val, rest) = buf.split_at(len);
248            *buf = rest;
249
250            Ok(Some(
251                str::from_utf8(val).map_err(|e| e.to_string())?.to_string(),
252            ))
253        }
254    }
255}
256
257#[cfg(test)]
258mod test {
259    use super::*;
260    use crate::PgValueFormat;
261
262    const EMPTY: &str = "00000000";
263
264    const NAME_SURNAME_AGE: &str =
265        "0000000300000003616765ffffffff000000046e616d65000000044a6f686e000000077375726e616d6500000003446f65";
266
267    #[test]
268    fn hstore_deserialize_ok() {
269        let empty = hex::decode(EMPTY).unwrap();
270        let name_surname_age = hex::decode(NAME_SURNAME_AGE).unwrap();
271
272        let empty = PgValueRef {
273            value: Some(empty.as_slice()),
274            row: None,
275            type_info: PgTypeInfo::with_name("hstore"),
276            format: PgValueFormat::Binary,
277        };
278
279        let name_surname = PgValueRef {
280            value: Some(name_surname_age.as_slice()),
281            row: None,
282            type_info: PgTypeInfo::with_name("hstore"),
283            format: PgValueFormat::Binary,
284        };
285
286        let res_empty = PgHstore::decode(empty).unwrap();
287        let res_name_surname = PgHstore::decode(name_surname).unwrap();
288
289        assert!(res_empty.is_empty());
290        assert_eq!(res_name_surname["name"], Some("John".to_string()));
291        assert_eq!(res_name_surname["surname"], Some("Doe".to_string()));
292        assert_eq!(res_name_surname["age"], None);
293    }
294
295    #[test]
296    #[should_panic(expected = "PgHstore: length out of range: -5")]
297    fn hstore_deserialize_buffer_length_error() {
298        let buf = PgValueRef {
299            value: Some(&[255, 255, 255, 251]),
300            row: None,
301            type_info: PgTypeInfo::with_name("hstore"),
302            format: PgValueFormat::Binary,
303        };
304
305        PgHstore::decode(buf).unwrap();
306    }
307
308    #[test]
309    fn hstore_serialize_ok() {
310        let mut buff = PgArgumentBuffer::default();
311        let _ = PgHstore::from_iter::<[(String, String); 0]>([])
312            .encode_by_ref(&mut buff)
313            .unwrap();
314
315        assert_eq!(hex::encode(buff.as_slice()), EMPTY);
316
317        buff.clear();
318
319        let _ = PgHstore::from_iter([
320            ("name".to_string(), Some("John".to_string())),
321            ("surname".to_string(), Some("Doe".to_string())),
322            ("age".to_string(), None),
323        ])
324        .encode_by_ref(&mut buff)
325        .unwrap();
326
327        assert_eq!(hex::encode(buff.as_slice()), NAME_SURNAME_AGE);
328    }
329}