Support conversion of the UNKNOWN type to String

Closes #256
This commit is contained in:
Steven Fackler
2017-05-22 22:04:28 -07:00
parent f892c4b57b
commit 06a6273f74
2 changed files with 149 additions and 138 deletions

View File

@@ -156,11 +156,11 @@ impl Other {
#[doc(hidden)]
pub fn new(name: String, oid: Oid, kind: Kind, schema: String) -> Other {
Other(Arc::new(OtherInner {
name: name,
oid: oid,
kind: kind,
schema: schema,
}))
name: name,
oid: oid,
kind: kind,
schema: schema,
}))
}
}
@@ -236,19 +236,19 @@ impl WrongType {
/// The following implementations are provided by this crate, along with the
/// corresponding Postgres types:
///
/// | Rust type | Postgres type(s) |
/// |-----------------------------------|--------------------------------------|
/// | `bool` | BOOL |
/// | `i8` | "char" |
/// | `i16` | SMALLINT, SMALLSERIAL |
/// | `i32` | INT, SERIAL |
/// | `u32` | OID |
/// | `i64` | BIGINT, BIGSERIAL |
/// | `f32` | REAL |
/// | `f64` | DOUBLE PRECISION |
/// | `String` | VARCHAR, CHAR(n), TEXT, CITEXT, NAME |
/// | `Vec<u8>` | BYTEA |
/// | `HashMap<String, Option<String>>` | HSTORE |
/// | Rust type | Postgres type(s) |
/// |-----------------------------------|-----------------------------------------------|
/// | `bool` | BOOL |
/// | `i8` | "char" |
/// | `i16` | SMALLINT, SMALLSERIAL |
/// | `i32` | INT, SERIAL |
/// | `u32` | OID |
/// | `i64` | BIGINT, BIGSERIAL |
/// | `f32` | REAL |
/// | `f64` | DOUBLE PRECISION |
/// | `String` | VARCHAR, CHAR(n), TEXT, CITEXT, NAME, UNKNOWN |
/// | `Vec<u8>` | BYTEA |
/// | `HashMap<String, Option<String>>` | HSTORE |
///
/// In addition, some implementations are provided for types in third party
/// crates. These are disabled by default; to opt into one of these
@@ -288,9 +288,7 @@ pub trait FromSql: Sized {
///
/// The caller of this method is responsible for ensuring that this type
/// is compatible with the Postgres `Type`.
fn from_sql(ty: &Type,
raw: &[u8])
-> Result<Self, Box<Error + Sync + Send>>;
fn from_sql(ty: &Type, raw: &[u8]) -> Result<Self, Box<Error + Sync + Send>>;
/// Creates a new value of this type from a `NULL` SQL value.
///
@@ -306,9 +304,7 @@ pub trait FromSql: Sized {
/// A convenience function that delegates to `from_sql` and `from_sql_null` depending on the
/// value of `raw`.
fn from_sql_nullable(ty: &Type,
raw: Option<&[u8]>)
-> Result<Self, Box<Error + Sync + Send>> {
fn from_sql_nullable(ty: &Type, raw: Option<&[u8]>) -> Result<Self, Box<Error + Sync + Send>> {
match raw {
Some(raw) => Self::from_sql(ty, raw),
None => Self::from_sql_null(ty),
@@ -321,9 +317,7 @@ pub trait FromSql: Sized {
}
impl<T: FromSql> FromSql for Option<T> {
fn from_sql(ty: &Type,
raw: &[u8])
-> Result<Option<T>, Box<Error + Sync + Send>> {
fn from_sql(ty: &Type, raw: &[u8]) -> Result<Option<T>, Box<Error + Sync + Send>> {
<T as FromSql>::from_sql(ty, raw).map(Some)
}
@@ -337,9 +331,7 @@ impl<T: FromSql> FromSql for Option<T> {
}
impl<T: FromSql> FromSql for Vec<T> {
fn from_sql(ty: &Type,
raw: &[u8])
-> Result<Vec<T>, Box<Error + Sync + Send>> {
fn from_sql(ty: &Type, raw: &[u8]) -> Result<Vec<T>, Box<Error + Sync + Send>> {
let member_type = match *ty.kind() {
Kind::Array(ref member) => member,
_ => panic!("expected array type"),
@@ -350,7 +342,8 @@ impl<T: FromSql> FromSql for Vec<T> {
return Err("array contains too many dimensions".into());
}
array.values()
array
.values()
.and_then(|v| T::from_sql_nullable(member_type, v))
.collect()
}
@@ -364,9 +357,7 @@ impl<T: FromSql> FromSql for Vec<T> {
}
impl FromSql for Vec<u8> {
fn from_sql(_: &Type,
raw: &[u8])
-> Result<Vec<u8>, Box<Error + Sync + Send>> {
fn from_sql(_: &Type, raw: &[u8]) -> Result<Vec<u8>, Box<Error + Sync + Send>> {
Ok(types::bytea_from_sql(raw).to_owned())
}
@@ -380,7 +371,7 @@ impl FromSql for String {
fn accepts(ty: &Type) -> bool {
match *ty {
Type::Varchar | Type::Text | Type::Bpchar | Type::Name => true,
Type::Varchar | Type::Text | Type::Bpchar | Type::Name | Type::Unknown => true,
Type::Other(ref u) if u.name() == "citext" => true,
_ => false,
}
@@ -500,10 +491,7 @@ pub trait ToSql: fmt::Debug {
/// 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(&self,
ty: &Type,
out: &mut Vec<u8>)
-> Result<IsNull, Box<Error + Sync + Send>>
fn to_sql(&self, ty: &Type, out: &mut Vec<u8>) -> Result<IsNull, Box<Error + Sync + Send>>
where Self: Sized;
/// Determines if a value of this type can be converted to the specified
@@ -523,10 +511,7 @@ pub trait ToSql: fmt::Debug {
impl<'a, T> ToSql for &'a T
where T: ToSql
{
fn to_sql(&self,
ty: &Type,
out: &mut Vec<u8>)
-> Result<IsNull, Box<Error + Sync + Send>> {
fn to_sql(&self, ty: &Type, out: &mut Vec<u8>) -> Result<IsNull, Box<Error + Sync + Send>> {
(*self).to_sql(ty, out)
}
@@ -538,10 +523,7 @@ impl<'a, T> ToSql for &'a T
}
impl<T: ToSql> ToSql for Option<T> {
fn to_sql(&self,
ty: &Type,
out: &mut Vec<u8>)
-> Result<IsNull, Box<Error + Sync + Send>> {
fn to_sql(&self, ty: &Type, out: &mut Vec<u8>) -> Result<IsNull, Box<Error + Sync + Send>> {
match *self {
Some(ref val) => val.to_sql(ty, out),
None => Ok(IsNull::Yes),
@@ -556,10 +538,7 @@ impl<T: ToSql> ToSql for Option<T> {
}
impl<'a, T: ToSql> ToSql for &'a [T] {
fn to_sql(&self,
ty: &Type,
w: &mut Vec<u8>)
-> Result<IsNull, Box<Error + Sync + Send>> {
fn to_sql(&self, ty: &Type, w: &mut Vec<u8>) -> Result<IsNull, Box<Error + Sync + Send>> {
let member_type = match *ty.kind() {
Kind::Array(ref member) => member,
_ => panic!("expected array type"),
@@ -571,16 +550,14 @@ impl<'a, T: ToSql> ToSql for &'a [T] {
};
types::array_to_sql(Some(dimension),
true,
member_type.oid(),
self.iter(),
|e, w| {
match e.to_sql(member_type, w)? {
IsNull::No => Ok(postgres_protocol::IsNull::No),
IsNull::Yes => Ok(postgres_protocol::IsNull::Yes),
}
},
w)?;
true,
member_type.oid(),
self.iter(),
|e, w| match e.to_sql(member_type, w)? {
IsNull::No => Ok(postgres_protocol::IsNull::No),
IsNull::Yes => Ok(postgres_protocol::IsNull::Yes),
},
w)?;
Ok(IsNull::No)
}
@@ -595,10 +572,7 @@ impl<'a, T: ToSql> ToSql for &'a [T] {
}
impl<'a> ToSql for &'a [u8] {
fn to_sql(&self,
_: &Type,
w: &mut Vec<u8>)
-> Result<IsNull, Box<Error + Sync + Send>> {
fn to_sql(&self, _: &Type, w: &mut Vec<u8>) -> Result<IsNull, Box<Error + Sync + Send>> {
types::bytea_to_sql(*self, w);
Ok(IsNull::No)
}
@@ -609,10 +583,7 @@ impl<'a> ToSql for &'a [u8] {
}
impl<T: ToSql> ToSql for Vec<T> {
fn to_sql(&self,
ty: &Type,
w: &mut Vec<u8>)
-> Result<IsNull, Box<Error + Sync + Send>> {
fn to_sql(&self, ty: &Type, w: &mut Vec<u8>) -> Result<IsNull, Box<Error + Sync + Send>> {
<&[T] as ToSql>::to_sql(&&**self, ty, w)
}
@@ -624,10 +595,7 @@ impl<T: ToSql> ToSql for Vec<T> {
}
impl ToSql for Vec<u8> {
fn to_sql(&self,
ty: &Type,
w: &mut Vec<u8>)
-> Result<IsNull, Box<Error + Sync + Send>> {
fn to_sql(&self, ty: &Type, w: &mut Vec<u8>) -> Result<IsNull, Box<Error + Sync + Send>> {
<&[u8] as ToSql>::to_sql(&&**self, ty, w)
}
@@ -639,10 +607,7 @@ impl ToSql for Vec<u8> {
}
impl<'a> ToSql for &'a str {
fn to_sql(&self,
_: &Type,
w: &mut Vec<u8>)
-> Result<IsNull, Box<Error + Sync + Send>> {
fn to_sql(&self, _: &Type, w: &mut Vec<u8>) -> Result<IsNull, Box<Error + Sync + Send>> {
types::text_to_sql(*self, w);
Ok(IsNull::No)
}
@@ -659,10 +624,7 @@ impl<'a> ToSql for &'a str {
}
impl ToSql for String {
fn to_sql(&self,
ty: &Type,
w: &mut Vec<u8>)
-> Result<IsNull, Box<Error + Sync + Send>> {
fn to_sql(&self, ty: &Type, w: &mut Vec<u8>) -> Result<IsNull, Box<Error + Sync + Send>> {
<&str as ToSql>::to_sql(&&**self, ty, w)
}
@@ -701,11 +663,9 @@ simple_to!(f32, float4_to_sql, Type::Float4);
simple_to!(f64, float8_to_sql, Type::Float8);
impl ToSql for HashMap<String, Option<String>> {
fn to_sql(&self,
_: &Type,
w: &mut Vec<u8>)
-> Result<IsNull, Box<Error + Sync + Send>> {
types::hstore_to_sql(self.iter().map(|(k, v)| (&**k, v.as_ref().map(|v| &**v))), w)?;
fn to_sql(&self, _: &Type, w: &mut Vec<u8>) -> Result<IsNull, Box<Error + Sync + Send>> {
types::hstore_to_sql(self.iter().map(|(k, v)| (&**k, v.as_ref().map(|v| &**v))),
w)?;
Ok(IsNull::No)
}

View File

@@ -26,7 +26,7 @@ mod chrono;
#[cfg(feature = "with-geo")]
mod geo;
fn test_type<T: PartialEq+FromSql+ToSql, S: fmt::Display>(sql_type: &str, checks: &[(T, S)]) {
fn test_type<T: PartialEq + FromSql + ToSql, S: fmt::Display>(sql_type: &str, checks: &[(T, S)]) {
let conn = or_panic!(Connection::connect("postgres://postgres@localhost", TlsMode::None));
for &(ref val, ref repr) in checks.iter() {
let stmt = or_panic!(conn.prepare(&*format!("SELECT {}::{}", *repr, sql_type)));
@@ -49,8 +49,8 @@ fn test_ref_tosql() {
#[test]
fn test_bool_params() {
test_type("BOOL", &[(Some(true), "'t'"), (Some(false), "'f'"),
(None, "NULL")]);
test_type("BOOL",
&[(Some(true), "'t'"), (Some(false), "'f'"), (None, "NULL")]);
}
#[test]
@@ -60,63 +60,79 @@ fn test_i8_params() {
#[test]
fn test_name_params() {
test_type("NAME", &[(Some("hello world".to_owned()), "'hello world'"),
(Some("イロハニホヘト チリヌルヲ".to_owned()), "'イロハニホヘト チリヌルヲ'"),
(None, "NULL")]);
test_type("NAME",
&[(Some("hello world".to_owned()), "'hello world'"),
(Some("イロハニホヘト チリヌルヲ".to_owned()),
"'イロハニホヘト チリヌルヲ'"),
(None, "NULL")]);
}
#[test]
fn test_i16_params() {
test_type("SMALLINT", &[(Some(15001i16), "15001"),
(Some(-15001i16), "-15001"), (None, "NULL")]);
test_type("SMALLINT",
&[(Some(15001i16), "15001"),
(Some(-15001i16), "-15001"),
(None, "NULL")]);
}
#[test]
fn test_i32_params() {
test_type("INT", &[(Some(2147483548i32), "2147483548"),
(Some(-2147483548i32), "-2147483548"), (None, "NULL")]);
test_type("INT",
&[(Some(2147483548i32), "2147483548"),
(Some(-2147483548i32), "-2147483548"),
(None, "NULL")]);
}
#[test]
fn test_oid_params() {
test_type("OID", &[(Some(2147483548u32), "2147483548"),
(Some(4000000000), "4000000000"), (None, "NULL")]);
test_type("OID",
&[(Some(2147483548u32), "2147483548"),
(Some(4000000000), "4000000000"),
(None, "NULL")]);
}
#[test]
fn test_i64_params() {
test_type("BIGINT", &[(Some(9223372036854775708i64), "9223372036854775708"),
(Some(-9223372036854775708i64), "-9223372036854775708"),
(None, "NULL")]);
test_type("BIGINT",
&[(Some(9223372036854775708i64), "9223372036854775708"),
(Some(-9223372036854775708i64), "-9223372036854775708"),
(None, "NULL")]);
}
#[test]
fn test_f32_params() {
test_type("REAL", &[(Some(f32::INFINITY), "'infinity'"),
(Some(f32::NEG_INFINITY), "'-infinity'"),
(Some(1000.55), "1000.55"), (None, "NULL")]);
test_type("REAL",
&[(Some(f32::INFINITY), "'infinity'"),
(Some(f32::NEG_INFINITY), "'-infinity'"),
(Some(1000.55), "1000.55"),
(None, "NULL")]);
}
#[test]
fn test_f64_params() {
test_type("DOUBLE PRECISION", &[(Some(f64::INFINITY), "'infinity'"),
(Some(f64::NEG_INFINITY), "'-infinity'"),
(Some(10000.55), "10000.55"),
(None, "NULL")]);
test_type("DOUBLE PRECISION",
&[(Some(f64::INFINITY), "'infinity'"),
(Some(f64::NEG_INFINITY), "'-infinity'"),
(Some(10000.55), "10000.55"),
(None, "NULL")]);
}
#[test]
fn test_varchar_params() {
test_type("VARCHAR", &[(Some("hello world".to_owned()), "'hello world'"),
(Some("イロハニホヘト チリヌルヲ".to_owned()), "'イロハニホヘト チリヌルヲ'"),
(None, "NULL")]);
test_type("VARCHAR",
&[(Some("hello world".to_owned()), "'hello world'"),
(Some("イロハニホヘト チリヌルヲ".to_owned()),
"'イロハニホヘト チリヌルヲ'"),
(None, "NULL")]);
}
#[test]
fn test_text_params() {
test_type("TEXT", &[(Some("hello world".to_owned()), "'hello world'"),
(Some("イロハニホヘト チリヌルヲ".to_owned()), "'イロハニホヘト チリヌルヲ'"),
(None, "NULL")]);
test_type("TEXT",
&[(Some("hello world".to_owned()), "'hello world'"),
(Some("イロハニホヘト チリヌルヲ".to_owned()),
"'イロハニホヘト チリヌルヲ'"),
(None, "NULL")]);
}
#[test]
@@ -125,13 +141,14 @@ fn test_bpchar_params() {
or_panic!(conn.execute("CREATE TEMPORARY TABLE foo (
id SERIAL PRIMARY KEY,
b CHAR(5)
)", &[]));
)",
&[]));
or_panic!(conn.execute("INSERT INTO foo (b) VALUES ($1), ($2), ($3)",
&[&Some("12345"), &Some("123"), &None::<&'static str>]));
let stmt = or_panic!(conn.prepare("SELECT b FROM foo ORDER BY id"));
let res = or_panic!(stmt.query(&[]));
assert_eq!(vec!(Some("12345".to_owned()), Some("123 ".to_owned()), None),
assert_eq!(vec![Some("12345".to_owned()), Some("123 ".to_owned()), None],
res.iter().map(|row| row.get(0)).collect::<Vec<_>>());
}
@@ -141,19 +158,22 @@ fn test_citext_params() {
or_panic!(conn.execute("CREATE TEMPORARY TABLE foo (
id SERIAL PRIMARY KEY,
b CITEXT
)", &[]));
)",
&[]));
or_panic!(conn.execute("INSERT INTO foo (b) VALUES ($1), ($2), ($3)",
&[&Some("foobar"), &Some("FooBar"), &None::<&'static str>]));
let stmt = or_panic!(conn.prepare("SELECT id FROM foo WHERE b = 'FOOBAR' ORDER BY id"));
let res = or_panic!(stmt.query(&[]));
assert_eq!(vec!(Some(1i32), Some(2i32)), res.iter().map(|row| row.get(0)).collect::<Vec<_>>());
assert_eq!(vec![Some(1i32), Some(2i32)],
res.iter().map(|row| row.get(0)).collect::<Vec<_>>());
}
#[test]
fn test_bytea_params() {
test_type("BYTEA", &[(Some(vec!(0u8, 1, 2, 3, 254, 255)), "'\\x00010203feff'"),
(None, "NULL")]);
test_type("BYTEA",
&[(Some(vec![0u8, 1, 2, 3, 254, 255]), "'\\x00010203feff'"),
(None, "NULL")]);
}
#[test]
@@ -167,22 +187,23 @@ fn test_hstore_params() {
}
test_type("hstore",
&[(Some(make_map!("a".to_owned() => Some("1".to_owned()))), "'a=>1'"),
(Some(make_map!("hello".to_owned() => Some("world!".to_owned()),
(Some(make_map!("hello".to_owned() => Some("world!".to_owned()),
"hola".to_owned() => Some("mundo!".to_owned()),
"what".to_owned() => None)),
"'hello=>world!,hola=>mundo!,what=>NULL'"),
"'hello=>world!,hola=>mundo!,what=>NULL'"),
(None, "NULL")]);
}
#[test]
fn test_array_params() {
test_type("integer[]", &[(Some(vec!(1i32, 2i32)), "ARRAY[1,2]"),
(Some(vec!(1i32)), "ARRAY[1]"),
(Some(vec!()), "ARRAY[]"),
(None, "NULL")]);
test_type("integer[]",
&[(Some(vec![1i32, 2i32]), "ARRAY[1,2]"),
(Some(vec![1i32]), "ARRAY[1]"),
(Some(vec![]), "ARRAY[]"),
(None, "NULL")]);
}
fn test_nan_param<T: PartialEq+ToSql+FromSql>(sql_type: &str) {
fn test_nan_param<T: PartialEq + ToSql + FromSql>(sql_type: &str) {
let conn = or_panic!(Connection::connect("postgres://postgres@localhost", TlsMode::None));
let stmt = or_panic!(conn.prepare(&*format!("SELECT 'NaN'::{}", sql_type)));
let result = or_panic!(stmt.query(&[]));
@@ -215,20 +236,27 @@ fn test_pg_database_datname() {
fn test_slice() {
let conn = Connection::connect("postgres://postgres@localhost", TlsMode::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();
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(&[&&[1i32, 3, 4][..]]).unwrap();
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<_>>());
}
#[test]
fn test_slice_wrong_type() {
let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap();
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(&[&&["hi"][..]]) {
Ok(_) => panic!("Unexpected success"),
Err(Error::Conversion(ref e)) if e.is::<WrongType>() => {}
@@ -254,7 +282,10 @@ fn domain() {
struct SessionId(Vec<u8>);
impl ToSql for SessionId {
fn to_sql(&self, ty: &Type, out: &mut Vec<u8>) -> result::Result<IsNull, Box<error::Error + Sync + Send>> {
fn to_sql(&self,
ty: &Type,
out: &mut Vec<u8>)
-> result::Result<IsNull, Box<error::Error + Sync + Send>> {
let inner = match *ty.kind() {
Kind::Domain(ref inner) => inner,
_ => unreachable!(),
@@ -263,14 +294,20 @@ fn domain() {
}
fn accepts(ty: &Type) -> bool {
ty.name() == "session_id" && match *ty.kind() { Kind::Domain(_) => true, _ => false }
ty.name() == "session_id" &&
match *ty.kind() {
Kind::Domain(_) => true,
_ => false,
}
}
to_sql_checked!();
}
impl FromSql for SessionId {
fn from_sql(ty: &Type, raw: &[u8]) -> result::Result<Self, Box<error::Error + Sync + Send>> {
fn from_sql(ty: &Type,
raw: &[u8])
-> result::Result<Self, Box<error::Error + Sync + Send>> {
Vec::<u8>::from_sql(ty, raw).map(SessionId)
}
@@ -286,7 +323,8 @@ fn domain() {
.unwrap();
let id = SessionId(b"0123456789abcdef".to_vec());
conn.execute("INSERT INTO pg_temp.foo (id) VALUES ($1)", &[&id]).unwrap();
conn.execute("INSERT INTO pg_temp.foo (id) VALUES ($1)", &[&id])
.unwrap();
let rows = conn.query("SELECT id FROM pg_temp.foo", &[]).unwrap();
assert_eq!(id, rows.get(0).get(0));
}
@@ -320,15 +358,28 @@ fn composite() {
#[test]
fn enum_() {
let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap();
conn.batch_execute("CREATE TYPE pg_temp.mood AS ENUM ('sad', 'ok', 'happy');").unwrap();
conn.batch_execute("CREATE TYPE pg_temp.mood AS ENUM ('sad', 'ok', 'happy');")
.unwrap();
let stmt = conn.prepare("SELECT $1::mood").unwrap();
let type_ = &stmt.param_types()[0];
assert_eq!(type_.name(), "mood");
match *type_.kind() {
Kind::Enum(ref variants) => {
assert_eq!(variants, &["sad".to_owned(), "ok".to_owned(), "happy".to_owned()]);
assert_eq!(variants,
&["sad".to_owned(), "ok".to_owned(), "happy".to_owned()]);
}
_ => panic!("bad type"),
}
}
#[test]
fn unknown() {
let conn = Connection::connect("postgres://postgres@localhost", TlsMode::None).unwrap();
let rows = conn.query("SELECT 'hello world'", &[]).unwrap();
match *rows.columns()[0].type_() {
Type::Unknown => {}
_ => panic!("bad type"),
}
assert_eq!("hello world", rows.get(0).get::<_, String>(0));
}