Skip to main content

reifydb_codec/encoded/
blob.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_value::{
5	reifydb_assertions,
6	value::{blob::Blob, value_type::ValueType},
7};
8
9use crate::encoded::{row::EncodedRow, shape::RowShape};
10
11impl RowShape {
12	pub fn set_blob(&self, row: &mut EncodedRow, index: usize, value: &Blob) {
13		reifydb_assertions! {
14			assert!(
15				row.len() >= self.total_static_size(),
16				"row/shape size mismatch: row.len()={} < total_static_size()={}",
17				row.len(),
18				self.total_static_size()
19			);
20			assert_eq!(*self.fields()[index].constraint.get_type().inner_type(), ValueType::Blob);
21		}
22		self.replace_dynamic_data(row, index, value.as_bytes());
23	}
24
25	pub fn get_blob(&self, row: &EncodedRow, index: usize) -> Blob {
26		let field = &self.fields()[index];
27		reifydb_assertions! {
28			assert!(
29				row.len() >= self.total_static_size(),
30				"row/shape size mismatch: row.len()={} < total_static_size()={}",
31				row.len(),
32				self.total_static_size()
33			);
34			assert_eq!(*field.constraint.get_type().inner_type(), ValueType::Blob);
35		}
36
37		let ref_slice = &row.as_slice()[field.offset as usize..field.offset as usize + 8];
38		let offset = u32::from_le_bytes([ref_slice[0], ref_slice[1], ref_slice[2], ref_slice[3]]) as usize;
39		let length = u32::from_le_bytes([ref_slice[4], ref_slice[5], ref_slice[6], ref_slice[7]]) as usize;
40
41		let dynamic_start = self.dynamic_section_start();
42		let blob_start = dynamic_start + offset;
43		let blob_slice = &row.as_slice()[blob_start..blob_start + length];
44
45		Blob::from_slice(blob_slice)
46	}
47
48	pub fn try_get_blob(&self, row: &EncodedRow, index: usize) -> Option<Blob> {
49		if row.is_defined(index) && self.fields()[index].constraint.get_type() == ValueType::Blob {
50			Some(self.get_blob(row, index))
51		} else {
52			None
53		}
54	}
55}
56
57#[cfg(test)]
58pub mod tests {
59	use reifydb_value::value::{blob::Blob, value_type::ValueType};
60
61	use crate::encoded::shape::RowShape;
62
63	#[test]
64	fn test_set_get_blob() {
65		let shape = RowShape::testing(&[ValueType::Blob]);
66		let mut row = shape.allocate();
67
68		let blob = Blob::from_slice(&[1, 2, 3, 4, 5]);
69		shape.set_blob(&mut row, 0, &blob);
70		assert_eq!(shape.get_blob(&row, 0), blob);
71	}
72
73	#[test]
74	fn test_try_get_blob() {
75		let shape = RowShape::testing(&[ValueType::Blob]);
76		let mut row = shape.allocate();
77
78		assert_eq!(shape.try_get_blob(&row, 0), None);
79
80		let blob = Blob::from_slice(&[1, 2, 3, 4, 5]);
81		shape.set_blob(&mut row, 0, &blob);
82		assert_eq!(shape.try_get_blob(&row, 0), Some(blob));
83	}
84
85	#[test]
86	fn test_empty() {
87		let shape = RowShape::testing(&[ValueType::Blob]);
88		let mut row = shape.allocate();
89
90		let empty_blob = Blob::from_slice(&[]);
91		shape.set_blob(&mut row, 0, &empty_blob);
92		assert_eq!(shape.get_blob(&row, 0), empty_blob);
93		assert_eq!(shape.try_get_blob(&row, 0), Some(empty_blob));
94	}
95
96	#[test]
97	fn test_binary_data() {
98		let shape = RowShape::testing(&[ValueType::Blob]);
99		let mut row = shape.allocate();
100
101		// Test with various binary data patterns
102		let binary_data = vec![
103			0x00, 0xFF, 0xAA, 0x55, 0xCC, 0x33, 0x00, 0xFF, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80,
104		];
105		let blob = Blob::from_slice(&binary_data);
106		shape.set_blob(&mut row, 0, &blob);
107		assert_eq!(shape.get_blob(&row, 0), blob);
108	}
109
110	#[test]
111	fn test_large_data() {
112		let shape = RowShape::testing(&[ValueType::Blob]);
113		let mut row = shape.allocate();
114
115		// Create a large blob (1KB)
116		let large_data: Vec<u8> = (0..1024).map(|i| (i % 256) as u8).collect();
117		let large_blob = Blob::from_slice(&large_data);
118		shape.set_blob(&mut row, 0, &large_blob);
119		assert_eq!(shape.get_blob(&row, 0), large_blob);
120	}
121
122	#[test]
123	fn test_multiple_fields() {
124		let shape = RowShape::testing(&[ValueType::Blob, ValueType::Blob, ValueType::Blob]);
125		let mut row = shape.allocate();
126
127		let blob1 = Blob::from_slice(&[1, 2, 3]);
128		let blob2 = Blob::from_slice(&[4, 5, 6, 7, 8]);
129		let blob3 = Blob::from_slice(&[9]);
130
131		shape.set_blob(&mut row, 0, &blob1);
132		shape.set_blob(&mut row, 1, &blob2);
133		shape.set_blob(&mut row, 2, &blob3);
134
135		assert_eq!(shape.get_blob(&row, 0), blob1);
136		assert_eq!(shape.get_blob(&row, 1), blob2);
137		assert_eq!(shape.get_blob(&row, 2), blob3);
138	}
139
140	#[test]
141	fn test_mixed_with_static_fields() {
142		let shape = RowShape::testing(&[ValueType::Boolean, ValueType::Blob, ValueType::Int4, ValueType::Blob]);
143		let mut row = shape.allocate();
144
145		let blob1 = Blob::from_slice(&[0xFF, 0x00, 0xAA]);
146		let blob2 = Blob::from_slice(&[0x11, 0x22, 0x33, 0x44]);
147
148		shape.set_bool(&mut row, 0, true);
149		shape.set_blob(&mut row, 1, &blob1);
150		shape.set_i32(&mut row, 2, -12345);
151		shape.set_blob(&mut row, 3, &blob2);
152
153		assert_eq!(shape.get_bool(&row, 0), true);
154		assert_eq!(shape.get_blob(&row, 1), blob1);
155		assert_eq!(shape.get_i32(&row, 2), -12345);
156		assert_eq!(shape.get_blob(&row, 3), blob2);
157	}
158
159	#[test]
160	fn test_different_sizes() {
161		let shape = RowShape::testing(&[ValueType::Blob, ValueType::Blob, ValueType::Blob]);
162		let mut row = shape.allocate();
163
164		let empty_blob = Blob::from_slice(&[]);
165		let medium_blob = Blob::from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
166		let single_byte_blob = Blob::from_slice(&[42]);
167
168		shape.set_blob(&mut row, 0, &empty_blob);
169		shape.set_blob(&mut row, 1, &medium_blob);
170		shape.set_blob(&mut row, 2, &single_byte_blob);
171
172		assert_eq!(shape.get_blob(&row, 0), empty_blob);
173		assert_eq!(shape.get_blob(&row, 1), medium_blob);
174		assert_eq!(shape.get_blob(&row, 2), single_byte_blob);
175	}
176
177	#[test]
178	fn test_arbitrary_setting_order() {
179		let shape = RowShape::testing(&[ValueType::Blob, ValueType::Blob, ValueType::Blob, ValueType::Blob]);
180		let mut row = shape.allocate();
181
182		let blob0 = Blob::from_slice(&[10, 20]);
183		let blob1 = Blob::from_slice(&[30, 40, 50]);
184		let blob2 = Blob::from_slice(&[60]);
185		let blob3 = Blob::from_slice(&[70, 80, 90, 100]);
186
187		// Set in reverse order
188		shape.set_blob(&mut row, 3, &blob3);
189		shape.set_blob(&mut row, 1, &blob1);
190		shape.set_blob(&mut row, 0, &blob0);
191		shape.set_blob(&mut row, 2, &blob2);
192
193		assert_eq!(shape.get_blob(&row, 0), blob0);
194		assert_eq!(shape.get_blob(&row, 1), blob1);
195		assert_eq!(shape.get_blob(&row, 2), blob2);
196		assert_eq!(shape.get_blob(&row, 3), blob3);
197	}
198
199	#[test]
200	fn test_undefined_handling() {
201		let shape = RowShape::testing(&[ValueType::Blob, ValueType::Blob, ValueType::Blob]);
202		let mut row = shape.allocate();
203
204		let blob = Blob::from_slice(&[1, 2, 3, 4]);
205
206		// Set only some fields
207		shape.set_blob(&mut row, 0, &blob);
208		shape.set_blob(&mut row, 2, &blob);
209
210		assert_eq!(shape.try_get_blob(&row, 0), Some(blob.clone()));
211		assert_eq!(shape.try_get_blob(&row, 1), None);
212		assert_eq!(shape.try_get_blob(&row, 2), Some(blob.clone()));
213
214		// Set field as undefined
215		shape.set_none(&mut row, 0);
216		assert_eq!(shape.try_get_blob(&row, 0), None);
217		assert_eq!(shape.try_get_blob(&row, 2), Some(blob));
218	}
219
220	#[test]
221	fn test_all_byte_values() {
222		let shape = RowShape::testing(&[ValueType::Blob]);
223		let mut row = shape.allocate();
224
225		// Create blob with all possible byte values (0-255)
226		let all_bytes: Vec<u8> = (0..=255).collect();
227		let full_range_blob = Blob::from_slice(&all_bytes);
228		shape.set_blob(&mut row, 0, &full_range_blob);
229		assert_eq!(shape.get_blob(&row, 0), full_range_blob);
230	}
231
232	#[test]
233	fn test_try_get_blob_wrong_type() {
234		let shape = RowShape::testing(&[ValueType::Boolean]);
235		let mut row = shape.allocate();
236
237		shape.set_bool(&mut row, 0, true);
238
239		assert_eq!(shape.try_get_blob(&row, 0), None);
240	}
241
242	#[test]
243	fn test_update_blob() {
244		let shape = RowShape::testing(&[ValueType::Blob]);
245		let mut row = shape.allocate();
246
247		let blob1 = Blob::from_slice(&[1, 2, 3]);
248		shape.set_blob(&mut row, 0, &blob1);
249		assert_eq!(shape.get_blob(&row, 0), blob1);
250
251		// Overwrite with larger blob
252		let blob2 = Blob::from_slice(&[4, 5, 6, 7, 8]);
253		shape.set_blob(&mut row, 0, &blob2);
254		assert_eq!(shape.get_blob(&row, 0), blob2);
255
256		// Overwrite with smaller blob
257		let blob3 = Blob::from_slice(&[9]);
258		shape.set_blob(&mut row, 0, &blob3);
259		assert_eq!(shape.get_blob(&row, 0), blob3);
260		assert_eq!(row.len(), shape.total_static_size() + 1);
261
262		// Overwrite with empty blob
263		let empty = Blob::from_slice(&[]);
264		shape.set_blob(&mut row, 0, &empty);
265		assert_eq!(shape.get_blob(&row, 0), empty);
266		assert_eq!(row.len(), shape.total_static_size());
267	}
268
269	#[test]
270	fn test_update_blob_with_other_dynamic_fields() {
271		let shape = RowShape::testing(&[ValueType::Blob, ValueType::Utf8, ValueType::Blob]);
272		let mut row = shape.allocate();
273
274		shape.set_blob(&mut row, 0, &Blob::from_slice(&[1, 2, 3]));
275		shape.set_utf8(&mut row, 1, "hello");
276		shape.set_blob(&mut row, 2, &Blob::from_slice(&[4, 5]));
277
278		// Update first blob
279		shape.set_blob(&mut row, 0, &Blob::from_slice(&[10, 20, 30, 40, 50]));
280
281		assert_eq!(shape.get_blob(&row, 0), Blob::from_slice(&[10, 20, 30, 40, 50]));
282		assert_eq!(shape.get_utf8(&row, 1), "hello");
283		assert_eq!(shape.get_blob(&row, 2), Blob::from_slice(&[4, 5]));
284	}
285}