blob: 0f59e33ff9eacd111cbad4e54e442fb61c7ffa5e [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use datafusion_physical_plan::{DisplayAs, DisplayFormatType};
use crate::file_groups::FileGroup;
use std::fmt::{Debug, Formatter, Result as FmtResult};
/// A wrapper to customize partitioned file display
///
/// Prints in the format:
/// ```text
/// {NUM_GROUPS groups: [[file1, file2,...], [fileN, fileM, ...], ...]}
/// ```
#[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 => {
// To avoid showing too many partitions
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, "]}}")
}
}
/// A wrapper to customize partitioned group of files display
///
/// Prints in the format:
/// ```text
/// [file1, file2,...]
/// ```
#[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 => {
// To avoid showing too many files
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, "]")
}
}
/// helper to format an array of up to N elements
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)
})?;
// Remaining elements are showed as `...` (to indicate there is more)
if len > n {
write!(f, ", ...")?;
}
Ok(())
}
/// helper formatting array elements with a comma and a space between them
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::{ObjectMeta, path::Path};
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
);
}
/// create a PartitionedFile for testing
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::new_from_meta(object_meta)
}
}