1use std::fmt::Debug;
5use std::hash::Hash;
6use std::hash::Hasher;
7
8use itertools::Itertools;
9use vortex_buffer::ByteBufferMut;
10use vortex_error::VortexExpect;
11use vortex_error::VortexResult;
12use vortex_error::vortex_ensure;
13use vortex_error::vortex_panic;
14use vortex_session::VortexSession;
15use vortex_session::registry::CachedId;
16
17use crate::ArrayEq;
18use crate::ArrayHash;
19use crate::ArrayParts;
20use crate::ArrayRef;
21use crate::EqMode;
22use crate::ExecutionCtx;
23use crate::ExecutionResult;
24use crate::IntoArray;
25use crate::array::Array;
26use crate::array::ArrayId;
27use crate::array::ArrayView;
28use crate::array::VTable;
29use crate::array::unsupported_buffer_replacement;
30use crate::arrays::ExtensionArray;
31use crate::arrays::constant::ConstantData;
32use crate::arrays::constant::compute::rules::PARENT_RULES;
33use crate::arrays::constant::vtable::canonical::constant_canonicalize;
34use crate::buffer::BufferHandle;
35use crate::builders::ArrayBuilder;
36use crate::builders::BoolBuilder;
37use crate::builders::DecimalBuilder;
38use crate::builders::FixedSizeListBuilder;
39use crate::builders::ListViewBuilder;
40use crate::builders::NullBuilder;
41use crate::builders::PrimitiveBuilder;
42use crate::builders::VarBinViewBuilder;
43use crate::builders::builder_with_capacity;
44use crate::canonical::Canonical;
45use crate::dtype::DType;
46use crate::dtype::OffsetBuilderPType;
47use crate::match_each_decimal_value;
48use crate::match_each_listview_builder;
49use crate::match_each_native_ptype;
50use crate::match_each_varbin_builder;
51use crate::scalar::DecimalValue;
52use crate::scalar::ListScalar;
53use crate::scalar::Scalar;
54use crate::scalar::ScalarValue;
55use crate::serde::ArrayChildren;
56pub(crate) mod canonical;
57mod operations;
58mod validity;
59
60pub type ConstantArray = Array<Constant>;
62
63#[derive(Clone, Debug)]
64pub struct Constant;
65
66impl ArrayHash for ConstantData {
67 fn array_hash<H: Hasher>(&self, state: &mut H, _accuracy: EqMode) {
68 self.scalar.hash(state);
69 }
70}
71
72impl ArrayEq for ConstantData {
73 fn array_eq(&self, other: &Self, _accuracy: EqMode) -> bool {
74 self.scalar == other.scalar
75 }
76}
77
78impl VTable for Constant {
79 type TypedArrayData = ConstantData;
80
81 type OperationsVTable = Self;
82 type ValidityVTable = Self;
83
84 fn id(&self) -> ArrayId {
85 static ID: CachedId = CachedId::new("vortex.constant");
86 *ID
87 }
88
89 fn validate(
90 &self,
91 data: &ConstantData,
92 dtype: &DType,
93 _len: usize,
94 _slots: &[Option<ArrayRef>],
95 ) -> VortexResult<()> {
96 vortex_ensure!(
97 data.scalar.dtype() == dtype,
98 "ConstantArray scalar dtype does not match outer dtype"
99 );
100 Ok(())
101 }
102
103 fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
104 1
105 }
106
107 fn buffer(array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
108 match idx {
109 0 => BufferHandle::new_host(
110 ScalarValue::to_proto_bytes::<ByteBufferMut>(array.scalar.value()).freeze(),
111 ),
112 _ => vortex_panic!("ConstantArray buffer index {idx} out of bounds"),
113 }
114 }
115
116 fn buffer_name(_array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
117 match idx {
118 0 => Some("scalar".to_string()),
119 _ => None,
120 }
121 }
122
123 fn with_buffers(
124 &self,
125 array: ArrayView<'_, Self>,
126 buffers: &[BufferHandle],
127 ) -> VortexResult<ArrayParts<Self>> {
128 unsupported_buffer_replacement(array, buffers)
129 }
130
131 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
132 vortex_panic!("ConstantArray slot_name index {idx} out of bounds")
133 }
134
135 fn serialize(
136 _array: ArrayView<'_, Self>,
137 _session: &VortexSession,
138 ) -> VortexResult<Option<Vec<u8>>> {
139 Ok(Some(vec![]))
142 }
143
144 fn deserialize(
145 &self,
146 dtype: &DType,
147 len: usize,
148 _metadata: &[u8],
149
150 buffers: &[BufferHandle],
151 _children: &dyn ArrayChildren,
152 session: &VortexSession,
153 ) -> VortexResult<ArrayParts<Self>> {
154 vortex_ensure!(
155 buffers.len() == 1,
156 "Expected 1 buffer, got {}",
157 buffers.len()
158 );
159
160 let buffer = buffers[0].clone().try_to_host_sync()?;
161 let bytes: &[u8] = buffer.as_ref();
162
163 let scalar_value = ScalarValue::from_proto_bytes(bytes, dtype, session)?;
164 let scalar = Scalar::try_new(dtype.clone(), scalar_value)?;
165
166 Ok(ArrayParts::new(
167 self.clone(),
168 dtype.clone(),
169 len,
170 ConstantData::new(scalar),
171 ))
172 }
173
174 fn reduce_parent(
175 array: ArrayView<'_, Self>,
176 parent: &ArrayRef,
177 child_idx: usize,
178 ) -> VortexResult<Option<ArrayRef>> {
179 PARENT_RULES.evaluate(array, parent, child_idx)
180 }
181
182 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
183 Ok(ExecutionResult::done(constant_canonicalize(
184 array.as_view(),
185 ctx,
186 )?))
187 }
188
189 fn append_to_builder(
190 array: ArrayView<'_, Self>,
191 builder: &mut dyn ArrayBuilder,
192 ctx: &mut ExecutionCtx,
193 ) -> VortexResult<()> {
194 let n = array.len();
195 let scalar = array.scalar();
196
197 match array.dtype() {
198 DType::Null => append_value_or_nulls::<NullBuilder>(builder, true, n, |_| {}),
199 DType::Bool(_) => {
200 append_value_or_nulls::<BoolBuilder>(builder, scalar.is_null(), n, |b| {
201 b.append_values(
202 scalar
203 .as_bool()
204 .value()
205 .vortex_expect("non-null bool scalar must have a value"),
206 n,
207 );
208 })
209 }
210 DType::Primitive(ptype, _) => {
211 match_each_native_ptype!(ptype, |P| {
212 append_value_or_nulls::<PrimitiveBuilder<P>>(
213 builder,
214 scalar.is_null(),
215 n,
216 |b| {
217 let value = P::try_from(scalar)
218 .vortex_expect("Couldn't unwrap constant scalar to primitive");
219 b.append_n_values(value, n);
220 },
221 );
222 });
223 }
224 DType::Decimal(..) => {
225 append_value_or_nulls::<DecimalBuilder>(builder, scalar.is_null(), n, |b| {
226 let value = scalar
227 .as_decimal()
228 .decimal_value()
229 .vortex_expect("non-null decimal scalar must have a value");
230 match_each_decimal_value!(value, |v| { b.append_n_values(v, n) });
231 });
232 }
233 DType::Utf8(_) => {
234 if let Some(result) = match_each_varbin_builder!(builder, |builder| {
235 builder.append_scalar_repeated(scalar, n)
236 }) {
237 result?;
238 } else {
239 append_value_or_nulls::<VarBinViewBuilder>(builder, scalar.is_null(), n, |b| {
240 let value = scalar
241 .as_utf8()
242 .value()
243 .vortex_expect("non-null utf8 scalar must have a value");
244 b.append_n_values(value.as_bytes(), n);
245 });
246 }
247 }
248 DType::Binary(_) => {
249 if let Some(result) = match_each_varbin_builder!(builder, |builder| {
250 builder.append_scalar_repeated(scalar, n)
251 }) {
252 result?;
253 } else {
254 append_value_or_nulls::<VarBinViewBuilder>(builder, scalar.is_null(), n, |b| {
255 let value = scalar
256 .as_binary()
257 .value()
258 .vortex_expect("non-null binary scalar must have a value");
259 b.append_n_values(value, n);
260 });
261 }
262 }
263 DType::List(..) => append_constant_list_run(array, n, builder, ctx)?,
264 DType::Extension(ext_dtype) => {
265 let storage = ConstantArray::new(scalar.as_extension().to_storage_scalar(), n);
270 ExtensionArray::new(ext_dtype.clone(), storage.into_array())
271 .into_array()
272 .append_to_builder(builder, ctx)?
273 }
274 DType::FixedSizeList(..) => {
275 append_constant_fixed_size_list_run(array, n, builder, ctx)?
276 }
277 _ => append_via_canonical(array, builder, ctx)?,
282 }
283
284 Ok(())
285 }
286}
287
288fn append_constant_list_run(
296 array: ArrayView<'_, Constant>,
297 n: usize,
298 builder: &mut dyn ArrayBuilder,
299 ctx: &mut ExecutionCtx,
300) -> VortexResult<()> {
301 let scalar = array.scalar();
302 match match_each_listview_builder!(builder, |b| append_repeated_list_run(
303 b,
304 scalar.as_list(),
305 n,
306 ctx
307 )) {
308 Some(result) => result,
309 None => append_via_canonical(array, builder, ctx),
310 }
311}
312
313fn append_repeated_list_run<O: OffsetBuilderPType, S: OffsetBuilderPType>(
315 builder: &mut ListViewBuilder<O, S>,
316 scalar: ListScalar,
317 n: usize,
318 ctx: &mut ExecutionCtx,
319) -> VortexResult<()> {
320 if n == 0 {
321 return Ok(());
322 }
323
324 let Some(elements) = scalar.elements() else {
325 builder.append_nulls(n);
327 return Ok(());
328 };
329
330 let mut elements_builder = builder_with_capacity(scalar.element_dtype(), elements.len());
331 for element in &elements {
332 elements_builder.append_scalar(element)?;
333 }
334
335 builder.append_array_as_repeated_list(&elements_builder.finish(), n, ctx)
336}
337
338fn append_constant_fixed_size_list_run(
344 array: ArrayView<'_, Constant>,
345 n: usize,
346 builder: &mut dyn ArrayBuilder,
347 ctx: &mut ExecutionCtx,
348) -> VortexResult<()> {
349 let Some(builder) = builder.as_any_mut().downcast_mut::<FixedSizeListBuilder>() else {
350 return append_via_canonical(array, builder, ctx);
351 };
352
353 if n == 0 {
354 return Ok(());
355 }
356
357 let scalar = array.scalar().as_list();
358 let Some(elements) = scalar.elements() else {
359 builder.append_nulls(n);
361 return Ok(());
362 };
363
364 let tile = match elements.iter().all_equal_value() {
365 Ok(uniform) => ConstantArray::new(uniform.clone(), elements.len()).into_array(),
366 Err(_) => {
367 let mut tile_builder = builder_with_capacity(builder.element_dtype(), elements.len());
368 for element in &elements {
369 tile_builder.append_scalar(element)?;
370 }
371 tile_builder.finish()
372 }
373 };
374
375 builder.append_array_as_repeated_list(&tile, n, ctx)
376}
377
378fn append_via_canonical(
380 array: ArrayView<'_, Constant>,
381 builder: &mut dyn ArrayBuilder,
382 ctx: &mut ExecutionCtx,
383) -> VortexResult<()> {
384 let canonical = array
385 .array()
386 .clone()
387 .execute::<Canonical>(ctx)?
388 .into_array();
389 canonical.append_to_builder(builder, ctx)
390}
391
392fn append_value_or_nulls<B: ArrayBuilder + 'static>(
397 builder: &mut dyn ArrayBuilder,
398 is_null: bool,
399 n: usize,
400 fill: impl FnOnce(&mut B),
401) {
402 let b = builder
403 .as_any_mut()
404 .downcast_mut::<B>()
405 .vortex_expect("builder dtype must match array dtype");
406 if is_null {
407 unsafe { b.append_nulls_unchecked(n) };
409 } else {
410 fill(b);
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use std::sync::Arc;
417
418 use rstest::rstest;
419 use vortex_error::VortexResult;
420
421 use crate::IntoArray;
422 use crate::VortexSessionExecute;
423 use crate::arrays::Chunked;
424 use crate::arrays::Constant;
425 use crate::arrays::ConstantArray;
426 use crate::arrays::Extension;
427 use crate::arrays::FixedSizeList;
428 use crate::arrays::ListView;
429 use crate::arrays::Struct;
430 use crate::arrays::chunked::ChunkedArrayExt;
431 use crate::arrays::constant::vtable::canonical::constant_canonicalize;
432 use crate::arrays::extension::ExtensionArrayExt;
433 use crate::arrays::fixed_size_list::FixedSizeListArraySlotsExt;
434 use crate::arrays::listview::ListViewArraySlotsExt;
435 use crate::arrays::struct_::StructArrayExt;
436 use crate::assert_arrays_eq;
437 use crate::builders::ArrayBuilder;
438 use crate::builders::ListBuilder;
439 use crate::builders::builder_with_capacity;
440 use crate::dtype::DType;
441 use crate::dtype::Nullability;
442 use crate::dtype::PType;
443 use crate::dtype::StructFields;
444 use crate::extension::datetime::Date;
445 use crate::extension::datetime::TimeUnit;
446 use crate::scalar::Scalar;
447
448 fn assert_append_matches_canonical(array: ConstantArray) -> VortexResult<()> {
450 let mut ctx = crate::array_session().create_execution_ctx();
451
452 let expected = constant_canonicalize(array.as_view(), &mut ctx)?.into_array();
453 let mut builder = builder_with_capacity(array.dtype(), array.len());
454 array
455 .into_array()
456 .append_to_builder(builder.as_mut(), &mut ctx)?;
457 let result = builder.finish();
458 assert_arrays_eq!(&result, &expected, &mut ctx);
459 Ok(())
460 }
461
462 #[test]
463 fn test_null_constant_append() -> VortexResult<()> {
464 assert_append_matches_canonical(ConstantArray::new(Scalar::null(DType::Null), 5))
465 }
466
467 #[test]
468 fn test_with_buffers_rejects_serialized_scalar_buffer() {
469 let array =
470 ConstantArray::new(Scalar::primitive(42i32, Nullability::NonNullable), 3).into_array();
471 let buffers = array.buffer_handles();
472
473 let Err(err) = (unsafe { array.with_buffers(buffers) }) else {
476 panic!("ConstantArray should reject replacing its serialized scalar buffer");
477 };
478 assert!(
479 err.to_string()
480 .contains("does not support in-memory buffer replacement")
481 );
482 }
483
484 #[rstest]
485 #[case::bool_true(true, 5)]
486 #[case::bool_false(false, 3)]
487 fn test_bool_constant_append(#[case] value: bool, #[case] n: usize) -> VortexResult<()> {
488 assert_append_matches_canonical(ConstantArray::new(
489 Scalar::bool(value, Nullability::NonNullable),
490 n,
491 ))
492 }
493
494 #[test]
495 fn test_bool_null_constant_append() -> VortexResult<()> {
496 assert_append_matches_canonical(ConstantArray::new(
497 Scalar::null(DType::Bool(Nullability::Nullable)),
498 4,
499 ))
500 }
501
502 #[rstest]
503 #[case::i32(Scalar::primitive(42i32, Nullability::NonNullable), 5)]
504 #[case::u8(Scalar::primitive(7u8, Nullability::NonNullable), 3)]
505 #[case::f64(Scalar::primitive(1.5f64, Nullability::NonNullable), 4)]
506 #[case::i32_null(Scalar::null(DType::Primitive(PType::I32, Nullability::Nullable)), 3)]
507 fn test_primitive_constant_append(
508 #[case] scalar: Scalar,
509 #[case] n: usize,
510 ) -> VortexResult<()> {
511 assert_append_matches_canonical(ConstantArray::new(scalar, n))
512 }
513
514 #[rstest]
515 #[case::utf8_inline("hi", 5)] #[case::utf8_noninline("hello world!!", 5)] #[case::utf8_empty("", 3)]
518 #[case::utf8_n_zero("hello world!!", 0)] fn test_utf8_constant_append(#[case] value: &str, #[case] n: usize) -> VortexResult<()> {
520 assert_append_matches_canonical(ConstantArray::new(
521 Scalar::utf8(value, Nullability::NonNullable),
522 n,
523 ))
524 }
525
526 #[test]
527 fn test_utf8_null_constant_append() -> VortexResult<()> {
528 assert_append_matches_canonical(ConstantArray::new(
529 Scalar::null(DType::Utf8(Nullability::Nullable)),
530 4,
531 ))
532 }
533
534 #[rstest]
535 #[case::binary_inline(vec![1u8, 2, 3], 5)] #[case::binary_noninline(vec![0u8; 13], 5)] fn test_binary_constant_append(#[case] value: Vec<u8>, #[case] n: usize) -> VortexResult<()> {
538 assert_append_matches_canonical(ConstantArray::new(
539 Scalar::binary(value, Nullability::NonNullable),
540 n,
541 ))
542 }
543
544 #[test]
545 fn test_binary_null_constant_append() -> VortexResult<()> {
546 assert_append_matches_canonical(ConstantArray::new(
547 Scalar::null(DType::Binary(Nullability::Nullable)),
548 4,
549 ))
550 }
551
552 #[rstest]
553 #[case::non_empty(vec![Scalar::from(1i32), Scalar::from(2i32)], 4)]
554 #[case::empty(vec![], 3)]
555 #[case::n_zero(vec![Scalar::from(1i32)], 0)]
556 fn test_list_constant_append(
557 #[case] elements: Vec<Scalar>,
558 #[case] n: usize,
559 ) -> VortexResult<()> {
560 let scalar = Scalar::list(
561 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
562 elements,
563 Nullability::NonNullable,
564 );
565 assert_append_matches_canonical(ConstantArray::new(scalar, n))
566 }
567
568 #[test]
569 fn test_null_list_constant_append() -> VortexResult<()> {
570 let dtype = DType::List(
571 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
572 Nullability::Nullable,
573 );
574 assert_append_matches_canonical(ConstantArray::new(Scalar::null(dtype), 3))
575 }
576
577 #[test]
580 fn test_list_constant_append_keeps_one_copy_of_the_elements() -> VortexResult<()> {
581 let mut ctx = crate::array_session().create_execution_ctx();
582 let scalar = Scalar::list(
583 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
584 vec![Scalar::from(1i32), Scalar::from(2i32), Scalar::from(3i32)],
585 Nullability::NonNullable,
586 );
587 let array = ConstantArray::new(scalar, 1_000);
588
589 let mut builder = builder_with_capacity(array.dtype(), array.len());
590 array
591 .into_array()
592 .append_to_builder(builder.as_mut(), &mut ctx)?;
593 let result = builder.finish();
594
595 assert_eq!(
596 result.as_::<ListView>().elements().len(),
597 3,
598 "the run's elements should be stored once, not once per row",
599 );
600 Ok(())
601 }
602
603 #[test]
606 fn test_list_constant_append_into_list_builder() -> VortexResult<()> {
607 let mut ctx = crate::array_session().create_execution_ctx();
608 let element_dtype: Arc<DType> =
609 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable));
610 let scalar = Scalar::list(
611 Arc::clone(&element_dtype),
612 vec![Scalar::from(1i32), Scalar::from(2i32)],
613 Nullability::NonNullable,
614 );
615 let array = ConstantArray::new(scalar, 4).into_array();
616
617 let mut builder =
618 ListBuilder::<u32>::with_capacity(element_dtype, Nullability::NonNullable, 0, 0);
619 array.append_to_builder(&mut builder, &mut ctx)?;
620
621 assert_arrays_eq!(&builder.finish(), &array, &mut ctx);
622 Ok(())
623 }
624
625 #[test]
626 fn test_struct_constant_append() -> VortexResult<()> {
627 let fields = StructFields::new(
628 ["x", "y"].into(),
629 vec![
630 DType::Primitive(PType::I32, Nullability::NonNullable),
631 DType::Utf8(Nullability::NonNullable),
632 ],
633 );
634 let scalar = Scalar::struct_(
635 DType::Struct(fields, Nullability::NonNullable),
636 [
637 Scalar::primitive(42i32, Nullability::NonNullable),
638 Scalar::utf8("hi", Nullability::NonNullable),
639 ],
640 );
641 assert_append_matches_canonical(ConstantArray::new(scalar, 3))
642 }
643
644 #[test]
645 fn test_null_struct_constant_append() -> VortexResult<()> {
646 let fields = StructFields::new(
647 ["x"].into(),
648 vec![DType::Primitive(PType::I32, Nullability::Nullable)],
649 );
650 let dtype = DType::Struct(fields, Nullability::Nullable);
651 assert_append_matches_canonical(ConstantArray::new(Scalar::null(dtype), 4))
652 }
653
654 #[test]
657 fn test_struct_constant_append_keeps_fields_constant() -> VortexResult<()> {
658 let mut ctx = crate::array_session().create_execution_ctx();
659 let fields = StructFields::new(
660 ["x", "y"].into(),
661 vec![
662 DType::Primitive(PType::I32, Nullability::NonNullable),
663 DType::Utf8(Nullability::NonNullable),
664 ],
665 );
666 let scalar = Scalar::struct_(
667 DType::Struct(fields, Nullability::NonNullable),
668 [
669 Scalar::primitive(42i32, Nullability::NonNullable),
670 Scalar::utf8("hi", Nullability::NonNullable),
671 ],
672 );
673 let array = ConstantArray::new(scalar, 1_000);
674
675 let mut builder = builder_with_capacity(array.dtype(), array.len());
676 array
677 .into_array()
678 .append_to_builder(builder.as_mut(), &mut ctx)?;
679 let result = builder.finish();
680
681 let struct_array = result.as_::<Struct>();
682 for field in 0..2 {
683 assert!(
684 struct_array.unmasked_field(field).is::<Constant>(),
685 "field {field} should have stayed constant-encoded",
686 );
687 }
688 Ok(())
689 }
690
691 #[rstest]
692 #[case::non_uniform(vec![Scalar::from(1i32), Scalar::from(2i32)])]
693 #[case::uniform(vec![Scalar::from(7i32), Scalar::from(7i32)])]
694 fn test_fixed_size_list_constant_append(#[case] elements: Vec<Scalar>) -> VortexResult<()> {
695 let scalar = Scalar::fixed_size_list(
696 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
697 elements,
698 Nullability::NonNullable,
699 );
700 assert_append_matches_canonical(ConstantArray::new(scalar, 4))
701 }
702
703 #[test]
704 fn test_null_fixed_size_list_constant_append() -> VortexResult<()> {
705 let dtype = DType::FixedSizeList(
706 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
707 2,
708 Nullability::Nullable,
709 );
710 assert_append_matches_canonical(ConstantArray::new(Scalar::null(dtype), 3))
711 }
712
713 #[test]
716 fn test_uniform_fixed_size_list_constant_append_keeps_elements_constant() -> VortexResult<()> {
717 let mut ctx = crate::array_session().create_execution_ctx();
718 let scalar = Scalar::fixed_size_list(
719 Arc::new(DType::Primitive(PType::I32, Nullability::NonNullable)),
720 vec![Scalar::from(7i32), Scalar::from(7i32)],
721 Nullability::NonNullable,
722 );
723 let array = ConstantArray::new(scalar, 1_000);
724
725 let mut builder = builder_with_capacity(array.dtype(), array.len());
726 array
727 .into_array()
728 .append_to_builder(builder.as_mut(), &mut ctx)?;
729 let result = builder.finish();
730
731 let elements = result.as_::<FixedSizeList>().elements().clone();
732 assert!(
733 elements
734 .as_::<Chunked>()
735 .iter_chunks()
736 .all(|chunk| chunk.is::<Constant>()),
737 "a uniform tile should have stayed constant-encoded",
738 );
739 Ok(())
740 }
741
742 #[test]
743 fn test_extension_constant_append() -> VortexResult<()> {
744 let scalar = Scalar::extension::<Date>(TimeUnit::Days, Scalar::from(Some(42i32)));
745 assert_append_matches_canonical(ConstantArray::new(scalar, 5))
746 }
747
748 #[test]
751 fn test_extension_constant_append_keeps_storage_constant() -> VortexResult<()> {
752 let mut ctx = crate::array_session().create_execution_ctx();
753 let scalar = Scalar::extension::<Date>(TimeUnit::Days, Scalar::from(Some(42i32)));
754 let array = ConstantArray::new(scalar, 1_000);
755
756 let mut builder = builder_with_capacity(array.dtype(), array.len());
757 array
758 .into_array()
759 .append_to_builder(builder.as_mut(), &mut ctx)?;
760 let result = builder.finish();
761
762 assert!(
763 result.as_::<Extension>().storage_array().is::<Constant>(),
764 "the storage should have stayed constant-encoded",
765 );
766 Ok(())
767 }
768}