Skip to main content

qubit_metadata/
condition.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! A single comparison predicate against one metadata key.
10
11use serde::{
12    Deserialize,
13    Serialize,
14};
15use std::cmp::Ordering;
16
17use serde_json::{
18    Number,
19    Value,
20};
21
22use crate::Metadata;
23
24/// A single comparison operator applied to one metadata key.
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub enum Condition {
27    /// Key equals value.
28    Equal {
29        /// The metadata key.
30        key: String,
31        /// The expected value.
32        value: Value,
33    },
34    /// Key does not equal value.
35    NotEqual {
36        /// The metadata key.
37        key: String,
38        /// The value to compare against.
39        value: Value,
40    },
41    /// Key is less than value.
42    Less {
43        /// The metadata key.
44        key: String,
45        /// The upper bound (exclusive).
46        value: Value,
47    },
48    /// Key is less than or equal to value.
49    LessEqual {
50        /// The metadata key.
51        key: String,
52        /// The upper bound (inclusive).
53        value: Value,
54    },
55    /// Key is greater than value (numeric / string comparison).
56    Greater {
57        /// The metadata key.
58        key: String,
59        /// The lower bound (exclusive).
60        value: Value,
61    },
62    /// Key is greater than or equal to value.
63    GreaterEqual {
64        /// The metadata key.
65        key: String,
66        /// The lower bound (inclusive).
67        value: Value,
68    },
69    /// The stored value is one of the listed candidates.
70    In {
71        /// The metadata key.
72        key: String,
73        /// The set of acceptable values.
74        values: Vec<Value>,
75    },
76    /// The stored value is not any of the listed candidates.
77    NotIn {
78        /// The metadata key.
79        key: String,
80        /// The set of excluded values.
81        values: Vec<Value>,
82    },
83    /// Key exists in the metadata (regardless of its value).
84    Exists {
85        /// The metadata key.
86        key: String,
87    },
88    /// Key does not exist in the metadata.
89    NotExists {
90        /// The metadata key.
91        key: String,
92    },
93}
94
95impl Condition {
96    #[inline]
97    pub(crate) fn matches(&self, meta: &Metadata) -> bool {
98        match self {
99            Condition::Equal { key, value } => meta.get_raw(key) == Some(value),
100            Condition::NotEqual { key, value } => meta.get_raw(key) != Some(value),
101            Condition::Less { key, value } => meta
102                .get_raw(key)
103                .is_some_and(|v| compare_values(v, value) == Some(Ordering::Less)),
104            Condition::LessEqual { key, value } => meta.get_raw(key).is_some_and(|v| {
105                matches!(
106                    compare_values(v, value),
107                    Some(Ordering::Less) | Some(Ordering::Equal)
108                )
109            }),
110            Condition::Greater { key, value } => meta
111                .get_raw(key)
112                .is_some_and(|v| compare_values(v, value) == Some(Ordering::Greater)),
113            Condition::GreaterEqual { key, value } => meta.get_raw(key).is_some_and(|v| {
114                matches!(
115                    compare_values(v, value),
116                    Some(Ordering::Greater) | Some(Ordering::Equal)
117                )
118            }),
119            Condition::In { key, values } => meta.get_raw(key).is_some_and(|v| values.contains(v)),
120            Condition::NotIn { key, values } => {
121                meta.get_raw(key).map_or(true, |v| !values.contains(v))
122            }
123            Condition::Exists { key } => meta.contains_key(key),
124            Condition::NotExists { key } => !meta.contains_key(key),
125        }
126    }
127}
128
129/// Compares two [`Value`]s where both are the same numeric or string variant.
130/// Returns `None` when the values are incomparable (different types).
131#[inline]
132fn compare_values(a: &Value, b: &Value) -> Option<Ordering> {
133    match (a, b) {
134        (Value::Number(x), Value::Number(y)) => compare_numbers(x, y),
135        (Value::String(x), Value::String(y)) => x.partial_cmp(y),
136        _ => None,
137    }
138}
139
140const MAX_SAFE_INTEGER_F64_U64: u64 = 9_007_199_254_740_992; // 2^53
141const I64_MIN_F64: f64 = -9_223_372_036_854_775_808.0; // -2^63
142const I64_EXCLUSIVE_MAX_F64: f64 = 9_223_372_036_854_775_808.0; // 2^63
143const U64_EXCLUSIVE_MAX_F64: f64 = 18_446_744_073_709_551_616.0; // 2^64
144
145fn compare_numbers(a: &Number, b: &Number) -> Option<Ordering> {
146    if let (Some(xi), Some(yi)) = (a.as_i64(), b.as_i64()) {
147        return Some(xi.cmp(&yi));
148    }
149    if let (Some(xi), Some(yu)) = (a.as_i64(), b.as_u64()) {
150        return Some(compare_i64_u64(xi, yu));
151    }
152    if let (Some(xu), Some(yi)) = (a.as_u64(), b.as_i64()) {
153        return Some(compare_i64_u64(yi, xu).reverse());
154    }
155    if let (Some(xu), Some(yu)) = (a.as_u64(), b.as_u64()) {
156        return Some(xu.cmp(&yu));
157    }
158    if let (Some(xi), Some(yf)) = (a.as_i64(), b.as_f64()) {
159        return compare_i64_f64(xi, yf);
160    }
161    if let (Some(xf), Some(yi)) = (a.as_f64(), b.as_i64()) {
162        return compare_i64_f64(yi, xf).map(Ordering::reverse);
163    }
164    if let (Some(xu), Some(yf)) = (a.as_u64(), b.as_f64()) {
165        return compare_u64_f64(xu, yf);
166    }
167    if let (Some(xf), Some(yu)) = (a.as_f64(), b.as_u64()) {
168        return compare_u64_f64(yu, xf).map(Ordering::reverse);
169    }
170    if let (Some(xf), Some(yf)) = (a.as_f64(), b.as_f64()) {
171        return xf.partial_cmp(&yf);
172    }
173
174    // serde_json::Number always represents one of i64/u64/f64.
175    unreachable!("Number must be representable as i64/u64/f64")
176}
177
178#[inline]
179fn compare_i64_u64(x: i64, y: u64) -> Ordering {
180    if x < 0 {
181        Ordering::Less
182    } else {
183        (x as u64).cmp(&y)
184    }
185}
186
187#[inline]
188fn compare_i64_f64(x: i64, y: f64) -> Option<Ordering> {
189    if y.fract() == 0.0 && (I64_MIN_F64..I64_EXCLUSIVE_MAX_F64).contains(&y) {
190        // Integer-vs-integer path avoids precision loss for values > 2^53.
191        return Some(x.cmp(&(y as i64)));
192    }
193
194    if x.unsigned_abs() <= MAX_SAFE_INTEGER_F64_U64 {
195        return (x as f64).partial_cmp(&y);
196    }
197
198    None
199}
200
201#[inline]
202fn compare_u64_f64(x: u64, y: f64) -> Option<Ordering> {
203    if y < 0.0 {
204        return Some(Ordering::Greater);
205    }
206
207    if y.fract() == 0.0 && (0.0..U64_EXCLUSIVE_MAX_F64).contains(&y) {
208        // Integer-vs-integer path avoids precision loss for values > 2^53.
209        return Some(x.cmp(&(y as u64)));
210    }
211
212    None
213}