diff --git a/diesel/src/expression/operators.rs b/diesel/src/expression/operators.rs index 836389908ae6..c4b84480d3e3 100644 --- a/diesel/src/expression/operators.rs +++ b/diesel/src/expression/operators.rs @@ -557,12 +557,14 @@ use crate::insertable::{ColumnInsertValue, Insertable}; use crate::query_builder::{QueryFragment, QueryId, ValuesClause}; use crate::query_source::Column; use crate::sql_types::{DieselNumericOps, SqlType}; +use crate::Table; -impl Insertable for Eq +impl Insertable for Eq where T: Column, + T::Source: Table, { - type Values = ValuesClause, T::Table>; + type Values = ValuesClause, T::Source>; fn values(self) -> Self::Values { ValuesClause::new(ColumnInsertValue::new(self.right)) diff --git a/diesel/src/insertable.rs b/diesel/src/insertable.rs index 39e6d576475a..f4c20541b9ca 100644 --- a/diesel/src/insertable.rs +++ b/diesel/src/insertable.rs @@ -154,11 +154,12 @@ impl Default for DefaultableColumnInsertValue { } } -impl InsertValues +impl InsertValues for DefaultableColumnInsertValue> where DB: Backend + SqlDialect, Col: Column, + Col::Source: Table, Expr: Expression + AppearsOnTable, Self: QueryFragment, { @@ -168,10 +169,11 @@ where } } -impl InsertValues for ColumnInsertValue +impl InsertValues for ColumnInsertValue where DB: Backend, Col: Column, + Col::Source: Table, Expr: Expression + AppearsOnTable, Self: QueryFragment, { @@ -218,10 +220,11 @@ where } #[cfg(feature = "sqlite")] -impl InsertValues +impl InsertValues for DefaultableColumnInsertValue> where Col: Column, + Col::Source: Table, Expr: Expression + AppearsOnTable, Self: QueryFragment, { diff --git a/diesel/src/lib.rs b/diesel/src/lib.rs index edac5b9fd3e4..c5c55891498d 100644 --- a/diesel/src/lib.rs +++ b/diesel/src/lib.rs @@ -725,6 +725,8 @@ pub mod prelude { }; #[doc(inline)] pub use diesel_derives::table_proc as table; + #[doc(inline)] + pub use diesel_derives::view_proc as view; #[cfg(feature = "mysql")] #[doc(inline)] @@ -742,6 +744,8 @@ pub mod prelude { #[doc(inline)] pub use crate::macros::table; +#[doc(inline)] +pub use crate::macros::view; pub use crate::prelude::*; #[doc(inline)] pub use crate::query_builder::debug_query; diff --git a/diesel/src/macros/mod.rs b/diesel/src/macros/mod.rs index 8290d0517398..ab19e259e654 100644 --- a/diesel/src/macros/mod.rs +++ b/diesel/src/macros/mod.rs @@ -14,6 +14,8 @@ pub(crate) mod prelude { #[doc(inline)] pub use diesel_derives::table_proc as table; +#[doc(inline)] +pub use diesel_derives::view_proc as view; /// Allow two tables to be referenced in a join query without providing an /// explicit `ON` clause. @@ -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 {} + impl $crate::query_source::ViewNotEqual<$right_mod::table> for $left_mod::table {} $crate::__diesel_internal_backend_specific_allow_tables_to_appear_in_same_query!($left_mod, $right_mod); )+ $crate::allow_tables_to_appear_in_same_query!($($right_mod,)+); @@ -227,36 +229,36 @@ macro_rules! allow_tables_to_appear_in_same_query { #[cfg(feature = "postgres_backend")] macro_rules! __diesel_internal_backend_specific_allow_tables_to_appear_in_same_query { ($left:ident, $right:ident) => { - impl $crate::query_source::TableNotEqual<$left::table> + impl $crate::query_source::ViewNotEqual<$left::table> for $crate::query_builder::Only<$right::table> { } - impl $crate::query_source::TableNotEqual<$right::table> + impl $crate::query_source::ViewNotEqual<$right::table> for $crate::query_builder::Only<$left::table> { } - impl $crate::query_source::TableNotEqual<$crate::query_builder::Only<$left::table>> + impl $crate::query_source::ViewNotEqual<$crate::query_builder::Only<$left::table>> for $right::table { } - impl $crate::query_source::TableNotEqual<$crate::query_builder::Only<$right::table>> + impl $crate::query_source::ViewNotEqual<$crate::query_builder::Only<$right::table>> for $left::table { } - impl $crate::query_source::TableNotEqual<$left::table> + impl $crate::query_source::ViewNotEqual<$left::table> for $crate::query_builder::Tablesample<$right::table, TSM> where TSM: $crate::internal::table_macro::TablesampleMethod, { } - impl $crate::query_source::TableNotEqual<$right::table> + impl $crate::query_source::ViewNotEqual<$right::table> for $crate::query_builder::Tablesample<$left::table, TSM> where TSM: $crate::internal::table_macro::TablesampleMethod, { } impl - $crate::query_source::TableNotEqual< + $crate::query_source::ViewNotEqual< $crate::query_builder::Tablesample<$left::table, TSM>, > for $right::table where @@ -264,7 +266,7 @@ macro_rules! __diesel_internal_backend_specific_allow_tables_to_appear_in_same_q { } impl - $crate::query_source::TableNotEqual< + $crate::query_source::ViewNotEqual< $crate::query_builder::Tablesample<$right::table, TSM>, > for $left::table where diff --git a/diesel/src/pg/expression/operators.rs b/diesel/src/pg/expression/operators.rs index 8d7b834f29e8..00413873a930 100644 --- a/diesel/src/pg/expression/operators.rs +++ b/diesel/src/pg/expression/operators.rs @@ -7,7 +7,7 @@ use crate::query_builder::{AstPass, QueryFragment, QueryId}; use crate::sql_types::{ Array, Bigint, Bool, DieselNumericOps, Inet, Integer, Jsonb, SqlType, Text, }; -use crate::{Column, QueryResult}; +use crate::{Column, QueryResult, Table}; __diesel_infix_operator!(IsDistinctFrom, " IS DISTINCT FROM ", ConstantNullability Bool, backend: Pg); __diesel_infix_operator!(IsNotDistinctFrom, " IS NOT DISTINCT FROM ", ConstantNullability Bool, backend: Pg); @@ -94,8 +94,9 @@ where impl AssignmentTarget for ArrayIndex where L: Column, + L::Source: Table, { - type Table = ::Table; + type Table = ::Source; type QueryAstNode = ArrayIndex, R>; fn into_target(self) -> Self::QueryAstNode { diff --git a/diesel/src/pg/query_builder/copy/copy_from.rs b/diesel/src/pg/query_builder/copy/copy_from.rs index a5dfe7c08e15..7f5d8928ad13 100644 --- a/diesel/src/pg/query_builder/copy/copy_from.rs +++ b/diesel/src/pg/query_builder/copy/copy_from.rs @@ -198,7 +198,7 @@ macro_rules! impl_copy_from_insertable_helper_for_values_clause { T> where T: Table, - $($ST: Column,)* + $($ST: Column,)* ($($ST,)*): CopyTarget, $($TT: ToSql<$T, Pg>,)* { @@ -229,7 +229,7 @@ macro_rules! impl_copy_from_insertable_helper_for_values_clause { T> where T: Table, - $($ST: Column
,)* + $($ST: Column,)* ($($ST,)*): CopyTarget, $($TT: ToSql<$T, Pg>,)* { diff --git a/diesel/src/pg/query_builder/copy/mod.rs b/diesel/src/pg/query_builder/copy/mod.rs index a2a0320c6b19..06cb84d7436a 100644 --- a/diesel/src/pg/query_builder/copy/mod.rs +++ b/diesel/src/pg/query_builder/copy/mod.rs @@ -148,7 +148,7 @@ macro_rules! copy_target_for_columns { $( impl CopyTarget for ($($ST,)*) where - $($ST: Column
,)* + $($ST: Column,)* ($(<$ST as Expression>::SqlType,)*): SqlType, T: Table + StaticQueryFragment, T::Component: QueryFragment, diff --git a/diesel/src/pg/query_builder/only.rs b/diesel/src/pg/query_builder/only.rs index 6fad25ea9fc1..a1aa1e81dd60 100644 --- a/diesel/src/pg/query_builder/only.rs +++ b/diesel/src/pg/query_builder/only.rs @@ -1,7 +1,7 @@ use crate::expression::{Expression, ValidGrouping}; use crate::pg::Pg; use crate::query_builder::{AsQuery, AstPass, FromClause, QueryFragment, QueryId, SelectStatement}; -use crate::query_source::QuerySource; +use crate::query_source::{QuerySource, View}; use crate::result::QueryResult; use crate::{JoinTo, SelectableExpression, Table}; @@ -74,22 +74,32 @@ where >::join_target(rhs) } } + +impl View for Only +where + S: Table + Clone + AsQuery, + + ::PrimaryKey: SelectableExpression>, + ::AllColumns: SelectableExpression>, + ::DefaultSelection: ValidGrouping<()> + SelectableExpression>, +{ + type AllColumns = ::AllColumns; + fn all_columns() -> Self::AllColumns { + S::all_columns() + } +} + impl Table for Only where S: Table + Clone + AsQuery, ::PrimaryKey: SelectableExpression>, - ::AllColumns: SelectableExpression>, + ::AllColumns: SelectableExpression>, ::DefaultSelection: ValidGrouping<()> + SelectableExpression>, { type PrimaryKey = ::PrimaryKey; - type AllColumns = ::AllColumns; fn primary_key(&self) -> Self::PrimaryKey { self.source.primary_key() } - - fn all_columns() -> Self::AllColumns { - S::all_columns() - } } diff --git a/diesel/src/pg/query_builder/tablesample.rs b/diesel/src/pg/query_builder/tablesample.rs index 6906db780b97..2aa2d6e15415 100644 --- a/diesel/src/pg/query_builder/tablesample.rs +++ b/diesel/src/pg/query_builder/tablesample.rs @@ -1,7 +1,7 @@ use crate::expression::{Expression, ValidGrouping}; use crate::pg::Pg; use crate::query_builder::{AsQuery, AstPass, FromClause, QueryFragment, QueryId, SelectStatement}; -use crate::query_source::QuerySource; +use crate::query_source::{QuerySource, View}; use crate::result::QueryResult; use crate::sql_types::{Double, SmallInt}; use crate::{JoinTo, SelectableExpression, Table}; @@ -147,26 +147,38 @@ where } } +impl View for Tablesample +where + S: Table + Clone + AsQuery, + TSM: TablesampleMethod, + + ::PrimaryKey: SelectableExpression>, + ::AllColumns: SelectableExpression>, + ::DefaultSelection: + ValidGrouping<()> + SelectableExpression>, +{ + type AllColumns = ::AllColumns; + + fn all_columns() -> Self::AllColumns { + S::all_columns() + } +} + impl Table for Tablesample where S: Table + Clone + AsQuery, TSM: TablesampleMethod, ::PrimaryKey: SelectableExpression>, - ::AllColumns: SelectableExpression>, + ::AllColumns: SelectableExpression>, ::DefaultSelection: ValidGrouping<()> + SelectableExpression>, { type PrimaryKey = ::PrimaryKey; - type AllColumns = ::AllColumns; fn primary_key(&self) -> Self::PrimaryKey { self.source.primary_key() } - - fn all_columns() -> Self::AllColumns { - S::all_columns() - } } #[cfg(test)] diff --git a/diesel/src/query_builder/insert_statement/column_list.rs b/diesel/src/query_builder/insert_statement/column_list.rs index ec11a6b2c405..e063fbd48715 100644 --- a/diesel/src/query_builder/insert_statement/column_list.rs +++ b/diesel/src/query_builder/insert_statement/column_list.rs @@ -1,5 +1,5 @@ -use crate::query_builder::*; use crate::query_source::Column; +use crate::{query_builder::*, Table}; /// Represents the column list for use in an insert statement. /// @@ -17,8 +17,9 @@ pub trait ColumnList { impl ColumnList for C where C: Column, + C::Source: Table, { - type Table = ::Table; + type Table = ::Source; fn walk_ast(&self, mut out: AstPass<'_, '_, DB>) -> QueryResult<()> { out.push_identifier(C::NAME)?; diff --git a/diesel/src/query_builder/insert_statement/mod.rs b/diesel/src/query_builder/insert_statement/mod.rs index a8e0ca566377..72792c2f2d3d 100644 --- a/diesel/src/query_builder/insert_statement/mod.rs +++ b/diesel/src/query_builder/insert_statement/mod.rs @@ -322,12 +322,18 @@ impl<'a, T, Tab> UndecoratedInsertRecord for &'a T where { } -impl UndecoratedInsertRecord for ColumnInsertValue where T: Column {} +impl UndecoratedInsertRecord for ColumnInsertValue +where + T: Column, + T::Source: Table, +{ +} -impl UndecoratedInsertRecord +impl UndecoratedInsertRecord for DefaultableColumnInsertValue> where T: Column, + T::Source: Table, { } @@ -342,14 +348,24 @@ where impl UndecoratedInsertRecord
for Vec where [T]: UndecoratedInsertRecord
{} -impl UndecoratedInsertRecord for Eq where Lhs: Column {} +impl UndecoratedInsertRecord for Eq +where + Lhs: Column, + Lhs::Source: Table, +{ +} impl UndecoratedInsertRecord for Option> where Eq: UndecoratedInsertRecord { } -impl UndecoratedInsertRecord for Grouped> where Lhs: Column {} +impl UndecoratedInsertRecord for Grouped> +where + Lhs: Column, + Lhs::Source: Table, +{ +} impl UndecoratedInsertRecord for Option>> where Eq: UndecoratedInsertRecord diff --git a/diesel/src/query_builder/update_statement/changeset.rs b/diesel/src/query_builder/update_statement/changeset.rs index b3349b372c30..8ee3ebcbb4d0 100644 --- a/diesel/src/query_builder/update_statement/changeset.rs +++ b/diesel/src/query_builder/update_statement/changeset.rs @@ -120,8 +120,9 @@ where impl AssignmentTarget for C where C: Column, + C::Source: Table, { - type Table = C::Table; + type Table = C::Source; type QueryAstNode = ColumnWrapperForUpdate; fn into_target(self) -> Self::QueryAstNode { diff --git a/diesel/src/query_builder/upsert/on_conflict_actions.rs b/diesel/src/query_builder/upsert/on_conflict_actions.rs index 4f3e144ac147..a7c958cdd98a 100644 --- a/diesel/src/query_builder/upsert/on_conflict_actions.rs +++ b/diesel/src/query_builder/upsert/on_conflict_actions.rs @@ -120,9 +120,10 @@ where type SqlType = T::SqlType; } -impl AppearsOnTable for Excluded +impl AppearsOnTable for Excluded where T: Column, + T::Source: Table, Excluded: Expression, { } diff --git a/diesel/src/query_builder/upsert/on_conflict_target.rs b/diesel/src/query_builder/upsert/on_conflict_target.rs index 125cda7bdaca..64149f05aa8e 100644 --- a/diesel/src/query_builder/upsert/on_conflict_target.rs +++ b/diesel/src/query_builder/upsert/on_conflict_target.rs @@ -1,7 +1,7 @@ use crate::backend::sql_dialect; use crate::expression::SqlLiteral; -use crate::query_builder::*; use crate::query_source::Column; +use crate::{query_builder::*, Table}; #[doc(hidden)] pub trait OnConflictTarget
{} @@ -50,7 +50,12 @@ where } } -impl OnConflictTarget for ConflictTarget where T: Column {} +impl OnConflictTarget for ConflictTarget +where + T: Column, + T::Source: Table, +{ +} impl QueryFragment for ConflictTarget> where @@ -81,7 +86,12 @@ where } } -impl OnConflictTarget for ConflictTarget<(T,)> where T: Column {} +impl OnConflictTarget for ConflictTarget<(T,)> +where + T: Column, + T::Source: Table, +{ +} macro_rules! on_conflict_tuples { ($( @@ -94,7 +104,7 @@ macro_rules! on_conflict_tuples { _DB: Backend, _SP: sql_dialect::on_conflict_clause::PgLikeOnConflictClause, _T: Column, - $($T: Column,)* + $($T: Column,)* { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, _DB>) -> QueryResult<()> { @@ -109,9 +119,9 @@ macro_rules! on_conflict_tuples { } } - impl<_T, $($T),*> OnConflictTarget<_T::Table> for ConflictTarget<(_T, $($T),*)> where + impl<_T, $($T),*> OnConflictTarget<_T::Source> for ConflictTarget<(_T, $($T),*)> where _T: Column, - $($T: Column,)* + $($T: Column,)* { } )* diff --git a/diesel/src/query_dsl/mod.rs b/diesel/src/query_dsl/mod.rs index 8be915e759fd..cc35485bd220 100644 --- a/diesel/src/query_dsl/mod.rs +++ b/diesel/src/query_dsl/mod.rs @@ -17,7 +17,7 @@ use crate::expression::count::CountStar; use crate::expression::Expression; use crate::helper_types::*; use crate::query_builder::locking_clause as lock; -use crate::query_source::{joins, Table}; +use crate::query_source::{joins, View}; use crate::result::QueryResult; mod belonging_to_dsl; @@ -1387,7 +1387,7 @@ pub trait QueryDsl: Sized { } } -impl QueryDsl for T {} +impl QueryDsl for T {} /// Methods used to execute queries. pub trait RunQueryDsl: Sized { @@ -1788,4 +1788,4 @@ pub trait RunQueryDsl: Sized { // resolution. Otherwise our users will get an error saying `<3 page long type>: // ExecuteDsl is not satisfied` instead of a specific error telling them what // part of their query is wrong. -impl RunQueryDsl for T where T: Table {} +impl RunQueryDsl for T where T: View {} diff --git a/diesel/src/query_dsl/save_changes_dsl.rs b/diesel/src/query_dsl/save_changes_dsl.rs index 3a56e1ab3598..fc8ad8c30d97 100644 --- a/diesel/src/query_dsl/save_changes_dsl.rs +++ b/diesel/src/query_dsl/save_changes_dsl.rs @@ -13,9 +13,9 @@ use crate::query_builder::{AsChangeset, IntoUpdateTarget}; use crate::query_dsl::methods::{ExecuteDsl, FindDsl}; #[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))] use crate::query_dsl::{LoadQuery, RunQueryDsl}; -use crate::result::QueryResult; #[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))] -use crate::Table; +use crate::query_source::View; +use crate::result::QueryResult; /// A trait defining how to update a record and fetch the updated entry /// on a certain backend. @@ -40,8 +40,8 @@ impl<'b, Changes, Output> UpdateAndFetchResults for PgConnectio where Changes: Copy + AsChangeset::Table> + IntoUpdateTarget, Update: LoadQuery<'b, PgConnection, Output>, - ::AllColumns: ValidGrouping<()>, - <::AllColumns as ValidGrouping<()>>::IsAggregate: + ::AllColumns: ValidGrouping<()>, + <::AllColumns as ValidGrouping<()>>::IsAggregate: MixedAggregates, { fn update_and_fetch(&mut self, changeset: Changes) -> QueryResult { @@ -60,8 +60,8 @@ where Changes::Table: FindDsl, Update: ExecuteDsl, Find: LoadQuery<'b, SqliteConnection, Output>, - ::AllColumns: ValidGrouping<()>, - <::AllColumns as ValidGrouping<()>>::IsAggregate: + ::AllColumns: ValidGrouping<()>, + <::AllColumns as ValidGrouping<()>>::IsAggregate: MixedAggregates, { fn update_and_fetch(&mut self, changeset: Changes) -> QueryResult { @@ -81,8 +81,8 @@ where Changes::Table: FindDsl, Update: ExecuteDsl, Find: LoadQuery<'b, MysqlConnection, Output>, - ::AllColumns: ValidGrouping<()>, - <::AllColumns as ValidGrouping<()>>::IsAggregate: + ::AllColumns: ValidGrouping<()>, + <::AllColumns as ValidGrouping<()>>::IsAggregate: MixedAggregates, { fn update_and_fetch(&mut self, changeset: Changes) -> QueryResult { diff --git a/diesel/src/query_dsl/select_dsl.rs b/diesel/src/query_dsl/select_dsl.rs index 60b0d7ac741f..3377e6c35b1b 100644 --- a/diesel/src/query_dsl/select_dsl.rs +++ b/diesel/src/query_dsl/select_dsl.rs @@ -1,5 +1,5 @@ use crate::expression::Expression; -use crate::query_source::Table; +use crate::query_source::View; /// The `select` method /// @@ -22,7 +22,7 @@ pub trait SelectDsl { impl SelectDsl for T where Selection: Expression, - T: Table, + T: View, T::Query: SelectDsl, { type Output = >::Output; diff --git a/diesel/src/query_source/aliasing/alias.rs b/diesel/src/query_source/aliasing/alias.rs index 42f645292efc..27fe88fec8a7 100644 --- a/diesel/src/query_source/aliasing/alias.rs +++ b/diesel/src/query_source/aliasing/alias.rs @@ -5,7 +5,7 @@ use crate::backend::{sql_dialect, Backend}; use crate::expression::{Expression, SelectableExpression, ValidGrouping}; use crate::helper_types::AliasedFields; use crate::query_builder::{AsQuery, AstPass, FromClause, QueryFragment, QueryId, SelectStatement}; -use crate::query_source::{AppearsInFromClause, Column, Never, QuerySource, Table, TableNotEqual}; +use crate::query_source::{AppearsInFromClause, Column, Never, QuerySource, Table, ViewNotEqual}; use crate::result::QueryResult; use std::marker::PhantomData; @@ -27,7 +27,7 @@ impl Alias { /// Maps a single field of the source table in this alias pub fn field(&self, field: F) -> AliasedField where - F: Column
, + F: Column, { AliasedField { _alias_source: PhantomData, @@ -185,7 +185,7 @@ pub trait AliasAliasAppearsInFromClauseSameTable { // where T1 != T2 impl AliasAppearsInFromClause for T1 where - T1: TableNotEqual + Table, + T1: ViewNotEqual + Table, T2: Table, S: AliasSource, { @@ -196,7 +196,7 @@ where // where S1: AliasSource, S2: AliasSource, S1::Table != S2::Table impl AliasAliasAppearsInFromClause for T2 where - T1: TableNotEqual + Table, + T1: ViewNotEqual + Table, T2: Table, S1: AliasSource, S2: AliasSource, diff --git a/diesel/src/query_source/aliasing/aliased_field.rs b/diesel/src/query_source/aliasing/aliased_field.rs index 82c2ecb8b00e..c059b8b8b1c1 100644 --- a/diesel/src/query_source/aliasing/aliased_field.rs +++ b/diesel/src/query_source/aliasing/aliased_field.rs @@ -28,7 +28,7 @@ impl QueryId for AliasedField where S: AliasSource + 'static, S::Target: 'static, - C: Column
+ 'static + QueryId, + C: Column + 'static + QueryId, { type QueryId = Self; const HAS_STATIC_QUERY_ID: bool = ::HAS_STATIC_QUERY_ID; @@ -38,7 +38,7 @@ impl AppearsOnTable for AliasedField where S: AliasSource, QS: AppearsInFromClause, Count = Once>, - C: Column
, + C: Column, { } @@ -46,7 +46,7 @@ impl QueryFragment for AliasedField where S: AliasSource, DB: Backend, - C: Column
, + C: Column, { fn walk_ast<'b>(&'b self, mut pass: AstPass<'_, 'b, DB>) -> QueryResult<()> { pass.push_identifier(S::NAME)?; @@ -59,7 +59,7 @@ where impl Expression for AliasedField where S: AliasSource, - C: Column
+ Expression, + C: Column + Expression, { type SqlType = C::SqlType; } @@ -67,7 +67,7 @@ where impl SelectableExpression> for AliasedField where S: AliasSource, - C: Column
, + C: Column, Self: AppearsOnTable>, { } @@ -75,7 +75,7 @@ where impl ValidGrouping<()> for AliasedField where S: AliasSource, - C: Column
, + C: Column, { type IsAggregate = is_aggregate::No; } @@ -83,8 +83,8 @@ where impl ValidGrouping> for AliasedField where S: AliasSource, - C1: Column
, - C2: Column
, + C1: Column, + C2: Column, C2: ValidGrouping, { type IsAggregate = is_aggregate::Yes; @@ -101,7 +101,7 @@ where impl EqAll for AliasedField where S: AliasSource, - C: Column
, + C: Column, Self: ExpressionMethods, ::SqlType: sql_types::SqlType, T: AsExpression<::SqlType>, diff --git a/diesel/src/query_source/aliasing/field_alias_mapper.rs b/diesel/src/query_source/aliasing/field_alias_mapper.rs index 9c97d4f71649..0c9adf8dfaeb 100644 --- a/diesel/src/query_source/aliasing/field_alias_mapper.rs +++ b/diesel/src/query_source/aliasing/field_alias_mapper.rs @@ -1,7 +1,7 @@ use super::{Alias, AliasSource, AliasedField}; use crate::expression; -use crate::query_source::{Column, Table, TableNotEqual}; +use crate::query_source::{Column, Table, ViewNotEqual}; /// Serves to map `Self` to `Alias` /// @@ -29,7 +29,7 @@ pub trait FieldAliasMapper { /// Allows implementing `FieldAliasMapper` in external crates without running into conflicting impl /// errors due to https://github.com/rust-lang/rust/issues/20400 /// -/// We will always have `Self = S::Table` and `CT = C::Table` +/// We will always have `Self = S::Viewable` and `CT = C::Viewable` pub trait FieldAliasMapperAssociatedTypesDisjointnessTrick { type Out; fn map(column: C, alias: &Alias) -> Self::Out; @@ -38,12 +38,13 @@ impl FieldAliasMapper for C where S: AliasSource, C: Column, - S::Target: FieldAliasMapperAssociatedTypesDisjointnessTrick, + S::Target: FieldAliasMapperAssociatedTypesDisjointnessTrick, { - type Out = >::Out; + type Out = + >::Out; fn map(self, alias: &Alias) -> Self::Out { - >::map( + >::map( self, alias, ) } @@ -52,9 +53,9 @@ where impl FieldAliasMapperAssociatedTypesDisjointnessTrick for TS where S: AliasSource, - C: Column
, + C: Column, TC: Table, - TS: TableNotEqual, + TS: ViewNotEqual, { type Out = C; diff --git a/diesel/src/query_source/mod.rs b/diesel/src/query_source/mod.rs index 70ae0b9b22f7..fdb5f2e85197 100644 --- a/diesel/src/query_source/mod.rs +++ b/diesel/src/query_source/mod.rs @@ -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; /// The name of this column const NAME: &'static str; } +/// A SQL database view. Types which implement this trait should have been +/// generated by the [`view!` macro](crate::view!). +pub trait View: QuerySource + AsQuery + Sized { + /// The type returned by `all_columns` + type AllColumns: SelectableExpression + ValidGrouping<()>; + /// Returns a tuple of all columns belonging to this table. + fn all_columns() -> Self::AllColumns; +} + /// 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 { /// The type returned by `primary_key` type PrimaryKey: SelectableExpression + ValidGrouping<()>; - /// The type returned by `all_columns` - type AllColumns: SelectableExpression + ValidGrouping<()>; /// Returns the primary key of this table. /// /// If the table has a composite primary key, this will be a tuple. fn primary_key(&self) -> Self::PrimaryKey; - /// Returns a tuple of all columns belonging to this table. - fn all_columns() -> Self::AllColumns; } /// Determines how many times `Self` appears in `QS` @@ -101,14 +106,14 @@ pub trait AppearsInFromClause { /// - You are attempting to use two aliases to the same table in the same query, but they /// were declared through different calls to [`alias!`](crate::alias) #[diagnostic::on_unimplemented( - note = "double check that `{T}` and `{Self}` appear in the same `allow_tables_to_appear_in_same_query!` \ncall if both are tables" + note = "double check that `{V}` and `{Self}` appear in the same `allow_tables_to_appear_in_same_query!` \ncall if both are tables" )] -pub trait TableNotEqual: Table {} +pub trait ViewNotEqual: View {} -impl AppearsInFromClause for T1 +impl AppearsInFromClause for V1 where - T1: TableNotEqual + Table, - T2: Table, + V1: ViewNotEqual + View, + V2: View, { type Count = Never; } diff --git a/diesel_bench/benches/rust_orm_benches.rs b/diesel_bench/benches/rust_orm_benches.rs index 97edd0268116..b864cf8c376d 100644 --- a/diesel_bench/benches/rust_orm_benches.rs +++ b/diesel_bench/benches/rust_orm_benches.rs @@ -28,7 +28,7 @@ mod for_insert { mod for_load { use super::*; - use rustorm::dao::FromDao; + use rustorm::table::FromDao; use rustorm::Dao; #[derive(Debug, FromDao, ToColumnNames, ToTableName)] @@ -56,18 +56,18 @@ mod for_load { pub struct UserWithPost(Users, Option); impl FromDao for UserWithPost { - fn from_dao(dao: &Dao) -> Self { + fn from_dao(table: &Dao) -> Self { let user = Users { - id: dao.get("myuser_id").unwrap(), - name: dao.get("name").unwrap(), - hair_color: dao.get("hair_color").unwrap(), + id: table.get("myuser_id").unwrap(), + name: table.get("name").unwrap(), + hair_color: table.get("hair_color").unwrap(), }; - let post = if let Some(id) = dao.get("post_id").unwrap() { + let post = if let Some(id) = table.get("post_id").unwrap() { Some(Posts { id, - user_id: dao.get("user_id").unwrap(), - title: dao.get("title").unwrap(), - body: dao.get("body").unwrap(), + user_id: table.get("user_id").unwrap(), + title: table.get("title").unwrap(), + body: table.get("body").unwrap(), }) } else { None diff --git a/diesel_cli/src/infer_schema_internals/data_structures.rs b/diesel_cli/src/infer_schema_internals/data_structures.rs index c84fed346b5a..0b7c9d4f1b03 100644 --- a/diesel_cli/src/infer_schema_internals/data_structures.rs +++ b/diesel_cli/src/infer_schema_internals/data_structures.rs @@ -1,6 +1,6 @@ use diesel_table_macro_syntax::ColumnDef; -use super::table_data::TableName; +use super::{table_data::TableName, TableData, ViewData}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ColumnInformation { @@ -79,7 +79,10 @@ impl ColumnType { } } -use std::fmt; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; impl fmt::Display for ColumnType { fn fmt(&self, out: &mut fmt::Formatter) -> Result<(), fmt::Error> { @@ -156,3 +159,71 @@ impl ForeignKeyConstraint { ) } } + +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub enum SupportedColumnStructures { + View, + Table, +} + +#[derive(Debug)] +pub enum ColumnData { + View(ViewData), + Table(TableData), +} + +impl ColumnData { + pub fn table_name(&self) -> &TableName { + match &self { + Self::Table(table) => &table.name, + Self::View(view) => &view.name, + } + } + + pub fn columns(&self) -> &Vec { + match self { + Self::Table(table) => &table.column_data, + Self::View(view) => &view.column_data, + } + } + + pub fn comment(&self) -> &Option { + match self { + Self::Table(table) => &table.comment, + Self::View(view) => &view.comment, + } + } +} + +impl Display for SupportedColumnStructures { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let format = match self { + Self::Table => "BASE TABLE", + Self::View => "VIEW", + }; + write!(f, "{format}") + } +} + +impl FromStr for SupportedColumnStructures { + type Err = (); + fn from_str(s: &str) -> Result { + match s { + "BASE TABLE" => Ok(Self::Table), + "VIEW" => Ok(Self::View), + _ => unreachable!("This should never happen. Read {s}"), + } + } +} + +impl SupportedColumnStructures { + pub fn display_all() -> Vec { + SupportedColumnStructures::all() + .into_iter() + .map(|s| s.to_string()) + .collect() + } + pub fn all() -> Vec { + vec![Self::View, Self::Table] + } +} diff --git a/diesel_cli/src/infer_schema_internals/inference.rs b/diesel_cli/src/infer_schema_internals/inference.rs index 2e95ebbb5c1f..f7d8035dc562 100644 --- a/diesel_cli/src/infer_schema_internals/inference.rs +++ b/diesel_cli/src/infer_schema_internals/inference.rs @@ -111,7 +111,7 @@ pub fn rust_name_for_sql_name(sql_name: &str) -> String { pub fn load_table_names( connection: &mut InferConnection, schema_name: Option<&str>, -) -> Result, crate::errors::Error> { +) -> Result, crate::errors::Error> { let tables = match connection { #[cfg(feature = "sqlite")] InferConnection::Sqlite(ref mut c) => super::sqlite::load_table_names(c, schema_name), @@ -129,10 +129,24 @@ pub fn load_table_names( Ok(tables) } -pub fn filter_table_names(table_names: Vec, table_filter: &Filtering) -> Vec { +pub fn filter_column_structure( + table_names: &[(SupportedColumnStructures, TableName)], + structure: SupportedColumnStructures, +) -> Vec { + table_names + .iter() + .filter_map(|(s, t)| if *s == structure { Some(t) } else { None }) + .cloned() + .collect() +} + +pub fn filter_table_names( + table_names: Vec<(SupportedColumnStructures, TableName)>, + table_filter: &Filtering, +) -> Vec<(SupportedColumnStructures, TableName)> { table_names .into_iter() - .filter(|t| !table_filter.should_ignore_table(t)) + .filter(|(_, t)| !table_filter.should_ignore_table(t)) .collect::<_>() } @@ -261,26 +275,32 @@ pub fn load_foreign_key_constraints( } #[tracing::instrument(skip(connection))] -pub fn load_table_data( +fn load_column_structure_data( connection: &mut InferConnection, - name: TableName, + name: &TableName, config: &PrintSchema, -) -> Result { + primary_key: Option<&Vec>, +) -> Result<(Option, Vec), crate::errors::Error> { // No point in loading table comments if they are not going to be displayed let table_comment = match config.with_docs { DocConfig::NoDocComments => None, DocConfig::OnlyDatabaseComments | DocConfig::DatabaseCommentsFallbackToAutoGeneratedDocComment => { - get_table_comment(connection, &name)? + get_table_comment(connection, name)? } }; - let primary_key = get_primary_keys(connection, &name)?; - - let column_data = get_column_information(connection, &name, &config.column_sorting)? + get_column_information(connection, name, &config.column_sorting)? .into_iter() .map(|c| { - let ty = determine_column_type(&c, connection, &name, &primary_key, config)?; + let default_pk = vec![]; + let ty = determine_column_type( + &c, + connection, + name, + primary_key.unwrap_or(&default_pk), + config, + )?; let ColumnInformation { column_name, @@ -296,7 +316,19 @@ pub fn load_table_data( comment, }) }) - .collect::>()?; + .collect::, crate::errors::Error>>() + .map(|data| (table_comment, data)) +} + +#[tracing::instrument(skip(connection))] +pub fn load_table_data( + connection: &mut InferConnection, + name: TableName, + config: &PrintSchema, +) -> Result { + let primary_key = get_primary_keys(connection, &name)?; + let (table_comment, column_data) = + load_column_structure_data(connection, &name, config, Some(&primary_key))?; let primary_key = primary_key .iter() @@ -310,3 +342,17 @@ pub fn load_table_data( comment: table_comment, }) } + +#[tracing::instrument(skip(connection))] +pub fn load_view_data( + connection: &mut InferConnection, + name: TableName, + config: &PrintSchema, +) -> Result { + let (table_comment, column_data) = load_column_structure_data(connection, &name, config, None)?; + Ok(ViewData { + name, + column_data, + comment: table_comment, + }) +} diff --git a/diesel_cli/src/infer_schema_internals/information_schema.rs b/diesel_cli/src/infer_schema_internals/information_schema.rs index 44d8775ac900..5482408dc3ce 100644 --- a/diesel_cli/src/infer_schema_internals/information_schema.rs +++ b/diesel_cli/src/infer_schema_internals/information_schema.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::str::FromStr; use diesel::backend::Backend; use diesel::connection::LoadConnection; @@ -12,6 +13,8 @@ use diesel::pg::Pg; use diesel::query_builder::QueryFragment; use diesel::*; +use crate::infer_schema_internals::SupportedColumnStructures; + use self::information_schema::{key_column_usage, table_constraints, tables}; use super::inference; use super::table_data::TableName; @@ -140,7 +143,7 @@ where pub fn load_table_names<'a, Conn>( connection: &mut Conn, schema_name: Option<&'a str>, -) -> Result, crate::errors::Error> +) -> Result, crate::errors::Error> where Conn: LoadConnection, Conn::Backend: DefaultSchema + 'static, @@ -148,12 +151,12 @@ where Filter< Filter< Filter< - Select, + Select, Eq>, >, NotLike, >, - Like, + EqAny>, >: QueryFragment, Conn::Backend: QueryMetadata, { @@ -165,20 +168,24 @@ where .unwrap_or_else(|| Cow::Owned(default_schema.clone())); let mut table_names = tables - .select(table_name) + .select((table_name, table_type)) .filter(table_schema.eq(db_schema_name)) .filter(table_name.not_like("\\_\\_%")) - .filter(table_type.like("BASE TABLE")) - .load::(connection)?; + .filter(table_type.eq_any(SupportedColumnStructures::display_all())) + .load::<(String, String)>(connection)?; table_names.sort_unstable(); Ok(table_names .into_iter() - .map(|name| TableName { - rust_name: inference::rust_name_for_sql_name(&name), - sql_name: name, - schema: schema_name - .filter(|&schema| schema != default_schema) - .map(|schema| schema.to_owned()), + .map(|(name, tpy)| { + let tpy = SupportedColumnStructures::from_str(&tpy).expect("This should never happen."); + let data = TableName { + rust_name: inference::rust_name_for_sql_name(&name), + sql_name: name, + schema: schema_name + .filter(|&schema| schema != default_schema) + .map(|schema| schema.to_owned()), + }; + (tpy, data) }) .collect()) } @@ -203,7 +210,7 @@ mod tests { } #[test] - fn skip_views() { + fn include_views() { let mut connection = connection(); diesel::sql_query("CREATE TABLE a_regular_table (id SERIAL PRIMARY KEY)") @@ -213,10 +220,14 @@ mod tests { .execute(&mut connection) .unwrap(); - let table_names = load_table_names(&mut connection, None).unwrap(); + let table_names = load_table_names(&mut connection, None) + .unwrap() + .into_iter() + .map(|(_, table)| table) + .collect::>(); assert!(table_names.contains(&TableName::from_name("a_regular_table"))); - assert!(!table_names.contains(&TableName::from_name("a_view"))); + assert!(table_names.contains(&TableName::from_name("a_view"))); } #[test] @@ -229,12 +240,16 @@ mod tests { .unwrap(); let table_names = load_table_names(&mut connection, None).unwrap(); - for TableName { schema, .. } in &table_names { + for (_, TableName { schema, .. }) in &table_names { assert_eq!(None, *schema); } - assert!(table_names.contains(&TableName::from_name( - "load_table_names_loads_from_public_schema_if_none_given", - ),)); + assert!(table_names + .into_iter() + .map(|(_, table)| table) + .collect::>() + .contains(&TableName::from_name( + "load_table_names_loads_from_public_schema_if_none_given", + ),)); } #[test] @@ -248,14 +263,22 @@ mod tests { .execute(&mut connection) .unwrap(); - let table_names = load_table_names(&mut connection, Some("test_schema")).unwrap(); + let table_names = load_table_names(&mut connection, Some("test_schema")) + .unwrap() + .into_iter() + .map(|(_, table)| table) + .collect::>(); assert_eq!(vec![TableName::new("table_1", "test_schema")], table_names); diesel::sql_query("CREATE TABLE test_schema.table_2 (id SERIAL PRIMARY KEY)") .execute(&mut connection) .unwrap(); - let table_names = load_table_names(&mut connection, Some("test_schema")).unwrap(); + let table_names = load_table_names(&mut connection, Some("test_schema")) + .unwrap() + .into_iter() + .map(|(_, table)| table) + .collect::>(); let expected = vec![ TableName::new("table_1", "test_schema"), TableName::new("table_2", "test_schema"), @@ -269,13 +292,21 @@ mod tests { .execute(&mut connection) .unwrap(); - let table_names = load_table_names(&mut connection, Some("test_schema")).unwrap(); + let table_names = load_table_names(&mut connection, Some("test_schema")) + .unwrap() + .into_iter() + .map(|(_, table)| table) + .collect::>(); let expected = vec![ TableName::new("table_1", "test_schema"), TableName::new("table_2", "test_schema"), ]; assert_eq!(expected, table_names); - let table_names = load_table_names(&mut connection, Some("other_test_schema")).unwrap(); + let table_names = load_table_names(&mut connection, Some("other_test_schema")) + .unwrap() + .into_iter() + .map(|(_, table)| table) + .collect::>(); assert_eq!( vec![TableName::new("table_1", "other_test_schema")], table_names @@ -301,7 +332,7 @@ mod tests { let table_names = load_table_names(&mut connection, Some("test_schema")) .unwrap() .iter() - .map(|table| table.to_string()) + .map(|(_, table)| table.to_string()) .collect::>(); assert_eq!( vec!["test_schema.aaa", "test_schema.bbb", "test_schema.ccc"], diff --git a/diesel_cli/src/infer_schema_internals/sqlite.rs b/diesel_cli/src/infer_schema_internals/sqlite.rs index c89a9ffdfd56..29a5d3057ae3 100644 --- a/diesel_cli/src/infer_schema_internals/sqlite.rs +++ b/diesel_cli/src/infer_schema_internals/sqlite.rs @@ -34,14 +34,13 @@ table! { pub fn load_table_names( connection: &mut SqliteConnection, schema_name: Option<&str>, -) -> Result, crate::errors::Error> { +) -> Result, crate::errors::Error> { use self::sqlite_master::dsl::*; if schema_name.is_some() { return Err(crate::errors::Error::InvalidSqliteSchema); } - - Ok(sqlite_master + let tables = sqlite_master .select(name) .filter(name.not_like("\\_\\_%").escape('\\')) .filter(name.not_like("sqlite%")) @@ -49,8 +48,23 @@ pub fn load_table_names( .order(name) .load::(connection)? .into_iter() - .map(TableName::from_name) - .collect()) + .map(|table| { + ( + SupportedColumnStructures::Table, + TableName::from_name(table), + ) + }); + let view = sqlite_master + .select(name) + .filter(name.not_like("\\_\\_%").escape('\\')) + .filter(name.not_like("sqlite%")) + .filter(sql::("type='view'")) + .order(name) + .load::(connection)? + .into_iter() + .map(|table| (SupportedColumnStructures::View, TableName::from_name(table))); + + Ok(tables.chain(view).collect()) } pub fn load_foreign_key_constraints( @@ -60,7 +74,7 @@ pub fn load_foreign_key_constraints( let tables = load_table_names(connection, schema_name)?; let rows = tables .into_iter() - .map(|child_table| { + .map(|(_, child_table)| { let query = format!("PRAGMA FOREIGN_KEY_LIST('{}')", child_table.sql_name); sql::(&query) .load::(connection)? @@ -413,7 +427,11 @@ fn load_table_names_returns_nothing_when_no_tables_exist() { let mut conn = SqliteConnection::establish(":memory:").unwrap(); assert_eq!( Vec::::new(), - load_table_names(&mut conn, None).unwrap() + load_table_names(&mut conn, None) + .unwrap() + .into_iter() + .map(|(_, table)| table) + .collect::>() ); } @@ -423,7 +441,11 @@ fn load_table_names_includes_tables_that_exist() { diesel::sql_query("CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT)") .execute(&mut conn) .unwrap(); - let table_names = load_table_names(&mut conn, None).unwrap(); + let table_names = load_table_names(&mut conn, None) + .unwrap() + .into_iter() + .map(|(_, table)| table) + .collect::>(); assert!(table_names.contains(&TableName::from_name("users"))); } @@ -433,7 +455,11 @@ fn load_table_names_excludes_diesel_metadata_tables() { diesel::sql_query("CREATE TABLE __diesel_metadata (id INTEGER PRIMARY KEY AUTOINCREMENT)") .execute(&mut conn) .unwrap(); - let table_names = load_table_names(&mut conn, None).unwrap(); + let table_names = load_table_names(&mut conn, None) + .unwrap() + .into_iter() + .map(|(_, table)| table) + .collect::>(); assert!(!table_names.contains(&TableName::from_name("__diesel_metadata"))); } @@ -446,12 +472,16 @@ fn load_table_names_excludes_sqlite_metadata_tables() { diesel::sql_query("CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT)") .execute(&mut conn) .unwrap(); - let table_names = load_table_names(&mut conn, None); - assert_eq!(vec![TableName::from_name("users")], table_names.unwrap()); + let table_names = load_table_names(&mut conn, None) + .unwrap() + .into_iter() + .map(|(_, table)| table) + .collect::>(); + assert_eq!(vec![TableName::from_name("users")], table_names); } #[test] -fn load_table_names_excludes_views() { +fn load_table_names_includes_views() { let mut conn = SqliteConnection::establish(":memory:").unwrap(); diesel::sql_query("CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT)") .execute(&mut conn) @@ -459,8 +489,18 @@ fn load_table_names_excludes_views() { diesel::sql_query("CREATE VIEW answer AS SELECT 42") .execute(&mut conn) .unwrap(); - let table_names = load_table_names(&mut conn, None); - assert_eq!(vec![TableName::from_name("users")], table_names.unwrap()); + let table_names = load_table_names(&mut conn, None) + .unwrap() + .into_iter() + .map(|(_, table)| table) + .collect::>(); + assert_eq!( + vec![ + TableName::from_name("users"), + TableName::from_name("answer") + ], + table_names + ); } #[test] @@ -494,7 +534,7 @@ fn load_table_names_output_is_ordered() { let table_names = load_table_names(&mut conn, None) .unwrap() .iter() - .map(|table| table.to_string()) + .map(|(_, table)| table.to_string()) .collect::>(); assert_eq!(vec!["aaa", "bbb", "ccc"], table_names); } diff --git a/diesel_cli/src/infer_schema_internals/table_data.rs b/diesel_cli/src/infer_schema_internals/table_data.rs index bb4d2b4ee106..ac0d62775157 100644 --- a/diesel_cli/src/infer_schema_internals/table_data.rs +++ b/diesel_cli/src/infer_schema_internals/table_data.rs @@ -98,6 +98,13 @@ pub struct TableData { pub comment: Option, } +#[derive(Debug)] +pub struct ViewData { + pub name: TableName, + pub column_data: Vec, + pub comment: Option, +} + mod serde_impls { extern crate serde; diff --git a/diesel_cli/src/main.rs b/diesel_cli/src/main.rs index 77767ae06bbc..45c660bcc901 100644 --- a/diesel_cli/src/main.rs +++ b/diesel_cli/src/main.rs @@ -52,6 +52,9 @@ fn inner_main() -> Result<(), crate::errors::Error> { let filter = EnvFilter::from_default_env(); let fmt = tracing_subscriber::fmt::layer(); + // let stdout_log = tracing_subscriber::fmt::layer() + // .pretty(); + tracing_subscriber::Registry::default() .with(filter) .with(fmt) diff --git a/diesel_cli/src/migrations/diff_schema.rs b/diesel_cli/src/migrations/diff_schema.rs index 113c73fdcc8c..551737e1a902 100644 --- a/diesel_cli/src/migrations/diff_schema.rs +++ b/diesel_cli/src/migrations/diff_schema.rs @@ -2,7 +2,7 @@ use clap::ArgMatches; use diesel::backend::Backend; use diesel::query_builder::QueryBuilder; use diesel::QueryResult; -use diesel_table_macro_syntax::{ColumnDef, TableDecl}; +use diesel_table_macro_syntax::{ColumnDef, TableDecl, ViewDecl}; use std::collections::HashMap; use std::path::Path; use syn::visit::Visit; @@ -11,7 +11,7 @@ use crate::config::PrintSchema; use crate::database::InferConnection; use crate::infer_schema_internals::{ filter_table_names, load_table_names, ColumnDefinition, ColumnType, ForeignKeyConstraint, - TableData, TableName, + SupportedColumnStructures, TableData, TableName, }; use crate::print_schema::{ColumnSorting, DocConfig}; @@ -78,8 +78,8 @@ pub fn generate_sql_based_on_diff_schema( .map(ToString::to_string) .collect::>() }); - table_pk_key_list.insert(t.table_name.to_string(), keys); - expected_schema_map.insert(t.table_name.to_string(), t); + table_pk_key_list.insert(t.view.table_name.to_string(), keys); + expected_schema_map.insert(t.view.table_name.to_string(), t); } config.with_docs = DocConfig::NoDocComments; config.column_sorting = ColumnSorting::OrdinalPosition; @@ -99,99 +99,74 @@ pub fn generate_sql_based_on_diff_schema( let mut schema_diff = Vec::new(); let table_names = load_table_names(&mut conn, None)?; let tables_from_database = filter_table_names(table_names.clone(), &config.filter); - for table in tables_from_database { + for (structure, table) in tables_from_database { tracing::info!(?table, "Diff for existing table"); - let columns = - crate::infer_schema_internals::load_table_data(&mut conn, table.clone(), &config)?; - if let Some(t) = expected_schema_map.remove(&table.sql_name.to_lowercase()) { - tracing::info!(table = ?t.sql_name, "Table exists in schema.rs"); - let mut primary_keys_in_db = - crate::infer_schema_internals::get_primary_keys(&mut conn, &table)?; - primary_keys_in_db.sort(); - let mut primary_keys_in_schema = t - .primary_keys - .map(|pk| pk.keys.iter().map(|k| k.to_string()).collect::>()) - .unwrap_or_else(|| vec!["id".into()]); - primary_keys_in_schema.sort(); - if primary_keys_in_db != primary_keys_in_schema { - tracing::debug!( - ?primary_keys_in_schema, - ?primary_keys_in_db, - "Primary keys changed" - ); - return Err(crate::errors::Error::UnsupportedFeature( - "Cannot change primary keys with --diff-schema yet".into(), - )); - } - - let mut expected_column_map = t - .column_defs - .into_iter() - .map(|c| (c.sql_name.to_lowercase(), c)) - .collect::>(); - - let mut added_columns = Vec::new(); - let mut removed_columns = Vec::new(); - let mut changed_columns = Vec::new(); - - for c in columns.column_data { - if let Some(def) = expected_column_map.remove(&c.sql_name.to_lowercase()) { - let tpe = ColumnType::for_column_def(&def)?; - if !is_same_type(&c.ty, tpe) { - tracing::info!(old = ?c, new = ?def.sql_name, "Column changed type"); - changed_columns.push((c, def)); + match structure { + SupportedColumnStructures::Table => { + let columns = crate::infer_schema_internals::load_table_data( + &mut conn, + table.clone(), + &config, + )?; + if let Some(TableDecl { primary_keys, view }) = + expected_schema_map.remove(&table.sql_name.to_lowercase()) + { + tracing::info!(table = ?view.sql_name, "Table exists in schema.rs"); + let mut primary_keys_in_db = + crate::infer_schema_internals::get_primary_keys(&mut conn, &table)?; + primary_keys_in_db.sort(); + let mut primary_keys_in_schema = primary_keys + .map(|pk| pk.keys.iter().map(|k| k.to_string()).collect::>()) + .unwrap_or_else(|| vec!["id".into()]); + primary_keys_in_schema.sort(); + if primary_keys_in_db != primary_keys_in_schema { + tracing::debug!( + ?primary_keys_in_schema, + ?primary_keys_in_db, + "Primary keys changed" + ); + return Err(crate::errors::Error::UnsupportedFeature( + "Cannot change primary keys with --diff-schema yet".into(), + )); } + schema_diff.push(update_columns(view, columns.column_data)?); } else { - tracing::info!(column = ?c, "Column was removed"); - removed_columns.push(c); + tracing::info!("Table does not exist yet"); + let foreign_keys = foreign_key_map + .get(&table.rust_name) + .cloned() + .unwrap_or_default(); + if foreign_keys.iter().any(|fk| { + fk.foreign_key_columns.len() != 1 || fk.primary_key_columns.len() != 1 + }) { + return Err(crate::errors::Error::UnsupportedFeature( + "Tables with composite foreign keys are not supported by --diff-schema" + .into(), + )); + } + schema_diff.push(SchemaDiff::DropTable { + table, + columns, + foreign_keys, + }); } } - - if !expected_column_map.is_empty() { - let columns = expected_column_map - .values() - .map(|v| v.column_name.to_string()) - .collect::>(); - tracing::info!(added = ?columns, "Added columns"); - } - added_columns.extend(expected_column_map.into_values()); - - schema_diff.push(SchemaDiff::ChangeTable { - table: t.sql_name, - added_columns, - removed_columns, - changed_columns, - }); - } else { - tracing::info!("Table does not exist yet"); - let foreign_keys = foreign_key_map - .get(&table.rust_name) - .cloned() - .unwrap_or_default(); - if foreign_keys - .iter() - .any(|fk| fk.foreign_key_columns.len() != 1 || fk.primary_key_columns.len() != 1) - { + SupportedColumnStructures::View => { return Err(crate::errors::Error::UnsupportedFeature( - "Tables with composite foreign keys are not supported by --diff-schema".into(), + "Views are not supported by --diff-schema yet".into(), )); } - schema_diff.push(SchemaDiff::DropTable { - table, - columns, - foreign_keys, - }); } } schema_diff.extend(expected_schema_map.into_values().map(|t| { - tracing::info!(table = ?t.sql_name, "Tables does not exist in database"); + tracing::info!(table = ?t.view.sql_name, "Tables does not exist in database"); let foreign_keys = expected_fk_map - .remove(&t.table_name.to_string()) + .remove(&t.view.table_name.to_string()) .unwrap_or_default() .into_iter() .filter_map(|j| { - let referenced_table = table_pk_key_list.get(&t.table_name.to_string())?; + let referenced_table = table_pk_key_list.get(&t.view.table_name.to_string())?; match referenced_table { None => Some((j, "id".into())), Some(pks) if pks.len() == 1 => Some((j, pks.first()?.to_string())), @@ -259,6 +234,50 @@ pub fn generate_sql_based_on_diff_schema( Ok((up_sql, down_sql)) } +fn update_columns( + view: ViewDecl, + columns: Vec, +) -> Result { + let mut expected_column_map = view + .column_defs + .into_iter() + .map(|c| (c.sql_name.to_lowercase(), c)) + .collect::>(); + + let mut added_columns = Vec::new(); + let mut removed_columns = Vec::new(); + let mut changed_columns = Vec::new(); + + for c in columns { + if let Some(def) = expected_column_map.remove(&c.sql_name.to_lowercase()) { + let tpe = ColumnType::for_column_def(&def)?; + if !is_same_type(&c.ty, tpe) { + tracing::info!(old = ?c, new = ?def.sql_name, "Column changed type"); + changed_columns.push((c, def)); + } + } else { + tracing::info!(column = ?c, "Column was removed"); + removed_columns.push(c); + } + } + + if !expected_column_map.is_empty() { + let columns = expected_column_map + .values() + .map(|v| v.column_name.to_string()) + .collect::>(); + tracing::info!(added = ?columns, "Added columns"); + } + added_columns.extend(expected_column_map.into_values()); + + Ok(SchemaDiff::ChangeTable { + table: view.sql_name, + added_columns, + removed_columns, + changed_columns, + }) +} + fn is_same_type(ty: &ColumnType, tpe: ColumnType) -> bool { if ty.is_array != tpe.is_array || ty.is_nullable != tpe.is_nullable @@ -329,13 +348,14 @@ impl SchemaDiff { to_create, foreign_keys, } => { - let table = &to_create.sql_name.to_lowercase(); + let table = &to_create.view.sql_name.to_lowercase(); let primary_keys = to_create .primary_keys .as_ref() .map(|keys| keys.keys.iter().map(|k| k.to_string()).collect()) .unwrap_or_else(|| vec![String::from("id")]); let column_data = to_create + .view .column_defs .iter() .map(|c| { @@ -441,7 +461,7 @@ impl SchemaDiff { )?; } SchemaDiff::CreateTable { to_create, .. } => { - generate_drop_table(query_builder, &to_create.sql_name.to_lowercase())?; + generate_drop_table(query_builder, &to_create.view.sql_name.to_lowercase())?; } SchemaDiff::ChangeTable { table, diff --git a/diesel_cli/src/print_schema.rs b/diesel_cli/src/print_schema.rs index d5b1d6e4a024..20cf7c816c23 100644 --- a/diesel_cli/src/print_schema.rs +++ b/diesel_cli/src/print_schema.rs @@ -156,18 +156,28 @@ pub fn output_schema( ); let foreign_keys = load_foreign_key_constraints(connection, config.schema_name())?; - let foreign_keys = - remove_unsafe_foreign_keys_for_codegen(connection, &foreign_keys, &table_names); - let table_data = table_names - .into_iter() - .map(|t| load_table_data(connection, t, config)) - .collect::, crate::errors::Error>>()?; + let foreign_keys = remove_unsafe_foreign_keys_for_codegen( + connection, + &foreign_keys, + &filter_column_structure(&table_names, SupportedColumnStructures::Table), + ); let mut out = String::new(); writeln!(out, "{SCHEMA_HEADER}")?; - let backend = Backend::for_connection(connection); + let data = table_names + .into_iter() + .map(|(kind, t)| match kind { + SupportedColumnStructures::Table => { + Ok(ColumnData::Table(load_table_data(connection, t, config)?)) + } + SupportedColumnStructures::View => { + Ok(ColumnData::View(load_view_data(connection, t, config)?)) + } + }) + .collect::, crate::errors::Error>>()?; + let columns_custom_types = if config.generate_missing_sql_type_definitions() { let diesel_provided_types = match backend { #[cfg(feature = "postgres")] @@ -179,10 +189,9 @@ pub fn output_schema( }; Some( - table_data - .iter() - .map(|t| { - t.column_data + data.iter() + .map(|cd| { + cd.columns() .iter() .map(|c| { Some(&c.ty) @@ -208,7 +217,9 @@ pub fn output_schema( ColumnType { rust_name: format!( "{} {} {}", - &t.name.rust_name, &c.rust_name, &ty.rust_name + &cd.table_name().rust_name, + &c.rust_name, + &ty.rust_name ) .to_upper_camel_case(), ..ty.clone() @@ -224,8 +235,8 @@ pub fn output_schema( None }; - let definitions = TableDefinitions { - tables: table_data, + let definitions = ColumnStructureDefinitions { + data, fk_constraints: foreign_keys, with_docs: config.with_docs, custom_types_for_tables: columns_custom_types.map(|custom_types_sorted| { @@ -252,7 +263,7 @@ pub fn output_schema( "{}", CustomTypesForTablesForDisplay { custom_types: custom_types_for_tables, - tables: &definitions.tables + tables: &definitions.data } )?; } @@ -296,7 +307,7 @@ struct CustomTypesForTables { pub struct CustomTypesForTablesForDisplay<'a> { custom_types: &'a CustomTypesForTables, - tables: &'a [TableData], + tables: &'a [ColumnData], } #[allow(clippy::print_in_format_impl)] @@ -388,24 +399,28 @@ impl Display for CustomTypesForTablesForDisplay<'_> { } #[cfg(feature = "mysql")] Backend::Mysql => { - let mut types_to_generate: Vec<(&ColumnType, &TableName, &ColumnDefinition)> = self - .custom_types - .types_overrides_sorted - .iter() - .zip(self.tables) - .flat_map(|(ct, t)| { - ct.iter() - .zip(&t.column_data) - .map(move |(ct, c)| (ct, c, &t.name)) - }) - .filter_map(|(ct, c, t)| ct.as_ref().map(|ct| (ct, t, c))) - .collect(); + let CustomTypesForTables { + types_overrides_sorted, + with_docs, + derives, + .. + } = self.custom_types; + let mut types_to_generate: Vec<(&ColumnType, &TableName, &ColumnDefinition)> = + types_overrides_sorted + .iter() + .zip(self.tables) + .flat_map(|(ct, t)| { + ct.iter().zip(t.columns()).filter_map(move |(ct, c)| { + ct.as_ref().map(|ct| (ct, t.table_name(), c)) + }) + }) + .collect(); if types_to_generate.is_empty() { return Ok(()); } types_to_generate.sort_by_key(|(column_type, _, _)| column_type.rust_name.as_str()); - if self.custom_types.with_docs { + if *with_docs { writeln!(f, "/// A module containing custom SQL type definitions")?; writeln!(f, "///")?; writeln!(f, "/// (Automatically generated by Diesel.)")?; @@ -432,7 +447,7 @@ impl Display for CustomTypesForTablesForDisplay<'_> { writeln!(out, "/// (Automatically generated by Diesel.)")?; } - writeln!(out, "#[derive({})]", self.custom_types.derives.join(", "))?; + writeln!(out, "#[derive({})]", derives.join(", "))?; let mysql_name = { let mut c = custom_type.sql_name.chars(); @@ -454,7 +469,7 @@ impl Display for CustomTypesForTablesForDisplay<'_> { } } -struct ModuleDefinition<'a>(&'a str, TableDefinitions<'a>); +struct ModuleDefinition<'a>(&'a str, ColumnStructureDefinitions<'a>); impl<'a> Display for ModuleDefinition<'a> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { @@ -467,7 +482,7 @@ impl<'a> Display for ModuleDefinition<'a> { "{}", CustomTypesForTablesForDisplay { custom_types: custom_types_for_tables, - tables: &self.1.tables + tables: &self.1.data } )?; } @@ -478,18 +493,18 @@ impl<'a> Display for ModuleDefinition<'a> { } } -struct TableDefinitions<'a> { - tables: Vec, +struct ColumnStructureDefinitions<'a> { + data: Vec, fk_constraints: Vec, with_docs: DocConfig, import_types: Option<&'a [String]>, custom_types_for_tables: Option, } -impl<'a> Display for TableDefinitions<'a> { +impl<'a> Display for ColumnStructureDefinitions<'a> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let mut is_first = true; - for (table_idx, table) in self.tables.iter().enumerate() { + for (table_idx, table) in self.data.iter().enumerate() { if is_first { is_first = false; } else { @@ -498,7 +513,7 @@ impl<'a> Display for TableDefinitions<'a> { writeln!( f, "{}", - TableDefinition { + ColumnStructureDefinition { table, with_docs: self.with_docs, import_types: self.import_types, @@ -518,16 +533,16 @@ impl<'a> Display for TableDefinitions<'a> { writeln!(f, "{}", Joinable(foreign_key))?; } - if self.tables.len() > 1 { + if self.data.len() > 1 { write!(f, "\ndiesel::allow_tables_to_appear_in_same_query!(")?; { let mut out = PadAdapter::new(f); writeln!(out)?; - for table in &self.tables { - if table.name.rust_name == table.name.sql_name { - writeln!(out, "{},", table.name.sql_name)?; + for table in &self.data { + if table.table_name().rust_name == table.table_name().sql_name { + writeln!(out, "{},", table.table_name().sql_name)?; } else { - writeln!(out, "{},", table.name.rust_name)?; + writeln!(out, "{},", table.table_name().rust_name)?; } } } @@ -538,8 +553,8 @@ impl<'a> Display for TableDefinitions<'a> { } } -struct TableDefinition<'a> { - table: &'a TableData, +struct ColumnStructureDefinition<'a> { + table: &'a ColumnData, with_docs: DocConfig, import_types: Option<&'a [String]>, custom_type_overrides: Option<&'a [Option]>, @@ -553,9 +568,13 @@ fn write_doc_comments(out: &mut impl fmt::Write, doc: &str) -> fmt::Result { Ok(()) } -impl<'a> Display for TableDefinition<'a> { +impl<'a> Display for ColumnStructureDefinition<'a> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "diesel::table! {{")?; + match &self.table { + ColumnData::Table(_) => write!(f, "diesel::table! {{")?, + ColumnData::View(_) => write!(f, "diesel::view! {{")?, + } + { let mut out = PadAdapter::new(f); writeln!(out)?; @@ -595,17 +614,17 @@ impl<'a> Display for TableDefinition<'a> { writeln!(out)?; } - let full_sql_name = self.table.name.full_sql_name(); + let full_sql_name = self.table.table_name().full_sql_name(); match self.with_docs { DocConfig::NoDocComments => {} DocConfig::OnlyDatabaseComments => { - if let Some(comment) = self.table.comment.as_deref() { + if let Some(comment) = self.table.comment().as_deref() { write_doc_comments(&mut out, comment)?; } } DocConfig::DatabaseCommentsFallbackToAutoGeneratedDocComment => { - if let Some(comment) = self.table.comment.as_deref() { + if let Some(comment) = self.table.comment().as_deref() { write_doc_comments(&mut out, comment)?; } else { write_doc_comments( @@ -620,24 +639,28 @@ impl<'a> Display for TableDefinition<'a> { } } - if self.table.name.rust_name != self.table.name.sql_name { + if self.table.table_name().rust_name != self.table.table_name().sql_name { writeln!(out, r#"#[sql_name = "{full_sql_name}"]"#,)?; } - write!(out, "{} (", self.table.name)?; + write!(out, "{}", self.table.table_name())?; - for (i, pk) in self.table.primary_key.iter().enumerate() { - if i != 0 { - write!(out, ", ")?; + if let ColumnData::Table(t) = self.table { + write!(out, " (")?; + for (i, pk) in t.primary_key.iter().enumerate() { + if i != 0 { + write!(out, ", ")?; + } + write!(out, "{pk}")?; } - write!(out, "{pk}")?; + write!(out, ") ")?; } write!( out, - ") {}", + "{}", ColumnDefinitions { - columns: &self.table.column_data, + columns: self.table.columns(), with_docs: self.with_docs, table_full_sql_name: &full_sql_name, custom_type_overrides: self.custom_type_overrides diff --git a/diesel_compile_tests/tests/fail/copy_can_only_use_options_with_raw_variant.stderr b/diesel_compile_tests/tests/fail/copy_can_only_use_options_with_raw_variant.stderr index 3c9f82c63e61..7bfe14623498 100644 --- a/diesel_compile_tests/tests/fail/copy_can_only_use_options_with_raw_variant.stderr +++ b/diesel_compile_tests/tests/fail/copy_can_only_use_options_with_raw_variant.stderr @@ -26,12 +26,12 @@ error[E0599]: the method `load` exists for struct `CopyToQuery { - | ---------------------------- doesn't satisfy `_: RunQueryDsl<_>` or `_: Table` + | ---------------------------- doesn't satisfy `_: RunQueryDsl<_>` or `_: View` | = note: the following trait bounds were not satisfied: - `CopyToQuery: Table` + `CopyToQuery: View` which is required by `CopyToQuery: diesel::RunQueryDsl<_>` - `&CopyToQuery: Table` + `&CopyToQuery: View` which is required by `&CopyToQuery: diesel::RunQueryDsl<_>` - `&mut CopyToQuery: Table` + `&mut CopyToQuery: View` which is required by `&mut CopyToQuery: diesel::RunQueryDsl<_>` diff --git a/diesel_compile_tests/tests/fail/derive/aliases.stderr b/diesel_compile_tests/tests/fail/derive/aliases.stderr index 2a62944255fe..782fde551aea 100644 --- a/diesel_compile_tests/tests/fail/derive/aliases.stderr +++ b/diesel_compile_tests/tests/fail/derive/aliases.stderr @@ -1,8 +1,8 @@ -error[E0271]: type mismatch resolving `::Table == table` +error[E0271]: type mismatch resolving `::Source == table` --> tests/fail/derive/aliases.rs:36:22 | 36 | user_alias.field(posts::id); - | ----- ^^^^^^^^^ type mismatch resolving `::Table == table` + | ----- ^^^^^^^^^ type mismatch resolving `::Source == table` | | | required by a bound introduced by this call | @@ -39,8 +39,8 @@ note: required by a bound in `Alias::::field` | pub fn field(&self, field: F) -> AliasedField | ----- required by a bound in this associated function | where - | F: Column
, - | ^^^^^^^^^^^^^^^^^ required by this bound in `Alias::::field` + | F: Column, + | ^^^^^^^^^^^^^^^^^^ required by this bound in `Alias::::field` = note: this error originates in the macro `table` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0271]: type mismatch resolving `>::Output == Once` @@ -102,9 +102,9 @@ error[E0599]: the method `select` exists for struct `SelectStatement, Inner>, diesel::expression::grouped::Grouped>, NullableExpression>>>, Alias, Inner>, diesel::expression::grouped::Grouped>, NullableExpression>>>>>: Table` + `&SelectStatement, Inner>, diesel::expression::grouped::Grouped>, NullableExpression>>>, Alias, Inner>, diesel::expression::grouped::Grouped>, NullableExpression>>>>>: View` which is required by `&SelectStatement, Inner>, diesel::expression::grouped::Grouped>, NullableExpression>>>, Alias, Inner>, diesel::expression::grouped::Grouped>, NullableExpression>>>>>: diesel::QueryDsl` - `&mut SelectStatement, Inner>, diesel::expression::grouped::Grouped>, NullableExpression>>>, Alias, Inner>, diesel::expression::grouped::Grouped>, NullableExpression>>>>>: Table` + `&mut SelectStatement, Inner>, diesel::expression::grouped::Grouped>, NullableExpression>>>, Alias, Inner>, diesel::expression::grouped::Grouped>, NullableExpression>>>>>: View` which is required by `&mut SelectStatement, Inner>, diesel::expression::grouped::Grouped>, NullableExpression>>>, Alias, Inner>, diesel::expression::grouped::Grouped>, NullableExpression>>>>>: diesel::QueryDsl` error[E0277]: Cannot select `users::columns::id` from `Alias` @@ -274,7 +274,7 @@ error[E0599]: the method `select` exists for struct `SelectStatement, Alias, Inner>, diesel::expression::grouped::Grouped, AliasedField>>>>>: Table` + `&SelectStatement, Alias, Inner>, diesel::expression::grouped::Grouped, AliasedField>>>>>: View` which is required by `&SelectStatement, Alias, Inner>, diesel::expression::grouped::Grouped, AliasedField>>>>>: diesel::QueryDsl` - `&mut SelectStatement, Alias, Inner>, diesel::expression::grouped::Grouped, AliasedField>>>>>: Table` + `&mut SelectStatement, Alias, Inner>, diesel::expression::grouped::Grouped, AliasedField>>>>>: View` which is required by `&mut SelectStatement, Alias, Inner>, diesel::expression::grouped::Grouped, AliasedField>>>>>: diesel::QueryDsl` diff --git a/diesel_compile_tests/tests/fail/insert_cannot_reference_columns_from_other_table.stderr b/diesel_compile_tests/tests/fail/insert_cannot_reference_columns_from_other_table.stderr index 755c654edded..5a9f58cbeb10 100644 --- a/diesel_compile_tests/tests/fail/insert_cannot_reference_columns_from_other_table.stderr +++ b/diesel_compile_tests/tests/fail/insert_cannot_reference_columns_from_other_table.stderr @@ -1,8 +1,8 @@ -error[E0271]: type mismatch resolving `::Table == table` +error[E0271]: type mismatch resolving `::Source == table` --> tests/fail/insert_cannot_reference_columns_from_other_table.rs:22:18 | 22 | .values(&posts::id.eq(1)); - | ------ ^^^^^^^^^^^^^^^ type mismatch resolving `::Table == table` + | ------ ^^^^^^^^^^^^^^^ type mismatch resolving `::Source == table` | | | required by a bound introduced by this call | @@ -114,11 +114,11 @@ note: required by a bound in `IncompleteInsertStatement::::values` = note: consider using `--verbose` to print the full type name to the console = note: this error originates in the macro `table` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0271]: type mismatch resolving `::Table == table` +error[E0271]: type mismatch resolving `::Source == table` --> tests/fail/insert_cannot_reference_columns_from_other_table.rs:25:18 | 25 | .values(&(posts::id.eq(1), users::id.eq(2))); - | ------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type mismatch resolving `::Table == table` + | ------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type mismatch resolving `::Source == table` | | | required by a bound introduced by this call | diff --git a/diesel_compile_tests/tests/fail/mysql_on_conflict_tests.stderr b/diesel_compile_tests/tests/fail/mysql_on_conflict_tests.stderr index 787ccc26f3f7..0b1366b2cde0 100644 --- a/diesel_compile_tests/tests/fail/mysql_on_conflict_tests.stderr +++ b/diesel_compile_tests/tests/fail/mysql_on_conflict_tests.stderr @@ -69,14 +69,14 @@ error[E0277]: the trait bound `diesel::query_builder::upsert::on_conflict_target | required by a bound introduced by this call | = help: the following other types implement trait `diesel::query_builder::upsert::on_conflict_target::OnConflictTarget
`: - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> and $N others note: required by a bound in `diesel::upsert::on_conflict_extension::>::on_conflict` --> $DIESEL/src/upsert/on_conflict_extension.rs @@ -215,14 +215,14 @@ error[E0277]: the trait bound `diesel::query_builder::upsert::on_conflict_target | required by a bound introduced by this call | = help: the following other types implement trait `diesel::query_builder::upsert::on_conflict_target::OnConflictTarget
`: - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> and $N others note: required by a bound in `diesel::upsert::on_conflict_extension::>::on_conflict` --> $DIESEL/src/upsert/on_conflict_extension.rs @@ -277,14 +277,14 @@ error[E0277]: the trait bound `diesel::query_builder::upsert::on_conflict_target | required by a bound introduced by this call | = help: the following other types implement trait `diesel::query_builder::upsert::on_conflict_target::OnConflictTarget
`: - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> - as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Table>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> + as diesel::query_builder::upsert::on_conflict_target::OnConflictTarget<<_T as Column>::Source>> and $N others note: required by a bound in `diesel::upsert::on_conflict_extension::>::on_conflict` --> $DIESEL/src/upsert/on_conflict_extension.rs diff --git a/diesel_compile_tests/tests/fail/only_only_on_table.stderr b/diesel_compile_tests/tests/fail/only_only_on_table.stderr index 21696e399b5f..a6bd37dcb1b2 100644 --- a/diesel_compile_tests/tests/fail/only_only_on_table.stderr +++ b/diesel_compile_tests/tests/fail/only_only_on_table.stderr @@ -68,8 +68,8 @@ error[E0599]: the method `only` exists for struct `id`, but its trait bounds wer note: the trait `Table` must be implemented --> $DIESEL/src/query_source/mod.rs | - | pub trait Table: QuerySource + AsQuery + Sized { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | pub trait Table: View { + | ^^^^^^^^^^^^^^^^^^^^^ = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `only`, perhaps you need to implement it: candidate #1: `diesel::dsl::OnlyDsl` diff --git a/diesel_compile_tests/tests/fail/pg_on_conflict_requires_valid_conflict_target.stderr b/diesel_compile_tests/tests/fail/pg_on_conflict_requires_valid_conflict_target.stderr index 4a9b53401f22..3d4cfe3d63b0 100644 --- a/diesel_compile_tests/tests/fail/pg_on_conflict_requires_valid_conflict_target.stderr +++ b/diesel_compile_tests/tests/fail/pg_on_conflict_requires_valid_conflict_target.stderr @@ -1,8 +1,8 @@ -error[E0271]: type mismatch resolving `::Table == table` +error[E0271]: type mismatch resolving `::Source == table` --> tests/fail/pg_on_conflict_requires_valid_conflict_target.rs:38:22 | 38 | .on_conflict(posts::id); - | ----------- ^^^^^^^^^ type mismatch resolving `::Table == table` + | ----------- ^^^^^^^^^ type mismatch resolving `::Source == table` | | | required by a bound introduced by this call | diff --git a/diesel_compile_tests/tests/fail/pg_specific_expressions_cant_be_used_in_a_sqlite_query.stderr b/diesel_compile_tests/tests/fail/pg_specific_expressions_cant_be_used_in_a_sqlite_query.stderr index 4a6585135343..ccd86d9f02f4 100644 --- a/diesel_compile_tests/tests/fail/pg_specific_expressions_cant_be_used_in_a_sqlite_query.stderr +++ b/diesel_compile_tests/tests/fail/pg_specific_expressions_cant_be_used_in_a_sqlite_query.stderr @@ -101,13 +101,13 @@ error[E0599]: the method `execute` exists for struct `IncompleteOnConflict { - | --------------------------------------------- doesn't satisfy `_: RunQueryDsl<_>` or `_: Table` + | --------------------------------------------- doesn't satisfy `_: RunQueryDsl<_>` or `_: View` | = note: consider using `--verbose` to print the full type name to the console = note: the following trait bounds were not satisfied: - `IncompleteOnConflict>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>>: Table` + `IncompleteOnConflict>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>>: View` which is required by `IncompleteOnConflict>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>>: diesel::RunQueryDsl<_>` - `&IncompleteOnConflict>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>>: Table` + `&IncompleteOnConflict>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>>: View` which is required by `&IncompleteOnConflict>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>>: diesel::RunQueryDsl<_>` - `&mut IncompleteOnConflict>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>>: Table` + `&mut IncompleteOnConflict>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>>: View` which is required by `&mut IncompleteOnConflict>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>>: diesel::RunQueryDsl<_>` diff --git a/diesel_compile_tests/tests/fail/pg_upsert_do_update_requires_valid_update.stderr b/diesel_compile_tests/tests/fail/pg_upsert_do_update_requires_valid_update.stderr index b379a6fd1d43..eb2e4d0d5348 100644 --- a/diesel_compile_tests/tests/fail/pg_upsert_do_update_requires_valid_update.stderr +++ b/diesel_compile_tests/tests/fail/pg_upsert_do_update_requires_valid_update.stderr @@ -13,15 +13,15 @@ error[E0599]: the method `execute` exists for struct `IncompleteDoUpdate { - | ------------------------------------------- doesn't satisfy `_: RunQueryDsl<_>` or `_: Table` + | ------------------------------------------- doesn't satisfy `_: RunQueryDsl<_>` or `_: View` | = note: consider using `--verbose` to print the full type name to the console = note: the following trait bounds were not satisfied: - `IncompleteDoUpdate>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>: Table` + `IncompleteDoUpdate>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>: View` which is required by `IncompleteDoUpdate>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>: diesel::RunQueryDsl<_>` - `&IncompleteDoUpdate>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>: Table` + `&IncompleteDoUpdate>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>: View` which is required by `&IncompleteDoUpdate>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>: diesel::RunQueryDsl<_>` - `&mut IncompleteDoUpdate>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>: Table` + `&mut IncompleteDoUpdate>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>: View` which is required by `&mut IncompleteDoUpdate>>,), users::table>>, diesel::query_builder::upsert::on_conflict_target::ConflictTarget>: diesel::RunQueryDsl<_>` error[E0271]: type mismatch resolving `>> as AsChangeset>::Target == table` @@ -84,11 +84,11 @@ note: required for `posts::columns::title` to implement `AppearsOnTable` to implement `AsChangeset` -error[E0271]: type mismatch resolving `::Table == table` +error[E0271]: type mismatch resolving `<title as Column>::Source == table` --> tests/fail/pg_upsert_do_update_requires_valid_update.rs:65:10 | 65 | .set(name.eq(excluded(posts::title))); - | ^^^ type mismatch resolving `<title as Column>::Table == table` + | ^^^ type mismatch resolving `<title as Column>::Source == table` | note: expected this to be `posts::table` --> tests/fail/pg_upsert_do_update_requires_valid_update.rs:16:9 diff --git a/diesel_derives/src/table.rs b/diesel_derives/src/expansion.rs similarity index 58% rename from diesel_derives/src/table.rs rename to diesel_derives/src/expansion.rs index 53545cb597c0..b7875bfbb993 100644 --- a/diesel_derives/src/table.rs +++ b/diesel_derives/src/expansion.rs @@ -1,47 +1,145 @@ -use diesel_table_macro_syntax::{ColumnDef, TableDecl}; +use diesel_table_macro_syntax::{ColumnDef, TableDecl, ViewDecl}; use proc_macro2::TokenStream; use syn::parse_quote; use syn::Ident; const DEFAULT_PRIMARY_KEY_NAME: &str = "id"; -pub(crate) fn expand(input: TableDecl) -> TokenStream { - if input.column_defs.len() > super::diesel_for_each_tuple::MAX_TUPLE_SIZE as usize { - let txt = if input.column_defs.len() > 128 { - "You reached the end. Diesel does not support tables with \ - more than 128 columns. Consider using less columns." - } else if input.column_defs.len() > 64 { - "Table contains more than 64 columns. Consider enabling the \ - `128-column-tables` feature to enable diesels support for \ - tables with more than 64 columns." - } else if input.column_defs.len() > 32 { - "Table contains more than 32 columns. Consider enabling the \ - `64-column-tables` feature to enable diesels support for \ - tables with more than 32 columns." - } else { - "Table contains more than 16 columns. Consider enabling the \ - `32-column-tables` feature to enable diesels support for \ - tables with more than 16 columns." - }; - return quote::quote! { - compile_error!(#txt); - }; +pub(crate) fn expand_view(input: ViewDecl) -> TokenStream { + if let Err(err) = check_too_many_columns(&input) { + return err; } let meta = &input.meta; let table_name = &input.table_name; - let imports = if input.use_statements.is_empty() { - vec![parse_quote!( - use diesel::sql_types::*; - )] + let imports = input.imports(); + + let static_query_fragment_impl_for_table = static_query_fragment_impl(&input); + + let backend_specific_table_impls = if cfg!(feature = "postgres") { + Some(quote::quote! { + impl<S> diesel::JoinTo<diesel::query_builder::Only<S>> for table + where + diesel::query_builder::Only<S>: diesel::JoinTo<table>, + { + type FromClause = diesel::query_builder::Only<S>; + type OnClause = <diesel::query_builder::Only<S> as diesel::JoinTo<table>>::OnClause; + + fn join_target(__diesel_internal_rhs: diesel::query_builder::Only<S>) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = diesel::query_builder::Only::<S>::join_target(table); + (__diesel_internal_rhs, __diesel_internal_on_clause) + } + } + + impl diesel::query_source::AppearsInFromClause<diesel::query_builder::Only<table>> + for table + { + type Count = diesel::query_source::Once; + } + + impl diesel::query_source::AppearsInFromClause<table> + for diesel::query_builder::Only<table> + { + type Count = diesel::query_source::Once; + } + + impl<S, TSM> diesel::JoinTo<diesel::query_builder::Tablesample<S, TSM>> for table + where + diesel::query_builder::Tablesample<S, TSM>: diesel::JoinTo<table>, + TSM: diesel::internal::table_macro::TablesampleMethod + { + type FromClause = diesel::query_builder::Tablesample<S, TSM>; + type OnClause = <diesel::query_builder::Tablesample<S, TSM> as diesel::JoinTo<table>>::OnClause; + + fn join_target(__diesel_internal_rhs: diesel::query_builder::Tablesample<S, TSM>) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = diesel::query_builder::Tablesample::<S, TSM>::join_target(table); + (__diesel_internal_rhs, __diesel_internal_on_clause) + } + } + + impl<TSM> diesel::query_source::AppearsInFromClause<diesel::query_builder::Tablesample<table, TSM>> + for table + where + TSM: diesel::internal::table_macro::TablesampleMethod + { + type Count = diesel::query_source::Once; + } + + impl<TSM> diesel::query_source::AppearsInFromClause<table> + for diesel::query_builder::Tablesample<table, TSM> + where + TSM: diesel::internal::table_macro::TablesampleMethod + { + type Count = diesel::query_source::Once; + } + }) } else { - input.use_statements.clone() + None }; - let column_names = input - .column_defs - .iter() - .map(|c| &c.column_name) - .collect::<Vec<_>>(); + + // let valid_grouping_for_table_columns = generate_valid_grouping_for_table_columns(&input); + let base_view_impl = base_struct_impl( + &input, + quote::quote! { + // #(#valid_grouping_for_table_columns)* + }, + ); + let reexport_column_from_dsl = input.column_defs.iter().map(|c| { + let column_name = &c.column_name; + if c.column_name == *table_name { + let span = c.column_name.span(); + let message = format!( + "Column `{column_name}` cannot be named the same as it's table.\n\ + You may use `#[sql_name = \"{column_name}\"]` to reference the table's \ + `{column_name}` column \n\ + Docs available at: `https://docs.diesel.rs/master/diesel/macro.table.html`\n" + ); + quote::quote_spanned! { span => + compile_error!(#message); + } + } else { + quote::quote! { + pub use super::columns::#column_name; + } + } + }); + + quote::quote! { + #(#meta)* + #[allow(unused_imports, dead_code, unreachable_pub)] + pub mod #table_name { + use ::diesel; + pub use self::columns::*; + use diesel::query_source::View; + + #(#imports)* + + /// Re-exports all of the columns of this table, as well as the + /// table struct renamed to the module name. This is meant to be + /// glob imported for functions which only deal with one table. + pub mod dsl { + #(#reexport_column_from_dsl)* + pub use super::table as #table_name; + } + + #base_view_impl + + #static_query_fragment_impl_for_table + + #backend_specific_table_impls + } + } +} + +pub(crate) fn expand_table(input: TableDecl) -> TokenStream { + if let Err(err) = check_too_many_columns(&input.view) { + return err; + } + + let meta = &input.view.meta; + let table_name = &input.view.table_name; + let imports = input.view.imports(); + let column_names = input.view.column_names(); let column_names = &column_names; let primary_key: TokenStream = match input.primary_keys.as_ref() { None if column_names.contains(&&syn::Ident::new( @@ -63,7 +161,7 @@ pub(crate) fn expand(input: TableDecl) -> TokenStream { column_names[0], ); message += &format!("\t{table_name} ({}) {{\n", &column_names[0]); - for c in &input.column_defs { + for c in &input.view.column_defs { let tpe = c .tpe .path @@ -76,7 +174,7 @@ pub(crate) fn expand(input: TableDecl) -> TokenStream { } message += "\t}\n}"; - let span = input.table_name.span(); + let span = input.view.table_name.span(); return quote::quote_spanned! {span=> compile_error!(#message); }; @@ -96,55 +194,7 @@ pub(crate) fn expand(input: TableDecl) -> TokenStream { } }; - let column_defs = input.column_defs.iter().map(expand_column_def); - let column_ty = input.column_defs.iter().map(|c| &c.tpe); - let valid_grouping_for_table_columns = generate_valid_grouping_for_table_columns(&input); - - let sql_name = &input.sql_name; - let static_query_fragment_impl_for_table = if let Some(schema) = input.schema { - let schema_name = schema.to_string(); - quote::quote! { - impl diesel::internal::table_macro::StaticQueryFragment for table { - type Component = diesel::internal::table_macro::InfixNode< - diesel::internal::table_macro::Identifier<'static>, - diesel::internal::table_macro::Identifier<'static>, - &'static str - >; - const STATIC_COMPONENT: &'static Self::Component = &diesel::internal::table_macro::InfixNode::new( - diesel::internal::table_macro::Identifier(#schema_name), - diesel::internal::table_macro::Identifier(#sql_name), - "." - ); - } - } - } else { - quote::quote! { - impl diesel::internal::table_macro::StaticQueryFragment for table { - type Component = diesel::internal::table_macro::Identifier<'static>; - const STATIC_COMPONENT: &'static Self::Component = &diesel::internal::table_macro::Identifier(#sql_name); - } - } - }; - - let reexport_column_from_dsl = input.column_defs.iter().map(|c| { - let column_name = &c.column_name; - if c.column_name == *table_name { - let span = c.column_name.span(); - let message = format!( - "Column `{column_name}` cannot be named the same as it's table.\n\ - You may use `#[sql_name = \"{column_name}\"]` to reference the table's \ - `{column_name}` column \n\ - Docs available at: `https://docs.diesel.rs/master/diesel/macro.table.html`\n" - ); - quote::quote_spanned! { span => - compile_error!(#message); - } - } else { - quote::quote! { - pub use super::columns::#column_name; - } - } - }); + let static_query_fragment_impl_for_table = static_query_fragment_impl(&input.view); let backend_specific_table_impls = if cfg!(feature = "postgres") { Some(quote::quote! { @@ -207,7 +257,32 @@ pub(crate) fn expand(input: TableDecl) -> TokenStream { None }; - let imports_for_column_module = imports.iter().map(fix_import_for_submodule); + let valid_grouping_for_table_columns = generate_valid_grouping_for_table_columns(&input); + let base_view_impl = base_struct_impl( + &input.view, + quote::quote! { + #(#valid_grouping_for_table_columns)* + }, + ); + let reexport_column_from_dsl = input.view.column_defs.iter().map(|c| { + let column_name = &c.column_name; + if c.column_name == *table_name { + let span = c.column_name.span(); + let message = format!( + "Column `{column_name}` cannot be named the same as it's table.\n\ + You may use `#[sql_name = \"{column_name}\"]` to reference the table's \ + `{column_name}` column \n\ + Docs available at: `https://docs.diesel.rs/master/diesel/macro.table.html`\n" + ); + quote::quote_spanned! { span => + compile_error!(#message); + } + } else { + quote::quote! { + pub use super::columns::#column_name; + } + } + }); quote::quote! { #(#meta)* @@ -215,6 +290,8 @@ pub(crate) fn expand(input: TableDecl) -> TokenStream { pub mod #table_name { use ::diesel; pub use self::columns::*; + use diesel::query_source::View; + #(#imports)* /// Re-exports all of the columns of this table, as well as the @@ -225,79 +302,16 @@ pub(crate) fn expand(input: TableDecl) -> TokenStream { pub use super::table as #table_name; } - #[allow(non_upper_case_globals, dead_code)] - /// A tuple of all of the columns on this table - pub const all_columns: (#(#column_names,)*) = (#(#column_names,)*); - - #[allow(non_camel_case_types)] - #[derive(Debug, Clone, Copy, diesel::query_builder::QueryId, Default)] - /// The actual table struct - /// - /// This is the type which provides the base methods of the query - /// builder, such as `.select` and `.filter`. - pub struct table; - - impl table { - #[allow(dead_code)] - /// Represents `table_name.*`, which is sometimes necessary - /// for efficient count queries. It cannot be used in place of - /// `all_columns` - pub fn star(&self) -> star { - star - } - } - - /// The SQL type of all of the columns on this table - pub type SqlType = (#(#column_ty,)*); - - /// Helper type for representing a boxed query from this table - pub type BoxedQuery<'a, DB, ST = SqlType> = diesel::internal::table_macro::BoxedSelectStatement<'a, ST, diesel::internal::table_macro::FromClause<table>, DB>; - - impl diesel::QuerySource for table { - type FromClause = diesel::internal::table_macro::StaticQueryFragmentInstance<table>; - type DefaultSelection = <Self as diesel::Table>::AllColumns; - - fn from_clause(&self) -> Self::FromClause { - diesel::internal::table_macro::StaticQueryFragmentInstance::new() - } - - fn default_selection(&self) -> Self::DefaultSelection { - use diesel::Table; - Self::all_columns() - } - } - - impl<DB> diesel::query_builder::QueryFragment<DB> for table where - DB: diesel::backend::Backend, - <table as diesel::internal::table_macro::StaticQueryFragment>::Component: diesel::query_builder::QueryFragment<DB> - { - fn walk_ast<'b>(&'b self, __diesel_internal_pass: diesel::query_builder::AstPass<'_, 'b, DB>) -> diesel::result::QueryResult<()> { - <table as diesel::internal::table_macro::StaticQueryFragment>::STATIC_COMPONENT.walk_ast(__diesel_internal_pass) - } - } + #base_view_impl #static_query_fragment_impl_for_table - impl diesel::query_builder::AsQuery for table { - type SqlType = SqlType; - type Query = diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<Self>>; - - fn as_query(self) -> Self::Query { - diesel::internal::table_macro::SelectStatement::simple(self) - } - } - impl diesel::Table for table { type PrimaryKey = #primary_key; - type AllColumns = (#(#column_names,)*); fn primary_key(&self) -> Self::PrimaryKey { #primary_key } - - fn all_columns() -> Self::AllColumns { - (#(#column_names,)*) - } } impl diesel::associations::HasTable for table { @@ -318,114 +332,6 @@ pub(crate) fn expand(input: TableDecl) -> TokenStream { } } - impl diesel::query_source::AppearsInFromClause<table> for table { - type Count = diesel::query_source::Once; - } - - // impl<S: AliasSource<Table=table>> AppearsInFromClause<table> for Alias<S> - impl<S> diesel::internal::table_macro::AliasAppearsInFromClause<S, table> for table - where S: diesel::query_source::AliasSource<Target=table>, - { - type Count = diesel::query_source::Never; - } - - // impl<S1: AliasSource<Table=table>, S2: AliasSource<Table=table>> AppearsInFromClause<Alias<S1>> for Alias<S2> - // Those are specified by the `alias!` macro, but this impl will allow it to implement this trait even in downstream - // crates from the schema - impl<S1, S2> diesel::internal::table_macro::AliasAliasAppearsInFromClause<table, S2, S1> for table - where S1: diesel::query_source::AliasSource<Target=table>, - S2: diesel::query_source::AliasSource<Target=table>, - S1: diesel::internal::table_macro::AliasAliasAppearsInFromClauseSameTable<S2, table>, - { - type Count = <S1 as diesel::internal::table_macro::AliasAliasAppearsInFromClauseSameTable<S2, table>>::Count; - } - - impl<S> diesel::query_source::AppearsInFromClause<diesel::query_source::Alias<S>> for table - where S: diesel::query_source::AliasSource, - { - type Count = diesel::query_source::Never; - } - - impl<S, C> diesel::internal::table_macro::FieldAliasMapperAssociatedTypesDisjointnessTrick<table, S, C> for table - where - S: diesel::query_source::AliasSource<Target = table> + ::std::clone::Clone, - C: diesel::query_source::Column<Table = table>, - { - type Out = diesel::query_source::AliasedField<S, C>; - - fn map(__diesel_internal_column: C, __diesel_internal_alias: &diesel::query_source::Alias<S>) -> Self::Out { - __diesel_internal_alias.field(__diesel_internal_column) - } - } - - impl diesel::query_source::AppearsInFromClause<table> for diesel::internal::table_macro::NoFromClause { - type Count = diesel::query_source::Never; - } - - impl<Left, Right, Kind> diesel::JoinTo<diesel::internal::table_macro::Join<Left, Right, Kind>> for table where - diesel::internal::table_macro::Join<Left, Right, Kind>: diesel::JoinTo<table>, - Left: diesel::query_source::QuerySource, - Right: diesel::query_source::QuerySource, - { - type FromClause = diesel::internal::table_macro::Join<Left, Right, Kind>; - type OnClause = <diesel::internal::table_macro::Join<Left, Right, Kind> as diesel::JoinTo<table>>::OnClause; - - fn join_target(__diesel_internal_rhs: diesel::internal::table_macro::Join<Left, Right, Kind>) -> (Self::FromClause, Self::OnClause) { - let (_, __diesel_internal_on_clause) = diesel::internal::table_macro::Join::join_target(table); - (__diesel_internal_rhs, __diesel_internal_on_clause) - } - } - - impl<Join, On> diesel::JoinTo<diesel::internal::table_macro::JoinOn<Join, On>> for table where - diesel::internal::table_macro::JoinOn<Join, On>: diesel::JoinTo<table>, - { - type FromClause = diesel::internal::table_macro::JoinOn<Join, On>; - type OnClause = <diesel::internal::table_macro::JoinOn<Join, On> as diesel::JoinTo<table>>::OnClause; - - fn join_target(__diesel_internal_rhs: diesel::internal::table_macro::JoinOn<Join, On>) -> (Self::FromClause, Self::OnClause) { - let (_, __diesel_internal_on_clause) = diesel::internal::table_macro::JoinOn::join_target(table); - (__diesel_internal_rhs, __diesel_internal_on_clause) - } - } - - impl<F, S, D, W, O, L, Of, G> diesel::JoinTo<diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<F>, S, D, W, O, L, Of, G>> for table where - diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<F>, S, D, W, O, L, Of, G>: diesel::JoinTo<table>, - F: diesel::query_source::QuerySource - { - type FromClause = diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<F>, S, D, W, O, L, Of, G>; - type OnClause = <diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<F>, S, D, W, O, L, Of, G> as diesel::JoinTo<table>>::OnClause; - - fn join_target(__diesel_internal_rhs: diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<F>, S, D, W, O, L, Of, G>) -> (Self::FromClause, Self::OnClause) { - let (_, __diesel_internal_on_clause) = diesel::internal::table_macro::SelectStatement::join_target(table); - (__diesel_internal_rhs, __diesel_internal_on_clause) - } - } - - impl<'a, QS, ST, DB> diesel::JoinTo<diesel::internal::table_macro::BoxedSelectStatement<'a, diesel::internal::table_macro::FromClause<QS>, ST, DB>> for table where - diesel::internal::table_macro::BoxedSelectStatement<'a, diesel::internal::table_macro::FromClause<QS>, ST, DB>: diesel::JoinTo<table>, - QS: diesel::query_source::QuerySource, - { - type FromClause = diesel::internal::table_macro::BoxedSelectStatement<'a, diesel::internal::table_macro::FromClause<QS>, ST, DB>; - type OnClause = <diesel::internal::table_macro::BoxedSelectStatement<'a, diesel::internal::table_macro::FromClause<QS>, ST, DB> as diesel::JoinTo<table>>::OnClause; - fn join_target(__diesel_internal_rhs: diesel::internal::table_macro::BoxedSelectStatement<'a, diesel::internal::table_macro::FromClause<QS>, ST, DB>) -> (Self::FromClause, Self::OnClause) { - let (_, __diesel_internal_on_clause) = diesel::internal::table_macro::BoxedSelectStatement::join_target(table); - (__diesel_internal_rhs, __diesel_internal_on_clause) - } - } - - impl<S> diesel::JoinTo<diesel::query_source::Alias<S>> for table - where - diesel::query_source::Alias<S>: diesel::JoinTo<table>, - { - type FromClause = diesel::query_source::Alias<S>; - type OnClause = <diesel::query_source::Alias<S> as diesel::JoinTo<table>>::OnClause; - - fn join_target(__diesel_internal_rhs: diesel::query_source::Alias<S>) -> (Self::FromClause, Self::OnClause) { - let (_, __diesel_internal_on_clause) = diesel::query_source::Alias::<S>::join_target(table); - (__diesel_internal_rhs, __diesel_internal_on_clause) - } - } - // This impl should be able to live in Diesel, // but Rust tries to recurse for no reason impl<T> diesel::insertable::Insertable<T> for table @@ -452,67 +358,64 @@ pub(crate) fn expand(input: TableDecl) -> TokenStream { } #backend_specific_table_impls + } + } +} - /// Contains all of the columns of this table - pub mod columns { - use ::diesel; - use super::table; - #(#imports_for_column_module)* - - #[allow(non_camel_case_types, dead_code)] - #[derive(Debug, Clone, Copy, diesel::query_builder::QueryId)] - /// Represents `table_name.*`, which is sometimes needed for - /// efficient count queries. It cannot be used in place of - /// `all_columns`, and has a `SqlType` of `()` to prevent it - /// being used that way - pub struct star; - - impl<__GB> diesel::expression::ValidGrouping<__GB> for star - where - (#(#column_names,)*): diesel::expression::ValidGrouping<__GB>, - { - type IsAggregate = <(#(#column_names,)*) as diesel::expression::ValidGrouping<__GB>>::IsAggregate; - } - - impl diesel::Expression for star { - type SqlType = diesel::expression::expression_types::NotSelectable; - } - - impl<DB: diesel::backend::Backend> diesel::query_builder::QueryFragment<DB> for star where - <table as diesel::QuerySource>::FromClause: diesel::query_builder::QueryFragment<DB>, - { - #[allow(non_snake_case)] - fn walk_ast<'b>(&'b self, mut __diesel_internal_out: diesel::query_builder::AstPass<'_, 'b, DB>) -> diesel::result::QueryResult<()> - { - use diesel::QuerySource; - - if !__diesel_internal_out.should_skip_from() { - const FROM_CLAUSE: diesel::internal::table_macro::StaticQueryFragmentInstance<table> = diesel::internal::table_macro::StaticQueryFragmentInstance::new(); - - FROM_CLAUSE.walk_ast(__diesel_internal_out.reborrow())?; - __diesel_internal_out.push_sql("."); - } - __diesel_internal_out.push_sql("*"); - Ok(()) - } - } - - impl diesel::SelectableExpression<table> for star { - } - - impl diesel::AppearsOnTable<table> for star { - } - - #(#column_defs)* +fn check_too_many_columns(ViewDecl { column_defs, .. }: &ViewDecl) -> Result<(), TokenStream> { + if column_defs.len() > super::diesel_for_each_tuple::MAX_TUPLE_SIZE as usize { + let txt = if column_defs.len() > 128 { + "You reached the end. Diesel does not support tables with \ + more than 128 columns. Consider using less columns." + } else if column_defs.len() > 64 { + "Table contains more than 64 columns. Consider enabling the \ + `128-column-tables` feature to enable diesels support for \ + tables with more than 64 columns." + } else if column_defs.len() > 32 { + "Table contains more than 32 columns. Consider enabling the \ + `64-column-tables` feature to enable diesels support for \ + tables with more than 32 columns." + } else { + "Table contains more than 16 columns. Consider enabling the \ + `32-column-tables` feature to enable diesels support for \ + tables with more than 16 columns." + }; + return Err(quote::quote! { + compile_error!(#txt); + }); + } + Ok(()) +} - #(#valid_grouping_for_table_columns)* +fn static_query_fragment_impl(input: &ViewDecl) -> TokenStream { + let sql_name = &input.sql_name; + if let Some(schema_name) = input.schema.as_ref().map(|i| i.to_string()) { + quote::quote! { + impl diesel::internal::table_macro::StaticQueryFragment for table { + type Component = diesel::internal::table_macro::InfixNode< + diesel::internal::table_macro::Identifier<'static>, + diesel::internal::table_macro::Identifier<'static>, + &'static str + >; + const STATIC_COMPONENT: &'static Self::Component = &diesel::internal::table_macro::InfixNode::new( + diesel::internal::table_macro::Identifier(#schema_name), + diesel::internal::table_macro::Identifier(#sql_name), + "." + ); + } + } + } else { + quote::quote! { + impl diesel::internal::table_macro::StaticQueryFragment for table { + type Component = diesel::internal::table_macro::Identifier<'static>; + const STATIC_COMPONENT: &'static Self::Component = &diesel::internal::table_macro::Identifier(#sql_name); } } } } fn generate_valid_grouping_for_table_columns(table: &TableDecl) -> Vec<TokenStream> { - let mut ret = Vec::with_capacity(table.column_defs.len() * table.column_defs.len()); + let mut ret = Vec::with_capacity(table.view.column_defs.len() * table.view.column_defs.len()); let primary_key = if let Some(ref pk) = table.primary_keys { if pk.keys.len() == 1 { @@ -524,8 +427,8 @@ fn generate_valid_grouping_for_table_columns(table: &TableDecl) -> Vec<TokenStre Some(DEFAULT_PRIMARY_KEY_NAME.into()) }; - for (id, right_col) in table.column_defs.iter().enumerate() { - for left_col in table.column_defs.iter().skip(id) { + for (id, right_col) in table.view.column_defs.iter().enumerate() { + for left_col in table.view.column_defs.iter().skip(id) { let right_to_left = if Some(left_col.column_name.to_string()) == primary_key { Ident::new("Yes", proc_macro2::Span::call_site()) } else { @@ -557,6 +460,249 @@ fn generate_valid_grouping_for_table_columns(table: &TableDecl) -> Vec<TokenStre ret } +fn base_struct_impl(input: &ViewDecl, column_impl: TokenStream) -> TokenStream { + let column_names = input.column_names(); + let column_defs = input.column_defs.iter().map(expand_column_def); + let column_ty = input.column_defs.iter().map(|c| &c.tpe); + + let import = input.imports(); + let imports_for_column_module = import.iter().map(fix_import_for_submodule); + + quote::quote! { + + #[allow(non_upper_case_globals, dead_code)] + /// A tuple of all of the columns on this table + pub const all_columns: (#(#column_names,)*) = (#(#column_names,)*); + + #[allow(non_camel_case_types)] + #[derive(Debug, Clone, Copy, diesel::query_builder::QueryId, Default)] + /// The actual table struct + /// + /// This is the type which provides the base methods of the query + /// builder, such as `.select` and `.filter`. + pub struct table; + + impl table { + #[allow(dead_code)] + /// Represents `table_name.*`, which is sometimes necessary + /// for efficient count queries. It cannot be used in place of + /// `all_columns` + pub fn star(&self) -> star { + star + } + } + + /// The SQL type of all of the columns on this table + pub type SqlType = (#(#column_ty,)*); + + /// Helper type for representing a boxed query from this table + pub type BoxedQuery<'a, DB, ST = SqlType> = diesel::internal::table_macro::BoxedSelectStatement<'a, ST, diesel::internal::table_macro::FromClause<table>, DB>; + + impl diesel::QuerySource for table { + type FromClause = diesel::internal::table_macro::StaticQueryFragmentInstance<table>; + type DefaultSelection = <Self as diesel::query_source::View>::AllColumns; + + fn from_clause(&self) -> Self::FromClause { + diesel::internal::table_macro::StaticQueryFragmentInstance::new() + } + + fn default_selection(&self) -> Self::DefaultSelection { + use diesel::Table; + Self::all_columns() + } + } + + impl<DB> diesel::query_builder::QueryFragment<DB> for table where + DB: diesel::backend::Backend, + <table as diesel::internal::table_macro::StaticQueryFragment>::Component: diesel::query_builder::QueryFragment<DB> + { + fn walk_ast<'b>(&'b self, __diesel_internal_pass: diesel::query_builder::AstPass<'_, 'b, DB>) -> diesel::result::QueryResult<()> { + <table as diesel::internal::table_macro::StaticQueryFragment>::STATIC_COMPONENT.walk_ast(__diesel_internal_pass) + } + } + + impl diesel::query_builder::AsQuery for table { + type SqlType = SqlType; + type Query = diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<Self>>; + + fn as_query(self) -> Self::Query { + diesel::internal::table_macro::SelectStatement::simple(self) + } + } + + impl diesel::query_source::View for table { + type AllColumns = (#(#column_names,)*); + + fn all_columns() -> Self::AllColumns { + (#(#column_names,)*) + } + } + + impl diesel::query_source::AppearsInFromClause<table> for table { + type Count = diesel::query_source::Once; + } + + // impl<S: AliasSource<Table=table>> AppearsInFromClause<table> for Alias<S> + impl<S> diesel::internal::table_macro::AliasAppearsInFromClause<S, table> for table + where S: diesel::query_source::AliasSource<Target=table>, + { + type Count = diesel::query_source::Never; + } + + // impl<S1: AliasSource<Table=table>, S2: AliasSource<Table=table>> AppearsInFromClause<Alias<S1>> for Alias<S2> + // Those are specified by the `alias!` macro, but this impl will allow it to implement this trait even in downstream + // crates from the schema + impl<S1, S2> diesel::internal::table_macro::AliasAliasAppearsInFromClause<table, S2, S1> for table + where S1: diesel::query_source::AliasSource<Target=table>, + S2: diesel::query_source::AliasSource<Target=table>, + S1: diesel::internal::table_macro::AliasAliasAppearsInFromClauseSameTable<S2, table>, + { + type Count = <S1 as diesel::internal::table_macro::AliasAliasAppearsInFromClauseSameTable<S2, table>>::Count; + } + + impl<S> diesel::query_source::AppearsInFromClause<diesel::query_source::Alias<S>> for table + where S: diesel::query_source::AliasSource, + { + type Count = diesel::query_source::Never; + } + + impl<S, C> diesel::internal::table_macro::FieldAliasMapperAssociatedTypesDisjointnessTrick<table, S, C> for table + where + S: diesel::query_source::AliasSource<Target = table> + ::std::clone::Clone, + C: diesel::query_source::Column<Source = table>, + { + type Out = diesel::query_source::AliasedField<S, C>; + + fn map(__diesel_internal_column: C, __diesel_internal_alias: &diesel::query_source::Alias<S>) -> Self::Out { + __diesel_internal_alias.field(__diesel_internal_column) + } + } + + impl diesel::query_source::AppearsInFromClause<table> for diesel::internal::table_macro::NoFromClause { + type Count = diesel::query_source::Never; + } + + impl<Left, Right, Kind> diesel::JoinTo<diesel::internal::table_macro::Join<Left, Right, Kind>> for table where + diesel::internal::table_macro::Join<Left, Right, Kind>: diesel::JoinTo<table>, + Left: diesel::query_source::QuerySource, + Right: diesel::query_source::QuerySource, + { + type FromClause = diesel::internal::table_macro::Join<Left, Right, Kind>; + type OnClause = <diesel::internal::table_macro::Join<Left, Right, Kind> as diesel::JoinTo<table>>::OnClause; + + fn join_target(__diesel_internal_rhs: diesel::internal::table_macro::Join<Left, Right, Kind>) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = diesel::internal::table_macro::Join::join_target(table); + (__diesel_internal_rhs, __diesel_internal_on_clause) + } + } + + impl<Join, On> diesel::JoinTo<diesel::internal::table_macro::JoinOn<Join, On>> for table where + diesel::internal::table_macro::JoinOn<Join, On>: diesel::JoinTo<table>, + { + type FromClause = diesel::internal::table_macro::JoinOn<Join, On>; + type OnClause = <diesel::internal::table_macro::JoinOn<Join, On> as diesel::JoinTo<table>>::OnClause; + + fn join_target(__diesel_internal_rhs: diesel::internal::table_macro::JoinOn<Join, On>) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = diesel::internal::table_macro::JoinOn::join_target(table); + (__diesel_internal_rhs, __diesel_internal_on_clause) + } + } + + impl<F, S, D, W, O, L, Of, G> diesel::JoinTo<diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<F>, S, D, W, O, L, Of, G>> for table where + diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<F>, S, D, W, O, L, Of, G>: diesel::JoinTo<table>, + F: diesel::query_source::QuerySource + { + type FromClause = diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<F>, S, D, W, O, L, Of, G>; + type OnClause = <diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<F>, S, D, W, O, L, Of, G> as diesel::JoinTo<table>>::OnClause; + + fn join_target(__diesel_internal_rhs: diesel::internal::table_macro::SelectStatement<diesel::internal::table_macro::FromClause<F>, S, D, W, O, L, Of, G>) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = diesel::internal::table_macro::SelectStatement::join_target(table); + (__diesel_internal_rhs, __diesel_internal_on_clause) + } + } + + impl<'a, QS, ST, DB> diesel::JoinTo<diesel::internal::table_macro::BoxedSelectStatement<'a, diesel::internal::table_macro::FromClause<QS>, ST, DB>> for table where + diesel::internal::table_macro::BoxedSelectStatement<'a, diesel::internal::table_macro::FromClause<QS>, ST, DB>: diesel::JoinTo<table>, + QS: diesel::query_source::QuerySource, + { + type FromClause = diesel::internal::table_macro::BoxedSelectStatement<'a, diesel::internal::table_macro::FromClause<QS>, ST, DB>; + type OnClause = <diesel::internal::table_macro::BoxedSelectStatement<'a, diesel::internal::table_macro::FromClause<QS>, ST, DB> as diesel::JoinTo<table>>::OnClause; + fn join_target(__diesel_internal_rhs: diesel::internal::table_macro::BoxedSelectStatement<'a, diesel::internal::table_macro::FromClause<QS>, ST, DB>) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = diesel::internal::table_macro::BoxedSelectStatement::join_target(table); + (__diesel_internal_rhs, __diesel_internal_on_clause) + } + } + + impl<S> diesel::JoinTo<diesel::query_source::Alias<S>> for table + where + diesel::query_source::Alias<S>: diesel::JoinTo<table>, + { + type FromClause = diesel::query_source::Alias<S>; + type OnClause = <diesel::query_source::Alias<S> as diesel::JoinTo<table>>::OnClause; + + fn join_target(__diesel_internal_rhs: diesel::query_source::Alias<S>) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = diesel::query_source::Alias::<S>::join_target(table); + (__diesel_internal_rhs, __diesel_internal_on_clause) + } + } + + /// Contains all of the columns of this table + pub mod columns { + use ::diesel; + use super::table; + #(#imports_for_column_module)* + + #[allow(non_camel_case_types, dead_code)] + #[derive(Debug, Clone, Copy, diesel::query_builder::QueryId)] + /// Represents `table_name.*`, which is sometimes needed for + /// efficient count queries. It cannot be used in place of + /// `all_columns`, and has a `SqlType` of `()` to prevent it + /// being used that way + pub struct star; + + impl<__GB> diesel::expression::ValidGrouping<__GB> for star + where + (#(#column_names,)*): diesel::expression::ValidGrouping<__GB>, + { + type IsAggregate = <(#(#column_names,)*) as diesel::expression::ValidGrouping<__GB>>::IsAggregate; + } + + impl diesel::Expression for star { + type SqlType = diesel::expression::expression_types::NotSelectable; + } + + impl<DB: diesel::backend::Backend> diesel::query_builder::QueryFragment<DB> for star where + <table as diesel::QuerySource>::FromClause: diesel::query_builder::QueryFragment<DB>, + { + #[allow(non_snake_case)] + fn walk_ast<'b>(&'b self, mut __diesel_internal_out: diesel::query_builder::AstPass<'_, 'b, DB>) -> diesel::result::QueryResult<()> + { + use diesel::QuerySource; + + if !__diesel_internal_out.should_skip_from() { + const FROM_CLAUSE: diesel::internal::table_macro::StaticQueryFragmentInstance<table> = diesel::internal::table_macro::StaticQueryFragmentInstance::new(); + + FROM_CLAUSE.walk_ast(__diesel_internal_out.reborrow())?; + __diesel_internal_out.push_sql("."); + } + __diesel_internal_out.push_sql("*"); + Ok(()) + } + } + + impl diesel::SelectableExpression<table> for star { + } + + impl diesel::AppearsOnTable<table> for star { + } + + #(#column_defs)* + + #column_impl + } + } +} + fn fix_import_for_submodule(import: &syn::ItemUse) -> syn::ItemUse { let mut ret = import.clone(); @@ -830,7 +976,7 @@ fn expand_column_def(column_def: &ColumnDef) -> TokenStream { } impl diesel::query_source::Column for #column_name { - type Table = super::table; + type Source = super::table; const NAME: &'static str = #sql_name; } diff --git a/diesel_derives/src/lib.rs b/diesel_derives/src/lib.rs index 2c909941de92..f53173258a21 100644 --- a/diesel_derives/src/lib.rs +++ b/diesel_derives/src/lib.rs @@ -38,6 +38,7 @@ mod associations; mod diesel_for_each_tuple; mod diesel_numeric_ops; mod diesel_public_if; +mod expansion; mod from_sql_row; mod identifiable; mod insertable; @@ -48,7 +49,6 @@ mod queryable_by_name; mod selectable; mod sql_function; mod sql_type; -mod table; mod valid_grouping; /// Implements `AsChangeset` @@ -1540,7 +1540,7 @@ pub fn __diesel_public_if(attrs: TokenStream, input: TokenStream) -> TokenStream #[proc_macro] pub fn table_proc(input: TokenStream) -> TokenStream { match syn::parse(input) { - Ok(input) => table::expand(input).into(), + Ok(input) => expansion::expand_table(input).into(), Err(_) => quote::quote! { compile_error!( "Invalid `table!` syntax. Please see the `table!` macro docs for more info.\n\ @@ -1551,6 +1551,20 @@ pub fn table_proc(input: TokenStream) -> TokenStream { } } +#[proc_macro] +pub fn view_proc(input: TokenStream) -> TokenStream { + match syn::parse(input) { + Ok(input) => expansion::expand_view(input).into(), + Err(_) => quote::quote! { + compile_error!( + "Invalid `view!` syntax. Please see the `view!` macro docs for more info.\n\ + Docs available at: `https://docs.diesel.rs/master/diesel/macro.view.html`\n" + ); + } + .into(), + } +} + /// This derives implements `diesel::Connection` and related traits for an enum of /// connections to different databases. /// diff --git a/diesel_derives/src/multiconnection.rs b/diesel_derives/src/multiconnection.rs index fde7838aad8b..b33da930096e 100644 --- a/diesel_derives/src/multiconnection.rs +++ b/diesel_derives/src/multiconnection.rs @@ -1195,7 +1195,7 @@ fn generate_querybuilder(connection_types: &[ConnectionVariant]) -> TokenStream let ty = c.ty; quote::quote! { super::backend::MultiBackend::#ident(_) => { - <Self as diesel::insertable::InsertValues<<#ty as diesel::connection::Connection>::Backend, Col::Table>>::column_names( + <Self as diesel::insertable::InsertValues<<#ty as diesel::connection::Connection>::Backend, Col::Source>>::column_names( &self, out.cast_database( super::bind_collector::MultiBindCollector::#lower_ident, @@ -1214,7 +1214,7 @@ fn generate_querybuilder(connection_types: &[ConnectionVariant]) -> TokenStream let insert_values_backend_bounds = connection_types.iter().map(|c| { let ty = c.ty; quote::quote! { - diesel::insertable::DefaultableColumnInsertValue<diesel::insertable::ColumnInsertValue<Col, Expr>>: diesel::insertable::InsertValues<<#ty as diesel::connection::Connection>::Backend, Col::Table> + diesel::insertable::DefaultableColumnInsertValue<diesel::insertable::ColumnInsertValue<Col, Expr>>: diesel::insertable::InsertValues<<#ty as diesel::connection::Connection>::Backend, Col::Source> } }); @@ -1405,10 +1405,11 @@ fn generate_querybuilder(connection_types: &[ConnectionVariant]) -> TokenStream } } - impl<Col, Expr> diesel::insertable::InsertValues<super::multi_connection_impl::backend::MultiBackend, Col::Table> + impl<Col, Expr> diesel::insertable::InsertValues<super::multi_connection_impl::backend::MultiBackend, Col::Source> for diesel::insertable::DefaultableColumnInsertValue<diesel::insertable::ColumnInsertValue<Col, Expr>> where Col: diesel::prelude::Column, + Col::Source: diesel::Table, Expr: diesel::prelude::Expression<SqlType = Col::SqlType>, Expr: diesel::prelude::AppearsOnTable<diesel::internal::derives::multiconnection::NoFromClause>, Self: diesel::query_builder::QueryFragment<super::multi_connection_impl::backend::MultiBackend>, diff --git a/diesel_dynamic_schema/src/table.rs b/diesel_dynamic_schema/src/table.rs index da1317f1b6e8..3b8054090abc 100644 --- a/diesel_dynamic_schema/src/table.rs +++ b/diesel_dynamic_schema/src/table.rs @@ -72,18 +72,23 @@ where } } -impl<T, U> diesel::Table for Table<T, U> +impl<T, U> diesel::query_source::View for Table<T, U> where Self: QuerySource + AsQuery, { - type PrimaryKey = DummyExpression; type AllColumns = DummyExpression; - - fn primary_key(&self) -> Self::PrimaryKey { + fn all_columns() -> Self::AllColumns { DummyExpression::new() } +} - fn all_columns() -> Self::AllColumns { +impl<T, U> diesel::Table for Table<T, U> +where + Self: QuerySource + AsQuery, +{ + type PrimaryKey = DummyExpression; + + fn primary_key(&self) -> Self::PrimaryKey { DummyExpression::new() } } diff --git a/diesel_table_macro_syntax/src/lib.rs b/diesel_table_macro_syntax/src/lib.rs index dbbd9d2f437f..9fd5be8d3b81 100644 --- a/diesel_table_macro_syntax/src/lib.rs +++ b/diesel_table_macro_syntax/src/lib.rs @@ -1,19 +1,43 @@ +use syn::parse_quote; use syn::spanned::Spanned; use syn::Ident; +use syn::ItemUse; use syn::MetaNameValue; -pub struct TableDecl { +pub struct ViewDecl { pub use_statements: Vec<syn::ItemUse>, pub meta: Vec<syn::Attribute>, pub schema: Option<Ident>, _punct: Option<syn::Token![.]>, pub sql_name: String, pub table_name: Ident, - pub primary_keys: Option<PrimaryKey>, _brace_token: syn::token::Brace, pub column_defs: syn::punctuated::Punctuated<ColumnDef, syn::Token![,]>, } +impl ViewDecl { + pub fn column_names(&self) -> Vec<&syn::Ident> { + self.column_defs + .iter() + .map(|c| &c.column_name) + .collect::<Vec<_>>() + } + pub fn imports(&self) -> Vec<ItemUse> { + if self.use_statements.is_empty() { + vec![parse_quote!( + use diesel::sql_types::*; + )] + } else { + self.use_statements.clone() + } + } +} + +pub struct TableDecl { + pub view: ViewDecl, + pub primary_keys: Option<PrimaryKey>, +} + #[allow(dead_code)] // paren_token is currently unused pub struct PrimaryKey { paren_token: syn::token::Paren, @@ -29,6 +53,43 @@ pub struct ColumnDef { pub max_length: Option<syn::LitInt>, } +impl syn::parse::Parse for ViewDecl { + fn parse(buf: &syn::parse::ParseBuffer<'_>) -> Result<Self, syn::Error> { + let mut use_statements = Vec::new(); + loop { + let fork = buf.fork(); + if fork.parse::<syn::ItemUse>().is_ok() { + use_statements.push(buf.parse()?); + } else { + break; + }; + } + let mut meta = syn::Attribute::parse_outer(buf)?; + let fork = buf.fork(); + let (schema, punct, table_name) = if parse_table_with_schema(&fork).is_ok() { + let (schema, punct, table_name) = parse_table_with_schema(buf)?; + (Some(schema), Some(punct), table_name) + } else { + let table_name = buf.parse()?; + (None, None, table_name) + }; + let content; + let brace_token = syn::braced!(content in buf); + let column_defs = syn::punctuated::Punctuated::parse_terminated(&content)?; + let sql_name = get_sql_name(&mut meta, &table_name)?; + Ok(Self { + use_statements, + meta, + schema, + _punct: punct, + sql_name, + table_name, + _brace_token: brace_token, + column_defs, + }) + } +} + impl syn::parse::Parse for TableDecl { fn parse(buf: &syn::parse::ParseBuffer<'_>) -> Result<Self, syn::Error> { let mut use_statements = Vec::new(); @@ -60,15 +121,17 @@ impl syn::parse::Parse for TableDecl { let column_defs = syn::punctuated::Punctuated::parse_terminated(&content)?; let sql_name = get_sql_name(&mut meta, &table_name)?; Ok(Self { - use_statements, - meta, - table_name, + view: ViewDecl { + use_statements, + meta, + schema, + _punct: punct, + sql_name, + table_name, + _brace_token: brace_token, + column_defs, + }, primary_keys, - _brace_token: brace_token, - column_defs, - sql_name, - _punct: punct, - schema, }) } }