1use std::future::Future;
41use std::sync::Arc;
42use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
43
44use base64::Engine as _;
45use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, SecondsFormat, TimeZone};
46use serde_json::{Number, Value};
47
48pub const MAX_SAFE_INTEGER: u64 = (1 << 53) - 1;
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55#[non_exhaustive]
56pub enum TimePrecision {
57 Seconds,
59 Millis,
61 Micros,
63 Nanos,
65}
66
67impl TimePrecision {
68 pub fn from_digits(digits: u32) -> Self {
71 match digits {
72 0 => Self::Seconds,
73 1..=3 => Self::Millis,
74 4..=6 => Self::Micros,
75 _ => Self::Nanos,
76 }
77 }
78
79 fn seconds_format(self) -> SecondsFormat {
80 match self {
81 Self::Seconds => SecondsFormat::Secs,
82 Self::Millis => SecondsFormat::Millis,
83 Self::Micros => SecondsFormat::Micros,
84 Self::Nanos => SecondsFormat::Nanos,
85 }
86 }
87
88 fn fraction(self, nanos: u32) -> String {
89 match self {
90 Self::Seconds => String::new(),
91 Self::Millis => format!(".{:03}", nanos / 1_000_000),
92 Self::Micros => format!(".{:06}", nanos / 1_000),
93 Self::Nanos => format!(".{nanos:09}"),
94 }
95 }
96
97 fn per_second(self) -> i64 {
98 match self {
99 Self::Seconds => 1,
100 Self::Millis => 1_000,
101 Self::Micros => 1_000_000,
102 Self::Nanos => 1_000_000_000,
103 }
104 }
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
109#[non_exhaustive]
110pub enum FidelityError {
111 #[error("{0:?} is not a decimal number")]
113 NotANumber(String),
114 #[error("{0:?} is not an integer")]
116 NotAnInteger(String),
117 #[error("epoch value {0} is outside the representable date-time range")]
119 OutOfRange(i64),
120}
121
122fn parse_decimal(text: &str) -> Option<(bool, String, i64)> {
125 let t = text.trim();
126 let (neg, rest) = match t.as_bytes().first()? {
127 b'-' => (true, &t[1..]),
128 b'+' => (false, &t[1..]),
129 _ => (false, t),
130 };
131 let (mantissa, exp) = match rest.find(['e', 'E']) {
132 Some(i) => (&rest[..i], rest[i + 1..].parse::<i64>().ok()?),
133 None => (rest, 0),
134 };
135 let (int, frac) = mantissa.split_once('.').unwrap_or((mantissa, ""));
136 if (int.is_empty() && frac.is_empty())
137 || !int.bytes().chain(frac.bytes()).all(|b| b.is_ascii_digit())
138 {
139 return None;
140 }
141 let digits: String = format!("{int}{frac}");
142 let digits = digits.trim_start_matches('0').to_string();
143 let scale = (frac.len() as i64).checked_sub(exp)?;
144 Some((neg, digits, scale))
145}
146
147fn render_decimal(negative: bool, digits: &str, scale: i64) -> String {
149 let mut out = String::new();
150 if scale <= 0 {
151 if digits.is_empty() {
152 return "0".to_string();
153 }
154 if negative {
155 out.push('-');
156 }
157 out.push_str(digits);
158 out.extend(std::iter::repeat_n('0', (-scale) as usize));
159 return out;
160 }
161 let scale = scale as usize;
162 let padded = if digits.len() <= scale {
163 format!("{}{digits}", "0".repeat(scale + 1 - digits.len()))
164 } else {
165 digits.to_string()
166 };
167 let (int, frac) = padded.split_at(padded.len() - scale);
168 if negative && !digits.is_empty() {
169 out.push('-');
170 }
171 out.push_str(int);
172 out.push('.');
173 out.push_str(frac);
174 out
175}
176
177fn is_non_finite_word(text: &str) -> bool {
178 let t = text.trim().trim_start_matches(['+', '-']);
179 t.eq_ignore_ascii_case("nan")
180 || t.eq_ignore_ascii_case("inf")
181 || t.eq_ignore_ascii_case("infinity")
182}
183
184pub fn decimal_text(text: &str) -> Result<Value, FidelityError> {
188 if is_non_finite_word(text) {
189 record_non_finite();
190 return Ok(Value::Null);
191 }
192 let (neg, digits, scale) =
193 parse_decimal(text).ok_or_else(|| FidelityError::NotANumber(text.to_string()))?;
194 Ok(Value::String(render_decimal(neg, &digits, scale)))
195}
196
197pub fn decimal_from_i128(unscaled: i128, scale: i32) -> Value {
200 let digits = unscaled.unsigned_abs().to_string();
201 let digits = digits.trim_start_matches('0');
202 Value::String(render_decimal(unscaled < 0, digits, i64::from(scale)))
203}
204
205pub fn integer_i128(v: i128) -> Value {
208 if let Ok(i) = i64::try_from(v) {
209 Value::Number(i.into())
210 } else if let Ok(u) = u64::try_from(v) {
211 Value::Number(u.into())
212 } else {
213 Value::String(v.to_string())
214 }
215}
216
217pub fn integer_u128(v: u128) -> Value {
219 match u64::try_from(v) {
220 Ok(u) => Value::Number(u.into()),
221 Err(_) => Value::String(v.to_string()),
222 }
223}
224
225pub fn integer_text(text: &str) -> Result<Value, FidelityError> {
227 let t = text.trim();
228 let body = t.strip_prefix(['-', '+']).unwrap_or(t);
229 if body.is_empty() || !body.bytes().all(|b| b.is_ascii_digit()) {
230 return Err(FidelityError::NotAnInteger(text.to_string()));
231 }
232 if let Ok(i) = t.parse::<i64>() {
233 return Ok(Value::Number(i.into()));
234 }
235 if let Ok(u) = t.trim_start_matches('+').parse::<u64>() {
236 return Ok(Value::Number(u.into()));
237 }
238 let digits = body.trim_start_matches('0');
239 let neg = t.starts_with('-') && !digits.is_empty();
240 Ok(Value::String(format!(
241 "{}{}",
242 if neg { "-" } else { "" },
243 if digits.is_empty() { "0" } else { digits }
244 )))
245}
246
247pub fn number_text(text: &str) -> Result<Value, FidelityError> {
251 let t = text.trim();
252 if is_non_finite_word(t) {
253 record_non_finite();
254 return Ok(Value::Null);
255 }
256 if !t.contains(['.', 'e', 'E'])
257 && let Ok(v) = integer_text(t)
258 {
259 return Ok(v);
260 }
261 let (neg, digits, scale) =
262 parse_decimal(t).ok_or_else(|| FidelityError::NotANumber(text.to_string()))?;
263 if digits.is_empty() {
264 return Ok(Value::Number(0.into()));
265 }
266 let positional = render_decimal(neg, &digits, scale);
267 for candidate in [t.trim_start_matches('+'), positional.as_str()] {
268 if crate::json_numbers::is_exact(candidate)
269 && let Ok(Value::Number(n)) = serde_json::from_str::<Value>(candidate)
270 {
271 return Ok(Value::Number(n));
272 }
273 }
274 if scale <= 0 && digits.len() as i64 - scale <= 39 {
275 return integer_text(&positional);
276 }
277 Ok(Value::String(positional))
278}
279
280pub fn float64(v: f64) -> Value {
282 match Number::from_f64(v) {
283 Some(n) => Value::Number(n),
284 None => {
285 record_non_finite();
286 Value::Null
287 }
288 }
289}
290
291pub fn float32(v: f32) -> Value {
294 if !v.is_finite() {
295 record_non_finite();
296 return Value::Null;
297 }
298 v.to_string()
299 .parse::<f64>()
300 .map_or_else(|_| float64(f64::from(v)), float64)
301}
302
303pub fn non_integer_text(n: &Number) -> Option<String> {
308 n.is_f64().then(|| n.to_string())
309}
310
311pub fn decimal_from_pg_numeric(bytes: &[u8]) -> Option<Value> {
316 let word = |i: usize| -> Option<u16> {
317 Some(u16::from_be_bytes([*bytes.get(i)?, *bytes.get(i + 1)?]))
318 };
319 let ndigits = usize::from(word(0)?);
320 let weight = i64::from(word(2)? as i16);
321 let sign = word(4)?;
322 let dscale = usize::from(word(6)?);
323 if matches!(sign, 0xC000 | 0xD000 | 0xF000) {
324 record_non_finite();
325 return Some(Value::Null);
326 }
327 if !matches!(sign, 0x0000 | 0x4000) || bytes.len() != 8 + 2 * ndigits {
328 return None;
329 }
330 let digits: Vec<u16> = (0..ndigits)
331 .map(|i| word(8 + 2 * i))
332 .collect::<Option<_>>()?;
333 if digits.iter().any(|d| *d > 9999) {
334 return None;
335 }
336 let group = |g: i64| -> u16 {
337 usize::try_from(g)
338 .ok()
339 .and_then(|i| digits.get(i).copied())
340 .unwrap_or(0)
341 };
342 let mut text = String::new();
343 if sign == 0x4000 {
344 text.push('-');
345 }
346 if weight < 0 {
347 text.push('0');
348 } else {
349 text.push_str(&group(0).to_string());
350 for g in 1..=weight {
351 text.push_str(&format!("{:04}", group(g)));
352 }
353 }
354 if dscale > 0 {
355 let mut frac = String::new();
356 let mut g = weight + 1;
357 while frac.len() < dscale {
358 frac.push_str(&format!("{:04}", group(g)));
359 g += 1;
360 }
361 frac.truncate(dscale);
362 text.push('.');
363 text.push_str(&frac);
364 }
365 decimal_text(&text).ok()
366}
367
368pub fn is_exact_decimal_type(sql_type: &str) -> bool {
372 let t = sql_type.trim().to_ascii_lowercase();
373 [
374 "numeric",
375 "decimal",
376 "dec",
377 "money",
378 "smallmoney",
379 "number",
380 "bignumeric",
381 "bigdecimal",
382 ]
383 .iter()
384 .any(|p| t == *p || t.starts_with(&format!("{p}(")) || t.starts_with(&format!("{p} ")))
385}
386
387pub fn binary(bytes: &[u8]) -> Value {
389 Value::String(base64::engine::general_purpose::STANDARD.encode(bytes))
390}
391
392pub fn timestamp<Tz: TimeZone>(dt: &DateTime<Tz>, precision: TimePrecision) -> Value {
394 Value::String(
395 dt.fixed_offset()
396 .to_rfc3339_opts(precision.seconds_format(), true),
397 )
398}
399
400pub fn timestamp_naive(dt: &NaiveDateTime, precision: TimePrecision) -> Value {
403 Value::String(format!(
404 "{}{}",
405 dt.format("%Y-%m-%dT%H:%M:%S"),
406 precision.fraction(dt.and_utc().timestamp_subsec_nanos() % 1_000_000_000)
407 ))
408}
409
410pub fn date(d: &NaiveDate) -> Value {
412 Value::String(d.format("%Y-%m-%d").to_string())
413}
414
415pub fn time(t: &NaiveTime, precision: TimePrecision) -> Value {
417 use chrono::Timelike;
418 Value::String(format!(
419 "{}{}",
420 t.format("%H:%M:%S"),
421 precision.fraction(t.nanosecond() % 1_000_000_000)
422 ))
423}
424
425pub fn timestamp_from_epoch(
428 value: i64,
429 unit: TimePrecision,
430 zone: Option<FixedOffset>,
431) -> Result<Value, FidelityError> {
432 let per = unit.per_second();
433 let secs = value.div_euclid(per);
434 let nanos = (value.rem_euclid(per) * (1_000_000_000 / per)) as u32;
435 let utc = DateTime::from_timestamp(secs, nanos).ok_or(FidelityError::OutOfRange(value))?;
436 Ok(match zone {
437 Some(z) => timestamp(&utc.with_timezone(&z), unit),
438 None => timestamp_naive(&utc.naive_utc(), unit),
439 })
440}
441
442pub fn from_str(text: &str) -> Result<Value, serde_json::Error> {
449 let value: Value = serde_json::from_str(text)?;
450 let lossy = crate::file_format::json::unfaithful_literals(text.as_bytes());
451 if lossy.is_empty() {
452 return Ok(value);
453 }
454 let mut rewritten = String::with_capacity(text.len() + 2 * lossy.len());
455 let mut last = 0;
456 for r in &lossy {
457 rewritten.push_str(&text[last..r.start]);
458 rewritten.push('"');
459 rewritten.push_str(&text[r.clone()]);
460 rewritten.push('"');
461 last = r.end;
462 }
463 rewritten.push_str(&text[last..]);
464 serde_json::from_str(&rewritten)
465}
466
467pub fn from_slice(bytes: &[u8]) -> Result<Value, serde_json::Error> {
469 match std::str::from_utf8(bytes) {
470 Ok(text) => from_str(text),
471 Err(_) => serde_json::from_slice(bytes),
472 }
473}
474
475pub fn parse_json(text: &[u8]) -> Result<Value, FidelityError> {
480 let text = std::str::from_utf8(text)
481 .map_err(|e| FidelityError::NotANumber(format!("invalid UTF-8 in JSON: {e}")))?;
482 crate::json_numbers::parse_json_exact(text, crate::json_numbers::JsonBigNumbers::String)
483 .map(|(v, _)| v)
484 .map_err(|e| FidelityError::NotANumber(e.to_string()))
485}
486
487#[derive(Debug, Clone, Default)]
495pub struct NonFiniteCounter(Arc<AtomicU64>);
496
497tokio::task_local! {
498 static NON_FINITE: NonFiniteCounter;
499}
500
501static UNSCOPED: AtomicU64 = AtomicU64::new(0);
502static WARNED: AtomicBool = AtomicBool::new(false);
503
504impl NonFiniteCounter {
505 pub fn new() -> Self {
507 Self::default()
508 }
509
510 pub fn count(&self) -> u64 {
512 self.0.load(Ordering::Relaxed)
513 }
514
515 pub fn current() -> Option<Self> {
517 NON_FINITE.try_with(Clone::clone).ok()
518 }
519
520 pub fn scope<F: Future>(&self, fut: F) -> impl Future<Output = F::Output> {
522 NON_FINITE.scope(self.clone(), fut)
523 }
524
525 pub fn scope_sync<R>(&self, f: impl FnOnce() -> R) -> R {
527 NON_FINITE.sync_scope(self.clone(), f)
528 }
529}
530
531pub fn propagate<R>(counter: &Option<NonFiniteCounter>, f: impl FnOnce() -> R) -> R {
534 match counter {
535 Some(c) => c.scope_sync(f),
536 None => f(),
537 }
538}
539
540pub fn record_non_finite() {
544 if NON_FINITE
545 .try_with(|c| c.0.fetch_add(1, Ordering::Relaxed))
546 .is_err()
547 {
548 UNSCOPED.fetch_add(1, Ordering::Relaxed);
549 metrics::counter!("faucet_fidelity_non_finite_total", "pipeline" => "", "row" => "")
550 .increment(1);
551 }
552 if !WARNED.swap(true, Ordering::Relaxed) {
553 tracing::warn!(
554 "a NaN or infinite value has no JSON representation and was written as null; \
555 the run summary counts them as `non_finite`"
556 );
557 }
558}
559
560pub fn unscoped_non_finite() -> u64 {
562 UNSCOPED.load(Ordering::Relaxed)
563}
564
565#[cfg(test)]
566mod tests {
567 use super::*;
568 use serde_json::json;
569
570 #[test]
571 fn decimals_render_as_exact_strings_at_their_scale() {
572 for (input, out) in [
573 ("1.50", "1.50"),
574 ("-1.50", "-1.50"),
575 ("+7", "7"),
576 ("0007.10", "7.10"),
577 ("1.2E+3", "1200"),
578 ("1.25e-3", "0.00125"),
579 (".5", "0.5"),
580 ("5.", "5"),
581 ("-0.00", "0.00"),
582 ("0", "0"),
583 ("-0", "0"),
584 (
585 "12345678901234567890.123456789",
586 "12345678901234567890.123456789",
587 ),
588 ("1e2", "100"),
589 ] {
590 assert_eq!(decimal_text(input).unwrap(), json!(out), "{input}");
591 }
592 for bad in ["", "-", "1.2.3", "abc", "1e", "1e9999999999999999999", "."] {
593 assert!(decimal_text(bad).is_err(), "{bad}");
594 }
595 assert_eq!(
596 decimal_text("x").unwrap_err().to_string(),
597 "\"x\" is not a decimal number"
598 );
599 }
600
601 #[test]
602 fn decimals_from_unscaled_parts() {
603 assert_eq!(decimal_from_i128(12345, 2), json!("123.45"));
604 assert_eq!(decimal_from_i128(-5, 3), json!("-0.005"));
605 assert_eq!(decimal_from_i128(0, 2), json!("0.00"));
606 assert_eq!(decimal_from_i128(42, 0), json!("42"));
607 assert_eq!(decimal_from_i128(42, -2), json!("4200"));
608 assert_eq!(decimal_from_i128(0, -2), json!("0"));
609 assert_eq!(
610 decimal_from_i128(i128::MIN, 10),
611 json!("-17014118346046923173168730371.5884105728")
612 );
613 }
614
615 #[test]
616 fn integers_are_numbers_until_a_value_cannot_hold_them() {
617 assert_eq!(integer_i128(i128::from(i64::MIN)), json!(i64::MIN));
618 assert_eq!(integer_i128(i128::from(u64::MAX)), json!(u64::MAX));
619 assert_eq!(
620 integer_i128(i128::from(u64::MAX) + 1),
621 json!("18446744073709551616")
622 );
623 assert_eq!(
624 integer_i128(i128::from(i64::MIN) - 1),
625 json!("-9223372036854775809")
626 );
627 assert_eq!(integer_u128(7), json!(7));
628 assert_eq!(integer_u128(u128::MAX), json!(u128::MAX.to_string()));
629 assert_eq!(integer_text(" 42 ").unwrap(), json!(42));
630 assert_eq!(integer_text("+42").unwrap(), json!(42));
631 assert_eq!(
632 integer_text("-000123456789012345678901").unwrap(),
633 json!("-123456789012345678901")
634 );
635 assert_eq!(integer_text("-0000000000000000000000").unwrap(), json!(0));
636 assert_eq!(
637 integer_text("18446744073709551615").unwrap(),
638 json!(u64::MAX)
639 );
640 assert!(integer_text("1.5").is_err());
641 assert!(integer_text("-").is_err());
642 assert_eq!(
643 integer_text("x").unwrap_err().to_string(),
644 "\"x\" is not an integer"
645 );
646 const { assert!(MAX_SAFE_INTEGER == 9_007_199_254_740_991) };
647 }
648
649 #[test]
650 fn unscaled_numbers_stay_numbers_only_when_exact() {
651 assert_eq!(number_text("1.5").unwrap(), json!(1.5));
652 assert_eq!(number_text(".25").unwrap(), json!(0.25));
653 assert_eq!(number_text("-.5").unwrap(), json!(-0.5));
654 assert_eq!(number_text("+2.5").unwrap(), json!(2.5));
655 assert_eq!(number_text("1E+3").unwrap(), json!(1000.0));
656 assert_eq!(number_text("0.1").unwrap(), json!(0.1));
657 assert_eq!(number_text("42").unwrap(), json!(42));
658 assert_eq!(number_text("1e2").unwrap(), json!(100.0));
659 assert_eq!(number_text("0.000").unwrap(), json!(0));
660 assert_eq!(
661 number_text("12345678901234567890.123").unwrap(),
662 json!("12345678901234567890.123")
663 );
664 assert_eq!(
665 number_text("123456789012345678901234567890").unwrap(),
666 json!("123456789012345678901234567890")
667 );
668 assert_eq!(number_text("1e30").unwrap(), json!(1e30));
669 assert_eq!(
670 number_text("1.000000000000000000001e400").unwrap(),
671 json!(format!("1000000000000000000001{}", "0".repeat(379)))
672 );
673 assert_eq!(
674 number_text("123456789012345678901234567890e3").unwrap(),
675 json!("123456789012345678901234567890000")
676 );
677 assert!(number_text("abc").is_err());
678 }
679
680 #[test]
681 fn floats_keep_their_value_and_sign() {
682 assert_eq!(float64(1.5), json!(1.5));
683 assert_eq!(float64(-0.0).to_string(), "-0.0");
684 assert_eq!(float32(0.1), json!(0.1));
685 assert_eq!(float32(-0.0).to_string(), "-0.0");
686 assert_eq!(float32(3.0e38), json!(3.0e38));
687 assert_eq!(float32(16_777_217.0), json!(16_777_216.0));
688 }
689
690 #[tokio::test]
691 async fn non_finite_values_become_counted_nulls() {
692 let counter = NonFiniteCounter::new();
693 let seen = counter
694 .scope(async {
695 assert!(NonFiniteCounter::current().is_some());
696 assert_eq!(float64(f64::NAN), Value::Null);
697 assert_eq!(float64(f64::INFINITY), Value::Null);
698 assert_eq!(float32(f32::NEG_INFINITY), Value::Null);
699 assert_eq!(decimal_text("NaN").unwrap(), Value::Null);
700 assert_eq!(number_text("-Infinity").unwrap(), Value::Null);
701 let inner = NonFiniteCounter::current();
702 std::thread::spawn(move || propagate(&inner, || float64(f64::NAN)))
703 .join()
704 .unwrap();
705 propagate(&None, || ());
706 NonFiniteCounter::current().unwrap().count()
707 })
708 .await;
709 assert_eq!(seen, 6);
710 assert_eq!(counter.count(), 6);
711 assert!(NonFiniteCounter::current().is_none());
712 let before = unscoped_non_finite();
713 assert_eq!(float64(f64::NAN), Value::Null);
714 assert!(unscoped_non_finite() > before);
715 assert_eq!(counter.scope_sync(|| 1), 1);
716 }
717
718 #[test]
719 fn non_integers_bind_as_their_shortest_text() {
720 let n = |v: Value| match v {
721 Value::Number(n) => n,
722 _ => unreachable!(),
723 };
724 assert_eq!(non_integer_text(&n(json!(0.1))), Some("0.1".into()));
725 assert_eq!(
726 non_integer_text(&n(json!(-1.5e300))),
727 Some("-1.5e+300".into())
728 );
729 assert_eq!(non_integer_text(&n(json!(7))), None);
730 assert_eq!(non_integer_text(&n(json!(u64::MAX))), None);
731 }
732
733 fn pg_numeric(weight: i16, sign: u16, dscale: u16, digits: &[u16]) -> Vec<u8> {
735 let mut b = Vec::new();
736 for w in [digits.len() as u16, weight as u16, sign, dscale] {
737 b.extend_from_slice(&w.to_be_bytes());
738 }
739 for d in digits {
740 b.extend_from_slice(&d.to_be_bytes());
741 }
742 b
743 }
744
745 #[test]
746 fn binary_pg_numerics_keep_their_scale() {
747 assert_eq!(
749 decimal_from_pg_numeric(&pg_numeric(0, 0, 2, &[1, 5000])),
750 Some(json!("1.50"))
751 );
752 assert_eq!(
754 decimal_from_pg_numeric(&pg_numeric(
755 4,
756 0,
757 9,
758 &[1234, 5678, 9012, 3456, 7890, 1234, 5678, 9000]
759 )),
760 Some(json!("12345678901234567890.123456789"))
761 );
762 assert_eq!(
764 decimal_from_pg_numeric(&pg_numeric(-1, 0x4000, 4, &[5])),
765 Some(json!("-0.0005"))
766 );
767 assert_eq!(
769 decimal_from_pg_numeric(&pg_numeric(-2, 0, 8, &[1])),
770 Some(json!("0.00000001"))
771 );
772 assert_eq!(
774 decimal_from_pg_numeric(&pg_numeric(1, 0, 0, &[2])),
775 Some(json!("20000"))
776 );
777 assert_eq!(
778 decimal_from_pg_numeric(&pg_numeric(0, 0, 0, &[])),
779 Some(json!("0"))
780 );
781 assert_eq!(
782 decimal_from_pg_numeric(&pg_numeric(0, 0xC000, 0, &[])),
783 Some(Value::Null)
784 );
785 assert_eq!(
786 decimal_from_pg_numeric(&pg_numeric(0, 0x1234, 0, &[])),
787 None
788 );
789 assert_eq!(
790 decimal_from_pg_numeric(&pg_numeric(0, 0, 0, &[10000])),
791 None
792 );
793 assert_eq!(decimal_from_pg_numeric(&[0, 1]), None);
794 let mut short = pg_numeric(0, 0, 0, &[1, 2]);
795 short.pop();
796 assert_eq!(decimal_from_pg_numeric(&short), None);
797 }
798
799 #[test]
800 fn exact_decimal_types_are_recognised() {
801 for t in [
802 "NUMERIC(38,9)",
803 "numeric",
804 "DECIMAL(10, 2)",
805 "money",
806 "NUMBER",
807 "dec(5)",
808 "BIGNUMERIC",
809 ] {
810 assert!(is_exact_decimal_type(t), "{t}");
811 }
812 for t in [
813 "double precision",
814 "real",
815 "bigint",
816 "text",
817 "numerical_thing",
818 "decimals",
819 ] {
820 assert!(!is_exact_decimal_type(t), "{t}");
821 }
822 }
823
824 #[test]
825 fn binary_is_standard_base64() {
826 assert_eq!(binary(&[0, 1, 2, 253, 254, 255]), json!("AAEC/f7/"));
827 assert_eq!(binary(b""), json!(""));
828 }
829
830 #[test]
831 fn instants_carry_an_offset_and_native_precision() {
832 let utc = DateTime::from_timestamp(1_758_198_896, 123_000_000).unwrap();
833 assert_eq!(
834 timestamp(&utc, TimePrecision::Micros),
835 json!("2025-09-18T12:34:56.123000Z")
836 );
837 assert_eq!(
838 timestamp(&utc, TimePrecision::Seconds),
839 json!("2025-09-18T12:34:56Z")
840 );
841 let ist = FixedOffset::east_opt(5 * 3600 + 1800).unwrap();
842 assert_eq!(
843 timestamp(&utc.with_timezone(&ist), TimePrecision::Millis),
844 json!("2025-09-18T18:04:56.123+05:30")
845 );
846 assert_eq!(
847 timestamp(&utc, TimePrecision::Nanos),
848 json!("2025-09-18T12:34:56.123000000Z")
849 );
850 assert_eq!(
851 timestamp_naive(&utc.naive_utc(), TimePrecision::Micros),
852 json!("2025-09-18T12:34:56.123000")
853 );
854 assert_eq!(
855 timestamp_naive(&utc.naive_utc(), TimePrecision::Seconds),
856 json!("2025-09-18T12:34:56")
857 );
858 assert_eq!(date(&utc.date_naive()), json!("2025-09-18"));
859 assert_eq!(
860 time(&utc.time(), TimePrecision::Nanos),
861 json!("12:34:56.123000000")
862 );
863 }
864
865 #[test]
866 fn epoch_values_honour_unit_and_zone() {
867 assert_eq!(
868 timestamp_from_epoch(-1, TimePrecision::Micros, None).unwrap(),
869 json!("1969-12-31T23:59:59.999999")
870 );
871 assert_eq!(
872 timestamp_from_epoch(
873 1_500,
874 TimePrecision::Millis,
875 Some(FixedOffset::east_opt(0).unwrap())
876 )
877 .unwrap(),
878 json!("1970-01-01T00:00:01.500Z")
879 );
880 assert_eq!(
881 timestamp_from_epoch(5, TimePrecision::Seconds, None).unwrap(),
882 json!("1970-01-01T00:00:05")
883 );
884 assert_eq!(
885 timestamp_from_epoch(1, TimePrecision::Nanos, None).unwrap(),
886 json!("1970-01-01T00:00:00.000000001")
887 );
888 let err = timestamp_from_epoch(i64::MAX, TimePrecision::Seconds, None).unwrap_err();
889 assert!(err.to_string().contains("outside"), "{err}");
890 }
891
892 #[test]
893 fn precision_from_declared_digits() {
894 assert_eq!(TimePrecision::from_digits(0), TimePrecision::Seconds);
895 assert_eq!(TimePrecision::from_digits(2), TimePrecision::Millis);
896 assert_eq!(TimePrecision::from_digits(6), TimePrecision::Micros);
897 assert_eq!(TimePrecision::from_digits(7), TimePrecision::Nanos);
898 }
899
900 #[test]
901 fn serialized_json_keeps_unfaithful_literals_as_strings() {
902 let v = from_str(
903 r#"{"id": 18446744073709551616, "amt": 0.12345678901234567890, "d": 0.10000000000000001, "s": "1234567890123456789", "n": [1, 2.5]}"#,
904 )
905 .unwrap();
906 assert_eq!(
907 v,
908 json!({"id": "18446744073709551616", "amt": "0.12345678901234567890", "d": 0.1, "s": "1234567890123456789", "n": [1, 2.5]})
909 );
910 assert_eq!(from_slice(b"[1]").unwrap(), json!([1]));
911 assert!(from_str("{").is_err());
912 assert!(from_slice(&[b'"', 0xff, b'"']).is_err());
913 assert_eq!(
914 from_str("1e99999").is_err(),
915 serde_json::from_str::<Value>("1e99999").is_err()
916 );
917 }
918
919 #[test]
920 fn json_text_parses_with_exact_numbers() {
921 let v = parse_json(br#"{"a": 12345678901234567890.5, "b": 1.5, "c": [7]}"#).unwrap();
922 assert_eq!(
923 v,
924 json!({"a": "12345678901234567890.5", "b": 1.5, "c": [7]})
925 );
926 assert!(parse_json(b"{").is_err());
927 assert!(parse_json(&[0xff]).is_err());
928 }
929}