diff --git a/sqlx-core/src/types/mod.rs b/sqlx-core/src/types/mod.rs index 2c592e24..3ff6c80f 100644 --- a/sqlx-core/src/types/mod.rs +++ b/sqlx-core/src/types/mod.rs @@ -104,7 +104,7 @@ pub use json::{Json, JsonRawValue, JsonValue}; /// /// ### Transparent /// -/// Rust-only domain or wrappers around SQL types. The generated implementations directly delegate +/// Rust-only domain wrappers around SQL types. The generated implementations directly delegate /// to the implementation of the inner type. /// /// ```rust,ignore @@ -113,6 +113,35 @@ pub use json::{Json, JsonRawValue, JsonValue}; /// struct UserId(i64); /// ``` /// +/// ##### Note: `PgHasArrayType` +/// If you have the `postgres` feature enabled, this derive also generates a `PgHasArrayType` impl +/// so that you may use it with `Vec` and other types that decode from an array in Postgres: +/// +/// ```rust,ignore +/// let user_ids: Vec = sqlx::query_scalar("select '{ 123, 456 }'::int8[]") +/// .fetch(&mut pg_connection) +/// .await?; +/// ``` +/// +/// However, if you are wrapping a type that does not implement `PgHasArrayType` +/// (e.g. `Vec` itself, because we don't currently support multidimensional arrays), +/// you may receive an error: +/// +/// ```rust,ignore +/// #[derive(sqlx::Type)] // ERROR: `Vec` does not implement `PgHasArrayType` +/// #[sqlx(transparent)] +/// struct UserIds(Vec); +/// ``` +/// +/// To remedy this, add `#[sqlx(no_pg_array)]`, which disables the generation +/// of the `PgHasArrayType` impl: +/// +/// ```rust,ignore +/// #[derive(sqlx::Type)] +/// #[sqlx(transparent, no_pg_array)] +/// struct UserIds(Vec); +/// ``` +/// /// ##### Attributes /// /// * `#[sqlx(type_name = "")]` on struct definition: instead of inferring the SQL @@ -121,6 +150,7 @@ pub use json::{Json, JsonRawValue, JsonValue}; /// given type is different than that of the inferred type (e.g. if you rename the above to /// `VARCHAR`). Affects Postgres only. /// * `#[sqlx(rename_all = "")]` on struct definition: See [`derive docs in FromRow`](crate::from_row::FromRow#rename_all) +/// * `#[sqlx(no_pg_array)]`: do not emit a `PgHasArrayType` impl (see above). /// /// ### Enumeration /// diff --git a/sqlx-macros-core/src/derives/attributes.rs b/sqlx-macros-core/src/derives/attributes.rs index 7d32e003..a0f08b1a 100644 --- a/sqlx-macros-core/src/derives/attributes.rs +++ b/sqlx-macros-core/src/derives/attributes.rs @@ -56,6 +56,7 @@ pub struct SqlxContainerAttributes { pub type_name: Option, pub rename_all: Option, pub repr: Option, + pub no_pg_array: bool, } pub struct SqlxChildAttributes { @@ -71,6 +72,7 @@ pub fn parse_container_attributes(input: &[Attribute]) -> syn::Result syn::Result { + try_set!(no_pg_array, true, value); + } + Meta::NameValue(MetaNameValue { path, lit: Lit::Str(val), @@ -148,6 +154,7 @@ pub fn parse_container_attributes(input: &[Attribute]) -> syn::Result syn::Result( input ); + assert_attribute!( + !attributes.no_pg_array, + "unused #[sqlx(no_pg_array)]; derive does not emit `PgHasArrayType` impls for custom structs", + input + ); + assert_attribute!(attributes.repr.is_none(), "unexpected #[repr(..)]", input); for field in fields { diff --git a/sqlx-macros-core/src/derives/type.rs b/sqlx-macros-core/src/derives/type.rs index 2dd1d3fd..a4962ee9 100644 --- a/sqlx-macros-core/src/derives/type.rs +++ b/sqlx-macros-core/src/derives/type.rs @@ -90,7 +90,7 @@ fn expand_derive_has_sql_type_transparent( } ); - if cfg!(feature = "postgres") { + if cfg!(feature = "postgres") && !attr.no_pg_array { tokens.extend(quote!( #[automatically_derived] impl #array_impl_generics ::sqlx::postgres::PgHasArrayType for #ident #ty_generics diff --git a/sqlx-postgres/src/types/array.rs b/sqlx-postgres/src/types/array.rs index 0ae06240..124da53c 100644 --- a/sqlx-postgres/src/types/array.rs +++ b/sqlx-postgres/src/types/array.rs @@ -9,6 +9,44 @@ use crate::types::Oid; use crate::types::Type; use crate::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; +/// Provides information necessary to encode and decode Postgres arrays as compatible Rust types. +/// +/// Implementing this trait for some type `T` enables relevant `Type`,`Encode` and `Decode` impls +/// for `Vec`, `&[T]` (slices), `[T; N]` (arrays), etc. +/// +/// ### Note: `#[derive(sqlx::Type)]` +/// If you have the `postgres` feature enabled, `#[derive(sqlx::Type)]` will also generate +/// an impl of this trait for your type if your wrapper is marked `#[sqlx(transparent)]`: +/// +/// ```rust,ignore +/// #[derive(sqlx::Type)] +/// #[sqlx(transparent)] +/// struct UserId(i64); +/// +/// let user_ids: Vec = sqlx::query_scalar("select '{ 123, 456 }'::int8[]") +/// .fetch(&mut pg_connection) +/// .await?; +/// ``` +/// +/// However, this may cause an error if the type being wrapped does not implement `PgHasArrayType`, +/// e.g. `Vec` itself, because we don't currently support multidimensional arrays: +/// +/// ```rust,ignore +/// #[derive(sqlx::Type)] // ERROR: `Vec` does not implement `PgHasArrayType` +/// #[sqlx(transparent)] +/// struct UserIds(Vec); +/// ``` +/// +/// To remedy this, add `#[sqlx(no_pg_array)]`, which disables the generation +/// of the `PgHasArrayType` impl: +/// +/// ```rust,ignore +/// #[derive(sqlx::Type)] +/// #[sqlx(transparent, no_pg_array)] +/// struct UserIds(Vec); +/// ``` +/// +/// See [the documentation of `Type`][Type] for more details. pub trait PgHasArrayType { fn array_type_info() -> PgTypeInfo; fn array_compatible(ty: &PgTypeInfo) -> bool { diff --git a/tests/postgres/derives.rs b/tests/postgres/derives.rs index 26ab3c9a..c0ce2345 100644 --- a/tests/postgres/derives.rs +++ b/tests/postgres/derives.rs @@ -10,6 +10,13 @@ use std::ops::Bound; #[sqlx(transparent)] struct Transparent(i32); +#[derive(PartialEq, Debug, sqlx::Type)] +// https://github.com/launchbadge/sqlx/issues/2611 +// Previously, the derive would generate a `PgHasArrayType` impl that errored on an +// impossible-to-satisfy `where` bound. This attribute allows the user to opt-out. +#[sqlx(transparent, no_pg_array)] +struct TransparentArray(Vec); + #[sqlx_macros::test] async fn test_transparent_slice_to_array() -> anyhow::Result<()> { let mut conn = new::().await?; @@ -139,6 +146,11 @@ test_type!(transparent(Postgres, "23523" == Transparent(23523) )); +test_type!(transparent_array(Postgres, + "'{}'::int8[]" == TransparentArray(vec![]), + "'{ 23523, 123456, 789 }'::int8[]" == TransparentArray(vec![23523, 123456, 789]) +)); + test_type!(weak_enum(Postgres, "0::int4" == Weak::One, "2::int4" == Weak::Two,