Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 36 additions & 46 deletions cot/src/db/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,102 +11,121 @@ use crate::db::{
LimitedString, Model, PrimaryKey, Result, SqlxValueRef, ToDbFieldValue, ToDbValue,
};

mod chrono_fields;
mod chrono_wrapper;

macro_rules! impl_from_sqlite_default {
() => {
#[cfg(feature = "sqlite")]
fn from_sqlite(value: SqliteValueRef<'_>) -> Result<Self> {
fn from_sqlite(
value: crate::db::impl_sqlite::SqliteValueRef<'_>,
) -> crate::db::Result<Self> {
use crate::db::SqlxValueRef;
value.get::<Self>()
}
};
($wrapper_ty:ty) => {
#[cfg(feature = "sqlite")]
fn from_sqlite(value: SqliteValueRef<'_>) -> Result<Self> {
fn from_sqlite(
value: crate::db::impl_sqlite::SqliteValueRef<'_>,
) -> crate::db::Result<Self> {
<$wrapper_ty as FromDbValue>::from_sqlite(value).map(|val| val.into())
}
};
($wrapper_ty:ty, option) => {
#[cfg(feature = "sqlite")]
fn from_sqlite(value: SqliteValueRef<'_>) -> Result<Self> {
fn from_sqlite(
value: crate::db::impl_sqlite::SqliteValueRef<'_>,
) -> crate::db::Result<Self> {
<$wrapper_ty as FromDbValue>::from_sqlite(value).map(|val| val.map(|val| val.into()))
}
};
}
use impl_from_sqlite_default;

macro_rules! impl_from_postgres_default {
() => {
#[cfg(feature = "postgres")]
fn from_postgres(value: PostgresValueRef<'_>) -> Result<Self> {
fn from_postgres(
value: crate::db::impl_postgres::PostgresValueRef<'_>,
) -> crate::db::Result<Self> {
use crate::db::SqlxValueRef;
value.get::<Self>()
}
};
($wrapper_ty:ty) => {
#[cfg(feature = "postgres")]
fn from_postgres(value: PostgresValueRef<'_>) -> Result<Self> {
fn from_postgres(
value: crate::db::impl_postgres::PostgresValueRef<'_>,
) -> crate::db::Result<Self> {
<$wrapper_ty as FromDbValue>::from_postgres(value).map(|val| val.into())
}
};
($wrapper_ty:ty, option) => {
#[cfg(feature = "postgres")]
fn from_postgres(value: PostgresValueRef<'_>) -> Result<Self> {
fn from_postgres(
value: crate::db::impl_postgres::PostgresValueRef<'_>,
) -> crate::db::Result<Self> {
<$wrapper_ty as FromDbValue>::from_postgres(value).map(|val| val.map(|val| val.into()))
}
};
}
use impl_from_postgres_default;

macro_rules! impl_from_mysql_default {
() => {
#[cfg(feature = "mysql")]
fn from_mysql(value: MySqlValueRef<'_>) -> Result<Self> {
fn from_mysql(value: crate::db::impl_mysql::MySqlValueRef<'_>) -> Result<Self> {
use crate::db::SqlxValueRef;
value.get::<Self>()
}
};
($wrapper_ty:ty) => {
#[cfg(feature = "mysql")]
fn from_mysql(value: MySqlValueRef<'_>) -> Result<Self> {
fn from_mysql(value: crate::db::impl_mysql::MySqlValueRef<'_>) -> Result<Self> {
<$wrapper_ty as FromDbValue>::from_mysql(value).map(|val| val.into())
}
};
($wrapper_ty:ty, option) => {
#[cfg(feature = "mysql")]
fn from_mysql(value: MySqlValueRef<'_>) -> Result<Self> {
fn from_mysql(value: crate::db::impl_mysql::MySqlValueRef<'_>) -> Result<Self> {
<$wrapper_ty as FromDbValue>::from_mysql(value).map(|val| val.map(|val| val.into()))
}
};
}

macro_rules! impl_to_db_value_default {
($ty:ty) => {
impl ToDbValue for $ty {
fn to_db_value(&self) -> DbValue {
impl crate::db::ToDbValue for $ty {
fn to_db_value(&self) -> crate::db::DbValue {
self.clone().into()
}
}

impl ToDbValue for Option<$ty> {
fn to_db_value(&self) -> DbValue {
impl crate::db::ToDbValue for Option<$ty> {
fn to_db_value(&self) -> crate::db::DbValue {
self.clone().into()
}
}
};

($ty:ty, $wrapper_ty:ty) => {
impl ToDbValue for $ty {
fn to_db_value(&self) -> DbValue {
impl crate::db::ToDbValue for $ty {
fn to_db_value(&self) -> crate::db::DbValue {
Into::<$wrapper_ty>::into(self.clone()).to_db_value()
}
}

impl ToDbValue for Option<$ty> {
fn to_db_value(&self) -> DbValue {
impl crate::db::ToDbValue for Option<$ty> {
fn to_db_value(&self) -> crate::db::DbValue {
self.clone()
.map(|val| Into::<$wrapper_ty>::into(val))
.to_db_value()
}
}
};
}
use impl_to_db_value_default;

macro_rules! impl_db_field {
($ty:ty, $column_type:ident) => {
Expand Down Expand Up @@ -231,35 +250,6 @@ impl ToDbValue for &str {
}
}

impl DatabaseField for chrono::DateTime<chrono::FixedOffset> {
const TYPE: ColumnType = ColumnType::DateTimeWithTimeZone;
}

impl FromDbValue for chrono::DateTime<chrono::FixedOffset> {
impl_from_sqlite_default!();

impl_from_postgres_default!();

#[cfg(feature = "mysql")]
fn from_mysql(value: MySqlValueRef<'_>) -> Result<Self> {
Ok(value.get::<chrono::DateTime<chrono::Utc>>()?.fixed_offset())
}
}
impl FromDbValue for Option<chrono::DateTime<chrono::FixedOffset>> {
impl_from_sqlite_default!();

impl_from_postgres_default!();

#[cfg(feature = "mysql")]
fn from_mysql(value: MySqlValueRef<'_>) -> Result<Self> {
Ok(value
.get::<Option<chrono::DateTime<chrono::Utc>>>()?
.map(|dt| dt.fixed_offset()))
}
}

impl_to_db_value_default!(chrono::DateTime<chrono::FixedOffset>);

impl ToDbValue for Option<&str> {
fn to_db_value(&self) -> DbValue {
self.map(ToString::to_string).into()
Expand Down
166 changes: 166 additions & 0 deletions cot/src/db/fields/chrono_fields.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use crate::db::fields::{
impl_from_postgres_default, impl_from_sqlite_default, impl_to_db_value_default,
};
#[cfg(feature = "mysql")]
use crate::db::impl_mysql::MySqlValueRef;
use crate::db::{ColumnType, DatabaseField, FromDbValue, Result, SqlxValueRef};

impl DatabaseField for chrono::DateTime<chrono::FixedOffset> {
const TYPE: ColumnType = ColumnType::DateTimeWithTimeZone;
}

impl FromDbValue for chrono::DateTime<chrono::FixedOffset> {
impl_from_sqlite_default!();

impl_from_postgres_default!();

#[cfg(feature = "mysql")]
fn from_mysql(value: MySqlValueRef<'_>) -> Result<Self> {
Ok(value.get::<chrono::DateTime<chrono::Utc>>()?.fixed_offset())
}
}
impl FromDbValue for Option<chrono::DateTime<chrono::FixedOffset>> {
impl_from_sqlite_default!();

impl_from_postgres_default!();

#[cfg(feature = "mysql")]
fn from_mysql(value: MySqlValueRef<'_>) -> Result<Self> {
Ok(value
.get::<Option<chrono::DateTime<chrono::Utc>>>()?
.map(|dt| dt.fixed_offset()))
}
}

impl_to_db_value_default!(chrono::DateTime<chrono::FixedOffset>);

impl DatabaseField for chrono::DateTime<chrono::Utc> {
const TYPE: ColumnType = ColumnType::DateTimeWithTimeZone;
}

impl FromDbValue for chrono::DateTime<chrono::Utc> {
impl_from_sqlite_default!();

impl_from_postgres_default!();

#[cfg(feature = "mysql")]
fn from_mysql(value: MySqlValueRef<'_>) -> Result<Self> {
value.get::<Self>()
}
}
impl FromDbValue for Option<chrono::DateTime<chrono::Utc>> {
impl_from_sqlite_default!();

impl_from_postgres_default!();

#[cfg(feature = "mysql")]
fn from_mysql(value: MySqlValueRef<'_>) -> Result<Self> {
value.get::<Option<chrono::DateTime<chrono::Utc>>>()
}
}

impl_to_db_value_default!(chrono::DateTime<chrono::Utc>);

#[cfg(test)]
mod tests {
use chrono::{DateTime, FixedOffset, Utc};

use crate::db::{ColumnType, DatabaseField, DbValue, ToDbValue};

#[test]
fn test_datetime_fixed_offset_column_type() {
assert_eq!(
<DateTime<FixedOffset> as DatabaseField>::TYPE,
ColumnType::DateTimeWithTimeZone
);
const {
assert!(!<DateTime<FixedOffset> as DatabaseField>::NULLABLE);
}
}

#[test]
fn test_datetime_utc_column_type() {
assert_eq!(
<DateTime<Utc> as DatabaseField>::TYPE,
ColumnType::DateTimeWithTimeZone
);
const {
assert!(!<DateTime<Utc> as DatabaseField>::NULLABLE);
}
}

#[test]
fn test_option_datetime_column_type() {
assert_eq!(
<Option<DateTime<FixedOffset>> as DatabaseField>::TYPE,
ColumnType::DateTimeWithTimeZone
);
const {
assert!(<Option<DateTime<FixedOffset>> as DatabaseField>::NULLABLE);
}

assert_eq!(
<Option<DateTime<Utc>> as DatabaseField>::TYPE,
ColumnType::DateTimeWithTimeZone
);
const {
assert!(<Option<DateTime<Utc>> as DatabaseField>::NULLABLE);
}
}

#[test]
fn test_datetime_fixed_offset_to_db_value() {
let dt = DateTime::parse_from_rfc3339("2023-01-01T12:00:00+01:00").unwrap();
let db_value = dt.to_db_value();

match db_value {
DbValue::ChronoDateTimeWithTimeZone(Some(v)) => assert_eq!(*v, dt),
_ => panic!("Expected DbValue::ChronoDateTimeWithTimeZone, got {db_value:?}"),
}
}

#[test]
fn test_datetime_utc_to_db_value() {
let dt = Utc::now();
let db_value = dt.to_db_value();

match db_value {
DbValue::ChronoDateTimeUtc(Some(v)) => assert_eq!(*v, dt),
_ => panic!("Expected DbValue::ChronoDateTimeUtc, got {db_value:?}"),
}
}

#[test]
fn test_option_datetime_to_db_value() {
let dt = DateTime::parse_from_rfc3339("2023-01-01T12:00:00+01:00").unwrap();
let some_dt = Some(dt);
let none_dt: Option<DateTime<FixedOffset>> = None;

match some_dt.to_db_value() {
DbValue::ChronoDateTimeWithTimeZone(Some(v)) => assert_eq!(*v, dt),
_ => panic!(
"Expected DbValue::ChronoDateTimeWithTimeZone(Some), got {:?}",
some_dt.to_db_value()
),
}

assert_eq!(
none_dt.to_db_value(),
DbValue::ChronoDateTimeWithTimeZone(None)
);

let dt_utc = Utc::now();
let some_dt_utc = Some(dt_utc);
let none_dt_utc: Option<DateTime<Utc>> = None;

match some_dt_utc.to_db_value() {
DbValue::ChronoDateTimeUtc(Some(v)) => assert_eq!(*v, dt_utc),
_ => panic!(
"Expected DbValue::ChronoDateTimeUtc(Some), got {:?}",
some_dt_utc.to_db_value()
),
}

assert_eq!(none_dt_utc.to_db_value(), DbValue::ChronoDateTimeUtc(None));
}
}
Loading