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}