use chrono::{Datelike, NaiveDate};
use vibesql_types::{DataType, SqlValue, TypeAffinity};
use crate::errors::ExecutorError;
pub fn coerce_to_date(value: &SqlValue) -> Result<SqlValue, ExecutorError> {
match value {
SqlValue::Date(_) => Ok(value.clone()),
SqlValue::Timestamp(ts) => Ok(SqlValue::Date(ts.date)),
SqlValue::Varchar(s) | SqlValue::Character(s) => parse_date_string(s),
SqlValue::Null => Ok(SqlValue::Null),
_ => Err(ExecutorError::TypeMismatch {
left: value.clone(),
op: "date coercion".to_string(),
right: SqlValue::Null,
}),
}
}
fn parse_date_string(s: &str) -> Result<SqlValue, ExecutorError> {
if let Ok(date) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
let vibe_date = vibesql_types::Date::new(date.year(), date.month() as u8, date.day() as u8)
.map_err(|e| ExecutorError::UnsupportedFeature(format!("Invalid date: {}", e)))?;
return Ok(SqlValue::Date(vibe_date));
}
Err(ExecutorError::UnsupportedFeature(format!(
"Cannot parse '{}' as date. Expected format: YYYY-MM-DD",
s
)))
}
pub fn coerce_value_to_column_type(val: SqlValue, col_type: &DataType) -> SqlValue {
let col_affinity = col_type.sqlite_affinity();
match (col_affinity, &val) {
(
TypeAffinity::Integer | TypeAffinity::Numeric,
SqlValue::Varchar(s) | SqlValue::Character(s),
) => {
if let Ok(i) = s.parse::<i64>() {
return SqlValue::Integer(i);
}
if let Ok(f) = s.parse::<f64>() {
return SqlValue::Double(f);
}
val
}
(TypeAffinity::Real, SqlValue::Varchar(s) | SqlValue::Character(s)) => {
if let Ok(f) = s.parse::<f64>() {
return SqlValue::Double(f);
}
val
}
(TypeAffinity::Text, SqlValue::Integer(i)) => {
SqlValue::Varchar(arcstr::ArcStr::from(i.to_string()))
}
(TypeAffinity::Text, SqlValue::Double(f)) => {
SqlValue::Varchar(arcstr::ArcStr::from(f.to_string()))
}
(TypeAffinity::Text, SqlValue::Float(f)) => {
SqlValue::Varchar(arcstr::ArcStr::from(f.to_string()))
}
(TypeAffinity::Text, SqlValue::Real(f)) => {
SqlValue::Varchar(arcstr::ArcStr::from(f.to_string()))
}
_ => val,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_coerce_date_passthrough() {
let date = vibesql_types::Date::new(2024, 1, 15).unwrap();
let result = coerce_to_date(&SqlValue::Date(date)).unwrap();
assert_eq!(result, SqlValue::Date(date));
}
#[test]
fn test_coerce_timestamp_to_date() {
let date = vibesql_types::Date::new(2024, 1, 15).unwrap();
let time = vibesql_types::Time::new(10, 30, 45, 0).unwrap();
let timestamp = vibesql_types::Timestamp::new(date, time);
let result = coerce_to_date(&SqlValue::Timestamp(timestamp)).unwrap();
assert_eq!(result, SqlValue::Date(date));
}
#[test]
fn test_coerce_varchar_to_date() {
let result =
coerce_to_date(&SqlValue::Varchar(arcstr::ArcStr::from("2024-01-15"))).unwrap();
let expected = vibesql_types::Date::new(2024, 1, 15).unwrap();
assert_eq!(result, SqlValue::Date(expected));
}
#[test]
fn test_coerce_character_to_date() {
let result =
coerce_to_date(&SqlValue::Character(arcstr::ArcStr::from("2024-12-31"))).unwrap();
let expected = vibesql_types::Date::new(2024, 12, 31).unwrap();
assert_eq!(result, SqlValue::Date(expected));
}
#[test]
fn test_coerce_null_returns_null() {
let result = coerce_to_date(&SqlValue::Null).unwrap();
assert_eq!(result, SqlValue::Null);
}
#[test]
fn test_coerce_invalid_date_string() {
let result = coerce_to_date(&SqlValue::Varchar(arcstr::ArcStr::from("not-a-date")));
assert!(result.is_err());
}
#[test]
fn test_coerce_invalid_date_format() {
let result = coerce_to_date(&SqlValue::Varchar(arcstr::ArcStr::from("01/15/2024")));
assert!(result.is_err());
}
#[test]
fn test_coerce_invalid_date_values() {
let result = coerce_to_date(&SqlValue::Varchar(arcstr::ArcStr::from("2024-13-01")));
assert!(result.is_err());
let result = coerce_to_date(&SqlValue::Varchar(arcstr::ArcStr::from("2024-02-30")));
assert!(result.is_err());
}
#[test]
fn test_coerce_integer_returns_error() {
let result = coerce_to_date(&SqlValue::Integer(20240115));
assert!(result.is_err());
}
#[test]
fn test_coerce_leap_year_date() {
let result =
coerce_to_date(&SqlValue::Varchar(arcstr::ArcStr::from("2024-02-29"))).unwrap();
let expected = vibesql_types::Date::new(2024, 2, 29).unwrap();
assert_eq!(result, SqlValue::Date(expected));
}
#[test]
fn test_coerce_non_leap_year_feb_29() {
let result = coerce_to_date(&SqlValue::Varchar(arcstr::ArcStr::from("2023-02-29")));
assert!(result.is_err());
}
#[test]
fn test_pk_coerce_text_column_integer_literal() {
let result = coerce_value_to_column_type(
SqlValue::Integer(1200),
&DataType::Varchar { max_length: Some(255) },
);
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("1200")));
}
#[test]
fn test_pk_coerce_integer_column_string_literal() {
let result = coerce_value_to_column_type(
SqlValue::Varchar(arcstr::ArcStr::from("12")),
&DataType::Integer,
);
assert_eq!(result, SqlValue::Integer(12));
}
#[test]
fn test_pk_coerce_integer_column_unparseable_string() {
let result = coerce_value_to_column_type(
SqlValue::Varchar(arcstr::ArcStr::from("foo")),
&DataType::Integer,
);
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("foo")));
}
#[test]
fn test_pk_coerce_text_column_text_literal_passthrough() {
let result = coerce_value_to_column_type(
SqlValue::Varchar(arcstr::ArcStr::from("1200")),
&DataType::Varchar { max_length: Some(255) },
);
assert_eq!(result, SqlValue::Varchar(arcstr::ArcStr::from("1200")));
}
#[test]
fn test_pk_coerce_integer_column_integer_literal_passthrough() {
let result = coerce_value_to_column_type(SqlValue::Integer(12), &DataType::Integer);
assert_eq!(result, SqlValue::Integer(12));
}
#[test]
fn test_pk_coerce_real_column_string_literal() {
let result = coerce_value_to_column_type(
SqlValue::Varchar(arcstr::ArcStr::from("1.5")),
&DataType::Real,
);
assert_eq!(result, SqlValue::Double(1.5));
}
}