mod source;
mod worker;
use std::collections::{BTreeMap, BTreeSet};
use std::io;
use std::ops::Range;
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::sync::Arc;
use std::thread;
use arrow_schema::SchemaRef;
use densa_grid::{CellStyle, CellText, Column, GridModel, Palette, SortOrder, CELL_BYTES};
pub use source::{FetchError, PageError, PageOptions, PageQuery, PageSource, SortKey};
use worker::{Ask, Fetched, Got, Wanted, Worker};
use crate::reader::{Format, Reader};
use crate::table::{
columns_of, new_formats, size_column, widen_column, write_value, CellState, TableError,
};
#[derive(Debug)]
struct Page {
epoch: u64,
rows: usize,
readers: Vec<Reader>,
}
#[derive(Debug)]
pub struct PagedModel {
schema: SchemaRef,
options: PageOptions,
formats: Vec<Format>,
sized: bool,
columns: Vec<Column>,
columns_revision: u64,
sortable: Vec<bool>,
pages: BTreeMap<u64, Page>,
asked: BTreeSet<u64>,
failed: BTreeSet<u64>,
rows: Option<u64>,
counted: bool,
counting: bool,
count_epoch: u64,
query: PageQuery,
sort: Option<(usize, SortOrder)>,
epoch: u64,
error: Option<FetchError>,
stopped: Option<FetchError>,
want: Range<u64>,
wanted: Arc<Wanted>,
asks: Sender<Ask>,
gots: Receiver<Got>,
}
impl PagedModel {
pub fn new(
schema: SchemaRef,
source: impl PageSource,
wake: impl Fn() + Send + 'static,
) -> io::Result<Self> {
Self::with_options(schema, source, wake, PageOptions::default())
}
pub fn with_options(
schema: SchemaRef,
source: impl PageSource,
wake: impl Fn() + Send + 'static,
options: PageOptions,
) -> io::Result<Self> {
let options = PageOptions {
page_rows: options.page_rows.max(1),
pages_kept: options.pages_kept.max(1),
};
let sortable = schema
.fields()
.iter()
.map(|field| source.can_sort(field.name()))
.collect();
let (asks, asked) = mpsc::channel();
let (sent, gots) = mpsc::channel();
let wanted = Arc::new(Wanted::default());
let worker = Worker {
source,
schema: schema.clone(),
page_rows: options.page_rows,
asks: asked,
gots: sent,
wanted: Arc::clone(&wanted),
wake: Box::new(wake),
};
thread::Builder::new()
.name("densa-pages".to_owned())
.spawn(move || worker.serve())?;
let formats = new_formats(&schema);
let columns = columns_of(&schema, &formats);
let mut model = Self {
schema,
options,
formats,
sized: false,
columns,
columns_revision: 0,
sortable,
pages: BTreeMap::new(),
asked: BTreeSet::new(),
failed: BTreeSet::new(),
rows: None,
counted: false,
counting: false,
count_epoch: 0,
query: PageQuery::new(),
sort: None,
epoch: 0,
error: None,
stopped: None,
want: 0..0,
wanted,
asks,
gots,
};
model.ask(true);
Ok(model)
}
pub fn schema(&self) -> &SchemaRef {
&self.schema
}
pub fn row_count(&self) -> Option<u64> {
self.rows
}
pub fn error(&self) -> Option<&FetchError> {
self.stopped.as_ref().or(self.error.as_ref())
}
pub fn is_fetched(&self, row: usize) -> bool {
self.pages
.get(&self.page_of(row))
.is_some_and(|page| page.epoch == self.epoch)
}
pub fn pages_held(&self) -> usize {
self.pages.len()
}
pub fn page_rows(&self) -> usize {
self.options.page_rows
}
pub fn retry(&mut self) {
self.failed.clear();
self.error = None;
if !self.counted && !self.counting {
self.ask(true);
}
}
pub fn refresh(&mut self) {
self.ask(true);
}
fn ask(&mut self, count: bool) {
self.epoch = self.epoch.wrapping_add(1);
self.asked.clear();
self.failed.clear();
self.error = None;
if count {
self.counted = false;
self.counting = true;
self.count_epoch = self.epoch;
}
let _ = self.asks.send(Ask::Query {
epoch: self.epoch,
query: self.query.clone(),
count,
});
}
pub fn set_decimals(
&mut self,
column: usize,
decimals: Option<usize>,
) -> Result<(), TableError> {
self.formats
.get_mut(column)
.ok_or(TableError::NoSuchColumn(column))?
.decimals = decimals;
self.size_again(column);
Ok(())
}
pub fn set_column_chars(&mut self, column: usize, chars: usize) -> Result<(), TableError> {
let format = self
.formats
.get_mut(column)
.ok_or(TableError::NoSuchColumn(column))?;
format.max_chars = chars.clamp(1, CELL_BYTES);
format.width_set = true;
self.read_columns();
Ok(())
}
fn size_again(&mut self, column: usize) {
let (Some(field), Some(format)) = (
self.schema.fields().get(column),
self.formats.get_mut(column),
) else {
return;
};
let mut held = self
.pages
.values()
.filter_map(|page| page.readers.get(column));
if let Some(first) = held.next() {
size_column(format, first, field.name());
let mut text = CellText::new();
for reader in held {
widen_column(format, reader, field.data_type(), &mut text);
}
}
self.read_columns();
}
fn read_columns(&mut self) {
self.columns = columns_of(&self.schema, &self.formats);
self.columns_revision = self.columns_revision.wrapping_add(1);
}
fn page_of(&self, row: usize) -> u64 {
(row / self.options.page_rows) as u64
}
fn page_of_row(&self, row: u64) -> u64 {
row / self.options.page_rows as u64
}
fn rows_of(&self, pages: Range<u64>) -> Range<u64> {
let page_rows = self.options.page_rows as u64;
let start = pages.start.saturating_mul(page_rows);
let end = pages.end.saturating_mul(page_rows);
start..self.rows.map_or(end, |rows| end.min(rows)).max(start)
}
fn take_in(&mut self) {
loop {
match self.gots.try_recv() {
Ok(got) => self.take(got),
Err(TryRecvError::Empty) => return,
Err(TryRecvError::Disconnected) => {
if self.stopped.is_none() {
self.stopped = Some(FetchError::Stopped {
message: "it ended without saying why".to_owned(),
});
}
return;
}
}
}
}
fn take(&mut self, got: Got) {
match got {
Got::Count { epoch, rows } if self.is_count(epoch) => {
self.rows = Some(rows);
self.counted = true;
self.counting = false;
}
Got::Pages {
epoch,
first,
pages,
} if epoch == self.epoch => {
for (fetched, page) in pages.into_iter().zip(first..) {
self.asked.remove(&page);
self.take_page(page, fetched);
}
}
Got::Failed {
epoch,
pages: Some(pages),
message,
} if epoch == self.epoch => {
for page in pages.clone() {
self.asked.remove(&page);
self.failed.insert(page);
}
let rows = self.rows_of(pages);
self.error = Some(FetchError::Rows { rows, message });
}
Got::Failed {
epoch,
pages: None,
message,
} if self.is_count(epoch) => {
self.rows = None;
self.counting = false;
self.error = Some(FetchError::Count { message });
}
Got::Skipped { epoch, pages } if epoch == self.epoch => {
for page in pages {
self.asked.remove(&page);
}
}
Got::Stopped { message } => self.stopped = Some(FetchError::Stopped { message }),
_ => {}
}
}
fn is_count(&self, epoch: u64) -> bool {
epoch.wrapping_sub(self.count_epoch) <= self.epoch.wrapping_sub(self.count_epoch)
}
fn take_page(&mut self, page: u64, fetched: Fetched) {
let rows = self.rows_of(page..page + 1);
let got = fetched.rows as u64;
if got < rows.end - rows.start {
self.failed.insert(page);
self.error = Some(FetchError::Short { rows, got });
return;
}
self.keep(page, fetched);
}
fn keep(&mut self, page: u64, fetched: Fetched) {
let readers: Vec<Reader> = fetched.columns.into_iter().map(Reader::new).collect();
let fields = self.schema.fields();
let mut text = CellText::new();
let mut widened = false;
for ((reader, format), field) in readers.iter().zip(&mut self.formats).zip(fields.iter()) {
if self.sized {
widened |= widen_column(format, reader, field.data_type(), &mut text);
} else {
size_column(format, reader, field.name());
}
}
if widened || !self.sized {
self.sized = true;
self.read_columns();
}
let (epoch, rows) = (self.epoch, fetched.rows);
self.pages.insert(
page,
Page {
epoch,
rows,
readers,
},
);
self.evict();
}
fn evict(&mut self) {
while self.pages.len() > self.options.pages_kept {
let (Some(&low), Some(&high)) =
(self.pages.keys().next(), self.pages.keys().next_back())
else {
return;
};
let below = self.want.start.saturating_sub(low);
let above = high.saturating_sub(self.want.end.saturating_sub(1));
let page = if below >= above { low } else { high };
if self.want.contains(&page) {
return;
}
self.pages.remove(&page);
}
}
fn ask_pages(&mut self, rows: Range<usize>) {
let Some(count) = self.rows.filter(|&n| n > 0 && self.counted) else {
return;
};
if rows.is_empty() {
return;
}
let first = self.page_of(rows.start).saturating_sub(1);
let last = (self.page_of(rows.end - 1) + 1).min(self.page_of_row(count - 1));
if first > last {
return;
}
self.want = first..last + 1;
self.wanted.set(first, last);
let mut pages = Vec::new();
for page in first..=last {
let known = self.pages.get(&page).is_some_and(|p| p.epoch == self.epoch)
|| self.asked.contains(&page)
|| self.failed.contains(&page);
if !known {
pages.push(page);
}
}
if pages.is_empty() {
return;
}
self.asked.extend(pages.iter().copied());
let _ = self.asks.send(Ask::Pages {
epoch: self.epoch,
pages,
});
}
fn write(&self, row: usize, column: usize, text: &mut CellText) -> CellState {
let local = row % self.options.page_rows;
let reader = self
.pages
.get(&self.page_of(row))
.and_then(|page| page.readers.get(column));
match (reader, self.formats.get(column)) {
(Some(reader), Some(format)) if local < reader.len() => {
write_value(reader, local, format, text)
}
_ => CellState::OutOfRange,
}
}
}
impl GridModel for PagedModel {
fn columns(&self) -> &[Column] {
&self.columns
}
fn rows(&self) -> usize {
self.rows
.map_or(0, |rows| usize::try_from(rows).unwrap_or(usize::MAX))
}
fn cell(&self, row: usize, column: usize, text: &mut CellText, palette: &Palette) -> CellStyle {
let color = match self.write(row, column, text) {
CellState::Value => palette.text,
_ => palette.dim_text,
};
CellStyle::new(color)
}
fn prepare(&mut self) {
self.take_in();
}
fn showing(&mut self, rows: Range<usize>) {
self.ask_pages(rows);
}
fn copy_rows(&self, rows: Range<usize>) -> Vec<Range<usize>> {
let mut runs: Vec<Range<usize>> = Vec::new();
if rows.is_empty() {
return runs;
}
let pages = self.page_of(rows.start)..=self.page_of(rows.end - 1);
for (&page, held) in self.pages.range(pages) {
let Some(start) = usize::try_from(page)
.ok()
.and_then(|page| page.checked_mul(self.options.page_rows))
else {
continue;
};
let run = start.max(rows.start)..start.saturating_add(held.rows).min(rows.end);
if run.is_empty() {
continue;
}
match runs.last_mut() {
Some(last) if last.end == run.start => last.end = run.end,
_ => runs.push(run),
}
}
runs
}
fn sort(&mut self, column: usize, order: SortOrder) -> bool {
let Some(field) = self.schema.fields().get(column) else {
return false;
};
let named_once = self
.schema
.column_with_name(field.name())
.is_some_and(|(at, _)| at == column);
if !named_once || !self.sortable.get(column).copied().unwrap_or(false) {
return false;
}
self.sort = Some((column, order));
self.query = PageQuery::new().sorted_by(SortKey::new(field.name().clone(), order));
self.pages.clear();
self.ask(!self.counted && !self.counting);
true
}
fn sort_state(&self) -> Option<(usize, SortOrder)> {
self.sort
}
fn columns_revision(&self) -> u64 {
self.columns_revision
}
}