1use std::cmp::Ordering;
7use std::hash::Hash;
8use std::hash::Hasher;
9
10use vortex_error::VortexError;
11use vortex_error::VortexResult;
12use vortex_error::vortex_ensure_eq;
13use vortex_error::vortex_panic;
14
15use crate::dtype::DType;
16use crate::dtype::NativeDType;
17use crate::dtype::PType;
18use crate::dtype::StructFields;
19use crate::scalar::Scalar;
20use crate::scalar::ScalarValue;
21
22impl Scalar {
23 pub fn null(dtype: DType) -> Self {
31 assert!(
32 dtype.is_nullable(),
33 "Cannot create null scalar with non-nullable dtype {dtype}"
34 );
35
36 Self { dtype, value: None }
37 }
38
39 pub fn null_native<T: NativeDType>() -> Self {
44 Self {
45 dtype: T::dtype().as_nullable(),
46 value: None,
47 }
48 }
49
50 #[cfg(test)]
60 pub fn new(dtype: DType, value: Option<ScalarValue>) -> Self {
61 use vortex_error::VortexExpect;
62
63 Self::try_new(dtype, value).vortex_expect("Failed to create Scalar")
64 }
65
66 #[inline]
73 pub fn try_new(dtype: DType, value: Option<ScalarValue>) -> VortexResult<Self> {
74 if !Self::is_compatible(&dtype, value.as_ref()) {
75 return Err(Self::try_new_failed(dtype, value));
76 }
77
78 Ok(Self { dtype, value })
79 }
80
81 #[cold]
85 #[inline(never)]
86 fn try_new_failed(dtype: DType, value: Option<ScalarValue>) -> VortexError {
87 Self::incompatible_error(&dtype, value.as_ref())
88 }
89
90 pub unsafe fn new_unchecked(dtype: DType, value: Option<ScalarValue>) -> Self {
98 #[cfg(debug_assertions)]
99 {
100 use vortex_error::VortexExpect;
101
102 Self::validate(&dtype, value.as_ref())
103 .vortex_expect("Scalar::new_unchecked called with incompatible dtype and value");
104 }
105
106 Self { dtype, value }
107 }
108
109 pub fn default_value(dtype: &DType) -> Self {
128 Self::try_default_value(dtype)
129 .unwrap_or_else(|| vortex_panic!("{dtype} has no default value"))
130 }
131
132 pub(crate) fn try_default_value(dtype: &DType) -> Option<Self> {
134 let value = ScalarValue::try_default_value(dtype)?;
135 Self::try_new(dtype.clone(), value).ok()
136 }
137
138 pub fn zero_value(dtype: &DType) -> Self {
166 let value = ScalarValue::zero_value(dtype);
167
168 unsafe { Self::new_unchecked(dtype.clone(), Some(value)) }
170 }
171
172 pub fn eq_ignore_nullability(&self, other: &Self) -> bool {
176 self.dtype.eq_ignore_nullability(&other.dtype) && self.value == other.value
177 }
178
179 pub fn into_parts(self) -> (DType, Option<ScalarValue>) {
181 (self.dtype, self.value)
182 }
183
184 pub fn dtype(&self) -> &DType {
186 &self.dtype
187 }
188
189 pub fn value(&self) -> Option<&ScalarValue> {
191 self.value.as_ref()
192 }
193
194 pub fn into_value(self) -> Option<ScalarValue> {
197 self.value
198 }
199
200 pub fn is_valid(&self) -> bool {
202 self.value.is_some()
203 }
204
205 pub fn is_null(&self) -> bool {
207 self.value.is_none()
208 }
209
210 pub fn is_zero(&self) -> Option<bool> {
219 let value = self.value()?;
220
221 let is_zero = match self.dtype() {
222 DType::Null => vortex_panic!("non-null value somehow had `DType::Null`"),
223 DType::Bool(_) => !value.as_bool(),
224 DType::Primitive(..) => value.as_primitive().is_zero(),
225 DType::Decimal(..) => value.as_decimal().is_zero(),
226 DType::Utf8(_) => value.as_utf8().is_empty(),
227 DType::Binary(_) => value.as_binary().is_empty(),
228 DType::List(..) => value.as_list().is_empty(),
229 DType::Map(..) => self.as_map().is_empty(),
230 DType::FixedSizeList(_, list_size, _) => {
233 let list = self.as_list();
234 list.len() == *list_size as usize
235 && (0..list.len())
236 .all(|i| list.element(i).is_some_and(|e| e.is_zero() == Some(true)))
237 }
238 DType::Struct(..) => self
240 .as_struct()
241 .fields_iter()
242 .is_some_and(|mut fields| fields.all(|f| f.is_zero() == Some(true))),
243 DType::Union(..) => {
246 let union = self.as_union();
247 if union.child_index() != Some(0) {
248 false
249 } else {
250 union.child().and_then(|child| child.is_zero())?
251 }
252 }
253 DType::Variant(_) => self.as_variant().is_zero()?,
254 DType::Extension(_) => self.as_extension().to_storage_scalar().is_zero()?,
255 };
256
257 Some(is_zero)
258 }
259
260 pub fn primitive_reinterpret_cast(&self, ptype: PType) -> VortexResult<Self> {
266 let primitive = self.as_primitive();
267 if primitive.ptype() == ptype {
268 return Ok(self.clone());
269 }
270
271 vortex_ensure_eq!(
272 primitive.ptype().byte_width(),
273 ptype.byte_width(),
274 "can't reinterpret cast between integers of two different widths"
275 );
276
277 Scalar::try_new(
278 DType::Primitive(ptype, self.dtype().nullability()),
279 primitive
280 .pvalue()
281 .map(|p| p.reinterpret_cast(ptype))
282 .map(ScalarValue::Primitive),
283 )
284 }
285
286 pub fn approx_nbytes(&self) -> usize {
291 use crate::dtype::NativeDecimalType;
292 use crate::dtype::i256;
293
294 match self.dtype() {
295 DType::Null => 0,
296 DType::Bool(_) => 1,
297 DType::Primitive(ptype, _) => ptype.byte_width(),
298 DType::Decimal(dt, _) => {
299 if dt.precision() <= i128::MAX_PRECISION {
300 size_of::<i128>()
301 } else {
302 size_of::<i256>()
303 }
304 }
305 DType::Utf8(_) => self
306 .value()
307 .map_or_else(|| 0, |value| value.as_utf8().len()),
308 DType::Binary(_) => self
309 .value()
310 .map_or_else(|| 0, |value| value.as_binary().len()),
311 DType::List(..) | DType::FixedSizeList(..) => self
312 .as_list()
313 .elements()
314 .map(|fields| fields.into_iter().map(|f| f.approx_nbytes()).sum::<usize>())
315 .unwrap_or_default(),
316 DType::Map(..) => self
317 .as_map()
318 .entries()
319 .map(|(key, value)| key.approx_nbytes() + value.approx_nbytes())
320 .sum(),
321 DType::Struct(..) => self
322 .as_struct()
323 .fields_iter()
324 .map(|fields| fields.into_iter().map(|f| f.approx_nbytes()).sum::<usize>())
325 .unwrap_or_default(),
326 DType::Union(..) => self
327 .as_union()
328 .child()
329 .map_or(0, |value| 1 + value.approx_nbytes()),
330 DType::Variant(_) => self.as_variant().value().map_or(0, Scalar::approx_nbytes),
331 DType::Extension(_) => self.as_extension().to_storage_scalar().approx_nbytes(),
332 }
333 }
334}
335
336impl Hash for Scalar {
340 fn hash<H: Hasher>(&self, state: &mut H) {
341 self.dtype.hash_ignore_nullability(state);
342 self.value.hash(state);
343 }
344}
345
346impl PartialEq for Scalar {
353 fn eq(&self, other: &Self) -> bool {
354 self.dtype.eq_ignore_nullability(&other.dtype) && self.value == other.value
355 }
356}
357
358impl PartialOrd for Scalar {
359 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
395 if !self.dtype().eq_ignore_nullability(other.dtype()) {
396 return None;
397 }
398
399 partial_cmp_scalar_values(self.dtype(), self.value(), other.value())
400 }
401}
402
403fn partial_cmp_scalar_values(
405 dtype: &DType,
406 lhs: Option<&ScalarValue>,
407 rhs: Option<&ScalarValue>,
408) -> Option<Ordering> {
409 match (lhs, rhs) {
410 (None, None) => Some(Ordering::Equal),
411 (None, Some(_)) => Some(Ordering::Less),
412 (Some(_), None) => Some(Ordering::Greater),
413 (Some(lhs), Some(rhs)) => partial_cmp_non_null_scalar_values(dtype, lhs, rhs),
414 }
415}
416
417fn partial_cmp_non_null_scalar_values(
419 dtype: &DType,
420 lhs: &ScalarValue,
421 rhs: &ScalarValue,
422) -> Option<Ordering> {
423 match (lhs, rhs) {
426 (ScalarValue::Bool(lhs), ScalarValue::Bool(rhs)) => lhs.partial_cmp(rhs),
427 (ScalarValue::Primitive(lhs), ScalarValue::Primitive(rhs)) => lhs.partial_cmp(rhs),
428 (ScalarValue::Decimal(lhs), ScalarValue::Decimal(rhs)) => lhs.partial_cmp(rhs),
429 (ScalarValue::Utf8(lhs), ScalarValue::Utf8(rhs)) => lhs.partial_cmp(rhs),
430 (ScalarValue::Binary(lhs), ScalarValue::Binary(rhs)) => lhs.partial_cmp(rhs),
431 (ScalarValue::Tuple(lhs), ScalarValue::Tuple(rhs)) => {
434 partial_cmp_tuple_values(dtype, lhs, rhs)
435 }
436 (ScalarValue::Union(lhs), ScalarValue::Union(rhs)) => {
437 if lhs.type_id() != rhs.type_id() {
438 return None;
439 }
440
441 let DType::Union(variants, _) = dtype else {
442 return None;
443 };
444 let child_index = variants.tag_to_child_index(lhs.type_id())?;
445 let child_dtype = variants.variant_by_index(child_index)?;
446
447 partial_cmp_scalar_values(&child_dtype, lhs.child_value(), rhs.child_value())
448 }
449 (ScalarValue::Variant(_), ScalarValue::Variant(_)) => None,
452 _ => None,
453 }
454}
455
456fn partial_cmp_tuple_values(
458 dtype: &DType,
459 lhs: &[Option<ScalarValue>],
460 rhs: &[Option<ScalarValue>],
461) -> Option<Ordering> {
462 match dtype {
463 DType::List(element_dtype, _) | DType::FixedSizeList(element_dtype, ..) => {
464 partial_cmp_list_values(element_dtype, lhs, rhs)
465 }
466 DType::Struct(fields, _) => partial_cmp_struct_values(fields, lhs, rhs),
467 DType::Map(map_dtype, _) => partial_cmp_list_values(&map_dtype.entries_dtype(), lhs, rhs),
469 DType::Extension(ext_dtype) => {
470 partial_cmp_tuple_values(ext_dtype.storage_dtype(), lhs, rhs)
471 }
472 _ => None,
473 }
474}
475
476fn partial_cmp_list_values(
478 element_dtype: &DType,
479 lhs: &[Option<ScalarValue>],
480 rhs: &[Option<ScalarValue>],
481) -> Option<Ordering> {
482 for (lhs, rhs) in lhs.iter().zip(rhs.iter()) {
483 match partial_cmp_scalar_values(element_dtype, lhs.as_ref(), rhs.as_ref())? {
484 Ordering::Equal => continue,
485 ordering => return Some(ordering),
486 }
487 }
488
489 Some(lhs.len().cmp(&rhs.len()))
490}
491
492fn partial_cmp_struct_values(
494 fields: &StructFields,
495 lhs: &[Option<ScalarValue>],
496 rhs: &[Option<ScalarValue>],
497) -> Option<Ordering> {
498 if lhs.len() != fields.nfields() || rhs.len() != fields.nfields() {
499 return None;
500 }
501
502 for ((field_dtype, lhs), rhs) in fields.field_dtypes().zip(lhs.iter()).zip(rhs.iter()) {
503 match partial_cmp_scalar_values(field_dtype, lhs.as_ref(), rhs.as_ref())? {
504 Ordering::Equal => continue,
505 ordering => return Some(ordering),
506 }
507 }
508
509 Some(Ordering::Equal)
510}
511
512#[cfg(test)]
513mod tests {
514 use std::cmp::Ordering;
515 use std::sync::Arc;
516
517 use rstest::rstest;
518 use vortex_error::VortexResult;
519
520 use crate::dtype::DType;
521 use crate::dtype::Nullability;
522 use crate::dtype::PType;
523 use crate::dtype::StructFields;
524 use crate::scalar::Scalar;
525
526 fn i32_scalar(value: i32) -> Scalar {
527 Scalar::primitive::<i32>(value, Nullability::NonNullable)
528 }
529
530 fn nullable_i32(value: Option<i32>) -> Scalar {
531 match value {
532 Some(value) => Scalar::primitive::<i32>(value, Nullability::Nullable),
533 None => Scalar::null(DType::Primitive(PType::I32, Nullability::Nullable)),
534 }
535 }
536
537 fn map_dtype(nullability: Nullability) -> VortexResult<DType> {
538 DType::map(
539 DType::Primitive(PType::I32, Nullability::NonNullable),
540 DType::Utf8(Nullability::Nullable),
541 false,
542 nullability,
543 )
544 }
545
546 fn map_scalar(entries: Vec<(i32, Option<&str>)>) -> VortexResult<Scalar> {
547 Scalar::try_map(
548 map_dtype(Nullability::Nullable)?,
549 entries.into_iter().map(|(key, value)| {
550 (
551 i32_scalar(key),
552 match value {
553 Some(value) => Scalar::utf8(value, Nullability::Nullable),
554 None => Scalar::null(DType::Utf8(Nullability::Nullable)),
555 },
556 )
557 }),
558 )
559 }
560
561 #[rstest]
563 #[case(vec![(1, Some("a"))], vec![(1, Some("a"))], Ordering::Equal)]
564 #[case(vec![(1, Some("a"))], vec![(1, Some("b"))], Ordering::Less)]
565 #[case(vec![(2, Some("a"))], vec![(1, Some("z"))], Ordering::Greater)]
566 #[case(vec![(1, Some("a"))], vec![(1, Some("a")), (2, Some("b"))], Ordering::Less)]
567 #[case(vec![], vec![], Ordering::Equal)]
568 #[case(vec![], vec![(1, Some("a"))], Ordering::Less)]
569 #[case(vec![(1, Some("a"))], vec![(1, None)], Ordering::Greater)]
570 fn map_ordering(
571 #[case] lhs: Vec<(i32, Option<&str>)>,
572 #[case] rhs: Vec<(i32, Option<&str>)>,
573 #[case] expected: Ordering,
574 ) -> VortexResult<()> {
575 assert_eq!(
576 map_scalar(lhs)?.partial_cmp(&map_scalar(rhs)?),
577 Some(expected)
578 );
579 Ok(())
580 }
581
582 #[test]
583 fn null_map_orders_before_every_non_null_map() -> VortexResult<()> {
584 let null = Scalar::null(map_dtype(Nullability::Nullable)?);
585
586 assert_eq!(null.partial_cmp(&map_scalar(vec![])?), Some(Ordering::Less));
587 assert_eq!(null.partial_cmp(&null), Some(Ordering::Equal));
588
589 Ok(())
590 }
591
592 fn ab_struct_dtype(nullability: Nullability) -> DType {
593 DType::Struct(
594 StructFields::new(
595 ["a", "b"].into(),
596 vec![
597 DType::Primitive(PType::I32, Nullability::NonNullable),
598 DType::Utf8(Nullability::NonNullable),
599 ],
600 ),
601 nullability,
602 )
603 }
604
605 #[rstest]
606 #[case(vec![0, 0], Some(true))]
608 #[case(vec![0], Some(true))]
609 #[case(vec![0, 5], Some(false))]
612 #[case(vec![5, 0], Some(false))]
613 #[case(vec![1, 2], Some(false))]
614 fn fixed_size_list_is_zero(#[case] values: Vec<i32>, #[case] expected: Option<bool>) {
615 let element_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
616 let children: Vec<Scalar> = values.into_iter().map(i32_scalar).collect();
617 let scalar = Scalar::fixed_size_list(element_dtype, children, Nullability::NonNullable);
618 assert_eq!(scalar.is_zero(), expected);
619 }
620
621 #[test]
622 fn null_fixed_size_list_is_zero_is_none() {
623 let element_dtype = Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable));
624 let scalar = Scalar::null(DType::FixedSizeList(
625 element_dtype,
626 2,
627 Nullability::Nullable,
628 ));
629 assert_eq!(scalar.is_zero(), None);
630 }
631
632 #[test]
633 fn fixed_size_list_with_null_element_is_not_zero() {
634 let element_dtype = DType::Primitive(PType::I32, Nullability::Nullable);
637 let children = vec![nullable_i32(Some(0)), nullable_i32(None)];
638 let scalar = Scalar::fixed_size_list(element_dtype, children, Nullability::NonNullable);
639 assert_eq!(scalar.is_zero(), Some(false));
640 }
641
642 #[test]
643 fn struct_with_all_zero_fields_is_zero() {
644 let scalar = Scalar::struct_(
645 ab_struct_dtype(Nullability::NonNullable),
646 vec![i32_scalar(0), Scalar::utf8("", Nullability::NonNullable)],
647 );
648 assert_eq!(scalar.is_zero(), Some(true));
649 }
650
651 #[rstest]
652 #[case(5, "")]
655 #[case(0, "x")]
656 #[case(7, "y")]
657 fn struct_with_non_zero_field_is_not_zero(#[case] a: i32, #[case] b: &str) {
658 let scalar = Scalar::struct_(
659 ab_struct_dtype(Nullability::NonNullable),
660 vec![i32_scalar(a), Scalar::utf8(b, Nullability::NonNullable)],
661 );
662 assert_eq!(scalar.is_zero(), Some(false));
663 }
664
665 #[test]
666 fn null_struct_is_zero_is_none() {
667 let scalar = Scalar::null(ab_struct_dtype(Nullability::Nullable));
668 assert_eq!(scalar.is_zero(), None);
669 }
670
671 #[test]
672 fn struct_with_null_field_is_not_zero() {
673 let dtype = DType::Struct(
676 StructFields::new(
677 ["a", "b"].into(),
678 vec![
679 DType::Primitive(PType::I32, Nullability::Nullable),
680 DType::Primitive(PType::I32, Nullability::Nullable),
681 ],
682 ),
683 Nullability::NonNullable,
684 );
685 let scalar = Scalar::struct_(dtype, vec![nullable_i32(Some(0)), nullable_i32(None)]);
686 assert_eq!(scalar.is_zero(), Some(false));
687 }
688
689 #[test]
690 fn nested_struct_of_fixed_size_list_recurses() {
691 let element_dtype = Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable));
694 let fsl_dtype =
695 DType::FixedSizeList(Arc::clone(&element_dtype), 2, Nullability::NonNullable);
696 let struct_dtype = DType::Struct(
697 StructFields::new(["fsl"].into(), vec![fsl_dtype]),
698 Nullability::NonNullable,
699 );
700
701 let all_zero = Scalar::struct_(
702 struct_dtype.clone(),
703 vec![Scalar::fixed_size_list(
704 Arc::clone(&element_dtype),
705 vec![i32_scalar(0), i32_scalar(0)],
706 Nullability::NonNullable,
707 )],
708 );
709 assert_eq!(all_zero.is_zero(), Some(true));
710
711 let with_non_zero = Scalar::struct_(
712 struct_dtype,
713 vec![Scalar::fixed_size_list(
714 element_dtype,
715 vec![i32_scalar(0), i32_scalar(9)],
716 Nullability::NonNullable,
717 )],
718 );
719 assert_eq!(with_non_zero.is_zero(), Some(false));
720 }
721}