Skip to main content

graphmind/graph/
store.rs

1//! # In-Memory Graph Storage -- Adjacency Lists, Indices, and Statistics
2//!
3//! [`GraphStore`] is the central data structure of the Graphmind graph engine. It
4//! holds all nodes, edges, indices, and metadata in memory, optimized for the
5//! access patterns of a graph query engine.
6//!
7//! ## Why adjacency lists, not adjacency matrices?
8//!
9//! An adjacency matrix for V vertices requires O(V^2) space and O(V) time to
10//! find a node's neighbors. Real-world graphs are overwhelmingly **sparse** --
11//! a social network with 1 million users might have 100 million edges, but a
12//! matrix would need 10^12 cells. Adjacency lists use O(V + E) space and give
13//! O(degree) neighbor iteration -- a far better fit.
14//!
15//! ## Arena allocation with MVCC versioning
16//!
17//! Nodes and edges are stored in `Vec<Vec<T>>` structures (arena allocation).
18//! The outer `Vec` is indexed by entity ID (acting as a dense array), and the
19//! inner `Vec` holds successive **MVCC versions** of that entity. This layout
20//! gives O(1) lookup by ID (direct indexing, no hash computation), excellent
21//! cache locality for sequential scans, and natural support for snapshot reads
22//! at any historical version. This is similar to how PostgreSQL stores tuple
23//! versions in heap pages, but without the overhead of disk I/O.
24//!
25//! ## Sorted adjacency lists and `edge_between()`
26//!
27//! The `outgoing` and `incoming` adjacency lists store `Vec<(NodeId, EdgeId)>`
28//! tuples **sorted by NodeId**. This enables `edge_between(a, b)` to use
29//! **binary search** with O(log d) complexity (where d = degree of the node),
30//! which is critical for the `ExpandIntoOperator` that checks edge existence
31//! between two already-bound nodes in triangle-pattern queries.
32//!
33//! ## Secondary indices: label and edge-type
34//!
35//! `label_index: HashMap<Label, HashSet<NodeId>>` and
36//! `edge_type_index: HashMap<EdgeType, HashSet<EdgeId>>` act as secondary
37//! indices, analogous to B-tree indices in an RDBMS. When a Cypher query
38//! specifies `MATCH (n:Person)`, the engine looks up the `Person` entry in
39//! `label_index` and scans only matching nodes, avoiding a full table scan.
40//! We use `HashMap` (not `BTreeMap`) because we only need equality lookups
41//! on labels, not range scans.
42//!
43//! ## ColumnStore integration (late materialization)
44//!
45//! `node_columns` and `edge_columns` provide **columnar storage** for
46//! frequently accessed properties. In the traditional row store (`PropertyMap`
47//! on each node), reading one property from 1000 nodes touches 1000 scattered
48//! `HashMap`s. The columnar store groups all values of the same property
49//! contiguously, enabling vectorized reads and better CPU cache utilization.
50//! The query engine uses **late materialization**: scan operators produce
51//! lightweight `Value::NodeRef(id)` references instead of cloning full nodes,
52//! and properties are resolved on demand from the column store.
53//!
54//! ## GraphStatistics for cost-based optimization
55//!
56//! The query planner uses [`GraphStatistics`] to estimate the cardinality
57//! (number of rows) at each stage of a query plan, choosing the plan with
58//! the lowest estimated cost. See the struct documentation for details.
59//!
60//! ## Key Rust patterns
61//!
62//! - **`Vec<Vec<T>>` arena**: dense ID-indexed storage avoids HashMap overhead;
63//!   inner Vec holds MVCC versions.
64//! - **`HashMap` vs `BTreeMap`**: `HashMap` is used for indices because we need
65//!   O(1) point lookups (label equality), not ordered iteration. `BTreeMap`
66//!   would add an unnecessary O(log n) factor.
67//! - **`Arc`**: `vector_index` and `property_index` are wrapped in `Arc`
68//!   (atomic reference counting) for shared ownership across the query engine
69//!   and background index-maintenance tasks.
70//! - **`thiserror`**: the [`GraphError`] enum uses the `thiserror` crate's
71//!   derive macro to auto-generate `Display` and `Error` implementations,
72//!   reducing boilerplate for error types.
73//!
74//! ## Requirements coverage
75//!
76//! - REQ-GRAPH-001: Property graph data model
77//! - REQ-MEM-001: In-memory storage
78//! - REQ-MEM-003: Memory-optimized data structures
79
80use super::catalog::GraphCatalog;
81use super::edge::Edge;
82use super::node::Node;
83use super::property::{PropertyMap, PropertyValue};
84use super::types::{EdgeId, EdgeType, Label, NodeId};
85use crate::agent::{tools::WebSearchTool, AgentRuntime};
86use crate::graph::storage::ColumnStore;
87use crate::index::IndexManager;
88use crate::vector::{DistanceMetric, VectorIndexManager, VectorResult};
89use std::collections::{HashMap, HashSet};
90use std::sync::Arc;
91use thiserror::Error;
92use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
93
94// Add chrono dependency (local hack like in node.rs)
95mod chrono {
96    pub struct Utc;
97    impl Utc {
98        pub fn now() -> DateTime {
99            DateTime
100        }
101    }
102    pub struct DateTime;
103    impl DateTime {
104        pub fn timestamp_millis(&self) -> i64 {
105            // Use system time for now
106            std::time::SystemTime::now()
107                .duration_since(std::time::UNIX_EPOCH)
108                .unwrap()
109                .as_millis() as i64
110        }
111    }
112}
113
114/// Errors that can occur during graph operations
115#[derive(Error, Debug, PartialEq)]
116pub enum GraphError {
117    #[error("Node {0} not found")]
118    NodeNotFound(NodeId),
119
120    #[error("Edge {0} not found")]
121    EdgeNotFound(EdgeId),
122
123    #[error("Node {0} already exists")]
124    NodeAlreadyExists(NodeId),
125
126    #[error("Edge {0} already exists")]
127    EdgeAlreadyExists(EdgeId),
128
129    #[error("Invalid edge: source node {0} does not exist")]
130    InvalidEdgeSource(NodeId),
131
132    #[error("Invalid edge: target node {0} does not exist")]
133    InvalidEdgeTarget(NodeId),
134
135    #[error("Constraint violation: {0}")]
136    ConstraintViolation(String),
137}
138
139pub type GraphResult<T> = Result<T, GraphError>;
140
141/// Statistics about graph contents for **cost-based query optimization**.
142///
143/// # What is cardinality estimation?
144///
145/// Every query plan is a tree of operators (scan, filter, join, sort, ...).
146/// The query planner must choose among many possible orderings of these
147/// operators. To compare plans, it estimates the **cardinality** (number of
148/// rows) flowing through each operator. For example, if `:Person` has 10,000
149/// nodes and `:Company` has 500, the planner should scan `:Company` first in
150/// a join -- fewer rows means less work at every subsequent stage.
151///
152/// # How selectivity works
153///
154/// **Selectivity** is the probability that a predicate evaluates to `true`
155/// for a randomly chosen row. A selectivity of 0.01 on a 10,000-row scan
156/// means the filter will pass approximately 100 rows. Selectivity is
157/// estimated as `1 / distinct_count` for equality predicates (assuming a
158/// uniform distribution). The `null_fraction` is subtracted before
159/// estimation because NULL values never match equality predicates.
160///
161/// # Sampling approach for property stats
162///
163/// Computing exact statistics for every property on every label would be
164/// expensive in a large graph. Instead, `compute_statistics()` samples the
165/// first 1,000 nodes per label and extrapolates `distinct_count`,
166/// `null_fraction`, and `selectivity`. This is similar to PostgreSQL's
167/// `ANALYZE` command, which samples a configurable fraction of each table.
168/// The trade-off is speed vs. accuracy -- sampling may miss rare values,
169/// but is sufficient for plan selection in practice.
170#[derive(Debug, Clone)]
171pub struct GraphStatistics {
172    /// Total number of nodes
173    pub total_nodes: usize,
174    /// Total number of edges
175    pub total_edges: usize,
176    /// Node count per label
177    pub label_counts: HashMap<Label, usize>,
178    /// Edge count per type
179    pub edge_type_counts: HashMap<EdgeType, usize>,
180    /// Average outgoing degree
181    pub avg_out_degree: f64,
182    /// Property statistics per (label, property_name)
183    pub property_stats: HashMap<(Label, String), PropertyStats>,
184}
185
186/// Statistics about a specific property
187#[derive(Debug, Clone)]
188pub struct PropertyStats {
189    /// Fraction of nodes with this label that have NULL for this property
190    pub null_fraction: f64,
191    /// Estimated number of distinct values
192    pub distinct_count: usize,
193    /// Selectivity: probability of matching a random value (1/distinct_count)
194    pub selectivity: f64,
195}
196
197impl GraphStatistics {
198    /// Estimate the number of rows from a label scan
199    pub fn estimate_label_scan(&self, label: &Label) -> usize {
200        self.label_counts
201            .get(label)
202            .copied()
203            .unwrap_or(self.total_nodes)
204    }
205
206    /// Estimate the number of rows after an expand (edge traversal)
207    pub fn estimate_expand(&self, edge_type: Option<&EdgeType>) -> f64 {
208        match edge_type {
209            Some(et) => self.edge_type_counts.get(et).copied().unwrap_or(0) as f64,
210            None => self.total_edges as f64,
211        }
212    }
213
214    /// Estimate selectivity of an equality filter on a property
215    pub fn estimate_equality_selectivity(&self, label: &Label, property: &str) -> f64 {
216        self.property_stats
217            .get(&(label.clone(), property.to_string()))
218            .map(|ps| ps.selectivity)
219            .unwrap_or(0.1) // Default 10% selectivity
220    }
221
222    /// Format statistics as human-readable text
223    pub fn format(&self) -> String {
224        let mut result = String::new();
225        result.push_str("Graph Statistics:\n");
226        result.push_str(&format!("  Total nodes: {}\n", self.total_nodes));
227        result.push_str(&format!("  Total edges: {}\n", self.total_edges));
228        result.push_str(&format!("  Avg out-degree: {:.2}\n", self.avg_out_degree));
229        result.push_str("  Labels:\n");
230        let mut labels: Vec<_> = self.label_counts.iter().collect();
231        labels.sort_by(|a, b| b.1.cmp(a.1));
232        for (label, count) in labels {
233            result.push_str(&format!("    :{} = {} nodes\n", label.as_str(), count));
234        }
235        result.push_str("  Edge types:\n");
236        let mut types: Vec<_> = self.edge_type_counts.iter().collect();
237        types.sort_by(|a, b| b.1.cmp(a.1));
238        for (etype, count) in types {
239            result.push_str(&format!("    :{} = {} edges\n", etype.as_str(), count));
240        }
241        result
242    }
243}
244
245/// In-memory graph storage engine.
246///
247/// `GraphStore` is the authoritative source of truth for all graph data.
248/// Its data structure layout is designed for the access patterns of a
249/// Cypher query engine:
250///
251/// | Field | Type | Purpose | Lookup cost |
252/// |---|---|---|---|
253/// | `nodes` | `Vec<Vec<Node>>` | Arena-allocated node versions, indexed by `NodeId` | O(1) |
254/// | `edges` | `Vec<Vec<Edge>>` | Arena-allocated edge versions, indexed by `EdgeId` | O(1) |
255/// | `outgoing` | `Vec<Vec<(NodeId, EdgeId)>>` | Sorted adjacency list per node (outgoing) | O(log d) binary search |
256/// | `incoming` | `Vec<Vec<(NodeId, EdgeId)>>` | Sorted adjacency list per node (incoming) | O(log d) binary search |
257/// | `label_index` | `HashMap<Label, HashSet<NodeId>>` | Secondary index: label -> node set | O(1) lookup, O(n) scan |
258/// | `edge_type_index` | `HashMap<EdgeType, HashSet<EdgeId>>` | Secondary index: type -> edge set | O(1) lookup, O(n) scan |
259/// | `node_columns` | `ColumnStore` | Columnar property storage for late materialization | O(1) per cell |
260/// | `catalog` | `GraphCatalog` | Triple-level statistics for graph-native planning (ADR-015) | O(1) |
261///
262/// The `free_node_ids` / `free_edge_ids` vectors enable **ID reuse** after
263/// deletions, avoiding unbounded growth of the arena vectors.
264///
265/// Thread safety: `GraphStore` is not `Sync` by itself. Concurrent access
266/// is managed by the server layer, which wraps it in `Arc<RwLock<GraphStore>>`
267/// for shared-nothing read parallelism with exclusive write access.
268#[derive(Debug)]
269pub struct GraphStore {
270    /// Node storage (Arena with versioning: NodeId -> [Versions])
271    nodes: Vec<Vec<Node>>,
272
273    /// Edge storage (Arena with versioning: EdgeId -> [Versions])
274    edges: Vec<Vec<Edge>>,
275
276    /// Outgoing edges for each node, sorted by target NodeId: (target_NodeId, EdgeId)
277    outgoing: Vec<Vec<(NodeId, EdgeId)>>,
278
279    /// Incoming edges for each node, sorted by source NodeId: (source_NodeId, EdgeId)
280    incoming: Vec<Vec<(NodeId, EdgeId)>>,
281
282    /// Current global version for MVCC
283    pub current_version: u64,
284
285    /// Free node IDs for reuse
286    free_node_ids: Vec<u64>,
287
288    /// Free edge IDs for reuse
289    free_edge_ids: Vec<u64>,
290
291    /// Label index for fast lookups
292    label_index: HashMap<Label, HashSet<NodeId>>,
293
294    /// Edge type index for fast lookups
295    edge_type_index: HashMap<EdgeType, HashSet<EdgeId>>,
296
297    /// Vector indices manager
298    pub vector_index: Arc<VectorIndexManager>,
299
300    /// Property indices manager
301    pub property_index: Arc<IndexManager>,
302
303    /// Columnar storage for node properties
304    pub node_columns: ColumnStore,
305
306    /// Columnar storage for edge properties
307    pub edge_columns: ColumnStore,
308
309    /// Async index event sender
310    pub index_sender: Option<UnboundedSender<crate::graph::event::IndexEvent>>,
311
312    /// Next node ID
313    next_node_id: u64,
314
315    /// Next edge ID
316    next_edge_id: u64,
317
318    /// Triple-level statistics catalog for graph-native query planning (ADR-015)
319    catalog: GraphCatalog,
320}
321
322impl GraphStore {
323    /// Create a new empty graph store
324    pub fn new() -> Self {
325        GraphStore {
326            nodes: Vec::with_capacity(1024),
327            edges: Vec::with_capacity(4096),
328            outgoing: Vec::with_capacity(1024),
329            incoming: Vec::with_capacity(1024),
330            current_version: 1,
331            free_node_ids: Vec::new(),
332            free_edge_ids: Vec::new(),
333            label_index: HashMap::new(),
334            edge_type_index: HashMap::new(),
335            vector_index: Arc::new(VectorIndexManager::new()),
336            property_index: Arc::new(IndexManager::new()),
337            node_columns: ColumnStore::new(),
338            edge_columns: ColumnStore::new(),
339            index_sender: None,
340            next_node_id: 1,
341            next_edge_id: 1,
342            catalog: GraphCatalog::new(),
343        }
344    }
345
346    /// Create a new GraphStore with async indexing enabled
347    pub fn with_async_indexing() -> (
348        Self,
349        tokio::sync::mpsc::UnboundedReceiver<crate::graph::event::IndexEvent>,
350    ) {
351        let (tx, rx) = unbounded_channel();
352        let mut store = Self::new();
353        store.index_sender = Some(tx);
354        (store, rx)
355    }
356
357    /// Start the background indexer loop
358    pub async fn start_background_indexer(
359        mut receiver: tokio::sync::mpsc::UnboundedReceiver<crate::graph::event::IndexEvent>,
360        vector_index: Arc<VectorIndexManager>,
361        property_index: Arc<IndexManager>,
362        tenant_manager: Arc<crate::persistence::TenantManager>,
363    ) {
364        use crate::graph::event::IndexEvent::*;
365
366        while let Some(event) = receiver.recv().await {
367            match event {
368                NodeCreated {
369                    tenant_id,
370                    id,
371                    labels,
372                    properties,
373                } => {
374                    for (key, value) in &properties {
375                        if let PropertyValue::Vector(vec) = value {
376                            for label in &labels {
377                                let _ = vector_index.add_vector(label.as_str(), key, id, vec);
378                            }
379                        }
380                        for label in &labels {
381                            property_index.index_insert(label, key, value.clone(), id);
382                        }
383
384                        // Auto-Embed check
385                        if let PropertyValue::String(text) = value {
386                            if let Ok(tenant) = tenant_manager.get_tenant(&tenant_id) {
387                                if let Some(config) = tenant.embed_config {
388                                    for label in &labels {
389                                        if let Some(keys) =
390                                            config.embedding_policies.get(label.as_str())
391                                        {
392                                            if keys.contains(key) {
393                                                // Trigger Auto-Embed
394                                                let vector_index_clone = Arc::clone(&vector_index);
395                                                let label_str = label.as_str().to_string();
396                                                let key_clone = key.clone();
397                                                let text_clone = text.clone();
398                                                let config_clone = config.clone();
399
400                                                tokio::spawn(async move {
401                                                    if let Ok(pipeline) =
402                                                        crate::embed::EmbedPipeline::new(
403                                                            config_clone,
404                                                        )
405                                                    {
406                                                        if let Ok(chunks) =
407                                                            pipeline.process_text(&text_clone).await
408                                                        {
409                                                            for chunk in chunks {
410                                                                let _ = vector_index_clone
411                                                                    .add_vector(
412                                                                        &label_str,
413                                                                        &key_clone,
414                                                                        id,
415                                                                        &chunk.embedding,
416                                                                    );
417                                                            }
418                                                        }
419                                                    }
420                                                });
421                                            }
422                                        }
423                                    }
424                                }
425                            }
426                        }
427                    }
428
429                    // Agentic Enrichment Trigger
430                    if let Ok(tenant) = tenant_manager.get_tenant(&tenant_id) {
431                        if let Some(agent_config) = tenant.agent_config {
432                            if agent_config.enabled {
433                                for label in &labels {
434                                    if let Some(trigger_prompt) =
435                                        agent_config.policies.get(label.as_str())
436                                    {
437                                        // Construct context from node properties
438                                        let context =
439                                            format!("Node: {} {:?}", label.as_str(), properties);
440                                        let task = trigger_prompt.clone();
441                                        let mut runtime = AgentRuntime::new(agent_config.clone());
442                                        let label_str = label.as_str().to_string();
443
444                                        // Register tools based on config/availability
445                                        if let Some(api_key) = &agent_config.api_key {
446                                            runtime.register_tool(Arc::new(WebSearchTool::new(
447                                                api_key.clone(),
448                                            )));
449                                        } else {
450                                            // Mock/Prototype mode
451                                            runtime.register_tool(Arc::new(WebSearchTool::new(
452                                                "mock".to_string(),
453                                            )));
454                                        }
455
456                                        tokio::spawn(async move {
457                                            if let Ok(result) =
458                                                runtime.process_trigger(&task, &context).await
459                                            {
460                                                println!(
461                                                    "Agent Action [{}] -> {}",
462                                                    label_str, result
463                                                );
464                                                // Future: Write result back to graph properties using a GraphStore client
465                                            }
466                                        });
467                                    }
468                                }
469                            }
470                        }
471                    }
472                }
473                NodeDeleted {
474                    tenant_id: _,
475                    id,
476                    labels,
477                    properties,
478                } => {
479                    for (key, value) in properties {
480                        for label in &labels {
481                            property_index.index_remove(label, &key, &value, id);
482                        }
483                    }
484                }
485                PropertySet {
486                    tenant_id,
487                    id,
488                    labels,
489                    key,
490                    old_value,
491                    new_value,
492                } => {
493                    if let Some(old) = old_value {
494                        for label in &labels {
495                            property_index.index_remove(label, &key, &old, id);
496                        }
497                    }
498                    for label in &labels {
499                        property_index.index_insert(label, &key, new_value.clone(), id);
500                    }
501                    if let PropertyValue::Vector(vec) = &new_value {
502                        for label in &labels {
503                            let _ = vector_index.add_vector(label.as_str(), &key, id, vec);
504                        }
505                    }
506
507                    // Auto-Embed check
508                    if let PropertyValue::String(text) = &new_value {
509                        if let Ok(tenant) = tenant_manager.get_tenant(&tenant_id) {
510                            if let Some(config) = tenant.embed_config {
511                                for label in &labels {
512                                    if let Some(keys) =
513                                        config.embedding_policies.get(label.as_str())
514                                    {
515                                        if keys.contains(&key) {
516                                            let vector_index_clone = Arc::clone(&vector_index);
517                                            let label_str = label.as_str().to_string();
518                                            let key_clone = key.clone();
519                                            let text_clone = text.clone();
520                                            let config_clone = config.clone();
521
522                                            tokio::spawn(async move {
523                                                if let Ok(pipeline) =
524                                                    crate::embed::EmbedPipeline::new(config_clone)
525                                                {
526                                                    if let Ok(chunks) =
527                                                        pipeline.process_text(&text_clone).await
528                                                    {
529                                                        if let Some(first) = chunks.first() {
530                                                            let _ = vector_index_clone.add_vector(
531                                                                &label_str,
532                                                                &key_clone,
533                                                                id,
534                                                                &first.embedding,
535                                                            );
536                                                        }
537                                                    }
538                                                }
539                                            });
540                                        }
541                                    }
542                                }
543                            }
544                        }
545                    }
546
547                    // Agentic Enrichment Trigger (PropertySet)
548                    if let Ok(tenant) = tenant_manager.get_tenant(&tenant_id) {
549                        if let Some(agent_config) = tenant.agent_config {
550                            if agent_config.enabled {
551                                for label in &labels {
552                                    if let Some(trigger_prompt) =
553                                        agent_config.policies.get(label.as_str())
554                                    {
555                                        let context = format!(
556                                            "Node: {} (Property Updated: {})",
557                                            label.as_str(),
558                                            key
559                                        );
560                                        let task = trigger_prompt.clone();
561                                        let mut runtime = AgentRuntime::new(agent_config.clone());
562                                        let label_str = label.as_str().to_string();
563
564                                        if let Some(api_key) = &agent_config.api_key {
565                                            runtime.register_tool(Arc::new(WebSearchTool::new(
566                                                api_key.clone(),
567                                            )));
568                                        } else {
569                                            runtime.register_tool(Arc::new(WebSearchTool::new(
570                                                "mock".to_string(),
571                                            )));
572                                        }
573
574                                        tokio::spawn(async move {
575                                            if let Ok(result) =
576                                                runtime.process_trigger(&task, &context).await
577                                            {
578                                                println!(
579                                                    "Agent Action [{}] -> {}",
580                                                    label_str, result
581                                                );
582                                            }
583                                        });
584                                    }
585                                }
586                            }
587                        }
588                    }
589                }
590                LabelAdded {
591                    tenant_id,
592                    id,
593                    label,
594                    properties,
595                } => {
596                    for (key, value) in properties {
597                        if let PropertyValue::Vector(vec) = &value {
598                            let _ = vector_index.add_vector(label.as_str(), &key, id, vec);
599                        }
600                        property_index.index_insert(&label, &key, value.clone(), id);
601
602                        // Auto-Embed check
603                        if let PropertyValue::String(text) = &value {
604                            if let Ok(tenant) = tenant_manager.get_tenant(&tenant_id) {
605                                if let Some(config) = tenant.embed_config {
606                                    if let Some(keys) =
607                                        config.embedding_policies.get(label.as_str())
608                                    {
609                                        if keys.contains(&key) {
610                                            let vector_index_clone = Arc::clone(&vector_index);
611                                            let label_str = label.as_str().to_string();
612                                            let key_clone = key.clone();
613                                            let text_clone = text.clone();
614                                            let config_clone = config.clone();
615
616                                            tokio::spawn(async move {
617                                                if let Ok(pipeline) =
618                                                    crate::embed::EmbedPipeline::new(config_clone)
619                                                {
620                                                    if let Ok(chunks) =
621                                                        pipeline.process_text(&text_clone).await
622                                                    {
623                                                        if let Some(first) = chunks.first() {
624                                                            let _ = vector_index_clone.add_vector(
625                                                                &label_str,
626                                                                &key_clone,
627                                                                id,
628                                                                &first.embedding,
629                                                            );
630                                                        }
631                                                    }
632                                                }
633                                            });
634                                        }
635                                    }
636                                }
637                            }
638                        }
639                    }
640                }
641            }
642        }
643    }
644
645    /// Create a node with auto-generated ID and single label
646    pub fn create_node(&mut self, label: impl Into<Label>) -> NodeId {
647        let node_id_u64 = if let Some(id) = self.free_node_ids.pop() {
648            id
649        } else {
650            let id = self.next_node_id;
651            self.next_node_id += 1;
652            id
653        };
654        let node_id = NodeId::new(node_id_u64);
655        let idx = node_id_u64 as usize;
656
657        let label = label.into();
658        let mut node = Node::new(node_id, label.clone());
659        node.version = self.current_version;
660
661        // Add to label index
662        self.label_index
663            .entry(label.clone())
664            .or_default()
665            .insert(node_id);
666
667        // Update catalog label count
668        self.catalog.on_label_added(&label);
669
670        // Ensure storage capacity
671        if idx >= self.nodes.len() {
672            self.nodes.resize(idx + 1, Vec::new());
673            self.outgoing.resize(idx + 1, Vec::new());
674            self.incoming.resize(idx + 1, Vec::new());
675        }
676
677        let event = crate::graph::event::IndexEvent::NodeCreated {
678            tenant_id: "default".to_string(),
679            id: node_id,
680            labels: node.labels.iter().cloned().collect(),
681            properties: node.properties.clone(),
682        };
683
684        if let Some(sender) = &self.index_sender {
685            let _ = sender.send(event);
686        } else {
687            self.handle_index_event(event, None);
688        }
689
690        self.nodes[idx].push(node);
691        node_id
692    }
693
694    /// Create a node with multiple labels and properties
695    pub fn create_node_with_properties(
696        &mut self,
697        tenant_id: &str,
698        labels: Vec<Label>,
699        properties: PropertyMap,
700    ) -> GraphResult<NodeId> {
701        // Enforce UNIQUE constraints before creating the node
702        for label in &labels {
703            for (key, value) in &properties {
704                if self.property_index.has_unique_constraint(label, key) {
705                    if let Some(existing_ids) = self.property_index.lookup(label, key, value) {
706                        if !existing_ids.is_empty() {
707                            return Err(GraphError::ConstraintViolation(format!(
708                                "Node already exists with label '{}' and {} = {:?}",
709                                label.as_str(),
710                                key,
711                                value
712                            )));
713                        }
714                    }
715                }
716            }
717        }
718
719        let node_id_u64 = if let Some(id) = self.free_node_ids.pop() {
720            id
721        } else {
722            let id = self.next_node_id;
723            self.next_node_id += 1;
724            id
725        };
726        let node_id = NodeId::new(node_id_u64);
727        let idx = node_id_u64 as usize;
728
729        // Populate columnar storage
730        for (key, value) in &properties {
731            self.node_columns.set_property(idx, key, value.clone());
732        }
733
734        let mut node = Node::new_with_properties(node_id, labels.clone(), properties);
735        node.version = self.current_version;
736
737        // Add to label indices
738        for label in &labels {
739            self.label_index
740                .entry(label.clone())
741                .or_default()
742                .insert(node_id);
743            // Update catalog label count
744            self.catalog.on_label_added(label);
745        }
746
747        // Ensure storage capacity
748        if idx >= self.nodes.len() {
749            self.nodes.resize(idx + 1, Vec::new());
750            self.outgoing.resize(idx + 1, Vec::new());
751            self.incoming.resize(idx + 1, Vec::new());
752        }
753
754        let event = crate::graph::event::IndexEvent::NodeCreated {
755            tenant_id: tenant_id.to_string(),
756            id: node_id,
757            labels: node.labels.iter().cloned().collect(),
758            properties: node.properties.clone(),
759        };
760
761        if let Some(sender) = &self.index_sender {
762            let _ = sender.send(event);
763        } else {
764            self.handle_index_event(event, None);
765        }
766
767        self.nodes[idx].push(node);
768        Ok(node_id)
769    }
770
771    /// Get a node by ID at a specific version (MVCC)
772    pub fn get_node_at_version(&self, id: NodeId, version: u64) -> Option<&Node> {
773        let idx = id.as_u64() as usize;
774        let versions = self.nodes.get(idx)?;
775
776        // Find the latest version <= requested version
777        // Versions are sorted by creation time
778        versions.iter().rev().find(|n| n.version <= version)
779    }
780
781    /// Get a node by ID (uses current version)
782    pub fn get_node(&self, id: NodeId) -> Option<&Node> {
783        self.get_node_at_version(id, self.current_version)
784    }
785
786    /// Get a mutable node by ID (always latest version)
787    pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut Node> {
788        self.nodes
789            .get_mut(id.as_u64() as usize)
790            .and_then(|v| v.last_mut())
791    }
792
793    /// Check if a node exists
794    pub fn has_node(&self, id: NodeId) -> bool {
795        self.get_node(id).is_some()
796    }
797
798    /// Set a property on a node and update vector indices if necessary
799    pub fn set_node_property(
800        &mut self,
801        tenant_id: &str,
802        node_id: NodeId,
803        key: impl Into<String>,
804        value: impl Into<PropertyValue>,
805    ) -> GraphResult<()> {
806        let key_str = key.into();
807        let val = value.into();
808        let idx = node_id.as_u64() as usize;
809
810        // Enforce UNIQUE constraints: check if this label+property+value already exists
811        if let Some(node_versions) = self.nodes.get(idx) {
812            if let Some(node) = node_versions.last() {
813                for label in &node.labels {
814                    if self.property_index.has_unique_constraint(label, &key_str) {
815                        // Check if another node already has this value
816                        if let Some(existing_ids) =
817                            self.property_index.lookup(label, &key_str, &val)
818                        {
819                            for existing_id in existing_ids {
820                                if existing_id != node_id {
821                                    return Err(GraphError::ConstraintViolation(format!(
822                                        "Node already exists with label '{}' and {} = {:?}",
823                                        label.as_str(),
824                                        key_str,
825                                        val
826                                    )));
827                                }
828                            }
829                        }
830                    }
831                }
832            }
833        }
834
835        // Update columnar storage (always latest)
836        self.node_columns.set_property(idx, &key_str, val.clone());
837
838        // Get access to versions
839        let versions = self
840            .nodes
841            .get_mut(idx)
842            .ok_or(GraphError::NodeNotFound(node_id))?;
843        let latest_node = versions.last().ok_or(GraphError::NodeNotFound(node_id))?;
844
845        let old_val;
846
847        if latest_node.version < self.current_version {
848            // COW: Create new version
849            let mut new_node = latest_node.clone();
850            new_node.version = self.current_version;
851            new_node.updated_at = chrono::Utc::now().timestamp_millis();
852            old_val = new_node.set_property(key_str.clone(), val.clone());
853            versions.push(new_node);
854        } else {
855            // Update in place (same transaction/version)
856            let node = versions.last_mut().unwrap();
857            old_val = node.set_property(key_str.clone(), val.clone());
858        }
859
860        let event = crate::graph::event::IndexEvent::PropertySet {
861            tenant_id: tenant_id.to_string(),
862            id: node_id,
863            labels: versions.last().unwrap().labels.iter().cloned().collect(),
864            key: key_str,
865            old_value: old_val,
866            new_value: val,
867        };
868
869        if let Some(sender) = &self.index_sender {
870            let _ = sender.send(event);
871        } else {
872            self.handle_index_event(event, None);
873        }
874
875        Ok(())
876    }
877
878    /// Delete a node and all its connected edges
879    pub fn delete_node(&mut self, tenant_id: &str, id: NodeId) -> GraphResult<Node> {
880        let idx = id.as_u64() as usize;
881        let latest_node = self
882            .get_node(id)
883            .ok_or(GraphError::NodeNotFound(id))?
884            .clone();
885
886        // Create a tombstone version (we use a special property or metadata in a real system)
887        // For now, we'll just not return it in get_node_at_version if we had a flag.
888        // Let's add a `deleted` flag to Node/Edge for true MVCC.
889
890        // Add to free list for reuse (In true MVCC, we only reuse after compaction/vacuum)
891        self.free_node_ids.push(id.as_u64());
892
893        // For this prototype, we'll keep the removal logic but wrap it in MVCC metadata if needed.
894        // Actually, let's keep it simple: removal from the latest version effectively deletes it.
895        // But to keep history, we should NOT remove from the Vec.
896
897        // Remove from label indices and update catalog
898        for label in &latest_node.labels {
899            if let Some(node_set) = self.label_index.get_mut(label) {
900                node_set.remove(&id);
901            }
902            self.catalog.on_label_removed(label);
903        }
904
905        let event = crate::graph::event::IndexEvent::NodeDeleted {
906            tenant_id: tenant_id.to_string(),
907            id,
908            labels: latest_node.labels.iter().cloned().collect(),
909            properties: latest_node.properties.clone(),
910        };
911
912        if let Some(sender) = &self.index_sender {
913            let _ = sender.send(event);
914        } else {
915            self.handle_index_event(event, None);
916        }
917
918        // Remove from the versions (breaking historical reads for now, full MVCC is complex)
919        // TODO: Implement proper tombstone versions
920        let node = self.nodes[idx].pop().unwrap();
921
922        // Remove all connected edges
923        let outgoing_edges: Vec<EdgeId> = std::mem::take(&mut self.outgoing[idx])
924            .into_iter()
925            .map(|(_, eid)| eid)
926            .collect();
927        let incoming_edges: Vec<EdgeId> = std::mem::take(&mut self.incoming[idx])
928            .into_iter()
929            .map(|(_, eid)| eid)
930            .collect();
931
932        for edge_id in outgoing_edges.iter().chain(incoming_edges.iter()) {
933            let _ = self.delete_edge(*edge_id);
934        }
935
936        Ok(node)
937    }
938
939    /// Add a label to an existing node AND update the label index
940    ///
941    /// This is the correct way to add labels to nodes after creation.
942    /// Using `node.add_label()` directly will NOT update the label_index,
943    /// making the node invisible to `get_nodes_by_label()` queries.
944    pub fn add_label_to_node(
945        &mut self,
946        tenant_id: &str,
947        node_id: NodeId,
948        label: impl Into<Label>,
949    ) -> GraphResult<()> {
950        let label = label.into();
951        let idx = node_id.as_u64() as usize;
952
953        // Get the node and add the label
954        let node = self
955            .nodes
956            .get_mut(idx)
957            .and_then(|v| v.last_mut())
958            .ok_or(GraphError::NodeNotFound(node_id))?;
959        node.add_label(label.clone());
960
961        // Update the label index so queries can find this node by the new label
962        self.label_index
963            .entry(label.clone())
964            .or_default()
965            .insert(node_id);
966
967        // Update catalog label count
968        self.catalog.on_label_added(&label);
969
970        let event = crate::graph::event::IndexEvent::LabelAdded {
971            tenant_id: tenant_id.to_string(),
972            id: node_id,
973            label: label.clone(),
974            properties: node.properties.clone(),
975        };
976
977        if let Some(sender) = &self.index_sender {
978            let _ = sender.send(event);
979        } else {
980            self.handle_index_event(event, None);
981        }
982
983        Ok(())
984    }
985
986    /// Create an edge between two nodes
987    pub fn create_edge(
988        &mut self,
989        source: NodeId,
990        target: NodeId,
991        edge_type: impl Into<EdgeType>,
992    ) -> GraphResult<EdgeId> {
993        // Validate nodes exist
994        if !self.has_node(source) {
995            return Err(GraphError::InvalidEdgeSource(source));
996        }
997        if !self.has_node(target) {
998            return Err(GraphError::InvalidEdgeTarget(target));
999        }
1000
1001        let edge_id_u64 = if let Some(id) = self.free_edge_ids.pop() {
1002            id
1003        } else {
1004            let id = self.next_edge_id;
1005            self.next_edge_id += 1;
1006            id
1007        };
1008        let edge_id = EdgeId::new(edge_id_u64);
1009        let idx = edge_id_u64 as usize;
1010
1011        let edge_type = edge_type.into();
1012        let mut edge = Edge::new(edge_id, source, target, edge_type.clone());
1013        edge.version = self.current_version;
1014
1015        // Update adjacency lists (sorted insert by target/source NodeId)
1016        {
1017            let out_list = &mut self.outgoing[source.as_u64() as usize];
1018            let pos = out_list
1019                .binary_search_by_key(&target, |(nid, _)| *nid)
1020                .unwrap_or_else(|p| p);
1021            out_list.insert(pos, (target, edge_id));
1022        }
1023        {
1024            let in_list = &mut self.incoming[target.as_u64() as usize];
1025            let pos = in_list
1026                .binary_search_by_key(&source, |(nid, _)| *nid)
1027                .unwrap_or_else(|p| p);
1028            in_list.insert(pos, (source, edge_id));
1029        }
1030
1031        // Ensure storage capacity
1032        if idx >= self.edges.len() {
1033            self.edges.resize(idx + 1, Vec::new());
1034        }
1035
1036        // Update edge type index
1037        self.edge_type_index
1038            .entry(edge_type.clone())
1039            .or_default()
1040            .insert(edge_id);
1041
1042        // Update catalog triple stats
1043        let src_labels: Vec<Label> = self
1044            .get_node(source)
1045            .map(|n| n.labels.iter().cloned().collect())
1046            .unwrap_or_default();
1047        let tgt_labels: Vec<Label> = self
1048            .get_node(target)
1049            .map(|n| n.labels.iter().cloned().collect())
1050            .unwrap_or_default();
1051        self.catalog
1052            .on_edge_created(source, &src_labels, &edge_type, target, &tgt_labels);
1053
1054        self.edges[idx].push(edge);
1055        Ok(edge_id)
1056    }
1057
1058    /// Create an edge with properties
1059    pub fn create_edge_with_properties(
1060        &mut self,
1061        source: NodeId,
1062        target: NodeId,
1063        edge_type: impl Into<EdgeType>,
1064        properties: PropertyMap,
1065    ) -> GraphResult<EdgeId> {
1066        // Validate nodes exist
1067        if !self.has_node(source) {
1068            return Err(GraphError::InvalidEdgeSource(source));
1069        }
1070        if !self.has_node(target) {
1071            return Err(GraphError::InvalidEdgeTarget(target));
1072        }
1073
1074        let edge_id_u64 = if let Some(id) = self.free_edge_ids.pop() {
1075            id
1076        } else {
1077            let id = self.next_edge_id;
1078            self.next_edge_id += 1;
1079            id
1080        };
1081        let edge_id = EdgeId::new(edge_id_u64);
1082        let idx = edge_id_u64 as usize;
1083
1084        // Populate columnar storage
1085        for (key, value) in &properties {
1086            self.edge_columns.set_property(idx, key, value.clone());
1087        }
1088
1089        let edge_type = edge_type.into();
1090        let mut edge =
1091            Edge::new_with_properties(edge_id, source, target, edge_type.clone(), properties);
1092        edge.version = self.current_version;
1093
1094        // Update adjacency lists (sorted insert by target/source NodeId)
1095        {
1096            let out_list = &mut self.outgoing[source.as_u64() as usize];
1097            let pos = out_list
1098                .binary_search_by_key(&target, |(nid, _)| *nid)
1099                .unwrap_or_else(|p| p);
1100            out_list.insert(pos, (target, edge_id));
1101        }
1102        {
1103            let in_list = &mut self.incoming[target.as_u64() as usize];
1104            let pos = in_list
1105                .binary_search_by_key(&source, |(nid, _)| *nid)
1106                .unwrap_or_else(|p| p);
1107            in_list.insert(pos, (source, edge_id));
1108        }
1109
1110        // Ensure storage capacity
1111        if idx >= self.edges.len() {
1112            self.edges.resize(idx + 1, Vec::new());
1113        }
1114
1115        // Update edge type index
1116        self.edge_type_index
1117            .entry(edge_type.clone())
1118            .or_default()
1119            .insert(edge_id);
1120
1121        // Update catalog triple stats
1122        let src_labels: Vec<Label> = self
1123            .get_node(source)
1124            .map(|n| n.labels.iter().cloned().collect())
1125            .unwrap_or_default();
1126        let tgt_labels: Vec<Label> = self
1127            .get_node(target)
1128            .map(|n| n.labels.iter().cloned().collect())
1129            .unwrap_or_default();
1130        self.catalog
1131            .on_edge_created(source, &src_labels, &edge_type, target, &tgt_labels);
1132
1133        self.edges[idx].push(edge);
1134        Ok(edge_id)
1135    }
1136
1137    /// Get an edge by ID at a specific version (MVCC)
1138    pub fn get_edge_at_version(&self, id: EdgeId, version: u64) -> Option<&Edge> {
1139        let idx = id.as_u64() as usize;
1140        let versions = self.edges.get(idx)?;
1141
1142        // Find the latest version <= requested version
1143        versions.iter().rev().find(|e| e.version <= version)
1144    }
1145
1146    /// Get an edge by ID (uses current version)
1147    pub fn get_edge(&self, id: EdgeId) -> Option<&Edge> {
1148        self.get_edge_at_version(id, self.current_version)
1149    }
1150
1151    /// Get a mutable edge by ID (always latest version)
1152    pub fn get_edge_mut(&mut self, id: EdgeId) -> Option<&mut Edge> {
1153        self.edges
1154            .get_mut(id.as_u64() as usize)
1155            .and_then(|v| v.last_mut())
1156    }
1157
1158    /// Check if an edge exists
1159    pub fn has_edge(&self, id: EdgeId) -> bool {
1160        self.get_edge(id).is_some()
1161    }
1162
1163    /// Delete an edge
1164    pub fn delete_edge(&mut self, id: EdgeId) -> GraphResult<Edge> {
1165        let idx = id.as_u64() as usize;
1166
1167        // Collect catalog info before removal
1168        let (src_labels, tgt_labels, source_id, target_id, edge_type_clone) = {
1169            let edge = self
1170                .edges
1171                .get(idx)
1172                .and_then(|v| v.last())
1173                .ok_or(GraphError::EdgeNotFound(id))?;
1174            let src_labels: Vec<Label> = self
1175                .get_node(edge.source)
1176                .map(|n| n.labels.iter().cloned().collect())
1177                .unwrap_or_default();
1178            let tgt_labels: Vec<Label> = self
1179                .get_node(edge.target)
1180                .map(|n| n.labels.iter().cloned().collect())
1181                .unwrap_or_default();
1182            (
1183                src_labels,
1184                tgt_labels,
1185                edge.source,
1186                edge.target,
1187                edge.edge_type.clone(),
1188            )
1189        };
1190
1191        let edge = self
1192            .edges
1193            .get_mut(idx)
1194            .and_then(|v| v.pop())
1195            .ok_or(GraphError::EdgeNotFound(id))?;
1196
1197        // Add to free list
1198        self.free_edge_ids.push(id.as_u64());
1199
1200        // Remove from edge type index
1201        if let Some(edge_set) = self.edge_type_index.get_mut(&edge.edge_type) {
1202            edge_set.remove(&id);
1203        }
1204
1205        // Remove from adjacency lists
1206        if let Some(adj) = self.outgoing.get_mut(edge.source.as_u64() as usize) {
1207            adj.retain(|&(_, eid)| eid != id);
1208        }
1209        if let Some(adj) = self.incoming.get_mut(edge.target.as_u64() as usize) {
1210            adj.retain(|&(_, eid)| eid != id);
1211        }
1212
1213        // Update catalog triple stats
1214        self.catalog.on_edge_deleted(
1215            source_id,
1216            &src_labels,
1217            &edge_type_clone,
1218            target_id,
1219            &tgt_labels,
1220        );
1221
1222        Ok(edge)
1223    }
1224
1225    /// Get all outgoing edges from a node
1226    pub fn get_outgoing_edges(&self, node_id: NodeId) -> Vec<&Edge> {
1227        self.outgoing
1228            .get(node_id.as_u64() as usize)
1229            .map(|entries| {
1230                entries
1231                    .iter()
1232                    .filter_map(|&(_, eid)| self.get_edge(eid))
1233                    .collect()
1234            })
1235            .unwrap_or_default()
1236    }
1237
1238    /// Get all incoming edges to a node
1239    pub fn get_incoming_edges(&self, node_id: NodeId) -> Vec<&Edge> {
1240        self.incoming
1241            .get(node_id.as_u64() as usize)
1242            .map(|entries| {
1243                entries
1244                    .iter()
1245                    .filter_map(|&(_, eid)| self.get_edge(eid))
1246                    .collect()
1247            })
1248            .unwrap_or_default()
1249    }
1250
1251    /// Get outgoing edge targets as lightweight tuples (no Edge clone)
1252    /// Returns (EdgeId, source NodeId, target NodeId, &EdgeType) for each outgoing edge
1253    pub fn get_outgoing_edge_targets(
1254        &self,
1255        node_id: NodeId,
1256    ) -> Vec<(EdgeId, NodeId, NodeId, &EdgeType)> {
1257        self.outgoing
1258            .get(node_id.as_u64() as usize)
1259            .map(|entries| {
1260                entries
1261                    .iter()
1262                    .filter_map(|&(_, eid)| {
1263                        self.get_edge(eid)
1264                            .map(|e| (eid, e.source, e.target, &e.edge_type))
1265                    })
1266                    .collect()
1267            })
1268            .unwrap_or_default()
1269    }
1270
1271    /// Get incoming edge sources as lightweight tuples (no Edge clone)
1272    /// Returns (EdgeId, source NodeId, target NodeId, &EdgeType) for each incoming edge
1273    pub fn get_incoming_edge_sources(
1274        &self,
1275        node_id: NodeId,
1276    ) -> Vec<(EdgeId, NodeId, NodeId, &EdgeType)> {
1277        self.incoming
1278            .get(node_id.as_u64() as usize)
1279            .map(|entries| {
1280                entries
1281                    .iter()
1282                    .filter_map(|&(_, eid)| {
1283                        self.get_edge(eid)
1284                            .map(|e| (eid, e.source, e.target, &e.edge_type))
1285                    })
1286                    .collect()
1287            })
1288            .unwrap_or_default()
1289    }
1290
1291    /// Check if an edge exists between source and target, optionally filtered by edge type.
1292    /// Uses binary search on sorted adjacency lists for O(log d) lookup.
1293    /// Returns the first matching EdgeId, or None.
1294    pub fn edge_between(
1295        &self,
1296        source: NodeId,
1297        target: NodeId,
1298        edge_type: Option<&EdgeType>,
1299    ) -> Option<EdgeId> {
1300        let out_len = self
1301            .outgoing
1302            .get(source.as_u64() as usize)
1303            .map(|v| v.len())
1304            .unwrap_or(0);
1305        let in_len = self
1306            .incoming
1307            .get(target.as_u64() as usize)
1308            .map(|v| v.len())
1309            .unwrap_or(0);
1310
1311        // Use outgoing from source (search for target) or incoming to target (search for source)
1312        let (entries, search_key) = if out_len <= in_len {
1313            (self.outgoing.get(source.as_u64() as usize)?, target)
1314        } else {
1315            (self.incoming.get(target.as_u64() as usize)?, source)
1316        };
1317
1318        // Binary search to find the neighborhood of matching entries
1319        let start = match entries.binary_search_by_key(&search_key, |(nid, _)| *nid) {
1320            Ok(pos) => {
1321                // Walk back to find the first entry with this key
1322                let mut p = pos;
1323                while p > 0 && entries[p - 1].0 == search_key {
1324                    p -= 1;
1325                }
1326                p
1327            }
1328            Err(_) => return None,
1329        };
1330
1331        // Scan forward through entries with the matching key
1332        for &(nid, eid) in entries.iter().skip(start) {
1333            if nid != search_key {
1334                break;
1335            }
1336            if let Some(e) = self.get_edge(eid) {
1337                if e.source == source && e.target == target {
1338                    match edge_type {
1339                        Some(et) if &e.edge_type != et => continue,
1340                        _ => return Some(eid),
1341                    }
1342                }
1343            }
1344        }
1345        None
1346    }
1347
1348    /// Get all edges between source and target, optionally filtered by edge type.
1349    /// Uses binary search on sorted adjacency lists.
1350    pub fn edges_between(
1351        &self,
1352        source: NodeId,
1353        target: NodeId,
1354        edge_type: Option<&EdgeType>,
1355    ) -> Vec<EdgeId> {
1356        let out_len = self
1357            .outgoing
1358            .get(source.as_u64() as usize)
1359            .map(|v| v.len())
1360            .unwrap_or(0);
1361        let in_len = self
1362            .incoming
1363            .get(target.as_u64() as usize)
1364            .map(|v| v.len())
1365            .unwrap_or(0);
1366
1367        let (entries, search_key) = if out_len <= in_len {
1368            match self.outgoing.get(source.as_u64() as usize) {
1369                Some(e) => (e, target),
1370                None => return Vec::new(),
1371            }
1372        } else {
1373            match self.incoming.get(target.as_u64() as usize) {
1374                Some(e) => (e, source),
1375                None => return Vec::new(),
1376            }
1377        };
1378
1379        let start = match entries.binary_search_by_key(&search_key, |(nid, _)| *nid) {
1380            Ok(pos) => {
1381                let mut p = pos;
1382                while p > 0 && entries[p - 1].0 == search_key {
1383                    p -= 1;
1384                }
1385                p
1386            }
1387            Err(_) => return Vec::new(),
1388        };
1389
1390        let mut result = Vec::new();
1391        for &(nid, eid) in entries.iter().skip(start) {
1392            if nid != search_key {
1393                break;
1394            }
1395            if let Some(e) = self.get_edge(eid) {
1396                if e.source == source && e.target == target {
1397                    match edge_type {
1398                        Some(et) if &e.edge_type != et => {}
1399                        _ => result.push(eid),
1400                    }
1401                }
1402            }
1403        }
1404        result
1405    }
1406
1407    /// Get all nodes with a specific label
1408    pub fn get_nodes_by_label(&self, label: &Label) -> Vec<&Node> {
1409        self.label_index
1410            .get(label)
1411            .map(|node_ids| {
1412                node_ids
1413                    .iter()
1414                    .filter_map(|&id| self.get_node(id))
1415                    .collect()
1416            })
1417            .unwrap_or_default()
1418    }
1419
1420    /// Get all edges of a specific type
1421    pub fn get_edges_by_type(&self, edge_type: &EdgeType) -> Vec<&Edge> {
1422        self.edge_type_index
1423            .get(edge_type)
1424            .map(|edge_ids| {
1425                edge_ids
1426                    .iter()
1427                    .filter_map(|&id| self.get_edge(id))
1428                    .collect()
1429            })
1430            .unwrap_or_default()
1431    }
1432
1433    /// Get total number of nodes
1434    pub fn node_count(&self) -> usize {
1435        self.nodes.iter().flatten().count()
1436    }
1437
1438    /// Get total number of edges
1439    pub fn edge_count(&self) -> usize {
1440        self.edges.iter().flatten().count()
1441    }
1442
1443    /// Get all nodes in the graph
1444    pub fn all_nodes(&self) -> Vec<&Node> {
1445        self.nodes.iter().flatten().collect()
1446    }
1447
1448    /// Get all edges in the graph
1449    pub fn all_edges(&self) -> Vec<&Edge> {
1450        self.edges.iter().flatten().collect()
1451    }
1452
1453    // ============================================================
1454    // Graph Statistics (for cost-based query optimization)
1455    // ============================================================
1456
1457    /// Compute statistics for the current graph state
1458    pub fn compute_statistics(&self) -> GraphStatistics {
1459        let total_nodes = self.node_count();
1460        let total_edges = self.edge_count();
1461
1462        let mut label_counts = HashMap::new();
1463        for (label, node_ids) in &self.label_index {
1464            label_counts.insert(label.clone(), node_ids.len());
1465        }
1466
1467        let mut edge_type_counts = HashMap::new();
1468        for (edge_type, edge_ids) in &self.edge_type_index {
1469            edge_type_counts.insert(edge_type.clone(), edge_ids.len());
1470        }
1471
1472        // Compute average degree
1473        let avg_out_degree = if total_nodes > 0 {
1474            total_edges as f64 / total_nodes as f64
1475        } else {
1476            0.0
1477        };
1478
1479        // Sample property selectivity for common properties
1480        let mut property_stats: HashMap<(Label, String), PropertyStats> = HashMap::new();
1481        for (label, node_ids) in &self.label_index {
1482            let sample_size = node_ids.len().min(1000);
1483            let mut property_presence: HashMap<String, usize> = HashMap::new();
1484            let mut property_distinct: HashMap<String, HashSet<u64>> = HashMap::new();
1485
1486            for (i, &node_id) in node_ids.iter().enumerate() {
1487                if i >= sample_size {
1488                    break;
1489                }
1490                if let Some(node) = self.get_node(node_id) {
1491                    for (key, val) in &node.properties {
1492                        *property_presence.entry(key.clone()).or_insert(0) += 1;
1493
1494                        let hash = {
1495                            use std::hash::{Hash, Hasher};
1496                            let mut hasher = std::collections::hash_map::DefaultHasher::new();
1497                            val.hash(&mut hasher);
1498                            hasher.finish()
1499                        };
1500                        property_distinct
1501                            .entry(key.clone())
1502                            .or_default()
1503                            .insert(hash);
1504                    }
1505                }
1506            }
1507
1508            for (prop, count) in &property_presence {
1509                let distinct = property_distinct.get(prop).map(|s| s.len()).unwrap_or(0);
1510                let selectivity = if distinct > 0 {
1511                    1.0 / distinct as f64
1512                } else {
1513                    1.0
1514                };
1515                property_stats.insert(
1516                    (label.clone(), prop.clone()),
1517                    PropertyStats {
1518                        null_fraction: 1.0 - (*count as f64 / sample_size as f64),
1519                        distinct_count: distinct,
1520                        selectivity,
1521                    },
1522                );
1523            }
1524        }
1525
1526        GraphStatistics {
1527            total_nodes,
1528            total_edges,
1529            label_counts,
1530            edge_type_counts,
1531            avg_out_degree,
1532            property_stats,
1533        }
1534    }
1535
1536    /// Get the triple-level statistics catalog (for graph-native query planning)
1537    pub fn catalog(&self) -> &GraphCatalog {
1538        &self.catalog
1539    }
1540
1541    /// Get node count for a specific label (fast, O(1))
1542    pub fn label_node_count(&self, label: &Label) -> usize {
1543        self.label_index.get(label).map(|s| s.len()).unwrap_or(0)
1544    }
1545
1546    /// Get edge count for a specific type (fast, O(1))
1547    pub fn edge_type_count(&self, edge_type: &EdgeType) -> usize {
1548        self.edge_type_index
1549            .get(edge_type)
1550            .map(|s| s.len())
1551            .unwrap_or(0)
1552    }
1553
1554    /// Get all label names in the graph
1555    pub fn all_labels(&self) -> Vec<&Label> {
1556        self.label_index.keys().collect()
1557    }
1558
1559    /// Get all edge type names in the graph
1560    pub fn all_edge_types(&self) -> Vec<&EdgeType> {
1561        self.edge_type_index.keys().collect()
1562    }
1563
1564    /// Clear all data from the graph
1565    pub fn clear(&mut self) {
1566        self.nodes.clear();
1567        self.edges.clear();
1568        self.outgoing.clear();
1569        self.incoming.clear();
1570        self.free_node_ids.clear();
1571        self.free_edge_ids.clear();
1572        self.label_index.clear();
1573        self.edge_type_index.clear();
1574        self.vector_index = Arc::new(VectorIndexManager::new());
1575        self.property_index = Arc::new(IndexManager::new());
1576        self.node_columns = ColumnStore::new();
1577        self.edge_columns = ColumnStore::new();
1578        self.next_node_id = 1;
1579        self.next_edge_id = 1;
1580        self.catalog.clear();
1581    }
1582
1583    // ============================================================
1584    // Event Handling
1585    // ============================================================
1586
1587    pub fn handle_index_event(
1588        &self,
1589        event: crate::graph::event::IndexEvent,
1590        _tenant_manager: Option<Arc<crate::persistence::TenantManager>>,
1591    ) {
1592        use crate::graph::event::IndexEvent::*;
1593        match event {
1594            NodeCreated {
1595                tenant_id: _,
1596                id,
1597                labels,
1598                properties,
1599            } => {
1600                for (key, value) in properties {
1601                    if let PropertyValue::Vector(vec) = &value {
1602                        for label in &labels {
1603                            let _ = self.vector_index.add_vector(label.as_str(), &key, id, vec);
1604                        }
1605                    }
1606                    for label in &labels {
1607                        self.property_index
1608                            .index_insert(label, &key, value.clone(), id);
1609                    }
1610                }
1611            }
1612            NodeDeleted {
1613                tenant_id: _,
1614                id,
1615                labels,
1616                properties,
1617            } => {
1618                for (key, value) in properties {
1619                    for label in &labels {
1620                        self.property_index.index_remove(label, &key, &value, id);
1621                    }
1622                }
1623            }
1624            PropertySet {
1625                tenant_id: _,
1626                id,
1627                labels,
1628                key,
1629                old_value,
1630                new_value,
1631            } => {
1632                if let Some(old) = old_value {
1633                    for label in &labels {
1634                        self.property_index.index_remove(label, &key, &old, id);
1635                    }
1636                }
1637                for label in &labels {
1638                    self.property_index
1639                        .index_insert(label, &key, new_value.clone(), id);
1640                }
1641                if let PropertyValue::Vector(vec) = &new_value {
1642                    for label in &labels {
1643                        let _ = self.vector_index.add_vector(label.as_str(), &key, id, vec);
1644                    }
1645                }
1646            }
1647            LabelAdded {
1648                tenant_id: _,
1649                id,
1650                label,
1651                properties,
1652            } => {
1653                for (key, value) in properties {
1654                    if let PropertyValue::Vector(vec) = &value {
1655                        let _ = self.vector_index.add_vector(label.as_str(), &key, id, vec);
1656                    }
1657                    self.property_index
1658                        .index_insert(&label, &key, value.clone(), id);
1659                }
1660            }
1661        }
1662    }
1663
1664    // ============================================================
1665    // Vector Index methods
1666    // ============================================================
1667
1668    /// Create a vector index for a specific label and property
1669    pub fn create_vector_index(
1670        &self,
1671        label: &str,
1672        property_key: &str,
1673        dimensions: usize,
1674        metric: DistanceMetric,
1675    ) -> VectorResult<()> {
1676        self.vector_index
1677            .create_index(label, property_key, dimensions, metric)
1678    }
1679
1680    /// Search for nearest neighbors using a vector index
1681    pub fn vector_search(
1682        &self,
1683        label: &str,
1684        property_key: &str,
1685        query: &[f32],
1686        k: usize,
1687    ) -> VectorResult<Vec<(NodeId, f32)>> {
1688        self.vector_index.search(label, property_key, query, k)
1689    }
1690
1691    // ============================================================
1692    // Recovery methods - used to rebuild graph from persisted data
1693    // ============================================================
1694
1695    /// Insert a recovered node (used during recovery from persistence)
1696    /// Unlike create_node(), this preserves the node's existing ID
1697    pub fn insert_recovered_node(&mut self, node: Node) {
1698        let node_id = node.id;
1699        let idx = node_id.as_u64() as usize;
1700
1701        // Ensure storage capacity
1702        if idx >= self.nodes.len() {
1703            self.nodes.resize(idx + 1, Vec::new());
1704            self.outgoing.resize(idx + 1, Vec::new());
1705            self.incoming.resize(idx + 1, Vec::new());
1706        }
1707
1708        // Update label indices for all labels
1709        for label in &node.labels {
1710            self.label_index
1711                .entry(label.clone())
1712                .or_default()
1713                .insert(node_id);
1714        }
1715
1716        // Insert the node
1717        self.nodes[idx].push(node);
1718
1719        // Update next_node_id to be higher than any recovered node
1720        if node_id.as_u64() >= self.next_node_id {
1721            self.next_node_id = node_id.as_u64() + 1;
1722        }
1723    }
1724
1725    /// Insert a recovered edge (used during recovery from persistence)
1726    /// Unlike create_edge(), this preserves the edge's existing ID
1727    /// Note: Source and target nodes must already exist
1728    pub fn insert_recovered_edge(&mut self, edge: Edge) -> GraphResult<()> {
1729        let edge_id = edge.id;
1730        let idx = edge_id.as_u64() as usize;
1731        let source = edge.source;
1732        let target = edge.target;
1733
1734        // Ensure capacity
1735        if idx >= self.edges.len() {
1736            self.edges.resize(idx + 1, Vec::new());
1737        }
1738
1739        // Validate nodes exist
1740        if !self.has_node(source) {
1741            return Err(GraphError::InvalidEdgeSource(source));
1742        }
1743        if !self.has_node(target) {
1744            return Err(GraphError::InvalidEdgeTarget(target));
1745        }
1746
1747        // Update adjacency lists (sorted insert)
1748        {
1749            let out_list = &mut self.outgoing[source.as_u64() as usize];
1750            let pos = out_list
1751                .binary_search_by_key(&target, |(nid, _)| *nid)
1752                .unwrap_or_else(|p| p);
1753            out_list.insert(pos, (target, edge_id));
1754        }
1755        {
1756            let in_list = &mut self.incoming[target.as_u64() as usize];
1757            let pos = in_list
1758                .binary_search_by_key(&source, |(nid, _)| *nid)
1759                .unwrap_or_else(|p| p);
1760            in_list.insert(pos, (source, edge_id));
1761        }
1762
1763        // Update edge type index
1764        self.edge_type_index
1765            .entry(edge.edge_type.clone())
1766            .or_default()
1767            .insert(edge_id);
1768
1769        // Insert the edge
1770        self.edges[idx].push(edge);
1771
1772        // Update next_edge_id to be higher than any recovered edge
1773        if edge_id.as_u64() >= self.next_edge_id {
1774            self.next_edge_id = edge_id.as_u64() + 1;
1775        }
1776
1777        Ok(())
1778    }
1779}
1780
1781impl Default for GraphStore {
1782    fn default() -> Self {
1783        Self::new()
1784    }
1785}
1786
1787#[cfg(test)]
1788mod tests {
1789    use super::*;
1790
1791    #[test]
1792    fn test_create_and_get_node() {
1793        let mut store = GraphStore::new();
1794        let node_id = store.create_node("Person");
1795
1796        assert_eq!(store.node_count(), 1);
1797        let node = store.get_node(node_id).unwrap();
1798        assert_eq!(node.id, node_id);
1799        assert!(node.has_label(&Label::new("Person")));
1800    }
1801
1802    #[test]
1803    fn test_create_node_with_properties() {
1804        let mut store = GraphStore::new();
1805        let mut props = PropertyMap::new();
1806        props.insert("name".to_string(), "Alice".into());
1807        props.insert("age".to_string(), 30i64.into());
1808
1809        let node_id = store
1810            .create_node_with_properties(
1811                "default",
1812                vec![Label::new("Person"), Label::new("Employee")],
1813                props,
1814            )
1815            .unwrap();
1816
1817        let node = store.get_node(node_id).unwrap();
1818        assert_eq!(node.label_count(), 2);
1819        assert_eq!(
1820            node.get_property("name").unwrap().as_string(),
1821            Some("Alice")
1822        );
1823        assert_eq!(node.get_property("age").unwrap().as_integer(), Some(30));
1824    }
1825
1826    #[test]
1827    fn test_create_and_get_edge() {
1828        let mut store = GraphStore::new();
1829        let node1 = store.create_node("Person");
1830        let node2 = store.create_node("Person");
1831
1832        let edge_id = store.create_edge(node1, node2, "KNOWS").unwrap();
1833
1834        assert_eq!(store.edge_count(), 1);
1835        let edge = store.get_edge(edge_id).unwrap();
1836        assert_eq!(edge.source, node1);
1837        assert_eq!(edge.target, node2);
1838        assert_eq!(edge.edge_type, EdgeType::new("KNOWS"));
1839    }
1840
1841    #[test]
1842    fn test_edge_validation() {
1843        let mut store = GraphStore::new();
1844        let node1 = store.create_node("Person");
1845        let invalid_node = NodeId::new(999);
1846
1847        // Invalid source node
1848        let result = store.create_edge(invalid_node, node1, "KNOWS");
1849        assert_eq!(result, Err(GraphError::InvalidEdgeSource(invalid_node)));
1850
1851        // Invalid target node
1852        let result = store.create_edge(node1, invalid_node, "KNOWS");
1853        assert_eq!(result, Err(GraphError::InvalidEdgeTarget(invalid_node)));
1854    }
1855
1856    #[test]
1857    fn test_adjacency_lists() {
1858        let mut store = GraphStore::new();
1859        let node1 = store.create_node("Person");
1860        let node2 = store.create_node("Person");
1861        let node3 = store.create_node("Person");
1862
1863        store.create_edge(node1, node2, "KNOWS").unwrap();
1864        store.create_edge(node1, node3, "KNOWS").unwrap();
1865        store.create_edge(node2, node3, "FOLLOWS").unwrap();
1866
1867        // Node1 has 2 outgoing edges
1868        let outgoing = store.get_outgoing_edges(node1);
1869        assert_eq!(outgoing.len(), 2);
1870
1871        // Node2 has 1 outgoing, 1 incoming
1872        let outgoing = store.get_outgoing_edges(node2);
1873        assert_eq!(outgoing.len(), 1);
1874        let incoming = store.get_incoming_edges(node2);
1875        assert_eq!(incoming.len(), 1);
1876
1877        // Node3 has 0 outgoing, 2 incoming
1878        let outgoing = store.get_outgoing_edges(node3);
1879        assert_eq!(outgoing.len(), 0);
1880        let incoming = store.get_incoming_edges(node3);
1881        assert_eq!(incoming.len(), 2);
1882    }
1883
1884    #[test]
1885    fn test_label_index() {
1886        let mut store = GraphStore::new();
1887        store.create_node("Person");
1888        store.create_node("Person");
1889        store.create_node("Company");
1890
1891        let persons = store.get_nodes_by_label(&Label::new("Person"));
1892        assert_eq!(persons.len(), 2);
1893
1894        let companies = store.get_nodes_by_label(&Label::new("Company"));
1895        assert_eq!(companies.len(), 1);
1896    }
1897
1898    #[test]
1899    fn test_edge_type_index() {
1900        let mut store = GraphStore::new();
1901        let n1 = store.create_node("Person");
1902        let n2 = store.create_node("Person");
1903        let n3 = store.create_node("Person");
1904
1905        store.create_edge(n1, n2, "KNOWS").unwrap();
1906        store.create_edge(n2, n3, "KNOWS").unwrap();
1907        store.create_edge(n1, n3, "FOLLOWS").unwrap();
1908
1909        let knows_edges = store.get_edges_by_type(&EdgeType::new("KNOWS"));
1910        assert_eq!(knows_edges.len(), 2);
1911
1912        let follows_edges = store.get_edges_by_type(&EdgeType::new("FOLLOWS"));
1913        assert_eq!(follows_edges.len(), 1);
1914    }
1915
1916    #[test]
1917    fn test_delete_node() {
1918        let mut store = GraphStore::new();
1919        let node1 = store.create_node("Person");
1920        let node2 = store.create_node("Person");
1921        store.create_edge(node1, node2, "KNOWS").unwrap();
1922
1923        assert_eq!(store.node_count(), 2);
1924        assert_eq!(store.edge_count(), 1);
1925
1926        // Delete node1 (should also delete connected edge)
1927        let deleted = store.delete_node("default", node1);
1928        assert!(deleted.is_ok());
1929        assert_eq!(store.node_count(), 1);
1930        assert_eq!(store.edge_count(), 0);
1931    }
1932
1933    #[test]
1934    fn test_delete_edge() {
1935        let mut store = GraphStore::new();
1936        let node1 = store.create_node("Person");
1937        let node2 = store.create_node("Person");
1938        let edge_id = store.create_edge(node1, node2, "KNOWS").unwrap();
1939
1940        assert_eq!(store.edge_count(), 1);
1941
1942        let deleted = store.delete_edge(edge_id);
1943        assert!(deleted.is_ok());
1944        assert_eq!(store.edge_count(), 0);
1945
1946        // Edge removed from adjacency lists
1947        assert_eq!(store.get_outgoing_edges(node1).len(), 0);
1948        assert_eq!(store.get_incoming_edges(node2).len(), 0);
1949    }
1950
1951    #[test]
1952    fn test_multiple_edges_between_nodes() {
1953        // REQ-GRAPH-008: Multiple edges between same nodes
1954        let mut store = GraphStore::new();
1955        let node1 = store.create_node("Person");
1956        let node2 = store.create_node("Person");
1957
1958        let edge1 = store.create_edge(node1, node2, "KNOWS").unwrap();
1959        let edge2 = store.create_edge(node1, node2, "WORKS_WITH").unwrap();
1960        let edge3 = store.create_edge(node1, node2, "KNOWS").unwrap();
1961
1962        assert_eq!(store.edge_count(), 3);
1963        assert_ne!(edge1, edge2);
1964        assert_ne!(edge1, edge3);
1965
1966        let outgoing = store.get_outgoing_edges(node1);
1967        assert_eq!(outgoing.len(), 3);
1968    }
1969
1970    #[test]
1971    fn test_clear() {
1972        let mut store = GraphStore::new();
1973        store.create_node("Person");
1974        store.create_node("Person");
1975
1976        assert_eq!(store.node_count(), 2);
1977
1978        store.clear();
1979        assert_eq!(store.node_count(), 0);
1980        assert_eq!(store.edge_count(), 0);
1981    }
1982
1983    #[test]
1984    fn test_add_label_to_node() {
1985        let mut store = GraphStore::new();
1986        let node_id = store.create_node("Person");
1987
1988        // Initially only "Person" label is indexed
1989        assert_eq!(store.get_nodes_by_label(&Label::new("Person")).len(), 1);
1990        assert_eq!(store.get_nodes_by_label(&Label::new("Employee")).len(), 0);
1991
1992        // Add "Employee" label using the proper method
1993        store
1994            .add_label_to_node("default", node_id, "Employee")
1995            .unwrap();
1996
1997        // Now both labels should be indexed and queryable
1998        assert_eq!(store.get_nodes_by_label(&Label::new("Person")).len(), 1);
1999        assert_eq!(store.get_nodes_by_label(&Label::new("Employee")).len(), 1);
2000
2001        // Verify the node actually has both labels
2002        let node = store.get_node(node_id).unwrap();
2003        assert!(node.has_label(&Label::new("Person")));
2004        assert!(node.has_label(&Label::new("Employee")));
2005    }
2006
2007    #[test]
2008    fn test_add_label_to_nonexistent_node() {
2009        let mut store = GraphStore::new();
2010        let invalid_id = NodeId::new(999);
2011
2012        let result = store.add_label_to_node("default", invalid_id, "Employee");
2013        assert_eq!(result, Err(GraphError::NodeNotFound(invalid_id)));
2014    }
2015
2016    #[test]
2017    fn test_mvcc_node_versioning() {
2018        let mut store = GraphStore::new();
2019
2020        // Version 1: Create node
2021        let node_id = store.create_node("Person");
2022        store
2023            .set_node_property("default", node_id, "name", "Alice")
2024            .unwrap();
2025
2026        // Check version 1
2027        let v1_node = store.get_node_at_version(node_id, 1).unwrap();
2028        assert_eq!(v1_node.version, 1);
2029        assert_eq!(
2030            v1_node.get_property("name").unwrap().as_string(),
2031            Some("Alice")
2032        );
2033
2034        // Version 2: Update property (creates new version)
2035        store.current_version = 2;
2036        store
2037            .set_node_property("default", node_id, "name", "Alice Cooper")
2038            .unwrap();
2039
2040        // Check version 2
2041        let v2_node = store.get_node_at_version(node_id, 2).unwrap();
2042        assert_eq!(v2_node.version, 2);
2043        assert_eq!(
2044            v2_node.get_property("name").unwrap().as_string(),
2045            Some("Alice Cooper")
2046        );
2047
2048        // Historical read (Version 1 should still be Alice)
2049        let v1_lookup = store.get_node_at_version(node_id, 1).unwrap();
2050        assert_eq!(v1_lookup.version, 1);
2051        assert_eq!(
2052            v1_lookup.get_property("name").unwrap().as_string(),
2053            Some("Alice")
2054        );
2055
2056        let node = store.get_node(node_id).unwrap();
2057        assert_eq!(node.version, 2); // Should be latest version
2058    }
2059
2060    #[test]
2061    fn test_arena_resize() {
2062        let mut store = GraphStore::new();
2063        // Default capacity is 1024. Let's force a resize.
2064        // We can't easily peek capacity, but we can add > 1024 nodes.
2065
2066        for _ in 0..1100 {
2067            store.create_node("Item");
2068        }
2069
2070        assert_eq!(store.node_count(), 1100);
2071        let last_id = NodeId::new(1100);
2072        assert!(store.has_node(last_id));
2073    }
2074
2075    #[test]
2076    fn test_id_reuse() {
2077        let mut store = GraphStore::new();
2078        let n1 = store.create_node("A");
2079        let _n2 = store.create_node("B");
2080
2081        store.delete_node("default", n1).unwrap();
2082
2083        // Next creation should reuse n1's ID (which is 1)
2084        // n2 is 2.
2085        let n3 = store.create_node("C");
2086
2087        assert_eq!(n3, n1); // ID reuse
2088        assert_eq!(store.node_count(), 2); // B and C
2089    }
2090
2091    // ========== Batch 5: Additional Store Tests ==========
2092
2093    #[test]
2094    fn test_get_node() {
2095        let mut store = GraphStore::new();
2096        let id = store.create_node("Person");
2097        let node = store.get_node(id);
2098        assert!(node.is_some());
2099        assert!(node.unwrap().labels.contains(&Label::new("Person")));
2100
2101        // Non-existent node
2102        assert!(store.get_node(NodeId::new(9999)).is_none());
2103    }
2104
2105    #[test]
2106    fn test_get_node_mut() {
2107        let mut store = GraphStore::new();
2108        let id = store.create_node("Person");
2109        {
2110            let node = store.get_node_mut(id).unwrap();
2111            node.set_property(
2112                "name".to_string(),
2113                PropertyValue::String("Alice".to_string()),
2114            );
2115        }
2116        let node = store.get_node(id).unwrap();
2117        assert_eq!(
2118            node.get_property("name"),
2119            Some(&PropertyValue::String("Alice".to_string()))
2120        );
2121
2122        // Non-existent node
2123        assert!(store.get_node_mut(NodeId::new(9999)).is_none());
2124    }
2125
2126    #[test]
2127    fn test_has_node() {
2128        let mut store = GraphStore::new();
2129        let id = store.create_node("A");
2130        assert!(store.has_node(id));
2131        store.delete_node("default", id).unwrap();
2132        assert!(!store.has_node(id));
2133    }
2134
2135    #[test]
2136    fn test_set_node_property() {
2137        let mut store = GraphStore::new();
2138        let id = store.create_node("Person");
2139        store
2140            .set_node_property("default", id, "age", PropertyValue::Integer(30))
2141            .unwrap();
2142        let node = store.get_node(id).unwrap();
2143        assert_eq!(node.get_property("age"), Some(&PropertyValue::Integer(30)));
2144
2145        // Update existing property
2146        store
2147            .set_node_property("default", id, "age", PropertyValue::Integer(31))
2148            .unwrap();
2149        let node = store.get_node(id).unwrap();
2150        assert_eq!(node.get_property("age"), Some(&PropertyValue::Integer(31)));
2151
2152        // Non-existent node
2153        let result =
2154            store.set_node_property("default", NodeId::new(9999), "x", PropertyValue::Null);
2155        assert!(result.is_err());
2156    }
2157
2158    #[test]
2159    fn test_create_edge_with_properties() {
2160        let mut store = GraphStore::new();
2161        let a = store.create_node("Person");
2162        let b = store.create_node("Person");
2163
2164        let mut props = std::collections::HashMap::new();
2165        props.insert("since".to_string(), PropertyValue::Integer(2020));
2166        props.insert("weight".to_string(), PropertyValue::Float(0.8));
2167
2168        let eid = store
2169            .create_edge_with_properties(a, b, "KNOWS", props)
2170            .unwrap();
2171        let edge = store.get_edge(eid).unwrap();
2172        assert_eq!(edge.source, a);
2173        assert_eq!(edge.target, b);
2174        assert_eq!(
2175            edge.get_property("since"),
2176            Some(&PropertyValue::Integer(2020))
2177        );
2178        assert_eq!(
2179            edge.get_property("weight"),
2180            Some(&PropertyValue::Float(0.8))
2181        );
2182    }
2183
2184    #[test]
2185    fn test_create_edge_with_properties_invalid_nodes() {
2186        let mut store = GraphStore::new();
2187        let a = store.create_node("A");
2188        let props = std::collections::HashMap::new();
2189
2190        // Invalid target
2191        let result = store.create_edge_with_properties(a, NodeId::new(9999), "E", props.clone());
2192        assert!(result.is_err());
2193
2194        // Invalid source
2195        let result = store.create_edge_with_properties(NodeId::new(9999), a, "E", props);
2196        assert!(result.is_err());
2197    }
2198
2199    #[test]
2200    fn test_get_edge_and_has_edge() {
2201        let mut store = GraphStore::new();
2202        let a = store.create_node("A");
2203        let b = store.create_node("B");
2204        let eid = store.create_edge(a, b, "LINKS").unwrap();
2205
2206        assert!(store.has_edge(eid));
2207        let edge = store.get_edge(eid).unwrap();
2208        assert_eq!(edge.source, a);
2209        assert_eq!(edge.target, b);
2210
2211        // Non-existent
2212        assert!(!store.has_edge(EdgeId::new(9999)));
2213        assert!(store.get_edge(EdgeId::new(9999)).is_none());
2214    }
2215
2216    #[test]
2217    fn test_get_edge_mut() {
2218        let mut store = GraphStore::new();
2219        let a = store.create_node("A");
2220        let b = store.create_node("B");
2221        let eid = store.create_edge(a, b, "LINKS").unwrap();
2222
2223        {
2224            let edge = store.get_edge_mut(eid).unwrap();
2225            edge.set_property("weight".to_string(), PropertyValue::Float(1.5));
2226        }
2227        let edge = store.get_edge(eid).unwrap();
2228        assert_eq!(
2229            edge.get_property("weight"),
2230            Some(&PropertyValue::Float(1.5))
2231        );
2232
2233        assert!(store.get_edge_mut(EdgeId::new(9999)).is_none());
2234    }
2235
2236    #[test]
2237    fn test_get_outgoing_edge_targets() {
2238        let mut store = GraphStore::new();
2239        let a = store.create_node("A");
2240        let b = store.create_node("B");
2241        let c = store.create_node("C");
2242        let e1 = store.create_edge(a, b, "KNOWS").unwrap();
2243        let e2 = store.create_edge(a, c, "LIKES").unwrap();
2244
2245        let targets = store.get_outgoing_edge_targets(a);
2246        assert_eq!(targets.len(), 2);
2247        // Each tuple is (EdgeId, source, target, &EdgeType)
2248        let edge_ids: Vec<EdgeId> = targets.iter().map(|t| t.0).collect();
2249        assert!(edge_ids.contains(&e1));
2250        assert!(edge_ids.contains(&e2));
2251
2252        // Node with no outgoing edges
2253        let targets = store.get_outgoing_edge_targets(b);
2254        assert!(targets.is_empty());
2255    }
2256
2257    #[test]
2258    fn test_get_incoming_edge_sources() {
2259        let mut store = GraphStore::new();
2260        let a = store.create_node("A");
2261        let b = store.create_node("B");
2262        let c = store.create_node("C");
2263        store.create_edge(a, c, "KNOWS").unwrap();
2264        store.create_edge(b, c, "LIKES").unwrap();
2265
2266        let sources = store.get_incoming_edge_sources(c);
2267        assert_eq!(sources.len(), 2);
2268        let src_nodes: Vec<NodeId> = sources.iter().map(|t| t.1).collect();
2269        assert!(src_nodes.contains(&a));
2270        assert!(src_nodes.contains(&b));
2271
2272        // Node with no incoming edges
2273        let sources = store.get_incoming_edge_sources(a);
2274        assert!(sources.is_empty());
2275    }
2276
2277    #[test]
2278    fn test_all_nodes() {
2279        let mut store = GraphStore::new();
2280        assert!(store.all_nodes().is_empty());
2281
2282        store.create_node("A");
2283        store.create_node("B");
2284        store.create_node("C");
2285        assert_eq!(store.all_nodes().len(), 3);
2286    }
2287
2288    #[test]
2289    fn test_all_edges() {
2290        let mut store = GraphStore::new();
2291        assert!(store.all_edges().is_empty());
2292
2293        let a = store.create_node("A");
2294        let b = store.create_node("B");
2295        let c = store.create_node("C");
2296        store.create_edge(a, b, "R1").unwrap();
2297        store.create_edge(b, c, "R2").unwrap();
2298        assert_eq!(store.all_edges().len(), 2);
2299    }
2300
2301    #[test]
2302    fn test_compute_statistics() {
2303        let mut store = GraphStore::new();
2304        let a = store.create_node("Person");
2305        let b = store.create_node("Person");
2306        let c = store.create_node("Company");
2307        store.get_node_mut(a).unwrap().set_property(
2308            "name".to_string(),
2309            PropertyValue::String("Alice".to_string()),
2310        );
2311        store
2312            .get_node_mut(b)
2313            .unwrap()
2314            .set_property("name".to_string(), PropertyValue::String("Bob".to_string()));
2315        store.get_node_mut(c).unwrap().set_property(
2316            "name".to_string(),
2317            PropertyValue::String("Acme".to_string()),
2318        );
2319        store.create_edge(a, b, "KNOWS").unwrap();
2320        store.create_edge(a, c, "WORKS_AT").unwrap();
2321
2322        let stats = store.compute_statistics();
2323        assert_eq!(stats.total_nodes, 3);
2324        assert_eq!(stats.total_edges, 2);
2325        assert_eq!(*stats.label_counts.get(&Label::new("Person")).unwrap(), 2);
2326        assert_eq!(*stats.label_counts.get(&Label::new("Company")).unwrap(), 1);
2327        assert_eq!(
2328            *stats.edge_type_counts.get(&EdgeType::new("KNOWS")).unwrap(),
2329            1
2330        );
2331        assert_eq!(
2332            *stats
2333                .edge_type_counts
2334                .get(&EdgeType::new("WORKS_AT"))
2335                .unwrap(),
2336            1
2337        );
2338        assert!(stats.avg_out_degree > 0.0);
2339        // Property stats should exist for Person.name
2340        let person_name_stats = stats
2341            .property_stats
2342            .get(&(Label::new("Person"), "name".to_string()));
2343        assert!(person_name_stats.is_some());
2344        let ps = person_name_stats.unwrap();
2345        assert_eq!(ps.null_fraction, 0.0); // All Person nodes have "name"
2346        assert_eq!(ps.distinct_count, 2); // Alice, Bob
2347    }
2348
2349    #[test]
2350    fn test_compute_statistics_empty_graph() {
2351        let store = GraphStore::new();
2352        let stats = store.compute_statistics();
2353        assert_eq!(stats.total_nodes, 0);
2354        assert_eq!(stats.total_edges, 0);
2355        assert_eq!(stats.avg_out_degree, 0.0);
2356        assert!(stats.label_counts.is_empty());
2357        assert!(stats.edge_type_counts.is_empty());
2358    }
2359
2360    #[test]
2361    fn test_label_node_count() {
2362        let mut store = GraphStore::new();
2363        store.create_node("Person");
2364        store.create_node("Person");
2365        store.create_node("Company");
2366
2367        assert_eq!(store.label_node_count(&Label::new("Person")), 2);
2368        assert_eq!(store.label_node_count(&Label::new("Company")), 1);
2369        assert_eq!(store.label_node_count(&Label::new("NotExist")), 0);
2370    }
2371
2372    #[test]
2373    fn test_edge_type_count() {
2374        let mut store = GraphStore::new();
2375        let a = store.create_node("A");
2376        let b = store.create_node("B");
2377        let c = store.create_node("C");
2378        store.create_edge(a, b, "KNOWS").unwrap();
2379        store.create_edge(a, c, "KNOWS").unwrap();
2380        store.create_edge(b, c, "LIKES").unwrap();
2381
2382        assert_eq!(store.edge_type_count(&EdgeType::new("KNOWS")), 2);
2383        assert_eq!(store.edge_type_count(&EdgeType::new("LIKES")), 1);
2384        assert_eq!(store.edge_type_count(&EdgeType::new("NOPE")), 0);
2385    }
2386
2387    #[test]
2388    fn test_all_labels() {
2389        let mut store = GraphStore::new();
2390        store.create_node("Person");
2391        store.create_node("Company");
2392        store.create_node("Person"); // duplicate label
2393
2394        let labels = store.all_labels();
2395        assert_eq!(labels.len(), 2);
2396        let label_strs: Vec<&str> = labels.iter().map(|l| l.as_str()).collect();
2397        assert!(label_strs.contains(&"Person"));
2398        assert!(label_strs.contains(&"Company"));
2399    }
2400
2401    #[test]
2402    fn test_all_edge_types() {
2403        let mut store = GraphStore::new();
2404        let a = store.create_node("A");
2405        let b = store.create_node("B");
2406        let c = store.create_node("C");
2407        store.create_edge(a, b, "KNOWS").unwrap();
2408        store.create_edge(b, c, "LIKES").unwrap();
2409
2410        let types = store.all_edge_types();
2411        assert_eq!(types.len(), 2);
2412        let type_strs: Vec<&str> = types.iter().map(|t| t.as_str()).collect();
2413        assert!(type_strs.contains(&"KNOWS"));
2414        assert!(type_strs.contains(&"LIKES"));
2415    }
2416
2417    #[test]
2418    fn test_get_node_at_version() {
2419        let mut store = GraphStore::new();
2420        let id = store.create_node("Person");
2421        let v0 = store.get_node(id).unwrap().version;
2422
2423        // Node at its creation version should exist
2424        let node = store.get_node_at_version(id, v0);
2425        assert!(node.is_some());
2426        assert!(node.unwrap().labels.contains(&Label::new("Person")));
2427
2428        // Node at a version before creation should not exist
2429        // (only if v0 > 0, otherwise any version >= 0 finds it)
2430        if v0 > 0 {
2431            assert!(store.get_node_at_version(id, v0 - 1).is_none());
2432        }
2433
2434        // Non-existent node
2435        assert!(store.get_node_at_version(NodeId::new(9999), 0).is_none());
2436    }
2437
2438    #[test]
2439    fn test_get_edge_at_version() {
2440        let mut store = GraphStore::new();
2441        let a = store.create_node("A");
2442        let b = store.create_node("B");
2443        let eid = store.create_edge(a, b, "KNOWS").unwrap();
2444        let v0 = store.get_edge(eid).unwrap().version;
2445
2446        // Edge at version 0 should exist
2447        assert!(store.get_edge_at_version(eid, v0).is_some());
2448
2449        // Non-existent edge
2450        assert!(store.get_edge_at_version(EdgeId::new(9999), 0).is_none());
2451    }
2452
2453    #[test]
2454    fn test_create_vector_index() {
2455        let store = GraphStore::new();
2456        let result = store.create_vector_index(
2457            "Person",
2458            "embedding",
2459            128,
2460            crate::vector::DistanceMetric::Cosine,
2461        );
2462        assert!(result.is_ok());
2463
2464        // Creating a second index with different label should also succeed
2465        let result2 =
2466            store.create_vector_index("Document", "vec", 256, crate::vector::DistanceMetric::L2);
2467        assert!(result2.is_ok());
2468    }
2469
2470    #[test]
2471    fn test_set_node_property_updates_in_place() {
2472        let mut store = GraphStore::new();
2473        let id = store.create_node("Person");
2474        store
2475            .set_node_property(
2476                "default",
2477                id,
2478                "name",
2479                PropertyValue::String("Alice".to_string()),
2480            )
2481            .unwrap();
2482
2483        // Same version update — in-place
2484        store
2485            .set_node_property(
2486                "default",
2487                id,
2488                "name",
2489                PropertyValue::String("Bob".to_string()),
2490            )
2491            .unwrap();
2492        let node = store.get_node(id).unwrap();
2493        assert_eq!(
2494            node.get_property("name"),
2495            Some(&PropertyValue::String("Bob".to_string()))
2496        );
2497
2498        // Add another property
2499        store
2500            .set_node_property("default", id, "age", PropertyValue::Integer(30))
2501            .unwrap();
2502        let node = store.get_node(id).unwrap();
2503        assert_eq!(node.get_property("age"), Some(&PropertyValue::Integer(30)));
2504        assert_eq!(
2505            node.get_property("name"),
2506            Some(&PropertyValue::String("Bob".to_string()))
2507        );
2508    }
2509
2510    // ========== Coverage Enhancement Tests ==========
2511
2512    #[test]
2513    fn test_graph_error_display_node_not_found() {
2514        let err = GraphError::NodeNotFound(NodeId::new(42));
2515        assert_eq!(format!("{}", err), "Node NodeId(42) not found");
2516    }
2517
2518    #[test]
2519    fn test_graph_error_display_edge_not_found() {
2520        let err = GraphError::EdgeNotFound(EdgeId::new(7));
2521        assert_eq!(format!("{}", err), "Edge EdgeId(7) not found");
2522    }
2523
2524    #[test]
2525    fn test_graph_error_display_node_already_exists() {
2526        let err = GraphError::NodeAlreadyExists(NodeId::new(1));
2527        assert_eq!(format!("{}", err), "Node NodeId(1) already exists");
2528    }
2529
2530    #[test]
2531    fn test_graph_error_display_edge_already_exists() {
2532        let err = GraphError::EdgeAlreadyExists(EdgeId::new(3));
2533        assert_eq!(format!("{}", err), "Edge EdgeId(3) already exists");
2534    }
2535
2536    #[test]
2537    fn test_graph_error_display_invalid_edge_source() {
2538        let err = GraphError::InvalidEdgeSource(NodeId::new(99));
2539        assert_eq!(
2540            format!("{}", err),
2541            "Invalid edge: source node NodeId(99) does not exist"
2542        );
2543    }
2544
2545    #[test]
2546    fn test_graph_error_display_invalid_edge_target() {
2547        let err = GraphError::InvalidEdgeTarget(NodeId::new(88));
2548        assert_eq!(
2549            format!("{}", err),
2550            "Invalid edge: target node NodeId(88) does not exist"
2551        );
2552    }
2553
2554    #[test]
2555    fn test_graph_error_equality() {
2556        assert_eq!(
2557            GraphError::NodeNotFound(NodeId::new(1)),
2558            GraphError::NodeNotFound(NodeId::new(1))
2559        );
2560        assert_ne!(
2561            GraphError::NodeNotFound(NodeId::new(1)),
2562            GraphError::NodeNotFound(NodeId::new(2))
2563        );
2564        assert_ne!(
2565            GraphError::NodeNotFound(NodeId::new(1)),
2566            GraphError::EdgeNotFound(EdgeId::new(1))
2567        );
2568    }
2569
2570    #[test]
2571    fn test_graph_statistics_estimate_label_scan() {
2572        let mut store = GraphStore::new();
2573        for _ in 0..50 {
2574            store.create_node("Person");
2575        }
2576        for _ in 0..20 {
2577            store.create_node("Company");
2578        }
2579        let stats = store.compute_statistics();
2580        assert_eq!(stats.estimate_label_scan(&Label::new("Person")), 50);
2581        assert_eq!(stats.estimate_label_scan(&Label::new("Company")), 20);
2582        // Unknown label falls back to total_nodes (all-node scan estimate)
2583        assert_eq!(stats.estimate_label_scan(&Label::new("Unknown")), 70);
2584    }
2585
2586    #[test]
2587    fn test_graph_statistics_estimate_expand() {
2588        let mut store = GraphStore::new();
2589        let a = store.create_node("A");
2590        let b = store.create_node("B");
2591        let c = store.create_node("C");
2592        store.create_edge(a, b, "KNOWS").unwrap();
2593        store.create_edge(a, c, "KNOWS").unwrap();
2594        store.create_edge(b, c, "LIKES").unwrap();
2595
2596        let stats = store.compute_statistics();
2597        assert_eq!(
2598            stats.estimate_expand(Some(&EdgeType::new("KNOWS"))) as usize,
2599            2
2600        );
2601        assert_eq!(
2602            stats.estimate_expand(Some(&EdgeType::new("LIKES"))) as usize,
2603            1
2604        );
2605        assert_eq!(
2606            stats.estimate_expand(Some(&EdgeType::new("NOPE"))) as usize,
2607            0
2608        );
2609        // None means all edges
2610        assert_eq!(stats.estimate_expand(None) as usize, 3);
2611    }
2612
2613    #[test]
2614    fn test_graph_statistics_estimate_equality_selectivity() {
2615        let mut store = GraphStore::new();
2616        for i in 0..10 {
2617            let id = store.create_node("Person");
2618            store.get_node_mut(id).unwrap().set_property(
2619                "city".to_string(),
2620                PropertyValue::String(format!("City{}", i % 5)),
2621            );
2622        }
2623        let stats = store.compute_statistics();
2624        // 5 distinct cities among 10 Person nodes => selectivity = 1/5 = 0.2
2625        let sel = stats.estimate_equality_selectivity(&Label::new("Person"), "city");
2626        assert!((sel - 0.2).abs() < 0.01);
2627        // Unknown property should return default 0.1
2628        let default_sel =
2629            stats.estimate_equality_selectivity(&Label::new("Person"), "unknown_prop");
2630        assert!((default_sel - 0.1).abs() < 0.01);
2631        // Unknown label should return default 0.1
2632        let default_sel2 = stats.estimate_equality_selectivity(&Label::new("NotExist"), "city");
2633        assert!((default_sel2 - 0.1).abs() < 0.01);
2634    }
2635
2636    #[test]
2637    fn test_graph_statistics_format() {
2638        let mut store = GraphStore::new();
2639        let a = store.create_node("Person");
2640        let b = store.create_node("Company");
2641        store.create_edge(a, b, "WORKS_AT").unwrap();
2642
2643        let stats = store.compute_statistics();
2644        let formatted = stats.format();
2645        assert!(formatted.contains("Graph Statistics:"));
2646        assert!(formatted.contains("Total nodes: 2"));
2647        assert!(formatted.contains("Total edges: 1"));
2648        assert!(formatted.contains("Avg out-degree:"));
2649        assert!(formatted.contains(":Person"));
2650        assert!(formatted.contains(":Company"));
2651        assert!(formatted.contains(":WORKS_AT"));
2652    }
2653
2654    #[test]
2655    fn test_graph_statistics_property_null_fraction() {
2656        let mut store = GraphStore::new();
2657        // Create 4 Person nodes, only 2 have the "email" property
2658        for i in 0..4 {
2659            let id = store.create_node("Person");
2660            store.get_node_mut(id).unwrap().set_property(
2661                "name".to_string(),
2662                PropertyValue::String(format!("Person{}", i)),
2663            );
2664            if i < 2 {
2665                store.get_node_mut(id).unwrap().set_property(
2666                    "email".to_string(),
2667                    PropertyValue::String(format!("person{}@example.com", i)),
2668                );
2669            }
2670        }
2671        let stats = store.compute_statistics();
2672        // name should have null_fraction = 0.0 (all 4 have it)
2673        let name_stats = stats
2674            .property_stats
2675            .get(&(Label::new("Person"), "name".to_string()))
2676            .unwrap();
2677        assert_eq!(name_stats.null_fraction, 0.0);
2678        // email should have null_fraction = 0.5 (2 out of 4 have it)
2679        let email_stats = stats
2680            .property_stats
2681            .get(&(Label::new("Person"), "email".to_string()))
2682            .unwrap();
2683        assert!((email_stats.null_fraction - 0.5).abs() < 0.01);
2684    }
2685
2686    #[test]
2687    fn test_vector_search_with_data() {
2688        let store = GraphStore::new();
2689        // Create a 4-dimensional index
2690        store
2691            .create_vector_index(
2692                "Document",
2693                "embedding",
2694                4,
2695                crate::vector::DistanceMetric::Cosine,
2696            )
2697            .unwrap();
2698
2699        // Add some vectors
2700        let n1 = NodeId::new(1);
2701        let n2 = NodeId::new(2);
2702        let n3 = NodeId::new(3);
2703        let v1 = vec![1.0, 0.0, 0.0, 0.0];
2704        let v2 = vec![0.0, 1.0, 0.0, 0.0];
2705        let v3 = vec![0.9, 0.1, 0.0, 0.0]; // close to v1
2706
2707        store
2708            .vector_index
2709            .add_vector("Document", "embedding", n1, &v1)
2710            .unwrap();
2711        store
2712            .vector_index
2713            .add_vector("Document", "embedding", n2, &v2)
2714            .unwrap();
2715        store
2716            .vector_index
2717            .add_vector("Document", "embedding", n3, &v3)
2718            .unwrap();
2719
2720        // Search for vectors similar to v1
2721        let results = store
2722            .vector_search("Document", "embedding", &[1.0, 0.0, 0.0, 0.0], 2)
2723            .unwrap();
2724        assert_eq!(results.len(), 2);
2725        // n1 should be the closest (exact match)
2726        assert_eq!(results[0].0, n1);
2727    }
2728
2729    #[test]
2730    fn test_vector_search_nonexistent_index() {
2731        let store = GraphStore::new();
2732        // Search on a non-existent index should return empty results
2733        let results = store
2734            .vector_search("NoLabel", "noprop", &[1.0, 2.0], 5)
2735            .unwrap();
2736        assert!(results.is_empty());
2737    }
2738
2739    #[test]
2740    fn test_clear_thorough() {
2741        let mut store = GraphStore::new();
2742        let a = store.create_node("Person");
2743        let b = store.create_node("Company");
2744        store
2745            .set_node_property(
2746                "default",
2747                a,
2748                "name",
2749                PropertyValue::String("Alice".to_string()),
2750            )
2751            .unwrap();
2752        let eid = store.create_edge(a, b, "WORKS_AT").unwrap();
2753
2754        // Verify state before clear
2755        assert_eq!(store.node_count(), 2);
2756        assert_eq!(store.edge_count(), 1);
2757        assert_eq!(store.all_labels().len(), 2);
2758        assert_eq!(store.all_edge_types().len(), 1);
2759        assert!(store.has_node(a));
2760        assert!(store.has_edge(eid));
2761
2762        store.clear();
2763
2764        // Verify everything is cleaned up
2765        assert_eq!(store.node_count(), 0);
2766        assert_eq!(store.edge_count(), 0);
2767        assert!(store.all_labels().is_empty());
2768        assert!(store.all_edge_types().is_empty());
2769        assert!(!store.has_node(a));
2770        assert!(!store.has_edge(eid));
2771        assert!(store.get_nodes_by_label(&Label::new("Person")).is_empty());
2772        assert!(store
2773            .get_edges_by_type(&EdgeType::new("WORKS_AT"))
2774            .is_empty());
2775
2776        // After clear, creating new nodes should start from ID 1 again
2777        let new_node = store.create_node("NewLabel");
2778        assert_eq!(new_node, NodeId::new(1));
2779    }
2780
2781    #[test]
2782    fn test_delete_edge_verifies_edge_type_index_cleanup() {
2783        let mut store = GraphStore::new();
2784        let a = store.create_node("A");
2785        let b = store.create_node("B");
2786        let c = store.create_node("C");
2787
2788        let e1 = store.create_edge(a, b, "KNOWS").unwrap();
2789        let e2 = store.create_edge(a, c, "KNOWS").unwrap();
2790        assert_eq!(store.get_edges_by_type(&EdgeType::new("KNOWS")).len(), 2);
2791
2792        // Delete one edge
2793        store.delete_edge(e1).unwrap();
2794        assert_eq!(store.get_edges_by_type(&EdgeType::new("KNOWS")).len(), 1);
2795
2796        // Delete the other
2797        store.delete_edge(e2).unwrap();
2798        assert_eq!(store.get_edges_by_type(&EdgeType::new("KNOWS")).len(), 0);
2799    }
2800
2801    #[test]
2802    fn test_delete_edge_nonexistent() {
2803        let mut store = GraphStore::new();
2804        let result = store.delete_edge(EdgeId::new(999));
2805        assert_eq!(result, Err(GraphError::EdgeNotFound(EdgeId::new(999))));
2806    }
2807
2808    #[test]
2809    fn test_delete_node_nonexistent() {
2810        let mut store = GraphStore::new();
2811        let result = store.delete_node("default", NodeId::new(999));
2812        assert_eq!(result, Err(GraphError::NodeNotFound(NodeId::new(999))));
2813    }
2814
2815    #[test]
2816    fn test_delete_node_removes_from_label_index() {
2817        let mut store = GraphStore::new();
2818        let a = store.create_node("Person");
2819        let _b = store.create_node("Person");
2820        assert_eq!(store.get_nodes_by_label(&Label::new("Person")).len(), 2);
2821
2822        store.delete_node("default", a).unwrap();
2823        assert_eq!(store.get_nodes_by_label(&Label::new("Person")).len(), 1);
2824    }
2825
2826    #[test]
2827    fn test_delete_node_cascades_edges() {
2828        let mut store = GraphStore::new();
2829        let a = store.create_node("A");
2830        let b = store.create_node("B");
2831        let c = store.create_node("C");
2832        store.create_edge(a, b, "E1").unwrap();
2833        store.create_edge(c, a, "E2").unwrap();
2834
2835        assert_eq!(store.edge_count(), 2);
2836        store.delete_node("default", a).unwrap();
2837        assert_eq!(store.edge_count(), 0);
2838        // b and c should still exist
2839        assert!(store.has_node(b));
2840        assert!(store.has_node(c));
2841    }
2842
2843    #[test]
2844    fn test_edge_id_reuse() {
2845        let mut store = GraphStore::new();
2846        let a = store.create_node("A");
2847        let b = store.create_node("B");
2848        let c = store.create_node("C");
2849
2850        let e1 = store.create_edge(a, b, "X").unwrap();
2851        store.delete_edge(e1).unwrap();
2852
2853        // Next edge should reuse e1's ID
2854        let e2 = store.create_edge(a, c, "Y").unwrap();
2855        assert_eq!(e2, e1);
2856    }
2857
2858    #[test]
2859    fn test_insert_recovered_node() {
2860        let mut store = GraphStore::new();
2861        let node = Node::new(NodeId::new(10), Label::new("Recovered"));
2862
2863        store.insert_recovered_node(node);
2864        assert!(store.has_node(NodeId::new(10)));
2865        assert_eq!(store.get_nodes_by_label(&Label::new("Recovered")).len(), 1);
2866
2867        // Next node created should have ID > 10
2868        let new_id = store.create_node("New");
2869        assert!(new_id.as_u64() > 10);
2870    }
2871
2872    #[test]
2873    fn test_insert_recovered_edge() {
2874        let mut store = GraphStore::new();
2875        let a = store.create_node("A");
2876        let b = store.create_node("B");
2877
2878        let edge = Edge::new(EdgeId::new(50), a, b, EdgeType::new("RECOVERED"));
2879        store.insert_recovered_edge(edge).unwrap();
2880
2881        assert!(store.has_edge(EdgeId::new(50)));
2882        assert_eq!(store.get_outgoing_edges(a).len(), 1);
2883        assert_eq!(store.get_incoming_edges(b).len(), 1);
2884        assert_eq!(
2885            store.get_edges_by_type(&EdgeType::new("RECOVERED")).len(),
2886            1
2887        );
2888
2889        // Next edge should have ID > 50
2890        let new_eid = store.create_edge(a, b, "NEW").unwrap();
2891        assert!(new_eid.as_u64() > 50);
2892    }
2893
2894    #[test]
2895    fn test_insert_recovered_edge_invalid_source() {
2896        let mut store = GraphStore::new();
2897        let b = store.create_node("B");
2898        let edge = Edge::new(EdgeId::new(1), NodeId::new(999), b, EdgeType::new("E"));
2899        let result = store.insert_recovered_edge(edge);
2900        assert_eq!(result, Err(GraphError::InvalidEdgeSource(NodeId::new(999))));
2901    }
2902
2903    #[test]
2904    fn test_insert_recovered_edge_invalid_target() {
2905        let mut store = GraphStore::new();
2906        let a = store.create_node("A");
2907        let edge = Edge::new(EdgeId::new(1), a, NodeId::new(999), EdgeType::new("E"));
2908        let result = store.insert_recovered_edge(edge);
2909        assert_eq!(result, Err(GraphError::InvalidEdgeTarget(NodeId::new(999))));
2910    }
2911
2912    #[test]
2913    fn test_default_impl() {
2914        let store = GraphStore::default();
2915        assert_eq!(store.node_count(), 0);
2916        assert_eq!(store.edge_count(), 0);
2917    }
2918
2919    #[test]
2920    fn test_mvcc_cow_versioning() {
2921        let mut store = GraphStore::new();
2922        let id = store.create_node("Person");
2923        store
2924            .set_node_property(
2925                "default",
2926                id,
2927                "name",
2928                PropertyValue::String("Alice".to_string()),
2929            )
2930            .unwrap();
2931
2932        // Bump version to trigger COW
2933        store.current_version = 2;
2934        store
2935            .set_node_property(
2936                "default",
2937                id,
2938                "name",
2939                PropertyValue::String("Bob".to_string()),
2940            )
2941            .unwrap();
2942
2943        // Latest version should be Bob
2944        let latest = store.get_node(id).unwrap();
2945        assert_eq!(
2946            latest.get_property("name"),
2947            Some(&PropertyValue::String("Bob".to_string()))
2948        );
2949        assert_eq!(latest.version, 2);
2950
2951        // Version 1 should still be Alice
2952        let v1 = store.get_node_at_version(id, 1).unwrap();
2953        assert_eq!(
2954            v1.get_property("name"),
2955            Some(&PropertyValue::String("Alice".to_string()))
2956        );
2957        assert_eq!(v1.version, 1);
2958    }
2959
2960    #[test]
2961    fn test_get_outgoing_edge_targets_detail() {
2962        let mut store = GraphStore::new();
2963        let a = store.create_node("A");
2964        let b = store.create_node("B");
2965        let c = store.create_node("C");
2966        let e1 = store.create_edge(a, b, "KNOWS").unwrap();
2967        let e2 = store.create_edge(a, c, "LIKES").unwrap();
2968
2969        let targets = store.get_outgoing_edge_targets(a);
2970        assert_eq!(targets.len(), 2);
2971
2972        // Check that tuples contain correct (edge_id, source, target, edge_type)
2973        for (eid, src, tgt, etype) in &targets {
2974            assert_eq!(*src, a);
2975            if *eid == e1 {
2976                assert_eq!(*tgt, b);
2977                assert_eq!(etype.as_str(), "KNOWS");
2978            } else if *eid == e2 {
2979                assert_eq!(*tgt, c);
2980                assert_eq!(etype.as_str(), "LIKES");
2981            } else {
2982                panic!("Unexpected edge ID");
2983            }
2984        }
2985
2986        // Non-existent node returns empty
2987        let empty = store.get_outgoing_edge_targets(NodeId::new(9999));
2988        assert!(empty.is_empty());
2989    }
2990
2991    #[test]
2992    fn test_get_incoming_edge_sources_detail() {
2993        let mut store = GraphStore::new();
2994        let a = store.create_node("A");
2995        let b = store.create_node("B");
2996        let c = store.create_node("C");
2997        let e1 = store.create_edge(a, c, "KNOWS").unwrap();
2998        let e2 = store.create_edge(b, c, "LIKES").unwrap();
2999
3000        let sources = store.get_incoming_edge_sources(c);
3001        assert_eq!(sources.len(), 2);
3002
3003        for (eid, src, tgt, etype) in &sources {
3004            assert_eq!(*tgt, c);
3005            if *eid == e1 {
3006                assert_eq!(*src, a);
3007                assert_eq!(etype.as_str(), "KNOWS");
3008            } else if *eid == e2 {
3009                assert_eq!(*src, b);
3010                assert_eq!(etype.as_str(), "LIKES");
3011            } else {
3012                panic!("Unexpected edge ID");
3013            }
3014        }
3015
3016        // Non-existent node returns empty
3017        let empty = store.get_incoming_edge_sources(NodeId::new(9999));
3018        assert!(empty.is_empty());
3019    }
3020
3021    #[test]
3022    fn test_all_labels_empty() {
3023        let store = GraphStore::new();
3024        assert!(store.all_labels().is_empty());
3025    }
3026
3027    #[test]
3028    fn test_all_edge_types_empty() {
3029        let store = GraphStore::new();
3030        assert!(store.all_edge_types().is_empty());
3031    }
3032
3033    #[test]
3034    fn test_columnar_storage_integration() {
3035        let mut store = GraphStore::new();
3036        let id = store
3037            .create_node_with_properties("default", vec![Label::new("Person")], {
3038                let mut props = PropertyMap::new();
3039                props.insert(
3040                    "name".to_string(),
3041                    PropertyValue::String("Alice".to_string()),
3042                );
3043                props.insert("age".to_string(), PropertyValue::Integer(30));
3044                props
3045            })
3046            .unwrap();
3047
3048        // Verify columnar storage has the values
3049        let idx = id.as_u64() as usize;
3050        let name_col = store.node_columns.get_property(idx, "name");
3051        assert_eq!(name_col, PropertyValue::String("Alice".to_string()));
3052        let age_col = store.node_columns.get_property(idx, "age");
3053        assert_eq!(age_col, PropertyValue::Integer(30));
3054    }
3055
3056    #[test]
3057    fn test_edge_columnar_storage_integration() {
3058        let mut store = GraphStore::new();
3059        let a = store.create_node("A");
3060        let b = store.create_node("B");
3061        let mut props = std::collections::HashMap::new();
3062        props.insert("weight".to_string(), PropertyValue::Float(0.75));
3063        let eid = store
3064            .create_edge_with_properties(a, b, "WEIGHTED", props)
3065            .unwrap();
3066
3067        let idx = eid.as_u64() as usize;
3068        let weight_col = store.edge_columns.get_property(idx, "weight");
3069        assert_eq!(weight_col, PropertyValue::Float(0.75));
3070    }
3071
3072    #[test]
3073    fn test_set_node_property_updates_columnar_storage() {
3074        let mut store = GraphStore::new();
3075        let id = store.create_node("Person");
3076        store
3077            .set_node_property("default", id, "score", PropertyValue::Float(1.0))
3078            .unwrap();
3079
3080        let idx = id.as_u64() as usize;
3081        assert_eq!(
3082            store.node_columns.get_property(idx, "score"),
3083            PropertyValue::Float(1.0)
3084        );
3085
3086        // Update
3087        store
3088            .set_node_property("default", id, "score", PropertyValue::Float(2.5))
3089            .unwrap();
3090        assert_eq!(
3091            store.node_columns.get_property(idx, "score"),
3092            PropertyValue::Float(2.5)
3093        );
3094    }
3095
3096    #[test]
3097    fn test_get_nodes_by_label_after_deletions() {
3098        let mut store = GraphStore::new();
3099        let a = store.create_node("Person");
3100        let b_id = store.create_node("Person");
3101        let c = store.create_node("Person");
3102
3103        store.delete_node("default", b_id).unwrap();
3104        let persons = store.get_nodes_by_label(&Label::new("Person"));
3105        assert_eq!(persons.len(), 2);
3106        let ids: Vec<NodeId> = persons.iter().map(|n| n.id).collect();
3107        assert!(ids.contains(&a));
3108        assert!(ids.contains(&c));
3109        assert!(!ids.contains(&b_id));
3110    }
3111
3112    #[test]
3113    fn test_get_edges_by_type_after_deletion() {
3114        let mut store = GraphStore::new();
3115        let a = store.create_node("A");
3116        let b = store.create_node("B");
3117        let c = store.create_node("C");
3118        let e1 = store.create_edge(a, b, "KNOWS").unwrap();
3119        let e2 = store.create_edge(b, c, "KNOWS").unwrap();
3120
3121        store.delete_edge(e1).unwrap();
3122        let knows_edges = store.get_edges_by_type(&EdgeType::new("KNOWS"));
3123        assert_eq!(knows_edges.len(), 1);
3124        assert_eq!(knows_edges[0].id, e2);
3125    }
3126
3127    #[test]
3128    fn test_all_nodes_after_operations() {
3129        let mut store = GraphStore::new();
3130        let a = store.create_node("A");
3131        store.create_node("B");
3132        store.create_node("C");
3133        store.delete_node("default", a).unwrap();
3134
3135        let all = store.all_nodes();
3136        assert_eq!(all.len(), 2);
3137    }
3138
3139    #[test]
3140    fn test_compute_statistics_with_large_sample() {
3141        let mut store = GraphStore::new();
3142        // Create more than 1000 nodes to test sample limiting
3143        for i in 0..1100 {
3144            let id = store.create_node("BigLabel");
3145            store
3146                .get_node_mut(id)
3147                .unwrap()
3148                .set_property("idx".to_string(), PropertyValue::Integer(i));
3149        }
3150        let stats = store.compute_statistics();
3151        assert_eq!(stats.total_nodes, 1100);
3152        // Property stats should exist (sampled from first 1000)
3153        let idx_stats = stats
3154            .property_stats
3155            .get(&(Label::new("BigLabel"), "idx".to_string()));
3156        assert!(idx_stats.is_some());
3157        let ps = idx_stats.unwrap();
3158        // All sampled nodes have the property => null_fraction = 0
3159        assert_eq!(ps.null_fraction, 0.0);
3160        // distinct_count is from the sample (first 1000 nodes), each has unique value
3161        assert_eq!(ps.distinct_count, 1000);
3162    }
3163
3164    #[test]
3165    fn test_add_label_then_label_count() {
3166        let mut store = GraphStore::new();
3167        let id = store.create_node("Person");
3168        store.add_label_to_node("default", id, "Employee").unwrap();
3169
3170        assert_eq!(store.label_node_count(&Label::new("Person")), 1);
3171        assert_eq!(store.label_node_count(&Label::new("Employee")), 1);
3172    }
3173
3174    #[test]
3175    fn test_insert_recovered_node_with_multiple_labels() {
3176        let mut store = GraphStore::new();
3177        let mut node = Node::new(NodeId::new(5), Label::new("Person"));
3178        node.add_label(Label::new("Employee"));
3179
3180        store.insert_recovered_node(node);
3181        assert_eq!(store.get_nodes_by_label(&Label::new("Person")).len(), 1);
3182        assert_eq!(store.get_nodes_by_label(&Label::new("Employee")).len(), 1);
3183    }
3184
3185    #[test]
3186    fn test_graph_statistics_avg_out_degree() {
3187        let mut store = GraphStore::new();
3188        let a = store.create_node("A");
3189        let b = store.create_node("B");
3190        let c = store.create_node("C");
3191        let d = store.create_node("D");
3192        // a -> b, a -> c, a -> d (3 edges, 4 nodes)
3193        store.create_edge(a, b, "E").unwrap();
3194        store.create_edge(a, c, "E").unwrap();
3195        store.create_edge(a, d, "E").unwrap();
3196
3197        let stats = store.compute_statistics();
3198        assert!((stats.avg_out_degree - 0.75).abs() < 0.01); // 3/4 = 0.75
3199    }
3200
3201    #[test]
3202    fn test_self_loop_edge() {
3203        let mut store = GraphStore::new();
3204        let a = store.create_node("A");
3205        let eid = store.create_edge(a, a, "SELF").unwrap();
3206
3207        assert_eq!(store.get_outgoing_edges(a).len(), 1);
3208        assert_eq!(store.get_incoming_edges(a).len(), 1);
3209
3210        store.delete_edge(eid).unwrap();
3211        assert_eq!(store.get_outgoing_edges(a).len(), 0);
3212        assert_eq!(store.get_incoming_edges(a).len(), 0);
3213    }
3214
3215    #[test]
3216    fn test_get_outgoing_edges_nonexistent_node() {
3217        let store = GraphStore::new();
3218        let edges = store.get_outgoing_edges(NodeId::new(999));
3219        assert!(edges.is_empty());
3220    }
3221
3222    #[test]
3223    fn test_get_incoming_edges_nonexistent_node() {
3224        let store = GraphStore::new();
3225        let edges = store.get_incoming_edges(NodeId::new(999));
3226        assert!(edges.is_empty());
3227    }
3228
3229    #[test]
3230    fn test_get_nodes_by_label_nonexistent() {
3231        let store = GraphStore::new();
3232        let nodes = store.get_nodes_by_label(&Label::new("NoSuch"));
3233        assert!(nodes.is_empty());
3234    }
3235
3236    #[test]
3237    fn test_get_edges_by_type_nonexistent() {
3238        let store = GraphStore::new();
3239        let edges = store.get_edges_by_type(&EdgeType::new("NoSuch"));
3240        assert!(edges.is_empty());
3241    }
3242
3243    // ---- edge_between / edges_between tests (TDD) ----
3244
3245    #[test]
3246    fn test_edge_between_exists() {
3247        let mut store = GraphStore::new();
3248        let n1 = store.create_node("Person");
3249        let n2 = store.create_node("Person");
3250        let eid = store.create_edge(n1, n2, "KNOWS").unwrap();
3251
3252        assert_eq!(
3253            store.edge_between(n1, n2, Some(&EdgeType::new("KNOWS"))),
3254            Some(eid)
3255        );
3256    }
3257
3258    #[test]
3259    fn test_edge_between_not_exists() {
3260        let mut store = GraphStore::new();
3261        let n1 = store.create_node("Person");
3262        let n2 = store.create_node("Person");
3263
3264        assert_eq!(
3265            store.edge_between(n1, n2, Some(&EdgeType::new("KNOWS"))),
3266            None
3267        );
3268    }
3269
3270    #[test]
3271    fn test_edge_between_wrong_type() {
3272        let mut store = GraphStore::new();
3273        let n1 = store.create_node("Person");
3274        let n2 = store.create_node("Person");
3275        store.create_edge(n1, n2, "KNOWS").unwrap();
3276
3277        assert_eq!(
3278            store.edge_between(n1, n2, Some(&EdgeType::new("FOLLOWS"))),
3279            None
3280        );
3281    }
3282
3283    #[test]
3284    fn test_edge_between_any_type() {
3285        let mut store = GraphStore::new();
3286        let n1 = store.create_node("Person");
3287        let n2 = store.create_node("Person");
3288        let eid = store.create_edge(n1, n2, "KNOWS").unwrap();
3289
3290        // None edge_type means any type
3291        assert_eq!(store.edge_between(n1, n2, None), Some(eid));
3292    }
3293
3294    #[test]
3295    fn test_edge_between_reverse_direction() {
3296        let mut store = GraphStore::new();
3297        let n1 = store.create_node("Person");
3298        let n2 = store.create_node("Person");
3299        store.create_edge(n1, n2, "KNOWS").unwrap();
3300
3301        // Reverse direction should NOT find the edge
3302        assert_eq!(
3303            store.edge_between(n2, n1, Some(&EdgeType::new("KNOWS"))),
3304            None
3305        );
3306    }
3307
3308    #[test]
3309    fn test_edges_between_multi() {
3310        let mut store = GraphStore::new();
3311        let n1 = store.create_node("Person");
3312        let n2 = store.create_node("Person");
3313        let eid1 = store.create_edge(n1, n2, "KNOWS").unwrap();
3314        let eid2 = store.create_edge(n1, n2, "FOLLOWS").unwrap();
3315        let eid3 = store.create_edge(n1, n2, "KNOWS").unwrap();
3316
3317        // All edges between n1 and n2 (any type)
3318        let all = store.edges_between(n1, n2, None);
3319        assert_eq!(all.len(), 3);
3320
3321        // Only KNOWS edges
3322        let knows = store.edges_between(n1, n2, Some(&EdgeType::new("KNOWS")));
3323        assert_eq!(knows.len(), 2);
3324        assert!(knows.contains(&eid1));
3325        assert!(knows.contains(&eid3));
3326
3327        // Only FOLLOWS edges
3328        let follows = store.edges_between(n1, n2, Some(&EdgeType::new("FOLLOWS")));
3329        assert_eq!(follows.len(), 1);
3330        assert!(follows.contains(&eid2));
3331    }
3332
3333    // ---- Sorted adjacency list tests (Phase 1B TDD) ----
3334
3335    #[test]
3336    fn test_sorted_adjacency_insert_order() {
3337        let mut store = GraphStore::new();
3338        let n1 = store.create_node("Person");
3339        let n3 = store.create_node("Person");
3340        let n2 = store.create_node("Person");
3341
3342        // Insert edges to n3 first, then n2 — adjacency should still be sorted by target NodeId
3343        store.create_edge(n1, n3, "KNOWS").unwrap();
3344        store.create_edge(n1, n2, "KNOWS").unwrap();
3345
3346        // Outgoing from n1 should be sorted by target: n2 before n3
3347        let out = &store.outgoing[n1.as_u64() as usize];
3348        assert_eq!(out.len(), 2);
3349        assert!(
3350            out[0].0 <= out[1].0,
3351            "outgoing adjacency should be sorted by target NodeId"
3352        );
3353    }
3354
3355    #[test]
3356    fn test_sorted_adjacency_delete() {
3357        let mut store = GraphStore::new();
3358        let n1 = store.create_node("Person");
3359        let n2 = store.create_node("Person");
3360        let n3 = store.create_node("Person");
3361
3362        let e1 = store.create_edge(n1, n2, "KNOWS").unwrap();
3363        let _e2 = store.create_edge(n1, n3, "KNOWS").unwrap();
3364
3365        store.delete_edge(e1).unwrap();
3366
3367        let out = &store.outgoing[n1.as_u64() as usize];
3368        assert_eq!(out.len(), 1);
3369        assert_eq!(out[0].0, n3); // only n3 remains
3370    }
3371
3372    #[test]
3373    fn test_sorted_adjacency_multiple_edges_same_target() {
3374        let mut store = GraphStore::new();
3375        let n1 = store.create_node("Person");
3376        let n2 = store.create_node("Person");
3377
3378        let _e1 = store.create_edge(n1, n2, "KNOWS").unwrap();
3379        let _e2 = store.create_edge(n1, n2, "FOLLOWS").unwrap();
3380
3381        // Both edges should be to the same target
3382        let out = &store.outgoing[n1.as_u64() as usize];
3383        assert_eq!(out.len(), 2);
3384        assert_eq!(out[0].0, n2);
3385        assert_eq!(out[1].0, n2);
3386
3387        // edge_between with binary search should find the right one
3388        assert!(store
3389            .edge_between(n1, n2, Some(&EdgeType::new("KNOWS")))
3390            .is_some());
3391        assert!(store
3392            .edge_between(n1, n2, Some(&EdgeType::new("FOLLOWS")))
3393            .is_some());
3394    }
3395
3396    #[test]
3397    fn test_edge_between_binary_search_high_degree() {
3398        let mut store = GraphStore::new();
3399        let hub = store.create_node("Hub");
3400
3401        // Create 100 target nodes and edges from hub
3402        let mut targets = Vec::new();
3403        for _ in 0..100 {
3404            let t = store.create_node("Target");
3405            store.create_edge(hub, t, "LINKS").unwrap();
3406            targets.push(t);
3407        }
3408
3409        // Binary search should find any of them
3410        for &t in &targets {
3411            assert!(
3412                store
3413                    .edge_between(hub, t, Some(&EdgeType::new("LINKS")))
3414                    .is_some(),
3415                "edge_between should find edge to target {:?}",
3416                t
3417            );
3418        }
3419
3420        // Non-existent target
3421        let fake = NodeId::new(9999);
3422        assert!(store
3423            .edge_between(hub, fake, Some(&EdgeType::new("LINKS")))
3424            .is_none());
3425    }
3426}