use std::cmp::Ordering;
use qubit_value::Value;
use serde::{Deserialize, Serialize};
use crate::{Metadata, MissingKeyPolicy, NumberComparisonPolicy};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Condition {
Equal {
key: String,
value: Value,
},
NotEqual {
key: String,
value: Value,
},
Less {
key: String,
value: Value,
},
LessEqual {
key: String,
value: Value,
},
Greater {
key: String,
value: Value,
},
GreaterEqual {
key: String,
value: Value,
},
In {
key: String,
values: Vec<Value>,
},
NotIn {
key: String,
values: Vec<Value>,
},
Exists {
key: String,
},
NotExists {
key: String,
},
}
impl Condition {
#[inline]
pub(crate) fn matches(
&self,
meta: &Metadata,
missing_key_policy: MissingKeyPolicy,
number_comparison_policy: NumberComparisonPolicy,
) -> bool {
match self {
Condition::Equal { key, value } => meta
.get_raw(key)
.is_some_and(|stored| values_equal(stored, value, number_comparison_policy)),
Condition::NotEqual { key, value } => match meta.get_raw(key) {
Some(stored) => !values_equal(stored, value, number_comparison_policy),
None => missing_key_policy.matches_negative_predicates(),
},
Condition::Less { key, value } => meta.get_raw(key).is_some_and(|stored| {
compare_values(stored, value, number_comparison_policy) == Some(Ordering::Less)
}),
Condition::LessEqual { key, value } => meta.get_raw(key).is_some_and(|stored| {
matches!(
compare_values(stored, value, number_comparison_policy),
Some(Ordering::Less) | Some(Ordering::Equal)
)
}),
Condition::Greater { key, value } => meta.get_raw(key).is_some_and(|stored| {
compare_values(stored, value, number_comparison_policy) == Some(Ordering::Greater)
}),
Condition::GreaterEqual { key, value } => meta.get_raw(key).is_some_and(|stored| {
matches!(
compare_values(stored, value, number_comparison_policy),
Some(Ordering::Greater) | Some(Ordering::Equal)
)
}),
Condition::In { key, values } => meta.get_raw(key).is_some_and(|stored| {
values
.iter()
.any(|value| values_equal(stored, value, number_comparison_policy))
}),
Condition::NotIn { key, values } => match meta.get_raw(key) {
Some(stored) => values
.iter()
.all(|value| !values_equal(stored, value, number_comparison_policy)),
None => missing_key_policy.matches_negative_predicates(),
},
Condition::Exists { key } => meta.contains_key(key),
Condition::NotExists { key } => !meta.contains_key(key),
}
}
}
#[inline]
fn values_equal(a: &Value, b: &Value, number_comparison_policy: NumberComparisonPolicy) -> bool {
if is_numeric_value(a) && is_numeric_value(b) {
return compare_numbers(a, b, number_comparison_policy) == Some(Ordering::Equal);
}
a == b
}
#[inline]
fn compare_values(
a: &Value,
b: &Value,
number_comparison_policy: NumberComparisonPolicy,
) -> Option<Ordering> {
if is_numeric_value(a) && is_numeric_value(b) {
return compare_numbers(a, b, number_comparison_policy);
}
match (a, b) {
(Value::String(x), Value::String(y)) => x.partial_cmp(y),
_ => None,
}
}
#[derive(Debug, Clone, Copy)]
enum NumberValue {
Signed(i128),
Unsigned(u128),
Float(f64),
}
#[inline]
fn is_numeric_value(value: &Value) -> bool {
matches!(
value,
Value::Int8(_)
| Value::Int16(_)
| Value::Int32(_)
| Value::Int64(_)
| Value::Int128(_)
| Value::UInt8(_)
| Value::UInt16(_)
| Value::UInt32(_)
| Value::UInt64(_)
| Value::UInt128(_)
| Value::IntSize(_)
| Value::UIntSize(_)
| Value::Float32(_)
| Value::Float64(_)
| Value::BigInteger(_)
| Value::BigDecimal(_)
)
}
#[inline]
fn number_value(value: &Value, policy: NumberComparisonPolicy) -> Option<NumberValue> {
match value {
Value::Int8(v) => Some(NumberValue::Signed(i128::from(*v))),
Value::Int16(v) => Some(NumberValue::Signed(i128::from(*v))),
Value::Int32(v) => Some(NumberValue::Signed(i128::from(*v))),
Value::Int64(v) => Some(NumberValue::Signed(i128::from(*v))),
Value::Int128(v) => Some(NumberValue::Signed(*v)),
Value::UInt8(v) => Some(NumberValue::Unsigned(u128::from(*v))),
Value::UInt16(v) => Some(NumberValue::Unsigned(u128::from(*v))),
Value::UInt32(v) => Some(NumberValue::Unsigned(u128::from(*v))),
Value::UInt64(v) => Some(NumberValue::Unsigned(u128::from(*v))),
Value::UInt128(v) => Some(NumberValue::Unsigned(*v)),
Value::IntSize(v) => Some(NumberValue::Signed(*v as i128)),
Value::UIntSize(v) => Some(NumberValue::Unsigned(*v as u128)),
Value::Float32(v) => Some(NumberValue::Float(f64::from(*v))),
Value::Float64(v) => Some(NumberValue::Float(*v)),
Value::BigInteger(_) | Value::BigDecimal(_)
if matches!(policy, NumberComparisonPolicy::Approximate) =>
{
value.to::<f64>().ok().map(NumberValue::Float)
}
_ => None,
}
}
#[inline]
fn compare_numbers(
a: &Value,
b: &Value,
number_comparison_policy: NumberComparisonPolicy,
) -> Option<Ordering> {
match (
number_value(a, number_comparison_policy)?,
number_value(b, number_comparison_policy)?,
) {
(NumberValue::Signed(x), NumberValue::Signed(y)) => Some(x.cmp(&y)),
(NumberValue::Unsigned(x), NumberValue::Unsigned(y)) => Some(x.cmp(&y)),
(NumberValue::Signed(x), NumberValue::Unsigned(y)) => Some(compare_i128_u128(x, y)),
(NumberValue::Unsigned(x), NumberValue::Signed(y)) => {
Some(compare_i128_u128(y, x).reverse())
}
(NumberValue::Signed(x), NumberValue::Float(y)) => {
compare_i128_f64(x, y, number_comparison_policy)
}
(NumberValue::Float(x), NumberValue::Signed(y)) => {
compare_i128_f64(y, x, number_comparison_policy).map(Ordering::reverse)
}
(NumberValue::Unsigned(x), NumberValue::Float(y)) => {
compare_u128_f64(x, y, number_comparison_policy)
}
(NumberValue::Float(x), NumberValue::Unsigned(y)) => {
compare_u128_f64(y, x, number_comparison_policy).map(Ordering::reverse)
}
(NumberValue::Float(x), NumberValue::Float(y)) => x.partial_cmp(&y),
}
}
const MAX_SAFE_INTEGER_F64_U64: u64 = 9_007_199_254_740_992;
const I64_MIN_F64: f64 = -9_223_372_036_854_775_808.0;
const I64_EXCLUSIVE_MAX_F64: f64 = 9_223_372_036_854_775_808.0;
const U64_EXCLUSIVE_MAX_F64: f64 = 18_446_744_073_709_551_616.0;
#[inline]
fn compare_i128_u128(x: i128, y: u128) -> Ordering {
if x < 0 {
Ordering::Less
} else {
(x as u128).cmp(&y)
}
}
fn compare_i128_f64(
x: i128,
y: f64,
number_comparison_policy: NumberComparisonPolicy,
) -> Option<Ordering> {
if let Ok(x64) = i64::try_from(x) {
return compare_i64_f64(x64, y, number_comparison_policy);
}
if matches!(
number_comparison_policy,
NumberComparisonPolicy::Approximate
) {
return (x as f64).partial_cmp(&y);
}
None
}
fn compare_u128_f64(
x: u128,
y: f64,
number_comparison_policy: NumberComparisonPolicy,
) -> Option<Ordering> {
if let Ok(x64) = u64::try_from(x) {
return compare_u64_f64(x64, y, number_comparison_policy);
}
if matches!(
number_comparison_policy,
NumberComparisonPolicy::Approximate
) {
return (x as f64).partial_cmp(&y);
}
None
}
fn compare_i64_f64(
x: i64,
y: f64,
number_comparison_policy: NumberComparisonPolicy,
) -> Option<Ordering> {
if y.fract() == 0.0 && (I64_MIN_F64..I64_EXCLUSIVE_MAX_F64).contains(&y) {
return Some(x.cmp(&(y as i64)));
}
if x.unsigned_abs() <= MAX_SAFE_INTEGER_F64_U64 {
return (x as f64).partial_cmp(&y);
}
if matches!(
number_comparison_policy,
NumberComparisonPolicy::Approximate
) {
return (x as f64).partial_cmp(&y);
}
None
}
fn compare_u64_f64(
x: u64,
y: f64,
number_comparison_policy: NumberComparisonPolicy,
) -> Option<Ordering> {
if y < 0.0 {
return Some(Ordering::Greater);
}
if y.fract() == 0.0 && (0.0..U64_EXCLUSIVE_MAX_F64).contains(&y) {
return Some(x.cmp(&(y as u64)));
}
if x <= MAX_SAFE_INTEGER_F64_U64 {
return (x as f64).partial_cmp(&y);
}
if matches!(
number_comparison_policy,
NumberComparisonPolicy::Approximate
) {
return (x as f64).partial_cmp(&y);
}
None
}