-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Adds support for Views. #4077
base: master
Are you sure you want to change the base?
Adds support for Views. #4077
Conversation
bca7e68
to
92d81f2
Compare
d73e402
to
0fd7dfd
Compare
Thanks for opening this PR. I'm currently away for holidays. I will have a look at this PR as after I'm back. |
@@ -211,8 +213,8 @@ macro_rules! joinable_inner { | |||
macro_rules! allow_tables_to_appear_in_same_query { | |||
($left_mod:ident, $($right_mod:ident),+ $(,)*) => { | |||
$( | |||
impl $crate::query_source::TableNotEqual<$left_mod::table> for $right_mod::table {} | |||
impl $crate::query_source::TableNotEqual<$right_mod::table> for $left_mod::table {} | |||
impl $crate::query_source::ViewNotEqual<$left_mod::table> for $right_mod::table {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm in some other places we name this around Source
as the common denominator of view and table, it looks like this may be something we want to be consistent with here.
Have a nice and well deserved holiday ^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @coderPaddyS - thanks for making a great start at this! It will be great to have view support. This PR is a bit more complex than it needs to be, and there's a bit more work to do until it's complete - I'll try to outline what's needed.
I agree with the #3473 approach of doing the simplest part first, i.e., read-only views with all fields nullable. So please can you keep this PR entirely about that, with no discussion or code that serves the future rather than this goal. For example:
- The comment at the top of this PR starts a long discussion about nullability, updating, and automatic joins. If anyone takes you up on this discussion, all it can do is delay this PR getting merged. That discussion belongs on Should we have a story for database views? #43 or a future discussion thread, not here - please can you drop it from here, and start it elsewhere. Then we can get this PR merged and gain experience that will help move that discussion forward.
- The code changes are more invasive than necessary. I don't think you need
all_columns
until you need to support insert/delete/update/append; if you don't needall_columns
then you don't need to be able to generateColumn
s for a view; and so you don't need to relaxColumn::Table
. Since this PR only addresses the read-only case, none of this is necessary. You also probably don't need a separateView
trait (or at least, it's very simple with no methods and there's no need for it to be a supertrait ofTable
). And avoiding type changes means you don't need to consider whether the changes make error messages worse.
I suspect with these changes you can make the PR a lot simpler, which will also make it easier to test and review. Please also ensure CI is passing, ensuring that all clippy and rustfmt issues have been addressed before review.
Thanks again - and do let me know if anything above is unclear or if I've misunderstood something.
As far as I'm concerned I was happy to read the note about already already-done investigation and potential future evolutions on this topic, as that is something I'd likely have felt the need for investigating as well otherwise, upon noticing that everything was marked nullable 😄 |
Thanks! Yes I agree it is necessary to have a comment here explaining why everything is nullable! But IMHO it would be best to keep discussion about how to fix that later in a separate thread. |
Gracefully done :D
So If I get you right, you suggest removing the conflicting implementations of trait `query_source::AppearsInFromClause<_>` for type `select_statement::SelectStatement<_>`
downstream crates may implement trait `query_source::QuerySourceNotEqual<_>` for type `query_builder::select_statement::SelectStatement<_>` I currently don't see how I fix that without introducing another trait or changing seemingly unrelated code.
I'm not that well versed with clippy and multiple crate features. The only issue clippy reports is using only the sqlite-feature in the diesel_cli having some unused methods due to them being used with the mysql- or postgres feature. Is it common practice to check for multiple features on such impl blocks? |
Thanks :-) On the clippy problem, if you look in https://github.com/diesel-rs/diesel/blob/master/diesel_cli/Cargo.toml#L74C1-L82C29 you see that there's an internal feature On the bigger issue, it's obviously more complex than I hoped. I'm working to get my head around how it currently works (I've not delved into this before) - it may be that changing existing code as you have done is unavoidable. We may have to wait until @weiznich returns to confirm. |
Ah, this is interesting. I was wrong earlier - we do still need We can get part of the way there by omitting the This suggests that we could add a new marker trait I think this would achieve the desired effect, while changing a lot less code.
What do you think? |
It's unclear to me why a new |
It's not implemented by |
/// A SQL database table. Types which implement this trait should have been | ||
/// generated by the [`table!` macro](crate::table!). | ||
pub trait Table: QuerySource + AsQuery + Sized { | ||
pub trait Table: View { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SQL-wise, a table is not a view, so I think we need a different naming for the concept of "either a view or a table", because the code is otherwise going to be very hard to read.
(This is linked to #4077 (comment))
(It's clear from just this line and the documentation of View
that there's something wrong: View
documentation says it represents an SQL database view generated by view!
, but Table: View
and types that implement Table
don't match that)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I also agree there's something wrong here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(at least in Postgres) Views, Materialized Views and Tables are all "Relations" so maybe that's a good starting point when it comes to naming?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It mentions that "composite types and indexes are relations" as well but that may still be a good naming, if we need one that's separate from QuerySource.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @Ten0 here that View
shouldn't be the super trait of Table
. Instead I would propose the following structure:
trait Relation: QuerySource + AsQuery + Sized {
type AllColumns: SelectableExpression<Self> + ValidGrouping<()>;
/// Returns a tuple of all columns belonging to this table.
fn all_columns() -> Self::AllColumns;
}
trait Table: Relation {
/// The type returned by `primary_key`
type PrimaryKey: SelectableExpression<Self> + ValidGrouping<()>;
/// Returns the primary key of this table.
///
///
/// If the table has a composite primary key, this will be a tuple.
/// If the table has a composite primary key, this will be a tuple.
fn primary_key(&self) -> Self::PrimaryKey;
}
trait View: Relation {
// for now only a marker trait?
}
I think the "Relation" name suggested by @czotomo is a good choice here for the new trait with the shared behavior.
By having the super trait relation we also ensure that this isn't a breaking change and by having a seperate View
trait that is not implemented for Table
(and Table
is not implemented for View
by default) we can use that to differentiate behavior between these two. Also I think this opens up the road to add more table like constructions, like sql functions returning a "table`, in the future more easily.
For the time being I wouldn't want to expose the structures purely based on the Relation
trait (without implementing Table
nor View
) in an easy way for the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had another look at this and unfortunately the suggested trait structure won't be possible without breaking changes. In fact we cannot remove items from the Table
trait without causing locations that manually implement this trait to break.
I think we might be able to workaround this issue by using the following structure:
trait Relation: QuerySource + AsQuery + Sized {
type AllColumns: SelectableExpression<Self> + ValidGrouping<()>;
/// Returns a tuple of all columns belonging to this table.
fn all_columns() -> Self::AllColumns;
}
trait Table: QuerySource {
// unchanged
}
impl<T> Relation for T where T: Table {
// forward relevant types/functions to the new trait
}
trait View: Relation {
// for now only a marker trait?
}
And then change most of the existing AsQuery
impls to use the new Relation
trait instead. By that we won't break any existing usage, but can use the new trait in the relevant places. We might want to have a look at the impact on the compiler error messages there.
Given the supertrait constraint that I'm commenting on:
it seems impossible for anything but a |
I think it could probably be relaxed to |
I like the gist of that idea. As you've mentioned, this would prevent the modification of views entirely for the moment, but lets the API just open enough to add backend-specific support for modification later on in the |
Cool! Yes, but I think not renaming everything is a win - there's a lot of tricky code that checks for appearances of |
Then what's your opinion on separating I'm fine with implementing just |
Ah, I'd missed that comment from @weiznich. It does say pretty clearly not to use But there's a tension here: as we've seen, views do need to be treated like tables, in that they appear as field qualifiers ( We have to pick one - either views are I think the first is probably less disruptive, but I'm not certain, and it sounds like @weiznich might have the opposite preference. Do you have a sense of the impact and pros/cons of each approach? |
My two cents on this decision, but firstly a small point on my normal contact with views: While it is possible to have a table without primary key, I do share the opinion of @weiznich to ensure a primary key is used. Regarding the implementation overhead, I expect the latter one to require more altercations, especially the whole query related API, due to the supertrait. So I sense modifications similar to the initial version of this PR. Both implementations from above require the introduction of several traits ((only latter) supertrait, primary key trait and perhaps other modification traits) and thus renaming and altering bounds allover the place. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First I would like to thank @coderPaddyS for working on this. This is already a really good starting point ❤️
I would also thank @Ten0, @kw217 and @czotomo for their helpful reviews and comments. That made it much easier for me to come back from holiday and dive into a complex PR like that one.
I left some comments about the naming of the fundamental traits and a structure that I would consider fitting.
On a more fundamental level: I think it's fine to start with something rather barebone for now, which means marking all view columns as Nullable
+ not trying to infer any relations at all. I would even consider to put this, for now, behind an explicit, experimental opt-in flag in diesel-cli so that we can improve the generation code in later version. (For example adding a way to infer whether a column is not null, etc)
Finally: I've not done a full review of all changes in this PR. Before doing that I would like to see at least some tests in diesel_tests
+ in diesel_cli
(for the print-schema part). Maybe we should even consider splitting the PR into two parts. The first part could add the technical foundation in diesel
+ tests in diesel_tests
, while the second part would add support for generating view!
calls to diesel_cli
? That would keep the changeset smaller and easier to review.
/// A SQL database table. Types which implement this trait should have been | ||
/// generated by the [`table!` macro](crate::table!). | ||
pub trait Table: QuerySource + AsQuery + Sized { | ||
pub trait Table: View { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @Ten0 here that View
shouldn't be the super trait of Table
. Instead I would propose the following structure:
trait Relation: QuerySource + AsQuery + Sized {
type AllColumns: SelectableExpression<Self> + ValidGrouping<()>;
/// Returns a tuple of all columns belonging to this table.
fn all_columns() -> Self::AllColumns;
}
trait Table: Relation {
/// The type returned by `primary_key`
type PrimaryKey: SelectableExpression<Self> + ValidGrouping<()>;
/// Returns the primary key of this table.
///
///
/// If the table has a composite primary key, this will be a tuple.
/// If the table has a composite primary key, this will be a tuple.
fn primary_key(&self) -> Self::PrimaryKey;
}
trait View: Relation {
// for now only a marker trait?
}
I think the "Relation" name suggested by @czotomo is a good choice here for the new trait with the shared behavior.
By having the super trait relation we also ensure that this isn't a breaking change and by having a seperate View
trait that is not implemented for Table
(and Table
is not implemented for View
by default) we can use that to differentiate behavior between these two. Also I think this opens up the road to add more table like constructions, like sql functions returning a "table`, in the future more easily.
For the time being I wouldn't want to expose the structures purely based on the Relation
trait (without implementing Table
nor View
) in an easy way for the user.
)] | ||
pub trait TableNotEqual<T: Table>: Table {} | ||
pub trait ViewNotEqual<V: View>: View {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should follow the Relation
based naming then as well.
@@ -46,26 +46,31 @@ pub trait QuerySource { | |||
/// been generated by the [`table!` macro](crate::table!). | |||
pub trait Column: Expression { | |||
/// The table which this column belongs to | |||
type Table: Table; | |||
type Source: QuerySource; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I can understand the motivation for renaming this field, it remains a breaking change. I would rather go with
type Source: QuerySource; | |
type Table: Releation; |
instead. (As Relation
is the super trait of Table
that's fine)
Maybe we can also add a Fixme(diesel 3.0): rename to Source
or something like that.
@@ -79,7 +79,10 @@ impl ColumnType { | |||
} | |||
} | |||
|
|||
use std::fmt; | |||
use std::{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor stylistic nit: Diesel we use unnested imports in all other parts of the code base, so I would like to keep this consistent.
This PR introduces the support of Views in a query-only version.
Views are implemented as a supertrait of Table without any primary key and no way to insert or update anything on them. To allow querying the bounds on select- and querydsl are lowered as required.
A view is created by using the
diesel::view!
based on thediesel::table!
macro.Diesel_cli successfully (at least on postgres more complicated views) the schema for views, but currently only as null-able fields. Further implementation is required to perhaps improve this situation.
This is somewhat related to #43 and only implements the easy-enough and is basically the implementation of #3473.
To improve the usability and usefulness of views one may add some features discussed there.
Remaining for this PR: