mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-31 13:54:27 +01:00
Merge 'Strict numeric cast for op_must_be_int' from bit-aloo
closes: #3302 Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com> Closes #3771
This commit is contained in:
54
core/util.rs
54
core/util.rs
@@ -855,11 +855,19 @@ pub fn cast_text_to_real(text: &str) -> Value {
|
||||
/// IEEE 754 64-bit float and thus provides a 1-bit of margin for the text-to-float conversion operation.)
|
||||
/// Any text input that describes a value outside the range of a 64-bit signed integer yields a REAL result.
|
||||
/// Casting a REAL or INTEGER value to NUMERIC is a no-op, even if a real value could be losslessly converted to an integer.
|
||||
pub fn checked_cast_text_to_numeric(text: &str) -> std::result::Result<Value, ()> {
|
||||
///
|
||||
/// `lossless`: If `true`, rejects the input if any characters remain after the numeric prefix (strict / exact conversion).
|
||||
pub fn checked_cast_text_to_numeric(text: &str, lossless: bool) -> std::result::Result<Value, ()> {
|
||||
// sqlite will parse the first N digits of a string to numeric value, then determine
|
||||
// whether _that_ value is more likely a real or integer value. e.g.
|
||||
// '-100234-2344.23e14' evaluates to -100234 instead of -100234.0
|
||||
let original_len = text.trim().len();
|
||||
let (kind, text) = parse_numeric_str(text)?;
|
||||
|
||||
if original_len != text.len() && lossless {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
match kind {
|
||||
ValueType::Integer => match text.parse::<i64>() {
|
||||
Ok(i) => Ok(Value::Integer(i)),
|
||||
@@ -940,7 +948,7 @@ fn parse_numeric_str(text: &str) -> Result<(ValueType, &str), ()> {
|
||||
}
|
||||
|
||||
pub fn cast_text_to_numeric(txt: &str) -> Value {
|
||||
checked_cast_text_to_numeric(txt).unwrap_or(Value::Integer(0))
|
||||
checked_cast_text_to_numeric(txt, false).unwrap_or(Value::Integer(0))
|
||||
}
|
||||
|
||||
// Check if float can be losslessly converted to 51-bit integer
|
||||
@@ -1368,7 +1376,7 @@ pub fn rewrite_column_references_if_needed(
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::schema::Type as SchemaValueType;
|
||||
use crate::{schema::Type as SchemaValueType, types::Text};
|
||||
use turso_parser::ast::{self, Expr, Literal, Name, Operator::*, Type};
|
||||
|
||||
#[test]
|
||||
@@ -2379,4 +2387,44 @@ pub mod tests {
|
||||
assert_eq!(result, expected, "Failed for input: {input}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_checked_cast_text_to_numeric_lossless_property() {
|
||||
use Value::*;
|
||||
assert_eq!(checked_cast_text_to_numeric("1.xx", true), Err(()));
|
||||
assert_eq!(checked_cast_text_to_numeric("abc", true), Err(()));
|
||||
assert_eq!(checked_cast_text_to_numeric("--5", true), Err(()));
|
||||
assert_eq!(checked_cast_text_to_numeric("12.34.56", true), Err(()));
|
||||
assert_eq!(checked_cast_text_to_numeric("", true), Err(()));
|
||||
assert_eq!(checked_cast_text_to_numeric(" ", true), Err(()));
|
||||
assert_eq!(checked_cast_text_to_numeric("0", true), Ok(Integer(0)));
|
||||
assert_eq!(checked_cast_text_to_numeric("42", true), Ok(Integer(42)));
|
||||
assert_eq!(checked_cast_text_to_numeric("-42", true), Ok(Integer(-42)));
|
||||
assert_eq!(
|
||||
checked_cast_text_to_numeric("999999999999", true),
|
||||
Ok(Integer(999_999_999_999))
|
||||
);
|
||||
assert_eq!(checked_cast_text_to_numeric("1.0", true), Ok(Float(1.0)));
|
||||
assert_eq!(
|
||||
checked_cast_text_to_numeric("-3.22", true),
|
||||
Ok(Float(-3.22))
|
||||
);
|
||||
assert_eq!(
|
||||
checked_cast_text_to_numeric("0.001", true),
|
||||
Ok(Float(0.001))
|
||||
);
|
||||
assert_eq!(checked_cast_text_to_numeric("2e3", true), Ok(Float(2000.0)));
|
||||
assert_eq!(
|
||||
checked_cast_text_to_numeric("-5.5e-2", true),
|
||||
Ok(Float(-0.055))
|
||||
);
|
||||
assert_eq!(
|
||||
checked_cast_text_to_numeric(" 123 ", true),
|
||||
Ok(Integer(123))
|
||||
);
|
||||
assert_eq!(
|
||||
checked_cast_text_to_numeric("\t-3.22\n", true),
|
||||
Ok(Float(-3.22))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6699,11 +6699,12 @@ pub fn op_must_be_int(
|
||||
Ok(i) => state.registers[*reg] = Register::Value(Value::Integer(i)),
|
||||
Err(_) => crate::bail_parse_error!("datatype mismatch"),
|
||||
},
|
||||
Value::Text(text) => match checked_cast_text_to_numeric(text.as_str()) {
|
||||
Value::Text(text) => match checked_cast_text_to_numeric(text.as_str(), true) {
|
||||
Ok(Value::Integer(i)) => state.registers[*reg] = Register::Value(Value::Integer(i)),
|
||||
Ok(Value::Float(f)) => {
|
||||
state.registers[*reg] = Register::Value(Value::Integer(f as i64))
|
||||
}
|
||||
Ok(Value::Float(f)) => match cast_real_to_integer(f) {
|
||||
Ok(i) => state.registers[*reg] = Register::Value(Value::Integer(i)),
|
||||
Err(_) => crate::bail_parse_error!("datatype mismatch"),
|
||||
},
|
||||
_ => crate::bail_parse_error!("datatype mismatch"),
|
||||
},
|
||||
_ => {
|
||||
@@ -9709,7 +9710,7 @@ fn apply_affinity_char(target: &mut Register, affinity: Affinity) -> bool {
|
||||
if s.starts_with("0x") {
|
||||
return false;
|
||||
}
|
||||
if let Ok(num) = checked_cast_text_to_numeric(s) {
|
||||
if let Ok(num) = checked_cast_text_to_numeric(s, false) {
|
||||
*value = num;
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -1008,6 +1008,11 @@ do_execsql_test_in_memory_error_content limit-expr-invalid-data-type-4 {
|
||||
SELECT 1 LIMIT 4+NULL;
|
||||
} {"datatype mismatch"}
|
||||
|
||||
do_execsql_test_in_memory_error_content limit-expression-invalid-type {
|
||||
SELECT 1 LIMIT '1.xx';
|
||||
} {"datatype mismatch"}
|
||||
|
||||
|
||||
do_execsql_test_on_specific_db {:memory:} rowid-references {
|
||||
CREATE TABLE test_table (id INTEGER);
|
||||
INSERT INTO test_table VALUES (5),(5);
|
||||
@@ -1093,4 +1098,4 @@ do_execsql_test_on_specific_db {:memory:} unambiguous-self-join {
|
||||
2|3
|
||||
3|1
|
||||
3|2
|
||||
3|3}
|
||||
3|3}
|
||||
Reference in New Issue
Block a user