use crate::{
db::predicate::{
CoercionId, CompareOp, ComparePredicate, Predicate,
encoding::{
canonicalize_compare_literal_for_coercion, encode_compare_value_sort_key_into,
encode_predicate_sort_key, encode_value_sort_key_into, push_bytes_u64, push_len_u64,
push_value_sort_key_framed,
},
},
value::Value,
};
use std::borrow::Cow;
#[test]
fn compare_encoding_borrows_unchanged_literals_and_owns_coercions() {
let nested = Value::List(vec![Value::Map(vec![(
Value::Text("key".repeat(256)),
Value::NatBig(crate::types::NatBig::from_biguint(
num_bigint::BigUint::from(1_u8) << 4096_usize,
)),
)])]);
for coercion in [
CoercionId::Strict,
CoercionId::CollectionElement,
CoercionId::NumericWiden,
CoercionId::TextCasefold,
] {
let canonical = canonicalize_compare_literal_for_coercion(coercion, &nested);
assert!(
matches!(canonical, Cow::Borrowed(value) if std::ptr::eq(value, &raw const nested))
);
}
for (coercion, value, expected) in [
(
CoercionId::NumericWiden,
Value::Int64(7),
Value::Decimal(crate::types::Decimal::new(7, 0)),
),
(
CoercionId::TextCasefold,
Value::Text("ADA".to_string()),
Value::Text("ada".to_string()),
),
] {
let canonical = canonicalize_compare_literal_for_coercion(coercion, &value);
assert!(matches!(canonical, Cow::Owned(_)));
assert_eq!(*canonical, expected);
}
}
#[test]
fn borrowed_compare_encoding_preserves_scalar_and_membership_bytes() {
let nested = Value::Map(vec![(Value::Text("z".repeat(256)), Value::Nat64(7))]);
let source = vec![
Value::Text("ADA".to_string()),
nested.clone(),
Value::Int64(7),
Value::Text("ada".to_string()),
nested,
Value::Decimal(crate::types::Decimal::new(7, 0)),
];
let snapshot = source.clone();
for coercion in [
CoercionId::Strict,
CoercionId::CollectionElement,
CoercionId::NumericWiden,
CoercionId::TextCasefold,
] {
let mut expected_values = source.clone();
match coercion {
CoercionId::NumericWiden => {
expected_values[2] = Value::Decimal(crate::types::Decimal::new(7, 0));
}
CoercionId::TextCasefold => {
expected_values[0] = Value::Text("ada".to_string());
}
CoercionId::Strict | CoercionId::CollectionElement => {}
}
for (value, expected_value) in source.iter().zip(&expected_values) {
let mut expected = Vec::new();
encode_value_sort_key_into(&mut expected, expected_value);
let mut actual = Vec::new();
encode_compare_value_sort_key_into(&mut actual, CompareOp::Eq, coercion, value, false);
assert_eq!(actual, expected);
}
for normalized in [false, true] {
let mut values = expected_values.clone();
values.sort_unstable_by(Value::canonical_cmp);
values.dedup();
let input = Value::List(if normalized {
values.clone()
} else {
source.clone()
});
let mut expected = vec![input.canonical_tag().to_u8()];
push_len_u64(&mut expected, values.len());
for value in &values {
push_value_sort_key_framed(&mut expected, value);
}
for op in [CompareOp::In, CompareOp::NotIn] {
let mut actual = Vec::new();
encode_compare_value_sort_key_into(&mut actual, op, coercion, &input, normalized);
assert_eq!(actual, expected);
}
}
}
assert_eq!(source, snapshot);
}
#[test]
fn bigint_sort_key_stream_preserves_length_and_candid_payload() {
let magnitude = num_bigint::BigUint::from(1_u8) << 4096_usize;
let mut unsigned = Vec::new();
candid::Nat::from(magnitude.clone())
.encode(&mut unsigned)
.unwrap();
let negative = -num_bigint::BigInt::from(magnitude.clone());
let mut signed = Vec::new();
candid::Int::from(negative.clone())
.encode(&mut signed)
.unwrap();
for (value, bytes) in [
(
Value::NatBig(crate::types::NatBig::from_biguint(magnitude)),
unsigned,
),
(
Value::IntBig(crate::types::IntBig::from_bigint(negative)),
signed,
),
] {
let mut expected = vec![value.canonical_tag().to_u8()];
push_bytes_u64(&mut expected, &bytes);
let mut actual = Vec::new();
encode_value_sort_key_into(&mut actual, &value);
assert_eq!(actual, expected);
}
}
#[test]
fn predicate_sort_key_normalizes_map_entry_order() {
let map_a = Value::Map(vec![
(Value::Text("z".to_string()), Value::Int64(9)),
(Value::Text("a".to_string()), Value::Int64(1)),
]);
let map_b = Value::Map(vec![
(Value::Text("a".to_string()), Value::Int64(1)),
(Value::Text("z".to_string()), Value::Int64(9)),
]);
let predicate_a = Predicate::Compare(ComparePredicate::eq("payload".to_string(), map_a));
let predicate_b = Predicate::Compare(ComparePredicate::eq("payload".to_string(), map_b));
assert_eq!(
encode_predicate_sort_key(&predicate_a),
encode_predicate_sort_key(&predicate_b)
);
}
#[test]
fn predicate_sort_key_normalizes_duplicate_map_keys_by_value_order() {
let map_a = Value::Map(vec![
(Value::Text("a".to_string()), Value::Int64(2)),
(Value::Text("a".to_string()), Value::Int64(1)),
]);
let map_b = Value::Map(vec![
(Value::Text("a".to_string()), Value::Int64(1)),
(Value::Text("a".to_string()), Value::Int64(2)),
]);
let predicate_a = Predicate::Compare(ComparePredicate::eq("payload".to_string(), map_a));
let predicate_b = Predicate::Compare(ComparePredicate::eq("payload".to_string(), map_b));
assert_eq!(
encode_predicate_sort_key(&predicate_a),
encode_predicate_sort_key(&predicate_b)
);
}
#[test]
fn predicate_sort_key_normalizes_in_list_literal_order() {
let predicate_a = Predicate::Compare(ComparePredicate::in_(
"rank".to_string(),
vec![Value::Nat64(3), Value::Nat64(1), Value::Nat64(2)],
));
let predicate_b = Predicate::Compare(ComparePredicate::in_(
"rank".to_string(),
vec![Value::Nat64(1), Value::Nat64(2), Value::Nat64(3)],
));
assert_eq!(
encode_predicate_sort_key(&predicate_a),
encode_predicate_sort_key(&predicate_b)
);
}
#[test]
fn predicate_sort_key_normalizes_in_list_duplicate_literals() {
let predicate_a = Predicate::Compare(ComparePredicate::in_(
"rank".to_string(),
vec![
Value::Nat64(3),
Value::Nat64(1),
Value::Nat64(3),
Value::Nat64(2),
],
));
let predicate_b = Predicate::Compare(ComparePredicate::in_(
"rank".to_string(),
vec![Value::Nat64(1), Value::Nat64(2), Value::Nat64(3)],
));
assert_eq!(
encode_predicate_sort_key(&predicate_a),
encode_predicate_sort_key(&predicate_b)
);
}
#[test]
fn predicate_sort_key_numeric_widen_treats_equivalent_literal_subtypes_as_identical() {
let predicate_int = Predicate::Compare(ComparePredicate::with_coercion(
"rank",
CompareOp::Eq,
Value::Int64(1),
CoercionId::NumericWiden,
));
let predicate_decimal = Predicate::Compare(ComparePredicate::with_coercion(
"rank",
CompareOp::Eq,
Value::Decimal(crate::types::Decimal::new(10, 1)),
CoercionId::NumericWiden,
));
assert_eq!(
encode_predicate_sort_key(&predicate_int),
encode_predicate_sort_key(&predicate_decimal)
);
}
#[test]
fn predicate_sort_key_strict_keeps_numeric_literal_subtypes_distinct() {
let predicate_int = Predicate::Compare(ComparePredicate::with_coercion(
"rank",
CompareOp::Eq,
Value::Int64(1),
CoercionId::Strict,
));
let predicate_decimal = Predicate::Compare(ComparePredicate::with_coercion(
"rank",
CompareOp::Eq,
Value::Decimal(crate::types::Decimal::new(10, 1)),
CoercionId::Strict,
));
assert_ne!(
encode_predicate_sort_key(&predicate_int),
encode_predicate_sort_key(&predicate_decimal)
);
}
#[test]
fn predicate_sort_key_text_casefold_treats_case_only_literals_as_identical() {
let predicate_lower = Predicate::Compare(ComparePredicate::with_coercion(
"name",
CompareOp::Eq,
Value::Text("ada".to_string()),
CoercionId::TextCasefold,
));
let predicate_upper = Predicate::Compare(ComparePredicate::with_coercion(
"name",
CompareOp::Eq,
Value::Text("ADA".to_string()),
CoercionId::TextCasefold,
));
assert_eq!(
encode_predicate_sort_key(&predicate_lower),
encode_predicate_sort_key(&predicate_upper)
);
}
#[test]
fn predicate_sort_key_strict_keeps_text_case_variants_distinct() {
let predicate_lower = Predicate::Compare(ComparePredicate::with_coercion(
"name",
CompareOp::Eq,
Value::Text("ada".to_string()),
CoercionId::Strict,
));
let predicate_upper = Predicate::Compare(ComparePredicate::with_coercion(
"name",
CompareOp::Eq,
Value::Text("ADA".to_string()),
CoercionId::Strict,
));
assert_ne!(
encode_predicate_sort_key(&predicate_lower),
encode_predicate_sort_key(&predicate_upper)
);
}
#[test]
fn predicate_sort_key_text_casefold_normalizes_in_list_case_variants() {
let predicate_mixed = Predicate::Compare(ComparePredicate::with_coercion(
"name",
CompareOp::In,
Value::List(vec![
Value::Text("ADA".to_string()),
Value::Text("ada".to_string()),
Value::Text("Bob".to_string()),
]),
CoercionId::TextCasefold,
));
let predicate_canonical = Predicate::Compare(ComparePredicate::with_coercion(
"name",
CompareOp::In,
Value::List(vec![
Value::Text("ada".to_string()),
Value::Text("bob".to_string()),
]),
CoercionId::TextCasefold,
));
assert_eq!(
encode_predicate_sort_key(&predicate_mixed),
encode_predicate_sort_key(&predicate_canonical)
);
}