Merge pull request #806 from LukasKalbertodt/add-from-to-sql-arrays

Add `FromSql` and `ToSql` impls for arrays (guarded behind feature)
This commit is contained in:
Steven Fackler 2021-07-13 14:13:01 -04:00 committed by GitHub
commit 8e2e90a161
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 6 deletions

View File

@ -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

View File

@ -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 }

View File

@ -428,8 +428,10 @@ impl WrongType {
///
/// # Arrays
///
/// `FromSql` is implemented for `Vec<T>` where `T` implements `FromSql`, and
/// corresponds to one-dimensional Postgres arrays.
/// `FromSql` is implemented for `Vec<T>` 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<T> {
}
}
#[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<Self, Box<dyn Error + Sync + Send>> {
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<dyn Error + Sync + Send> {
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<u8> {
fn from_sql(_: &Type, raw: &'a [u8]) -> Result<Vec<u8>, Box<dyn Error + Sync + Send>> {
Ok(types::bytea_from_sql(raw).to_owned())
@ -691,8 +734,10 @@ pub enum IsNull {
///
/// # Arrays
///
/// `ToSql` is implemented for `Vec<T>` 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>`, `&[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<T: ToSql, const N: usize> ToSql for [T; N] {
fn to_sql(&self, ty: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
<&[T] as ToSql>::to_sql(&&self[..], ty, w)
}
fn accepts(ty: &Type) -> bool {
<&[T] as ToSql>::accepts(ty)
}
to_sql_checked!();
}
impl<T: ToSql> ToSql for Vec<T> {
fn to_sql(&self, ty: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
<&[T] as ToSql>::to_sql(&&**self, ty, w)

View File

@ -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"]

View File

@ -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"]

View File

@ -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<T>(sql_type: &str)
where