use std::num::NonZeroUsize;
use std::sync::{Arc, Mutex};
use nodedb_types::error::{NodeDbError, NodeDbResult};
use crate::config::LiteConfig;
use crate::engine::columnar::ColumnarEngine;
use crate::engine::fts::FtsState;
use crate::engine::htap::HtapBridge;
use crate::engine::sparse_vector::SparseVectorState;
use crate::engine::strict::StrictEngine;
use crate::engine::vector::{RestoredVectorState, VectorState};
use crate::nodedb::lock_ext::LockExt;
use crate::storage::engine::StorageEngine;
use crate::nodedb::core::kv_local::KvLocalState;
use crate::nodedb::core::types::NodeDbLite;
impl<S: StorageEngine> NodeDbLite<S> {
pub async fn open(storage: S) -> NodeDbResult<Arc<Self>> {
Self::open_with_config(storage, LiteConfig::from_env()).await
}
pub async fn open_with_config(storage: S, config: LiteConfig) -> NodeDbResult<Arc<Self>> {
Self::open_inner(storage, config).await
}
pub async fn open_with_budget(storage: S, memory_budget: usize) -> NodeDbResult<Arc<Self>> {
let config = LiteConfig {
memory_budget,
..LiteConfig::default()
};
Self::open_with_config(storage, config).await
}
async fn open_inner(storage: S, config: LiteConfig) -> NodeDbResult<Arc<Self>> {
config.validate()?;
let governor = Arc::new(
nodedb_mem::MemoryGovernor::new(config.to_governor_config())
.map_err(NodeDbError::config)?,
);
#[cfg(not(target_arch = "wasm32"))]
let sync_enabled = config.sync_enabled;
let kv_cache_capacity = NonZeroUsize::new(config.kv_cache_capacity)
.ok_or_else(|| NodeDbError::config("kv_cache_capacity must be greater than 0"))?;
let kv_local = Arc::new(KvLocalState::new(kv_cache_capacity));
#[cfg(not(target_arch = "wasm32"))]
let outbound_queue_cap = config.outbound_queue_cap;
let storage = Arc::new(storage);
let (crdt, lite_identity, replayed_rows) =
Self::restore_identity_and_crdt(&storage, config.corruption_policy).await?;
let (fts_manager, fts_checkpoint_complete) =
Self::restore_fts_indices(&storage, &governor).await?;
let (sparse_manager, sparse_checkpoint_present) =
Self::restore_sparse_indices(&storage).await;
let graph_memory = nodedb_mem::ScopedMemory::new(
Arc::clone(&governor),
nodedb_types::DatabaseId::DEFAULT,
nodedb_types::TenantId::new(0),
nodedb_mem::EngineId::Graph,
);
let csr = Self::restore_csr_indices(&storage, &graph_memory).await?;
let (hnsw_map, hnsw_id_map) = Self::restore_hnsw_indices(&storage).await?;
let spatial_memory = nodedb_mem::ScopedMemory::new(
Arc::clone(&governor),
nodedb_types::DatabaseId::DEFAULT,
nodedb_types::TenantId::new(0),
nodedb_mem::EngineId::Spatial,
);
let spatial = Arc::new(Mutex::new(
Self::restore_spatial_indices(&storage, &spatial_memory).await,
));
let strict = StrictEngine::restore(Arc::clone(&storage))
.await
.map_err(NodeDbError::storage)?;
let columnar_memory = nodedb_mem::ScopedMemory::new(
Arc::clone(&governor),
nodedb_types::DatabaseId::DEFAULT,
nodedb_types::TenantId::new(0),
nodedb_mem::EngineId::Columnar,
);
#[cfg(not(target_arch = "wasm32"))]
let mut columnar = ColumnarEngine::restore(Arc::clone(&storage), columnar_memory.clone())
.await
.map_err(NodeDbError::storage)?;
#[cfg(target_arch = "wasm32")]
let columnar = ColumnarEngine::restore(Arc::clone(&storage), columnar_memory.clone())
.await
.map_err(NodeDbError::storage)?;
#[cfg(not(target_arch = "wasm32"))]
let outbound_queues =
Self::build_outbound_queues(&storage, sync_enabled, outbound_queue_cap, &mut columnar)
.await?;
#[cfg(not(target_arch = "wasm32"))]
let columnar_outbound = outbound_queues.columnar_outbound;
#[cfg(not(target_arch = "wasm32"))]
let vector_outbound = outbound_queues.vector_outbound;
#[cfg(not(target_arch = "wasm32"))]
let fts_outbound_init = outbound_queues.fts_outbound;
#[cfg(not(target_arch = "wasm32"))]
let spatial_outbound_init = outbound_queues.spatial_outbound;
#[cfg(not(target_arch = "wasm32"))]
let timeseries_outbound_init = outbound_queues.timeseries_outbound;
#[cfg(not(target_arch = "wasm32"))]
let kv_outbound = outbound_queues.kv_outbound;
let crdt = Arc::new(Mutex::new(crdt));
let strict = Arc::new(strict);
let columnar = Arc::new(columnar);
let htap = Arc::new(HtapBridge::new());
let timeseries = Arc::new(Mutex::new(
crate::engine::timeseries::engine::TimeseriesEngine::new(),
));
let vector_memory = nodedb_mem::ScopedMemory::new(
Arc::clone(&governor),
nodedb_types::DatabaseId::DEFAULT,
nodedb_types::TenantId::new(0),
nodedb_mem::EngineId::Vector,
);
let vector_state = Arc::new(VectorState::from_restored(RestoredVectorState {
storage: Arc::clone(&storage),
search_ef: 128,
indices: hnsw_map,
id_map: hnsw_id_map,
memory: vector_memory,
}));
let fts_state = Arc::new(FtsState::from_restored(fts_manager));
let sparse_state = Arc::new(SparseVectorState::from_restored(sparse_manager));
let array_engine = crate::engine::array::ArrayEngineState::open(&storage)
.await
.map_err(NodeDbError::storage)?;
let array_state = Arc::new(tokio::sync::Mutex::new(array_engine));
let csr_arc = Arc::new(Mutex::new(csr));
let query_engine =
crate::query::LiteQueryEngine::new(crate::query::LiteQueryEngineParams {
crdt: Arc::clone(&crdt),
strict: Arc::clone(&strict),
columnar: Arc::clone(&columnar),
htap: Arc::clone(&htap),
storage: Arc::clone(&storage),
timeseries: Arc::clone(×eries),
vector_state: Arc::clone(&vector_state),
array_state: Arc::clone(&array_state),
fts_state: Arc::clone(&fts_state),
sparse_state: Arc::clone(&sparse_state),
spatial: Arc::clone(&spatial),
csr: Arc::clone(&csr_arc),
governor: Arc::clone(&governor),
kv_local: Arc::clone(&kv_local),
});
#[cfg(not(target_arch = "wasm32"))]
let query_engine = {
let mut query_engine = query_engine;
if let Some(ref q) = fts_outbound_init {
query_engine.set_fts_outbound(Arc::clone(q));
}
if let Some(ref q) = spatial_outbound_init {
query_engine.set_spatial_outbound(Arc::clone(q));
}
if let Some(ref q) = kv_outbound {
query_engine.set_kv_outbound(Arc::clone(q));
}
query_engine
};
#[cfg(not(target_arch = "wasm32"))]
let array_sync = Self::build_array_sync_state(&storage, &array_state).await?;
#[cfg(not(target_arch = "wasm32"))]
let array_schemas = array_sync.array_schemas;
#[cfg(not(target_arch = "wasm32"))]
let array_outbound = array_sync.array_outbound;
#[cfg(not(target_arch = "wasm32"))]
let array_inbound = array_sync.array_inbound;
#[cfg(not(target_arch = "wasm32"))]
let stream_seq = array_sync.stream_seq;
let db = Self {
storage,
vector_state,
csr: csr_arc,
crdt,
governor,
vector_mem_token: Mutex::new(None),
graph_mem_token: Mutex::new(None),
crdt_mem_token: Mutex::new(None),
query_engine,
fts_state,
sparse_state,
spatial,
strict,
columnar,
htap,
timeseries,
array_state,
#[cfg(not(target_arch = "wasm32"))]
array_schemas,
#[cfg(not(target_arch = "wasm32"))]
array_outbound,
#[cfg(not(target_arch = "wasm32"))]
array_inbound,
#[cfg(not(target_arch = "wasm32"))]
stream_seq,
#[cfg(not(target_arch = "wasm32"))]
columnar_outbound,
#[cfg(not(target_arch = "wasm32"))]
vector_outbound,
#[cfg(not(target_arch = "wasm32"))]
fts_outbound: fts_outbound_init,
#[cfg(not(target_arch = "wasm32"))]
spatial_outbound: spatial_outbound_init,
#[cfg(not(target_arch = "wasm32"))]
timeseries_outbound: timeseries_outbound_init,
#[cfg(not(target_arch = "wasm32"))]
kv_outbound,
identity: Mutex::new(lite_identity),
identity_change: tokio::sync::Mutex::new(()),
flush_lock: tokio::sync::Mutex::new(()),
kv_local,
sync_gate: std::sync::RwLock::new(None),
tasks: crate::tasks::TaskRegistry::default(),
};
{
let mut known: std::collections::BTreeSet<String> = db
.crdt
.lock_or_recover()
.collection_names()
.into_iter()
.collect();
known.extend(db.strict.collection_names());
known.extend(db.columnar.collection_names());
known.extend(
crate::nodedb::collection::ddl::load_persisted_collection_metas(&*db.storage)
.await?
.into_keys(),
);
crate::index::legacy::clear_legacy_entries(&*db.storage, &known)
.await
.map_err(NodeDbError::storage)?;
let indexes = &db.query_engine.indexes;
let loaded = indexes
.load(&*db.storage)
.await
.map_err(NodeDbError::storage)?;
crate::index::rebuild::rebuild_at_open(
indexes,
&*db.storage,
&db.crdt,
&db.strict,
loaded.rebuild_all,
)
.await
.map_err(NodeDbError::storage)?;
}
{
let fts_empty = db.fts_state.manager.lock_or_recover().is_empty();
if fts_empty || !fts_checkpoint_complete || !sparse_checkpoint_present {
db.rebuild_text_indices().await?;
}
db.rebuild_authoritative_text_indices().await?;
for (collection, rows) in &replayed_rows {
if db.strict.schema(collection).is_some()
|| crate::engine::document::history::ops::is_bitemporal(
db.storage.as_ref(),
collection,
)
.await?
{
continue;
}
db.reindex_documents_local(collection, rows.iter().map(String::as_str))?;
}
db.fts_state.mark_checkpoint_trusted();
}
let spatial_empty = db.spatial.lock_or_recover().is_empty();
if spatial_empty {
db.rebuild_spatial_indices();
}
db.migrate_legacy_edge_keys()
.await
.map_err(NodeDbError::storage)?;
let csr_empty = db.csr.lock_or_recover().is_empty();
if csr_empty {
db.rebuild_graph_indices().await;
}
let db = Arc::new(db);
db.start_auto_flush(config.auto_flush_ms);
db.start_auto_compact(config.auto_compact_ms);
Ok(db)
}
}