diff --git a/core/util.rs b/core/util.rs index c4a6c3516..62a94da72 100644 --- a/core/util.rs +++ b/core/util.rs @@ -1,9 +1,11 @@ +use core::num::IntErrorKind; use limbo_sqlite3_parser::ast::{self, CreateTableBody, Expr, FunctionTail, Literal}; use std::{rc::Rc, sync::Arc}; use crate::{ schema::{self, Column, Schema, Type}, LimboError, OpenFlags, Result, Statement, StepResult, IO, + types::OwnedValue, }; // https://sqlite.org/lang_keywords.html @@ -602,6 +604,184 @@ pub fn decode_percent(uri: &str) -> String { String::from_utf8_lossy(&decoded).to_string() } +#[derive(Debug, PartialEq)] +/// Reference: +/// https://github.com/sqlite/sqlite/blob/master/src/util.c#L798 +pub enum CastTextToIntResultCode { + NotInt = -1, + Success = 0, + ExcessSpace = 1, + TooLargeOrMalformed = 2, + #[allow(dead_code)] + SpecialCase = 3, +} + +pub fn text_to_integer(text: &str) -> (OwnedValue, CastTextToIntResultCode) { + let text = text.trim(); + if text.is_empty() { + return (OwnedValue::Integer(0), CastTextToIntResultCode::NotInt); + } + let mut accum = String::new(); + let mut sign = false; + let mut has_digit = false; + let mut excess_space = false; + + let chars = text.chars(); + + for c in chars { + match c { + '0'..='9' => { + has_digit = true; + accum.push(c); + } + '+' | '-' if !has_digit && !sign => { + sign = true; + accum.push(c); + } + _ => { + excess_space = true; + break; + } + } + } + + match accum.parse::() { + Ok(num) => { + if excess_space { + return ( + OwnedValue::Integer(num), + CastTextToIntResultCode::ExcessSpace, + ); + } + + return (OwnedValue::Integer(num), CastTextToIntResultCode::Success); + } + Err(e) => match e.kind() { + IntErrorKind::NegOverflow | IntErrorKind::PosOverflow => ( + OwnedValue::Integer(0), + CastTextToIntResultCode::TooLargeOrMalformed, + ), + _ => (OwnedValue::Integer(0), CastTextToIntResultCode::NotInt), + }, + } +} + +#[derive(Debug, PartialEq)] +/// Reference +/// https://github.com/sqlite/sqlite/blob/master/src/util.c#L529 +pub enum CastTextToRealResultCode { + PureInt = 1, + HasDecimal = 2, + NotValid = 0, + NotValidButPrefix = -1, +} + +pub fn text_to_real(text: &str) -> (OwnedValue, CastTextToRealResultCode) { + let text = text.trim(); + if text.is_empty() { + return (OwnedValue::Float(0.0), CastTextToRealResultCode::NotValid); + } + let mut accum = String::new(); + let mut has_decimal_separator = false; + let mut sign = false; + let mut exp_sign = false; + let mut has_exponent = false; + let mut has_digit = false; + let mut has_decimal_digit = false; + let mut excess_space = false; + + let mut chars = text.chars(); + + 'outer: while let Some(c) = chars.next() { + match c { + '0'..='9' if !has_decimal_separator => { + has_digit = true; + accum.push(c); + } + '0'..='9' => { + // This pattern is used for both decimal and exponent digits + has_decimal_digit = true; + accum.push(c); + } + '+' | '-' if !has_digit && !sign => { + sign = true; + accum.push(c); + } + '.' if !has_decimal_separator => { + // Check if next char is a number + if let Some(ch) = chars.next() { + match ch { + '0'..='9' => { + has_decimal_separator = true; + accum.push(c); + accum.push(ch); + } + _ => { + excess_space = true; + break; + } + } + } else { + excess_space = true; + } + } + 'E' | 'e' if !has_exponent && (!has_decimal_separator || has_decimal_digit) => { + // Lookahead if next char is a number or sign + let mut curr_sign = None; + loop { + if let Some(ch) = chars.next() { + match ch { + '0'..='9' => { + has_exponent = true; + accum.push(c); + if let Some(sign) = curr_sign { + exp_sign = true; + accum.push(sign); + } + accum.push(ch); + break; + } + '+' | '-' => { + curr_sign = Some(ch); + } + _ => { + excess_space = true; + break 'outer; + } + } + } else { + excess_space = true; + break 'outer; + } + } + } + _ => { + excess_space = true; + break; + } + } + } + + if let Ok(num) = accum.parse::() { + if !has_decimal_separator && !exp_sign && !has_exponent && !excess_space { + return (OwnedValue::Float(num), CastTextToRealResultCode::PureInt); + } + + if excess_space { + // TODO see if this branch satisfies: not a valid number, but has a valid prefix which + // includes a decimal point and/or an eNNN clause + return ( + OwnedValue::Float(num), + CastTextToRealResultCode::NotValidButPrefix, + ); + } + + return (OwnedValue::Float(num), CastTextToRealResultCode::HasDecimal); + } + + return (OwnedValue::Float(0.0), CastTextToRealResultCode::NotValid); +} + #[cfg(test)] pub mod tests { use super::*; @@ -1152,4 +1332,264 @@ pub mod tests { "/home/user/db.sqlite" ); } + + #[test] + fn test_text_to_integer() { + assert_eq!( + text_to_integer("1"), + (OwnedValue::Integer(1), CastTextToIntResultCode::Success), + ); + assert_eq!( + text_to_integer("-1"), + (OwnedValue::Integer(-1), CastTextToIntResultCode::Success), + ); + assert_eq!( + text_to_integer("10000000"), + ( + OwnedValue::Integer(10000000), + CastTextToIntResultCode::Success, + ), + ); + assert_eq!( + text_to_integer("-10000000"), + ( + OwnedValue::Integer(-10000000), + CastTextToIntResultCode::Success, + ), + ); + assert_eq!( + text_to_integer("xxx"), + (OwnedValue::Integer(0), CastTextToIntResultCode::NotInt), + ); + assert_eq!( + text_to_integer("123xxx"), + ( + OwnedValue::Integer(123), + CastTextToIntResultCode::ExcessSpace, + ), + ); + assert_eq!( + text_to_integer("9223372036854775807"), + ( + OwnedValue::Integer(i64::MAX), + CastTextToIntResultCode::Success, + ), + ); + assert_eq!( + text_to_integer("9223372036854775808"), + ( + OwnedValue::Integer(0), + CastTextToIntResultCode::TooLargeOrMalformed, + ), + ); + assert_eq!( + text_to_integer("-9223372036854775808"), + ( + OwnedValue::Integer(i64::MIN), + CastTextToIntResultCode::Success, + ), + ); + assert_eq!( + text_to_integer("-9223372036854775809"), + ( + OwnedValue::Integer(0), + CastTextToIntResultCode::TooLargeOrMalformed, + ), + ); + assert_eq!( + text_to_integer("-"), + (OwnedValue::Integer(0), CastTextToIntResultCode::NotInt,), + ); + } + + #[test] + fn test_text_to_real() { + assert_eq!( + text_to_real("1"), + (OwnedValue::Float(1.0), CastTextToRealResultCode::PureInt), + ); + assert_eq!( + text_to_real("-1"), + (OwnedValue::Float(-1.0), CastTextToRealResultCode::PureInt), + ); + assert_eq!( + text_to_real("1.0"), + (OwnedValue::Float(1.0), CastTextToRealResultCode::HasDecimal), + ); + assert_eq!( + text_to_real("-1.0"), + ( + OwnedValue::Float(-1.0), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("1e10"), + ( + OwnedValue::Float(1e10), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("-1e10"), + ( + OwnedValue::Float(-1e10), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("1e-10"), + ( + OwnedValue::Float(1e-10), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("-1e-10"), + ( + OwnedValue::Float(-1e-10), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("1.123e10"), + ( + OwnedValue::Float(1.123e10), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("-1.123e10"), + ( + OwnedValue::Float(-1.123e10), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("1.123e-10"), + ( + OwnedValue::Float(1.123e-10), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("-1.123e-10"), + ( + OwnedValue::Float(-1.123e-10), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("1-282584294928"), + ( + OwnedValue::Float(1.0), + CastTextToRealResultCode::NotValidButPrefix + ), + ); + assert_eq!( + text_to_real("xxx"), + (OwnedValue::Float(0.0), CastTextToRealResultCode::NotValid), + ); + assert_eq!( + text_to_real("1.7976931348623157e308"), + ( + OwnedValue::Float(f64::MAX), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("1.7976931348623157e309"), + ( + OwnedValue::Float(f64::INFINITY), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("-1.7976931348623157e308"), + ( + OwnedValue::Float(f64::MIN), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("-1.7976931348623157e309"), + ( + OwnedValue::Float(f64::NEG_INFINITY), + CastTextToRealResultCode::HasDecimal, + ), + ); + assert_eq!( + text_to_real("1E"), + ( + OwnedValue::Float(1.0), + CastTextToRealResultCode::NotValidButPrefix, + ), + ); + assert_eq!( + text_to_real("1EE"), + ( + OwnedValue::Float(1.0), + CastTextToRealResultCode::NotValidButPrefix, + ), + ); + assert_eq!( + text_to_real("-1E"), + ( + OwnedValue::Float(-1.0), + CastTextToRealResultCode::NotValidButPrefix, + ), + ); + assert_eq!( + text_to_real("1."), + ( + OwnedValue::Float(1.0), + CastTextToRealResultCode::NotValidButPrefix, + ), + ); + assert_eq!( + text_to_real("-1."), + ( + OwnedValue::Float(-1.0), + CastTextToRealResultCode::NotValidButPrefix, + ), + ); + assert_eq!( + text_to_real("1.23E"), + ( + OwnedValue::Float(1.23), + CastTextToRealResultCode::NotValidButPrefix, + ), + ); + assert_eq!( + text_to_real("1.23E-"), + ( + OwnedValue::Float(1.23), + CastTextToRealResultCode::NotValidButPrefix, + ), + ); + assert_eq!( + text_to_real("0"), + (OwnedValue::Float(0.0), CastTextToRealResultCode::PureInt,), + ); + assert_eq!( + text_to_real("-0"), + (OwnedValue::Float(-0.0), CastTextToRealResultCode::PureInt,), + ); + assert_eq!( + text_to_real("-0"), + (OwnedValue::Float(0.0), CastTextToRealResultCode::PureInt,), + ); + assert_eq!( + text_to_real("-0.0"), + (OwnedValue::Float(0.0), CastTextToRealResultCode::HasDecimal,), + ); + assert_eq!( + text_to_real("0.0"), + (OwnedValue::Float(0.0), CastTextToRealResultCode::HasDecimal,), + ); + assert_eq!( + text_to_real("-"), + (OwnedValue::Float(0.0), CastTextToRealResultCode::NotValid,), + ); + } } diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 6b04ef462..01b6b5469 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -1,6 +1,6 @@ use std::num::NonZero; -use super::{AggFunc, BranchOffset, CursorID, FuncCtx, PageIdx}; +use super::{cast_text_to_numeric, AggFunc, BranchOffset, CursorID, FuncCtx, PageIdx}; use crate::storage::wal::CheckpointMode; use crate::types::{OwnedValue, Record}; use limbo_macros::Description; @@ -688,16 +688,6 @@ pub enum Cookie { UserVersion = 6, } -fn cast_text_to_numerical(value: &str) -> OwnedValue { - if let Ok(x) = value.parse::() { - OwnedValue::Integer(x) - } else if let Ok(x) = value.parse::() { - OwnedValue::Float(x) - } else { - OwnedValue::Integer(0) - } -} - pub fn exec_add(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { if let OwnedValue::Agg(agg) = lhs { lhs = agg.final_value(); @@ -719,11 +709,11 @@ pub fn exec_add(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { | (OwnedValue::Integer(i), OwnedValue::Float(f)) => OwnedValue::Float(*f + *i as f64), (OwnedValue::Null, _) | (_, OwnedValue::Null) => OwnedValue::Null, (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_add( - &cast_text_to_numerical(lhs.as_str()), - &cast_text_to_numerical(rhs.as_str()), + &cast_text_to_numeric(lhs.as_str()), + &cast_text_to_numeric(rhs.as_str()), ), (OwnedValue::Text(text), other) | (other, OwnedValue::Text(text)) => { - exec_add(&cast_text_to_numerical(text.as_str()), other) + exec_add(&cast_text_to_numeric(text.as_str()), other) } _ => todo!(), } @@ -750,14 +740,14 @@ pub fn exec_subtract(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { (OwnedValue::Integer(lhs), OwnedValue::Float(rhs)) => OwnedValue::Float(*lhs as f64 - rhs), (OwnedValue::Null, _) | (_, OwnedValue::Null) => OwnedValue::Null, (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_subtract( - &cast_text_to_numerical(lhs.as_str()), - &cast_text_to_numerical(rhs.as_str()), + &cast_text_to_numeric(lhs.as_str()), + &cast_text_to_numeric(rhs.as_str()), ), (OwnedValue::Text(text), other) => { - exec_subtract(&cast_text_to_numerical(text.as_str()), other) + exec_subtract(&cast_text_to_numeric(text.as_str()), other) } (other, OwnedValue::Text(text)) => { - exec_subtract(other, &cast_text_to_numerical(text.as_str())) + exec_subtract(other, &cast_text_to_numeric(text.as_str())) } _ => todo!(), } @@ -783,11 +773,11 @@ pub fn exec_multiply(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { | (OwnedValue::Float(f), OwnedValue::Integer(i)) => OwnedValue::Float(*i as f64 * { *f }), (OwnedValue::Null, _) | (_, OwnedValue::Null) => OwnedValue::Null, (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_multiply( - &cast_text_to_numerical(lhs.as_str()), - &cast_text_to_numerical(rhs.as_str()), + &cast_text_to_numeric(lhs.as_str()), + &cast_text_to_numeric(rhs.as_str()), ), (OwnedValue::Text(text), other) | (other, OwnedValue::Text(text)) => { - exec_multiply(&cast_text_to_numerical(text.as_str()), other) + exec_multiply(&cast_text_to_numeric(text.as_str()), other) } _ => todo!(), @@ -816,15 +806,11 @@ pub fn exec_divide(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { (OwnedValue::Integer(lhs), OwnedValue::Float(rhs)) => OwnedValue::Float(*lhs as f64 / rhs), (OwnedValue::Null, _) | (_, OwnedValue::Null) => OwnedValue::Null, (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_divide( - &cast_text_to_numerical(lhs.as_str()), - &cast_text_to_numerical(rhs.as_str()), + &cast_text_to_numeric(lhs.as_str()), + &cast_text_to_numeric(rhs.as_str()), ), - (OwnedValue::Text(text), other) => { - exec_divide(&cast_text_to_numerical(text.as_str()), other) - } - (other, OwnedValue::Text(text)) => { - exec_divide(other, &cast_text_to_numerical(text.as_str())) - } + (OwnedValue::Text(text), other) => exec_divide(&cast_text_to_numeric(text.as_str()), other), + (other, OwnedValue::Text(text)) => exec_divide(other, &cast_text_to_numeric(text.as_str())), _ => todo!(), } } @@ -849,11 +835,11 @@ pub fn exec_bit_and(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { (OwnedValue::Float(lh), OwnedValue::Integer(rh)) => OwnedValue::Integer(*lh as i64 & rh), (OwnedValue::Integer(lh), OwnedValue::Float(rh)) => OwnedValue::Integer(lh & *rh as i64), (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_bit_and( - &cast_text_to_numerical(lhs.as_str()), - &cast_text_to_numerical(rhs.as_str()), + &cast_text_to_numeric(lhs.as_str()), + &cast_text_to_numeric(rhs.as_str()), ), (OwnedValue::Text(text), other) | (other, OwnedValue::Text(text)) => { - exec_bit_and(&cast_text_to_numerical(text.as_str()), other) + exec_bit_and(&cast_text_to_numeric(text.as_str()), other) } _ => todo!(), } @@ -875,11 +861,11 @@ pub fn exec_bit_or(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { OwnedValue::Integer(*lh as i64 | *rh as i64) } (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_bit_or( - &cast_text_to_numerical(lhs.as_str()), - &cast_text_to_numerical(rhs.as_str()), + &cast_text_to_numeric(lhs.as_str()), + &cast_text_to_numeric(rhs.as_str()), ), (OwnedValue::Text(text), other) | (other, OwnedValue::Text(text)) => { - exec_bit_or(&cast_text_to_numerical(text.as_str()), other) + exec_bit_or(&cast_text_to_numeric(text.as_str()), other) } _ => todo!(), } @@ -939,7 +925,7 @@ pub fn exec_bit_not(mut reg: &OwnedValue) -> OwnedValue { OwnedValue::Null => OwnedValue::Null, OwnedValue::Integer(i) => OwnedValue::Integer(!i), OwnedValue::Float(f) => OwnedValue::Integer(!(*f as i64)), - OwnedValue::Text(text) => exec_bit_not(&cast_text_to_numerical(text.as_str())), + OwnedValue::Text(text) => exec_bit_not(&cast_text_to_numeric(text.as_str())), _ => todo!(), } } @@ -966,14 +952,14 @@ pub fn exec_shift_left(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue OwnedValue::Integer(compute_shl(*lh as i64, *rh as i64)) } (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_shift_left( - &cast_text_to_numerical(lhs.as_str()), - &cast_text_to_numerical(rhs.as_str()), + &cast_text_to_numeric(lhs.as_str()), + &cast_text_to_numeric(rhs.as_str()), ), (OwnedValue::Text(text), other) => { - exec_shift_left(&cast_text_to_numerical(text.as_str()), other) + exec_shift_left(&cast_text_to_numeric(text.as_str()), other) } (other, OwnedValue::Text(text)) => { - exec_shift_left(other, &cast_text_to_numerical(text.as_str())) + exec_shift_left(other, &cast_text_to_numeric(text.as_str())) } _ => todo!(), } @@ -1005,14 +991,14 @@ pub fn exec_shift_right(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValu OwnedValue::Integer(compute_shr(*lh as i64, *rh as i64)) } (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_shift_right( - &cast_text_to_numerical(lhs.as_str()), - &cast_text_to_numerical(rhs.as_str()), + &cast_text_to_numeric(lhs.as_str()), + &cast_text_to_numeric(rhs.as_str()), ), (OwnedValue::Text(text), other) => { - exec_shift_right(&cast_text_to_numerical(text.as_str()), other) + exec_shift_right(&cast_text_to_numeric(text.as_str()), other) } (other, OwnedValue::Text(text)) => { - exec_shift_right(other, &cast_text_to_numerical(text.as_str())) + exec_shift_right(other, &cast_text_to_numeric(text.as_str())) } _ => todo!(), } @@ -1043,7 +1029,7 @@ pub fn exec_boolean_not(mut reg: &OwnedValue) -> OwnedValue { OwnedValue::Null => OwnedValue::Null, OwnedValue::Integer(i) => OwnedValue::Integer((*i == 0) as i64), OwnedValue::Float(f) => OwnedValue::Integer((*f == 0.0) as i64), - OwnedValue::Text(text) => exec_boolean_not(&cast_text_to_numerical(text.as_str())), + OwnedValue::Text(text) => exec_boolean_not(&cast_text_to_numeric(text.as_str())), _ => todo!(), } } @@ -1125,11 +1111,11 @@ pub fn exec_and(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { | (OwnedValue::Float(0.0), _) => OwnedValue::Integer(0), (OwnedValue::Null, _) | (_, OwnedValue::Null) => OwnedValue::Null, (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_and( - &cast_text_to_numerical(lhs.as_str()), - &cast_text_to_numerical(rhs.as_str()), + &cast_text_to_numeric(lhs.as_str()), + &cast_text_to_numeric(rhs.as_str()), ), (OwnedValue::Text(text), other) | (other, OwnedValue::Text(text)) => { - exec_and(&cast_text_to_numerical(text.as_str()), other) + exec_and(&cast_text_to_numeric(text.as_str()), other) } _ => OwnedValue::Integer(1), } @@ -1154,11 +1140,11 @@ pub fn exec_or(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { | (OwnedValue::Float(0.0), OwnedValue::Float(0.0)) | (OwnedValue::Integer(0), OwnedValue::Integer(0)) => OwnedValue::Integer(0), (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_or( - &cast_text_to_numerical(lhs.as_str()), - &cast_text_to_numerical(rhs.as_str()), + &cast_text_to_numeric(lhs.as_str()), + &cast_text_to_numeric(rhs.as_str()), ), (OwnedValue::Text(text), other) | (other, OwnedValue::Text(text)) => { - exec_or(&cast_text_to_numerical(text.as_str()), other) + exec_or(&cast_text_to_numeric(text.as_str()), other) } _ => OwnedValue::Integer(1), } diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index a07145dc9..264ae770e 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -41,7 +41,10 @@ use crate::translate::plan::{ResultSetColumn, TableReference}; use crate::types::{ AggContext, Cursor, CursorResult, ExternalAggState, OwnedValue, Record, SeekKey, SeekOp, }; -use crate::util::parse_schema_rows; +use crate::util::{ + parse_schema_rows, text_to_integer, text_to_real, CastTextToIntResultCode, + CastTextToRealResultCode, +}; use crate::vdbe::builder::CursorType; use crate::vdbe::insn::Insn; use crate::vector::{vector32, vector64, vector_distance_cos, vector_extract}; @@ -3544,9 +3547,9 @@ fn exec_cast(value: &OwnedValue, datatype: &str) -> OwnedValue { OwnedValue::Blob(b) => { // Convert BLOB to TEXT first let text = String::from_utf8_lossy(b); - cast_text_to_real(&text) + cast_text_to_real(&text).0 } - OwnedValue::Text(t) => cast_text_to_real(t.as_str()), + OwnedValue::Text(t) => cast_text_to_real(t.as_str()).0, OwnedValue::Integer(i) => OwnedValue::Float(*i as f64), OwnedValue::Float(f) => OwnedValue::Float(*f), _ => OwnedValue::Float(0.0), @@ -3555,9 +3558,9 @@ fn exec_cast(value: &OwnedValue, datatype: &str) -> OwnedValue { OwnedValue::Blob(b) => { // Convert BLOB to TEXT first let text = String::from_utf8_lossy(b); - cast_text_to_integer(&text) + cast_text_to_integer(&text).0 } - OwnedValue::Text(t) => cast_text_to_integer(t.as_str()), + OwnedValue::Text(t) => cast_text_to_integer(t.as_str()).0, OwnedValue::Integer(i) => OwnedValue::Integer(*i), // A cast of a REAL value into an INTEGER results in the integer between the REAL value and zero // that is closest to the REAL value. If a REAL is greater than the greatest possible signed integer (+9223372036854775807) @@ -3629,48 +3632,16 @@ fn exec_replace(source: &OwnedValue, pattern: &OwnedValue, replacement: &OwnedVa /// When casting to INTEGER, if the text looks like a floating point value with an exponent, the exponent will be ignored /// because it is no part of the integer prefix. For example, "CAST('123e+5' AS INTEGER)" results in 123, not in 12300000. /// The CAST operator understands decimal integers only — conversion of hexadecimal integers stops at the "x" in the "0x" prefix of the hexadecimal integer string and thus result of the CAST is always zero. -fn cast_text_to_integer(text: &str) -> OwnedValue { - let text = text.trim(); - if text.is_empty() { - return OwnedValue::Integer(0); - } - if let Ok(i) = text.parse::() { - return OwnedValue::Integer(i); - } - // Try to find longest valid prefix that parses as an integer - // TODO: inefficient - let mut end_index = text.len().saturating_sub(1) as isize; - while end_index >= 0 { - if let Ok(i) = text[..=end_index as usize].parse::() { - return OwnedValue::Integer(i); - } - end_index -= 1; - } - OwnedValue::Integer(0) +fn cast_text_to_integer(text: &str) -> (OwnedValue, CastTextToIntResultCode) { + text_to_integer(text) } /// When casting a TEXT value to REAL, the longest possible prefix of the value that can be interpreted /// as a real number is extracted from the TEXT value and the remainder ignored. Any leading spaces in /// the TEXT value are ignored when converging from TEXT to REAL. /// If there is no prefix that can be interpreted as a real number, the result of the conversion is 0.0. -fn cast_text_to_real(text: &str) -> OwnedValue { - let trimmed = text.trim_start(); - if trimmed.is_empty() { - return OwnedValue::Float(0.0); - } - if let Ok(num) = trimmed.parse::() { - return OwnedValue::Float(num); - } - // Try to find longest valid prefix that parses as a float - // TODO: inefficient - let mut end_index = trimmed.len().saturating_sub(1) as isize; - while end_index >= 0 { - if let Ok(num) = trimmed[..=end_index as usize].parse::() { - return OwnedValue::Float(num); - } - end_index -= 1; - } - OwnedValue::Float(0.0) +fn cast_text_to_real(text: &str) -> (OwnedValue, CastTextToRealResultCode) { + text_to_real(text) } /// NUMERIC Casting a TEXT or BLOB value into NUMERIC yields either an INTEGER or a REAL result. @@ -3700,9 +3671,27 @@ fn checked_cast_text_to_numeric(text: &str) -> std::result::Result OwnedValue { - checked_cast_text_to_numeric(text).unwrap_or(OwnedValue::Integer(0)) + let (real_cast, rc_real) = cast_text_to_real(text); + let (int_cast, rc_int) = cast_text_to_integer(text); + match (rc_real, rc_int) { + ( + CastTextToRealResultCode::NotValid, + CastTextToIntResultCode::ExcessSpace + | CastTextToIntResultCode::Success + | CastTextToIntResultCode::NotInt, + ) => int_cast, + ( + CastTextToRealResultCode::NotValid, + CastTextToIntResultCode::TooLargeOrMalformed | CastTextToIntResultCode::SpecialCase, + ) => real_cast, + (CastTextToRealResultCode::NotValidButPrefix, _) => real_cast, + (CastTextToRealResultCode::PureInt, CastTextToIntResultCode::Success) => int_cast, + (CastTextToRealResultCode::HasDecimal, _) => real_cast, + _ => real_cast, + } } // Check if float can be losslessly converted to 51-bit integer