use crate::expr::{Expr, IntoExpr};
use crate::writer::{Expression, SqlWriter};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum RowsKeyword {
Row,
#[default]
Rows,
}
impl RowsKeyword {
pub fn as_str(self) -> &'static str {
match self {
RowsKeyword::Row => "ROW",
RowsKeyword::Rows => "ROWS",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Offset {
pub count: Option<Expr>,
pub rows: Option<RowsKeyword>,
}
impl Offset {
pub fn set_offset(&mut self, count: impl IntoExpr) {
self.count = Some(count.into_expr());
}
pub fn is_empty(&self) -> bool {
self.count.is_none()
}
}
impl Expression for Offset {
fn write_sql(&self, w: &mut SqlWriter<'_>) {
let Some(count) = &self.count else {
return;
};
w.push_str("OFFSET ");
w.write_expr(count);
if let Some(rows) = self.rows {
w.push_str(" ");
w.push_str(rows.as_str());
}
}
}
pub trait HasOffset {
fn offset_mut(&mut self) -> &mut Offset;
}
impl HasOffset for Offset {
fn offset_mut(&mut self) -> &mut Offset {
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 {}"#;
#[test]
fn an_unset_offset_writes_nothing() {
assert_frag_sql(FRAME, &build(&Numbered, &Offset::default()).unwrap().0, "");
assert!(Offset::default().is_empty());
}
#[test]
fn the_count_is_written_after_the_keyword() {
let mut o = Offset::default();
o.set_offset(5i64);
assert_frag_sql(FRAME, &build(&Numbered, &o).unwrap().0, "OFFSET 5");
o.set_offset(arg(5i64));
let (sql, args) = build(&Numbered, &o).unwrap();
assert_frag_sql(FRAME, &sql, "OFFSET $1");
assert_eq!(args, vec![Value::I64(5)]);
}
#[test]
fn the_rows_keyword_follows_the_count_in_either_number() {
let mut o = Offset::default();
o.set_offset(5i64);
o.rows = Some(RowsKeyword::Rows);
assert_frag_sql(FRAME, &build(&Numbered, &o).unwrap().0, "OFFSET 5 ROWS");
o.rows = Some(RowsKeyword::Row);
assert_frag_sql(FRAME, &build(&Numbered, &o).unwrap().0, "OFFSET 5 ROW");
}
#[test]
fn the_count_may_be_bound_with_the_keyword_present() {
let mut o = Offset::default();
o.set_offset(arg(5i64));
o.rows = Some(RowsKeyword::Rows);
let (sql, args) = build(&Numbered, &o).unwrap();
assert_frag_sql(FRAME, &sql, "OFFSET $1 ROWS");
assert_eq!(args, vec![Value::I64(5)]);
}
#[test]
fn the_keyword_alone_writes_nothing() {
let o = Offset {
count: None,
rows: Some(RowsKeyword::Rows),
};
assert_frag_sql(FRAME, &build(&Numbered, &o).unwrap().0, "");
assert!(o.is_empty());
}
}