mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-06 00:34:23 +01:00
fix: math function parameter conversion
This commit is contained in:
@@ -46,6 +46,44 @@ pub enum Numeric {
|
||||
}
|
||||
|
||||
impl Numeric {
|
||||
pub fn from_value_strict(value: &Value) -> Numeric {
|
||||
match value {
|
||||
Value::Null | Value::Blob(_) => Self::Null,
|
||||
Value::Integer(v) => Self::Integer(*v),
|
||||
Value::Float(v) => match NonNan::new(*v) {
|
||||
Some(v) => Self::Float(v),
|
||||
None => Self::Null,
|
||||
},
|
||||
Value::Text(text) => {
|
||||
let s = text.as_str();
|
||||
|
||||
match str_to_f64(s) {
|
||||
None
|
||||
| Some(StrToF64::FractionalPrefix(_))
|
||||
| Some(StrToF64::DecimalPrefix(_)) => Self::Null,
|
||||
Some(StrToF64::Fractional(value)) => Self::Float(value),
|
||||
Some(StrToF64::Decimal(real)) => {
|
||||
let integer = str_to_i64(s).unwrap_or(0);
|
||||
|
||||
if real == integer as f64 {
|
||||
Self::Integer(integer)
|
||||
} else {
|
||||
Self::Float(real)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_into_f64(&self) -> Option<f64> {
|
||||
match self {
|
||||
Numeric::Null => None,
|
||||
Numeric::Integer(v) => Some(*v as _),
|
||||
Numeric::Float(v) => Some((*v).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_into_bool(&self) -> Option<bool> {
|
||||
match self {
|
||||
Numeric::Null => None,
|
||||
@@ -82,8 +120,10 @@ impl<T: AsRef<str>> From<T> for Numeric {
|
||||
|
||||
match str_to_f64(text) {
|
||||
None => Self::Integer(0),
|
||||
Some(StrToF64::Fractional(value)) => Self::Float(value),
|
||||
Some(StrToF64::Decimal(real)) => {
|
||||
Some(StrToF64::Fractional(value) | StrToF64::FractionalPrefix(value)) => {
|
||||
Self::Float(value)
|
||||
}
|
||||
Some(StrToF64::Decimal(real) | StrToF64::DecimalPrefix(real)) => {
|
||||
let integer = str_to_i64(text).unwrap_or(0);
|
||||
|
||||
if real == integer as f64 {
|
||||
@@ -460,9 +500,23 @@ pub fn str_to_i64(input: impl AsRef<str>) -> Option<i64> {
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StrToF64 {
|
||||
Fractional(NonNan),
|
||||
Decimal(NonNan),
|
||||
FractionalPrefix(NonNan),
|
||||
DecimalPrefix(NonNan),
|
||||
}
|
||||
|
||||
impl From<StrToF64> for f64 {
|
||||
fn from(value: StrToF64) -> Self {
|
||||
match value {
|
||||
StrToF64::Fractional(non_nan) => non_nan.into(),
|
||||
StrToF64::Decimal(non_nan) => non_nan.into(),
|
||||
StrToF64::FractionalPrefix(non_nan) => non_nan.into(),
|
||||
StrToF64::DecimalPrefix(non_nan) => non_nan.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn str_to_f64(input: impl AsRef<str>) -> Option<StrToF64> {
|
||||
@@ -480,10 +534,6 @@ pub fn str_to_f64(input: impl AsRef<str>) -> Option<StrToF64> {
|
||||
let mut had_digits = false;
|
||||
let mut is_fractional = false;
|
||||
|
||||
if matches!(input.peek(), Some('e' | 'E')) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut significant: u64 = 0;
|
||||
|
||||
// Copy as many significant digits as we can
|
||||
@@ -509,12 +559,12 @@ pub fn str_to_f64(input: impl AsRef<str>) -> Option<StrToF64> {
|
||||
}
|
||||
|
||||
if input.next_if(|ch| matches!(ch, '.')).is_some() {
|
||||
if matches!(input.peek(), Some('e' | 'E')) {
|
||||
return None;
|
||||
if had_digits {
|
||||
is_fractional = true;
|
||||
}
|
||||
|
||||
if had_digits || input.peek().is_some_and(char::is_ascii_digit) {
|
||||
is_fractional = true
|
||||
if input.peek().is_some_and(char::is_ascii_digit) {
|
||||
is_fractional = true;
|
||||
}
|
||||
|
||||
while let Some(digit) = input.peek().and_then(|ch| ch.to_digit(10)) {
|
||||
@@ -527,27 +577,32 @@ pub fn str_to_f64(input: impl AsRef<str>) -> Option<StrToF64> {
|
||||
}
|
||||
};
|
||||
|
||||
if input.next_if(|ch| matches!(ch, 'e' | 'E')).is_some() {
|
||||
let mut valid_exponent = true;
|
||||
|
||||
if (had_digits || is_fractional) && input.next_if(|ch| matches!(ch, 'e' | 'E')).is_some() {
|
||||
let sign = match input.next_if(|ch| matches!(ch, '-' | '+')) {
|
||||
Some('-') => -1,
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
if input.peek().is_some_and(char::is_ascii_digit) {
|
||||
is_fractional = true
|
||||
}
|
||||
is_fractional = true;
|
||||
let mut e = 0;
|
||||
|
||||
let e = input.map_while(|ch| ch.to_digit(10)).fold(0, |acc, digit| {
|
||||
if acc < 1000 {
|
||||
acc * 10 + digit as i32
|
||||
} else {
|
||||
1000
|
||||
while let Some(ch) = input.next_if(char::is_ascii_digit) {
|
||||
e = (e * 10 + ch.to_digit(10).unwrap() as i32).min(1000);
|
||||
}
|
||||
});
|
||||
|
||||
exponent += sign * e;
|
||||
exponent += sign * e;
|
||||
} else {
|
||||
valid_exponent = false;
|
||||
}
|
||||
};
|
||||
|
||||
if !(had_digits || is_fractional) {
|
||||
return None;
|
||||
}
|
||||
|
||||
while exponent.is_positive() && significant < MAX_EXACT / 10 {
|
||||
significant *= 10;
|
||||
exponent -= 1;
|
||||
@@ -591,6 +646,14 @@ pub fn str_to_f64(input: impl AsRef<str>) -> Option<StrToF64> {
|
||||
let result = NonNan::new(f64::from(result) * sign)
|
||||
.unwrap_or_else(|| NonNan::new(sign * f64::INFINITY).unwrap());
|
||||
|
||||
if !valid_exponent || input.count() > 0 {
|
||||
if is_fractional {
|
||||
return Some(StrToF64::FractionalPrefix(result));
|
||||
} else {
|
||||
return Some(StrToF64::DecimalPrefix(result));
|
||||
}
|
||||
}
|
||||
|
||||
Some(if is_fractional {
|
||||
StrToF64::Fractional(result)
|
||||
} else {
|
||||
|
||||
@@ -226,10 +226,7 @@ where
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
match crate::numeric::str_to_f64(s) {
|
||||
Some(result) => Ok(match result {
|
||||
crate::numeric::StrToF64::Fractional(non_nan) => non_nan.into(),
|
||||
crate::numeric::StrToF64::Decimal(non_nan) => non_nan.into(),
|
||||
}),
|
||||
Some(result) => Ok(result.into()),
|
||||
None => Err(serde::de::Error::custom("")),
|
||||
}
|
||||
}
|
||||
@@ -667,7 +664,7 @@ impl PartialEq<Value> for Value {
|
||||
match (self, other) {
|
||||
(Self::Integer(int_left), Self::Integer(int_right)) => int_left == int_right,
|
||||
(Self::Integer(int), Self::Float(float)) | (Self::Float(float), Self::Integer(int)) => {
|
||||
int_float_cmp(*int, *float).is_eq()
|
||||
sqlite_int_float_compare(*int, *float).is_eq()
|
||||
}
|
||||
(Self::Float(float_left), Self::Float(float_right)) => float_left == float_right,
|
||||
(Self::Integer(_) | Self::Float(_), Self::Text(_) | Self::Blob(_)) => false,
|
||||
@@ -682,25 +679,6 @@ impl PartialEq<Value> for Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn int_float_cmp(int: i64, float: f64) -> std::cmp::Ordering {
|
||||
if float.is_nan() {
|
||||
return std::cmp::Ordering::Greater;
|
||||
}
|
||||
|
||||
if float < -9223372036854775808.0 {
|
||||
return std::cmp::Ordering::Greater;
|
||||
}
|
||||
|
||||
if float >= 9223372036854775808.0 {
|
||||
return std::cmp::Ordering::Less;
|
||||
}
|
||||
|
||||
match int.cmp(&(float as i64)) {
|
||||
std::cmp::Ordering::Equal => (int as f64).total_cmp(&float),
|
||||
cmp => cmp,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::non_canonical_partial_ord_impl)]
|
||||
impl PartialOrd<Value> for Value {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![allow(unused)]
|
||||
use crate::incremental::view::IncrementalView;
|
||||
use crate::numeric::StrToF64;
|
||||
use crate::translate::expr::WalkControl;
|
||||
use crate::types::IOResult;
|
||||
use crate::{
|
||||
@@ -1185,8 +1186,12 @@ pub fn parse_numeric_literal(text: &str) -> Result<Value> {
|
||||
return Ok(Value::Integer(int_value));
|
||||
}
|
||||
|
||||
let float_value = text.parse::<f64>()?;
|
||||
Ok(Value::Float(float_value))
|
||||
let Some(StrToF64::Fractional(float) | StrToF64::Decimal(float)) =
|
||||
crate::numeric::str_to_f64(text)
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
Ok(Value::Float(float.into()))
|
||||
}
|
||||
|
||||
pub fn parse_signed_number(expr: &Expr) -> Result<Value> {
|
||||
|
||||
@@ -8217,27 +8217,19 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_f64(&self) -> Option<f64> {
|
||||
match self {
|
||||
Value::Integer(i) => Some(*i as f64),
|
||||
Value::Float(f) => Some(*f),
|
||||
Value::Text(t) => t.as_str().parse::<f64>().ok(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn exec_math_unary(&self, function: &MathFunc) -> Value {
|
||||
let v = Numeric::from_value_strict(self);
|
||||
|
||||
// In case of some functions and integer input, return the input as is
|
||||
if let Value::Integer(_) = self {
|
||||
if let Numeric::Integer(i) = v {
|
||||
if matches! { function, MathFunc::Ceil | MathFunc::Ceiling | MathFunc::Floor | MathFunc::Trunc }
|
||||
{
|
||||
return self.clone();
|
||||
return Value::Integer(i);
|
||||
}
|
||||
}
|
||||
|
||||
let f = match self.to_f64() {
|
||||
Some(f) => f,
|
||||
None => return Value::Null,
|
||||
let Some(f) = v.try_into_f64() else {
|
||||
return Value::Null;
|
||||
};
|
||||
|
||||
let result = match function {
|
||||
@@ -8274,14 +8266,12 @@ impl Value {
|
||||
}
|
||||
|
||||
fn exec_math_binary(&self, rhs: &Value, function: &MathFunc) -> Value {
|
||||
let lhs = match self.to_f64() {
|
||||
Some(f) => f,
|
||||
None => return Value::Null,
|
||||
let Some(lhs) = Numeric::from_value_strict(self).try_into_f64() else {
|
||||
return Value::Null;
|
||||
};
|
||||
|
||||
let rhs = match rhs.to_f64() {
|
||||
Some(f) => f,
|
||||
None => return Value::Null,
|
||||
let Some(rhs) = Numeric::from_value_strict(rhs).try_into_f64() else {
|
||||
return Value::Null;
|
||||
};
|
||||
|
||||
let result = match function {
|
||||
@@ -8299,16 +8289,13 @@ impl Value {
|
||||
}
|
||||
|
||||
fn exec_math_log(&self, base: Option<&Value>) -> Value {
|
||||
let f = match self.to_f64() {
|
||||
Some(f) => f,
|
||||
None => return Value::Null,
|
||||
let Some(f) = Numeric::from_value_strict(self).try_into_f64() else {
|
||||
return Value::Null;
|
||||
};
|
||||
|
||||
let base = match base {
|
||||
Some(base) => match base.to_f64() {
|
||||
Some(f) => f,
|
||||
None => return Value::Null,
|
||||
},
|
||||
let base = match base.map(|value| Numeric::from_value_strict(value).try_into_f64()) {
|
||||
Some(Some(f)) => f,
|
||||
Some(None) => return Value::Null,
|
||||
None => 10.0,
|
||||
};
|
||||
|
||||
@@ -8389,11 +8376,9 @@ impl Value {
|
||||
|
||||
pub fn exec_concat(&self, rhs: &Value) -> Value {
|
||||
if let (Value::Blob(lhs), Value::Blob(rhs)) = (self, rhs) {
|
||||
return Value::build_text(String::from_utf8_lossy(dbg!(&[
|
||||
lhs.as_slice(),
|
||||
rhs.as_slice()
|
||||
]
|
||||
.concat())));
|
||||
return Value::build_text(String::from_utf8_lossy(
|
||||
&[lhs.as_slice(), rhs.as_slice()].concat(),
|
||||
));
|
||||
}
|
||||
|
||||
let Some(lhs) = self.cast_text() else {
|
||||
|
||||
10
fuzz/Cargo.lock
generated
10
fuzz/Cargo.lock
generated
@@ -1182,7 +1182,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "turso_core"
|
||||
version = "0.1.5-pre.3"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"aegis",
|
||||
"aes",
|
||||
@@ -1225,7 +1225,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "turso_ext"
|
||||
version = "0.1.5-pre.3"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"getrandom 0.3.1",
|
||||
@@ -1234,7 +1234,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "turso_macros"
|
||||
version = "0.1.5-pre.3"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1243,7 +1243,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "turso_parser"
|
||||
version = "0.1.5-pre.3"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"miette",
|
||||
@@ -1255,7 +1255,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "turso_sqlite3_parser"
|
||||
version = "0.1.5-pre.3"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
|
||||
@@ -116,11 +116,26 @@ impl rusqlite::types::FromSql for Value {
|
||||
}
|
||||
}
|
||||
|
||||
str_enum! {
|
||||
enum UnaryFunc {
|
||||
Ceil => "ceil",
|
||||
Floor => "floor",
|
||||
}
|
||||
}
|
||||
|
||||
str_enum! {
|
||||
enum BinaryFunc {
|
||||
Power => "pow",
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Arbitrary)]
|
||||
enum Expr {
|
||||
Value(Value),
|
||||
Binary(Binary, Box<Expr>, Box<Expr>),
|
||||
Unary(Unary, Box<Expr>),
|
||||
UnaryFunc(UnaryFunc, Box<Expr>),
|
||||
BinaryFunc(BinaryFunc, Box<Expr>, Box<Expr>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -158,6 +173,26 @@ impl Expr {
|
||||
depth: lhs.depth.max(rhs.depth) + 1,
|
||||
}
|
||||
}
|
||||
Expr::BinaryFunc(func, lhs, rhs) => {
|
||||
let mut lhs = lhs.lower();
|
||||
let mut rhs = rhs.lower();
|
||||
Output {
|
||||
query: format!("{func}({}, {})", lhs.query, rhs.query),
|
||||
parameters: {
|
||||
lhs.parameters.append(&mut rhs.parameters);
|
||||
lhs.parameters
|
||||
},
|
||||
depth: lhs.depth.max(rhs.depth) + 1,
|
||||
}
|
||||
}
|
||||
Expr::UnaryFunc(func, expr) => {
|
||||
let expr = expr.lower();
|
||||
Output {
|
||||
query: format!("{func}({})", expr.query),
|
||||
parameters: expr.parameters,
|
||||
depth: expr.depth + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user