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 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 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 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 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}