Allow arrays containing null to be serialized

Deserialization already worked, so there's no reason we shouldn't just
allow this. It's worth noting that there is no way to represent whether
an array is allowed to contain nulls or not within postgres's type
system. As such, the type `Array<Nullable<ST>>` will never be generated
by `infer_schema!` (I do want to make `FromSql<Array<ST>> for
`Vec<Option<T>>` just work though). Since we never provide a
`ToSql<Array<ST>> for Vec<Option<T>>`, Diesel will prevent you from
accidentally inserting nulls at compile time. Ultimately the user does
just have to make sure they represent whether the array could contain
null in the output type, however.

One alternative would be to represent the fact that all arrays can
contain null within the type system, and only provide `impl
FromSql<Array<ST>> for Vec<Option<T>>`. I suspect this would hinder
ergonomics pretty heavily, but we should consider it before 1.0.

One note about the implementation -- Technically we should be setting
`flags` to `1` if the array contains any nulls, as that's what happens
on the data sent to us. However, setting it would require us to perform
an additional level of intermediate buffering on all the values to
determine whether the flag should be set, and that flag is currently
ignored by PG. "But Sean, that's just relying on an implementation
detail!" this is true, but our entire usage of the binary format is
relying on an implementation detail (See #695).
This commit is contained in:
Sean Griffin 2017-02-13 09:41:37 -05:00
parent ea6c55c51a
commit a571e417e6
3 changed files with 14 additions and 5 deletions

View File

@ -14,6 +14,10 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
[select-0.11.0]: http://docs.diesel.rs/diesel/fn.select.html
[boxed-0.11.0]: http://docs.diesel.rs/diesel/prelude/trait.BoxedDsl.html
* Arrays containing null are now supported. `infer_schema!` will never infer an
array that contains null, but a `table!` definition which specifies a type of
`Array<Nullable<X>>` can now be deserialized to `Vec<Option<T>>`
### Changed
* It is no longer possible to exhaustively match against

View File

@ -134,10 +134,14 @@ impl<'a, ST, T> ToSql<Array<ST>, Pg> for &'a [T] where
let mut buffer = Vec::new();
for elem in self.iter() {
let is_null = try!(elem.to_sql(&mut buffer));
assert!(is_null == IsNull::No, "Arrays containing null are not supported");
try!(out.write_i32::<NetworkEndian>(buffer.len() as i32));
try!(out.write_all(&buffer));
buffer.clear();
if let IsNull::No = is_null {
try!(out.write_i32::<NetworkEndian>(buffer.len() as i32));
try!(out.write_all(&buffer));
buffer.clear();
} else {
// https://github.com/postgres/postgres/blob/82f8107b92c9104ec9d9465f3f6a4c6dab4c124a/src/backend/utils/adt/arrayfuncs.c#L1461
try!(out.write_i32::<NetworkEndian>(-1));
}
}
Ok(IsNull::No)

View File

@ -102,6 +102,7 @@ mod pg_types {
test_round_trip!(array_of_int_roundtrips, Array<Integer>, Vec<i32>);
test_round_trip!(array_of_bigint_roundtrips, Array<BigInt>, Vec<i64>);
test_round_trip!(array_of_dynamic_size_roundtrips, Array<Text>, Vec<String>);
test_round_trip!(array_of_nullable_roundtrips, Array<Nullable<Text>>, Vec<Option<String>>);
fn mk_uuid(data: (u32, u16, u16, (u8, u8, u8, u8, u8, u8, u8, u8))) -> self::uuid::Uuid {
let a = data.3;
@ -131,7 +132,7 @@ pub fn mk_naive_date(days: u32) -> NaiveDate {
#[cfg(feature = "postgres")]
mod unstable_types {
use super::{quickcheck, types, test_type_round_trips};
use super::{quickcheck, test_type_round_trips};
use std::time::*;
fn strip_nanosecond_precision(time: SystemTime) -> SystemTime {