From 58cffa2de6a474a20f4fc84f2cd6ef6005498175 Mon Sep 17 00:00:00 2001 From: Hugo Saracino Date: Fri, 23 Sep 2022 01:48:49 +0200 Subject: [PATCH] Add duration functions for calculating durations as integers (#257) --- lib/src/fnc/duration.rs | 44 ++++++++++++++++++++++++++++++++++++++ lib/src/fnc/mod.rs | 8 +++++++ lib/src/sql/datetime.rs | 13 ++++++++++- lib/src/sql/duration.rs | 35 ++++++++++++++++++++++++++++++ lib/src/sql/function.rs | 12 +++++++++++ lib/src/sql/value/value.rs | 1 + 6 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 lib/src/fnc/duration.rs diff --git a/lib/src/fnc/duration.rs b/lib/src/fnc/duration.rs new file mode 100644 index 00000000..b699d340 --- /dev/null +++ b/lib/src/fnc/duration.rs @@ -0,0 +1,44 @@ +use crate::err::Error; +use crate::sql::value::Value; + +pub fn days((duration,): (Value,)) -> Result { + Ok(match duration { + Value::Duration(d) => d.days(), + _ => Value::None, + }) +} + +pub fn hours((duration,): (Value,)) -> Result { + Ok(match duration { + Value::Duration(d) => d.hours(), + _ => Value::None, + }) +} + +pub fn mins((duration,): (Value,)) -> Result { + Ok(match duration { + Value::Duration(d) => d.mins(), + _ => Value::None, + }) +} + +pub fn secs((duration,): (Value,)) -> Result { + Ok(match duration { + Value::Duration(d) => d.secs(), + _ => Value::None, + }) +} + +pub fn weeks((duration,): (Value,)) -> Result { + Ok(match duration { + Value::Duration(d) => d.weeks(), + _ => Value::None, + }) +} + +pub fn years((duration,): (Value,)) -> Result { + Ok(match duration { + Value::Duration(d) => d.years(), + _ => Value::None, + }) +} diff --git a/lib/src/fnc/mod.rs b/lib/src/fnc/mod.rs index 4b2d1ec4..fa6085ec 100644 --- a/lib/src/fnc/mod.rs +++ b/lib/src/fnc/mod.rs @@ -7,6 +7,7 @@ pub mod array; pub mod cast; pub mod count; pub mod crypto; +pub mod duration; pub mod future; pub mod geo; pub mod http; @@ -77,6 +78,13 @@ pub fn synchronous(ctx: &Context<'_>, name: &str, args: Vec) -> Result crypto::sha256, "crypto::sha512" => crypto::sha512, // + "duration::days" => duration::days, + "duration::hours" => duration::hours, + "duration::mins" => duration::mins, + "duration::secs" => duration::secs, + "duration::weeks" => duration::weeks, + "duration::years" => duration::years, + // "geo::area" => geo::area, "geo::bearing" => geo::bearing, "geo::centroid" => geo::centroid, diff --git a/lib/src/sql/datetime.rs b/lib/src/sql/datetime.rs index 86ab65c2..21204b2a 100644 --- a/lib/src/sql/datetime.rs +++ b/lib/src/sql/datetime.rs @@ -1,4 +1,5 @@ use crate::sql::common::{take_digits, take_digits_range, take_u32_len}; +use crate::sql::duration::Duration; use crate::sql::error::IResult; use crate::sql::serde::is_internal_serialization; use chrono::{DateTime, FixedOffset, TimeZone, Utc}; @@ -7,9 +8,9 @@ use nom::character::complete::char; use nom::combinator::map; use nom::sequence::delimited; use serde::{Deserialize, Serialize}; -use std::fmt; use std::ops::Deref; use std::str; +use std::{fmt, ops}; const SINGLE: char = '\''; const DOUBLE: char = '"'; @@ -70,6 +71,16 @@ impl Serialize for Datetime { } } +impl ops::Sub for Datetime { + type Output = Duration; + fn sub(self, other: Datetime) -> Duration { + match (self.0 - other.0).to_std() { + Ok(d) => Duration::from(d), + Err(_) => Duration::default(), + } + } +} + pub fn datetime(i: &str) -> IResult<&str, Datetime> { alt(( delimited(char(DOUBLE), datetime_raw, char(DOUBLE)), diff --git a/lib/src/sql/duration.rs b/lib/src/sql/duration.rs index d6469ae2..807e2203 100644 --- a/lib/src/sql/duration.rs +++ b/lib/src/sql/duration.rs @@ -2,6 +2,7 @@ use crate::sql::common::take_u64; use crate::sql::datetime::Datetime; use crate::sql::error::IResult; use crate::sql::serde::is_internal_serialization; +use crate::sql::value::Value; use chrono::DurationRound; use nom::branch::alt; use nom::bytes::complete::tag; @@ -54,6 +55,40 @@ impl Duration { pub fn to_raw(&self) -> String { self.to_string() } + + pub fn secs(&self) -> Value { + self.0.as_secs().into() + } + + pub fn mins(&self) -> Value { + let secs = self.0.as_secs(); + let mins = secs / SECONDS_PER_MINUTE; + mins.into() + } + + pub fn hours(&self) -> Value { + let secs = self.0.as_secs(); + let hours = secs / SECONDS_PER_HOUR; + hours.into() + } + + pub fn days(&self) -> Value { + let secs = self.0.as_secs(); + let days = secs / SECONDS_PER_DAY; + days.into() + } + + pub fn weeks(&self) -> Value { + let secs = self.0.as_secs(); + let weeks = secs / SECONDS_PER_WEEK; + weeks.into() + } + + pub fn years(&self) -> Value { + let secs = self.0.as_secs(); + let years = secs / SECONDS_PER_YEAR; + years.into() + } } impl fmt::Display for Duration { diff --git a/lib/src/sql/function.rs b/lib/src/sql/function.rs index 4d21064c..7cd1bdb4 100644 --- a/lib/src/sql/function.rs +++ b/lib/src/sql/function.rs @@ -239,6 +239,7 @@ fn function_names(i: &str) -> IResult<&str, &str> { function_array, function_count, function_crypto, + function_duration, function_geo, function_http, function_is, @@ -289,6 +290,17 @@ fn function_crypto(i: &str) -> IResult<&str, &str> { ))(i) } +fn function_duration(i: &str) -> IResult<&str, &str> { + alt(( + tag("duration::days"), + tag("duration::hours"), + tag("duration::mins"), + tag("duration::secs"), + tag("duration::weeks"), + tag("duration::years"), + ))(i) +} + fn function_geo(i: &str) -> IResult<&str, &str> { alt(( tag("geo::area"), diff --git a/lib/src/sql/value/value.rs b/lib/src/sql/value/value.rs index 0508f504..8fafe0e3 100644 --- a/lib/src/sql/value/value.rs +++ b/lib/src/sql/value/value.rs @@ -1199,6 +1199,7 @@ impl ops::Sub for Value { fn sub(self, other: Self) -> Self { match (self, other) { (Value::Number(v), Value::Number(w)) => Value::Number(v - w), + (Value::Datetime(v), Value::Datetime(w)) => Value::Duration(v - w), (Value::Datetime(v), Value::Duration(w)) => Value::Datetime(w - v), (Value::Duration(v), Value::Datetime(w)) => Value::Datetime(v - w), (Value::Duration(v), Value::Duration(w)) => Value::Duration(v - w),