|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +// SPDX-FileCopyrightText: Copyright the Vortex contributors |
| 3 | + |
| 4 | +//! Shared physical storage helpers for canonical fixed-width arrays. |
| 5 | +//! |
| 6 | +//! Primitive and decimal arrays have distinct VTables and logical behavior, but use the same |
| 7 | +//! one-buffer-plus-validity physical representation. |
| 8 | +
|
| 9 | +use std::fmt::Display; |
| 10 | +use std::fmt::Formatter; |
| 11 | +use std::hash::Hash; |
| 12 | +use std::hash::Hasher; |
| 13 | + |
| 14 | +use vortex_error::VortexResult; |
| 15 | +use vortex_error::vortex_bail; |
| 16 | +use vortex_error::vortex_ensure; |
| 17 | +use vortex_error::vortex_panic; |
| 18 | + |
| 19 | +use crate::ArrayParts; |
| 20 | +use crate::ArrayRef; |
| 21 | +use crate::EqMode; |
| 22 | +use crate::array::ArrayView; |
| 23 | +use crate::array::VTable; |
| 24 | +use crate::buffer::BufferHandle; |
| 25 | +use crate::dtype::DecimalType; |
| 26 | +use crate::dtype::Nullability; |
| 27 | +use crate::dtype::PType; |
| 28 | +use crate::hash::ArrayEq; |
| 29 | +use crate::hash::ArrayHash; |
| 30 | +use crate::serde::ArrayChildren; |
| 31 | +use crate::validity::Validity; |
| 32 | + |
| 33 | +/// The element-level physical interpretation of a canonical fixed-width values buffer. |
| 34 | +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
| 35 | +pub enum FixedWidthType { |
| 36 | + /// A native primitive value. |
| 37 | + Primitive(PType), |
| 38 | + /// An integer storage type used for logical decimal values. |
| 39 | + Decimal(DecimalType), |
| 40 | +} |
| 41 | + |
| 42 | +impl FixedWidthType { |
| 43 | + /// Returns the number of bytes occupied by one value. |
| 44 | + pub fn byte_width(self) -> usize { |
| 45 | + match self { |
| 46 | + Self::Primitive(ptype) => ptype.byte_width(), |
| 47 | + Self::Decimal(decimal_type) => decimal_type.byte_width(), |
| 48 | + } |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +/// The shared physical data stored by the primitive and decimal VTables. |
| 53 | +#[derive(Clone, Debug)] |
| 54 | +pub struct FixedWidthData { |
| 55 | + pub(crate) physical_type: FixedWidthType, |
| 56 | + pub(crate) buffer: BufferHandle, |
| 57 | + pub(crate) len: usize, |
| 58 | +} |
| 59 | + |
| 60 | +impl Display for FixedWidthData { |
| 61 | + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| 62 | + match self.physical_type { |
| 63 | + FixedWidthType::Primitive(ptype) => write!(f, "physical_type: {ptype}"), |
| 64 | + FixedWidthType::Decimal(values_type) => write!(f, "physical_type: {values_type}"), |
| 65 | + } |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +impl ArrayHash for FixedWidthData { |
| 70 | + fn array_hash<H: Hasher>(&self, state: &mut H, accuracy: EqMode) { |
| 71 | + self.buffer.array_hash(state, accuracy); |
| 72 | + self.physical_type.hash(state); |
| 73 | + self.len.hash(state); |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +impl ArrayEq for FixedWidthData { |
| 78 | + fn array_eq(&self, other: &Self, accuracy: EqMode) -> bool { |
| 79 | + self.buffer.array_eq(&other.buffer, accuracy) |
| 80 | + && self.physical_type == other.physical_type |
| 81 | + && self.len == other.len |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +pub(crate) fn nbuffers<V>(_array: ArrayView<'_, V>) -> usize |
| 86 | +where |
| 87 | + V: VTable<TypedArrayData = FixedWidthData>, |
| 88 | +{ |
| 89 | + 1 |
| 90 | +} |
| 91 | + |
| 92 | +pub(crate) fn buffer<V>(array: ArrayView<'_, V>, idx: usize) -> BufferHandle |
| 93 | +where |
| 94 | + V: VTable<TypedArrayData = FixedWidthData>, |
| 95 | +{ |
| 96 | + match idx { |
| 97 | + 0 => array.data().buffer_handle().clone(), |
| 98 | + _ => vortex_panic!("Fixed-width buffer index {idx} out of bounds"), |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +pub(crate) fn buffer_name<V>(_array: ArrayView<'_, V>, idx: usize) -> Option<String> |
| 103 | +where |
| 104 | + V: VTable<TypedArrayData = FixedWidthData>, |
| 105 | +{ |
| 106 | + match idx { |
| 107 | + 0 => Some("values".to_string()), |
| 108 | + _ => None, |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +pub(crate) fn with_buffers<V>( |
| 113 | + vtable: &V, |
| 114 | + array: ArrayView<'_, V>, |
| 115 | + buffers: &[BufferHandle], |
| 116 | +) -> VortexResult<ArrayParts<V>> |
| 117 | +where |
| 118 | + V: VTable<TypedArrayData = FixedWidthData>, |
| 119 | +{ |
| 120 | + vortex_ensure!( |
| 121 | + buffers.len() == 1, |
| 122 | + "Expected 1 buffer, got {}", |
| 123 | + buffers.len() |
| 124 | + ); |
| 125 | + let mut data = array.data().clone(); |
| 126 | + data.buffer = buffers[0].clone(); |
| 127 | + Ok( |
| 128 | + ArrayParts::new(vtable.clone(), array.dtype().clone(), array.len(), data) |
| 129 | + .with_slots(array.slots().iter().cloned().collect()), |
| 130 | + ) |
| 131 | +} |
| 132 | + |
| 133 | +pub(crate) fn validate_layout( |
| 134 | + data: &FixedWidthData, |
| 135 | + len: usize, |
| 136 | + slots: &[Option<ArrayRef>], |
| 137 | + nullability: Nullability, |
| 138 | + name: &str, |
| 139 | +) -> VortexResult<()> { |
| 140 | + vortex_ensure!(slots.len() == 1, "{name} expects one validity slot"); |
| 141 | + vortex_ensure!( |
| 142 | + data.len() == len, |
| 143 | + InvalidArgument: "{name} length {} does not match outer length {len}", |
| 144 | + data.len(), |
| 145 | + ); |
| 146 | + let validity = crate::array::child_to_validity(slots[0].as_ref(), nullability); |
| 147 | + if let Some(validity_len) = validity.maybe_len() { |
| 148 | + vortex_ensure!( |
| 149 | + validity_len == len, |
| 150 | + InvalidArgument: "{name} validity len {validity_len} does not match outer length {len}", |
| 151 | + ); |
| 152 | + } |
| 153 | + Ok(()) |
| 154 | +} |
| 155 | + |
| 156 | +pub(crate) fn deserialize_validity( |
| 157 | + dtype_nullability: Nullability, |
| 158 | + len: usize, |
| 159 | + children: &dyn ArrayChildren, |
| 160 | +) -> VortexResult<Validity> { |
| 161 | + if children.is_empty() { |
| 162 | + Ok(Validity::from(dtype_nullability)) |
| 163 | + } else if children.len() == 1 { |
| 164 | + Ok(Validity::Array(children.get(0, &Validity::DTYPE, len)?)) |
| 165 | + } else { |
| 166 | + vortex_bail!("Expected 0 or 1 child, got {}", children.len()) |
| 167 | + } |
| 168 | +} |
| 169 | + |
| 170 | +pub(crate) fn validity<V>(array: ArrayView<'_, V>) -> VortexResult<Validity> |
| 171 | +where |
| 172 | + V: VTable<TypedArrayData = FixedWidthData>, |
| 173 | +{ |
| 174 | + Ok(crate::array::child_to_validity( |
| 175 | + array.slots()[0].as_ref(), |
| 176 | + array.dtype().nullability(), |
| 177 | + )) |
| 178 | +} |
| 179 | + |
| 180 | +pub(crate) fn slot_name(idx: usize) -> String { |
| 181 | + match idx { |
| 182 | + 0 => "validity".to_string(), |
| 183 | + _ => vortex_panic!("Fixed-width slot index {idx} out of bounds"), |
| 184 | + } |
| 185 | +} |
0 commit comments