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}