-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Correctly enforce nullability with nested joins
When multiple joins are nested together, the resulting type is essentially a binary tree of each individal join. What this means is that Diesel only has to consider one join at a time in trait implementations. Prior to this commit, when considering nullability, we mistakenly only considered the outermost join. "Considering nullability" in this case means `impl SelectableExpression<TheJoin> for column`. If that trait is implemented, we allow selecting the column directly. If it appears on the right side of a left join, we do not want that trait to be implemented, but we do want it implemented for `Nullable<column>`. The problem code lived in two generated impls in the `table!` macro. For each individual column, we generate two relevant `SelectableExpression` impls: one for inner joins, and one for left joins. Both of these impls were wrong. The impl for left joins was written as: Left: AppearsInFromClause<$table, Count=Once>, Right: AppearsInFromClause<$table, Count=Never>, which in English means "where my table appears once in the left side of the join, and doesn't appear in the right side of the join". This incorrectly assumes that `$table` is the left-most table, or all joins contained in `Left` are inner joins. If `Left == Join<other_table, $table, LeftOuter>`, this constraint incorrectly holds. This first case was much easier to solve. The first line is changed to: Self: SelectableExpression<Left> The inner join case was much harder to solve. The constraint was written as: Join<Left, Right, Inner>: AppearsInFromClause<$table, Count=Once>, In English, this means "my table appears in this join clause exactly once". This fails to consider that the position the table appears in may be the right side of a left join. What we really want to write here is something like this: Self: SelectableExpression<Left> | SelectableExpression<Right>, Where the pipe means "either of these constraints are true". Of course no such impl exists. If we write two separate impls, they are overlapping (and overlapping marker traits are not stable). So we need something different. Since the problem case applies only to columns, we know that the only reason that `SelectableExpression<Left>` and `SelectableExpression<Right>` both hold is if the table appears more than once in the from clause, which we already reject. Because of this, we know that when we want this impl to apply, `(<Left as AppearsInFromClause<$table>>::Count, <Right as AppearsInFromClause<$table>>::Count>)` will either be `(Once, Never)` or `(Never, Once)`. Now that we know we have two disjoint types, we can use that to implement a new trait, which gives us either `Left` or `Right` depending on which of the two counts was `Once`, and use that as the parameter for `SelectableExpression`. In English, the new bounds mean "I am selectable from whichever of these two type parameters contains my table" Since this new trait is only meant as a hack for this single impl, and is not public API, I wanted to avoid having it appear in compiler errors. For this reason, I've added additional impls for all the invalid count combinations. This will lead to an error such as: > table3::id: SelectableExpression<this_table_does_not_appear_in_the_from_clause> instead of > (Never, Never): Select<table1, table2> I did not add explicit compile tests for these errors, as I want to allow for the deletion of those impls if they become a problem in the future. This lead to the final challenge which is that our `impl SelectableExpression<Join<...>> for Nullable<T>` actually relied on this bug. We considered `Nullable<T>: SelectableExpression<AnyJoin>` if `T: SelectableExpression<InnerJoin>`. This of course would break if the actual table is on the right side of a nested left join. To work around this, we've introduced another new trait which recursively converts all joins in a tree to inner joins, which we then use for our check. This also simplified the various impls for `Nullable<T>`, and should correctly ensure that `Nullable<T>` is always usable. Fixes #2001.
- Loading branch information
Showing
7 changed files
with
237 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters