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}