1use std::cmp::Ordering;
7use std::hash::Hash;
8use std::hash::Hasher;
9
10use vortex_error::VortexResult;
11use vortex_error::vortex_ensure_eq;
12use vortex_error::vortex_panic;
13
14use crate::dtype::DType;
15use crate::dtype::NativeDType;
16use crate::dtype::PType;
17use crate::dtype::StructFields;
18use crate::scalar::Scalar;
19use crate::scalar::ScalarValue;
20
21impl Scalar {
22 pub fn null(dtype: DType) -> Self {
30 assert!(
31 dtype.is_nullable(),
32 "Cannot create null scalar with non-nullable dtype {dtype}"
33 );
34
35 Self { dtype, value: None }
36 }
37
38 pub fn null_native<T: NativeDType>() -> Self {
43 Self {
44 dtype: T::dtype().as_nullable(),
45 value: None,
46 }
47 }
48
49 #[cfg(test)]
59 pub fn new(dtype: DType, value: Option<ScalarValue>) -> Self {
60 use vortex_error::VortexExpect;
61
62 Self::try_new(dtype, value).vortex_expect("Failed to create Scalar")
63 }
64
65 pub fn try_new(dtype: DType, value: Option<ScalarValue>) -> VortexResult<Self> {
72 Self::validate(&dtype, value.as_ref())?;
73
74 Ok(Self { dtype, value })
75 }
76
77 pub unsafe fn new_unchecked(dtype: DType, value: Option<ScalarValue>) -> Self {
85 #[cfg(debug_assertions)]
86 {
87 use vortex_error::VortexExpect;
88
89 Self::validate(&dtype, value.as_ref())
90 .vortex_expect("Scalar::new_unchecked called with incompatible dtype and value");
91 }
92
93 Self { dtype, value }
94 }
95
96 pub fn default_value(dtype: &DType) -> Self {
115 Self::try_default_value(dtype)
116 .unwrap_or_else(|| vortex_panic!("{dtype} has no default value"))
117 }
118
119 pub(crate) fn try_default_value(dtype: &DType) -> Option<Self> {
121 let value = ScalarValue::try_default_value(dtype)?;
122 Self::try_new(dtype.clone(), value).ok()
123 }
124
125 pub fn zero_value(dtype: &DType) -> Self {
153 let value = ScalarValue::zero_value(dtype);
154
155 unsafe { Self::new_unchecked(dtype.clone(), Some(value)) }
157 }
158
159 pub fn eq_ignore_nullability(&self, other: &Self) -> bool {
163 self.dtype.eq_ignore_nullability(&other.dtype) && self.value == other.value
164 }
165
166 pub fn into_parts(self) -> (DType, Option<ScalarValue>) {
168 (self.dtype, self.value)
169 }
170
171 pub fn dtype(&self) -> &DType {
173 &self.dtype
174 }
175
176 pub fn value(&self) -> Option<&ScalarValue> {
178 self.value.as_ref()
179 }
180
181 pub fn into_value(self) -> Option<ScalarValue> {
184 self.value
185 }
186
187 pub fn is_valid(&self) -> bool {
189 self.value.is_some()
190 }
191
192 pub fn is_null(&self) -> bool {
194 self.value.is_none()
195 }
196
197 pub fn is_zero(&self) -> Option<bool> {
206 let value = self.value()?;
207
208 let is_zero = match self.dtype() {
209 DType::Null => vortex_panic!("non-null value somehow had `DType::Null`"),
210 DType::Bool(_) => !value.as_bool(),
211 DType::Primitive(..) => value.as_primitive().is_zero(),
212 DType::Decimal(..) => value.as_decimal().is_zero(),
213 DType::Utf8(_) => value.as_utf8().is_empty(),
214 DType::Binary(_) => value.as_binary().is_empty(),
215 DType::List(..) => value.as_list().is_empty(),
216 DType::Map(..) => self.as_map().is_empty(),
217 DType::FixedSizeList(_, list_size, _) => {
220 let list = self.as_list();
221 list.len() == *list_size as usize
222 && (0..list.len())
223 .all(|i| list.element(i).is_some_and(|e| e.is_zero() == Some(true)))
224 }
225 DType::Struct(..) => self
227 .as_struct()
228 .fields_iter()
229 .is_some_and(|mut fields| fields.all(|f| f.is_zero() == Some(true))),
230 DType::Union(..) => {
233 let union = self.as_union();
234 if union.child_index() != Some(0) {
235 false
236 } else {
237 union.child().and_then(|child| child.is_zero())?
238 }
239 }
240 DType::Variant(_) => self.as_variant().is_zero()?,
241 DType::Extension(_) => self.as_extension().to_storage_scalar().is_zero()?,
242 };
243
244 Some(is_zero)
245 }
246
247 pub fn primitive_reinterpret_cast(&self, ptype: PType) -> VortexResult<Self> {
253 let primitive = self.as_primitive();
254 if primitive.ptype() == ptype {
255 return Ok(self.clone());
256 }
257
258 vortex_ensure_eq!(
259 primitive.ptype().byte_width(),
260 ptype.byte_width(),
261 "can't reinterpret cast between integers of two different widths"
262 );
263
264 Scalar::try_new(
265 DType::Primitive(ptype, self.dtype().nullability()),
266 primitive
267 .pvalue()
268 .map(|p| p.reinterpret_cast(ptype))
269 .map(ScalarValue::Primitive),
270 )
271 }
272
273 pub fn approx_nbytes(&self) -> usize {
278 use crate::dtype::NativeDecimalType;
279 use crate::dtype::i256;
280
281 match self.dtype() {
282 DType::Null => 0,
283 DType::Bool(_) => 1,
284 DType::Primitive(ptype, _) => ptype.byte_width(),
285 DType::Decimal(dt, _) => {
286 if dt.precision() <= i128::MAX_PRECISION {
287 size_of::<i128>()
288 } else {
289 size_of::<i256>()
290 }
291 }
292 DType::Utf8(_) => self
293 .value()
294 .map_or_else(|| 0, |value| value.as_utf8().len()),
295 DType::Binary(_) => self
296 .value()
297 .map_or_else(|| 0, |value| value.as_binary().len()),
298 DType::List(..) | DType::FixedSizeList(..) => self
299 .as_list()
300 .elements()
301 .map(|fields| fields.into_iter().map(|f| f.approx_nbytes()).sum::<usize>())
302 .unwrap_or_default(),
303 DType::Map(..) => self
304 .as_map()
305 .entries()
306 .map(|(key, value)| key.approx_nbytes() + value.approx_nbytes())
307 .sum(),
308 DType::Struct(..) => self
309 .as_struct()
310 .fields_iter()
311 .map(|fields| fields.into_iter().map(|f| f.approx_nbytes()).sum::<usize>())
312 .unwrap_or_default(),
313 DType::Union(..) => self
314 .as_union()
315 .child()
316 .map_or(0, |value| 1 + value.approx_nbytes()),
317 DType::Variant(_) => self.as_variant().value().map_or(0, Scalar::approx_nbytes),
318 DType::Extension(_) => self.as_extension().to_storage_scalar().approx_nbytes(),
319 }
320 }
321}
322
323impl Hash for Scalar {
327 fn hash<H: Hasher>(&self, state: &mut H) {
328 self.dtype.hash_ignore_nullability(state);
329 self.value.hash(state);
330 }
331}
332
333impl PartialEq for Scalar {
340 fn eq(&self, other: &Self) -> bool {
341 self.dtype.eq_ignore_nullability(&other.dtype) && self.value == other.value
342 }
343}
344
345impl PartialOrd for Scalar {
346 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
382 if !self.dtype().eq_ignore_nullability(other.dtype()) {
383 return None;
384 }
385
386 partial_cmp_scalar_values(self.dtype(), self.value(), other.value())
387 }
388}
389
390fn partial_cmp_scalar_values(
392 dtype: &DType,
393 lhs: Option<&ScalarValue>,
394 rhs: Option<&ScalarValue>,
395) -> Option<Ordering> {
396 match (lhs, rhs) {
397 (None, None) => Some(Ordering::Equal),
398 (None, Some(_)) => Some(Ordering::Less),
399 (Some(_), None) => Some(Ordering::Greater),
400 (Some(lhs), Some(rhs)) => partial_cmp_non_null_scalar_values(dtype, lhs, rhs),
401 }
402}
403
404fn partial_cmp_non_null_scalar_values(
406 dtype: &DType,
407 lhs: &ScalarValue,
408 rhs: &ScalarValue,
409) -> Option<Ordering> {
410 match (lhs, rhs) {
413 (ScalarValue::Bool(lhs), ScalarValue::Bool(rhs)) => lhs.partial_cmp(rhs),
414 (ScalarValue::Primitive(lhs), ScalarValue::Primitive(rhs)) => lhs.partial_cmp(rhs),
415 (ScalarValue::Decimal(lhs), ScalarValue::Decimal(rhs)) => lhs.partial_cmp(rhs),
416 (ScalarValue::Utf8(lhs), ScalarValue::Utf8(rhs)) => lhs.partial_cmp(rhs),
417 (ScalarValue::Binary(lhs), ScalarValue::Binary(rhs)) => lhs.partial_cmp(rhs),
418 (ScalarValue::Tuple(lhs), ScalarValue::Tuple(rhs)) => {
421 partial_cmp_tuple_values(dtype, lhs, rhs)
422 }
423 (ScalarValue::Union(lhs), ScalarValue::Union(rhs)) => {
424 if lhs.type_id() != rhs.type_id() {
425 return None;
426 }
427
428 let DType::Union(variants, _) = dtype else {
429 return None;
430 };
431 let child_index = variants.tag_to_child_index(lhs.type_id())?;
432 let child_dtype = variants.variant_by_index(child_index)?;
433
434 partial_cmp_scalar_values(&child_dtype, lhs.child_value(), rhs.child_value())
435 }
436 (ScalarValue::Variant(_), ScalarValue::Variant(_)) => None,
439 _ => None,
440 }
441}
442
443fn partial_cmp_tuple_values(
445 dtype: &DType,
446 lhs: &[Option<ScalarValue>],
447 rhs: &[Option<ScalarValue>],
448) -> Option<Ordering> {
449 match dtype {
450 DType::List(element_dtype, _) | DType::FixedSizeList(element_dtype, ..) => {
451 partial_cmp_list_values(element_dtype, lhs, rhs)
452 }
453 DType::Struct(fields, _) => partial_cmp_struct_values(fields, lhs, rhs),
454 DType::Map(map_dtype, _) => partial_cmp_list_values(&map_dtype.entries_dtype(), lhs, rhs),
456 DType::Extension(ext_dtype) => {
457 partial_cmp_tuple_values(ext_dtype.storage_dtype(), lhs, rhs)
458 }
459 _ => None,
460 }
461}
462
463fn partial_cmp_list_values(
465 element_dtype: &DType,
466 lhs: &[Option<ScalarValue>],
467 rhs: &[Option<ScalarValue>],
468) -> Option<Ordering> {
469 for (lhs, rhs) in lhs.iter().zip(rhs.iter()) {
470 match partial_cmp_scalar_values(element_dtype, lhs.as_ref(), rhs.as_ref())? {
471 Ordering::Equal => continue,
472 ordering => return Some(ordering),
473 }
474 }
475
476 Some(lhs.len().cmp(&rhs.len()))
477}
478
479fn partial_cmp_struct_values(
481 fields: &StructFields,
482 lhs: &[Option<ScalarValue>],
483 rhs: &[Option<ScalarValue>],
484) -> Option<Ordering> {
485 if lhs.len() != fields.nfields() || rhs.len() != fields.nfields() {
486 return None;
487 }
488
489 for ((field_dtype, lhs), rhs) in fields.fields().zip(lhs.iter()).zip(rhs.iter()) {
490 match partial_cmp_scalar_values(&field_dtype, lhs.as_ref(), rhs.as_ref())? {
491 Ordering::Equal => continue,
492 ordering => return Some(ordering),
493 }
494 }
495
496 Some(Ordering::Equal)
497}
498
499#[cfg(test)]
500mod tests {
501 use std::cmp::Ordering;
502 use std::sync::Arc;
503
504 use rstest::rstest;
505 use vortex_error::VortexResult;
506
507 use crate::dtype::DType;
508 use crate::dtype::Nullability;
509 use crate::dtype::PType;
510 use crate::dtype::StructFields;
511 use crate::scalar::Scalar;
512
513 fn i32_scalar(value: i32) -> Scalar {
514 Scalar::primitive::<i32>(value, Nullability::NonNullable)
515 }
516
517 fn nullable_i32(value: Option<i32>) -> Scalar {
518 match value {
519 Some(value) => Scalar::primitive::<i32>(value, Nullability::Nullable),
520 None => Scalar::null(DType::Primitive(PType::I32, Nullability::Nullable)),
521 }
522 }
523
524 fn map_dtype(nullability: Nullability) -> VortexResult<DType> {
525 DType::map(
526 DType::Primitive(PType::I32, Nullability::NonNullable),
527 DType::Utf8(Nullability::Nullable),
528 false,
529 nullability,
530 )
531 }
532
533 fn map_scalar(entries: Vec<(i32, Option<&str>)>) -> VortexResult<Scalar> {
534 Scalar::try_map(
535 map_dtype(Nullability::Nullable)?,
536 entries.into_iter().map(|(key, value)| {
537 (
538 i32_scalar(key),
539 match value {
540 Some(value) => Scalar::utf8(value, Nullability::Nullable),
541 None => Scalar::null(DType::Utf8(Nullability::Nullable)),
542 },
543 )
544 }),
545 )
546 }
547
548 #[rstest]
550 #[case(vec![(1, Some("a"))], vec![(1, Some("a"))], Ordering::Equal)]
551 #[case(vec![(1, Some("a"))], vec![(1, Some("b"))], Ordering::Less)]
552 #[case(vec![(2, Some("a"))], vec![(1, Some("z"))], Ordering::Greater)]
553 #[case(vec![(1, Some("a"))], vec![(1, Some("a")), (2, Some("b"))], Ordering::Less)]
554 #[case(vec![], vec![], Ordering::Equal)]
555 #[case(vec![], vec![(1, Some("a"))], Ordering::Less)]
556 #[case(vec![(1, Some("a"))], vec![(1, None)], Ordering::Greater)]
557 fn map_ordering(
558 #[case] lhs: Vec<(i32, Option<&str>)>,
559 #[case] rhs: Vec<(i32, Option<&str>)>,
560 #[case] expected: Ordering,
561 ) -> VortexResult<()> {
562 assert_eq!(
563 map_scalar(lhs)?.partial_cmp(&map_scalar(rhs)?),
564 Some(expected)
565 );
566 Ok(())
567 }
568
569 #[test]
570 fn null_map_orders_before_every_non_null_map() -> VortexResult<()> {
571 let null = Scalar::null(map_dtype(Nullability::Nullable)?);
572
573 assert_eq!(null.partial_cmp(&map_scalar(vec![])?), Some(Ordering::Less));
574 assert_eq!(null.partial_cmp(&null), Some(Ordering::Equal));
575
576 Ok(())
577 }
578
579 fn ab_struct_dtype(nullability: Nullability) -> DType {
580 DType::Struct(
581 StructFields::new(
582 ["a", "b"].into(),
583 vec![
584 DType::Primitive(PType::I32, Nullability::NonNullable),
585 DType::Utf8(Nullability::NonNullable),
586 ],
587 ),
588 nullability,
589 )
590 }
591
592 #[rstest]
593 #[case(vec![0, 0], Some(true))]
595 #[case(vec![0], Some(true))]
596 #[case(vec![0, 5], Some(false))]
599 #[case(vec![5, 0], Some(false))]
600 #[case(vec![1, 2], Some(false))]
601 fn fixed_size_list_is_zero(#[case] values: Vec<i32>, #[case] expected: Option<bool>) {
602 let element_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
603 let children: Vec<Scalar> = values.into_iter().map(i32_scalar).collect();
604 let scalar = Scalar::fixed_size_list(element_dtype, children, Nullability::NonNullable);
605 assert_eq!(scalar.is_zero(), expected);
606 }
607
608 #[test]
609 fn null_fixed_size_list_is_zero_is_none() {
610 let element_dtype = Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable));
611 let scalar = Scalar::null(DType::FixedSizeList(
612 element_dtype,
613 2,
614 Nullability::Nullable,
615 ));
616 assert_eq!(scalar.is_zero(), None);
617 }
618
619 #[test]
620 fn fixed_size_list_with_null_element_is_not_zero() {
621 let element_dtype = DType::Primitive(PType::I32, Nullability::Nullable);
624 let children = vec![nullable_i32(Some(0)), nullable_i32(None)];
625 let scalar = Scalar::fixed_size_list(element_dtype, children, Nullability::NonNullable);
626 assert_eq!(scalar.is_zero(), Some(false));
627 }
628
629 #[test]
630 fn struct_with_all_zero_fields_is_zero() {
631 let scalar = Scalar::struct_(
632 ab_struct_dtype(Nullability::NonNullable),
633 vec![i32_scalar(0), Scalar::utf8("", Nullability::NonNullable)],
634 );
635 assert_eq!(scalar.is_zero(), Some(true));
636 }
637
638 #[rstest]
639 #[case(5, "")]
642 #[case(0, "x")]
643 #[case(7, "y")]
644 fn struct_with_non_zero_field_is_not_zero(#[case] a: i32, #[case] b: &str) {
645 let scalar = Scalar::struct_(
646 ab_struct_dtype(Nullability::NonNullable),
647 vec![i32_scalar(a), Scalar::utf8(b, Nullability::NonNullable)],
648 );
649 assert_eq!(scalar.is_zero(), Some(false));
650 }
651
652 #[test]
653 fn null_struct_is_zero_is_none() {
654 let scalar = Scalar::null(ab_struct_dtype(Nullability::Nullable));
655 assert_eq!(scalar.is_zero(), None);
656 }
657
658 #[test]
659 fn struct_with_null_field_is_not_zero() {
660 let dtype = DType::Struct(
663 StructFields::new(
664 ["a", "b"].into(),
665 vec![
666 DType::Primitive(PType::I32, Nullability::Nullable),
667 DType::Primitive(PType::I32, Nullability::Nullable),
668 ],
669 ),
670 Nullability::NonNullable,
671 );
672 let scalar = Scalar::struct_(dtype, vec![nullable_i32(Some(0)), nullable_i32(None)]);
673 assert_eq!(scalar.is_zero(), Some(false));
674 }
675
676 #[test]
677 fn nested_struct_of_fixed_size_list_recurses() {
678 let element_dtype = Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable));
681 let fsl_dtype =
682 DType::FixedSizeList(Arc::clone(&element_dtype), 2, Nullability::NonNullable);
683 let struct_dtype = DType::Struct(
684 StructFields::new(["fsl"].into(), vec![fsl_dtype]),
685 Nullability::NonNullable,
686 );
687
688 let all_zero = Scalar::struct_(
689 struct_dtype.clone(),
690 vec![Scalar::fixed_size_list(
691 Arc::clone(&element_dtype),
692 vec![i32_scalar(0), i32_scalar(0)],
693 Nullability::NonNullable,
694 )],
695 );
696 assert_eq!(all_zero.is_zero(), Some(true));
697
698 let with_non_zero = Scalar::struct_(
699 struct_dtype,
700 vec![Scalar::fixed_size_list(
701 element_dtype,
702 vec![i32_scalar(0), i32_scalar(9)],
703 Nullability::NonNullable,
704 )],
705 );
706 assert_eq!(with_non_zero.is_zero(), Some(false));
707 }
708}