vortex_scalar/
display.rs

1use std::fmt::{Display, Formatter};
2
3use itertools::Itertools;
4use vortex_dtype::DType;
5use vortex_dtype::datetime::{TemporalMetadata, is_temporal_ext_type};
6use vortex_error::{VortexExpect, vortex_panic};
7
8use crate::binary::BinaryScalar;
9use crate::extension::ExtScalar;
10use crate::struct_::StructScalar;
11use crate::utf8::Utf8Scalar;
12use crate::{ListScalar, Scalar};
13
14impl Display for Scalar {
15    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
16        match self.dtype() {
17            DType::Null | DType::Bool(_) | DType::Primitive(..) => Display::fmt(&self.value, f),
18            DType::Utf8(_) => {
19                match Utf8Scalar::try_from(self)
20                    .map_err(|_| std::fmt::Error)?
21                    .value()
22                {
23                    None => write!(f, "null"),
24                    Some(bs) => write!(f, "\"{}\"", bs.as_str()),
25                }
26            }
27            DType::Binary(_) => {
28                match BinaryScalar::try_from(self)
29                    .map_err(|_| std::fmt::Error)?
30                    .value()
31                {
32                    None => write!(f, "null"),
33                    Some(buf) => {
34                        write!(
35                            f,
36                            "\"{}\"",
37                            buf.as_slice().iter().map(|b| format!("{b:x}")).format(",")
38                        )
39                    }
40                }
41            }
42            DType::Struct(dtype, _) => {
43                let v = StructScalar::try_from(self).map_err(|_| std::fmt::Error)?;
44
45                if v.is_null() {
46                    write!(f, "null")
47                } else {
48                    write!(f, "{{")?;
49                    let formatted_fields = dtype
50                        .names()
51                        .iter()
52                        .enumerate()
53                        .map(|(idx, name)| {
54                            let val = v.field_by_idx(idx).vortex_expect("not out of bounds");
55                            format!("{name}:{val}")
56                        })
57                        .format(",");
58                    write!(f, "{}", formatted_fields)?;
59                    write!(f, "}}")
60                }
61            }
62            DType::List(..) => {
63                let v = ListScalar::try_from(self).map_err(|_| std::fmt::Error)?;
64                match v.elements() {
65                    None => write!(f, "null"),
66                    Some(elems) => {
67                        write!(f, "[{}]", elems.iter().format(","))
68                    }
69                }
70            }
71            // Specialized handling for date/time/timestamp builtin extension types.
72            DType::Extension(dtype) if is_temporal_ext_type(dtype.id()) => {
73                let metadata =
74                    TemporalMetadata::try_from(dtype.as_ref()).map_err(|_| std::fmt::Error)?;
75                let storage_scalar = self.as_extension().storage();
76
77                match storage_scalar.dtype() {
78                    DType::Null => {
79                        write!(f, "null")
80                    }
81                    DType::Primitive(..) => {
82                        let maybe_timestamp = storage_scalar
83                            .as_primitive()
84                            .as_::<i64>()
85                            .and_then(|maybe_timestamp| {
86                                maybe_timestamp.map(|v| metadata.to_jiff(v)).transpose()
87                            })
88                            .map_err(|_| std::fmt::Error)?;
89                        match maybe_timestamp {
90                            None => write!(f, "null"),
91                            Some(v) => write!(f, "{}", v),
92                        }
93                    }
94                    _ => {
95                        vortex_panic!(
96                            "Expected temporal extension data type to have Primitive or Null storage type"
97                        )
98                    }
99                }
100            }
101            // Generic handling of unknown extension types.
102            // TODO(aduffy): Allow extension authors plugin their own Scalar display.
103            DType::Extension(..) => {
104                let storage_value = ExtScalar::try_from(self)
105                    .map_err(|_| std::fmt::Error)?
106                    .storage();
107                write!(f, "{}", storage_value)
108            }
109        }
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use std::sync::Arc;
116
117    use vortex_buffer::ByteBuffer;
118    use vortex_dtype::Nullability::{NonNullable, Nullable};
119    use vortex_dtype::datetime::{DATE_ID, TIME_ID, TIMESTAMP_ID, TemporalMetadata, TimeUnit};
120    use vortex_dtype::{DType, ExtDType, ExtMetadata, PType, StructDType};
121
122    use crate::{InnerScalarValue, PValue, Scalar, ScalarValue};
123
124    const MINUTES: i32 = 60;
125    const HOURS: i32 = 60 * MINUTES;
126    const DAYS: i32 = 24 * HOURS;
127
128    #[test]
129    fn display_bool() {
130        assert_eq!(format!("{}", Scalar::from(false)), "false");
131        assert_eq!(format!("{}", Scalar::from(true)), "true");
132        assert_eq!(format!("{}", Scalar::null(DType::Bool(Nullable))), "null");
133    }
134
135    #[test]
136    fn display_primitive() {
137        assert_eq!(format!("{}", Scalar::from(0_u8)), "0_u8");
138        assert_eq!(format!("{}", Scalar::from(255_u8)), "255_u8");
139
140        assert_eq!(format!("{}", Scalar::from(0_u16)), "0_u16");
141        assert_eq!(format!("{}", Scalar::from(!0_u16)), "65535_u16");
142
143        assert_eq!(format!("{}", Scalar::from(0_u32)), "0_u32");
144        assert_eq!(format!("{}", Scalar::from(!0_u32)), "4294967295_u32");
145
146        assert_eq!(format!("{}", Scalar::from(0_u64)), "0_u64");
147        assert_eq!(
148            format!("{}", Scalar::from(!0_u64)),
149            "18446744073709551615_u64"
150        );
151
152        assert_eq!(
153            format!("{}", Scalar::null(DType::Primitive(PType::U8, Nullable))),
154            "null"
155        );
156    }
157
158    #[test]
159    fn display_utf8() {
160        assert_eq!(
161            format!("{}", Scalar::from("Hello World!")),
162            "\"Hello World!\""
163        );
164        assert_eq!(format!("{}", Scalar::null(DType::Utf8(Nullable))), "null");
165    }
166
167    #[test]
168    fn display_binary() {
169        assert_eq!(
170            format!(
171                "{}",
172                Scalar::binary(
173                    ByteBuffer::from("Hello World!".as_bytes().to_vec()),
174                    NonNullable
175                )
176            ),
177            "\"48,65,6c,6c,6f,20,57,6f,72,6c,64,21\""
178        );
179        assert_eq!(format!("{}", Scalar::null(DType::Binary(Nullable))), "null");
180    }
181
182    #[test]
183    fn display_empty_struct() {
184        fn dtype() -> DType {
185            DType::Struct(Arc::new(StructDType::new([].into(), vec![])), Nullable)
186        }
187
188        assert_eq!(format!("{}", Scalar::null(dtype())), "null");
189
190        assert_eq!(format!("{}", Scalar::struct_(dtype(), vec![])), "{}");
191    }
192
193    #[test]
194    fn display_one_field_struct() {
195        fn dtype() -> DType {
196            DType::Struct(
197                Arc::new(StructDType::new(
198                    [Arc::from("foo")].into(),
199                    vec![DType::Primitive(PType::U32, Nullable)],
200                )),
201                Nullable,
202            )
203        }
204
205        assert_eq!(format!("{}", Scalar::null(dtype())), "null");
206
207        assert_eq!(
208            format!(
209                "{}",
210                Scalar::struct_(dtype(), vec![Scalar::null_typed::<u32>()])
211            ),
212            "{foo:null}"
213        );
214
215        assert_eq!(
216            format!("{}", Scalar::struct_(dtype(), vec![Scalar::from(32_u32)])),
217            "{foo:32_u32}"
218        );
219    }
220
221    #[test]
222    fn display_two_field_struct() {
223        // fn dtype() -> (DType, DType, DType) {
224        let f1 = DType::Bool(Nullable);
225        let f2 = DType::Primitive(PType::U32, Nullable);
226        let dtype = DType::Struct(
227            Arc::new(StructDType::new(
228                [Arc::from("foo"), Arc::from("bar")].into(),
229                vec![f1.clone(), f2.clone()],
230            )),
231            Nullable,
232        );
233        // }
234
235        assert_eq!(format!("{}", Scalar::null(dtype.clone())), "null");
236
237        assert_eq!(
238            format!(
239                "{}",
240                Scalar::struct_(
241                    dtype.clone(),
242                    vec![Scalar::null(f1), Scalar::null(f2.clone())]
243                )
244            ),
245            "{foo:null,bar:null}"
246        );
247
248        assert_eq!(
249            format!(
250                "{}",
251                Scalar::struct_(dtype.clone(), vec![Scalar::from(true), Scalar::null(f2)])
252            ),
253            "{foo:true,bar:null}"
254        );
255
256        assert_eq!(
257            format!(
258                "{}",
259                Scalar::struct_(dtype, vec![Scalar::from(true), Scalar::from(32_u32)])
260            ),
261            "{foo:true,bar:32_u32}"
262        );
263    }
264
265    #[test]
266    fn display_time() {
267        fn dtype() -> DType {
268            DType::Extension(Arc::new(ExtDType::new(
269                TIME_ID.clone(),
270                Arc::new(DType::Primitive(PType::I32, Nullable)),
271                Some(ExtMetadata::from(TemporalMetadata::Time(TimeUnit::S))),
272            )))
273        }
274
275        assert_eq!(format!("{}", Scalar::null(dtype())), "null");
276
277        assert_eq!(
278            format!(
279                "{}",
280                Scalar::new(
281                    dtype(),
282                    ScalarValue(InnerScalarValue::Primitive(PValue::I32(3 * MINUTES + 25)))
283                )
284            ),
285            "00:03:25"
286        );
287    }
288
289    #[test]
290    fn display_date() {
291        fn dtype() -> DType {
292            DType::Extension(Arc::new(ExtDType::new(
293                DATE_ID.clone(),
294                Arc::new(DType::Primitive(PType::I32, Nullable)),
295                Some(ExtMetadata::from(TemporalMetadata::Date(TimeUnit::D))),
296            )))
297        }
298
299        assert_eq!(format!("{}", Scalar::null(dtype())), "null");
300
301        assert_eq!(
302            format!(
303                "{}",
304                Scalar::new(
305                    dtype(),
306                    ScalarValue(InnerScalarValue::Primitive(PValue::I32(25)))
307                )
308            ),
309            "1970-01-26"
310        );
311
312        assert_eq!(
313            format!(
314                "{}",
315                Scalar::new(
316                    dtype(),
317                    ScalarValue(InnerScalarValue::Primitive(PValue::I32(365)))
318                )
319            ),
320            "1971-01-01"
321        );
322
323        assert_eq!(
324            format!(
325                "{}",
326                Scalar::new(
327                    dtype(),
328                    ScalarValue(InnerScalarValue::Primitive(PValue::I32(365 * 4)))
329                )
330            ),
331            "1973-12-31"
332        );
333    }
334
335    #[test]
336    fn display_local_timestamp() {
337        fn dtype() -> DType {
338            DType::Extension(Arc::new(ExtDType::new(
339                TIMESTAMP_ID.clone(),
340                Arc::new(DType::Primitive(PType::I32, Nullable)),
341                Some(ExtMetadata::from(TemporalMetadata::Timestamp(
342                    TimeUnit::S,
343                    None,
344                ))),
345            )))
346        }
347
348        assert_eq!(format!("{}", Scalar::null(dtype())), "null");
349
350        assert_eq!(
351            format!(
352                "{}",
353                Scalar::new(
354                    dtype(),
355                    ScalarValue(InnerScalarValue::Primitive(PValue::I32(
356                        3 * DAYS + 2 * HOURS + 5 * MINUTES + 10
357                    )))
358                )
359            ),
360            "1970-01-04T02:05:10Z"
361        );
362    }
363
364    #[cfg_attr(miri, ignore)]
365    #[test]
366    fn display_zoned_timestamp() {
367        fn dtype() -> DType {
368            DType::Extension(Arc::new(ExtDType::new(
369                TIMESTAMP_ID.clone(),
370                Arc::new(DType::Primitive(PType::I64, Nullable)),
371                Some(ExtMetadata::from(TemporalMetadata::Timestamp(
372                    TimeUnit::S,
373                    Some(String::from("Pacific/Guam")),
374                ))),
375            )))
376        }
377
378        assert_eq!(format!("{}", Scalar::null(dtype())), "null");
379
380        assert_eq!(
381            format!(
382                "{}",
383                Scalar::new(
384                    dtype(),
385                    ScalarValue(InnerScalarValue::Primitive(PValue::I32(0)))
386                )
387            ),
388            "1970-01-01T10:00:00+10:00[Pacific/Guam]"
389        );
390
391        assert_eq!(
392            format!(
393                "{}",
394                Scalar::new(
395                    dtype(),
396                    ScalarValue(InnerScalarValue::Primitive(PValue::I32(
397                        3 * DAYS + 2 * HOURS + 5 * MINUTES + 10
398                    )))
399                )
400            ),
401            "1970-01-04T12:05:10+10:00[Pacific/Guam]"
402        );
403    }
404}