icydb-core 0.180.13

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
use super::canonical_group_key_equals;
use crate::{
    db::executor::group::{CanonicalKey, GroupKey, GroupKeySet, KeyCanonicalError},
    types::Decimal,
    value::{MapValueError, Value, with_test_hash_override},
};

fn map_value(entries: Vec<(Value, Value)>) -> Value {
    Value::Map(entries)
}

#[test]
fn canonical_key_normalizes_decimal_scale() {
    let key = Value::Decimal(Decimal::new(100, 2))
        .canonical_key()
        .expect("canonical key");

    let Value::Decimal(normalized) = key.raw() else {
        panic!("canonical decimal value expected");
    };
    assert_eq!(normalized.scale(), 0);
}

#[test]
fn canonical_key_normalizes_map_order() {
    let left = map_value(vec![
        (Value::Text("z".to_string()), Value::Nat64(9)),
        (Value::Text("a".to_string()), Value::Nat64(1)),
    ]);
    let right = map_value(vec![
        (Value::Text("a".to_string()), Value::Nat64(1)),
        (Value::Text("z".to_string()), Value::Nat64(9)),
    ]);

    let left_key = left.canonical_key().expect("left canonical key");
    let right_key = right.canonical_key().expect("right canonical key");

    assert_eq!(left_key, right_key);
    assert_eq!(left_key.hash(), right_key.hash());
}

#[test]
fn canonical_key_rejects_duplicate_map_keys_after_normalization() {
    let value = map_value(vec![
        (Value::Text("a".to_string()), Value::Nat64(1)),
        (Value::Text("a".to_string()), Value::Nat64(2)),
    ]);

    let err = value
        .canonical_key()
        .expect_err("duplicate map keys should fail");
    std::assert_matches!(
        err,
        KeyCanonicalError::InvalidMapValue(MapValueError::DuplicateKey { .. })
    );
}

#[test]
fn group_key_set_deduplicates_canonical_equivalents() {
    let mut set = GroupKeySet::default();
    let first = Value::Decimal(Decimal::new(100, 2));
    let second = Value::Decimal(Decimal::new(1, 0));

    assert!(
        set.insert_value(&first).expect("insert"),
        "first insert should be new"
    );
    assert!(
        !set.insert_value(&second).expect("insert"),
        "second insert should be deduplicated by canonical key equality"
    );
}

#[test]
fn canonical_equal_keys_always_share_stable_hash() {
    let equivalent_pairs = vec![
        (
            Value::Decimal(Decimal::new(1000, 3)),
            Value::Decimal(Decimal::new(1, 0)),
        ),
        (
            Value::Map(vec![
                (Value::Text("z".to_string()), Value::Nat64(9)),
                (Value::Text("a".to_string()), Value::Nat64(1)),
            ]),
            Value::Map(vec![
                (Value::Text("a".to_string()), Value::Nat64(1)),
                (Value::Text("z".to_string()), Value::Nat64(9)),
            ]),
        ),
        (
            Value::List(vec![Value::Decimal(Decimal::new(10, 1)), Value::Nat64(4)]),
            Value::List(vec![Value::Decimal(Decimal::new(1, 0)), Value::Nat64(4)]),
        ),
        (
            Value::List(vec![
                Value::Map(vec![
                    (Value::Text("z".to_string()), Value::Nat64(9)),
                    (Value::Text("a".to_string()), Value::Nat64(1)),
                ]),
                Value::Decimal(Decimal::new(2500, 2)),
            ]),
            Value::List(vec![
                Value::Map(vec![
                    (Value::Text("a".to_string()), Value::Nat64(1)),
                    (Value::Text("z".to_string()), Value::Nat64(9)),
                ]),
                Value::Decimal(Decimal::new(25, 0)),
            ]),
        ),
    ];

    for (left_value, right_value) in equivalent_pairs {
        let left_key = left_value.canonical_key().expect("left canonical key");
        let right_key = right_value.canonical_key().expect("right canonical key");
        assert!(
            canonical_group_key_equals(&left_key, &right_key),
            "pair should be canonical-equal under group key contract",
        );
        assert_eq!(
            left_key.hash(),
            right_key.hash(),
            "canonical-equal keys must hash to the same stable hash",
        );
    }
}

#[test]
fn group_key_set_handles_hash_collisions_with_equality_check() {
    with_test_hash_override([0xAB; 16], || {
        let mut set = GroupKeySet::default();
        let first = Value::Text("alpha".to_string())
            .canonical_key()
            .expect("first canonical key");
        let second = Value::Text("beta".to_string())
            .canonical_key()
            .expect("second canonical key");

        assert_eq!(
            first.hash(),
            second.hash(),
            "test setup requires an artificial hash collision",
        );
        assert!(
            !canonical_group_key_equals(&first, &second),
            "collision pair must remain distinct by canonical equality",
        );
        assert!(
            set.insert_key(first.clone()),
            "first colliding key should insert as new",
        );
        assert!(
            set.insert_key(second.clone()),
            "second colliding key must not be dropped on hash match alone",
        );
        assert!(
            !set.insert_key(first),
            "re-inserting first key should dedupe by canonical equality",
        );
        assert!(
            !set.insert_key(second),
            "re-inserting second key should dedupe by canonical equality",
        );
    });
}

#[test]
fn group_key_from_single_group_value_matches_group_values_path() {
    let single = Value::Decimal(Decimal::new(100, 2));
    let single_owned =
        GroupKey::from_single_group_value(single.clone()).expect("single owned canonical key");
    let list_owned = GroupKey::from_group_values(vec![single]).expect("list owned canonical key");

    assert_eq!(single_owned, list_owned);
    assert_eq!(single_owned.hash(), list_owned.hash());
}

#[test]
fn group_key_from_prehashed_paths_match_unhashed_paths() {
    let group_values = vec![
        Value::Decimal(Decimal::new(100, 2)),
        Value::Text("alpha".to_string()),
    ];
    let borrowed_hash = Value::List(group_values.clone())
        .canonical_key()
        .expect("borrowed canonical key")
        .hash();
    let prehashed_multi =
        GroupKey::from_group_values_with_hash(group_values.clone(), borrowed_hash)
            .expect("prehashed multi key");
    let unhashed_multi = GroupKey::from_group_values(group_values).expect("unhashed multi key");

    assert_eq!(prehashed_multi, unhashed_multi);
    assert_eq!(prehashed_multi.hash(), unhashed_multi.hash());

    let single = Value::Decimal(Decimal::new(100, 2));
    let single_hash = Value::List(vec![single.clone()])
        .canonical_key()
        .expect("borrowed single canonical key")
        .hash();
    let prehashed_single = GroupKey::from_single_group_value_with_hash(single.clone(), single_hash)
        .expect("prehashed single key");
    let unhashed_single = GroupKey::from_single_group_value(single).expect("unhashed single key");

    assert_eq!(prehashed_single, unhashed_single);
    assert_eq!(prehashed_single.hash(), unhashed_single.hash());
}

#[test]
fn group_key_from_single_canonical_group_value_matches_hashed_single_path() {
    let single = Value::Nat64(7);
    let single_hash = Value::List(vec![single.clone()])
        .canonical_key()
        .expect("borrowed single canonical key")
        .hash();
    let canonical =
        GroupKey::from_single_canonical_group_value_with_hash(single.clone(), single_hash);
    let hashed = GroupKey::from_single_group_value_with_hash(single, single_hash)
        .expect("hashed single canonical key");

    assert_eq!(canonical, hashed);
    assert_eq!(canonical.hash(), hashed.hash());
}

#[test]
fn group_key_from_group_values_matches_borrowed_canonical_key_path() {
    let group_values = vec![
        Value::Decimal(Decimal::new(100, 2)),
        Value::Text("alpha".to_string()),
        map_value(vec![(Value::Text("z".to_string()), Value::Nat64(9))]),
    ];
    let borrowed = Value::List(group_values.clone())
        .canonical_key()
        .expect("borrowed canonical key");
    let owned = GroupKey::from_group_values(group_values).expect("owned canonical key");

    assert_eq!(borrowed, owned);
    assert_eq!(borrowed.hash(), owned.hash());
}