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,
|
crate will synthesize `ToSql` and `FromSql` implementations for enum, domain,
|
||||||
and composite Postgres types.
|
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.
|
[postgres-array](https://github.com/sfackler/rust-postgres-array) crate.
|
||||||
|
|
||||||
Support for range types is located in the
|
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
|
/// In addition to the types listed above, `FromSql` is implemented for
|
||||||
/// `Option<T>` where `T` implements `FromSql`. An `Option<T>` represents a
|
/// `Option<T>` where `T` implements `FromSql`. An `Option<T>` represents a
|
||||||
/// nullable Postgres value.
|
/// 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 {
|
pub trait FromSql: Sized {
|
||||||
/// Creates a new value of this type from a `Read`er of the binary format
|
/// Creates a new value of this type from a `Read`er of the binary format
|
||||||
/// of the specified Postgres `Type`.
|
/// of the specified Postgres `Type`.
|
||||||
@ -343,6 +348,46 @@ impl FromSql for bool {
|
|||||||
accepts!(Type::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> {
|
impl FromSql for Vec<u8> {
|
||||||
fn from_sql<R: Read>(_: &Type, raw: &mut R, _: &SessionInfo) -> Result<Vec<u8>> {
|
fn from_sql<R: Read>(_: &Type, raw: &mut R, _: &SessionInfo) -> Result<Vec<u8>> {
|
||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
@ -496,6 +541,12 @@ pub enum IsNull {
|
|||||||
/// In addition to the types listed above, `ToSql` is implemented for
|
/// In addition to the types listed above, `ToSql` is implemented for
|
||||||
/// `Option<T>` where `T` implements `ToSql`. An `Option<T>` represents a
|
/// `Option<T>` where `T` implements `ToSql`. An `Option<T>` represents a
|
||||||
/// nullable Postgres value.
|
/// 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 {
|
pub trait ToSql: fmt::Debug {
|
||||||
/// Converts the value of `self` into the binary format of the specified
|
/// Converts the value of `self` into the binary format of the specified
|
||||||
/// Postgres `Type`, writing it to `out`.
|
/// Postgres `Type`, writing it to `out`.
|
||||||
@ -573,6 +624,48 @@ impl ToSql for bool {
|
|||||||
accepts!(Type::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] {
|
impl<'a> ToSql for &'a [u8] {
|
||||||
to_sql_checked!();
|
to_sql_checked!();
|
||||||
|
|
||||||
@ -584,6 +677,18 @@ impl<'a> ToSql for &'a [u8] {
|
|||||||
accepts!(Type::Bytea);
|
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> {
|
impl ToSql for Vec<u8> {
|
||||||
to_sql_checked!();
|
to_sql_checked!();
|
||||||
|
|
||||||
|
@ -1,70 +1,21 @@
|
|||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use byteorder::{WriteBytesExt, BigEndian};
|
|
||||||
|
|
||||||
use Result;
|
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
|
/// `ToSql` is now implemented directly for slices.
|
||||||
/// 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() {
|
|
||||||
/// // ...
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Slice<'a, T: 'a + ToSql>(pub &'a [T]);
|
pub struct Slice<'a, T: 'a + ToSql>(pub &'a [T]);
|
||||||
|
|
||||||
impl<'a, T: 'a + ToSql> ToSql for Slice<'a, T> {
|
impl<'a, T: 'a + ToSql> ToSql for Slice<'a, T> {
|
||||||
fn to_sql<W: Write + ?Sized>(&self,
|
fn to_sql<W: Write + ?Sized>(&self, ty: &Type, w: &mut W, ctx: &SessionInfo) -> Result<IsNull> {
|
||||||
ty: &Type,
|
self.0.to_sql(ty, w, ctx)
|
||||||
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 accepts(ty: &Type) -> bool {
|
fn accepts(ty: &Type) -> bool {
|
||||||
match *ty.kind() {
|
<&[T] as ToSql>::accepts(ty)
|
||||||
Kind::Array(ref member) => T::accepts(member),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
to_sql_checked!();
|
to_sql_checked!();
|
||||||
|
@ -6,7 +6,7 @@ use std::io::{Read, Write};
|
|||||||
|
|
||||||
use postgres::{Connection, SslMode, Result};
|
use postgres::{Connection, SslMode, Result};
|
||||||
use postgres::error::Error;
|
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")]
|
#[cfg(feature = "bit-vec")]
|
||||||
mod bit_vec;
|
mod bit_vec;
|
||||||
@ -207,7 +207,7 @@ fn test_slice() {
|
|||||||
INSERT INTO foo (f) VALUES ('a'), ('b'), ('c'), ('d');").unwrap();
|
INSERT INTO foo (f) VALUES ('a'), ('b'), ('c'), ('d');").unwrap();
|
||||||
|
|
||||||
let stmt = conn.prepare("SELECT f FROM foo WHERE id = ANY($1)").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()],
|
assert_eq!(vec!["a".to_owned(), "c".to_owned(), "d".to_owned()],
|
||||||
result.iter().map(|r| r.get::<_, String>(0)).collect::<Vec<_>>());
|
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();
|
conn.batch_execute("CREATE TEMPORARY TABLE foo (id SERIAL PRIMARY KEY)").unwrap();
|
||||||
|
|
||||||
let stmt = conn.prepare("SELECT * FROM foo WHERE id = ANY($1)").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"),
|
Ok(_) => panic!("Unexpected success"),
|
||||||
Err(Error::Conversion(ref e)) if e.is::<WrongType>() => {}
|
Err(Error::Conversion(ref e)) if e.is::<WrongType>() => {}
|
||||||
Err(e) => panic!("Unexpected error {:?}", e),
|
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 conn = Connection::connect("postgres://postgres@localhost", SslMode::None).unwrap();
|
||||||
|
|
||||||
let stmt = conn.prepare("SELECT $1::INT8RANGE").unwrap();
|
let stmt = conn.prepare("SELECT $1::INT8RANGE").unwrap();
|
||||||
match stmt.query(&[&Slice(&[1i64])]) {
|
match stmt.query(&[&&[1i64][..]]) {
|
||||||
Ok(_) => panic!("Unexpected success"),
|
Ok(_) => panic!("Unexpected success"),
|
||||||
Err(Error::Conversion(ref e)) if e.is::<WrongType>() => {}
|
Err(Error::Conversion(ref e)) if e.is::<WrongType>() => {}
|
||||||
Err(e) => panic!("Unexpected error {:?}", e),
|
Err(e) => panic!("Unexpected error {:?}", e),
|
||||||
|
Loading…
Reference in New Issue
Block a user