Skip to main content

hidane_core/
bson.rs

1//! The BSON types of an enterprise database (#338), as the SDKs write them: maps of one reserved
2//! key. `{__min__: null}` and `{__max__: null}` sort before and after every other value;
3//! `{__oid__: "<24 hex digits>"}` is an object ID; `{__regex__: {pattern, options}}` a regular
4//! expression; `{__int__: <integer>}` a 32-bit integer and `{__decimal128__: "<decimal>"}` a
5//! 128-bit decimal, both numbers; `{__request_timestamp__: {seconds, increment}}` a BSON
6//! timestamp; `{__binary__: <subtype byte, then the data>}` bytes with a subtype
7//! (`tests/fixtures/bson_types.json`).
8
9use 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
22/// The keys of the BSON types.
23pub const KEYS: &[&str] = &[
24    MIN_KEY, MAX_KEY, OBJECT_ID, REGEX, INT32, DECIMAL128, TIMESTAMP, BINARY,
25];
26
27/// A BSON value, read from its map.
28#[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    /// The subtype, then the data.
44    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
51/// The BSON value `map` holds: one of the reserved keys alone, with a value of its type.
52pub 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
87/// The BSON value `value` holds, if it is one.
88pub fn of(value: &Value) -> Option<Bson<'_>> {
89    match &value.value_type {
90        Some(ValueType::MapValue(map)) => bson(map),
91        _ => None,
92    }
93}
94
95/// The BSON values in `fields` as an enterprise database stores them: a decimal in its
96/// canonical form ([`Decimal`]'s `Display`), a regular expression's and a BSON timestamp's map
97/// without keys of their own.
98pub 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/// A 128-bit decimal (IEEE 754-2008 decimal128), as Java's `org.bson.types.Decimal128` reads
148/// and prints it.
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum Decimal {
151    NaN,
152    Infinity {
153        negative: bool,
154    },
155    /// `coefficient × 10^exponent`, the coefficient at most 34 digits.
156    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    /// Reads a decimal as `Decimal128.parse` does: `NaN`, `Infinity` (or `Inf`, either signed,
169    /// any case), or a decimal (`-1.2e3`), held exactly in 34 digits once trailing zeros are
170    /// dropped and the exponent clamped; `None` otherwise ("Invalid decimal128 string").
171    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        // Round exactly to 34 digits: only trailing zeros may go.
213        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    /// The decimal as an exact number, or `None` for NaN and the infinities.
249    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    /// `Decimal128.toString()`: plain notation for an exponent at most 0 and an adjusted
267    /// exponent at least -6, else scientific (`-1.2E+3`).
268    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/// A finite number exactly: a sign, its significant digits (no leading or trailing zero; none
309/// for zero), and the power of ten of the place after the first digit (`0.d1d2… × 10^point`).
310#[derive(Debug, Clone, PartialEq, Eq)]
311struct Exact {
312    negative: bool,
313    digits: Vec<u8>,
314    point: i64,
315}
316
317impl Exact {
318    /// `digits × 10^exponent`.
319    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        // Rust prints a double's exact decimal value when asked for enough digits.
332        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
381/// Whether `value` is NaN: a double, or a decimal128 that reads `NaN`.
382pub 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/// A number of any kind, for comparisons.
394#[derive(Debug, Clone, Copy)]
395pub enum AnyNumber {
396    Integer(i64),
397    Double(f64),
398    Decimal(Decimal),
399}
400
401impl AnyNumber {
402    /// The number `value` holds: an integer, a double, a 32-bit integer or a decimal128.
403    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    /// `+1` or `-1` for an infinity.
421    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
439/// Firestore's order of numbers of any kind: NaN first (every NaN equal), then by exact value,
440/// `-0 == 0`.
441pub 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
478/// Bytes with a subtype, plain bytes being subtype 0: by subtype, then the data.
479pub 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}