toasty-core 0.10.0

Core types, schema representations, and driver interface for Toasty
Documentation
//! Mapping between app-level models and database-level tables.
//!
//! The types in this module define how each model field corresponds to one or
//! more database columns. The mapping supports:
//!
//! - Primitive fields that map 1:1 to a column
//! - Embedded structs that flatten into multiple columns
//! - Embedded enums stored as a discriminant column plus per-variant data columns
//! - Relation fields that have no direct column storage
//!
//! The root type is [`Mapping`], which holds a [`Model`] entry for each model.
//! Each `Model` contains per-field [`Field`] mappings and the expression
//! templates ([`Model::model_to_table`] and [`TableToModel`]) used during
//! query lowering.
//!
//! # Examples
//!
//! ```ignore
//! use toasty_core::schema::mapping::Mapping;
//!
//! // Access the mapping for a specific model
//! let model_mapping = mapping.model(model_id);
//! println!("backed by table {:?}", model_mapping.table);
//! ```

mod field;
pub use field::{EnumVariant, Field, FieldEnum, FieldPrimitive, FieldRelation, FieldStruct};

mod model;
pub use model::{Model, TableToModel};

use super::{app::ModelId, db::ColumnId};
use crate::stmt;
use indexmap::IndexMap;

/// Defines the correspondence between app-level models and database-level
/// tables.
///
/// The mapping is constructed during schema building and remains immutable at
/// runtime. It provides the translation layer that enables the query engine to
/// convert model-oriented statements into table-oriented statements during the
/// lowering phase.
///
/// # Examples
///
/// ```ignore
/// use toasty_core::schema::mapping::Mapping;
/// use indexmap::IndexMap;
///
/// let mapping = Mapping { models: IndexMap::new() };
/// assert_eq!(mapping.models.len(), 0);
/// ```
#[derive(Debug, Clone)]
pub struct Mapping {
    /// Per-model mappings indexed by model identifier.
    pub models: IndexMap<ModelId, Model>,

    /// The app-level type of each `#[document]` column.
    ///
    /// A document column's [`db::Column`](crate::schema::db::Column) is typed
    /// by the structural [`stmt::Type::Object`] — the column does not know
    /// which embedded model it stores. That knowledge normally travels inside
    /// the mapping's cast expressions (`model_to_table` carries the lowering
    /// cast, `table_to_model` the raising cast); this index covers the
    /// operations that bypass those templates — an `Append` assignment's
    /// operand cast during statement lowering.
    pub document_columns: IndexMap<ColumnId, stmt::Type>,
}

impl Mapping {
    /// Returns the mapping for the specified model.
    ///
    /// # Panics
    ///
    /// Panics if the model ID does not exist in the mapping.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let model_mapping = mapping.model(model_id);
    /// println!("table: {:?}", model_mapping.table);
    /// ```
    pub fn model(&self, id: impl Into<ModelId>) -> &Model {
        self.models.get(&id.into()).expect("invalid model ID")
    }

    /// Returns a mutable reference to the mapping for the specified model.
    ///
    /// # Panics
    ///
    /// Panics if the model ID does not exist in the mapping.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// let model_mapping = mapping.model_mut(model_id);
    /// // modify fields, columns, etc.
    /// ```
    pub fn model_mut(&mut self, id: impl Into<ModelId>) -> &mut Model {
        self.models.get_mut(&id.into()).expect("invalid model ID")
    }

    /// Returns the app-level type of a `#[document]` column — `Type::Model`
    /// for a bare embed, `List(Model)` for an embed collection — or `None` if
    /// the column does not store a document.
    pub fn document_column_ty(&self, id: impl Into<ColumnId>) -> Option<&stmt::Type> {
        self.document_columns.get(&id.into())
    }
}