Skip to content

Commit

Permalink
remote date time wrappers
Browse files Browse the repository at this point in the history
Signed-off-by: Michelle Dhanani <michelle@fermyon.com>
Co-authored-by: Brian Hardock <brian.hardock@fermyon.com>
  • Loading branch information
michelleN and fibonacci1729 committed Nov 7, 2024
1 parent 38f006d commit 96c677c
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 54 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion examples/postgres-v3/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ anyhow = "1"
http = "1.0.0"
# The Spin SDK.
spin-sdk = { path = "../.." }

# For handling date/time types
chrono = "0.4.38"
9 changes: 3 additions & 6 deletions examples/postgres-v3/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#![allow(dead_code)]
use anyhow::Result;
use http::{Request, Response};
use spin_sdk::{
http_component, pg3,
pg3::{Date, Decode},
};
use spin_sdk::{http_component, pg3, pg3::Decode};

// The environment variable set in `spin.toml` that points to the
// address of the Pg server that the component will write to
Expand All @@ -16,7 +13,7 @@ struct Article {
title: String,
content: String,
authorname: String,
published: Date,
published: chrono::NaiveDate,
coauthor: Option<String>,
}

Expand All @@ -28,7 +25,7 @@ impl TryFrom<&pg3::Row> for Article {
let title = String::decode(&row[1])?;
let content = String::decode(&row[2])?;
let authorname = String::decode(&row[3])?;
let published = Date::decode(&row[4])?;
let published = chrono::NaiveDate::decode(&row[4])?;
let coauthor = Option::<String>::decode(&row[5])?;

Ok(Self {
Expand Down
112 changes: 65 additions & 47 deletions src/pg3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
//!
//! # Types
//!
//! | Rust type | WIT (db-value) | Postgres type(s) |
//! |------------|-----------------------------------------------|----------------------------- |
//! | `bool` | boolean(bool) | BOOL |
//! | `i16` | int16(s16) | SMALLINT, SMALLSERIAL, INT2 |
//! | `i32` | int32(s32) | INT, SERIAL, INT4 |
//! | `i64` | int64(s64) | BIGINT, BIGSERIAL, INT8 |
//! | `f32` | floating32(float32) | REAL, FLOAT4 |
//! | `f64` | floating64(float64) | DOUBLE PRECISION, FLOAT8 |
//! | `String` | str(string) | VARCHAR, CHAR(N), TEXT |
//! | `Vec<u8>` | binary(list\<u8\>) | BYTEA |
//! | `Date` | date(tuple<s32, u8, u8>) | DATE |
//! | `Time` | time(tuple<u8, u8, u8, u32>) | TIME |
//! | `Datetime` | datetime(tuple<s32, u8, u8, u8, u8, u8, u32>) | TIMESTAMP |
//! | `Timestamp`| timestamp(s64) | BIGINT |
//! | Rust type | WIT (db-value) | Postgres type(s) |
//! |-------------------------|-----------------------------------------------|----------------------------- |
//! | `bool` | boolean(bool) | BOOL |
//! | `i16` | int16(s16) | SMALLINT, SMALLSERIAL, INT2 |
//! | `i32` | int32(s32) | INT, SERIAL, INT4 |
//! | `i64` | int64(s64) | BIGINT, BIGSERIAL, INT8 |
//! | `f32` | floating32(float32) | REAL, FLOAT4 |
//! | `f64` | floating64(float64) | DOUBLE PRECISION, FLOAT8 |
//! | `String` | str(string) | VARCHAR, CHAR(N), TEXT |
//! | `Vec<u8>` | binary(list\<u8\>) | BYTEA |
//! | `chrono::NaiveDate` | date(tuple<s32, u8, u8>) | DATE |
//! | `chrono::NaiveTime` | time(tuple<u8, u8, u8, u32>) | TIME |
//! | `chrono::NaiveDateTime` | datetime(tuple<s32, u8, u8, u8, u8, u8, u32>) | TIMESTAMP |
//! | `chrono::Duration` | timestamp(s64) | BIGINT |
#[doc(inline)]
pub use super::wit::pg3::{Error as PgError, *};
Expand Down Expand Up @@ -124,11 +124,7 @@ impl Decode for String {
}
}

/// Native representation of the WIT postgres Date value.
#[derive(Clone, Debug, PartialEq)]
pub struct Date(pub chrono::NaiveDate);

impl Decode for Date {
impl Decode for chrono::NaiveDate {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Date((year, month, day)) => {
Expand All @@ -140,18 +136,14 @@ impl Decode for Date {
year, month, day
))
})?;
Ok(Date(naive_date))
Ok(naive_date)
}
_ => Err(Error::Decode(format_decode_err("DATE", value))),
}
}
}

/// Native representation of the WIT postgres Time value.
#[derive(Clone, Debug, PartialEq)]
pub struct Time(pub chrono::NaiveTime);

impl Decode for Time {
impl Decode for chrono::NaiveTime {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Time((hour, minute, second, nanosecond)) => {
Expand All @@ -167,18 +159,14 @@ impl Decode for Time {
hour, minute, second, nanosecond
))
})?;
Ok(Time(naive_time))
Ok(naive_time)
}
_ => Err(Error::Decode(format_decode_err("TIME", value))),
}
}
}

/// Native representation of the WIT postgres DateTime value.
#[derive(Clone, Debug, PartialEq)]
pub struct DateTime(pub chrono::NaiveDateTime);

impl Decode for DateTime {
impl Decode for chrono::NaiveDateTime {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Datetime((year, month, day, hour, minute, second, nanosecond)) => {
Expand All @@ -203,19 +191,30 @@ impl Decode for DateTime {
))
})?;
let dt = chrono::NaiveDateTime::new(naive_date, naive_time);
Ok(DateTime(dt))
Ok(dt)
}
_ => Err(Error::Decode(format_decode_err("DATETIME", value))),
}
}
}

impl Decode for chrono::Duration {
fn decode(value: &DbValue) -> Result<Self, Error> {
match value {
DbValue::Timestamp(n) => Ok(chrono::Duration::seconds(*n)),
_ => Err(Error::Decode(format_decode_err("BIGINT", value))),
}
}
}

fn format_decode_err(types: &str, value: &DbValue) -> String {
format!("Expected {} from the DB but got {:?}", types, value)
}

#[cfg(test)]
mod tests {
use chrono::NaiveDateTime;

use super::*;

#[test]
Expand Down Expand Up @@ -285,44 +284,63 @@ mod tests {
#[test]
fn date() {
assert_eq!(
Date::decode(&DbValue::Date((1, 2, 4))).unwrap(),
Date(chrono::NaiveDate::from_ymd_opt(1, 2, 4).unwrap())
chrono::NaiveDate::decode(&DbValue::Date((1, 2, 4))).unwrap(),
chrono::NaiveDate::from_ymd_opt(1, 2, 4).unwrap()
);
assert_ne!(
Date::decode(&DbValue::Date((1, 2, 4))).unwrap(),
Date(chrono::NaiveDate::from_ymd_opt(1, 2, 5).unwrap())
chrono::NaiveDate::decode(&DbValue::Date((1, 2, 4))).unwrap(),
chrono::NaiveDate::from_ymd_opt(1, 2, 5).unwrap()
);
assert!(Option::<Date>::decode(&DbValue::DbNull).unwrap().is_none());
assert!(Option::<chrono::NaiveDate>::decode(&DbValue::DbNull)
.unwrap()
.is_none());
}

#[test]
fn time() {
assert_eq!(
Time::decode(&DbValue::Time((1, 2, 3, 4))).unwrap(),
Time(chrono::NaiveTime::from_hms_nano_opt(1, 2, 3, 4).unwrap())
chrono::NaiveTime::decode(&DbValue::Time((1, 2, 3, 4))).unwrap(),
chrono::NaiveTime::from_hms_nano_opt(1, 2, 3, 4).unwrap()
);
assert_ne!(
Time::decode(&DbValue::Time((1, 2, 3, 4))).unwrap(),
Time(chrono::NaiveTime::from_hms_nano_opt(1, 2, 4, 5).unwrap())
chrono::NaiveTime::decode(&DbValue::Time((1, 2, 3, 4))).unwrap(),
chrono::NaiveTime::from_hms_nano_opt(1, 2, 4, 5).unwrap()
);
assert!(Option::<Time>::decode(&DbValue::DbNull).unwrap().is_none());
assert!(Option::<chrono::NaiveTime>::decode(&DbValue::DbNull)
.unwrap()
.is_none());
}

#[test]
fn datetime() {
let date = chrono::NaiveDate::from_ymd_opt(1, 2, 3).unwrap();
let mut time = chrono::NaiveTime::from_hms_nano_opt(4, 5, 6, 7).unwrap();
assert_eq!(
DateTime::decode(&DbValue::Datetime((1, 2, 3, 4, 5, 6, 7))).unwrap(),
DateTime(chrono::NaiveDateTime::new(date, time))
chrono::NaiveDateTime::decode(&DbValue::Datetime((1, 2, 3, 4, 5, 6, 7))).unwrap(),
chrono::NaiveDateTime::new(date, time)
);

time = chrono::NaiveTime::from_hms_nano_opt(4, 5, 6, 8).unwrap();
assert_ne!(
DateTime::decode(&DbValue::Datetime((1, 2, 3, 4, 5, 6, 7))).unwrap(),
DateTime(chrono::NaiveDateTime::new(date, time))
NaiveDateTime::decode(&DbValue::Datetime((1, 2, 3, 4, 5, 6, 7))).unwrap(),
chrono::NaiveDateTime::new(date, time)
);
assert!(Option::<chrono::NaiveDateTime>::decode(&DbValue::DbNull)
.unwrap()
.is_none());
}

#[test]
fn timestamp() {
assert_eq!(
chrono::Duration::decode(&DbValue::Timestamp(1)).unwrap(),
chrono::Duration::seconds(1),
);
assert_ne!(
chrono::Duration::decode(&DbValue::Timestamp(2)).unwrap(),
chrono::Duration::seconds(1)
);
assert!(Option::<DateTime>::decode(&DbValue::DbNull)
assert!(Option::<chrono::Duration>::decode(&DbValue::DbNull)
.unwrap()
.is_none());
}
Expand Down

0 comments on commit 96c677c

Please sign in to comment.