1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use std::collections::HashMap;

#[derive(Clone, Debug, PartialEq)]
pub struct Hstore(HashMap<String, Option<String>>);

impl Hstore {
    pub fn new() -> Self {
        Self(HashMap::new())
    }

    fn read_string(buf: &mut &[u8]) -> crate::Result<Option<String>> {
        use byteorder::ReadBytesExt;

        let len = buf.read_i32::<byteorder::BigEndian>()?;

        let s = if len < 0 {
            None
        }
        else {
            let mut vec = Vec::new();
            for _ in 0..len {
                vec.push(buf.read_u8()?);
            }

            Some(String::from_utf8(vec)?)
        };

        Ok(s)
    }
}

impl Default for Hstore {
    fn default() -> Self {
        Self::new()
    }
}

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::types::TEXT
    }

    fn to_sql(&self) -> crate::Result<Option<Vec<u8>>> {
        let mut vec = Vec::new();

        for (key, value) in self.iter() {
            let v = value
                .as_ref()
                .map(|x| format!("\"{}\"", x))
                .unwrap_or_else(|| "NULL".to_string());

            vec.push(format!("\"{}\"=>{}", key, v));
        }

        vec.join(", ").to_sql()
    }
}

impl crate::FromSql for Hstore {
    fn from_text(
        _: &crate::pq::Type,
        raw: Option<&str>,
    ) -> crate::Result<Self> {
        let mut hstore = Self::new();
        // @TODO static
        let regex = regex::Regex::new(
            "\"(?P<key>.*?)\"=>(\"(?P<value>.*?)\"|(?P<null>NULL))",
        )
        .unwrap();

        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)
    }

    /*
     * https://github.com/postgres/postgres/blob/REL_12_0/contrib/hstore/hstore_io.c#L1226
     */
    fn from_binary(
        _: &crate::pq::Type,
        raw: Option<&[u8]>,
    ) -> crate::Result<Self> {
        use byteorder::ReadBytesExt;

        let mut hstore = Self::new();
        let mut buf = crate::from_sql::not_null(raw)?;
        let count = buf.read_i32::<byteorder::BigEndian>()?;

        for _ in 0..count {
            let key = Self::read_string(&mut buf)?.unwrap();
            let value = Self::read_string(&mut buf)?;

            hstore.insert(key, value);
        }

        Ok(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
    })]);
}