reifydb_codec/frame/encoding/
dict.rs1use std::{collections::HashMap, str};
5
6use arrow_array::{Array, LargeBinaryArray, LargeStringArray};
7use reifydb_value::value::blob::Blob;
8
9use crate::{error::DecodeError, frame::format::dict_index_width_to_flags, tag::ValueKind};
10
11pub struct DictEncoded {
12 pub data: Vec<u8>,
13
14 pub extra: Vec<u8>,
15
16 pub type_code: u8,
17
18 pub flags_bits: u8,
19}
20
21pub fn try_dict_encode_utf8(container: &LargeStringArray, min_ratio: f64) -> Option<DictEncoded> {
22 let row_count = container.len();
23 if row_count == 0 {
24 return None;
25 }
26
27 let mut dict_map: HashMap<&str, u32> = HashMap::new();
28 let mut dict_entries: Vec<&str> = Vec::new();
29
30 for s in (0..row_count).map(|i| container.value(i)) {
31 if !dict_map.contains_key(s) {
32 let idx = dict_entries.len() as u32;
33 dict_map.insert(s, idx);
34 dict_entries.push(s);
35 }
36 }
37
38 let dict_count = dict_entries.len();
39
40 if dict_count as f64 >= row_count as f64 * min_ratio {
41 return None;
42 }
43
44 let (index_width, flags_bits) = if dict_count <= 255 {
45 (1usize, dict_index_width_to_flags(1))
46 } else if dict_count <= 65535 {
47 (2, dict_index_width_to_flags(2))
48 } else {
49 (4, dict_index_width_to_flags(4))
50 };
51
52 let mut data = Vec::with_capacity(row_count * index_width);
53 for s in (0..row_count).map(|i| container.value(i)) {
54 let idx = dict_map[s];
55 match index_width {
56 1 => data.push(idx as u8),
57 2 => data.extend_from_slice(&(idx as u16).to_le_bytes()),
58 4 => data.extend_from_slice(&idx.to_le_bytes()),
59 _ => unreachable!(),
60 }
61 }
62
63 let mut extra = Vec::new();
64 extra.extend_from_slice(&(dict_count as u32).to_le_bytes());
65
66 let mut offset: u32 = 0;
67 extra.extend_from_slice(&offset.to_le_bytes());
68 for entry in &dict_entries {
69 offset += entry.len() as u32;
70 extra.extend_from_slice(&offset.to_le_bytes());
71 }
72
73 for entry in &dict_entries {
74 extra.extend_from_slice(entry.as_bytes());
75 }
76
77 Some(DictEncoded {
78 data,
79 extra,
80 type_code: ValueKind::Utf8.byte(),
81 flags_bits,
82 })
83}
84
85pub fn try_dict_encode_blob(container: &LargeBinaryArray, min_ratio: f64) -> Option<DictEncoded> {
86 let row_count = container.len();
87 if row_count == 0 {
88 return None;
89 }
90
91 let mut dict_map: HashMap<&[u8], u32> = HashMap::new();
92 let mut dict_entries: Vec<&[u8]> = Vec::new();
93
94 for bytes in (0..row_count).map(|i| container.value(i)) {
95 if !dict_map.contains_key(bytes) {
96 let idx = dict_entries.len() as u32;
97 dict_map.insert(bytes, idx);
98 dict_entries.push(bytes);
99 }
100 }
101
102 let dict_count = dict_entries.len();
103
104 if dict_count as f64 >= row_count as f64 * min_ratio {
105 return None;
106 }
107
108 let (index_width, flags_bits) = if dict_count <= 255 {
109 (1usize, dict_index_width_to_flags(1))
110 } else if dict_count <= 65535 {
111 (2, dict_index_width_to_flags(2))
112 } else {
113 (4, dict_index_width_to_flags(4))
114 };
115
116 let mut data = Vec::with_capacity(row_count * index_width);
117 for bytes in (0..row_count).map(|i| container.value(i)) {
118 let idx = dict_map[bytes];
119 match index_width {
120 1 => data.push(idx as u8),
121 2 => data.extend_from_slice(&(idx as u16).to_le_bytes()),
122 4 => data.extend_from_slice(&idx.to_le_bytes()),
123 _ => unreachable!(),
124 }
125 }
126
127 let mut extra = Vec::new();
128 extra.extend_from_slice(&(dict_count as u32).to_le_bytes());
129
130 let mut offset: u32 = 0;
131 extra.extend_from_slice(&offset.to_le_bytes());
132 for entry in &dict_entries {
133 offset += entry.len() as u32;
134 extra.extend_from_slice(&offset.to_le_bytes());
135 }
136 for entry in &dict_entries {
137 extra.extend_from_slice(entry);
138 }
139
140 Some(DictEncoded {
141 data,
142 extra,
143 type_code: ValueKind::Blob.byte(),
144 flags_bits,
145 })
146}
147
148pub fn decode_dict_utf8(
149 data: &[u8],
150 extra: &[u8],
151 row_count: usize,
152 index_width: usize,
153) -> Result<Vec<String>, DecodeError> {
154 let dict_entries = decode_dict_table(extra)?;
155
156 let mut values = Vec::with_capacity(row_count);
157 for i in 0..row_count {
158 let idx = read_index(data, i, index_width) as usize;
159 if idx >= dict_entries.len() {
160 return Err(DecodeError::InvalidData(format!(
161 "dict index {} out of range (dict has {} entries)",
162 idx,
163 dict_entries.len()
164 )));
165 }
166 values.push(dict_entries[idx].clone());
167 }
168
169 Ok(values)
170}
171
172pub fn decode_dict_blob(
173 data: &[u8],
174 extra: &[u8],
175 row_count: usize,
176 index_width: usize,
177) -> Result<Vec<Blob>, DecodeError> {
178 let dict_entries = decode_dict_table_bytes(extra)?;
179
180 let mut values = Vec::with_capacity(row_count);
181 for i in 0..row_count {
182 let idx = read_index(data, i, index_width) as usize;
183 if idx >= dict_entries.len() {
184 return Err(DecodeError::InvalidData(format!(
185 "dict index {} out of range (dict has {} entries)",
186 idx,
187 dict_entries.len()
188 )));
189 }
190 values.push(Blob::new(dict_entries[idx].clone()));
191 }
192
193 Ok(values)
194}
195
196fn decode_dict_table(extra: &[u8]) -> Result<Vec<String>, DecodeError> {
197 if extra.len() < 4 {
198 return Err(DecodeError::InvalidData("dict table too short".to_string()));
199 }
200
201 let dict_count = u32::from_le_bytes([extra[0], extra[1], extra[2], extra[3]]) as usize;
202 let offsets_start = 4;
203 let offsets_end = offsets_start + (dict_count + 1) * 4;
204
205 if extra.len() < offsets_end {
206 return Err(DecodeError::InvalidData("dict offsets truncated".to_string()));
207 }
208
209 let mut offsets = Vec::with_capacity(dict_count + 1);
210 for i in 0..=dict_count {
211 let pos = offsets_start + i * 4;
212 offsets.push(u32::from_le_bytes([extra[pos], extra[pos + 1], extra[pos + 2], extra[pos + 3]]) as usize);
213 }
214
215 let data_start = offsets_end;
216 let mut entries = Vec::with_capacity(dict_count);
217 for i in 0..dict_count {
218 let start = data_start + offsets[i];
219 let end = data_start + offsets[i + 1];
220 let s = str::from_utf8(&extra[start..end])
221 .map_err(|e| DecodeError::InvalidData(format!("invalid dict UTF-8: {}", e)))?;
222 entries.push(s.to_string());
223 }
224
225 Ok(entries)
226}
227
228pub fn decode_dict_table_bytes(extra: &[u8]) -> Result<Vec<Vec<u8>>, DecodeError> {
229 if extra.len() < 4 {
230 return Err(DecodeError::InvalidData("dict table too short".to_string()));
231 }
232
233 let dict_count = u32::from_le_bytes([extra[0], extra[1], extra[2], extra[3]]) as usize;
234 let offsets_start = 4;
235 let offsets_end = offsets_start + (dict_count + 1) * 4;
236
237 if extra.len() < offsets_end {
238 return Err(DecodeError::InvalidData("dict offsets truncated".to_string()));
239 }
240
241 let mut offsets = Vec::with_capacity(dict_count + 1);
242 for i in 0..=dict_count {
243 let pos = offsets_start + i * 4;
244 offsets.push(u32::from_le_bytes([extra[pos], extra[pos + 1], extra[pos + 2], extra[pos + 3]]) as usize);
245 }
246
247 let data_start = offsets_end;
248 let mut entries = Vec::with_capacity(dict_count);
249 for i in 0..dict_count {
250 let start = data_start + offsets[i];
251 let end = data_start + offsets[i + 1];
252 entries.push(extra[start..end].to_vec());
253 }
254
255 Ok(entries)
256}
257
258pub fn read_index(data: &[u8], i: usize, width: usize) -> u32 {
259 let off = i * width;
260 match width {
261 1 => data[off] as u32,
262 2 => u16::from_le_bytes([data[off], data[off + 1]]) as u32,
263 4 => u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]),
264 _ => unreachable!(),
265 }
266}