1use datafusion_physical_plan::{DisplayAs, DisplayFormatType};
19
20use std::fmt::{Debug, Formatter, Result as FmtResult};
21
22use crate::PartitionedFile;
23
24#[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 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#[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 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
94fn 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 if len > n {
110 write!(f, ", ...")?;
111 }
112 Ok(())
113}
114
115fn 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 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}