Skip to content

Commit 9621ebc

Browse files
authored
fix: to_i64 and to_u64 function support rounded numeric conversion (#100)
* fix: `to_i64` and `to_u64` function support rounded numeric conversion * fix * fix
1 parent fc84214 commit 9621ebc

3 files changed

Lines changed: 551 additions & 117 deletions

File tree

src/functions/scalar.rs

Lines changed: 103 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -460,18 +460,18 @@ impl RawJsonb<'_> {
460460
}
461461
}
462462

463-
/// Checks if the JSONB value is an integer that can be represented as an i64.
463+
/// Checks whether the JSONB value is an exact `i64`.
464464
///
465-
/// This function checks if the JSONB value is a number and can be converted to an `i64` without loss of information.
465+
/// Decimal and floating-point numbers must already be integral.
466466
///
467467
/// # Arguments
468468
///
469469
/// * `self` - The JSONB value.
470470
///
471471
/// # Returns
472472
///
473-
/// * `Ok(true)` - If the value is an integer representable as an `i64`.
474-
/// * `Ok(false)` - If the value is not an integer or cannot be represented as an `i64`.
473+
/// * `Ok(true)` - If the value is an exact `i64`.
474+
/// * `Ok(false)` - Otherwise.
475475
/// * `Err(Error)` - If the JSONB data is invalid.
476476
///
477477
/// # Examples
@@ -484,12 +484,16 @@ impl RawJsonb<'_> {
484484
/// let raw_i64 = i64_jsonb.as_raw();
485485
/// assert!(raw_i64.is_i64().unwrap());
486486
///
487-
/// let i64_jsonb = "-123456789012345678".parse::<OwnedJsonb>().unwrap();
487+
/// let i64_jsonb = "-123456789012345678.0".parse::<OwnedJsonb>().unwrap();
488488
/// let raw_i64 = i64_jsonb.as_raw();
489489
/// assert!(raw_i64.is_i64().unwrap());
490490
///
491-
/// // Non-i64 values
492-
/// let float_jsonb = "123.45".parse::<OwnedJsonb>().unwrap();
491+
/// let float_jsonb = "1.5e0".parse::<OwnedJsonb>().unwrap();
492+
/// let raw_float = float_jsonb.as_raw();
493+
/// assert!(!raw_float.is_i64().unwrap());
494+
///
495+
/// // Out-of-range values
496+
/// let float_jsonb = "1e100".parse::<OwnedJsonb>().unwrap();
493497
/// let raw_float = float_jsonb.as_raw();
494498
/// assert!(!raw_float.is_i64().unwrap());
495499
///
@@ -507,20 +511,19 @@ impl RawJsonb<'_> {
507511
self.as_i64().map(|v| v.is_some())
508512
}
509513

510-
/// Extracts an i64 integer from a JSONB value.
514+
/// Extracts an exact `i64` from a JSONB value.
511515
///
512-
/// This function attempts to extract an `i64` integer from the JSONB value.
513-
/// If the JSONB value is a number and can be represented as an `i64` without loss of information, the integer value is returned.
514-
/// Otherwise, `None` is returned.
516+
/// Decimal and floating-point numbers are accepted only when they already
517+
/// represent an integer.
515518
///
516519
/// # Arguments
517520
///
518521
/// * `self` - The JSONB value.
519522
///
520523
/// # Returns
521524
///
522-
/// * `Ok(Some(i64))` - If the value is an integer that can be represented as an `i64`.
523-
/// * `Ok(None)` - If the value is not an integer or cannot be represented as an `i64`.
525+
/// * `Ok(Some(i64))` - If the value is an exact `i64`.
526+
/// * `Ok(None)` - Otherwise.
524527
/// * `Err(Error)` - If the JSONB data is invalid.
525528
///
526529
/// # Examples
@@ -533,8 +536,16 @@ impl RawJsonb<'_> {
533536
/// let raw_i64 = i64_jsonb.as_raw();
534537
/// assert_eq!(raw_i64.as_i64().unwrap(), Some(123456789012345678));
535538
///
539+
/// let decimal_jsonb = "123.0".parse::<OwnedJsonb>().unwrap();
540+
/// let raw_decimal = decimal_jsonb.as_raw();
541+
/// assert_eq!(raw_decimal.as_i64().unwrap(), Some(123));
542+
///
543+
/// let float_jsonb = "123.0".parse::<OwnedJsonb>().unwrap();
544+
/// let raw_float = float_jsonb.as_raw();
545+
/// assert_eq!(raw_float.as_i64().unwrap(), Some(123));
546+
///
536547
/// // Non-i64 values
537-
/// let float_jsonb = "123.45".parse::<OwnedJsonb>().unwrap();
548+
/// let float_jsonb = "123.1".parse::<OwnedJsonb>().unwrap();
538549
/// let raw_float = float_jsonb.as_raw();
539550
/// assert_eq!(raw_float.as_i64().unwrap(), None);
540551
///
@@ -567,21 +578,18 @@ impl RawJsonb<'_> {
567578
}
568579
}
569580

570-
/// Converts a JSONB value to an i64 integer.
581+
/// Converts a JSONB value to `i64`.
571582
///
572-
/// This function attempts to convert a JSONB value to an `i64` integer.
573-
/// It prioritizes direct conversion from a number if possible.
574-
/// If the value is a boolean, it's converted to 1 (for `true`) or 0 (for `false`).
575-
/// If the value is a string that can be parsed as an `i64`, that parsed value is returned.
576-
/// Otherwise, an error is returned.
583+
/// Numbers are rounded to the nearest integer. Booleans map to `1` and
584+
/// `0`. Strings are parsed as `i64`, or as `f64` and then rounded.
577585
///
578586
/// # Arguments
579587
///
580588
/// * `self` - The JSONB value.
581589
///
582590
/// # Returns
583591
///
584-
/// * `Ok(i64)` - The `i64` representation of the JSONB value.
592+
/// * `Ok(i64)` - The converted value.
585593
/// * `Err(Error::InvalidCast)` - If the value cannot be converted to an `i64`.
586594
/// * `Err(Error)` - If the JSONB data is invalid.
587595
///
@@ -608,8 +616,18 @@ impl RawJsonb<'_> {
608616
/// let str_jsonb = r#""123""#.parse::<OwnedJsonb>().unwrap();
609617
/// assert_eq!(str_jsonb.as_raw().to_i64().unwrap(), 123);
610618
///
619+
/// // Decimal and float numbers are rounded before conversion
620+
/// let decimal_jsonb = "123.5".parse::<OwnedJsonb>().unwrap();
621+
/// assert_eq!(decimal_jsonb.as_raw().to_i64().unwrap(), 124);
622+
///
623+
/// let float_jsonb = "1.5e0".parse::<OwnedJsonb>().unwrap();
624+
/// assert_eq!(float_jsonb.as_raw().to_i64().unwrap(), 2);
625+
///
626+
/// let str_jsonb = r#""1.5""#.parse::<OwnedJsonb>().unwrap();
627+
/// assert_eq!(str_jsonb.as_raw().to_i64().unwrap(), 2);
628+
///
611629
/// // Invalid conversions
612-
/// let float_jsonb = "123.45".parse::<OwnedJsonb>().unwrap();
630+
/// let float_jsonb = "1e100".parse::<OwnedJsonb>().unwrap();
613631
/// let result = float_jsonb.as_raw().to_i64();
614632
/// assert!(result.is_err());
615633
///
@@ -647,12 +665,12 @@ impl RawJsonb<'_> {
647665
}
648666
JsonbItem::Number(num) => {
649667
let value = num.as_number()?;
650-
if let Some(v) = value.as_i64() {
668+
if let Some(v) = value.to_i64() {
651669
return Ok(v);
652670
}
653671
}
654672
JsonbItem::String(s) => {
655-
if let Ok(v) = s.parse::<i64>() {
673+
if let Some(v) = parse_string_to_i64(&s) {
656674
return Ok(v);
657675
}
658676
}
@@ -661,18 +679,18 @@ impl RawJsonb<'_> {
661679
Err(Error::InvalidCast)
662680
}
663681

664-
/// Checks if the JSONB value is an unsigned integer that can be represented as a u64.
682+
/// Checks whether the JSONB value is an exact `u64`.
665683
///
666-
/// This function checks if the JSONB value is a number and can be converted to a `u64` without loss of information.
684+
/// Decimal and floating-point numbers must already be integral.
667685
///
668686
/// # Arguments
669687
///
670688
/// * `self` - The JSONB value.
671689
///
672690
/// # Returns
673691
///
674-
/// * `Ok(true)` - If the value is an unsigned integer representable as a `u64`.
675-
/// * `Ok(false)` - If the value is not an unsigned integer or cannot be represented as a `u64`.
692+
/// * `Ok(true)` - If the value is an exact `u64`.
693+
/// * `Ok(false)` - Otherwise.
676694
/// * `Err(Error)` - If the JSONB data is invalid.
677695
///
678696
/// # Examples
@@ -689,8 +707,12 @@ impl RawJsonb<'_> {
689707
/// let raw_u64 = u64_jsonb.as_raw();
690708
/// assert!(raw_u64.is_u64().unwrap());
691709
///
710+
/// let float_jsonb = "123.0".parse::<OwnedJsonb>().unwrap();
711+
/// let raw_float = float_jsonb.as_raw();
712+
/// assert!(raw_float.is_u64().unwrap());
713+
///
692714
/// // Non-u64 values
693-
/// let float_jsonb = "123.45".parse::<OwnedJsonb>().unwrap();
715+
/// let float_jsonb = "123.1".parse::<OwnedJsonb>().unwrap();
694716
/// let raw_float = float_jsonb.as_raw();
695717
/// assert!(!raw_float.is_u64().unwrap());
696718
///
@@ -728,20 +750,19 @@ impl RawJsonb<'_> {
728750
self.as_u64().map(|v| v.is_some())
729751
}
730752

731-
/// Extracts a u64 unsigned integer from a JSONB value.
753+
/// Extracts an exact `u64` from a JSONB value.
732754
///
733-
/// This function attempts to extract a `u64` unsigned integer from the JSONB value.
734-
/// If the JSONB value is a number and can be represented as a `u64` without loss of information (i.e., it's a non-negative integer within the `u64` range),
735-
/// the unsigned integer value is returned. Otherwise, `None` is returned.
755+
/// Decimal and floating-point numbers are accepted only when they already
756+
/// represent a non-negative integer.
736757
///
737758
/// # Arguments
738759
///
739760
/// * `self` - The JSONB value.
740761
///
741762
/// # Returns
742763
///
743-
/// * `Ok(Some(u64))` - If the value is an unsigned integer that can be represented as a `u64`.
744-
/// * `Ok(None)` - If the value is not an unsigned integer or cannot be represented as a `u64`.
764+
/// * `Ok(Some(u64))` - If the value is an exact `u64`.
765+
/// * `Ok(None)` - Otherwise.
745766
/// * `Err(Error)` - If the JSONB data is invalid.
746767
///
747768
/// # Examples
@@ -754,8 +775,16 @@ impl RawJsonb<'_> {
754775
/// let raw_u64 = u64_jsonb.as_raw();
755776
/// assert_eq!(raw_u64.as_u64().unwrap(), Some(1234567890123456789));
756777
///
778+
/// let decimal_jsonb = "123.0".parse::<OwnedJsonb>().unwrap();
779+
/// let raw_decimal = decimal_jsonb.as_raw();
780+
/// assert_eq!(raw_decimal.as_u64().unwrap(), Some(123));
781+
///
782+
/// let float_jsonb = "123.0".parse::<OwnedJsonb>().unwrap();
783+
/// let raw_float = float_jsonb.as_raw();
784+
/// assert_eq!(raw_float.as_u64().unwrap(), Some(123));
785+
///
757786
/// // Non-u64 values
758-
/// let float_jsonb = "123.45".parse::<OwnedJsonb>().unwrap();
787+
/// let float_jsonb = "123.1".parse::<OwnedJsonb>().unwrap();
759788
/// let raw_float = float_jsonb.as_raw();
760789
/// assert_eq!(raw_float.as_u64().unwrap(), None);
761790
///
@@ -800,22 +829,19 @@ impl RawJsonb<'_> {
800829
}
801830
}
802831

803-
/// Converts a JSONB value to a u64 unsigned integer.
832+
/// Converts a JSONB value to `u64`.
804833
///
805-
/// This function attempts to convert a JSONB value to a `u64` unsigned integer.
806-
/// It prioritizes direct conversion from a number if possible.
807-
/// If the value is a boolean, it's converted to 1 (for `true`) or 0 (for `false`).
808-
/// If the value is a string that can be parsed as a `u64`, that parsed value is returned.
809-
/// Otherwise, an error is returned.
834+
/// Numbers are rounded to the nearest integer. Booleans map to `1` and
835+
/// `0`. Strings are parsed as `u64`, or as `f64` and then rounded.
810836
///
811837
/// # Arguments
812838
///
813839
/// * `self` - The JSONB value.
814840
///
815841
/// # Returns
816842
///
817-
/// * `Ok(u64)` - The `u64` representation of the JSONB value.
818-
/// * `Err(Error::InvalidCast)` - If the value cannot be converted to a `u64` (e.g., it's a floating-point number, a negative number, an array, an object, or a string that is not a valid unsigned integer).
843+
/// * `Ok(u64)` - The converted value.
844+
/// * `Err(Error::InvalidCast)` - If conversion fails.
819845
/// * `Err(Error)` - If the JSONB data is invalid.
820846
///
821847
/// # Examples
@@ -841,8 +867,18 @@ impl RawJsonb<'_> {
841867
/// let str_jsonb = r#""123""#.parse::<OwnedJsonb>().unwrap();
842868
/// assert_eq!(str_jsonb.as_raw().to_u64().unwrap(), 123);
843869
///
870+
/// // Decimal and float numbers are rounded before conversion
871+
/// let decimal_jsonb = "123.5".parse::<OwnedJsonb>().unwrap();
872+
/// assert_eq!(decimal_jsonb.as_raw().to_u64().unwrap(), 124);
873+
///
874+
/// let float_jsonb = "1.5e0".parse::<OwnedJsonb>().unwrap();
875+
/// assert_eq!(float_jsonb.as_raw().to_u64().unwrap(), 2);
876+
///
877+
/// let str_jsonb = r#""1.5""#.parse::<OwnedJsonb>().unwrap();
878+
/// assert_eq!(str_jsonb.as_raw().to_u64().unwrap(), 2);
879+
///
844880
/// // Invalid conversions
845-
/// let float_jsonb = "123.45".parse::<OwnedJsonb>().unwrap();
881+
/// let float_jsonb = "1e100".parse::<OwnedJsonb>().unwrap();
846882
/// let result = float_jsonb.as_raw().to_u64();
847883
/// assert!(result.is_err());
848884
///
@@ -884,12 +920,12 @@ impl RawJsonb<'_> {
884920
}
885921
JsonbItem::Number(num) => {
886922
let value = num.as_number()?;
887-
if let Some(v) = value.as_u64() {
923+
if let Some(v) = value.to_u64() {
888924
return Ok(v);
889925
}
890926
}
891927
JsonbItem::String(s) => {
892-
if let Ok(v) = s.parse::<u64>() {
928+
if let Some(v) = parse_string_to_u64(&s) {
893929
return Ok(v);
894930
}
895931
}
@@ -1937,3 +1973,23 @@ impl RawJsonb<'_> {
19371973
}
19381974
}
19391975
}
1976+
1977+
fn parse_string_to_i64(s: &str) -> Option<i64> {
1978+
if let Ok(v) = s.parse::<i64>() {
1979+
return Some(v);
1980+
}
1981+
1982+
s.parse::<f64>()
1983+
.ok()
1984+
.and_then(|value| Number::Float64(value).to_i64())
1985+
}
1986+
1987+
fn parse_string_to_u64(s: &str) -> Option<u64> {
1988+
if let Ok(v) = s.parse::<u64>() {
1989+
return Some(v);
1990+
}
1991+
1992+
s.parse::<f64>()
1993+
.ok()
1994+
.and_then(|value| Number::Float64(value).to_u64())
1995+
}

0 commit comments

Comments
 (0)