use rudb_common::Result;
use rudb_plan::{Node, Plan};
use crate::pass::{Context, Pass, top_down};
#[derive(Debug, Clone, Copy)]
pub struct TopN;
impl Pass for TopN {
fn name(&self) -> &'static str {
"top_n"
}
fn run(&self, plan: &mut Plan, _context: &Context) -> Result<()> {
fuse(plan);
Ok(())
}
}
pub fn fuse(plan: &mut Plan) {
for node in top_down(plan) {
let (input, count, offset) = match *plan.node(node) {
Node::Limit { input, count: Some(count), offset } => (input, count, offset),
_ => continue,
};
let (below, keys) = match *plan.node(input) {
Node::Sort { input: below, keys } => (below, keys),
_ => continue,
};
*plan.node_mut(node) = Node::TopN { input: below, keys, count, offset };
}
}
#[cfg(test)]
mod tests {
use rudb_plan::Plan;
use super::fuse;
fn fused(text: &str) -> String {
let mut plan =
Plan::parse(text).unwrap_or_else(|error| panic!("{text} did not parse: {error}"));
fuse(&mut plan);
plan.validate().unwrap_or_else(|error| panic!("{text} did not stay valid: {error}"));
plan.to_string()
}
#[test]
fn a_limit_over_a_sort_becomes_one_operator() {
assert_eq!(
fused(concat!(
"Limit 10 offset 0\n",
" Sort [#0.1::INTEGER ASC NULLS LAST]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
)),
concat!(
"TopN 10 offset 0 [#0.1::INTEGER ASC NULLS LAST]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
)
);
}
#[test]
fn the_offset_comes_along_with_the_count() {
assert_eq!(
fused(concat!(
"Limit 5 offset 20\n",
" Sort [#0.0::INTEGER DESC NULLS FIRST, #0.1::INTEGER ASC NULLS LAST]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
)),
concat!(
"TopN 5 offset 20 [#0.0::INTEGER DESC NULLS FIRST, #0.1::INTEGER ASC NULLS LAST]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
)
);
}
#[test]
fn a_limit_with_no_count_is_left_alone_because_there_is_no_bound_to_hold() {
let text = concat!(
"Limit ALL offset 4\n",
" Sort [#0.0::INTEGER ASC NULLS LAST]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
);
assert_eq!(fused(text), text);
}
#[test]
fn a_limit_over_anything_else_is_left_alone() {
let text = concat!(
"Limit 10 offset 0\n",
" Distinct on=[]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
);
assert_eq!(fused(text), text);
}
#[test]
fn a_sort_with_no_limit_over_it_is_still_a_sort() {
let text = concat!(
"Sort [#0.0::INTEGER ASC NULLS LAST]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
);
assert_eq!(fused(text), text);
}
#[test]
fn running_it_twice_is_running_it_once() {
let text = concat!(
"Limit 3 offset 1\n",
" Sort [#0.0::INTEGER ASC NULLS LAST]\n",
" Get memory.main.t AS t #0 [a::INTEGER, b::INTEGER]\n",
);
let once = fused(text);
assert_eq!(fused(&once), once);
}
}