icydb-core 0.98.1

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
//! Module: data::structural_field
//! Responsibility: canonical persisted-field structural decode helpers.
//! Does not own: row layout planning, typed entity reconstruction, or query semantics.
//! Boundary: runtime paths use this module when they need one persisted field decoded without `E`.

mod binary;
mod composite;
mod encode;
mod leaf;
mod scalar;
mod storage_key;
mod value_storage;

use crate::{model::field::FieldKind, value::Value};
use thiserror::Error as ThisError;

use composite::{decode_composite_field_by_kind_bytes, validate_composite_field_by_kind_bytes};
use leaf::decode_leaf_field_by_kind_bytes;
use scalar::decode_scalar_fast_path_bytes;

pub(in crate::db) use encode::encode_structural_field_by_kind_bytes;
pub(in crate::db) use storage_key::{
    decode_relation_target_storage_keys_bytes, decode_storage_key_binary_value_bytes,
    decode_storage_key_field_bytes, encode_storage_key_binary_value_bytes,
    supports_storage_key_binary_kind, validate_storage_key_binary_value_bytes,
};
pub(in crate::db) use value_storage::{
    decode_structural_value_storage_bytes, encode_structural_value_storage_bytes,
    validate_structural_value_storage_bytes,
};

///
/// FieldDecodeError
///
/// FieldDecodeError captures one persisted-field structural decode
/// failure.
/// It keeps structural decode diagnostics local to the field boundary so row
/// and relation callers can map them into taxonomy-correct higher-level errors.
///

#[derive(Clone, Debug, ThisError)]
#[error("{message}")]
pub(in crate::db) struct FieldDecodeError {
    message: String,
}

impl FieldDecodeError {
    // Build one structural field-decode failure message.
    fn new(message: impl Into<String>) -> Self {
        Self {
            message: message.into(),
        }
    }
}

/// Decode one encoded persisted field payload strictly by semantic field kind.
pub(in crate::db) fn decode_structural_field_by_kind_bytes(
    raw_bytes: &[u8],
    kind: FieldKind,
) -> Result<Value, FieldDecodeError> {
    // Keep byte-backed `ByKind` leaves off the generic `ValueWire` bridge
    // whenever their persisted shape is fixed or already owned by the leaf
    // type.
    if let Some(value) = decode_scalar_fast_path_bytes(raw_bytes, kind)? {
        return Ok(value);
    }

    // Keep the root entrypoint as a thin lane router: scalar fast path above,
    // then non-recursive leaves, then the recursive composite authority.
    if let Some(value) = decode_leaf_field_by_kind_bytes(raw_bytes, kind)? {
        return Ok(value);
    }

    decode_composite_field_by_kind_bytes(raw_bytes, kind)
}

/// Validate one encoded persisted field payload strictly by semantic field
/// kind without eagerly building the final runtime `Value`.
pub(in crate::db) fn validate_structural_field_by_kind_bytes(
    raw_bytes: &[u8],
    kind: FieldKind,
) -> Result<(), FieldDecodeError> {
    // Keep the validate-only entrypoint aligned with the existing decode lane
    // ordering so row-open validation and later materialization still share one
    // field-contract authority.
    if decode_scalar_fast_path_bytes(raw_bytes, kind)?.is_some() {
        return Ok(());
    }

    if decode_leaf_field_by_kind_bytes(raw_bytes, kind)?.is_some() {
        return Ok(());
    }

    validate_composite_field_by_kind_bytes(raw_bytes, kind)
}

///
/// TESTS
///

#[cfg(test)]
mod tests {
    use super::{
        decode_relation_target_storage_keys_bytes, decode_structural_field_by_kind_bytes,
        decode_structural_value_storage_bytes, encode_storage_key_binary_value_bytes,
        encode_structural_field_by_kind_bytes, encode_structural_value_storage_bytes,
        validate_structural_field_by_kind_bytes, validate_structural_value_storage_bytes,
    };
    use crate::{
        db::data::structural_field::binary::{
            push_binary_bytes, push_binary_list_len, push_binary_text, push_binary_uint64,
        },
        model::field::{FieldKind, RelationStrength},
        types::{
            Account, Decimal, EntityTag, Float32, Float64, Int128, Nat128, Principal, Subaccount,
            Ulid,
        },
        value::{StorageKey, Value, ValueEnum},
    };

    static RELATION_ULID_KEY_KIND: FieldKind = FieldKind::Ulid;
    static STRONG_RELATION_KIND: FieldKind = FieldKind::Relation {
        target_path: "RelationTargetEntity",
        target_entity_name: "RelationTargetEntity",
        target_entity_tag: EntityTag::new(7),
        target_store_path: "RelationTargetStore",
        key_kind: &RELATION_ULID_KEY_KIND,
        strength: RelationStrength::Strong,
    };
    static STRONG_RELATION_LIST_KIND: FieldKind = FieldKind::List(&STRONG_RELATION_KIND);

    #[test]
    fn relation_target_storage_key_decode_handles_single_ulid_and_null() {
        let target = Ulid::from_u128(7);
        let target_bytes =
            encode_storage_key_binary_value_bytes(STRONG_RELATION_KIND, &Value::Ulid(target), "id")
                .expect("storage-key relation bytes should encode")
                .expect("relation kind should use storage-key binary lane");
        let null_bytes =
            encode_storage_key_binary_value_bytes(STRONG_RELATION_KIND, &Value::Null, "id")
                .expect("null relation bytes should encode")
                .expect("relation kind should use storage-key binary lane");

        let decoded =
            decode_relation_target_storage_keys_bytes(&target_bytes, STRONG_RELATION_KIND)
                .expect("single relation should decode");
        let decoded_null =
            decode_relation_target_storage_keys_bytes(&null_bytes, STRONG_RELATION_KIND)
                .expect("null relation should decode");

        assert_eq!(decoded, vec![StorageKey::Ulid(target)]);
        assert!(
            decoded_null.is_empty(),
            "null relation should yield no targets"
        );
    }

    #[test]
    fn relation_target_storage_key_decode_handles_list_and_skips_null_items() {
        let left = Ulid::from_u128(8);
        let right = Ulid::from_u128(9);
        let bytes = encode_storage_key_binary_value_bytes(
            STRONG_RELATION_LIST_KIND,
            &Value::List(vec![Value::Ulid(left), Value::Null, Value::Ulid(right)]),
            "ids",
        )
        .expect("relation list bytes should encode")
        .expect("relation list should use storage-key binary lane");

        let decoded = decode_relation_target_storage_keys_bytes(&bytes, STRONG_RELATION_LIST_KIND)
            .expect("relation list should decode");

        assert_eq!(
            decoded,
            vec![StorageKey::Ulid(left), StorageKey::Ulid(right)],
        );
    }

    #[test]
    fn structural_field_decode_list_bytes_preserves_scalar_items() {
        let bytes = encode_structural_field_by_kind_bytes(
            FieldKind::List(&FieldKind::Text),
            &Value::List(vec![
                Value::Text("left".to_string()),
                Value::Text("right".to_string()),
            ]),
            "items",
        )
        .expect("list bytes should encode");

        let decoded =
            decode_structural_field_by_kind_bytes(&bytes, FieldKind::List(&FieldKind::Text))
                .expect("scalar list field should decode");

        assert_eq!(
            decoded,
            Value::List(vec![
                Value::Text("left".to_string()),
                Value::Text("right".to_string()),
            ]),
        );
    }

    #[test]
    fn structural_field_decode_map_bytes_preserves_scalar_entries() {
        let bytes = encode_structural_field_by_kind_bytes(
            FieldKind::Map {
                key: &FieldKind::Text,
                value: &FieldKind::Uint,
            },
            &Value::Map(vec![
                (Value::Text("alpha".to_string()), Value::Uint(1)),
                (Value::Text("beta".to_string()), Value::Uint(2)),
            ]),
            "entries",
        )
        .expect("map bytes should encode");

        let decoded = decode_structural_field_by_kind_bytes(
            &bytes,
            FieldKind::Map {
                key: &FieldKind::Text,
                value: &FieldKind::Uint,
            },
        )
        .expect("scalar map field should decode");

        assert_eq!(
            decoded,
            Value::Map(vec![
                (Value::Text("alpha".to_string()), Value::Uint(1)),
                (Value::Text("beta".to_string()), Value::Uint(2)),
            ]),
        );
    }

    #[test]
    fn structural_field_decode_float_scalars_uses_binary_lane() {
        let float32 = Value::Float32(Float32::try_new(3.5).expect("finite f32"));
        let float64 = Value::Float64(Float64::try_new(9.25).expect("finite f64"));

        let float32_bytes =
            encode_structural_field_by_kind_bytes(FieldKind::Float32, &float32, "ratio")
                .expect("float32 bytes should encode");
        let float64_bytes =
            encode_structural_field_by_kind_bytes(FieldKind::Float64, &float64, "score")
                .expect("float64 bytes should encode");

        let decoded_float32 =
            decode_structural_field_by_kind_bytes(&float32_bytes, FieldKind::Float32)
                .expect("float32 payload should decode");
        let decoded_float64 =
            decode_structural_field_by_kind_bytes(&float64_bytes, FieldKind::Float64)
                .expect("float64 payload should decode");

        assert_eq!(decoded_float32, float32);
        assert_eq!(decoded_float64, float64);
    }

    #[test]
    fn structural_field_decode_value_storage_handles_enum_payload() {
        let value = Value::Enum(
            ValueEnum::new("Active", Some("Status")).with_payload(Value::Map(vec![(
                Value::Text("count".into()),
                Value::Uint(7),
            )])),
        );
        let bytes =
            encode_structural_value_storage_bytes(&value).expect("value bytes should encode");

        let decoded = decode_structural_value_storage_bytes(&bytes)
            .expect("value enum payload should decode");

        assert_eq!(decoded, value);
    }

    #[test]
    fn structural_field_decode_typed_wrappers_preserves_payloads() {
        let account = Account::from_parts(Principal::dummy(7), Some(Subaccount::from([7_u8; 32])));
        let decimal = Decimal::new(1234, 2);

        let account_bytes = encode_structural_field_by_kind_bytes(
            FieldKind::Account,
            &Value::Account(account),
            "account",
        )
        .expect("account bytes should encode");
        let decimal_bytes = encode_structural_field_by_kind_bytes(
            FieldKind::Decimal { scale: 2 },
            &Value::Decimal(decimal),
            "amount",
        )
        .expect("decimal bytes should encode");

        let decoded_account =
            decode_structural_field_by_kind_bytes(&account_bytes, FieldKind::Account)
                .expect("account payload should decode");
        let decoded_decimal =
            decode_structural_field_by_kind_bytes(&decimal_bytes, FieldKind::Decimal { scale: 2 })
                .expect("decimal payload should decode");

        assert_eq!(decoded_account, Value::Account(account));
        assert_eq!(decoded_decimal, Value::Decimal(decimal));
    }

    #[test]
    fn structural_field_decode_value_storage_roundtrips_nested_bytes_like_variants() {
        let nested = Value::from_map(vec![
            (
                Value::Text("blob".to_string()),
                Value::Blob(vec![0x10, 0x20, 0x30]),
            ),
            (
                Value::Text("i128".to_string()),
                Value::Int128(Int128::from(-123i128)),
            ),
            (
                Value::Text("u128".to_string()),
                Value::Uint128(Nat128::from(456u128)),
            ),
            (
                Value::Text("list".to_string()),
                Value::List(vec![
                    Value::Blob(vec![0xAA, 0xBB]),
                    Value::Int128(Int128::from(7i128)),
                    Value::Uint128(Nat128::from(8u128)),
                ]),
            ),
            (
                Value::Text("enum".to_string()),
                Value::Enum(
                    ValueEnum::new("Loaded", Some("tests::StructuredPayload"))
                        .with_payload(Value::Blob(vec![0xCC, 0xDD])),
                ),
            ),
        ])
        .expect("nested value payload should normalize");
        let bytes = encode_structural_value_storage_bytes(&nested)
            .expect("nested value payload should serialize");

        let decoded = decode_structural_value_storage_bytes(&bytes)
            .expect("nested value payload should decode through value storage");

        assert_eq!(decoded, nested);
    }

    #[test]
    fn structural_field_validate_matches_decode_for_malformed_leaf_payloads() {
        let mut bytes = Vec::new();
        push_binary_list_len(&mut bytes, 2);
        push_binary_bytes(&mut bytes, &1_i128.to_be_bytes());
        push_binary_uint64(&mut bytes, u64::from(Decimal::max_supported_scale() + 1));

        let decode = decode_structural_field_by_kind_bytes(
            bytes.as_slice(),
            FieldKind::Decimal { scale: 2 },
        );
        let validate = validate_structural_field_by_kind_bytes(
            bytes.as_slice(),
            FieldKind::Decimal { scale: 2 },
        );

        assert!(
            decode.is_err(),
            "malformed decimal payload must fail decode"
        );
        assert!(
            validate.is_err(),
            "malformed decimal payload must fail validate"
        );
    }

    #[test]
    fn structural_field_validate_matches_decode_for_malformed_storage_key_payloads() {
        let mut bytes = Vec::new();
        push_binary_text(&mut bytes, "aaaaa-aa");

        let decode = decode_structural_field_by_kind_bytes(bytes.as_slice(), FieldKind::Principal);
        let validate =
            validate_structural_field_by_kind_bytes(bytes.as_slice(), FieldKind::Principal);

        assert!(decode.is_err(), "principal text payload must fail decode");
        assert!(
            validate.is_err(),
            "principal text payload must fail validate"
        );
    }

    #[test]
    fn structural_field_validate_matches_decode_for_malformed_composite_payloads() {
        let mut bytes = encode_structural_field_by_kind_bytes(
            FieldKind::List(&FieldKind::Text),
            &Value::List(vec![Value::Text("left".to_string())]),
            "items",
        )
        .expect("list bytes should encode");
        bytes.push(0x00);

        let decode = decode_structural_field_by_kind_bytes(
            bytes.as_slice(),
            FieldKind::List(&FieldKind::Text),
        );
        let validate = validate_structural_field_by_kind_bytes(
            bytes.as_slice(),
            FieldKind::List(&FieldKind::Text),
        );

        assert!(decode.is_err(), "trailing list bytes must fail decode");
        assert!(validate.is_err(), "trailing list bytes must fail validate");
    }

    #[test]
    fn structural_value_storage_validate_matches_decode_for_malformed_payloads() {
        let bytes = [0xF6];

        let decode = decode_structural_value_storage_bytes(&bytes);
        let validate = validate_structural_value_storage_bytes(&bytes);

        assert!(decode.is_err(), "unknown value tag must fail decode");
        assert!(validate.is_err(), "unknown value tag must fail validate");
    }
}