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 { pub enum StrToF64 {
Fractional(NonNan), Fractional(NonNan),
Decimal(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. /// 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 { 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 // Rule 1: INT -> INTEGER affinity
if datatype.contains("INT") { if datatype.contains("INT") {
return Affinity::Integer; return Affinity::Integer;

View File

@@ -662,7 +662,7 @@ pub fn translate_expr(
ast::Expr::Cast { expr, type_name } => { ast::Expr::Cast { expr, type_name } => {
let type_name = type_name.as_ref().unwrap(); // TODO: why is this optional? let type_name = type_name.as_ref().unwrap(); // TODO: why is this optional?
translate_expr(program, referenced_tables, expr, target_register, resolver)?; 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 { program.emit_insn(Insn::Cast {
reg: target_register, reg: target_register,
affinity: type_affinity, affinity: type_affinity,
@@ -3433,6 +3433,9 @@ pub fn get_expr_affinity(
Affinity::Blob 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), ast::Expr::Collate(expr, _) => get_expr_affinity(expr, referenced_tables),
// Literals have NO affinity in SQLite! // Literals have NO affinity in SQLite!
ast::Expr::Literal(_) => Affinity::Blob, // No affinity! ast::Expr::Literal(_) => Affinity::Blob, // No affinity!

View File

@@ -55,10 +55,7 @@ use crate::{
AggContext, Cursor, ExternalAggState, IOResult, SeekKey, SeekOp, SumAggState, Value, AggContext, Cursor, ExternalAggState, IOResult, SeekKey, SeekOp, SumAggState, Value,
ValueType, ValueType,
}, },
util::{ util::{cast_real_to_integer, checked_cast_text_to_numeric, parse_schema_rows},
cast_real_to_integer, cast_text_to_integer, cast_text_to_numeric, cast_text_to_real,
checked_cast_text_to_numeric, parse_schema_rows,
},
vdbe::{ vdbe::{
builder::CursorType, builder::CursorType,
insn::{IdxInsertFlags, Insn}, insn::{IdxInsertFlags, Insn},
@@ -8180,11 +8177,16 @@ impl Value {
} }
Affinity::Real => match self { Affinity::Real => match self {
Value::Blob(b) => { Value::Blob(b) => {
// Convert BLOB to TEXT first
let text = String::from_utf8_lossy(b); 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::Integer(i) => Value::Float(*i as f64),
Value::Float(f) => Value::Float(*f), Value::Float(f) => Value::Float(*f),
_ => Value::Float(0.0), _ => Value::Float(0.0),
@@ -8193,9 +8195,9 @@ impl Value {
Value::Blob(b) => { Value::Blob(b) => {
// Convert BLOB to TEXT first // Convert BLOB to TEXT first
let text = String::from_utf8_lossy(b); 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), 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 // 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) // 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), _ => Value::Integer(0),
}, },
Affinity::Numeric => match self { Affinity::Numeric => match self {
Value::Blob(b) => { Value::Null => Value::Null,
let text = String::from_utf8_lossy(b); Value::Integer(v) => Value::Integer(*v),
cast_text_to_numeric(&text) 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)] #[derive(Debug, Arbitrary)]
enum Expr { enum Expr {
Value(Value), Value(Value),
Binary(Binary, Box<Expr>, Box<Expr>), Binary(Binary, Box<Expr>, Box<Expr>),
Unary(Unary, Box<Expr>), Unary(Unary, Box<Expr>),
Cast(Box<Expr>, CastType),
UnaryFunc(UnaryFunc, Box<Expr>), UnaryFunc(UnaryFunc, Box<Expr>),
BinaryFunc(BinaryFunc, Box<Expr>, Box<Expr>), BinaryFunc(BinaryFunc, Box<Expr>, Box<Expr>),
} }
@@ -229,6 +239,14 @@ impl Expr {
depth: expr.depth + 1, 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,
}
}
} }
} }
} }