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:
parent
4501395784
commit
bd60ecfc1f
@ -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
|
||||
|
105
src/types/mod.rs
105
src/types/mod.rs
@ -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!();
|
||||
|
||||
|
@ -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!();
|
||||
|
@ -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),
|
||||
|
Loading…
Reference in New Issue
Block a user