use nodedb_types::Namespace;
use nodedb_types::result::QueryResult;
use nodedb_types::value::Value;
use crate::error::LiteError;
use crate::index::durable::RowImage;
use crate::index::key::{doc_id_of_key, key_of_doc_id};
use crate::index::{IndexCatalog, IndexEngine};
use crate::query::document_ops::indexes::{CreateIndexRequest, declare_index, drop_field_index};
use crate::query::engine::LiteQueryEngine;
use crate::storage::engine::{StorageEngine, WriteOp};
use super::body::decode_kv_map;
use super::reads::{decode_value, is_expired, kv_key, split_kv_key};
use super::sorted::plan_sorted_ops;
fn stored_row(stored: &[u8]) -> Result<Option<Value>, LiteError> {
let Some((deadline, body)) = decode_value(stored) else {
return Ok(None);
};
if is_expired(deadline) {
return Ok(None);
}
Ok(Some(Value::Object(
decode_kv_map(body)?.unwrap_or_default(),
)))
}
pub(crate) async fn kv_index_rows<S: StorageEngine>(
storage: &S,
collection: &str,
) -> Result<Vec<(String, Value)>, LiteError> {
let mut prefix = collection.as_bytes().to_vec();
prefix.push(0);
let mut rows = Vec::new();
for (composite, stored) in storage.scan_prefix(Namespace::Kv, &prefix).await? {
let Some((coll, key)) = split_kv_key(&composite) else {
continue;
};
if coll != collection {
continue;
}
if let Some(row) = stored_row(&stored)? {
rows.push((doc_id_of_key(key), row));
}
}
Ok(rows)
}
pub(crate) async fn kv_row_by_doc_id<S: StorageEngine>(
storage: &S,
collection: &str,
doc_id: &str,
) -> Result<Option<Value>, LiteError> {
let Some(key) = key_of_doc_id(doc_id) else {
return Ok(None);
};
match storage
.get(Namespace::Kv, &kv_key(collection, &key))
.await?
{
Some(stored) => stored_row(&stored),
None => Ok(None),
}
}
fn kv_images(ops: &[WriteOp]) -> Result<Vec<RowImage>, LiteError> {
let mut images = Vec::new();
for op in ops {
let (key, row) = match op {
WriteOp::Put { ns, key, value } if *ns == Namespace::Kv => (key, stored_row(value)?),
WriteOp::Delete { ns, key } if *ns == Namespace::Kv => (key, None),
_ => continue,
};
if let Some((collection, user_key)) = split_kv_key(key) {
images.push(RowImage {
collection: collection.to_string(),
doc_id: doc_id_of_key(user_key),
row,
});
}
}
Ok(images)
}
pub(crate) async fn kv_check<S: StorageEngine>(
storage: &S,
indexes: &IndexCatalog,
ops: &[WriteOp],
) -> Result<(), LiteError> {
indexes
.check_rows(
IndexEngine::KeyValue,
&kv_images(ops)?,
|collection, id| async move { kv_row_by_doc_id(storage, &collection, &id).await },
)
.await
}
pub(crate) async fn kv_commit<S: StorageEngine>(
storage: &S,
indexes: &IndexCatalog,
ops: Vec<WriteOp>,
) -> Result<(), LiteError> {
let images = kv_images(&ops)?;
indexes
.commit_rows_with(
storage,
IndexEngine::KeyValue,
ops,
images,
|collection, id| async move { kv_row_by_doc_id(storage, &collection, &id).await },
|mut ops| async move {
let sorted =
plan_sorted_ops(storage, &indexes.sorted_by_collection(), &ops).await?;
ops.extend(sorted);
Ok(ops)
},
)
.await
}
pub(crate) async fn kv_truncate_commit<S: StorageEngine>(
storage: &S,
indexes: &IndexCatalog,
collection: &str,
) -> Result<(), LiteError> {
let _ddl = indexes.durable_ddl.write().await;
let mut ops = kv_row_deletes(storage, collection).await?;
let sorted = indexes
.sorted_removal(storage, indexes.sorted_on(collection))
.await?;
ops.extend(sorted.ops);
let images = kv_images(&ops)?;
indexes
.commit_rows_held(
storage,
IndexEngine::KeyValue,
ops,
images,
|collection, id| async move { kv_row_by_doc_id(storage, &collection, &id).await },
|ops| async move { Ok(ops) },
)
.await?;
indexes.forget_sorted(&sorted.names);
Ok(())
}
pub(crate) async fn kv_drop_collection<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
collection: &str,
) -> Result<(), LiteError> {
let _order = engine.kv_local.write_order.lock().await;
engine.kv_local.forget_collection(collection);
let storage = &*engine.storage;
engine
.indexes
.drop_collection_with(storage, collection, || kv_row_deletes(storage, collection))
.await
}
pub(crate) async fn kv_row_deletes<S: StorageEngine>(
storage: &S,
collection: &str,
) -> Result<Vec<WriteOp>, LiteError> {
let mut ops = Vec::new();
for (composite, _) in storage
.scan_prefix(Namespace::Kv, &kv_key(collection, b""))
.await?
{
if split_kv_key(&composite).is_some_and(|(coll, _)| coll == collection) {
ops.push(WriteOp::Delete {
ns: Namespace::Kv,
key: composite,
});
}
}
Ok(ops)
}
pub async fn kv_register_index<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
collection: &str,
field: &str,
_backfill: bool,
) -> Result<QueryResult, LiteError> {
declare_index(
engine,
CreateIndexRequest {
name: None,
collection,
field,
unique: false,
case_insensitive: false,
predicate: None,
if_not_exists: true,
},
IndexEngine::KeyValue,
)
.await
}
pub async fn kv_drop_index<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
collection: &str,
field: &str,
) -> Result<QueryResult, LiteError> {
drop_field_index(engine, collection, field).await
}