use std::cmp::Ordering;
use icu_collator::CollatorBorrowed;
use crate::error::{PondError, Result};
use crate::join::root_collator;
use crate::rows::Row;
use crate::series::{Key, Labels, TimeSeries};
#[derive(Debug, Clone, PartialEq)]
pub enum Label {
Number(f64),
String(String),
}
impl From<f64> for Label {
fn from(v: f64) -> Self {
Label::Number(v)
}
}
impl From<i64> for Label {
fn from(v: i64) -> Self {
Label::Number(v as f64)
}
}
impl From<&str> for Label {
fn from(v: &str) -> Self {
Label::String(v.to_string())
}
}
impl From<String> for Label {
fn from(v: String) -> Self {
Label::String(v)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EventKey {
begin: i64,
end: i64,
label: Option<Label>,
kind: KeyKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum KeyKind {
Interval,
Time,
TimeRange,
}
impl EventKey {
pub fn time(at: i64) -> Self {
Self {
begin: at,
end: at,
label: None,
kind: KeyKind::Time,
}
}
pub fn time_range(begin: i64, end: i64) -> Result<Self> {
check_order("EventKey::time_range", begin, end)?;
Ok(Self {
begin,
end,
label: None,
kind: KeyKind::TimeRange,
})
}
pub fn interval(label: impl Into<Label>, begin: i64, end: i64) -> Result<Self> {
const CALL: &str = "EventKey::interval";
check_order(CALL, begin, end)?;
let label = label.into();
if let Label::Number(v) = label
&& !v.is_finite()
{
return Err(PondError::InvalidData {
call: CALL,
message: format!("a label must be finite, got {v}"),
});
}
Ok(Self {
begin,
end,
label: Some(label),
kind: KeyKind::Interval,
})
}
pub fn begin(&self) -> i64 {
self.begin
}
pub fn end(&self) -> i64 {
self.end
}
pub fn label(&self) -> Option<&Label> {
self.label.as_ref()
}
pub fn kind(&self) -> &'static str {
match self.kind {
KeyKind::Time => "time",
KeyKind::TimeRange => "timeRange",
KeyKind::Interval => "interval",
}
}
}
impl From<i64> for EventKey {
fn from(at: i64) -> Self {
EventKey::time(at)
}
}
fn check_order(call: &'static str, begin: i64, end: i64) -> Result<()> {
if begin > end {
return Err(PondError::InvalidData {
call,
message: format!("start must be <= end, got {begin} > {end}"),
});
}
Ok(())
}
#[cfg(feature = "live")]
#[inline]
pub(crate) fn compare_keys(a: &EventKey, b: &EventKey) -> Ordering {
if a.begin != b.begin {
return a.begin.cmp(&b.begin);
}
compare_same_begin(a, b)
}
#[cfg(feature = "live")]
#[inline(never)]
fn compare_same_begin(a: &EventKey, b: &EventKey) -> Ordering {
let c = a.end.cmp(&b.end).then(a.kind.cmp(&b.kind));
if c != Ordering::Equal {
return c;
}
match (&a.label, &b.label) {
(Some(Label::Number(x)), Some(Label::Number(y))) => {
x.partial_cmp(y).unwrap_or(Ordering::Equal)
}
(Some(Label::Number(_)), Some(Label::String(_))) => Ordering::Less,
(Some(Label::String(_)), Some(Label::Number(_))) => Ordering::Greater,
(Some(Label::String(x)), Some(Label::String(y))) if x != y => root_collator().compare(x, y),
_ => Ordering::Equal,
}
}
#[cfg(feature = "live")]
pub(crate) fn keys_equal(a: &EventKey, b: &EventKey) -> bool {
a.kind == b.kind && a.begin == b.begin && a.end == b.end && a.label == b.label
}
struct KeyCompare<'a> {
key: &'a Key,
query: &'a EventKey,
collator: Option<CollatorBorrowed<'static>>,
}
impl<'a> KeyCompare<'a> {
fn new(key: &'a Key, query: &'a EventKey) -> Self {
Self {
key,
query,
collator: None,
}
}
fn kind(&self) -> KeyKind {
match self.key {
Key::Time(_) => KeyKind::Time,
Key::TimeRange(_) => KeyKind::TimeRange,
Key::Interval(_) => KeyKind::Interval,
}
}
fn cmp(&mut self, i: usize) -> Ordering {
let q = self.query;
let begin = self.key.begin()[i];
let end = self.key.end().map_or(begin, |e| e[i]);
let c = begin
.cmp(&q.begin)
.then(end.cmp(&q.end))
.then(self.kind().cmp(&q.kind));
if c != Ordering::Equal {
return c;
}
let (Key::Interval(k), Some(ql)) = (self.key, &q.label) else {
return c;
};
match (&k.label, ql) {
(Labels::Number(a), Label::Number(b)) => a[i].partial_cmp(b).unwrap_or(Ordering::Equal),
(Labels::Number(_), Label::String(_)) => Ordering::Less,
(Labels::String(_), Label::Number(_)) => Ordering::Greater,
(Labels::String(a), Label::String(b)) => {
let a = a.get(i).unwrap_or("");
if a == b {
return Ordering::Equal;
}
self.collator
.get_or_insert_with(root_collator)
.compare(a, b)
}
}
}
fn equals(&self, i: usize) -> bool {
let q = self.query;
let begin = self.key.begin()[i];
let end = self.key.end().map_or(begin, |e| e[i]);
if self.kind() != q.kind || begin != q.begin || end != q.end {
return false;
}
match (self.key, &q.label) {
(Key::Interval(k), Some(ql)) => match (&k.label, ql) {
(Labels::Number(a), Label::Number(b)) => a[i] == *b,
(Labels::String(a), Label::String(b)) => a.get(i) == Some(b.as_str()),
_ => false,
},
_ => true,
}
}
fn bisect(&mut self) -> usize {
let (mut low, mut high) = (0, self.key.len());
while low < high {
let mid = (low + high) >> 1;
if self.cmp(mid) == Ordering::Less {
low = mid + 1;
} else {
high = mid;
}
}
low
}
}
impl TimeSeries {
pub fn at(&self, index: i64) -> Option<Row<'_>> {
let n = self.len() as i64;
let i = if index < 0 { n + index } else { index };
if i < 0 || i >= n {
return None;
}
self.row(i as usize)
}
pub fn bisect(&self, key: impl Into<EventKey>) -> usize {
let key = key.into();
KeyCompare::new(self.key(), &key).bisect()
}
pub fn includes_key(&self, key: impl Into<EventKey>) -> bool {
let key = key.into();
let mut c = KeyCompare::new(self.key(), &key);
let i = c.bisect();
i < self.len() && c.equals(i)
}
pub fn at_or_before(&self, key: impl Into<EventKey>) -> Option<Row<'_>> {
let key = key.into();
let mut c = KeyCompare::new(self.key(), &key);
let i = c.bisect();
if i < self.len() && c.equals(i) {
return self.row(i);
}
i.checked_sub(1).and_then(|i| self.row(i))
}
pub fn at_or_after(&self, key: impl Into<EventKey>) -> Option<Row<'_>> {
self.row(self.bisect(key))
}
pub fn nearest(&self, key: impl Into<EventKey>) -> Option<Row<'_>> {
let n = self.len();
if n == 0 {
return None;
}
let key = key.into();
let i = KeyCompare::new(self.key(), &key).bisect();
if i == 0 {
return self.row(0);
}
if i >= n {
return self.row(n - 1);
}
let begin = self.key().begin();
let target = key.begin as i128;
let (before, after) = (begin[i - 1] as i128, begin[i] as i128);
self.row(if target - before <= after - target {
i - 1
} else {
i
})
}
pub fn find<F>(&self, mut test: F) -> Result<Option<Row<'_>>>
where
F: FnMut(&Row<'_>) -> Result<bool>,
{
for row in self.rows() {
if test(&row)? {
return Ok(Some(row));
}
}
Ok(None)
}
pub fn some<F>(&self, test: F) -> Result<bool>
where
F: FnMut(&Row<'_>) -> Result<bool>,
{
Ok(self.find(test)?.is_some())
}
pub fn every<F>(&self, mut test: F) -> Result<bool>
where
F: FnMut(&Row<'_>) -> Result<bool>,
{
for row in self.rows() {
if !test(&row)? {
return Ok(false);
}
}
Ok(true)
}
}
impl Row<'_> {
pub fn key(&self) -> EventKey {
let key = self.series().key();
let i = self.index();
let (begin, end) = (self.begin(), self.end());
match key {
Key::Time(_) => EventKey::time(begin),
Key::TimeRange(_) => EventKey {
begin,
end,
label: None,
kind: KeyKind::TimeRange,
},
Key::Interval(k) => EventKey {
begin,
end,
label: Some(match &k.label {
Labels::Number(v) => Label::Number(v[i]),
Labels::String(s) => Label::String(s.get(i).unwrap_or("").to_string()),
}),
kind: KeyKind::Interval,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Column;
use crate::series::{IntervalKeys, TimeRangeKeys};
fn times() -> TimeSeries {
TimeSeries::new(
"t",
vec![0, 1000, 1000, 2500],
vec![Column::required("v", vec![1.0, 2.0, 3.0, 4.0])],
)
.unwrap()
}
#[test]
fn bisect_lands_before_equal_rows() {
let s = times();
assert_eq!(s.bisect(-5), 0);
assert_eq!(s.bisect(1000), 1);
assert_eq!(s.bisect(1001), 3);
assert_eq!(s.bisect(9000), 4);
}
#[test]
fn at_counts_back_from_the_end() {
let s = times();
assert_eq!(s.at(0).map(|r| r.index()), Some(0));
assert_eq!(s.at(-1).map(|r| r.index()), Some(3));
assert_eq!(s.at(-4).map(|r| r.index()), Some(0));
assert!(s.at(-5).is_none());
assert!(s.at(4).is_none());
assert!(s.at(i64::MIN).is_none());
}
#[test]
fn nearest_ties_go_to_the_earlier_row() {
let s = times();
assert_eq!(s.nearest(500).map(|r| r.index()), Some(0));
assert_eq!(s.nearest(501).map(|r| r.index()), Some(1));
assert_eq!(s.nearest(-100).map(|r| r.index()), Some(0));
assert_eq!(s.nearest(1 << 40).map(|r| r.index()), Some(3));
assert!(s.slice(0..0).nearest(0).is_none());
}
#[test]
fn a_range_key_is_not_a_time_key() {
let s = times();
let r = EventKey::time_range(1000, 1000).unwrap();
assert!(!s.includes_key(r.clone()));
assert_eq!(s.bisect(r), 3);
assert!(EventKey::time_range(2, 1).is_err());
assert!(EventKey::interval(f64::NAN, 0, 1).is_err());
}
#[test]
fn interval_labels_order_numbers_before_text() {
let s = TimeSeries::from_parts(
"i".into(),
"interval".into(),
Key::Interval(IntervalKeys {
label: vec!["a", "B", "b"].into(),
begin: vec![0, 0, 0],
end: vec![10, 10, 10],
}),
vec![],
);
let k = |l: &str| EventKey::interval(l, 0, 10).unwrap();
assert_eq!(s.bisect(EventKey::interval(5.0, 0, 10).unwrap()), 0);
assert_eq!(s.bisect(k("b")), 1); assert!(s.includes_key(k("B")));
assert_eq!(s.at(1).unwrap().key(), k("B"));
let r = TimeSeries::from_parts(
"r".into(),
"timeRange".into(),
Key::TimeRange(TimeRangeKeys {
begin: vec![0],
end: vec![5],
}),
vec![],
);
assert_eq!(r.at(0).unwrap().key(), EventKey::time_range(0, 5).unwrap());
assert_eq!(r.at(0).unwrap().key().kind(), "timeRange");
}
#[test]
fn row_key_gives_time_and_number_labelled_keys() {
let t = times().at(1).unwrap().key();
assert_eq!(
(t.clone(), t.kind(), t.label()),
(EventKey::time(1000), "time", None)
);
let n = TimeSeries::from_parts(
"n".into(),
"interval".into(),
Key::Interval(IntervalKeys {
label: vec![0.0, 5000.0].into(),
begin: vec![0, 5000],
end: vec![5000, 10000],
}),
vec![],
);
let k = n.at(-1).unwrap().key();
assert_eq!(k, EventKey::interval(5000.0, 5000, 10000).unwrap());
assert_eq!(k.label(), Some(&Label::Number(5000.0)));
assert!(n.includes_key(k));
}
#[test]
fn find_some_every_stop_early_and_pass_errors() {
let s = times();
let mut seen = 0;
let hit = s
.find(|r| {
seen += 1;
Ok(r.number("v")? == Some(2.0))
})
.unwrap();
assert_eq!((hit.map(|r| r.index()), seen), (Some(1), 2));
assert!(s.some(|r| Ok(r.begin() == 2500)).unwrap());
assert!(!s.every(|r| Ok(r.begin() > 0)).unwrap());
assert!(s.slice(0..0).every(|_| Ok(false)).unwrap());
assert!(!s.slice(0..0).some(|_| Ok(true)).unwrap());
assert!(s.find(|r| r.number("nope").map(|_| true)).is_err());
}
}