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 #92
This commit is contained in:
Steven Fackler 2015-02-12 22:52:55 -08:00
parent 23c49c0219
commit a0cca5fa77
4 changed files with 87 additions and 1 deletions

View File

@ -82,6 +82,8 @@ use url::Url;
pub use error::{Error, ConnectError, SqlState, DbError, ErrorPosition};
#[doc(inline)]
pub use types::{Oid, Type, ToSql, FromSql};
#[doc(inline)]
pub use types::Slice;
use io::{InternalStream, Timeout};
use message::BackendMessage::*;
use message::FrontendMessage::*;

View File

@ -1,4 +1,6 @@
//! Traits dealing with Postgres data types
pub use self::slice::Slice;
use serialize::json;
use std::collections::HashMap;
use std::old_io::net::ip::IpAddr;
@ -132,6 +134,7 @@ macro_rules! to_raw_to_impl {
#[cfg(feature = "uuid")]
mod uuid;
mod time;
mod slice;
/// A Postgres OID
pub type Oid = u32;

56
src/types/slice.rs Normal file
View File

@ -0,0 +1,56 @@
use byteorder::{BigEndian, WriterBytesExt};
use {Type, ToSql, Result, Error};
/// An adapter type mapping slices to Postgres arrays.
///
/// `Slice`'s `ToSql` implementation maps the slice to a one-dimensional
/// Postgres array of the relevant type. This is particularly useful with the
/// `ANY` operator to match a column against multiple values without having
/// to dynamically construct the query string.
///
/// # Examples
///
/// ```rust,no_run
/// # fn foo() -> postgres::Result<()> {
/// # use postgres::{Connection, SslMode, Slice};
/// # let conn = Connection::connect("", &SslMode::None).unwrap();
/// let values = &[1i32, 2, 3, 4, 5, 6];
/// let stmt = try!(conn.prepare("SELECT * FROM foo WHERE id = ANY($1)"));
/// for row in try!(stmt.query(&[&Slice(values)])) {
/// // ...
/// }
/// # Ok(()) }
/// ```
pub struct Slice<'a, T: 'a + ToSql>(pub &'a [T]);
impl<'a, T: 'a + ToSql> ToSql for Slice<'a, T> {
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>> {
let member_type = match ty.element_type() {
Some(member) => member,
None => return Err(Error::WrongType(ty.clone())),
};
let mut buf = vec![];
let _ = buf.write_i32::<BigEndian>(1); // number of dimensions
let _ = buf.write_i32::<BigEndian>(1); // has nulls
let _ = buf.write_u32::<BigEndian>(member_type.to_oid());
let _ = buf.write_i32::<BigEndian>(self.0.len() as i32);
let _ = buf.write_i32::<BigEndian>(0); // index offset
for e in self.0 {
match try!(e.to_sql(&member_type)) {
Some(inner_buf) => {
let _ = buf.write_i32::<BigEndian>(inner_buf.len() as i32);
let _ = buf.write_all(&inner_buf);
}
None => {
let _ = buf.write_i32::<BigEndian>(-1);
}
}
}
Ok(Some(buf))
}
}

View File

@ -6,7 +6,7 @@ use std::fmt;
use std::num::Float;
use std::old_io::net::ip::IpAddr;
use postgres::{Connection, SslMode};
use postgres::{Connection, SslMode, Slice, Error};
use postgres::types::{ToSql, FromSql};
#[cfg(feature = "uuid")]
@ -223,3 +223,28 @@ fn test_pg_database_datname() {
or_panic!(next.get_opt::<usize, String>(0));
or_panic!(next.get_opt::<&str, String>("datname"));
}
#[test]
fn test_slice() {
let conn = Connection::connect("postgres://postgres@localhost", &SslMode::None).unwrap();
conn.batch_execute("CREATE TEMPORARY TABLE foo (id SERIAL PRIMARY KEY, f VARCHAR);
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();
assert_eq!(&["a".to_string(), "c".to_string(), "d".to_string()][],
result.map(|r| r.get::<_, String>(0)).collect::<Vec<_>>());
}
#[test]
fn test_slice_wrong_type() {
let conn = Connection::connect("postgres://postgres@localhost", &SslMode::None).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();
match stmt.query(&[&Slice(&["hi"])]) {
Ok(_) => panic!("Unexpected success"),
Err(Error::WrongType(..)) => {}
Err(e) => panic!("Unexpected error {}", e),
}
}