#![forbid(unsafe_code)]
use std::collections::BTreeSet;
use obj_core::btree::BTree;
use obj_core::index::{extract_index_keys, EncodedIndexKey};
use obj_core::pager::page::PageId;
use obj_core::pager::Pager;
use obj_core::platform::FileHandle;
use obj_core::{
CollectionDescriptor, Document, Error, Id, IndexDescriptor, IndexKind, IndexSpec, IndexStatus,
Result,
};
pub(crate) fn apply_doc_change<T: Document>(
pager: &mut Pager<FileHandle>,
descriptor: &mut CollectionDescriptor,
old: Option<&T>,
new: Option<&T>,
id: Id,
) -> Result<()> {
let active_indexes: Vec<usize> = descriptor
.indexes
.iter()
.enumerate()
.filter_map(|(i, d)| (d.status == IndexStatus::Active).then_some(i))
.collect();
for idx in active_indexes {
maintain_one_index::<T>(pager, descriptor, idx, old, new, id)?;
}
Ok(())
}
fn maintain_one_index<T: Document>(
pager: &mut Pager<FileHandle>,
descriptor: &mut CollectionDescriptor,
idx: usize,
old: Option<&T>,
new: Option<&T>,
id: Id,
) -> Result<()> {
let collection_name = T::COLLECTION;
let spec = descriptor_to_spec(&descriptor.indexes[idx])?;
let old_keys = match old {
Some(doc) => extract_index_keys(collection_name, &spec, doc)?,
None => Vec::new(),
};
let new_keys = match new {
Some(doc) => extract_index_keys(collection_name, &spec, doc)?,
None => Vec::new(),
};
maintain_index_from_keys(
pager,
descriptor,
idx,
collection_name,
&spec,
&old_keys,
&new_keys,
id,
)
}
#[allow(clippy::too_many_arguments)] pub(crate) fn maintain_index_from_keys(
pager: &mut Pager<FileHandle>,
descriptor: &mut CollectionDescriptor,
idx: usize,
collection_name: &str,
spec: &IndexSpec,
old_keys: &[EncodedIndexKey],
new_keys: &[EncodedIndexKey],
id: Id,
) -> Result<()> {
let id_suffix = id.get().to_be_bytes();
let mut tree = open_index_tree(pager, &descriptor.indexes[idx])?;
match spec.kind {
IndexKind::Unique => {
apply_unique_diff(
pager,
&mut tree,
collection_name,
spec,
old_keys,
new_keys,
&id_suffix,
)?;
}
IndexKind::Standard | IndexKind::Each | IndexKind::Composite => {
apply_nonunique_diff(pager, &mut tree, old_keys, new_keys, &id_suffix)?;
}
_ => return Err(Error::InvalidArgument("unsupported index kind")),
}
let new_root = tree.root().get();
if new_root != descriptor.indexes[idx].root_page_id {
descriptor.indexes[idx].root_page_id = new_root;
}
Ok(())
}
fn apply_unique_diff(
pager: &mut Pager<FileHandle>,
tree: &mut BTree<FileHandle>,
collection: &str,
spec: &IndexSpec,
old_keys: &[EncodedIndexKey],
new_keys: &[EncodedIndexKey],
id_suffix: &[u8],
) -> Result<()> {
debug_assert!(old_keys.len() <= 1, "unique old key set must be 0..=1");
debug_assert!(new_keys.len() <= 1, "unique new key set must be 0..=1");
let old = old_keys.first().map(EncodedIndexKey::as_bytes);
let new = new_keys.first().map(EncodedIndexKey::as_bytes);
if old == new {
return Ok(());
}
if let Some(new_bytes) = new {
if let Some(existing_value) = tree.get(pager, new_bytes)? {
if existing_value.as_slice() != id_suffix {
return Err(Error::UniqueConstraintViolation {
collection: collection.to_owned(),
index: spec.name.clone(),
key: new_bytes.to_vec(),
});
}
}
}
if let Some(old_bytes) = old {
let _ = tree.delete(pager, old_bytes)?;
}
if let Some(new_bytes) = new {
tree.insert(pager, new_bytes, id_suffix)?;
}
Ok(())
}
fn apply_nonunique_diff(
pager: &mut Pager<FileHandle>,
tree: &mut BTree<FileHandle>,
old_keys: &[EncodedIndexKey],
new_keys: &[EncodedIndexKey],
id_suffix: &[u8],
) -> Result<()> {
let old_set: BTreeSet<Vec<u8>> = old_keys
.iter()
.map(|k| append_id_suffix(k.as_bytes(), id_suffix))
.collect();
let new_set: BTreeSet<Vec<u8>> = new_keys
.iter()
.map(|k| append_id_suffix(k.as_bytes(), id_suffix))
.collect();
for to_delete in old_set.difference(&new_set) {
let _ = tree.delete(pager, to_delete)?;
}
for to_insert in new_set.difference(&old_set) {
tree.insert(pager, to_insert, id_suffix)?;
}
Ok(())
}
fn append_id_suffix(user_key: &[u8], id_suffix: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(user_key.len() + id_suffix.len());
out.extend_from_slice(user_key);
out.extend_from_slice(id_suffix);
out
}
fn open_index_tree(
pager: &Pager<FileHandle>,
descriptor: &IndexDescriptor,
) -> Result<BTree<FileHandle>> {
let root = PageId::new(descriptor.root_page_id)
.ok_or(Error::InvalidArgument("index descriptor root is zero"))?;
BTree::<FileHandle>::open(pager, root)
}
pub(crate) fn descriptor_to_spec(d: &IndexDescriptor) -> Result<IndexSpec> {
IndexSpec::from_parts(d.name.clone(), d.kind, d.key_paths.clone())
}