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 structural 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    StructuralFallback,
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 structural decode.
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    /// Insert-time generation contract admitted on reduced SQL write lanes.
143    pub(crate) insert_generation: Option<FieldInsertGeneration>,
144    /// Auto-managed write contract emitted for derive-owned system fields.
145    pub(crate) write_management: Option<FieldWriteManagement>,
146}
147
148///
149/// FieldInsertGeneration
150///
151/// FieldInsertGeneration declares whether one runtime field may be synthesized
152/// by the reduced SQL insert boundary when the user omits that field.
153/// This stays separate from typed-Rust `Default` behavior so write-time
154/// generation remains an explicit schema contract.
155///
156
157#[derive(Clone, Copy, Debug, Eq, PartialEq)]
158pub enum FieldInsertGeneration {
159    /// Generate one fresh `Ulid` value at insert time.
160    Ulid,
161    /// Generate one current wall-clock `Timestamp` value at insert time.
162    Timestamp,
163}
164
165///
166/// FieldWriteManagement
167///
168/// FieldWriteManagement declares whether one runtime field is owned by the
169/// write boundary during insert or update synthesis.
170/// This keeps auto-managed system fields explicit in schema/runtime metadata
171/// instead of relying on literal field names in write paths.
172///
173
174#[derive(Clone, Copy, Debug, Eq, PartialEq)]
175pub enum FieldWriteManagement {
176    /// Fill only on insert when the row is first created.
177    CreatedAt,
178    /// Refresh on insert and every update.
179    UpdatedAt,
180}
181
182impl FieldModel {
183    /// Build one generated runtime field descriptor.
184    ///
185    /// This constructor exists for derive/codegen output and trusted test
186    /// fixtures. Runtime planning and execution treat `FieldModel` values as
187    /// build-time-validated metadata.
188    #[must_use]
189    #[doc(hidden)]
190    pub const fn generated(name: &'static str, kind: FieldKind) -> Self {
191        Self::generated_with_storage_decode_and_nullability(
192            name,
193            kind,
194            FieldStorageDecode::ByKind,
195            false,
196        )
197    }
198
199    /// Build one runtime field descriptor with an explicit persisted decode contract.
200    #[must_use]
201    #[doc(hidden)]
202    pub const fn generated_with_storage_decode(
203        name: &'static str,
204        kind: FieldKind,
205        storage_decode: FieldStorageDecode,
206    ) -> Self {
207        Self::generated_with_storage_decode_and_nullability(name, kind, storage_decode, false)
208    }
209
210    /// Build one runtime field descriptor with an explicit decode contract and nullability.
211    #[must_use]
212    #[doc(hidden)]
213    pub const fn generated_with_storage_decode_and_nullability(
214        name: &'static str,
215        kind: FieldKind,
216        storage_decode: FieldStorageDecode,
217        nullable: bool,
218    ) -> Self {
219        Self::generated_with_storage_decode_nullability_and_write_policies(
220            name,
221            kind,
222            storage_decode,
223            nullable,
224            None,
225            None,
226        )
227    }
228
229    /// Build one runtime field descriptor with an explicit decode contract, nullability,
230    /// and insert-time generation contract.
231    #[must_use]
232    #[doc(hidden)]
233    pub const fn generated_with_storage_decode_nullability_and_insert_generation(
234        name: &'static str,
235        kind: FieldKind,
236        storage_decode: FieldStorageDecode,
237        nullable: bool,
238        insert_generation: Option<FieldInsertGeneration>,
239    ) -> Self {
240        Self::generated_with_storage_decode_nullability_and_write_policies(
241            name,
242            kind,
243            storage_decode,
244            nullable,
245            insert_generation,
246            None,
247        )
248    }
249
250    /// Build one runtime field descriptor with explicit insert-generation and
251    /// write-management policies.
252    #[must_use]
253    #[doc(hidden)]
254    pub const fn generated_with_storage_decode_nullability_and_write_policies(
255        name: &'static str,
256        kind: FieldKind,
257        storage_decode: FieldStorageDecode,
258        nullable: bool,
259        insert_generation: Option<FieldInsertGeneration>,
260        write_management: Option<FieldWriteManagement>,
261    ) -> Self {
262        Self {
263            name,
264            kind,
265            nullable,
266            storage_decode,
267            leaf_codec: leaf_codec_for(kind, storage_decode),
268            insert_generation,
269            write_management,
270        }
271    }
272
273    /// Return the stable field name.
274    #[must_use]
275    pub const fn name(&self) -> &'static str {
276        self.name
277    }
278
279    /// Return the runtime type-kind descriptor.
280    #[must_use]
281    pub const fn kind(&self) -> FieldKind {
282        self.kind
283    }
284
285    /// Return whether the persisted field contract permits explicit `NULL`.
286    #[must_use]
287    pub const fn nullable(&self) -> bool {
288        self.nullable
289    }
290
291    /// Return the persisted field decode contract.
292    #[must_use]
293    pub const fn storage_decode(&self) -> FieldStorageDecode {
294        self.storage_decode
295    }
296
297    /// Return the persisted leaf payload codec.
298    #[must_use]
299    pub const fn leaf_codec(&self) -> LeafCodec {
300        self.leaf_codec
301    }
302
303    /// Return the reduced-SQL insert-time generation contract for this field.
304    #[must_use]
305    pub const fn insert_generation(&self) -> Option<FieldInsertGeneration> {
306        self.insert_generation
307    }
308
309    /// Return the write-boundary management contract for this field.
310    #[must_use]
311    pub const fn write_management(&self) -> Option<FieldWriteManagement> {
312        self.write_management
313    }
314}
315
316// Resolve the canonical leaf codec from semantic field kind plus storage
317// contract. Fields that intentionally persist as `Value` or that still require
318// recursive payload decoding remain on the shared structural fallback.
319const fn leaf_codec_for(kind: FieldKind, storage_decode: FieldStorageDecode) -> LeafCodec {
320    if matches!(storage_decode, FieldStorageDecode::Value) {
321        return LeafCodec::StructuralFallback;
322    }
323
324    match kind {
325        FieldKind::Blob => LeafCodec::Scalar(ScalarCodec::Blob),
326        FieldKind::Bool => LeafCodec::Scalar(ScalarCodec::Bool),
327        FieldKind::Date => LeafCodec::Scalar(ScalarCodec::Date),
328        FieldKind::Duration => LeafCodec::Scalar(ScalarCodec::Duration),
329        FieldKind::Float32 => LeafCodec::Scalar(ScalarCodec::Float32),
330        FieldKind::Float64 => LeafCodec::Scalar(ScalarCodec::Float64),
331        FieldKind::Int => LeafCodec::Scalar(ScalarCodec::Int64),
332        FieldKind::Principal => LeafCodec::Scalar(ScalarCodec::Principal),
333        FieldKind::Subaccount => LeafCodec::Scalar(ScalarCodec::Subaccount),
334        FieldKind::Text => LeafCodec::Scalar(ScalarCodec::Text),
335        FieldKind::Timestamp => LeafCodec::Scalar(ScalarCodec::Timestamp),
336        FieldKind::Uint => LeafCodec::Scalar(ScalarCodec::Uint64),
337        FieldKind::Ulid => LeafCodec::Scalar(ScalarCodec::Ulid),
338        FieldKind::Unit => LeafCodec::Scalar(ScalarCodec::Unit),
339        FieldKind::Relation { key_kind, .. } => leaf_codec_for(*key_kind, storage_decode),
340        FieldKind::Account
341        | FieldKind::Decimal { .. }
342        | FieldKind::Enum { .. }
343        | FieldKind::Int128
344        | FieldKind::IntBig
345        | FieldKind::List(_)
346        | FieldKind::Map { .. }
347        | FieldKind::Set(_)
348        | FieldKind::Structured { .. }
349        | FieldKind::Uint128
350        | FieldKind::UintBig => LeafCodec::StructuralFallback,
351    }
352}
353
354///
355/// RelationStrength
356///
357/// Explicit relation intent for save-time referential integrity.
358///
359
360#[derive(Clone, Copy, Debug, Eq, PartialEq)]
361pub enum RelationStrength {
362    Strong,
363    Weak,
364}
365
366///
367/// FieldKind
368///
369/// Minimal runtime type surface needed by planning, validation, and execution.
370///
371/// This is aligned with `Value` variants and intentionally lossy: it encodes
372/// only the shape required for predicate compatibility and index planning.
373///
374
375#[derive(Clone, Copy, Debug)]
376pub enum FieldKind {
377    // Scalar primitives
378    Account,
379    Blob,
380    Bool,
381    Date,
382    Decimal {
383        /// Required schema-declared fractional scale for decimal fields.
384        scale: u32,
385    },
386    Duration,
387    Enum {
388        /// Fully-qualified enum type path used for strict filter normalization.
389        path: &'static str,
390        /// Declared per-variant payload decode metadata.
391        variants: &'static [EnumVariantModel],
392    },
393    Float32,
394    Float64,
395    Int,
396    Int128,
397    IntBig,
398    Principal,
399    Subaccount,
400    Text,
401    Timestamp,
402    Uint,
403    Uint128,
404    UintBig,
405    Ulid,
406    Unit,
407
408    /// Typed relation; `key_kind` reflects the referenced key type.
409    /// `strength` encodes strong vs. weak relation intent.
410    Relation {
411        /// Fully-qualified Rust type path for diagnostics.
412        target_path: &'static str,
413        /// Stable external name used in storage keys.
414        target_entity_name: &'static str,
415        /// Stable runtime identity used on hot execution paths.
416        target_entity_tag: EntityTag,
417        /// Data store path where the target entity is persisted.
418        target_store_path: &'static str,
419        key_kind: &'static Self,
420        strength: RelationStrength,
421    },
422
423    // Collections
424    List(&'static Self),
425    Set(&'static Self),
426    /// Deterministic, unordered key/value collection.
427    ///
428    /// Map fields are persistable and patchable, but not queryable or indexable.
429    Map {
430        key: &'static Self,
431        value: &'static Self,
432    },
433
434    /// Structured (non-atomic) value.
435    /// Queryability here controls whether predicates may target this field,
436    /// not whether it may be stored or updated.
437    Structured {
438        queryable: bool,
439    },
440}
441
442impl FieldKind {
443    #[must_use]
444    pub const fn value_kind(&self) -> FieldValueKind {
445        match self {
446            Self::Account
447            | Self::Blob
448            | Self::Bool
449            | Self::Date
450            | Self::Duration
451            | Self::Enum { .. }
452            | Self::Float32
453            | Self::Float64
454            | Self::Int
455            | Self::Int128
456            | Self::IntBig
457            | Self::Principal
458            | Self::Subaccount
459            | Self::Text
460            | Self::Timestamp
461            | Self::Uint
462            | Self::Uint128
463            | Self::UintBig
464            | Self::Ulid
465            | Self::Unit
466            | Self::Decimal { .. }
467            | Self::Relation { .. } => FieldValueKind::Atomic,
468            Self::List(_) | Self::Set(_) => FieldValueKind::Structured { queryable: true },
469            Self::Map { .. } => FieldValueKind::Structured { queryable: false },
470            Self::Structured { queryable } => FieldValueKind::Structured {
471                queryable: *queryable,
472            },
473        }
474    }
475
476    /// Returns `true` if this field shape is permitted in
477    /// persisted or query-visible schemas under the current
478    /// determinism policy.
479    ///
480    /// This shape-level check is structural only; query-time policy
481    /// enforcement (for example, map predicate fencing) is applied at
482    /// query construction and validation boundaries.
483    #[must_use]
484    pub const fn is_deterministic_collection_shape(&self) -> bool {
485        match self {
486            Self::Relation { key_kind, .. } => key_kind.is_deterministic_collection_shape(),
487
488            Self::List(inner) | Self::Set(inner) => inner.is_deterministic_collection_shape(),
489
490            Self::Map { key, value } => {
491                key.is_deterministic_collection_shape() && value.is_deterministic_collection_shape()
492            }
493
494            _ => true,
495        }
496    }
497}