1use std::collections::{BTreeMap, HashMap};
22use std::fs::File;
23use std::io::{BufReader, Read};
24use std::path::{Path, PathBuf};
25use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
26use std::sync::Arc;
27
28use parking_lot::RwLock;
29
30use super::context_index::ContextIndex;
31use super::entity::{
32 CrossRef, EmbeddingSlot, EntityData, EntityId, EntityKind, RefType, RowData,
33 TimeSeriesPointKind, UnifiedEntity,
34};
35use super::manager::{ManagerConfig, ManagerStats, SegmentManager};
36use super::metadata::{Metadata, MetadataFilter, MetadataValue};
37use crate::api::{DurabilityMode, GroupCommitOptions};
38use crate::physical::{ManifestEvent, ManifestEventKind};
39use crate::storage::engine::pager::PagerError;
40use crate::storage::engine::{BTree, BTreeError, Pager, PagerConfig, PhysicalFileHeader};
41use crate::storage::primitives::encoding::read_varu64;
42use reddb_types::types::Value;
43
44pub use reddb_file::{
45 is_supported_store_version, NativeCatalogCollectionSummary, NativeCatalogSummary,
46 NativeExportSummary, NativeManifestEntrySummary, NativeManifestSummary,
47 NativeMetadataStateSummary, NativeRecoverySummary, NativeRegistryIndexSummary,
48 NativeRegistryJobSummary, NativeRegistryProjectionSummary, NativeRegistrySummary,
49 NativeSnapshotSummary, NativeVectorArtifactPageSummary, NativeVectorArtifactSummary,
50 ENTITY_RECORD_MAGIC, METADATA_MAGIC, METADATA_OVERFLOW_MAGIC, NATIVE_BLOB_MAGIC,
51 NATIVE_CATALOG_MAGIC, NATIVE_COLLECTION_ROOTS_MAGIC, NATIVE_MANIFEST_MAGIC,
52 NATIVE_MANIFEST_SAMPLE_LIMIT, NATIVE_METADATA_STATE_MAGIC, NATIVE_RECOVERY_MAGIC,
53 NATIVE_REGISTRY_MAGIC, NATIVE_VECTOR_ARTIFACT_MAGIC, STORE_MAGIC, STORE_VERSION_CURRENT,
54 STORE_VERSION_V1, STORE_VERSION_V11, STORE_VERSION_V12, STORE_VERSION_V2, STORE_VERSION_V3,
55 STORE_VERSION_V4, STORE_VERSION_V5, STORE_VERSION_V6, STORE_VERSION_V7, STORE_VERSION_V8,
56 STORE_VERSION_V9,
57};
58
59#[derive(Debug, Clone, Default, PartialEq, Eq)]
60pub struct MvccVacuumStats {
61 pub scanned_versions: u64,
62 pub retained_versions: u64,
63 pub reclaimed_versions: u64,
64 pub retained_history_versions: u64,
65 pub reclaimed_history_versions: u64,
66 pub retained_tombstones: u64,
67 pub reclaimed_tombstones: u64,
68}
69
70impl MvccVacuumStats {
71 pub fn add(&mut self, other: &Self) {
72 self.scanned_versions += other.scanned_versions;
73 self.retained_versions += other.retained_versions;
74 self.reclaimed_versions += other.reclaimed_versions;
75 self.retained_history_versions += other.retained_history_versions;
76 self.reclaimed_history_versions += other.reclaimed_history_versions;
77 self.retained_tombstones += other.retained_tombstones;
78 self.reclaimed_tombstones += other.reclaimed_tombstones;
79 }
80}
81
82#[derive(Debug, Clone)]
83pub struct NativePhysicalState {
84 pub header: PhysicalFileHeader,
85 pub collection_roots: BTreeMap<String, u64>,
86 pub manifest: Option<NativeManifestSummary>,
87 pub registry: Option<NativeRegistrySummary>,
88 pub recovery: Option<NativeRecoverySummary>,
89 pub catalog: Option<NativeCatalogSummary>,
90 pub metadata_state: Option<NativeMetadataStateSummary>,
91 pub vector_artifact_pages: Option<Vec<NativeVectorArtifactPageSummary>>,
92}
93
94#[derive(Debug, Clone)]
100pub struct UnifiedStoreConfig {
101 pub manager_config: ManagerConfig,
103 pub auto_index_refs: bool,
105 pub auto_index_id: bool,
114 pub max_cross_refs: usize,
116 pub enable_wal: bool,
118 pub durability_mode: DurabilityMode,
120 pub group_commit: GroupCommitOptions,
122 pub data_dir: Option<std::path::PathBuf>,
124 pub embedded_wal_path: Option<std::path::PathBuf>,
126 pub page_cache_slots: Option<usize>,
131 pub resident_memory_bytes_max: Option<u64>,
134}
135
136impl Default for UnifiedStoreConfig {
137 fn default() -> Self {
138 Self {
139 manager_config: ManagerConfig::default(),
140 auto_index_refs: true,
141 auto_index_id: true,
142 max_cross_refs: 1000,
143 enable_wal: false,
144 durability_mode: DurabilityMode::WalDurableGrouped,
147 group_commit: GroupCommitOptions::default(),
148 data_dir: None,
149 embedded_wal_path: None,
150 page_cache_slots: None,
151 resident_memory_bytes_max: None,
152 }
153 }
154}
155
156impl UnifiedStoreConfig {
157 pub fn with_data_dir(mut self, path: impl Into<std::path::PathBuf>) -> Self {
159 self.data_dir = Some(path.into());
160 self
161 }
162
163 pub fn with_wal(mut self) -> Self {
165 self.enable_wal = true;
166 self
167 }
168
169 pub fn with_durability_mode(mut self, mode: DurabilityMode) -> Self {
170 self.durability_mode = mode;
171 self
172 }
173
174 pub fn with_group_commit(mut self, options: GroupCommitOptions) -> Self {
175 self.group_commit = options;
176 self
177 }
178
179 pub fn with_embedded_wal_path(mut self, path: impl Into<std::path::PathBuf>) -> Self {
180 self.embedded_wal_path = Some(path.into());
181 self
182 }
183
184 pub fn with_page_cache_slots(mut self, slots: usize) -> Self {
186 self.page_cache_slots = Some(slots);
187 self
188 }
189
190 pub fn with_max_refs(mut self, max: usize) -> Self {
192 self.max_cross_refs = max;
193 self
194 }
195}
196
197pub use reddb_engine::storage::error::StoreError;
202
203#[derive(Debug, Clone, Default)]
209pub struct StoreStats {
210 pub collection_count: usize,
212 pub total_entities: usize,
214 pub total_memory_bytes: usize,
216 pub collections: HashMap<String, ManagerStats>,
218 pub cross_ref_count: usize,
220}
221
222impl StoreStats {
223 pub fn avg_entities_per_collection(&self) -> f64 {
225 if self.collection_count == 0 {
226 0.0
227 } else {
228 self.total_entities as f64 / self.collection_count as f64
229 }
230 }
231
232 pub fn memory_mb(&self) -> f64 {
234 self.total_memory_bytes as f64 / (1024.0 * 1024.0)
235 }
236}
237
238struct CheckpointVersion {
243 manager: std::sync::Weak<SegmentManager>,
244 segments: Vec<(u64, u64)>,
245 visibility_revision: u64,
246 untracked_visibility_revision: u64,
247 excluded_xids: Arc<std::collections::HashSet<u64>>,
248 pending_vault: Arc<std::collections::HashSet<EntityId>>,
249 format_version: u32,
250 deferred_rows: Option<Arc<Vec<EntityId>>>,
254 has_unprojected_rows: bool,
256}
257
258impl CheckpointVersion {
259 fn remember_deferred_row(&mut self, id: EntityId) {
260 self.has_unprojected_rows = true;
261 let Some(rows) = self.deferred_rows.as_mut() else {
262 return;
263 };
264 if rows.len() == 1024 {
265 self.deferred_rows = None;
266 } else {
267 Arc::make_mut(rows).push(id);
268 }
269 }
270}
271
272pub struct UnifiedStore {
305 resident: reddb_engine::storage::resident_store::ResidentStore,
307 config: UnifiedStoreConfig,
309 format_version: AtomicU32,
311 pager: Option<Arc<Pager>>,
313 db_path: Option<PathBuf>,
315 btree_indices: RwLock<HashMap<String, Arc<BTree>>>,
322 key_indices: RwLock<HashMap<String, PublishedKeyIndex>>,
323 secondary_index_specs: RwLock<secondary_index::SecondaryIndexSpecs>,
324 recovery_xid_summary: RwLock<Option<recovery_xid::RecoveryXidSummary>>,
325 checkpoint_versions: RwLock<HashMap<String, CheckpointVersion>>,
327 checkpoint_durable_rows: RwLock<checkpoint_delta::DurableRows>,
328 retired_btrees: parking_lot::Mutex<Vec<RetiredBTree>>,
331 ownership_catalog_epoch: AtomicU64,
333 ownership_active_epoch: AtomicU64,
334 detached_native_blob_pages: parking_lot::Mutex<std::collections::BTreeSet<u32>>,
336 commit: Option<Arc<StoreCommitCoordinator>>,
338 pub(crate) replayed_turbo_inserts: parking_lot::Mutex<HashMap<String, Vec<(u64, Vec<f32>)>>>,
345 pub(crate) replayed_probabilistic_deltas:
349 parking_lot::Mutex<Vec<(u8, u8, String, Vec<Vec<u8>>)>>,
350 pub(crate) vault_publication_lock: parking_lot::ReentrantMutex<()>,
355 pub(crate) pending_vault_versions: RwLock<std::collections::HashSet<EntityId>>,
357 pub(crate) snapshot_manager:
360 std::sync::OnceLock<Arc<crate::storage::transaction::snapshot::SnapshotManager>>,
361 embedded_commit_session: std::sync::OnceLock<crate::storage::embedded::EmbeddedCommitSession>,
362 #[cfg(test)]
363 pub(crate) test_fail_deferred_wal_append: std::sync::atomic::AtomicBool,
364 #[cfg(test)]
365 pub(crate) test_require_deferred_publication_fence: std::sync::atomic::AtomicBool,
366}
367
368#[derive(Clone)]
369struct PublishedKeyIndex {
370 indexed: bool,
371 source: std::sync::Weak<BTree>,
372 tree: Arc<BTree>,
373 secondary: BTreeMap<String, secondary_index::PublishedSecondaryIndex>,
374}
375
376struct RetiredBTree {
377 tree: Arc<BTree>,
378 published: bool,
379}
380
381mod builder;
382mod checkpoint_cold_append;
383mod checkpoint_delta;
384mod checkpoint_wal;
385mod commit;
386mod impl_entities;
387mod impl_file;
388mod impl_native_a;
389mod impl_native_b;
390mod impl_native_c;
391mod impl_pages;
392mod impl_vault_import;
393mod key_index;
394mod keyed_write;
395mod mvcc_reclaim;
396pub(crate) use mvcc_reclaim::MvccReclaimCursor;
397pub(crate) mod count_index;
398mod native_helpers;
399mod recovery_xid;
400pub mod secondary_index;
401
402pub use self::builder::EntityBuilder;
403pub(crate) use self::commit::DeferredStoreWalActions;
404use self::commit::{StoreCommitCoordinator, StoreWalAction};
405use self::native_helpers::*;
406
407#[cfg(test)]
408mod shared_state_tests {
409 use super::*;
410
411 #[test]
412 fn native_mutations_and_shared_reads_use_one_resident_state() {
413 let store = UnifiedStore::new();
414 let resident: &reddb_engine::storage::resident_store::ResidentStore = &store.resident;
415 store.create_collection("nodes").expect("collection");
416 let native = store.get_collection("nodes").expect("native manager");
417 let shared = resident.get_collection("nodes").expect("shared manager");
418 assert!(
419 Arc::ptr_eq(&native, &shared),
420 "one canonical collection owner"
421 );
422 let id = store
423 .insert_auto(
424 "nodes",
425 UnifiedEntity::graph_node(EntityId::new(0), "Person", "person", HashMap::new()),
426 )
427 .expect("native mutation");
428 assert_eq!(id.raw(), 1);
429 assert_eq!(
430 resident.get_any(id).expect("shared read").1.kind,
431 store.get("nodes", id).expect("native read").kind
432 );
433 assert_eq!(
434 resident.lookup_graph_nodes_by_label_in("nodes", "Person"),
435 [id]
436 );
437 assert_eq!(resident.entity_cache_stats().entries, 1);
438 assert!(store.delete("nodes", id).expect("native delete"));
439 assert!(resident.get_any(id).is_none());
440 assert!(resident.lookup_graph_nodes_by_label("Person").is_empty());
441 assert_eq!(resident.entity_cache_stats().entries, 0);
442 store.set_aux_metadata(b"catalog".to_vec());
443 assert_eq!(resident.aux_metadata(), b"catalog");
444 assert_eq!(
445 store.next_entity_id().raw(),
446 2,
447 "native allocator is the shared allocator"
448 );
449 }
450}