1use std::cmp::Ordering;
13
14use bigdecimal::BigDecimal;
15use num_bigint::BigInt;
16use qubit_value::Value;
17use serde::{Deserialize, Deserializer, Serialize, Serializer};
18
19use super::missing_key_policy::MissingKeyPolicy;
20use super::number_comparison_policy::NumberComparisonPolicy;
21use super::wire::ConditionWire;
22use crate::Metadata;
23
24#[derive(Debug, Clone, PartialEq)]
26pub enum Condition {
27 Equal {
29 key: String,
31 value: Value,
33 },
34 NotEqual {
36 key: String,
38 value: Value,
40 },
41 Less {
43 key: String,
45 value: Value,
47 },
48 LessEqual {
50 key: String,
52 value: Value,
54 },
55 Greater {
57 key: String,
59 value: Value,
61 },
62 GreaterEqual {
64 key: String,
66 value: Value,
68 },
69 In {
71 key: String,
73 values: Vec<Value>,
75 },
76 NotIn {
78 key: String,
80 values: Vec<Value>,
82 },
83 Exists {
85 key: String,
87 },
88 NotExists {
90 key: String,
92 },
93}
94
95impl Serialize for Condition {
96 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
97 where
98 S: Serializer,
99 {
100 ConditionWire::from(self).serialize(serializer)
101 }
102}
103
104impl<'de> Deserialize<'de> for Condition {
105 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106 where
107 D: Deserializer<'de>,
108 {
109 Ok(ConditionWire::deserialize(deserializer)?.into_condition())
110 }
111}
112
113impl Condition {
114 #[inline]
116 pub(crate) fn matches(
117 &self,
118 meta: &Metadata,
119 missing_key_policy: MissingKeyPolicy,
120 number_comparison_policy: NumberComparisonPolicy,
121 ) -> bool {
122 match self {
123 Condition::Equal { key, value } => meta
124 .get_raw(key)
125 .is_some_and(|stored| values_equal(stored, value, number_comparison_policy)),
126 Condition::NotEqual { key, value } => match meta.get_raw(key) {
127 Some(stored) => !values_equal(stored, value, number_comparison_policy),
128 None => missing_key_policy.matches_negative_predicates(),
129 },
130 Condition::Less { key, value } => meta.get_raw(key).is_some_and(|stored| {
131 compare_values(stored, value, number_comparison_policy) == Some(Ordering::Less)
132 }),
133 Condition::LessEqual { key, value } => meta.get_raw(key).is_some_and(|stored| {
134 matches!(
135 compare_values(stored, value, number_comparison_policy),
136 Some(Ordering::Less) | Some(Ordering::Equal)
137 )
138 }),
139 Condition::Greater { key, value } => meta.get_raw(key).is_some_and(|stored| {
140 compare_values(stored, value, number_comparison_policy) == Some(Ordering::Greater)
141 }),
142 Condition::GreaterEqual { key, value } => meta.get_raw(key).is_some_and(|stored| {
143 matches!(
144 compare_values(stored, value, number_comparison_policy),
145 Some(Ordering::Greater) | Some(Ordering::Equal)
146 )
147 }),
148 Condition::In { key, values } => meta.get_raw(key).is_some_and(|stored| {
149 values
150 .iter()
151 .any(|value| values_equal(stored, value, number_comparison_policy))
152 }),
153 Condition::NotIn { key, values } => match meta.get_raw(key) {
154 Some(stored) => values
155 .iter()
156 .all(|value| !values_equal(stored, value, number_comparison_policy)),
157 None => missing_key_policy.matches_negative_predicates(),
158 },
159 Condition::Exists { key } => meta.contains_key(key),
160 Condition::NotExists { key } => !meta.contains_key(key),
161 }
162 }
163}
164
165#[inline]
167fn values_equal(a: &Value, b: &Value, number_comparison_policy: NumberComparisonPolicy) -> bool {
168 if is_numeric_value(a) && is_numeric_value(b) {
169 return compare_numbers(a, b, number_comparison_policy) == Some(Ordering::Equal);
170 }
171 a == b
172}
173
174#[inline]
176fn compare_values(
177 a: &Value,
178 b: &Value,
179 number_comparison_policy: NumberComparisonPolicy,
180) -> Option<Ordering> {
181 if is_numeric_value(a) && is_numeric_value(b) {
182 return compare_numbers(a, b, number_comparison_policy);
183 }
184 match (a, b) {
185 (Value::String(x), Value::String(y)) => x.partial_cmp(y),
186 _ => None,
187 }
188}
189
190#[derive(Debug, Clone, Copy)]
192enum NumberValue {
193 Signed(i128),
195 Unsigned(u128),
197 Float(f64),
199}
200
201#[inline]
203fn is_numeric_value(value: &Value) -> bool {
204 matches!(
205 value,
206 Value::Int8(_)
207 | Value::Int16(_)
208 | Value::Int32(_)
209 | Value::Int64(_)
210 | Value::Int128(_)
211 | Value::UInt8(_)
212 | Value::UInt16(_)
213 | Value::UInt32(_)
214 | Value::UInt64(_)
215 | Value::UInt128(_)
216 | Value::IntSize(_)
217 | Value::UIntSize(_)
218 | Value::Float32(_)
219 | Value::Float64(_)
220 | Value::BigInteger(_)
221 | Value::BigDecimal(_)
222 )
223}
224
225#[inline]
227fn number_value(value: &Value) -> Option<NumberValue> {
228 match value {
229 Value::Int8(v) => Some(NumberValue::Signed(i128::from(*v))),
230 Value::Int16(v) => Some(NumberValue::Signed(i128::from(*v))),
231 Value::Int32(v) => Some(NumberValue::Signed(i128::from(*v))),
232 Value::Int64(v) => Some(NumberValue::Signed(i128::from(*v))),
233 Value::Int128(v) => Some(NumberValue::Signed(*v)),
234 Value::UInt8(v) => Some(NumberValue::Unsigned(u128::from(*v))),
235 Value::UInt16(v) => Some(NumberValue::Unsigned(u128::from(*v))),
236 Value::UInt32(v) => Some(NumberValue::Unsigned(u128::from(*v))),
237 Value::UInt64(v) => Some(NumberValue::Unsigned(u128::from(*v))),
238 Value::UInt128(v) => Some(NumberValue::Unsigned(*v)),
239 Value::IntSize(v) => Some(NumberValue::Signed(*v as i128)),
240 Value::UIntSize(v) => Some(NumberValue::Unsigned(*v as u128)),
241 Value::Float32(v) => Some(NumberValue::Float(f64::from(*v))),
242 Value::Float64(v) => Some(NumberValue::Float(*v)),
243 _ => None,
244 }
245}
246
247#[inline]
249fn compare_numbers(
250 a: &Value,
251 b: &Value,
252 number_comparison_policy: NumberComparisonPolicy,
253) -> Option<Ordering> {
254 if contains_big_number(a, b) {
255 return compare_big_numbers(a, b, number_comparison_policy);
256 }
257 match (number_value(a)?, number_value(b)?) {
258 (NumberValue::Signed(x), NumberValue::Signed(y)) => Some(x.cmp(&y)),
259 (NumberValue::Unsigned(x), NumberValue::Unsigned(y)) => Some(x.cmp(&y)),
260 (NumberValue::Signed(x), NumberValue::Unsigned(y)) => Some(compare_i128_u128(x, y)),
261 (NumberValue::Unsigned(x), NumberValue::Signed(y)) => {
262 Some(compare_i128_u128(y, x).reverse())
263 }
264 (NumberValue::Signed(x), NumberValue::Float(y)) => {
265 compare_i128_f64(x, y, number_comparison_policy)
266 }
267 (NumberValue::Float(x), NumberValue::Signed(y)) => {
268 compare_i128_f64(y, x, number_comparison_policy).map(Ordering::reverse)
269 }
270 (NumberValue::Unsigned(x), NumberValue::Float(y)) => {
271 compare_u128_f64(x, y, number_comparison_policy)
272 }
273 (NumberValue::Float(x), NumberValue::Unsigned(y)) => {
274 compare_u128_f64(y, x, number_comparison_policy).map(Ordering::reverse)
275 }
276 (NumberValue::Float(x), NumberValue::Float(y)) => x.partial_cmp(&y),
277 }
278}
279
280#[inline]
282fn contains_big_number(a: &Value, b: &Value) -> bool {
283 matches!(a, Value::BigInteger(_) | Value::BigDecimal(_))
284 || matches!(b, Value::BigInteger(_) | Value::BigDecimal(_))
285}
286
287fn compare_big_numbers(
289 a: &Value,
290 b: &Value,
291 number_comparison_policy: NumberComparisonPolicy,
292) -> Option<Ordering> {
293 if let (Some(x), Some(y)) = (big_integer_value(a), big_integer_value(b)) {
294 return Some(x.cmp(&y));
295 }
296 if let (Some(x), Some(y)) = (big_decimal_value(a), big_decimal_value(b)) {
297 return Some(x.cmp(&y));
298 }
299 if matches!(
300 number_comparison_policy,
301 NumberComparisonPolicy::Approximate
302 ) {
303 return compare_as_f64(a, b);
304 }
305 None
306}
307
308fn big_integer_value(value: &Value) -> Option<BigInt> {
310 match value {
311 Value::Int8(v) => Some(BigInt::from(*v)),
312 Value::Int16(v) => Some(BigInt::from(*v)),
313 Value::Int32(v) => Some(BigInt::from(*v)),
314 Value::Int64(v) => Some(BigInt::from(*v)),
315 Value::Int128(v) => Some(BigInt::from(*v)),
316 Value::UInt8(v) => Some(BigInt::from(*v)),
317 Value::UInt16(v) => Some(BigInt::from(*v)),
318 Value::UInt32(v) => Some(BigInt::from(*v)),
319 Value::UInt64(v) => Some(BigInt::from(*v)),
320 Value::UInt128(v) => Some(BigInt::from(*v)),
321 Value::IntSize(v) => Some(BigInt::from(*v)),
322 Value::UIntSize(v) => Some(BigInt::from(*v)),
323 Value::BigInteger(v) => Some(v.clone()),
324 _ => None,
325 }
326}
327
328fn big_decimal_value(value: &Value) -> Option<BigDecimal> {
330 match value {
331 Value::BigDecimal(v) => Some(v.clone()),
332 _ => big_integer_value(value).map(BigDecimal::from),
333 }
334}
335
336#[inline]
338fn compare_as_f64(a: &Value, b: &Value) -> Option<Ordering> {
339 a.to::<f64>().ok()?.partial_cmp(&b.to::<f64>().ok()?)
340}
341
342const MAX_SAFE_INTEGER_F64_U64: u64 = 9_007_199_254_740_992;
343const I64_MIN_F64: f64 = -9_223_372_036_854_775_808.0;
344const I64_EXCLUSIVE_MAX_F64: f64 = 9_223_372_036_854_775_808.0;
345const U64_EXCLUSIVE_MAX_F64: f64 = 18_446_744_073_709_551_616.0;
346
347#[inline]
349fn compare_i128_u128(x: i128, y: u128) -> Ordering {
350 if x < 0 {
351 Ordering::Less
352 } else {
353 (x as u128).cmp(&y)
354 }
355}
356
357fn compare_i128_f64(
359 x: i128,
360 y: f64,
361 number_comparison_policy: NumberComparisonPolicy,
362) -> Option<Ordering> {
363 if let Ok(x64) = i64::try_from(x) {
364 return compare_i64_f64(x64, y, number_comparison_policy);
365 }
366 if matches!(
367 number_comparison_policy,
368 NumberComparisonPolicy::Approximate
369 ) {
370 return (x as f64).partial_cmp(&y);
371 }
372 None
373}
374
375fn compare_u128_f64(
377 x: u128,
378 y: f64,
379 number_comparison_policy: NumberComparisonPolicy,
380) -> Option<Ordering> {
381 if let Ok(x64) = u64::try_from(x) {
382 return compare_u64_f64(x64, y, number_comparison_policy);
383 }
384 if matches!(
385 number_comparison_policy,
386 NumberComparisonPolicy::Approximate
387 ) {
388 return (x as f64).partial_cmp(&y);
389 }
390 None
391}
392
393fn compare_i64_f64(
395 x: i64,
396 y: f64,
397 number_comparison_policy: NumberComparisonPolicy,
398) -> Option<Ordering> {
399 if y.fract() == 0.0 && (I64_MIN_F64..I64_EXCLUSIVE_MAX_F64).contains(&y) {
400 return Some(x.cmp(&(y as i64)));
401 }
402
403 if x.unsigned_abs() <= MAX_SAFE_INTEGER_F64_U64 {
404 return (x as f64).partial_cmp(&y);
405 }
406
407 if matches!(
408 number_comparison_policy,
409 NumberComparisonPolicy::Approximate
410 ) {
411 return (x as f64).partial_cmp(&y);
412 }
413
414 None
415}
416
417fn compare_u64_f64(
419 x: u64,
420 y: f64,
421 number_comparison_policy: NumberComparisonPolicy,
422) -> Option<Ordering> {
423 if y < 0.0 {
424 return Some(Ordering::Greater);
425 }
426
427 if y.fract() == 0.0 && (0.0..U64_EXCLUSIVE_MAX_F64).contains(&y) {
428 return Some(x.cmp(&(y as u64)));
429 }
430
431 if x <= MAX_SAFE_INTEGER_F64_U64 {
432 return (x as f64).partial_cmp(&y);
433 }
434
435 if matches!(
436 number_comparison_policy,
437 NumberComparisonPolicy::Approximate
438 ) {
439 return (x as f64).partial_cmp(&y);
440 }
441
442 None
443}