1mod commit_api;
8pub mod context;
9mod cursor_impl;
10mod index_engine;
11mod index_manager;
12mod ops;
13mod persistence;
14mod persistence_api;
15mod reader;
16mod reader_api;
17mod schema_api;
18mod schema_engine;
19mod schema_writer;
20mod serial;
21mod stats_reader_impl;
22pub mod ttl;
23pub(crate) mod ttl_purge;
24mod writer;
25mod writer_api;
26
27pub use context::{AutoCommitBatchWindow, GraphStorageContext, WriteGateStats};
28pub use serial::SerialKey;
29
30use std::path::PathBuf;
31use std::sync::Arc;
32
33use crate::engine::background_freeze::{BackgroundFreezeManager, FreezeStats};
34use crate::engine::PersistenceConfig;
35use crate::index::key_codec::KeyBuilder;
36use crate::index::types::IndexIdentity;
37use crate::index::IndexGcConfig;
38use crate::{
39 StorageAdmin, StorageAuthOps, StorageGcOps, StorageOperationContext,
40 StorageOperationContextOps, StorageRecoveryOps, StorageSchemaContextOps, StorageStats,
41 StorageSyncContextOps, StorageTtlPurgeOps,
42};
43use linkrs_core::metadata::{IndexMetadataManager, SchemaManager};
44use linkrs_core::types::{
45 CommitLsn, PasswordInfo, SnapshotTimestamp, UserAlterInfo, UserInfo, VertexId,
46};
47use linkrs_core::{Edge, RoleType, StorageError, StorageResult, Value};
48use linkrs_metrics::StatsManager;
49
50#[derive(Clone)]
51pub struct GraphStorage {
52 ctx: Arc<GraphStorageContext>,
53}
54
55impl std::fmt::Debug for GraphStorage {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.debug_struct("GraphStorage")
58 .field("work_dir", &self.ctx.work_dir())
59 .field("db_path", &self.ctx.db_path())
60 .finish()
61 }
62}
63
64impl crate::stats_reader::ColumnStatsReader for GraphStorage {
65 fn vertex_column_stats(
66 &self,
67 space: &str,
68 tag: &str,
69 column: &str,
70 ) -> Option<std::sync::Arc<crate::stats_reader::ColumnStatsSnapshot>> {
71 stats_reader_impl::vertex_column_stats(&self.ctx, space, tag, column)
72 }
73
74 fn edge_column_stats(
75 &self,
76 space: &str,
77 edge_type: &str,
78 column: &str,
79 ) -> Option<std::sync::Arc<crate::stats_reader::ColumnStatsSnapshot>> {
80 stats_reader_impl::edge_column_stats(&self.ctx, space, edge_type, column)
81 }
82
83 fn vertex_table_stats(
84 &self,
85 space: &str,
86 tag: &str,
87 ) -> Option<std::sync::Arc<crate::stats_reader::TableCardinalitySnapshot>> {
88 stats_reader_impl::vertex_table_stats(&self.ctx, space, tag)
89 }
90
91 fn edge_table_stats(
92 &self,
93 space: &str,
94 edge_type: &str,
95 ) -> Option<std::sync::Arc<crate::stats_reader::TableCardinalitySnapshot>> {
96 stats_reader_impl::edge_table_stats(&self.ctx, space, edge_type)
97 }
98
99 fn stats_epoch(&self) -> u64 {
100 self.ctx.version_manager().write_timestamp()
104 }
105}
106
107impl GraphStorage {
108 pub fn version_manager(&self) -> Arc<linkrs_transaction::VersionManager> {
110 self.ctx.version_manager().clone()
111 }
112
113 pub fn write_gate_stats(&self) -> context::WriteGateStats {
116 self.ctx.write_gate_stats()
117 }
118
119 pub const RECOMMENDED_BATCH_CHUNK_EDGES: usize = writer::RECOMMENDED_BATCH_CHUNK_EDGES;
126
127 pub fn chunk_edges_for_commit(edges: &[Edge]) -> Vec<&[Edge]> {
134 writer::chunk_edges_for_commit(edges)
135 }
136
137 pub fn outbox_pending(&self) -> u64 {
139 self.ctx.outbox_pending()
140 }
141
142 pub fn begin_auto_commit_batch(&self) -> StorageResult<Arc<context::AutoCommitBatchWindow>> {
143 self.ctx.begin_auto_commit_batch()
144 }
145
146 pub fn bind_auto_commit_statement(
147 &self,
148 window: &Arc<context::AutoCommitBatchWindow>,
149 ) -> StorageResult<Self> {
150 Ok(Self {
151 ctx: Arc::new(window.bind_statement()?),
152 })
153 }
154
155 pub fn finalize_auto_commit_batch(
156 &self,
157 window: &context::AutoCommitBatchWindow,
158 ) -> StorageResult<()> {
159 window.finalize()
160 }
161
162 pub fn begin_auto_commit_group(&self) -> StorageResult<Arc<context::AutoCommitBatchWindow>> {
163 self.ctx.begin_auto_commit_group()
164 }
165
166 pub fn finalize_auto_commit_group(
167 &self,
168 window: &context::AutoCommitBatchWindow,
169 ) -> StorageResult<()> {
170 window.finalize_group()
171 }
172
173 fn commit_auto_if_needed(&self) -> StorageResult<()> {
174 let Some(context) = self.ctx.operation_context() else {
175 return Ok(());
176 };
177 if !context.auto_commit || context.read_only {
178 return Ok(());
179 }
180 if self.ctx.is_group_bound() {
181 return Ok(());
182 }
183 let transaction_id = context.transaction_id.ok_or_else(|| {
184 StorageError::db_error("Auto-commit storage context has no transaction ID".to_string())
185 })?;
186 self.ctx.commit_staged_writes(transaction_id, &[])?;
187 Ok(())
188 }
189
190 pub fn new() -> StorageResult<Self> {
191 Ok(Self {
192 ctx: Arc::new(GraphStorageContext::new()),
193 })
194 }
195
196 pub fn new_with_config(
198 config: crate::engine::config::PropertyGraphConfig,
199 ) -> StorageResult<Self> {
200 Ok(Self {
201 ctx: Arc::new(GraphStorageContext::new_with_config(config)?),
202 })
203 }
204
205 pub fn new_with_path(path: PathBuf) -> StorageResult<Self> {
206 GraphStorageContext::new_with_path(path).map(|ctx| Self { ctx: Arc::new(ctx) })
207 }
208
209 pub fn open(path: PathBuf) -> StorageResult<Self> {
214 let config = PersistenceConfig::for_work_dir(&path);
215 Self::open_with_persistence_config(path, config)
216 }
217
218 pub fn open_with_config(
220 path: PathBuf,
221 property_config: crate::engine::config::PropertyGraphConfig,
222 ) -> StorageResult<Self> {
223 let config =
224 PersistenceConfig::for_work_dir(&path).with_property_graph_config(property_config);
225 Self::open_with_persistence_config(path, config)
226 }
227
228 pub fn open_with_persistence_config(
234 path: PathBuf,
235 config: PersistenceConfig,
236 ) -> StorageResult<Self> {
237 let storage = Self::new_with_persistence(path, config)?;
238 let _recovery_stats = persistence::initialize_with_recovery(&storage.ctx)?;
239 Ok(storage)
240 }
241
242 pub fn new_with_persistence(path: PathBuf, config: PersistenceConfig) -> StorageResult<Self> {
243 GraphStorageContext::new_with_persistence(path, config)
244 .map(|ctx| Self { ctx: Arc::new(ctx) })
245 }
246
247 pub fn open_with_persistence(
248 path: PathBuf,
249 enable_wal: bool,
250 sync_policy: Option<linkrs_transaction::wal::SyncPolicy>,
251 ) -> StorageResult<Self> {
252 let mut config = PersistenceConfig::for_work_dir(&path);
253 config.enable_wal = enable_wal;
254 config.sync_policy = sync_policy;
255 let storage = Self::new_with_persistence(path, config)?;
256 let _recovery_stats = persistence::initialize_with_recovery(&storage.ctx)?;
257 Ok(storage)
258 }
259
260 pub fn with_index_gc(mut self, config: IndexGcConfig) -> Self {
261 let new_ctx = Arc::new((*self.ctx).clone().with_index_gc(config));
262 self.ctx = new_ctx;
263 self
264 }
265
266 pub fn staged_wal_len(&self) -> usize {
270 self.ctx.staged_wal_len()
271 }
272
273 pub fn retired_generation_count(&self) -> usize {
275 self.ctx
276 .index_data_manager()
277 .read()
278 .retired_generation_count()
279 }
280
281 pub fn with_vertex_gc(mut self, config: crate::vertex::VertexGcConfig) -> Self {
282 let new_ctx = Arc::new((*self.ctx).clone().with_vertex_gc(config));
283 self.ctx = new_ctx;
284 self
285 }
286
287 pub fn set_stats_manager(mut self, stats: Arc<StatsManager>) -> Self {
293 let mut ctx = (*self.ctx).clone();
294 ctx.set_stats_manager(stats);
295 self.ctx = Arc::new(ctx);
296 self
297 }
298
299 pub fn is_persistence_enabled(&self) -> bool {
300 self.ctx.is_persistence_enabled()
301 }
302
303 pub fn with_background_freeze(mut self) -> Self {
304 let freeze_config = self.ctx.get_freeze_config_full();
305 let manager = Arc::new(BackgroundFreezeManager::from_config(freeze_config));
306 let new_ctx = (*self.ctx)
307 .clone()
308 .with_background_freeze(Arc::clone(&manager));
309 self.ctx = Arc::new(new_ctx);
310 self
311 }
312
313 pub fn get_freeze_stats(&self) -> Option<FreezeStats> {
314 self.ctx.get_freeze_stats()
315 }
316
317 pub fn resource_snapshot(&self) -> crate::ResourceSnapshot {
319 self.ctx.resource_snapshot()
320 }
321
322 pub fn wal_metrics(&self) -> Option<crate::WalMetrics> {
324 self.ctx.wal_metrics()
325 }
326
327 pub fn check_snapshot_admission(&self) -> StorageResult<()> {
329 self.ctx.check_snapshot_admission()
330 }
331
332 pub fn trigger_background_freeze(&self) -> StorageResult<()> {
333 self.ctx.trigger_background_freeze()
334 }
335
336 pub fn trigger_background_maintenance(&self) -> StorageResult<()> {
341 self.ctx.trigger_background_maintenance()
342 }
343
344 pub fn cleanup_old_checkpoints(&self, max_checkpoints: usize) -> StorageResult<usize> {
346 let persistence = self
347 .ctx
348 .persistence()
349 .as_ref()
350 .ok_or_else(|| StorageError::not_supported("Persistence is not enabled"))?;
351 persistence.read().cleanup_old_checkpoints(max_checkpoints)
352 }
353
354 pub fn split_native_index(
360 &self,
361 space: &str,
362 index_name: &str,
363 boundary: Vec<u8>,
364 ) -> StorageResult<()> {
365 let space_id = self.ctx.schema_manager().get_space_id(space)?;
366 let index = self
367 .ctx
368 .index_metadata_manager()
369 .get_tag_index(space_id, index_name)?
370 .or(self
371 .ctx
372 .index_metadata_manager()
373 .get_edge_index(space_id, index_name)?)
374 .ok_or_else(|| StorageError::not_found(format!("Index {index_name} not found")))?;
375 let rebuild_gate = self.ctx.index_data_manager().read().rebuild_gate();
380 let _rebuild_guard = rebuild_gate.write();
381 let snapshot_timestamp = SnapshotTimestamp::new(self.ctx.get_read_timestamp().max(1));
382 let start_lsn = {
383 let current = index_manager::current_wal_lsn(&self.ctx);
384 if current == CommitLsn::ZERO {
385 CommitLsn::new(1)
386 } else {
387 current
388 }
389 };
390 let wal_context = Arc::clone(&self.ctx);
391 let wal_index = index.clone();
392 let result = self.ctx.index_data_manager().write().split_native_index(
393 IndexIdentity {
394 space_id,
395 index_id: index.id,
396 },
397 boundary,
398 snapshot_timestamp,
399 start_lsn,
400 {
401 let wal_context = Arc::clone(&wal_context);
402 move || {
403 let current = index_manager::current_wal_lsn(&wal_context);
404 Ok(if current < start_lsn {
405 start_lsn
406 } else {
407 current
408 })
409 }
410 },
411 move |from_lsn, to_lsn| {
412 index_manager::wal_intents_for_index(
413 &wal_context,
414 space_id,
415 &wal_index,
416 from_lsn,
417 to_lsn,
418 )
419 },
420 );
421 if let Some(stats) = self.ctx.stats_manager() {
422 stats.record_split(result.is_ok());
423 if result.is_err() {
424 stats.record_fence_failure();
425 }
426 }
427 result
428 }
429
430 pub fn split_native_index_at_value(
432 &self,
433 space: &str,
434 index_name: &str,
435 value: &Value,
436 ) -> StorageResult<()> {
437 let space_id = self.ctx.schema_manager().get_space_id(space)?;
438 let index = self
439 .ctx
440 .index_metadata_manager()
441 .get_tag_index(space_id, index_name)?
442 .or(self
443 .ctx
444 .index_metadata_manager()
445 .get_edge_index(space_id, index_name)?)
446 .ok_or_else(|| StorageError::not_found(format!("Index {index_name} not found")))?;
447 let boundary = match index.index_type {
448 linkrs_core::types::IndexType::TagIndex => {
449 KeyBuilder::build_vertex_index_value_prefix(space_id, index_name, value)?.0
450 }
451 linkrs_core::types::IndexType::EdgeIndex => {
452 KeyBuilder::build_edge_index_value_prefix(space_id, index_name, value)?.0
453 }
454 };
455 self.split_native_index(space, index_name, boundary)
456 }
457}
458
459impl crate::AutoCommitBatchOps for GraphStorage {
460 fn begin_auto_commit_batch(&self) -> StorageResult<Arc<context::AutoCommitBatchWindow>> {
461 self.ctx.begin_auto_commit_batch()
462 }
463
464 fn bind_auto_commit_statement(
465 &self,
466 window: &Arc<context::AutoCommitBatchWindow>,
467 ) -> StorageResult<Self> {
468 GraphStorage::bind_auto_commit_statement(self, window)
469 }
470
471 fn finalize_auto_commit_batch(
472 &self,
473 window: &context::AutoCommitBatchWindow,
474 ) -> StorageResult<()> {
475 GraphStorage::finalize_auto_commit_batch(self, window)
476 }
477
478 fn bind_auto_commit_writer(
479 &self,
480 window: &Arc<context::AutoCommitBatchWindow>,
481 ) -> StorageResult<Box<dyn crate::StorageWriter + '_>> {
482 let bound = window.bind_statement()?;
483 Ok(Box::new(GraphStorage {
484 ctx: Arc::new(bound),
485 }))
486 }
487}
488
489impl crate::AutoCommitGroupOps for GraphStorage {
490 fn begin_auto_commit_group(&self) -> StorageResult<Arc<context::AutoCommitBatchWindow>> {
491 self.ctx.begin_auto_commit_group()
492 }
493
494 fn finalize_auto_commit_group(
495 &self,
496 window: &context::AutoCommitBatchWindow,
497 ) -> StorageResult<()> {
498 window.finalize_group()
499 }
500
501 fn rollback_auto_commit_group(
502 &self,
503 window: &context::AutoCommitBatchWindow,
504 ) -> StorageResult<()> {
505 window.rollback_group()
506 }
507}
508
509impl StorageAuthOps for GraphStorage {
510 fn change_password(&mut self, info: &PasswordInfo) -> Result<bool, StorageError> {
511 self.ctx.check_write_admission()?;
512 ops::change_password(&self.ctx, info)
513 }
514
515 fn create_user(&mut self, info: &UserInfo) -> Result<bool, StorageError> {
516 self.ctx.check_write_admission()?;
517 ops::create_user(&self.ctx, info)
518 }
519
520 fn alter_user(&mut self, info: &UserAlterInfo) -> Result<bool, StorageError> {
521 self.ctx.check_write_admission()?;
522 ops::alter_user(&self.ctx, info)
523 }
524
525 fn drop_user(&mut self, username: &str) -> Result<bool, StorageError> {
526 self.ctx.check_write_admission()?;
527 ops::drop_user(&self.ctx, username)
528 }
529
530 fn user_exists(&self, username: &str) -> bool {
531 self.ctx.user_storage().user_exists(username)
532 }
533
534 fn list_users(&self) -> Vec<String> {
535 self.ctx.user_storage().list_users()
536 }
537
538 fn get_user(&self, username: &str) -> Option<UserInfo> {
539 self.ctx.user_storage().get_user(username)
540 }
541
542 fn update_last_login(&self, username: &str) -> Result<bool, StorageError> {
543 self.ctx.user_storage().update_last_login(username)
544 }
545
546 fn list_user_roles(&self, username: &str) -> Vec<(i64, RoleType)> {
547 self.ctx
548 .user_storage()
549 .get_user_roles(username)
550 .into_iter()
551 .collect()
552 }
553
554 fn list_all_user_roles(&self) -> Vec<(String, Vec<(i64, RoleType)>)> {
555 self.ctx
556 .user_storage()
557 .list_all_roles()
558 .into_iter()
559 .map(|(user, roles)| {
560 let mut entries: Vec<(i64, RoleType)> = roles.into_iter().collect();
561 entries.sort_by_key(|(space_id, _)| *space_id);
562 (user, entries)
563 })
564 .collect()
565 }
566
567 fn grant_role(
568 &mut self,
569 username: &str,
570 space_id: i64,
571 role: RoleType,
572 ) -> Result<bool, StorageError> {
573 self.ctx.check_write_admission()?;
574 ops::grant_role(&self.ctx, username, space_id, role)
575 }
576
577 fn revoke_role(&mut self, username: &str, space_id: i64) -> Result<bool, StorageError> {
578 self.ctx.check_write_admission()?;
579 ops::revoke_role(&self.ctx, username, space_id)
580 }
581}
582
583impl StorageAdmin for GraphStorage {
584 fn load_from_disk(&mut self) -> Result<(), StorageError> {
585 persistence::load_from_disk(&self.ctx)
586 }
587
588 fn save_to_disk(&self) -> Result<(), StorageError> {
589 persistence::save_to_disk(&self.ctx)
590 }
591
592 fn get_storage_stats(&self) -> StorageStats {
593 ops::get_storage_stats(&self.ctx)
594 }
595
596 fn find_dangling_edges(&self, space: &str) -> Result<Vec<Edge>, StorageError> {
597 ops::find_dangling_edges(&self.ctx, space)
598 }
599
600 fn repair_dangling_edges(&mut self, space: &str) -> Result<usize, StorageError> {
601 ops::repair_dangling_edges(&self.ctx, space)
602 }
603
604 fn purge_expired(
605 &self,
606 space: &str,
607 target: Option<crate::PurgeTarget<'_>>,
608 ) -> Result<Vec<crate::TtlPurgeEntry>, StorageError> {
609 GraphStorage::purge_expired(self, space, target)
610 }
611
612 fn get_db_path(&self) -> &str {
613 self.ctx.db_path()
614 }
615}
616
617impl StorageSchemaContextOps for GraphStorage {
618 fn get_schema_manager(&self) -> Option<Arc<SchemaManager>> {
619 Some(self.ctx.schema_manager().clone())
620 }
621
622 fn get_index_metadata_manager(&self) -> Option<Arc<dyn IndexMetadataManager>> {
623 Some(self.ctx.index_metadata_manager().clone())
624 }
625}
626
627impl StorageOperationContextOps for GraphStorage {
628 fn bind_auto_commit_context(&self) -> StorageResult<Self> {
629 Ok(Self {
630 ctx: Arc::new(self.ctx.with_auto_commit_context()?),
631 })
632 }
633
634 fn bind_read_operation_context(&self) -> StorageResult<Self> {
635 Ok(Self {
636 ctx: Arc::new(self.ctx.with_read_operation_context()?),
637 })
638 }
639
640 fn bind_operation_context(&self, context: StorageOperationContext) -> Self {
641 Self {
642 ctx: Arc::new(self.ctx.with_operation_context(context)),
643 }
644 }
645
646 fn operation_context(&self) -> Option<Arc<StorageOperationContext>> {
647 self.ctx.operation_context()
648 }
649
650 fn finalize_operation(&self, committed: bool) -> linkrs_core::StorageResult<()> {
651 self.ctx.finalize_operation(committed)
652 }
653}
654
655impl StorageSyncContextOps for GraphStorage {
656 fn get_sync_manager(&self) -> Option<Arc<linkrs_sync::SyncManager>> {
657 None
658 }
659}
660
661impl StorageRecoveryOps for GraphStorage {
662 fn needs_recovery(&self) -> bool {
663 persistence::needs_recovery(&self.ctx)
664 }
665
666 fn recover_from_wal(&self) -> StorageResult<linkrs_transaction::wal::recovery::RecoveryStats> {
667 persistence::recover_from_wal(&self.ctx)
668 }
669
670 fn recover_from_wal_with_config(
671 &self,
672 config: linkrs_transaction::wal::recovery::RecoveryConfig,
673 ) -> StorageResult<linkrs_transaction::wal::recovery::RecoveryStats> {
674 persistence::recover_from_wal_with_config(&self.ctx, config)
675 }
676
677 fn init_with_recovery(
678 &self,
679 ) -> StorageResult<Option<linkrs_transaction::wal::recovery::RecoveryStats>> {
680 persistence::initialize_with_recovery(&self.ctx)
681 }
682}
683
684impl StorageGcOps for GraphStorage {
685 fn is_index_gc_running(&self) -> bool {
686 self.ctx.is_index_gc_running()
687 }
688
689 fn start_index_gc(&self) -> Option<crate::thread_pool::BackgroundTaskHandle> {
690 self.ctx.start_index_gc()
691 }
692
693 fn stop_index_gc(&self) {
694 self.ctx.stop_index_gc();
695 }
696}
697
698impl StorageTtlPurgeOps for GraphStorage {
699 fn start_ttl_purge(&self, interval: std::time::Duration) {
700 self.ctx.start_ttl_purge(interval);
701 }
702
703 fn stop_ttl_purge(&self) {
704 self.ctx.stop_ttl_purge();
705 }
706
707 fn is_ttl_purge_running(&self) -> bool {
708 self.ctx.is_ttl_purge_running()
709 }
710}
711
712impl GraphStorage {
714 pub fn is_vertex_gc_running(&self) -> bool {
715 self.ctx.is_vertex_gc_running()
716 }
717
718 pub fn start_vertex_gc(&self) -> Option<crate::thread_pool::BackgroundTaskHandle> {
719 self.ctx.start_vertex_gc()
720 }
721
722 pub fn stop_vertex_gc(&self) {
723 self.ctx.stop_vertex_gc();
724 }
725
726 pub fn register_schema_callback(
729 &self,
730 callback: linkrs_core::metadata::SchemaChangeCallback,
731 ) -> linkrs_core::event_dispatch::SubscriptionId {
732 self.ctx.register_schema_callback(callback)
733 }
734
735 pub fn unregister_schema_callback(
737 &self,
738 id: linkrs_core::event_dispatch::SubscriptionId,
739 ) -> bool {
740 self.ctx.unregister_schema_callback(id)
741 }
742
743 pub fn schema_callback_count(&self) -> usize {
745 self.ctx.schema_callback_count()
746 }
747
748 pub fn shared_schema_callbacks(
750 &self,
751 ) -> Arc<
752 linkrs_core::event_dispatch::EventSubscriptions<linkrs_core::metadata::SchemaChangeEvent>,
753 > {
754 self.ctx.shared_schema_callbacks()
755 }
756
757 pub fn register_storage_callback(
763 &self,
764 callback: crate::engine::persistence_coordinator::StorageEventCallback,
765 ) -> Option<linkrs_core::event_dispatch::SubscriptionId> {
766 self.ctx
767 .persistence()
768 .as_ref()
769 .map(|persistence| persistence.read().register_storage_callback(callback))
770 }
771
772 pub fn register_storage_callback_filtered(
775 &self,
776 callback: crate::engine::persistence_coordinator::StorageEventCallback,
777 filter: linkrs_core::event_dispatch::EventFilter<
778 crate::engine::persistence_coordinator::StorageEvent,
779 >,
780 ) -> Option<linkrs_core::event_dispatch::SubscriptionId> {
781 self.ctx.persistence().as_ref().map(|persistence| {
782 persistence
783 .read()
784 .register_storage_callback_filtered(callback, filter)
785 })
786 }
787
788 pub fn unregister_storage_callback(
791 &self,
792 id: linkrs_core::event_dispatch::SubscriptionId,
793 ) -> bool {
794 if let Some(persistence) = self.ctx.persistence() {
795 persistence.read().unregister_storage_callback(id)
796 } else {
797 false
798 }
799 }
800
801 pub fn storage_callback_count(&self) -> usize {
803 if let Some(persistence) = self.ctx.persistence() {
804 persistence.read().storage_callback_count()
805 } else {
806 0
807 }
808 }
809
810 pub fn shared_storage_callbacks(
813 &self,
814 ) -> Option<
815 Arc<
816 linkrs_core::event_dispatch::EventSubscriptions<
817 crate::engine::persistence_coordinator::StorageEvent,
818 >,
819 >,
820 > {
821 self.ctx
822 .persistence()
823 .as_ref()
824 .map(|persistence| persistence.read().shared_storage_callbacks())
825 }
826
827 pub fn batch_delete_vertices(
829 &self,
830 label: linkrs_core::types::LabelId,
831 external_ids: &[&str],
832 ts: linkrs_core::types::Timestamp,
833 ) -> linkrs_core::StorageResult<usize> {
834 self.ctx.batch_delete_vertices(label, external_ids, ts)
835 }
836
837 pub fn batch_delete_vertices_by_i64(
839 &self,
840 label: linkrs_core::types::LabelId,
841 external_ids: &[i64],
842 ts: linkrs_core::types::Timestamp,
843 ) -> linkrs_core::StorageResult<usize> {
844 self.ctx
845 .batch_delete_vertices_by_i64(label, external_ids, ts)
846 }
847
848 pub fn batch_delete_vertices_with_edges(
850 &self,
851 space: &str,
852 tag: &str,
853 ids: &[VertexId],
854 ) -> linkrs_core::StorageResult<usize> {
855 self.ctx.check_write_admission()?;
856 crate::engine::graph_storage::writer::batch_delete_vertices_with_edges(
857 &self.ctx, space, tag, ids,
858 )
859 }
860}
861
862impl crate::client::StorageSnapshotOps for GraphStorage {
863 fn get_freeze_stats(&self) -> Option<FreezeStats> {
864 self.ctx.get_freeze_stats()
865 }
866
867 fn trigger_background_freeze(&self) -> StorageResult<()> {
868 self.ctx.trigger_background_freeze()
869 }
870}
871
872impl linkrs_transaction::UndoTarget for GraphStorage {
873 fn delete_vertex_type(
874 &self,
875 label: linkrs_core::types::LabelId,
876 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
877 linkrs_core::types::UndoTarget::delete_vertex_type(&*self.ctx, label)
878 }
879
880 fn delete_edge_type(
881 &self,
882 edge_key: linkrs_core::types::EdgeKey,
883 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
884 linkrs_core::types::UndoTarget::delete_edge_type(&*self.ctx, edge_key)
885 }
886
887 fn delete_vertex(
888 &self,
889 vertex: linkrs_core::types::VertexIdentifier,
890 ts: linkrs_transaction::wal::Timestamp,
891 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
892 linkrs_core::types::UndoTarget::delete_vertex(&*self.ctx, vertex, ts)
893 }
894
895 fn delete_edge(
896 &self,
897 edge_ctx: linkrs_core::types::EdgeDeletionContext,
898 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
899 linkrs_core::types::UndoTarget::delete_edge(&*self.ctx, edge_ctx)
900 }
901
902 fn restore_edge(
903 &self,
904 edge: linkrs_core::types::EdgeIdentifier,
905 properties: Vec<(std::sync::Arc<str>, linkrs_core::Value)>,
906 ts: linkrs_transaction::wal::Timestamp,
907 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
908 linkrs_core::types::UndoTarget::restore_edge(&*self.ctx, edge, properties, ts)
909 }
910
911 fn undo_update_edge_property(
912 &self,
913 edge_id: linkrs_core::types::EdgeIdentifier,
914 col_id: linkrs_core::types::ColumnId,
915 value: linkrs_core::Value,
916 ts: linkrs_transaction::wal::Timestamp,
917 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
918 linkrs_core::types::UndoTarget::undo_update_edge_property(
919 &*self.ctx, edge_id, col_id, value, ts,
920 )
921 }
922
923 fn revert_delete_edge(
924 &self,
925 edge_ctx: linkrs_core::types::EdgeDeletionContext,
926 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
927 linkrs_core::types::UndoTarget::revert_delete_edge(&*self.ctx, edge_ctx)
928 }
929
930 fn revert_delete_vertex_properties(
931 &self,
932 label_name: &str,
933 prop_names: &[Arc<str>],
934 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
935 linkrs_core::types::UndoTarget::revert_delete_vertex_properties(
936 &*self.ctx, label_name, prop_names,
937 )
938 }
939
940 fn revert_delete_edge_properties(
941 &self,
942 src_label: &str,
943 dst_label: &str,
944 edge_label: &str,
945 prop_names: &[Arc<str>],
946 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
947 linkrs_core::types::UndoTarget::revert_delete_edge_properties(
948 &*self.ctx, src_label, dst_label, edge_label, prop_names,
949 )
950 }
951
952 fn revert_delete_vertex_label(
953 &self,
954 label_name: &str,
955 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
956 linkrs_core::types::UndoTarget::revert_delete_vertex_label(&*self.ctx, label_name)
957 }
958
959 fn revert_delete_edge_label(
960 &self,
961 src_label: &str,
962 dst_label: &str,
963 edge_label: &str,
964 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
965 linkrs_core::types::UndoTarget::revert_delete_edge_label(
966 &*self.ctx, src_label, dst_label, edge_label,
967 )
968 }
969
970 fn revert_rename_vertex_properties(
971 &self,
972 label: &str,
973 current_names: &[Arc<str>],
974 original_names: &[Arc<str>],
975 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
976 linkrs_core::types::UndoTarget::revert_rename_vertex_properties(
977 &*self.ctx,
978 label,
979 current_names,
980 original_names,
981 )
982 }
983
984 fn revert_rename_edge_properties(
985 &self,
986 src_label: &str,
987 dst_label: &str,
988 edge_label: &str,
989 current_names: &[Arc<str>],
990 original_names: &[Arc<str>],
991 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
992 linkrs_core::types::UndoTarget::revert_rename_edge_properties(
993 &*self.ctx,
994 src_label,
995 dst_label,
996 edge_label,
997 current_names,
998 original_names,
999 )
1000 }
1001
1002 fn staged_write_mark(
1003 &self,
1004 txn_id: linkrs_core::types::TransactionId,
1005 ) -> Option<linkrs_core::types::StagedWriteMark> {
1006 linkrs_core::types::UndoTarget::staged_write_mark(&*self.ctx, txn_id)
1007 }
1008
1009 fn rollback_staged_writes(
1010 &self,
1011 txn_id: linkrs_core::types::TransactionId,
1012 mark: linkrs_core::types::StagedWriteMark,
1013 ) -> linkrs_transaction::undo_log::UndoLogResult<()> {
1014 linkrs_core::types::UndoTarget::rollback_staged_writes(&*self.ctx, txn_id, mark)
1015 }
1016}
1017
1018#[cfg(test)]
1019mod tests;