diesel-rs / diesel

A safe, extensible ORM and Query Builder for Rust
https://diesel.rs
Apache License 2.0
12.55k stars 1.05k forks source link

`belonging_to` expected `()`, found `&uuid::Uuid` #2460

Open Ran4 opened 4 years ago

Ran4 commented 4 years ago

Setup

Versions

Feature Flags

Problem Description

I have a setup that looks like this:

#[derive(Serialize, Deserialize, Insertable, Identifiable, Queryable, PartialEq, Debug)]
#[table_name = "api_keys"]
#[primary_key("uuid")]
pub struct ApiKey {
    pub uuid: Uuid,
}

#[derive(Serialize, Insertable, Queryable, Identifiable, Associations, PartialEq, Debug)]
#[table_name = "rights"]
pub struct Right {
    id: i32,
    name: String,
    description: Option<String>,
}

#[derive(Serialize, Queryable, Identifiable, Associations, PartialEq, Debug)]
#[belongs_to(ApiKey, foreign_key = "api_key_uuid")]
#[belongs_to(Right, foreign_key = "rights_id")]
#[table_name = "api_key_rights"]
pub struct ApiKeyRight {
    id: i32,
    api_key_uuid: Uuid,
    rights_id: i32,
}

Schema.rs:

table! {
    api_key_rights (id) {
        id -> Int4,
        api_key_uuid -> Nullable<Uuid>,
        rights_id -> Int4,
    }
}

table! {
    api_keys (uuid) {
        uuid -> Uuid,
    }
}

table! {
    rights (id) {
        id -> Int4,
        name -> Varchar,
        description -> Nullable<Text>,
    }
}

joinable!(api_key_rights -> api_keys (api_key_uuid));
joinable!(api_key_rights -> rights (rights_id));

allow_tables_to_appear_in_same_query!(
    api_key_rights,
    api_keys,
    rights,
);

I'm trying to get all ApiKeyRight:s that belong to ApiKey:

fn not_working() {
    use diesel::prelude::*;
    use uuid::Uuid;
    let api_key: ApiKey = ApiKey { uuid: Uuid::new_v4() };

    // This fails, see error message below
    let _ = ApiKeyRight::belonging_to(&api_key);
}

Error message:

error[E0271]: type mismatch resolving `for<'__a> <&'__a rustapi::models::ApiKey as rocket_contrib::databases::diesel::Identifiable>::Id == &'__a uuid::Uuid`
  --> src/api/mod.rs:67:17
   |
67 |         let _ = ApiKeyRight::belonging_to(&api_key);
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `&uuid::Uuid`
   |
   = note: required because of the requirements on the impl of `rocket_contrib::databases::diesel::associations::BelongsTo<rustapi::models::ApiKey>` for `rustapi::models::ApiKeyRight`
   = note: required because of the requirements on the impl of `rocket_contrib::databases::diesel::BelongingToDsl<&rustapi::models::ApiKey>` for `rustapi::models::ApiKeyRight`

error[E0277]: the trait bound `(): rocket_contrib::databases::diesel::Expression` is not satisfied
  --> src/api/mod.rs:67:17
   |
67 |         let _ = ApiKeyRight::belonging_to(&api_key);
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `rocket_contrib::databases::diesel::Expression` is not implemented for `()`
   |
   = note: required because of the requirements on the impl of `rocket_contrib::databases::diesel::expression::AsExpression<rocket_contrib::databases::diesel::sql_types::Nullable<rocket_contrib::databases::diesel::sql_types::Uuid>>` for `()`
   = note: required because of the requirements on the impl of `rocket_contrib::databases::diesel::BelongingToDsl<&rustapi::models::ApiKey>` for `rustapi::models::ApiKeyRight`

error[E0277]: the trait bound `rocket_contrib::databases::diesel::query_builder::SelectStatement<rustapi::schema::api_key_rights::table>: rocket_contrib::databases::diesel::query_dsl::filter_dsl::FilterDsl<rocket_contrib::databases::diesel::expression::operators::Eq<rustapi::schema::api_key_rights::columns::api_key_uuid, ()>>` is not satisfied
  --> src/api/mod.rs:67:17
   |
67 |         let _ = ApiKeyRight::belonging_to(&api_key);
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `rocket_contrib::databases::diesel::query_dsl::filter_dsl::FilterDsl<rocket_contrib::databases::diesel::expression::operators::Eq<rustapi::schema::api_key_rights::columns::api_key_uuid, ()>>` is not implemented for `rocket_contrib::databases::diesel::query_builder::SelectStatement<rustapi::schema::api_key_rights::table>`
   |
   = help: the following implementations were found:
             <rocket_contrib::databases::diesel::query_builder::SelectStatement<F, S, D, W, O, L, Of, G, LC> as rocket_contrib::databases::diesel::query_dsl::filter_dsl::FilterDsl<Predicate>>
   = note: required because of the requirements on the impl of `rocket_contrib::databases::diesel::query_dsl::filter_dsl::FilterDsl<rocket_contrib::databases::diesel::expression::operators::Eq<rustapi::schema::api_key_rights::columns::api_key_uuid, ()>>` for `rustapi::schema::api_key_rights::table`

error: aborting due to 3 previous errors

What is going on here? ApiKeyRight should be using Uuid to compare with ApiKey, right? Where does it expect ()?

The latter errors comes from trying to Eq between schema::api_key_rights::columns::api_key_uuid and (), where it should be between schema::api_key_rights::columns::api_key_uuid and schema::api_keys::columns::uuid

I tried doing the ...as BelongingToDsl trick:

let _ = <ApiKeyRight as BelongingToDsl<&ApiKey>>::belonging_to(&api_key);

but it gives me the same type of errors (expected (), found &uuid::Uuid.

weiznich commented 4 years ago

It needs to be

#[derive(Serialize, Deserialize, Insertable, Identifiable, Queryable, PartialEq, Debug)]
#[table_name = "api_keys"]
#[primary_key(uuid)]
pub struct ApiKey {
    pub uuid: Uuid,
}

That said: I'm not sure why the corresponding custom derive even accepted the malformed primary key attribute.

Ran4 commented 4 years ago

Thanks, that solved the issue. Is there any examples of how you would do a transient belonging_to?

E.g. I have an ApiKey, I want all Rights belonging to the ApiKey. I can't do Right::belonging_to(&api_key) because the belongs_to is on ApiKeyRight, not Right, and I'm not sure how I would specify a belongs_to on Right that combines both ApiKey and ApiKeyRight?

weiznich commented 4 years ago

For m:n relations like this one I normally just use a join for the second relation, so something like ApiKeyRight::belonging_to(&api_key).inner_join(rights::table)

Ran4 commented 4 years ago

For m:n relations like this one I normally just use a join for the second relation, so something like ApiKeyRight::belonging_to(&api_key).inner_join(rights::table)

Wouldn't you have to turn that into a Vec<(ApiKeyRight, Right)> instead of just a Vec<Right>? Or is there a way to "collect" into just a Vec<Right> even if you've joined with another table? I guess I could just pick out a Vec of fields, but then I would have to manually construct the output which would be kind of annoying.

Is there any reason to even use belonging_to instead of just inner joining all three tables on the two id's?

Just trying to figure out the best practise for this type of somewhat common operation :)

weiznich commented 4 years ago

Wouldn't you have to turn that into a Vec<(ApiKeyRight, Right)> instead of just a Vec? Or is there a way to "collect" into just a Vec even if you've joined with another table? I guess I could just pick out a Vec of fields, but then I would have to manually construct the output which would be kind of annoying.

Correct, the query that I've posted above would yield Vec<(ApiKeyRight, Right)>. To only get Vec<Right> simply add a select clause like .select(rights::all_columns).

Is there any reason to even use belonging_to instead of just inner joining all three tables on the two id's?

That really depends on what you want to do with the data afterwards. If you try to load for example all rights for all api keys, using the BelongingTo API is probably a good idea. If you just want the right for a specific key I would probably just go for the raw join variant (and not even brother with including api_key::table in that query.)