pub mod csv;
#[cfg(feature = "er")]
pub mod er;
pub mod infer;
pub mod jsonl;
pub mod profile;
pub mod quality;
mod record;
pub mod serialize;
pub mod sql;
pub mod stats;
pub mod window;
#[cfg(feature = "arrow")]
pub mod arrow;
use std::fmt;
use rich::Justify;
use rich_ext::table::{Column, TableData};
pub use record::RecordSource;
pub use rich_ext::schema::{DataType, Field, Schema};
pub use rich_ext::table::Value;
pub type Row = Vec<Value>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DataError {
line: Option<usize>,
message: String,
}
impl DataError {
pub fn new(message: impl Into<String>) -> Self {
DataError {
line: None,
message: message.into(),
}
}
pub fn at(line: usize, message: impl Into<String>) -> Self {
DataError {
line: Some(line),
message: message.into(),
}
}
pub fn line(&self) -> Option<usize> {
self.line
}
pub fn message(&self) -> &str {
&self.message
}
}
impl fmt::Display for DataError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.line {
Some(line) => write!(f, "line {line}: {}", self.message),
None => f.write_str(&self.message),
}
}
}
impl std::error::Error for DataError {}
pub trait RowSource {
fn columns(&self) -> &[String];
fn schema(&self) -> Option<&Schema> {
None
}
fn next_row(&mut self) -> Option<Result<Row, DataError>>;
fn collect_rows(mut self) -> Result<Rows, DataError>
where
Self: Sized,
{
let mut rows = Rows::new(self.columns().to_vec());
rows.schema = self.schema().cloned();
while let Some(row) = self.next_row() {
rows.push(row?);
}
Ok(rows)
}
}
#[derive(Clone, Debug, Default)]
pub struct Rows {
columns: Vec<String>,
schema: Option<Schema>,
rows: Vec<Row>,
}
impl Rows {
pub fn new(columns: impl IntoIterator<Item = impl Into<String>>) -> Self {
Rows {
columns: columns.into_iter().map(Into::into).collect(),
schema: None,
rows: Vec::new(),
}
}
pub fn with_schema(mut self, schema: Schema) -> Self {
self.schema = Some(schema);
self
}
pub fn set_schema(&mut self, schema: Option<Schema>) {
self.schema = schema;
}
pub fn push(&mut self, row: impl IntoIterator<Item = Value>) -> &mut Self {
let mut row: Row = row.into_iter().take(self.columns.len()).collect();
row.resize(self.columns.len(), Value::Null);
self.rows.push(row);
self
}
pub fn columns(&self) -> &[String] {
&self.columns
}
pub fn schema(&self) -> Option<&Schema> {
self.schema.as_ref()
}
pub fn rows(&self) -> &[Row] {
&self.rows
}
pub fn rows_mut(&mut self) -> &mut [Row] {
&mut self.rows
}
pub fn len(&self) -> usize {
self.rows.len()
}
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
pub fn column_index(&self, name: &str) -> Option<usize> {
self.columns.iter().position(|c| c == name)
}
pub fn column(&self, index: usize) -> impl Iterator<Item = &Value> {
self.rows.iter().filter_map(move |row| row.get(index))
}
pub fn into_parts(self) -> (Vec<String>, Option<Schema>, Vec<Row>) {
(self.columns, self.schema, self.rows)
}
pub fn into_source(self) -> RowsSource {
RowsSource {
columns: self.columns,
schema: self.schema,
rows: self.rows.into_iter(),
}
}
pub fn to_table_data(&self) -> TableData {
let columns = self.columns.iter().enumerate().map(|(index, name)| {
let numeric = self
.schema
.as_ref()
.and_then(|s| s.fields().get(index))
.is_some_and(|f| f.data_type().is_numeric());
let column = Column::new(name.clone());
if numeric {
column.justify(Justify::Right)
} else {
column
}
});
let mut data = TableData::new(columns);
data.extend(self.rows.iter().cloned());
data
}
}
#[derive(Debug)]
pub struct RowsSource {
columns: Vec<String>,
schema: Option<Schema>,
rows: std::vec::IntoIter<Row>,
}
impl RowSource for RowsSource {
fn columns(&self) -> &[String] {
&self.columns
}
fn schema(&self) -> Option<&Schema> {
self.schema.as_ref()
}
fn next_row(&mut self) -> Option<Result<Row, DataError>> {
self.rows.next().map(Ok)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rows_pad_and_cut_to_their_columns() {
let mut rows = Rows::new(["a", "b"]);
rows.push([Value::Int(1)]);
rows.push([Value::Int(1), Value::Int(2), Value::Int(3)]);
assert_eq!(rows.rows()[0], [Value::Int(1), Value::Null]);
assert_eq!(rows.rows()[1], [Value::Int(1), Value::Int(2)]);
assert_eq!(rows.column_index("b"), Some(1));
assert_eq!(rows.column(0).count(), 2);
}
#[test]
fn a_source_collects_back_into_the_same_rows() {
let mut rows =
Rows::new(["x"]).with_schema(Schema::new([Field::new("x", DataType::Integer)]));
rows.push([Value::Int(7)]);
let back = rows.clone().into_source().collect_rows().unwrap();
assert_eq!(back.rows(), rows.rows());
assert_eq!(back.schema(), rows.schema());
}
#[test]
fn errors_name_their_line() {
assert_eq!(DataError::at(3, "bad").to_string(), "line 3: bad");
assert_eq!(DataError::new("bad").to_string(), "bad");
}
}