use crate::expression::{Expression, SortOrder};
#[derive(Debug, Clone, PartialEq)]
pub enum FrameBound {
UnboundedPreceding,
Preceding(i64),
CurrentRow,
Following(i64),
UnboundedFollowing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameType {
Row,
Range,
}
#[derive(Debug, Clone, PartialEq)]
pub struct WindowSpec {
pub partition_spec: Vec<Expression>,
pub order_spec: Vec<SortOrder>,
pub frame_spec: Option<(FrameType, FrameBound, FrameBound)>,
}
impl WindowSpec {
pub fn new() -> Self {
Self {
partition_spec: Vec::new(),
order_spec: Vec::new(),
frame_spec: None,
}
}
pub fn partition_by(mut self, cols: Vec<Expression>) -> Self {
self.partition_spec.extend(cols);
self
}
pub fn order_by(mut self, sorts: Vec<SortOrder>) -> Self {
self.order_spec.extend(sorts);
self
}
pub fn rows_between(mut self, start: FrameBound, end: FrameBound) -> Self {
self.frame_spec = Some((FrameType::Row, start, end));
self
}
pub fn range_between(mut self, start: FrameBound, end: FrameBound) -> Self {
self.frame_spec = Some((FrameType::Range, start, end));
self
}
}
impl Default for WindowSpec {
fn default() -> Self {
Self::new()
}
}
pub struct Window;
impl Window {
pub const fn unbounded_preceding() -> FrameBound {
FrameBound::UnboundedPreceding
}
pub const fn current_row() -> FrameBound {
FrameBound::CurrentRow
}
pub const fn unbounded_following() -> FrameBound {
FrameBound::UnboundedFollowing
}
pub fn partition_by(cols: Vec<Expression>) -> WindowSpec {
WindowSpec::new().partition_by(cols)
}
pub fn order_by(sorts: Vec<SortOrder>) -> WindowSpec {
WindowSpec::new().order_by(sorts)
}
pub fn rows_between(start: FrameBound, end: FrameBound) -> WindowSpec {
WindowSpec::new().rows_between(start, end)
}
pub fn range_between(start: FrameBound, end: FrameBound) -> WindowSpec {
WindowSpec::new().range_between(start, end)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::expression::{ColumnReference, NullOrdering, SortOrder};
fn col(name: &str) -> Expression {
Expression::ColumnReference(ColumnReference::new(name))
}
fn sort(name: &str) -> SortOrder {
SortOrder {
child: col(name),
ascending: true,
null_ordering: NullOrdering::First,
}
}
#[test]
fn new_and_default_are_empty() {
let w = WindowSpec::new();
assert!(w.partition_spec.is_empty());
assert!(w.order_spec.is_empty());
assert!(w.frame_spec.is_none());
assert_eq!(WindowSpec::default(), w);
}
#[test]
fn spec_builders_chain() {
let w = WindowSpec::new()
.partition_by(vec![col("a"), col("b")])
.order_by(vec![sort("c")]);
assert_eq!(w.partition_spec.len(), 2);
assert_eq!(w.order_spec.len(), 1);
}
#[test]
fn rows_between_sets_row_frame() {
let w = WindowSpec::new().rows_between(FrameBound::Preceding(1), FrameBound::Following(2));
assert_eq!(
w.frame_spec,
Some((
FrameType::Row,
FrameBound::Preceding(1),
FrameBound::Following(2)
))
);
}
#[test]
fn range_between_sets_range_frame() {
let w =
WindowSpec::new().range_between(FrameBound::UnboundedPreceding, FrameBound::CurrentRow);
assert_eq!(
w.frame_spec,
Some((
FrameType::Range,
FrameBound::UnboundedPreceding,
FrameBound::CurrentRow
))
);
}
#[test]
fn a_later_frame_replaces_an_earlier_one() {
let w = WindowSpec::new()
.rows_between(FrameBound::UnboundedPreceding, FrameBound::CurrentRow)
.range_between(FrameBound::CurrentRow, FrameBound::UnboundedFollowing);
assert_eq!(
w.frame_spec,
Some((
FrameType::Range,
FrameBound::CurrentRow,
FrameBound::UnboundedFollowing
))
);
}
#[test]
fn window_static_constructors_match_spec_builders() {
assert_eq!(
Window::partition_by(vec![col("a")]),
WindowSpec::new().partition_by(vec![col("a")])
);
assert_eq!(
Window::order_by(vec![sort("a")]),
WindowSpec::new().order_by(vec![sort("a")])
);
assert_eq!(
Window::rows_between(FrameBound::Preceding(1), FrameBound::CurrentRow),
WindowSpec::new().rows_between(FrameBound::Preceding(1), FrameBound::CurrentRow)
);
assert_eq!(
Window::range_between(FrameBound::CurrentRow, FrameBound::Following(3)),
WindowSpec::new().range_between(FrameBound::CurrentRow, FrameBound::Following(3))
);
}
#[test]
fn frame_bound_constants() {
assert_eq!(
Window::unbounded_preceding(),
FrameBound::UnboundedPreceding
);
assert_eq!(Window::current_row(), FrameBound::CurrentRow);
assert_eq!(
Window::unbounded_following(),
FrameBound::UnboundedFollowing
);
}
}