1use num_traits::ToBytes;
7use num_traits::ToPrimitive;
8use prost::Message;
9use vortex_buffer::BufferString;
10use vortex_buffer::ByteBuffer;
11use vortex_dtype::DType;
12use vortex_dtype::PType;
13use vortex_dtype::half::f16;
14use vortex_dtype::i256;
15use vortex_error::VortexExpect;
16use vortex_error::VortexResult;
17use vortex_error::vortex_bail;
18use vortex_error::vortex_ensure;
19use vortex_error::vortex_err;
20use vortex_proto::scalar as pb;
21use vortex_proto::scalar::ListValue;
22use vortex_proto::scalar::scalar_value::Kind;
23use vortex_session::VortexSession;
24
25use crate::scalar::DecimalValue;
26use crate::scalar::PValue;
27use crate::scalar::Scalar;
28use crate::scalar::ScalarValue;
29
30impl From<&Scalar> for pb::Scalar {
35 fn from(value: &Scalar) -> Self {
36 pb::Scalar {
37 dtype: Some(
38 (value.dtype())
39 .try_into()
40 .vortex_expect("Failed to convert DType to proto"),
41 ),
42 value: Some(ScalarValue::to_proto(value.value())),
43 }
44 }
45}
46
47impl ScalarValue {
48 pub fn to_proto(this: Option<&Self>) -> pb::ScalarValue {
58 match this {
59 None => pb::ScalarValue {
60 kind: Some(Kind::NullValue(0)),
61 },
62 Some(this) => pb::ScalarValue::from(this),
63 }
64 }
65
66 pub fn to_proto_bytes<B: Default + bytes::BufMut>(value: Option<&ScalarValue>) -> B {
68 let proto = Self::to_proto(value);
69 let mut buf = B::default();
70 proto
71 .encode(&mut buf)
72 .vortex_expect("Failed to encode scalar value");
73 buf
74 }
75}
76
77impl From<&ScalarValue> for pb::ScalarValue {
78 fn from(value: &ScalarValue) -> Self {
79 match value {
80 ScalarValue::Bool(v) => pb::ScalarValue {
81 kind: Some(Kind::BoolValue(*v)),
82 },
83 ScalarValue::Primitive(v) => pb::ScalarValue::from(v),
84 ScalarValue::Decimal(v) => {
85 let inner_value = match v {
86 DecimalValue::I8(v) => v.to_le_bytes().to_vec(),
87 DecimalValue::I16(v) => v.to_le_bytes().to_vec(),
88 DecimalValue::I32(v) => v.to_le_bytes().to_vec(),
89 DecimalValue::I64(v) => v.to_le_bytes().to_vec(),
90 DecimalValue::I128(v128) => v128.to_le_bytes().to_vec(),
91 DecimalValue::I256(v256) => v256.to_le_bytes().to_vec(),
92 };
93
94 pb::ScalarValue {
95 kind: Some(Kind::BytesValue(inner_value)),
96 }
97 }
98 ScalarValue::Utf8(v) => pb::ScalarValue {
99 kind: Some(Kind::StringValue(v.to_string())),
100 },
101 ScalarValue::Binary(v) => pb::ScalarValue {
102 kind: Some(Kind::BytesValue(v.to_vec())),
103 },
104 ScalarValue::List(v) => {
105 let mut values = Vec::with_capacity(v.len());
106 for elem in v.iter() {
107 values.push(ScalarValue::to_proto(elem.as_ref()));
108 }
109 pb::ScalarValue {
110 kind: Some(Kind::ListValue(ListValue { values })),
111 }
112 }
113 }
114 }
115}
116
117impl From<&PValue> for pb::ScalarValue {
118 fn from(value: &PValue) -> Self {
119 match value {
120 PValue::I8(v) => pb::ScalarValue {
121 kind: Some(Kind::Int64Value(*v as i64)),
122 },
123 PValue::I16(v) => pb::ScalarValue {
124 kind: Some(Kind::Int64Value(*v as i64)),
125 },
126 PValue::I32(v) => pb::ScalarValue {
127 kind: Some(Kind::Int64Value(*v as i64)),
128 },
129 PValue::I64(v) => pb::ScalarValue {
130 kind: Some(Kind::Int64Value(*v)),
131 },
132 PValue::U8(v) => pb::ScalarValue {
133 kind: Some(Kind::Uint64Value(*v as u64)),
134 },
135 PValue::U16(v) => pb::ScalarValue {
136 kind: Some(Kind::Uint64Value(*v as u64)),
137 },
138 PValue::U32(v) => pb::ScalarValue {
139 kind: Some(Kind::Uint64Value(*v as u64)),
140 },
141 PValue::U64(v) => pb::ScalarValue {
142 kind: Some(Kind::Uint64Value(*v)),
143 },
144 PValue::F16(v) => pb::ScalarValue {
145 kind: Some(Kind::F16Value(v.to_bits() as u64)),
146 },
147 PValue::F32(v) => pb::ScalarValue {
148 kind: Some(Kind::F32Value(*v)),
149 },
150 PValue::F64(v) => pb::ScalarValue {
151 kind: Some(Kind::F64Value(*v)),
152 },
153 }
154 }
155}
156
157impl Scalar {
162 pub fn from_proto_value(value: &pb::ScalarValue, dtype: &DType) -> VortexResult<Self> {
171 let scalar_value = ScalarValue::from_proto(value, dtype)?;
172
173 Scalar::try_new(dtype.clone(), scalar_value)
174 }
175
176 pub fn from_proto(value: &pb::Scalar, session: &VortexSession) -> VortexResult<Self> {
182 let dtype = DType::from_proto(
183 value
184 .dtype
185 .as_ref()
186 .ok_or_else(|| vortex_err!(Serde: "Scalar missing dtype"))?,
187 session,
188 )?;
189
190 let pb_scalar_value: &pb::ScalarValue = value
191 .value
192 .as_ref()
193 .ok_or_else(|| vortex_err!(Serde: "Scalar missing value"))?;
194
195 let value: Option<ScalarValue> = ScalarValue::from_proto(pb_scalar_value, &dtype)?;
196
197 Scalar::try_new(dtype, value)
198 }
199}
200
201impl ScalarValue {
202 pub fn from_proto_bytes(bytes: &[u8], dtype: &DType) -> VortexResult<Option<Self>> {
211 let proto = pb::ScalarValue::decode(bytes)?;
212 Self::from_proto(&proto, dtype)
213 }
214
215 pub fn from_proto(value: &pb::ScalarValue, dtype: &DType) -> VortexResult<Option<Self>> {
224 let kind = value
225 .kind
226 .as_ref()
227 .ok_or_else(|| vortex_err!(Serde: "Scalar value missing kind"))?;
228
229 let dtype = match dtype {
231 DType::Extension(ext) => ext.storage_dtype(),
232 _ => dtype,
233 };
234
235 Ok(Some(match kind {
236 Kind::NullValue(_) => return Ok(None),
237 Kind::BoolValue(v) => bool_from_proto(*v, dtype)?,
238 Kind::Int64Value(v) => int64_from_proto(*v, dtype)?,
239 Kind::Uint64Value(v) => uint64_from_proto(*v, dtype)?,
240 Kind::F16Value(v) => f16_from_proto(*v, dtype)?,
241 Kind::F32Value(v) => f32_from_proto(*v, dtype)?,
242 Kind::F64Value(v) => f64_from_proto(*v, dtype)?,
243 Kind::StringValue(s) => string_from_proto(s, dtype)?,
244 Kind::BytesValue(b) => bytes_from_proto(b, dtype)?,
245 Kind::ListValue(v) => list_from_proto(v, dtype)?,
246 }))
247 }
248}
249
250fn bool_from_proto(v: bool, dtype: &DType) -> VortexResult<ScalarValue> {
252 vortex_ensure!(
253 dtype.is_boolean(),
254 Serde: "expected Bool dtype for BoolValue, got {dtype}"
255 );
256
257 Ok(ScalarValue::Bool(v))
258}
259
260fn int64_from_proto(v: i64, dtype: &DType) -> VortexResult<ScalarValue> {
265 vortex_ensure!(
266 dtype.is_primitive(),
267 Serde: "expected Primitive dtype for Int64Value, got {dtype}"
268 );
269
270 let pvalue = match dtype.as_ptype() {
271 PType::I8 => v.to_i8().map(PValue::I8),
272 PType::I16 => v.to_i16().map(PValue::I16),
273 PType::I32 => v.to_i32().map(PValue::I32),
274 PType::I64 => Some(PValue::I64(v)),
275 ptype => vortex_bail!(
276 Serde: "expected signed integer ptype for Int64Value, got {ptype}"
277 ),
278 }
279 .ok_or_else(|| vortex_err!(Serde: "Int64 value {v} out of range for dtype {dtype}"))?;
280
281 Ok(ScalarValue::Primitive(pvalue))
282}
283
284fn uint64_from_proto(v: u64, dtype: &DType) -> VortexResult<ScalarValue> {
290 vortex_ensure!(
291 dtype.is_primitive(),
292 Serde: "expected Primitive dtype for Uint64Value, got {dtype}"
293 );
294
295 let pvalue = match dtype.as_ptype() {
296 PType::U8 => v.to_u8().map(PValue::U8),
297 PType::U16 => v.to_u16().map(PValue::U16),
298 PType::U32 => v.to_u32().map(PValue::U32),
299 PType::U64 => Some(PValue::U64(v)),
300 PType::F16 => v.to_u16().map(f16::from_bits).map(PValue::F16),
302 ptype => vortex_bail!(
303 Serde: "expected unsigned integer ptype for Uint64Value, got {ptype}"
304 ),
305 }
306 .ok_or_else(|| vortex_err!(Serde: "Uint64 value {v} out of range for dtype {dtype}"))?;
307
308 Ok(ScalarValue::Primitive(pvalue))
309}
310
311fn f16_from_proto(v: u64, dtype: &DType) -> VortexResult<ScalarValue> {
313 vortex_ensure!(
314 matches!(dtype, DType::Primitive(PType::F16, _)),
315 Serde: "expected F16 dtype for F16Value, got {dtype}"
316 );
317
318 let bits = u16::try_from(v)
319 .map_err(|_| vortex_err!(Serde: "f16 bitwise representation has more than 16 bits: {v}"))?;
320
321 Ok(ScalarValue::Primitive(PValue::F16(f16::from_bits(bits))))
322}
323
324fn f32_from_proto(v: f32, dtype: &DType) -> VortexResult<ScalarValue> {
326 vortex_ensure!(
327 matches!(dtype, DType::Primitive(PType::F32, _)),
328 Serde: "expected F32 dtype for F32Value, got {dtype}"
329 );
330
331 Ok(ScalarValue::Primitive(PValue::F32(v)))
332}
333
334fn f64_from_proto(v: f64, dtype: &DType) -> VortexResult<ScalarValue> {
336 vortex_ensure!(
337 matches!(dtype, DType::Primitive(PType::F64, _)),
338 Serde: "expected F64 dtype for F64Value, got {dtype}"
339 );
340
341 Ok(ScalarValue::Primitive(PValue::F64(v)))
342}
343
344fn string_from_proto(s: &str, dtype: &DType) -> VortexResult<ScalarValue> {
347 match dtype {
348 DType::Utf8(_) => Ok(ScalarValue::Utf8(BufferString::from(s))),
349 DType::Binary(_) => Ok(ScalarValue::Binary(ByteBuffer::copy_from(s.as_bytes()))),
350 _ => vortex_bail!(
351 Serde: "expected Utf8 or Binary dtype for StringValue, got {dtype}"
352 ),
353 }
354}
355
356fn bytes_from_proto(bytes: &[u8], dtype: &DType) -> VortexResult<ScalarValue> {
361 match dtype {
362 DType::Utf8(_) => Ok(ScalarValue::Utf8(BufferString::try_from(bytes)?)),
363 DType::Binary(_) => Ok(ScalarValue::Binary(ByteBuffer::copy_from(bytes))),
364 DType::Decimal(..) => Ok(ScalarValue::Decimal(match bytes.len() {
366 1 => DecimalValue::I8(bytes[0] as i8),
367 2 => DecimalValue::I16(i16::from_le_bytes(
368 bytes
369 .try_into()
370 .ok()
371 .vortex_expect("Buffer has invalid number of bytes"),
372 )),
373 4 => DecimalValue::I32(i32::from_le_bytes(
374 bytes
375 .try_into()
376 .ok()
377 .vortex_expect("Buffer has invalid number of bytes"),
378 )),
379 8 => DecimalValue::I64(i64::from_le_bytes(
380 bytes
381 .try_into()
382 .ok()
383 .vortex_expect("Buffer has invalid number of bytes"),
384 )),
385 16 => DecimalValue::I128(i128::from_le_bytes(
386 bytes
387 .try_into()
388 .ok()
389 .vortex_expect("Buffer has invalid number of bytes"),
390 )),
391 32 => DecimalValue::I256(i256::from_le_bytes(
392 bytes
393 .try_into()
394 .ok()
395 .vortex_expect("Buffer has invalid number of bytes"),
396 )),
397 l => vortex_bail!(Serde: "invalid decimal byte length: {l}"),
398 })),
399 _ => vortex_bail!(
400 Serde: "expected Utf8, Binary, or Decimal dtype for BytesValue, got {dtype}"
401 ),
402 }
403}
404
405fn list_from_proto(v: &ListValue, dtype: &DType) -> VortexResult<ScalarValue> {
407 let element_dtype = dtype
408 .as_list_element_opt()
409 .ok_or_else(|| vortex_err!(Serde: "expected List dtype for ListValue, got {dtype}"))?;
410
411 let mut values = Vec::with_capacity(v.values.len());
412 for elem in v.values.iter() {
413 values.push(ScalarValue::from_proto(elem, element_dtype.as_ref())?);
414 }
415
416 Ok(ScalarValue::List(values))
417}
418
419#[cfg(test)]
420mod tests {
421 use std::sync::Arc;
422
423 use vortex_buffer::BufferString;
424 use vortex_dtype::DType;
425 use vortex_dtype::DecimalDType;
426 use vortex_dtype::Nullability;
427 use vortex_dtype::PType;
428 use vortex_dtype::half::f16;
429 use vortex_error::vortex_panic;
430 use vortex_proto::scalar as pb;
431 use vortex_session::VortexSession;
432
433 use super::*;
434 use crate::scalar::DecimalValue;
435 use crate::scalar::Scalar;
436 use crate::scalar::ScalarValue;
437
438 fn session() -> VortexSession {
439 VortexSession::empty()
440 }
441
442 fn round_trip(scalar: Scalar) {
443 assert_eq!(
444 scalar,
445 Scalar::from_proto(&pb::Scalar::from(&scalar), &session()).unwrap(),
446 );
447 }
448
449 #[test]
450 fn test_null() {
451 round_trip(Scalar::null(DType::Null));
452 }
453
454 #[test]
455 fn test_bool() {
456 round_trip(Scalar::new(
457 DType::Bool(Nullability::Nullable),
458 Some(ScalarValue::Bool(true)),
459 ));
460 }
461
462 #[test]
463 fn test_primitive() {
464 round_trip(Scalar::new(
465 DType::Primitive(PType::I32, Nullability::Nullable),
466 Some(ScalarValue::Primitive(42i32.into())),
467 ));
468 }
469
470 #[test]
471 fn test_buffer() {
472 round_trip(Scalar::new(
473 DType::Binary(Nullability::Nullable),
474 Some(ScalarValue::Binary(vec![1, 2, 3].into())),
475 ));
476 }
477
478 #[test]
479 fn test_buffer_string() {
480 round_trip(Scalar::new(
481 DType::Utf8(Nullability::Nullable),
482 Some(ScalarValue::Utf8(BufferString::from("hello".to_string()))),
483 ));
484 }
485
486 #[test]
487 fn test_list() {
488 round_trip(Scalar::new(
489 DType::List(
490 Arc::new(DType::Primitive(PType::I32, Nullability::Nullable)),
491 Nullability::Nullable,
492 ),
493 Some(ScalarValue::List(vec![
494 Some(ScalarValue::Primitive(42i32.into())),
495 Some(ScalarValue::Primitive(43i32.into())),
496 ])),
497 ));
498 }
499
500 #[test]
501 fn test_f16() {
502 round_trip(Scalar::primitive(
503 f16::from_f32(0.42),
504 Nullability::Nullable,
505 ));
506 }
507
508 #[test]
509 fn test_i8() {
510 round_trip(Scalar::new(
511 DType::Primitive(PType::I8, Nullability::Nullable),
512 Some(ScalarValue::Primitive(i8::MIN.into())),
513 ));
514
515 round_trip(Scalar::new(
516 DType::Primitive(PType::I8, Nullability::Nullable),
517 Some(ScalarValue::Primitive(0i8.into())),
518 ));
519
520 round_trip(Scalar::new(
521 DType::Primitive(PType::I8, Nullability::Nullable),
522 Some(ScalarValue::Primitive(i8::MAX.into())),
523 ));
524 }
525
526 #[test]
527 fn test_decimal_i32_roundtrip() {
528 round_trip(Scalar::decimal(
530 DecimalValue::I32(123_456),
531 DecimalDType::new(10, 2),
532 Nullability::NonNullable,
533 ));
534 }
535
536 #[test]
537 fn test_decimal_i128_roundtrip() {
538 round_trip(Scalar::decimal(
540 DecimalValue::I128(99_999_999_999_999_999_999),
541 DecimalDType::new(38, 6),
542 Nullability::Nullable,
543 ));
544 }
545
546 #[test]
547 fn test_decimal_null_roundtrip() {
548 round_trip(Scalar::null(DType::Decimal(
549 DecimalDType::new(10, 2),
550 Nullability::Nullable,
551 )));
552 }
553
554 #[test]
555 fn test_scalar_value_serde_roundtrip_binary() {
556 round_trip(Scalar::binary(
557 ByteBuffer::copy_from(b"hello"),
558 Nullability::NonNullable,
559 ));
560 }
561
562 #[test]
563 fn test_scalar_value_serde_roundtrip_utf8() {
564 round_trip(Scalar::utf8("hello", Nullability::NonNullable));
565 }
566
567 #[test]
568 fn test_backcompat_f16_serialized_as_u64() {
569 let f16_value = f16::from_f32(0.42);
585 let f16_bits_as_u64 = f16_value.to_bits() as u64; let pb_scalar_value = pb::ScalarValue {
588 kind: Some(Kind::Uint64Value(f16_bits_as_u64)),
589 };
590
591 let scalar_value = ScalarValue::from_proto(
593 &pb_scalar_value,
594 &DType::Primitive(PType::U64, Nullability::NonNullable),
595 )
596 .unwrap();
597 assert_eq!(
598 scalar_value.as_ref().map(|v| v.as_primitive()),
599 Some(&PValue::U64(14008u64)),
600 );
601
602 let scalar_value_f16 = ScalarValue::from_proto(
604 &pb_scalar_value,
605 &DType::Primitive(PType::F16, Nullability::Nullable),
606 )
607 .unwrap();
608
609 let scalar = Scalar::new(
610 DType::Primitive(PType::F16, Nullability::Nullable),
611 scalar_value_f16,
612 );
613
614 assert_eq!(
615 scalar.as_primitive().pvalue().unwrap(),
616 PValue::F16(f16::from_f32(0.42)),
617 "Uint64Value should be correctly interpreted as f16 when dtype is F16"
618 );
619 }
620
621 #[test]
622 fn test_scalar_value_direct_roundtrip_f16() {
623 let f16_values = vec![
625 f16::from_f32(0.0),
626 f16::from_f32(1.0),
627 f16::from_f32(-1.0),
628 f16::from_f32(0.42),
629 f16::from_f32(5.722046e-6),
630 f16::from_f32(std::f32::consts::PI),
631 f16::INFINITY,
632 f16::NEG_INFINITY,
633 f16::NAN,
634 ];
635
636 for f16_val in f16_values {
637 let scalar_value = ScalarValue::Primitive(PValue::F16(f16_val));
638 let pb_value = ScalarValue::to_proto(Some(&scalar_value));
639 let read_back = ScalarValue::from_proto(
640 &pb_value,
641 &DType::Primitive(PType::F16, Nullability::NonNullable),
642 )
643 .unwrap();
644
645 match (&scalar_value, read_back.as_ref()) {
646 (
647 ScalarValue::Primitive(PValue::F16(original)),
648 Some(ScalarValue::Primitive(PValue::F16(roundtripped))),
649 ) => {
650 if original.is_nan() && roundtripped.is_nan() {
651 continue;
653 }
654 assert_eq!(
655 original, roundtripped,
656 "F16 value {original:?} did not roundtrip correctly"
657 );
658 }
659 _ => {
660 vortex_panic!(
661 "Expected f16 primitive values, got {scalar_value:?} and {read_back:?}"
662 )
663 }
664 }
665 }
666 }
667
668 #[test]
669 fn test_scalar_value_direct_roundtrip_preserves_values() {
670 let exact_roundtrip_cases: Vec<(&str, Option<ScalarValue>, DType)> = vec![
675 ("null", None, DType::Null),
676 (
677 "bool_true",
678 Some(ScalarValue::Bool(true)),
679 DType::Bool(Nullability::Nullable),
680 ),
681 (
682 "bool_false",
683 Some(ScalarValue::Bool(false)),
684 DType::Bool(Nullability::Nullable),
685 ),
686 (
687 "u64",
688 Some(ScalarValue::Primitive(PValue::U64(18446744073709551615))),
689 DType::Primitive(PType::U64, Nullability::Nullable),
690 ),
691 (
692 "i64",
693 Some(ScalarValue::Primitive(PValue::I64(-9223372036854775808))),
694 DType::Primitive(PType::I64, Nullability::Nullable),
695 ),
696 (
697 "f32",
698 Some(ScalarValue::Primitive(PValue::F32(std::f32::consts::E))),
699 DType::Primitive(PType::F32, Nullability::Nullable),
700 ),
701 (
702 "f64",
703 Some(ScalarValue::Primitive(PValue::F64(std::f64::consts::PI))),
704 DType::Primitive(PType::F64, Nullability::Nullable),
705 ),
706 (
707 "string",
708 Some(ScalarValue::Utf8(BufferString::from("test"))),
709 DType::Utf8(Nullability::Nullable),
710 ),
711 (
712 "bytes",
713 Some(ScalarValue::Binary(vec![1, 2, 3, 4, 5].into())),
714 DType::Binary(Nullability::Nullable),
715 ),
716 ];
717
718 for (name, value, dtype) in exact_roundtrip_cases {
719 let pb_value = ScalarValue::to_proto(value.as_ref());
720 let read_back = ScalarValue::from_proto(&pb_value, &dtype).unwrap();
721
722 let original_debug = format!("{value:?}");
723 let roundtrip_debug = format!("{read_back:?}");
724 assert_eq!(
725 original_debug, roundtrip_debug,
726 "ScalarValue {name} did not roundtrip exactly"
727 );
728 }
729
730 let unsigned_cases = vec![
733 (
734 "u8",
735 ScalarValue::Primitive(PValue::U8(255)),
736 DType::Primitive(PType::U8, Nullability::Nullable),
737 255u64,
738 ),
739 (
740 "u16",
741 ScalarValue::Primitive(PValue::U16(65535)),
742 DType::Primitive(PType::U16, Nullability::Nullable),
743 65535u64,
744 ),
745 (
746 "u32",
747 ScalarValue::Primitive(PValue::U32(4294967295)),
748 DType::Primitive(PType::U32, Nullability::Nullable),
749 4294967295u64,
750 ),
751 ];
752
753 for (name, value, dtype, expected) in unsigned_cases {
754 let pb_value = ScalarValue::to_proto(Some(&value));
755 let read_back = ScalarValue::from_proto(&pb_value, &dtype).unwrap();
756
757 match read_back.as_ref() {
758 Some(ScalarValue::Primitive(pv)) => {
759 let v = match pv {
760 PValue::U8(v) => *v as u64,
761 PValue::U16(v) => *v as u64,
762 PValue::U32(v) => *v as u64,
763 PValue::U64(v) => *v,
764 _ => vortex_panic!("Unexpected primitive type for {name}: {pv:?}"),
765 };
766 assert_eq!(
767 v, expected,
768 "ScalarValue {name} value not preserved: expected {expected}, got {v}"
769 );
770 }
771 _ => vortex_panic!("Unexpected type after roundtrip for {name}: {read_back:?}"),
772 }
773 }
774
775 let signed_cases = vec![
777 (
778 "i8",
779 ScalarValue::Primitive(PValue::I8(-128)),
780 DType::Primitive(PType::I8, Nullability::Nullable),
781 -128i64,
782 ),
783 (
784 "i16",
785 ScalarValue::Primitive(PValue::I16(-32768)),
786 DType::Primitive(PType::I16, Nullability::Nullable),
787 -32768i64,
788 ),
789 (
790 "i32",
791 ScalarValue::Primitive(PValue::I32(-2147483648)),
792 DType::Primitive(PType::I32, Nullability::Nullable),
793 -2147483648i64,
794 ),
795 ];
796
797 for (name, value, dtype, expected) in signed_cases {
798 let pb_value = ScalarValue::to_proto(Some(&value));
799 let read_back = ScalarValue::from_proto(&pb_value, &dtype).unwrap();
800
801 match read_back.as_ref() {
802 Some(ScalarValue::Primitive(pv)) => {
803 let v = match pv {
804 PValue::I8(v) => *v as i64,
805 PValue::I16(v) => *v as i64,
806 PValue::I32(v) => *v as i64,
807 PValue::I64(v) => *v,
808 _ => vortex_panic!("Unexpected primitive type for {name}: {pv:?}"),
809 };
810 assert_eq!(
811 v, expected,
812 "ScalarValue {name} value not preserved: expected {expected}, got {v}"
813 );
814 }
815 _ => vortex_panic!("Unexpected type after roundtrip for {name}: {read_back:?}"),
816 }
817 }
818 }
819}