1use std::fmt::Debug;
5use std::hash::Hash;
6use std::hash::Hasher;
7
8use vortex_buffer::ByteBufferMut;
9use vortex_error::VortexExpect;
10use vortex_error::VortexResult;
11use vortex_error::vortex_ensure;
12use vortex_error::vortex_panic;
13use vortex_session::VortexSession;
14use vortex_session::registry::CachedId;
15
16use crate::ArrayEq;
17use crate::ArrayHash;
18use crate::ArrayParts;
19use crate::ArrayRef;
20use crate::EqMode;
21use crate::ExecutionCtx;
22use crate::ExecutionResult;
23use crate::IntoArray;
24use crate::array::Array;
25use crate::array::ArrayId;
26use crate::array::ArrayView;
27use crate::array::VTable;
28use crate::array::unsupported_buffer_replacement;
29use crate::arrays::constant::ConstantData;
30use crate::arrays::constant::compute::rules::PARENT_RULES;
31use crate::arrays::constant::vtable::canonical::constant_canonicalize;
32use crate::buffer::BufferHandle;
33use crate::builders::ArrayBuilder;
34use crate::builders::BoolBuilder;
35use crate::builders::DecimalBuilder;
36use crate::builders::NullBuilder;
37use crate::builders::PrimitiveBuilder;
38use crate::builders::VarBinViewBuilder;
39use crate::canonical::Canonical;
40use crate::dtype::DType;
41use crate::match_each_decimal_value;
42use crate::match_each_native_ptype;
43use crate::match_each_varbin_builder;
44use crate::scalar::DecimalValue;
45use crate::scalar::Scalar;
46use crate::scalar::ScalarValue;
47use crate::serde::ArrayChildren;
48pub(crate) mod canonical;
49mod operations;
50mod validity;
51
52pub type ConstantArray = Array<Constant>;
54
55#[derive(Clone, Debug)]
56pub struct Constant;
57
58impl ArrayHash for ConstantData {
59 fn array_hash<H: Hasher>(&self, state: &mut H, _accuracy: EqMode) {
60 self.scalar.hash(state);
61 }
62}
63
64impl ArrayEq for ConstantData {
65 fn array_eq(&self, other: &Self, _accuracy: EqMode) -> bool {
66 self.scalar == other.scalar
67 }
68}
69
70impl VTable for Constant {
71 type TypedArrayData = ConstantData;
72
73 type OperationsVTable = Self;
74 type ValidityVTable = Self;
75
76 fn id(&self) -> ArrayId {
77 static ID: CachedId = CachedId::new("vortex.constant");
78 *ID
79 }
80
81 fn validate(
82 &self,
83 data: &ConstantData,
84 dtype: &DType,
85 _len: usize,
86 _slots: &[Option<ArrayRef>],
87 ) -> VortexResult<()> {
88 vortex_ensure!(
89 data.scalar.dtype() == dtype,
90 "ConstantArray scalar dtype does not match outer dtype"
91 );
92 Ok(())
93 }
94
95 fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
96 1
97 }
98
99 fn buffer(array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
100 match idx {
101 0 => BufferHandle::new_host(
102 ScalarValue::to_proto_bytes::<ByteBufferMut>(array.scalar.value()).freeze(),
103 ),
104 _ => vortex_panic!("ConstantArray buffer index {idx} out of bounds"),
105 }
106 }
107
108 fn buffer_name(_array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
109 match idx {
110 0 => Some("scalar".to_string()),
111 _ => None,
112 }
113 }
114
115 fn with_buffers(
116 &self,
117 array: ArrayView<'_, Self>,
118 buffers: &[BufferHandle],
119 ) -> VortexResult<ArrayParts<Self>> {
120 unsupported_buffer_replacement(array, buffers)
121 }
122
123 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
124 vortex_panic!("ConstantArray slot_name index {idx} out of bounds")
125 }
126
127 fn serialize(
128 _array: ArrayView<'_, Self>,
129 _session: &VortexSession,
130 ) -> VortexResult<Option<Vec<u8>>> {
131 Ok(Some(vec![]))
134 }
135
136 fn deserialize(
137 &self,
138 dtype: &DType,
139 len: usize,
140 _metadata: &[u8],
141
142 buffers: &[BufferHandle],
143 _children: &dyn ArrayChildren,
144 session: &VortexSession,
145 ) -> VortexResult<ArrayParts<Self>> {
146 vortex_ensure!(
147 buffers.len() == 1,
148 "Expected 1 buffer, got {}",
149 buffers.len()
150 );
151
152 let buffer = buffers[0].clone().try_to_host_sync()?;
153 let bytes: &[u8] = buffer.as_ref();
154
155 let scalar_value = ScalarValue::from_proto_bytes(bytes, dtype, session)?;
156 let scalar = Scalar::try_new(dtype.clone(), scalar_value)?;
157
158 Ok(ArrayParts::new(
159 self.clone(),
160 dtype.clone(),
161 len,
162 ConstantData::new(scalar),
163 ))
164 }
165
166 fn reduce_parent(
167 array: ArrayView<'_, Self>,
168 parent: &ArrayRef,
169 child_idx: usize,
170 ) -> VortexResult<Option<ArrayRef>> {
171 PARENT_RULES.evaluate(array, parent, child_idx)
172 }
173
174 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
175 Ok(ExecutionResult::done(constant_canonicalize(
176 array.as_view(),
177 ctx,
178 )?))
179 }
180
181 fn append_to_builder(
182 array: ArrayView<'_, Self>,
183 builder: &mut dyn ArrayBuilder,
184 ctx: &mut ExecutionCtx,
185 ) -> VortexResult<()> {
186 let n = array.len();
187 let scalar = array.scalar();
188
189 match array.dtype() {
190 DType::Null => append_value_or_nulls::<NullBuilder>(builder, true, n, |_| {}),
191 DType::Bool(_) => {
192 append_value_or_nulls::<BoolBuilder>(builder, scalar.is_null(), n, |b| {
193 b.append_values(
194 scalar
195 .as_bool()
196 .value()
197 .vortex_expect("non-null bool scalar must have a value"),
198 n,
199 );
200 })
201 }
202 DType::Primitive(ptype, _) => {
203 match_each_native_ptype!(ptype, |P| {
204 append_value_or_nulls::<PrimitiveBuilder<P>>(
205 builder,
206 scalar.is_null(),
207 n,
208 |b| {
209 let value = P::try_from(scalar)
210 .vortex_expect("Couldn't unwrap constant scalar to primitive");
211 b.append_n_values(value, n);
212 },
213 );
214 });
215 }
216 DType::Decimal(..) => {
217 append_value_or_nulls::<DecimalBuilder>(builder, scalar.is_null(), n, |b| {
218 let value = scalar
219 .as_decimal()
220 .decimal_value()
221 .vortex_expect("non-null decimal scalar must have a value");
222 match_each_decimal_value!(value, |v| { b.append_n_values(v, n) });
223 });
224 }
225 DType::Utf8(_) => {
226 if let Some(result) = match_each_varbin_builder!(builder, |builder| {
227 builder.append_scalar_repeated(scalar, n)
228 }) {
229 result?;
230 } else {
231 append_value_or_nulls::<VarBinViewBuilder>(builder, scalar.is_null(), n, |b| {
232 let value = scalar
233 .as_utf8()
234 .value()
235 .vortex_expect("non-null utf8 scalar must have a value");
236 b.append_n_values(value.as_bytes(), n);
237 });
238 }
239 }
240 DType::Binary(_) => {
241 if let Some(result) = match_each_varbin_builder!(builder, |builder| {
242 builder.append_scalar_repeated(scalar, n)
243 }) {
244 result?;
245 } else {
246 append_value_or_nulls::<VarBinViewBuilder>(builder, scalar.is_null(), n, |b| {
247 let value = scalar
248 .as_binary()
249 .value()
250 .vortex_expect("non-null binary scalar must have a value");
251 b.append_n_values(value, n);
252 });
253 }
254 }
255 _ => {
257 let canonical = array
258 .array()
259 .clone()
260 .execute::<Canonical>(ctx)?
261 .into_array();
262 canonical.append_to_builder(builder, ctx)?;
263 }
264 }
265
266 Ok(())
267 }
268}
269
270fn append_value_or_nulls<B: ArrayBuilder + 'static>(
275 builder: &mut dyn ArrayBuilder,
276 is_null: bool,
277 n: usize,
278 fill: impl FnOnce(&mut B),
279) {
280 let b = builder
281 .as_any_mut()
282 .downcast_mut::<B>()
283 .vortex_expect("builder dtype must match array dtype");
284 if is_null {
285 unsafe { b.append_nulls_unchecked(n) };
287 } else {
288 fill(b);
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use rstest::rstest;
295 use vortex_error::VortexResult;
296
297 use crate::IntoArray;
298 use crate::VortexSessionExecute;
299 use crate::arrays::ConstantArray;
300 use crate::arrays::constant::vtable::canonical::constant_canonicalize;
301 use crate::assert_arrays_eq;
302 use crate::builders::builder_with_capacity;
303 use crate::dtype::DType;
304 use crate::dtype::Nullability;
305 use crate::dtype::PType;
306 use crate::dtype::StructFields;
307 use crate::scalar::Scalar;
308
309 fn assert_append_matches_canonical(array: ConstantArray) -> VortexResult<()> {
311 let mut ctx = crate::array_session().create_execution_ctx();
312
313 let expected = constant_canonicalize(array.as_view(), &mut ctx)?.into_array();
314 let mut builder = builder_with_capacity(array.dtype(), array.len());
315 array
316 .into_array()
317 .append_to_builder(builder.as_mut(), &mut ctx)?;
318 let result = builder.finish();
319 assert_arrays_eq!(&result, &expected, &mut ctx);
320 Ok(())
321 }
322
323 #[test]
324 fn test_null_constant_append() -> VortexResult<()> {
325 assert_append_matches_canonical(ConstantArray::new(Scalar::null(DType::Null), 5))
326 }
327
328 #[test]
329 fn test_with_buffers_rejects_serialized_scalar_buffer() {
330 let array =
331 ConstantArray::new(Scalar::primitive(42i32, Nullability::NonNullable), 3).into_array();
332 let buffers = array.buffer_handles();
333
334 let Err(err) = (unsafe { array.with_buffers(buffers) }) else {
337 panic!("ConstantArray should reject replacing its serialized scalar buffer");
338 };
339 assert!(
340 err.to_string()
341 .contains("does not support in-memory buffer replacement")
342 );
343 }
344
345 #[rstest]
346 #[case::bool_true(true, 5)]
347 #[case::bool_false(false, 3)]
348 fn test_bool_constant_append(#[case] value: bool, #[case] n: usize) -> VortexResult<()> {
349 assert_append_matches_canonical(ConstantArray::new(
350 Scalar::bool(value, Nullability::NonNullable),
351 n,
352 ))
353 }
354
355 #[test]
356 fn test_bool_null_constant_append() -> VortexResult<()> {
357 assert_append_matches_canonical(ConstantArray::new(
358 Scalar::null(DType::Bool(Nullability::Nullable)),
359 4,
360 ))
361 }
362
363 #[rstest]
364 #[case::i32(Scalar::primitive(42i32, Nullability::NonNullable), 5)]
365 #[case::u8(Scalar::primitive(7u8, Nullability::NonNullable), 3)]
366 #[case::f64(Scalar::primitive(1.5f64, Nullability::NonNullable), 4)]
367 #[case::i32_null(Scalar::null(DType::Primitive(PType::I32, Nullability::Nullable)), 3)]
368 fn test_primitive_constant_append(
369 #[case] scalar: Scalar,
370 #[case] n: usize,
371 ) -> VortexResult<()> {
372 assert_append_matches_canonical(ConstantArray::new(scalar, n))
373 }
374
375 #[rstest]
376 #[case::utf8_inline("hi", 5)] #[case::utf8_noninline("hello world!!", 5)] #[case::utf8_empty("", 3)]
379 #[case::utf8_n_zero("hello world!!", 0)] fn test_utf8_constant_append(#[case] value: &str, #[case] n: usize) -> VortexResult<()> {
381 assert_append_matches_canonical(ConstantArray::new(
382 Scalar::utf8(value, Nullability::NonNullable),
383 n,
384 ))
385 }
386
387 #[test]
388 fn test_utf8_null_constant_append() -> VortexResult<()> {
389 assert_append_matches_canonical(ConstantArray::new(
390 Scalar::null(DType::Utf8(Nullability::Nullable)),
391 4,
392 ))
393 }
394
395 #[rstest]
396 #[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<()> {
399 assert_append_matches_canonical(ConstantArray::new(
400 Scalar::binary(value, Nullability::NonNullable),
401 n,
402 ))
403 }
404
405 #[test]
406 fn test_binary_null_constant_append() -> VortexResult<()> {
407 assert_append_matches_canonical(ConstantArray::new(
408 Scalar::null(DType::Binary(Nullability::Nullable)),
409 4,
410 ))
411 }
412
413 #[test]
414 fn test_struct_constant_append() -> VortexResult<()> {
415 let fields = StructFields::new(
416 ["x", "y"].into(),
417 vec![
418 DType::Primitive(PType::I32, Nullability::NonNullable),
419 DType::Utf8(Nullability::NonNullable),
420 ],
421 );
422 let scalar = Scalar::struct_(
423 DType::Struct(fields, Nullability::NonNullable),
424 [
425 Scalar::primitive(42i32, Nullability::NonNullable),
426 Scalar::utf8("hi", Nullability::NonNullable),
427 ],
428 );
429 assert_append_matches_canonical(ConstantArray::new(scalar, 3))
430 }
431
432 #[test]
433 fn test_null_struct_constant_append() -> VortexResult<()> {
434 let fields = StructFields::new(
435 ["x"].into(),
436 vec![DType::Primitive(PType::I32, Nullability::Nullable)],
437 );
438 let dtype = DType::Struct(fields, Nullability::Nullable);
439 assert_append_matches_canonical(ConstantArray::new(Scalar::null(dtype), 4))
440 }
441}