Skip to main content

rust_ef/
entity.rs

1//! Entity type trait and state definitions.
2
3use crate::error::EFResult;
4use crate::metadata::EntityTypeMeta;
5use crate::provider::DbValue;
6use std::any::TypeId;
7use std::collections::HashMap;
8
9// ---------------------------------------------------------------------------
10// Entity materialization ?converts raw rows into entity instances
11// ---------------------------------------------------------------------------
12
13/// Materializes entities from raw database row data using the `IFromRow` trait.
14pub fn materialize_entities<T: IEntityType + IFromRow>(rows: &[Vec<DbValue>]) -> EFResult<Vec<T>> {
15    let mut entities = Vec::with_capacity(rows.len());
16    for row in rows {
17        let entity = T::from_row(row)?;
18        entities.push(entity);
19    }
20    Ok(entities)
21}
22
23/// Represents the state of an entity in the change tracker.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum EntityState {
26    Detached,
27    Added,
28    Unchanged,
29    Modified,
30    Deleted,
31}
32
33/// The core trait that every entity type must implement.
34pub trait IEntityType: Send + Sync + 'static {
35    fn entity_meta() -> EntityTypeMeta
36    where
37        Self: Sized;
38
39    fn entity_meta_instance(&self) -> EntityTypeMeta
40    where
41        Self: Sized,
42    {
43        Self::entity_meta()
44    }
45}
46
47/// Trait for materializing an entity from a database row.
48///
49/// Used by [`materialize_entities`] to deserialize query results.
50/// Auto-generated by `#[derive(EntityType)]`.
51pub trait IFromRow: IEntityType + Sized {
52    fn from_row(values: &[DbValue]) -> EFResult<Self>;
53}
54
55/// Extracts primary key values from an entity for SaveChanges WHERE clauses.
56///
57/// Auto-generated by `#[derive(EntityType)]`.
58pub trait IGetKeyValues: IEntityType {
59    fn key_values(&self) -> HashMap<String, DbValue>;
60
61    /// Sets the auto-increment primary key after INSERT.
62    ///
63    /// Called by the change executor to backfill database-generated keys into
64    /// the entity. The default implementation is a no-op (for entities without
65    /// an auto-increment PK, or for manual implementations that haven't
66    /// overridden it). The `#[derive(EntityType)]` macro overrides this for
67    /// entities with a single `#[auto_increment]` `#[primary_key]` field.
68    fn set_auto_increment_key(&mut self, _key: i64) {}
69
70    /// Sets the foreign key field pointing to `target_type` to `key`.
71    ///
72    /// Called by the cascade save pipeline to fixup child FKs after the
73    /// principal's auto-increment PK is backfilled. The default
74    /// implementation is a no-op. The `#[derive(EntityType)]` macro
75    /// overrides this for each `#[foreign_key(Target)]` scalar field.
76    fn set_foreign_key(&mut self, _target_type: TypeId, _key: i64) {}
77}
78
79/// Extracts all scalar property values from an entity for INSERT/UPDATE.
80///
81/// Auto-generated by `#[derive(EntityType)]`.
82pub trait IEntitySnapshot: IEntityType {
83    fn snapshot(&self) -> HashMap<String, DbValue>;
84}
85
86/// Attaches lazy-loading contexts to navigation properties.
87///
88/// Auto-generated by `#[derive(EntityType)]`. Called by `QueryBuilder::to_list()`
89/// when lazy loading is enabled on the `DbContext`. For each navigation field,
90/// the macro implementation constructs a `LazyContextImpl` and calls
91/// `set_lazy_context` on the corresponding container.
92///
93/// Entities without navigation properties get a no-op implementation.
94pub trait ILazyInit: IEntityType {
95    /// Attaches lazy contexts to all navigation properties on this entity.
96    ///
97    /// `depth` is the current recursion depth (0 for top-level entities
98    /// materialized by `to_list`). Each lazy `load()` call increments the
99    /// depth when attaching contexts to child entities.
100    fn attach_lazy_contexts(
101        &mut self,
102        provider: std::sync::Arc<dyn crate::provider::IDatabaseProvider>,
103        filter_map: Option<
104            std::sync::Arc<std::collections::HashMap<String, crate::query::CompiledFilter>>,
105        >,
106        depth: usize,
107    );
108}
109
110/// Applies eagerly-loaded navigation data to an entity.
111///
112/// Auto-generated by `#[derive(EntityType)]` for types with navigation properties.
113#[async_trait::async_trait]
114pub trait INavigationSetter: IEntityType {
115    fn apply_has_many(&mut self, field: &str, rows: &[Vec<DbValue>]) -> EFResult<()> {
116        let _ = (field, rows);
117        Ok(())
118    }
119
120    fn apply_reference(&mut self, field: &str, row: &[DbValue]) -> EFResult<()> {
121        let _ = (field, row);
122        Ok(())
123    }
124
125    /// Drains all items from a HasMany navigation field, returning them as
126    /// type-erased boxed values. The container is left empty after this call.
127    ///
128    /// Used by the cascade save pipeline to extract Added children from
129    /// principal entities before INSERT. Returns `None` if the field is not a
130    /// HasMany navigation or the container is empty.
131    ///
132    /// The `#[derive(EntityType)]` macro overrides this for each HasMany field.
133    fn drain_has_many(&mut self, field: &str) -> Option<Vec<Box<dyn std::any::Any + Send + Sync>>> {
134        let _ = field;
135        None
136    }
137
138    /// Loads nested includes across multiple entities in batch.
139    ///
140    /// Collects children for `parent_navigation` across all `entities`,
141    /// batch-loads their nested includes with a single SQL query per level
142    /// (avoids N+1), then recurses for deeper ThenInclude levels.
143    ///
144    /// `filter_map` carries per-table query filters (e.g. tenant isolation)
145    /// so that nested navigation loading respects the same filters as
146    /// top-level loading.
147    #[allow(clippy::type_complexity)]
148    async fn load_nested_includes(
149        entities: &mut [Self],
150        parent_navigation: &str,
151        nested: &[crate::query::IncludePath],
152        provider: &dyn crate::provider::IDatabaseProvider,
153        filter_map: Option<&std::collections::HashMap<String, crate::query::CompiledFilter>>,
154    ) -> EFResult<()>
155    where
156        Self: Sized,
157    {
158        let _ = (entities, parent_navigation, nested, provider, filter_map);
159        Ok(())
160    }
161}