Skip to content

Commit 6c1542f

Browse files
Add FromSql and ToSql impls for arrays (guarded behind feature)
This is feature-gated because those impls require Rust 1.51.
1 parent 9ead59e commit 6c1542f

File tree

5 files changed

+79
-5
lines changed

5 files changed

+79
-5
lines changed

postgres-types/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ categories = ["database"]
1212

1313
[features]
1414
derive = ["postgres-derive"]
15+
array-impls = ["array-init"]
1516
with-bit-vec-0_6 = ["bit-vec-06"]
1617
with-chrono-0_4 = ["chrono-04"]
1718
with-eui48-0_4 = ["eui48-04"]
@@ -28,6 +29,7 @@ fallible-iterator = "0.2"
2829
postgres-protocol = { version = "0.6.1", path = "../postgres-protocol" }
2930
postgres-derive = { version = "0.4.0", optional = true, path = "../postgres-derive" }
3031

32+
array-init = { version = "2", optional = true }
3133
bit-vec-06 = { version = "0.6", package = "bit-vec", optional = true }
3234
chrono-04 = { version = "0.4.16", package = "chrono", default-features = false, features = ["clock"], optional = true }
3335
eui48-04 = { version = "0.4", package = "eui48", optional = true }

postgres-types/src/lib.rs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -428,8 +428,10 @@ impl WrongType {
428428
///
429429
/// # Arrays
430430
///
431-
/// `FromSql` is implemented for `Vec<T>` where `T` implements `FromSql`, and
432-
/// corresponds to one-dimensional Postgres arrays.
431+
/// `FromSql` is implemented for `Vec<T>` and `[T; N]` where `T` implements
432+
/// `FromSql`, and corresponds to one-dimensional Postgres arrays. **Note:**
433+
/// the impl for arrays only exist when the Cargo feature `array-impls` is
434+
/// enabled.
433435
pub trait FromSql<'a>: Sized {
434436
/// Creates a new value of this type from a buffer of data of the specified
435437
/// Postgres `Type` in its binary format.
@@ -513,6 +515,47 @@ impl<'a, T: FromSql<'a>> FromSql<'a> for Vec<T> {
513515
}
514516
}
515517

518+
#[cfg(feature = "array-impls")]
519+
impl<'a, T: FromSql<'a>, const N: usize> FromSql<'a> for [T; N] {
520+
fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
521+
let member_type = match *ty.kind() {
522+
Kind::Array(ref member) => member,
523+
_ => panic!("expected array type"),
524+
};
525+
526+
let array = types::array_from_sql(raw)?;
527+
if array.dimensions().count()? > 1 {
528+
return Err("array contains too many dimensions".into());
529+
}
530+
531+
let mut values = array.values();
532+
let out = array_init::try_array_init(|i| {
533+
let v = values
534+
.next()?
535+
.ok_or_else(|| -> Box<dyn Error + Sync + Send> {
536+
format!("too few elements in array (expected {}, got {})", N, i).into()
537+
})?;
538+
T::from_sql_nullable(member_type, v)
539+
})?;
540+
if values.next()?.is_some() {
541+
return Err(format!(
542+
"excess elements in array (expected {}, got more than that)",
543+
N,
544+
)
545+
.into());
546+
}
547+
548+
Ok(out)
549+
}
550+
551+
fn accepts(ty: &Type) -> bool {
552+
match *ty.kind() {
553+
Kind::Array(ref inner) => T::accepts(inner),
554+
_ => false,
555+
}
556+
}
557+
}
558+
516559
impl<'a> FromSql<'a> for Vec<u8> {
517560
fn from_sql(_: &Type, raw: &'a [u8]) -> Result<Vec<u8>, Box<dyn Error + Sync + Send>> {
518561
Ok(types::bytea_from_sql(raw).to_owned())
@@ -691,8 +734,10 @@ pub enum IsNull {
691734
///
692735
/// # Arrays
693736
///
694-
/// `ToSql` is implemented for `Vec<T>` and `&[T]` where `T` implements `ToSql`,
695-
/// and corresponds to one-dimensional Postgres arrays with an index offset of 1.
737+
/// `ToSql` is implemented for `Vec<T>`, `&[T]` and `[T; N]` where `T`
738+
/// implements `ToSql`, and corresponds to one-dimensional Postgres arrays with
739+
/// an index offset of 1. **Note:** the impl for arrays only exist when the
740+
/// Cargo feature `array-impls` is enabled.
696741
pub trait ToSql: fmt::Debug {
697742
/// Converts the value of `self` into the binary format of the specified
698743
/// Postgres `Type`, appending it to `out`.
@@ -808,6 +853,19 @@ impl<'a> ToSql for &'a [u8] {
808853
to_sql_checked!();
809854
}
810855

856+
#[cfg(feature = "array-impls")]
857+
impl<T: ToSql, const N: usize> ToSql for [T; N] {
858+
fn to_sql(&self, ty: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
859+
<&[T] as ToSql>::to_sql(&&self[..], ty, w)
860+
}
861+
862+
fn accepts(ty: &Type) -> bool {
863+
<&[T] as ToSql>::accepts(ty)
864+
}
865+
866+
to_sql_checked!();
867+
}
868+
811869
impl<T: ToSql> ToSql for Vec<T> {
812870
fn to_sql(&self, ty: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
813871
<&[T] as ToSql>::to_sql(&&**self, ty, w)

postgres/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ all-features = true
2121
circle-ci = { repository = "sfackler/rust-postgres" }
2222

2323
[features]
24+
array-impls = ["tokio-postgres/array-impls"]
2425
with-bit-vec-0_6 = ["tokio-postgres/with-bit-vec-0_6"]
2526
with-chrono-0_4 = ["tokio-postgres/with-chrono-0_4"]
2627
with-eui48-0_4 = ["tokio-postgres/with-eui48-0_4"]

tokio-postgres/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ circle-ci = { repository = "sfackler/rust-postgres" }
2727
default = ["runtime"]
2828
runtime = ["tokio/net", "tokio/time"]
2929

30+
array-impls = ["postgres-types/array-impls"]
3031
with-bit-vec-0_6 = ["postgres-types/with-bit-vec-0_6"]
3132
with-chrono-0_4 = ["postgres-types/with-chrono-0_4"]
3233
with-eui48-0_4 = ["postgres-types/with-eui48-0_4"]

tokio-postgres/tests/test/types/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ async fn test_hstore_params() {
350350
}
351351

352352
#[tokio::test]
353-
async fn test_array_params() {
353+
async fn test_array_vec_params() {
354354
test_type(
355355
"integer[]",
356356
&[
@@ -363,6 +363,18 @@ async fn test_array_params() {
363363
.await;
364364
}
365365

366+
#[cfg(feature = "array-impls")]
367+
#[tokio::test]
368+
async fn test_array_array_params() {
369+
test_type("integer[]", &[(Some([1i32, 2i32]), "ARRAY[1,2]")]).await;
370+
test_type("text[]", &[(Some(["peter".to_string()]), "ARRAY['peter']")]).await;
371+
test_type(
372+
"integer[]",
373+
&[(Some([] as [i32; 0]), "ARRAY[]"), (None, "NULL")],
374+
)
375+
.await;
376+
}
377+
366378
#[allow(clippy::eq_op)]
367379
async fn test_nan_param<T>(sql_type: &str)
368380
where

0 commit comments

Comments
 (0)