Add impls for array types

Turns out the thing I thought was a blocker (the bytea impls) actually
isn't since u8 doesn't implement these traits. Yay for pre-1.0 thinking!
This commit is contained in:
Steven Fackler 2016-03-27 09:42:21 -07:00
parent 4501395784
commit bd60ecfc1f
4 changed files with 116 additions and 60 deletions

View File

@ -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

View File

@ -296,6 +296,11 @@ impl WrongTypeNew for WrongType {
/// In addition to the types listed above, `FromSql` is implemented for
/// `Option<T>` where `T` implements `FromSql`. An `Option<T>` represents a
/// nullable Postgres value.
///
/// # Arrays
///
/// `FromSql` is implemented for `Vec<T>` 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<T: FromSql> FromSql for Vec<T> {
fn from_sql<R: Read>(ty: &Type, raw: &mut R, info: &SessionInfo) -> Result<Vec<T>> {
let member_type = match *ty.kind() {
Kind::Array(ref member) => member,
_ => panic!("expected array type"),
};
if try!(raw.read_i32::<BigEndian>()) != 1 {
return Err(Error::Conversion("array contains too many dimensions".into()));
}
let _has_nulls = try!(raw.read_i32::<BigEndian>());
let _member_oid = try!(raw.read_u32::<BigEndian>());
let count = try!(raw.read_i32::<BigEndian>());
let _index_offset = try!(raw.read_i32::<BigEndian>());
let mut out = Vec::with_capacity(count as usize);
for _ in 0..count {
let len = try!(raw.read_i32::<BigEndian>());
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<u8> {
fn from_sql<R: Read>(_: &Type, raw: &mut R, _: &SessionInfo) -> Result<Vec<u8>> {
let mut buf = vec![];
@ -496,6 +541,12 @@ pub enum IsNull {
/// In addition to the types listed above, `ToSql` is implemented for
/// `Option<T>` where `T` implements `ToSql`. An `Option<T>` represents a
/// nullable Postgres value.
///
/// # Arrays
///
/// `ToSql` is implemented for `Vec<T>` 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<W: Write + ?Sized>(&self, ty: &Type,
mut w: &mut W,
ctx: &SessionInfo)
-> Result<IsNull> {
let member_type = match *ty.kind() {
Kind::Array(ref member) => member,
_ => panic!("expected array type"),
};
try!(w.write_i32::<BigEndian>(1)); // number of dimensions
try!(w.write_i32::<BigEndian>(1)); // has nulls
try!(w.write_u32::<BigEndian>(member_type.oid()));
try!(w.write_i32::<BigEndian>(try!(downcast(self.len()))));
try!(w.write_i32::<BigEndian>(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::<BigEndian>(try!(downcast(inner_buf.len()))));
try!(w.write_all(&inner_buf));
}
IsNull::Yes => try!(w.write_i32::<BigEndian>(-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<T: ToSql> ToSql for Vec<T> {
to_sql_checked!();
fn to_sql<W: Write + ?Sized>(&self, ty: &Type, w: &mut W, ctx: &SessionInfo) -> Result<IsNull> {
<&[T] as ToSql>::to_sql(&&**self, ty, w, ctx)
}
fn accepts(ty: &Type) -> bool {
<&[T] as ToSql>::accepts(ty)
}
}
impl ToSql for Vec<u8> {
to_sql_checked!();

View File

@ -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<W: Write + ?Sized>(&self,
ty: &Type,
mut w: &mut W,
ctx: &SessionInfo)
-> Result<IsNull> {
let member_type = match *ty.kind() {
Kind::Array(ref member) => member,
_ => panic!("expected array type"),
};
try!(w.write_i32::<BigEndian>(1)); // number of dimensions
try!(w.write_i32::<BigEndian>(1)); // has nulls
try!(w.write_u32::<BigEndian>(member_type.oid()));
try!(w.write_i32::<BigEndian>(try!(downcast(self.0.len()))));
try!(w.write_i32::<BigEndian>(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::<BigEndian>(try!(downcast(inner_buf.len()))));
try!(w.write_all(&inner_buf));
}
IsNull::Yes => try!(w.write_i32::<BigEndian>(-1)),
}
inner_buf.clear();
}
Ok(IsNull::No)
fn to_sql<W: Write + ?Sized>(&self, ty: &Type, w: &mut W, ctx: &SessionInfo) -> Result<IsNull> {
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!();

View File

@ -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::<Vec<_>>());
}
@ -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::<WrongType>() => {}
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::<WrongType>() => {}
Err(e) => panic!("Unexpected error {:?}", e),