rust-ef 1.5.2

Rust Entity Framework - An EFCore-inspired ORM for Rust
Documentation
//! Entity type trait and state definitions.

use crate::error::EFResult;
use crate::metadata::EntityTypeMeta;
use crate::provider::DbValue;
use std::any::TypeId;
use std::collections::HashMap;

// ---------------------------------------------------------------------------
// Entity materialization ?converts raw rows into entity instances
// ---------------------------------------------------------------------------

/// Materializes entities from raw database row data using the `IFromRow` trait.
pub fn materialize_entities<T: IEntityType + IFromRow>(rows: &[Vec<DbValue>]) -> EFResult<Vec<T>> {
    let mut entities = Vec::with_capacity(rows.len());
    for row in rows {
        let entity = T::from_row(row)?;
        entities.push(entity);
    }
    Ok(entities)
}

/// Represents the state of an entity in the change tracker.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntityState {
    Detached,
    Added,
    Unchanged,
    Modified,
    Deleted,
}

/// The core trait that every entity type must implement.
pub trait IEntityType: Send + Sync + 'static {
    fn entity_meta() -> EntityTypeMeta
    where
        Self: Sized;

    fn entity_meta_instance(&self) -> EntityTypeMeta
    where
        Self: Sized,
    {
        Self::entity_meta()
    }
}

/// Trait for materializing an entity from a database row.
///
/// Used by [`materialize_entities`] to deserialize query results.
/// Auto-generated by `#[derive(EntityType)]`.
pub trait IFromRow: IEntityType + Sized {
    fn from_row(values: &[DbValue]) -> EFResult<Self>;
}

/// Extracts primary key values from an entity for SaveChanges WHERE clauses.
///
/// Auto-generated by `#[derive(EntityType)]`.
pub trait IGetKeyValues: IEntityType {
    fn key_values(&self) -> HashMap<String, DbValue>;

    /// Sets the auto-increment primary key after INSERT.
    ///
    /// Called by the change executor to backfill database-generated keys into
    /// the entity. The default implementation is a no-op (for entities without
    /// an auto-increment PK, or for manual implementations that haven't
    /// overridden it). The `#[derive(EntityType)]` macro overrides this for
    /// entities with a single `#[auto_increment]` `#[primary_key]` field.
    fn set_auto_increment_key(&mut self, _key: i64) {}

    /// Sets the foreign key field pointing to `target_type` to `key`.
    ///
    /// Called by the cascade save pipeline to fixup child FKs after the
    /// principal's auto-increment PK is backfilled. The default
    /// implementation is a no-op. The `#[derive(EntityType)]` macro
    /// overrides this for each `#[foreign_key(Target)]` scalar field.
    fn set_foreign_key(&mut self, _target_type: TypeId, _key: i64) {}
}

/// Extracts all scalar property values from an entity for INSERT/UPDATE.
///
/// Auto-generated by `#[derive(EntityType)]`.
pub trait IEntitySnapshot: IEntityType {
    fn snapshot(&self) -> HashMap<String, DbValue>;
}

/// Attaches lazy-loading contexts to navigation properties.
///
/// Auto-generated by `#[derive(EntityType)]`. Called by `QueryBuilder::to_list()`
/// when lazy loading is enabled on the `DbContext`. For each navigation field,
/// the macro implementation constructs a `LazyContextImpl` and calls
/// `set_lazy_context` on the corresponding container.
///
/// Entities without navigation properties get a no-op implementation.
pub trait ILazyInit: IEntityType {
    /// Attaches lazy contexts to all navigation properties on this entity.
    ///
    /// `depth` is the current recursion depth (0 for top-level entities
    /// materialized by `to_list`). Each lazy `load()` call increments the
    /// depth when attaching contexts to child entities.
    fn attach_lazy_contexts(
        &mut self,
        provider: std::sync::Arc<dyn crate::provider::IDatabaseProvider>,
        filter_map: Option<
            std::sync::Arc<std::collections::HashMap<String, crate::query::CompiledFilter>>,
        >,
        depth: usize,
    );
}

/// Applies eagerly-loaded navigation data to an entity.
///
/// Auto-generated by `#[derive(EntityType)]` for types with navigation properties.
#[async_trait::async_trait]
pub trait INavigationSetter: IEntityType {
    fn apply_has_many(&mut self, field: &str, rows: &[Vec<DbValue>]) -> EFResult<()> {
        let _ = (field, rows);
        Ok(())
    }

    fn apply_reference(&mut self, field: &str, row: &[DbValue]) -> EFResult<()> {
        let _ = (field, row);
        Ok(())
    }

    /// Drains all items from a HasMany navigation field, returning them as
    /// type-erased boxed values. The container is left empty after this call.
    ///
    /// Used by the cascade save pipeline to extract Added children from
    /// principal entities before INSERT. Returns `None` if the field is not a
    /// HasMany navigation or the container is empty.
    ///
    /// The `#[derive(EntityType)]` macro overrides this for each HasMany field.
    fn drain_has_many(
        &mut self,
        field: &str,
    ) -> Option<Vec<Box<dyn std::any::Any + Send + Sync>>> {
        let _ = field;
        None
    }

    /// Loads nested includes across multiple entities in batch.
    ///
    /// Collects children for `parent_navigation` across all `entities`,
    /// batch-loads their nested includes with a single SQL query per level
    /// (avoids N+1), then recurses for deeper ThenInclude levels.
    ///
    /// `filter_map` carries per-table query filters (e.g. tenant isolation)
    /// so that nested navigation loading respects the same filters as
    /// top-level loading.
    #[allow(clippy::type_complexity)]
    async fn load_nested_includes(
        entities: &mut [Self],
        parent_navigation: &str,
        nested: &[crate::query::IncludePath],
        provider: &dyn crate::provider::IDatabaseProvider,
        filter_map: Option<&std::collections::HashMap<String, crate::query::CompiledFilter>>,
    ) -> EFResult<()>
    where
        Self: Sized,
    {
        let _ = (entities, parent_navigation, nested, provider, filter_map);
        Ok(())
    }
}