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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explain that not only the order of fields needed #2631

Merged
merged 13 commits into from
Mar 4, 2021

Conversation

pickfire
Copy link
Contributor

@pickfire pickfire commented Jan 22, 2021

Queryable need to match all the fields, not just the order but the number
in some cases (I don't know what case).

@weiznich @Mingun #2627 (comment)

I am confused myself about the behavior too, I only know in some cases it needs to be the same, after I read the docs I became more confused (since it works even when the number of fields is not the same but at least the order is the same), I tried my best to reduce the confusion saying that all the items needs to be the same.

At first I even thought this will do a smart job extracting the fields needed, like the struct being part of the schema.

@weiznich weiznich requested a review from a team January 22, 2021 17:15
Comment on lines 400 to 401
/// no effect. In some cases, all the fields in the struct (including the order)
/// must be exactly the same (like `Eq`) as the fields in the query.**
Copy link
Member

@Ten0 Ten0 Jan 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what cases you're referring to that don't respect that rule.
How about:

Suggested change
/// no effect. In some cases, all the fields in the struct (including the order)
/// must be exactly the same (like `Eq`) as the fields in the query.**
/// no effect.** There should always be as many fields in your structure as
/// elements in the tuple used as in the `select` part of the query. This also
/// applies if the `select` is implicit.

Copy link
Contributor Author

@pickfire pickfire Jan 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some cases like when join it needs the whole items, not as many fields but all the fields. But I am not sure if there are any other cases besides that, I only encountered it once (I rarely use diesel). So this changes doesn't seemed to improve a lot from what is there currently (I find it a bit incorrect from what I know).

Copy link
Member

@Ten0 Ten0 Jan 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not as many fields but all the fields

I don't understand. Can you explain along with an example of a code that exhibits that behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pickfire As already explained there: This is not related to joins. The behaviour is the same for every query. To word it really simply: Each query has a at compile time know SQL type, which is based on the expressions in your select clause (note that expressions are not necessarily columns…). Let's name that ST. If you now call .load() (or a similar method) the result struct needs to implement Queryable<ST, _> for your return type. The #[derive(Queryable)] now implements Queryable by just using mapping each return field by index to one of your struct fields, so that ST corresponds to each rust type. There is really no magic going on here and the behaviour is just the same for all queries.

Copy link
Member

@weiznich weiznich Jan 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, the whole discussion/problem seems to be more about what's the default select clause in which case, then how #[derive(Queryable)] work.
For me it does not seem like a good idea to document that all on fn load() is this function is not related to constructing the select clause.
There are exactly three cases for how the select clause looks like:

  • A user passes an explicit select clause via select. It think the examples and the explanation there cover this case quite well. Otherwise that's the right location to improve documentation about this case.
  • A user does not call .select(…) and does not perform a join. This is the case with the weakest current documentation, as this is done implicitly and there is no "right" place to document this. It is somewhat covered in the Getting Started but that does not go in depth there.
  • A user does not call .select(…) but performs one or more joins. This case is documented via inner_join (All other join methods like this, it's basically the same for them). Now this documentation is quite detailed about the return types, but could mentioned a bit more what's the behaviour if there is already an existing select clause and how the new default select clause is constructed.

The right place to cover all that would probably be to write a All about selects guide (See #1108), but at least I do not have the time to do that.

I believe the usual case is that one can load a single struct with implicit select if there are less fields in the struct but not in the case of join. Maybe we need to explain the need to have explicit select in some parts?

Again: The rules for calling load() are the same, regardless if there is an join or not. There is no such thing like a implicit select that just drops fields and that's an explicit design decision of diesel. The field could and structure needs to match with that data structure you are loading data into. (At least as long as you use the derive…)

maybe this should be explained in load in which an explicit select is needed if a join is used, since it became a tuple.

It's perfectly fine to use load() on a joined query without calling select.

Copy link
Member

@Ten0 Ten0 Jan 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT: Oh, previous answer already covered that. I missed it when I posted this.

I believe the usual case is that one can load a single struct with implicit select if there are less fields in the struct but not in the case of join.

I don't think there is such a case even when there are no joins: it's always mandatory when deriving Queryable that there are as many fields in your structure as elements in the tuple used as in the 'select' part of the query, even with an implicit select.

Copy link
Contributor Author

@pickfire pickfire Jan 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying, now I think I understand more.

Is there a way to prevent footguns like these where we can check the original schema to see if it matches the struct? These seemed like implicit knowledge which the users needs to check themselves which make it a potential footgun, I think it would be best to have these enforced during #[derive(Queryable)] but not sure if that's possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main point of having something like Queryable is that it does not require the schema to work, because otherwise you wouldn't be able to represent the result of a more complex query with it. How would you handle the following SQL with only checking the schema? SELECT departments.id, 42, max(employees.salary) FROM departments INNER JOIN employees ON departments.id = employees.department GROUP BY departments.id ? Such query does not necessarily contain references to only one table, could mix columns through arbitrarily complex expressions and can even contain expressions without reference to columns. You cannot check that with "the schema".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is resolved right?

@pickfire
Copy link
Contributor Author

I have updated it to say that all fields needs to be the same.

diesel_derives/src/lib.rs Outdated Show resolved Hide resolved
diesel_derives/src/lib.rs Outdated Show resolved Hide resolved
diesel_derives/src/lib.rs Outdated Show resolved Hide resolved
pickfire added 6 commits March 1, 2021 22:59
Queryable need to match all the fields, not just the order but the number
in some cases.
weiznich suggested to have a concrete example
Following serde.rs terminology on attributes would be clearer, the
word "type" can mean many things. https://serde.rs/attributes.html
@pickfire
Copy link
Contributor Author

pickfire commented Mar 1, 2021

@weiznich Do we still need the FIXME in QueryDsl::select?

Copy link
Member

@weiznich weiznich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. This sounds much better now 👍
I've left a few comments with some minor formulation improvements + 2 places where the change incorrectly mentions that a #[table_name] attribute is allowed while it isn't.
If that's changed I'm fine with merging this.

diesel/src/query_dsl/mod.rs Outdated Show resolved Hide resolved
diesel/src/query_dsl/mod.rs Outdated Show resolved Hide resolved
diesel_derives/src/lib.rs Outdated Show resolved Hide resolved
diesel_derives/src/lib.rs Outdated Show resolved Hide resolved
diesel_derives/src/lib.rs Outdated Show resolved Hide resolved
diesel/src/query_dsl/mod.rs Outdated Show resolved Hide resolved
diesel/src/query_dsl/mod.rs Outdated Show resolved Hide resolved
@weiznich weiznich merged commit bc6fddf into diesel-rs:master Mar 4, 2021
@pickfire pickfire deleted the patch-1 branch March 4, 2021 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants