Implement SQL Duration as a newtype tuple struct
This commit is contained in:
parent
10f2911d44
commit
98fc9055af
4 changed files with 116 additions and 57 deletions
|
@ -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),
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue