1use super::*;
2
3impl UnifiedStore {
4 pub fn new() -> Self {
5 Self::with_config(UnifiedStoreConfig::default())
6 }
7
8 pub fn format_version(&self) -> u32 {
10 self.format_version.load(Ordering::SeqCst)
11 }
12
13 pub(crate) fn set_format_version(&self, version: u32) {
14 self.format_version.store(version, Ordering::SeqCst);
15 }
16
17 pub fn next_entity_id(&self) -> EntityId {
19 EntityId::new(self.next_entity_id.fetch_add(1, Ordering::SeqCst))
20 }
21
22 pub fn reserve_entity_ids(&self, n: u64) -> std::ops::Range<u64> {
25 let start = self.next_entity_id.fetch_add(n, Ordering::SeqCst);
26 start..start + n
27 }
28
29 pub(crate) fn register_entity_id(&self, id: EntityId) {
30 let candidate = id.raw().saturating_add(1);
31 let mut current = self.next_entity_id.load(Ordering::SeqCst);
32 while candidate > current {
33 match self.next_entity_id.compare_exchange(
34 current,
35 candidate,
36 Ordering::SeqCst,
37 Ordering::SeqCst,
38 ) {
39 Ok(_) => break,
40 Err(updated) => current = updated,
41 }
42 }
43 }
44
45 pub fn load_from_file(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
57 let file = File::open(path)?;
58 let mut reader = BufReader::new(file);
59 let mut buf = Vec::new();
60 reader.read_to_end(&mut buf)?;
61 Self::load_from_bytes(&buf)
62 }
63
64 pub(crate) fn load_from_bytes(buf: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
65 Self::load_from_bytes_with_config(buf, UnifiedStoreConfig::default())
66 }
67
68 pub(crate) fn load_from_bytes_with_config(
69 buf: &[u8],
70 config: UnifiedStoreConfig,
71 ) -> Result<Self, Box<dyn std::error::Error>> {
72 let mut buf = buf.to_vec();
73 let version =
74 reddb_file::decode_native_store_header(&buf).map_err(|err| err.to_string())?;
75 let mut pos = 8;
76
77 reddb_file::verify_native_store_crc32_footer(&mut buf, version)
79 .map_err(|err| err.to_string())?;
80
81 let mut store = Self::with_config(config);
82 let embedded_wal_path = store.config.embedded_wal_path.take();
85 store.set_format_version(version);
86
87 let collection_count =
89 reddb_file::decode_native_dump_count(&buf, &mut pos).map_err(|e| e.to_string())?;
90
91 for _ in 0..collection_count {
93 let collection_header =
94 reddb_file::decode_native_dump_collection_header(&buf, &mut pos)
95 .map_err(|e| e.to_string())?;
96
97 for _ in 0..collection_header.entity_count {
99 if version >= STORE_VERSION_V7 {
100 let record_bytes = reddb_file::decode_native_dump_entity_record(&buf, &mut pos)
102 .map_err(|e| e.to_string())?;
103
104 let (entity, metadata) = Self::deserialize_entity_record(record_bytes, version)
105 .map_err(|e| format!("Entity record deserialization error: {e}"))?;
106
107 store.insert_auto(&collection_header.name, entity.clone())?;
108
109 if let Some(metadata) = metadata {
110 if let Some(manager) = store.get_collection(&collection_header.name) {
111 let _ = manager.set_metadata(entity.id, metadata);
112 }
113 }
114 } else {
115 let entity = Self::read_entity_binary(&buf, &mut pos, version)?;
117 store.insert_auto(&collection_header.name, entity)?;
118 }
119 }
120 }
121
122 if pos < buf.len() {
123 let cross_ref_count =
125 reddb_file::decode_native_dump_count(&buf, &mut pos).map_err(|e| e.to_string())?;
126
127 for _ in 0..cross_ref_count {
128 let cross_ref = reddb_file::decode_native_dump_cross_ref(&buf, &mut pos)
129 .map_err(|e| e.to_string())?;
130 let ref_type = RefType::from_byte(cross_ref.ref_type);
131
132 let source_collection = store
133 .get_any(EntityId::new(cross_ref.source_id))
134 .map(|(name, _)| name)
135 .unwrap_or_else(|| cross_ref.target_collection.clone());
136 let _ = store.add_cross_ref(
137 &source_collection,
138 EntityId::new(cross_ref.source_id),
139 &cross_ref.target_collection,
140 EntityId::new(cross_ref.target_id),
141 ref_type,
142 1.0,
143 );
144 }
145 }
146
147 if version >= reddb_file::STORE_VERSION_V10 && pos < buf.len() {
152 let aux = reddb_file::decode_native_dump_entity_record(&buf, &mut pos)
153 .map_err(|e| e.to_string())?;
154 if !aux.is_empty() {
155 store.set_aux_metadata(aux.to_vec());
156 }
157 }
158
159 if store.format_version() < STORE_VERSION_CURRENT {
160 store.set_format_version(STORE_VERSION_CURRENT);
161 }
162 store.config.embedded_wal_path = embedded_wal_path;
163
164 Ok(store)
165 }
166
167 pub fn save_to_file(&self, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
172 let buf = self.to_binary_dump_bytes();
173 reddb_file::write_native_store_bytes_atomically(path, &buf)?;
174 Ok(())
175 }
176
177 pub(crate) fn to_binary_dump_bytes(&self) -> Vec<u8> {
178 let mut buf = Vec::new();
179
180 buf.extend_from_slice(&reddb_file::encode_native_store_header(
183 STORE_VERSION_CURRENT,
184 ));
185
186 let collections = self.collections.read();
188 reddb_file::encode_native_dump_count(&mut buf, collections.len() as u32);
189
190 let fv = STORE_VERSION_CURRENT;
191 for (name, manager) in collections.iter() {
192 let entities = manager.query_all(|entity| {
194 !matches!(entity.data, EntityData::Row(_) | EntityData::Vector(_))
195 || !self
196 .snapshot_manager
197 .get()
198 .is_some_and(|manager| manager.is_aborted(entity.xmin))
199 });
200 reddb_file::encode_native_dump_collection_header(&mut buf, name, entities.len() as u32);
201
202 for mut entity in entities {
205 if matches!(entity.data, EntityData::Row(_))
208 && self
209 .snapshot_manager
210 .get()
211 .is_some_and(|manager| manager.is_aborted(entity.xmax))
212 {
213 entity.xmax = 0;
214 }
215 let metadata = manager.get_metadata(entity.id);
216 let record = Self::serialize_entity_record(&entity, metadata.as_ref(), fv);
217 reddb_file::encode_native_dump_entity_record(&mut buf, &record);
218 }
219 }
220
221 let cross_refs = self.cross_refs.read();
223 let total_refs: usize = cross_refs.values().map(|v| v.len()).sum();
224 reddb_file::encode_native_dump_count(&mut buf, total_refs as u32);
225
226 for (source_id, refs) in cross_refs.iter() {
227 for (target_id, ref_type, collection) in refs {
228 reddb_file::encode_native_dump_cross_ref(
229 &mut buf,
230 source_id.raw(),
231 target_id.raw(),
232 ref_type.to_byte(),
233 collection,
234 );
235 }
236 }
237
238 {
242 let aux = self.aux_metadata.read();
243 reddb_file::encode_native_dump_entity_record(&mut buf, &aux);
244 }
245
246 self.set_format_version(STORE_VERSION_CURRENT);
247
248 reddb_file::append_native_store_crc32_footer(&mut buf);
249 buf
250 }
251
252 pub(crate) fn read_entity_binary(
254 buf: &[u8],
255 pos: &mut usize,
256 format_version: u32,
257 ) -> Result<UnifiedEntity, Box<dyn std::error::Error>> {
258 let id = read_varu64(buf, pos).map_err(|e| format!("Failed to read entity id: {:?}", e))?;
260
261 let kind_type = buf[*pos];
263 *pos += 1;
264
265 let kind = match kind_type {
267 0 => {
268 let table_len = Self::read_varu32_safe(buf, pos)?;
270 let table = String::from_utf8(buf[*pos..*pos + table_len].to_vec())?;
271 *pos += table_len;
272 let row_id = Self::read_varu64_safe(buf, pos)?;
273 EntityKind::TableRow {
274 table: table.into(),
275 row_id,
276 }
277 }
278 1 => {
279 let label_len = Self::read_varu32_safe(buf, pos)?;
281 let label = String::from_utf8(buf[*pos..*pos + label_len].to_vec())?;
282 *pos += label_len;
283 let node_type_len = Self::read_varu32_safe(buf, pos)?;
284 let node_type = String::from_utf8(buf[*pos..*pos + node_type_len].to_vec())?;
285 *pos += node_type_len;
286 EntityKind::GraphNode(Box::new(GraphNodeKind { label, node_type }))
287 }
288 2 => {
289 let label_len = Self::read_varu32_safe(buf, pos)?;
291 let label = String::from_utf8(buf[*pos..*pos + label_len].to_vec())?;
292 *pos += label_len;
293 let from_node_len = Self::read_varu32_safe(buf, pos)?;
294 let from_node = String::from_utf8(buf[*pos..*pos + from_node_len].to_vec())?;
295 *pos += from_node_len;
296 let to_node_len = Self::read_varu32_safe(buf, pos)?;
297 let to_node = String::from_utf8(buf[*pos..*pos + to_node_len].to_vec())?;
298 *pos += to_node_len;
299 let weight =
300 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
301 *pos += 4;
302 EntityKind::GraphEdge(Box::new(GraphEdgeKind {
303 label,
304 from_node,
305 to_node,
306 weight,
307 }))
308 }
309 3 => {
310 let collection_len = Self::read_varu32_safe(buf, pos)?;
312 let collection = String::from_utf8(buf[*pos..*pos + collection_len].to_vec())?;
313 *pos += collection_len;
314 EntityKind::Vector { collection }
315 }
316 4 => {
317 let series_len = Self::read_varu32_safe(buf, pos)?;
319 let series = String::from_utf8(buf[*pos..*pos + series_len].to_vec())?;
320 *pos += series_len;
321 let metric_len = Self::read_varu32_safe(buf, pos)?;
322 let metric = String::from_utf8(buf[*pos..*pos + metric_len].to_vec())?;
323 *pos += metric_len;
324 EntityKind::TimeSeriesPoint(Box::new(TimeSeriesPointKind { series, metric }))
325 }
326 5 => {
327 let queue_len = Self::read_varu32_safe(buf, pos)?;
329 let queue = String::from_utf8(buf[*pos..*pos + queue_len].to_vec())?;
330 *pos += queue_len;
331 let position = Self::read_varu64_safe(buf, pos)?;
332 EntityKind::QueueMessage { queue, position }
333 }
334 _ => return Err(format!("Unknown EntityKind type: {}", kind_type).into()),
335 };
336
337 let data_type = buf[*pos];
339 *pos += 1;
340
341 let mut data = match data_type {
343 0 => {
344 let col_count = Self::read_varu32_safe(buf, pos)?;
346 let mut columns = Vec::with_capacity(col_count);
347 for _ in 0..col_count {
348 columns.push(Self::read_value_binary(buf, pos)?);
349 }
350 EntityData::Row(RowData::new(columns))
351 }
352 1 => {
353 let prop_count = Self::read_varu32_safe(buf, pos)?;
355 let mut properties = HashMap::new();
356 for _ in 0..prop_count {
357 let key_len = Self::read_varu32_safe(buf, pos)?;
358 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
359 *pos += key_len;
360 let value = Self::read_value_binary(buf, pos)?;
361 properties.insert(key, value);
362 }
363 EntityData::Node(NodeData::with_properties(properties))
364 }
365 2 => {
366 let weight_bytes = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
368 *pos += 4;
369 let weight = f32::from_le_bytes(weight_bytes);
370 let prop_count = Self::read_varu32_safe(buf, pos)?;
371 let mut properties = HashMap::new();
372 for _ in 0..prop_count {
373 let key_len = Self::read_varu32_safe(buf, pos)?;
374 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
375 *pos += key_len;
376 let value = Self::read_value_binary(buf, pos)?;
377 properties.insert(key, value);
378 }
379 let mut edge = EdgeData::new(weight);
380 edge.properties = properties;
381 EntityData::Edge(edge)
382 }
383 3 => {
384 let dim = Self::read_varu32_safe(buf, pos)?;
386 let mut dense = Vec::with_capacity(dim);
387 for _ in 0..dim {
388 let bytes = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
389 *pos += 4;
390 dense.push(f32::from_le_bytes(bytes));
391 }
392 let mut vector = VectorData::new(dense);
393 if format_version >= STORE_VERSION_V6 {
394 let has_content = buf[*pos] != 0;
395 *pos += 1;
396 if has_content {
397 let content_len = Self::read_varu32_safe(buf, pos)?;
398 vector.content =
399 Some(String::from_utf8(buf[*pos..*pos + content_len].to_vec())?);
400 *pos += content_len;
401 }
402 }
403 EntityData::Vector(vector)
404 }
405 4 => {
406 let metric_len = Self::read_varu32_safe(buf, pos)?;
408 let metric = String::from_utf8(buf[*pos..*pos + metric_len].to_vec())?;
409 *pos += metric_len;
410 let timestamp_ns = Self::read_varu64_safe(buf, pos)?;
411 let value_bytes = [
412 buf[*pos],
413 buf[*pos + 1],
414 buf[*pos + 2],
415 buf[*pos + 3],
416 buf[*pos + 4],
417 buf[*pos + 5],
418 buf[*pos + 6],
419 buf[*pos + 7],
420 ];
421 *pos += 8;
422 let mut tags = HashMap::new();
423 let series_id = if format_version >= STORE_VERSION_V11 {
424 let interned = Self::read_varu32_safe(buf, pos)?;
429 if interned == 1 {
430 Some(Self::read_varu64_safe(buf, pos)?)
431 } else {
432 let tag_count = Self::read_varu32_safe(buf, pos)?;
433 tags = HashMap::with_capacity(tag_count);
434 for _ in 0..tag_count {
435 let key_len = Self::read_varu32_safe(buf, pos)?;
436 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
437 *pos += key_len;
438 let value_len = Self::read_varu32_safe(buf, pos)?;
439 let value = String::from_utf8(buf[*pos..*pos + value_len].to_vec())?;
440 *pos += value_len;
441 tags.insert(key, value);
442 }
443 None
444 }
445 } else if format_version >= STORE_VERSION_V5 {
446 let tag_count = Self::read_varu32_safe(buf, pos)?;
447 tags = HashMap::with_capacity(tag_count);
448 for _ in 0..tag_count {
449 let key_len = Self::read_varu32_safe(buf, pos)?;
450 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
451 *pos += key_len;
452 let value_len = Self::read_varu32_safe(buf, pos)?;
453 let value = String::from_utf8(buf[*pos..*pos + value_len].to_vec())?;
454 *pos += value_len;
455 tags.insert(key, value);
456 }
457 None
458 } else {
459 None
460 };
461 let fields = if format_version >= STORE_VERSION_V12 {
462 let field_count = Self::read_varu32_safe(buf, pos)?;
463 let mut fields = HashMap::with_capacity(field_count);
464 for _ in 0..field_count {
465 let key_len = Self::read_varu32_safe(buf, pos)?;
466 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
467 *pos += key_len;
468 let value = Self::read_value_binary(buf, pos)?;
469 fields.insert(key, value);
470 }
471 fields
472 } else {
473 HashMap::new()
474 };
475 EntityData::TimeSeries(crate::storage::unified::entity::TimeSeriesData {
476 metric,
477 series_id,
478 timestamp_ns,
479 value: f64::from_le_bytes(value_bytes),
480 tags,
481 fields,
482 })
483 }
484 5 => {
485 let payload = Self::read_value_binary(buf, pos)?;
487 let enqueued_at_ns = Self::read_varu64_safe(buf, pos)?;
488 let attempts = Self::read_varu32_safe(buf, pos)? as u32;
489 EntityData::QueueMessage(crate::storage::unified::entity::QueueMessageData {
490 payload,
491 priority: None,
492 enqueued_at_ns,
493 attempts,
494 max_attempts: 3,
495 acked: false,
496 })
497 }
498 6 => {
499 let field_count = Self::read_varu32_safe(buf, pos)?;
501 let mut named = HashMap::with_capacity(field_count);
502 for _ in 0..field_count {
503 let key_len = Self::read_varu32_safe(buf, pos)?;
504 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
505 *pos += key_len;
506 let value = Self::read_value_binary(buf, pos)?;
507 named.insert(key, value);
508 }
509 EntityData::Row(RowData {
510 columns: Vec::new(),
511 named: Some(named),
512 schema: None,
513 })
514 }
515 _ => return Err(format!("Unknown EntityData type: {}", data_type).into()),
516 };
517
518 let created_at = Self::read_varu64_safe(buf, pos)?;
520 let updated_at = Self::read_varu64_safe(buf, pos)?;
521
522 let embedding_count = Self::read_varu32_safe(buf, pos)?;
524 let mut embeddings = Vec::with_capacity(embedding_count);
525 for _ in 0..embedding_count {
526 let name_len = Self::read_varu32_safe(buf, pos)?;
527 let name = String::from_utf8(buf[*pos..*pos + name_len].to_vec())?;
528 *pos += name_len;
529
530 let dim = Self::read_varu32_safe(buf, pos)?;
531 let mut vector = Vec::with_capacity(dim);
532 for _ in 0..dim {
533 let bytes = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
534 *pos += 4;
535 vector.push(f32::from_le_bytes(bytes));
536 }
537
538 let model_len = Self::read_varu32_safe(buf, pos)?;
539 let model = String::from_utf8(buf[*pos..*pos + model_len].to_vec())?;
540 *pos += model_len;
541
542 embeddings.push(EmbeddingSlot::new(name, vector, model));
543 }
544
545 let cross_ref_count = Self::read_varu32_safe(buf, pos)?;
547 let mut cross_refs = Vec::with_capacity(cross_ref_count);
548 for _ in 0..cross_ref_count {
549 let source = Self::read_varu64_safe(buf, pos)?;
550 let target = Self::read_varu64_safe(buf, pos)?;
551 let ref_type_byte = buf[*pos];
552 *pos += 1;
553 let (target_collection, weight, created_at) = if format_version >= STORE_VERSION_V2 {
554 let coll_len = Self::read_varu32_safe(buf, pos)?;
555 let collection = String::from_utf8(buf[*pos..*pos + coll_len].to_vec())?;
556 *pos += coll_len;
557 let weight_bytes = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
558 *pos += 4;
559 let weight = f32::from_le_bytes(weight_bytes);
560 let created_at = Self::read_varu64_safe(buf, pos)?;
561 (collection, weight, created_at)
562 } else {
563 (String::new(), 1.0, 0)
564 };
565
566 let mut cross_ref = CrossRef::new(
567 EntityId::new(source),
568 EntityId::new(target),
569 target_collection,
570 RefType::from_byte(ref_type_byte),
571 );
572 cross_ref.weight = weight;
573 cross_ref.created_at = created_at;
574 cross_refs.push(cross_ref);
575 }
576
577 let sequence_id = Self::read_varu64_safe(buf, pos)?;
579
580 if format_version >= STORE_VERSION_V4 {
581 if let EntityData::QueueMessage(message) = &mut data {
582 if *pos < buf.len() {
583 let priority_present = buf[*pos] != 0;
584 *pos += 1;
585 message.priority = if priority_present {
586 if *pos + 4 > buf.len() {
587 return Err("truncated queue priority".into());
588 }
589 let bytes = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
590 *pos += 4;
591 Some(i32::from_le_bytes(bytes))
592 } else {
593 None
594 };
595 message.max_attempts = Self::read_varu32_safe(buf, pos)? as u32;
596 if *pos >= buf.len() {
597 return Err("truncated queue ack flag".into());
598 }
599 message.acked = buf[*pos] != 0;
600 *pos += 1;
601 }
602 }
603 }
604
605 let mut entity = UnifiedEntity::new(EntityId::new(id), kind, data);
606 entity.created_at = created_at;
607 entity.updated_at = updated_at;
608 entity.sequence_id = sequence_id;
609 if format_version >= STORE_VERSION_V8 && *pos < buf.len() {
610 let has_logical_id = buf[*pos] != 0;
611 *pos += 1;
612 if has_logical_id {
613 let logical_id = Self::read_varu64_safe(buf, pos)?;
614 entity.set_logical_id(EntityId::new(logical_id));
615 }
616 }
617 if format_version >= STORE_VERSION_V9 && *pos < buf.len() {
618 let xmin = Self::read_varu64_safe(buf, pos)?;
619 let xmax = Self::read_varu64_safe(buf, pos)?;
620 entity.set_xmin(xmin);
621 entity.set_xmax(xmax);
622 }
623 if !embeddings.is_empty() || !cross_refs.is_empty() {
624 entity.embeddings_mut().extend(embeddings);
625 entity.cross_refs_mut().extend(cross_refs);
626 }
627
628 Ok(entity)
629 }
630
631 fn read_varu32_safe(buf: &[u8], pos: &mut usize) -> Result<usize, Box<dyn std::error::Error>> {
633 read_varu32(buf, pos)
634 .map(|v| v as usize)
635 .map_err(|e| format!("Decode error: {:?}", e).into())
636 }
637
638 fn read_varu64_safe(buf: &[u8], pos: &mut usize) -> Result<u64, Box<dyn std::error::Error>> {
640 read_varu64(buf, pos).map_err(|e| format!("Decode error: {:?}", e).into())
641 }
642
643 pub(crate) fn write_entity_binary(
645 buf: &mut Vec<u8>,
646 entity: &UnifiedEntity,
647 format_version: u32,
648 ) {
649 write_varu64(buf, entity.id.raw());
651
652 match &entity.kind {
654 EntityKind::TableRow { table, row_id } => {
655 buf.push(0);
656 write_varu32(buf, table.len() as u32);
657 buf.extend_from_slice(table.as_bytes());
658 write_varu64(buf, *row_id);
659 }
660 EntityKind::GraphNode(ref node) => {
661 buf.push(1);
662 write_varu32(buf, node.label.len() as u32);
663 buf.extend_from_slice(node.label.as_bytes());
664 write_varu32(buf, node.node_type.len() as u32);
665 buf.extend_from_slice(node.node_type.as_bytes());
666 }
667 EntityKind::GraphEdge(ref edge) => {
668 buf.push(2);
669 write_varu32(buf, edge.label.len() as u32);
670 buf.extend_from_slice(edge.label.as_bytes());
671 write_varu32(buf, edge.from_node.len() as u32);
672 buf.extend_from_slice(edge.from_node.as_bytes());
673 write_varu32(buf, edge.to_node.len() as u32);
674 buf.extend_from_slice(edge.to_node.as_bytes());
675 buf.extend_from_slice(&edge.weight.to_le_bytes());
676 }
677 EntityKind::Vector { collection } => {
678 buf.push(3);
679 write_varu32(buf, collection.len() as u32);
680 buf.extend_from_slice(collection.as_bytes());
681 }
682 EntityKind::TimeSeriesPoint(ref ts) => {
683 buf.push(4);
684 write_varu32(buf, ts.series.len() as u32);
685 buf.extend_from_slice(ts.series.as_bytes());
686 write_varu32(buf, ts.metric.len() as u32);
687 buf.extend_from_slice(ts.metric.as_bytes());
688 }
689 EntityKind::QueueMessage { queue, position } => {
690 buf.push(5);
691 write_varu32(buf, queue.len() as u32);
692 buf.extend_from_slice(queue.as_bytes());
693 write_varu64(buf, *position);
694 }
695 }
696
697 match &entity.data {
699 EntityData::Row(row) => {
700 if let Some(ref named) = row.named {
701 buf.push(6);
708 write_varu32(buf, named.len() as u32);
709 let mut entries: Vec<_> = named.iter().collect();
710 entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
711 for (key, value) in entries {
712 write_varu32(buf, key.len() as u32);
713 buf.extend_from_slice(key.as_bytes());
714 Self::write_value_binary(buf, value);
715 }
716 } else {
717 buf.push(0);
718 write_varu32(buf, row.columns.len() as u32);
719 for col in &row.columns {
720 Self::write_value_binary(buf, col);
721 }
722 }
723 }
724 EntityData::Node(node) => {
725 buf.push(1);
726 write_varu32(buf, node.properties.len() as u32);
727 let mut entries: Vec<_> = node.properties.iter().collect();
728 entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
729 for (key, value) in entries {
730 write_varu32(buf, key.len() as u32);
731 buf.extend_from_slice(key.as_bytes());
732 Self::write_value_binary(buf, value);
733 }
734 }
735 EntityData::Edge(edge) => {
736 buf.push(2);
737 buf.extend_from_slice(&edge.weight.to_le_bytes());
738 write_varu32(buf, edge.properties.len() as u32);
739 let mut entries: Vec<_> = edge.properties.iter().collect();
740 entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
741 for (key, value) in entries {
742 write_varu32(buf, key.len() as u32);
743 buf.extend_from_slice(key.as_bytes());
744 Self::write_value_binary(buf, value);
745 }
746 }
747 EntityData::Vector(vec) => {
748 buf.push(3);
749 write_varu32(buf, vec.dense.len() as u32);
750 for f in &vec.dense {
751 buf.extend_from_slice(&f.to_le_bytes());
752 }
753 if format_version >= STORE_VERSION_V6 {
754 buf.push(u8::from(vec.content.is_some()));
755 if let Some(content) = &vec.content {
756 write_varu32(buf, content.len() as u32);
757 buf.extend_from_slice(content.as_bytes());
758 }
759 }
760 }
761 EntityData::TimeSeries(ts) => {
762 buf.push(4);
763 write_varu32(buf, ts.metric.len() as u32);
764 buf.extend_from_slice(ts.metric.as_bytes());
765 write_varu64(buf, ts.timestamp_ns);
766 buf.extend_from_slice(&ts.value.to_le_bytes());
767 if format_version >= STORE_VERSION_V11 {
768 match ts.series_id {
774 Some(series_id) => {
775 write_varu32(buf, 1);
776 write_varu64(buf, series_id);
777 }
778 None => {
779 write_varu32(buf, 0);
780 write_varu32(buf, ts.tags.len() as u32);
781 let mut tag_entries: Vec<_> = ts.tags.iter().collect();
782 tag_entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
783 for (key, value) in tag_entries {
784 write_varu32(buf, key.len() as u32);
785 buf.extend_from_slice(key.as_bytes());
786 write_varu32(buf, value.len() as u32);
787 buf.extend_from_slice(value.as_bytes());
788 }
789 }
790 }
791 } else if format_version >= STORE_VERSION_V5 {
792 write_varu32(buf, ts.tags.len() as u32);
793 let mut tag_entries: Vec<_> = ts.tags.iter().collect();
794 tag_entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
795 for (key, value) in tag_entries {
796 write_varu32(buf, key.len() as u32);
797 buf.extend_from_slice(key.as_bytes());
798 write_varu32(buf, value.len() as u32);
799 buf.extend_from_slice(value.as_bytes());
800 }
801 }
802 if format_version >= STORE_VERSION_V12 {
803 write_varu32(buf, ts.fields.len() as u32);
804 let mut field_entries: Vec<_> = ts.fields.iter().collect();
805 field_entries.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
806 for (key, value) in field_entries {
807 write_varu32(buf, key.len() as u32);
808 buf.extend_from_slice(key.as_bytes());
809 Self::write_value_binary(buf, value);
810 }
811 }
812 }
813 EntityData::QueueMessage(msg) => {
814 buf.push(5);
815 Self::write_value_binary(buf, &msg.payload);
816 write_varu64(buf, msg.enqueued_at_ns);
817 write_varu32(buf, msg.attempts);
818 }
819 }
820
821 write_varu64(buf, entity.created_at);
823 write_varu64(buf, entity.updated_at);
824
825 write_varu32(buf, entity.embeddings().len() as u32);
827 for emb in entity.embeddings() {
828 write_varu32(buf, emb.name.len() as u32);
829 buf.extend_from_slice(emb.name.as_bytes());
830 write_varu32(buf, emb.vector.len() as u32);
831 for f in &emb.vector {
832 buf.extend_from_slice(&f.to_le_bytes());
833 }
834 write_varu32(buf, emb.model.len() as u32);
835 buf.extend_from_slice(emb.model.as_bytes());
836 }
837
838 write_varu32(buf, entity.cross_refs().len() as u32);
840 for cross_ref in entity.cross_refs() {
841 write_varu64(buf, cross_ref.source.raw());
842 write_varu64(buf, cross_ref.target.raw());
843 buf.push(cross_ref.ref_type.to_byte());
844 if format_version >= STORE_VERSION_V2 {
845 write_varu32(buf, cross_ref.target_collection.len() as u32);
846 buf.extend_from_slice(cross_ref.target_collection.as_bytes());
847 buf.extend_from_slice(&cross_ref.weight.to_le_bytes());
848 write_varu64(buf, cross_ref.created_at);
849 }
850 }
851
852 write_varu64(buf, entity.sequence_id);
854
855 if format_version >= STORE_VERSION_V4 {
856 if let EntityData::QueueMessage(message) = &entity.data {
857 buf.push(u8::from(message.priority.is_some()));
858 if let Some(priority) = message.priority {
859 buf.extend_from_slice(&priority.to_le_bytes());
860 }
861 write_varu32(buf, message.max_attempts);
862 buf.push(u8::from(message.acked));
863 }
864 }
865
866 if format_version >= STORE_VERSION_V8 {
867 buf.push(u8::from(entity.has_explicit_logical_id()));
868 if entity.has_explicit_logical_id() {
869 write_varu64(buf, entity.logical_id().raw());
870 }
871 }
872 if format_version >= STORE_VERSION_V9 {
873 write_varu64(buf, entity.xmin);
874 write_varu64(buf, entity.xmax);
875 }
876 }
877
878 fn read_value_binary(buf: &[u8], pos: &mut usize) -> Result<Value, Box<dyn std::error::Error>> {
883 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
884
885 let type_byte = buf[*pos];
886 *pos += 1;
887
888 Ok(match type_byte {
889 0 => Value::Null,
890 1 => {
891 let b = buf[*pos] != 0;
892 *pos += 1;
893 Value::Boolean(b)
894 }
895 2 => {
896 let val = i64::from_le_bytes([
897 buf[*pos],
898 buf[*pos + 1],
899 buf[*pos + 2],
900 buf[*pos + 3],
901 buf[*pos + 4],
902 buf[*pos + 5],
903 buf[*pos + 6],
904 buf[*pos + 7],
905 ]);
906 *pos += 8;
907 Value::Integer(val)
908 }
909 3 => {
910 let val = u64::from_le_bytes([
911 buf[*pos],
912 buf[*pos + 1],
913 buf[*pos + 2],
914 buf[*pos + 3],
915 buf[*pos + 4],
916 buf[*pos + 5],
917 buf[*pos + 6],
918 buf[*pos + 7],
919 ]);
920 *pos += 8;
921 Value::UnsignedInteger(val)
922 }
923 4 => {
924 let val = f64::from_le_bytes([
925 buf[*pos],
926 buf[*pos + 1],
927 buf[*pos + 2],
928 buf[*pos + 3],
929 buf[*pos + 4],
930 buf[*pos + 5],
931 buf[*pos + 6],
932 buf[*pos + 7],
933 ]);
934 *pos += 8;
935 Value::Float(val)
936 }
937 5 => {
938 let len = Self::read_varu32_safe(buf, pos)?;
939 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
940 *pos += len;
941 Value::text(s)
942 }
943 6 => {
944 let len = Self::read_varu32_safe(buf, pos)?;
945 let bytes = buf[*pos..*pos + len].to_vec();
946 *pos += len;
947 Value::Blob(bytes)
948 }
949 7 => {
950 let val = i64::from_le_bytes([
951 buf[*pos],
952 buf[*pos + 1],
953 buf[*pos + 2],
954 buf[*pos + 3],
955 buf[*pos + 4],
956 buf[*pos + 5],
957 buf[*pos + 6],
958 buf[*pos + 7],
959 ]);
960 *pos += 8;
961 Value::Timestamp(val)
962 }
963 8 => {
964 let val = i64::from_le_bytes([
965 buf[*pos],
966 buf[*pos + 1],
967 buf[*pos + 2],
968 buf[*pos + 3],
969 buf[*pos + 4],
970 buf[*pos + 5],
971 buf[*pos + 6],
972 buf[*pos + 7],
973 ]);
974 *pos += 8;
975 Value::Duration(val)
976 }
977 9 => {
978 let version = buf[*pos];
980 *pos += 1;
981 if version == 4 {
982 let octets = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
983 *pos += 4;
984 Value::IpAddr(IpAddr::V4(Ipv4Addr::from(octets)))
985 } else {
986 let mut octets = [0u8; 16];
987 octets.copy_from_slice(&buf[*pos..*pos + 16]);
988 *pos += 16;
989 Value::IpAddr(IpAddr::V6(Ipv6Addr::from(octets)))
990 }
991 }
992 10 => {
993 let mut mac = [0u8; 6];
994 mac.copy_from_slice(&buf[*pos..*pos + 6]);
995 *pos += 6;
996 Value::MacAddr(mac)
997 }
998 11 => {
999 let len = Self::read_varu32_safe(buf, pos)?;
1000 let mut vector = Vec::with_capacity(len);
1001 for _ in 0..len {
1002 let bytes = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
1003 *pos += 4;
1004 vector.push(f32::from_le_bytes(bytes));
1005 }
1006 Value::Vector(vector)
1007 }
1008 12 => {
1009 let len = Self::read_varu32_safe(buf, pos)?;
1010 let bytes = buf[*pos..*pos + len].to_vec();
1011 *pos += len;
1012 Value::Json(bytes)
1013 }
1014 13 => {
1015 let mut uuid = [0u8; 16];
1016 uuid.copy_from_slice(&buf[*pos..*pos + 16]);
1017 *pos += 16;
1018 Value::Uuid(uuid)
1019 }
1020 14 => {
1021 let len = Self::read_varu32_safe(buf, pos)?;
1022 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1023 *pos += len;
1024 Value::NodeRef(s)
1025 }
1026 15 => {
1027 let len = Self::read_varu32_safe(buf, pos)?;
1028 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1029 *pos += len;
1030 Value::EdgeRef(s)
1031 }
1032 16 => {
1033 let len = Self::read_varu32_safe(buf, pos)?;
1034 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1035 *pos += len;
1036 let id = u64::from_le_bytes([
1037 buf[*pos],
1038 buf[*pos + 1],
1039 buf[*pos + 2],
1040 buf[*pos + 3],
1041 buf[*pos + 4],
1042 buf[*pos + 5],
1043 buf[*pos + 6],
1044 buf[*pos + 7],
1045 ]);
1046 *pos += 8;
1047 Value::VectorRef(s, id)
1048 }
1049 17 => {
1050 let len = Self::read_varu32_safe(buf, pos)?;
1051 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1052 *pos += len;
1053 let id = u64::from_le_bytes([
1054 buf[*pos],
1055 buf[*pos + 1],
1056 buf[*pos + 2],
1057 buf[*pos + 3],
1058 buf[*pos + 4],
1059 buf[*pos + 5],
1060 buf[*pos + 6],
1061 buf[*pos + 7],
1062 ]);
1063 *pos += 8;
1064 Value::RowRef(s, id)
1065 }
1066 18 => {
1067 let rgb = [buf[*pos], buf[*pos + 1], buf[*pos + 2]];
1068 *pos += 3;
1069 Value::Color(rgb)
1070 }
1071 19 => {
1072 let len = Self::read_varu32_safe(buf, pos)?;
1073 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1074 *pos += len;
1075 Value::Email(s)
1076 }
1077 20 => {
1078 let len = Self::read_varu32_safe(buf, pos)?;
1079 let s = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1080 *pos += len;
1081 Value::Url(s)
1082 }
1083 21 => {
1084 let val = u64::from_le_bytes([
1085 buf[*pos],
1086 buf[*pos + 1],
1087 buf[*pos + 2],
1088 buf[*pos + 3],
1089 buf[*pos + 4],
1090 buf[*pos + 5],
1091 buf[*pos + 6],
1092 buf[*pos + 7],
1093 ]);
1094 *pos += 8;
1095 Value::Phone(val)
1096 }
1097 22 => {
1098 let val =
1099 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1100 *pos += 4;
1101 Value::Semver(val)
1102 }
1103 23 => {
1104 let ip =
1105 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1106 *pos += 4;
1107 let prefix = buf[*pos];
1108 *pos += 1;
1109 Value::Cidr(ip, prefix)
1110 }
1111 24 => {
1112 let val =
1113 i32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1114 *pos += 4;
1115 Value::Date(val)
1116 }
1117 25 => {
1118 let val =
1119 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1120 *pos += 4;
1121 Value::Time(val)
1122 }
1123 26 => {
1124 let val = i64::from_le_bytes([
1125 buf[*pos],
1126 buf[*pos + 1],
1127 buf[*pos + 2],
1128 buf[*pos + 3],
1129 buf[*pos + 4],
1130 buf[*pos + 5],
1131 buf[*pos + 6],
1132 buf[*pos + 7],
1133 ]);
1134 *pos += 8;
1135 Value::Decimal(val)
1136 }
1137 27 => {
1138 let val = buf[*pos];
1139 *pos += 1;
1140 Value::EnumValue(val)
1141 }
1142 28 => {
1143 let len = Self::read_varu32_safe(buf, pos)?;
1144 let mut elems = Vec::with_capacity(len);
1145 for _ in 0..len {
1146 elems.push(Self::read_value_binary(buf, pos)?);
1147 }
1148 Value::Array(elems)
1149 }
1150 29 => {
1151 let val = i64::from_le_bytes([
1152 buf[*pos],
1153 buf[*pos + 1],
1154 buf[*pos + 2],
1155 buf[*pos + 3],
1156 buf[*pos + 4],
1157 buf[*pos + 5],
1158 buf[*pos + 6],
1159 buf[*pos + 7],
1160 ]);
1161 *pos += 8;
1162 Value::TimestampMs(val)
1163 }
1164 30 => {
1165 let val =
1166 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1167 *pos += 4;
1168 Value::Ipv4(val)
1169 }
1170 31 => {
1171 let mut bytes = [0u8; 16];
1172 bytes.copy_from_slice(&buf[*pos..*pos + 16]);
1173 *pos += 16;
1174 Value::Ipv6(bytes)
1175 }
1176 32 => {
1177 let ip =
1178 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1179 *pos += 4;
1180 let mask =
1181 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1182 *pos += 4;
1183 Value::Subnet(ip, mask)
1184 }
1185 33 => {
1186 let val = u16::from_le_bytes([buf[*pos], buf[*pos + 1]]);
1187 *pos += 2;
1188 Value::Port(val)
1189 }
1190 34 => {
1191 let val =
1192 i32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1193 *pos += 4;
1194 Value::Latitude(val)
1195 }
1196 35 => {
1197 let val =
1198 i32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1199 *pos += 4;
1200 Value::Longitude(val)
1201 }
1202 36 => {
1203 let lat =
1204 i32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1205 *pos += 4;
1206 let lon =
1207 i32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1208 *pos += 4;
1209 Value::GeoPoint(lat, lon)
1210 }
1211 37 => {
1212 let c = [buf[*pos], buf[*pos + 1]];
1213 *pos += 2;
1214 Value::Country2(c)
1215 }
1216 38 => {
1217 let c = [buf[*pos], buf[*pos + 1], buf[*pos + 2]];
1218 *pos += 3;
1219 Value::Country3(c)
1220 }
1221 39 => {
1222 let c = [buf[*pos], buf[*pos + 1]];
1223 *pos += 2;
1224 Value::Lang2(c)
1225 }
1226 40 => {
1227 let c = [
1228 buf[*pos],
1229 buf[*pos + 1],
1230 buf[*pos + 2],
1231 buf[*pos + 3],
1232 buf[*pos + 4],
1233 ];
1234 *pos += 5;
1235 Value::Lang5(c)
1236 }
1237 41 => {
1238 let c = [buf[*pos], buf[*pos + 1], buf[*pos + 2]];
1239 *pos += 3;
1240 Value::Currency(c)
1241 }
1242 42 => {
1243 let rgba = [buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]];
1244 *pos += 4;
1245 Value::ColorAlpha(rgba)
1246 }
1247 43 => {
1248 let val = i64::from_le_bytes([
1249 buf[*pos],
1250 buf[*pos + 1],
1251 buf[*pos + 2],
1252 buf[*pos + 3],
1253 buf[*pos + 4],
1254 buf[*pos + 5],
1255 buf[*pos + 6],
1256 buf[*pos + 7],
1257 ]);
1258 *pos += 8;
1259 Value::BigInt(val)
1260 }
1261 44 => {
1262 let col_len = Self::read_varu32_safe(buf, pos)?;
1263 let col = String::from_utf8(buf[*pos..*pos + col_len].to_vec())?;
1264 *pos += col_len;
1265 let key_len = Self::read_varu32_safe(buf, pos)?;
1266 let key = String::from_utf8(buf[*pos..*pos + key_len].to_vec())?;
1267 *pos += key_len;
1268 Value::KeyRef(col, key)
1269 }
1270 45 => {
1271 let col_len = Self::read_varu32_safe(buf, pos)?;
1272 let col = String::from_utf8(buf[*pos..*pos + col_len].to_vec())?;
1273 *pos += col_len;
1274 let id = u64::from_le_bytes([
1275 buf[*pos],
1276 buf[*pos + 1],
1277 buf[*pos + 2],
1278 buf[*pos + 3],
1279 buf[*pos + 4],
1280 buf[*pos + 5],
1281 buf[*pos + 6],
1282 buf[*pos + 7],
1283 ]);
1284 *pos += 8;
1285 Value::DocRef(col, id)
1286 }
1287 46 => {
1288 let len = Self::read_varu32_safe(buf, pos)?;
1289 let name = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1290 *pos += len;
1291 Value::TableRef(name)
1292 }
1293 47 => {
1294 let val =
1295 u32::from_le_bytes([buf[*pos], buf[*pos + 1], buf[*pos + 2], buf[*pos + 3]]);
1296 *pos += 4;
1297 Value::PageRef(val)
1298 }
1299 48 => {
1300 let len = Self::read_varu32_safe(buf, pos)?;
1301 let bytes = buf[*pos..*pos + len].to_vec();
1302 *pos += len;
1303 Value::Secret(bytes)
1304 }
1305 49 => {
1306 let len = Self::read_varu32_safe(buf, pos)?;
1307 let hash = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1308 *pos += len;
1309 Value::Password(hash)
1310 }
1311 50 => {
1312 let len = Self::read_varu32_safe(buf, pos)?;
1313 let code = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1314 *pos += len;
1315 Value::AssetCode(code)
1316 }
1317 51 => {
1318 let len = Self::read_varu32_safe(buf, pos)?;
1319 let asset_code = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1320 *pos += len;
1321 let scale = buf[*pos];
1322 *pos += 1;
1323 let minor_units = i64::from_le_bytes([
1324 buf[*pos],
1325 buf[*pos + 1],
1326 buf[*pos + 2],
1327 buf[*pos + 3],
1328 buf[*pos + 4],
1329 buf[*pos + 5],
1330 buf[*pos + 6],
1331 buf[*pos + 7],
1332 ]);
1333 *pos += 8;
1334 Value::Money {
1335 asset_code,
1336 minor_units,
1337 scale,
1338 }
1339 }
1340 52 => {
1341 let len = Self::read_varu32_safe(buf, pos)?;
1342 let value = String::from_utf8(buf[*pos..*pos + len].to_vec())?;
1343 *pos += len;
1344 Value::DecimalText(value)
1345 }
1346 0x85 => {
1348 let orig_len = Self::read_varu32_safe(buf, pos)?;
1349 let comp_len = Self::read_varu32_safe(buf, pos)?;
1350 let compressed = &buf[*pos..*pos + comp_len];
1351 *pos += comp_len;
1352 let mut out = vec![0u8; orig_len];
1353 zstd::bulk::decompress_to_buffer(compressed, &mut out)
1354 .map_err(|e| format!("C3 Text decompress: {e}"))?;
1355 Value::text(String::from_utf8(out).map_err(|e| format!("C3 Text UTF-8: {e}"))?)
1356 }
1357 0x86 => {
1358 let orig_len = Self::read_varu32_safe(buf, pos)?;
1359 let comp_len = Self::read_varu32_safe(buf, pos)?;
1360 let compressed = &buf[*pos..*pos + comp_len];
1361 *pos += comp_len;
1362 let mut out = vec![0u8; orig_len];
1363 zstd::bulk::decompress_to_buffer(compressed, &mut out)
1364 .map_err(|e| format!("C3 Blob decompress: {e}"))?;
1365 Value::Blob(out)
1366 }
1367 _ => return Err(format!("Unknown Value type: {}", type_byte).into()),
1368 })
1369 }
1370
1371 fn write_value_binary(buf: &mut Vec<u8>, value: &Value) {
1376 use std::net::IpAddr;
1377
1378 match value {
1379 Value::Null => buf.push(0),
1380 Value::Boolean(b) => {
1381 buf.push(1);
1382 buf.push(if *b { 1 } else { 0 });
1383 }
1384 Value::Integer(i) => {
1385 buf.push(2);
1386 buf.extend_from_slice(&i.to_le_bytes());
1387 }
1388 Value::UnsignedInteger(u) => {
1389 buf.push(3);
1390 buf.extend_from_slice(&u.to_le_bytes());
1391 }
1392 Value::Float(f) => {
1393 buf.push(4);
1394 buf.extend_from_slice(&f.to_le_bytes());
1395 }
1396 Value::Text(s) => {
1397 const TEXT_COMPRESS_THRESHOLD: usize = 2048;
1401 if s.len() >= TEXT_COMPRESS_THRESHOLD {
1402 if let Ok(compressed) = zstd::bulk::compress(s.as_bytes(), 3) {
1403 if compressed.len() < s.len() {
1404 buf.push(0x85); write_varu32(buf, s.len() as u32); write_varu32(buf, compressed.len() as u32);
1407 buf.extend_from_slice(&compressed);
1408 return; }
1410 }
1411 }
1412 buf.push(5);
1413 write_varu32(buf, s.len() as u32);
1414 buf.extend_from_slice(s.as_bytes());
1415 }
1416 Value::Blob(bytes) => {
1417 const BLOB_COMPRESS_THRESHOLD: usize = 2048;
1420 if bytes.len() >= BLOB_COMPRESS_THRESHOLD {
1421 if let Ok(compressed) = zstd::bulk::compress(bytes.as_slice(), 3) {
1422 if compressed.len() < bytes.len() {
1423 buf.push(0x86); write_varu32(buf, bytes.len() as u32);
1425 write_varu32(buf, compressed.len() as u32);
1426 buf.extend_from_slice(&compressed);
1427 return;
1428 }
1429 }
1430 }
1431 buf.push(6);
1432 write_varu32(buf, bytes.len() as u32);
1433 buf.extend_from_slice(bytes);
1434 }
1435 Value::Timestamp(t) => {
1436 buf.push(7);
1437 buf.extend_from_slice(&t.to_le_bytes());
1438 }
1439 Value::Duration(d) => {
1440 buf.push(8);
1441 buf.extend_from_slice(&d.to_le_bytes());
1442 }
1443 Value::IpAddr(ip) => {
1444 buf.push(9);
1445 match ip {
1446 IpAddr::V4(v4) => {
1447 buf.push(4);
1448 buf.extend_from_slice(&v4.octets());
1449 }
1450 IpAddr::V6(v6) => {
1451 buf.push(6);
1452 buf.extend_from_slice(&v6.octets());
1453 }
1454 }
1455 }
1456 Value::MacAddr(mac) => {
1457 buf.push(10);
1458 buf.extend_from_slice(mac);
1459 }
1460 Value::Vector(vec) => {
1461 buf.push(11);
1462 write_varu32(buf, vec.len() as u32);
1463 for f in vec {
1464 buf.extend_from_slice(&f.to_le_bytes());
1465 }
1466 }
1467 Value::Json(bytes) => {
1468 buf.push(12);
1469 write_varu32(buf, bytes.len() as u32);
1470 buf.extend_from_slice(bytes);
1471 }
1472 Value::Uuid(uuid) => {
1473 buf.push(13);
1474 buf.extend_from_slice(uuid);
1475 }
1476 Value::NodeRef(s) => {
1477 buf.push(14);
1478 write_varu32(buf, s.len() as u32);
1479 buf.extend_from_slice(s.as_bytes());
1480 }
1481 Value::EdgeRef(s) => {
1482 buf.push(15);
1483 write_varu32(buf, s.len() as u32);
1484 buf.extend_from_slice(s.as_bytes());
1485 }
1486 Value::VectorRef(s, id) => {
1487 buf.push(16);
1488 write_varu32(buf, s.len() as u32);
1489 buf.extend_from_slice(s.as_bytes());
1490 buf.extend_from_slice(&id.to_le_bytes());
1491 }
1492 Value::RowRef(s, id) => {
1493 buf.push(17);
1494 write_varu32(buf, s.len() as u32);
1495 buf.extend_from_slice(s.as_bytes());
1496 buf.extend_from_slice(&id.to_le_bytes());
1497 }
1498 Value::Color(rgb) => {
1499 buf.push(18);
1500 buf.extend_from_slice(rgb);
1501 }
1502 Value::Email(s) => {
1503 buf.push(19);
1504 write_varu32(buf, s.len() as u32);
1505 buf.extend_from_slice(s.as_bytes());
1506 }
1507 Value::Url(s) => {
1508 buf.push(20);
1509 write_varu32(buf, s.len() as u32);
1510 buf.extend_from_slice(s.as_bytes());
1511 }
1512 Value::Phone(n) => {
1513 buf.push(21);
1514 buf.extend_from_slice(&n.to_le_bytes());
1515 }
1516 Value::Semver(v) => {
1517 buf.push(22);
1518 buf.extend_from_slice(&v.to_le_bytes());
1519 }
1520 Value::Cidr(ip, prefix) => {
1521 buf.push(23);
1522 buf.extend_from_slice(&ip.to_le_bytes());
1523 buf.push(*prefix);
1524 }
1525 Value::Date(d) => {
1526 buf.push(24);
1527 buf.extend_from_slice(&d.to_le_bytes());
1528 }
1529 Value::Time(t) => {
1530 buf.push(25);
1531 buf.extend_from_slice(&t.to_le_bytes());
1532 }
1533 Value::Decimal(v) => {
1534 buf.push(26);
1535 buf.extend_from_slice(&v.to_le_bytes());
1536 }
1537 Value::EnumValue(i) => {
1538 buf.push(27);
1539 buf.push(*i);
1540 }
1541 Value::Array(elems) => {
1542 buf.push(28);
1543 write_varu32(buf, elems.len() as u32);
1544 for elem in elems {
1545 Self::write_value_binary(buf, elem);
1546 }
1547 }
1548 Value::TimestampMs(v) => {
1549 buf.push(29);
1550 buf.extend_from_slice(&v.to_le_bytes());
1551 }
1552 Value::Ipv4(v) => {
1553 buf.push(30);
1554 buf.extend_from_slice(&v.to_le_bytes());
1555 }
1556 Value::Ipv6(bytes) => {
1557 buf.push(31);
1558 buf.extend_from_slice(bytes);
1559 }
1560 Value::Subnet(ip, mask) => {
1561 buf.push(32);
1562 buf.extend_from_slice(&ip.to_le_bytes());
1563 buf.extend_from_slice(&mask.to_le_bytes());
1564 }
1565 Value::Port(v) => {
1566 buf.push(33);
1567 buf.extend_from_slice(&v.to_le_bytes());
1568 }
1569 Value::Latitude(v) => {
1570 buf.push(34);
1571 buf.extend_from_slice(&v.to_le_bytes());
1572 }
1573 Value::Longitude(v) => {
1574 buf.push(35);
1575 buf.extend_from_slice(&v.to_le_bytes());
1576 }
1577 Value::GeoPoint(lat, lon) => {
1578 buf.push(36);
1579 buf.extend_from_slice(&lat.to_le_bytes());
1580 buf.extend_from_slice(&lon.to_le_bytes());
1581 }
1582 Value::Country2(c) => {
1583 buf.push(37);
1584 buf.extend_from_slice(c);
1585 }
1586 Value::Country3(c) => {
1587 buf.push(38);
1588 buf.extend_from_slice(c);
1589 }
1590 Value::Lang2(c) => {
1591 buf.push(39);
1592 buf.extend_from_slice(c);
1593 }
1594 Value::Lang5(c) => {
1595 buf.push(40);
1596 buf.extend_from_slice(c);
1597 }
1598 Value::Currency(c) => {
1599 buf.push(41);
1600 buf.extend_from_slice(c);
1601 }
1602 Value::AssetCode(code) => {
1603 buf.push(50);
1604 write_varu32(buf, code.len() as u32);
1605 buf.extend_from_slice(code.as_bytes());
1606 }
1607 Value::Money {
1608 asset_code,
1609 minor_units,
1610 scale,
1611 } => {
1612 buf.push(51);
1613 write_varu32(buf, asset_code.len() as u32);
1614 buf.extend_from_slice(asset_code.as_bytes());
1615 buf.push(*scale);
1616 buf.extend_from_slice(&minor_units.to_le_bytes());
1617 }
1618 Value::ColorAlpha(rgba) => {
1619 buf.push(42);
1620 buf.extend_from_slice(rgba);
1621 }
1622 Value::BigInt(v) => {
1623 buf.push(43);
1624 buf.extend_from_slice(&v.to_le_bytes());
1625 }
1626 Value::KeyRef(col, key) => {
1627 buf.push(44);
1628 write_varu32(buf, col.len() as u32);
1629 buf.extend_from_slice(col.as_bytes());
1630 write_varu32(buf, key.len() as u32);
1631 buf.extend_from_slice(key.as_bytes());
1632 }
1633 Value::DocRef(col, id) => {
1634 buf.push(45);
1635 write_varu32(buf, col.len() as u32);
1636 buf.extend_from_slice(col.as_bytes());
1637 buf.extend_from_slice(&id.to_le_bytes());
1638 }
1639 Value::TableRef(name) => {
1640 buf.push(46);
1641 write_varu32(buf, name.len() as u32);
1642 buf.extend_from_slice(name.as_bytes());
1643 }
1644 Value::PageRef(page_id) => {
1645 buf.push(47);
1646 buf.extend_from_slice(&page_id.to_le_bytes());
1647 }
1648 Value::Secret(bytes) => {
1649 buf.push(48);
1650 write_varu32(buf, bytes.len() as u32);
1651 buf.extend_from_slice(bytes);
1652 }
1653 Value::Password(hash) => {
1654 buf.push(49);
1655 write_varu32(buf, hash.len() as u32);
1656 buf.extend_from_slice(hash.as_bytes());
1657 }
1658 Value::DecimalText(value) => {
1659 buf.push(52);
1660 write_varu32(buf, value.len() as u32);
1661 buf.extend_from_slice(value.as_bytes());
1662 }
1663 }
1664 }
1665}
1666
1667#[cfg(test)]
1668mod aux_metadata_dump_tests {
1669 use super::*;
1670 use std::collections::HashMap;
1671
1672 #[test]
1673 fn snapshot_load_preserves_existing_wal_and_restores_future_appends() {
1674 let directory = tempfile::tempdir().expect("directory");
1675 let path = directory.path().join("snapshot.rdb");
1676 let store = UnifiedStore::with_config(UnifiedStoreConfig::default());
1677 let old =
1678 UnifiedEntity::table_row(EntityId::new(1), "rows", 1, vec![Value::text("snapshot")]);
1679 store.insert_auto("rows", old).expect("snapshot row");
1680 let snapshot = store.to_binary_dump_bytes();
1681 crate::storage::EmbeddedRdbArtifact::create_with_snapshot(&path, &snapshot)
1682 .expect("artifact");
1683 let newer = UnifiedEntity::table_row(
1684 EntityId::new(1),
1685 "rows",
1686 1,
1687 vec![Value::text("durable WAL")],
1688 );
1689 let action = StoreWalAction::upsert_entity("rows", &newer, None, STORE_VERSION_CURRENT);
1690 UnifiedStore::with_config(UnifiedStoreConfig::default().with_embedded_wal_path(&path))
1691 .finish_paged_write([action])
1692 .expect("durable update after snapshot");
1693 let before = std::fs::read(&path).expect("original artifact bytes");
1694 let loaded = UnifiedStore::load_from_bytes_with_config(
1695 &snapshot,
1696 UnifiedStoreConfig::default().with_embedded_wal_path(&path),
1697 )
1698 .expect("reconstruct snapshot");
1699 assert_eq!(std::fs::read(&path).expect("artifact after load"), before);
1700 assert_eq!(
1701 loaded.config().embedded_wal_path.as_deref(),
1702 Some(path.as_path())
1703 );
1704
1705 let generation = crate::storage::EmbeddedRdbArtifact::open(&path)
1706 .expect("boundary before future write")
1707 .selected_superblock
1708 .generation;
1709 loaded
1710 .insert_auto(
1711 "rows",
1712 UnifiedEntity::table_row(
1713 EntityId::new(2),
1714 "rows",
1715 2,
1716 vec![Value::text("future write")],
1717 ),
1718 )
1719 .expect("future writes still append WAL");
1720 assert_eq!(
1721 crate::storage::EmbeddedRdbArtifact::open(&path)
1722 .expect("boundary after future write")
1723 .selected_superblock
1724 .generation,
1725 generation + 1
1726 );
1727 }
1728
1729 #[test]
1730 fn aux_metadata_round_trips_through_binary_dump() {
1731 let store = UnifiedStore::with_config(UnifiedStoreConfig::default());
1732 store.create_collection("k").expect("create collection");
1733 store.set_aux_metadata(b"contract-blob".to_vec());
1734
1735 let bytes = store.to_binary_dump_bytes();
1736 let reloaded = UnifiedStore::load_from_bytes(&bytes).expect("reload dump");
1737
1738 assert_eq!(reloaded.aux_metadata(), b"contract-blob".to_vec());
1739 }
1740
1741 #[test]
1742 fn empty_aux_metadata_round_trips_as_empty() {
1743 let store = UnifiedStore::with_config(UnifiedStoreConfig::default());
1744 let bytes = store.to_binary_dump_bytes();
1745 let reloaded = UnifiedStore::load_from_bytes(&bytes).expect("reload dump");
1746
1747 assert!(reloaded.aux_metadata().is_empty());
1748 }
1749
1750 #[test]
1751 fn interned_timeseries_series_reduces_repeated_tag_storage() {
1752 let tags = HashMap::from([
1753 (
1754 "host".to_string(),
1755 "srv-very-long-hostname-0001".to_string(),
1756 ),
1757 ("region".to_string(), "us-east-1".to_string()),
1758 ("service".to_string(), "checkout-api".to_string()),
1759 ]);
1760 let inline_bytes: usize = (0..100)
1761 .map(|timestamp_ns| {
1762 let entity = UnifiedEntity::new(
1763 EntityId::new(0),
1764 EntityKind::TimeSeriesPoint(Box::new(TimeSeriesPointKind {
1765 series: "cpu_metrics".to_string(),
1766 metric: "cpu.idle".to_string(),
1767 })),
1768 EntityData::TimeSeries(crate::storage::TimeSeriesData {
1769 metric: "cpu.idle".to_string(),
1770 series_id: None,
1771 timestamp_ns,
1772 value: 94.8,
1773 tags: tags.clone(),
1774 fields: HashMap::new(),
1775 }),
1776 );
1777 UnifiedStore::serialize_entity_record(&entity, None, reddb_file::STORE_VERSION_V10)
1778 .len()
1779 })
1780 .sum();
1781
1782 let mut dictionary_fields = HashMap::new();
1783 dictionary_fields.insert("collection".to_string(), Value::text("cpu_metrics"));
1784 dictionary_fields.insert("series_id".to_string(), Value::UnsignedInteger(0));
1785 dictionary_fields.insert("metric".to_string(), Value::text("cpu.idle"));
1786 dictionary_fields.insert(
1787 "canonical_tags".to_string(),
1788 Value::text(
1789 r#"{"host":"srv-very-long-hostname-0001","region":"us-east-1","service":"checkout-api"}"#,
1790 ),
1791 );
1792 dictionary_fields.insert(
1793 "tags".to_string(),
1794 Value::Json(
1795 br#"{"host":"srv-very-long-hostname-0001","region":"us-east-1","service":"checkout-api"}"#
1796 .to_vec(),
1797 ),
1798 );
1799 let dictionary = UnifiedEntity::new(
1800 EntityId::new(0),
1801 EntityKind::TableRow {
1802 table: Arc::from("red_timeseries_series"),
1803 row_id: 0,
1804 },
1805 EntityData::Row(RowData {
1806 columns: Vec::new(),
1807 named: Some(dictionary_fields),
1808 schema: None,
1809 }),
1810 );
1811 let dictionary_bytes =
1812 UnifiedStore::serialize_entity_record(&dictionary, None, STORE_VERSION_V11).len();
1813 let interned_bytes = dictionary_bytes
1814 + (0..100)
1815 .map(|timestamp_ns| {
1816 let entity = UnifiedEntity::new(
1817 EntityId::new(0),
1818 EntityKind::TimeSeriesPoint(Box::new(TimeSeriesPointKind {
1819 series: "cpu_metrics".to_string(),
1820 metric: "cpu.idle".to_string(),
1821 })),
1822 EntityData::TimeSeries(crate::storage::TimeSeriesData {
1823 metric: "cpu.idle".to_string(),
1824 series_id: Some(0),
1825 timestamp_ns,
1826 value: 94.8,
1827 tags: HashMap::new(),
1828 fields: HashMap::new(),
1829 }),
1830 );
1831 UnifiedStore::serialize_entity_record(&entity, None, STORE_VERSION_V11).len()
1832 })
1833 .sum::<usize>();
1834
1835 assert!(
1836 interned_bytes < inline_bytes,
1837 "interned series storage should shrink repeated tags: inline={inline_bytes}, interned={interned_bytes}"
1838 );
1839 }
1840}