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 WIDEN_ROWS: usize = 64;
15const MAX_SAMPLED_CHARS: usize = 40;
17const DEFAULT_CHARS: usize = 8;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[non_exhaustive]
23pub enum CellState {
24 Value,
26 Null,
28 Unsupported,
30 Invalid,
33 OutOfRange,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39#[non_exhaustive]
40pub enum TableError {
41 NoBatches,
43 ColumnCount {
45 expected: usize,
47 found: usize,
49 },
50 ColumnMismatch {
52 column: usize,
54 },
55 NoSuchColumn(usize),
57}
58
59impl fmt::Display for TableError {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 Self::NoBatches => f.write_str("no record batch was given, so there is no schema"),
63 Self::ColumnCount { expected, found } => {
64 write!(f, "the batch has {found} columns, the table {expected}")
65 }
66 Self::ColumnMismatch { column } => {
67 write!(f, "column {column} of the batch has another data type")
68 }
69 Self::NoSuchColumn(column) => write!(f, "there is no column {column}"),
70 }
71 }
72}
73
74impl std::error::Error for TableError {}
75
76#[derive(Debug, Clone)]
90pub struct ArrowTable {
91 schema: SchemaRef,
92 batches: Vec<Vec<Reader>>,
94 starts: Vec<usize>,
96 rows: usize,
97 formats: Vec<Format>,
98 sized: bool,
100 widths: u64,
103}
104
105impl ArrowTable {
106 pub fn new(schema: SchemaRef) -> Self {
109 let formats = new_formats(&schema);
110 Self {
111 schema,
112 batches: Vec::new(),
113 starts: Vec::new(),
114 rows: 0,
115 formats,
116 sized: false,
117 widths: 0,
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> {
146 self.check(&batch)?;
147 if batch.num_rows() == 0 {
148 return Ok(());
149 }
150 let readers: Vec<Reader> = batch.columns().iter().cloned().map(Reader::new).collect();
151 if self.sized {
152 self.widen_columns(&readers);
153 } else {
154 self.size_columns(&readers);
155 }
156 self.starts.push(self.rows);
157 self.rows += batch.num_rows();
158 self.batches.push(readers);
159 Ok(())
160 }
161
162 pub fn schema(&self) -> &SchemaRef {
164 &self.schema
165 }
166
167 pub fn rows(&self) -> usize {
169 self.rows
170 }
171
172 pub fn columns(&self) -> Vec<Column> {
174 columns_of(&self.schema, &self.formats)
175 }
176
177 pub fn set_decimals(
184 &mut self,
185 column: usize,
186 decimals: Option<usize>,
187 ) -> Result<(), TableError> {
188 self.format_mut(column)?.decimals = decimals;
189 if let (Some(reader), Some(field)) = (
190 self.batches.first().and_then(|b| b.get(column)),
191 self.schema.fields().get(column),
192 ) {
193 let format = &mut self.formats[column];
194 size_column(format, reader, field.name());
195 if is_numeric(field.data_type()) && !format.width_set {
197 let mut text = CellText::new();
198 for readers in &self.batches[1..] {
199 let rows = spread(readers[column].len(), WIDEN_ROWS);
200 let widest = widest(&readers[column], format, rows, &mut text);
201 format.max_chars = format.max_chars.max(widest);
202 }
203 }
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 write_value(reader, local, format, text)
249 }
250
251 pub fn view<'a>(&'a self, first_row: usize, palette: &Palette) -> ArrowGrid<'a> {
254 ArrowGrid {
255 table: self,
256 first_row,
257 text: palette.text,
258 dim: palette.dim_text,
259 }
260 }
261
262 fn check(&self, batch: &RecordBatch) -> Result<(), TableError> {
263 let ours = self.schema.fields();
264 let theirs = batch.schema_ref().fields();
265 if ours.len() != theirs.len() {
266 return Err(TableError::ColumnCount {
267 expected: ours.len(),
268 found: theirs.len(),
269 });
270 }
271 match ours
272 .iter()
273 .zip(theirs.iter())
274 .position(|(a, b)| a.data_type() != b.data_type())
275 {
276 Some(column) => Err(TableError::ColumnMismatch { column }),
277 None => Ok(()),
278 }
279 }
280
281 fn size_columns(&mut self, readers: &[Reader]) {
283 for ((reader, format), field) in readers
284 .iter()
285 .zip(&mut self.formats)
286 .zip(self.schema.fields().iter())
287 {
288 size_column(format, reader, field.name());
289 }
290 self.sized = true;
291 self.widths = self.widths.wrapping_add(1);
292 }
293
294 fn widen_columns(&mut self, readers: &[Reader]) {
297 let mut text = CellText::new();
298 let mut widened = false;
299 for ((reader, format), field) in readers
300 .iter()
301 .zip(&mut self.formats)
302 .zip(self.schema.fields().iter())
303 {
304 widened |= widen_column(format, reader, field.data_type(), &mut text);
305 }
306 if widened {
307 self.widths = self.widths.wrapping_add(1);
308 }
309 }
310
311 pub(crate) fn widths(&self) -> u64 {
313 self.widths
314 }
315
316 pub(crate) fn stored_batches(&self) -> usize {
318 self.batches.len()
319 }
320
321 pub(crate) fn clear_batches(&mut self) {
323 self.batches.clear();
324 self.starts.clear();
325 self.rows = 0;
326 }
327
328 fn format_mut(&mut self, column: usize) -> Result<&mut Format, TableError> {
329 self.formats
330 .get_mut(column)
331 .ok_or(TableError::NoSuchColumn(column))
332 }
333}
334
335pub(crate) fn new_formats(schema: &SchemaRef) -> Vec<Format> {
337 schema
338 .fields()
339 .iter()
340 .map(|field| Format {
341 decimals: None,
342 max_chars: field.name().chars().count().max(DEFAULT_CHARS),
343 width_set: false,
344 })
345 .collect()
346}
347
348pub(crate) fn columns_of(schema: &SchemaRef, formats: &[Format]) -> Vec<Column> {
351 schema
352 .fields()
353 .iter()
354 .zip(formats)
355 .map(|(field, format)| {
356 let align = if is_numeric(field.data_type()) {
357 Align::Right
358 } else {
359 Align::Left
360 };
361 let chars = u32::try_from(format.max_chars).unwrap_or(u32::MAX);
362 Column::new(field.name().clone(), chars, align)
363 })
364 .collect()
365}
366
367pub(crate) fn write_value(
371 reader: &Reader,
372 local: usize,
373 format: &Format,
374 text: &mut CellText,
375) -> CellState {
376 if reader.is_null(local) {
377 let _ = text.push_str("null");
379 return CellState::Null;
380 }
381 if !reader.is_supported() {
382 reader.write(local, text, format);
383 return CellState::Unsupported;
384 }
385 let mark = text.len();
386 if reader.write(local, text, format) {
387 return CellState::Value;
388 }
389 text.truncate(mark);
390 let _ = text.push_str("#");
392 CellState::Invalid
393}
394
395pub(crate) fn widen_column(
398 format: &mut Format,
399 reader: &Reader,
400 data_type: &arrow_schema::DataType,
401 text: &mut CellText,
402) -> bool {
403 if format.width_set || !is_numeric(data_type) {
404 return false;
405 }
406 let widest = widest(reader, format, spread(reader.len(), WIDEN_ROWS), text);
407 let wider = widest > format.max_chars;
408 if wider {
409 format.max_chars = widest;
410 }
411 wider
412}
413
414pub(crate) fn size_column(format: &mut Format, reader: &Reader, title: &str) {
417 if format.width_set {
418 return;
419 }
420 let mut text = CellText::new();
421 let rows = 0..reader.len().min(SAMPLE_ROWS);
422 let widest = widest(reader, format, rows, &mut text).max(title.chars().count());
423 format.max_chars = widest.max(1);
424}
425
426fn widest(
429 reader: &Reader,
430 format: &Format,
431 rows: impl Iterator<Item = usize>,
432 text: &mut CellText,
433) -> usize {
434 let sampling = Format {
435 max_chars: MAX_SAMPLED_CHARS,
436 ..*format
437 };
438 let mut widest = 0;
439 for row in rows {
440 text.clear();
441 if !reader.is_null(row) {
442 reader.write(row, text, &sampling);
443 }
444 widest = widest.max(text.as_str().chars().count());
445 }
446 widest.min(MAX_SAMPLED_CHARS)
447}
448
449fn spread(len: usize, most: usize) -> impl Iterator<Item = usize> {
451 let count = len.min(most);
452 (0..count).map(move |i| i * (len - 1) / (count - 1).max(1))
454}
455
456#[derive(Debug, Clone, Copy)]
458pub struct ArrowGrid<'a> {
459 table: &'a ArrowTable,
460 first_row: usize,
461 text: Color,
462 dim: Color,
463}
464
465impl GridSource for ArrowGrid<'_> {
466 fn rows(&self) -> usize {
467 self.table.rows().saturating_sub(self.first_row)
468 }
469
470 fn cell(&self, row: usize, column: usize, text: &mut CellText) -> CellStyle {
471 let state = self
472 .table
473 .write_cell(self.first_row.saturating_add(row), column, text);
474 let color = if state == CellState::Value {
475 self.text
476 } else {
477 self.dim
478 };
479 CellStyle::new(color)
480 }
481}