use super::offset::RowsKeyword;
use crate::expr::{Expr, IntoExpr};
use crate::writer::{Expression, SqlWriter};
#[derive(Debug, Clone, Default)]
pub struct Fetch {
pub count: Option<Expr>,
pub first_or_next: FirstOrNext,
pub rows: RowsKeyword,
pub with_ties: bool,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum FirstOrNext {
First,
#[default]
Next,
}
impl FirstOrNext {
pub fn as_str(self) -> &'static str {
match self {
FirstOrNext::First => "FIRST",
FirstOrNext::Next => "NEXT",
}
}
}
impl Fetch {
pub fn new(count: impl IntoExpr) -> Self {
Fetch {
count: Some(count.into_expr()),
..Fetch::default()
}
}
pub fn set_fetch(&mut self, count: impl IntoExpr) {
self.count = Some(count.into_expr());
}
pub fn is_empty(&self) -> bool {
self.count.is_none()
}
}
impl Expression for Fetch {
fn write_sql(&self, w: &mut SqlWriter<'_>) {
let Some(count) = &self.count else {
return;
};
w.push_str("FETCH ");
w.push_str(self.first_or_next.as_str());
w.push_str(" ");
w.write_expr(count);
w.push_str(" ");
w.push_str(self.rows.as_str());
w.push_str(if self.with_ties {
" WITH TIES"
} else {
" ONLY"
});
}
}
pub trait HasFetch {
fn fetch_mut(&mut self) -> &mut Fetch;
}
impl HasFetch for Fetch {
fn fetch_mut(&mut self) -> &mut Fetch {
self
}
}
#[cfg(test)]
mod tests {
use keelson_sqlcheck::testing::assert_frag_sql;
use super::*;
use crate::dialect::testing::Numbered;
use crate::expr::arg;
use crate::value::Value;
use crate::writer::build;
const FRAME: &str = r#"SELECT "id" FROM users ORDER BY "id" {}"#;
fn sql(f: &Fetch) -> String {
build(&Numbered, f).expect("render").0
}
#[test]
fn an_unset_fetch_writes_nothing_even_with_spellings_asked_for() {
let f = Fetch {
count: None,
first_or_next: FirstOrNext::First,
rows: RowsKeyword::Row,
with_ties: true,
};
assert_frag_sql(FRAME, &sql(&f), "");
assert!(Fetch::default().is_empty());
}
#[test]
fn the_suffix_switches_on_with_ties() {
let mut f = Fetch::new(3i64);
assert_frag_sql(FRAME, &sql(&f), "FETCH NEXT 3 ROWS ONLY");
f.with_ties = true;
assert_frag_sql(FRAME, &sql(&f), "FETCH NEXT 3 ROWS WITH TIES");
}
#[test]
fn first_and_row_are_the_other_spellings() {
let mut f = Fetch::new(3i64);
f.first_or_next = FirstOrNext::First;
assert_frag_sql(FRAME, &sql(&f), "FETCH FIRST 3 ROWS ONLY");
f.rows = RowsKeyword::Row;
assert_frag_sql(FRAME, &sql(&f), "FETCH FIRST 3 ROW ONLY");
f.first_or_next = FirstOrNext::Next;
f.set_fetch(1i64);
assert_frag_sql(FRAME, &sql(&f), "FETCH NEXT 1 ROW ONLY");
}
#[test]
fn the_count_may_be_bound() {
let mut f = Fetch::default();
f.set_fetch(arg(3i64));
let (rendered, args) = build(&Numbered, &f).unwrap();
assert_frag_sql(FRAME, &rendered, "FETCH NEXT $1 ROWS ONLY");
assert_eq!(args, vec![Value::I64(3)]);
}
#[test]
fn first_composes_with_a_bound_count_and_ties() {
let mut f = Fetch::default();
f.set_fetch(arg(3i64));
f.first_or_next = FirstOrNext::First;
f.with_ties = true;
let (rendered, args) = build(&Numbered, &f).unwrap();
assert_frag_sql(FRAME, &rendered, "FETCH FIRST $1 ROWS WITH TIES");
assert_eq!(args, vec![Value::I64(3)]);
}
}