feat: add CAST to fuzzer

This commit is contained in:
Levy A.
2025-09-24 16:37:35 -03:00
parent 85e0f1444d
commit 5dfd67b118
5 changed files with 60 additions and 19 deletions

View File

@@ -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),

View File

@@ -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;

View File

@@ -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!

View File

@@ -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
},
}
}

View File

@@ -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,
}
}
}
}
}