From 6c1542f634ae0d7733811024cd63835045f75784 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Tue, 13 Jul 2021 11:21:11 +0200 Subject: [PATCH 1/2] Add `FromSql` and `ToSql` impls for arrays (guarded behind feature) This is feature-gated because those impls require Rust 1.51. --- postgres-types/Cargo.toml | 2 + postgres-types/src/lib.rs | 66 ++++++++++++++++++++++++-- postgres/Cargo.toml | 1 + tokio-postgres/Cargo.toml | 1 + tokio-postgres/tests/test/types/mod.rs | 14 +++++- 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/postgres-types/Cargo.toml b/postgres-types/Cargo.toml index b258cee1..4fd69f61 100644 --- a/postgres-types/Cargo.toml +++ b/postgres-types/Cargo.toml @@ -12,6 +12,7 @@ categories = ["database"] [features] derive = ["postgres-derive"] +array-impls = ["array-init"] with-bit-vec-0_6 = ["bit-vec-06"] with-chrono-0_4 = ["chrono-04"] with-eui48-0_4 = ["eui48-04"] @@ -28,6 +29,7 @@ fallible-iterator = "0.2" postgres-protocol = { version = "0.6.1", path = "../postgres-protocol" } postgres-derive = { version = "0.4.0", optional = true, path = "../postgres-derive" } +array-init = { version = "2", optional = true } bit-vec-06 = { version = "0.6", package = "bit-vec", optional = true } chrono-04 = { version = "0.4.16", package = "chrono", default-features = false, features = ["clock"], optional = true } eui48-04 = { version = "0.4", package = "eui48", optional = true } diff --git a/postgres-types/src/lib.rs b/postgres-types/src/lib.rs index 1973f3d0..4c559b95 100644 --- a/postgres-types/src/lib.rs +++ b/postgres-types/src/lib.rs @@ -428,8 +428,10 @@ impl WrongType { /// /// # Arrays /// -/// `FromSql` is implemented for `Vec` where `T` implements `FromSql`, and -/// corresponds to one-dimensional Postgres arrays. +/// `FromSql` is implemented for `Vec` and `[T; N]` where `T` implements +/// `FromSql`, and corresponds to one-dimensional Postgres arrays. **Note:** +/// the impl for arrays only exist when the Cargo feature `array-impls` is +/// enabled. pub trait FromSql<'a>: Sized { /// Creates a new value of this type from a buffer of data of the specified /// Postgres `Type` in its binary format. @@ -513,6 +515,47 @@ impl<'a, T: FromSql<'a>> FromSql<'a> for Vec { } } +#[cfg(feature = "array-impls")] +impl<'a, T: FromSql<'a>, const N: usize> FromSql<'a> for [T; N] { + fn from_sql(ty: &Type, raw: &'a [u8]) -> Result> { + let member_type = match *ty.kind() { + Kind::Array(ref member) => member, + _ => panic!("expected array type"), + }; + + let array = types::array_from_sql(raw)?; + if array.dimensions().count()? > 1 { + return Err("array contains too many dimensions".into()); + } + + let mut values = array.values(); + let out = array_init::try_array_init(|i| { + let v = values + .next()? + .ok_or_else(|| -> Box { + format!("too few elements in array (expected {}, got {})", N, i).into() + })?; + T::from_sql_nullable(member_type, v) + })?; + if values.next()?.is_some() { + return Err(format!( + "excess elements in array (expected {}, got more than that)", + N, + ) + .into()); + } + + Ok(out) + } + + fn accepts(ty: &Type) -> bool { + match *ty.kind() { + Kind::Array(ref inner) => T::accepts(inner), + _ => false, + } + } +} + impl<'a> FromSql<'a> for Vec { fn from_sql(_: &Type, raw: &'a [u8]) -> Result, Box> { Ok(types::bytea_from_sql(raw).to_owned()) @@ -691,8 +734,10 @@ pub enum IsNull { /// /// # Arrays /// -/// `ToSql` is implemented for `Vec` and `&[T]` where `T` implements `ToSql`, -/// and corresponds to one-dimensional Postgres arrays with an index offset of 1. +/// `ToSql` is implemented for `Vec`, `&[T]` and `[T; N]` where `T` +/// implements `ToSql`, and corresponds to one-dimensional Postgres arrays with +/// an index offset of 1. **Note:** the impl for arrays only exist when the +/// Cargo feature `array-impls` is enabled. pub trait ToSql: fmt::Debug { /// Converts the value of `self` into the binary format of the specified /// Postgres `Type`, appending it to `out`. @@ -808,6 +853,19 @@ impl<'a> ToSql for &'a [u8] { to_sql_checked!(); } +#[cfg(feature = "array-impls")] +impl ToSql for [T; N] { + fn to_sql(&self, ty: &Type, w: &mut BytesMut) -> Result> { + <&[T] as ToSql>::to_sql(&&self[..], ty, w) + } + + fn accepts(ty: &Type) -> bool { + <&[T] as ToSql>::accepts(ty) + } + + to_sql_checked!(); +} + impl ToSql for Vec { fn to_sql(&self, ty: &Type, w: &mut BytesMut) -> Result> { <&[T] as ToSql>::to_sql(&&**self, ty, w) diff --git a/postgres/Cargo.toml b/postgres/Cargo.toml index c7c0746f..ca1d0b23 100644 --- a/postgres/Cargo.toml +++ b/postgres/Cargo.toml @@ -21,6 +21,7 @@ all-features = true circle-ci = { repository = "sfackler/rust-postgres" } [features] +array-impls = ["tokio-postgres/array-impls"] with-bit-vec-0_6 = ["tokio-postgres/with-bit-vec-0_6"] with-chrono-0_4 = ["tokio-postgres/with-chrono-0_4"] with-eui48-0_4 = ["tokio-postgres/with-eui48-0_4"] diff --git a/tokio-postgres/Cargo.toml b/tokio-postgres/Cargo.toml index 3a1537a8..edffde49 100644 --- a/tokio-postgres/Cargo.toml +++ b/tokio-postgres/Cargo.toml @@ -27,6 +27,7 @@ circle-ci = { repository = "sfackler/rust-postgres" } default = ["runtime"] runtime = ["tokio/net", "tokio/time"] +array-impls = ["postgres-types/array-impls"] with-bit-vec-0_6 = ["postgres-types/with-bit-vec-0_6"] with-chrono-0_4 = ["postgres-types/with-chrono-0_4"] with-eui48-0_4 = ["postgres-types/with-eui48-0_4"] diff --git a/tokio-postgres/tests/test/types/mod.rs b/tokio-postgres/tests/test/types/mod.rs index 85eed0e2..54a111b3 100644 --- a/tokio-postgres/tests/test/types/mod.rs +++ b/tokio-postgres/tests/test/types/mod.rs @@ -350,7 +350,7 @@ async fn test_hstore_params() { } #[tokio::test] -async fn test_array_params() { +async fn test_array_vec_params() { test_type( "integer[]", &[ @@ -363,6 +363,18 @@ async fn test_array_params() { .await; } +#[cfg(feature = "array-impls")] +#[tokio::test] +async fn test_array_array_params() { + test_type("integer[]", &[(Some([1i32, 2i32]), "ARRAY[1,2]")]).await; + test_type("text[]", &[(Some(["peter".to_string()]), "ARRAY['peter']")]).await; + test_type( + "integer[]", + &[(Some([] as [i32; 0]), "ARRAY[]"), (None, "NULL")], + ) + .await; +} + #[allow(clippy::eq_op)] async fn test_nan_param(sql_type: &str) where From 06952e2bb09bdcc404b82f24774c5ada756a360f Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Tue, 13 Jul 2021 14:38:11 +0200 Subject: [PATCH 2/2] Use Rust 1.51 in CI We needed to bump the version because the `array-impls` feature requires const generics. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a95dbe0..8b3a3420 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: - uses: actions/checkout@v2 - uses: sfackler/actions/rustup@master with: - version: 1.46.0 + version: 1.51.0 - run: echo "::set-output name=version::$(rustc --version)" id: rust-version - uses: actions/cache@v1