icydb-core 0.261.17

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
use crate::{
    db::{
        data::{
            decode_structural_field_by_accepted_kind_bytes,
            encode_structural_field_by_accepted_kind_bytes,
            validate_structural_field_by_accepted_kind_bytes,
        },
        schema::AcceptedFieldKind,
    },
    types::{Decimal, IntBig},
    value::Value,
};

fn assert_accepted_roundtrip(kind: &AcceptedFieldKind, value: &Value) {
    let encoded = encode_structural_field_by_accepted_kind_bytes(kind, value)
        .expect("accepted payload should encode");
    let decoded = decode_structural_field_by_accepted_kind_bytes(&encoded, kind)
        .expect("accepted payload should decode");

    validate_structural_field_by_accepted_kind_bytes(&encoded, kind)
        .expect("accepted payload should validate");
    assert_eq!(decoded, *value);
}

fn assert_accepted_rejects(kind: &AcceptedFieldKind, raw_bytes: &[u8]) {
    assert!(decode_structural_field_by_accepted_kind_bytes(raw_bytes, kind).is_err());
    assert!(validate_structural_field_by_accepted_kind_bytes(raw_bytes, kind).is_err());
}

#[test]
fn accepted_kind_codec_roundtrips_nested_collections() {
    let kind = AcceptedFieldKind::Map {
        key: Box::new(AcceptedFieldKind::Text { max_len: None }),
        value: Box::new(AcceptedFieldKind::List(Box::new(AcceptedFieldKind::Nat64))),
    };
    let value = Value::Map(vec![
        (
            Value::Text("alpha".to_string()),
            Value::List(vec![Value::Nat64(1), Value::Nat64(2)]),
        ),
        (
            Value::Text("beta".to_string()),
            Value::List(vec![Value::Nat64(3)]),
        ),
    ]);

    assert_accepted_roundtrip(&kind, &value);
}

#[test]
fn accepted_kind_codec_rejects_truncated_nested_collections() {
    let cases = [
        (
            AcceptedFieldKind::List(Box::new(AcceptedFieldKind::Nat64)),
            Value::List(vec![Value::Nat64(1), Value::Nat64(2)]),
        ),
        (
            AcceptedFieldKind::Map {
                key: Box::new(AcceptedFieldKind::Text { max_len: None }),
                value: Box::new(AcceptedFieldKind::Nat64),
            },
            Value::Map(vec![(Value::Text("alpha".to_string()), Value::Nat64(1))]),
        ),
    ];

    for (kind, value) in cases {
        let mut malformed = encode_structural_field_by_accepted_kind_bytes(&kind, &value)
            .expect("accepted payload should encode");
        malformed.pop();
        assert_accepted_rejects(&kind, malformed.as_slice());
    }
}

#[test]
fn accepted_kind_codec_roundtrips_relation_lists() {
    let kind = AcceptedFieldKind::List(Box::new(AcceptedFieldKind::Relation {
        target_path: "tests::Target".to_string(),
        target_entity_name: "Target".to_string(),
        target_entity_tag: crate::testing::PROBE_ENTITY_TAG,
        target_store_path: "tests::TargetStore".to_string(),
        key_kind: Box::new(AcceptedFieldKind::Ulid),
    }));
    let value = Value::List(vec![
        Value::Ulid(crate::types::Ulid::from_u128(11)),
        Value::Ulid(crate::types::Ulid::from_u128(12)),
    ]);

    assert_accepted_roundtrip(&kind, &value);
}

// Both operations must accept the same wire language. Materialization may
// allocate output, but must not depend on a preceding validation-only walk.
fn assert_decode_validation_parity(kind: &AcceptedFieldKind, bytes: &[u8]) {
    assert_eq!(
        decode_structural_field_by_accepted_kind_bytes(bytes, kind).is_ok(),
        validate_structural_field_by_accepted_kind_bytes(bytes, kind).is_ok(),
        "decode/validation parity for {kind:?} and {bytes:?}",
    );
}

#[test]
fn accepted_by_kind_decode_and_validation_agree_on_mutated_wire() {
    let cases = [
        (AcceptedFieldKind::Nat8, Value::Nat64(255)),
        (AcceptedFieldKind::Int8, Value::Int64(-128)),
        (AcceptedFieldKind::Bool, Value::Bool(true)),
        (
            AcceptedFieldKind::Text { max_len: None },
            Value::Text("text".into()),
        ),
        (
            AcceptedFieldKind::Blob { max_len: None },
            Value::Blob(vec![1, 2, 3]),
        ),
        (
            AcceptedFieldKind::Decimal { scale: 2 },
            Value::Decimal(Decimal::from_i128_with_scale(123, 2)),
        ),
        (
            AcceptedFieldKind::IntBig { max_bytes: 16 },
            Value::IntBig(IntBig::from(-123_i64)),
        ),
        (
            AcceptedFieldKind::Set(Box::new(AcceptedFieldKind::Nat64)),
            Value::List(vec![Value::Nat64(1), Value::Nat64(2)]),
        ),
        (
            AcceptedFieldKind::Map {
                key: Box::new(AcceptedFieldKind::Text { max_len: None }),
                value: Box::new(AcceptedFieldKind::List(Box::new(AcceptedFieldKind::Nat64))),
            },
            Value::Map(vec![(
                Value::Text("key".into()),
                Value::List(vec![Value::Nat64(1), Value::Nat64(2)]),
            )]),
        ),
    ];
    for (kind, value) in cases {
        assert_accepted_roundtrip(&kind, &value);
        let wire = encode_structural_field_by_accepted_kind_bytes(&kind, &value).unwrap();
        for end in 0..wire.len() {
            assert_accepted_rejects(&kind, &wire[..end]);
        }
        let mut trailing = wire.clone();
        trailing.push(0);
        assert_accepted_rejects(&kind, &trailing);
        for index in 0..wire.len() {
            for byte in [0, 1, 127, 255] {
                let mut mutated = wire.clone();
                mutated[index] = byte;
                assert_decode_validation_parity(&kind, &mutated);
            }
        }
    }
}

#[test]
fn accepted_relation_null_filtering_still_checks_following_items() {
    let relation = AcceptedFieldKind::List(Box::new(AcceptedFieldKind::Relation {
        target_path: "tests::Target".into(),
        target_entity_name: "Target".into(),
        target_entity_tag: crate::testing::PROBE_ENTITY_TAG,
        target_store_path: "tests::TargetStore".into(),
        key_kind: Box::new(AcceptedFieldKind::Ulid),
    }));
    let key = Value::Ulid(crate::types::Ulid::from_u128(11));
    // Encode as a plain list so a null relation item remains on the wire.
    let wire = encode_structural_field_by_accepted_kind_bytes(
        &AcceptedFieldKind::List(Box::new(AcceptedFieldKind::Ulid)),
        &Value::List(vec![Value::Null, key.clone()]),
    )
    .unwrap();
    validate_structural_field_by_accepted_kind_bytes(&wire, &relation).unwrap();
    assert_eq!(
        decode_structural_field_by_accepted_kind_bytes(&wire, &relation).unwrap(),
        Value::List(vec![key])
    );
    for end in 0..wire.len() {
        assert_accepted_rejects(&relation, &wire[..end]);
    }
    let invalid = encode_structural_field_by_accepted_kind_bytes(
        &AcceptedFieldKind::List(Box::new(AcceptedFieldKind::Nat64)),
        &Value::List(vec![Value::Null, Value::Nat64(11)]),
    )
    .unwrap();
    assert_accepted_rejects(&relation, &invalid);
}

#[test]
fn accepted_big_integer_collections_preserve_magnitude_boundaries() {
    use crate::types::NatBig;

    for (kind, values) in [
        (
            AcceptedFieldKind::IntBig { max_bytes: 2 },
            vec![
                Value::IntBig(IntBig::from(-8192)),
                Value::IntBig(IntBig::from(8191)),
            ],
        ),
        (
            AcceptedFieldKind::NatBig { max_bytes: 2 },
            vec![
                Value::NatBig(NatBig::from(0_u32)),
                Value::NatBig(NatBig::from(16383_u32)),
            ],
        ),
    ] {
        let kind = AcceptedFieldKind::Map {
            key: Box::new(AcceptedFieldKind::Text { max_len: None }),
            value: Box::new(AcceptedFieldKind::List(Box::new(kind))),
        };
        let value = Value::Map(vec![(Value::Text("numbers".into()), Value::List(values))]);
        assert_accepted_roundtrip(&kind, &value);
        let encoded = encode_structural_field_by_accepted_kind_bytes(&kind, &value).unwrap();
        for end in 0..encoded.len() {
            assert_accepted_rejects(&kind, &encoded[..end]);
        }
    }
}

#[test]
fn big_integer_storage_sizes_cover_one_thousand_256_bit_values() {
    use crate::{
        db::data::structural_field::{
            decode_canonical_value_storage_bytes, encode_canonical_value_storage_bytes,
        },
        types::NatBig,
    };
    use num_bigint::{BigInt, BigUint, Sign};

    let magnitude = BigUint::from_bytes_le(&[255; 32]);
    for (kind, value, direct_len) in [
        (
            AcceptedFieldKind::NatBig { max_bytes: 40 },
            Value::NatBig(NatBig::from_biguint(magnitude.clone())),
            37,
        ),
        (
            AcceptedFieldKind::IntBig { max_bytes: 40 },
            Value::IntBig(IntBig::from_bigint(BigInt::from_biguint(
                Sign::Minus,
                magnitude,
            ))),
            38,
        ),
    ] {
        let direct = encode_structural_field_by_accepted_kind_bytes(&kind, &value).unwrap();
        assert_eq!(direct.len(), direct_len);
        let list = Value::List(vec![value; 1000]);
        let encoded = encode_structural_field_by_accepted_kind_bytes(
            &AcceptedFieldKind::List(Box::new(kind.clone())),
            &list,
        )
        .unwrap();
        assert_eq!(encoded.len(), 5 + 1000 * direct_len);
        assert_eq!(
            decode_structural_field_by_accepted_kind_bytes(
                &encoded,
                &AcceptedFieldKind::List(Box::new(kind))
            )
            .unwrap(),
            list
        );
        let canonical = encode_canonical_value_storage_bytes(&list).unwrap();
        assert_eq!(canonical.len(), 5 + 1000 * (direct_len + 1));
        assert_eq!(
            decode_canonical_value_storage_bytes(&canonical).unwrap(),
            list
        );
    }
}