use rudb_common::{Error, LogicalType, Result};
use rudb_vector::{Data, Validity, Vector};
use crate::types::DataType;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Array {
data_type: DataType,
len: usize,
null_count: usize,
validity: Option<Vec<u8>>,
offsets: Option<Vec<u8>>,
values: Vec<u8>,
}
impl Array {
pub fn of(vector: &Vector) -> Result<Self> {
let flat = vector.flatten()?;
let data_type = DataType::of(flat.logical_type())?;
let len = flat.len();
let null_count = len - flat.validity().count_valid(len);
let validity = bitmap(flat.validity(), len);
let empty = Data::Empty;
let data = flat.data().unwrap_or(&empty);
let (values, offsets) = match &data_type {
DataType::Null => (Vec::new(), None),
DataType::Boolean => (bits(data, len), None),
DataType::Utf8 | DataType::Binary => {
let (values, offsets) = varlen(data, len)?;
(values, Some(offsets))
}
DataType::Interval => (intervals(data, len)?, None),
DataType::Decimal128 { .. } => (decimals(data, len, flat.logical_type())?, None),
other => {
let width = other.width().ok_or_else(|| {
Error::internal(format!("{other:?} has no width and no buffer of its own"))
})?;
(fixed(data, len, width)?, None)
}
};
Ok(Self { data_type, len, null_count, validity, offsets, values })
}
#[must_use]
pub fn empty(data_type: DataType) -> Self {
let offsets = (data_type.buffer_count() == 3).then(|| 0i32.to_le_bytes().to_vec());
Self { data_type, len: 0, null_count: 0, validity: None, offsets, values: Vec::new() }
}
#[must_use]
pub fn data_type(&self) -> &DataType {
&self.data_type
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn null_count(&self) -> usize {
self.null_count
}
#[must_use]
pub fn validity(&self) -> Option<&[u8]> {
self.validity.as_deref()
}
#[must_use]
pub fn offsets(&self) -> Option<&[u8]> {
self.offsets.as_deref()
}
#[must_use]
pub fn values(&self) -> &[u8] {
&self.values
}
#[must_use]
pub fn buffers(&self) -> Vec<Option<&[u8]>> {
match self.data_type.buffer_count() {
0 => Vec::new(),
3 => vec![self.validity(), self.offsets(), Some(self.values())],
_ => vec![self.validity(), Some(self.values())],
}
}
}
fn bitmap(validity: &Validity, len: usize) -> Option<Vec<u8>> {
if !validity.has_nulls(len) {
return None;
}
let bytes = len.div_ceil(8);
let mut out = vec![0u8; bytes];
for index in 0..len {
if validity.is_valid(index) {
out[index / 8] |= 1 << (index % 8);
}
}
Some(out)
}
fn bits(data: &Data, len: usize) -> Vec<u8> {
let mut out = vec![0u8; len.div_ceil(8)];
if let Data::Bool(values) = data {
for (index, &value) in values.as_slice().iter().take(len).enumerate() {
if value {
out[index / 8] |= 1 << (index % 8);
}
}
}
out
}
fn varlen(data: &Data, len: usize) -> Result<(Vec<u8>, Vec<u8>)> {
let mut values = Vec::new();
let mut offsets = Vec::with_capacity((len + 1) * 4);
offsets.extend_from_slice(&0i32.to_le_bytes());
for index in 0..len {
if let Data::Varlen(column) = data {
if let Some(bytes) = column.bytes(index) {
values.extend_from_slice(bytes);
}
}
let so_far = i32::try_from(values.len()).map_err(|_| {
Error::not_implemented(
"a column of strings longer than two gigabytes, which 32 bit offsets cannot \
address, and which is what Arrow has LargeUtf8 for",
)
})?;
offsets.extend_from_slice(&so_far.to_le_bytes());
}
Ok((values, offsets))
}
fn intervals(data: &Data, len: usize) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(len * 16);
if let Data::Interval(values) = data {
for &(months, days, micros) in values.as_slice().iter().take(len) {
out.extend_from_slice(&months.to_le_bytes());
out.extend_from_slice(&days.to_le_bytes());
out.extend_from_slice(µs.saturating_mul(1_000).to_le_bytes());
}
}
out.resize(len * 16, 0);
Ok(out)
}
fn decimals(data: &Data, len: usize, ty: &LogicalType) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(len * 16);
for index in 0..len {
let value = match data {
Data::Empty => 0,
_ => data.signed_at(index).ok_or_else(|| {
Error::internal(format!("{ty} is stored as something that is not an integer"))
})?,
};
out.extend_from_slice(&value.to_le_bytes());
}
Ok(out)
}
fn fixed(data: &Data, len: usize, width: usize) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(len * width);
macro_rules! pack {
($values:expr) => {
for value in $values.as_slice().iter().take(len) {
out.extend_from_slice(&value.to_le_bytes());
}
};
}
match data {
Data::Empty => {}
Data::Int8(values) => pack!(values),
Data::Int16(values) => pack!(values),
Data::Int32(values) => pack!(values),
Data::Int64(values) => pack!(values),
Data::Int128(values) => pack!(values),
Data::UInt8(values) => pack!(values),
Data::UInt16(values) => pack!(values),
Data::UInt32(values) => pack!(values),
Data::UInt64(values) => pack!(values),
Data::UInt128(values) => pack!(values),
Data::Float32(values) => pack!(values),
Data::Float64(values) => pack!(values),
other => {
return Err(Error::internal(format!(
"{other:?} is not a fixed width layout and reached the fixed width path"
)));
}
}
if out.len() > len * width {
return Err(Error::internal(format!(
"a column of {len} values of {width} bytes came to {} bytes",
out.len()
)));
}
out.resize(len * width, 0);
Ok(out)
}
#[cfg(test)]
mod tests {
use rudb_common::{LogicalType, Value};
use rudb_vector::Vector;
use super::{Array, DataType};
use crate::types::TimeUnit;
fn vector(ty: LogicalType, values: &[Value]) -> Vector {
Vector::from_values(ty, values).expect("the values are of the type")
}
#[test]
fn an_integer_column_is_four_little_endian_bytes_per_value() {
let array = Array::of(&vector(
LogicalType::Integer,
&[Value::Integer(1), Value::Integer(-2), Value::Integer(3)],
))
.expect("an integer maps onto Arrow");
assert_eq!(array.data_type(), &DataType::Int32);
assert_eq!(array.len(), 3);
assert_eq!(array.null_count(), 0);
assert_eq!(array.values(), &[1, 0, 0, 0, 254, 255, 255, 255, 3, 0, 0, 0]);
}
#[test]
fn a_column_with_no_nulls_has_no_validity_bitmap_at_all() {
let array = Array::of(&vector(LogicalType::BigInt, &[Value::BigInt(7)]))
.expect("a bigint maps onto Arrow");
assert_eq!(array.validity(), None);
assert_eq!(array.buffers().len(), 2);
assert_eq!(array.buffers()[0], None);
}
#[test]
fn a_null_sets_its_bit_to_zero_and_leaves_the_value_slot_readable() {
let array = Array::of(&vector(
LogicalType::Integer,
&[Value::Integer(1), Value::Null, Value::Integer(3)],
))
.expect("an integer maps onto Arrow");
assert_eq!(array.null_count(), 1);
assert_eq!(array.validity(), Some(&[0b0000_0101u8][..]));
assert_eq!(array.values().len(), 12);
}
#[test]
fn a_boolean_column_is_packed_a_bit_per_value() {
let values: Vec<Value> =
[true, false, true, true, false, false, false, true, true].map(Value::Boolean).to_vec();
let array =
Array::of(&vector(LogicalType::Boolean, &values)).expect("a boolean maps onto Arrow");
assert_eq!(array.len(), 9);
assert_eq!(array.values(), &[0b1000_1101u8, 0b0000_0001]);
}
#[test]
fn a_string_column_is_offsets_and_one_run_of_bytes() {
let array = Array::of(&vector(
LogicalType::Varchar,
&[
Value::Varchar("a".to_string()),
Value::Varchar("bc".to_string()),
Value::Varchar(String::new()),
],
))
.expect("a varchar maps onto Arrow");
assert_eq!(array.data_type(), &DataType::Utf8);
assert_eq!(array.values(), b"abc");
assert_eq!(offsets(&array), vec![0, 1, 3, 3]);
assert_eq!(array.buffers().len(), 3);
}
#[test]
fn a_null_string_gets_the_offset_of_the_one_before_it() {
let array = Array::of(&vector(
LogicalType::Varchar,
&[Value::Varchar("ab".to_string()), Value::Null, Value::Varchar("c".to_string())],
))
.expect("a varchar maps onto Arrow");
assert_eq!(offsets(&array), vec![0, 2, 2, 3]);
assert_eq!(array.values(), b"abc");
assert_eq!(array.validity(), Some(&[0b0000_0101u8][..]));
}
#[test]
fn a_string_longer_than_the_inline_prefix_survives_the_arena() {
let long = "the quick brown fox jumps over the lazy dog";
let array = Array::of(&vector(LogicalType::Varchar, &[Value::Varchar(long.to_string())]))
.expect("a varchar maps onto Arrow");
assert_eq!(array.values(), long.as_bytes());
}
#[test]
fn a_hugeint_is_widened_to_the_decimal_arrow_stores_it_in() {
let array = Array::of(&vector(LogicalType::HugeInt, &[Value::HugeInt(-1)]))
.expect("a hugeint maps onto Arrow");
assert_eq!(array.data_type(), &DataType::Decimal128 { precision: 38, scale: 0 });
assert_eq!(array.values(), &[0xff; 16]);
}
#[test]
fn a_narrow_decimal_is_widened_to_sixteen_bytes_and_keeps_its_scale() {
let array = Array::of(&vector(
LogicalType::Decimal { width: 4, scale: 2 },
&[Value::Decimal { unscaled: 1234, width: 4, scale: 2 }],
))
.expect("a decimal maps onto Arrow");
assert_eq!(array.data_type(), &DataType::Decimal128 { precision: 4, scale: 2 });
assert_eq!(array.values().len(), 16);
assert_eq!(i128::from_le_bytes(array.values().try_into().expect("sixteen bytes")), 1234);
}
#[test]
fn an_interval_turns_its_microseconds_into_arrows_nanoseconds() {
let array = Array::of(&vector(
LogicalType::Interval,
&[Value::Interval { months: 1, days: 2, micros: 3 }],
))
.expect("an interval maps onto Arrow");
assert_eq!(array.values()[0..4], 1i32.to_le_bytes());
assert_eq!(array.values()[4..8], 2i32.to_le_bytes());
assert_eq!(array.values()[8..16], 3_000i64.to_le_bytes());
}
#[test]
fn a_timestamp_keeps_the_microseconds_it_already_counts_in() {
let array = Array::of(&vector(LogicalType::Timestamp, &[Value::Timestamp(1_700_000)]))
.expect("a timestamp maps onto Arrow");
assert_eq!(array.data_type(), &DataType::Timestamp(TimeUnit::Microsecond, None));
assert_eq!(array.values(), 1_700_000i64.to_le_bytes());
}
#[test]
fn the_null_type_has_no_buffers_and_nothing_in_them() {
let array = Array::of(&vector(LogicalType::Null, &[Value::Null, Value::Null]))
.expect("the null type maps onto Arrow");
assert_eq!(array.data_type(), &DataType::Null);
assert_eq!(array.len(), 2);
assert_eq!(array.null_count(), 2);
assert!(array.buffers().is_empty());
assert!(array.values().is_empty());
}
#[test]
fn a_constant_vector_is_flattened_into_the_values_it_stands_for() {
let array = Array::of(&Vector::constant(LogicalType::Integer, Value::Integer(9), 4))
.expect("an integer maps onto Arrow");
assert_eq!(array.len(), 4);
assert_eq!(array.values(), &[9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0]);
}
#[test]
fn a_column_of_nothing_but_nulls_still_has_a_values_buffer_the_right_size() {
let array = Array::of(&vector(LogicalType::BigInt, &[Value::Null, Value::Null]))
.expect("a bigint maps onto Arrow");
assert_eq!(array.null_count(), 2);
assert_eq!(array.values(), &[0u8; 16]);
assert_eq!(array.validity(), Some(&[0u8][..]));
}
#[test]
fn an_empty_string_array_still_carries_the_leading_offset() {
let array = Array::empty(DataType::Utf8);
assert!(array.is_empty());
assert_eq!(array.offsets(), Some(&0i32.to_le_bytes()[..]));
assert_eq!(array.buffers().len(), 3);
}
#[test]
fn a_type_with_no_arrow_counterpart_is_refused_rather_than_guessed_at() {
let error = Array::of(&Vector::constant(LogicalType::Uuid, Value::Null, 1))
.expect_err("uuid has no Arrow type here yet");
assert!(error.to_string().contains("UUID"), "{error}");
}
fn offsets(array: &Array) -> Vec<i32> {
array
.offsets()
.expect("a variable width array has offsets")
.chunks_exact(4)
.map(|bytes| i32::from_le_bytes(bytes.try_into().expect("four bytes")))
.collect()
}
}