Skip to main content

reddb_server/storage/unified/store/
impl_file.rs

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