1use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::hash::Hasher;
8
9use prost::Message as _;
10use vortex_array::Array;
11use vortex_array::ArrayEq;
12use vortex_array::ArrayHash;
13use vortex_array::ArrayId;
14use vortex_array::ArrayParts;
15use vortex_array::ArrayRef;
16use vortex_array::ArrayView;
17use vortex_array::Canonical;
18use vortex_array::EqMode;
19use vortex_array::ExecutionCtx;
20use vortex_array::ExecutionResult;
21use vortex_array::IntoArray;
22use vortex_array::array_slots;
23use vortex_array::buffer::BufferHandle;
24use vortex_array::builders::ArrayBuilder;
25use vortex_array::builders::VarBinViewBuilder;
26use vortex_array::dtype::DType;
27use vortex_array::dtype::Nullability;
28use vortex_array::dtype::PType;
29use vortex_array::serde::ArrayChildren;
30use vortex_array::validity::Validity;
31use vortex_array::vtable::VTable;
32use vortex_array::vtable::ValidityVTable;
33use vortex_array::vtable::child_to_validity;
34use vortex_array::vtable::validity_to_child;
35use vortex_buffer::ByteBuffer;
36use vortex_error::VortexResult;
37use vortex_error::vortex_bail;
38use vortex_error::vortex_ensure;
39use vortex_error::vortex_err;
40use vortex_error::vortex_panic;
41use vortex_session::VortexSession;
42use vortex_session::registry::CachedId;
43
44use crate::canonical::canonicalize_onpair;
45use crate::canonical::onpair_decode_views;
46use crate::rules::RULES;
47
48pub type OnPairArray = Array<OnPair>;
50
51#[derive(Clone, prost::Message)]
65pub struct OnPairMetadata {
66 #[prost(enumeration = "PType", tag = "1")]
68 pub uncompressed_lengths_ptype: i32,
69 #[prost(uint32, tag = "2")]
72 pub bits: u32,
73 #[prost(uint32, tag = "3")]
76 pub dict_size: u32,
77 #[prost(uint64, tag = "4")]
80 pub total_tokens: u64,
81 #[prost(enumeration = "PType", tag = "5")]
84 pub dict_offsets_ptype: i32,
85 #[prost(enumeration = "PType", tag = "6")]
88 pub codes_ptype: i32,
89 #[prost(enumeration = "PType", tag = "7")]
91 pub codes_offsets_ptype: i32,
92}
93
94impl OnPairMetadata {
95 pub fn get_uncompressed_lengths_ptype(&self) -> VortexResult<PType> {
96 PType::try_from(self.uncompressed_lengths_ptype)
97 .map_err(|_| vortex_err!("Invalid PType {}", self.uncompressed_lengths_ptype))
98 }
99}
100
101#[array_slots(OnPair)]
102pub struct OnPairSlots {
103 pub dict_offsets: ArrayRef,
106 pub codes: ArrayRef,
110 pub codes_offsets: ArrayRef,
113 pub uncompressed_lengths: ArrayRef,
116 pub validity: Option<ArrayRef>,
118}
119
120#[derive(Clone)]
127pub struct OnPairData {
128 dict_bytes: BufferHandle,
141 bits: u32,
142 len: usize,
143}
144
145impl OnPairData {
146 pub fn new(dict_bytes: BufferHandle, bits: u32, len: usize) -> Self {
147 Self {
148 dict_bytes,
149 bits,
150 len,
151 }
152 }
153
154 pub fn len(&self) -> usize {
155 self.len
156 }
157
158 pub fn is_empty(&self) -> bool {
159 self.len == 0
160 }
161
162 pub fn bits(&self) -> u32 {
163 self.bits
164 }
165
166 pub fn dict_bytes(&self) -> &ByteBuffer {
167 self.dict_bytes.as_host()
168 }
169
170 pub fn dict_bytes_handle(&self) -> &BufferHandle {
171 &self.dict_bytes
172 }
173}
174
175impl Display for OnPairData {
176 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
177 write!(
178 f,
179 "len: {}, bits: {}, dict_bytes_len: {}",
180 self.len,
181 self.bits,
182 self.dict_bytes.len()
183 )
184 }
185}
186
187impl Debug for OnPairData {
188 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
189 f.debug_struct("OnPairData")
190 .field("len", &self.len)
191 .field("bits", &self.bits)
192 .field("dict_bytes_len", &self.dict_bytes.len())
193 .finish()
194 }
195}
196
197impl ArrayHash for OnPairData {
198 fn array_hash<H: Hasher>(&self, state: &mut H, accuracy: EqMode) {
199 self.dict_bytes.as_host().array_hash(state, accuracy);
200 state.write_u32(self.bits);
201 }
202}
203
204impl ArrayEq for OnPairData {
205 fn array_eq(&self, other: &Self, accuracy: EqMode) -> bool {
206 self.bits == other.bits
207 && self
208 .dict_bytes
209 .as_host()
210 .array_eq(other.dict_bytes.as_host(), accuracy)
211 }
212}
213
214#[derive(Clone, Debug)]
216pub struct OnPair;
217
218impl OnPair {
219 #[expect(clippy::too_many_arguments, reason = "every child is a real input")]
221 pub fn try_new(
222 dtype: DType,
223 dict_bytes: BufferHandle,
224 dict_offsets: ArrayRef,
225 codes: ArrayRef,
226 codes_offsets: ArrayRef,
227 uncompressed_lengths: ArrayRef,
228 validity: Validity,
229 bits: u32,
230 ) -> VortexResult<OnPairArray> {
231 validate_parts(
232 &dtype,
233 &dict_offsets,
234 &codes,
235 &codes_offsets,
236 &uncompressed_lengths,
237 bits,
238 )?;
239 let len = uncompressed_lengths.len();
240 let data = OnPairData::new(dict_bytes, bits, len);
241 let slots = OnPairSlots {
242 dict_offsets,
243 codes,
244 codes_offsets,
245 uncompressed_lengths,
246 validity: validity_to_child(&validity, len),
247 }
248 .into_slots();
249 Ok(unsafe {
250 Array::from_parts_unchecked(ArrayParts::new(OnPair, dtype, len, data).with_slots(slots))
251 })
252 }
253
254 #[expect(clippy::too_many_arguments, reason = "every child is a real input")]
255 pub(crate) unsafe fn new_unchecked(
256 dtype: DType,
257 dict_bytes: BufferHandle,
258 dict_offsets: ArrayRef,
259 codes: ArrayRef,
260 codes_offsets: ArrayRef,
261 uncompressed_lengths: ArrayRef,
262 validity: Validity,
263 bits: u32,
264 ) -> OnPairArray {
265 let len = uncompressed_lengths.len();
266 let data = OnPairData::new(dict_bytes, bits, len);
267 let slots = OnPairSlots {
268 dict_offsets,
269 codes,
270 codes_offsets,
271 uncompressed_lengths,
272 validity: validity_to_child(&validity, len),
273 }
274 .into_slots();
275 unsafe {
276 Array::from_parts_unchecked(ArrayParts::new(OnPair, dtype, len, data).with_slots(slots))
277 }
278 }
279}
280
281fn validate_parts(
282 dtype: &DType,
283 dict_offsets: &ArrayRef,
284 codes: &ArrayRef,
285 codes_offsets: &ArrayRef,
286 uncompressed_lengths: &ArrayRef,
287 bits: u32,
288) -> VortexResult<()> {
289 vortex_ensure!(
290 matches!(dtype, DType::Binary(_) | DType::Utf8(_)),
291 "OnPair arrays must be Binary or Utf8, found {dtype}"
292 );
293 vortex_ensure!((9..=16).contains(&bits), "bits {bits} out of range [9, 16]");
294
295 if !dict_offsets.dtype().is_int() || dict_offsets.dtype().is_nullable() {
296 vortex_bail!(InvalidArgument: "dict_offsets must be non-nullable integer");
297 }
298 if !codes.dtype().is_int() || codes.dtype().is_nullable() {
299 vortex_bail!(InvalidArgument: "codes must be non-nullable integer");
300 }
301 if !codes_offsets.dtype().is_int() || codes_offsets.dtype().is_nullable() {
302 vortex_bail!(InvalidArgument: "codes_offsets must be non-nullable integer");
303 }
304 if !uncompressed_lengths.dtype().is_int() || uncompressed_lengths.dtype().is_nullable() {
305 vortex_bail!(InvalidArgument: "uncompressed_lengths must be non-nullable integer");
306 }
307 if codes_offsets.len() != uncompressed_lengths.len() + 1 {
308 vortex_bail!(InvalidArgument:
309 "codes_offsets.len ({}) != uncompressed_lengths.len + 1 ({})",
310 codes_offsets.len(),
311 uncompressed_lengths.len() + 1
312 );
313 }
314 Ok(())
315}
316
317impl VTable for OnPair {
318 type TypedArrayData = OnPairData;
319 type OperationsVTable = Self;
320 type ValidityVTable = Self;
321
322 fn id(&self) -> ArrayId {
323 static ID: CachedId = CachedId::new("vortex.onpair");
324 *ID
325 }
326
327 fn validate(
328 &self,
329 data: &Self::TypedArrayData,
330 dtype: &DType,
331 len: usize,
332 slots: &[Option<ArrayRef>],
333 ) -> VortexResult<()> {
334 let s = OnPairSlotsView::from_slots(slots);
335 validate_parts(
336 dtype,
337 s.dict_offsets,
338 s.codes,
339 s.codes_offsets,
340 s.uncompressed_lengths,
341 data.bits,
342 )?;
343 if s.uncompressed_lengths.len() != len {
344 vortex_bail!(InvalidArgument: "uncompressed_lengths must have same len as outer array");
345 }
346 if data.len != len {
347 vortex_bail!(InvalidArgument: "OnPairData len {} != outer len {}", data.len, len);
348 }
349 Ok(())
350 }
351
352 fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
353 1
354 }
355
356 fn buffer(array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
357 match idx {
358 0 => array.dict_bytes_handle().clone(),
359 _ => vortex_panic!("OnPairArray buffer index {idx} out of bounds"),
360 }
361 }
362
363 fn buffer_name(_array: ArrayView<'_, Self>, idx: usize) -> Option<String> {
364 match idx {
365 0 => Some("dict_bytes".to_string()),
366 _ => vortex_panic!("OnPairArray buffer_name index {idx} out of bounds"),
367 }
368 }
369
370 fn with_buffers(
371 &self,
372 array: ArrayView<'_, Self>,
373 buffers: &[BufferHandle],
374 ) -> VortexResult<ArrayParts<Self>> {
375 vortex_ensure!(
376 buffers.len() == 1,
377 "Expected 1 buffer, got {}",
378 buffers.len()
379 );
380 let mut data = array.data().clone();
381 data.dict_bytes = buffers[0].clone();
382 Ok(
383 ArrayParts::new(self.clone(), array.dtype().clone(), array.len(), data)
384 .with_slots(array.slots().iter().cloned().collect()),
385 )
386 }
387
388 fn serialize(
389 array: ArrayView<'_, Self>,
390 _session: &VortexSession,
391 ) -> VortexResult<Option<Vec<u8>>> {
392 let dict_size = u32::try_from(array.dict_offsets().len().saturating_sub(1))
393 .map_err(|_| vortex_err!("OnPair dict_size exceeds u32"))?;
394 let total_tokens = array.codes().len() as u64;
395 Ok(Some(
396 OnPairMetadata {
397 uncompressed_lengths_ptype: array.uncompressed_lengths().dtype().as_ptype().into(),
398 bits: array.bits(),
399 dict_size,
400 total_tokens,
401 dict_offsets_ptype: array.dict_offsets().dtype().as_ptype().into(),
402 codes_ptype: array.codes().dtype().as_ptype().into(),
403 codes_offsets_ptype: array.codes_offsets().dtype().as_ptype().into(),
404 }
405 .encode_to_vec(),
406 ))
407 }
408
409 fn deserialize(
410 &self,
411 dtype: &DType,
412 len: usize,
413 metadata: &[u8],
414 buffers: &[BufferHandle],
415 children: &dyn ArrayChildren,
416 _session: &VortexSession,
417 ) -> VortexResult<ArrayParts<Self>> {
418 if buffers.len() != 1 {
419 vortex_bail!(InvalidArgument: "Expected 1 buffer, got {}", buffers.len());
420 }
421 let metadata = OnPairMetadata::decode(metadata)?;
422 let uncompressed_ptype = metadata.get_uncompressed_lengths_ptype()?;
423
424 let dict_offsets_len = metadata.dict_size as usize + 1;
428 let total_tokens = usize::try_from(metadata.total_tokens)
429 .map_err(|_| vortex_err!("total_tokens {} overflows usize", metadata.total_tokens))?;
430 let dict_offsets_ptype = PType::try_from(metadata.dict_offsets_ptype).map_err(|_| {
434 vortex_err!("invalid dict_offsets_ptype {}", metadata.dict_offsets_ptype)
435 })?;
436 let codes_ptype = PType::try_from(metadata.codes_ptype)
437 .map_err(|_| vortex_err!("invalid codes_ptype {}", metadata.codes_ptype))?;
438 let codes_offsets_ptype = PType::try_from(metadata.codes_offsets_ptype).map_err(|_| {
439 vortex_err!(
440 "invalid codes_offsets_ptype {}",
441 metadata.codes_offsets_ptype
442 )
443 })?;
444 let dict_offsets = children.get(
445 0,
446 &DType::Primitive(dict_offsets_ptype, Nullability::NonNullable),
447 dict_offsets_len,
448 )?;
449 let codes = children.get(
450 1,
451 &DType::Primitive(codes_ptype, Nullability::NonNullable),
452 total_tokens,
453 )?;
454 let codes_offsets = children.get(
455 2,
456 &DType::Primitive(codes_offsets_ptype, Nullability::NonNullable),
457 len + 1,
458 )?;
459 let uncompressed_lengths = children.get(
460 3,
461 &DType::Primitive(uncompressed_ptype, Nullability::NonNullable),
462 len,
463 )?;
464 let validity = match children.len() {
465 4 => Validity::from(dtype.nullability()),
466 5 => Validity::Array(children.get(4, &Validity::DTYPE, len)?),
467 other => vortex_bail!(InvalidArgument: "Expected 4 or 5 children, got {other}"),
468 };
469
470 let data = OnPairData::new(buffers[0].clone(), metadata.bits, len);
471 let slots = OnPairSlots {
472 dict_offsets,
473 codes,
474 codes_offsets,
475 uncompressed_lengths,
476 validity: validity_to_child(&validity, len),
477 }
478 .into_slots();
479 Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
480 }
481
482 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
483 OnPairSlots::NAMES[idx].to_string()
484 }
485
486 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
487 canonicalize_onpair(array.as_view(), ctx).map(ExecutionResult::done)
488 }
489
490 fn append_to_builder(
491 array: ArrayView<'_, Self>,
492 builder: &mut dyn ArrayBuilder,
493 ctx: &mut ExecutionCtx,
494 ) -> VortexResult<()> {
495 let Some(builder) = builder.as_any_mut().downcast_mut::<VarBinViewBuilder>() else {
496 builder.extend_from_array(
497 &array
498 .array()
499 .clone()
500 .execute::<Canonical>(ctx)?
501 .into_array(),
502 );
503 return Ok(());
504 };
505
506 let next_buffer_index = builder.completed_block_count() + u32::from(builder.in_progress());
507 let (buffers, views) = onpair_decode_views(array, next_buffer_index, ctx)?;
508 builder.push_buffer_and_adjusted_views(
509 &buffers,
510 &views,
511 array
512 .array()
513 .validity()?
514 .execute_mask(array.array().len(), ctx)?,
515 );
516 Ok(())
517 }
518
519 fn reduce_parent(
520 array: ArrayView<'_, Self>,
521 parent: &ArrayRef,
522 child_idx: usize,
523 ) -> VortexResult<Option<ArrayRef>> {
524 RULES.evaluate(array, parent, child_idx)
525 }
526}
527
528impl ValidityVTable<OnPair> for OnPair {
529 fn validity(array: ArrayView<'_, OnPair>) -> VortexResult<Validity> {
530 Ok(child_to_validity(
531 array.slots()[OnPairSlots::VALIDITY].as_ref(),
532 array.dtype().nullability(),
533 ))
534 }
535}
536
537pub trait OnPairArrayExt: OnPairArraySlotsExt {
539 fn array_validity(&self) -> Validity {
540 child_to_validity(
541 self.as_ref().slots()[OnPairSlots::VALIDITY].as_ref(),
542 self.as_ref().dtype().nullability(),
543 )
544 }
545}
546
547impl<T: OnPairArraySlotsExt> OnPairArrayExt for T {}