use std::collections::HashMap;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Hstore(HashMap<String, Option<String>>);
impl Hstore {
#[must_use]
pub fn new() -> Self {
Self::default()
}
fn read_string(buf: &mut &[u8]) -> crate::Result<Option<String>> {
let len = crate::from_sql::read_i32(buf)?;
let s = if len < 0 {
None
} else {
let mut vec = Vec::new();
for _ in 0..len {
vec.push(crate::from_sql::read_u8(buf)?);
}
Some(String::from_utf8(vec)?)
};
Ok(s)
}
}
impl std::ops::DerefMut for Hstore {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl std::ops::Deref for Hstore {
type Target = HashMap<String, Option<String>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl crate::ToSql for crate::Hstore {
fn ty(&self) -> crate::pq::Type {
crate::pq::Type {
descr: "HSTORE - data type for storing sets of (key, value) pairs",
name: "hstore",
..crate::pq::types::UNKNOWN
}
}
fn to_text(&self) -> crate::Result<Option<String>> {
let mut vec = Vec::new();
for (key, value) in self.iter() {
let v = value
.as_ref()
.map_or_else(|| "NULL".to_string(), |x| format!("\"{x}\""));
vec.push(format!("\"{key}\"=>{v}"));
}
vec.join(", ").to_text()
}
fn to_binary(&self) -> crate::Result<Option<Vec<u8>>> {
let mut buf = Vec::new();
crate::to_sql::write_i32(&mut buf, self.len() as i32)?;
for (key, value) in self.iter() {
let k = key.to_text()?.unwrap();
crate::to_sql::write_i32(&mut buf, k.len() as i32)?;
buf.append(&mut k.into_bytes());
if let Some(v) = value.to_text()? {
crate::to_sql::write_i32(&mut buf, v.len() as i32)?;
buf.append(&mut v.into_bytes());
} else {
crate::to_sql::write_i32(&mut buf, -1)?;
}
}
Ok(Some(buf))
}
}
impl crate::FromSql for Hstore {
fn from_text(_: &crate::pq::Type, raw: Option<&str>) -> crate::Result<Self> {
let regex = crate::regex!("\"(?P<key>.*?)\"=>(\"(?P<value>.*?)\"|(?P<null>NULL))");
let mut hstore = Self::new();
for capture in regex.captures_iter(crate::not_null(raw)?) {
let key = capture.name("key").unwrap().as_str().to_string();
let value = if capture.name("null").is_some() {
None
} else {
Some(capture.name("value").unwrap().as_str().to_string())
};
hstore.insert(key, value);
}
Ok(hstore)
}
fn from_binary(ty: &crate::pq::Type, raw: Option<&[u8]>) -> crate::Result<Self> {
let mut hstore = Self::new();
let mut buf = crate::from_sql::not_null(raw)?;
let count = crate::from_sql::read_i32(&mut buf)?;
for _ in 0..count {
let key = Self::read_string(&mut buf)?.ok_or_else(|| Self::error(ty, raw))?;
let value = Self::read_string(&mut buf)?;
hstore.insert(key, value);
}
Ok(hstore)
}
}
impl crate::entity::Simple for Hstore {}
#[cfg(test)]
mod test {
crate::sql_test!(
hstore,
crate::Hstore,
[("'a=>1, b => 2, c=>null'", {
let mut hstore = crate::Hstore::new();
hstore.insert("a".to_string(), Some("1".to_string()));
hstore.insert("b".to_string(), Some("2".to_string()));
hstore.insert("c".to_string(), None);
hstore
})]
);
}