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