reifydb_value/value/container/
dictionary_array.rs1use arrow_array::{Array, FixedSizeBinaryArray};
5use arrow_buffer::MutableBuffer;
6
7use crate::value::{
8 Value,
9 container::fixed_array::{assert_whole_rows, from_buffer},
10 dictionary::DictionaryEntryId,
11 value_type::ValueType,
12};
13
14pub const DICTIONARY_ENTRY_WIDTH: usize = 17;
15
16pub fn encode(entry: DictionaryEntryId) -> [u8; DICTIONARY_ENTRY_WIDTH] {
17 let (tag, value) = match entry {
18 DictionaryEntryId::U1(v) => (0u8, v as u128),
19 DictionaryEntryId::U2(v) => (1u8, v as u128),
20 DictionaryEntryId::U4(v) => (2u8, v as u128),
21 DictionaryEntryId::U8(v) => (3u8, v as u128),
22 DictionaryEntryId::U16(v) => (4u8, v),
23 };
24 let mut row = [0u8; DICTIONARY_ENTRY_WIDTH];
25 row[0] = tag;
26 row[1..].copy_from_slice(&value.to_le_bytes());
27 row
28}
29
30pub fn decode(row: &[u8; DICTIONARY_ENTRY_WIDTH]) -> DictionaryEntryId {
31 let [tag, value @ ..] = *row;
32 let value = u128::from_le_bytes(value);
33 match tag {
34 0 => DictionaryEntryId::U1(value as u8),
35 1 => DictionaryEntryId::U2(value as u16),
36 2 => DictionaryEntryId::U4(value as u32),
37 3 => DictionaryEntryId::U8(value as u64),
38 4 => DictionaryEntryId::U16(value),
39 other => panic!("dictionary entry row has width byte {other}, expected 0 to 4"),
40 }
41}
42
43fn rows(array: &FixedSizeBinaryArray) -> &[[u8; DICTIONARY_ENTRY_WIDTH]] {
44 assert_eq!(
45 array.value_length() as usize,
46 DICTIONARY_ENTRY_WIDTH,
47 "dictionary id column must hold {DICTIONARY_ENTRY_WIDTH} byte values, found {}",
48 array.value_length()
49 );
50 assert_whole_rows(DICTIONARY_ENTRY_WIDTH, array.value_data().len());
51 array.value_data()[..array.len() * DICTIONARY_ENTRY_WIDTH].as_chunks::<DICTIONARY_ENTRY_WIDTH>().0
52}
53
54pub fn push_entry(buffer: &mut MutableBuffer, entry: DictionaryEntryId) {
55 buffer.extend_from_slice(&encode(entry));
56}
57
58pub fn dictionary_array(values: impl IntoIterator<Item = DictionaryEntryId>) -> FixedSizeBinaryArray {
59 let values = values.into_iter();
60 let mut buffer = MutableBuffer::with_capacity(values.size_hint().0 * DICTIONARY_ENTRY_WIDTH);
61 for entry in values {
62 push_entry(&mut buffer, entry);
63 }
64 from_buffer(DICTIONARY_ENTRY_WIDTH, buffer)
65}
66
67pub fn iter(array: &FixedSizeBinaryArray) -> impl ExactSizeIterator<Item = DictionaryEntryId> + '_ {
68 rows(array).iter().map(decode)
69}
70
71pub fn get(array: &FixedSizeBinaryArray, index: usize) -> Option<DictionaryEntryId> {
72 rows(array).get(index).map(decode)
73}
74
75pub fn get_value(array: &FixedSizeBinaryArray, index: usize) -> Value {
76 get(array, index).map(Value::DictionaryId).unwrap_or(Value::none_of(ValueType::DictionaryId))
77}
78
79pub fn as_string(array: &FixedSizeBinaryArray, index: usize) -> String {
80 get(array, index).map(|id| id.to_string()).unwrap_or_else(|| "none".to_string())
81}
82
83#[cfg(test)]
84mod tests {
85
86 use super::*;
87
88 fn boundaries() -> Vec<DictionaryEntryId> {
89 vec![
90 DictionaryEntryId::U1(0),
91 DictionaryEntryId::U1(u8::MAX),
92 DictionaryEntryId::U2(0),
93 DictionaryEntryId::U2(u16::MAX),
94 DictionaryEntryId::U4(0),
95 DictionaryEntryId::U4(u32::MAX),
96 DictionaryEntryId::U8(0),
97 DictionaryEntryId::U8(u64::MAX),
98 DictionaryEntryId::U16(0),
99 DictionaryEntryId::U16(u128::MAX),
100 ]
101 }
102
103 #[test]
104 fn every_width_round_trips_at_zero_and_its_max() {
105 let entries = boundaries();
107 for entry in &entries {
108 assert_eq!(decode(&encode(*entry)), *entry);
109 }
110 let array = dictionary_array(entries.clone());
111 assert_eq!(array.value_length() as usize, DICTIONARY_ENTRY_WIDTH);
112 assert_eq!(array.len(), entries.len());
113 assert_eq!(iter(&array).collect::<Vec<_>>(), entries);
114 for (i, entry) in entries.iter().enumerate() {
115 assert_eq!(get(&array, i), Some(*entry));
116 assert_eq!(get_value(&array, i), Value::DictionaryId(*entry));
117 }
118 assert_eq!(get(&array, entries.len()), None);
119 assert_eq!(get_value(&array, entries.len()), Value::none_of(ValueType::DictionaryId));
120 }
121
122 #[test]
123 fn row_layout_is_width_byte_then_little_endian_u128() {
124 let row = encode(DictionaryEntryId::U4(0x0102_0304));
126 assert_eq!(row, [2, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
127 let row = encode(DictionaryEntryId::U16(u128::MAX));
128 assert_eq!(row[0], 4);
129 assert!(row[1..].iter().all(|&b| b == 0xff));
130 }
131
132 #[test]
133 fn all_zero_row_reads_the_default_entry() {
134 assert_eq!(decode(&[0; DICTIONARY_ENTRY_WIDTH]), DictionaryEntryId::U1(0));
136 assert_eq!(decode(&[0; DICTIONARY_ENTRY_WIDTH]), DictionaryEntryId::default());
137 let mut buffer = MutableBuffer::new(0);
138 buffer.extend_zeros(DICTIONARY_ENTRY_WIDTH * 2);
139 let array = from_buffer(DICTIONARY_ENTRY_WIDTH, buffer);
140 assert_eq!(iter(&array).collect::<Vec<_>>(), [DictionaryEntryId::U1(0); 2]);
141 }
142
143 #[test]
144 #[should_panic(expected = "width byte 5")]
145 fn bad_width_byte_panics_naming_the_byte() {
146 let mut row = [0; DICTIONARY_ENTRY_WIDTH];
148 row[0] = 5;
149 decode(&row);
150 }
151
152 #[test]
153 #[should_panic(expected = "not a whole number of 17 byte rows")]
154 fn partial_row_buffer_is_refused() {
155 let mut buffer = MutableBuffer::new(0);
157 push_entry(&mut buffer, DictionaryEntryId::U2(7));
158 buffer.push(0u8);
159 from_buffer(DICTIONARY_ENTRY_WIDTH, buffer);
160 }
161
162 #[test]
163 #[should_panic(expected = "must hold 17 byte values")]
164 fn wrong_width_array_is_refused() {
165 let array = FixedSizeBinaryArray::new(16, vec![0u8; 32].into(), None);
167 get(&array, 0);
168 }
169}