Skip to main content

rudb_arrow/
array.rs

1//! One column, in Arrow's memory layout.
2//!
3//! Arrow says an array is a length, a null count, and a list of buffers whose meaning comes from
4//! the type: a validity bitmap first, then offsets for a variable width type, then the values. That
5//! is what this builds, as owned little endian bytes, which is the form the C data interface hands
6//! across a boundary and the form a reader on the other side of one already knows how to read.
7//!
8//! Nothing here is zero copy yet and the crate's own description promises it is where the layouts
9//! permit. Two of the three buffers already do permit it. Our validity bitmap is the same LSB first
10//! layout as Arrow's, and a run of `i32` is a run of `i32`, so the copy is a memcpy that a later
11//! change can drop once there is an owner to hand the pages to. The one that cannot is `VARCHAR`:
12//! we store a sixteen byte view and an arena and Arrow's `u` is offsets and a contiguous run of
13//! bytes, and no arrangement of the two is the other one.
14
15use rudb_common::{Error, LogicalType, Result};
16use rudb_vector::{Data, Validity, Vector};
17
18use crate::types::DataType;
19
20/// A column of values, in Arrow's layout.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Array {
23    data_type: DataType,
24    len: usize,
25    null_count: usize,
26    validity: Option<Vec<u8>>,
27    offsets: Option<Vec<u8>>,
28    values: Vec<u8>,
29}
30
31impl Array {
32    /// The Arrow array a vector becomes.
33    ///
34    /// The vector is flattened first, so a constant, a sequence and a dictionary all arrive here as
35    /// the values they stand for. Arrow has a run end encoding and a dictionary array of its own
36    /// and they are worth having later, but an export that sometimes hands back a dictionary is an
37    /// export every reader has to have two paths for, and the reader is the one we are trying to
38    /// make cheap.
39    ///
40    /// # Errors
41    ///
42    /// For a type with no Arrow counterpart, and for a vector whose values are not the layout its
43    /// type says they are.
44    pub fn of(vector: &Vector) -> Result<Self> {
45        let flat = vector.flatten()?;
46        let data_type = DataType::of(flat.logical_type())?;
47        let len = flat.len();
48        let null_count = len - flat.validity().count_valid(len);
49        let validity = bitmap(flat.validity(), len);
50        let empty = Data::Empty;
51        let data = flat.data().unwrap_or(&empty);
52        let (values, offsets) = match &data_type {
53            // The null type has no buffers at all, so there is nothing to read and nothing to
54            // write. Everything in it is null by being that type.
55            DataType::Null => (Vec::new(), None),
56            DataType::Boolean => (bits(data, len), None),
57            DataType::Utf8 | DataType::Binary => {
58                let (values, offsets) = varlen(data, len)?;
59                (values, Some(offsets))
60            }
61            DataType::Interval => (intervals(data, len)?, None),
62            DataType::Decimal128 { .. } => (decimals(data, len, flat.logical_type())?, None),
63            other => {
64                let width = other.width().ok_or_else(|| {
65                    Error::internal(format!("{other:?} has no width and no buffer of its own"))
66                })?;
67                (fixed(data, len, width)?, None)
68            }
69        };
70        Ok(Self { data_type, len, null_count, validity, offsets, values })
71    }
72
73    /// An array of this type with no values in it.
74    ///
75    /// A variable width type still gets its offsets buffer, holding the single zero that says the
76    /// first value would start at the beginning. Arrow's rule is that offsets are one longer than
77    /// the array, and an array of nothing is the case where forgetting it is easiest and where a
78    /// reader that trusts the rule reads past the end.
79    #[must_use]
80    pub fn empty(data_type: DataType) -> Self {
81        let offsets = (data_type.buffer_count() == 3).then(|| 0i32.to_le_bytes().to_vec());
82        Self { data_type, len: 0, null_count: 0, validity: None, offsets, values: Vec::new() }
83    }
84
85    /// What the column holds.
86    #[must_use]
87    pub fn data_type(&self) -> &DataType {
88        &self.data_type
89    }
90
91    /// How many values.
92    #[must_use]
93    pub fn len(&self) -> usize {
94        self.len
95    }
96
97    /// Whether there are none.
98    #[must_use]
99    pub fn is_empty(&self) -> bool {
100        self.len == 0
101    }
102
103    /// How many of them are null.
104    #[must_use]
105    pub fn null_count(&self) -> usize {
106        self.null_count
107    }
108
109    /// The validity bitmap, or nothing when nothing is null.
110    ///
111    /// Nothing is what Arrow means by a null pointer in the first buffer slot, and it is the case
112    /// worth keeping rather than materializing: a reader that sees it can skip the per value check
113    /// for the whole array, which is the same reason `Validity::AllValid` exists on our side.
114    #[must_use]
115    pub fn validity(&self) -> Option<&[u8]> {
116        self.validity.as_deref()
117    }
118
119    /// The offsets buffer, for the variable width types, as little endian `i32`.
120    #[must_use]
121    pub fn offsets(&self) -> Option<&[u8]> {
122        self.offsets.as_deref()
123    }
124
125    /// The values buffer.
126    #[must_use]
127    pub fn values(&self) -> &[u8] {
128        &self.values
129    }
130
131    /// The buffers in the order the C data interface lists them.
132    ///
133    /// Two or three of them, and which is which is the type's business rather than the caller's,
134    /// which is why this exists next to the three accessors above.
135    #[must_use]
136    pub fn buffers(&self) -> Vec<Option<&[u8]>> {
137        match self.data_type.buffer_count() {
138            0 => Vec::new(),
139            3 => vec![self.validity(), self.offsets(), Some(self.values())],
140            _ => vec![self.validity(), Some(self.values())],
141        }
142    }
143}
144
145/// The validity bitmap as bytes, or nothing when there is nothing to say.
146///
147/// Our bitmap is already Arrow's: one bit per value, set meaning valid, least significant bit
148/// first. So the only work is writing the words out little endian, and on a little endian machine
149/// that is the bytes they already are.
150fn bitmap(validity: &Validity, len: usize) -> Option<Vec<u8>> {
151    if !validity.has_nulls(len) {
152        return None;
153    }
154    let bytes = len.div_ceil(8);
155    let mut out = vec![0u8; bytes];
156    for index in 0..len {
157        if validity.is_valid(index) {
158            out[index / 8] |= 1 << (index % 8);
159        }
160    }
161    Some(out)
162}
163
164/// A boolean column, which Arrow stores as a bit per value rather than a byte per value.
165fn bits(data: &Data, len: usize) -> Vec<u8> {
166    let mut out = vec![0u8; len.div_ceil(8)];
167    if let Data::Bool(values) = data {
168        for (index, &value) in values.as_slice().iter().take(len).enumerate() {
169            if value {
170                out[index / 8] |= 1 << (index % 8);
171            }
172        }
173    }
174    out
175}
176
177/// A string or blob column, as offsets and one run of bytes.
178///
179/// This is the copy that cannot be avoided. Our strings are views into an arena, in whatever order
180/// they were written and with the short ones not in the arena at all, and Arrow's are back to back
181/// in row order with an offset each.
182fn varlen(data: &Data, len: usize) -> Result<(Vec<u8>, Vec<u8>)> {
183    let mut values = Vec::new();
184    let mut offsets = Vec::with_capacity((len + 1) * 4);
185    offsets.extend_from_slice(&0i32.to_le_bytes());
186    for index in 0..len {
187        if let Data::Varlen(column) = data {
188            if let Some(bytes) = column.bytes(index) {
189                values.extend_from_slice(bytes);
190            }
191        }
192        // A null still gets an offset, and it is the same one as the value before it, which is what
193        // makes a null and an empty string the same two numbers and the validity bitmap the only
194        // thing that tells them apart. That is Arrow's rule and not a shortcut here.
195        let so_far = i32::try_from(values.len()).map_err(|_| {
196            Error::not_implemented(
197                "a column of strings longer than two gigabytes, which 32 bit offsets cannot \
198                 address, and which is what Arrow has LargeUtf8 for",
199            )
200        })?;
201        offsets.extend_from_slice(&so_far.to_le_bytes());
202    }
203    Ok((values, offsets))
204}
205
206/// An interval column, as Arrow's month day nano triple.
207///
208/// Ours is months, days and microseconds, and Arrow's third field is nanoseconds, so the only
209/// conversion is the factor of a thousand. It cannot overflow for any interval a query can produce:
210/// the microseconds field is an `i64` and a thousand times it is still inside an `i128`, but Arrow
211/// stores it in an `i64`, so an interval of more than about 292 years of microseconds saturates
212/// rather than wrapping. DuckDB has the same limit from the same arithmetic.
213fn intervals(data: &Data, len: usize) -> Result<Vec<u8>> {
214    let mut out = Vec::with_capacity(len * 16);
215    if let Data::Interval(values) = data {
216        for &(months, days, micros) in values.as_slice().iter().take(len) {
217            out.extend_from_slice(&months.to_le_bytes());
218            out.extend_from_slice(&days.to_le_bytes());
219            out.extend_from_slice(&micros.saturating_mul(1_000).to_le_bytes());
220        }
221    }
222    out.resize(len * 16, 0);
223    Ok(out)
224}
225
226/// A decimal column, or a `HUGEINT` one, widened to the 128 bits Arrow stores a decimal in.
227///
228/// Our decimals live in the narrowest integer that holds the precision, which is what makes a
229/// `DECIMAL(4, 2)` four times cheaper to add than a 128 bit one. Arrow has one decimal width, so
230/// the export widens. `Data::signed_at` is the widening, and it is there rather than here because
231/// four other places need the same five arms.
232fn decimals(data: &Data, len: usize, ty: &LogicalType) -> Result<Vec<u8>> {
233    let mut out = Vec::with_capacity(len * 16);
234    for index in 0..len {
235        let value = match data {
236            Data::Empty => 0,
237            _ => data.signed_at(index).ok_or_else(|| {
238                Error::internal(format!("{ty} is stored as something that is not an integer"))
239            })?,
240        };
241        out.extend_from_slice(&value.to_le_bytes());
242    }
243    Ok(out)
244}
245
246/// A fixed width column, as the bytes it already is.
247fn fixed(data: &Data, len: usize, width: usize) -> Result<Vec<u8>> {
248    let mut out = Vec::with_capacity(len * width);
249    macro_rules! pack {
250        ($values:expr) => {
251            for value in $values.as_slice().iter().take(len) {
252                out.extend_from_slice(&value.to_le_bytes());
253            }
254        };
255    }
256    match data {
257        // A vector where every value is null keeps no values at all, and Arrow still wants a buffer
258        // of the right size under the bitmap that says to ignore it. The resize below writes it.
259        Data::Empty => {}
260        Data::Int8(values) => pack!(values),
261        Data::Int16(values) => pack!(values),
262        Data::Int32(values) => pack!(values),
263        Data::Int64(values) => pack!(values),
264        Data::Int128(values) => pack!(values),
265        Data::UInt8(values) => pack!(values),
266        Data::UInt16(values) => pack!(values),
267        Data::UInt32(values) => pack!(values),
268        Data::UInt64(values) => pack!(values),
269        Data::UInt128(values) => pack!(values),
270        Data::Float32(values) => pack!(values),
271        Data::Float64(values) => pack!(values),
272        other => {
273            return Err(Error::internal(format!(
274                "{other:?} is not a fixed width layout and reached the fixed width path"
275            )));
276        }
277    }
278    if out.len() > len * width {
279        return Err(Error::internal(format!(
280            "a column of {len} values of {width} bytes came to {} bytes",
281            out.len()
282        )));
283    }
284    out.resize(len * width, 0);
285    Ok(out)
286}
287
288#[cfg(test)]
289mod tests {
290    use rudb_common::{LogicalType, Value};
291    use rudb_vector::Vector;
292
293    use super::{Array, DataType};
294    use crate::types::TimeUnit;
295
296    fn vector(ty: LogicalType, values: &[Value]) -> Vector {
297        Vector::from_values(ty, values).expect("the values are of the type")
298    }
299
300    #[test]
301    fn an_integer_column_is_four_little_endian_bytes_per_value() {
302        let array = Array::of(&vector(
303            LogicalType::Integer,
304            &[Value::Integer(1), Value::Integer(-2), Value::Integer(3)],
305        ))
306        .expect("an integer maps onto Arrow");
307        assert_eq!(array.data_type(), &DataType::Int32);
308        assert_eq!(array.len(), 3);
309        assert_eq!(array.null_count(), 0);
310        assert_eq!(array.values(), &[1, 0, 0, 0, 254, 255, 255, 255, 3, 0, 0, 0]);
311    }
312
313    #[test]
314    fn a_column_with_no_nulls_has_no_validity_bitmap_at_all() {
315        let array = Array::of(&vector(LogicalType::BigInt, &[Value::BigInt(7)]))
316            .expect("a bigint maps onto Arrow");
317        assert_eq!(array.validity(), None);
318        assert_eq!(array.buffers().len(), 2);
319        assert_eq!(array.buffers()[0], None);
320    }
321
322    #[test]
323    fn a_null_sets_its_bit_to_zero_and_leaves_the_value_slot_readable() {
324        let array = Array::of(&vector(
325            LogicalType::Integer,
326            &[Value::Integer(1), Value::Null, Value::Integer(3)],
327        ))
328        .expect("an integer maps onto Arrow");
329        assert_eq!(array.null_count(), 1);
330        // Bits 0 and 2 set, bit 1 clear, and the byte is padded with zeros up to eight bits.
331        assert_eq!(array.validity(), Some(&[0b0000_0101u8][..]));
332        // Arrow says the value under a null is undefined rather than absent, so the buffer is still
333        // the full three values wide and a reader that ignores the bitmap reads something.
334        assert_eq!(array.values().len(), 12);
335    }
336
337    #[test]
338    fn a_boolean_column_is_packed_a_bit_per_value() {
339        let values: Vec<Value> =
340            [true, false, true, true, false, false, false, true, true].map(Value::Boolean).to_vec();
341        let array =
342            Array::of(&vector(LogicalType::Boolean, &values)).expect("a boolean maps onto Arrow");
343        assert_eq!(array.len(), 9);
344        assert_eq!(array.values(), &[0b1000_1101u8, 0b0000_0001]);
345    }
346
347    #[test]
348    fn a_string_column_is_offsets_and_one_run_of_bytes() {
349        let array = Array::of(&vector(
350            LogicalType::Varchar,
351            &[
352                Value::Varchar("a".to_string()),
353                Value::Varchar("bc".to_string()),
354                Value::Varchar(String::new()),
355            ],
356        ))
357        .expect("a varchar maps onto Arrow");
358        assert_eq!(array.data_type(), &DataType::Utf8);
359        assert_eq!(array.values(), b"abc");
360        assert_eq!(offsets(&array), vec![0, 1, 3, 3]);
361        assert_eq!(array.buffers().len(), 3);
362    }
363
364    #[test]
365    fn a_null_string_gets_the_offset_of_the_one_before_it() {
366        let array = Array::of(&vector(
367            LogicalType::Varchar,
368            &[Value::Varchar("ab".to_string()), Value::Null, Value::Varchar("c".to_string())],
369        ))
370        .expect("a varchar maps onto Arrow");
371        // A null and an empty string are the same pair of offsets. The bitmap is the only thing
372        // that tells them apart, which is Arrow's rule rather than a shortcut here.
373        assert_eq!(offsets(&array), vec![0, 2, 2, 3]);
374        assert_eq!(array.values(), b"abc");
375        assert_eq!(array.validity(), Some(&[0b0000_0101u8][..]));
376    }
377
378    #[test]
379    fn a_string_longer_than_the_inline_prefix_survives_the_arena() {
380        let long = "the quick brown fox jumps over the lazy dog";
381        let array = Array::of(&vector(LogicalType::Varchar, &[Value::Varchar(long.to_string())]))
382            .expect("a varchar maps onto Arrow");
383        assert_eq!(array.values(), long.as_bytes());
384    }
385
386    #[test]
387    fn a_hugeint_is_widened_to_the_decimal_arrow_stores_it_in() {
388        let array = Array::of(&vector(LogicalType::HugeInt, &[Value::HugeInt(-1)]))
389            .expect("a hugeint maps onto Arrow");
390        assert_eq!(array.data_type(), &DataType::Decimal128 { precision: 38, scale: 0 });
391        assert_eq!(array.values(), &[0xff; 16]);
392    }
393
394    #[test]
395    fn a_narrow_decimal_is_widened_to_sixteen_bytes_and_keeps_its_scale() {
396        let array = Array::of(&vector(
397            LogicalType::Decimal { width: 4, scale: 2 },
398            &[Value::Decimal { unscaled: 1234, width: 4, scale: 2 }],
399        ))
400        .expect("a decimal maps onto Arrow");
401        assert_eq!(array.data_type(), &DataType::Decimal128 { precision: 4, scale: 2 });
402        assert_eq!(array.values().len(), 16);
403        assert_eq!(i128::from_le_bytes(array.values().try_into().expect("sixteen bytes")), 1234);
404    }
405
406    #[test]
407    fn an_interval_turns_its_microseconds_into_arrows_nanoseconds() {
408        let array = Array::of(&vector(
409            LogicalType::Interval,
410            &[Value::Interval { months: 1, days: 2, micros: 3 }],
411        ))
412        .expect("an interval maps onto Arrow");
413        assert_eq!(array.values()[0..4], 1i32.to_le_bytes());
414        assert_eq!(array.values()[4..8], 2i32.to_le_bytes());
415        assert_eq!(array.values()[8..16], 3_000i64.to_le_bytes());
416    }
417
418    #[test]
419    fn a_timestamp_keeps_the_microseconds_it_already_counts_in() {
420        let array = Array::of(&vector(LogicalType::Timestamp, &[Value::Timestamp(1_700_000)]))
421            .expect("a timestamp maps onto Arrow");
422        assert_eq!(array.data_type(), &DataType::Timestamp(TimeUnit::Microsecond, None));
423        assert_eq!(array.values(), 1_700_000i64.to_le_bytes());
424    }
425
426    #[test]
427    fn the_null_type_has_no_buffers_and_nothing_in_them() {
428        let array = Array::of(&vector(LogicalType::Null, &[Value::Null, Value::Null]))
429            .expect("the null type maps onto Arrow");
430        assert_eq!(array.data_type(), &DataType::Null);
431        assert_eq!(array.len(), 2);
432        assert_eq!(array.null_count(), 2);
433        assert!(array.buffers().is_empty());
434        assert!(array.values().is_empty());
435    }
436
437    #[test]
438    fn a_constant_vector_is_flattened_into_the_values_it_stands_for() {
439        let array = Array::of(&Vector::constant(LogicalType::Integer, Value::Integer(9), 4))
440            .expect("an integer maps onto Arrow");
441        assert_eq!(array.len(), 4);
442        assert_eq!(array.values(), &[9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0]);
443    }
444
445    #[test]
446    fn a_column_of_nothing_but_nulls_still_has_a_values_buffer_the_right_size() {
447        let array = Array::of(&vector(LogicalType::BigInt, &[Value::Null, Value::Null]))
448            .expect("a bigint maps onto Arrow");
449        assert_eq!(array.null_count(), 2);
450        assert_eq!(array.values(), &[0u8; 16]);
451        assert_eq!(array.validity(), Some(&[0u8][..]));
452    }
453
454    #[test]
455    fn an_empty_string_array_still_carries_the_leading_offset() {
456        let array = Array::empty(DataType::Utf8);
457        assert!(array.is_empty());
458        assert_eq!(array.offsets(), Some(&0i32.to_le_bytes()[..]));
459        assert_eq!(array.buffers().len(), 3);
460    }
461
462    #[test]
463    fn a_type_with_no_arrow_counterpart_is_refused_rather_than_guessed_at() {
464        let error = Array::of(&Vector::constant(LogicalType::Uuid, Value::Null, 1))
465            .expect_err("uuid has no Arrow type here yet");
466        assert!(error.to_string().contains("UUID"), "{error}");
467    }
468
469    fn offsets(array: &Array) -> Vec<i32> {
470        array
471            .offsets()
472            .expect("a variable width array has offsets")
473            .chunks_exact(4)
474            .map(|bytes| i32::from_le_bytes(bytes.try_into().expect("four bytes")))
475            .collect()
476    }
477}