1use std::borrow::Cow;
4use std::ops::RangeBounds;
5use std::sync::Arc;
6
7use imbl::HashMap;
8use roaring::RoaringBitmap;
9use rustc_hash::FxHashMap;
10use serde::{Deserialize, Serialize};
11use smallvec::SmallVec;
12
13use selene_core::{DbString, EdgeId, GraphId, LabelSet, NodeId, PropertyMap, Value};
14
15use crate::adjacency::AdjacencyEntry;
16use crate::composite_typed_index::CompositeTypedIndex;
17use crate::graph_types::GraphTypeDef;
18use crate::id_map::{EngineIdMap, engine_id_map};
19use crate::store::{EdgeStore, NodeStore, RowIndex};
20use crate::text_index::TextIndex;
21use crate::typed_index::{TypedIndex, TypedIndexKind};
22use crate::vector_index::VectorIndex;
23
24mod index_entries;
25
26pub use index_entries::{
27 CompositePropertyIndexEntry, CompositePropertyIndexEntryRow, PropertyIndexEntry,
28 TextIndexEntry, TextIndexEntryRow, VectorIndexEntry, VectorIndexEntryRow,
29};
30
31#[derive(
33 Clone,
34 Debug,
35 Deserialize,
36 PartialEq,
37 rkyv::Archive,
38 rkyv::Deserialize,
39 rkyv::Serialize,
40 Serialize,
41)]
42pub struct GraphMeta {
43 pub graph_id: GraphId,
45 pub generation: u64,
47 pub next_node_id: u64,
49 pub next_edge_id: u64,
51 pub bound_type: Option<Arc<GraphTypeDef>>,
53}
54
55#[derive(Clone, Debug)]
57pub struct SeleneGraph {
58 pub meta: GraphMeta,
60 pub node_store: NodeStore,
62 pub edge_store: EdgeStore,
64 pub adjacency_out: EngineIdMap<NodeId, AdjacencyEntry>,
66 pub adjacency_in: EngineIdMap<NodeId, AdjacencyEntry>,
68 pub idx_label: HashMap<DbString, RoaringBitmap>,
70 pub idx_edge_label: HashMap<DbString, RoaringBitmap>,
72 pub property_index: FxHashMap<(DbString, DbString), PropertyIndexEntry>,
74 pub edge_property_index: FxHashMap<(DbString, DbString), PropertyIndexEntry>,
76 pub composite_property_index:
78 FxHashMap<(DbString, SmallVec<[DbString; 4]>), CompositePropertyIndexEntry>,
79 pub vector_index: FxHashMap<(DbString, DbString), VectorIndexEntry>,
81 pub text_index: FxHashMap<(DbString, DbString), TextIndexEntry>,
83 pub node_id_to_row: EngineIdMap<NodeId, RowIndex>,
88 pub edge_id_to_row: EngineIdMap<EdgeId, RowIndex>,
90}
91
92impl SeleneGraph {
93 #[must_use]
95 pub fn new(graph_id: GraphId) -> Self {
96 Self {
97 meta: GraphMeta {
98 graph_id,
99 generation: 0,
100 next_node_id: 1,
101 next_edge_id: 1,
102 bound_type: None,
103 },
104 node_store: NodeStore::new(),
105 edge_store: EdgeStore::new(),
106 adjacency_out: engine_id_map(),
107 adjacency_in: engine_id_map(),
108 idx_label: HashMap::new(),
109 idx_edge_label: HashMap::new(),
110 property_index: FxHashMap::default(),
111 edge_property_index: FxHashMap::default(),
112 composite_property_index: FxHashMap::default(),
113 vector_index: FxHashMap::default(),
114 text_index: FxHashMap::default(),
115 node_id_to_row: engine_id_map(),
116 edge_id_to_row: engine_id_map(),
117 }
118 }
119
120 #[must_use]
122 pub const fn graph_id(&self) -> GraphId {
123 self.meta.graph_id
124 }
125
126 #[must_use]
128 pub fn node_count(&self) -> usize {
129 self.node_store.alive.len() as usize
130 }
131
132 #[must_use]
140 pub fn live_nodes(&self) -> &RoaringBitmap {
141 &self.node_store.alive
144 }
145
146 #[must_use]
148 pub fn edge_count(&self) -> usize {
149 self.edge_store.alive.len() as usize
150 }
151
152 #[must_use]
157 pub fn compaction_stats(&self) -> crate::compaction::CompactionStats {
158 crate::compaction::CompactionStats::from_graph(self)
159 }
160
161 #[must_use]
171 pub fn live_edges(&self) -> &RoaringBitmap {
172 &self.edge_store.alive
174 }
175
176 #[must_use]
184 pub fn row_for_node_id(&self, id: NodeId) -> Option<RowIndex> {
185 self.node_id_to_row.get(&id).copied()
186 }
187
188 #[must_use]
191 pub fn row_for_edge_id(&self, id: EdgeId) -> Option<RowIndex> {
192 self.edge_id_to_row.get(&id).copied()
193 }
194
195 #[must_use]
201 pub fn node_id_for_row(&self, row: RowIndex) -> Option<NodeId> {
202 self.node_store
203 .row_to_id
204 .get(row.get() as usize)
205 .copied()
206 .filter(|id| *id != NodeId::TOMBSTONE)
207 }
208
209 #[must_use]
212 pub fn edge_id_for_row(&self, row: RowIndex) -> Option<EdgeId> {
213 self.edge_store
214 .row_to_id
215 .get(row.get() as usize)
216 .copied()
217 .filter(|id| *id != EdgeId::TOMBSTONE)
218 }
219
220 #[must_use]
222 pub fn is_node_alive(&self, id: NodeId) -> bool {
223 self.live_node_row(id).is_some()
224 }
225
226 #[must_use]
228 pub fn is_edge_alive(&self, id: EdgeId) -> bool {
229 self.live_edge_row(id).is_some()
230 }
231
232 #[must_use]
234 pub fn node_labels(&self, id: NodeId) -> Option<&LabelSet> {
235 self.live_node_row(id)
236 .and_then(|row| self.node_store.labels.get(row))
237 }
238
239 #[must_use]
241 pub fn node_properties(&self, id: NodeId) -> Option<&PropertyMap> {
242 self.live_node_row(id)
243 .and_then(|row| self.node_store.properties.get(row))
244 }
245
246 #[must_use]
248 pub fn edge_label(&self, id: EdgeId) -> Option<&DbString> {
249 self.live_edge_row(id)
250 .and_then(|row| self.edge_store.label.get(row))
251 }
252
253 #[must_use]
255 pub fn edge_endpoints(&self, id: EdgeId) -> Option<(NodeId, NodeId)> {
256 self.live_edge_row(id).and_then(|row| {
257 Some((
258 *self.edge_store.source.get(row)?,
259 *self.edge_store.target.get(row)?,
260 ))
261 })
262 }
263
264 #[must_use]
266 pub fn edge_properties(&self, id: EdgeId) -> Option<&PropertyMap> {
267 self.live_edge_row(id)
268 .and_then(|row| self.edge_store.properties.get(row))
269 }
270
271 #[must_use]
273 pub fn outgoing_edges(&self, source: NodeId) -> Option<&AdjacencyEntry> {
274 self.adjacency_out.get(&source)
275 }
276
277 #[must_use]
279 pub fn incoming_edges(&self, target: NodeId) -> Option<&AdjacencyEntry> {
280 self.adjacency_in.get(&target)
281 }
282
283 #[must_use]
285 pub fn node_has_incident_edges(&self, id: NodeId) -> bool {
286 self.outgoing_edges(id)
287 .is_some_and(|entry| !entry.is_empty())
288 || self
289 .incoming_edges(id)
290 .is_some_and(|entry| !entry.is_empty())
291 }
292
293 #[must_use]
295 pub fn nodes_with_label(&self, label: &DbString) -> Option<&RoaringBitmap> {
296 self.idx_label.get(label)
297 }
298
299 #[must_use]
301 pub fn edges_with_label(&self, label: &DbString) -> Option<&RoaringBitmap> {
302 self.idx_edge_label.get(label)
303 }
304
305 #[must_use]
307 pub fn label_count(&self) -> usize {
308 self.idx_label.len()
309 }
310
311 #[must_use]
313 pub fn edge_label_count(&self) -> usize {
314 self.idx_edge_label.len()
315 }
316
317 #[must_use]
319 pub fn property_index_for(
320 &self,
321 label: &DbString,
322 property: &DbString,
323 ) -> Option<Arc<TypedIndex>> {
324 self.property_index
325 .get(&(label.clone(), property.clone()))
326 .map(|entry| Arc::clone(&entry.index))
327 }
328
329 #[must_use]
331 pub fn edge_property_index_for(
332 &self,
333 label: &DbString,
334 property: &DbString,
335 ) -> Option<Arc<TypedIndex>> {
336 self.edge_property_index
337 .get(&(label.clone(), property.clone()))
338 .map(|entry| Arc::clone(&entry.index))
339 }
340
341 #[must_use]
343 pub fn composite_property_index_for(
344 &self,
345 label: &DbString,
346 properties: &[DbString],
347 ) -> Option<Arc<CompositeTypedIndex>> {
348 self.composite_property_index_entry_for(label, properties)
349 .map(|entry| Arc::clone(&entry.index))
350 }
351
352 #[must_use]
354 pub fn composite_property_index_entry_for(
355 &self,
356 label: &DbString,
357 properties: &[DbString],
358 ) -> Option<&CompositePropertyIndexEntry> {
359 let key = composite_property_key(properties);
360 self.composite_property_index.get(&(label.clone(), key))
361 }
362
363 #[must_use]
365 pub fn vector_index_for(
366 &self,
367 label: &DbString,
368 property: &DbString,
369 ) -> Option<Arc<VectorIndex>> {
370 self.vector_index
371 .get(&(label.clone(), property.clone()))
372 .map(|entry| Arc::clone(&entry.index))
373 }
374
375 #[must_use]
377 pub fn text_index_for(&self, label: &DbString, property: &DbString) -> Option<Arc<TextIndex>> {
378 self.text_index
379 .get(&(label.clone(), property.clone()))
380 .map(|entry| Arc::clone(&entry.index))
381 }
382
383 #[must_use]
385 pub fn property_index_count(&self) -> usize {
386 self.property_index.len()
387 }
388
389 #[must_use]
391 pub fn edge_property_index_count(&self) -> usize {
392 self.edge_property_index.len()
393 }
394
395 #[must_use]
397 pub fn composite_property_index_count(&self) -> usize {
398 self.composite_property_index.len()
399 }
400
401 #[must_use]
403 pub fn vector_index_count(&self) -> usize {
404 self.vector_index.len()
405 }
406
407 #[must_use]
409 pub fn text_index_count(&self) -> usize {
410 self.text_index.len()
411 }
412
413 pub fn iter_property_indexes(
419 &self,
420 ) -> impl Iterator<Item = (DbString, DbString, TypedIndexKind)> + '_ {
421 self.property_index
422 .iter()
423 .map(|((label, property), entry)| (label.clone(), property.clone(), entry.kind()))
424 }
425
426 pub fn iter_property_index_entries(
428 &self,
429 ) -> impl Iterator<Item = (DbString, DbString, TypedIndexKind, Option<DbString>)> + '_ {
430 self.property_index
431 .iter()
432 .map(|((label, property), entry)| {
433 (
434 label.clone(),
435 property.clone(),
436 entry.kind(),
437 entry.name.clone(),
438 )
439 })
440 }
441
442 pub fn iter_composite_property_index_entries(
444 &self,
445 ) -> impl Iterator<Item = CompositePropertyIndexEntryRow> + '_ {
446 self.composite_property_index
447 .iter()
448 .map(|((label, _), entry)| {
449 (
450 label.clone(),
451 entry.declared_properties.clone(),
452 entry.kinds(),
453 entry.name.clone(),
454 )
455 })
456 }
457
458 pub fn iter_vector_index_entries(&self) -> impl Iterator<Item = VectorIndexEntryRow> + '_ {
460 self.vector_index.iter().map(|((label, property), entry)| {
461 (
462 label.clone(),
463 property.clone(),
464 entry.kind(),
465 entry.dimension(),
466 entry.hnsw_config(),
467 entry.ivf_config(),
468 entry.name.clone(),
469 )
470 })
471 }
472
473 pub fn iter_text_index_entries(&self) -> impl Iterator<Item = TextIndexEntryRow> + '_ {
475 self.text_index.iter().map(|((label, property), entry)| {
476 (
477 label.clone(),
478 property.clone(),
479 entry.stats(),
480 entry.memory_usage(),
481 entry.name.clone(),
482 )
483 })
484 }
485
486 #[must_use]
494 pub fn nodes_with_property_eq(
495 &self,
496 label: &DbString,
497 property: &DbString,
498 value: &Value,
499 ) -> Option<Cow<'_, RoaringBitmap>> {
500 self.property_index
501 .get(&(label.clone(), property.clone()))
502 .and_then(|entry| entry.index.lookup_eq(value))
503 }
504
505 #[must_use]
511 pub fn nodes_with_property_any(
512 &self,
513 label: &DbString,
514 property: &DbString,
515 values: &[Value],
516 ) -> Option<RoaringBitmap> {
517 let entry = self
518 .property_index
519 .get(&(label.clone(), property.clone()))?;
520 let mut rows = RoaringBitmap::new();
521 for value in values {
522 rows |= entry.index.lookup_eq(value)?.as_ref();
523 }
524 Some(rows)
525 }
526
527 #[must_use]
533 pub fn edges_with_property_eq(
534 &self,
535 label: &DbString,
536 property: &DbString,
537 value: &Value,
538 ) -> Option<Cow<'_, RoaringBitmap>> {
539 self.edge_property_index
540 .get(&(label.clone(), property.clone()))
541 .and_then(|entry| entry.index.lookup_eq(value))
542 }
543
544 #[must_use]
551 pub fn edges_with_property_any(
552 &self,
553 label: &DbString,
554 property: &DbString,
555 values: &[Value],
556 ) -> Option<RoaringBitmap> {
557 let entry = self
558 .edge_property_index
559 .get(&(label.clone(), property.clone()))?;
560 let mut rows = RoaringBitmap::new();
561 for value in values {
562 rows |= entry.index.lookup_eq(value)?.as_ref();
563 }
564 Some(rows)
565 }
566
567 #[must_use]
573 pub fn nodes_with_property_range<R>(
574 &self,
575 label: &DbString,
576 property: &DbString,
577 range: R,
578 ) -> Option<RoaringBitmap>
579 where
580 R: RangeBounds<Value>,
581 {
582 self.property_index
583 .get(&(label.clone(), property.clone()))
584 .and_then(|entry| entry.index.lookup_range(range))
585 }
586
587 #[must_use]
593 pub fn edges_with_property_range<R>(
594 &self,
595 label: &DbString,
596 property: &DbString,
597 range: R,
598 ) -> Option<RoaringBitmap>
599 where
600 R: RangeBounds<Value>,
601 {
602 self.edge_property_index
603 .get(&(label.clone(), property.clone()))
604 .and_then(|entry| entry.index.lookup_range(range))
605 }
606
607 #[must_use]
612 pub fn nodes_with_property_prefix(
613 &self,
614 label: &DbString,
615 property: &DbString,
616 prefix: &str,
617 ) -> Option<RoaringBitmap> {
618 self.property_index
619 .get(&(label.clone(), property.clone()))
620 .and_then(|entry| entry.index.lookup_prefix(prefix))
621 }
622
623 fn live_node_row(&self, id: NodeId) -> Option<usize> {
624 let row = self.row_for_node_id(id)?.get();
625 ((row as usize) < self.node_store.len() && self.node_store.is_alive(row))
626 .then_some(row as usize)
627 }
628
629 fn live_edge_row(&self, id: EdgeId) -> Option<usize> {
630 let row = self.row_for_edge_id(id)?.get();
631 ((row as usize) < self.edge_store.len() && self.edge_store.is_alive(row))
632 .then_some(row as usize)
633 }
634}
635
636pub(crate) fn composite_property_key(properties: &[DbString]) -> SmallVec<[DbString; 4]> {
637 let mut key: SmallVec<[DbString; 4]> = properties.iter().cloned().collect();
638 key.sort();
639 key
640}
641
642#[cfg(test)]
643mod tests;