Skip to content

Commit bd60ecf

Browse files
committed
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!
1 parent 4501395 commit bd60ecf

File tree

4 files changed

+116
-60
lines changed

4 files changed

+116
-60
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ The [postgres-derive](https://github.com/sfackler/rust-postgres-derive)
269269
crate will synthesize `ToSql` and `FromSql` implementations for enum, domain,
270270
and composite Postgres types.
271271

272-
Support for array types is located in the
272+
Full support for array types is located in the
273273
[postgres-array](https://github.com/sfackler/rust-postgres-array) crate.
274274

275275
Support for range types is located in the

src/types/mod.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ impl WrongTypeNew for WrongType {
296296
/// In addition to the types listed above, `FromSql` is implemented for
297297
/// `Option<T>` where `T` implements `FromSql`. An `Option<T>` represents a
298298
/// nullable Postgres value.
299+
///
300+
/// # Arrays
301+
///
302+
/// `FromSql` is implemented for `Vec<T>` where `T` implements `FromSql`, and
303+
/// corresponds to one-dimensional Postgres arrays.
299304
pub trait FromSql: Sized {
300305
/// Creates a new value of this type from a `Read`er of the binary format
301306
/// of the specified Postgres `Type`.
@@ -343,6 +348,46 @@ impl FromSql for bool {
343348
accepts!(Type::Bool);
344349
}
345350

351+
impl<T: FromSql> FromSql for Vec<T> {
352+
fn from_sql<R: Read>(ty: &Type, raw: &mut R, info: &SessionInfo) -> Result<Vec<T>> {
353+
let member_type = match *ty.kind() {
354+
Kind::Array(ref member) => member,
355+
_ => panic!("expected array type"),
356+
};
357+
358+
if try!(raw.read_i32::<BigEndian>()) != 1 {
359+
return Err(Error::Conversion("array contains too many dimensions".into()));
360+
}
361+
362+
let _has_nulls = try!(raw.read_i32::<BigEndian>());
363+
let _member_oid = try!(raw.read_u32::<BigEndian>());
364+
365+
let count = try!(raw.read_i32::<BigEndian>());
366+
let _index_offset = try!(raw.read_i32::<BigEndian>());
367+
368+
let mut out = Vec::with_capacity(count as usize);
369+
for _ in 0..count {
370+
let len = try!(raw.read_i32::<BigEndian>());
371+
let value = if len < 0 {
372+
try!(T::from_sql_null(&member_type, info))
373+
} else {
374+
let mut raw = raw.take(len as u64);
375+
try!(T::from_sql(&member_type, &mut raw, info))
376+
};
377+
out.push(value)
378+
}
379+
380+
Ok(out)
381+
}
382+
383+
fn accepts(ty: &Type) -> bool {
384+
match *ty.kind() {
385+
Kind::Array(ref inner) => T::accepts(inner),
386+
_ => false,
387+
}
388+
}
389+
}
390+
346391
impl FromSql for Vec<u8> {
347392
fn from_sql<R: Read>(_: &Type, raw: &mut R, _: &SessionInfo) -> Result<Vec<u8>> {
348393
let mut buf = vec![];
@@ -496,6 +541,12 @@ pub enum IsNull {
496541
/// In addition to the types listed above, `ToSql` is implemented for
497542
/// `Option<T>` where `T` implements `ToSql`. An `Option<T>` represents a
498543
/// nullable Postgres value.
544+
///
545+
/// # Arrays
546+
///
547+
/// `ToSql` is implemented for `Vec<T>` and `&[T]` where `T` implements `ToSql`,
548+
/// and corresponds to one-dimentional Postgres arrays with an index offset of
549+
/// 0.
499550
pub trait ToSql: fmt::Debug {
500551
/// Converts the value of `self` into the binary format of the specified
501552
/// Postgres `Type`, writing it to `out`.
@@ -573,6 +624,48 @@ impl ToSql for bool {
573624
accepts!(Type::Bool);
574625
}
575626

627+
impl<'a, T: ToSql> ToSql for &'a [T] {
628+
to_sql_checked!();
629+
630+
fn to_sql<W: Write + ?Sized>(&self, ty: &Type,
631+
mut w: &mut W,
632+
ctx: &SessionInfo)
633+
-> Result<IsNull> {
634+
let member_type = match *ty.kind() {
635+
Kind::Array(ref member) => member,
636+
_ => panic!("expected array type"),
637+
};
638+
639+
try!(w.write_i32::<BigEndian>(1)); // number of dimensions
640+
try!(w.write_i32::<BigEndian>(1)); // has nulls
641+
try!(w.write_u32::<BigEndian>(member_type.oid()));
642+
643+
try!(w.write_i32::<BigEndian>(try!(downcast(self.len()))));
644+
try!(w.write_i32::<BigEndian>(0)); // index offset
645+
646+
let mut inner_buf = vec![];
647+
for e in *self {
648+
match try!(e.to_sql(&member_type, &mut inner_buf, ctx)) {
649+
IsNull::No => {
650+
try!(w.write_i32::<BigEndian>(try!(downcast(inner_buf.len()))));
651+
try!(w.write_all(&inner_buf));
652+
}
653+
IsNull::Yes => try!(w.write_i32::<BigEndian>(-1)),
654+
}
655+
inner_buf.clear();
656+
}
657+
658+
Ok(IsNull::No)
659+
}
660+
661+
fn accepts(ty: &Type) -> bool {
662+
match *ty.kind() {
663+
Kind::Array(ref member) => T::accepts(member),
664+
_ => false,
665+
}
666+
}
667+
}
668+
576669
impl<'a> ToSql for &'a [u8] {
577670
to_sql_checked!();
578671

@@ -584,6 +677,18 @@ impl<'a> ToSql for &'a [u8] {
584677
accepts!(Type::Bytea);
585678
}
586679

680+
impl<T: ToSql> ToSql for Vec<T> {
681+
to_sql_checked!();
682+
683+
fn to_sql<W: Write + ?Sized>(&self, ty: &Type, w: &mut W, ctx: &SessionInfo) -> Result<IsNull> {
684+
<&[T] as ToSql>::to_sql(&&**self, ty, w, ctx)
685+
}
686+
687+
fn accepts(ty: &Type) -> bool {
688+
<&[T] as ToSql>::accepts(ty)
689+
}
690+
}
691+
587692
impl ToSql for Vec<u8> {
588693
to_sql_checked!();
589694

src/types/slice.rs

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,21 @@
11
use std::io::prelude::*;
2-
use byteorder::{WriteBytesExt, BigEndian};
32

43
use Result;
5-
use types::{Type, ToSql, Kind, IsNull, SessionInfo, downcast};
4+
use types::{Type, ToSql, IsNull, SessionInfo};
65

7-
/// An adapter type mapping slices to Postgres arrays.
6+
/// # Deprecated
87
///
9-
/// `Slice`'s `ToSql` implementation maps the slice to a one-dimensional
10-
/// Postgres array of the relevant type. This is particularly useful with the
11-
/// `ANY` function to match a column against multiple values without having
12-
/// to dynamically construct the query string.
13-
///
14-
/// # Examples
15-
///
16-
/// ```rust,no_run
17-
/// # use postgres::{Connection, SslMode};
18-
/// use postgres::types::Slice;
19-
///
20-
/// # let conn = Connection::connect("", SslMode::None).unwrap();
21-
/// let values = &[1i32, 2, 3, 4, 5, 6];
22-
/// let stmt = conn.prepare("SELECT * FROM foo WHERE id = ANY($1)").unwrap();
23-
/// for row in &stmt.query(&[&Slice(values)]).unwrap() {
24-
/// // ...
25-
/// }
26-
/// ```
8+
/// `ToSql` is now implemented directly for slices.
279
#[derive(Debug)]
2810
pub struct Slice<'a, T: 'a + ToSql>(pub &'a [T]);
2911

3012
impl<'a, T: 'a + ToSql> ToSql for Slice<'a, T> {
31-
fn to_sql<W: Write + ?Sized>(&self,
32-
ty: &Type,
33-
mut w: &mut W,
34-
ctx: &SessionInfo)
35-
-> Result<IsNull> {
36-
let member_type = match *ty.kind() {
37-
Kind::Array(ref member) => member,
38-
_ => panic!("expected array type"),
39-
};
40-
41-
try!(w.write_i32::<BigEndian>(1)); // number of dimensions
42-
try!(w.write_i32::<BigEndian>(1)); // has nulls
43-
try!(w.write_u32::<BigEndian>(member_type.oid()));
44-
45-
try!(w.write_i32::<BigEndian>(try!(downcast(self.0.len()))));
46-
try!(w.write_i32::<BigEndian>(0)); // index offset
47-
48-
let mut inner_buf = vec![];
49-
for e in self.0 {
50-
match try!(e.to_sql(&member_type, &mut inner_buf, ctx)) {
51-
IsNull::No => {
52-
try!(w.write_i32::<BigEndian>(try!(downcast(inner_buf.len()))));
53-
try!(w.write_all(&inner_buf));
54-
}
55-
IsNull::Yes => try!(w.write_i32::<BigEndian>(-1)),
56-
}
57-
inner_buf.clear();
58-
}
59-
60-
Ok(IsNull::No)
13+
fn to_sql<W: Write + ?Sized>(&self, ty: &Type, w: &mut W, ctx: &SessionInfo) -> Result<IsNull> {
14+
self.0.to_sql(ty, w, ctx)
6115
}
6216

6317
fn accepts(ty: &Type) -> bool {
64-
match *ty.kind() {
65-
Kind::Array(ref member) => T::accepts(member),
66-
_ => false,
67-
}
18+
<&[T] as ToSql>::accepts(ty)
6819
}
6920

7021
to_sql_checked!();

tests/types/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::io::{Read, Write};
66

77
use postgres::{Connection, SslMode, Result};
88
use postgres::error::Error;
9-
use postgres::types::{ToSql, FromSql, Slice, WrongType, Type, IsNull, Kind, SessionInfo};
9+
use postgres::types::{ToSql, FromSql, WrongType, Type, IsNull, Kind, SessionInfo};
1010

1111
#[cfg(feature = "bit-vec")]
1212
mod bit_vec;
@@ -207,7 +207,7 @@ fn test_slice() {
207207
INSERT INTO foo (f) VALUES ('a'), ('b'), ('c'), ('d');").unwrap();
208208

209209
let stmt = conn.prepare("SELECT f FROM foo WHERE id = ANY($1)").unwrap();
210-
let result = stmt.query(&[&Slice(&[1i32, 3, 4])]).unwrap();
210+
let result = stmt.query(&[&&[1i32, 3, 4][..]]).unwrap();
211211
assert_eq!(vec!["a".to_owned(), "c".to_owned(), "d".to_owned()],
212212
result.iter().map(|r| r.get::<_, String>(0)).collect::<Vec<_>>());
213213
}
@@ -218,7 +218,7 @@ fn test_slice_wrong_type() {
218218
conn.batch_execute("CREATE TEMPORARY TABLE foo (id SERIAL PRIMARY KEY)").unwrap();
219219

220220
let stmt = conn.prepare("SELECT * FROM foo WHERE id = ANY($1)").unwrap();
221-
match stmt.query(&[&Slice(&["hi"])]) {
221+
match stmt.query(&[&&["hi"][..]]) {
222222
Ok(_) => panic!("Unexpected success"),
223223
Err(Error::Conversion(ref e)) if e.is::<WrongType>() => {}
224224
Err(e) => panic!("Unexpected error {:?}", e),
@@ -230,7 +230,7 @@ fn test_slice_range() {
230230
let conn = Connection::connect("postgres://postgres@localhost", SslMode::None).unwrap();
231231

232232
let stmt = conn.prepare("SELECT $1::INT8RANGE").unwrap();
233-
match stmt.query(&[&Slice(&[1i64])]) {
233+
match stmt.query(&[&&[1i64][..]]) {
234234
Ok(_) => panic!("Unexpected success"),
235235
Err(Error::Conversion(ref e)) if e.is::<WrongType>() => {}
236236
Err(e) => panic!("Unexpected error {:?}", e),

0 commit comments

Comments
 (0)