Fold a CAST at plan time. Only applies when the inner expression is already a constant. The `to_type` string comes from sqlparser's `format!("{data_type}")` output, so parameterised types like `NUMERIC(5,1)` must be matched by prefix.
(inner: SqlValue, to_type: &str)
| 71 | /// a constant. The `to_type` string comes from sqlparser's `format!("{data_type}")` |
| 72 | /// output, so parameterised types like `NUMERIC(5,1)` must be matched by prefix. |
| 73 | fn fold_cast(inner: SqlValue, to_type: &str) -> Option<SqlValue> { |
| 74 | let upper = to_type.to_uppercase(); |
| 75 | // Strip any precision/scale suffix: "NUMERIC(5,1)" → "NUMERIC". |
| 76 | let base = upper |
| 77 | .split('(') |
| 78 | .next() |
| 79 | .map(str::trim) |
| 80 | .unwrap_or(upper.as_str()); |
| 81 | |
| 82 | match base { |
| 83 | "NUMERIC" | "DECIMAL" => match inner { |
| 84 | SqlValue::Decimal(d) => Some(SqlValue::Decimal(d)), |
| 85 | SqlValue::Int(i) => Some(SqlValue::Decimal(rust_decimal::Decimal::from(i))), |
| 86 | SqlValue::Float(f) => rust_decimal::Decimal::try_from(f) |
| 87 | .ok() |
| 88 | .map(SqlValue::Decimal), |
| 89 | SqlValue::String(s) => rust_decimal::Decimal::from_str_exact(&s) |
| 90 | .ok() |
| 91 | .map(SqlValue::Decimal), |
| 92 | _ => None, |
| 93 | }, |
| 94 | "INTEGER" | "INT" | "BIGINT" | "SMALLINT" | "INT2" | "INT4" | "INT8" => match inner { |
| 95 | SqlValue::Int(i) => Some(SqlValue::Int(i)), |
| 96 | SqlValue::Decimal(d) => { |
| 97 | rust_decimal::prelude::ToPrimitive::to_i64(&d).map(SqlValue::Int) |
| 98 | } |
| 99 | SqlValue::Float(f) => { |
| 100 | if f.is_finite() { |
| 101 | Some(SqlValue::Int(f as i64)) |
| 102 | } else { |
| 103 | None |
| 104 | } |
| 105 | } |
| 106 | SqlValue::String(s) => s.parse::<i64>().ok().map(SqlValue::Int), |
| 107 | _ => None, |
| 108 | }, |
| 109 | "FLOAT" | "DOUBLE" | "REAL" | "FLOAT4" | "FLOAT8" | "DOUBLE PRECISION" => match inner { |
| 110 | SqlValue::Float(f) => Some(SqlValue::Float(f)), |
| 111 | SqlValue::Int(i) => Some(SqlValue::Float(i as f64)), |
| 112 | SqlValue::Decimal(d) => { |
| 113 | rust_decimal::prelude::ToPrimitive::to_f64(&d).map(SqlValue::Float) |
| 114 | } |
| 115 | SqlValue::String(s) => s.parse::<f64>().ok().map(SqlValue::Float), |
| 116 | _ => None, |
| 117 | }, |
| 118 | "TEXT" | "VARCHAR" | "CHAR" | "CHARACTER VARYING" | "CHARACTER" | "BPCHAR" => match inner { |
| 119 | SqlValue::String(s) => Some(SqlValue::String(s)), |
| 120 | SqlValue::Int(i) => Some(SqlValue::String(i.to_string())), |
| 121 | SqlValue::Float(f) => Some(SqlValue::String(f.to_string())), |
| 122 | SqlValue::Decimal(d) => Some(SqlValue::String(d.to_string())), |
| 123 | SqlValue::Bool(b) => Some(SqlValue::String(b.to_string())), |
| 124 | _ => None, |
| 125 | }, |
| 126 | "BOOL" | "BOOLEAN" => match inner { |
| 127 | SqlValue::Bool(b) => Some(SqlValue::Bool(b)), |
| 128 | SqlValue::Int(i) => Some(SqlValue::Bool(i != 0)), |
| 129 | SqlValue::String(s) => match s.to_lowercase().as_str() { |
| 130 | "true" | "t" | "yes" | "1" | "on" => Some(SqlValue::Bool(true)), |