Skip to main content

toasty_core/schema/
mapping.rs

1//! Mapping between app-level models and database-level tables.
2//!
3//! The types in this module define how each model field corresponds to one or
4//! more database columns. The mapping supports:
5//!
6//! - Primitive fields that map 1:1 to a column
7//! - Embedded structs that flatten into multiple columns
8//! - Embedded enums stored as a discriminant column plus per-variant data columns
9//! - Relation fields that have no direct column storage
10//!
11//! The root type is [`Mapping`], which holds a [`Model`] entry for each model.
12//! Each `Model` contains per-field [`Field`] mappings and the expression
13//! templates ([`Model::model_to_table`] and [`TableToModel`]) used during
14//! query lowering.
15//!
16//! # Examples
17//!
18//! ```ignore
19//! use toasty_core::schema::mapping::Mapping;
20//!
21//! // Access the mapping for a specific model
22//! let model_mapping = mapping.model(model_id);
23//! println!("backed by table {:?}", model_mapping.table);
24//! ```
25
26mod field;
27pub use field::{EnumVariant, Field, FieldEnum, FieldPrimitive, FieldRelation, FieldStruct};
28
29mod model;
30pub use model::{Model, TableToModel};
31
32use super::{app::ModelId, db::ColumnId};
33use crate::stmt;
34use indexmap::IndexMap;
35
36/// Defines the correspondence between app-level models and database-level
37/// tables.
38///
39/// The mapping is constructed during schema building and remains immutable at
40/// runtime. It provides the translation layer that enables the query engine to
41/// convert model-oriented statements into table-oriented statements during the
42/// lowering phase.
43///
44/// # Examples
45///
46/// ```ignore
47/// use toasty_core::schema::mapping::Mapping;
48/// use indexmap::IndexMap;
49///
50/// let mapping = Mapping { models: IndexMap::new() };
51/// assert_eq!(mapping.models.len(), 0);
52/// ```
53#[derive(Debug, Clone)]
54pub struct Mapping {
55    /// Per-model mappings indexed by model identifier.
56    pub models: IndexMap<ModelId, Model>,
57
58    /// The app-level type of each `#[document]` column.
59    ///
60    /// A document column's [`db::Column`](crate::schema::db::Column) is typed
61    /// by the structural [`stmt::Type::Object`] — the column does not know
62    /// which embedded model it stores. That knowledge normally travels inside
63    /// the mapping's cast expressions (`model_to_table` carries the lowering
64    /// cast, `table_to_model` the raising cast); this index covers the
65    /// operations that bypass those templates — an `Append` assignment's
66    /// operand cast during statement lowering.
67    pub document_columns: IndexMap<ColumnId, stmt::Type>,
68}
69
70impl Mapping {
71    /// Returns the mapping for the specified model.
72    ///
73    /// # Panics
74    ///
75    /// Panics if the model ID does not exist in the mapping.
76    ///
77    /// # Examples
78    ///
79    /// ```ignore
80    /// let model_mapping = mapping.model(model_id);
81    /// println!("table: {:?}", model_mapping.table);
82    /// ```
83    pub fn model(&self, id: impl Into<ModelId>) -> &Model {
84        self.models.get(&id.into()).expect("invalid model ID")
85    }
86
87    /// Returns a mutable reference to the mapping for the specified model.
88    ///
89    /// # Panics
90    ///
91    /// Panics if the model ID does not exist in the mapping.
92    ///
93    /// # Examples
94    ///
95    /// ```ignore
96    /// let model_mapping = mapping.model_mut(model_id);
97    /// // modify fields, columns, etc.
98    /// ```
99    pub fn model_mut(&mut self, id: impl Into<ModelId>) -> &mut Model {
100        self.models.get_mut(&id.into()).expect("invalid model ID")
101    }
102
103    /// Returns the app-level type of a `#[document]` column — `Type::Model`
104    /// for a bare embed, `List(Model)` for an embed collection — or `None` if
105    /// the column does not store a document.
106    pub fn document_column_ty(&self, id: impl Into<ColumnId>) -> Option<&stmt::Type> {
107        self.document_columns.get(&id.into())
108    }
109}