use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
use sqlx::Arguments;
use sqlx::postgres::PgArguments;
#[derive(Debug, Clone)]
pub enum FieldValue {
Int32(i32),
Int64(i64),
Float32(f32),
Float64(f64),
Bool(bool),
String(String),
Numeric(String),
Bytes(Vec<u8>),
Date(NaiveDate),
Time(NaiveTime),
DateTime(NaiveDateTime),
DateTimeUtc(DateTime<Utc>),
Null,
}
impl FieldValue {
pub fn bind_to(&self, args: &mut PgArguments) {
match self {
FieldValue::Int32(v) => args.add(v).unwrap(),
FieldValue::Int64(v) => args.add(v).unwrap(),
FieldValue::Float32(v) => args.add(v).unwrap(),
FieldValue::Float64(v) => args.add(v).unwrap(),
FieldValue::Bool(v) => args.add(v).unwrap(),
FieldValue::String(v) => args.add(v).unwrap(),
FieldValue::Numeric(v) => args.add(v).unwrap(),
FieldValue::Bytes(v) => args.add(v).unwrap(),
FieldValue::Date(v) => args.add(v).unwrap(),
FieldValue::Time(v) => args.add(v).unwrap(),
FieldValue::DateTime(v) => args.add(v).unwrap(),
FieldValue::DateTimeUtc(v) => args.add(v).unwrap(),
FieldValue::Null => args.add(None::<i32>).unwrap(),
}
}
}
impl From<i32> for FieldValue {
fn from(v: i32) -> Self {
FieldValue::Int32(v)
}
}
impl From<i64> for FieldValue {
fn from(v: i64) -> Self {
FieldValue::Int64(v)
}
}
impl From<f32> for FieldValue {
fn from(v: f32) -> Self {
FieldValue::Float32(v)
}
}
impl From<f64> for FieldValue {
fn from(v: f64) -> Self {
FieldValue::Float64(v)
}
}
impl From<bool> for FieldValue {
fn from(v: bool) -> Self {
FieldValue::Bool(v)
}
}
impl From<String> for FieldValue {
fn from(v: String) -> Self {
FieldValue::String(v)
}
}
impl From<&str> for FieldValue {
fn from(v: &str) -> Self {
FieldValue::String(v.to_owned())
}
}
impl From<Vec<u8>> for FieldValue {
fn from(v: Vec<u8>) -> Self {
FieldValue::Bytes(v)
}
}
impl From<NaiveDate> for FieldValue {
fn from(v: NaiveDate) -> Self {
FieldValue::Date(v)
}
}
impl From<NaiveTime> for FieldValue {
fn from(v: NaiveTime) -> Self {
FieldValue::Time(v)
}
}
impl From<NaiveDateTime> for FieldValue {
fn from(v: NaiveDateTime) -> Self {
FieldValue::DateTime(v)
}
}
impl From<DateTime<Utc>> for FieldValue {
fn from(v: DateTime<Utc>) -> Self {
FieldValue::DateTimeUtc(v)
}
}
impl<T: Into<FieldValue>> From<Option<T>> for FieldValue {
fn from(v: Option<T>) -> Self {
match v {
Some(val) => val.into(),
None => FieldValue::Null,
}
}
}
#[derive(Debug, Clone)]
pub struct Field {
pub name: String,
pub value: FieldValue,
}
impl Field {
pub fn new(name: impl Into<String>, value: impl Into<FieldValue>) -> Self {
Self {
name: name.into(),
value: value.into(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct UpsertOptions {
pub version_field: Option<String>,
pub do_nothing_on_conflict: bool,
}
impl UpsertOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_version_field(mut self, field: impl Into<String>) -> Self {
self.version_field = Some(field.into());
self
}
pub fn with_do_nothing_on_conflict(mut self, do_nothing: bool) -> Self {
self.do_nothing_on_conflict = do_nothing;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_date_field_conversion() {
let date = NaiveDate::from_ymd_opt(2025, 12, 26).unwrap();
let field_value: FieldValue = date.into();
assert!(matches!(field_value, FieldValue::Date(_)));
}
#[test]
fn test_time_field_conversion() {
let time = NaiveTime::from_hms_opt(14, 30, 0).unwrap();
let field_value: FieldValue = time.into();
assert!(matches!(field_value, FieldValue::Time(_)));
}
#[test]
fn test_datetime_field_conversion() {
let datetime = NaiveDate::from_ymd_opt(2025, 12, 26)
.unwrap()
.and_hms_opt(14, 30, 0)
.unwrap();
let field_value: FieldValue = datetime.into();
assert!(matches!(field_value, FieldValue::DateTime(_)));
}
#[test]
fn test_datetime_utc_field_conversion() {
let datetime = DateTime::<Utc>::from_timestamp(1735225800, 0).unwrap();
let field_value: FieldValue = datetime.into();
assert!(matches!(field_value, FieldValue::DateTimeUtc(_)));
}
#[test]
fn test_optional_date_field() {
let some_date: Option<NaiveDate> = Some(NaiveDate::from_ymd_opt(2025, 12, 26).unwrap());
let field_value: FieldValue = some_date.into();
assert!(matches!(field_value, FieldValue::Date(_)));
let none_date: Option<NaiveDate> = None;
let field_value: FieldValue = none_date.into();
assert!(matches!(field_value, FieldValue::Null));
}
#[test]
fn test_numeric_field_from_string() {
let numeric_value = String::from("123.456789");
let field_value = FieldValue::Numeric(numeric_value.clone());
assert!(matches!(field_value, FieldValue::Numeric(_)));
if let FieldValue::Numeric(v) = field_value {
assert_eq!(v, "123.456789");
}
}
#[test]
fn test_numeric_field_high_precision() {
let high_precision = "99999999999999999999999999.999999999999";
let field_value = FieldValue::Numeric(high_precision.to_string());
if let FieldValue::Numeric(v) = field_value {
assert_eq!(v, high_precision);
}
}
}