Skip to main content

densa_arrow/paged/
mod.rs

1//! Rows of a table too large to hold, fetched a page at a time as the grid shows them.
2
3mod source;
4mod worker;
5
6use std::collections::{BTreeMap, BTreeSet};
7use std::io;
8use std::ops::Range;
9use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
10use std::sync::Arc;
11use std::thread;
12
13use arrow_schema::SchemaRef;
14use densa_grid::{CellStyle, CellText, Column, GridModel, Palette, SortOrder, CELL_BYTES};
15
16pub use source::{FetchError, PageError, PageOptions, PageQuery, PageSource, SortKey};
17use worker::{Ask, Fetched, Got, Wanted, Worker};
18
19use crate::reader::{Format, Reader};
20use crate::table::{
21    columns_of, new_formats, size_column, widen_column, write_value, CellState, TableError,
22};
23
24/// A page held: its rows, its columns, and the epoch it was fetched in (a page of an earlier
25/// one is shown until the page fetched again replaces it).
26#[derive(Debug)]
27struct Page {
28    epoch: u64,
29    rows: usize,
30    readers: Vec<Reader>,
31}
32
33/// A grid model (for `densa_widgets::DataGrid`) of rows fetched a page at a time from a
34/// [`PageSource`]: a table too large to hold, such as a query engine's result over files of
35/// billions of rows. The grid shows only some rows, and the model fetches their pages and one
36/// on each side, on a thread of its own, so the window never waits; rows not yet fetched show
37/// empty until they arrive.
38///
39/// A click on a column title asks the source for its rows in that order. Cells are formatted
40/// as in [`ArrowTable`](crate::ArrowTable); columns are sized by the first rows and widened
41/// when later numbers no longer fit. Rows have no keys: a selection is a position. A copy of
42/// selected rows holds those among them that are held ([`GridModel::copy_rows`]): selecting all
43/// of a billion rows copies those held, which can be runs far apart. On a 32-bit target, rows
44/// past `usize::MAX` are not shown.
45///
46/// Costs: a frame whose pages are held allocates nothing; one that asks for pages or takes
47/// them in does. Dropping the model ends its thread once the source's current call returns.
48#[derive(Debug)]
49pub struct PagedModel {
50    schema: SchemaRef,
51    options: PageOptions,
52    formats: Vec<Format>,
53    /// Whether rows have sized the columns.
54    sized: bool,
55    columns: Vec<Column>,
56    columns_revision: u64,
57    /// Whether the source can sort by each column.
58    sortable: Vec<bool>,
59    pages: BTreeMap<u64, Page>,
60    /// Pages asked for and not yet back.
61    asked: BTreeSet<u64>,
62    /// Pages the source failed to give, not asked again until a retry or a refresh.
63    failed: BTreeSet<u64>,
64    /// The rows there are, once counted.
65    rows: Option<u64>,
66    /// Whether the rows were counted since the last refresh: pages are asked for only then.
67    counted: bool,
68    /// Whether a count is on its way.
69    counting: bool,
70    /// The epoch the count on its way was asked in: one of an earlier epoch is dropped, one of
71    /// a later epoch (a sort, which does not count again) taken.
72    count_epoch: u64,
73    query: PageQuery,
74    sort: Option<(usize, SortOrder)>,
75    /// Bumped by every new query or refresh: replies of earlier ones are dropped.
76    epoch: u64,
77    error: Option<FetchError>,
78    /// Why the thread fetching pages stopped, once it has.
79    stopped: Option<FetchError>,
80    /// The pages wanted by the rows shown last.
81    want: Range<u64>,
82    wanted: Arc<Wanted>,
83    asks: Sender<Ask>,
84    gots: Receiver<Got>,
85}
86
87impl PagedModel {
88    /// A model of the rows `source` gives, with `schema` (every page's columns), fetched on a
89    /// thread of its own in pages of 256 rows.
90    ///
91    /// `wake` is called on that thread each time it sends something back (rows, a count, a
92    /// failure), and possibly once after the model is dropped: give it
93    /// `move || { waker.wake(); }` with a `densa_window::Waker`, so the window paints what
94    /// came. If `wake` panics, the thread stops ([`FetchError::Stopped`]).
95    ///
96    /// # Errors
97    /// The thread could not be started.
98    pub fn new(
99        schema: SchemaRef,
100        source: impl PageSource,
101        wake: impl Fn() + Send + 'static,
102    ) -> io::Result<Self> {
103        Self::with_options(schema, source, wake, PageOptions::default())
104    }
105
106    /// As [`new`](Self::new), paging as `options` say.
107    ///
108    /// # Errors
109    /// The thread could not be started.
110    pub fn with_options(
111        schema: SchemaRef,
112        source: impl PageSource,
113        wake: impl Fn() + Send + 'static,
114        options: PageOptions,
115    ) -> io::Result<Self> {
116        let options = PageOptions {
117            page_rows: options.page_rows.max(1),
118            pages_kept: options.pages_kept.max(1),
119        };
120        let sortable = schema
121            .fields()
122            .iter()
123            .map(|field| source.can_sort(field.name()))
124            .collect();
125        let (asks, asked) = mpsc::channel();
126        let (sent, gots) = mpsc::channel();
127        let wanted = Arc::new(Wanted::default());
128        let worker = Worker {
129            source,
130            schema: schema.clone(),
131            page_rows: options.page_rows,
132            asks: asked,
133            gots: sent,
134            wanted: Arc::clone(&wanted),
135            wake: Box::new(wake),
136        };
137        thread::Builder::new()
138            .name("densa-pages".to_owned())
139            .spawn(move || worker.serve())?;
140        let formats = new_formats(&schema);
141        let columns = columns_of(&schema, &formats);
142        let mut model = Self {
143            schema,
144            options,
145            formats,
146            sized: false,
147            columns,
148            columns_revision: 0,
149            sortable,
150            pages: BTreeMap::new(),
151            asked: BTreeSet::new(),
152            failed: BTreeSet::new(),
153            rows: None,
154            counted: false,
155            counting: false,
156            count_epoch: 0,
157            query: PageQuery::new(),
158            sort: None,
159            epoch: 0,
160            error: None,
161            stopped: None,
162            want: 0..0,
163            wanted,
164            asks,
165            gots,
166        };
167        model.ask(true);
168        Ok(model)
169    }
170
171    /// The schema of the rows.
172    pub fn schema(&self) -> &SchemaRef {
173        &self.schema
174    }
175
176    /// How many rows there are, once the source counted them: `None` until then, and after a
177    /// count failed.
178    pub fn row_count(&self) -> Option<u64> {
179        self.rows
180    }
181
182    /// Why rows are missing: the source's last failure (to count, to give rows, rows that came
183    /// back short), cleared by [`retry`](Self::retry), [`refresh`](Self::refresh) and sorting;
184    /// or that the thread fetching pages stopped, which nothing clears.
185    pub fn error(&self) -> Option<&FetchError> {
186        self.stopped.as_ref().or(self.error.as_ref())
187    }
188
189    /// Whether `row`'s page is held as the source gives it now (not one kept from before a
190    /// [`refresh`](Self::refresh) until it is fetched again).
191    pub fn is_fetched(&self, row: usize) -> bool {
192        self.pages
193            .get(&self.page_of(row))
194            .is_some_and(|page| page.epoch == self.epoch)
195    }
196
197    /// How many pages are held.
198    pub fn pages_held(&self) -> usize {
199        self.pages.len()
200    }
201
202    /// Rows a page holds.
203    pub fn page_rows(&self) -> usize {
204        self.options.page_rows
205    }
206
207    /// Asks again for what failed: the pages shown that the source failed to give, or the
208    /// count (not one on its way). Pages held stay.
209    pub fn retry(&mut self) {
210        self.failed.clear();
211        self.error = None;
212        if !self.counted && !self.counting {
213            self.ask(true);
214        }
215    }
216
217    /// Counts the rows and fetches the pages shown again: for when the source's data changed.
218    /// The rows held are shown until their pages are back, and the count until the new one is.
219    /// Sorting fetches the pages of the new order by itself.
220    pub fn refresh(&mut self) {
221        self.ask(true);
222    }
223
224    /// Starts a new epoch: asks for the query from now on, counted again if `count`, and
225    /// forgets what was asked or failed before.
226    fn ask(&mut self, count: bool) {
227        self.epoch = self.epoch.wrapping_add(1);
228        self.asked.clear();
229        self.failed.clear();
230        self.error = None;
231        if count {
232            self.counted = false;
233            self.counting = true;
234            self.count_epoch = self.epoch;
235        }
236        // A thread that stopped is reported by `take_in`.
237        let _ = self.asks.send(Ask::Query {
238            epoch: self.epoch,
239            query: self.query.clone(),
240            count,
241        });
242    }
243
244    /// As [`ArrowTable::set_decimals`](crate::ArrowTable::set_decimals): floating-point values
245    /// of `column` with `decimals` fixed decimal places (`None`: the shortest exact form). A
246    /// column sized by the pages is sized again from those held, as now printed; the grid
247    /// takes in the new width.
248    ///
249    /// # Errors
250    /// [`TableError::NoSuchColumn`] if there is no such column.
251    pub fn set_decimals(
252        &mut self,
253        column: usize,
254        decimals: Option<usize>,
255    ) -> Result<(), TableError> {
256        self.formats
257            .get_mut(column)
258            .ok_or(TableError::NoSuchColumn(column))?
259            .decimals = decimals;
260        self.size_again(column);
261        Ok(())
262    }
263
264    /// As [`ArrowTable::set_column_chars`](crate::ArrowTable::set_column_chars): `column` is
265    /// `chars` characters wide (1 to [`CELL_BYTES`]), longer text cut to fit ending in `…`, and
266    /// pages no longer size it; the grid takes in the new width.
267    ///
268    /// # Errors
269    /// [`TableError::NoSuchColumn`] if there is no such column.
270    pub fn set_column_chars(&mut self, column: usize, chars: usize) -> Result<(), TableError> {
271        let format = self
272            .formats
273            .get_mut(column)
274            .ok_or(TableError::NoSuchColumn(column))?;
275        format.max_chars = chars.clamp(1, CELL_BYTES);
276        format.width_set = true;
277        self.read_columns();
278        Ok(())
279    }
280
281    /// Sizes `column` again from the pages held: by the first, widened by the others.
282    fn size_again(&mut self, column: usize) {
283        let (Some(field), Some(format)) = (
284            self.schema.fields().get(column),
285            self.formats.get_mut(column),
286        ) else {
287            return;
288        };
289        let mut held = self
290            .pages
291            .values()
292            .filter_map(|page| page.readers.get(column));
293        if let Some(first) = held.next() {
294            size_column(format, first, field.name());
295            let mut text = CellText::new();
296            for reader in held {
297                widen_column(format, reader, field.data_type(), &mut text);
298            }
299        }
300        self.read_columns();
301    }
302
303    /// Reads the columns again and tells the grid.
304    fn read_columns(&mut self) {
305        self.columns = columns_of(&self.schema, &self.formats);
306        self.columns_revision = self.columns_revision.wrapping_add(1);
307    }
308
309    /// The page holding `row`.
310    fn page_of(&self, row: usize) -> u64 {
311        // Lossless: usize is at most 64 bits wide.
312        (row / self.options.page_rows) as u64
313    }
314
315    /// The page holding row `row` of the source's (a `u64`, counted by it).
316    fn page_of_row(&self, row: u64) -> u64 {
317        // Lossless: usize is at most 64 bits wide.
318        row / self.options.page_rows as u64
319    }
320
321    /// The rows of `pages`, up to the last there is.
322    fn rows_of(&self, pages: Range<u64>) -> Range<u64> {
323        // Lossless: usize is at most 64 bits wide.
324        let page_rows = self.options.page_rows as u64;
325        let start = pages.start.saturating_mul(page_rows);
326        let end = pages.end.saturating_mul(page_rows);
327        start..self.rows.map_or(end, |rows| end.min(rows)).max(start)
328    }
329
330    /// Takes in what the thread sent back.
331    fn take_in(&mut self) {
332        loop {
333            match self.gots.try_recv() {
334                Ok(got) => self.take(got),
335                Err(TryRecvError::Empty) => return,
336                Err(TryRecvError::Disconnected) => {
337                    // Said once: the thread says why it stopped when it can.
338                    if self.stopped.is_none() {
339                        self.stopped = Some(FetchError::Stopped {
340                            message: "it ended without saying why".to_owned(),
341                        });
342                    }
343                    return;
344                }
345            }
346        }
347    }
348
349    /// Takes in one reply, unless it is of an epoch the model no longer wants.
350    fn take(&mut self, got: Got) {
351        match got {
352            Got::Count { epoch, rows } if self.is_count(epoch) => {
353                self.rows = Some(rows);
354                self.counted = true;
355                self.counting = false;
356            }
357            Got::Pages {
358                epoch,
359                first,
360                pages,
361            } if epoch == self.epoch => {
362                for (fetched, page) in pages.into_iter().zip(first..) {
363                    self.asked.remove(&page);
364                    self.take_page(page, fetched);
365                }
366            }
367            Got::Failed {
368                epoch,
369                pages: Some(pages),
370                message,
371            } if epoch == self.epoch => {
372                for page in pages.clone() {
373                    self.asked.remove(&page);
374                    self.failed.insert(page);
375                }
376                let rows = self.rows_of(pages);
377                self.error = Some(FetchError::Rows { rows, message });
378            }
379            Got::Failed {
380                epoch,
381                pages: None,
382                message,
383            } if self.is_count(epoch) => {
384                // No rows to show: those counted before may be gone.
385                self.rows = None;
386                self.counting = false;
387                self.error = Some(FetchError::Count { message });
388            }
389            Got::Skipped { epoch, pages } if epoch == self.epoch => {
390                for page in pages {
391                    self.asked.remove(&page);
392                }
393            }
394            Got::Stopped { message } => self.stopped = Some(FetchError::Stopped { message }),
395            // An epoch the model no longer wants.
396            _ => {}
397        }
398    }
399
400    /// Whether a count of `epoch` answers the count asked last: one of its epoch or later (a
401    /// sort asks for none), not of one before (a refresh asks again). The thread answers the
402    /// last count asked once (those asked before with it in one answer).
403    fn is_count(&self, epoch: u64) -> bool {
404        epoch.wrapping_sub(self.count_epoch) <= self.epoch.wrapping_sub(self.count_epoch)
405    }
406
407    /// Holds page `page` if it has every row the count says it has. One short of them (the data
408    /// changed since it was counted) is not held, and says so.
409    fn take_page(&mut self, page: u64, fetched: Fetched) {
410        let rows = self.rows_of(page..page + 1);
411        // Lossless: usize is at most 64 bits wide.
412        let got = fetched.rows as u64;
413        if got < rows.end - rows.start {
414            self.failed.insert(page);
415            self.error = Some(FetchError::Short { rows, got });
416            return;
417        }
418        self.keep(page, fetched);
419    }
420
421    /// Holds `fetched` as page `page`: sizing the columns by the first rows, widening number and
422    /// time columns for later ones, and dropping pages far from those wanted.
423    fn keep(&mut self, page: u64, fetched: Fetched) {
424        let readers: Vec<Reader> = fetched.columns.into_iter().map(Reader::new).collect();
425        let fields = self.schema.fields();
426        let mut text = CellText::new();
427        let mut widened = false;
428        for ((reader, format), field) in readers.iter().zip(&mut self.formats).zip(fields.iter()) {
429            if self.sized {
430                widened |= widen_column(format, reader, field.data_type(), &mut text);
431            } else {
432                size_column(format, reader, field.name());
433            }
434        }
435        if widened || !self.sized {
436            self.sized = true;
437            self.read_columns();
438        }
439        let (epoch, rows) = (self.epoch, fetched.rows);
440        self.pages.insert(
441            page,
442            Page {
443                epoch,
444                rows,
445                readers,
446            },
447        );
448        self.evict();
449    }
450
451    /// Drops pages past [`PageOptions::pages_kept`], those farthest from the pages wanted first;
452    /// never a page wanted.
453    fn evict(&mut self) {
454        while self.pages.len() > self.options.pages_kept {
455            let (Some(&low), Some(&high)) =
456                (self.pages.keys().next(), self.pages.keys().next_back())
457            else {
458                return;
459            };
460            let below = self.want.start.saturating_sub(low);
461            let above = high.saturating_sub(self.want.end.saturating_sub(1));
462            let page = if below >= above { low } else { high };
463            if self.want.contains(&page) {
464                // Every page held is wanted.
465                return;
466            }
467            self.pages.remove(&page);
468        }
469    }
470
471    /// Asks for the pages of `rows` and one on each side, those not held as the source gives
472    /// them now, asked or failed; none until the rows are counted.
473    fn ask_pages(&mut self, rows: Range<usize>) {
474        let Some(count) = self.rows.filter(|&n| n > 0 && self.counted) else {
475            return;
476        };
477        if rows.is_empty() {
478            return;
479        }
480        let first = self.page_of(rows.start).saturating_sub(1);
481        let last = (self.page_of(rows.end - 1) + 1).min(self.page_of_row(count - 1));
482        if first > last {
483            return;
484        }
485        self.want = first..last + 1;
486        self.wanted.set(first, last);
487        // Allocates only when there are pages to ask for.
488        let mut pages = Vec::new();
489        for page in first..=last {
490            let known = self.pages.get(&page).is_some_and(|p| p.epoch == self.epoch)
491                || self.asked.contains(&page)
492                || self.failed.contains(&page);
493            if !known {
494                pages.push(page);
495            }
496        }
497        if pages.is_empty() {
498            return;
499        }
500        self.asked.extend(pages.iter().copied());
501        // A thread that stopped is reported by `take_in`.
502        let _ = self.asks.send(Ask::Pages {
503            epoch: self.epoch,
504            pages,
505        });
506    }
507
508    /// Writes the cell at `row`, `column` if its page is held.
509    fn write(&self, row: usize, column: usize, text: &mut CellText) -> CellState {
510        let local = row % self.options.page_rows;
511        let reader = self
512            .pages
513            .get(&self.page_of(row))
514            .and_then(|page| page.readers.get(column));
515        match (reader, self.formats.get(column)) {
516            (Some(reader), Some(format)) if local < reader.len() => {
517                write_value(reader, local, format, text)
518            }
519            _ => CellState::OutOfRange,
520        }
521    }
522}
523
524impl GridModel for PagedModel {
525    fn columns(&self) -> &[Column] {
526        &self.columns
527    }
528
529    fn rows(&self) -> usize {
530        self.rows
531            .map_or(0, |rows| usize::try_from(rows).unwrap_or(usize::MAX))
532    }
533
534    fn cell(&self, row: usize, column: usize, text: &mut CellText, palette: &Palette) -> CellStyle {
535        let color = match self.write(row, column, text) {
536            CellState::Value => palette.text,
537            _ => palette.dim_text,
538        };
539        CellStyle::new(color)
540    }
541
542    fn prepare(&mut self) {
543        self.take_in();
544    }
545
546    fn showing(&mut self, rows: Range<usize>) {
547        self.ask_pages(rows);
548    }
549
550    /// The rows of `rows` held, in runs: a copy holds no rows not fetched.
551    fn copy_rows(&self, rows: Range<usize>) -> Vec<Range<usize>> {
552        let mut runs: Vec<Range<usize>> = Vec::new();
553        if rows.is_empty() {
554            return runs;
555        }
556        let pages = self.page_of(rows.start)..=self.page_of(rows.end - 1);
557        for (&page, held) in self.pages.range(pages) {
558            let Some(start) = usize::try_from(page)
559                .ok()
560                .and_then(|page| page.checked_mul(self.options.page_rows))
561            else {
562                continue;
563            };
564            let run = start.max(rows.start)..start.saturating_add(held.rows).min(rows.end);
565            if run.is_empty() {
566                continue;
567            }
568            match runs.last_mut() {
569                Some(last) if last.end == run.start => last.end = run.end,
570                _ => runs.push(run),
571            }
572        }
573        runs
574    }
575
576    /// Asks the source for its rows sorted by `column`; `false` for a column the schema does
577    /// not have, whose name an earlier column has (the source sorts by name), or that the
578    /// source cannot sort by ([`PageSource::can_sort`]).
579    fn sort(&mut self, column: usize, order: SortOrder) -> bool {
580        let Some(field) = self.schema.fields().get(column) else {
581            return false;
582        };
583        let named_once = self
584            .schema
585            .column_with_name(field.name())
586            .is_some_and(|(at, _)| at == column);
587        if !named_once || !self.sortable.get(column).copied().unwrap_or(false) {
588            return false;
589        }
590        self.sort = Some((column, order));
591        self.query = PageQuery::new().sorted_by(SortKey::new(field.name().clone(), order));
592        // The rows held are in the old order. A new order has as many rows: counted only if
593        // they were not, and no count is on its way.
594        self.pages.clear();
595        self.ask(!self.counted && !self.counting);
596        true
597    }
598
599    fn sort_state(&self) -> Option<(usize, SortOrder)> {
600        self.sort
601    }
602
603    fn columns_revision(&self) -> u64 {
604        self.columns_revision
605    }
606}