use rusqlite::{
Row,
types::{ToSql, ToSqlOutput, Value as SqlValue, ValueRef},
};
use toasty_core::stmt::{self, Value as CoreValue};
#[derive(Debug)]
pub(crate) struct Value(CoreValue);
impl From<CoreValue> for Value {
fn from(value: CoreValue) -> Self {
Self(value)
}
}
impl Value {
pub(crate) fn into_inner(self) -> CoreValue {
self.0
}
pub(crate) fn from_sql(row: &Row, index: usize, ty: &stmt::Type) -> Self {
let value: Option<SqlValue> = row.get(index).unwrap();
let core_value = match value {
Some(SqlValue::Null) | None => stmt::Value::Null,
Some(SqlValue::Integer(value)) => match ty {
stmt::Type::Bool => stmt::Value::Bool(value != 0),
stmt::Type::I8 => stmt::Value::I8(value as i8),
stmt::Type::I16 => stmt::Value::I16(value as i16),
stmt::Type::I32 => stmt::Value::I32(value as i32),
stmt::Type::I64 => stmt::Value::I64(value),
stmt::Type::U8 => stmt::Value::U8(value as u8),
stmt::Type::U16 => stmt::Value::U16(value as u16),
stmt::Type::U32 => stmt::Value::U32(value as u32),
stmt::Type::U64 => stmt::Value::U64(value as u64),
_ => todo!("ty={ty:#?}"),
},
Some(SqlValue::Real(value)) => match ty {
stmt::Type::F32 => stmt::Value::F32(value as f32),
stmt::Type::F64 => stmt::Value::F64(value),
_ => todo!("ty={ty:#?}"),
},
Some(SqlValue::Text(value)) => match ty {
stmt::Type::Uuid => stmt::Value::Uuid(value.parse().expect("text is a valid uuid")),
stmt::Type::List(elem) => json_text_to_value_list(&value, elem),
_ => stmt::Value::String(value),
},
Some(SqlValue::Blob(value)) => match ty {
stmt::Type::Bytes => stmt::Value::Bytes(value),
_ => todo!("value={value:#?}"),
},
};
Value(core_value)
}
pub(crate) fn from_sql_infer(row: &Row, index: usize) -> Self {
let value: Option<SqlValue> = row.get(index).unwrap();
let core_value = match value {
Some(SqlValue::Null) | None => stmt::Value::Null,
Some(SqlValue::Integer(value)) => stmt::Value::I64(value),
Some(SqlValue::Real(value)) => stmt::Value::F64(value),
Some(SqlValue::Text(value)) => stmt::Value::String(value),
Some(SqlValue::Blob(value)) => stmt::Value::Bytes(value),
};
Value(core_value)
}
}
impl ToSql for Value {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
use stmt::Value;
match &self.0 {
Value::Bool(true) => Ok(ToSqlOutput::Owned(SqlValue::Integer(1))),
Value::Bool(false) => Ok(ToSqlOutput::Owned(SqlValue::Integer(0))),
Value::I8(v) => Ok(ToSqlOutput::Owned(SqlValue::Integer(*v as i64))),
Value::I16(v) => Ok(ToSqlOutput::Owned(SqlValue::Integer(*v as i64))),
Value::I32(v) => Ok(ToSqlOutput::Owned(SqlValue::Integer(*v as i64))),
Value::I64(v) => Ok(ToSqlOutput::Owned(SqlValue::Integer(*v))),
Value::U8(v) => Ok(ToSqlOutput::Owned(SqlValue::Integer(*v as i64))),
Value::U16(v) => Ok(ToSqlOutput::Owned(SqlValue::Integer(*v as i64))),
Value::U32(v) => Ok(ToSqlOutput::Owned(SqlValue::Integer(*v as i64))),
Value::U64(v) => Ok(ToSqlOutput::Owned(SqlValue::Integer(*v as i64))),
Value::F32(v) => Ok(ToSqlOutput::Owned(SqlValue::Real(*v as f64))),
Value::F64(v) => Ok(ToSqlOutput::Owned(SqlValue::Real(*v))),
Value::String(v) => Ok(ToSqlOutput::Borrowed(ValueRef::Text(v.as_bytes()))),
Value::Bytes(v) => Ok(ToSqlOutput::Borrowed(ValueRef::Blob(&v[..]))),
Value::Null => Ok(ToSqlOutput::Owned(SqlValue::Null)),
Value::List(_) => Ok(ToSqlOutput::Owned(SqlValue::Text(value_list_to_json_text(
&self.0,
)))),
_ => todo!("value = {:#?}", self.0),
}
}
}
fn value_list_to_json_text(value: &CoreValue) -> String {
let json = toasty_sql::value_json::value_list_to_json(value);
serde_json::to_string(&json).expect("serialize Vec<scalar> to JSON")
}
fn json_text_to_value_list(text: &str, elem_ty: &stmt::Type) -> CoreValue {
let json: serde_json::Value =
serde_json::from_str(text).expect("SQLite returned non-JSON for a Vec<scalar> column");
toasty_sql::value_json::value_list_from_json(json, elem_ty)
}