1use num_traits::ToBytes;
7use num_traits::ToPrimitive;
8use prost::Message;
9use vortex_buffer::BufferString;
10use vortex_buffer::ByteBuffer;
11use vortex_error::VortexExpect;
12use vortex_error::VortexResult;
13use vortex_error::vortex_bail;
14use vortex_error::vortex_ensure;
15use vortex_error::vortex_ensure_eq;
16use vortex_error::vortex_err;
17use vortex_session::VortexSession;
18
19use crate::dtype::DType;
20use crate::dtype::PType;
21use crate::dtype::half::f16;
22use crate::dtype::i256;
23use crate::proto::scalar as pb;
24use crate::proto::scalar::ListValue;
25use crate::proto::scalar::UnionValue as PbUnionValue;
26use crate::proto::scalar::scalar_value::Kind;
27use crate::scalar::DecimalValue;
28use crate::scalar::PValue;
29use crate::scalar::Scalar;
30use crate::scalar::ScalarValue;
31use crate::scalar::UnionValue;
32
33impl From<&Scalar> for pb::Scalar {
38 fn from(value: &Scalar) -> Self {
39 pb::Scalar {
40 dtype: Some(
41 (value.dtype())
42 .try_into()
43 .vortex_expect("Failed to convert DType to proto"),
44 ),
45 value: Some(Box::new(ScalarValue::to_proto(value.value()))),
46 }
47 }
48}
49
50impl ScalarValue {
51 pub fn to_proto(this: Option<&Self>) -> pb::ScalarValue {
61 match this {
62 None => pb::ScalarValue {
63 kind: Some(Kind::NullValue(0)),
64 },
65 Some(this) => pb::ScalarValue::from(this),
66 }
67 }
68
69 pub fn to_proto_bytes<B: Default + bytes::BufMut>(value: Option<&ScalarValue>) -> B {
71 let proto = Self::to_proto(value);
72 let mut buf = B::default();
73 proto
74 .encode(&mut buf)
75 .vortex_expect("Failed to encode scalar value");
76 buf
77 }
78}
79
80impl From<&ScalarValue> for pb::ScalarValue {
81 fn from(value: &ScalarValue) -> Self {
82 match value {
83 ScalarValue::Bool(v) => pb::ScalarValue {
84 kind: Some(Kind::BoolValue(*v)),
85 },
86 ScalarValue::Primitive(v) => pb::ScalarValue::from(v),
87 ScalarValue::Decimal(v) => {
88 let inner_value = match v {
89 DecimalValue::I8(v) => v.to_le_bytes().to_vec(),
90 DecimalValue::I16(v) => v.to_le_bytes().to_vec(),
91 DecimalValue::I32(v) => v.to_le_bytes().to_vec(),
92 DecimalValue::I64(v) => v.to_le_bytes().to_vec(),
93 DecimalValue::I128(v128) => v128.to_le_bytes().to_vec(),
94 DecimalValue::I256(v256) => v256.to_le_bytes().to_vec(),
95 };
96
97 pb::ScalarValue {
98 kind: Some(Kind::BytesValue(inner_value)),
99 }
100 }
101 ScalarValue::Utf8(v) => pb::ScalarValue {
102 kind: Some(Kind::StringValue(v.to_string())),
103 },
104 ScalarValue::Binary(v) => pb::ScalarValue {
105 kind: Some(Kind::BytesValue(v.to_vec())),
106 },
107 ScalarValue::Tuple(v) => {
108 let mut values = Vec::with_capacity(v.len());
109 for elem in v.iter() {
110 values.push(ScalarValue::to_proto(elem.as_ref()));
111 }
112 pb::ScalarValue {
113 kind: Some(Kind::ListValue(ListValue { values })),
114 }
115 }
116 ScalarValue::Union(v) => pb::ScalarValue {
117 kind: Some(Kind::UnionValue(Box::new(PbUnionValue {
118 type_id: u32::from(v.type_id()),
119 value: Some(Box::new(ScalarValue::to_proto(v.child_value()))),
120 }))),
121 },
122 ScalarValue::Variant(v) => pb::ScalarValue {
123 kind: Some(Kind::VariantValue(Box::new(pb::Scalar::from(v.as_ref())))),
124 },
125 }
126 }
127}
128
129impl From<&PValue> for pb::ScalarValue {
130 fn from(value: &PValue) -> Self {
131 match value {
132 PValue::I8(v) => pb::ScalarValue {
133 kind: Some(Kind::Int64Value(*v as i64)),
134 },
135 PValue::I16(v) => pb::ScalarValue {
136 kind: Some(Kind::Int64Value(*v as i64)),
137 },
138 PValue::I32(v) => pb::ScalarValue {
139 kind: Some(Kind::Int64Value(*v as i64)),
140 },
141 PValue::I64(v) => pb::ScalarValue {
142 kind: Some(Kind::Int64Value(*v)),
143 },
144 PValue::U8(v) => pb::ScalarValue {
145 kind: Some(Kind::Uint64Value(*v as u64)),
146 },
147 PValue::U16(v) => pb::ScalarValue {
148 kind: Some(Kind::Uint64Value(*v as u64)),
149 },
150 PValue::U32(v) => pb::ScalarValue {
151 kind: Some(Kind::Uint64Value(*v as u64)),
152 },
153 PValue::U64(v) => pb::ScalarValue {
154 kind: Some(Kind::Uint64Value(*v)),
155 },
156 PValue::F16(v) => pb::ScalarValue {
157 kind: Some(Kind::F16Value(v.to_bits() as u64)),
158 },
159 PValue::F32(v) => pb::ScalarValue {
160 kind: Some(Kind::F32Value(*v)),
161 },
162 PValue::F64(v) => pb::ScalarValue {
163 kind: Some(Kind::F64Value(*v)),
164 },
165 }
166 }
167}
168
169impl Scalar {
174 pub fn from_proto_value(
183 value: &pb::ScalarValue,
184 dtype: &DType,
185 session: &VortexSession,
186 ) -> VortexResult<Self> {
187 let scalar_value = ScalarValue::from_proto(value, dtype, session)?;
188
189 Scalar::try_new(dtype.clone(), scalar_value)
190 }
191
192 pub fn from_proto(value: &pb::Scalar, session: &VortexSession) -> VortexResult<Self> {
198 let dtype = DType::from_proto(
199 value
200 .dtype
201 .as_ref()
202 .ok_or_else(|| vortex_err!(Serde: "Scalar missing dtype"))?,
203 session,
204 )?;
205
206 let pb_scalar_value: &pb::ScalarValue = value
207 .value
208 .as_ref()
209 .ok_or_else(|| vortex_err!(Serde: "Scalar missing value"))?;
210
211 let value: Option<ScalarValue> = ScalarValue::from_proto(pb_scalar_value, &dtype, session)?;
212
213 Scalar::try_new(dtype, value)
214 }
215}
216
217impl ScalarValue {
218 pub fn from_proto_bytes(
227 bytes: &[u8],
228 dtype: &DType,
229 session: &VortexSession,
230 ) -> VortexResult<Option<Self>> {
231 let proto = pb::ScalarValue::decode(bytes)?;
232 Self::from_proto(&proto, dtype, session)
233 }
234
235 pub fn from_proto(
244 value: &pb::ScalarValue,
245 dtype: &DType,
246 session: &VortexSession,
247 ) -> VortexResult<Option<Self>> {
248 let kind = value
249 .kind
250 .as_ref()
251 .ok_or_else(|| vortex_err!(Serde: "Scalar value missing kind"))?;
252
253 let dtype = match dtype {
255 DType::Extension(ext) => ext.storage_dtype(),
256 _ => dtype,
257 };
258
259 Ok(match kind {
260 Kind::NullValue(_) => None,
261 Kind::BoolValue(v) => Some(bool_from_proto(*v, dtype)?),
262 Kind::Int64Value(v) => Some(int64_from_proto(*v, dtype)?),
263 Kind::Uint64Value(v) => Some(uint64_from_proto(*v, dtype)?),
264 Kind::F16Value(v) => Some(f16_from_proto(*v, dtype)?),
265 Kind::F32Value(v) => Some(f32_from_proto(*v, dtype)?),
266 Kind::F64Value(v) => Some(f64_from_proto(*v, dtype)?),
267 Kind::StringValue(s) => Some(string_from_proto(s, dtype)?),
268 Kind::BytesValue(b) => Some(bytes_from_proto(b, dtype)?),
269 Kind::ListValue(v) => Some(list_from_proto(v, dtype, session)?),
270 Kind::UnionValue(v) => Some(union_from_proto(v, dtype, session)?),
271 Kind::VariantValue(v) => match dtype {
272 DType::Variant(_) => Some(ScalarValue::Variant(Box::new(Scalar::from_proto(
273 v, session,
274 )?))),
275 _ => vortex_bail!(Serde: "expected non-Variant scalar proto for dtype {dtype}"),
276 },
277 })
278 }
279}
280
281fn bool_from_proto(v: bool, dtype: &DType) -> VortexResult<ScalarValue> {
283 vortex_ensure!(
284 dtype.is_boolean(),
285 Serde: "expected Bool dtype for BoolValue, got {dtype}"
286 );
287
288 Ok(ScalarValue::Bool(v))
289}
290
291fn int64_from_proto(v: i64, dtype: &DType) -> VortexResult<ScalarValue> {
296 vortex_ensure!(
297 dtype.is_primitive(),
298 Serde: "expected Primitive dtype for Int64Value, got {dtype}"
299 );
300
301 let pvalue = match dtype.as_ptype() {
302 PType::I8 => v.to_i8().map(PValue::I8),
303 PType::I16 => v.to_i16().map(PValue::I16),
304 PType::I32 => v.to_i32().map(PValue::I32),
305 PType::I64 => Some(PValue::I64(v)),
306 PType::U8 => v.to_u8().map(PValue::U8),
309 PType::U16 => v.to_u16().map(PValue::U16),
310 PType::U32 => v.to_u32().map(PValue::U32),
311 PType::U64 => v.to_u64().map(PValue::U64),
312 ftype @ (PType::F16 | PType::F32 | PType::F64) => vortex_bail!(
313 Serde: "expected signed integer ptype for serialized Int64Value, got float {ftype}"
314 ),
315 }
316 .ok_or_else(|| vortex_err!(Serde: "Int64 value {v} out of range for dtype {dtype}"))?;
317
318 Ok(ScalarValue::Primitive(pvalue))
319}
320
321fn uint64_from_proto(v: u64, dtype: &DType) -> VortexResult<ScalarValue> {
327 vortex_ensure!(
328 dtype.is_primitive(),
329 Serde: "expected Primitive dtype for Uint64Value, got {dtype}"
330 );
331
332 let pvalue = match dtype.as_ptype() {
333 PType::U8 => v.to_u8().map(PValue::U8),
334 PType::U16 => v.to_u16().map(PValue::U16),
335 PType::U32 => v.to_u32().map(PValue::U32),
336 PType::U64 => Some(PValue::U64(v)),
337 PType::I8 => v.to_i8().map(PValue::I8),
340 PType::I16 => v.to_i16().map(PValue::I16),
341 PType::I32 => v.to_i32().map(PValue::I32),
342 PType::I64 => v.to_i64().map(PValue::I64),
343 PType::F16 => v.to_u16().map(f16::from_bits).map(PValue::F16),
345 ftype @ (PType::F32 | PType::F64) => vortex_bail!(
346 Serde: "expected unsigned integer ptype for serialized Uint64Value, got {ftype}"
347 ),
348 }
349 .ok_or_else(|| vortex_err!(Serde: "Uint64 value {v} out of range for dtype {dtype}"))?;
350
351 Ok(ScalarValue::Primitive(pvalue))
352}
353
354fn f16_from_proto(v: u64, dtype: &DType) -> VortexResult<ScalarValue> {
356 vortex_ensure!(
357 matches!(dtype, DType::Primitive(PType::F16, _)),
358 Serde: "expected F16 dtype for F16Value, got {dtype}"
359 );
360
361 let bits = u16::try_from(v)
362 .map_err(|_| vortex_err!(Serde: "f16 bitwise representation has more than 16 bits: {v}"))?;
363
364 Ok(ScalarValue::Primitive(PValue::F16(f16::from_bits(bits))))
365}
366
367fn f32_from_proto(v: f32, dtype: &DType) -> VortexResult<ScalarValue> {
369 vortex_ensure!(
370 matches!(dtype, DType::Primitive(PType::F32, _)),
371 Serde: "expected F32 dtype for F32Value, got {dtype}"
372 );
373
374 Ok(ScalarValue::Primitive(PValue::F32(v)))
375}
376
377fn f64_from_proto(v: f64, dtype: &DType) -> VortexResult<ScalarValue> {
379 vortex_ensure!(
380 matches!(dtype, DType::Primitive(PType::F64, _)),
381 Serde: "expected F64 dtype for F64Value, got {dtype}"
382 );
383
384 Ok(ScalarValue::Primitive(PValue::F64(v)))
385}
386
387fn string_from_proto(s: &str, dtype: &DType) -> VortexResult<ScalarValue> {
390 match dtype {
391 DType::Utf8(_) => Ok(ScalarValue::Utf8(BufferString::from(s))),
392 DType::Binary(_) => Ok(ScalarValue::Binary(ByteBuffer::copy_from(s.as_bytes()))),
393 _ => vortex_bail!(
394 Serde: "expected Utf8 or Binary dtype for StringValue, got {dtype}"
395 ),
396 }
397}
398
399fn bytes_from_proto(bytes: &[u8], dtype: &DType) -> VortexResult<ScalarValue> {
404 match dtype {
405 DType::Utf8(_) => Ok(ScalarValue::Utf8(BufferString::try_from(bytes)?)),
406 DType::Binary(_) => Ok(ScalarValue::Binary(ByteBuffer::copy_from(bytes))),
407 DType::Decimal(..) => Ok(ScalarValue::Decimal(match bytes.len() {
409 1 => DecimalValue::I8(bytes[0] as i8),
410 2 => DecimalValue::I16(i16::from_le_bytes(
411 bytes
412 .try_into()
413 .ok()
414 .vortex_expect("Buffer has invalid number of bytes"),
415 )),
416 4 => DecimalValue::I32(i32::from_le_bytes(
417 bytes
418 .try_into()
419 .ok()
420 .vortex_expect("Buffer has invalid number of bytes"),
421 )),
422 8 => DecimalValue::I64(i64::from_le_bytes(
423 bytes
424 .try_into()
425 .ok()
426 .vortex_expect("Buffer has invalid number of bytes"),
427 )),
428 16 => DecimalValue::I128(i128::from_le_bytes(
429 bytes
430 .try_into()
431 .ok()
432 .vortex_expect("Buffer has invalid number of bytes"),
433 )),
434 32 => DecimalValue::I256(i256::from_le_bytes(
435 bytes
436 .try_into()
437 .ok()
438 .vortex_expect("Buffer has invalid number of bytes"),
439 )),
440 l => vortex_bail!(Serde: "invalid decimal byte length: {l}"),
441 })),
442 _ => vortex_bail!(
443 Serde: "expected Utf8, Binary, or Decimal dtype for BytesValue, got {dtype}"
444 ),
445 }
446}
447
448fn list_from_proto(
450 v: &ListValue,
451 dtype: &DType,
452 session: &VortexSession,
453) -> VortexResult<ScalarValue> {
454 let values = match dtype {
455 DType::List(element_dtype, _) | DType::FixedSizeList(element_dtype, ..) => v
456 .values
457 .iter()
458 .map(|elem| ScalarValue::from_proto(elem, element_dtype.as_ref(), session))
459 .collect::<VortexResult<Vec<_>>>()?,
460 DType::Struct(fields, _) => {
461 vortex_ensure_eq!(
462 v.values.len(),
463 fields.nfields(),
464 Serde: "ListValue has the wrong number of struct fields"
465 );
466
467 v.values
468 .iter()
469 .zip(fields.fields())
470 .map(|(value, field_dtype)| ScalarValue::from_proto(value, &field_dtype, session))
471 .collect::<VortexResult<Vec<_>>>()?
472 }
473 DType::Map(map, _) => {
474 let entry_dtype = map.entries_dtype();
475 v.values
476 .iter()
477 .map(|entry| ScalarValue::from_proto(entry, &entry_dtype, session))
478 .collect::<VortexResult<Vec<_>>>()?
479 }
480 _ => vortex_bail!(
481 Serde: "expected a tuple-backed dtype for ListValue, got {dtype}"
482 ),
483 };
484 Ok(ScalarValue::Tuple(values))
485}
486
487fn union_from_proto(
489 value: &PbUnionValue,
490 dtype: &DType,
491 session: &VortexSession,
492) -> VortexResult<ScalarValue> {
493 let DType::Union(variants, _) = dtype else {
494 vortex_bail!(Serde: "expected Union dtype for UnionValue, got {dtype}");
495 };
496
497 let type_id = u8::try_from(value.type_id).map_err(
498 |_| vortex_err!(Serde: "union type ID {} is outside the u8 range", value.type_id),
499 )?;
500
501 let child_index = variants.tag_to_child_index(type_id).ok_or_else(|| {
502 vortex_err!(
503 Serde: "union type ID {type_id} is not present in {:?}",
504 variants.type_ids()
505 )
506 })?;
507
508 let child_dtype = variants
509 .variant_by_index(child_index)
510 .ok_or_else(|| vortex_err!(Serde: "union type ID {type_id} resolved out of bounds"))?;
511
512 let child_proto = value
513 .value
514 .as_deref()
515 .ok_or_else(|| vortex_err!(Serde: "UnionValue missing child value"))?;
516
517 let child_value = ScalarValue::from_proto(child_proto, &child_dtype, session)?;
518 Scalar::validate(&child_dtype, child_value.as_ref()).map_err(|error| {
519 vortex_err!(
520 Serde: "union type ID {type_id} has invalid child for dtype {child_dtype}: {error}"
521 )
522 })?;
523
524 Ok(ScalarValue::Union(UnionValue::new(type_id, child_value)))
525}
526
527#[cfg(test)]
528mod tests {
529 use std::f32;
530 use std::f64;
531 use std::sync::Arc;
532
533 use vortex_buffer::BufferString;
534 use vortex_error::VortexError;
535 use vortex_error::vortex_panic;
536 use vortex_session::VortexSession;
537
538 use super::*;
539 use crate::dtype::DType;
540 use crate::dtype::DecimalDType;
541 use crate::dtype::Nullability;
542 use crate::dtype::PType;
543 use crate::dtype::UnionVariants;
544 use crate::dtype::half::f16;
545 use crate::proto::scalar as pb;
546 use crate::scalar::DecimalValue;
547 use crate::scalar::Scalar;
548 use crate::scalar::ScalarValue;
549
550 fn session() -> VortexSession {
551 VortexSession::empty()
552 }
553
554 fn round_trip(scalar: Scalar) {
555 let decoded = Scalar::from_proto(&pb::Scalar::from(&scalar), &session()).unwrap();
556 assert_eq!(decoded.dtype(), scalar.dtype());
557 assert_eq!(decoded, scalar);
558 }
559
560 #[test]
561 fn test_null() {
562 round_trip(Scalar::null(DType::Null));
563 }
564
565 #[test]
566 fn test_bool() {
567 round_trip(Scalar::new(
568 DType::Bool(Nullability::Nullable),
569 Some(ScalarValue::Bool(true)),
570 ));
571 }
572
573 #[test]
574 fn test_primitive() {
575 round_trip(Scalar::new(
576 DType::Primitive(PType::I32, Nullability::Nullable),
577 Some(ScalarValue::Primitive(42i32.into())),
578 ));
579 }
580
581 #[test]
582 fn test_buffer() {
583 round_trip(Scalar::new(
584 DType::Binary(Nullability::Nullable),
585 Some(ScalarValue::Binary(vec![1, 2, 3].into())),
586 ));
587 }
588
589 #[test]
590 fn test_buffer_string() {
591 round_trip(Scalar::new(
592 DType::Utf8(Nullability::Nullable),
593 Some(ScalarValue::Utf8(BufferString::from("hello".to_string()))),
594 ));
595 }
596
597 #[test]
598 fn test_list() {
599 round_trip(Scalar::new(
600 DType::List(
601 Arc::new(DType::Primitive(PType::I32, Nullability::Nullable)),
602 Nullability::Nullable,
603 ),
604 Some(ScalarValue::Tuple(vec![
605 Some(ScalarValue::Primitive(42i32.into())),
606 Some(ScalarValue::Primitive(43i32.into())),
607 ])),
608 ));
609 }
610
611 #[test]
612 fn test_map() {
613 let dtype = DType::map(
614 DType::Primitive(PType::I32, Nullability::NonNullable),
615 DType::Utf8(Nullability::Nullable),
616 true,
617 Nullability::Nullable,
618 )
619 .unwrap();
620 round_trip(
621 Scalar::try_map(
622 dtype.clone(),
623 [
624 (
625 Scalar::primitive(1i32, Nullability::NonNullable),
626 Scalar::utf8("one", Nullability::Nullable),
627 ),
628 (
629 Scalar::primitive(2i32, Nullability::NonNullable),
630 Scalar::null(DType::Utf8(Nullability::Nullable)),
631 ),
632 ],
633 )
634 .unwrap(),
635 );
636 round_trip(Scalar::null(dtype));
637 }
638
639 #[test]
640 fn test_f16() {
641 round_trip(Scalar::primitive(
642 f16::from_f32(0.42),
643 Nullability::Nullable,
644 ));
645 }
646
647 #[test]
648 fn test_i8() {
649 round_trip(Scalar::new(
650 DType::Primitive(PType::I8, Nullability::Nullable),
651 Some(ScalarValue::Primitive(i8::MIN.into())),
652 ));
653
654 round_trip(Scalar::new(
655 DType::Primitive(PType::I8, Nullability::Nullable),
656 Some(ScalarValue::Primitive(0i8.into())),
657 ));
658
659 round_trip(Scalar::new(
660 DType::Primitive(PType::I8, Nullability::Nullable),
661 Some(ScalarValue::Primitive(i8::MAX.into())),
662 ));
663 }
664
665 #[test]
666 fn test_decimal_i32_roundtrip() {
667 round_trip(Scalar::decimal(
669 DecimalValue::I32(123_456),
670 DecimalDType::new(10, 2),
671 Nullability::NonNullable,
672 ));
673 }
674
675 #[test]
676 fn test_decimal_i128_roundtrip() {
677 round_trip(Scalar::decimal(
679 DecimalValue::I128(99_999_999_999_999_999_999),
680 DecimalDType::new(38, 6),
681 Nullability::Nullable,
682 ));
683 }
684
685 #[test]
686 fn test_decimal_null_roundtrip() {
687 round_trip(Scalar::null(DType::Decimal(
688 DecimalDType::new(10, 2),
689 Nullability::Nullable,
690 )));
691 }
692
693 #[test]
694 fn test_scalar_value_serde_roundtrip_binary() {
695 round_trip(Scalar::binary(
696 ByteBuffer::copy_from(b"hello"),
697 Nullability::NonNullable,
698 ));
699 }
700
701 #[test]
702 fn test_scalar_value_serde_roundtrip_utf8() {
703 round_trip(Scalar::utf8("hello", Nullability::NonNullable));
704 }
705
706 #[test]
707 fn test_variant_scalar_roundtrip() {
708 let nums = Scalar::list(
709 Arc::new(DType::Variant(Nullability::NonNullable)),
710 vec![
711 Scalar::variant(Scalar::primitive(-7_i16, Nullability::NonNullable)),
712 Scalar::variant(Scalar::primitive(42_u32, Nullability::NonNullable)),
713 Scalar::variant(Scalar::decimal(
714 DecimalValue::I128(123_456_789),
715 DecimalDType::new(18, 0),
716 Nullability::NonNullable,
717 )),
718 ],
719 Nullability::NonNullable,
720 );
721
722 let nested = Scalar::list(
723 Arc::new(DType::Variant(Nullability::NonNullable)),
724 vec![
725 Scalar::variant(Scalar::from(true)),
726 Scalar::variant(nums),
727 Scalar::variant(Scalar::binary(
728 ByteBuffer::copy_from(b"abc"),
729 Nullability::NonNullable,
730 )),
731 Scalar::variant(Scalar::null(DType::Null)),
732 ],
733 Nullability::NonNullable,
734 );
735
736 round_trip(Scalar::variant(nested));
737 }
738
739 #[test]
740 fn test_variant_scalar_proto_preserves_scalar_null_vs_variant_null() {
741 let scalar_null = Scalar::null(DType::Variant(Nullability::Nullable));
742 let variant_null = Scalar::variant(Scalar::null(DType::Null));
743
744 let scalar_null_pb = pb::Scalar::from(&scalar_null);
745 let variant_null_pb = pb::Scalar::from(&variant_null);
746
747 assert_ne!(scalar_null_pb, variant_null_pb);
748 assert_eq!(
749 Scalar::from_proto(&scalar_null_pb, &session()).unwrap(),
750 scalar_null,
751 );
752 assert_eq!(
753 Scalar::from_proto(&variant_null_pb, &session()).unwrap(),
754 variant_null,
755 );
756 }
757
758 #[test]
759 fn test_union_scalar_roundtrip() -> VortexResult<()> {
760 let variants = UnionVariants::try_new(
761 ["int", "string"].into(),
762 vec![
763 DType::Primitive(PType::I32, Nullability::Nullable),
764 DType::Utf8(Nullability::NonNullable),
765 ],
766 vec![5, 9],
767 )?;
768
769 round_trip(Scalar::union(
770 variants.clone(),
771 5,
772 Scalar::primitive(42_i32, Nullability::Nullable),
773 Nullability::Nullable,
774 )?);
775
776 let inner_null = Scalar::union(
777 variants.clone(),
778 5,
779 Scalar::null(DType::Primitive(PType::I32, Nullability::Nullable)),
780 Nullability::Nullable,
781 )?;
782 let inner_null_proto = pb::Scalar::from(&inner_null);
783
784 assert!(matches!(
785 inner_null_proto
786 .value
787 .as_deref()
788 .and_then(|value| value.kind.as_ref()),
789 Some(Kind::UnionValue(union_value))
790 if matches!(
791 union_value
792 .value
793 .as_deref()
794 .and_then(|value| value.kind.as_ref()),
795 Some(Kind::NullValue(_))
796 )
797 ));
798 let outer_null = Scalar::null(DType::Union(variants, Nullability::Nullable));
799 let outer_null_proto = pb::Scalar::from(&outer_null);
800 assert!(matches!(
801 outer_null_proto
802 .value
803 .as_deref()
804 .and_then(|value| value.kind.as_ref()),
805 Some(Kind::NullValue(_))
806 ));
807
808 assert_ne!(inner_null_proto, outer_null_proto);
809 round_trip(inner_null);
810 round_trip(outer_null);
811
812 let struct_dtype = DType::struct_(
813 [
814 (
815 "number",
816 DType::Primitive(PType::I32, Nullability::NonNullable),
817 ),
818 ("label", DType::Utf8(Nullability::Nullable)),
819 ],
820 Nullability::NonNullable,
821 );
822 let struct_scalar = Scalar::struct_(
823 struct_dtype.clone(),
824 [
825 Scalar::primitive(42_i32, Nullability::NonNullable),
826 Scalar::utf8("answer", Nullability::Nullable),
827 ],
828 );
829 let struct_variants =
830 UnionVariants::try_new(["record"].into(), vec![struct_dtype], vec![13])?;
831 round_trip(Scalar::union(
832 struct_variants,
833 13,
834 struct_scalar,
835 Nullability::NonNullable,
836 )?);
837
838 Ok(())
839 }
840
841 #[test]
842 fn test_union_proto_rejects_malformed_values() -> VortexResult<()> {
843 let variants = UnionVariants::try_new(
844 ["int"].into(),
845 vec![DType::Primitive(PType::I32, Nullability::Nullable)],
846 vec![5],
847 )?;
848 let dtype = DType::Union(variants, Nullability::NonNullable);
849
850 let unknown_tag = pb::ScalarValue {
851 kind: Some(Kind::UnionValue(Box::new(PbUnionValue {
852 type_id: 7,
853 value: Some(Box::new(ScalarValue::to_proto(
854 Scalar::primitive(42_i32, Nullability::Nullable).value(),
855 ))),
856 }))),
857 };
858
859 assert!(ScalarValue::from_proto(&unknown_tag, &dtype, &session()).is_err());
860
861 let missing_child = pb::ScalarValue {
862 kind: Some(Kind::UnionValue(Box::new(PbUnionValue {
863 type_id: 5,
864 value: None,
865 }))),
866 };
867
868 assert!(matches!(
869 ScalarValue::from_proto(&missing_child, &dtype, &session()),
870 Err(VortexError::Serde(..))
871 ));
872
873 let wrong_child_value = pb::ScalarValue {
874 kind: Some(Kind::UnionValue(Box::new(PbUnionValue {
875 type_id: 5,
876 value: Some(Box::new(ScalarValue::to_proto(
877 Scalar::utf8("wrong", Nullability::NonNullable).value(),
878 ))),
879 }))),
880 };
881
882 assert!(matches!(
883 ScalarValue::from_proto(&wrong_child_value, &dtype, &session()),
884 Err(VortexError::Serde(..))
885 ));
886
887 Ok(())
888 }
889
890 #[test]
891 fn test_backcompat_f16_serialized_as_u64() {
892 let f16_value = f16::from_f32(0.42);
908 let f16_bits_as_u64 = f16_value.to_bits() as u64; let pb_scalar_value = pb::ScalarValue {
911 kind: Some(Kind::Uint64Value(f16_bits_as_u64)),
912 };
913
914 let scalar_value = ScalarValue::from_proto(
916 &pb_scalar_value,
917 &DType::Primitive(PType::U64, Nullability::NonNullable),
918 &session(),
919 )
920 .unwrap();
921 assert_eq!(
922 scalar_value.as_ref().map(|v| v.as_primitive()),
923 Some(&PValue::U64(14008u64)),
924 );
925
926 let scalar_value_f16 = ScalarValue::from_proto(
928 &pb_scalar_value,
929 &DType::Primitive(PType::F16, Nullability::Nullable),
930 &session(),
931 )
932 .unwrap();
933
934 let scalar = Scalar::new(
935 DType::Primitive(PType::F16, Nullability::Nullable),
936 scalar_value_f16,
937 );
938
939 assert_eq!(
940 scalar.as_primitive().pvalue().unwrap(),
941 PValue::F16(f16::from_f32(0.42)),
942 "Uint64Value should be correctly interpreted as f16 when dtype is F16"
943 );
944 }
945
946 #[test]
947 fn test_scalar_value_direct_roundtrip_f16() {
948 let f16_values = vec![
950 f16::from_f32(0.0),
951 f16::from_f32(1.0),
952 f16::from_f32(-1.0),
953 f16::from_f32(0.42),
954 f16::from_f32(5.722046e-6),
955 f16::from_f32(f32::consts::PI),
956 f16::INFINITY,
957 f16::NEG_INFINITY,
958 f16::NAN,
959 ];
960
961 for f16_val in f16_values {
962 let scalar_value = ScalarValue::Primitive(PValue::F16(f16_val));
963 let pb_value = ScalarValue::to_proto(Some(&scalar_value));
964 let read_back = ScalarValue::from_proto(
965 &pb_value,
966 &DType::Primitive(PType::F16, Nullability::NonNullable),
967 &session(),
968 )
969 .unwrap();
970
971 match (&scalar_value, read_back.as_ref()) {
972 (
973 ScalarValue::Primitive(PValue::F16(original)),
974 Some(ScalarValue::Primitive(PValue::F16(roundtripped))),
975 ) => {
976 if original.is_nan() && roundtripped.is_nan() {
977 continue;
979 }
980 assert_eq!(
981 original, roundtripped,
982 "F16 value {original:?} did not roundtrip correctly"
983 );
984 }
985 _ => {
986 vortex_panic!(
987 "Expected f16 primitive values, got {scalar_value:?} and {read_back:?}"
988 )
989 }
990 }
991 }
992 }
993
994 #[test]
995 fn test_scalar_value_direct_roundtrip_preserves_values() {
996 let exact_roundtrip_cases: Vec<(&str, Option<ScalarValue>, DType)> = vec![
1001 ("null", None, DType::Null),
1002 (
1003 "bool_true",
1004 Some(ScalarValue::Bool(true)),
1005 DType::Bool(Nullability::Nullable),
1006 ),
1007 (
1008 "bool_false",
1009 Some(ScalarValue::Bool(false)),
1010 DType::Bool(Nullability::Nullable),
1011 ),
1012 (
1013 "u64",
1014 Some(ScalarValue::Primitive(PValue::U64(18446744073709551615))),
1015 DType::Primitive(PType::U64, Nullability::Nullable),
1016 ),
1017 (
1018 "i64",
1019 Some(ScalarValue::Primitive(PValue::I64(-9223372036854775808))),
1020 DType::Primitive(PType::I64, Nullability::Nullable),
1021 ),
1022 (
1023 "f32",
1024 Some(ScalarValue::Primitive(PValue::F32(f32::consts::E))),
1025 DType::Primitive(PType::F32, Nullability::Nullable),
1026 ),
1027 (
1028 "f64",
1029 Some(ScalarValue::Primitive(PValue::F64(f64::consts::PI))),
1030 DType::Primitive(PType::F64, Nullability::Nullable),
1031 ),
1032 (
1033 "string",
1034 Some(ScalarValue::Utf8(BufferString::from("test"))),
1035 DType::Utf8(Nullability::Nullable),
1036 ),
1037 (
1038 "bytes",
1039 Some(ScalarValue::Binary(vec![1, 2, 3, 4, 5].into())),
1040 DType::Binary(Nullability::Nullable),
1041 ),
1042 ];
1043
1044 for (name, value, dtype) in exact_roundtrip_cases {
1045 let pb_value = ScalarValue::to_proto(value.as_ref());
1046 let read_back = ScalarValue::from_proto(&pb_value, &dtype, &session()).unwrap();
1047
1048 let original_debug = format!("{value:?}");
1049 let roundtrip_debug = format!("{read_back:?}");
1050 assert_eq!(
1051 original_debug, roundtrip_debug,
1052 "ScalarValue {name} did not roundtrip exactly"
1053 );
1054 }
1055
1056 let unsigned_cases = vec![
1058 (
1059 "u8",
1060 ScalarValue::Primitive(PValue::U8(255)),
1061 DType::Primitive(PType::U8, Nullability::Nullable),
1062 255u64,
1063 ),
1064 (
1065 "u16",
1066 ScalarValue::Primitive(PValue::U16(65535)),
1067 DType::Primitive(PType::U16, Nullability::Nullable),
1068 65535u64,
1069 ),
1070 (
1071 "u32",
1072 ScalarValue::Primitive(PValue::U32(4294967295)),
1073 DType::Primitive(PType::U32, Nullability::Nullable),
1074 4294967295u64,
1075 ),
1076 ];
1077
1078 for (name, value, dtype, expected) in unsigned_cases {
1079 let pb_value = ScalarValue::to_proto(Some(&value));
1080 let read_back = ScalarValue::from_proto(&pb_value, &dtype, &session()).unwrap();
1081
1082 match read_back.as_ref() {
1083 Some(ScalarValue::Primitive(pv)) => {
1084 assert_eq!(pv.ptype(), dtype.as_ptype(), "{name} primitive width");
1085 let v = match pv {
1086 PValue::U8(v) => *v as u64,
1087 PValue::U16(v) => *v as u64,
1088 PValue::U32(v) => *v as u64,
1089 PValue::U64(v) => *v,
1090 _ => vortex_panic!("Unexpected primitive type for {name}: {pv:?}"),
1091 };
1092 assert_eq!(
1093 v, expected,
1094 "ScalarValue {name} value not preserved: expected {expected}, got {v}"
1095 );
1096 }
1097 _ => vortex_panic!("Unexpected type after roundtrip for {name}: {read_back:?}"),
1098 }
1099 }
1100
1101 let signed_cases = vec![
1103 (
1104 "i8",
1105 ScalarValue::Primitive(PValue::I8(-128)),
1106 DType::Primitive(PType::I8, Nullability::Nullable),
1107 -128i64,
1108 ),
1109 (
1110 "i16",
1111 ScalarValue::Primitive(PValue::I16(-32768)),
1112 DType::Primitive(PType::I16, Nullability::Nullable),
1113 -32768i64,
1114 ),
1115 (
1116 "i32",
1117 ScalarValue::Primitive(PValue::I32(-2147483648)),
1118 DType::Primitive(PType::I32, Nullability::Nullable),
1119 -2147483648i64,
1120 ),
1121 ];
1122
1123 for (name, value, dtype, expected) in signed_cases {
1124 let pb_value = ScalarValue::to_proto(Some(&value));
1125 let read_back = ScalarValue::from_proto(&pb_value, &dtype, &session()).unwrap();
1126
1127 match read_back.as_ref() {
1128 Some(ScalarValue::Primitive(pv)) => {
1129 assert_eq!(pv.ptype(), dtype.as_ptype(), "{name} primitive width");
1130 let v = match pv {
1131 PValue::I8(v) => *v as i64,
1132 PValue::I16(v) => *v as i64,
1133 PValue::I32(v) => *v as i64,
1134 PValue::I64(v) => *v,
1135 _ => vortex_panic!("Unexpected primitive type for {name}: {pv:?}"),
1136 };
1137 assert_eq!(
1138 v, expected,
1139 "ScalarValue {name} value not preserved: expected {expected}, got {v}"
1140 );
1141 }
1142 _ => vortex_panic!("Unexpected type after roundtrip for {name}: {read_back:?}"),
1143 }
1144 }
1145 }
1146
1147 #[test]
1150 fn test_backcompat_signed_integer_deserialized_as_unsigned() {
1151 let v = ScalarValue::Primitive(PValue::I64(0));
1152 assert_eq!(
1153 Scalar::from_proto_value(
1154 &pb::ScalarValue::from(&v),
1155 &DType::Primitive(PType::U64, Nullability::Nullable),
1156 &session()
1157 )
1158 .unwrap(),
1159 Scalar::primitive(0u64, Nullability::Nullable)
1160 );
1161 }
1162
1163 #[test]
1166 fn test_backcompat_unsigned_integer_deserialized_as_signed() {
1167 let v = ScalarValue::Primitive(PValue::U64(0));
1168 assert_eq!(
1169 Scalar::from_proto_value(
1170 &pb::ScalarValue::from(&v),
1171 &DType::Primitive(PType::I64, Nullability::Nullable),
1172 &session()
1173 )
1174 .unwrap(),
1175 Scalar::primitive(0i64, Nullability::Nullable)
1176 );
1177 }
1178}