datafusion_datasource/
display.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use datafusion_physical_plan::{DisplayAs, DisplayFormatType};
19
20use crate::file_groups::FileGroup;
21use std::fmt::{Debug, Formatter, Result as FmtResult};
22
23/// A wrapper to customize partitioned file display
24///
25/// Prints in the format:
26/// ```text
27/// {NUM_GROUPS groups: [[file1, file2,...], [fileN, fileM, ...], ...]}
28/// ```
29#[derive(Debug)]
30pub(crate) struct FileGroupsDisplay<'a>(pub(crate) &'a [FileGroup]);
31
32impl DisplayAs for FileGroupsDisplay<'_> {
33    fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> FmtResult {
34        let n_groups = self.0.len();
35        let groups = if n_groups == 1 { "group" } else { "groups" };
36        write!(f, "{{{n_groups} {groups}: [")?;
37        match t {
38            DisplayFormatType::Default | DisplayFormatType::TreeRender => {
39                // To avoid showing too many partitions
40                let max_groups = 5;
41                fmt_up_to_n_elements(self.0, max_groups, f, |group, f| {
42                    FileGroupDisplay(group).fmt_as(t, f)
43                })?;
44            }
45            DisplayFormatType::Verbose => {
46                fmt_elements_split_by_commas(self.0.iter(), f, |group, f| {
47                    FileGroupDisplay(group).fmt_as(t, f)
48                })?
49            }
50        }
51        write!(f, "]}}")
52    }
53}
54
55/// A wrapper to customize partitioned group of files display
56///
57/// Prints in the format:
58/// ```text
59/// [file1, file2,...]
60/// ```
61#[derive(Debug)]
62pub struct FileGroupDisplay<'a>(pub &'a FileGroup);
63
64impl DisplayAs for FileGroupDisplay<'_> {
65    fn fmt_as(&self, t: DisplayFormatType, f: &mut Formatter) -> FmtResult {
66        write!(f, "[")?;
67        match t {
68            DisplayFormatType::Default | DisplayFormatType::TreeRender => {
69                // To avoid showing too many files
70                let max_files = 5;
71                fmt_up_to_n_elements(self.0.files(), max_files, f, |pf, f| {
72                    write!(f, "{}", pf.object_meta.location.as_ref())?;
73                    if let Some(range) = pf.range.as_ref() {
74                        write!(f, ":{}..{}", range.start, range.end)?;
75                    }
76                    Ok(())
77                })?
78            }
79            DisplayFormatType::Verbose => {
80                fmt_elements_split_by_commas(self.0.iter(), f, |pf, f| {
81                    write!(f, "{}", pf.object_meta.location.as_ref())?;
82                    if let Some(range) = pf.range.as_ref() {
83                        write!(f, ":{}..{}", range.start, range.end)?;
84                    }
85                    Ok(())
86                })?
87            }
88        }
89        write!(f, "]")
90    }
91}
92
93/// helper to format an array of up to N elements
94fn fmt_up_to_n_elements<E, F>(
95    elements: &[E],
96    n: usize,
97    f: &mut Formatter,
98    format_element: F,
99) -> FmtResult
100where
101    F: Fn(&E, &mut Formatter) -> FmtResult,
102{
103    let len = elements.len();
104    fmt_elements_split_by_commas(elements.iter().take(n), f, |element, f| {
105        format_element(element, f)
106    })?;
107    // Remaining elements are showed as `...` (to indicate there is more)
108    if len > n {
109        write!(f, ", ...")?;
110    }
111    Ok(())
112}
113
114/// helper formatting array elements with a comma and a space between them
115fn fmt_elements_split_by_commas<E, I, F>(
116    iter: I,
117    f: &mut Formatter,
118    format_element: F,
119) -> FmtResult
120where
121    I: Iterator<Item = E>,
122    F: Fn(E, &mut Formatter) -> FmtResult,
123{
124    for (idx, element) in iter.enumerate() {
125        if idx > 0 {
126            write!(f, ", ")?;
127        }
128        format_element(element, f)?;
129    }
130    Ok(())
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    use datafusion_physical_plan::{DefaultDisplay, VerboseDisplay};
138    use object_store::{path::Path, ObjectMeta};
139
140    use crate::PartitionedFile;
141    use chrono::Utc;
142
143    #[test]
144    fn file_groups_display_empty() {
145        let expected = "{0 groups: []}";
146        assert_eq!(DefaultDisplay(FileGroupsDisplay(&[])).to_string(), expected);
147    }
148
149    #[test]
150    fn file_groups_display_one() {
151        let files = [FileGroup::new(vec![
152            partitioned_file("foo"),
153            partitioned_file("bar"),
154        ])];
155
156        let expected = "{1 group: [[foo, bar]]}";
157        assert_eq!(
158            DefaultDisplay(FileGroupsDisplay(&files)).to_string(),
159            expected
160        );
161    }
162
163    #[test]
164    fn file_groups_display_many_default() {
165        let files = [
166            FileGroup::new(vec![partitioned_file("foo"), partitioned_file("bar")]),
167            FileGroup::new(vec![partitioned_file("baz")]),
168            FileGroup::default(),
169        ];
170
171        let expected = "{3 groups: [[foo, bar], [baz], []]}";
172        assert_eq!(
173            DefaultDisplay(FileGroupsDisplay(&files)).to_string(),
174            expected
175        );
176    }
177
178    #[test]
179    fn file_groups_display_many_verbose() {
180        let files = [
181            FileGroup::new(vec![partitioned_file("foo"), partitioned_file("bar")]),
182            FileGroup::new(vec![partitioned_file("baz")]),
183            FileGroup::default(),
184        ];
185
186        let expected = "{3 groups: [[foo, bar], [baz], []]}";
187        assert_eq!(
188            VerboseDisplay(FileGroupsDisplay(&files)).to_string(),
189            expected
190        );
191    }
192
193    #[test]
194    fn file_groups_display_too_many_default() {
195        let files = [
196            FileGroup::new(vec![partitioned_file("foo"), partitioned_file("bar")]),
197            FileGroup::new(vec![partitioned_file("baz")]),
198            FileGroup::new(vec![partitioned_file("qux")]),
199            FileGroup::new(vec![partitioned_file("quux")]),
200            FileGroup::new(vec![partitioned_file("quuux")]),
201            FileGroup::new(vec![partitioned_file("quuuux")]),
202            FileGroup::default(),
203        ];
204
205        let expected = "{7 groups: [[foo, bar], [baz], [qux], [quux], [quuux], ...]}";
206        assert_eq!(
207            DefaultDisplay(FileGroupsDisplay(&files)).to_string(),
208            expected
209        );
210    }
211
212    #[test]
213    fn file_groups_display_too_many_verbose() {
214        let files = [
215            FileGroup::new(vec![partitioned_file("foo"), partitioned_file("bar")]),
216            FileGroup::new(vec![partitioned_file("baz")]),
217            FileGroup::new(vec![partitioned_file("qux")]),
218            FileGroup::new(vec![partitioned_file("quux")]),
219            FileGroup::new(vec![partitioned_file("quuux")]),
220            FileGroup::new(vec![partitioned_file("quuuux")]),
221            FileGroup::default(),
222        ];
223
224        let expected =
225            "{7 groups: [[foo, bar], [baz], [qux], [quux], [quuux], [quuuux], []]}";
226        assert_eq!(
227            VerboseDisplay(FileGroupsDisplay(&files)).to_string(),
228            expected
229        );
230    }
231
232    #[test]
233    fn file_group_display_many_default() {
234        let files =
235            FileGroup::new(vec![partitioned_file("foo"), partitioned_file("bar")]);
236
237        let expected = "[foo, bar]";
238        assert_eq!(
239            DefaultDisplay(FileGroupDisplay(&files)).to_string(),
240            expected
241        );
242    }
243
244    #[test]
245    fn file_group_display_too_many_default() {
246        let files = FileGroup::new(vec![
247            partitioned_file("foo"),
248            partitioned_file("bar"),
249            partitioned_file("baz"),
250            partitioned_file("qux"),
251            partitioned_file("quux"),
252            partitioned_file("quuux"),
253        ]);
254
255        let expected = "[foo, bar, baz, qux, quux, ...]";
256        assert_eq!(
257            DefaultDisplay(FileGroupDisplay(&files)).to_string(),
258            expected
259        );
260    }
261
262    #[test]
263    fn file_group_display_too_many_verbose() {
264        let files = FileGroup::new(vec![
265            partitioned_file("foo"),
266            partitioned_file("bar"),
267            partitioned_file("baz"),
268            partitioned_file("qux"),
269            partitioned_file("quux"),
270            partitioned_file("quuux"),
271        ]);
272
273        let expected = "[foo, bar, baz, qux, quux, quuux]";
274        assert_eq!(
275            VerboseDisplay(FileGroupDisplay(&files)).to_string(),
276            expected
277        );
278    }
279
280    /// create a PartitionedFile for testing
281    fn partitioned_file(path: &str) -> PartitionedFile {
282        let object_meta = ObjectMeta {
283            location: Path::parse(path).unwrap(),
284            last_modified: Utc::now(),
285            size: 42,
286            e_tag: None,
287            version: None,
288        };
289
290        PartitionedFile {
291            object_meta,
292            partition_values: vec![],
293            range: None,
294            statistics: None,
295            extensions: None,
296            metadata_size_hint: None,
297        }
298    }
299}