diff --git a/README.md b/README.md index e6652f22..65e8bf5d 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,10 @@ types. The driver currently supports the following conversions: types::array::ArrayBase<Option<i64>> INT8[], INT8[][], ... + + std::hashmap::HashMap<~str, Option<~str>> + HSTORE + diff --git a/test.rs b/test.rs index 0b8c6430..d1da03e3 100644 --- a/test.rs +++ b/test.rs @@ -12,6 +12,7 @@ use extra::uuid::Uuid; use ssl::{SslContext, Sslv3}; use std::f32; use std::f64; +use std::hashmap::HashMap; use std::io::timer; use lib::{PostgresNoticeHandler, @@ -447,6 +448,24 @@ fn test_int8array_params() { [(Some(a), "'[-1:0][0:1]={{0,1},{NULL,3}}'")]); } +#[test] +fn test_hstore_params() { + macro_rules! make_map( + ($($k:expr => $v:expr),+) => ({ + let mut map = HashMap::new(); + $(map.insert($k, $v);)+ + map + }) + ) + test_type("hstore", + [(Some(make_map!(~"a" => Some(~"1"))), "'a=>1'"), + (Some(make_map!(~"hello" => Some(~"world!"), + ~"hola" => Some(~"mundo!"), + ~"what" => None)), + "'hello=>world!,hola=>mundo!,what=>NULL'"), + (None, "NULL")]); +} + fn test_nan_param(sql_type: &str) { let conn = PostgresConnection::connect("postgres://postgres@localhost", &NoSsl); let stmt = conn.prepare("SELECT 'NaN'::" + sql_type); diff --git a/types/mod.rs b/types/mod.rs index c1bb0e09..ec7956dc 100644 --- a/types/mod.rs +++ b/types/mod.rs @@ -6,6 +6,7 @@ use extra::time::Timespec; use extra::json; use extra::json::Json; use extra::uuid::Uuid; +use std::hashmap::HashMap; use std::io::Decorator; use std::io::mem::{MemWriter, BufReader}; use std::mem; @@ -143,6 +144,7 @@ impl PostgresType { /// Returns the wire format needed for the value of `self`. pub fn result_format(&self) -> Format { match *self { + PgUnknownType { name: ~"hstore", .. } => Binary, PgUnknownType { .. } => Text, _ => Binary } @@ -379,6 +381,31 @@ from_option_impl!(ArrayBase>) from_array_impl!(PgInt8Array, i64) from_option_impl!(ArrayBase>) +from_map_impl!(PgUnknownType { name: ~"hstore", .. }, + HashMap<~str, Option<~str>>, |buf| { + let mut rdr = BufReader::new(buf.as_slice()); + let mut map = HashMap::new(); + + let count = rdr.read_be_i32(); + + for _ in range(0, count) { + let key_len = rdr.read_be_i32(); + let key = str::from_utf8_owned(rdr.read_bytes(key_len as uint)); + + let val_len = rdr.read_be_i32(); + let val = if val_len < 0 { + None + } else { + Some(str::from_utf8_owned(rdr.read_bytes(val_len as uint))) + }; + + map.insert(key, val); + } + + map +}) +from_option_impl!(HashMap<~str, Option<~str>>) + /// A trait for types that can be converted into Postgres values pub trait ToSql { /// Converts the value of `self` into a format appropriate for the Postgres @@ -427,7 +454,7 @@ impl RawToSql for Timespec { } macro_rules! to_option_impl( - ($($oid:ident)|+, $t:ty) => ( + ($($oid:pat)|+, $t:ty) => ( impl ToSql for Option<$t> { fn to_sql(&self, ty: &PostgresType) -> (Format, Option<~[u8]>) { check_types!($($oid)|+, ty) @@ -438,8 +465,11 @@ macro_rules! to_option_impl( } } } - ); - (self, $($oid:ident)|+, $t:ty) => ( + ) +) + +macro_rules! to_option_impl_self( + ($($oid:pat)|+, $t:ty) => ( impl<'self> ToSql for Option<$t> { fn to_sql(&self, ty: &PostgresType) -> (Format, Option<~[u8]>) { check_types!($($oid)|+, ty) @@ -518,7 +548,7 @@ impl<'self> ToSql for &'self str { } to_option_impl!(PgVarchar | PgText | PgCharN, ~str) -to_option_impl!(self, PgVarchar | PgText | PgCharN, &'self str) +to_option_impl_self!(PgVarchar | PgText | PgCharN, &'self str) impl ToSql for ~[u8] { fn to_sql(&self, ty: &PostgresType) -> (Format, Option<~[u8]>) { @@ -535,7 +565,7 @@ impl<'self> ToSql for &'self [u8] { } to_option_impl!(PgByteA, ~[u8]) -to_option_impl!(self, PgByteA, &'self [u8]) +to_option_impl_self!(PgByteA, &'self [u8]) impl ToSql for Json { fn to_sql(&self, ty: &PostgresType) -> (Format, Option<~[u8]>) { @@ -652,3 +682,29 @@ to_option_impl!(PgInt4Array, ArrayBase>) to_array_impl!(PgInt8Array, INT8OID, i64) to_option_impl!(PgInt8Array, ArrayBase>) + +impl<'self> ToSql for HashMap<~str, Option<~str>> { + fn to_sql(&self, ty: &PostgresType) -> (Format, Option<~[u8]>) { + check_types!(PgUnknownType { name: ~"hstore", .. }, ty) + let mut buf = MemWriter::new(); + + buf.write_be_i32(self.len() as i32); + + for (key, val) in self.iter() { + buf.write_be_i32(key.len() as i32); + buf.write(key.as_bytes()); + + match *val { + Some(ref val) => { + buf.write_be_i32(val.len() as i32); + buf.write(val.as_bytes()); + } + None => buf.write_be_i32(-1) + } + } + + (Binary, Some(buf.inner())) + } +} +to_option_impl!(PgUnknownType { name: ~"hstore", .. }, + HashMap<~str, Option<~str>>)