use std::{
collections::{HashMap, HashSet},
ops::Bound,
};
use crate::{
error::{Error, Result},
store::{
handle::ReadHandle,
index_encoding::CanonicalKey,
key::{IndexKey, IndexKind, Key, index_index_prefix, index_multi_value_prefix},
},
transaction::commit::StagedWrite,
};
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct StagedIndexEntry {
pub(crate) index_id: u64,
pub(crate) unique: bool,
pub(crate) key: CanonicalKey,
pub(crate) row_id: u64,
pub(crate) delete: bool,
}
fn entry_key(entry: &StagedIndexEntry) -> Key {
if entry.unique {
Key::Index(IndexKey::Unique {
index_id: entry.index_id,
key: entry.key.clone(),
})
} else {
Key::Index(IndexKey::Multi {
index_id: entry.index_id,
key: entry.key.clone(),
row_id: entry.row_id,
})
}
}
fn decode_row_id(bytes: &[u8]) -> Result<u64> {
let array: [u8; 8] = bytes.try_into().map_err(|_| {
Error::Corruption(format!(
"index entry value is {} bytes, expected 8",
bytes.len()
))
})?;
Ok(u64::from_be_bytes(array))
}
pub(crate) async fn stage_index_entries(
reader: ReadHandle<'_>,
entries: &[StagedIndexEntry],
writes: &mut Vec<StagedWrite>,
) -> Result<()> {
let mut deleted_unique: HashSet<Vec<u8>> = HashSet::new();
for entry in entries.iter().filter(|entry| entry.delete) {
let key_bytes = entry_key(entry).encode();
if entry.unique {
deleted_unique.insert(key_bytes.clone());
}
writes.push((key_bytes, None));
}
let mut staged_unique: HashMap<Vec<u8>, u64> = HashMap::new();
for entry in entries.iter().filter(|entry| !entry.delete) {
let key_bytes = entry_key(entry).encode();
if !entry.unique {
writes.push((key_bytes, Some(Vec::new())));
continue;
}
if let Some(&existing) = staged_unique.get(&key_bytes) {
if existing != entry.row_id {
return Err(unique_violation(entry.index_id));
}
continue;
}
let present = if deleted_unique.contains(&key_bytes) {
None
} else {
reader.get(key_bytes.clone()).await.map_err(Error::from)?
};
if let Some(bytes) = present {
if decode_row_id(&bytes)? != entry.row_id {
return Err(unique_violation(entry.index_id));
}
continue;
}
writes.push((key_bytes.clone(), Some(entry.row_id.to_be_bytes().to_vec())));
staged_unique.insert(key_bytes, entry.row_id);
}
Ok(())
}
fn unique_violation(index_id: u64) -> Error {
Error::Constraint(format!(
"duplicate value violates equality index {index_id}"
))
}
pub(crate) async fn reclaim_entries(
tx: &slatedb::DbTransaction,
index_id: u64,
limit: usize,
) -> Result<usize> {
let mut deleted = 0;
for kind in [IndexKind::Unique, IndexKind::Multi] {
if deleted >= limit {
break;
}
let (batch, _) = reclaim_entries_from(tx, kind, index_id, limit - deleted, None).await?;
deleted += batch;
}
Ok(deleted)
}
pub(crate) async fn reclaim_entries_from(
tx: &slatedb::DbTransaction,
kind: IndexKind,
index_id: u64,
limit: usize,
start_from: Option<&[u8]>,
) -> Result<(usize, Option<Vec<u8>>)> {
let prefix = index_index_prefix(kind, index_id);
let suffix = match start_from {
Some(key) if key.len() >= prefix.len() => key[prefix.len()..].to_vec(),
_ => Vec::new(),
};
let mut iter = ReadHandle::Tx(tx).scan_prefix(prefix, suffix..).await?;
let mut deleted = 0;
let mut last = None;
while deleted < limit {
match iter.next().await? {
Some(entry) => {
let key = entry.key.to_vec();
tx.delete(entry.key)?;
deleted += 1;
last = Some(key);
}
None => break,
}
}
Ok((deleted, last))
}
pub(crate) async fn lookup_row_ids(
reader: ReadHandle<'_>,
index_id: u64,
unique: bool,
key: &CanonicalKey,
) -> Result<Vec<u64>> {
if unique {
let entry_key = Key::Index(IndexKey::Unique {
index_id,
key: key.clone(),
})
.encode();
return match reader.get(entry_key).await.map_err(Error::from)? {
Some(bytes) => Ok(vec![decode_row_id(&bytes)?]),
None => Ok(Vec::new()),
};
}
let prefix = index_multi_value_prefix(index_id, key);
let mut iter = reader.scan_prefix(prefix, ..).await.map_err(Error::from)?;
let mut row_ids = Vec::new();
while let Some(entry) = iter.next().await.map_err(Error::from)? {
match Key::decode(&entry.key)? {
Key::Index(IndexKey::Multi { row_id, .. }) => row_ids.push(row_id),
other => {
return Err(Error::Corruption(format!(
"non-multi key in index scan: {other:?}"
)));
}
}
}
Ok(row_ids)
}
pub(crate) async fn range_row_ids(
reader: ReadHandle<'_>,
index_id: u64,
unique: bool,
lower: Bound<CanonicalKey>,
upper: Bound<CanonicalKey>,
) -> Result<Vec<u64>> {
let kind = if unique {
IndexKind::Unique
} else {
IndexKind::Multi
};
let prefix = index_index_prefix(kind, index_id);
let prefix_len = prefix.len();
let suffix = |canon: &CanonicalKey| -> Vec<u8> {
let full = match kind {
IndexKind::Unique => Key::Index(IndexKey::Unique {
index_id,
key: canon.clone(),
})
.encode(),
IndexKind::Multi => index_multi_value_prefix(index_id, canon),
};
full[prefix_len..].to_vec()
};
let start = match lower {
Bound::Included(canon) => Bound::Included(suffix(&canon)),
Bound::Excluded(canon) => match increment_prefix(&suffix(&canon)) {
Some(above) => Bound::Included(above),
None => Bound::Excluded(suffix(&canon)),
},
Bound::Unbounded => Bound::Unbounded,
};
let end = match upper {
Bound::Included(canon) => match increment_prefix(&suffix(&canon)) {
Some(above) => Bound::Excluded(above),
None => Bound::Unbounded,
},
Bound::Excluded(canon) => Bound::Excluded(suffix(&canon)),
Bound::Unbounded => Bound::Unbounded,
};
let mut iter = reader
.scan_prefix(prefix, (start, end))
.await
.map_err(Error::from)?;
let mut row_ids = Vec::new();
while let Some(entry) = iter.next().await.map_err(Error::from)? {
if unique {
row_ids.push(decode_row_id(entry.value.as_ref())?);
} else {
match Key::decode(&entry.key)? {
Key::Index(IndexKey::Multi { row_id, .. }) => row_ids.push(row_id),
other => {
return Err(Error::Corruption(format!(
"non-multi key in index range scan: {other:?}"
)));
}
}
}
}
Ok(row_ids)
}
pub(crate) async fn null_prefix_row_ids(
reader: ReadHandle<'_>,
index_id: u64,
prefix: &CanonicalKey,
) -> Result<Vec<u64>> {
let mut scan_prefix = index_multi_value_prefix(index_id, prefix);
scan_prefix.pop();
let mut iter = reader
.scan_prefix(scan_prefix, ..)
.await
.map_err(Error::from)?;
let mut row_ids = Vec::new();
while let Some(entry) = iter.next().await.map_err(Error::from)? {
match Key::decode(&entry.key)? {
Key::Index(IndexKey::Multi { row_id, .. }) => row_ids.push(row_id),
other => {
return Err(Error::Corruption(format!(
"non-multi key in null-prefix scan: {other:?}"
)));
}
}
}
Ok(row_ids)
}
fn increment_prefix(prefix: &[u8]) -> Option<Vec<u8>> {
let mut bytes = prefix.to_vec();
while let Some(last) = bytes.last_mut() {
if *last != u8::MAX {
*last += 1;
return Some(bytes);
}
bytes.pop();
}
None
}