use std::collections::HashMap;
use prax_query::row::{RowError, RowRef};
use sqlx::{Column, Row, TypeInfo};
use crate::row::SqlxRow;
enum Value {
Null,
Bool(bool),
I64(i64),
F64(f64),
Text(String),
Bytes(Vec<u8>),
DateTimeUtc(chrono::DateTime<chrono::Utc>),
NaiveDateTime(chrono::NaiveDateTime),
NaiveDate(chrono::NaiveDate),
NaiveTime(chrono::NaiveTime),
Uuid(uuid::Uuid),
Json(serde_json::Value),
Decimal(rust_decimal::Decimal),
}
pub struct SqlxRowRef {
values: HashMap<String, Value>,
}
impl SqlxRowRef {
pub fn from_sqlx(row: &SqlxRow) -> Result<Self, RowError> {
let mut values = HashMap::new();
match row {
#[cfg(feature = "postgres")]
SqlxRow::Postgres(r) => {
for (i, col) in r.columns().iter().enumerate() {
let name = col.name().to_string();
let v = decode_pg_cell(r, i)?;
values.insert(name, v);
}
}
#[cfg(feature = "mysql")]
SqlxRow::MySql(r) => {
for (i, col) in r.columns().iter().enumerate() {
let name = col.name().to_string();
let v = decode_mysql_cell(r, i)?;
values.insert(name, v);
}
}
#[cfg(feature = "sqlite")]
SqlxRow::Sqlite(r) => {
for (i, col) in r.columns().iter().enumerate() {
let name = col.name().to_string();
let v = decode_sqlite_cell(r, i)?;
values.insert(name, v);
}
}
}
Ok(Self { values })
}
}
fn tc(column: &str, msg: impl Into<String>) -> RowError {
RowError::TypeConversion {
column: column.into(),
message: msg.into(),
}
}
macro_rules! decode_cell {
($row:expr, $idx:expr, $column:expr, $ty:ty, $wrap:expr) => {
match $row.try_get::<Option<$ty>, _>($idx) {
Ok(None) => Ok(Value::Null),
Ok(Some(v)) => Ok(($wrap)(v)),
Err(e) => Err(tc($column, e.to_string())),
}
};
}
#[cfg(feature = "postgres")]
fn decode_pg_cell(r: &sqlx::postgres::PgRow, i: usize) -> Result<Value, RowError> {
let column = r.columns()[i].name();
match r.columns()[i].type_info().name() {
"TEXT" | "VARCHAR" | "CHAR" | "NAME" | "UNKNOWN" => {
decode_cell!(r, i, column, String, Value::Text)
}
"BOOL" => decode_cell!(r, i, column, bool, Value::Bool),
"INT2" => decode_cell!(r, i, column, i16, |v| Value::I64(v as i64)),
"INT4" => decode_cell!(r, i, column, i32, |v| Value::I64(v as i64)),
"INT8" => decode_cell!(r, i, column, i64, Value::I64),
"FLOAT4" => decode_cell!(r, i, column, f32, |v| Value::F64(v as f64)),
"FLOAT8" => decode_cell!(r, i, column, f64, Value::F64),
"NUMERIC" => decode_cell!(r, i, column, rust_decimal::Decimal, Value::Decimal),
"TIMESTAMPTZ" => {
decode_cell!(
r,
i,
column,
chrono::DateTime<chrono::Utc>,
Value::DateTimeUtc
)
}
"TIMESTAMP" => decode_cell!(r, i, column, chrono::NaiveDateTime, Value::NaiveDateTime),
"DATE" => decode_cell!(r, i, column, chrono::NaiveDate, Value::NaiveDate),
"TIME" => decode_cell!(r, i, column, chrono::NaiveTime, Value::NaiveTime),
"UUID" => decode_cell!(r, i, column, uuid::Uuid, Value::Uuid),
"JSON" | "JSONB" => decode_cell!(r, i, column, serde_json::Value, Value::Json),
"BYTEA" => decode_cell!(r, i, column, Vec<u8>, Value::Bytes),
other => {
match r.try_get::<Option<String>, _>(i) {
Ok(None) => Ok(Value::Null),
Ok(Some(s)) => Ok(Value::Text(s)),
Err(e) => Err(tc(
column,
format!("unsupported Postgres type {other}: {e}"),
)),
}
}
}
}
#[cfg(feature = "mysql")]
fn decode_mysql_cell(r: &sqlx::mysql::MySqlRow, i: usize) -> Result<Value, RowError> {
let column = r.columns()[i].name();
let ty = r.columns()[i].type_info().name();
match ty {
"CHAR" | "VARCHAR" | "TINYTEXT" | "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "ENUM" => {
decode_cell!(r, i, column, String, Value::Text)
}
"BOOLEAN" => decode_cell!(r, i, column, bool, Value::Bool),
"TINYINT" | "SMALLINT" | "MEDIUMINT" | "INT" | "BIGINT" => {
decode_cell!(r, i, column, i64, Value::I64)
}
_ if ty.ends_with(" UNSIGNED") || ty == "YEAR" || ty == "BIT" => {
match r.try_get::<Option<u64>, _>(i) {
Ok(None) => Ok(Value::Null),
Ok(Some(v)) => i64::try_from(v)
.map(Value::I64)
.map_err(|_| tc(column, "u64 value overflows i64")),
Err(e) => Err(tc(column, e.to_string())),
}
}
"FLOAT" | "DOUBLE" => decode_cell!(r, i, column, f64, Value::F64),
"DECIMAL" => decode_cell!(r, i, column, rust_decimal::Decimal, Value::Decimal),
"TIMESTAMP" => {
decode_cell!(
r,
i,
column,
chrono::DateTime<chrono::Utc>,
Value::DateTimeUtc
)
}
"DATETIME" => decode_cell!(r, i, column, chrono::NaiveDateTime, Value::NaiveDateTime),
"DATE" => decode_cell!(r, i, column, chrono::NaiveDate, Value::NaiveDate),
"TIME" => decode_cell!(r, i, column, chrono::NaiveTime, Value::NaiveTime),
"JSON" => decode_cell!(r, i, column, serde_json::Value, Value::Json),
"BINARY" | "VARBINARY" | "TINYBLOB" | "BLOB" | "MEDIUMBLOB" | "LONGBLOB" => {
decode_cell!(r, i, column, Vec<u8>, Value::Bytes)
}
other => match r.try_get::<Option<String>, _>(i) {
Ok(None) => Ok(Value::Null),
Ok(Some(s)) => Ok(Value::Text(s)),
Err(e) => Err(tc(column, format!("unsupported MySQL type {other}: {e}"))),
},
}
}
#[cfg(feature = "sqlite")]
fn decode_sqlite_cell(r: &sqlx::sqlite::SqliteRow, i: usize) -> Result<Value, RowError> {
let column = r.columns()[i].name();
match r.columns()[i].type_info().name() {
"TEXT" => decode_cell!(r, i, column, String, Value::Text),
"INTEGER" => decode_cell!(r, i, column, i64, Value::I64),
"REAL" => decode_cell!(r, i, column, f64, Value::F64),
"BLOB" => decode_cell!(r, i, column, Vec<u8>, Value::Bytes),
"BOOLEAN" => decode_cell!(r, i, column, bool, Value::Bool),
"DATETIME" => decode_cell!(r, i, column, chrono::NaiveDateTime, Value::NaiveDateTime),
"DATE" => decode_cell!(r, i, column, chrono::NaiveDate, Value::NaiveDate),
"TIME" => decode_cell!(r, i, column, chrono::NaiveTime, Value::NaiveTime),
_ => {
if r.try_get::<Option<Vec<u8>>, _>(i)
.is_ok_and(|v| v.is_none())
{
return Ok(Value::Null);
}
if let Ok(Some(s)) = r.try_get::<Option<String>, _>(i) {
return Ok(Value::Text(s));
}
if let Ok(Some(n)) = r.try_get::<Option<i64>, _>(i) {
return Ok(Value::I64(n));
}
if let Ok(Some(f)) = r.try_get::<Option<f64>, _>(i) {
return Ok(Value::F64(f));
}
if let Ok(Some(b)) = r.try_get::<Option<Vec<u8>>, _>(i) {
return Ok(Value::Bytes(b));
}
Err(tc(column, "unsupported SQLite value"))
}
}
}
impl RowRef for SqlxRowRef {
fn get_i32(&self, c: &str) -> Result<i32, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::I64(i) => i32::try_from(*i).map_err(|_| tc(c, "i64 overflow")),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not an integer")),
}
}
fn get_i32_opt(&self, c: &str) -> Result<Option<i32>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::I64(i)) => i32::try_from(*i)
.map(Some)
.map_err(|_| tc(c, "i64 overflow")),
Some(_) => Err(tc(c, "not an integer")),
}
}
fn get_i64(&self, c: &str) -> Result<i64, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::I64(i) => Ok(*i),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not an integer")),
}
}
fn get_i64_opt(&self, c: &str) -> Result<Option<i64>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::I64(i)) => Ok(Some(*i)),
Some(_) => Err(tc(c, "not an integer")),
}
}
fn get_f64(&self, c: &str) -> Result<f64, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::F64(f) => Ok(*f),
Value::I64(i) => Ok(*i as f64),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not a number")),
}
}
fn get_f64_opt(&self, c: &str) -> Result<Option<f64>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::F64(f)) => Ok(Some(*f)),
Some(Value::I64(i)) => Ok(Some(*i as f64)),
Some(_) => Err(tc(c, "not a number")),
}
}
fn get_bool(&self, c: &str) -> Result<bool, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::Bool(b) => Ok(*b),
Value::I64(i) => Ok(*i != 0),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not a boolean")),
}
}
fn get_bool_opt(&self, c: &str) -> Result<Option<bool>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::Bool(b)) => Ok(Some(*b)),
Some(Value::I64(i)) => Ok(Some(*i != 0)),
Some(_) => Err(tc(c, "not a boolean")),
}
}
fn get_str(&self, c: &str) -> Result<&str, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::Text(s) => Ok(s.as_str()),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not text")),
}
}
fn get_str_opt(&self, c: &str) -> Result<Option<&str>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::Text(s)) => Ok(Some(s.as_str())),
Some(_) => Err(tc(c, "not text")),
}
}
fn get_string(&self, c: &str) -> Result<String, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::Text(s) => Ok(s.clone()),
Value::Uuid(u) => Ok(u.to_string()),
Value::Decimal(d) => Ok(d.to_string()),
Value::Json(j) => Ok(j.to_string()),
Value::DateTimeUtc(d) => Ok(d.to_rfc3339()),
Value::NaiveDateTime(d) => Ok(d.to_string()),
Value::NaiveDate(d) => Ok(d.to_string()),
Value::NaiveTime(t) => Ok(t.to_string()),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not text")),
}
}
fn get_string_opt(&self, c: &str) -> Result<Option<String>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::Text(s)) => Ok(Some(s.clone())),
Some(Value::Uuid(u)) => Ok(Some(u.to_string())),
Some(Value::Decimal(d)) => Ok(Some(d.to_string())),
Some(Value::Json(j)) => Ok(Some(j.to_string())),
Some(Value::DateTimeUtc(d)) => Ok(Some(d.to_rfc3339())),
Some(Value::NaiveDateTime(d)) => Ok(Some(d.to_string())),
Some(Value::NaiveDate(d)) => Ok(Some(d.to_string())),
Some(Value::NaiveTime(t)) => Ok(Some(t.to_string())),
Some(_) => Err(tc(c, "not text")),
}
}
fn get_bytes(&self, c: &str) -> Result<&[u8], RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::Bytes(b) => Ok(b.as_slice()),
Value::Text(s) => Ok(s.as_bytes()),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not bytes")),
}
}
fn get_bytes_opt(&self, c: &str) -> Result<Option<&[u8]>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::Bytes(b)) => Ok(Some(b.as_slice())),
Some(Value::Text(s)) => Ok(Some(s.as_bytes())),
Some(_) => Err(tc(c, "not bytes")),
}
}
fn get_datetime_utc(&self, c: &str) -> Result<chrono::DateTime<chrono::Utc>, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::DateTimeUtc(d) => Ok(*d),
Value::NaiveDateTime(d) => Ok(d.and_utc()),
Value::Text(s) => chrono::DateTime::parse_from_rfc3339(s)
.map(|d| d.with_timezone(&chrono::Utc))
.map_err(|e| tc(c, e.to_string())),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not a datetime")),
}
}
fn get_datetime_utc_opt(
&self,
c: &str,
) -> Result<Option<chrono::DateTime<chrono::Utc>>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::DateTimeUtc(d)) => Ok(Some(*d)),
Some(Value::NaiveDateTime(d)) => Ok(Some(d.and_utc())),
Some(Value::Text(s)) => chrono::DateTime::parse_from_rfc3339(s)
.map(|d| Some(d.with_timezone(&chrono::Utc)))
.map_err(|e| tc(c, e.to_string())),
Some(_) => Err(tc(c, "not a datetime")),
}
}
fn get_naive_datetime(&self, c: &str) -> Result<chrono::NaiveDateTime, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::NaiveDateTime(d) => Ok(*d),
Value::DateTimeUtc(d) => Ok(d.naive_utc()),
Value::Text(s) => chrono::DateTime::parse_from_rfc3339(s)
.map(|d| d.naive_utc())
.map_err(|e| tc(c, e.to_string())),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not a datetime")),
}
}
fn get_naive_datetime_opt(&self, c: &str) -> Result<Option<chrono::NaiveDateTime>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::NaiveDateTime(d)) => Ok(Some(*d)),
Some(Value::DateTimeUtc(d)) => Ok(Some(d.naive_utc())),
Some(Value::Text(s)) => chrono::DateTime::parse_from_rfc3339(s)
.map(|d| Some(d.naive_utc()))
.map_err(|e| tc(c, e.to_string())),
Some(_) => Err(tc(c, "not a datetime")),
}
}
fn get_naive_date(&self, c: &str) -> Result<chrono::NaiveDate, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::NaiveDate(d) => Ok(*d),
Value::Text(s) => s
.parse::<chrono::NaiveDate>()
.map_err(|e: chrono::ParseError| tc(c, e.to_string())),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not a date")),
}
}
fn get_naive_date_opt(&self, c: &str) -> Result<Option<chrono::NaiveDate>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::NaiveDate(d)) => Ok(Some(*d)),
Some(Value::Text(s)) => s
.parse::<chrono::NaiveDate>()
.map(Some)
.map_err(|e: chrono::ParseError| tc(c, e.to_string())),
Some(_) => Err(tc(c, "not a date")),
}
}
fn get_naive_time(&self, c: &str) -> Result<chrono::NaiveTime, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::NaiveTime(t) => Ok(*t),
Value::Text(s) => s
.parse::<chrono::NaiveTime>()
.map_err(|e: chrono::ParseError| tc(c, e.to_string())),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not a time")),
}
}
fn get_naive_time_opt(&self, c: &str) -> Result<Option<chrono::NaiveTime>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::NaiveTime(t)) => Ok(Some(*t)),
Some(Value::Text(s)) => s
.parse::<chrono::NaiveTime>()
.map(Some)
.map_err(|e: chrono::ParseError| tc(c, e.to_string())),
Some(_) => Err(tc(c, "not a time")),
}
}
fn get_uuid(&self, c: &str) -> Result<uuid::Uuid, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::Uuid(u) => Ok(*u),
Value::Text(s) => uuid::Uuid::parse_str(s).map_err(|e| tc(c, e.to_string())),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not a uuid")),
}
}
fn get_uuid_opt(&self, c: &str) -> Result<Option<uuid::Uuid>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::Uuid(u)) => Ok(Some(*u)),
Some(Value::Text(s)) => uuid::Uuid::parse_str(s)
.map(Some)
.map_err(|e| tc(c, e.to_string())),
Some(_) => Err(tc(c, "not a uuid")),
}
}
fn get_json(&self, c: &str) -> Result<serde_json::Value, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::Json(j) => Ok(j.clone()),
Value::Text(s) => serde_json::from_str(s).map_err(|e| tc(c, e.to_string())),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not json")),
}
}
fn get_json_opt(&self, c: &str) -> Result<Option<serde_json::Value>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::Json(j)) => Ok(Some(j.clone())),
Some(Value::Text(s)) => serde_json::from_str(s)
.map(Some)
.map_err(|e| tc(c, e.to_string())),
Some(_) => Err(tc(c, "not json")),
}
}
fn get_decimal(&self, c: &str) -> Result<rust_decimal::Decimal, RowError> {
match self
.values
.get(c)
.ok_or_else(|| RowError::ColumnNotFound(c.into()))?
{
Value::Decimal(d) => Ok(*d),
Value::I64(i) => Ok(rust_decimal::Decimal::from(*i)),
Value::Text(s) => s
.parse::<rust_decimal::Decimal>()
.map_err(|e: rust_decimal::Error| tc(c, e.to_string())),
Value::Null => Err(RowError::UnexpectedNull(c.into())),
_ => Err(tc(c, "not a decimal")),
}
}
fn get_decimal_opt(&self, c: &str) -> Result<Option<rust_decimal::Decimal>, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(Value::Null) => Ok(None),
Some(Value::Decimal(d)) => Ok(Some(*d)),
Some(Value::I64(i)) => Ok(Some(rust_decimal::Decimal::from(*i))),
Some(Value::Text(s)) => s
.parse::<rust_decimal::Decimal>()
.map(Some)
.map_err(|e: rust_decimal::Error| tc(c, e.to_string())),
Some(_) => Err(tc(c, "not a decimal")),
}
}
fn is_null(&self, c: &str) -> Result<bool, RowError> {
match self.values.get(c) {
None => Err(RowError::ColumnNotFound(c.into())),
Some(v) => Ok(matches!(v, Value::Null)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use prax_query::row::FromColumn;
fn row(pairs: Vec<(&str, Value)>) -> SqlxRowRef {
SqlxRowRef {
values: pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
}
}
fn naive_datetime() -> chrono::NaiveDateTime {
chrono::NaiveDate::from_ymd_opt(2024, 1, 2)
.unwrap()
.and_hms_opt(3, 4, 5)
.unwrap()
}
#[test]
fn is_null_reports_null_variant_as_null() {
let r = row(vec![("a", Value::Null)]);
assert!(r.is_null("a").unwrap());
}
#[test]
fn is_null_reports_every_non_null_variant_as_not_null() {
let r = row(vec![
("bool", Value::Bool(true)),
("i64", Value::I64(5)),
("f64", Value::F64(1.5)),
("text", Value::Text("hello".into())),
("bytes", Value::Bytes(vec![1, 2, 3])),
(
"dt_utc",
Value::DateTimeUtc(chrono::DateTime::from_timestamp(1_700_000_000, 0).unwrap()),
),
("ndt", Value::NaiveDateTime(naive_datetime())),
(
"nd",
Value::NaiveDate(chrono::NaiveDate::from_ymd_opt(2024, 1, 2).unwrap()),
),
(
"nt",
Value::NaiveTime(chrono::NaiveTime::from_hms_opt(3, 4, 5).unwrap()),
),
("uuid", Value::Uuid(uuid::Uuid::nil())),
("json", Value::Json(serde_json::json!({"a": 1}))),
("decimal", Value::Decimal(rust_decimal::Decimal::new(42, 1))),
]);
for col in [
"bool", "i64", "f64", "text", "bytes", "dt_utc", "ndt", "nd", "nt", "uuid", "json",
"decimal",
] {
assert!(!r.is_null(col).unwrap(), "{col} should not be null");
}
}
#[test]
fn is_null_missing_column_is_column_not_found() {
let r = row(vec![]);
assert!(matches!(
r.is_null("missing"),
Err(RowError::ColumnNotFound(_))
));
}
#[test]
fn option_from_column_decodes_non_text_cells() {
let r = row(vec![
("n", Value::I64(5)),
("f", Value::F64(2.5)),
("b", Value::Bool(true)),
("null_i", Value::Null),
]);
assert_eq!(Option::<i32>::from_column(&r, "n").unwrap(), Some(5));
assert_eq!(Option::<f64>::from_column(&r, "f").unwrap(), Some(2.5));
assert_eq!(Option::<bool>::from_column(&r, "b").unwrap(), Some(true));
assert_eq!(Option::<i32>::from_column(&r, "null_i").unwrap(), None);
}
#[test]
fn typed_variants_are_readable_through_their_getters() {
let uuid = uuid::Uuid::nil();
let ndt = naive_datetime();
let r = row(vec![
("u", Value::Uuid(uuid)),
("d", Value::Decimal(rust_decimal::Decimal::new(42, 1))),
("j", Value::Json(serde_json::json!([1, 2]))),
("t", Value::NaiveDateTime(ndt)),
]);
assert_eq!(r.get_uuid("u").unwrap(), uuid);
assert_eq!(r.get_string("u").unwrap(), uuid.to_string());
assert_eq!(
r.get_decimal("d").unwrap(),
rust_decimal::Decimal::new(42, 1)
);
assert_eq!(r.get_json("j").unwrap(), serde_json::json!([1, 2]));
assert_eq!(r.get_naive_datetime("t").unwrap(), ndt);
assert_eq!(r.get_datetime_utc("t").unwrap(), ndt.and_utc());
}
}