Skip to main content

turso_orm/query/
cursor.rs

1//! Keyset pagination, modeled by [`Cursor`].
2//!
3//! Offset pagination re-reads and discards every row before the page, and
4//! a page can shift when rows are inserted ahead of it. A cursor instead
5//! remembers the key of the last row seen and asks for the rows after it,
6//! so each page costs an index seek and stays stable under concurrent
7//! writes. The key is one or more columns of the entity, usually the primary
8//! key or a unique ordering column plus the primary key as a tie-breaker.
9//!
10//! A composite key is compared lexicographically through the expanded form
11//! `a > ?1 OR (a = ?1 AND b > ?2)` rather than a row-value comparison, so
12//! the statement only uses operators every SQLite build accepts. The key
13//! columns are given as an array, `[Column::Name, Column::Id]`, and the
14//! boundary values as a matching tuple.
15
16use std::marker::PhantomData;
17
18use turso_orm_driver::ConnectionTrait;
19use turso_sql::{Condition, Expr, Order, Statement, Value};
20
21use crate::Result;
22use crate::entity::{ColumnTrait, EntityTrait, FromQueryResult, IdenStatic};
23use crate::query::select::Selector;
24use crate::types::IntoValueTuple;
25
26/// A keyset-paginated `SELECT`.
27///
28/// Built by [`Select::cursor_by`](crate::query::Select::cursor_by). Set the
29/// boundary with [`after`](Self::after) or [`before`](Self::before), the
30/// page size and direction with [`first`](Self::first) or
31/// [`last`](Self::last), then run [`all`](Self::all).
32#[derive(Clone, Debug)]
33pub struct Cursor<E: EntityTrait, M> {
34    /// The query without boundary, ordering and limit.
35    query: turso_sql::Select,
36    /// The key columns, most significant first.
37    columns: Vec<E::Column>,
38    /// The exclusive lower bound, as one value per key column.
39    after: Option<Vec<Value>>,
40    /// The exclusive upper bound, as one value per key column.
41    before: Option<Vec<Value>>,
42    /// The page size and whether it is taken from the start (`true`) or the
43    /// end (`false`) of the key range.
44    page: Option<(u64, bool)>,
45    /// Whether the key is ordered descending.
46    descending: bool,
47    /// Ties the cursor to its entity and result type without storing them.
48    _m: PhantomData<(E, M)>,
49}
50
51impl<E: EntityTrait, M: FromQueryResult> Cursor<E, M> {
52    /// Wraps a query with the key columns it is paginated by.
53    pub(crate) fn new(query: turso_sql::Select, columns: Vec<E::Column>) -> Self {
54        Self {
55            query,
56            columns,
57            after: None,
58            before: None,
59            page: None,
60            descending: false,
61            _m: PhantomData,
62        }
63    }
64
65    /// Returns rows whose key is strictly greater than `key`.
66    #[must_use]
67    pub fn after(mut self, key: impl IntoValueTuple) -> Self {
68        self.after = Some(key.into_value_tuple());
69        self
70    }
71
72    /// Returns rows whose key is strictly less than `key`.
73    #[must_use]
74    pub fn before(mut self, key: impl IntoValueTuple) -> Self {
75        self.before = Some(key.into_value_tuple());
76        self
77    }
78
79    /// Takes the first `n` rows of the key range, in key order.
80    #[must_use]
81    pub fn first(mut self, n: u64) -> Self {
82        self.page = Some((n, true));
83        self
84    }
85
86    /// Takes the last `n` rows of the key range, still returned in key order.
87    ///
88    /// The statement reads them in reverse order with `LIMIT n` and the
89    /// result is reversed in memory, which is the only way to take the tail
90    /// of a range without knowing its size.
91    #[must_use]
92    pub fn last(mut self, n: u64) -> Self {
93        self.page = Some((n, false));
94        self
95    }
96
97    /// Orders the key ascending, the default.
98    #[must_use]
99    pub fn asc(mut self) -> Self {
100        self.descending = false;
101        self
102    }
103
104    /// Orders the key descending, so that `after` moves towards smaller keys.
105    #[must_use]
106    pub fn desc(mut self) -> Self {
107        self.descending = true;
108        self
109    }
110
111    /// Decodes rows into `T` instead of the current result type.
112    pub fn into_model<T: FromQueryResult>(self) -> Cursor<E, T> {
113        Cursor {
114            query: self.query,
115            columns: self.columns,
116            after: self.after,
117            before: self.before,
118            page: self.page,
119            descending: self.descending,
120            _m: PhantomData,
121        }
122    }
123
124    /// Builds `(a > v1) OR (a = v1 AND b > v2) OR ...`, or the mirror with
125    /// `<`, for the key boundary `values`.
126    fn boundary(&self, values: &[Value], greater: bool) -> Condition {
127        let mut any = Condition::any();
128        for (i, column) in self.columns.iter().enumerate() {
129            let mut all = Condition::all();
130            for (prefix, value) in self.columns.iter().zip(values).take(i) {
131                all = all.add(prefix.into_expr().eq(Expr::val(value.clone())));
132            }
133            let Some(value) = values.get(i) else {
134                break;
135            };
136            let bound = if greater {
137                column.into_expr().gt(Expr::val(value.clone()))
138            } else {
139                column.into_expr().lt(Expr::val(value.clone()))
140            };
141            any = any.add(all.add(bound));
142        }
143        any
144    }
145
146    /// Assembles the statement; the second value says whether the rows come
147    /// back in reverse key order and must be flipped.
148    fn assemble(&self) -> (turso_sql::Select, bool) {
149        let mut query = self.query.clone().clear_order_by();
150        // Under a descending key, "after" means smaller and "before" larger.
151        if let Some(after) = &self.after {
152            query = query.and_where(self.boundary(after, !self.descending));
153        }
154        if let Some(before) = &self.before {
155            query = query.and_where(self.boundary(before, self.descending));
156        }
157        let from_start = self.page.is_none_or(|(_, from_start)| from_start);
158        let forward = from_start != self.descending;
159        let order = if forward { Order::Asc } else { Order::Desc };
160        for column in &self.columns {
161            query = query.order_by_expr(column.into_expr(), order);
162        }
163        if let Some((n, _)) = self.page {
164            query = query.limit(n);
165        }
166        (query, !from_start)
167    }
168
169    /// Renders the statement.
170    pub fn build(&self) -> Statement {
171        turso_sql::Build::to_statement(&self.assemble().0)
172    }
173
174    /// Fetches the page, in key order.
175    ///
176    /// # Errors
177    ///
178    /// Returns [`DbErr::Driver`](crate::DbErr::Driver) when the query fails
179    /// or a row cannot be decoded.
180    pub async fn all<C: ConnectionTrait>(&self, db: &C) -> Result<Vec<M>> {
181        let (query, reversed) = self.assemble();
182        let mut rows = Selector::<M>::from_query(query).all(db).await?;
183        if reversed {
184            rows.reverse();
185        }
186        Ok(rows)
187    }
188
189    /// The names of the key columns, most significant first.
190    pub fn key_columns(&self) -> Vec<&'static str> {
191        self.columns.iter().map(IdenStatic::as_str).collect()
192    }
193}