Skip to content

Commit cc45371

Browse files
committed
Support view
1 parent 60e9468 commit cc45371

File tree

11 files changed

+457
-30
lines changed

11 files changed

+457
-30
lines changed

sea-orm-codegen/src/entity/base_entity.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ pub struct Entity {
1515
pub(crate) relations: Vec<Relation>,
1616
pub(crate) conjunct_relations: Vec<ConjunctRelation>,
1717
pub(crate) primary_keys: Vec<PrimaryKey>,
18+
pub(crate) is_view: bool,
1819
}
1920

2021
impl Entity {
22+
pub fn is_view(&self) -> bool {
23+
self.is_view
24+
}
25+
2126
pub fn get_table_name_snake_case(&self) -> String {
2227
self.table_name.to_snake_case()
2328
}
@@ -351,6 +356,7 @@ mod tests {
351356
primary_keys: vec![PrimaryKey {
352357
name: "id".to_owned(),
353358
}],
359+
is_view: false,
354360
}
355361
}
356362

sea-orm-codegen/src/entity/transformer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ impl EntityTransformer {
130130
relations: relations.clone(),
131131
conjunct_relations: vec![],
132132
primary_keys,
133+
is_view: false,
133134
};
134135
entities.insert(table_name.clone(), entity.clone());
135136
for mut rel in relations.into_iter() {

sea-orm-codegen/src/entity/writer.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,7 @@ mod tests {
921921
via: "cake_filling".to_owned(),
922922
to: "filling".to_owned(),
923923
}],
924+
is_view: false,
924925
primary_keys: vec![PrimaryKey {
925926
name: "id".to_owned(),
926927
}],
@@ -970,6 +971,7 @@ mod tests {
970971
},
971972
],
972973
conjunct_relations: vec![],
974+
is_view: false,
973975
primary_keys: vec![
974976
PrimaryKey {
975977
name: "cake_id".to_owned(),
@@ -1019,6 +1021,7 @@ mod tests {
10191021
impl_related: true,
10201022
}],
10211023
conjunct_relations: vec![],
1024+
is_view: false,
10221025
primary_keys: vec![
10231026
PrimaryKey {
10241027
name: "cake_id".to_owned(),
@@ -1053,6 +1056,7 @@ mod tests {
10531056
via: "cake_filling".to_owned(),
10541057
to: "cake".to_owned(),
10551058
}],
1059+
is_view: false,
10561060
primary_keys: vec![PrimaryKey {
10571061
name: "id".to_owned(),
10581062
}],
@@ -1110,6 +1114,7 @@ mod tests {
11101114
},
11111115
],
11121116
conjunct_relations: vec![],
1117+
is_view: false,
11131118
primary_keys: vec![PrimaryKey {
11141119
name: "id".to_owned(),
11151120
}],
@@ -1154,6 +1159,7 @@ mod tests {
11541159
impl_related: true,
11551160
}],
11561161
conjunct_relations: vec![],
1162+
is_view: false,
11571163
primary_keys: vec![PrimaryKey {
11581164
name: "id".to_owned(),
11591165
}],
@@ -1324,6 +1330,7 @@ mod tests {
13241330
},
13251331
],
13261332
conjunct_relations: vec![],
1333+
is_view: false,
13271334
primary_keys: vec![PrimaryKey {
13281335
name: "id".to_owned(),
13291336
}],
@@ -1371,6 +1378,7 @@ mod tests {
13711378
via: "cake_filling".to_owned(),
13721379
to: "filling".to_owned(),
13731380
}],
1381+
is_view: false,
13741382
primary_keys: vec![PrimaryKey {
13751383
name: "id".to_owned(),
13761384
}],
@@ -1418,6 +1426,7 @@ mod tests {
14181426
via: "cake_filling".to_owned(),
14191427
to: "filling".to_owned(),
14201428
}],
1429+
is_view: false,
14211430
primary_keys: vec![PrimaryKey {
14221431
name: "id".to_owned(),
14231432
}],
@@ -1452,6 +1461,7 @@ mod tests {
14521461
],
14531462
relations: vec![],
14541463
conjunct_relations: vec![],
1464+
is_view: false,
14551465
primary_keys: vec![PrimaryKey {
14561466
name: "id".to_owned(),
14571467
}],
@@ -1486,6 +1496,7 @@ mod tests {
14861496
],
14871497
relations: vec![],
14881498
conjunct_relations: vec![],
1499+
is_view: false,
14891500
primary_keys: vec![PrimaryKey {
14901501
name: "id".to_owned(),
14911502
}],
@@ -1522,6 +1533,7 @@ mod tests {
15221533
impl_related: true,
15231534
}],
15241535
conjunct_relations: vec![],
1536+
is_view: false,
15251537
primary_keys: vec![
15261538
PrimaryKey {
15271539
name: "id1".to_owned(),
@@ -1571,6 +1583,7 @@ mod tests {
15711583
impl_related: true,
15721584
}],
15731585
conjunct_relations: vec![],
1586+
is_view: false,
15741587
primary_keys: vec![PrimaryKey {
15751588
name: "id".to_owned(),
15761589
}],
@@ -2155,6 +2168,7 @@ mod tests {
21552168
via: "cake_filling".to_owned(),
21562169
to: "filling".to_owned(),
21572170
}],
2171+
is_view: false,
21582172
primary_keys: vec![PrimaryKey {
21592173
name: "id".to_owned(),
21602174
}],
@@ -2832,6 +2846,7 @@ mod tests {
28322846
],
28332847
relations: vec![],
28342848
conjunct_relations: vec![],
2849+
is_view: false,
28352850
primary_keys: vec![PrimaryKey {
28362851
name: "id".to_owned(),
28372852
}],
@@ -2941,6 +2956,7 @@ mod tests {
29412956
],
29422957
relations: vec![],
29432958
conjunct_relations: vec![],
2959+
is_view: false,
29442960
primary_keys: vec![PrimaryKey {
29452961
name: "id".to_owned(),
29462962
}],
@@ -3002,6 +3018,7 @@ mod tests {
30023018
],
30033019
relations: vec![],
30043020
conjunct_relations: vec![],
3021+
is_view: false,
30053022
primary_keys: vec![PrimaryKey {
30063023
name: "id".to_owned(),
30073024
}],

sea-orm-macros/src/derives/attributes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub mod derive_attr {
1515
pub relation: Option<syn::Ident>,
1616
pub schema_name: Option<syn::LitStr>,
1717
pub table_name: Option<syn::LitStr>,
18+
pub view: Option<()>,
1819
pub comment: Option<syn::LitStr>,
1920
pub table_iden: Option<()>,
2021
pub rename_all: Option<syn::LitStr>,

sea-orm-macros/src/derives/entity_model.rs

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use syn::{
1111
/// Method to derive an Model
1212
pub fn expand_derive_entity_model(data: &Data, attrs: &[Attribute]) -> syn::Result<TokenStream> {
1313
// if #[sea_orm(table_name = "foo", schema_name = "bar")] specified, create Entity struct
14+
// if #[sea_orm(table_name = "foo", view)] specified, create a View entity (read-only)
1415
let mut table_name = None;
16+
let mut is_view = false;
1517
let mut comment = quote! {None};
1618
let mut schema_name = quote! { None };
1719
let mut table_iden = false;
@@ -28,6 +30,8 @@ pub fn expand_derive_entity_model(data: &Data, attrs: &[Attribute]) -> syn::Resu
2830
comment = quote! { Some(#name) };
2931
} else if meta.path.is_ident("table_name") {
3032
table_name = Some(meta.value()?.parse::<LitStr>()?);
33+
} else if meta.path.is_ident("view") {
34+
is_view = true;
3135
} else if meta.path.is_ident("schema_name") {
3236
let name: Lit = meta.value()?.parse()?;
3337
schema_name = quote! { Some(#name) };
@@ -48,32 +52,92 @@ pub fn expand_derive_entity_model(data: &Data, attrs: &[Attribute]) -> syn::Resu
4852
})
4953
})?;
5054

51-
let entity_def = table_name
55+
if is_view && table_name.is_none() {
56+
return Err(syn::Error::new(
57+
Span::call_site(),
58+
"Attribute `view` requires `table_name` to be specified, e.g. #[sea_orm(table_name = \"foo\", view)].",
59+
));
60+
}
61+
62+
let entity_name = table_name.clone();
63+
64+
// TODO: Add registry support to views
65+
66+
let entity_def = entity_name
5267
.as_ref()
53-
.map(|table_name| {
54-
let entity_extra_attr = if model_ex {
55-
quote!(#[sea_orm(model_ex = ModelEx, active_model_ex = ActiveModelEx)])
56-
} else {
57-
quote!()
58-
};
59-
quote! {
60-
#[doc = " Generated by sea-orm-macros"]
61-
#[derive(Copy, Clone, Default, Debug, sea_orm::prelude::DeriveEntity)]
62-
#entity_extra_attr
63-
pub struct Entity;
64-
65-
#[automatically_derived]
66-
impl sea_orm::prelude::EntityName for Entity {
67-
fn schema_name(&self) -> Option<&str> {
68-
#schema_name
68+
.map(|name| {
69+
if is_view {
70+
quote! {
71+
#[doc = " Generated by sea-orm-macros"]
72+
#[derive(Copy, Clone, Default, Debug)]
73+
pub struct Entity;
74+
75+
#[automatically_derived]
76+
impl sea_orm::prelude::EntityName for Entity {
77+
fn schema_name(&self) -> Option<&str> {
78+
#schema_name
79+
}
80+
81+
fn table_name(&self) -> &'static str {
82+
#name
83+
}
84+
85+
fn comment(&self) -> Option<&str> {
86+
#comment
87+
}
88+
}
89+
90+
#[automatically_derived]
91+
impl sea_orm::prelude::Iden for Entity {
92+
fn unquoted(&self) -> &str {
93+
#name
94+
}
6995
}
7096

71-
fn table_name(&self) -> &'static str {
72-
#table_name
97+
#[automatically_derived]
98+
impl sea_orm::prelude::IdenStatic for Entity {
99+
fn as_str(&self) -> &'static str {
100+
#name
101+
}
102+
}
103+
104+
#[automatically_derived]
105+
impl sea_orm::prelude::EntityTrait for Entity {
106+
type Model = Model;
107+
type ModelEx = Model;
108+
type ActiveModel = sea_orm::NeverActiveModel<Entity>;
109+
type ActiveModelEx = sea_orm::NeverActiveModel<Entity>;
110+
type Column = Column;
111+
type PrimaryKey = PrimaryKey;
112+
type Relation = Relation;
73113
}
114+
}
115+
} else {
116+
// Generate regular Entity (implements EntityTrait)
117+
let entity_extra_attr = if model_ex {
118+
quote!(#[sea_orm(model_ex = ModelEx, active_model_ex = ActiveModelEx)])
119+
} else {
120+
quote!()
121+
};
122+
quote! {
123+
#[doc = "Generated by sea-orm-macros"]
124+
#[derive(Copy, Clone, Default, Debug, sea_orm::prelude::DeriveEntity)]
125+
#entity_extra_attr
126+
pub struct Entity;
127+
128+
#[automatically_derived]
129+
impl sea_orm::prelude::EntityName for Entity {
130+
fn schema_name(&self) -> Option<&str> {
131+
#schema_name
132+
}
74133

75-
fn comment(&self) -> Option<&str> {
76-
#comment
134+
fn table_name(&self) -> &'static str {
135+
#name
136+
}
137+
138+
fn comment(&self) -> Option<&str> {
139+
#comment
140+
}
77141
}
78142
}
79143
}
@@ -90,11 +154,11 @@ pub fn expand_derive_entity_model(data: &Data, attrs: &[Attribute]) -> syn::Resu
90154
let mut primary_key_types: Punctuated<_, Comma> = Punctuated::new();
91155
let mut auto_increment = true;
92156
if table_iden {
93-
if let Some(table_name) = &table_name {
157+
if let Some(name) = &entity_name {
94158
let table_field_name = Ident::new("Table", Span::call_site());
95159
columns_enum.push(quote! {
96160
#[doc = " Generated by sea-orm-macros"]
97-
#[sea_orm(table_name=#table_name)]
161+
#[sea_orm(table_name=#name)]
98162
#[strum(disabled)]
99163
#table_field_name
100164
});
@@ -403,7 +467,13 @@ pub fn expand_derive_entity_model(data: &Data, attrs: &[Attribute]) -> syn::Resu
403467
columns_save_as.push_punct(Comma::default());
404468
}
405469

406-
let primary_key = {
470+
let primary_key = if is_view && primary_keys.is_empty() {
471+
// Views may not have primary keys; use NeverPrimaryKey to satisfy EntityTrait bounds.
472+
quote! {
473+
#[doc = " Generated by sea-orm-macros"]
474+
pub type PrimaryKey = sea_orm::NeverPrimaryKey<Column>;
475+
}
476+
} else {
407477
let auto_increment = auto_increment && primary_keys.len() == 1;
408478
let primary_key_types = if primary_key_types.len() == 1 {
409479
let first = primary_key_types.first();

sea-orm-macros/src/lib.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,21 +146,40 @@ pub fn derive_entity_model(input: TokenStream) -> TokenStream {
146146
panic!("Struct name must be Model");
147147
}
148148

149+
let is_view = attrs.iter().any(|attr| {
150+
if attr.path().is_ident("sea_orm") {
151+
let mut found_view = false;
152+
let _ = attr.parse_nested_meta(|meta| {
153+
if meta.path.is_ident("view") {
154+
found_view = true;
155+
}
156+
Ok(())
157+
});
158+
found_view
159+
} else {
160+
false
161+
}
162+
});
163+
149164
let mut ts: TokenStream = derives::expand_derive_entity_model(&data, &attrs)
150165
.unwrap_or_else(Error::into_compile_error)
151166
.into();
152167

168+
// Views are read-only: we still derive Model (ModelTrait + FromQueryResult),
169+
// but skip deriving an ActiveModel / IntoActiveModel.
153170
ts.extend::<TokenStream>(
154171
derives::expand_derive_model(&ident, &data, &attrs)
155172
.unwrap_or_else(Error::into_compile_error)
156173
.into(),
157174
);
158175

159-
ts.extend::<TokenStream>(
160-
derives::expand_derive_active_model(&ident, &data)
161-
.unwrap_or_else(Error::into_compile_error)
162-
.into(),
163-
);
176+
if !is_view {
177+
ts.extend::<TokenStream>(
178+
derives::expand_derive_active_model(&ident, &data)
179+
.unwrap_or_else(Error::into_compile_error)
180+
.into(),
181+
);
182+
}
164183

165184
ts
166185
}

src/entity/active_model.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@ pub trait ActiveModelTrait: Clone + Debug {
7979
self.get(cols.next()?.into_column()).into_value()?
8080
};
8181
}
82-
match <<<Self::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType as PrimaryKeyArity>::ARITY {
82+
let arity =
83+
<<<Self::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType as PrimaryKeyArity>::ARITY;
84+
if arity == 0 {
85+
return None;
86+
}
87+
match arity {
8388
1 => {
8489
let s1 = next!();
8590
Some(ValueTuple::One(s1))

0 commit comments

Comments
 (0)