icydb-core 0.180.20

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
use super::{
    IndexEntryCorruption, IndexEntryExistenceWitness, IndexEntryValue, MAX_INDEX_ENTRY_BYTES,
};
use crate::{
    db::{
        index::{IndexId, IndexKey, IndexKeyKind, RawIndexStoreKey},
        key_taxonomy::{CompositePrimaryKeyValue, PrimaryKeyComponent, PrimaryKeyValue},
    },
    traits::Storable,
    types::{EntityTag, Principal},
};
use std::borrow::Cow;

fn raw_key_for(key: PrimaryKeyComponent) -> RawIndexStoreKey {
    let component = vec![0x42];
    IndexKey::new_from_components_with_primary_key_value(
        &IndexId::new(EntityTag::new(0x159), 1),
        IndexKeyKind::User,
        std::slice::from_ref(&component),
        &PrimaryKeyValue::from(key),
    )
    .to_raw()
}

fn raw_key_for_primary_key_value(key: &PrimaryKeyValue) -> RawIndexStoreKey {
    let component = vec![0x42];
    IndexKey::new_from_components_with_primary_key_value(
        &IndexId::new(EntityTag::new(0x159), 1),
        IndexKeyKind::User,
        std::slice::from_ref(&component),
        key,
    )
    .to_raw()
}

#[test]
fn index_entry_value_round_trip() {
    let key = PrimaryKeyComponent::Int64(1);
    let raw_key = raw_key_for(key);

    let raw = IndexEntryValue::presence();
    let decoded = raw
        .decode_row_witness(&raw_key)
        .expect("decode index entry")
        .primary_key_value()
        .scalar_component()
        .expect("decode scalar row identity");

    assert_eq!(decoded, key);
    assert_eq!(
        raw.as_bytes(),
        &[IndexEntryExistenceWitness::Present.to_stored_byte()]
    );
}

#[test]
fn index_entry_value_decode_primary_key_component_recovers_key_owned_row_identity() {
    let key = PrimaryKeyComponent::Int64(9);
    let raw_key = raw_key_for(key);
    let raw = IndexEntryValue::presence();

    assert_eq!(
        raw.decode_row_witness(&raw_key)
            .expect("decode key-owned row identity")
            .primary_key_value()
            .scalar_component()
            .expect("decode scalar row identity"),
        key
    );
}

#[test]
fn index_entry_value_presence_decodes_row_identity_from_raw_key() {
    let raw_key_key = PrimaryKeyComponent::Nat64(42);
    let raw_key = raw_key_for(raw_key_key);
    let raw = IndexEntryValue::presence();

    assert_eq!(
        raw.decode_row_witness(&raw_key)
            .expect("decode key-owned row witness")
            .primary_key_value()
            .scalar_component()
            .expect("decode scalar row identity"),
        raw_key_key
    );
    assert_eq!(
        raw.as_bytes(),
        &[IndexEntryExistenceWitness::Present.to_stored_byte()],
        "raw index-entry values must stay presence-only"
    );
}

#[test]
fn index_entry_value_decode_row_witness_recovers_present_witness() {
    let key = PrimaryKeyComponent::Int64(9);
    let raw_key = raw_key_for(key);
    let raw = IndexEntryValue::presence();
    let row_witness = raw
        .decode_row_witness(&raw_key)
        .expect("decode row witness");

    assert_eq!(
        row_witness
            .primary_key_value()
            .scalar_component()
            .expect("scalar row witness"),
        key
    );
    assert_eq!(
        row_witness.primary_key_value(),
        &PrimaryKeyValue::Scalar(key)
    );
    assert_eq!(
        row_witness.existence_witness(),
        IndexEntryExistenceWitness::Present
    );
}

#[test]
fn index_entry_value_decodes_composite_row_identity_from_raw_key() {
    let composite = CompositePrimaryKeyValue::try_from_components(&[
        PrimaryKeyComponent::Nat64(9),
        PrimaryKeyComponent::Principal(Principal::from_slice(&[1, 2, 3])),
    ])
    .expect("composite primary key should build");
    let key = PrimaryKeyValue::Composite(composite);
    let raw_key = raw_key_for_primary_key_value(&key);
    let raw = IndexEntryValue::presence();

    let row_witness = raw
        .decode_row_witness(&raw_key)
        .expect("decode composite row witness");
    assert_eq!(row_witness.primary_key_value(), &key);
}

#[test]
fn index_entry_value_roundtrip_via_bytes() {
    let key = PrimaryKeyComponent::Int64(9);
    let raw_key = raw_key_for(key);

    let raw = IndexEntryValue::presence();
    let encoded = Storable::to_bytes(&raw);
    let raw = IndexEntryValue::from_bytes(encoded);
    let decoded = raw
        .decode_row_witness(&raw_key)
        .expect("decode index entry")
        .primary_key_value()
        .scalar_component()
        .expect("decode scalar row identity");

    assert_eq!(decoded, key);
}

#[test]
fn index_entry_value_rejects_empty() {
    let raw_key = raw_key_for(PrimaryKeyComponent::Int64(1));
    let bytes = vec![];
    let raw = IndexEntryValue::from_bytes(Cow::Owned(bytes));
    std::assert_matches!(
        raw.decode_row_witness(&raw_key),
        Err(IndexEntryCorruption::EmptyEntry)
    );
}

#[test]
fn index_entry_value_rejects_invalid_witness() {
    let raw_key = raw_key_for(PrimaryKeyComponent::Int64(1));
    let raw = IndexEntryValue::from_bytes(Cow::Owned(vec![9]));
    std::assert_matches!(
        raw.decode_row_witness(&raw_key),
        Err(IndexEntryCorruption::InvalidWitness)
    );
}

#[test]
fn index_entry_value_rejects_oversized_payload() {
    let raw_key = raw_key_for(PrimaryKeyComponent::Int64(1));
    let bytes = vec![0u8; MAX_INDEX_ENTRY_BYTES as usize + 1];
    let raw = IndexEntryValue::from_bytes(Cow::Owned(bytes));
    std::assert_matches!(
        raw.decode_row_witness(&raw_key),
        Err(IndexEntryCorruption::TooLarge)
    );
}

#[test]
fn index_entry_value_rejects_invalid_raw_key_primary_suffix() {
    let raw = IndexEntryValue::presence();
    let invalid_raw_key = <RawIndexStoreKey as Storable>::from_bytes(Cow::Owned(vec![0]));
    std::assert_matches!(
        raw.decode_row_witness(&invalid_raw_key),
        Err(IndexEntryCorruption::InvalidKey)
    );
}

#[test]
#[expect(clippy::cast_possible_truncation)]
fn index_entry_value_decode_fuzz_does_not_panic() {
    const RUNS: u64 = 1_000;
    const MAX_LEN: usize = 256;

    let mut seed = 0xA5A5_5A5A_u64;
    for _ in 0..RUNS {
        seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
        let len = (seed as usize) % MAX_LEN;

        let mut bytes = vec![0u8; len];
        for byte in &mut bytes {
            seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
            *byte = (seed >> 24) as u8;
        }

        let raw = IndexEntryValue::from_bytes(Cow::Owned(bytes));
        let _ = raw.decode_row_witness(&raw_key_for(PrimaryKeyComponent::Int64(1)));
    }
}