use crate::distinct_functions::normalize_collection_result;
use crate::evaluator::EvaluationContext;
use helios_fhirpath_support::EvaluationError;
use helios_fhirpath_support::EvaluationResult;
use std::collections::HashSet;
pub fn intersect_function(
invocation_base: &EvaluationResult,
other_collection: &EvaluationResult,
context: &EvaluationContext,
) -> Result<EvaluationResult, EvaluationError> {
if invocation_base == &EvaluationResult::Empty || other_collection == &EvaluationResult::Empty {
return Ok(EvaluationResult::Empty);
}
let left_items = match invocation_base {
EvaluationResult::Collection { items, .. } => items.clone(),
single_item => vec![single_item.clone()],
};
let right_items = match other_collection {
EvaluationResult::Collection { items, .. } => items.clone(),
single_item => vec![single_item.clone()],
};
let mut intersection_items = Vec::new();
let mut added_items_set = HashSet::new();
for left_item in &left_items {
let exists_in_right = right_items
.iter()
.any(|right_item| equal_helper(left_item, right_item, context));
if exists_in_right {
if added_items_set.insert(left_item.clone()) {
intersection_items.push(left_item.clone());
}
}
}
Ok(normalize_collection_result(intersection_items, true))
}
pub fn exclude_function(
invocation_base: &EvaluationResult,
other_collection: &EvaluationResult,
context: &EvaluationContext,
) -> Result<EvaluationResult, EvaluationError> {
if invocation_base == &EvaluationResult::Empty {
return Ok(EvaluationResult::Empty);
}
if other_collection == &EvaluationResult::Empty {
return Ok(invocation_base.clone());
}
let left_items = match invocation_base {
EvaluationResult::Collection { items, .. } => items.clone(),
single_item => vec![single_item.clone()],
};
let right_items = match other_collection {
EvaluationResult::Collection { items, .. } => items.clone(),
single_item => vec![single_item.clone()],
};
let mut result_items = Vec::new();
for left_item in &left_items {
let exists_in_right = right_items
.iter()
.any(|right_item| equal_helper(left_item, right_item, context));
if !exists_in_right {
result_items.push(left_item.clone());
}
}
let input_was_unordered = matches!(
invocation_base,
EvaluationResult::Collection {
has_undefined_order: true,
type_info: None,
..
}
);
Ok(normalize_collection_result(
result_items,
input_was_unordered,
))
}
pub fn union_function(
invocation_base: &EvaluationResult,
other_collection: &EvaluationResult,
context: &EvaluationContext,
) -> Result<EvaluationResult, EvaluationError> {
let left_items = match invocation_base {
EvaluationResult::Collection { items, .. } => items.clone(),
EvaluationResult::Empty => vec![],
single_item => vec![single_item.clone()],
};
let right_items = match other_collection {
EvaluationResult::Collection { items, .. } => items.clone(),
EvaluationResult::Empty => vec![],
single_item => vec![single_item.clone()],
};
let mut union_items = Vec::new();
let mut added_items_set = HashSet::new();
for item in left_items {
if added_items_set.insert(item.clone()) {
union_items.push(item);
}
}
for item in right_items {
if !union_items
.iter()
.any(|existing| equal_helper(existing, &item, context))
{
union_items.push(item);
}
}
Ok(normalize_collection_result(union_items, true))
}
pub fn combine_function(
invocation_base: &EvaluationResult,
other_collection: &EvaluationResult,
preserve_order: bool,
_context: &EvaluationContext,
) -> Result<EvaluationResult, EvaluationError> {
let left_items = match invocation_base {
EvaluationResult::Collection { items, .. } => items.clone(),
EvaluationResult::Empty => vec![],
single_item => vec![single_item.clone()],
};
let right_items = match other_collection {
EvaluationResult::Collection { items, .. } => items.clone(),
EvaluationResult::Empty => vec![],
single_item => vec![single_item.clone()],
};
let mut combined_items = left_items;
combined_items.extend(right_items);
Ok(normalize_collection_result(combined_items, !preserve_order))
}
fn equal_helper(
left: &EvaluationResult,
right: &EvaluationResult,
_context: &EvaluationContext,
) -> bool {
if let (
EvaluationResult::Collection { items: l_items, .. },
EvaluationResult::Collection { items: r_items, .. },
) = (left, right)
{
if l_items.len() != r_items.len() {
return false;
}
return l_items
.iter()
.zip(r_items.iter())
.all(|(li, ri)| equal_helper(li, ri, _context));
}
if let (EvaluationResult::Collection { items, .. }, _) = (left, right) {
if items.len() == 1 && !right.is_collection() {
return equal_helper(&items[0], right, _context);
}
}
if let (_, EvaluationResult::Collection { items, .. }) = (left, right) {
if items.len() == 1 && !left.is_collection() {
return equal_helper(left, &items[0], _context);
}
}
if left.is_collection() || right.is_collection() {
return false;
}
if *left == EvaluationResult::Empty || *right == EvaluationResult::Empty {
return false;
}
match (left, right) {
(EvaluationResult::Boolean(l, _, _), EvaluationResult::Boolean(r, _, _)) => l == r,
(EvaluationResult::String(l, _, _), EvaluationResult::String(r, _, _)) => l == r,
(EvaluationResult::Integer(l, _, _), EvaluationResult::Integer(r, _, _)) => l == r,
(EvaluationResult::Decimal(l, _, _), EvaluationResult::Decimal(r, _, _)) => l == r,
(EvaluationResult::Decimal(l, _, _), EvaluationResult::Integer(r, _, _)) => {
*l == rust_decimal::Decimal::from(*r)
}
(EvaluationResult::Integer(l, _, _), EvaluationResult::Decimal(r, _, _)) => {
rust_decimal::Decimal::from(*l) == *r
}
(EvaluationResult::Date(l, _, _), EvaluationResult::Date(r, _, _)) => l == r,
(EvaluationResult::DateTime(l, _, _), EvaluationResult::DateTime(r, _, _)) => l == r,
(EvaluationResult::Time(l, _, _), EvaluationResult::Time(r, _, _)) => l == r,
(
EvaluationResult::Quantity(l_val, l_unit, _, _),
EvaluationResult::Quantity(r_val, r_unit, _, _),
) => l_val == r_val && units_are_equivalent(l_unit, r_unit),
_ => false,
}
}
fn units_are_equivalent(unit1: &str, unit2: &str) -> bool {
if unit1 == unit2 {
return true;
}
let normalized_unit1 = normalize_ucum_unit(unit1);
let normalized_unit2 = normalize_ucum_unit(unit2);
normalized_unit1 == normalized_unit2
}
fn normalize_ucum_unit(unit: &str) -> &str {
match unit {
"lbs" | "[lb_av]" => "[lb_av]", "kg" | "[kg]" => "kg", "g" | "[g]" => "g", "mg" | "[mg]" => "mg", _ => unit, }
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal::Decimal;
#[test]
fn test_intersect_basic() {
let collection1 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let collection2 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(2),
EvaluationResult::integer(3),
EvaluationResult::integer(4),
],
has_undefined_order: false,
type_info: None,
};
let context = EvaluationContext::new_empty_with_default_version();
let result = intersect_function(&collection1, &collection2, &context).unwrap();
match result {
EvaluationResult::Collection {
items,
has_undefined_order,
..
} => {
assert_eq!(items.len(), 2);
assert!(items.contains(&EvaluationResult::integer(2)));
assert!(items.contains(&EvaluationResult::integer(3)));
assert!(has_undefined_order); }
_ => panic!("Expected collection result"),
}
}
#[test]
fn test_intersect_no_common_elements() {
let collection1 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let collection2 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(4),
EvaluationResult::integer(5),
EvaluationResult::integer(6),
],
has_undefined_order: false,
type_info: None,
};
let context = EvaluationContext::new_empty_with_default_version();
let result = intersect_function(&collection1, &collection2, &context).unwrap();
assert_eq!(result, EvaluationResult::Empty);
}
#[test]
fn test_intersect_with_duplicates() {
let collection1 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let collection2 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(2),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
EvaluationResult::integer(4),
],
has_undefined_order: false,
type_info: None,
};
let context = EvaluationContext::new_empty_with_default_version();
let result = intersect_function(&collection1, &collection2, &context).unwrap();
match result {
EvaluationResult::Collection {
items,
has_undefined_order,
..
} => {
assert_eq!(items.len(), 2);
assert!(items.contains(&EvaluationResult::integer(2)));
assert!(items.contains(&EvaluationResult::integer(3)));
assert!(has_undefined_order); }
_ => panic!("Expected collection result"),
}
}
#[test]
fn test_intersect_with_empty() {
let collection = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let empty = EvaluationResult::Empty;
let context = EvaluationContext::new_empty_with_default_version();
let result = intersect_function(&collection, &empty, &context).unwrap();
assert_eq!(result, EvaluationResult::Empty);
let result = intersect_function(&empty, &collection, &context).unwrap();
assert_eq!(result, EvaluationResult::Empty);
}
#[test]
fn test_exclude_basic() {
let collection1 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let collection2 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(2),
EvaluationResult::integer(3),
EvaluationResult::integer(4),
],
has_undefined_order: false,
type_info: None,
};
let context = EvaluationContext::new_empty_with_default_version();
let result = exclude_function(&collection1, &collection2, &context).unwrap();
let expected = EvaluationResult::integer(1); assert_eq!(result, expected);
}
#[test]
fn test_exclude_no_common_elements() {
let collection1 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let collection2 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(4),
EvaluationResult::integer(5),
EvaluationResult::integer(6),
],
has_undefined_order: false,
type_info: None,
};
let context = EvaluationContext::new_empty_with_default_version();
let result = exclude_function(&collection1, &collection2, &context).unwrap();
assert_eq!(result, collection1);
}
#[test]
fn test_exclude_with_duplicates() {
let collection1 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(1),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let collection2 = EvaluationResult::Collection {
items: vec![EvaluationResult::integer(2), EvaluationResult::integer(3)],
has_undefined_order: false,
type_info: None,
};
let context = EvaluationContext::new_empty_with_default_version();
let result = exclude_function(&collection1, &collection2, &context).unwrap();
let expected = EvaluationResult::Collection {
items: vec![EvaluationResult::integer(1), EvaluationResult::integer(1)],
has_undefined_order: false,
type_info: None,
};
assert_eq!(result, expected);
}
#[test]
fn test_exclude_with_empty() {
let collection = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let empty = EvaluationResult::Empty;
let context = EvaluationContext::new_empty_with_default_version();
let result = exclude_function(&collection, &empty, &context).unwrap();
assert_eq!(result, collection);
let result = exclude_function(&empty, &collection, &context).unwrap();
assert_eq!(result, EvaluationResult::Empty);
}
#[test]
fn test_union_basic() {
let collection1 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let collection2 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(3),
EvaluationResult::integer(4),
EvaluationResult::integer(5),
],
has_undefined_order: false,
type_info: None,
};
let context = EvaluationContext::new_empty_with_default_version();
let result = union_function(&collection1, &collection2, &context).unwrap();
match result {
EvaluationResult::Collection {
items,
has_undefined_order,
..
} => {
assert_eq!(items.len(), 5);
assert!(items.contains(&EvaluationResult::integer(1)));
assert!(items.contains(&EvaluationResult::integer(2)));
assert!(items.contains(&EvaluationResult::integer(3)));
assert!(items.contains(&EvaluationResult::integer(4)));
assert!(items.contains(&EvaluationResult::integer(5)));
assert!(has_undefined_order); }
_ => panic!("Expected collection result"),
}
}
#[test]
fn test_union_with_duplicates() {
let collection1 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let collection2 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(2),
EvaluationResult::integer(3),
EvaluationResult::integer(3),
EvaluationResult::integer(4),
],
has_undefined_order: false,
type_info: None,
};
let context = EvaluationContext::new_empty_with_default_version();
let result = union_function(&collection1, &collection2, &context).unwrap();
match result {
EvaluationResult::Collection {
items,
has_undefined_order,
..
} => {
assert_eq!(items.len(), 4);
assert!(items.contains(&EvaluationResult::integer(1)));
assert!(items.contains(&EvaluationResult::integer(2)));
assert!(items.contains(&EvaluationResult::integer(3)));
assert!(items.contains(&EvaluationResult::integer(4)));
assert!(has_undefined_order); }
_ => panic!("Expected collection result"),
}
}
#[test]
fn test_union_with_empty() {
let collection = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let empty = EvaluationResult::Empty;
let context = EvaluationContext::new_empty_with_default_version();
let result = union_function(&collection, &empty, &context).unwrap();
match result {
EvaluationResult::Collection {
items,
has_undefined_order,
..
} => {
assert_eq!(items.len(), 3);
assert!(items.contains(&EvaluationResult::integer(1)));
assert!(items.contains(&EvaluationResult::integer(2)));
assert!(items.contains(&EvaluationResult::integer(3)));
assert!(has_undefined_order); }
_ => panic!("Expected collection result"),
}
let result = union_function(&empty, &collection, &context).unwrap();
match result {
EvaluationResult::Collection {
items,
has_undefined_order,
..
} => {
assert_eq!(items.len(), 3);
assert!(items.contains(&EvaluationResult::integer(1)));
assert!(items.contains(&EvaluationResult::integer(2)));
assert!(items.contains(&EvaluationResult::integer(3)));
assert!(has_undefined_order); }
_ => panic!("Expected collection result"),
}
}
#[test]
fn test_combine_basic() {
let collection1 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let collection2 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(4),
EvaluationResult::integer(5),
EvaluationResult::integer(6),
],
has_undefined_order: false,
type_info: None,
};
let context = EvaluationContext::new_empty_with_default_version();
let result = combine_function(&collection1, &collection2, false, &context).unwrap();
match result {
EvaluationResult::Collection {
items,
has_undefined_order,
..
} => {
assert_eq!(items.len(), 6);
let mut found_count = 0;
for i in 1..=6 {
if items.contains(&EvaluationResult::integer(i)) {
found_count += 1;
}
}
assert_eq!(found_count, 6);
assert!(has_undefined_order); }
_ => panic!("Expected collection result"),
}
}
#[test]
fn test_combine_with_duplicates() {
let collection1 = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(2),
],
has_undefined_order: false,
type_info: None,
};
let collection2 = EvaluationResult::Collection {
items: vec![EvaluationResult::integer(2), EvaluationResult::integer(3)],
has_undefined_order: false,
type_info: None,
};
let context = EvaluationContext::new_empty_with_default_version();
let result = combine_function(&collection1, &collection2, false, &context).unwrap();
match result {
EvaluationResult::Collection {
items,
has_undefined_order,
..
} => {
assert_eq!(items.len(), 5);
let mut count_1 = 0;
let mut count_2 = 0;
let mut count_3 = 0;
for item in items {
match item {
EvaluationResult::Integer(1, _, _) => count_1 += 1,
EvaluationResult::Integer(2, _, _) => count_2 += 1,
EvaluationResult::Integer(3, _, _) => count_3 += 1,
_ => panic!("Unexpected item in collection"),
}
}
assert_eq!(count_1, 1);
assert_eq!(count_2, 3);
assert_eq!(count_3, 1);
assert!(has_undefined_order); }
_ => panic!("Expected collection result"),
}
}
#[test]
fn test_combine_with_empty() {
let collection = EvaluationResult::Collection {
items: vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
has_undefined_order: false,
type_info: None,
};
let empty = EvaluationResult::Empty;
let context = EvaluationContext::new_empty_with_default_version();
let result = combine_function(&collection, &empty, false, &context).unwrap();
match result {
EvaluationResult::Collection {
items,
has_undefined_order,
..
} => {
assert_eq!(items.len(), 3);
assert!(items.contains(&EvaluationResult::integer(1)));
assert!(items.contains(&EvaluationResult::integer(2)));
assert!(items.contains(&EvaluationResult::integer(3)));
assert!(has_undefined_order); }
_ => panic!("Expected collection result"),
}
let result = combine_function(&empty, &collection, false, &context).unwrap();
match result {
EvaluationResult::Collection {
items,
has_undefined_order,
type_info: _,
} => {
assert_eq!(items.len(), 3);
assert!(items.contains(&EvaluationResult::integer(1)));
assert!(items.contains(&EvaluationResult::integer(2)));
assert!(items.contains(&EvaluationResult::integer(3)));
assert!(has_undefined_order); }
_ => panic!("Expected collection result"),
}
}
#[test]
fn test_equal_helper() {
let context = EvaluationContext::new_empty_with_default_version();
assert!(equal_helper(
&EvaluationResult::integer(1),
&EvaluationResult::integer(1),
&context
));
assert!(!equal_helper(
&EvaluationResult::integer(1),
&EvaluationResult::integer(2),
&context
));
assert!(!equal_helper(
&EvaluationResult::integer(1),
&EvaluationResult::string("1".to_string()),
&context
));
assert!(equal_helper(
&EvaluationResult::integer(1),
&EvaluationResult::decimal(Decimal::from(1)),
&context
));
assert!(equal_helper(
&EvaluationResult::decimal(Decimal::from(1)),
&EvaluationResult::integer(1),
&context
));
let collection1 = EvaluationResult::Collection {
items: vec![EvaluationResult::integer(1), EvaluationResult::integer(2)],
has_undefined_order: false,
type_info: None,
};
let collection2 = EvaluationResult::Collection {
items: vec![EvaluationResult::integer(1), EvaluationResult::integer(2)],
has_undefined_order: true, type_info: None,
};
let collection3 = EvaluationResult::Collection {
items: vec![EvaluationResult::integer(2), EvaluationResult::integer(1)],
has_undefined_order: false,
type_info: None,
};
assert!(equal_helper(&collection1, &collection2, &context));
assert!(!equal_helper(&collection1, &collection3, &context));
let singleton = EvaluationResult::Collection {
items: vec![EvaluationResult::integer(1)],
has_undefined_order: false,
type_info: None,
};
assert!(equal_helper(
&singleton,
&EvaluationResult::integer(1),
&context
));
assert!(equal_helper(
&EvaluationResult::integer(1),
&singleton,
&context
));
}
}