use std::collections::HashSet;
use std::sync::{Arc, Mutex};
use nodedb_types::value::Value;
use crate::engine::crdt::CrdtEngine;
use crate::engine::document::history::ops::{is_bitemporal, scan_live_documents};
use crate::engine::strict::StrictEngine;
use crate::error::LiteError;
use crate::query::kv_ops::indexes::kv_index_rows;
use crate::storage::engine::StorageEngine;
use super::catalog::{IndexDef, IndexEngine};
use super::document::row_value;
use super::store::IndexCatalog;
pub(crate) fn crdt_rows(crdt: &CrdtEngine, collection: &str) -> Vec<(String, Value)> {
crdt.list_ids(collection)
.into_iter()
.filter_map(|id| {
let row = crdt.read(collection, &id)?;
Some((id, row_value(&row)))
})
.collect()
}
async fn history_rows<S: StorageEngine>(
storage: &S,
collection: &str,
) -> Result<Vec<(String, Value)>, LiteError> {
let mut rows = Vec::new();
for (id, body) in scan_live_documents(storage, collection).await? {
let row = if body.is_empty() {
Value::Object(Default::default())
} else {
nodedb_types::json_msgpack::value_from_msgpack(&body).map_err(|e| {
LiteError::Serialization {
detail: format!("live version of '{collection}'/'{id}' does not decode: {e}"),
}
})?
};
rows.push((id, row));
}
Ok(rows)
}
pub(crate) async fn build_index<S: StorageEngine>(
catalog: &IndexCatalog,
storage: &S,
crdt: &Mutex<CrdtEngine>,
strict: &StrictEngine<S>,
def: Arc<IndexDef>,
) -> Result<u64, LiteError> {
match def.engine {
IndexEngine::Document => build_document_index(catalog, storage, crdt, def).await,
IndexEngine::Strict | IndexEngine::KeyValue => {
let _ddl = catalog.durable_ddl.write().await;
let rows = match def.engine {
IndexEngine::Strict => strict.rows_with_ids(&def.collection).await?,
_ => kv_index_rows(storage, &def.collection).await?,
};
let name = def.name.clone();
let fresh = catalog.def_named(&name).is_none();
let (built, ops) = catalog.install(def, rows)?;
if let Err(e) = storage.batch_write(&ops).await {
if fresh {
catalog.forget(&name);
}
return Err(e);
}
Ok(built)
}
}
}
async fn build_document_index<S: StorageEngine>(
catalog: &IndexCatalog,
storage: &S,
crdt: &Mutex<CrdtEngine>,
def: Arc<IndexDef>,
) -> Result<u64, LiteError> {
if is_bitemporal(storage, &def.collection).await? {
let _building = catalog.bitemporal_build.write().await;
let rows = history_rows(storage, &def.collection).await?;
let crdt = crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
let live: HashSet<&str> = rows.iter().map(|(id, _)| id.as_str()).collect();
let deleted: Vec<String> = crdt
.list_ids(&def.collection)
.into_iter()
.filter(|id| !live.contains(id.as_str()))
.collect();
let collection = def.collection.clone();
let (built, _flushed_later) = catalog.install(def, rows)?;
catalog.tombstone(&collection, deleted.iter().map(String::as_str));
return Ok(built);
}
let crdt = crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
let rows = crdt_rows(&crdt, &def.collection);
let (built, _flushed_later) = catalog.install(def, rows)?;
Ok(built)
}
pub(crate) async fn rebuild_at_open<S: StorageEngine>(
catalog: &IndexCatalog,
storage: &S,
crdt: &Mutex<CrdtEngine>,
strict: &StrictEngine<S>,
rebuild_all: bool,
) -> Result<(), LiteError> {
for def in catalog.defs() {
let stale = rebuild_all
|| (def.engine == IndexEngine::Document
&& is_bitemporal(storage, &def.collection).await?);
if stale {
build_index(catalog, storage, crdt, strict, def).await?;
}
}
Ok(())
}