1use std::cmp::Ordering;
10
11use hidane_proto::google::firestore::v1::{MapValue, Value, value::ValueType};
12
13pub const MIN_KEY: &str = "__min__";
14pub const MAX_KEY: &str = "__max__";
15pub const OBJECT_ID: &str = "__oid__";
16pub const REGEX: &str = "__regex__";
17pub const INT32: &str = "__int__";
18pub const DECIMAL128: &str = "__decimal128__";
19pub const TIMESTAMP: &str = "__request_timestamp__";
20pub const BINARY: &str = "__binary__";
21
22pub const KEYS: &[&str] = &[
24 MIN_KEY, MAX_KEY, OBJECT_ID, REGEX, INT32, DECIMAL128, TIMESTAMP, BINARY,
25];
26
27#[derive(Debug, Clone, Copy, PartialEq)]
29pub enum Bson<'a> {
30 MinKey,
31 MaxKey,
32 ObjectId(&'a str),
33 Regex {
34 pattern: &'a str,
35 options: &'a str,
36 },
37 Int32(i64),
38 Decimal128(&'a str),
39 Timestamp {
40 seconds: i64,
41 increment: i64,
42 },
43 Binary(&'a [u8]),
45}
46
47fn field<'a>(map: &'a MapValue, name: &str) -> Option<&'a ValueType> {
48 map.fields.get(name).and_then(|v| v.value_type.as_ref())
49}
50
51pub fn bson(map: &MapValue) -> Option<Bson<'_>> {
53 if map.fields.len() != 1 {
54 return None;
55 }
56 let (key, value) = map.fields.iter().next()?;
57 let inner = value.value_type.as_ref();
58 Some(match (key.as_str(), inner) {
59 (MIN_KEY, Some(ValueType::NullValue(_))) => Bson::MinKey,
60 (MAX_KEY, Some(ValueType::NullValue(_))) => Bson::MaxKey,
61 (OBJECT_ID, Some(ValueType::StringValue(s))) => Bson::ObjectId(s),
62 (REGEX, Some(ValueType::MapValue(m))) => match (field(m, "pattern"), field(m, "options")) {
63 (Some(ValueType::StringValue(pattern)), Some(ValueType::StringValue(options))) => {
64 Bson::Regex { pattern, options }
65 }
66 _ => return None,
67 },
68 (INT32, Some(ValueType::IntegerValue(i))) => Bson::Int32(*i),
69 (DECIMAL128, Some(ValueType::StringValue(s))) => Bson::Decimal128(s),
70 (TIMESTAMP, Some(ValueType::MapValue(m))) => {
71 match (field(m, "seconds"), field(m, "increment")) {
72 (
73 Some(ValueType::IntegerValue(seconds)),
74 Some(ValueType::IntegerValue(increment)),
75 ) => Bson::Timestamp {
76 seconds: *seconds,
77 increment: *increment,
78 },
79 _ => return None,
80 }
81 }
82 (BINARY, Some(ValueType::BytesValue(b))) => Bson::Binary(b),
83 _ => return None,
84 })
85}
86
87pub fn of(value: &Value) -> Option<Bson<'_>> {
89 match &value.value_type {
90 Some(ValueType::MapValue(map)) => bson(map),
91 _ => None,
92 }
93}
94
95pub fn normalize(fields: &mut crate::field_path::Fields) {
99 for value in fields.values_mut() {
100 normalize_value(value);
101 }
102}
103
104fn normalize_value(value: &mut Value) {
105 let Some(ValueType::MapValue(map)) = &mut value.value_type else {
106 if let Some(ValueType::ArrayValue(array)) = &mut value.value_type {
107 array.values.iter_mut().for_each(normalize_value);
108 }
109 return;
110 };
111 let kept: Option<&[&str]> = match bson(map) {
112 None => {
113 normalize(&mut map.fields);
114 return;
115 }
116 Some(Bson::Decimal128(text)) => {
117 if let Some(decimal) = Decimal::parse(text) {
118 let canonical = decimal.to_string();
119 if let Some(Value {
120 value_type: Some(ValueType::StringValue(s)),
121 }) = map.fields.get_mut(DECIMAL128)
122 {
123 *s = canonical;
124 }
125 }
126 None
127 }
128 Some(Bson::Regex { .. }) => Some(&["pattern", "options"]),
129 Some(Bson::Timestamp { .. }) => Some(&["seconds", "increment"]),
130 Some(_) => None,
131 };
132 if let Some(kept) = kept
133 && let Some(Value {
134 value_type: Some(ValueType::MapValue(inner)),
135 }) = map.fields.values_mut().next()
136 {
137 let mut fields = crate::field_path::Fields::new();
138 for key in kept {
139 if let Some(v) = inner.fields.get(key) {
140 fields.insert((*key).to_owned(), v.clone());
141 }
142 }
143 inner.fields = fields;
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum Decimal {
151 NaN,
152 Infinity {
153 negative: bool,
154 },
155 Finite {
157 negative: bool,
158 coefficient: u128,
159 exponent: i32,
160 },
161}
162
163const MAX_DIGITS: usize = 34;
164const MAX_EXPONENT: i64 = 6111;
165const MIN_EXPONENT: i64 = -6176;
166
167impl Decimal {
168 pub fn parse(text: &str) -> Option<Self> {
172 match text.to_ascii_lowercase().as_str() {
173 "nan" | "-nan" => return Some(Self::NaN),
174 "inf" | "+inf" | "infinity" | "+infinity" => {
175 return Some(Self::Infinity { negative: false });
176 }
177 "-inf" | "-infinity" => return Some(Self::Infinity { negative: true }),
178 _ => {}
179 }
180 let (negative, rest) = match text.as_bytes().first()? {
181 b'-' => (true, &text[1..]),
182 b'+' => (false, &text[1..]),
183 _ => (false, text),
184 };
185 let (mantissa, exponent) = match rest.find(['e', 'E']) {
186 Some(at) => (&rest[..at], Some(&rest[at + 1..])),
187 None => (rest, None),
188 };
189 let exponent: i64 = match exponent {
190 Some(e) => {
191 let digits = e.strip_prefix(['+', '-']).unwrap_or(e);
192 if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
193 return None;
194 }
195 e.parse::<i32>().ok()?.into()
196 }
197 None => 0,
198 };
199 let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
200 if whole.is_empty() && fraction.is_empty()
201 || !whole
202 .bytes()
203 .chain(fraction.bytes())
204 .all(|b| b.is_ascii_digit())
205 {
206 return None;
207 }
208 let digits: String = format!("{whole}{fraction}");
209 let digits = digits.trim_start_matches('0');
210 let mut exponent = exponent - i64::try_from(fraction.len()).ok()?;
211 let mut digits = digits.to_owned();
212 while digits.len() > MAX_DIGITS {
214 if !digits.ends_with('0') {
215 return None;
216 }
217 digits.pop();
218 exponent += 1;
219 }
220 if digits.is_empty() {
221 exponent = exponent.clamp(MIN_EXPONENT, MAX_EXPONENT);
222 } else if exponent > MAX_EXPONENT {
223 let diff = usize::try_from(exponent - MAX_EXPONENT).ok()?;
224 if diff + digits.len() > MAX_DIGITS {
225 return None;
226 }
227 digits.push_str(&"0".repeat(diff));
228 exponent = MAX_EXPONENT;
229 } else if exponent < MIN_EXPONENT {
230 let diff = usize::try_from(MIN_EXPONENT - exponent).ok()?;
231 if diff > digits.len() || !digits[digits.len() - diff..].bytes().all(|b| b == b'0') {
232 return None;
233 }
234 digits.truncate(digits.len() - diff);
235 exponent = MIN_EXPONENT;
236 }
237 Some(Self::Finite {
238 negative,
239 coefficient: if digits.is_empty() {
240 0
241 } else {
242 digits.parse().ok()?
243 },
244 exponent: i32::try_from(exponent).ok()?,
245 })
246 }
247
248 fn exact(&self) -> Option<Exact> {
250 match *self {
251 Self::Finite {
252 negative,
253 coefficient,
254 exponent,
255 } => Some(Exact::new(
256 negative,
257 &coefficient.to_string(),
258 i64::from(exponent),
259 )),
260 _ => None,
261 }
262 }
263}
264
265impl std::fmt::Display for Decimal {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 let (negative, coefficient, exponent) = match *self {
270 Self::NaN => return f.write_str("NaN"),
271 Self::Infinity { negative } => {
272 return f.write_str(if negative { "-Infinity" } else { "Infinity" });
273 }
274 Self::Finite {
275 negative,
276 coefficient,
277 exponent,
278 } => (negative, coefficient, i64::from(exponent)),
279 };
280 let significand = coefficient.to_string();
281 let length = i64::try_from(significand.len()).unwrap_or(0);
282 let adjusted = exponent + length - 1;
283 if negative {
284 f.write_str("-")?;
285 }
286 if exponent <= 0 && adjusted >= -6 {
287 if exponent == 0 {
288 return f.write_str(&significand);
289 }
290 let pad = -exponent - length;
291 if pad >= 0 {
292 let zeros = "0".repeat(usize::try_from(pad).unwrap_or(0));
293 write!(f, "0.{zeros}{significand}")
294 } else {
295 let point = usize::try_from(-pad).unwrap_or(0);
296 write!(f, "{}.{}", &significand[..point], &significand[point..])
297 }
298 } else {
299 f.write_str(&significand[..1])?;
300 if significand.len() > 1 {
301 write!(f, ".{}", &significand[1..])?;
302 }
303 write!(f, "E{}{adjusted}", if adjusted > 0 { "+" } else { "" })
304 }
305 }
306}
307
308#[derive(Debug, Clone, PartialEq, Eq)]
311struct Exact {
312 negative: bool,
313 digits: Vec<u8>,
314 point: i64,
315}
316
317impl Exact {
318 fn new(negative: bool, digits: &str, exponent: i64) -> Self {
320 let trimmed = digits.trim_start_matches('0');
321 let without_trailing = trimmed.trim_end_matches('0');
322 let point = exponent + i64::try_from(trimmed.len()).unwrap_or(0);
323 Self {
324 negative: negative && !without_trailing.is_empty(),
325 digits: without_trailing.bytes().collect(),
326 point,
327 }
328 }
329
330 fn of_double(d: f64) -> Self {
331 let text = format!("{:.1100e}", d.abs());
333 let (mantissa, exponent) = text.split_once('e').expect("an exponent");
334 let digits: String = mantissa.chars().filter(char::is_ascii_digit).collect();
335 let exponent: i64 = exponent.parse().expect("an exponent");
336 Self::new(
337 d < 0.0,
338 &digits,
339 exponent - i64::try_from(digits.len()).unwrap_or(0) + 1,
340 )
341 }
342
343 fn cmp(&self, other: &Self) -> Ordering {
344 match (self.digits.is_empty(), other.digits.is_empty()) {
345 (true, true) => return Ordering::Equal,
346 (true, false) => {
347 return if other.negative {
348 Ordering::Greater
349 } else {
350 Ordering::Less
351 };
352 }
353 (false, true) => {
354 return if self.negative {
355 Ordering::Less
356 } else {
357 Ordering::Greater
358 };
359 }
360 (false, false) => {}
361 }
362 if self.negative != other.negative {
363 return if self.negative {
364 Ordering::Less
365 } else {
366 Ordering::Greater
367 };
368 }
369 let magnitudes = self
370 .point
371 .cmp(&other.point)
372 .then_with(|| self.digits.cmp(&other.digits));
373 if self.negative {
374 magnitudes.reverse()
375 } else {
376 magnitudes
377 }
378 }
379}
380
381pub fn is_nan(value: &Value) -> bool {
383 match &value.value_type {
384 Some(ValueType::DoubleValue(d)) => d.is_nan(),
385 Some(ValueType::MapValue(map)) => matches!(
386 bson(map),
387 Some(Bson::Decimal128(text)) if matches!(Decimal::parse(text), Some(Decimal::NaN))
388 ),
389 _ => false,
390 }
391}
392
393#[derive(Debug, Clone, Copy)]
395pub enum AnyNumber {
396 Integer(i64),
397 Double(f64),
398 Decimal(Decimal),
399}
400
401impl AnyNumber {
402 pub fn of(value: &Value) -> Option<Self> {
404 match &value.value_type {
405 Some(ValueType::IntegerValue(i)) => Some(Self::Integer(*i)),
406 Some(ValueType::DoubleValue(d)) => Some(Self::Double(*d)),
407 Some(ValueType::MapValue(map)) => match bson(map)? {
408 Bson::Int32(i) => Some(Self::Integer(i)),
409 Bson::Decimal128(text) => Decimal::parse(text).map(Self::Decimal),
410 _ => None,
411 },
412 _ => None,
413 }
414 }
415
416 fn is_nan(self) -> bool {
417 matches!(self, Self::Double(d) if d.is_nan()) || matches!(self, Self::Decimal(Decimal::NaN))
418 }
419
420 fn infinity(self) -> Option<i8> {
422 match self {
423 Self::Double(d) if d.is_infinite() => Some(if d > 0.0 { 1 } else { -1 }),
424 Self::Decimal(Decimal::Infinity { negative }) => Some(if negative { -1 } else { 1 }),
425 _ => None,
426 }
427 }
428
429 fn exact(self) -> Option<Exact> {
430 match self {
431 Self::Integer(i) => Some(Exact::new(i < 0, &i.unsigned_abs().to_string(), 0)),
432 Self::Double(d) if d.is_finite() => Some(Exact::of_double(d)),
433 Self::Decimal(d) => d.exact(),
434 Self::Double(_) => None,
435 }
436 }
437}
438
439pub fn compare_numbers(a: AnyNumber, b: AnyNumber) -> Ordering {
442 match (a.is_nan(), b.is_nan()) {
443 (true, true) => return Ordering::Equal,
444 (true, false) => return Ordering::Less,
445 (false, true) => return Ordering::Greater,
446 (false, false) => {}
447 }
448 match (a.infinity(), b.infinity()) {
449 (Some(x), Some(y)) => return x.cmp(&y),
450 (Some(x), None) => {
451 return if x > 0 {
452 Ordering::Greater
453 } else {
454 Ordering::Less
455 };
456 }
457 (None, Some(y)) => {
458 return if y > 0 {
459 Ordering::Less
460 } else {
461 Ordering::Greater
462 };
463 }
464 (None, None) => {}
465 }
466 match (a, b) {
467 (AnyNumber::Integer(x), AnyNumber::Integer(y)) => x.cmp(&y),
468 (AnyNumber::Double(x), AnyNumber::Double(y)) => {
469 x.partial_cmp(&y).unwrap_or(Ordering::Equal)
470 }
471 _ => match (a.exact(), b.exact()) {
472 (Some(x), Some(y)) => x.cmp(&y),
473 _ => Ordering::Equal,
474 },
475 }
476}
477
478pub fn compare_bytes(a: (u8, &[u8]), b: (u8, &[u8])) -> Ordering {
480 a.0.cmp(&b.0).then_with(|| a.1.cmp(b.1))
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486
487 #[test]
488 fn decimals_print_as_java_does() {
489 for (text, printed) in [
490 ("-1.2e3", "-1.2E+3"),
491 ("4.2e+3", "4.2E+3"),
492 ("-1.2e-3", "-0.0012"),
493 ("1.2e-3", "0.0012"),
494 ("-1", "-1"),
495 ("-0", "-0"),
496 ("0", "0"),
497 ("-0.0", "-0.0"),
498 ("0.0", "0.0"),
499 ("1", "1"),
500 ("NaN", "NaN"),
501 ("-Infinity", "-Infinity"),
502 ("Infinity", "Infinity"),
503 (
504 "0.1234567890123456789012345678901234",
505 "0.1234567890123456789012345678901234",
506 ),
507 (
508 "1234567890123456789012345678901234",
509 "1234567890123456789012345678901234",
510 ),
511 ("1e-7", "1E-7"),
512 ("0.000001", "0.000001"),
513 ("123e-2", "1.23"),
514 ("0e3", "0E+3"),
515 ] {
516 assert_eq!(Decimal::parse(text).unwrap().to_string(), printed, "{text}");
517 }
518 }
519
520 #[test]
521 fn invalid_decimals() {
522 for text in [
523 "",
524 "1 23. 4",
525 "abc",
526 "1e6145",
527 "0.12345678901234567890123456789012345",
528 ".",
529 "1e",
530 "--1",
531 ] {
532 assert_eq!(Decimal::parse(text), None, "{text}");
533 }
534 }
535
536 #[test]
537 fn numbers_compare_exactly() {
538 let dec = |s: &str| AnyNumber::Decimal(Decimal::parse(s).unwrap());
539 assert_eq!(
540 compare_numbers(dec("1"), AnyNumber::Integer(1)),
541 Ordering::Equal
542 );
543 assert_eq!(
544 compare_numbers(dec("1.0"), AnyNumber::Double(1.0)),
545 Ordering::Equal
546 );
547 assert_eq!(
548 compare_numbers(dec("0.1"), AnyNumber::Double(0.1)),
549 Ordering::Less
550 );
551 assert_eq!(
552 compare_numbers(dec("-0"), AnyNumber::Integer(0)),
553 Ordering::Equal
554 );
555 assert_eq!(
556 compare_numbers(dec("9223372036854775808"), AnyNumber::Integer(i64::MAX)),
557 Ordering::Greater
558 );
559 assert_eq!(
560 compare_numbers(dec("NaN"), AnyNumber::Double(f64::NAN)),
561 Ordering::Equal
562 );
563 assert_eq!(
564 compare_numbers(dec("-Infinity"), AnyNumber::Double(f64::NEG_INFINITY)),
565 Ordering::Equal
566 );
567 assert_eq!(
568 compare_numbers(dec("-1200"), AnyNumber::Integer(-5)),
569 Ordering::Less
570 );
571 assert_eq!(
572 compare_numbers(dec("0.5"), AnyNumber::Double(0.5)),
573 Ordering::Equal
574 );
575 }
576}