Skip to main content

reifydb_codec/frame/encoding/
dict.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{collections::HashMap, str};
5
6use reifydb_value::value::{
7	blob::Blob,
8	container::{blob::BlobContainer, utf8::Utf8Container},
9};
10
11use super::plain::PlainEncoded;
12use crate::{error::DecodeError, frame::format::dict_index_width_to_flags, tag::ValueKind};
13
14pub struct DictEncoded {
15	pub data: Vec<u8>,
16
17	pub extra: Vec<u8>,
18
19	pub type_code: u8,
20
21	pub flags_bits: u8,
22}
23
24pub fn try_dict_encode_utf8(container: &Utf8Container, min_ratio: f64) -> Option<DictEncoded> {
25	let row_count = container.len();
26	if row_count == 0 {
27		return None;
28	}
29
30	let mut dict_map: HashMap<&str, u32> = HashMap::new();
31	let mut dict_entries: Vec<&str> = Vec::new();
32
33	for s in container.iter_str() {
34		if !dict_map.contains_key(s) {
35			let idx = dict_entries.len() as u32;
36			dict_map.insert(s, idx);
37			dict_entries.push(s);
38		}
39	}
40
41	let dict_count = dict_entries.len();
42
43	if dict_count as f64 >= row_count as f64 * min_ratio {
44		return None;
45	}
46
47	let (index_width, flags_bits) = if dict_count <= 255 {
48		(1usize, dict_index_width_to_flags(1))
49	} else if dict_count <= 65535 {
50		(2, dict_index_width_to_flags(2))
51	} else {
52		(4, dict_index_width_to_flags(4))
53	};
54
55	let mut data = Vec::with_capacity(row_count * index_width);
56	for s in container.iter_str() {
57		let idx = dict_map[s];
58		match index_width {
59			1 => data.push(idx as u8),
60			2 => data.extend_from_slice(&(idx as u16).to_le_bytes()),
61			4 => data.extend_from_slice(&idx.to_le_bytes()),
62			_ => unreachable!(),
63		}
64	}
65
66	let mut extra = Vec::new();
67	extra.extend_from_slice(&(dict_count as u32).to_le_bytes());
68
69	let mut offset: u32 = 0;
70	extra.extend_from_slice(&offset.to_le_bytes());
71	for entry in &dict_entries {
72		offset += entry.len() as u32;
73		extra.extend_from_slice(&offset.to_le_bytes());
74	}
75
76	for entry in &dict_entries {
77		extra.extend_from_slice(entry.as_bytes());
78	}
79
80	Some(DictEncoded {
81		data,
82		extra,
83		type_code: ValueKind::Utf8.byte(),
84		flags_bits,
85	})
86}
87
88pub fn try_dict_encode_blob(container: &BlobContainer, min_ratio: f64) -> Option<DictEncoded> {
89	let row_count = container.len();
90	if row_count == 0 {
91		return None;
92	}
93
94	let mut dict_map: HashMap<&[u8], u32> = HashMap::new();
95	let mut dict_entries: Vec<&[u8]> = Vec::new();
96
97	for bytes in container.iter_bytes() {
98		if !dict_map.contains_key(bytes) {
99			let idx = dict_entries.len() as u32;
100			dict_map.insert(bytes, idx);
101			dict_entries.push(bytes);
102		}
103	}
104
105	let dict_count = dict_entries.len();
106
107	if dict_count as f64 >= row_count as f64 * min_ratio {
108		return None;
109	}
110
111	let (index_width, flags_bits) = if dict_count <= 255 {
112		(1usize, dict_index_width_to_flags(1))
113	} else if dict_count <= 65535 {
114		(2, dict_index_width_to_flags(2))
115	} else {
116		(4, dict_index_width_to_flags(4))
117	};
118
119	let mut data = Vec::with_capacity(row_count * index_width);
120	for bytes in container.iter_bytes() {
121		let idx = dict_map[bytes];
122		match index_width {
123			1 => data.push(idx as u8),
124			2 => data.extend_from_slice(&(idx as u16).to_le_bytes()),
125			4 => data.extend_from_slice(&idx.to_le_bytes()),
126			_ => unreachable!(),
127		}
128	}
129
130	let mut extra = Vec::new();
131	extra.extend_from_slice(&(dict_count as u32).to_le_bytes());
132
133	let mut offset: u32 = 0;
134	extra.extend_from_slice(&offset.to_le_bytes());
135	for entry in &dict_entries {
136		offset += entry.len() as u32;
137		extra.extend_from_slice(&offset.to_le_bytes());
138	}
139	for entry in &dict_entries {
140		extra.extend_from_slice(entry);
141	}
142
143	Some(DictEncoded {
144		data,
145		extra,
146		type_code: ValueKind::Blob.byte(),
147		flags_bits,
148	})
149}
150
151pub fn try_dict_encode_bytes(serialized: &[Vec<u8>], type_code: u8, min_ratio: f64) -> Option<DictEncoded> {
152	if serialized.is_empty() {
153		return None;
154	}
155
156	let mut dict_map: HashMap<&[u8], u32> = HashMap::new();
157	let mut dict_entries: Vec<&[u8]> = Vec::new();
158
159	for bytes in serialized {
160		if !dict_map.contains_key(bytes.as_slice()) {
161			let idx = dict_entries.len() as u32;
162			dict_map.insert(bytes.as_slice(), idx);
163			dict_entries.push(bytes.as_slice());
164		}
165	}
166
167	let dict_count = dict_entries.len();
168
169	if dict_count as f64 >= serialized.len() as f64 * min_ratio {
170		return None;
171	}
172
173	let (index_width, flags_bits) = if dict_count <= 255 {
174		(1usize, dict_index_width_to_flags(1))
175	} else if dict_count <= 65535 {
176		(2, dict_index_width_to_flags(2))
177	} else {
178		(4, dict_index_width_to_flags(4))
179	};
180
181	let mut data = Vec::with_capacity(serialized.len() * index_width);
182	for bytes in serialized {
183		let idx = dict_map[bytes.as_slice()];
184		match index_width {
185			1 => data.push(idx as u8),
186			2 => data.extend_from_slice(&(idx as u16).to_le_bytes()),
187			4 => data.extend_from_slice(&idx.to_le_bytes()),
188			_ => unreachable!(),
189		}
190	}
191
192	let mut extra = Vec::new();
193	extra.extend_from_slice(&(dict_count as u32).to_le_bytes());
194	let mut offset: u32 = 0;
195	extra.extend_from_slice(&offset.to_le_bytes());
196	for entry in &dict_entries {
197		offset += entry.len() as u32;
198		extra.extend_from_slice(&offset.to_le_bytes());
199	}
200	for entry in &dict_entries {
201		extra.extend_from_slice(entry);
202	}
203
204	Some(DictEncoded {
205		data,
206		extra,
207		type_code,
208		flags_bits,
209	})
210}
211
212impl DictEncoded {
213	pub fn into_plain_encoded(self) -> PlainEncoded {
214		PlainEncoded {
215			data: self.data,
216			offsets: vec![],
217			nones: vec![],
218			type_code: self.type_code,
219			has_nones: false,
220		}
221	}
222}
223
224pub fn decode_dict_utf8(
225	data: &[u8],
226	extra: &[u8],
227	row_count: usize,
228	index_width: usize,
229) -> Result<Vec<String>, DecodeError> {
230	let dict_entries = decode_dict_table(extra)?;
231
232	let mut values = Vec::with_capacity(row_count);
233	for i in 0..row_count {
234		let idx = read_index(data, i, index_width) as usize;
235		if idx >= dict_entries.len() {
236			return Err(DecodeError::InvalidData(format!(
237				"dict index {} out of range (dict has {} entries)",
238				idx,
239				dict_entries.len()
240			)));
241		}
242		values.push(dict_entries[idx].clone());
243	}
244
245	Ok(values)
246}
247
248pub fn decode_dict_blob(
249	data: &[u8],
250	extra: &[u8],
251	row_count: usize,
252	index_width: usize,
253) -> Result<Vec<Blob>, DecodeError> {
254	let dict_entries = decode_dict_table_bytes(extra)?;
255
256	let mut values = Vec::with_capacity(row_count);
257	for i in 0..row_count {
258		let idx = read_index(data, i, index_width) as usize;
259		if idx >= dict_entries.len() {
260			return Err(DecodeError::InvalidData(format!(
261				"dict index {} out of range (dict has {} entries)",
262				idx,
263				dict_entries.len()
264			)));
265		}
266		values.push(Blob::new(dict_entries[idx].clone()));
267	}
268
269	Ok(values)
270}
271
272fn decode_dict_table(extra: &[u8]) -> Result<Vec<String>, DecodeError> {
273	if extra.len() < 4 {
274		return Err(DecodeError::InvalidData("dict table too short".to_string()));
275	}
276
277	let dict_count = u32::from_le_bytes([extra[0], extra[1], extra[2], extra[3]]) as usize;
278	let offsets_start = 4;
279	let offsets_end = offsets_start + (dict_count + 1) * 4;
280
281	if extra.len() < offsets_end {
282		return Err(DecodeError::InvalidData("dict offsets truncated".to_string()));
283	}
284
285	let mut offsets = Vec::with_capacity(dict_count + 1);
286	for i in 0..=dict_count {
287		let pos = offsets_start + i * 4;
288		offsets.push(u32::from_le_bytes([extra[pos], extra[pos + 1], extra[pos + 2], extra[pos + 3]]) as usize);
289	}
290
291	let data_start = offsets_end;
292	let mut entries = Vec::with_capacity(dict_count);
293	for i in 0..dict_count {
294		let start = data_start + offsets[i];
295		let end = data_start + offsets[i + 1];
296		let s = str::from_utf8(&extra[start..end])
297			.map_err(|e| DecodeError::InvalidData(format!("invalid dict UTF-8: {}", e)))?;
298		entries.push(s.to_string());
299	}
300
301	Ok(entries)
302}
303
304pub fn decode_dict_table_bytes(extra: &[u8]) -> Result<Vec<Vec<u8>>, DecodeError> {
305	if extra.len() < 4 {
306		return Err(DecodeError::InvalidData("dict table too short".to_string()));
307	}
308
309	let dict_count = u32::from_le_bytes([extra[0], extra[1], extra[2], extra[3]]) as usize;
310	let offsets_start = 4;
311	let offsets_end = offsets_start + (dict_count + 1) * 4;
312
313	if extra.len() < offsets_end {
314		return Err(DecodeError::InvalidData("dict offsets truncated".to_string()));
315	}
316
317	let mut offsets = Vec::with_capacity(dict_count + 1);
318	for i in 0..=dict_count {
319		let pos = offsets_start + i * 4;
320		offsets.push(u32::from_le_bytes([extra[pos], extra[pos + 1], extra[pos + 2], extra[pos + 3]]) as usize);
321	}
322
323	let data_start = offsets_end;
324	let mut entries = Vec::with_capacity(dict_count);
325	for i in 0..dict_count {
326		let start = data_start + offsets[i];
327		let end = data_start + offsets[i + 1];
328		entries.push(extra[start..end].to_vec());
329	}
330
331	Ok(entries)
332}
333
334pub fn read_index(data: &[u8], i: usize, width: usize) -> u32 {
335	let off = i * width;
336	match width {
337		1 => data[off] as u32,
338		2 => u16::from_le_bytes([data[off], data[off + 1]]) as u32,
339		4 => u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]),
340		_ => unreachable!(),
341	}
342}