launchbadge / sqlx

🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite.
Apache License 2.0
13.56k stars 1.29k forks source link

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

Open jamwaffles opened 4 years ago

jamwaffles commented 4 years ago

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`
psnszsn commented 4 years ago

I'm having the same issue.

janaakhterov commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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)

robo-corg commented 4 years ago

@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 commented 3 years ago

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 _,
)
apiraino commented 3 years ago

@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 commented 3 years ago

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

rex-remind101 commented 2 years ago

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

jplatte commented 2 years ago

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 commented 12 months ago
#[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 commented 11 months ago

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