use super::*;
use crate::datatypes::PropMap;
use crate::graph::core::membership::{kleene_contains_linear, MembershipSet};
fn list(values: Vec<Value>) -> Value {
Value::List(values)
}
fn map(key: &str, value: Value) -> Value {
Value::Map(PropMap::from_sorted_pairs(vec![(key.to_string(), value)]))
}
#[test]
fn recursive_predicate_unknown_and_false_dominance() {
let unknown = list(vec![Value::Null]);
assert_eq!(predicate_values_equal(&unknown, &unknown), None);
assert_eq!(
predicate_values_equal(&unknown, &list(vec![Value::Int64(1)])),
None
);
assert_eq!(predicate_values_equal(&unknown, &list(vec![])), Some(false));
for (left, right) in [
(
vec![Value::Null, Value::Int64(1)],
vec![Value::Int64(2), Value::Int64(3)],
),
(
vec![Value::Int64(1), Value::Null],
vec![Value::Int64(3), Value::Int64(2)],
),
] {
assert_eq!(
predicate_values_equal(&list(left), &list(right)),
Some(false)
);
}
assert_eq!(
predicate_values_equal(&map("a", unknown.clone()), &map("a", unknown.clone())),
None
);
assert_eq!(
predicate_values_equal(&map("a", unknown.clone()), &map("b", unknown)),
Some(false)
);
assert_eq!(
predicate_values_equal(
&list(vec![Value::Int64(1)]),
&list(vec![Value::Float64(1.0)])
),
Some(true)
);
assert_eq!(
predicate_values_equal(
&list(vec![Value::Float64(f64::NAN)]),
&list(vec![Value::Float64(f64::NAN)])
),
Some(false)
);
}
#[test]
fn structural_identity_is_independent_of_nullable_predicates() {
let value = map("a", list(vec![Value::Null]));
let mut seen = HashSet::new();
seen.insert(value.clone());
seen.insert(value.clone());
assert_eq!(seen.len(), 1);
assert_eq!(value, value.clone());
assert_eq!(predicate_values_equal(&value, &value), None);
assert!(!values_equal(&value, &value));
}
#[test]
fn an_empty_list_is_false_for_every_operand_including_null() {
let empty = MembershipSet::default();
assert_eq!(empty.kleene_contains(&Value::Null), Some(false));
assert_eq!(empty.kleene_contains(&Value::Int64(1)), Some(false));
assert_eq!(empty.kleene_contains(&list(vec![Value::Null])), Some(false));
assert_eq!(kleene_contains_linear(&Value::Null, &[]), Some(false));
assert_eq!(kleene_contains_linear(&Value::Int64(1), &[]), Some(false));
let ones = MembershipSet::new(vec![Value::Int64(1)]);
assert_eq!(ones.kleene_contains(&Value::Null), None);
assert_eq!(
kleene_contains_linear(&Value::Null, &[Value::Int64(1)]),
None
);
let nulls = MembershipSet::new(vec![Value::Null]);
assert_eq!(nulls.kleene_contains(&Value::Null), None);
assert_eq!(nulls.kleene_contains(&Value::Int64(1)), None);
assert!(!empty.matches(&Value::Null));
assert!(!empty.matches(&Value::Int64(1)));
}
#[test]
fn membership_preserves_unknown_on_both_sides_of_index_threshold() {
for count in [8, 9] {
let values: Vec<Value> = (1..=count).map(|i| list(vec![Value::Int64(i)])).collect();
let set = MembershipSet::new(values.clone());
let unknown = list(vec![Value::Null]);
assert_eq!(set.kleene_contains(&unknown), None);
assert_eq!(kleene_contains_linear(&unknown, &values), None);
assert!(!set.matches(&unknown));
assert_eq!(
set.kleene_contains(&list(vec![Value::Int64(1)])),
Some(true)
);
assert_eq!(set.kleene_contains(&list(vec![])), Some(false));
assert_eq!(set.kleene_contains(&Value::Int64(1)), Some(false));
let mut with_unknown = vec![unknown];
with_unknown.extend(values);
let set = MembershipSet::new(with_unknown);
assert_eq!(
set.kleene_contains(&list(vec![Value::Int64(1)])),
Some(true)
);
assert_eq!(set.kleene_contains(&list(vec![Value::Int64(99)])), None);
assert_eq!(set.kleene_contains(&list(vec![])), Some(false));
}
}
#[test]
fn true_only_scalar_dispatch_preserves_absolute_nullable_truth_table() {
let cases = [
(Value::Null, Value::Null, None),
(Value::Null, Value::Int64(1), None),
(Value::Int64(1), Value::Float64(1.0), Some(true)),
(Value::UniqueId(1), Value::Int64(1), Some(true)),
(Value::Int64(1), Value::Int64(2), Some(false)),
(
Value::Float64(f64::NAN),
Value::Float64(f64::NAN),
Some(false),
),
(
Value::String("Oslo".into()),
Value::String("[\"Oslo\"]".into()),
Some(true),
),
(list(vec![Value::Null]), list(vec![Value::Null]), None),
(
list(vec![Value::Int64(1)]),
list(vec![Value::Float64(1.0)]),
Some(true),
),
(
list(vec![Value::Null, Value::Int64(1)]),
list(vec![Value::Int64(2), Value::Int64(3)]),
Some(false),
),
(map("a", Value::Null), map("a", Value::Null), None),
(map("a", Value::Null), map("b", Value::Null), Some(false)),
(list(vec![Value::Int64(1)]), Value::Int64(1), Some(false)),
(map("a", Value::Int64(1)), Value::Int64(1), Some(false)),
(
list(vec![Value::Int64(1)]),
map("a", Value::Int64(1)),
Some(false),
),
(
Value::Point { lat: 1.0, lon: 2.0 },
Value::Point { lat: 1.0, lon: 2.0 },
Some(true),
),
(
Value::Point {
lat: f64::NAN,
lon: 2.0,
},
Value::Point {
lat: f64::NAN,
lon: 2.0,
},
Some(false),
),
(Value::NodeRef(1), Value::NodeRef(1), Some(true)),
(Value::NodeRef(1), Value::NodeRef(2), Some(false)),
];
for (left, right, expected) in cases {
for (a, b) in [(&left, &right), (&right, &left)] {
assert_eq!(predicate_values_equal(a, b), expected, "{a:?} = {b:?}");
assert_eq!(values_equal(a, b), expected == Some(true), "{a:?} = {b:?}");
}
}
}
#[test]
fn true_only_dispatch_preserves_entity_identity_with_nullable_properties() {
use crate::datatypes::values::{NodeValue, PathValue, RelValue};
for (property, expected) in [(Value::Null, true), (Value::Float64(f64::NAN), false)] {
let properties = PropMap::from_sorted_pairs(vec![("v".into(), property)]);
let node = NodeValue {
id: 1,
labels: vec!["N".into()],
properties: properties.clone(),
};
let rel = RelValue {
id: 1,
start_id: 1,
end_id: 1,
rel_type: "R".into(),
properties,
};
for value in [
Value::Node(Box::new(node.clone())),
Value::Relationship(Box::new(rel.clone())),
Value::Path(Box::new(PathValue {
nodes: vec![node.clone(), node],
rels: vec![rel],
})),
] {
assert_eq!(predicate_values_equal(&value, &value), Some(expected));
assert_eq!(values_equal(&value, &value), expected);
assert!(!values_equal(&value, &map("v", Value::Null)));
}
}
}
#[test]
fn node_reference_predicates_match_materialized_entity_slots() {
use crate::datatypes::values::NodeValue;
let node = |id| {
Value::Node(Box::new(NodeValue {
id,
labels: vec!["N".into()],
properties: PropMap::from_sorted_pairs(vec![("id".into(), Value::Int64(7))]),
}))
};
for (left, right, expected) in [
(Value::NodeRef(1), node(1), Some(true)),
(Value::NodeRef(1), node(2), Some(false)),
(Value::NodeRef(1), Value::Int64(1), Some(false)),
(Value::NodeRef(1), Value::Null, None),
(
list(vec![Value::NodeRef(1)]),
list(vec![node(1)]),
Some(true),
),
(map("n", Value::NodeRef(1)), map("n", node(1)), Some(true)),
(
list(vec![Value::NodeRef(1), Value::Null]),
list(vec![node(1), Value::Null]),
None,
),
] {
for (a, b) in [(&left, &right), (&right, &left)] {
assert_eq!(predicate_values_equal(a, b), expected, "{a:?} = {b:?}");
assert_eq!(values_equal(a, b), expected == Some(true));
}
}
for count in [8, 9] {
let set = MembershipSet::new((1..=count).map(node).collect());
assert_eq!(set.kleene_contains(&Value::NodeRef(1)), Some(true));
assert_eq!(set.kleene_contains(&Value::NodeRef(99)), Some(false));
let refs = MembershipSet::new((1..=count).map(Value::NodeRef).collect());
assert_eq!(refs.kleene_contains(&node(1)), Some(true));
assert_eq!(refs.kleene_contains(&node(99)), Some(false));
}
let values = [Value::NodeRef(1), node(1)]
.into_iter()
.collect::<HashSet<_>>();
assert_eq!(values.len(), 2);
}
#[test]
fn mixed_numeric_predicates_are_exact_without_changing_structural_identity() {
let boundary = 1i64 << 53;
for (integer, float, expected) in [
(boundary, boundary as f64, Some(true)),
(boundary + 1, boundary as f64, Some(false)),
(-boundary, -(boundary as f64), Some(true)),
(-boundary - 1, -(boundary as f64), Some(false)),
] {
let integer = Value::Int64(integer);
let float = Value::Float64(float);
assert_eq!(predicate_values_equal(&integer, &float), expected);
assert_eq!(predicate_values_equal(&float, &integer), expected);
}
let structurally_distinct = [Value::Int64(boundary), Value::Float64(boundary as f64)]
.into_iter()
.collect::<HashSet<_>>();
assert_eq!(structurally_distinct.len(), 2);
}