sqlx icon indicating copy to clipboard operation
sqlx copied to clipboard

[Postgres] Unable to use #[derive]d custom types with Vec<T>

Open jamwaffles opened this issue 4 years ago • 10 comments

Bit of a word soup of a title, sorry :grimacing:

I'm trying to deserialize a row into a struct that holds a Vec<Item>, where Item is an enum that derives sqlx::Type. This fails to decode, stating some trait bounds aren't satisfied. Is this a not-yet-implement feature, or am I doing something wrong/missing something in my code?

Code:

use sqlx::postgres::PgQueryAs;

#[derive(
    serde_derive::Deserialize, serde_derive::Serialize, PartialEq, Debug, Eq, Hash, sqlx::Type, Copy, Clone
)]
#[sqlx(rename = "varchar")]
enum Item {
    Foo,
    Bar,
}

#[derive(
    serde_derive::Deserialize, serde_derive::Serialize, sqlx::FromRow, Hash, PartialEq, Eq, Debug, Clone
)]
struct Things {
    items: Vec<Item>,
}

async fn query_things(pool: &sqlx::PgPool) -> Result<Vec<Things>, sqlx::Error> {
    sqlx::query_as("select * from things")
        .fetch_all(&mut pool)
        .await
}

The pile of derives is from our real code. Could any of them be causing issues?

Error:

error[E0277]: the trait bound `std::vec::Vec<Item>: sqlx_core::types::Type<sqlx_core::postgres::database::Postgres>` is not satisfied
   --> src/lib.rs:250:10
    |
250 |         .fetch_all(&mut pool)
    |          ^^^^^^^^^ the trait `sqlx_core::types::Type<sqlx_core::postgres::database::Postgres>` is not implemented for `std::vec::Vec<Item>`
    |
    = help: the following implementations were found:
              <std::vec::Vec<&[u8]> as sqlx_core::types::Type<sqlx_core::postgres::database::Postgres>>
              <std::vec::Vec<&str> as sqlx_core::types::Type<sqlx_core::postgres::database::Postgres>>
              <std::vec::Vec<(T1, T2)> as sqlx_core::types::Type<sqlx_core::postgres::database::Postgres>>
              <std::vec::Vec<(T1, T2, T3)> as sqlx_core::types::Type<sqlx_core::postgres::database::Postgres>>
            and 23 others
    = note: required because of the requirements on the impl of `for<'c> sqlx_core::row::FromRow<'c, sqlx_core::postgres::row::PgRow<'c>>` for `Things`

error[E0277]: the trait bound `[Item]: sqlx_core::types::Type<sqlx_core::postgres::database::Postgres>` is not satisfied
   --> src/lib.rs:250:10
    |
250 |         .fetch_all(&mut pool)
    |          ^^^^^^^^^ the trait `sqlx_core::types::Type<sqlx_core::postgres::database::Postgres>` is not implemented for `[Item]`
    |
    = help: the following implementations were found:
              <[&[u8]] as sqlx_core::types::Type<sqlx_core::postgres::database::Postgres>>
              <[&str] as sqlx_core::types::Type<sqlx_core::postgres::database::Postgres>>
              <[(T1, T2)] as sqlx_core::types::Type<sqlx_core::postgres::database::Postgres>>
              <[(T1, T2, T3)] as sqlx_core::types::Type<sqlx_core::postgres::database::Postgres>>
            and 23 others
    = note: required because of the requirements on the impl of `sqlx_core::decode::Decode<'_, sqlx_core::postgres::database::Postgres>` for `std::vec::Vec<Item>`
    = note: required because of the requirements on the impl of `for<'c> sqlx_core::row::FromRow<'c, sqlx_core::postgres::row::PgRow<'c>>` for `Things`

jamwaffles avatar May 05 '20 15:05 jamwaffles

I'm having the same issue.

psnszsn avatar Jun 03 '20 20:06 psnszsn

@jamwaffles I might be misunderstanding, but is your table things a single column of type item[]? From what I can tell it has to be for this to work. If you're your table instead looks like create table things (item Item) then you shouldn't be returning a Vec<Things>, but Vec<Item>.

janaakhterov avatar Jun 11 '20 22:06 janaakhterov

Sorry for not being clearer in the OP. My table looks like this:

create table my_table (
    id uuid primary_key,
    roles varchar array,
    ...
);

With the matching Rust:

#[derive(
    serde_derive::Deserialize, serde_derive::Serialize, PartialEq, Debug, Eq, Hash, sqlx::Type, Copy, Clone
)]
#[sqlx(rename = "varchar")]
enum Item {
    Foo,
    Bar,
}


#[derive(
    serde_derive::Deserialize, serde_derive::Serialize, sqlx::FromRow, Hash, PartialEq, Eq, Debug, Clone
)]
struct MyThing {
    id: Uuid,

    roles: Vec<Item>,

    // ...
}

Hope that makes things clearer!

jamwaffles avatar Jun 12 '20 09:06 jamwaffles

Hi! I looked into this as it turns out it would useful for something I need. Unfortunately, it's blocked right now. The impl itself is fairly simple:

impl<T> Type<Postgres> for Vec<T>
where
    T: Type<Postgres>,
{
    #[inline]
    fn type_info() -> PgTypeInfo {
        <T as Type<Postgres>>::type_info()
    }
}

But this dies because lazy normalization doesn't exist yet, (it tries to recursively resolve the trait forever)

izik1 avatar Jul 30 '20 23:07 izik1

@izik1 wouldn't your proposed implementation return type info for T not [T]? I think I have a work around using a newtype but the problem remains that I need a type_info for an array of T.

Using just T, for a custom type called job_stage I get:

thread 'subsystems::storage::sql::tests::test_sql_create_workflow_with_empty_dag_creates_dag' panicked at 'called `Result::unwrap()` on an `Err` value: cannot cast type job_stage to job_stage[]

In 0.3.5 stable.

robo-corg avatar Sep 01 '20 16:09 robo-corg

I see the workaround (that will hopefully soon not be needed anymore) isn't actually mentioned here (from https://github.com/launchbadge/sqlx/pull/1170#issuecomment-817738085):

#[derive(sqlx::Encode)]
struct Foos<'a>(&'a [Foo]);

impl sqlx::Type<sqlx::Postgres> for Foos<'_> {
    fn type_info() -> PgTypeInfo {
        PgTypeInfo::with_name("_foo")
    }
}

query_as!(
    Whatever,
    "<QUERY with $1 of type foo[]>",
    Foos(&foo_vec) as _,
)

jplatte avatar Aug 30 '21 16:08 jplatte

@jplatte thanks for reporting here that comment. Would it also work when trying to implement sqlx::Type on a struct? I'm trying to figure out this case and I can't map the context from your example to the following (pseudo)code:

#[derive(sqlx::Type)] // ?
struct Pet {
    name: String,
    age: u32
}

#[derive(sqlx::Type)]
struct Person  {
    name: String,
    pets: Vec<Pet>
}

let _ : Vec<Person> = query_as(
    "SELECT name,pets from persons"
).fetch_all(&db_pool).await.unwrap(); // ?

apiraino avatar Aug 31 '21 08:08 apiraino

I would assume that also works if you change the type of the pets field to such a wrapper type (can wrap Vec<Something> too, doesn't have to be a slice).

jplatte avatar Aug 31 '21 09:08 jplatte

That trick doesn't seem to work for Decode for me though. Any workaround there?

rex-remind101 avatar Jul 01 '22 00:07 rex-remind101

That trick is no longer needed, #1385 fixed it. And Decode can't possibly work for references, maybe that's what you're hitting? Hard to tell w/o more details.

jplatte avatar Jul 01 '22 07:07 jplatte

#[derive(Serialize, Deserialize)]
pub struct CartItem {
    pub id: i32,
    pub quantity: i32,
    pub price: i32
}

#[derive(sqlx::Type, Serialize, Deserialize)]
pub struct CartItems {
    pub items: Vec<CartItem>,
}

#[derive(sqlx::Type, Serialize, Deserialize)]
pub struct Voucher {
    pub id: i32,
    pub voucher_id: String,
    pub customer_name: Option<String>,
    pub customer_contact: Option<String>,
    pub cart_items: CartItems,
    pub time: chrono::NaiveDateTime,
    pub status: bool
}

I can't get it to working.

Matthtica avatar Dec 05 '23 15:12 Matthtica

Still not working today although it is stated in the design doc that multi dimensional data is not supported atm. I ran into trouble with Vec<(String, String)> type and other similar but more complex types, had to resort to serialising to string trick to satisfy the Encode trait

gborough avatar Dec 11 '23 02:12 gborough