use std::error::Error;
use std::fmt;
use std::ops::Range;
use arrow_array::RecordBatch;
use densa_grid::SortOrder;
pub type PageError = Box<dyn Error + Send + Sync>;
const PAGE_ROWS: usize = 256;
const PAGES_KEPT: usize = 64;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SortKey {
column: String,
order: SortOrder,
}
impl SortKey {
pub fn new(column: impl Into<String>, order: SortOrder) -> Self {
Self {
column: column.into(),
order,
}
}
pub fn column(&self) -> &str {
&self.column
}
pub fn order(&self) -> SortOrder {
self.order
}
pub fn is_descending(&self) -> bool {
self.order == SortOrder::Descending
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PageQuery {
sort: Vec<SortKey>,
}
impl PageQuery {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn sorted_by(mut self, key: SortKey) -> Self {
self.sort.push(key);
self
}
pub fn sort(&self) -> &[SortKey] {
&self.sort
}
pub fn order_by_sql<S: AsRef<str>>(&self, keys: &[S]) -> String {
let sorted = self
.sort
.iter()
.map(|key| (key.column(), key.is_descending()));
let then = keys.iter().map(|key| (key.as_ref(), false));
let mut named: Vec<&str> = Vec::new();
let mut sql = String::new();
for (column, descending) in sorted.chain(then) {
if named.contains(&column) {
continue;
}
sql.push_str(if named.is_empty() { "ORDER BY " } else { ", " });
named.push(column);
sql.push('"');
sql.push_str(&column.replace('"', "\"\""));
sql.push('"');
if descending {
sql.push_str(" DESC");
}
}
sql
}
}
pub trait PageSource: Send + 'static {
fn row_count(&mut self, query: &PageQuery) -> Result<u64, PageError>;
fn page(
&mut self,
query: &PageQuery,
offset: u64,
limit: usize,
) -> Result<Vec<RecordBatch>, PageError>;
fn can_sort(&self, column: &str) -> bool {
let _ = column;
true
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PageOptions {
pub page_rows: usize,
pub pages_kept: usize,
}
impl Default for PageOptions {
fn default() -> Self {
Self {
page_rows: PAGE_ROWS,
pages_kept: PAGES_KEPT,
}
}
}
impl PageOptions {
#[must_use]
pub fn page_rows(mut self, rows: usize) -> Self {
self.page_rows = rows;
self
}
#[must_use]
pub fn pages_kept(mut self, pages: usize) -> Self {
self.pages_kept = pages;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FetchError {
Count {
message: String,
},
Rows {
rows: Range<u64>,
message: String,
},
Short {
rows: Range<u64>,
got: u64,
},
Stopped {
message: String,
},
}
impl fmt::Display for FetchError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Count { message } => write!(f, "the rows could not be counted: {message}"),
Self::Rows { rows, message } => write!(
f,
"rows {} to {} could not be read: {message}",
rows.start,
rows.end.saturating_sub(1)
),
Self::Short { rows, got } => write!(
f,
"rows {} to {} came back short ({got} of {}): the data changed since it was \
counted; refresh to count it again",
rows.start,
rows.end.saturating_sub(1),
rows.end.saturating_sub(rows.start)
),
Self::Stopped { message } => write!(
f,
"the thread fetching pages stopped ({message}): make the model again"
),
}
}
}
impl Error for FetchError {}