1use std::fmt;
2
3use arrow_array::RecordBatch;
4use arrow_schema::SchemaRef;
5use densa_grid::{Align, CellStyle, CellText, Color, Column, GridSource, Palette, CELL_BYTES};
6
7use crate::reader::{is_numeric, Format, Reader};
8
9const SAMPLE_ROWS: usize = 1_000;
11const MAX_SAMPLED_CHARS: usize = 40;
13const DEFAULT_CHARS: usize = 8;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[non_exhaustive]
19pub enum CellState {
20 Value,
22 Null,
24 Unsupported,
26 Invalid,
29 OutOfRange,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35#[non_exhaustive]
36pub enum TableError {
37 NoBatches,
39 ColumnCount {
41 expected: usize,
43 found: usize,
45 },
46 ColumnMismatch {
48 column: usize,
50 },
51 NoSuchColumn(usize),
53}
54
55impl fmt::Display for TableError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 Self::NoBatches => f.write_str("no record batch was given, so there is no schema"),
59 Self::ColumnCount { expected, found } => {
60 write!(f, "the batch has {found} columns, the table {expected}")
61 }
62 Self::ColumnMismatch { column } => {
63 write!(f, "column {column} of the batch has another data type")
64 }
65 Self::NoSuchColumn(column) => write!(f, "there is no column {column}"),
66 }
67 }
68}
69
70impl std::error::Error for TableError {}
71
72#[derive(Debug, Clone)]
86pub struct ArrowTable {
87 schema: SchemaRef,
88 batches: Vec<Vec<Reader>>,
90 starts: Vec<usize>,
92 rows: usize,
93 formats: Vec<Format>,
94 sized: bool,
96}
97
98impl ArrowTable {
99 pub fn new(schema: SchemaRef) -> Self {
102 let formats = schema
103 .fields()
104 .iter()
105 .map(|field| Format {
106 decimals: None,
107 max_chars: field.name().chars().count().max(DEFAULT_CHARS),
108 width_set: false,
109 })
110 .collect();
111 Self {
112 schema,
113 batches: Vec::new(),
114 starts: Vec::new(),
115 rows: 0,
116 formats,
117 sized: false,
118 }
119 }
120
121 pub fn from_batches(
126 batches: impl IntoIterator<Item = RecordBatch>,
127 ) -> Result<Self, TableError> {
128 let mut batches = batches.into_iter();
129 let first = batches.next().ok_or(TableError::NoBatches)?;
130 let mut table = Self::new(first.schema());
131 for batch in std::iter::once(first).chain(batches) {
132 table.push(batch)?;
133 }
134 Ok(table)
135 }
136
137 pub fn push(&mut self, batch: RecordBatch) -> Result<(), TableError> {
144 self.check(&batch)?;
145 if batch.num_rows() == 0 {
146 return Ok(());
147 }
148 let readers: Vec<Reader> = batch.columns().iter().cloned().map(Reader::new).collect();
149 if !self.sized {
150 self.size_columns(&readers);
151 }
152 self.starts.push(self.rows);
153 self.rows += batch.num_rows();
154 self.batches.push(readers);
155 Ok(())
156 }
157
158 pub fn schema(&self) -> &SchemaRef {
160 &self.schema
161 }
162
163 pub fn rows(&self) -> usize {
165 self.rows
166 }
167
168 pub fn columns(&self) -> Vec<Column> {
170 self.schema
171 .fields()
172 .iter()
173 .zip(&self.formats)
174 .map(|(field, format)| {
175 let align = if is_numeric(field.data_type()) {
176 Align::Right
177 } else {
178 Align::Left
179 };
180 let chars = u32::try_from(format.max_chars).unwrap_or(u32::MAX);
181 Column::new(field.name().clone(), chars, align)
182 })
183 .collect()
184 }
185
186 pub fn set_decimals(
193 &mut self,
194 column: usize,
195 decimals: Option<usize>,
196 ) -> Result<(), TableError> {
197 self.format_mut(column)?.decimals = decimals;
198 if let (Some(reader), Some(field)) = (
199 self.batches.first().and_then(|b| b.get(column)),
200 self.schema.fields().get(column),
201 ) {
202 let format = &mut self.formats[column];
203 size_column(format, reader, field.name());
204 }
205 Ok(())
206 }
207
208 pub fn set_column_chars(&mut self, column: usize, chars: usize) -> Result<(), TableError> {
215 let format = self.format_mut(column)?;
216 format.max_chars = chars.clamp(1, CELL_BYTES);
217 format.width_set = true;
218 Ok(())
219 }
220
221 pub fn write_cell(&self, row: usize, column: usize, text: &mut CellText) -> CellState {
224 if row >= self.rows {
225 return CellState::OutOfRange;
226 }
227 let batch = self.starts.partition_point(|&start| start <= row) - 1;
229 self.write_at(batch, row - self.starts[batch], column, text)
230 }
231
232 pub(crate) fn write_at(
235 &self,
236 batch: usize,
237 local: usize,
238 column: usize,
239 text: &mut CellText,
240 ) -> CellState {
241 let reader = self.batches.get(batch).and_then(|b| b.get(column));
242 let (Some(reader), Some(format)) = (reader, self.formats.get(column)) else {
243 return CellState::OutOfRange;
244 };
245 if local >= reader.len() {
246 return CellState::OutOfRange;
247 }
248 if reader.is_null(local) {
249 let _ = text.push_str("null");
251 return CellState::Null;
252 }
253 if !reader.is_supported() {
254 reader.write(local, text, format);
255 return CellState::Unsupported;
256 }
257 let mark = text.len();
258 if reader.write(local, text, format) {
259 return CellState::Value;
260 }
261 text.truncate(mark);
262 let _ = text.push_str("#");
264 CellState::Invalid
265 }
266
267 pub fn view<'a>(&'a self, first_row: usize, palette: &Palette) -> ArrowGrid<'a> {
270 ArrowGrid {
271 table: self,
272 first_row,
273 text: palette.text,
274 dim: palette.dim_text,
275 }
276 }
277
278 fn check(&self, batch: &RecordBatch) -> Result<(), TableError> {
279 let ours = self.schema.fields();
280 let theirs = batch.schema_ref().fields();
281 if ours.len() != theirs.len() {
282 return Err(TableError::ColumnCount {
283 expected: ours.len(),
284 found: theirs.len(),
285 });
286 }
287 match ours
288 .iter()
289 .zip(theirs.iter())
290 .position(|(a, b)| a.data_type() != b.data_type())
291 {
292 Some(column) => Err(TableError::ColumnMismatch { column }),
293 None => Ok(()),
294 }
295 }
296
297 fn size_columns(&mut self, readers: &[Reader]) {
299 for ((reader, format), field) in readers
300 .iter()
301 .zip(&mut self.formats)
302 .zip(self.schema.fields().iter())
303 {
304 size_column(format, reader, field.name());
305 }
306 self.sized = true;
307 }
308
309 pub(crate) fn is_sized(&self) -> bool {
311 self.sized
312 }
313
314 pub(crate) fn stored_batches(&self) -> usize {
316 self.batches.len()
317 }
318
319 pub(crate) fn clear_batches(&mut self) {
321 self.batches.clear();
322 self.starts.clear();
323 self.rows = 0;
324 }
325
326 fn format_mut(&mut self, column: usize) -> Result<&mut Format, TableError> {
327 self.formats
328 .get_mut(column)
329 .ok_or(TableError::NoSuchColumn(column))
330 }
331}
332
333fn size_column(format: &mut Format, reader: &Reader, title: &str) {
336 if format.width_set {
337 return;
338 }
339 let sampling = Format {
340 max_chars: MAX_SAMPLED_CHARS,
341 ..*format
342 };
343 let mut text = CellText::new();
344 let mut widest = title.chars().count();
345 for row in 0..reader.len().min(SAMPLE_ROWS) {
346 text.clear();
347 if !reader.is_null(row) {
348 reader.write(row, &mut text, &sampling);
349 }
350 widest = widest.max(text.as_str().chars().count());
351 }
352 format.max_chars = widest.max(1);
353}
354
355#[derive(Debug, Clone, Copy)]
357pub struct ArrowGrid<'a> {
358 table: &'a ArrowTable,
359 first_row: usize,
360 text: Color,
361 dim: Color,
362}
363
364impl GridSource for ArrowGrid<'_> {
365 fn rows(&self) -> usize {
366 self.table.rows().saturating_sub(self.first_row)
367 }
368
369 fn cell(&self, row: usize, column: usize, text: &mut CellText) -> CellStyle {
370 let state = self
371 .table
372 .write_cell(self.first_row.saturating_add(row), column, text);
373 let color = if state == CellState::Value {
374 self.text
375 } else {
376 self.dim
377 };
378 CellStyle::new(color)
379 }
380}