use super::active_model::DeriveActiveModel;
use super::attributes::compound_attr;
use super::model_ex::infer_relation_name_from_entity;
use super::util::{
CardinalityKind, CompoundKind, CompoundType, Junction, RelationColumns, async_token,
await_token, consume_meta, escape_rust_keyword, is_self_entity, trim_starting_raw_identifier,
};
use heck::ToUpperCamelCase;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
use syn::{Attribute, Data, LitStr, PathArguments, Type, TypePath, Visibility};
enum RelationAttr {
BelongsTo {
from: RelationColumns,
relation_enum: Option<LitStr>,
},
HasOne,
HasMany,
HasManySelf {
relation_enum: LitStr,
},
ManyToMany {
junction: Junction,
},
ManyToManySelf {
junction_module: Ident,
reverse: bool,
},
}
impl RelationAttr {
fn from_attr(
attrs: &compound_attr::SeaOrm,
field_ident: &Ident,
compound_type: &CompoundType,
) -> syn::Result<Option<Self>> {
match attrs {
compound_attr::SeaOrm {
self_ref: Some(_),
via: Some(via),
..
} => {
if compound_type.kind != CompoundKind::HasMany
|| !is_self_entity(&compound_type.entity)
{
return Err(syn::Error::new_spanned(
via,
"self_ref + via field type must be `HasMany<Entity>`",
));
}
let junction = Junction::from_lit(via)?;
if junction.relation.is_some() {
return Err(syn::Error::new(
via.span(),
"`self_ref` via must name a junction entity",
));
}
Ok(Some(Self::ManyToManySelf {
junction_module: junction.module,
reverse: attrs.reverse.is_some(),
}))
}
compound_attr::SeaOrm {
relation_enum: Some(relation_enum),
self_ref: Some(_),
via: None,
from: Some(from),
to: Some(_),
..
} => {
if !matches!(
compound_type.kind,
CompoundKind::BelongsTo(_) | CompoundKind::HasOne
) {
return Err(syn::Error::new_spanned(
field_ident,
"self_ref belongs_to must be paired with BelongsTo or HasOne",
));
}
Ok(Some(Self::BelongsTo {
from: RelationColumns::from_lit(from.clone())?,
relation_enum: Some(relation_enum.clone()),
}))
}
compound_attr::SeaOrm {
relation_enum: Some(relation_enum),
self_ref: Some(_),
relation_reverse: Some(_),
via: None,
from: None,
to: None,
..
} => {
if compound_type.kind != CompoundKind::HasMany
|| !is_self_entity(&compound_type.entity)
{
return Err(syn::Error::new_spanned(
field_ident,
"self_ref has_many field type must be `HasMany<Entity>`",
));
}
Ok(Some(Self::HasManySelf {
relation_enum: relation_enum.clone(),
}))
}
compound_attr::SeaOrm {
self_ref: Some(_),
via: None,
relation_enum: None,
..
} => Err(syn::Error::new_spanned(
field_ident,
"Please specify `relation_enum` for `self_ref`",
)),
compound_attr::SeaOrm {
self_ref: Some(_), ..
} => Ok(None),
compound_attr::SeaOrm {
belongs_to: Some(_),
..
} => {
if !matches!(
compound_type.kind,
CompoundKind::BelongsTo(_) | CompoundKind::HasOne
) {
return Err(syn::Error::new_spanned(
field_ident,
"belongs_to must be paired with BelongsTo or HasOne",
));
}
Ok(Some(Self::BelongsTo {
from: RelationColumns::from_lit(attrs.from.clone().ok_or_else(|| {
syn::Error::new_spanned(field_ident, "belongs_to must specify `from`")
})?)?,
relation_enum: attrs.relation_enum.clone(),
}))
}
compound_attr::SeaOrm {
relation_enum: Some(_),
..
} => Ok(None),
compound_attr::SeaOrm {
has_one: Some(_), ..
} => {
if compound_type.kind != CompoundKind::HasOne {
return Err(syn::Error::new_spanned(
field_ident,
"#[sea_orm(has_one)] must be paired with HasOne<Entity>",
));
}
Ok(Some(Self::HasOne))
}
compound_attr::SeaOrm {
has_many: Some(_),
via: None,
..
} => {
if compound_type.kind != CompoundKind::HasMany {
return Err(syn::Error::new_spanned(
field_ident,
"has_many must be paired with HasMany",
));
}
Ok(Some(Self::HasMany))
}
compound_attr::SeaOrm {
has_many: Some(_),
via: Some(via),
..
} => {
if compound_type.kind != CompoundKind::HasMany {
return Err(syn::Error::new_spanned(
field_ident,
"has_many via must be paired with HasMany",
));
}
Ok(Some(Self::ManyToMany {
junction: Junction::from_lit(via)?,
}))
}
_ => Ok(None),
}
}
}
struct ScalarField<'a> {
ty: &'a Type,
column: Ident,
}
impl<'a> ScalarField<'a> {
fn from_field(field: &'a syn::Field, ident: &Ident) -> syn::Result<Self> {
let column = trim_starting_raw_identifier(ident).to_upper_camel_case();
let mut column = Ident::new(&escape_rust_keyword(column), ident.span());
for attr in &field.attrs {
if !attr.path().is_ident("sea_orm") {
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("enum_name") {
column = syn::parse_str(&meta.value()?.parse::<LitStr>()?.value())?;
} else {
consume_meta(meta);
}
Ok(())
})?;
}
Ok(Self {
ty: &field.ty,
column,
})
}
fn is_option(&self) -> bool {
if let Type::Path(type_path) = self.ty
&& let Some(segment) = type_path.path.segments.last()
{
segment.ident == "Option"
} else {
false
}
}
}
#[derive(Clone, Copy)]
enum FieldParseMode {
Compact,
Dense,
}
struct CompoundField {
compound_type: CompoundType,
}
struct RelationField {
compound_type: CompoundType,
attr: RelationAttr,
}
enum FieldKind<'a> {
Ignored,
Scalar(ScalarField<'a>),
Compound(CompoundField),
Relation(RelationField),
}
struct Field<'a> {
ident: &'a Ident,
kind: FieldKind<'a>,
}
impl<'a> Field<'a> {
fn from_field(field: &'a syn::Field, mode: FieldParseMode) -> syn::Result<Self> {
let Some(ident) = &field.ident else {
return Err(syn::Error::new_spanned(field, "expected named field"));
};
let mut ignored = false;
for attr in &field.attrs {
if !attr.path().is_ident("sea_orm") {
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("ignore") {
ignored = true;
} else {
consume_meta(meta);
}
Ok(())
})?;
}
let kind = if ignored {
FieldKind::Ignored
} else if let Type::Path(type_path) = &field.ty
&& let Some(compound_type) = CompoundType::from_type(type_path)?
{
let relation_attr = match mode {
FieldParseMode::Compact => None,
FieldParseMode::Dense => {
let attrs = compound_attr::SeaOrm::try_from_attributes(&field.attrs)?
.unwrap_or_default();
RelationAttr::from_attr(&attrs, ident, &compound_type)?
}
};
if let Some(attr) = relation_attr {
FieldKind::Relation(RelationField {
compound_type,
attr,
})
} else {
FieldKind::Compound(CompoundField { compound_type })
}
} else {
FieldKind::Scalar(ScalarField::from_field(field, ident)?)
};
Ok(Self { ident, kind })
}
fn expand_into(&'a self, output: &mut Output<'a>) {
let ident = self.ident;
match &self.kind {
FieldKind::Ignored => {
output.ignored_model_fields.push(ident);
}
FieldKind::Scalar(scalar) => {
let field_type = scalar.ty;
output.model_field_defs.push(quote! {
#[doc = " Generated by sea-orm-macros"]
pub #ident: sea_orm::ActiveValue<#field_type>
});
output.scalar_fields.push(ident);
}
FieldKind::Compound(CompoundField { compound_type })
| FieldKind::Relation(RelationField { compound_type, .. }) => {
self.expand_compound_into(output, compound_type);
}
}
}
fn expand_compound_into(&'a self, output: &mut Output<'a>, compound_type: &CompoundType) {
let ident = self.ident;
let entity_type = &compound_type.entity;
let field_type = match compound_type.kind {
CompoundKind::BelongsTo(cardinality) => {
let target_type = match cardinality {
CardinalityKind::Required => quote!(#entity_type),
CardinalityKind::Optional => quote!(Option<#entity_type>),
};
quote!(ActiveBelongsTo<#target_type>)
}
CompoundKind::HasOne => quote!(ActiveHasOne<#entity_type>),
CompoundKind::HasMany => quote!(ActiveHasMany<#entity_type>),
};
output.model_field_defs.push(quote! {
#[doc = " Generated by sea-orm-macros"]
pub #ident: #field_type
});
output.compound_fields.push(ident);
}
}
struct Fields<'a>(Vec<Field<'a>>);
impl<'a> Fields<'a> {
fn from_data(data: &'a Data, ident: &Ident, mode: FieldParseMode) -> syn::Result<Self> {
let fields = if let Data::Struct(r#struct) = data
&& let syn::Fields::Named(fields) = &r#struct.fields
{
fields
.named
.iter()
.map(|field| Field::from_field(field, mode))
.collect::<syn::Result<Vec<_>>>()?
} else {
return Err(syn::Error::new_spanned(
ident,
"You can only derive DeriveActiveModelEx on structs",
));
};
Ok(Self(fields))
}
fn optional_fields_for_columns(
&'a self,
columns: &RelationColumns,
) -> syn::Result<Vec<&'a Ident>> {
columns
.columns
.iter()
.try_fold(Vec::new(), |mut optional_fields, column| {
let (ident, scalar) = self
.0
.iter()
.find_map(|field| {
if let FieldKind::Scalar(scalar) = &field.kind
&& scalar.column == *column
{
Some((field.ident, scalar))
} else {
None
}
})
.ok_or_else(|| {
syn::Error::new(columns.span, format!("unknown `from` column `{column}`"))
})?;
if scalar.is_option() {
optional_fields.push(ident);
}
Ok(optional_fields)
})
}
fn relation_target_count(&self, entity: &TypePath) -> usize {
self.0
.iter()
.filter(|field| match &field.kind {
FieldKind::Compound(compound_field) => {
&compound_field.compound_type.entity == entity
}
FieldKind::Relation(relation_field) => {
&relation_field.compound_type.entity == entity
}
_ => false,
})
.count()
}
}
struct ActiveModelSetter<'a> {
field: &'a Field<'a>,
fields: &'a Fields<'a>,
}
impl<'a> ActiveModelSetter<'a> {
fn expand(&self) -> syn::Result<TokenStream> {
let field_ident = self.field.ident;
match &self.field.kind {
FieldKind::Ignored => Ok(quote!()),
FieldKind::Scalar(scalar) => {
let field_type = scalar.ty;
let setter = format_ident!("set_{}", field_ident);
Ok(quote! {
#[doc = " Generated by sea-orm-macros"]
pub fn #setter(mut self, v: impl Into<#field_type>) -> Self {
self.#field_ident = sea_orm::Set(v.into());
self
}
})
}
FieldKind::Compound(compound_field) => {
self.expand_compound(&compound_field.compound_type)
}
FieldKind::Relation(relation_field) => {
self.expand_compound(&relation_field.compound_type)
}
}
}
fn expand_compound(&self, compound_type: &CompoundType) -> syn::Result<TokenStream> {
let field_ident = self.field.ident;
let entity_path = &compound_type.entity;
let mut active_model_type = entity_path.path.clone();
let Some(segment) = active_model_type.segments.last_mut() else {
return Err(syn::Error::new_spanned(entity_path, "expected entity path"));
};
segment.ident = format_ident!("ActiveModelEx");
segment.arguments = PathArguments::None;
match compound_type.kind {
CompoundKind::BelongsTo(cardinality) => {
let (target_entity, maybe_some) = match cardinality {
CardinalityKind::Required => (quote!(#entity_path), quote!()),
CardinalityKind::Optional => (quote!(Option<#entity_path>), quote!(Some)),
};
let setter = format_ident!("set_{}", field_ident);
let optional_setters = if matches!(cardinality, CardinalityKind::Optional) {
let optional_setter = format_ident!("set_{}_option", field_ident);
let clear_method = format_ident!("clear_{}", field_ident);
let optional_foreign_key_fields = match &self.field.kind {
FieldKind::Relation(RelationField {
attr: RelationAttr::BelongsTo { from, .. },
..
}) => self.fields.optional_fields_for_columns(from)?,
_ => Vec::new(),
};
let clear_foreign_keys = quote! {
#(self.#optional_foreign_key_fields = sea_orm::Set(None);)*
};
quote! {
#[doc = " Generated by sea-orm-macros"]
pub fn #optional_setter(mut self, v: Option<impl Into<#active_model_type>>) -> Self {
if v.is_none() {
#clear_foreign_keys
}
self.#field_ident = sea_orm::ActiveBelongsTo::<#target_entity>::set(v);
self
}
#[doc = " Generated by sea-orm-macros"]
pub fn #clear_method(mut self) -> Self {
#clear_foreign_keys
self.#field_ident = sea_orm::ActiveBelongsTo::Set(None);
self
}
}
} else {
quote!()
};
Ok(quote! {
#[doc = " Generated by sea-orm-macros"]
pub fn #setter(mut self, v: impl Into<#active_model_type>) -> Self {
self.#field_ident = sea_orm::ActiveBelongsTo::<#target_entity>::set(#maybe_some(v.into()));
self
}
#optional_setters
})
}
CompoundKind::HasOne => {
let setter = format_ident!("set_{}", field_ident);
let optional_setter = format_ident!("set_{}_option", field_ident);
let clear_method = format_ident!("clear_{}", field_ident);
Ok(quote! {
#[doc = " Generated by sea-orm-macros"]
pub fn #setter(mut self, v: impl Into<#active_model_type>) -> Self {
self.#field_ident = sea_orm::ActiveHasOne::<#entity_path>::set(Some(v.into()));
self
}
#[doc = " Generated by sea-orm-macros"]
pub fn #optional_setter(mut self, v: Option<impl Into<#active_model_type>>) -> Self {
self.#field_ident = sea_orm::ActiveHasOne::<#entity_path>::set(v);
self
}
#[doc = " Generated by sea-orm-macros"]
pub fn #clear_method(mut self) -> Self {
self.#field_ident = sea_orm::ActiveHasOne::Set(None);
self
}
})
}
CompoundKind::HasMany => {
let setter = format_ident!(
"add_{}",
pluralizer::pluralize(&field_ident.to_string(), 1, false)
);
Ok(quote! {
#[doc = " Generated by sea-orm-macros"]
pub fn #setter(mut self, v: impl Into<#active_model_type>) -> Self {
self.#field_ident.push(v.into());
self
}
})
}
}
}
}
enum RelationLookup {
ByRelatedEntity,
ByRelationVariant(Ident),
}
enum Relation<'a> {
BelongsTo(BelongsToField<'a>),
HasOne(HasOneField<'a>),
HasMany(HasManyField<'a>),
HasManySelf(HasManySelfField<'a>),
ManyToMany(ManyToManyField<'a>),
}
#[derive(Default)]
struct ActiveModelActionTokens {
belongs_to_action: TokenStream,
belongs_to_after_action: TokenStream,
has_one_before_action: TokenStream,
has_one_action: TokenStream,
has_one_delete: TokenStream,
has_many_before_action: TokenStream,
has_many_action: TokenStream,
has_many_delete: TokenStream,
many_to_many_before_action: TokenStream,
many_to_many_action: TokenStream,
many_to_many_delete: TokenStream,
}
impl ActiveModelActionTokens {
fn from_fields(fields: &Fields<'_>) -> syn::Result<Self> {
fields
.0
.iter()
.filter_map(|field| {
let FieldKind::Relation(relation_field) = &field.kind else {
return None;
};
Some((field.ident, relation_field))
})
.try_fold(Self::default(), |mut output, (ident, relation_field)| {
let compound_type = &relation_field.compound_type;
let relation_attr = &relation_field.attr;
let is_unique_relation_target =
fields.relation_target_count(&compound_type.entity) == 1;
let relation = match relation_attr {
RelationAttr::HasManySelf { relation_enum } => {
Some(Relation::HasManySelf(HasManySelfField {
ident,
relation_variant: relation_enum.clone(),
}))
}
RelationAttr::BelongsTo {
from,
relation_enum,
} => {
let relation_lookup = match relation_enum {
Some(relation_enum) => RelationLookup::ByRelationVariant(Ident::new(
&relation_enum.value().to_upper_camel_case(),
relation_enum.span(),
)),
None => {
if is_unique_relation_target {
RelationLookup::ByRelatedEntity
} else {
RelationLookup::ByRelationVariant(Ident::new(
&infer_relation_name_from_entity(&compound_type.entity)
.to_upper_camel_case(),
Span::call_site(),
))
}
}
};
Some(Relation::BelongsTo(BelongsToField {
ident,
compound_type,
relation_lookup,
from,
fields,
}))
}
RelationAttr::HasOne if is_unique_relation_target => {
Some(Relation::HasOne(HasOneField {
ident,
entity: &compound_type.entity,
}))
}
RelationAttr::HasMany if is_unique_relation_target => {
Some(Relation::HasMany(HasManyField {
ident,
entity: &compound_type.entity,
}))
}
RelationAttr::ManyToMany { junction } if is_unique_relation_target => {
Some(Relation::ManyToMany(ManyToManyField {
ident,
junction_module: &junction.module,
kind: ManyToManyKind::Standard,
}))
}
RelationAttr::ManyToManySelf {
junction_module,
reverse,
} => Some(Relation::ManyToMany(ManyToManyField {
ident,
junction_module,
kind: ManyToManyKind::SelfRef { reverse: *reverse },
})),
_ => None,
};
if let Some(relation) = relation {
relation.expand_into(&mut output)?;
}
Ok::<_, syn::Error>(output)
})
}
fn expand(self) -> TokenStream {
let async_ = async_token();
let await_ = await_token();
let Self {
belongs_to_action,
belongs_to_after_action,
has_one_before_action,
has_one_action,
has_one_delete,
has_many_before_action,
has_many_action,
has_many_delete,
many_to_many_before_action,
many_to_many_action,
many_to_many_delete,
} = self;
quote! {
#[doc = " Generated by sea-orm-macros"]
pub #async_ fn insert<'a, C>(self, db: &'a C) -> Result<ModelEx, sea_orm::DbErr>
where
C: sea_orm::TransactionTrait,
{
let active_model = self.action(sea_orm::ActiveModelAction::Insert, db)#await_?;
active_model.try_into()
}
#[doc = " Generated by sea-orm-macros"]
pub #async_ fn update<'a, C>(self, db: &'a C) -> Result<ModelEx, sea_orm::DbErr>
where
C: sea_orm::TransactionTrait,
{
let active_model = self.action(sea_orm::ActiveModelAction::Update, db)#await_?;
active_model.try_into()
}
#[doc = " Generated by sea-orm-macros"]
pub #async_ fn save<'a, C>(self, db: &'a C) -> Result<Self, sea_orm::DbErr>
where
C: sea_orm::TransactionTrait,
{
self.action(sea_orm::ActiveModelAction::Save, db)#await_
}
#[doc = " Generated by sea-orm-macros"]
pub #async_ fn delete<'a, C>(self, db: &'a C) -> Result<sea_orm::DeleteResult, sea_orm::DbErr>
where
C: sea_orm::TransactionTrait,
{
use sea_orm::{IntoActiveModel, TransactionSession};
let txn = db.begin()#await_?;
let db = &txn;
let mut deleted = sea_orm::DeleteResult::empty();
#has_one_delete
#has_many_delete
#many_to_many_delete
let model: ActiveModel = self.into();
deleted.merge(model.delete(db)#await_?);
txn.commit()#await_?;
Ok(deleted)
}
#[doc = " Generated by sea-orm-macros"]
pub #async_ fn action<'a, C>(mut self, action: sea_orm::ActiveModelAction, db: &'a C) -> Result<Self, sea_orm::DbErr>
where
C: sea_orm::TransactionTrait,
{
use sea_orm::{ActiveBelongsTo, ActiveHasOne, ActiveHasMany, IntoActiveModel, TransactionSession};
let txn = db.begin()#await_?;
let db = &txn;
let mut deleted = sea_orm::DeleteResult::empty();
#belongs_to_action
#has_one_before_action
#has_many_before_action
#many_to_many_before_action
let model: ActiveModel = self.into();
let will_insert = !model.is_update()
&& matches!(
action,
sea_orm::ActiveModelAction::Insert | sea_orm::ActiveModelAction::Save
);
let mut model: Self = if model.is_changed() || will_insert {
match action {
sea_orm::ActiveModelAction::Insert => model.insert(db)#await_,
sea_orm::ActiveModelAction::Update => model.update(db)#await_,
sea_orm::ActiveModelAction::Save => if !model.is_update() {
model.insert(db)#await_
} else {
model.update(db)#await_
},
}?.into_ex().into()
} else {
model.into()
};
#belongs_to_after_action
#has_one_action
#has_many_action
#many_to_many_action
txn.commit()#await_?;
Ok(model)
}
}
}
}
#[derive(Default)]
struct Output<'a> {
model_field_defs: Vec<TokenStream>,
active_model_setters: TokenStream,
ignored_model_fields: Vec<&'a Ident>,
scalar_fields: Vec<&'a Ident>,
compound_fields: Vec<&'a Ident>,
}
impl<'a> Output<'a> {
fn from_fields(fields: &'a Fields<'a>) -> syn::Result<Self> {
let mut this = Self::default();
for field in &fields.0 {
field.expand_into(&mut this);
this.active_model_setters
.extend(ActiveModelSetter { field, fields }.expand()?);
}
Ok(this)
}
}
fn expand_active_model_ex<'a>(
vis: &Visibility,
ident: &Ident,
data: &Data,
fields: &'a Fields<'a>,
active_model_action: TokenStream,
) -> syn::Result<TokenStream> {
let async_ = async_token();
let await_ = await_token();
let active_model_trait_methods =
DeriveActiveModel::new(vis, ident, data)?.impl_active_model_trait_methods();
let Output {
model_field_defs,
active_model_setters,
ignored_model_fields,
scalar_fields,
compound_fields,
} = Output::from_fields(fields)?;
Ok(quote! {
#[doc = " Generated by sea-orm-macros"]
#[derive(Clone, Debug, PartialEq)]
#vis struct ActiveModelEx {
#(#model_field_defs),*
}
impl ActiveModel {
#[doc = " Generated by sea-orm-macros"]
pub fn into_ex(self) -> ActiveModelEx {
self.into()
}
}
#[automatically_derived]
impl sea_orm::ActiveModelTrait for ActiveModelEx {
type Entity = Entity;
#active_model_trait_methods
fn is_changed(&self) -> bool {
false #(|| self.#scalar_fields.is_set())* #(|| self.#compound_fields.is_changed())*
}
fn default() -> Self {
<ActiveModel as sea_orm::ActiveModelBehavior>::new().into()
}
}
impl ActiveModelEx {
#[doc = " Generated by sea-orm-macros"]
pub fn new() -> Self {
<Self as sea_orm::ActiveModelTrait>::default()
}
#active_model_action
#active_model_setters
}
#[automatically_derived]
impl std::default::Default for ActiveModelEx {
fn default() -> Self {
<Self as sea_orm::ActiveModelTrait>::default()
}
}
#[automatically_derived]
impl std::convert::From<ActiveModel> for ActiveModelEx {
fn from(m: ActiveModel) -> Self {
Self {
#(#scalar_fields: m.#scalar_fields,)*
#(#compound_fields: Default::default(),)*
}
}
}
#[automatically_derived]
impl std::convert::From<ActiveModelEx> for ActiveModel {
fn from(m: ActiveModelEx) -> Self {
Self {
#(#scalar_fields: m.#scalar_fields,)*
}
}
}
#[automatically_derived]
impl std::convert::From<ModelEx> for ActiveModelEx {
fn from(m: ModelEx) -> Self {
Self {
#(#scalar_fields: sea_orm::ActiveValue::Unchanged(m.#scalar_fields),)*
#(#compound_fields: m.#compound_fields.into_active_model(),)*
}
}
}
#[automatically_derived]
impl std::convert::TryFrom<ActiveModelEx> for ModelEx {
type Error = sea_orm::DbErr;
fn try_from(a: ActiveModelEx) -> Result<Self, sea_orm::DbErr> {
#(if a.#scalar_fields.is_not_set() {
return Err(sea_orm::DbErr::AttrNotSet(stringify!(#scalar_fields).to_owned()));
})*
Ok(
Self {
#(#scalar_fields: a.#scalar_fields.unwrap(),)*
#(#compound_fields: a.#compound_fields.try_into_model()?,)*
#(#ignored_model_fields: Default::default(),)*
}
)
}
}
#[automatically_derived]
impl sea_orm::IntoActiveModel<ActiveModelEx> for ModelEx {
fn into_active_model(self) -> ActiveModelEx {
self.into()
}
}
#[automatically_derived]
impl sea_orm::TryIntoModel<ModelEx> for ActiveModelEx {
fn try_into_model(self) -> Result<ModelEx, sea_orm::DbErr> {
self.try_into()
}
}
impl Model {
#[doc = " Generated by sea-orm-macros"]
pub #async_ fn cascade_delete<'a, C>(self, db: &'a C) -> Result<sea_orm::DeleteResult, sea_orm::DbErr>
where
C: sea_orm::TransactionTrait,
{
self.into_ex().delete(db)#await_
}
}
impl ModelEx {
#[doc = " Generated by sea-orm-macros"]
pub #async_ fn delete<'a, C>(self, db: &'a C) -> Result<sea_orm::DeleteResult, sea_orm::DbErr>
where
C: sea_orm::TransactionTrait,
{
let active_model: ActiveModelEx = self.into();
active_model.delete(db)#await_
}
}
impl ActiveModel {
#[doc = " Generated by sea-orm-macros"]
pub fn builder() -> ActiveModelEx {
ActiveModelEx::new()
}
}
})
}
struct BelongsToField<'a> {
ident: &'a Ident,
compound_type: &'a CompoundType,
relation_lookup: RelationLookup,
from: &'a RelationColumns,
fields: &'a Fields<'a>,
}
impl BelongsToField<'_> {
fn save_related_model(&self) -> TokenStream {
let await_ = await_token();
let box_pin = if cfg!(feature = "async") {
quote!(Box::pin)
} else {
quote!()
};
let set_parent_key = match &self.relation_lookup {
RelationLookup::ByRelatedEntity => quote!(self.set_parent_key(&model)?),
RelationLookup::ByRelationVariant(relation) => {
quote!(self.set_parent_key_for(&model, Relation::#relation)?)
}
};
let model_action = quote!(#box_pin(model.action(action, db))#await_?);
quote! {
let mut model = *model;
if model.is_update() {
#set_parent_key;
if model.is_changed() {
model = #model_action;
}
} else {
model = #model_action;
#set_parent_key;
}
}
}
fn expand_active_belongs_to_into(
&self,
output: &mut ActiveModelActionTokens,
cardinality: CardinalityKind,
) -> syn::Result<()> {
let ident = self.ident;
let related_entity = &self.compound_type.entity;
let save_model = self.save_related_model();
let from_columns = self.from;
let optional_foreign_key_fields = self.fields.optional_fields_for_columns(from_columns)?;
let action_arms = match cardinality {
CardinalityKind::Required => {
if !optional_foreign_key_fields.is_empty() {
return Err(syn::Error::new(
from_columns.span,
"BelongsTo<Entity> requires non-Option `from` fields",
));
}
quote! {
ActiveBelongsTo::Set(model) => {
#save_model
ActiveBelongsTo::<#related_entity>::set(model)
}
}
}
CardinalityKind::Optional => {
if optional_foreign_key_fields.is_empty() {
return Err(syn::Error::new(
from_columns.span,
"BelongsTo<Option<Entity>> requires at least one `Option<T>` `from` field",
));
}
quote! {
ActiveBelongsTo::Set(Some(model)) => {
#save_model
ActiveBelongsTo::<Option<#related_entity>>::set(Some(model))
}
ActiveBelongsTo::Set(None) => {
#(self.#optional_foreign_key_fields = sea_orm::Set(None);)*
ActiveBelongsTo::Set(None)
}
}
}
};
output.belongs_to_action.extend(quote! {
let #ident = match self.#ident.take() {
ActiveBelongsTo::NotSet => ActiveBelongsTo::NotSet,
#action_arms
};
});
output.belongs_to_after_action.extend(quote! {
if #ident.is_set() {
model.#ident = #ident;
}
});
Ok(())
}
fn expand_active_has_one_into(&self, output: &mut ActiveModelActionTokens) {
let ident = self.ident;
let save_model = self.save_related_model();
output.belongs_to_action.extend(quote! {
let #ident = match std::mem::take(&mut self.#ident) {
ActiveHasOne::Set(Some(model)) => {
#save_model
Some(model)
}
ActiveHasOne::NotSet | ActiveHasOne::Set(None) => None,
};
});
output.belongs_to_after_action.extend(quote! {
if let Some(#ident) = #ident {
model.#ident = ActiveHasOne::set(Some(#ident));
}
});
}
fn expand_into(&self, output: &mut ActiveModelActionTokens) -> syn::Result<()> {
match self.compound_type.kind {
CompoundKind::BelongsTo(cardinality) => {
self.expand_active_belongs_to_into(output, cardinality)
}
CompoundKind::HasOne => {
self.expand_active_has_one_into(output);
Ok(())
}
CompoundKind::HasMany => unreachable!("belongs_to field cannot be HasMany"),
}
}
}
struct HasOneField<'a> {
ident: &'a Ident,
entity: &'a TypePath,
}
impl HasOneField<'_> {
fn has_one_before_action(&self) -> TokenStream {
let ident = self.ident;
quote! {
let #ident = std::mem::take(&mut self.#ident);
}
}
fn has_one_action(&self) -> TokenStream {
let await_ = await_token();
let box_pin = if cfg!(feature = "async") {
quote!(Box::pin)
} else {
quote!()
};
let delete_associated_model = quote! {
let mut item = item.into_active_model();
if item.clear_parent_key::<Entity>()? {
item.update(db)#await_?;
} else {
deleted.merge(item.into_ex().delete(db)#await_?); }
};
let ident = self.ident;
let related_entity = self.entity;
let delete_existing_child = quote! {
if let Some(item) = model.find_related(#related_entity).one(db)#await_? {
let item_pk = sea_orm::ModelTrait::get_primary_key_value(&item);
let child_pk = sea_orm::ActiveModelTrait::get_primary_key_value(&child);
if child_pk.as_ref() != Some(&item_pk) {
#delete_associated_model
}
}
};
let set_child_action = quote! {
#delete_existing_child
child.set_parent_key(&model)?;
let child = if child.is_changed() {
#box_pin(child.action(action, db))#await_?
} else {
child
};
model.#ident = ActiveHasOne::<#related_entity>::set(Some(child));
};
quote! {
match #ident {
ActiveHasOne::NotSet => {}
ActiveHasOne::Set(Some(child)) => {
let mut child = *child;
#set_child_action
}
ActiveHasOne::Set(None) => {
if let Some(item) = model.find_related(#related_entity).one(db)#await_? {
#delete_associated_model
}
model.#ident = ActiveHasOne::Set(None);
}
}
}
}
fn has_one_delete(&self) -> TokenStream {
let await_ = await_token();
let related_entity = self.entity;
let delete_associated_model = quote! {
let mut item = item.into_active_model();
if item.clear_parent_key::<Entity>()? {
item.update(db)#await_?;
} else {
deleted.merge(item.into_ex().delete(db)#await_?); }
};
quote! {
if let Some(item) = self.find_related(#related_entity).one(db)#await_? {
#delete_associated_model
}
}
}
fn expand_into(&self, output: &mut ActiveModelActionTokens) {
output
.has_one_before_action
.extend(self.has_one_before_action());
output.has_one_action.extend(self.has_one_action());
output.has_one_delete.extend(self.has_one_delete());
}
}
struct HasManySelfField<'a> {
ident: &'a Ident,
relation_variant: LitStr,
}
impl HasManySelfField<'_> {
fn expand_into(&self, output: &mut ActiveModelActionTokens) {
let await_ = await_token();
let box_pin = if cfg!(feature = "async") {
quote!(Box::pin)
} else {
quote!()
};
let ident = self.ident;
let relation_variant =
Ident::new(&self.relation_variant.value(), self.relation_variant.span());
let relation_variant = quote!(Relation::#relation_variant);
let delete_associated_model = quote! {
let mut item = item.into_active_model();
if item.clear_parent_key_for_self_rev(#relation_variant)? {
item.update(db)#await_?;
} else {
return Err(sea_orm::DbErr::RecordNotUpdated);
}
};
let has_many_before_action = quote! {
let #ident = self.#ident.take();
};
let has_many_action = quote! {
if #ident.is_replace() {
for item in model.find_belongs_to_self(#relation_variant, db.get_database_backend())?.all(db)#await_? {
if !#ident.find(&item) {
#delete_associated_model
}
}
}
model.#ident = #ident.empty_holder();
for mut #ident in #ident.into_vec() {
#ident.set_parent_key_for_self_rev(&model, #relation_variant)?;
let #ident = if #ident.is_changed() {
#box_pin(#ident.action(action, db))#await_?
} else {
#ident
};
model.#ident.push(#ident);
}
};
let has_many_delete = quote! {
for item in self.find_belongs_to_self(#relation_variant, db.get_database_backend())?.all(db)#await_? {
#delete_associated_model
}
};
output.has_many_before_action.extend(has_many_before_action);
output.has_many_action.extend(has_many_action);
output.has_many_delete.extend(has_many_delete);
}
}
enum ManyToManyKind {
Standard,
SelfRef { reverse: bool },
}
struct ManyToManyField<'a> {
ident: &'a Ident,
junction_module: &'a Ident,
kind: ManyToManyKind,
}
impl ManyToManyField<'_> {
fn expand_into(&self, output: &mut ActiveModelActionTokens) {
let await_ = await_token();
let box_pin = if cfg!(feature = "async") {
quote!(Box::pin)
} else {
quote!()
};
let ident = self.ident;
let junction_module = self.junction_module;
let junction_entity = quote!(super::#junction_module::Entity);
let (establish_links, delete_links) = match &self.kind {
ManyToManyKind::Standard => ("establish_links", "delete_links"),
ManyToManyKind::SelfRef { reverse: false } => {
("establish_links_self", "delete_links_self")
}
ManyToManyKind::SelfRef { reverse: true } => {
("establish_links_self_rev", "delete_links_self")
}
};
let establish_links = Ident::new(establish_links, ident.span());
let delete_links = Ident::new(delete_links, ident.span());
let many_to_many_before_action = quote! {
let #ident = self.#ident.take();
};
let many_to_many_action = quote! {
model.#ident = #ident.empty_holder();
for item in #ident.into_vec() {
let item = if item.is_update() && !item.is_changed() {
item
} else {
#box_pin(item.action(action, db))#await_?
};
model.#ident.push(item);
}
model.#establish_links(
#junction_entity,
model.#ident.as_slice(),
model.#ident.is_replace(),
db
)#await_?;
};
let many_to_many_delete = quote! {
deleted.merge(self.#delete_links(#junction_entity, db)#await_?);
};
output
.many_to_many_before_action
.extend(many_to_many_before_action);
output.many_to_many_action.extend(many_to_many_action);
output.many_to_many_delete.extend(many_to_many_delete);
}
}
struct HasManyField<'a> {
ident: &'a Ident,
entity: &'a TypePath,
}
impl HasManyField<'_> {
fn has_many_before_action(&self) -> TokenStream {
let ident = self.ident;
quote! {
let #ident = self.#ident.take();
}
}
fn has_many_action(&self) -> TokenStream {
let await_ = await_token();
let box_pin = if cfg!(feature = "async") {
quote!(Box::pin)
} else {
quote!()
};
let ident = self.ident;
let related_entity = self.entity;
let delete_associated_model = quote! {
let mut item = item.into_active_model();
if item.clear_parent_key::<Entity>()? {
item.update(db)#await_?;
} else {
deleted.merge(item.into_ex().delete(db)#await_?); }
};
quote! {
if #ident.is_replace() {
for item in model.find_related(#related_entity).all(db)#await_? {
if !#ident.find(&item) {
#delete_associated_model
}
}
}
model.#ident = #ident.empty_holder();
for mut #ident in #ident.into_vec() {
#ident.set_parent_key(&model)?;
let #ident = if #ident.is_changed() {
#box_pin(#ident.action(action, db))#await_?
} else {
#ident
};
model.#ident.push(#ident);
}
}
}
fn has_many_delete(&self) -> TokenStream {
let await_ = await_token();
let related_entity = self.entity;
let delete_associated_model = quote! {
let mut item = item.into_active_model();
if item.clear_parent_key::<Entity>()? {
item.update(db)#await_?;
} else {
deleted.merge(item.into_ex().delete(db)#await_?); }
};
quote! {
for item in self.find_related(#related_entity).all(db)#await_? {
#delete_associated_model
}
}
}
fn expand_into(&self, output: &mut ActiveModelActionTokens) {
output
.has_many_before_action
.extend(self.has_many_before_action());
output.has_many_action.extend(self.has_many_action());
output.has_many_delete.extend(self.has_many_delete());
}
}
impl Relation<'_> {
fn expand_into(&self, output: &mut ActiveModelActionTokens) -> syn::Result<()> {
match self {
Self::BelongsTo(field) => field.expand_into(output)?,
Self::HasOne(field) => field.expand_into(output),
Self::HasMany(field) => field.expand_into(output),
Self::HasManySelf(field) => field.expand_into(output),
Self::ManyToMany(field) => field.expand_into(output),
}
Ok(())
}
}
pub fn expand_derive_active_model_ex(
vis: &Visibility,
ident: &Ident,
data: &Data,
attrs: &[Attribute],
) -> syn::Result<TokenStream> {
let mut compact = false;
attrs
.iter()
.filter(|attr| attr.path().is_ident("sea_orm"))
.try_for_each(|attr| {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("compact_model") {
compact = true;
} else {
consume_meta(meta);
}
Ok(())
})
})?;
let mode = if compact {
FieldParseMode::Compact
} else {
FieldParseMode::Dense
};
let fields = Fields::from_data(data, ident, mode)?;
let active_model_action_tokens = if compact {
ActiveModelActionTokens::default()
} else {
ActiveModelActionTokens::from_fields(&fields)?
};
let active_model_action = active_model_action_tokens.expand();
expand_active_model_ex(vis, ident, data, &fields, active_model_action)
}