Skip to main content

lora_store/memory/
impls.rs

1//! `GraphStorage` / `BorrowedGraphStorage` / `GraphStorageMut` impls
2//! for [`InMemoryGraph`]. The trait surfaces — read, borrow, mutate —
3//! all delegate into the inherent helpers defined in `super`.
4
5use std::collections::BTreeSet;
6
7use lora_ast::Direction;
8
9use crate::{
10    BorrowedGraphStorage, GraphStorage, GraphStorageMut, MutationEvent, NodeId, NodeRecord,
11    Properties, PropertyValue, RelationshipId, RelationshipRecord,
12};
13
14use super::property_index::PropertyIndexKey;
15use super::InMemoryGraph;
16
17impl GraphStorage for InMemoryGraph {
18    // ---------- Required primitives ----------
19
20    fn contains_node(&self, id: NodeId) -> bool {
21        self.node_at(id).is_some()
22    }
23
24    fn node(&self, id: NodeId) -> Option<NodeRecord> {
25        self.node_at(id).cloned()
26    }
27
28    fn all_node_ids(&self) -> Vec<NodeId> {
29        self.iter_node_ids().collect()
30    }
31
32    fn node_ids_by_label(&self, label: &str) -> Vec<NodeId> {
33        match self.nodes_by_label.get(label) {
34            Some(ids) => ids.clone(),
35            None => Vec::new(),
36        }
37    }
38
39    fn contains_relationship(&self, id: RelationshipId) -> bool {
40        self.rel_at(id).is_some()
41    }
42
43    fn relationship(&self, id: RelationshipId) -> Option<RelationshipRecord> {
44        self.rel_at(id).cloned()
45    }
46
47    fn all_rel_ids(&self) -> Vec<RelationshipId> {
48        self.iter_rel_ids().collect()
49    }
50
51    fn rel_ids_by_type(&self, rel_type: &str) -> Vec<RelationshipId> {
52        match self.relationships_by_type.get(rel_type) {
53            Some(ids) => ids.clone(),
54            None => Vec::new(),
55        }
56    }
57
58    fn relationship_endpoints(&self, id: RelationshipId) -> Option<(NodeId, NodeId)> {
59        self.rel_at(id).map(|r| (r.src, r.dst))
60    }
61
62    fn expand_ids(
63        &self,
64        node_id: NodeId,
65        direction: Direction,
66        types: &[String],
67    ) -> Vec<(RelationshipId, NodeId)> {
68        if self.node_at(node_id).is_none() {
69            return Vec::new();
70        }
71
72        // Walk the adjacency Vec(s) directly into a single output Vec,
73        // skipping the previous intermediate `Vec<RelationshipId>`
74        // allocation that `relationship_ids_for_direction` produced.
75        // For type-filtered traversal we read `rel.rel_type` once per
76        // edge against the (typically tiny) `types` slice.
77        let mut out: Vec<(RelationshipId, NodeId)> = Vec::new();
78
79        let push_from = |adj: &Vec<RelationshipId>, out: &mut Vec<(RelationshipId, NodeId)>| {
80            for &rel_id in adj {
81                let Some(rel) = self.rel_at(rel_id) else {
82                    continue;
83                };
84                if !types.is_empty() && !types.iter().any(|t| t == &rel.rel_type) {
85                    continue;
86                }
87                let Some(other_id) = Self::other_endpoint(rel, node_id) else {
88                    continue;
89                };
90                out.push((rel_id, other_id));
91            }
92        };
93
94        match direction {
95            Direction::Right => {
96                if let Some(adj) = self.outgoing_at(node_id) {
97                    out.reserve(adj.len());
98                    push_from(adj, &mut out);
99                }
100            }
101            Direction::Left => {
102                if let Some(adj) = self.incoming_at(node_id) {
103                    out.reserve(adj.len());
104                    push_from(adj, &mut out);
105                }
106            }
107            Direction::Undirected => {
108                let out_len = self.outgoing_at(node_id).map(Vec::len).unwrap_or(0);
109                let in_len = self.incoming_at(node_id).map(Vec::len).unwrap_or(0);
110                out.reserve(out_len + in_len);
111                if let Some(adj) = self.outgoing_at(node_id) {
112                    push_from(adj, &mut out);
113                }
114                if let Some(adj) = self.incoming_at(node_id) {
115                    // Skip self-loops we already counted on the outgoing side.
116                    for &rel_id in adj {
117                        if out.iter().any(|(r, _)| *r == rel_id) {
118                            continue;
119                        }
120                        let Some(rel) = self.rel_at(rel_id) else {
121                            continue;
122                        };
123                        if !types.is_empty() && !types.iter().any(|t| t == &rel.rel_type) {
124                            continue;
125                        }
126                        let Some(other_id) = Self::other_endpoint(rel, node_id) else {
127                            continue;
128                        };
129                        out.push((rel_id, other_id));
130                    }
131                }
132            }
133        }
134        out
135    }
136
137    fn all_labels(&self) -> Vec<String> {
138        self.nodes_by_label.keys().cloned().collect()
139    }
140
141    fn all_relationship_types(&self) -> Vec<String> {
142        self.relationships_by_type.keys().cloned().collect()
143    }
144
145    // ---------- Optimization hooks: zero-clone borrow access ----------
146
147    fn with_node<F, R>(&self, id: NodeId, f: F) -> Option<R>
148    where
149        F: FnOnce(&NodeRecord) -> R,
150        Self: Sized,
151    {
152        self.node_at(id).map(f)
153    }
154
155    fn with_relationship<F, R>(&self, id: RelationshipId, f: F) -> Option<R>
156    where
157        F: FnOnce(&RelationshipRecord) -> R,
158        Self: Sized,
159    {
160        self.rel_at(id).map(f)
161    }
162
163    // ---------- Overrides: counts + existence ----------
164
165    fn has_node(&self, id: NodeId) -> bool {
166        self.node_at(id).is_some()
167    }
168
169    fn has_relationship(&self, id: RelationshipId) -> bool {
170        self.rel_at(id).is_some()
171    }
172
173    fn node_count(&self) -> usize {
174        self.live_node_count
175    }
176
177    fn relationship_count(&self) -> usize {
178        self.live_rel_count
179    }
180
181    // ---------- Overrides: record-returning scans (direct iteration) ----------
182
183    fn all_nodes(&self) -> Vec<NodeRecord> {
184        self.iter_node_records().cloned().collect()
185    }
186
187    fn nodes_by_label(&self, label: &str) -> Vec<NodeRecord> {
188        self.nodes_by_label
189            .get(label)
190            .into_iter()
191            .flat_map(|ids| ids.iter())
192            .filter_map(|&id| self.node_at(id).cloned())
193            .collect()
194    }
195
196    fn all_relationships(&self) -> Vec<RelationshipRecord> {
197        self.iter_rel_records().cloned().collect()
198    }
199
200    fn relationships_by_type(&self, rel_type: &str) -> Vec<RelationshipRecord> {
201        self.relationships_by_type
202            .get(rel_type)
203            .into_iter()
204            .flat_map(|ids| ids.iter())
205            .filter_map(|&id| self.rel_at(id).cloned())
206            .collect()
207    }
208
209    fn relationship_ids_of(&self, node_id: NodeId, direction: Direction) -> Vec<RelationshipId> {
210        self.relationship_ids_for_direction(node_id, direction)
211    }
212
213    fn outgoing_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord> {
214        self.outgoing_at(node_id)
215            .into_iter()
216            .flat_map(|ids| ids.iter())
217            .filter_map(|&id| self.rel_at(id).cloned())
218            .collect()
219    }
220
221    fn incoming_relationships(&self, node_id: NodeId) -> Vec<RelationshipRecord> {
222        self.incoming_at(node_id)
223            .into_iter()
224            .flat_map(|ids| ids.iter())
225            .filter_map(|&id| self.rel_at(id).cloned())
226            .collect()
227    }
228
229    fn degree(&self, node_id: NodeId, direction: Direction) -> usize {
230        match direction {
231            Direction::Right => self.outgoing_at(node_id).map(|s| s.len()).unwrap_or(0),
232            Direction::Left => self.incoming_at(node_id).map(|s| s.len()).unwrap_or(0),
233            Direction::Undirected => {
234                self.outgoing_at(node_id).map(|s| s.len()).unwrap_or(0)
235                    + self.incoming_at(node_id).map(|s| s.len()).unwrap_or(0)
236            }
237        }
238    }
239
240    fn expand(
241        &self,
242        node_id: NodeId,
243        direction: Direction,
244        types: &[String],
245    ) -> Vec<(RelationshipRecord, NodeRecord)> {
246        if self.node_at(node_id).is_none() {
247            return Vec::new();
248        }
249
250        let type_filter: Option<BTreeSet<&str>> = if types.is_empty() {
251            None
252        } else {
253            Some(types.iter().map(String::as_str).collect())
254        };
255
256        self.relationship_ids_for_direction(node_id, direction)
257            .into_iter()
258            .filter_map(|rel_id| self.rel_at(rel_id))
259            .filter(|rel| {
260                type_filter
261                    .as_ref()
262                    .map(|allowed| allowed.contains(rel.rel_type.as_str()))
263                    .unwrap_or(true)
264            })
265            .filter_map(|rel| {
266                let other_id = Self::other_endpoint(rel, node_id)?;
267                let other = self.node_at(other_id)?;
268                Some((rel.clone(), other.clone()))
269            })
270            .collect()
271    }
272
273    fn all_node_property_keys(&self) -> Vec<String> {
274        let mut keys = BTreeSet::new();
275        for node in self.iter_node_records() {
276            for key in node.properties.keys() {
277                keys.insert(key.clone());
278            }
279        }
280        keys.into_iter().collect()
281    }
282
283    // ---------- Overrides: traversal (direct adjacency) ----------
284
285    fn all_relationship_property_keys(&self) -> Vec<String> {
286        let mut keys = BTreeSet::new();
287        for rel in self.iter_rel_records() {
288            for key in rel.properties.keys() {
289                keys.insert(key.clone());
290            }
291        }
292        keys.into_iter().collect()
293    }
294
295    fn label_property_keys(&self, label: &str) -> Vec<String> {
296        let mut keys = BTreeSet::new();
297
298        if let Some(ids) = self.nodes_by_label.get(label) {
299            for &id in ids {
300                if let Some(node) = self.node_at(id) {
301                    for key in node.properties.keys() {
302                        keys.insert(key.clone());
303                    }
304                }
305            }
306        }
307
308        keys.into_iter().collect()
309    }
310
311    fn rel_type_property_keys(&self, rel_type: &str) -> Vec<String> {
312        let mut keys = BTreeSet::new();
313
314        if let Some(ids) = self.relationships_by_type.get(rel_type) {
315            for &id in ids {
316                if let Some(rel) = self.rel_at(id) {
317                    for key in rel.properties.keys() {
318                        keys.insert(key.clone());
319                    }
320                }
321            }
322        }
323
324        keys.into_iter().collect()
325    }
326
327    fn find_nodes_by_property(
328        &self,
329        label: Option<&str>,
330        key: &str,
331        value: &PropertyValue,
332    ) -> Vec<NodeRecord>
333    where
334        Self: Sized,
335    {
336        if PropertyIndexKey::from_value(value).is_none() {
337            return self.scan_nodes_by_property(label, key, value);
338        }
339
340        self.ensure_node_property_index(key);
341        let indexes = self.indexes_read();
342
343        match label {
344            Some(label) => {
345                let Some(ids) = indexes.node_properties.scoped_ids_for(label, key, value) else {
346                    return Vec::new();
347                };
348                ids.iter()
349                    .filter_map(|&id| self.node_at(id).cloned())
350                    .collect()
351            }
352            None => indexes
353                .node_properties
354                .ids_for(key, value)
355                .into_iter()
356                .flat_map(|ids| ids.iter())
357                .filter_map(|&id| self.node_at(id).cloned())
358                .collect(),
359        }
360    }
361
362    fn find_node_ids_by_property(
363        &self,
364        label: Option<&str>,
365        key: &str,
366        value: &PropertyValue,
367    ) -> Vec<NodeId>
368    where
369        Self: Sized,
370    {
371        if PropertyIndexKey::from_value(value).is_none() {
372            return self
373                .scan_nodes_by_property(label, key, value)
374                .into_iter()
375                .map(|n| n.id)
376                .collect();
377        }
378
379        self.ensure_node_property_index(key);
380        let indexes = self.indexes_read();
381
382        match label {
383            Some(label) => indexes
384                .node_properties
385                .scoped_ids_for(label, key, value)
386                .map(|ids| ids.iter().copied().collect())
387                .unwrap_or_default(),
388            None => indexes
389                .node_properties
390                .ids_for(key, value)
391                .map(|ids| ids.iter().copied().collect())
392                .unwrap_or_default(),
393        }
394    }
395    // ---------- Overrides: schema introspection ----------
396
397    fn find_relationships_by_property(
398        &self,
399        rel_type: Option<&str>,
400        key: &str,
401        value: &PropertyValue,
402    ) -> Vec<RelationshipRecord>
403    where
404        Self: Sized,
405    {
406        if PropertyIndexKey::from_value(value).is_none() {
407            return self.scan_relationships_by_property(rel_type, key, value);
408        }
409
410        self.ensure_relationship_property_index(key);
411        let indexes = self.indexes_read();
412
413        match rel_type {
414            Some(rel_type) => {
415                let Some(ids) = indexes
416                    .relationship_properties
417                    .scoped_ids_for(rel_type, key, value)
418                else {
419                    return Vec::new();
420                };
421                ids.iter()
422                    .filter_map(|&id| self.rel_at(id).cloned())
423                    .collect()
424            }
425            None => indexes
426                .relationship_properties
427                .ids_for(key, value)
428                .into_iter()
429                .flat_map(|ids| ids.iter())
430                .filter_map(|&id| self.rel_at(id).cloned())
431                .collect(),
432        }
433    }
434
435    fn find_relationship_ids_by_property(
436        &self,
437        rel_type: Option<&str>,
438        key: &str,
439        value: &PropertyValue,
440    ) -> Vec<RelationshipId>
441    where
442        Self: Sized,
443    {
444        if PropertyIndexKey::from_value(value).is_none() {
445            return self
446                .scan_relationships_by_property(rel_type, key, value)
447                .into_iter()
448                .map(|r| r.id)
449                .collect();
450        }
451
452        self.ensure_relationship_property_index(key);
453        let indexes = self.indexes_read();
454
455        match rel_type {
456            Some(rel_type) => indexes
457                .relationship_properties
458                .scoped_ids_for(rel_type, key, value)
459                .map(|ids| ids.iter().copied().collect())
460                .unwrap_or_default(),
461            None => indexes
462                .relationship_properties
463                .ids_for(key, value)
464                .map(|ids| ids.iter().copied().collect())
465                .unwrap_or_default(),
466        }
467    }
468
469    fn node_exists_with_label_and_property(
470        &self,
471        label: &str,
472        key: &str,
473        value: &PropertyValue,
474    ) -> bool
475    where
476        Self: Sized,
477    {
478        if PropertyIndexKey::from_value(value).is_none() {
479            return !self
480                .scan_nodes_by_property(Some(label), key, value)
481                .is_empty();
482        }
483
484        self.ensure_node_property_index(key);
485        let indexes = self.indexes_read();
486        indexes
487            .node_properties
488            .scoped_ids_for(label, key, value)
489            .map(|ids| !ids.is_empty())
490            .unwrap_or(false)
491    }
492
493    fn relationship_exists_with_type_and_property(
494        &self,
495        rel_type: &str,
496        key: &str,
497        value: &PropertyValue,
498    ) -> bool
499    where
500        Self: Sized,
501    {
502        if PropertyIndexKey::from_value(value).is_none() {
503            return !self
504                .scan_relationships_by_property(Some(rel_type), key, value)
505                .is_empty();
506        }
507
508        self.ensure_relationship_property_index(key);
509        let indexes = self.indexes_read();
510        indexes
511            .relationship_properties
512            .scoped_ids_for(rel_type, key, value)
513            .map(|ids| !ids.is_empty())
514            .unwrap_or(false)
515    }
516}
517
518impl BorrowedGraphStorage for InMemoryGraph {
519    fn node_ref(&self, id: NodeId) -> Option<&NodeRecord> {
520        self.node_at(id)
521    }
522
523    fn relationship_ref(&self, id: RelationshipId) -> Option<&RelationshipRecord> {
524        self.rel_at(id)
525    }
526
527    fn node_refs(&self) -> Box<dyn Iterator<Item = &NodeRecord> + '_> {
528        Box::new(self.iter_node_records())
529    }
530
531    fn node_refs_by_label(&self, label: &str) -> Box<dyn Iterator<Item = &NodeRecord> + '_> {
532        Box::new(
533            self.nodes_by_label
534                .get(label)
535                .into_iter()
536                .flat_map(|ids| ids.iter())
537                .filter_map(|&id| self.node_at(id)),
538        )
539    }
540
541    fn relationship_refs(&self) -> Box<dyn Iterator<Item = &RelationshipRecord> + '_> {
542        Box::new(self.iter_rel_records())
543    }
544
545    fn relationship_refs_by_type(
546        &self,
547        rel_type: &str,
548    ) -> Box<dyn Iterator<Item = &RelationshipRecord> + '_> {
549        Box::new(
550            self.relationships_by_type
551                .get(rel_type)
552                .into_iter()
553                .flat_map(|ids| ids.iter())
554                .filter_map(|&id| self.rel_at(id)),
555        )
556    }
557}
558
559impl GraphStorageMut for InMemoryGraph {
560    fn create_node(&mut self, labels: Vec<String>, properties: Properties) -> NodeRecord {
561        let id = self.alloc_node_id();
562        let labels = Self::normalize_labels(labels);
563
564        let node = NodeRecord {
565            id,
566            labels: labels.clone(),
567            properties,
568        };
569
570        self.on_node_created(&node);
571
572        self.put_node(id, node.clone());
573
574        // ensure_node_slot grew both adjacency Vecs to cover this id when
575        // we put_node above.
576
577        self.emit(|| MutationEvent::CreateNode {
578            id,
579            labels: node.labels.clone(),
580            properties: node.properties.clone(),
581        });
582
583        node
584    }
585
586    fn create_relationship(
587        &mut self,
588        src: NodeId,
589        dst: NodeId,
590        rel_type: &str,
591        properties: Properties,
592    ) -> Option<RelationshipRecord> {
593        if self.node_at(src).is_none() || self.node_at(dst).is_none() {
594            return None;
595        }
596
597        let trimmed = rel_type.trim();
598        if trimmed.is_empty() {
599            return None;
600        }
601
602        let id = self.alloc_rel_id();
603        let rel = RelationshipRecord {
604            id,
605            src,
606            dst,
607            rel_type: trimmed.to_string(),
608            properties,
609        };
610
611        self.on_relationship_created(&rel);
612        self.put_rel(id, rel.clone());
613
614        self.emit(|| MutationEvent::CreateRelationship {
615            id,
616            src,
617            dst,
618            rel_type: rel.rel_type.clone(),
619            properties: rel.properties.clone(),
620        });
621
622        Some(rel)
623    }
624
625    fn set_node_property(&mut self, node_id: NodeId, key: String, value: PropertyValue) -> bool {
626        if self.node_at(node_id).is_none() {
627            return false;
628        }
629
630        let recorder_active = self.recorder.is_some();
631        let (stored_key, stored_value) = if recorder_active {
632            (Some(key.clone()), Some(value.clone()))
633        } else {
634            (None, None)
635        };
636
637        let old = match self.node_at_mut(node_id) {
638            Some(node) => node.properties.insert(key.clone(), value.clone()),
639            None => return false,
640        };
641        self.on_node_property_set(node_id, &key, old.as_ref(), &value);
642
643        self.emit(|| MutationEvent::SetNodeProperty {
644            node_id,
645            key: stored_key.unwrap(),
646            value: stored_value.unwrap(),
647        });
648
649        true
650    }
651
652    fn remove_node_property(&mut self, node_id: NodeId, key: &str) -> bool {
653        let removed = match self.node_at_mut(node_id) {
654            Some(node) => node.properties.remove(key),
655            None => return false,
656        };
657        let Some(removed) = removed else {
658            return false;
659        };
660
661        self.on_node_property_removed(node_id, key, &removed);
662
663        self.emit(|| MutationEvent::RemoveNodeProperty {
664            node_id,
665            key: key.to_string(),
666        });
667
668        true
669    }
670
671    fn add_node_label(&mut self, node_id: NodeId, label: &str) -> bool {
672        let label = label.trim();
673        if label.is_empty() {
674            return false;
675        }
676
677        let applied = match self.node_at_mut(node_id) {
678            Some(node) => {
679                if node.labels.iter().any(|l| l == label) {
680                    return false;
681                }
682
683                node.labels.push(label.to_string());
684                true
685            }
686            None => false,
687        };
688        if applied {
689            self.on_node_label_added(node_id, label);
690            self.emit(|| MutationEvent::AddNodeLabel {
691                node_id,
692                label: label.to_string(),
693            });
694        }
695        applied
696    }
697
698    fn remove_node_label(&mut self, node_id: NodeId, label: &str) -> bool {
699        let applied = match self.node_at_mut(node_id) {
700            Some(node) => {
701                let original_len = node.labels.len();
702                node.labels.retain(|l| l != label);
703                node.labels.len() != original_len
704            }
705            None => false,
706        };
707        if applied {
708            self.on_node_label_removed(node_id, label);
709            self.emit(|| MutationEvent::RemoveNodeLabel {
710                node_id,
711                label: label.to_string(),
712            });
713        }
714        applied
715    }
716
717    fn set_relationship_property(
718        &mut self,
719        rel_id: RelationshipId,
720        key: String,
721        value: PropertyValue,
722    ) -> bool {
723        if self.rel_at(rel_id).is_none() {
724            return false;
725        }
726
727        let recorder_active = self.recorder.is_some();
728        let (stored_key, stored_value) = if recorder_active {
729            (Some(key.clone()), Some(value.clone()))
730        } else {
731            (None, None)
732        };
733
734        let old = match self.rel_at_mut(rel_id) {
735            Some(rel) => rel.properties.insert(key.clone(), value.clone()),
736            None => return false,
737        };
738        self.on_relationship_property_set(rel_id, &key, old.as_ref(), &value);
739
740        self.emit(|| MutationEvent::SetRelationshipProperty {
741            rel_id,
742            key: stored_key.unwrap(),
743            value: stored_value.unwrap(),
744        });
745
746        true
747    }
748
749    fn remove_relationship_property(&mut self, rel_id: RelationshipId, key: &str) -> bool {
750        let removed = match self.rel_at_mut(rel_id) {
751            Some(rel) => rel.properties.remove(key),
752            None => return false,
753        };
754        let Some(removed) = removed else {
755            return false;
756        };
757
758        self.on_relationship_property_removed(rel_id, key, &removed);
759
760        self.emit(|| MutationEvent::RemoveRelationshipProperty {
761            rel_id,
762            key: key.to_string(),
763        });
764
765        true
766    }
767
768    fn delete_relationship(&mut self, rel_id: RelationshipId) -> bool {
769        let applied = match self.take_rel(rel_id) {
770            Some(rel) => {
771                self.on_relationship_deleted(&rel);
772                true
773            }
774            None => false,
775        };
776        if applied {
777            self.emit(|| MutationEvent::DeleteRelationship { rel_id });
778        }
779        applied
780    }
781
782    fn delete_node(&mut self, node_id: NodeId) -> bool {
783        if self.node_at(node_id).is_none() {
784            return false;
785        }
786
787        if self.has_incident_relationships(node_id) {
788            return false;
789        }
790
791        let node = match self.take_node(node_id) {
792            Some(node) => node,
793            None => return false,
794        };
795
796        self.on_node_deleted(&node);
797
798        // take_node already cleared the per-node adjacency Vecs.
799
800        self.emit(|| MutationEvent::DeleteNode { node_id });
801
802        true
803    }
804
805    fn detach_delete_node(&mut self, node_id: NodeId) -> bool {
806        if self.node_at(node_id).is_none() {
807            return false;
808        }
809
810        let rel_ids: Vec<_> = self
811            .incident_relationship_ids(node_id)
812            .into_iter()
813            .collect();
814
815        // We deliberately fire per-relationship DeleteRelationship events
816        // here (via `delete_relationship`) and a DetachDeleteNode event at
817        // the end. A WAL replayer that sees DetachDeleteNode can ignore the
818        // preceding DeleteRelationship events — or, equivalently, replay
819        // them and the DetachDeleteNode becomes a no-op on the remaining
820        // (now-empty) node. The emit-before-delete choice costs one extra
821        // event per mutation but keeps the replay contract simple:
822        // "apply every event in order".
823        for rel_id in rel_ids {
824            let _ = self.delete_relationship(rel_id);
825        }
826
827        if self.delete_node(node_id) {
828            self.emit(|| MutationEvent::DetachDeleteNode { node_id });
829            true
830        } else {
831            false
832        }
833    }
834
835    fn clear(&mut self) {
836        // Keep the recorder across clear so observers can see the Clear
837        // event plus whatever follows. Matches WAL semantics where the log
838        // is the source of truth across a truncation.
839        let recorder = self.recorder.take();
840        *self = Self::default();
841        self.recorder = recorder;
842        self.emit(|| MutationEvent::Clear);
843    }
844}