Skip to main content

keelson_core/clause/
limit.rs

1use crate::expr::{Expr, IntoExpr};
2use crate::writer::{Expression, SqlWriter};
3
4/// `LIMIT n`
5///
6/// The count is an expression, not a number: SQLite accepts an arbitrary
7/// expression there, and every dialect accepts a bound argument. `limit(10)` gives
8/// the literal `LIMIT 10` because a number converts to
9/// [`Expr::Raw`](crate::expr::Expr::Raw); `limit(arg(10))` binds it instead.
10/// Narrowing that back down to a literal is a dialect mod's job.
11#[derive(Debug, Clone, Default)]
12pub struct Limit {
13    /// How many rows.
14    pub count: Option<Expr>,
15}
16
17impl Limit {
18    /// Set the count.
19    pub fn set_limit(&mut self, count: impl IntoExpr) {
20        self.count = Some(count.into_expr());
21    }
22
23    /// Whether the clause is absent.
24    pub fn is_empty(&self) -> bool {
25        self.count.is_none()
26    }
27}
28
29impl Expression for Limit {
30    fn write_sql(&self, w: &mut SqlWriter<'_>) {
31        w.write_if_some(self.count.as_ref(), "LIMIT ", "");
32    }
33}
34
35/// A statement with a `LIMIT`.
36pub trait HasLimit {
37    /// The `LIMIT` clause to modify.
38    fn limit_mut(&mut self) -> &mut Limit;
39}
40
41impl HasLimit for Limit {
42    fn limit_mut(&mut self) -> &mut Limit {
43        self
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use keelson_sqlcheck::testing::assert_frag_sql;
50
51    use super::*;
52    use crate::dialect::testing::Numbered;
53    use crate::expr::arg;
54    use crate::value::Value;
55    use crate::writer::build;
56
57    /// `LIMIT` is a fragment; this is the statement it trails.
58    const FRAME: &str = r#"SELECT "id" FROM users {}"#;
59
60    fn sql(l: &Limit) -> String {
61        build(&Numbered, l).expect("render").0
62    }
63
64    #[test]
65    fn an_unset_limit_writes_nothing() {
66        assert_frag_sql(FRAME, &sql(&Limit::default()), "");
67        assert!(Limit::default().is_empty());
68    }
69
70    #[test]
71    fn a_count_is_a_literal_unless_it_is_bound() {
72        let mut l = Limit::default();
73        l.set_limit(10i64);
74        let (rendered, args) = build(&Numbered, &l).unwrap();
75        assert_frag_sql(FRAME, &rendered, "LIMIT 10");
76        assert!(args.is_empty(), "a number is a literal, not an argument");
77
78        l.set_limit(arg(10i64));
79        let (rendered, args) = build(&Numbered, &l).unwrap();
80        assert_frag_sql(FRAME, &rendered, "LIMIT $1");
81        assert_eq!(args, vec![Value::I64(10)]);
82    }
83
84    #[test]
85    fn a_count_may_be_any_expression() {
86        // SQLite: "LIMIT expr" takes a full expression. PostgreSQL 17 sql-select
87        // spells the same slot `LIMIT { count | ALL }` where count is an
88        // a_expr, so a scalar sub-query is one there too.
89        let mut l = Limit::default();
90        l.set_limit("(SELECT count(*) FROM users)");
91        assert_frag_sql(FRAME, &sql(&l), "LIMIT (SELECT count(*) FROM users)");
92    }
93}