Skip to main content

icydb_core/model/
field.rs

1//! Module: model::field
2//! Responsibility: module-local ownership and contracts for model::field.
3//! Does not own: cross-module orchestration outside this module.
4//! Boundary: exposes this module API while keeping implementation details internal.
5
6use crate::{traits::FieldValueKind, types::EntityTag};
7
8///
9/// FieldStorageDecode
10///
11/// FieldStorageDecode captures how one persisted field payload must be
12/// interpreted at structural decode boundaries.
13/// Semantic `FieldKind` alone is not always authoritative for persisted decode:
14/// some fields intentionally store raw `Value` payloads even when their planner
15/// shape is narrower.
16///
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum FieldStorageDecode {
20    /// Decode the persisted field payload according to semantic `FieldKind`.
21    ByKind,
22    /// Decode the persisted field payload directly into `Value`.
23    Value,
24}
25
26///
27/// EnumVariantModel
28///
29/// EnumVariantModel carries structural decode metadata for one generated enum
30/// variant payload.
31/// Runtime structural decode uses this to stay on the field-kind contract for
32/// enum payloads instead of falling back to generic untyped CBOR decoding.
33///
34
35#[derive(Clone, Copy, Debug)]
36pub struct EnumVariantModel {
37    /// Stable schema variant tag.
38    pub(crate) ident: &'static str,
39    /// Declared payload kind when this variant carries data.
40    pub(crate) payload_kind: Option<&'static FieldKind>,
41    /// Persisted payload decode contract for the carried data.
42    pub(crate) payload_storage_decode: FieldStorageDecode,
43}
44
45impl EnumVariantModel {
46    /// Build one enum variant structural decode descriptor.
47    #[must_use]
48    pub const fn new(
49        ident: &'static str,
50        payload_kind: Option<&'static FieldKind>,
51        payload_storage_decode: FieldStorageDecode,
52    ) -> Self {
53        Self {
54            ident,
55            payload_kind,
56            payload_storage_decode,
57        }
58    }
59
60    /// Return the stable schema variant tag.
61    #[must_use]
62    pub const fn ident(&self) -> &'static str {
63        self.ident
64    }
65
66    /// Return the declared payload kind when this variant carries data.
67    #[must_use]
68    pub const fn payload_kind(&self) -> Option<&'static FieldKind> {
69        self.payload_kind
70    }
71
72    /// Return the persisted payload decode contract for this variant.
73    #[must_use]
74    pub const fn payload_storage_decode(&self) -> FieldStorageDecode {
75        self.payload_storage_decode
76    }
77}
78
79///
80/// FieldModel
81///
82/// Runtime field metadata surfaced by macro-generated `EntityModel` values.
83///
84/// This is the smallest unit consumed by predicate validation, planning,
85/// and executor-side plan checks.
86///
87
88#[derive(Debug)]
89pub struct FieldModel {
90    /// Field name as used in predicates and indexing.
91    pub(crate) name: &'static str,
92    /// Runtime type shape (no schema-layer graph nodes).
93    pub(crate) kind: FieldKind,
94    /// Persisted field decode contract used by structural runtime decoders.
95    pub(crate) storage_decode: FieldStorageDecode,
96}
97
98impl FieldModel {
99    /// Build one runtime field descriptor.
100    #[must_use]
101    pub const fn new(name: &'static str, kind: FieldKind) -> Self {
102        Self::new_with_storage_decode(name, kind, FieldStorageDecode::ByKind)
103    }
104
105    /// Build one runtime field descriptor with an explicit persisted decode contract.
106    #[must_use]
107    pub const fn new_with_storage_decode(
108        name: &'static str,
109        kind: FieldKind,
110        storage_decode: FieldStorageDecode,
111    ) -> Self {
112        Self {
113            name,
114            kind,
115            storage_decode,
116        }
117    }
118
119    /// Return the stable field name.
120    #[must_use]
121    pub const fn name(&self) -> &'static str {
122        self.name
123    }
124
125    /// Return the runtime type-kind descriptor.
126    #[must_use]
127    pub const fn kind(&self) -> FieldKind {
128        self.kind
129    }
130
131    /// Return the persisted field decode contract.
132    #[must_use]
133    pub const fn storage_decode(&self) -> FieldStorageDecode {
134        self.storage_decode
135    }
136}
137
138///
139/// RelationStrength
140///
141/// Explicit relation intent for save-time referential integrity.
142///
143
144#[derive(Clone, Copy, Debug, Eq, PartialEq)]
145pub enum RelationStrength {
146    Strong,
147    Weak,
148}
149
150///
151/// FieldKind
152///
153/// Minimal runtime type surface needed by planning, validation, and execution.
154///
155/// This is aligned with `Value` variants and intentionally lossy: it encodes
156/// only the shape required for predicate compatibility and index planning.
157///
158
159#[derive(Clone, Copy, Debug)]
160pub enum FieldKind {
161    // Scalar primitives
162    Account,
163    Blob,
164    Bool,
165    Date,
166    Decimal {
167        /// Required schema-declared fractional scale for decimal fields.
168        scale: u32,
169    },
170    Duration,
171    Enum {
172        /// Fully-qualified enum type path used for strict filter normalization.
173        path: &'static str,
174        /// Declared per-variant payload decode metadata.
175        variants: &'static [EnumVariantModel],
176    },
177    Float32,
178    Float64,
179    Int,
180    Int128,
181    IntBig,
182    Principal,
183    Subaccount,
184    Text,
185    Timestamp,
186    Uint,
187    Uint128,
188    UintBig,
189    Ulid,
190    Unit,
191
192    /// Typed relation; `key_kind` reflects the referenced key type.
193    /// `strength` encodes strong vs. weak relation intent.
194    Relation {
195        /// Fully-qualified Rust type path for diagnostics.
196        target_path: &'static str,
197        /// Stable external name used in storage keys.
198        target_entity_name: &'static str,
199        /// Stable runtime identity used on hot execution paths.
200        target_entity_tag: EntityTag,
201        /// Data store path where the target entity is persisted.
202        target_store_path: &'static str,
203        key_kind: &'static Self,
204        strength: RelationStrength,
205    },
206
207    // Collections
208    List(&'static Self),
209    Set(&'static Self),
210    /// Deterministic, unordered key/value collection.
211    ///
212    /// Map fields are persistable and patchable, but not queryable or indexable.
213    Map {
214        key: &'static Self,
215        value: &'static Self,
216    },
217
218    /// Structured (non-atomic) value.
219    /// Queryability here controls whether predicates may target this field,
220    /// not whether it may be stored or updated.
221    Structured {
222        queryable: bool,
223    },
224}
225
226impl FieldKind {
227    #[must_use]
228    pub const fn value_kind(&self) -> FieldValueKind {
229        match self {
230            Self::Account
231            | Self::Blob
232            | Self::Bool
233            | Self::Date
234            | Self::Duration
235            | Self::Enum { .. }
236            | Self::Float32
237            | Self::Float64
238            | Self::Int
239            | Self::Int128
240            | Self::IntBig
241            | Self::Principal
242            | Self::Subaccount
243            | Self::Text
244            | Self::Timestamp
245            | Self::Uint
246            | Self::Uint128
247            | Self::UintBig
248            | Self::Ulid
249            | Self::Unit
250            | Self::Decimal { .. }
251            | Self::Relation { .. } => FieldValueKind::Atomic,
252            Self::List(_) | Self::Set(_) => FieldValueKind::Structured { queryable: true },
253            Self::Map { .. } => FieldValueKind::Structured { queryable: false },
254            Self::Structured { queryable } => FieldValueKind::Structured {
255                queryable: *queryable,
256            },
257        }
258    }
259
260    /// Returns `true` if this field shape is permitted in
261    /// persisted or query-visible schemas under the current
262    /// determinism policy.
263    ///
264    /// This shape-level check is structural only; query-time policy
265    /// enforcement (for example, map predicate fencing) is applied at
266    /// query construction and validation boundaries.
267    #[must_use]
268    pub const fn is_deterministic_collection_shape(&self) -> bool {
269        match self {
270            Self::Relation { key_kind, .. } => key_kind.is_deterministic_collection_shape(),
271
272            Self::List(inner) | Self::Set(inner) => inner.is_deterministic_collection_shape(),
273
274            Self::Map { key, value } => {
275                key.is_deterministic_collection_shape() && value.is_deterministic_collection_shape()
276            }
277
278            _ => true,
279        }
280    }
281}