vortex_array/arrays/variant/vtable/
mod.rs1mod kernel;
5mod operations;
6mod validity;
7
8use prost::Message;
9use vortex_error::VortexExpect;
10use vortex_error::VortexResult;
11use vortex_error::vortex_ensure;
12use vortex_error::vortex_ensure_eq;
13use vortex_error::vortex_panic;
14use vortex_session::VortexSession;
15use vortex_session::registry::CachedId;
16use vortex_utils::aliases::hash_set::HashSet;
17
18use crate::ArrayRef;
19use crate::ExecutionCtx;
20use crate::ExecutionResult;
21use crate::array::Array;
22use crate::array::ArrayId;
23use crate::array::ArrayParts;
24use crate::array::ArrayView;
25use crate::array::EmptyArrayData;
26use crate::array::VTable;
27use crate::array::with_empty_buffers;
28use crate::arrays::variant::VariantSlots;
29use crate::arrays::variant::compute::rules::RULES;
30use crate::buffer::BufferHandle;
31use crate::dtype::DType;
32use crate::dtype::FieldName;
33use crate::dtype::FieldNames;
34use crate::dtype::Nullability;
35use crate::dtype::StructFields;
36use crate::proto::dtype as pb;
37use crate::scalar::Scalar;
38use crate::scalar::ScalarValue;
39use crate::serde::ArrayChildren;
40
41pub type VariantArray = Array<Variant>;
43
44pub(crate) fn initialize(session: &VortexSession) {
45 kernel::initialize(session);
46}
47
48#[derive(Clone, Debug)]
49pub struct Variant;
50
51#[derive(Clone, prost::Message)]
52struct VariantMetadataProto {
53 #[prost(message, optional, tag = "1")]
54 pub shredded_dtype: Option<pb::DType>,
55}
56
57impl VTable for Variant {
58 type TypedArrayData = EmptyArrayData;
59
60 type OperationsVTable = Self;
61
62 type ValidityVTable = Self;
63
64 fn id(&self) -> ArrayId {
65 static ID: CachedId = CachedId::new("vortex.variant");
66 *ID
67 }
68
69 fn validate(
70 &self,
71 _data: &Self::TypedArrayData,
72 dtype: &DType,
73 len: usize,
74 slots: &[Option<ArrayRef>],
75 ) -> VortexResult<()> {
76 vortex_ensure_eq!(slots.len(), VariantSlots::COUNT);
77 vortex_ensure!(
78 slots[VariantSlots::CORE_STORAGE].is_some(),
79 "VariantArray core_storage slot must be present"
80 );
81 let core_storage = slots[VariantSlots::CORE_STORAGE]
82 .as_ref()
83 .vortex_expect("validated core_storage slot presence");
84 vortex_ensure!(
85 matches!(dtype, DType::Variant(_)),
86 "Expected Variant DType, got {dtype}"
87 );
88 vortex_ensure_eq!(
89 core_storage.dtype(),
90 dtype,
91 "VariantArray core_storage dtype does not match outer dtype",
92 );
93 vortex_ensure_eq!(
94 core_storage.len(),
95 len,
96 "VariantArray core_storage length does not match outer length",
97 );
98 if let Some(shredded) = slots[VariantSlots::SHREDDED].as_ref() {
99 vortex_ensure_eq!(
100 shredded.len(),
101 len,
102 "VariantArray shredded length does not match outer length",
103 );
104 }
105 Ok(())
106 }
107
108 fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
109 0
110 }
111
112 fn buffer(_array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
113 vortex_panic!("VariantArray buffer index {idx} out of bounds")
114 }
115
116 fn buffer_name(_array: ArrayView<'_, Self>, _idx: usize) -> Option<String> {
117 None
118 }
119
120 fn with_buffers(
121 &self,
122 array: ArrayView<'_, Self>,
123 buffers: &[BufferHandle],
124 ) -> VortexResult<ArrayParts<Self>> {
125 with_empty_buffers(self, array, buffers)
126 }
127
128 fn serialize(
129 array: ArrayView<'_, Self>,
130 _session: &VortexSession,
131 ) -> VortexResult<Option<Vec<u8>>> {
132 let shredded_dtype = array.slots()[VariantSlots::SHREDDED]
133 .as_ref()
134 .map(|shredded| shredded.dtype().try_into())
135 .transpose()?;
136 Ok(Some(
137 VariantMetadataProto { shredded_dtype }.encode_to_vec(),
138 ))
139 }
140
141 fn deserialize(
142 &self,
143 dtype: &DType,
144 len: usize,
145 metadata: &[u8],
146 buffers: &[BufferHandle],
147 children: &dyn ArrayChildren,
148 session: &VortexSession,
149 ) -> VortexResult<ArrayParts<Self>> {
150 vortex_ensure!(
151 buffers.is_empty(),
152 "VariantArray expects 0 buffers, got {}",
153 buffers.len()
154 );
155 let proto = VariantMetadataProto::decode(metadata)?;
156 let shredded_dtype = proto
157 .shredded_dtype
158 .as_ref()
159 .map(|dtype| DType::from_proto(dtype, session))
160 .transpose()?;
161 vortex_ensure!(matches!(dtype, DType::Variant(_)), "Expected Variant DType");
162 let expected_children = 1 + usize::from(shredded_dtype.is_some());
163 vortex_ensure_eq!(children.len(), expected_children);
164 let core_storage = children.get(0, dtype, len)?;
165 let shredded = shredded_dtype
166 .map(|dtype| children.get(1, &dtype, len))
167 .transpose()?;
168 Ok(ArrayParts::new(
169 self.clone(),
170 dtype.clone(),
171 len,
172 EmptyArrayData,
173 VariantSlots {
174 core_storage,
175 shredded,
176 }
177 .into_slots(),
178 ))
179 }
180
181 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
182 match VariantSlots::NAMES.get(idx) {
183 Some(name) => (*name).to_string(),
184 None => vortex_panic!("VariantArray slot_name index {idx} out of bounds"),
185 }
186 }
187
188 fn execute(array: Array<Self>, _ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
189 Ok(ExecutionResult::done(array))
190 }
191
192 fn reduce_parent(
193 array: ArrayView<'_, Self>,
194 parent: &ArrayRef,
195 child_idx: usize,
196 ) -> VortexResult<Option<ArrayRef>> {
197 RULES.evaluate(array, parent, child_idx)
198 }
199}
200
201fn merge_typed_scalar_as_variant(
202 typed_scalar: Scalar,
203 fallback_scalar: Option<Scalar>,
204 dtype: &DType,
205) -> VortexResult<Scalar> {
206 let scalar = if typed_scalar.is_null() {
207 fallback_scalar.unwrap_or_else(|| Scalar::null(dtype.clone()))
208 } else if matches!(
209 typed_scalar.dtype(),
210 DType::List(..) | DType::FixedSizeList(..)
211 ) {
212 Scalar::variant(typed_list_as_variant_payload(typed_scalar)?)
213 } else if typed_scalar.dtype().is_struct() {
214 merge_typed_object_as_variant(typed_scalar, fallback_scalar)?
215 } else if typed_scalar.dtype().is_variant() {
216 typed_scalar
217 } else {
218 Scalar::variant(typed_scalar)
219 };
220
221 if scalar.dtype() == dtype {
222 Ok(scalar)
223 } else {
224 scalar.cast(dtype)
225 }
226}
227
228fn typed_list_as_variant_payload(typed_scalar: Scalar) -> VortexResult<Scalar> {
229 let list = typed_scalar.as_list();
230 let elements = list
231 .elements()
232 .unwrap_or_default()
233 .into_iter()
234 .map(|element| {
235 if element.dtype().is_variant() {
236 element
237 } else {
238 Scalar::variant(element)
239 }
240 })
241 .collect();
242 Ok(Scalar::list(
243 DType::Variant(Nullability::NonNullable),
244 elements,
245 Nullability::NonNullable,
246 ))
247}
248
249fn merge_typed_object_as_variant(
250 typed_scalar: Scalar,
251 fallback_scalar: Option<Scalar>,
252) -> VortexResult<Scalar> {
253 let fallback_inner = fallback_scalar
254 .as_ref()
255 .and_then(|scalar| scalar.as_variant().value())
256 .filter(|scalar| scalar.dtype().is_struct() && !scalar.is_null());
257 let Some(fallback_inner) = fallback_inner else {
258 return Ok(Scalar::variant(typed_scalar));
259 };
260
261 merge_struct_payload(&typed_scalar, Some(fallback_inner)).map(Scalar::variant)
262}
263
264fn merge_struct_payload(typed: &Scalar, raw: Option<&Scalar>) -> VortexResult<Scalar> {
265 let typed_struct = typed.as_struct();
266 let raw_struct = raw
267 .filter(|scalar| scalar.dtype().is_struct() && !scalar.is_null())
268 .map(Scalar::as_struct);
269 let mut present_typed_fields = HashSet::new();
270 let mut names = Vec::new();
271 let mut values = Vec::new();
272
273 for name in typed_struct.names().iter() {
274 let Some(typed_field) = typed_struct.field(name.as_ref()) else {
275 continue;
276 };
277 if typed_field.is_null() {
278 continue;
279 }
280
281 let raw_field = raw_struct.and_then(|raw_struct| raw_struct.field(name.as_ref()));
282 let raw_payload = raw_field.as_ref().and_then(|scalar| {
283 if scalar.dtype().is_variant() {
284 scalar.as_variant().value()
285 } else {
286 Some(scalar)
287 }
288 });
289 let field = if typed_field.dtype().is_struct()
290 && raw_payload.is_some_and(|raw| raw.dtype().is_struct() && !raw.is_null())
291 {
292 Scalar::variant(merge_struct_payload(&typed_field, raw_payload)?)
293 } else if typed_field.dtype().is_variant() {
294 typed_field.cast(&DType::Variant(Nullability::NonNullable))?
295 } else {
296 Scalar::variant(typed_field)
297 };
298
299 present_typed_fields.insert(name.as_ref().to_string());
300 names.push(FieldName::from(name.as_ref()));
301 values.push(field.into_value());
302 }
303
304 if let Some(raw_struct) = raw_struct {
305 for name in raw_struct.names().iter() {
306 if present_typed_fields.contains(name.as_ref()) {
307 continue;
308 }
309 let Some(raw_field) = raw_struct.field(name.as_ref()) else {
310 continue;
311 };
312 if raw_field.is_null() {
313 continue;
314 }
315 let raw_field = if raw_field.dtype().is_variant() {
316 raw_field.cast(&DType::Variant(Nullability::NonNullable))?
317 } else {
318 Scalar::variant(raw_field)
319 };
320 names.push(FieldName::from(name.as_ref()));
321 values.push(raw_field.into_value());
322 }
323 }
324
325 let fields = StructFields::new(
326 FieldNames::from(names),
327 vec![DType::Variant(Nullability::NonNullable); values.len()],
328 );
329 Scalar::try_new(
330 DType::Struct(fields, Nullability::NonNullable),
331 Some(ScalarValue::Tuple(values)),
332 )
333}
334
335#[cfg(test)]
336mod tests {}