Skip to content

Commit a0cca5f

Browse files
committed
Add a Slice adapter type
Until impl specialization exists, we can't define this implementaion directly on &[T] because of the existing implementation for &[u8]. Closes rust-postgres#92
1 parent 23c49c0 commit a0cca5f

4 files changed

Lines changed: 87 additions & 1 deletion

File tree

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ use url::Url;
8282
pub use error::{Error, ConnectError, SqlState, DbError, ErrorPosition};
8383
#[doc(inline)]
8484
pub use types::{Oid, Type, ToSql, FromSql};
85+
#[doc(inline)]
86+
pub use types::Slice;
8587
use io::{InternalStream, Timeout};
8688
use message::BackendMessage::*;
8789
use message::FrontendMessage::*;

src/types/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
//! Traits dealing with Postgres data types
2+
pub use self::slice::Slice;
3+
24
use serialize::json;
35
use std::collections::HashMap;
46
use std::old_io::net::ip::IpAddr;
@@ -132,6 +134,7 @@ macro_rules! to_raw_to_impl {
132134
#[cfg(feature = "uuid")]
133135
mod uuid;
134136
mod time;
137+
mod slice;
135138

136139
/// A Postgres OID
137140
pub type Oid = u32;

src/types/slice.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use byteorder::{BigEndian, WriterBytesExt};
2+
3+
use {Type, ToSql, Result, Error};
4+
5+
/// An adapter type mapping slices to Postgres arrays.
6+
///
7+
/// `Slice`'s `ToSql` implementation maps the slice to a one-dimensional
8+
/// Postgres array of the relevant type. This is particularly useful with the
9+
/// `ANY` operator to match a column against multiple values without having
10+
/// to dynamically construct the query string.
11+
///
12+
/// # Examples
13+
///
14+
/// ```rust,no_run
15+
/// # fn foo() -> postgres::Result<()> {
16+
/// # use postgres::{Connection, SslMode, Slice};
17+
/// # let conn = Connection::connect("", &SslMode::None).unwrap();
18+
/// let values = &[1i32, 2, 3, 4, 5, 6];
19+
/// let stmt = try!(conn.prepare("SELECT * FROM foo WHERE id = ANY($1)"));
20+
/// for row in try!(stmt.query(&[&Slice(values)])) {
21+
/// // ...
22+
/// }
23+
/// # Ok(()) }
24+
/// ```
25+
pub struct Slice<'a, T: 'a + ToSql>(pub &'a [T]);
26+
27+
impl<'a, T: 'a + ToSql> ToSql for Slice<'a, T> {
28+
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>> {
29+
let member_type = match ty.element_type() {
30+
Some(member) => member,
31+
None => return Err(Error::WrongType(ty.clone())),
32+
};
33+
34+
let mut buf = vec![];
35+
let _ = buf.write_i32::<BigEndian>(1); // number of dimensions
36+
let _ = buf.write_i32::<BigEndian>(1); // has nulls
37+
let _ = buf.write_u32::<BigEndian>(member_type.to_oid());
38+
39+
let _ = buf.write_i32::<BigEndian>(self.0.len() as i32);
40+
let _ = buf.write_i32::<BigEndian>(0); // index offset
41+
42+
for e in self.0 {
43+
match try!(e.to_sql(&member_type)) {
44+
Some(inner_buf) => {
45+
let _ = buf.write_i32::<BigEndian>(inner_buf.len() as i32);
46+
let _ = buf.write_all(&inner_buf);
47+
}
48+
None => {
49+
let _ = buf.write_i32::<BigEndian>(-1);
50+
}
51+
}
52+
}
53+
54+
Ok(Some(buf))
55+
}
56+
}

tests/types/mod.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::fmt;
66
use std::num::Float;
77
use std::old_io::net::ip::IpAddr;
88

9-
use postgres::{Connection, SslMode};
9+
use postgres::{Connection, SslMode, Slice, Error};
1010
use postgres::types::{ToSql, FromSql};
1111

1212
#[cfg(feature = "uuid")]
@@ -223,3 +223,28 @@ fn test_pg_database_datname() {
223223
or_panic!(next.get_opt::<usize, String>(0));
224224
or_panic!(next.get_opt::<&str, String>("datname"));
225225
}
226+
227+
#[test]
228+
fn test_slice() {
229+
let conn = Connection::connect("postgres://postgres@localhost", &SslMode::None).unwrap();
230+
conn.batch_execute("CREATE TEMPORARY TABLE foo (id SERIAL PRIMARY KEY, f VARCHAR);
231+
INSERT INTO foo (f) VALUES ('a'), ('b'), ('c'), ('d');").unwrap();
232+
233+
let stmt = conn.prepare("SELECT f FROM foo WHERE id = ANY($1)").unwrap();
234+
let result = stmt.query(&[&Slice(&[1i32, 3, 4])]).unwrap();
235+
assert_eq!(&["a".to_string(), "c".to_string(), "d".to_string()][],
236+
result.map(|r| r.get::<_, String>(0)).collect::<Vec<_>>());
237+
}
238+
239+
#[test]
240+
fn test_slice_wrong_type() {
241+
let conn = Connection::connect("postgres://postgres@localhost", &SslMode::None).unwrap();
242+
conn.batch_execute("CREATE TEMPORARY TABLE foo (id SERIAL PRIMARY KEY)").unwrap();
243+
244+
let stmt = conn.prepare("SELECT * FROM foo WHERE id = ANY($1)").unwrap();
245+
match stmt.query(&[&Slice(&["hi"])]) {
246+
Ok(_) => panic!("Unexpected success"),
247+
Err(Error::WrongType(..)) => {}
248+
Err(e) => panic!("Unexpected error {}", e),
249+
}
250+
}

0 commit comments

Comments
 (0)