use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WindowFrameBoundary {
CurrentRow,
Preceding(usize),
Following(usize),
UnboundedPreceding,
UnboundedFollowing,
}
impl fmt::Display for WindowFrameBoundary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CurrentRow => write!(f, "CURRENT ROW"),
Self::Preceding(n) => write!(f, "{} PRECEDING", n),
Self::Following(n) => write!(f, "{} FOLLOWING", n),
Self::UnboundedPreceding => write!(f, "UNBOUNDED PRECEDING"),
Self::UnboundedFollowing => write!(f, "UNBOUNDED FOLLOWING"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WindowFrameType {
Rows,
Range,
}
impl fmt::Display for WindowFrameType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Rows => write!(f, "ROWS"),
Self::Range => write!(f, "RANGE"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WindowFrame {
frame_type: WindowFrameType,
start: WindowFrameBoundary,
end: WindowFrameBoundary,
}
impl WindowFrame {
pub fn new(
frame_type: WindowFrameType,
start: WindowFrameBoundary,
end: WindowFrameBoundary,
) -> Self {
Self {
frame_type,
start,
end,
}
}
pub fn rows(start: WindowFrameBoundary, end: WindowFrameBoundary) -> Self {
Self::new(WindowFrameType::Rows, start, end)
}
pub fn range(start: WindowFrameBoundary, end: WindowFrameBoundary) -> Self {
Self::new(WindowFrameType::Range, start, end)
}
pub fn preceding(n: usize) -> Self {
Self::rows(
WindowFrameBoundary::Preceding(n),
WindowFrameBoundary::CurrentRow,
)
}
pub fn unbounded_preceding() -> Self {
Self::rows(
WindowFrameBoundary::UnboundedPreceding,
WindowFrameBoundary::CurrentRow,
)
}
pub fn current_row() -> Self {
Self::rows(
WindowFrameBoundary::CurrentRow,
WindowFrameBoundary::CurrentRow,
)
}
pub fn surrounding(preceding: usize, following: usize) -> Self {
Self::rows(
WindowFrameBoundary::Preceding(preceding),
WindowFrameBoundary::Following(following),
)
}
pub fn entire_partition() -> Self {
Self::rows(
WindowFrameBoundary::UnboundedPreceding,
WindowFrameBoundary::UnboundedFollowing,
)
}
pub fn to_sql(&self) -> String {
format!(
"{} BETWEEN {} AND {}",
self.frame_type, self.start, self.end
)
}
}
#[derive(Debug, Clone)]
pub struct WindowFunction {
pub function: String,
pub inputs: Vec<String>,
pub output: String,
pub partition_by: Vec<String>,
pub order_by: Vec<(String, bool)>, pub frame: Option<WindowFrame>,
}
impl WindowFunction {
pub fn new(
function: &str,
inputs: &[&str],
output: &str,
partition_by: &[&str],
order_by: &[(&str, bool)],
frame: Option<WindowFrame>,
) -> Self {
Self {
function: function.to_string(),
inputs: inputs.iter().map(|s| s.to_string()).collect(),
output: output.to_string(),
partition_by: partition_by.iter().map(|s| s.to_string()).collect(),
order_by: order_by.iter().map(|(s, b)| (s.to_string(), *b)).collect(),
frame,
}
}
pub fn single_input(
function: &str,
input: &str,
output: &str,
partition_by: &[&str],
order_by: &[(&str, bool)],
frame: Option<WindowFrame>,
) -> Self {
Self::new(function, &[input], output, partition_by, order_by, frame)
}
pub fn to_sql(&self) -> String {
let inputs = if self.inputs.is_empty() {
"*".to_string()
} else {
self.inputs.join(", ")
};
let mut result = format!("{}({}) OVER (", self.function, inputs);
if !self.partition_by.is_empty() {
result.push_str(&format!("PARTITION BY {} ", self.partition_by.join(", ")));
}
if !self.order_by.is_empty() {
result.push_str("ORDER BY ");
let mut order_parts = Vec::new();
for (col, asc) in &self.order_by {
let direction = if *asc { "ASC" } else { "DESC" };
order_parts.push(format!("{} {}", col, direction));
}
result.push_str(&order_parts.join(", "));
result.push(' ');
}
if let Some(frame) = &self.frame {
result.push_str(&frame.to_sql());
}
result.push_str(")");
format!("{} AS {}", result, self.output)
}
}