diff --git a/docs/source/functions.rst b/docs/source/functions.rst index 18618d73..af37a1d8 100644 --- a/docs/source/functions.rst +++ b/docs/source/functions.rst @@ -627,7 +627,7 @@ Empty matches:: -------------------- -Misc functions +Timestamp functions -------------------- .. function:: now() @@ -638,4 +638,8 @@ Misc functions Interpret ``ts`` as seconds since the epoch and format as a string according to `RFC3339 `_. - If a second string argument is provided, it is interpreted as a `timezone `_ and used to format the timestamp. \ No newline at end of file + If a second string argument is provided, it is interpreted as a `timezone `_ and used to format the timestamp. + +.. function:: parse_timestamp(str) + + Parse ``str`` into seconds since the epoch according to RFC3339. \ No newline at end of file diff --git a/src/data/expr.rs b/src/data/expr.rs index ef31a870..00e353ff 100644 --- a/src/data/expr.rs +++ b/src/data/expr.rs @@ -336,7 +336,7 @@ impl Expr { if target == symb { let tar_val = match val.get_int() { Some(i) => DataValue::from(i), - None => val.clone() + None => val.clone(), }; return Ok(ValueRange::lower_bound(tar_val)); } @@ -347,7 +347,7 @@ impl Expr { if target == symb { let tar_val = match val.get_float() { Some(i) => DataValue::from(i), - None => val.clone() + None => val.clone(), }; return Ok(ValueRange::upper_bound(tar_val)); } @@ -361,7 +361,7 @@ impl Expr { if target == symb { let tar_val = match val.get_float() { Some(i) => DataValue::from(i), - None => val.clone() + None => val.clone(), }; return Ok(ValueRange::upper_bound(tar_val)); @@ -373,7 +373,7 @@ impl Expr { if target == symb { let tar_val = match val.get_int() { Some(i) => DataValue::from(i), - None => val.clone() + None => val.clone(), }; return Ok(ValueRange::lower_bound(tar_val)); @@ -653,6 +653,7 @@ pub(crate) fn get_op(name: &str) -> Option<&'static Op> { "uuid_timestamp" => &OP_UUID_TIMESTAMP, "now" => &OP_NOW, "format_timestamp" => &OP_FORMAT_TIMESTAMP, + "parse_timestamp" => &OP_PARSE_TIMESTAMP, _ => return None, }) } diff --git a/src/data/functions.rs b/src/data/functions.rs index 52085e13..8e7ae395 100644 --- a/src/data/functions.rs +++ b/src/data/functions.rs @@ -3,7 +3,7 @@ use std::ops::{Div, Rem}; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; -use chrono::{TimeZone, Utc}; +use chrono::{DateTime, TimeZone, Utc}; use itertools::Itertools; use miette::{bail, ensure, miette, Result}; use num_traits::FloatConst; @@ -1417,7 +1417,8 @@ pub(crate) fn op_format_timestamp(args: &[DataValue]) -> Result { let tz_s = tz_v.get_string().ok_or_else(|| { miette!("'format_timestamp' timezone specification requires a string") })?; - let tz = chrono_tz::Tz::from_str(tz_s).map_err(|_| miette!("bad timezone specification: {}", tz_s))?; + let tz = chrono_tz::Tz::from_str(tz_s) + .map_err(|_| miette!("bad timezone specification: {}", tz_s))?; let dt_tz = dt.with_timezone(&tz); let s = SmartString::from(dt_tz.to_rfc3339()); Ok(DataValue::Str(s)) @@ -1429,6 +1430,18 @@ pub(crate) fn op_format_timestamp(args: &[DataValue]) -> Result { } } +define_op!(OP_PARSE_TIMESTAMP, 1, false); +pub(crate) fn op_parse_timestamp(args: &[DataValue]) -> Result { + let s = args[0] + .get_string() + .ok_or_else(|| miette!("'parse_timestamp' expects a string"))?; + let dt = DateTime::parse_from_rfc3339(s).map_err(|_| miette!("bad datetime: {}", s))?; + let st: SystemTime = dt.into(); + Ok(DataValue::from( + st.duration_since(UNIX_EPOCH).unwrap().as_secs_f64(), + )) +} + define_op!(OP_RAND_UUID_V1, 0, false); pub(crate) fn op_rand_uuid_v1(_args: &[DataValue]) -> Result { let mut rng = rand::thread_rng(); diff --git a/src/data/tests/functions.rs b/src/data/tests/functions.rs index 59224fb6..480cf871 100644 --- a/src/data/tests/functions.rs +++ b/src/data/tests/functions.rs @@ -1328,5 +1328,6 @@ fn test_uuid() { fn test_now() { let now = op_now(&[]).unwrap(); assert!(matches!(now, DataValue::Num(_))); - op_format_timestamp(&[now]).unwrap(); + let s = op_format_timestamp(&[now]).unwrap(); + let _dt = op_parse_timestamp(&[s]).unwrap(); } \ No newline at end of file diff --git a/src/query/relation.rs b/src/query/relation.rs index 101985a3..5570053b 100644 --- a/src/query/relation.rs +++ b/src/query/relation.rs @@ -756,6 +756,7 @@ impl RelationRA { left_to_prefix_indices.push(left_join_indices[*idx]); } + // TODO don't build the whole thing is prefix scan suffices let mut right_join_vals = BTreeSet::new(); for tuple in self.storage.scan_all(tx) { let tuple = tuple?;