siderite_orm/queryset/cursor/
mod.rs1mod seek;
11mod token;
12
13use super::QuerySet;
14use crate::backend::Row;
15use crate::capabilities::Feature;
16use crate::error::{OrmError, QueryError};
17use crate::model::Model;
18use crate::value::Value;
19use seek::{Seek, Term};
20use token::Slot;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub(super) enum Way {
25 After,
27 Before,
29}
30
31impl Way {
32 fn byte(self) -> u8 {
34 match self {
35 Self::After => b'a',
36 Self::Before => b'b',
37 }
38 }
39}
40
41#[derive(Debug, Clone, PartialEq)]
47pub struct CursorPage<M> {
48 pub items: Vec<M>,
50 pub next: Option<String>,
52 pub previous: Option<String>,
54}
55
56impl<M> CursorPage<M> {
57 fn empty() -> Self {
58 Self {
59 items: Vec::new(),
60 next: None,
61 previous: None,
62 }
63 }
64}
65
66type Fetched<M> = Result<CursorPage<M>, OrmError>;
68
69type Token = Result<Option<String>, QueryError>;
71
72fn missing(column: &str) -> QueryError {
73 QueryError::Decode {
74 column: column.to_owned(),
75 reason: "column missing from result".into(),
76 }
77}
78
79struct Cursors<'a> {
81 terms: &'a [Term],
82 fingerprint: u64,
83}
84
85impl Cursors<'_> {
86 fn values(&self, row: &Row) -> Result<Vec<Value>, QueryError> {
88 self.terms
89 .iter()
90 .map(|term| {
91 let column = term.field.column;
92 let raw = row.get(column).cloned();
93 let raw = raw.ok_or_else(|| missing(column))?;
94 seek::canonical(column, term.field.sql_type, raw)
95 })
96 .collect()
97 }
98
99 fn token(&self, way: Way, values: Option<&[Value]>) -> Token {
101 let encode = |v: &[Value]| token::encode(self.fingerprint, way, v);
102 values.map(encode).transpose()
103 }
104}
105
106impl<M: Model> QuerySet<M> {
107 #[must_use]
113 pub fn after(self, token: &str) -> Self {
114 self.with_cursor(Way::After, token)
115 }
116
117 #[must_use]
120 pub fn before(self, token: &str) -> Self {
121 self.with_cursor(Way::Before, token)
122 }
123
124 fn with_cursor(mut self, way: Way, token: &str) -> Self {
125 if self.cursor.as_ref().is_some_and(|(set, _)| *set != way) {
126 return self.fail("`after` and `before` cannot be combined");
127 }
128 self.cursor = Some((way, token.to_owned()));
129 self
130 }
131
132 pub async fn cursor_page(self, limit: u64) -> Fetched<M> {
150 if limit == 0 {
151 let err = QueryError::InvalidPlan("limit starts at 1".into());
152 return Err(err.into());
153 }
154 self.ready()?;
155 if self.empty {
156 return Ok(CursorPage::empty());
157 }
158 let caps = self.db.capabilities();
159 caps.require(Feature::KeysetPagination)?;
160 self.plan.check(&caps)?;
161 seek::check_shape(&self.plan)?;
162 let terms = seek::terms::<M>(&self.plan)?;
163 if terms.iter().any(|t| t.field.nullable) {
164 caps.require(Feature::NullableKeyset)?;
165 }
166 let print = seek::fingerprint(&self.plan, &terms);
167 let cursors = Cursors {
168 terms: &terms,
169 fingerprint: print,
170 };
171 let slots: Vec<Slot> = terms.iter().map(Term::slot).collect();
172 let (way, at) = match &self.cursor {
173 None => (Way::After, None),
174 Some((way, raw)) => {
175 let values = token::decode(raw, print, *way, &slots)?;
176 (*way, Some(values))
177 }
178 };
179 let backward = way == Way::Before;
180 let seek = Seek::new(&self.plan, &terms).backwards(backward);
181 let order = seek.ordering();
182 let predicate = at.as_deref().map(|values| seek.predicate(values));
183 let paged = self.with_plan(|mut plan| {
184 plan.ordering = order;
185 if let Some(predicate) = predicate {
186 plan = plan.filter(predicate);
187 }
188 plan.limit(limit.saturating_add(1))
189 });
190 let mut rows = paged.db.fetch(&paged.plan).await?.rows;
191 let size = usize::try_from(limit).unwrap_or(usize::MAX);
192 let more = rows.len() > size;
193 rows.truncate(size);
194 if backward {
195 rows.reverse();
196 }
197 let first = rows.first().map(|row| cursors.values(row)).transpose()?;
198 let last = rows.last().map(|row| cursors.values(row)).transpose()?;
199 let items = paged
200 .decode_rows(rows)
201 .await?
202 .into_iter()
203 .map(|(model, _)| model)
204 .collect();
205 let (next, previous) = if backward {
206 let previous = if more { first.as_deref() } else { None };
207 let next = last.as_deref().or(at.as_deref());
208 (next, previous)
209 } else {
210 let next = if more { last.as_deref() } else { None };
211 let previous = at.as_ref().and(first.as_deref().or(at.as_deref()));
212 (next, previous)
213 };
214 let next = cursors.token(Way::After, next)?;
215 let previous = cursors.token(Way::Before, previous)?;
216 Ok(CursorPage {
217 items,
218 next,
219 previous,
220 })
221 }
222}
223
224#[cfg(test)]
225mod tests;