Skip to main content

icydb_core/model/
field.rs

1//! Module: model::field
2//! Responsibility: runtime field metadata and storage-decode contracts.
3//! Does not own: planner-wide query semantics or row-container orchestration.
4//! Boundary: field-level runtime schema surface used by storage and planning layers.
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/// ScalarCodec
28///
29/// ScalarCodec identifies the canonical binary leaf encoding used for one
30/// scalar persisted field payload.
31/// These codecs are fixed-width or span-bounded by the surrounding row slot
32/// container; they do not perform map/array/value dispatch.
33///
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum ScalarCodec {
37    Blob,
38    Bool,
39    Date,
40    Duration,
41    Float32,
42    Float64,
43    Int64,
44    Principal,
45    Subaccount,
46    Text,
47    Timestamp,
48    Uint64,
49    Ulid,
50    Unit,
51}
52
53///
54/// LeafCodec
55///
56/// LeafCodec declares whether one persisted field payload uses a dedicated
57/// scalar codec or falls back to CBOR leaf decoding.
58/// The row container consults this metadata before deciding whether a slot can
59/// stay on the scalar fast path.
60///
61
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub enum LeafCodec {
64    Scalar(ScalarCodec),
65    CborFallback,
66}
67
68///
69/// EnumVariantModel
70///
71/// EnumVariantModel carries structural decode metadata for one generated enum
72/// variant payload.
73/// Runtime structural decode uses this to stay on the field-kind contract for
74/// enum payloads instead of falling back to generic untyped CBOR decoding.
75///
76
77#[derive(Clone, Copy, Debug)]
78pub struct EnumVariantModel {
79    /// Stable schema variant tag.
80    pub(crate) ident: &'static str,
81    /// Declared payload kind when this variant carries data.
82    pub(crate) payload_kind: Option<&'static FieldKind>,
83    /// Persisted payload decode contract for the carried data.
84    pub(crate) payload_storage_decode: FieldStorageDecode,
85}
86
87impl EnumVariantModel {
88    /// Build one enum variant structural decode descriptor.
89    #[must_use]
90    pub const fn new(
91        ident: &'static str,
92        payload_kind: Option<&'static FieldKind>,
93        payload_storage_decode: FieldStorageDecode,
94    ) -> Self {
95        Self {
96            ident,
97            payload_kind,
98            payload_storage_decode,
99        }
100    }
101
102    /// Return the stable schema variant tag.
103    #[must_use]
104    pub const fn ident(&self) -> &'static str {
105        self.ident
106    }
107
108    /// Return the declared payload kind when this variant carries data.
109    #[must_use]
110    pub const fn payload_kind(&self) -> Option<&'static FieldKind> {
111        self.payload_kind
112    }
113
114    /// Return the persisted payload decode contract for this variant.
115    #[must_use]
116    pub const fn payload_storage_decode(&self) -> FieldStorageDecode {
117        self.payload_storage_decode
118    }
119}
120
121///
122/// FieldModel
123///
124/// Runtime field metadata surfaced by macro-generated `EntityModel` values.
125///
126/// This is the smallest unit consumed by predicate validation, planning,
127/// and executor-side plan checks.
128///
129
130#[derive(Debug)]
131pub struct FieldModel {
132    /// Field name as used in predicates and indexing.
133    pub(crate) name: &'static str,
134    /// Runtime type shape (no schema-layer graph nodes).
135    pub(crate) kind: FieldKind,
136    /// Whether the field may persist an explicit `NULL` payload.
137    pub(crate) nullable: bool,
138    /// Persisted field decode contract used by structural runtime decoders.
139    pub(crate) storage_decode: FieldStorageDecode,
140    /// Leaf payload codec used by slot readers and writers.
141    pub(crate) leaf_codec: LeafCodec,
142}
143
144impl FieldModel {
145    /// Build one generated runtime field descriptor.
146    ///
147    /// This constructor exists for derive/codegen output and trusted test
148    /// fixtures. Runtime planning and execution treat `FieldModel` values as
149    /// build-time-validated metadata.
150    #[must_use]
151    #[doc(hidden)]
152    pub const fn generated(name: &'static str, kind: FieldKind) -> Self {
153        Self::generated_with_storage_decode_and_nullability(
154            name,
155            kind,
156            FieldStorageDecode::ByKind,
157            false,
158        )
159    }
160
161    /// Build one runtime field descriptor with an explicit persisted decode contract.
162    #[must_use]
163    #[doc(hidden)]
164    pub const fn generated_with_storage_decode(
165        name: &'static str,
166        kind: FieldKind,
167        storage_decode: FieldStorageDecode,
168    ) -> Self {
169        Self::generated_with_storage_decode_and_nullability(name, kind, storage_decode, false)
170    }
171
172    /// Build one runtime field descriptor with an explicit decode contract and nullability.
173    #[must_use]
174    #[doc(hidden)]
175    pub const fn generated_with_storage_decode_and_nullability(
176        name: &'static str,
177        kind: FieldKind,
178        storage_decode: FieldStorageDecode,
179        nullable: bool,
180    ) -> Self {
181        Self {
182            name,
183            kind,
184            nullable,
185            storage_decode,
186            leaf_codec: leaf_codec_for(kind, storage_decode),
187        }
188    }
189
190    /// Return the stable field name.
191    #[must_use]
192    pub const fn name(&self) -> &'static str {
193        self.name
194    }
195
196    /// Return the runtime type-kind descriptor.
197    #[must_use]
198    pub const fn kind(&self) -> FieldKind {
199        self.kind
200    }
201
202    /// Return whether the persisted field contract permits explicit `NULL`.
203    #[must_use]
204    pub const fn nullable(&self) -> bool {
205        self.nullable
206    }
207
208    /// Return the persisted field decode contract.
209    #[must_use]
210    pub const fn storage_decode(&self) -> FieldStorageDecode {
211        self.storage_decode
212    }
213
214    /// Return the persisted leaf payload codec.
215    #[must_use]
216    pub const fn leaf_codec(&self) -> LeafCodec {
217        self.leaf_codec
218    }
219}
220
221// Resolve the canonical leaf codec from semantic field kind plus storage
222// contract. Fields that intentionally persist as `Value` or that still require
223// recursive payload decoding remain on the shared CBOR fallback.
224const fn leaf_codec_for(kind: FieldKind, storage_decode: FieldStorageDecode) -> LeafCodec {
225    if matches!(storage_decode, FieldStorageDecode::Value) {
226        return LeafCodec::CborFallback;
227    }
228
229    match kind {
230        FieldKind::Blob => LeafCodec::Scalar(ScalarCodec::Blob),
231        FieldKind::Bool => LeafCodec::Scalar(ScalarCodec::Bool),
232        FieldKind::Date => LeafCodec::Scalar(ScalarCodec::Date),
233        FieldKind::Duration => LeafCodec::Scalar(ScalarCodec::Duration),
234        FieldKind::Float32 => LeafCodec::Scalar(ScalarCodec::Float32),
235        FieldKind::Float64 => LeafCodec::Scalar(ScalarCodec::Float64),
236        FieldKind::Int => LeafCodec::Scalar(ScalarCodec::Int64),
237        FieldKind::Principal => LeafCodec::Scalar(ScalarCodec::Principal),
238        FieldKind::Subaccount => LeafCodec::Scalar(ScalarCodec::Subaccount),
239        FieldKind::Text => LeafCodec::Scalar(ScalarCodec::Text),
240        FieldKind::Timestamp => LeafCodec::Scalar(ScalarCodec::Timestamp),
241        FieldKind::Uint => LeafCodec::Scalar(ScalarCodec::Uint64),
242        FieldKind::Ulid => LeafCodec::Scalar(ScalarCodec::Ulid),
243        FieldKind::Unit => LeafCodec::Scalar(ScalarCodec::Unit),
244        FieldKind::Relation { key_kind, .. } => leaf_codec_for(*key_kind, storage_decode),
245        FieldKind::Account
246        | FieldKind::Decimal { .. }
247        | FieldKind::Enum { .. }
248        | FieldKind::Int128
249        | FieldKind::IntBig
250        | FieldKind::List(_)
251        | FieldKind::Map { .. }
252        | FieldKind::Set(_)
253        | FieldKind::Structured { .. }
254        | FieldKind::Uint128
255        | FieldKind::UintBig => LeafCodec::CborFallback,
256    }
257}
258
259///
260/// RelationStrength
261///
262/// Explicit relation intent for save-time referential integrity.
263///
264
265#[derive(Clone, Copy, Debug, Eq, PartialEq)]
266pub enum RelationStrength {
267    Strong,
268    Weak,
269}
270
271///
272/// FieldKind
273///
274/// Minimal runtime type surface needed by planning, validation, and execution.
275///
276/// This is aligned with `Value` variants and intentionally lossy: it encodes
277/// only the shape required for predicate compatibility and index planning.
278///
279
280#[derive(Clone, Copy, Debug)]
281pub enum FieldKind {
282    // Scalar primitives
283    Account,
284    Blob,
285    Bool,
286    Date,
287    Decimal {
288        /// Required schema-declared fractional scale for decimal fields.
289        scale: u32,
290    },
291    Duration,
292    Enum {
293        /// Fully-qualified enum type path used for strict filter normalization.
294        path: &'static str,
295        /// Declared per-variant payload decode metadata.
296        variants: &'static [EnumVariantModel],
297    },
298    Float32,
299    Float64,
300    Int,
301    Int128,
302    IntBig,
303    Principal,
304    Subaccount,
305    Text,
306    Timestamp,
307    Uint,
308    Uint128,
309    UintBig,
310    Ulid,
311    Unit,
312
313    /// Typed relation; `key_kind` reflects the referenced key type.
314    /// `strength` encodes strong vs. weak relation intent.
315    Relation {
316        /// Fully-qualified Rust type path for diagnostics.
317        target_path: &'static str,
318        /// Stable external name used in storage keys.
319        target_entity_name: &'static str,
320        /// Stable runtime identity used on hot execution paths.
321        target_entity_tag: EntityTag,
322        /// Data store path where the target entity is persisted.
323        target_store_path: &'static str,
324        key_kind: &'static Self,
325        strength: RelationStrength,
326    },
327
328    // Collections
329    List(&'static Self),
330    Set(&'static Self),
331    /// Deterministic, unordered key/value collection.
332    ///
333    /// Map fields are persistable and patchable, but not queryable or indexable.
334    Map {
335        key: &'static Self,
336        value: &'static Self,
337    },
338
339    /// Structured (non-atomic) value.
340    /// Queryability here controls whether predicates may target this field,
341    /// not whether it may be stored or updated.
342    Structured {
343        queryable: bool,
344    },
345}
346
347impl FieldKind {
348    #[must_use]
349    pub const fn value_kind(&self) -> FieldValueKind {
350        match self {
351            Self::Account
352            | Self::Blob
353            | Self::Bool
354            | Self::Date
355            | Self::Duration
356            | Self::Enum { .. }
357            | Self::Float32
358            | Self::Float64
359            | Self::Int
360            | Self::Int128
361            | Self::IntBig
362            | Self::Principal
363            | Self::Subaccount
364            | Self::Text
365            | Self::Timestamp
366            | Self::Uint
367            | Self::Uint128
368            | Self::UintBig
369            | Self::Ulid
370            | Self::Unit
371            | Self::Decimal { .. }
372            | Self::Relation { .. } => FieldValueKind::Atomic,
373            Self::List(_) | Self::Set(_) => FieldValueKind::Structured { queryable: true },
374            Self::Map { .. } => FieldValueKind::Structured { queryable: false },
375            Self::Structured { queryable } => FieldValueKind::Structured {
376                queryable: *queryable,
377            },
378        }
379    }
380
381    /// Returns `true` if this field shape is permitted in
382    /// persisted or query-visible schemas under the current
383    /// determinism policy.
384    ///
385    /// This shape-level check is structural only; query-time policy
386    /// enforcement (for example, map predicate fencing) is applied at
387    /// query construction and validation boundaries.
388    #[must_use]
389    pub const fn is_deterministic_collection_shape(&self) -> bool {
390        match self {
391            Self::Relation { key_kind, .. } => key_kind.is_deterministic_collection_shape(),
392
393            Self::List(inner) | Self::Set(inner) => inner.is_deterministic_collection_shape(),
394
395            Self::Map { key, value } => {
396                key.is_deterministic_collection_shape() && value.is_deterministic_collection_shape()
397            }
398
399            _ => true,
400        }
401    }
402}