diff --git a/README.md b/README.md index 2a359324..bcc25b83 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ The [postgres-derive](https://github.com/sfackler/rust-postgres-derive) crate will synthesize `ToSql` and `FromSql` implementations for enum, domain, and composite Postgres types. -Support for array types is located in the +Full support for array types is located in the [postgres-array](https://github.com/sfackler/rust-postgres-array) crate. Support for range types is located in the diff --git a/src/types/mod.rs b/src/types/mod.rs index 6bc40a5f..ef023cf9 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -296,6 +296,11 @@ impl WrongTypeNew for WrongType { /// In addition to the types listed above, `FromSql` is implemented for /// `Option` where `T` implements `FromSql`. An `Option` represents a /// nullable Postgres value. +/// +/// # Arrays +/// +/// `FromSql` is implemented for `Vec` where `T` implements `FromSql`, and +/// corresponds to one-dimensional Postgres arrays. pub trait FromSql: Sized { /// Creates a new value of this type from a `Read`er of the binary format /// of the specified Postgres `Type`. @@ -343,6 +348,46 @@ impl FromSql for bool { accepts!(Type::Bool); } +impl FromSql for Vec { + fn from_sql(ty: &Type, raw: &mut R, info: &SessionInfo) -> Result> { + let member_type = match *ty.kind() { + Kind::Array(ref member) => member, + _ => panic!("expected array type"), + }; + + if try!(raw.read_i32::()) != 1 { + return Err(Error::Conversion("array contains too many dimensions".into())); + } + + let _has_nulls = try!(raw.read_i32::()); + let _member_oid = try!(raw.read_u32::()); + + let count = try!(raw.read_i32::()); + let _index_offset = try!(raw.read_i32::()); + + let mut out = Vec::with_capacity(count as usize); + for _ in 0..count { + let len = try!(raw.read_i32::()); + let value = if len < 0 { + try!(T::from_sql_null(&member_type, info)) + } else { + let mut raw = raw.take(len as u64); + try!(T::from_sql(&member_type, &mut raw, info)) + }; + out.push(value) + } + + Ok(out) + } + + fn accepts(ty: &Type) -> bool { + match *ty.kind() { + Kind::Array(ref inner) => T::accepts(inner), + _ => false, + } + } +} + impl FromSql for Vec { fn from_sql(_: &Type, raw: &mut R, _: &SessionInfo) -> Result> { let mut buf = vec![]; @@ -496,6 +541,12 @@ pub enum IsNull { /// In addition to the types listed above, `ToSql` is implemented for /// `Option` where `T` implements `ToSql`. An `Option` represents a /// nullable Postgres value. +/// +/// # Arrays +/// +/// `ToSql` is implemented for `Vec` and `&[T]` where `T` implements `ToSql`, +/// and corresponds to one-dimentional Postgres arrays with an index offset of +/// 0. pub trait ToSql: fmt::Debug { /// Converts the value of `self` into the binary format of the specified /// Postgres `Type`, writing it to `out`. @@ -573,6 +624,48 @@ impl ToSql for bool { accepts!(Type::Bool); } +impl<'a, T: ToSql> ToSql for &'a [T] { + to_sql_checked!(); + + fn to_sql(&self, ty: &Type, + mut w: &mut W, + ctx: &SessionInfo) + -> Result { + let member_type = match *ty.kind() { + Kind::Array(ref member) => member, + _ => panic!("expected array type"), + }; + + try!(w.write_i32::(1)); // number of dimensions + try!(w.write_i32::(1)); // has nulls + try!(w.write_u32::(member_type.oid())); + + try!(w.write_i32::(try!(downcast(self.len())))); + try!(w.write_i32::(0)); // index offset + + let mut inner_buf = vec![]; + for e in *self { + match try!(e.to_sql(&member_type, &mut inner_buf, ctx)) { + IsNull::No => { + try!(w.write_i32::(try!(downcast(inner_buf.len())))); + try!(w.write_all(&inner_buf)); + } + IsNull::Yes => try!(w.write_i32::(-1)), + } + inner_buf.clear(); + } + + Ok(IsNull::No) + } + + fn accepts(ty: &Type) -> bool { + match *ty.kind() { + Kind::Array(ref member) => T::accepts(member), + _ => false, + } + } +} + impl<'a> ToSql for &'a [u8] { to_sql_checked!(); @@ -584,6 +677,18 @@ impl<'a> ToSql for &'a [u8] { accepts!(Type::Bytea); } +impl ToSql for Vec { + to_sql_checked!(); + + fn to_sql(&self, ty: &Type, w: &mut W, ctx: &SessionInfo) -> Result { + <&[T] as ToSql>::to_sql(&&**self, ty, w, ctx) + } + + fn accepts(ty: &Type) -> bool { + <&[T] as ToSql>::accepts(ty) + } +} + impl ToSql for Vec { to_sql_checked!(); diff --git a/src/types/slice.rs b/src/types/slice.rs index cbd67973..a5072921 100644 --- a/src/types/slice.rs +++ b/src/types/slice.rs @@ -1,70 +1,21 @@ use std::io::prelude::*; -use byteorder::{WriteBytesExt, BigEndian}; use Result; -use types::{Type, ToSql, Kind, IsNull, SessionInfo, downcast}; +use types::{Type, ToSql, IsNull, SessionInfo}; -/// An adapter type mapping slices to Postgres arrays. +/// # Deprecated /// -/// `Slice`'s `ToSql` implementation maps the slice to a one-dimensional -/// Postgres array of the relevant type. This is particularly useful with the -/// `ANY` function to match a column against multiple values without having -/// to dynamically construct the query string. -/// -/// # Examples -/// -/// ```rust,no_run -/// # use postgres::{Connection, SslMode}; -/// use postgres::types::Slice; -/// -/// # let conn = Connection::connect("", SslMode::None).unwrap(); -/// let values = &[1i32, 2, 3, 4, 5, 6]; -/// let stmt = conn.prepare("SELECT * FROM foo WHERE id = ANY($1)").unwrap(); -/// for row in &stmt.query(&[&Slice(values)]).unwrap() { -/// // ... -/// } -/// ``` +/// `ToSql` is now implemented directly for slices. #[derive(Debug)] pub struct Slice<'a, T: 'a + ToSql>(pub &'a [T]); impl<'a, T: 'a + ToSql> ToSql for Slice<'a, T> { - fn to_sql(&self, - ty: &Type, - mut w: &mut W, - ctx: &SessionInfo) - -> Result { - let member_type = match *ty.kind() { - Kind::Array(ref member) => member, - _ => panic!("expected array type"), - }; - - try!(w.write_i32::(1)); // number of dimensions - try!(w.write_i32::(1)); // has nulls - try!(w.write_u32::(member_type.oid())); - - try!(w.write_i32::(try!(downcast(self.0.len())))); - try!(w.write_i32::(0)); // index offset - - let mut inner_buf = vec![]; - for e in self.0 { - match try!(e.to_sql(&member_type, &mut inner_buf, ctx)) { - IsNull::No => { - try!(w.write_i32::(try!(downcast(inner_buf.len())))); - try!(w.write_all(&inner_buf)); - } - IsNull::Yes => try!(w.write_i32::(-1)), - } - inner_buf.clear(); - } - - Ok(IsNull::No) + fn to_sql(&self, ty: &Type, w: &mut W, ctx: &SessionInfo) -> Result { + self.0.to_sql(ty, w, ctx) } fn accepts(ty: &Type) -> bool { - match *ty.kind() { - Kind::Array(ref member) => T::accepts(member), - _ => false, - } + <&[T] as ToSql>::accepts(ty) } to_sql_checked!(); diff --git a/tests/types/mod.rs b/tests/types/mod.rs index fe71b053..c571b81f 100644 --- a/tests/types/mod.rs +++ b/tests/types/mod.rs @@ -6,7 +6,7 @@ use std::io::{Read, Write}; use postgres::{Connection, SslMode, Result}; use postgres::error::Error; -use postgres::types::{ToSql, FromSql, Slice, WrongType, Type, IsNull, Kind, SessionInfo}; +use postgres::types::{ToSql, FromSql, WrongType, Type, IsNull, Kind, SessionInfo}; #[cfg(feature = "bit-vec")] mod bit_vec; @@ -207,7 +207,7 @@ fn test_slice() { INSERT INTO foo (f) VALUES ('a'), ('b'), ('c'), ('d');").unwrap(); let stmt = conn.prepare("SELECT f FROM foo WHERE id = ANY($1)").unwrap(); - let result = stmt.query(&[&Slice(&[1i32, 3, 4])]).unwrap(); + let result = stmt.query(&[&&[1i32, 3, 4][..]]).unwrap(); assert_eq!(vec!["a".to_owned(), "c".to_owned(), "d".to_owned()], result.iter().map(|r| r.get::<_, String>(0)).collect::>()); } @@ -218,7 +218,7 @@ fn test_slice_wrong_type() { conn.batch_execute("CREATE TEMPORARY TABLE foo (id SERIAL PRIMARY KEY)").unwrap(); let stmt = conn.prepare("SELECT * FROM foo WHERE id = ANY($1)").unwrap(); - match stmt.query(&[&Slice(&["hi"])]) { + match stmt.query(&[&&["hi"][..]]) { Ok(_) => panic!("Unexpected success"), Err(Error::Conversion(ref e)) if e.is::() => {} Err(e) => panic!("Unexpected error {:?}", e), @@ -230,7 +230,7 @@ fn test_slice_range() { let conn = Connection::connect("postgres://postgres@localhost", SslMode::None).unwrap(); let stmt = conn.prepare("SELECT $1::INT8RANGE").unwrap(); - match stmt.query(&[&Slice(&[1i64])]) { + match stmt.query(&[&&[1i64][..]]) { Ok(_) => panic!("Unexpected success"), Err(Error::Conversion(ref e)) if e.is::() => {} Err(e) => panic!("Unexpected error {:?}", e),