Redesign ToSql and remove RawToSql

This commit is contained in:
Steven Fackler 2015-02-15 15:11:15 -08:00
parent 2380165c86
commit 1767661618
7 changed files with 232 additions and 288 deletions

View File

@ -76,6 +76,7 @@ use url::Url;
pub use error::{Error, ConnectError, SqlState, DbError, ErrorPosition};
#[doc(inline)]
pub use types::{Oid, Type, Kind, ToSql, FromSql};
use types::IsNull;
#[doc(inline)]
pub use types::Slice;
use io::{InternalStream, Timeout};
@ -727,12 +728,17 @@ impl InnerConnection {
}
// Ew @ doing this manually :(
let mut buf = vec![];
let value = match try!(oid.to_sql_checked(&Type::Oid, &mut buf)) {
IsNull::Yes => None,
IsNull::No => Some(buf),
};
try!(self.write_messages(&[
Bind {
portal: "",
statement: TYPEINFO_QUERY,
formats: &[1],
values: &[try!(oid.to_sql(&Type::Oid))],
values: &[value],
result_formats: &[1]
},
Execute {
@ -1319,7 +1325,11 @@ impl<'conn> Statement<'conn> {
params.len());
let mut values = vec![];
for (param, ty) in params.iter().zip(self.param_types.iter()) {
values.push(try!(param.to_sql(ty)));
let mut buf = vec![];
match try!(param.to_sql_checked(ty, &mut buf)) {
IsNull::Yes => values.push(None),
IsNull::No => values.push(Some(buf)),
}
};
try!(conn.write_messages(&[
@ -2054,13 +2064,14 @@ impl<'a> CopyInStatement<'a> {
loop {
match (row.next(), types.next()) {
(Some(val), Some(ty)) => {
match val.to_sql(ty) {
Ok(None) => {
let mut inner_buf = vec![];
match val.to_sql_checked(ty, &mut inner_buf) {
Ok(IsNull::Yes) => {
let _ = buf.write_be_i32(-1);
}
Ok(Some(val)) => {
let _ = buf.write_be_i32(val.len() as i32);
let _ = buf.write_all(&val);
Ok(IsNull::No) => {
let _ = buf.write_be_i32(inner_buf.len() as i32);
let _ = buf.write_all(&inner_buf);
}
Err(err) => {
// FIXME this is not the right way to handle this

View File

@ -1,7 +1,7 @@
use serialize::json;
use {Result, Error};
use types::{FromSql, RawToSql, Type};
use types::{FromSql, ToSql, IsNull, Type};
impl FromSql for json::Json {
fn from_sql<R: Reader>(ty: &Type, raw: &mut R) -> Result<json::Json> {
@ -17,14 +17,17 @@ impl FromSql for json::Json {
accepts!(Type::Json, Type::Jsonb);
}
impl RawToSql for json::Json {
fn raw_to_sql<W: Writer>(&self, ty: &Type, raw: &mut W) -> Result<()> {
impl ToSql for json::Json {
fn to_sql<W: Writer+?Sized>(&self, ty: &Type, out: &mut W) -> Result<IsNull> {
if let Type::Jsonb = *ty {
try!(raw.write_u8(1));
try!(out.write_u8(1));
}
Ok(try!(write!(raw, "{}", self)))
}
}
try!(write!(out, "{}", self));
to_raw_to_impl!(Type::Json, Type::Jsonb; json::Json);
Ok(IsNull::No)
}
accepts!(Type::Json, Type::Jsonb);
to_sql_checked!();
}

View File

@ -10,6 +10,7 @@ use error::Error;
pub use ugh_privacy::Other;
#[macro_export]
macro_rules! accepts {
($($expected:pat),+) => (
fn accepts(ty: &::types::Type) -> bool {
@ -21,126 +22,18 @@ macro_rules! accepts {
)
}
macro_rules! check_types {
($($expected:pat),+; $actual:ident) => (
match $actual {
$(&$expected)|+ => {}
actual => return Err(::Error::WrongType(actual.clone()))
}
)
}
macro_rules! raw_from_impl {
($t:ty, $f:ident) => (
impl RawFromSql for $t {
fn raw_from_sql<R: Reader>(_: &Type, raw: &mut R) -> Result<$t> {
Ok(try!(raw.$f()))
}
}
)
}
macro_rules! from_option_impl {
($t:ty) => {
impl ::types::FromSql for $t {
fn from_sql(ty: &Type, raw: Option<&[u8]>) -> Result<$t> {
use Error;
use types::FromSql;
// FIXME when you can specify Self types properly
let ret: Result<Option<$t>> = FromSql::from_sql(ty, raw);
match ret {
Ok(Some(val)) => Ok(val),
Ok(None) => Err(Error::WasNull),
Err(err) => Err(err)
}
#[macro_export]
macro_rules! to_sql_checked {
() => {
fn to_sql_checked(&self, ty: &Type, out: &mut Writer) -> Result<IsNull> {
if !<Self as ToSql>::accepts(ty) {
return Err($crate::Error::WrongType(ty.clone()));
}
self.to_sql(ty, out)
}
}
}
macro_rules! from_map_impl {
($($expected:pat),+; $t:ty, $blk:expr) => (
impl ::types::FromSql for Option<$t> {
fn from_sql(ty: &Type, raw: Option<&[u8]>) -> Result<Option<$t>> {
check_types!($($expected),+; ty);
match raw {
Some(buf) => ($blk)(ty, buf).map(|ok| Some(ok)),
None => Ok(None)
}
}
}
from_option_impl!($t);
)
}
macro_rules! from_raw_from_impl {
($($expected:pat),+; $t:ty) => (
from_map_impl!($($expected),+; $t, |ty, mut buf: &[u8]| {
use types::RawFromSql;
RawFromSql::raw_from_sql(ty, &mut buf)
});
)
}
macro_rules! raw_to_impl {
($t:ty, $f:ident) => (
impl RawToSql for $t {
fn raw_to_sql<W: Writer>(&self, _: &Type, w: &mut W) -> Result<()> {
Ok(try!(w.$f(*self)))
}
}
)
}
macro_rules! to_option_impl {
($($oid:pat),+; $t:ty) => (
impl ::types::ToSql for Option<$t> {
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>> {
check_types!($($oid),+; ty);
match *self {
None => Ok(None),
Some(ref val) => val.to_sql(ty)
}
}
}
)
}
macro_rules! to_option_impl_lifetime {
($($oid:pat),+; $t:ty) => (
impl<'a> ToSql for Option<$t> {
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>> {
check_types!($($oid),+; ty);
match *self {
None => Ok(None),
Some(ref val) => val.to_sql(ty)
}
}
}
)
}
macro_rules! to_raw_to_impl {
($($oid:pat),+; $t:ty) => (
impl ::types::ToSql for $t {
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>> {
check_types!($($oid),+; ty);
let mut writer = vec![];
try!(self.raw_to_sql(ty, &mut writer));
Ok(Some(writer))
}
}
to_option_impl!($($oid),+; $t);
)
}
#[cfg(feature = "uuid")]
mod uuid;
#[cfg(feature = "time")]
@ -712,175 +605,200 @@ impl FromSql for HashMap<String, Option<String>> {
}
}
/// A trait for types that can be converted into Postgres values
/// An enum representing the nullability of a Postgres value.
pub enum IsNull {
/// The value is NULL.
Yes,
/// The value is not NULL.
No,
}
/// A trait for types that can be converted into Postgres values.
pub trait ToSql {
/// Converts the value of `self` into the binary format appropriate for the
/// Postgres backend.
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>>;
}
/// A utility trait used by `ToSql` implementations.
pub trait RawToSql {
/// Converts the value of `self` into the binary format of the specified
/// Postgres type, writing it to `w`.
/// Converts the value of `self` into Postgres data.
///
/// It is the caller's responsibility to make sure that this type can be
/// converted to the specified Postgres type.
fn raw_to_sql<W: Writer>(&self, ty: &Type, w: &mut W) -> Result<()>;
/// The caller of this method is responsible for ensuring that this type
/// is compatible with the Postgres `Type`.
///
/// The return value indicates if this value should be represented as
/// `NULL`. If this is the case, implementations **must not** write
/// anything to `out`.
fn to_sql<W: ?Sized>(&self, ty: &Type, out: &mut W) -> Result<IsNull>
where Self: Sized, W: Writer;
/// Determines if a value of this type can be converted to the specified
/// Postgres `Type`.
fn accepts(ty: &Type) -> bool where Self: Sized;
/// An adaptor method used internally by Rust-Postgres.
///
/// *All* implementations of this method should be generated by the
/// `to_sql_checked!()` macro.
fn to_sql_checked(&self, ty: &Type, out: &mut Writer) -> Result<IsNull>;
}
impl RawToSql for bool {
fn raw_to_sql<W: Writer>(&self, _: &Type, w: &mut W) -> Result<()> {
Ok(try!(w.write_u8(*self as u8)))
}
}
impl<T: ToSql> ToSql for Option<T> {
to_sql_checked!();
impl RawToSql for Vec<u8> {
fn raw_to_sql<W: Writer>(&self, _: &Type, w: &mut W) -> Result<()> {
Ok(try!(w.write_all(&**self)))
}
}
impl RawToSql for String {
fn raw_to_sql<W: Writer>(&self, _: &Type, w: &mut W) -> Result<()> {
Ok(try!(w.write_all(self.as_bytes())))
}
}
raw_to_impl!(i8, write_i8);
raw_to_impl!(i16, write_be_i16);
raw_to_impl!(i32, write_be_i32);
raw_to_impl!(u32, write_be_u32);
raw_to_impl!(i64, write_be_i64);
raw_to_impl!(f32, write_be_f32);
raw_to_impl!(f64, write_be_f64);
impl RawToSql for IpAddr {
fn raw_to_sql<W: Writer>(&self, _: &Type, raw: &mut W) -> Result<()> {
fn to_sql<W: Writer+?Sized>(&self, ty: &Type, out: &mut W) -> Result<IsNull> {
match *self {
IpAddr::Ipv4Addr(a, b, c, d) => {
try!(raw.write_all(&[2, // family
32, // bits
0, // is_cidr
4, // nb
a, b, c, d // addr
]));
}
IpAddr::Ipv6Addr(a, b, c, d, e, f, g, h) => {
try!(raw.write_all(&[3, // family
128, // bits
0, // is_cidr
16, // nb
]));
try!(raw.write_be_u16(a));
try!(raw.write_be_u16(b));
try!(raw.write_be_u16(c));
try!(raw.write_be_u16(d));
try!(raw.write_be_u16(e));
try!(raw.write_be_u16(f));
try!(raw.write_be_u16(g));
try!(raw.write_be_u16(h));
}
Some(ref val) => val.to_sql(ty, out),
None => Ok(IsNull::Yes),
}
Ok(())
}
fn accepts(ty: &Type) -> bool {
<T as ToSql>::accepts(ty)
}
}
to_raw_to_impl!(Type::Bool; bool);
to_raw_to_impl!(Type::Bytea; Vec<u8>);
to_raw_to_impl!(Type::Inet, Type::Cidr; IpAddr);
to_raw_to_impl!(Type::Char; i8);
to_raw_to_impl!(Type::Int2; i16);
to_raw_to_impl!(Type::Int4; i32);
to_raw_to_impl!(Type::Oid; u32);
to_raw_to_impl!(Type::Int8; i64);
to_raw_to_impl!(Type::Float4; f32);
to_raw_to_impl!(Type::Float8; f64);
impl ToSql for bool {
to_sql_checked!();
impl ToSql for String {
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>> {
(&**self).to_sql(ty)
fn to_sql<W: Writer+?Sized>(&self, _: &Type, w: &mut W) -> Result<IsNull> {
try!(w.write_u8(*self as u8));
Ok(IsNull::No)
}
accepts!(Type::Bool);
}
impl ToSql for Option<String> {
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>> {
self.as_ref().map(|s| &**s).to_sql(ty)
impl<'a> ToSql for &'a [u8] {
to_sql_checked!();
fn to_sql<W: Writer+?Sized>(&self, _: &Type, w: &mut W) -> Result<IsNull> {
try!(w.write_all(*self));
Ok(IsNull::No)
}
accepts!(Type::Bytea);
}
impl ToSql for Vec<u8> {
to_sql_checked!();
fn to_sql<W: Writer+?Sized>(&self, ty: &Type, w: &mut W) -> Result<IsNull> {
(&**self).to_sql(ty, w)
}
fn accepts(ty: &Type) -> bool {
<&[u8] as ToSql>::accepts(ty)
}
}
impl<'a> ToSql for &'a str {
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>> {
to_sql_checked!();
fn to_sql<W: Writer+?Sized>(&self, _: &Type, w: &mut W) -> Result<IsNull> {
try!(w.write_all(self.as_bytes()));
Ok(IsNull::No)
}
fn accepts(ty: &Type) -> bool {
match *ty {
Type::Varchar | Type::Text | Type::Bpchar | Type::Name => {}
Type::Other(ref u) if u.name() == "citext" => {}
_ => return Err(Error::WrongType(ty.clone()))
Type::Varchar | Type::Text | Type::Bpchar | Type::Name => true,
Type::Other(ref u) if u.name() == "citext" => true,
_ => false,
}
Ok(Some(self.as_bytes().to_vec()))
}
}
impl<'a> ToSql for Option<&'a str> {
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>> {
match *ty {
Type::Varchar | Type::Text | Type::Bpchar | Type::Name => {}
Type::Other(ref u) if u.name() == "citext" => {}
_ => return Err(Error::WrongType(ty.clone()))
impl ToSql for String {
to_sql_checked!();
fn to_sql<W: Writer+?Sized>(&self, ty: &Type, w: &mut W) -> Result<IsNull> {
(&**self).to_sql(ty, w)
}
fn accepts(ty: &Type) -> bool {
<&str as ToSql>::accepts(ty)
}
}
macro_rules! to_primitive {
($t:ty, $f:ident, $($expected:pat),+) => {
impl ToSql for $t {
to_sql_checked!();
fn to_sql<W: Writer+?Sized>(&self, _: &Type, w: &mut W) -> Result<IsNull> {
try!(w.$f(*self));
Ok(IsNull::No)
}
accepts!($($expected),+);
}
}
}
to_primitive!(i8, write_i8, Type::Char);
to_primitive!(i16, write_be_i16, Type::Int2);
to_primitive!(i32, write_be_i32, Type::Int4);
to_primitive!(u32, write_be_u32, Type::Oid);
to_primitive!(i64, write_be_i64, Type::Int8);
to_primitive!(f32, write_be_f32, Type::Float4);
to_primitive!(f64, write_be_f64, Type::Float8);
impl ToSql for IpAddr {
to_sql_checked!();
fn to_sql<W: Writer+?Sized>(&self, _: &Type, w: &mut W) -> Result<IsNull> {
match *self {
Some(ref val) => val.to_sql(ty),
None => Ok(None)
IpAddr::Ipv4Addr(a, b, c, d) => {
try!(w.write_all(&[2, // family
32, // bits
0, // is_cidr
4, // nb
a, b, c, d // addr
]));
}
IpAddr::Ipv6Addr(a, b, c, d, e, f, g, h) => {
try!(w.write_all(&[3, // family
128, // bits
0, // is_cidr
16, // nb
]));
try!(w.write_be_u16(a));
try!(w.write_be_u16(b));
try!(w.write_be_u16(c));
try!(w.write_be_u16(d));
try!(w.write_be_u16(e));
try!(w.write_be_u16(f));
try!(w.write_be_u16(g));
try!(w.write_be_u16(h));
}
}
Ok(IsNull::No)
}
}
impl<'a> ToSql for &'a [u8] {
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>> {
check_types!(Type::Bytea; ty);
Ok(Some(self.to_vec()))
}
accepts!(Type::Cidr, Type::Inet);
}
to_option_impl_lifetime!(Type::Bytea; &'a [u8]);
impl ToSql for HashMap<String, Option<String>> {
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>> {
match *ty {
Type::Other(ref u) if u.name() == "hstore" => {}
_ => return Err(Error::WrongType(ty.clone()))
}
to_sql_checked!();
let mut buf = vec![];
try!(buf.write_be_i32(self.len() as i32));
fn to_sql<W: Writer+?Sized>(&self, _: &Type, w: &mut W) -> Result<IsNull> {
try!(w.write_be_i32(self.len() as i32));
for (key, val) in self.iter() {
try!(buf.write_be_i32(key.len() as i32));
try!(buf.write_all(key.as_bytes()));
try!(w.write_be_i32(key.len() as i32));
try!(w.write_all(key.as_bytes()));
match *val {
Some(ref val) => {
try!(buf.write_be_i32(val.len() as i32));
try!(buf.write_all(val.as_bytes()));
try!(w.write_be_i32(val.len() as i32));
try!(w.write_all(val.as_bytes()));
}
None => try!(buf.write_be_i32(-1))
None => try!(w.write_be_i32(-1))
}
}
Ok(Some(buf))
Ok(IsNull::No)
}
}
impl ToSql for Option<HashMap<String, Option<String>>> {
fn to_sql(&self, ty: &Type) -> Result<Option<Vec<u8>>> {
fn accepts(ty: &Type) -> bool {
match *ty {
Type::Other(ref u) if u.name() == "hstore" => {}
_ => return Err(Error::WrongType(ty.clone()))
}
match *self {
Some(ref inner) => inner.to_sql(ty),
None => Ok(None)
Type::Other(ref u) if u.name() == "hstore" => true,
_ => false,
}
}
}

View File

@ -1,6 +1,5 @@
use byteorder::{BigEndian, WriterBytesExt};
use {Type, ToSql, Result, Error, Kind};
use types::IsNull;
/// An adapter type mapping slices to Postgres arrays.
///
@ -25,32 +24,42 @@ use {Type, ToSql, Result, Error, Kind};
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>>> {
to_sql_checked!();
fn to_sql<W: Writer+?Sized>(&self, ty: &Type, w: &mut W) -> Result<IsNull> {
let member_type = match ty.kind() {
&Kind::Array(ref member) => member,
_ => return Err(Error::WrongType(ty.clone())),
_ => panic!("expected array type"),
};
if !<T as ToSql>::accepts(&member_type) {
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());
try!(w.write_be_i32(1)); // number of dimensions
try!(w.write_be_i32(1)); // has nulls
try!(w.write_be_u32(member_type.to_oid()));
let _ = buf.write_i32::<BigEndian>(self.0.len() as i32);
let _ = buf.write_i32::<BigEndian>(0); // index offset
try!(w.write_be_i32(self.0.len() as i32));
try!(w.write_be_i32(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);
let mut inner_buf = vec![];
match try!(e.to_sql(&member_type, &mut inner_buf)) {
IsNull::No => {
try!(w.write_be_i32(inner_buf.len() as i32));
try!(w.write_all(&inner_buf));
}
IsNull::Yes => try!(w.write_be_i32(-1)),
}
}
Ok(Some(buf))
Ok(IsNull::No)
}
fn accepts(ty: &Type) -> bool {
match ty.kind() {
&Kind::Array(..) => true,
_ => false,
}
}
}

View File

@ -1,6 +1,6 @@
use time::Timespec;
use Result;
use types::{Type, FromSql, RawToSql};
use types::{Type, FromSql, ToSql, IsNull};
const USEC_PER_SEC: i64 = 1_000_000;
const NSEC_PER_USEC: i64 = 1_000;
@ -25,12 +25,13 @@ impl FromSql for Timespec {
accepts!(Type::Timestamp, Type::TimestampTZ);
}
impl RawToSql for Timespec {
fn raw_to_sql<W: Writer>(&self, _: &Type, w: &mut W) -> Result<()> {
impl ToSql for Timespec {
fn to_sql<W: Writer+?Sized>(&self, _: &Type, w: &mut W) -> Result<IsNull> {
let t = (self.sec - TIME_SEC_CONVERSION) * USEC_PER_SEC + self.nsec as i64 / NSEC_PER_USEC;
Ok(try!(w.write_be_i64(t)))
try!(w.write_be_i64(t));
Ok(IsNull::No)
}
accepts!(Type::Timestamp, Type::TimestampTZ);
to_sql_checked!();
}
to_raw_to_impl!(Type::Timestamp, Type::TimestampTZ; Timespec);

View File

@ -1,7 +1,7 @@
extern crate uuid;
use self::uuid::Uuid;
use types::{FromSql, ToSql, RawToSql, Type};
use types::{FromSql, ToSql, Type, IsNull};
use Error;
use Result;
@ -16,10 +16,12 @@ impl FromSql for Uuid {
accepts!(Type::Uuid);
}
impl RawToSql for Uuid {
fn raw_to_sql<W: Writer>(&self, _: &Type, w: &mut W) -> Result<()> {
Ok(try!(w.write_all(self.as_bytes())))
impl ToSql for Uuid {
fn to_sql<W: Writer+?Sized>(&self, _: &Type, w: &mut W) -> Result<IsNull> {
try!(w.write_all(self.as_bytes()));
Ok(IsNull::No)
}
}
to_raw_to_impl!(Type::Uuid; Uuid);
accepts!(Type::Uuid);
to_sql_checked!();
}

View File

@ -229,7 +229,7 @@ fn test_slice_wrong_type() {
match stmt.query(&[&Slice(&["hi"])]) {
Ok(_) => panic!("Unexpected success"),
Err(Error::WrongType(..)) => {}
Err(e) => panic!("Unexpected error {}", e),
Err(e) => panic!("Unexpected error {:?}", e),
}
}
@ -241,6 +241,6 @@ fn test_slice_range() {
match stmt.query(&[&Slice(&[1i64])]) {
Ok(_) => panic!("Unexpected success"),
Err(Error::WrongType(..)) => {}
Err(e) => panic!("Unexpected error {}", e),
Err(e) => panic!("Unexpected error {:?}", e),
}
}