Merge 'Fix numeric overflow in numeric string parsing in logical comparision' from

Closes #2077-
This PR fixes an integer overflow bug that causes results from Turso to
differ from SQLite.
-Commit 1: Fixes incorrect logic that failed to detect integer overflow
when parsing long numeric strings.
-Commit 2: Handles the case where a parsed numeric string is stored as a
float and classified as PureInteger, but lies outside the integer range.
Previously, `parsed_value.as_integer()` would return None, causing Turso
to fall back to text comparison against numeric values. This caused
**another** erroneous result, as shown below.
`$> SELECT (-104614899632619 || 45597) > CAST(0 AS NUMERIC); -- tursodb
= 1(wrong), sqlite = 0`
 Now, if Turso fails to convert a very long numeric string to an
integer, it tries to convert it to a float. This is in line with the
`static void applyNumericAffinity` function in sqlite3
**Before**
<img width="623" height="238" alt="Screenshot 2025-08-01 at 12 11 49 PM"
src="https://github.com/user-attachments/assets/796d6ff6-768b-40ef-
ac83-e0c55fff6bd9" />
**After**
`SELECT (104614899632619 || 45597) > CAST(0 AS NUMERIC); -- tursodb = 1,
sqlite = 1`
`SELECT (-104614899632619 || 45597) > CAST(0 AS NUMERIC); -- tursodb =
0, sqlite = 0`

Reviewed-by: Preston Thorpe <preston@turso.tech>

Closes #2397
This commit is contained in:
Preston Thorpe
2025-08-01 21:39:30 -04:00
committed by GitHub
2 changed files with 17 additions and 4 deletions

View File

@@ -8010,11 +8010,9 @@ fn create_result_from_significand(
}
// For pure integers without exponent, try to return as integer
if !has_decimal && !has_exponent && exponent == 0 {
if !has_decimal && !has_exponent && exponent == 0 && significand <= i64::MAX as u64 {
let signed_val = (significand as i64).wrapping_mul(sign);
if (significand as i64) * sign == signed_val {
return (parse_result, ParsedNumber::Integer(signed_val));
}
return (parse_result, ParsedNumber::Integer(signed_val));
}
// Convert to float
@@ -8111,6 +8109,12 @@ pub fn apply_numeric_affinity(register: &mut Register, try_for_int: bool) -> boo
if let Some(int_val) = parsed_value.as_integer() {
*register = Register::Value(Value::Integer(int_val));
true
} else if let Some(float_val) = parsed_value.as_float() {
*register = Register::Value(Value::Float(float_val));
if try_for_int {
apply_integer_affinity(register);
}
true
} else {
false
}

View File

@@ -930,6 +930,15 @@ do_execsql_test cast-in-where {
select age from users where age = cast('45' as integer) limit 1;
} {45}
#Parsing test for large numeric strings in logical operations with numeric operands
do_execsql_test parse-large-integral-numeric-string-as-number {
SELECT (104614899632619 || 45597) > CAST(0 AS NUMERIC);
} {1}
do_execsql_test parse-large-integral-numeric-string-as-number {
SELECT (-104614899632619 || 45597) > CAST(0 AS NUMERIC);
} {0}
# TODO: sqlite seems not enable soundex() by default unless build it with SQLITE_SOUNDEX enabled.
# do_execsql_test soundex-text {
# select soundex('Pfister'), soundex('husobee'), soundex('Tymczak'), soundex('Ashcraft'), soundex('Robert'), soundex('Rupert'), soundex('Rubin'), soundex('Kant'), soundex('Knuth'), soundex('x'), soundex('');