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;
7
8///
9/// FieldModel
10///
11/// Runtime field metadata surfaced by macro-generated `EntityModel` values.
12///
13/// This is the smallest unit consumed by predicate validation, planning,
14/// and executor-side plan checks.
15///
16
17#[derive(Debug)]
18pub struct FieldModel {
19    /// Field name as used in predicates and indexing.
20    pub(crate) name: &'static str,
21    /// Runtime type shape (no schema-layer graph nodes).
22    pub(crate) kind: FieldKind,
23}
24
25impl FieldModel {
26    /// Build one runtime field descriptor.
27    #[must_use]
28    pub const fn new(name: &'static str, kind: FieldKind) -> Self {
29        Self { name, kind }
30    }
31
32    /// Return the stable field name.
33    #[must_use]
34    pub const fn name(&self) -> &'static str {
35        self.name
36    }
37
38    /// Return the runtime type-kind descriptor.
39    #[must_use]
40    pub const fn kind(&self) -> FieldKind {
41        self.kind
42    }
43}
44
45///
46/// RelationStrength
47///
48/// Explicit relation intent for save-time referential integrity.
49///
50
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
52pub enum RelationStrength {
53    Strong,
54    Weak,
55}
56
57///
58/// FieldKind
59///
60/// Minimal runtime type surface needed by planning, validation, and execution.
61///
62/// This is aligned with `Value` variants and intentionally lossy: it encodes
63/// only the shape required for predicate compatibility and index planning.
64///
65
66#[derive(Clone, Copy, Debug)]
67pub enum FieldKind {
68    // Scalar primitives
69    Account,
70    Blob,
71    Bool,
72    Date,
73    Decimal {
74        /// Required schema-declared fractional scale for decimal fields.
75        scale: u32,
76    },
77    Duration,
78    Enum {
79        /// Fully-qualified enum type path used for strict filter normalization.
80        path: &'static str,
81    },
82    Float32,
83    Float64,
84    Int,
85    Int128,
86    IntBig,
87    Principal,
88    Subaccount,
89    Text,
90    Timestamp,
91    Uint,
92    Uint128,
93    UintBig,
94    Ulid,
95    Unit,
96
97    /// Typed relation; `key_kind` reflects the referenced key type.
98    /// `strength` encodes strong vs. weak relation intent.
99    Relation {
100        /// Fully-qualified Rust type path for diagnostics.
101        target_path: &'static str,
102        /// Stable external name used in storage keys.
103        target_entity_name: &'static str,
104        /// Data store path where the target entity is persisted.
105        target_store_path: &'static str,
106        key_kind: &'static Self,
107        strength: RelationStrength,
108    },
109
110    // Collections
111    List(&'static Self),
112    Set(&'static Self),
113    /// Deterministic, unordered key/value collection.
114    ///
115    /// Map fields are persistable and patchable, but not queryable or indexable.
116    Map {
117        key: &'static Self,
118        value: &'static Self,
119    },
120
121    /// Structured (non-atomic) value.
122    /// Queryability here controls whether predicates may target this field,
123    /// not whether it may be stored or updated.
124    Structured {
125        queryable: bool,
126    },
127}
128
129impl FieldKind {
130    #[must_use]
131    pub const fn value_kind(&self) -> FieldValueKind {
132        match self {
133            Self::Account
134            | Self::Blob
135            | Self::Bool
136            | Self::Date
137            | Self::Duration
138            | Self::Enum { .. }
139            | Self::Float32
140            | Self::Float64
141            | Self::Int
142            | Self::Int128
143            | Self::IntBig
144            | Self::Principal
145            | Self::Subaccount
146            | Self::Text
147            | Self::Timestamp
148            | Self::Uint
149            | Self::Uint128
150            | Self::UintBig
151            | Self::Ulid
152            | Self::Unit
153            | Self::Decimal { .. }
154            | Self::Relation { .. } => FieldValueKind::Atomic,
155            Self::List(_) | Self::Set(_) => FieldValueKind::Structured { queryable: true },
156            Self::Map { .. } => FieldValueKind::Structured { queryable: false },
157            Self::Structured { queryable } => FieldValueKind::Structured {
158                queryable: *queryable,
159            },
160        }
161    }
162
163    /// Returns `true` if this field shape is permitted in
164    /// persisted or query-visible schemas under the current
165    /// determinism policy.
166    ///
167    /// This shape-level check is structural only; query-time policy
168    /// enforcement (for example, map predicate fencing) is applied at
169    /// query construction and validation boundaries.
170    #[must_use]
171    pub const fn is_deterministic_collection_shape(&self) -> bool {
172        match self {
173            Self::Relation { key_kind, .. } => key_kind.is_deterministic_collection_shape(),
174
175            Self::List(inner) | Self::Set(inner) => inner.is_deterministic_collection_shape(),
176
177            Self::Map { key, value } => {
178                key.is_deterministic_collection_shape() && value.is_deterministic_collection_shape()
179            }
180
181            _ => true,
182        }
183    }
184}