diesel-rs / diesel

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

Error using Identifiable struct with only id field #1013

Closed agersant closed 7 years ago

agersant commented 7 years ago

Setup

Versions

Feature Flags

Problem Description

When gathering key values for a join via belonging_to, it is not possible to store the keys in a struct which holds id and no other fields.

What are you trying to accomplish?

I run a setup very similar to the posts and users example from the docs so I'll use it for illustration.

The following code works fine (it's from the docs):

let user : User = try!(users::find(1).first(&connection));
let posts = Post::belonging_to(&user).load(&connection);

Now let's imagine the User table and its corresponding struct have many more columns. We would like to only select the id column from the users table (either for performance or to avoid exposing a full fledged User struct to a module that doesn't need it).

We can define a local User struct that only has a id field and update our user query accordingly:

#[derive(Identifiable, Queryable)]
pub struct User {
    id: i32,
}

let user : User = try!(users::find(1).select((id)).first(&connection));

The query returns the actual id and not a struct containing the id so we get this error: the traitdiesel::types::FromSqlRow<diesel::types::Integer, diesel::sqlite::Sqlite>is not implemented for(i32,)``.

This seems reasonable so let's change our code to:

let user : i32 = try!(users::find(1).select(id).first(&connection));
let posts = Post::belonging_to(user).load(&connection);

We now receive the following error: the traitdiesel::BelongingToDslis not implemented forPost`. Also fairly reasonable, so let's update ourbelongs_tostatement onPost` accordingly:

#[belongs_to(i32)]
// Note: my real use case also specifies a foreign_key name here but I don't think it's relevant

We now receive: conflicting implementations of traitdiesel::JoinTo<_>for typedb::schema::__diesel_infer_schema::infer_posts::posts::table:

I don't know where to go from here.

Workaround

The only workaround I found is to define a second field on the local User struct and change the user query to .select((id, other_field)). This actually lets everything compile but the Rust compiler correctly emits a warning that other_field is never used and could/should be removed.

Checklist

killercup commented 7 years ago

If you User struct only has the id field, do you even need the .select? Or was this just as an example? IIRC we always enumerate all the columns we want to query, so it's never select *. Thus, another workaround would be to write a SlimUser struct :)

agersant commented 7 years ago

Sorry if I didn't explain this right.

There are two User structs in this story. One of them has many fields and matches the layout of the users table closely. That struct is not exposed to this module and does not appear in the snippets above.

The second User struct (let's call it SlimUser) only has the id field. The issue is that diesel refuses to put the result of a SELECT id FROM users […] into a SlimUser.

sgrif commented 7 years ago

@agersant I think you're just running into a quirky (but intentional) edge case. #[derive(Queryable)] does not special case single-field structs. If you are manually calling select, you'll need to pass it a tuple (so .select((id,)). Is this an issue that occurs without a manual call to select?

agersant commented 7 years ago

Oh that makes sense. I had not realized that without the trailing comma, the select argument was a lone value and not a 1-item tuple (even with the extra parenthesis around the field name). Sorry for the false bug report 🙏.

Everything does work as expected without a select call.