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}