1use std::cmp::Ordering;
12
13use qubit_value::Value;
14use serde::{Deserialize, Serialize};
15
16use crate::{Metadata, MissingKeyPolicy, NumberComparisonPolicy};
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub enum Condition {
21 Equal {
23 key: String,
25 value: Value,
27 },
28 NotEqual {
30 key: String,
32 value: Value,
34 },
35 Less {
37 key: String,
39 value: Value,
41 },
42 LessEqual {
44 key: String,
46 value: Value,
48 },
49 Greater {
51 key: String,
53 value: Value,
55 },
56 GreaterEqual {
58 key: String,
60 value: Value,
62 },
63 In {
65 key: String,
67 values: Vec<Value>,
69 },
70 NotIn {
72 key: String,
74 values: Vec<Value>,
76 },
77 Exists {
79 key: String,
81 },
82 NotExists {
84 key: String,
86 },
87}
88
89impl Condition {
90 #[inline]
92 pub(crate) fn matches(
93 &self,
94 meta: &Metadata,
95 missing_key_policy: MissingKeyPolicy,
96 number_comparison_policy: NumberComparisonPolicy,
97 ) -> bool {
98 match self {
99 Condition::Equal { key, value } => meta
100 .get_raw(key)
101 .is_some_and(|stored| values_equal(stored, value, number_comparison_policy)),
102 Condition::NotEqual { key, value } => match meta.get_raw(key) {
103 Some(stored) => !values_equal(stored, value, number_comparison_policy),
104 None => missing_key_policy.matches_negative_predicates(),
105 },
106 Condition::Less { key, value } => meta.get_raw(key).is_some_and(|stored| {
107 compare_values(stored, value, number_comparison_policy) == Some(Ordering::Less)
108 }),
109 Condition::LessEqual { key, value } => meta.get_raw(key).is_some_and(|stored| {
110 matches!(
111 compare_values(stored, value, number_comparison_policy),
112 Some(Ordering::Less) | Some(Ordering::Equal)
113 )
114 }),
115 Condition::Greater { key, value } => meta.get_raw(key).is_some_and(|stored| {
116 compare_values(stored, value, number_comparison_policy) == Some(Ordering::Greater)
117 }),
118 Condition::GreaterEqual { key, value } => meta.get_raw(key).is_some_and(|stored| {
119 matches!(
120 compare_values(stored, value, number_comparison_policy),
121 Some(Ordering::Greater) | Some(Ordering::Equal)
122 )
123 }),
124 Condition::In { key, values } => meta.get_raw(key).is_some_and(|stored| {
125 values
126 .iter()
127 .any(|value| values_equal(stored, value, number_comparison_policy))
128 }),
129 Condition::NotIn { key, values } => match meta.get_raw(key) {
130 Some(stored) => values
131 .iter()
132 .all(|value| !values_equal(stored, value, number_comparison_policy)),
133 None => missing_key_policy.matches_negative_predicates(),
134 },
135 Condition::Exists { key } => meta.contains_key(key),
136 Condition::NotExists { key } => !meta.contains_key(key),
137 }
138 }
139}
140
141#[inline]
143fn values_equal(a: &Value, b: &Value, number_comparison_policy: NumberComparisonPolicy) -> bool {
144 if is_numeric_value(a) && is_numeric_value(b) {
145 return compare_numbers(a, b, number_comparison_policy) == Some(Ordering::Equal);
146 }
147 a == b
148}
149
150#[inline]
152fn compare_values(
153 a: &Value,
154 b: &Value,
155 number_comparison_policy: NumberComparisonPolicy,
156) -> Option<Ordering> {
157 if is_numeric_value(a) && is_numeric_value(b) {
158 return compare_numbers(a, b, number_comparison_policy);
159 }
160 match (a, b) {
161 (Value::String(x), Value::String(y)) => x.partial_cmp(y),
162 _ => None,
163 }
164}
165
166#[derive(Debug, Clone, Copy)]
168enum NumberValue {
169 Signed(i128),
171 Unsigned(u128),
173 Float(f64),
175}
176
177#[inline]
179fn is_numeric_value(value: &Value) -> bool {
180 matches!(
181 value,
182 Value::Int8(_)
183 | Value::Int16(_)
184 | Value::Int32(_)
185 | Value::Int64(_)
186 | Value::Int128(_)
187 | Value::UInt8(_)
188 | Value::UInt16(_)
189 | Value::UInt32(_)
190 | Value::UInt64(_)
191 | Value::UInt128(_)
192 | Value::IntSize(_)
193 | Value::UIntSize(_)
194 | Value::Float32(_)
195 | Value::Float64(_)
196 | Value::BigInteger(_)
197 | Value::BigDecimal(_)
198 )
199}
200
201#[inline]
203fn number_value(value: &Value, policy: NumberComparisonPolicy) -> Option<NumberValue> {
204 match value {
205 Value::Int8(v) => Some(NumberValue::Signed(i128::from(*v))),
206 Value::Int16(v) => Some(NumberValue::Signed(i128::from(*v))),
207 Value::Int32(v) => Some(NumberValue::Signed(i128::from(*v))),
208 Value::Int64(v) => Some(NumberValue::Signed(i128::from(*v))),
209 Value::Int128(v) => Some(NumberValue::Signed(*v)),
210 Value::UInt8(v) => Some(NumberValue::Unsigned(u128::from(*v))),
211 Value::UInt16(v) => Some(NumberValue::Unsigned(u128::from(*v))),
212 Value::UInt32(v) => Some(NumberValue::Unsigned(u128::from(*v))),
213 Value::UInt64(v) => Some(NumberValue::Unsigned(u128::from(*v))),
214 Value::UInt128(v) => Some(NumberValue::Unsigned(*v)),
215 Value::IntSize(v) => Some(NumberValue::Signed(*v as i128)),
216 Value::UIntSize(v) => Some(NumberValue::Unsigned(*v as u128)),
217 Value::Float32(v) => Some(NumberValue::Float(f64::from(*v))),
218 Value::Float64(v) => Some(NumberValue::Float(*v)),
219 Value::BigInteger(_) | Value::BigDecimal(_)
220 if matches!(policy, NumberComparisonPolicy::Approximate) =>
221 {
222 value.to::<f64>().ok().map(NumberValue::Float)
223 }
224 _ => None,
225 }
226}
227
228#[inline]
230fn compare_numbers(
231 a: &Value,
232 b: &Value,
233 number_comparison_policy: NumberComparisonPolicy,
234) -> Option<Ordering> {
235 match (
236 number_value(a, number_comparison_policy)?,
237 number_value(b, number_comparison_policy)?,
238 ) {
239 (NumberValue::Signed(x), NumberValue::Signed(y)) => Some(x.cmp(&y)),
240 (NumberValue::Unsigned(x), NumberValue::Unsigned(y)) => Some(x.cmp(&y)),
241 (NumberValue::Signed(x), NumberValue::Unsigned(y)) => Some(compare_i128_u128(x, y)),
242 (NumberValue::Unsigned(x), NumberValue::Signed(y)) => {
243 Some(compare_i128_u128(y, x).reverse())
244 }
245 (NumberValue::Signed(x), NumberValue::Float(y)) => {
246 compare_i128_f64(x, y, number_comparison_policy)
247 }
248 (NumberValue::Float(x), NumberValue::Signed(y)) => {
249 compare_i128_f64(y, x, number_comparison_policy).map(Ordering::reverse)
250 }
251 (NumberValue::Unsigned(x), NumberValue::Float(y)) => {
252 compare_u128_f64(x, y, number_comparison_policy)
253 }
254 (NumberValue::Float(x), NumberValue::Unsigned(y)) => {
255 compare_u128_f64(y, x, number_comparison_policy).map(Ordering::reverse)
256 }
257 (NumberValue::Float(x), NumberValue::Float(y)) => x.partial_cmp(&y),
258 }
259}
260
261const MAX_SAFE_INTEGER_F64_U64: u64 = 9_007_199_254_740_992;
262const I64_MIN_F64: f64 = -9_223_372_036_854_775_808.0;
263const I64_EXCLUSIVE_MAX_F64: f64 = 9_223_372_036_854_775_808.0;
264const U64_EXCLUSIVE_MAX_F64: f64 = 18_446_744_073_709_551_616.0;
265
266#[inline]
268fn compare_i128_u128(x: i128, y: u128) -> Ordering {
269 if x < 0 {
270 Ordering::Less
271 } else {
272 (x as u128).cmp(&y)
273 }
274}
275
276fn compare_i128_f64(
278 x: i128,
279 y: f64,
280 number_comparison_policy: NumberComparisonPolicy,
281) -> Option<Ordering> {
282 if let Ok(x64) = i64::try_from(x) {
283 return compare_i64_f64(x64, y, number_comparison_policy);
284 }
285 if matches!(
286 number_comparison_policy,
287 NumberComparisonPolicy::Approximate
288 ) {
289 return (x as f64).partial_cmp(&y);
290 }
291 None
292}
293
294fn compare_u128_f64(
296 x: u128,
297 y: f64,
298 number_comparison_policy: NumberComparisonPolicy,
299) -> Option<Ordering> {
300 if let Ok(x64) = u64::try_from(x) {
301 return compare_u64_f64(x64, y, number_comparison_policy);
302 }
303 if matches!(
304 number_comparison_policy,
305 NumberComparisonPolicy::Approximate
306 ) {
307 return (x as f64).partial_cmp(&y);
308 }
309 None
310}
311
312fn compare_i64_f64(
314 x: i64,
315 y: f64,
316 number_comparison_policy: NumberComparisonPolicy,
317) -> Option<Ordering> {
318 if y.fract() == 0.0 && (I64_MIN_F64..I64_EXCLUSIVE_MAX_F64).contains(&y) {
319 return Some(x.cmp(&(y as i64)));
320 }
321
322 if x.unsigned_abs() <= MAX_SAFE_INTEGER_F64_U64 {
323 return (x as f64).partial_cmp(&y);
324 }
325
326 if matches!(
327 number_comparison_policy,
328 NumberComparisonPolicy::Approximate
329 ) {
330 return (x as f64).partial_cmp(&y);
331 }
332
333 None
334}
335
336fn compare_u64_f64(
338 x: u64,
339 y: f64,
340 number_comparison_policy: NumberComparisonPolicy,
341) -> Option<Ordering> {
342 if y < 0.0 {
343 return Some(Ordering::Greater);
344 }
345
346 if y.fract() == 0.0 && (0.0..U64_EXCLUSIVE_MAX_F64).contains(&y) {
347 return Some(x.cmp(&(y as u64)));
348 }
349
350 if x <= MAX_SAFE_INTEGER_F64_U64 {
351 return (x as f64).partial_cmp(&y);
352 }
353
354 if matches!(
355 number_comparison_policy,
356 NumberComparisonPolicy::Approximate
357 ) {
358 return (x as f64).partial_cmp(&y);
359 }
360
361 None
362}