Skip to main content

rust_ef/
db_set.rs

1//! DbSet<T> �?entry point for querying and manipulating entity collections.
2//!
3//! `DbSet<T>` represents a typed collection of entities that can be queried
4//! and mutated. It implements two interfaces following ISP:
5//!   - `IQueryable<T>` �?query capabilities
6//!   - `IDbSet<T>`     �?collection mutation capabilities
7
8use crate::entity::{
9    EntityState, IEntitySnapshot, IEntityType, IFromRow, IGetKeyValues, INavigationSetter,
10};
11use crate::error::EFResult;
12use crate::provider::{DbValue, IDatabaseProvider};
13use crate::query::{BoolExpr, IQueryable, QueryBuilder};
14use std::collections::HashMap;
15use std::sync::Arc;
16
17// ---------------------------------------------------------------------------
18// IDbSet<T> �?interface for entity collection manipulation
19// ---------------------------------------------------------------------------
20
21/// Interface for manipulating a typed entity collection.
22///
23/// Separates mutation concerns from query concerns (see `IQueryable<T>`).
24/// Implemented by `DbSet<T>`.
25pub trait IDbSet<T: IEntityType>: IQueryable<T> + Send + Sync {
26    /// Adds a new entity to the set in Added state.
27    fn add(&mut self, entity: T);
28
29    /// Marks all entities in the set as Deleted.
30    fn remove_all(&mut self);
31
32    /// Marks the entity at the given index as Deleted.
33    fn remove_at(&mut self, index: usize) -> EFResult<()>;
34
35    /// Attaches an existing entity in Unchanged state.
36    fn attach(&mut self, entity: T);
37
38    /// Returns references to entities in Added state.
39    fn added_entities(&self) -> Vec<&T>;
40
41    /// Returns references to entities in Modified state.
42    fn modified_entities(&self) -> Vec<&T>;
43
44    /// Returns references to entities in Deleted state.
45    fn deleted_entities(&self) -> Vec<&T>;
46
47    /// Returns an iterator over entity references and their states.
48    fn entries_with_state(&self) -> Vec<(&T, EntityState)>;
49
50    /// Clears all tracked entries from the set.
51    fn clear_entries(&mut self);
52
53    /// Returns the number of tracked entries.
54    fn len(&self) -> usize;
55
56    /// Returns whether the set is empty.
57    fn is_empty(&self) -> bool;
58}
59
60// ---------------------------------------------------------------------------
61// DbSet<T> �?concrete implementation
62// ---------------------------------------------------------------------------
63
64pub struct DbSet<T: IEntityType> {
65    pub(crate) entries: Vec<TrackedEntry<T>>,
66    table_name: String,
67    provider: Option<Arc<dyn IDatabaseProvider>>,
68    query_filter: Option<BoolExpr>,
69    filter_map: Option<Arc<HashMap<String, crate::query::CompiledFilter>>>,
70    lazy_loading_enabled: bool,
71}
72
73pub struct TrackedEntry<T: IEntityType> {
74    pub entity: T,
75    pub state: EntityState,
76    /// Snapshot taken when the entity was attached (for change detection).
77    pub original: Option<HashMap<String, DbValue>>,
78}
79
80impl<T: IEntityType + IEntitySnapshot> DbSet<T> {
81    pub fn new(table_name: impl Into<String>) -> Self {
82        Self {
83            entries: Vec::new(),
84            table_name: table_name.into(),
85            provider: None,
86            query_filter: None,
87            filter_map: None,
88            lazy_loading_enabled: false,
89        }
90    }
91
92    pub fn with_provider(
93        table_name: impl Into<String>,
94        provider: Arc<dyn IDatabaseProvider>,
95    ) -> Self {
96        Self {
97            entries: Vec::new(),
98            table_name: table_name.into(),
99            provider: Some(provider),
100            query_filter: None,
101            filter_map: None,
102            lazy_loading_enabled: false,
103        }
104    }
105
106    pub fn set_query_filter(&mut self, filter: BoolExpr) {
107        self.query_filter = Some(filter);
108    }
109
110    /// Sets the global filter map (table_name → BoolExpr) used by
111    /// NavigationLoader to scope secondary queries.
112    pub fn set_filter_map(&mut self, map: Arc<HashMap<String, crate::query::CompiledFilter>>) {
113        self.filter_map = Some(map);
114    }
115
116    /// Propagates the lazy-loading flag from `DbContextOptions` to this set.
117    pub(crate) fn set_lazy_loading_enabled(&mut self, enabled: bool) {
118        self.lazy_loading_enabled = enabled;
119    }
120
121    /// Returns the configured query filter, if any. Used by `save_one_set`
122    /// to apply tenant isolation to UPDATE/DELETE WHERE clauses.
123    pub(crate) fn query_filter(&self) -> Option<&BoolExpr> {
124        self.query_filter.as_ref()
125    }
126
127    pub fn set_provider(&mut self, provider: Arc<dyn IDatabaseProvider>) {
128        self.provider = Some(provider);
129    }
130
131    // ── Convenience inherent methods �?delegate to trait implementations ──
132
133    /// Convenience inherent method �?delegates to `IDbSet::add`.
134    pub fn add(&mut self, entity: T) {
135        IDbSet::add(self, entity);
136    }
137
138    /// Convenience inherent method �?delegates to `IDbSet::remove_all`.
139    pub fn remove_all(&mut self) {
140        IDbSet::remove_all(self);
141    }
142
143    /// Convenience inherent method �?delegates to `IDbSet::remove_at`.
144    pub fn remove_at(&mut self, index: usize) -> EFResult<()> {
145        IDbSet::remove_at(self, index)
146    }
147
148    /// Convenience inherent method �?delegates to `IDbSet::clear_entries`.
149    pub fn clear_entries(&mut self) {
150        IDbSet::clear_entries(self);
151    }
152
153    /// Convenience inherent method �?delegates to `IDbSet::len`.
154    pub fn len(&self) -> usize {
155        IDbSet::len(self)
156    }
157
158    /// Convenience inherent method �?delegates to `IDbSet::is_empty`.
159    pub fn is_empty(&self) -> bool {
160        IDbSet::is_empty(self)
161    }
162
163    /// Convenience inherent method — delegates to `IQueryable::query`.
164    pub fn query(&self) -> QueryBuilder<T> {
165        IQueryable::query(self)
166    }
167
168    /// Returns a query builder that bypasses the configured query filter.
169    /// Use for administrative / cross-tenant queries.
170    pub fn query_ignore_filters(&self) -> QueryBuilder<T> {
171        let qb = match &self.provider {
172            Some(p) => QueryBuilder::with_provider(&self.table_name, p.clone()),
173            None => QueryBuilder::new(&self.table_name),
174        };
175        qb.with_filter_map(self.filter_map.clone())
176            .with_lazy_loading(self.lazy_loading_enabled)
177    }
178
179    pub fn attach(&mut self, entity: T) {
180        IDbSet::attach(self, entity);
181    }
182
183    pub fn tracked_entries(&self) -> impl Iterator<Item = &T> {
184        self.entries.iter().map(|e| &e.entity)
185    }
186
187    pub fn tracked_entries_mut(&mut self) -> impl Iterator<Item = &mut T> {
188        self.entries.iter_mut().map(|e| &mut e.entity)
189    }
190
191    pub fn retain(&mut self, f: impl FnMut(&TrackedEntry<T>) -> bool) {
192        self.entries.retain(f);
193    }
194
195    /// Marks multiple entities as deleted.
196    pub fn remove_range(&mut self, entities: &[T])
197    where
198        T: PartialEq,
199    {
200        for entity in entities {
201            if let Some(entry) = self.entries.iter_mut().find(|e| e.entity == *entity) {
202                entry.state = EntityState::Deleted;
203            }
204        }
205    }
206
207    /// Loads all rows from the database into the change tracker as Unchanged.
208    pub async fn load_all(&mut self) -> EFResult<()>
209    where
210        T: IFromRow
211            + INavigationSetter
212            + IGetKeyValues
213            + IEntitySnapshot
214            + crate::entity::ILazyInit,
215    {
216        let items = self.query().to_list().await?;
217        self.clear_entries();
218        for item in items {
219            self.attach(item);
220        }
221        Ok(())
222    }
223
224    /// Marks an entity as modified.
225    pub fn update(&mut self, entity: T) {
226        self.entries.push(TrackedEntry {
227            entity,
228            state: EntityState::Modified,
229            original: None,
230        });
231    }
232
233    /// Compares attached entities against their original snapshots and marks changes.
234    pub fn detect_changes(&mut self)
235    where
236        T: IEntitySnapshot,
237    {
238        for entry in &mut self.entries {
239            if entry.state != EntityState::Unchanged {
240                continue;
241            }
242            if let Some(ref original) = entry.original {
243                if entry.entity.snapshot() != *original {
244                    entry.state = EntityState::Modified;
245                }
246            }
247        }
248    }
249
250    /// Returns tracked entities in the given state with their original snapshots.
251    pub(crate) fn tracked_by_state(
252        &self,
253        state: EntityState,
254    ) -> Vec<(&T, Option<&HashMap<String, DbValue>>)> {
255        self.entries
256            .iter()
257            .filter(|e| e.state == state)
258            .map(|e| (&e.entity, e.original.as_ref()))
259            .collect()
260    }
261    pub async fn exists_by_id(&self, key_values: HashMap<String, DbValue>) -> EFResult<bool>
262    where
263        T: IFromRow
264            + INavigationSetter
265            + IGetKeyValues
266            + IEntitySnapshot
267            + crate::entity::ILazyInit,
268    {
269        let pairs: Vec<(&str, DbValue)> = key_values
270            .iter()
271            .map(|(k, v)| (k.as_str(), v.clone()))
272            .collect();
273        Ok(self.query().find_by_key(&pairs).await?.is_some())
274    }
275
276    /// Starts a query filtered by a compile-time LINQ expression tree (`linq!(�?`).
277    pub fn filter<F>(&self, apply: F) -> QueryBuilder<T>
278    where
279        F: FnOnce(QueryBuilder<T>) -> QueryBuilder<T>,
280    {
281        apply(self.query())
282    }
283}
284
285// ---------------------------------------------------------------------------
286// IQueryable<T> implementation
287// ---------------------------------------------------------------------------
288
289impl<T: IEntityType> IQueryable<T> for DbSet<T> {
290    fn query(&self) -> QueryBuilder<T> {
291        let mut qb = match &self.provider {
292            Some(p) => QueryBuilder::with_provider(&self.table_name, p.clone()),
293            None => QueryBuilder::new(&self.table_name),
294        };
295        if let Some(ref filter) = self.query_filter {
296            qb = qb.apply_query_filter(filter.clone());
297        }
298        qb.with_filter_map(self.filter_map.clone())
299            .with_lazy_loading(self.lazy_loading_enabled)
300    }
301}
302
303// ---------------------------------------------------------------------------
304// IDbSet<T> implementation
305// ---------------------------------------------------------------------------
306
307impl<T: IEntityType + IEntitySnapshot> IDbSet<T> for DbSet<T> {
308    fn add(&mut self, entity: T) {
309        self.entries.push(TrackedEntry {
310            entity,
311            state: EntityState::Added,
312            original: None,
313        });
314    }
315
316    fn remove_all(&mut self) {
317        for entry in &mut self.entries {
318            entry.state = EntityState::Deleted;
319        }
320    }
321
322    fn remove_at(&mut self, index: usize) -> EFResult<()> {
323        if let Some(entry) = self.entries.get_mut(index) {
324            entry.state = EntityState::Deleted;
325            Ok(())
326        } else {
327            Err(crate::error::EFError::not_found(
328                "Entity not found at the given index".to_string(),
329            ))
330        }
331    }
332
333    fn attach(&mut self, entity: T) {
334        let original = entity.snapshot();
335        self.entries.push(TrackedEntry {
336            entity,
337            state: EntityState::Unchanged,
338            original: Some(original),
339        });
340    }
341
342    fn added_entities(&self) -> Vec<&T> {
343        self.entries
344            .iter()
345            .filter(|e| e.state == EntityState::Added)
346            .map(|e| &e.entity)
347            .collect()
348    }
349
350    fn modified_entities(&self) -> Vec<&T> {
351        self.entries
352            .iter()
353            .filter(|e| e.state == EntityState::Modified)
354            .map(|e| &e.entity)
355            .collect()
356    }
357
358    fn deleted_entities(&self) -> Vec<&T> {
359        self.entries
360            .iter()
361            .filter(|e| e.state == EntityState::Deleted)
362            .map(|e| &e.entity)
363            .collect()
364    }
365
366    fn entries_with_state(&self) -> Vec<(&T, EntityState)> {
367        self.entries.iter().map(|e| (&e.entity, e.state)).collect()
368    }
369
370    fn clear_entries(&mut self) {
371        self.entries.clear();
372    }
373
374    fn len(&self) -> usize {
375        self.entries.len()
376    }
377
378    fn is_empty(&self) -> bool {
379        self.entries.is_empty()
380    }
381}