criterium 3.1.3

Lightweigt dynamic database queries for rusqlite.
Documentation
// SPDX-FileCopyrightText: 2025 Slatian
//
// SPDX-License-Identifier: LGPL-3.0-only

//! Implements DirectMatch on the NumberCriterium

use std::fmt::Debug;
use std::ops::RangeInclusive;

use crate::direct_match::DirectMatchResult;
use crate::number::AsInteger;
use crate::number::NumberCriterium;
use crate::DirectMatch;

macro_rules! direct_direct_match {
	($e:ty) => {
		impl DirectMatch<$e> for NumberCriterium<$e> {
			type Output = bool;

			fn criterium_match(&self, data: &$e) -> bool {
				match self {
					Self::Equals(my) => data == my,
					Self::LessThan(my) => data < my,
					Self::LessThanOrEqual(my) => data <= my,
					Self::GreaterThan(my) => data > my,
					Self::GreaterThanOrEqual(my) => data >= my,
					Self::InList(l) => l.contains(data),
					Self::IsNone => false,
				}
			}
		}
	};
}

direct_direct_match!(f32);
direct_direct_match!(f64);
direct_direct_match!(u64);
direct_direct_match!(i128);
direct_direct_match!(u128);
direct_direct_match!(isize);
direct_direct_match!(usize);

impl<V, N> DirectMatch<V> for NumberCriterium<N>
where
	V: AsInteger,
	N: AsInteger,
{
	type Output = bool;

	fn criterium_match(&self, data: &V) -> bool {
		match self {
			Self::Equals(my) => data.as_criterium_i64() == my.as_criterium_i64(),
			Self::LessThan(my) => data.as_criterium_i64() < my.as_criterium_i64(),
			Self::LessThanOrEqual(my) => data.as_criterium_i64() <= my.as_criterium_i64(),
			Self::GreaterThan(my) => data.as_criterium_i64() > my.as_criterium_i64(),
			Self::GreaterThanOrEqual(my) => data.as_criterium_i64() >= my.as_criterium_i64(),
			Self::InList(l) => {
				let d = data.as_criterium_i64();
				l.iter().any(|i| i.as_criterium_i64() == d)
			}
			Self::IsNone => false,
		}
	}
}

impl<V> DirectMatch<Option<V>> for NumberCriterium<V>
where
	NumberCriterium<V>: DirectMatch<V>,
{
	type Output = <NumberCriterium<V> as DirectMatch<V>>::Output;

	fn criterium_match(&self, data: &Option<V>) -> Self::Output {
		if let Some(n) = data {
			self.criterium_match(n)
		} else {
			Self::Output::new(matches!(self, Self::IsNone))
		}
	}
}

/// Matcher for comparing a NumberCriterium against a range of possibilities.
///
/// This is intended for integer ranges.
///
/// This tries to always return a known result if possible, but sometimes has to return an unknown if it is not possible to determine a result.
/// It is guaranteed to handle the spcial cases of length one ranges.
///
/// Empty ranges never match.
///
/// The trait bounds `Copy + AsInteger + TryFrom<i64, Error: Debug>` should be replaced with `Step` once that is stable.
impl<V> DirectMatch<RangeInclusive<V>> for NumberCriterium<V>
where
	V: PartialOrd<V> + AsInteger + TryFrom<i64, Error: Debug>,
{
	type Output = Option<bool>;

	fn criterium_match(&self, range: &RangeInclusive<V>) -> Option<bool> {
		if range.is_empty() {
			return Some(false);
		}
		match &self {
			Self::Equals(v) => {
				if range.contains(v) {
					if range.start() == range.end() {
						// Point range with only one possible value
						return Some(true);
					} else {
						return None;
					}
				}
				return Some(false);
			}
			Self::LessThan(v) => {
				if range.end() < v {
					return Some(true);
				} else if range.start() >= v {
					return Some(false);
				} else {
					return None;
				}
			}
			Self::LessThanOrEqual(v) => {
				if range.end() < v {
					return Some(true);
				} else if range.start() > v {
					return Some(false);
				} else {
					return None;
				}
			}
			Self::GreaterThan(v) => {
				if range.start() > v {
					return Some(true);
				} else if range.end() <= v {
					return Some(false);
				} else {
					return None;
				}
			}
			Self::GreaterThanOrEqual(v) => {
				if range.start() > v {
					return Some(true);
				} else if range.end() < v {
					return Some(false);
				} else {
					return None;
				}
			}
			Self::InList(list) => {
				let start = range.start().as_criterium_i64();
				let end = range.end().as_criterium_i64();
				if list.is_empty() {
					return Some(false);
				} else if (end - start).try_into().unwrap_or(usize::MAX) < list.len() {
					let mut had_matches: bool = false;
					let mut had_mismatches: bool = false;
					let mut i = range.start().as_criterium_i64();
					while i <= end {
						if list.contains(&i.try_into().expect("convert back to original type")) {
							had_matches = true
						} else {
							had_mismatches = true;
						}
						if had_mismatches && had_matches {
							break;
						}
						i += 1;
					}
					// No value in the range matched
					if !had_matches {
						return Some(false);
					// All values in the range matched
					} else if !had_mismatches {
						return Some(true);
					// Some but not all values matched
					} else {
						return None;
					}
				} else {
					// range has more possibilities than the list entries
					// => false or unknown
					for v in list {
						if range.contains(v) {
							return None;
						}
					}
					return Some(false);
				}
			}
			Self::IsNone => {
				return Some(false);
			}
		}
	}
}