Implement SQL Duration as a newtype tuple struct

This commit is contained in:
Tobie Morgan Hitchcock 2022-05-05 07:10:05 +01:00
parent 10f2911d44
commit 98fc9055af
4 changed files with 116 additions and 57 deletions

View file

@ -21,7 +21,7 @@ pub fn day(_: &Runtime, mut args: Vec<Value>) -> Result<Value, Error> {
pub fn floor(_: &Runtime, mut args: Vec<Value>) -> Result<Value, Error> {
match args.remove(0) {
Value::Datetime(v) => match args.remove(0) {
Value::Duration(w) => match chrono::Duration::from_std(w.value) {
Value::Duration(w) => match chrono::Duration::from_std(*w) {
Ok(d) => match v.duration_trunc(d) {
Ok(v) => Ok(v.into()),
_ => Ok(Value::None),
@ -114,7 +114,7 @@ pub fn now(_: &Runtime, _: Vec<Value>) -> Result<Value, Error> {
pub fn round(_: &Runtime, mut args: Vec<Value>) -> Result<Value, Error> {
match args.remove(0) {
Value::Datetime(v) => match args.remove(0) {
Value::Duration(w) => match chrono::Duration::from_std(w.value) {
Value::Duration(w) => match chrono::Duration::from_std(*w) {
Ok(d) => match v.duration_round(d) {
Ok(v) => Ok(v.into()),
_ => Ok(Value::None),

View file

@ -4,24 +4,24 @@ use crate::sql::error::IResult;
use chrono::DurationRound;
use nom::branch::alt;
use nom::bytes::complete::tag;
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops;
use std::ops::Deref;
use std::time;
static SECONDS_PER_YEAR: u64 = 31_536_000;
static SECONDS_PER_WEEK: u64 = 604_800;
static SECONDS_PER_DAY: u64 = 86400;
static SECONDS_PER_HOUR: u64 = 3600;
static SECONDS_PER_MINUTE: u64 = 60;
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Deserialize)]
pub struct Duration {
pub input: String,
pub value: time::Duration,
}
pub struct Duration(pub time::Duration);
impl From<time::Duration> for Duration {
fn from(t: time::Duration) -> Self {
Duration {
input: format!("{:?}", t),
value: t,
}
fn from(v: time::Duration) -> Self {
Duration(v)
}
}
@ -34,9 +34,63 @@ impl<'a> From<&'a str> for Duration {
}
}
impl Deref for Duration {
type Target = time::Duration;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.input)
// Split up the duration
let secs = self.0.as_secs();
let nano = self.0.subsec_nanos();
// Calculate the total years
let year = secs / SECONDS_PER_YEAR;
let secs = secs % SECONDS_PER_YEAR;
// Calculate the total weeks
let week = secs / SECONDS_PER_WEEK;
let secs = secs % SECONDS_PER_WEEK;
// Calculate the total days
let days = secs / SECONDS_PER_DAY;
let secs = secs % SECONDS_PER_DAY;
// Calculate the total hours
let hour = secs / SECONDS_PER_HOUR;
let secs = secs % SECONDS_PER_HOUR;
// Calculate the total mins
let mins = secs / SECONDS_PER_MINUTE;
let secs = secs % SECONDS_PER_MINUTE;
// Prepare the outpit
let mut o = Vec::with_capacity(7);
// Write the different parts
if year > 0 {
o.push(format!("{year}y"));
}
if week > 0 {
o.push(format!("{week}w"));
}
if days > 0 {
o.push(format!("{days}d"));
}
if hour > 0 {
o.push(format!("{hour}h"));
}
if mins > 0 {
o.push(format!("{mins}m"));
}
if secs > 0 {
o.push(format!("{secs}s"));
}
if nano > 0 {
o.push(format!("{nano}ns"));
}
// Ensure no empty output
if o.is_empty() {
o.push(format!("0ns"));
}
// Concatenate together
write!(f, "{}", o.concat())
}
}
@ -46,12 +100,9 @@ impl Serialize for Duration {
S: serde::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_some(&self.input)
serializer.serialize_some(&self.0)
} else {
let mut val = serializer.serialize_struct("Duration", 2)?;
val.serialize_field("input", &self.input)?;
val.serialize_field("value", &self.value)?;
val.end()
serializer.serialize_newtype_struct("Duration", &self.0)
}
}
}
@ -59,21 +110,21 @@ impl Serialize for Duration {
impl ops::Add for Duration {
type Output = Self;
fn add(self, other: Self) -> Self {
Duration::from(self.value + other.value)
Duration::from(self.0 + other.0)
}
}
impl ops::Sub for Duration {
type Output = Self;
fn sub(self, other: Self) -> Self {
Duration::from(self.value - other.value)
Duration::from(self.0 - other.0)
}
}
impl ops::Add<Datetime> for Duration {
type Output = Datetime;
fn add(self, other: Datetime) -> Datetime {
match chrono::Duration::from_std(self.value) {
match chrono::Duration::from_std(self.0) {
Ok(d) => Datetime::from(other.0 + d),
Err(_) => Datetime::default(),
}
@ -83,7 +134,7 @@ impl ops::Add<Datetime> for Duration {
impl ops::Sub<Datetime> for Duration {
type Output = Datetime;
fn sub(self, other: Datetime) -> Datetime {
match chrono::Duration::from_std(self.value) {
match chrono::Duration::from_std(self.0) {
Ok(d) => Datetime::from(other.0 - d),
Err(_) => Datetime::default(),
}
@ -93,7 +144,7 @@ impl ops::Sub<Datetime> for Duration {
impl ops::Div<Datetime> for Duration {
type Output = Datetime;
fn div(self, other: Datetime) -> Datetime {
match chrono::Duration::from_std(self.value) {
match chrono::Duration::from_std(self.0) {
Ok(d) => match other.duration_trunc(d) {
Ok(v) => Datetime::from(v),
Err(_) => Datetime::default(),
@ -112,9 +163,7 @@ pub fn duration_raw(i: &str) -> IResult<&str, Duration> {
let (i, u) = unit(i)?;
Ok((
i,
Duration {
input: format!("{}{}", v, u),
value: match u {
Duration(match u {
"ns" => time::Duration::new(0, v as u32),
"µs" => time::Duration::new(0, v as u32 * 1000),
"ms" => time::Duration::new(0, v as u32 * 1000 * 1000),
@ -124,8 +173,7 @@ pub fn duration_raw(i: &str) -> IResult<&str, Duration> {
"d" => time::Duration::new(v * 60 * 60 * 24, 0),
"w" => time::Duration::new(v * 60 * 60 * 24 * 7, 0),
_ => time::Duration::new(0, 0),
},
},
}),
))
}
@ -142,6 +190,7 @@ fn unit(i: &str) -> IResult<&str, &str> {
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn duration_nil() {
@ -150,7 +199,7 @@ mod tests {
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("0ns", format!("{}", out));
assert_eq!(out.value, Duration::from("0ns").value);
assert_eq!(out.0, Duration::new(0, 0));
}
#[test]
@ -160,7 +209,7 @@ mod tests {
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("1s", format!("{}", out));
assert_eq!(out.value, Duration::from("1s").value);
assert_eq!(out.0, Duration::new(1, 0));
}
#[test]
@ -169,8 +218,8 @@ mod tests {
let res = duration(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("1000ms", format!("{}", out));
assert_eq!(out.value, Duration::from("1s").value);
assert_eq!("1s", format!("{}", out));
assert_eq!(out.0, Duration::new(1, 0));
}
#[test]
@ -179,8 +228,8 @@ mod tests {
let res = duration(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("86400s", format!("{}", out));
assert_eq!(out.value, Duration::from("1d").value);
assert_eq!("1d", format!("{}", out));
assert_eq!(out.0, Duration::new(86_400, 0));
}
#[test]
@ -190,7 +239,7 @@ mod tests {
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("5d", format!("{}", out));
assert_eq!(out.value, Duration::from("5d").value);
assert_eq!(out.0, Duration::new(432_000, 0));
}
#[test]
@ -200,6 +249,16 @@ mod tests {
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("4w", format!("{}", out));
assert_eq!(out.value, Duration::from("4w").value);
assert_eq!(out.0, Duration::new(2_419_200, 0));
}
#[test]
fn duration_split() {
let sql = "129600s";
let res = duration(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("1d12h", format!("{}", out));
assert_eq!(out.0, Duration::new(129_600, 0));
}
}

View file

@ -84,12 +84,12 @@ pub enum Statement {
impl Statement {
pub fn timeout(&self) -> Option<Duration> {
match self {
Statement::Select(v) => v.timeout.as_ref().map(|v| v.value),
Statement::Create(v) => v.timeout.as_ref().map(|v| v.value),
Statement::Update(v) => v.timeout.as_ref().map(|v| v.value),
Statement::Relate(v) => v.timeout.as_ref().map(|v| v.value),
Statement::Delete(v) => v.timeout.as_ref().map(|v| v.value),
Statement::Insert(v) => v.timeout.as_ref().map(|v| v.value),
Statement::Select(v) => v.timeout.as_ref().map(|v| *v.0),
Statement::Create(v) => v.timeout.as_ref().map(|v| *v.0),
Statement::Update(v) => v.timeout.as_ref().map(|v| *v.0),
Statement::Relate(v) => v.timeout.as_ref().map(|v| *v.0),
Statement::Delete(v) => v.timeout.as_ref().map(|v| *v.0),
Statement::Insert(v) => v.timeout.as_ref().map(|v| *v.0),
_ => None,
}
}

View file

@ -502,7 +502,7 @@ impl Value {
Value::Object(v) => !v.is_empty(),
Value::Strand(v) => !v.is_empty() && v.to_ascii_lowercase() != "false",
Value::Number(v) => v.is_truthy(),
Value::Duration(v) => v.value.as_nanos() > 0,
Value::Duration(v) => v.as_nanos() > 0,
Value::Datetime(v) => v.timestamp() > 0,
_ => false,
}
@ -551,7 +551,7 @@ impl Value {
Value::True => 1,
Value::Strand(v) => v.parse::<i64>().unwrap_or(0),
Value::Number(v) => v.as_int(),
Value::Duration(v) => v.value.as_secs() as i64,
Value::Duration(v) => v.as_secs() as i64,
Value::Datetime(v) => v.timestamp(),
_ => 0,
}
@ -562,7 +562,7 @@ impl Value {
Value::True => 1.0,
Value::Strand(v) => v.parse::<f64>().unwrap_or(0.0),
Value::Number(v) => v.as_float(),
Value::Duration(v) => v.value.as_secs() as f64,
Value::Duration(v) => v.as_secs() as f64,
Value::Datetime(v) => v.timestamp() as f64,
_ => 0.0,
}
@ -573,7 +573,7 @@ impl Value {
Value::True => BigDecimal::from(1),
Value::Number(v) => v.as_decimal(),
Value::Strand(v) => BigDecimal::from_str(v.as_str()).unwrap_or_default(),
Value::Duration(v) => v.value.as_secs().into(),
Value::Duration(v) => v.as_secs().into(),
Value::Datetime(v) => v.timestamp().into(),
_ => BigDecimal::default(),
}
@ -584,7 +584,7 @@ impl Value {
Value::True => Number::from(1),
Value::Number(v) => v,
Value::Strand(v) => Number::from(v.as_str()),
Value::Duration(v) => v.value.as_secs().into(),
Value::Duration(v) => v.as_secs().into(),
Value::Datetime(v) => v.timestamp().into(),
_ => Number::default(),
}
@ -629,7 +629,7 @@ impl Value {
Value::True => Number::from(1),
Value::Number(v) => v.clone(),
Value::Strand(v) => Number::from(v.as_str()),
Value::Duration(v) => v.value.as_secs().into(),
Value::Duration(v) => v.as_secs().into(),
Value::Datetime(v) => v.timestamp().into(),
_ => Number::default(),
}