use datafusion_physical_plan::{DisplayAs, DisplayFormatType};
use crate::file_groups::FileGroup;
use std::fmt::{Debug, Formatter, Result as FmtResult};
#[derive(Debug)]
pub(crate) struct FileGroupsDisplay<'a>(pub(crate) &'a [FileGroup]);
impl DisplayAs for FileGroupsDisplay<'_> {
fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> FmtResult {
let n_groups = self.0.len();
let groups = if n_groups == 1 { "group" } else { "groups" };
write!(f, "{{{n_groups} {groups}: [")?;
match t {
DisplayFormatType::Default | DisplayFormatType::TreeRender => {
let max_groups = 5;
fmt_up_to_n_elements(self.0, max_groups, f, |group, f| {
FileGroupDisplay(group).fmt_as(t, f)
})?;
}
DisplayFormatType::Verbose => {
fmt_elements_split_by_commas(self.0.iter(), f, |group, f| {
FileGroupDisplay(group).fmt_as(t, f)
})?
}
}
write!(f, "]}}")
}
}
#[derive(Debug)]
pub struct FileGroupDisplay<'a>(pub &'a FileGroup);
impl DisplayAs for FileGroupDisplay<'_> {
fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> FmtResult {
write!(f, "[")?;
match t {
DisplayFormatType::Default | DisplayFormatType::TreeRender => {
let max_files = 5;
fmt_up_to_n_elements(self.0.files(), max_files, f, |pf, f| {
write!(f, "{}", pf.object_meta.location.as_ref())?;
if let Some(range) = pf.range.as_ref() {
write!(f, ":{}..{}", range.start, range.end)?;
}
Ok(())
})?
}
DisplayFormatType::Verbose => {
fmt_elements_split_by_commas(self.0.iter(), f, |pf, f| {
write!(f, "{}", pf.object_meta.location.as_ref())?;
if let Some(range) = pf.range.as_ref() {
write!(f, ":{}..{}", range.start, range.end)?;
}
Ok(())
})?
}
}
write!(f, "]")
}
}
fn fmt_up_to_n_elements<E, F>(
elements: &[E],
n: usize,
f: &mut Formatter,
format_element: F,
) -> FmtResult
where
F: Fn(&E, &mut Formatter) -> FmtResult,
{
let len = elements.len();
fmt_elements_split_by_commas(elements.iter().take(n), f, |element, f| {
format_element(element, f)
})?;
if len > n {
write!(f, ", ...")?;
}
Ok(())
}
fn fmt_elements_split_by_commas<E, I, F>(
iter: I,
f: &mut Formatter,
format_element: F,
) -> FmtResult
where
I: Iterator<Item = E>,
F: Fn(E, &mut Formatter) -> FmtResult,
{
for (idx, element) in iter.enumerate() {
if idx > 0 {
write!(f, ", ")?;
}
format_element(element, f)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use datafusion_physical_plan::{DefaultDisplay, VerboseDisplay};
use object_store::{path::Path, ObjectMeta};
use crate::PartitionedFile;
use chrono::Utc;
#[test]
fn file_groups_display_empty() {
let expected = "{0 groups: []}";
assert_eq!(DefaultDisplay(FileGroupsDisplay(&[])).to_string(), expected);
}
#[test]
fn file_groups_display_one() {
let files = [FileGroup::new(vec![
partitioned_file("foo"),
partitioned_file("bar"),
])];
let expected = "{1 group: [[foo, bar]]}";
assert_eq!(
DefaultDisplay(FileGroupsDisplay(&files)).to_string(),
expected
);
}
#[test]
fn file_groups_display_many_default() {
let files = [
FileGroup::new(vec![partitioned_file("foo"), partitioned_file("bar")]),
FileGroup::new(vec![partitioned_file("baz")]),
FileGroup::default(),
];
let expected = "{3 groups: [[foo, bar], [baz], []]}";
assert_eq!(
DefaultDisplay(FileGroupsDisplay(&files)).to_string(),
expected
);
}
#[test]
fn file_groups_display_many_verbose() {
let files = [
FileGroup::new(vec![partitioned_file("foo"), partitioned_file("bar")]),
FileGroup::new(vec![partitioned_file("baz")]),
FileGroup::default(),
];
let expected = "{3 groups: [[foo, bar], [baz], []]}";
assert_eq!(
VerboseDisplay(FileGroupsDisplay(&files)).to_string(),
expected
);
}
#[test]
fn file_groups_display_too_many_default() {
let files = [
FileGroup::new(vec![partitioned_file("foo"), partitioned_file("bar")]),
FileGroup::new(vec![partitioned_file("baz")]),
FileGroup::new(vec![partitioned_file("qux")]),
FileGroup::new(vec![partitioned_file("quux")]),
FileGroup::new(vec![partitioned_file("quuux")]),
FileGroup::new(vec![partitioned_file("quuuux")]),
FileGroup::default(),
];
let expected = "{7 groups: [[foo, bar], [baz], [qux], [quux], [quuux], ...]}";
assert_eq!(
DefaultDisplay(FileGroupsDisplay(&files)).to_string(),
expected
);
}
#[test]
fn file_groups_display_too_many_verbose() {
let files = [
FileGroup::new(vec![partitioned_file("foo"), partitioned_file("bar")]),
FileGroup::new(vec![partitioned_file("baz")]),
FileGroup::new(vec![partitioned_file("qux")]),
FileGroup::new(vec![partitioned_file("quux")]),
FileGroup::new(vec![partitioned_file("quuux")]),
FileGroup::new(vec![partitioned_file("quuuux")]),
FileGroup::default(),
];
let expected =
"{7 groups: [[foo, bar], [baz], [qux], [quux], [quuux], [quuuux], []]}";
assert_eq!(
VerboseDisplay(FileGroupsDisplay(&files)).to_string(),
expected
);
}
#[test]
fn file_group_display_many_default() {
let files =
FileGroup::new(vec![partitioned_file("foo"), partitioned_file("bar")]);
let expected = "[foo, bar]";
assert_eq!(
DefaultDisplay(FileGroupDisplay(&files)).to_string(),
expected
);
}
#[test]
fn file_group_display_too_many_default() {
let files = FileGroup::new(vec![
partitioned_file("foo"),
partitioned_file("bar"),
partitioned_file("baz"),
partitioned_file("qux"),
partitioned_file("quux"),
partitioned_file("quuux"),
]);
let expected = "[foo, bar, baz, qux, quux, ...]";
assert_eq!(
DefaultDisplay(FileGroupDisplay(&files)).to_string(),
expected
);
}
#[test]
fn file_group_display_too_many_verbose() {
let files = FileGroup::new(vec![
partitioned_file("foo"),
partitioned_file("bar"),
partitioned_file("baz"),
partitioned_file("qux"),
partitioned_file("quux"),
partitioned_file("quuux"),
]);
let expected = "[foo, bar, baz, qux, quux, quuux]";
assert_eq!(
VerboseDisplay(FileGroupDisplay(&files)).to_string(),
expected
);
}
fn partitioned_file(path: &str) -> PartitionedFile {
let object_meta = ObjectMeta {
location: Path::parse(path).unwrap(),
last_modified: Utc::now(),
size: 42,
e_tag: None,
version: None,
};
PartitionedFile {
object_meta,
partition_values: vec![],
range: None,
statistics: None,
extensions: None,
metadata_size_hint: None,
}
}
}