Skip to main content

icydb_core/model/
entity.rs

1//! Module: model::entity
2//! Responsibility: runtime entity metadata emitted by derives and used by the engine.
3//! Does not own: full schema graphs, validators, or registry orchestration.
4//! Boundary: authoritative entity-level runtime contract for planning and execution.
5
6use crate::model::{field::FieldModel, index::IndexModel};
7
8///
9/// PrimaryKeyModel
10///
11/// Ordered primary-key field metadata for one entity. The current execution
12/// engine consumes scalar projections, while this model carries the ordered
13/// field-list shape needed for composite primary keys.
14///
15
16#[derive(Debug)]
17pub struct PrimaryKeyModel {
18    fields: PrimaryKeyModelFields,
19}
20
21impl PrimaryKeyModel {
22    /// Build scalar primary-key metadata for existing generated/test models.
23    #[must_use]
24    pub const fn scalar(field: &'static FieldModel) -> Self {
25        Self {
26            fields: PrimaryKeyModelFields::Scalar(field),
27        }
28    }
29
30    /// Build ordered primary-key metadata from generated field references.
31    #[must_use]
32    pub const fn ordered(fields: &'static [&'static FieldModel]) -> Self {
33        assert!(!fields.is_empty(), "primary key model requires fields");
34        Self {
35            fields: PrimaryKeyModelFields::Ordered(fields),
36        }
37    }
38
39    /// Return the number of fields in this primary key.
40    #[must_use]
41    pub const fn len(&self) -> usize {
42        match self.fields {
43            PrimaryKeyModelFields::Scalar(_) => 1,
44            PrimaryKeyModelFields::Ordered(fields) => fields.len(),
45        }
46    }
47
48    /// Return whether this primary key has no fields.
49    #[must_use]
50    pub const fn is_empty(&self) -> bool {
51        self.len() == 0
52    }
53
54    /// Return whether this primary key is the scalar one-field case.
55    #[must_use]
56    pub const fn is_scalar(&self) -> bool {
57        self.len() == 1
58    }
59
60    /// Return the scalar/current-runtime primary-key field projection.
61    #[must_use]
62    pub const fn scalar_field(&self) -> &'static FieldModel {
63        match self.fields {
64            PrimaryKeyModelFields::Scalar(field) => field,
65            PrimaryKeyModelFields::Ordered(fields) => fields[0],
66        }
67    }
68
69    /// Iterate over ordered primary-key fields.
70    #[must_use]
71    pub const fn fields(&self) -> PrimaryKeyModelFields {
72        self.fields
73    }
74}
75
76///
77/// PrimaryKeyModelFields
78///
79/// Borrowed primary-key field list without allocating on hot metadata paths.
80///
81
82#[derive(Clone, Copy, Debug)]
83pub enum PrimaryKeyModelFields {
84    Scalar(&'static FieldModel),
85    Ordered(&'static [&'static FieldModel]),
86}
87
88impl PrimaryKeyModelFields {
89    /// Return the number of fields represented by this view.
90    #[must_use]
91    pub const fn len(self) -> usize {
92        match self {
93            Self::Scalar(_) => 1,
94            Self::Ordered(fields) => fields.len(),
95        }
96    }
97
98    /// Return whether this view has no fields.
99    #[must_use]
100    pub const fn is_empty(self) -> bool {
101        self.len() == 0
102    }
103
104    /// Return the field at `index`.
105    #[must_use]
106    pub fn get(self, index: usize) -> Option<&'static FieldModel> {
107        match self {
108            Self::Scalar(field) => (index == 0).then_some(field),
109            Self::Ordered(fields) => fields.get(index).copied(),
110        }
111    }
112
113    /// Iterate over ordered primary-key fields.
114    #[must_use]
115    pub const fn iter(self) -> PrimaryKeyModelFieldIter {
116        PrimaryKeyModelFieldIter {
117            fields: self,
118            index: 0,
119        }
120    }
121}
122
123///
124/// PrimaryKeyModelFieldIter
125///
126/// Iterator over primary-key field model references.
127///
128
129#[derive(Clone, Debug)]
130pub struct PrimaryKeyModelFieldIter {
131    fields: PrimaryKeyModelFields,
132    index: usize,
133}
134
135impl Iterator for PrimaryKeyModelFieldIter {
136    type Item = &'static FieldModel;
137
138    fn next(&mut self) -> Option<Self::Item> {
139        let item = self.fields.get(self.index)?;
140        self.index += 1;
141        Some(item)
142    }
143}
144
145#[cfg(test)]
146mod primary_key_model_tests {
147    use super::{PrimaryKeyModel, PrimaryKeyModelFields};
148    use crate::model::FieldModel;
149
150    static ID_FIELD: FieldModel = FieldModel::generated("id", crate::model::FieldKind::Nat64);
151    static TENANT_FIELD: FieldModel =
152        FieldModel::generated("tenant_id", crate::model::FieldKind::Nat64);
153    static ORDERED_FIELDS: [&FieldModel; 2] = [&ID_FIELD, &TENANT_FIELD];
154
155    #[test]
156    fn scalar_primary_key_model_exposes_one_field() {
157        let model = PrimaryKeyModel::scalar(&ID_FIELD);
158
159        assert_eq!(model.len(), 1);
160        assert!(model.is_scalar());
161        assert_eq!(model.scalar_field().name(), "id");
162        assert_eq!(
163            model
164                .fields()
165                .iter()
166                .map(FieldModel::name)
167                .collect::<Vec<_>>(),
168            ["id"]
169        );
170    }
171
172    #[test]
173    fn ordered_primary_key_model_preserves_field_order() {
174        let model = PrimaryKeyModel::ordered(&ORDERED_FIELDS);
175
176        assert_eq!(model.len(), 2);
177        assert!(!model.is_scalar());
178        assert_eq!(model.scalar_field().name(), "id");
179        assert_eq!(
180            model
181                .fields()
182                .iter()
183                .map(FieldModel::name)
184                .collect::<Vec<_>>(),
185            ["id", "tenant_id"],
186        );
187        assert!(matches!(model.fields(), PrimaryKeyModelFields::Ordered(_)));
188    }
189}
190
191///
192/// EntityModel
193///
194/// Macro-generated runtime schema snapshot for a single entity.
195/// The planner and predicate validator consume this model directly.
196///
197
198#[derive(Debug)]
199pub struct EntityModel {
200    /// Fully-qualified Rust type path (for diagnostics).
201    pub(crate) path: &'static str,
202
203    /// Stable external name used in keys and routing.
204    pub(crate) entity_name: &'static str,
205
206    /// Primary key field (points at an entry in `fields`).
207    pub(crate) primary_key: &'static FieldModel,
208
209    /// Stable primary-key slot within `fields`.
210    pub(crate) primary_key_slot: usize,
211
212    /// Ordered primary-key field metadata.
213    pub(crate) primary_key_model: PrimaryKeyModel,
214
215    /// Ordered field list (authoritative for runtime planning).
216    pub(crate) fields: &'static [FieldModel],
217
218    /// Index definitions (field order is significant).
219    pub(crate) indexes: &'static [&'static IndexModel],
220}
221
222impl EntityModel {
223    /// Construct one generated runtime entity descriptor.
224    ///
225    /// This constructor exists for derive/codegen output. Runtime query and
226    /// executor code treat `EntityModel` values as already validated build-time
227    /// artifacts and do not perform defensive model-shape validation per call.
228    #[must_use]
229    #[doc(hidden)]
230    pub const fn generated(
231        path: &'static str,
232        entity_name: &'static str,
233        primary_key: &'static FieldModel,
234        primary_key_slot: usize,
235        fields: &'static [FieldModel],
236        indexes: &'static [&'static IndexModel],
237    ) -> Self {
238        Self {
239            path,
240            entity_name,
241            primary_key,
242            primary_key_slot,
243            primary_key_model: PrimaryKeyModel::scalar(primary_key),
244            fields,
245            indexes,
246        }
247    }
248
249    /// Construct one generated runtime entity descriptor with explicit
250    /// ordered primary-key metadata.
251    #[must_use]
252    #[doc(hidden)]
253    pub const fn generated_with_primary_key_model(
254        path: &'static str,
255        entity_name: &'static str,
256        primary_key_model: PrimaryKeyModel,
257        primary_key_slot: usize,
258        fields: &'static [FieldModel],
259        indexes: &'static [&'static IndexModel],
260    ) -> Self {
261        Self {
262            path,
263            entity_name,
264            primary_key: primary_key_model.scalar_field(),
265            primary_key_slot,
266            primary_key_model,
267            fields,
268            indexes,
269        }
270    }
271
272    /// Return the fully-qualified Rust path for this entity.
273    #[must_use]
274    pub const fn path(&self) -> &'static str {
275        self.path
276    }
277
278    /// Return the stable external entity name.
279    #[must_use]
280    pub const fn name(&self) -> &'static str {
281        self.entity_name
282    }
283
284    /// Return the primary-key field descriptor.
285    #[must_use]
286    pub const fn primary_key(&self) -> &'static FieldModel {
287        self.primary_key
288    }
289
290    /// Return ordered primary-key field metadata.
291    #[must_use]
292    pub const fn primary_key_model(&self) -> &PrimaryKeyModel {
293        &self.primary_key_model
294    }
295
296    /// Return ordered primary-key field names.
297    #[must_use]
298    pub fn primary_key_names(&self) -> Vec<&'static str> {
299        self.primary_key_model()
300            .fields()
301            .iter()
302            .map(crate::model::field::FieldModel::name)
303            .collect()
304    }
305
306    /// Return the stable primary-key slot within the ordered field table.
307    #[must_use]
308    pub const fn primary_key_slot(&self) -> usize {
309        self.primary_key_slot
310    }
311
312    /// Return the ordered runtime field descriptors.
313    #[must_use]
314    pub const fn fields(&self) -> &'static [FieldModel] {
315        self.fields
316    }
317
318    /// Return the runtime index descriptors.
319    #[must_use]
320    pub const fn indexes(&self) -> &'static [&'static IndexModel] {
321        self.indexes
322    }
323
324    /// Resolve one schema field name into its stable slot index.
325    #[must_use]
326    pub(crate) fn resolve_field_slot(&self, field_name: &str) -> Option<usize> {
327        self.fields
328            .iter()
329            .position(|field| field.name == field_name)
330    }
331}