use std::collections::HashMap;
use loro::VersionVector;
use nodedb_types::result::QueryResult;
use nodedb_types::value::Value;
use crate::error::LiteError;
use crate::query::engine::LiteQueryEngine;
use crate::storage::engine::StorageEngine;
fn parse_version_vector(json: &str) -> Result<VersionVector, LiteError> {
let map: HashMap<String, i64> =
sonic_rs::from_str(json).map_err(|e| LiteError::BadRequest {
detail: format!("invalid version vector JSON: {e}"),
})?;
let pairs: Vec<(u64, i32)> = map
.into_iter()
.map(|(hex, counter)| {
let peer = u64::from_str_radix(&hex, 16).map_err(|e| LiteError::BadRequest {
detail: format!("peer ID '{hex}' is not valid hex: {e}"),
})?;
let counter_i32 = i32::try_from(counter).map_err(|_| LiteError::BadRequest {
detail: format!("peer '{hex}' counter {counter} exceeds Loro's i32 Counter range"),
})?;
Ok((peer, counter_i32))
})
.collect::<Result<Vec<_>, LiteError>>()?;
Ok(pairs.into_iter().collect())
}
fn missing_collection(op: &str, collection: &str) -> LiteError {
LiteError::BadRequest {
detail: format!("{op}: CRDT collection '{collection}' does not exist"),
}
}
pub async fn handle_read_at_version<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
collection: &str,
document_id: &str,
version_vector_json: &str,
) -> Result<QueryResult, LiteError> {
let vv = parse_version_vector(version_vector_json)?;
let crdt = engine.crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
let state = crdt
.state(collection)
.ok_or_else(|| missing_collection("ReadAtVersion", collection))?;
let value = state
.read_at_version(collection, document_id, &vv)
.map_err(|e| LiteError::crdt("ReadAtVersion", e))?;
let row = match value {
Some(v) => {
let json = sonic_rs::to_string(&v).map_err(|e| LiteError::Serialization {
detail: format!("ReadAtVersion serialize: {e}"),
})?;
vec![vec![Value::String(json)]]
}
None => vec![],
};
Ok(QueryResult {
columns: vec!["document".to_string()],
rows: row,
rows_affected: 0,
command: None,
})
}
pub async fn handle_get_version_vector<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
) -> Result<QueryResult, LiteError> {
let crdt = engine.crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
let clock = crdt.export_vector_clock();
let json = sonic_rs::to_string(&clock).map_err(|e| LiteError::Serialization {
detail: format!("GetVersionVector serialize: {e}"),
})?;
Ok(QueryResult {
columns: vec!["version_vector_json".to_string()],
rows: vec![vec![Value::String(json)]],
rows_affected: 0,
command: None,
})
}
pub async fn handle_export_delta<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
collection: &str,
from_version_json: &str,
) -> Result<QueryResult, LiteError> {
let vv = parse_version_vector(from_version_json)?;
let crdt = engine.crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
if crdt.state(collection).is_none() {
return Err(missing_collection("ExportDelta", collection));
}
let delta_bytes = crdt.export_delta_from(collection, &vv)?;
Ok(QueryResult {
columns: vec!["delta_bytes".to_string()],
rows: vec![vec![Value::Bytes(delta_bytes)]],
rows_affected: 0,
command: None,
})
}
pub async fn handle_restore_to_version<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
collection: &str,
document_id: &str,
target_version_json: &str,
) -> Result<QueryResult, LiteError> {
handle_restore_to_version_coordinated(
engine,
None,
collection,
document_id,
target_version_json,
)
.await
}
pub(crate) async fn handle_restore_to_version_coordinated<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
permit: Option<&crate::engine::fts::coordinator::TextMutationPermit>,
collection: &str,
document_id: &str,
target_version_json: &str,
) -> Result<QueryResult, LiteError> {
if let Some(permit) = permit {
return handle_restore_to_version_admitted(
engine,
permit,
collection,
document_id,
target_version_json,
)
.await;
}
let guard = engine.fts_state.admit_mutation().await;
let result = handle_restore_to_version_admitted(
engine,
guard.permit(),
collection,
document_id,
target_version_json,
)
.await;
guard.finish(result)
}
pub(crate) async fn handle_restore_to_version_admitted<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
permit: &crate::engine::fts::coordinator::TextMutationPermit,
collection: &str,
document_id: &str,
target_version_json: &str,
) -> Result<QueryResult, LiteError> {
let _permit = permit;
let vv = parse_version_vector(target_version_json)?;
let delta_bytes = {
let mut crdt = engine.crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
if crdt.state(collection).is_none() {
return Err(missing_collection("RestoreToVersion", collection));
}
crdt.restore_to_version(collection, document_id, &vv)?
};
crate::query::text_index::reindex_documents(engine, collection, [document_id])?;
Ok(QueryResult {
columns: vec!["delta_bytes".to_string()],
rows: vec![vec![Value::Bytes(delta_bytes)]],
rows_affected: 1,
command: None,
})
}
pub async fn handle_compact_at_version<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
collection: &str,
target_version_json: &str,
) -> Result<QueryResult, LiteError> {
handle_compact_at_version_coordinated(engine, None, collection, target_version_json).await
}
pub(crate) async fn handle_compact_at_version_coordinated<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
permit: Option<&crate::engine::fts::coordinator::TextMutationPermit>,
collection: &str,
target_version_json: &str,
) -> Result<QueryResult, LiteError> {
if let Some(permit) = permit {
return handle_compact_at_version_admitted(engine, permit, collection, target_version_json)
.await;
}
let guard = engine.fts_state.admit_mutation().await;
let result =
handle_compact_at_version_admitted(engine, guard.permit(), collection, target_version_json)
.await;
guard.finish(result)
}
pub(crate) async fn handle_compact_at_version_admitted<S: StorageEngine>(
engine: &LiteQueryEngine<S>,
permit: &crate::engine::fts::coordinator::TextMutationPermit,
collection: &str,
target_version_json: &str,
) -> Result<QueryResult, LiteError> {
let _permit = permit;
let vv = parse_version_vector(target_version_json)?;
let mut crdt = engine.crdt.lock().map_err(|_| LiteError::LockPoisoned)?;
if crdt.state(collection).is_none() {
return Err(missing_collection("CompactAtVersion", collection));
}
crdt.compact_at_version(collection, &vv)?;
Ok(QueryResult {
columns: vec![],
rows: vec![],
rows_affected: 1,
command: None,
})
}