use super::{
attach_collects_with_budget, reconstruct, Lmpg, QueryError, QuerySnapshot, SearchRequest,
};
#[must_use]
pub fn effective_budget(work_budget: Option<usize>, config: u64) -> Option<u64> {
match work_budget {
Some(bytes) => Some(u64::try_from(bytes).unwrap_or(u64::MAX)),
None => (config != 0).then_some(config),
}
}
pub(super) fn charge_record(
budget: Option<u64>,
charged: &mut u64,
reservation: Option<&mut rushdb_storage::resources::ByteReservation>,
record: &serde_json::Value,
) -> Result<(), QueryError> {
if let Some(limit) = budget {
let bytes =
serde_json::to_vec(record).map_or(0, |v| u64::try_from(v.len()).unwrap_or(u64::MAX));
*charged = charged.saturating_add(bytes);
if let Some(reservation) = reservation {
let _ = reservation.resize(usize::try_from(*charged).unwrap_or(usize::MAX));
}
if *charged > limit {
return Err(QueryError::ResourceLimit(format!(
"query working set exceeded {limit} bytes"
)));
}
}
Ok(())
}
pub(super) fn reconstruct_page_with_budget(
lmpg: &Lmpg,
req: &SearchRequest,
is_cancelled: &dyn Fn() -> bool,
snap: &QuerySnapshot,
page_roots: Vec<u64>,
budget: Option<u64>,
) -> Result<Vec<serde_json::Value>, QueryError> {
let mut charged: u64 = 0;
let mut reservation = lmpg
.store
.graph_phase_budget()
.and_then(|phase_budget| phase_budget.try_reserve(0).ok());
let mut session = reconstruct::ReconstructSession::new(snap, lmpg.tenant);
let mut records = Vec::with_capacity(page_roots.len());
for ord in page_roots {
if is_cancelled() {
return Err(QueryError::Cancelled);
}
let Some(json) = session.reconstruct_record(ord)? else {
continue;
};
charge_record(budget, &mut charged, reservation.as_mut(), &json)?;
let remaining = budget
.map(|limit| usize::try_from(limit.saturating_sub(charged)).unwrap_or(usize::MAX));
let record = attach_collects_with_budget(
lmpg,
snap,
ord,
json,
&req.collects,
remaining,
is_cancelled,
)?;
charge_record(budget, &mut charged, reservation.as_mut(), &record)?;
records.push(record);
}
Ok(records)
}