use std::collections::BTreeMap;
use std::sync::Arc;
use uqa_core::{memory::BudgetedVec, DocId};
use uqa_storage::mvcc::{PrivateRecordChanges, PrivateRecordSnapshot, RecordWrite, VersionError};
use uqa_storage::read_control::StorageReadControl;
use uqa_storage::{StorageBackendError, StorageBackendResult, StorageSavepointId};
pub type RelationGeneration = [u8; 16];
const KEY_BYTES: usize = 16 + size_of::<u64>();
const PAGE_ENTRIES: usize = 1024;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ChangedRowCounts {
pub changed: u64,
pub before: u64,
pub present: u64,
}
impl ChangedRowCounts {
pub fn visible_rows(self, snapshot_rows: u64) -> Option<u64> {
snapshot_rows
.checked_sub(self.before)?
.checked_add(self.present)
}
}
fn key(generation: &RelationGeneration, id: DocId) -> [u8; KEY_BYTES] {
let mut key = [0; KEY_BYTES];
key[..16].copy_from_slice(generation);
key[16..].copy_from_slice(&id.to_be_bytes());
key
}
fn invalid(message: &str) -> StorageBackendError {
StorageBackendError::Other(message.into())
}
fn decode_entry(value: Option<&[u8]>) -> StorageBackendResult<(bool, bool)> {
match value {
Some([present, before]) => Ok((*present == 1, *before == 1)),
_ => Err(invalid("a changed row entry is malformed")),
}
}
pub struct ChangedIdentities {
records: PrivateRecordChanges,
counts: BTreeMap<RelationGeneration, ChangedRowCounts>,
savepoints: Vec<(
StorageSavepointId,
BTreeMap<RelationGeneration, ChangedRowCounts>,
)>,
view: Arc<PrivateRecordSnapshot>,
current: bool,
}
impl ChangedIdentities {
pub fn new(control: &StorageReadControl) -> StorageBackendResult<Self> {
let records = PrivateRecordChanges::new(control.memory());
let view = Arc::new(
records
.snapshot()
.map_err(VersionError::into_storage_error)?,
);
Ok(Self {
records,
counts: BTreeMap::new(),
savepoints: Vec::new(),
view,
current: true,
})
}
pub fn note(
&mut self,
generation: &RelationGeneration,
id: DocId,
before: bool,
present: bool,
control: &StorageReadControl,
) -> StorageBackendResult<()> {
let key = key(generation, id);
let previous = self
.snapshot()?
.get(&key, control)
.map_err(VersionError::into_storage_error)?
.map(|write| decode_entry(write.value()))
.transpose()?;
let before = previous.map_or(before, |(_, before)| before);
let value = [u8::from(present), u8::from(before)];
self.records
.apply(
&[RecordWrite {
key: &key,
expected: None,
value: Some(&value),
}],
control,
)
.map_err(VersionError::into_storage_error)?;
self.current = false;
let counts = self.counts.entry(*generation).or_default();
match previous {
None => {
counts.changed += 1;
counts.before += u64::from(before);
counts.present += u64::from(present);
}
Some((was_present, _)) => {
counts.present = counts.present + u64::from(present) - u64::from(was_present);
}
}
Ok(())
}
fn snapshot(&mut self) -> StorageBackendResult<&Arc<PrivateRecordSnapshot>> {
if !self.current {
self.view = Arc::new(
self.records
.snapshot()
.map_err(VersionError::into_storage_error)?,
);
self.current = true;
}
Ok(&self.view)
}
pub fn savepoint(&mut self, id: StorageSavepointId) -> StorageBackendResult<()> {
self.records
.savepoint(id)
.map_err(VersionError::into_storage_error)?;
self.savepoints.push((id, self.counts.clone()));
Ok(())
}
pub fn release(&mut self, id: StorageSavepointId) -> StorageBackendResult<bool> {
let Some(position) = self.savepoints.iter().rposition(|(saved, _)| *saved == id) else {
return Ok(false);
};
self.records
.release_savepoint(id)
.map_err(VersionError::into_storage_error)?;
self.savepoints.truncate(position);
Ok(true)
}
pub fn rollback_to(&mut self, id: StorageSavepointId) -> StorageBackendResult<bool> {
let Some(position) = self.savepoints.iter().rposition(|(saved, _)| *saved == id) else {
return Ok(false);
};
self.records
.rollback_to_savepoint(id)
.map_err(VersionError::into_storage_error)?;
self.counts.clone_from(&self.savepoints[position].1);
self.savepoints.truncate(position + 1);
self.current = false;
Ok(true)
}
pub fn counts(&self, generation: &RelationGeneration) -> ChangedRowCounts {
self.counts.get(generation).copied().unwrap_or_default()
}
pub fn view(
&mut self,
generation: &RelationGeneration,
) -> StorageBackendResult<Option<ChangedIdentitiesView>> {
let counts = self.counts(generation);
if counts.changed == 0 {
return Ok(None);
}
Ok(Some(ChangedIdentitiesView {
snapshot: Arc::clone(self.snapshot()?),
generation: *generation,
counts,
}))
}
}
#[derive(Clone)]
pub struct ChangedIdentitiesView {
snapshot: Arc<PrivateRecordSnapshot>,
generation: RelationGeneration,
counts: ChangedRowCounts,
}
impl ChangedIdentitiesView {
pub fn counts(&self) -> ChangedRowCounts {
self.counts
}
pub(crate) fn presence(
&self,
id: DocId,
control: &StorageReadControl,
) -> StorageBackendResult<Option<bool>> {
self.snapshot
.get(&key(&self.generation, id), control)
.map_err(VersionError::into_storage_error)?
.map(|write| decode_entry(write.value()).map(|(present, _)| present))
.transpose()
}
pub(crate) fn cursor(&self, after: Option<DocId>) -> IdentityCursor {
IdentityCursor {
view: self.clone(),
page: None,
position: 0,
resume: after,
done: false,
}
}
fn page(
&self,
after: Option<DocId>,
control: &StorageReadControl,
) -> StorageBackendResult<(BudgetedVec<(DocId, bool)>, bool)> {
let after = after.map(|id| key(&self.generation, id));
let mut rows = BudgetedVec::new(control.memory());
let mut full = false;
self.snapshot
.visit(
&self.generation,
after.as_ref().map(<[u8; KEY_BYTES]>::as_slice),
control,
&mut |write| {
let id = write.key()[16..]
.try_into()
.map(DocId::from_be_bytes)
.map_err(|_| {
VersionError::from(invalid("a changed row key is malformed"))
})?;
let (present, _) = decode_entry(write.value()).map_err(VersionError::from)?;
rows.push((id, present))?;
full = rows.len() == PAGE_ENTRIES;
Ok(!full)
},
)
.map_err(VersionError::into_storage_error)?;
Ok((rows, full))
}
}
pub(crate) struct IdentityCursor {
view: ChangedIdentitiesView,
page: Option<BudgetedVec<(DocId, bool)>>,
position: usize,
resume: Option<DocId>,
done: bool,
}
impl IdentityCursor {
pub(crate) fn next(
&mut self,
control: &StorageReadControl,
) -> StorageBackendResult<Option<(DocId, bool)>> {
loop {
control.check()?;
if let Some(entry) = self.page.as_ref().and_then(|page| page.get(self.position)) {
self.position += 1;
self.resume = Some(entry.0);
return Ok(Some(*entry));
}
if self.done {
return Ok(None);
}
self.page = None;
let (page, full) = self.view.page(self.resume, control)?;
self.done = !full;
self.page = Some(page);
self.position = 0;
}
}
}
#[cfg(test)]
mod tests;