Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
jamwaffles opened this issue May 5, 2020 · 12 comments
Open

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

jamwaffles opened this issue May 5, 2020 · 12 comments
Labels

Comments

@jamwaffles
Copy link
Contributor

jamwaffles commented May 5, 2020

Bit of a word soup of a title, sorry 馃槵

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`
@psnszsn
Copy link

psnszsn commented Jun 3, 2020

I'm having the same issue.

@janaakhterov
Copy link
Contributor

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

@jamwaffles
Copy link
Contributor Author

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!

@izik1
Copy link
Contributor

izik1 commented Jul 30, 2020

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 izik1 added the blocked label Jul 30, 2020
@robo-corg
Copy link

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

@jplatte
Copy link
Contributor

jplatte commented Aug 30, 2021

I see the workaround (that will hopefully soon not be needed anymore) isn't actually mentioned here (from #1170 (comment)):

#[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 _,
)

@apiraino
Copy link

@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(); // ?

@jplatte
Copy link
Contributor

jplatte commented Aug 31, 2021

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).

connec pushed a commit to connec/rust-graphql-sample that referenced this issue Dec 11, 2021
This uses `sqlx` to read/write humans from/to the sample-db.

The schema consists of a custom enum type for episodes, and a table for
humans. For now, the same structs as were used for the GraphQL schema
are being used to (de)serialize database values. This would have been
quite smooth, but sadly, `sqlx`'s derivation can't handle `Vec`s of
custom enums (launchbadge/sqlx#298), so we
have to jump through some hoops by introducing `EpisodeSlice` and
`EpisodeVec` newtypes, which can then implement the required traits
(plus all the required juniper traits, which is the bigger pain).

Since we're using `sqlx`'s macros to check queries at compile time, we
need to connect to the database during compilation. The macro will use
the `DATABASE_URL` environment variable, which it can read from a `.env`
file, so we now write one of these files as part of `make prepare-db`
(note that this is for the benefit of editors, language servers, etc.,
`make run` would already inherit the `DATABASE_URL` when compiling).
@rex-remind101
Copy link

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

@jplatte
Copy link
Contributor

jplatte commented Jul 1, 2022

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.

@Matthtica
Copy link

Matthtica commented Dec 5, 2023

#[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.

@gborough
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

10 participants