mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-17 00:24:21 +01:00
feat: add CAST to fuzzer
This commit is contained in:
@@ -500,7 +500,7 @@ pub fn str_to_i64(input: impl AsRef<str>) -> Option<i64> {
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum StrToF64 {
|
||||
Fractional(NonNan),
|
||||
Decimal(NonNan),
|
||||
|
||||
@@ -1318,7 +1318,8 @@ impl From<&ColumnDefinition> for Column {
|
||||
///
|
||||
/// Note that the order of the rules for determining column affinity is important. A column whose declared type is "CHARINT" will match both rules 1 and 2 but the first rule takes precedence and so the column affinity will be INTEGER.
|
||||
pub fn affinity(datatype: &str) -> Affinity {
|
||||
// Note: callers of this function must ensure that the datatype is uppercase.
|
||||
let datatype = datatype.to_ascii_uppercase();
|
||||
|
||||
// Rule 1: INT -> INTEGER affinity
|
||||
if datatype.contains("INT") {
|
||||
return Affinity::Integer;
|
||||
|
||||
@@ -662,7 +662,7 @@ pub fn translate_expr(
|
||||
ast::Expr::Cast { expr, type_name } => {
|
||||
let type_name = type_name.as_ref().unwrap(); // TODO: why is this optional?
|
||||
translate_expr(program, referenced_tables, expr, target_register, resolver)?;
|
||||
let type_affinity = affinity(&type_name.name.to_uppercase());
|
||||
let type_affinity = affinity(&type_name.name);
|
||||
program.emit_insn(Insn::Cast {
|
||||
reg: target_register,
|
||||
affinity: type_affinity,
|
||||
@@ -3433,6 +3433,9 @@ pub fn get_expr_affinity(
|
||||
Affinity::Blob
|
||||
}
|
||||
}
|
||||
ast::Expr::Parenthesized(exprs) if exprs.len() == 1 => {
|
||||
get_expr_affinity(exprs.first().unwrap(), referenced_tables)
|
||||
}
|
||||
ast::Expr::Collate(expr, _) => get_expr_affinity(expr, referenced_tables),
|
||||
// Literals have NO affinity in SQLite!
|
||||
ast::Expr::Literal(_) => Affinity::Blob, // No affinity!
|
||||
|
||||
@@ -55,10 +55,7 @@ use crate::{
|
||||
AggContext, Cursor, ExternalAggState, IOResult, SeekKey, SeekOp, SumAggState, Value,
|
||||
ValueType,
|
||||
},
|
||||
util::{
|
||||
cast_real_to_integer, cast_text_to_integer, cast_text_to_numeric, cast_text_to_real,
|
||||
checked_cast_text_to_numeric, parse_schema_rows,
|
||||
},
|
||||
util::{cast_real_to_integer, checked_cast_text_to_numeric, parse_schema_rows},
|
||||
vdbe::{
|
||||
builder::CursorType,
|
||||
insn::{IdxInsertFlags, Insn},
|
||||
@@ -8180,11 +8177,16 @@ impl Value {
|
||||
}
|
||||
Affinity::Real => match self {
|
||||
Value::Blob(b) => {
|
||||
// Convert BLOB to TEXT first
|
||||
let text = String::from_utf8_lossy(b);
|
||||
cast_text_to_real(&text)
|
||||
Value::Float(
|
||||
crate::numeric::str_to_f64(&text)
|
||||
.map(f64::from)
|
||||
.unwrap_or(0.0),
|
||||
)
|
||||
}
|
||||
Value::Text(t) => {
|
||||
Value::Float(crate::numeric::str_to_f64(t).map(f64::from).unwrap_or(0.0))
|
||||
}
|
||||
Value::Text(t) => cast_text_to_real(t.as_str()),
|
||||
Value::Integer(i) => Value::Float(*i as f64),
|
||||
Value::Float(f) => Value::Float(*f),
|
||||
_ => Value::Float(0.0),
|
||||
@@ -8193,9 +8195,9 @@ impl Value {
|
||||
Value::Blob(b) => {
|
||||
// Convert BLOB to TEXT first
|
||||
let text = String::from_utf8_lossy(b);
|
||||
cast_text_to_integer(&text)
|
||||
Value::Integer(crate::numeric::str_to_i64(&text).unwrap_or(0))
|
||||
}
|
||||
Value::Text(t) => cast_text_to_integer(t.as_str()),
|
||||
Value::Text(t) => Value::Integer(crate::numeric::str_to_i64(t).unwrap_or(0)),
|
||||
Value::Integer(i) => Value::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)
|
||||
@@ -8214,14 +8216,31 @@ impl Value {
|
||||
_ => Value::Integer(0),
|
||||
},
|
||||
Affinity::Numeric => match self {
|
||||
Value::Blob(b) => {
|
||||
let text = String::from_utf8_lossy(b);
|
||||
cast_text_to_numeric(&text)
|
||||
Value::Null => Value::Null,
|
||||
Value::Integer(v) => Value::Integer(*v),
|
||||
Value::Float(v) => Self::Float(*v),
|
||||
_ => {
|
||||
let s = match self {
|
||||
Value::Text(text) => text.to_string(),
|
||||
Value::Blob(blob) => String::from_utf8_lossy(blob.as_slice()).to_string(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match crate::numeric::str_to_f64(&s) {
|
||||
Some(parsed) => {
|
||||
let Some(int) = crate::numeric::str_to_i64(&s) else {
|
||||
return Value::Integer(0);
|
||||
};
|
||||
|
||||
if f64::from(parsed) == int as f64 {
|
||||
return Value::Integer(int);
|
||||
}
|
||||
|
||||
Value::Float(parsed.into())
|
||||
}
|
||||
None => Value::Integer(0),
|
||||
}
|
||||
}
|
||||
Value::Text(t) => cast_text_to_numeric(t.as_str()),
|
||||
Value::Integer(i) => Value::Integer(*i),
|
||||
Value::Float(f) => Value::Float(*f),
|
||||
_ => self.clone(), // TODO probably wrong
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,11 +165,21 @@ str_enum! {
|
||||
}
|
||||
}
|
||||
|
||||
str_enum! {
|
||||
enum CastType {
|
||||
Text => "text",
|
||||
Real => "real",
|
||||
Integer => "integer",
|
||||
Numeric => "numeric",
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Arbitrary)]
|
||||
enum Expr {
|
||||
Value(Value),
|
||||
Binary(Binary, Box<Expr>, Box<Expr>),
|
||||
Unary(Unary, Box<Expr>),
|
||||
Cast(Box<Expr>, CastType),
|
||||
UnaryFunc(UnaryFunc, Box<Expr>),
|
||||
BinaryFunc(BinaryFunc, Box<Expr>, Box<Expr>),
|
||||
}
|
||||
@@ -229,6 +239,14 @@ impl Expr {
|
||||
depth: expr.depth + 1,
|
||||
}
|
||||
}
|
||||
Expr::Cast(expr, cast_type) => {
|
||||
let expr = expr.lower();
|
||||
Output {
|
||||
query: format!("cast({} as {cast_type})", expr.query),
|
||||
parameters: expr.parameters,
|
||||
depth: expr.depth + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user