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 = schema
110 .fields()
111 .iter()
112 .map(|field| Format {
113 decimals: None,
114 max_chars: field.name().chars().count().max(DEFAULT_CHARS),
115 width_set: false,
116 })
117 .collect();
118 Self {
119 schema,
120 batches: Vec::new(),
121 starts: Vec::new(),
122 rows: 0,
123 formats,
124 sized: false,
125 widths: 0,
126 }
127 }
128
129 pub fn from_batches(
134 batches: impl IntoIterator<Item = RecordBatch>,
135 ) -> Result<Self, TableError> {
136 let mut batches = batches.into_iter();
137 let first = batches.next().ok_or(TableError::NoBatches)?;
138 let mut table = Self::new(first.schema());
139 for batch in std::iter::once(first).chain(batches) {
140 table.push(batch)?;
141 }
142 Ok(table)
143 }
144
145 pub fn push(&mut self, batch: RecordBatch) -> Result<(), TableError> {
154 self.check(&batch)?;
155 if batch.num_rows() == 0 {
156 return Ok(());
157 }
158 let readers: Vec<Reader> = batch.columns().iter().cloned().map(Reader::new).collect();
159 if self.sized {
160 self.widen_columns(&readers);
161 } else {
162 self.size_columns(&readers);
163 }
164 self.starts.push(self.rows);
165 self.rows += batch.num_rows();
166 self.batches.push(readers);
167 Ok(())
168 }
169
170 pub fn schema(&self) -> &SchemaRef {
172 &self.schema
173 }
174
175 pub fn rows(&self) -> usize {
177 self.rows
178 }
179
180 pub fn columns(&self) -> Vec<Column> {
182 self.schema
183 .fields()
184 .iter()
185 .zip(&self.formats)
186 .map(|(field, format)| {
187 let align = if is_numeric(field.data_type()) {
188 Align::Right
189 } else {
190 Align::Left
191 };
192 let chars = u32::try_from(format.max_chars).unwrap_or(u32::MAX);
193 Column::new(field.name().clone(), chars, align)
194 })
195 .collect()
196 }
197
198 pub fn set_decimals(
205 &mut self,
206 column: usize,
207 decimals: Option<usize>,
208 ) -> Result<(), TableError> {
209 self.format_mut(column)?.decimals = decimals;
210 if let (Some(reader), Some(field)) = (
211 self.batches.first().and_then(|b| b.get(column)),
212 self.schema.fields().get(column),
213 ) {
214 let format = &mut self.formats[column];
215 size_column(format, reader, field.name());
216 if is_numeric(field.data_type()) && !format.width_set {
218 let mut text = CellText::new();
219 for readers in &self.batches[1..] {
220 let rows = spread(readers[column].len(), WIDEN_ROWS);
221 let widest = widest(&readers[column], format, rows, &mut text);
222 format.max_chars = format.max_chars.max(widest);
223 }
224 }
225 }
226 Ok(())
227 }
228
229 pub fn set_column_chars(&mut self, column: usize, chars: usize) -> Result<(), TableError> {
236 let format = self.format_mut(column)?;
237 format.max_chars = chars.clamp(1, CELL_BYTES);
238 format.width_set = true;
239 Ok(())
240 }
241
242 pub fn write_cell(&self, row: usize, column: usize, text: &mut CellText) -> CellState {
245 if row >= self.rows {
246 return CellState::OutOfRange;
247 }
248 let batch = self.starts.partition_point(|&start| start <= row) - 1;
250 self.write_at(batch, row - self.starts[batch], column, text)
251 }
252
253 pub(crate) fn write_at(
256 &self,
257 batch: usize,
258 local: usize,
259 column: usize,
260 text: &mut CellText,
261 ) -> CellState {
262 let reader = self.batches.get(batch).and_then(|b| b.get(column));
263 let (Some(reader), Some(format)) = (reader, self.formats.get(column)) else {
264 return CellState::OutOfRange;
265 };
266 if local >= reader.len() {
267 return CellState::OutOfRange;
268 }
269 if reader.is_null(local) {
270 let _ = text.push_str("null");
272 return CellState::Null;
273 }
274 if !reader.is_supported() {
275 reader.write(local, text, format);
276 return CellState::Unsupported;
277 }
278 let mark = text.len();
279 if reader.write(local, text, format) {
280 return CellState::Value;
281 }
282 text.truncate(mark);
283 let _ = text.push_str("#");
285 CellState::Invalid
286 }
287
288 pub fn view<'a>(&'a self, first_row: usize, palette: &Palette) -> ArrowGrid<'a> {
291 ArrowGrid {
292 table: self,
293 first_row,
294 text: palette.text,
295 dim: palette.dim_text,
296 }
297 }
298
299 fn check(&self, batch: &RecordBatch) -> Result<(), TableError> {
300 let ours = self.schema.fields();
301 let theirs = batch.schema_ref().fields();
302 if ours.len() != theirs.len() {
303 return Err(TableError::ColumnCount {
304 expected: ours.len(),
305 found: theirs.len(),
306 });
307 }
308 match ours
309 .iter()
310 .zip(theirs.iter())
311 .position(|(a, b)| a.data_type() != b.data_type())
312 {
313 Some(column) => Err(TableError::ColumnMismatch { column }),
314 None => Ok(()),
315 }
316 }
317
318 fn size_columns(&mut self, readers: &[Reader]) {
320 for ((reader, format), field) in readers
321 .iter()
322 .zip(&mut self.formats)
323 .zip(self.schema.fields().iter())
324 {
325 size_column(format, reader, field.name());
326 }
327 self.sized = true;
328 self.widths = self.widths.wrapping_add(1);
329 }
330
331 fn widen_columns(&mut self, readers: &[Reader]) {
334 let mut text = CellText::new();
335 let mut widened = false;
336 for ((reader, format), field) in readers
337 .iter()
338 .zip(&mut self.formats)
339 .zip(self.schema.fields().iter())
340 {
341 if format.width_set || !is_numeric(field.data_type()) {
342 continue;
343 }
344 let widest = widest(reader, format, spread(reader.len(), WIDEN_ROWS), &mut text);
345 if widest > format.max_chars {
346 format.max_chars = widest;
347 widened = true;
348 }
349 }
350 if widened {
351 self.widths = self.widths.wrapping_add(1);
352 }
353 }
354
355 pub(crate) fn widths(&self) -> u64 {
357 self.widths
358 }
359
360 pub(crate) fn stored_batches(&self) -> usize {
362 self.batches.len()
363 }
364
365 pub(crate) fn clear_batches(&mut self) {
367 self.batches.clear();
368 self.starts.clear();
369 self.rows = 0;
370 }
371
372 fn format_mut(&mut self, column: usize) -> Result<&mut Format, TableError> {
373 self.formats
374 .get_mut(column)
375 .ok_or(TableError::NoSuchColumn(column))
376 }
377}
378
379fn size_column(format: &mut Format, reader: &Reader, title: &str) {
382 if format.width_set {
383 return;
384 }
385 let mut text = CellText::new();
386 let rows = 0..reader.len().min(SAMPLE_ROWS);
387 let widest = widest(reader, format, rows, &mut text).max(title.chars().count());
388 format.max_chars = widest.max(1);
389}
390
391fn widest(
394 reader: &Reader,
395 format: &Format,
396 rows: impl Iterator<Item = usize>,
397 text: &mut CellText,
398) -> usize {
399 let sampling = Format {
400 max_chars: MAX_SAMPLED_CHARS,
401 ..*format
402 };
403 let mut widest = 0;
404 for row in rows {
405 text.clear();
406 if !reader.is_null(row) {
407 reader.write(row, text, &sampling);
408 }
409 widest = widest.max(text.as_str().chars().count());
410 }
411 widest.min(MAX_SAMPLED_CHARS)
412}
413
414fn spread(len: usize, most: usize) -> impl Iterator<Item = usize> {
416 let count = len.min(most);
417 (0..count).map(move |i| i * (len - 1) / (count - 1).max(1))
419}
420
421#[derive(Debug, Clone, Copy)]
423pub struct ArrowGrid<'a> {
424 table: &'a ArrowTable,
425 first_row: usize,
426 text: Color,
427 dim: Color,
428}
429
430impl GridSource for ArrowGrid<'_> {
431 fn rows(&self) -> usize {
432 self.table.rows().saturating_sub(self.first_row)
433 }
434
435 fn cell(&self, row: usize, column: usize, text: &mut CellText) -> CellStyle {
436 let state = self
437 .table
438 .write_cell(self.first_row.saturating_add(row), column, text);
439 let color = if state == CellState::Value {
440 self.text
441 } else {
442 self.dim
443 };
444 CellStyle::new(color)
445 }
446}