use uqa_core::memory::BudgetedVec;
use super::{
commit::{PreparedWritesBuilder, RecordWriteKind},
resolution::ResolutionMode,
CommittedRecordSnapshot, PreparedRecordCommit, VersionError, VersionResult,
};
use crate::{read_control::StorageReadControl, statistics_maintenance::StatisticsMaintenance};
pub trait MaintenanceRecordLayout: Send + Sync {
fn decode(
&self,
key: &[u8],
value: &[u8],
control: &StorageReadControl,
) -> VersionResult<StatisticsMaintenance>;
fn encode(
&self,
key: &[u8],
template: &[u8],
state: &StatisticsMaintenance,
control: &StorageReadControl,
) -> VersionResult<BudgetedVec<u8>>;
}
pub(super) fn resolve(
original: &PreparedRecordCommit,
base: &dyn CommittedRecordSnapshot,
current: &dyn CommittedRecordSnapshot,
layout: &dyn MaintenanceRecordLayout,
mode: ResolutionMode,
control: &StorageReadControl,
) -> VersionResult<PreparedRecordCommit> {
let mut writes = PreparedWritesBuilder::like(original, control)?;
let mut originals = original.writes();
while let Some(write) = originals.next(control)? {
if write.kind() != RecordWriteKind::StatisticsMaintenance {
writes.push(write, control)?;
continue;
}
let value = write.value().ok_or(VersionError::InvalidEncoding(
"statistics maintenance cannot stage a typed deletion",
))?;
let prior = base.get(write.key(), control)?;
let latest = current.get(write.key(), control)?;
let decode = |record: Option<&super::RecordVersion<super::SharedRecordValue>>| {
record
.and_then(|record| record.value())
.map(|value| layout.decode(write.key(), value, control))
.transpose()
.map(Option::unwrap_or_default)
};
let before = decode(prior.as_ref())?;
let after = layout.decode(write.key(), value, control)?;
let latest_state = decode(latest.as_ref())?;
let deleted = latest
.as_ref()
.is_none_or(|record| record.value().is_none())
&& prior.as_ref().map(super::RecordVersion::sequence)
!= latest.as_ref().map(super::RecordVersion::sequence);
let merged = if deleted {
None
} else {
StatisticsMaintenance::merge(&before, &after, &latest_state)?
};
let Some(merged) = merged else {
writes.push(write.clone().with_kind(RecordWriteKind::Canonical), control)?;
continue;
};
let encoded = layout.encode(write.key(), value, &merged, control)?;
writes.push(
write
.with_value(encoded)
.rebase(latest.as_ref().map(super::RecordVersion::sequence))
.with_kind(mode.kind(RecordWriteKind::StatisticsMaintenance)),
control,
)?;
}
Ok(writes
.finish(control)?
.resolved(original, current.sequence()))
}