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