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}