use super::{BLOCK_BITS, StoreError};
use crate::hashing::{HashMap, HashSet};
use rusqlite::{OptionalExtension, Transaction, params};
const BATCH: usize = 8_000_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Posting<'b> {
pub unit: i64,
pub roles: u32,
blocks: &'b [u8],
rows: &'b [u8],
}
impl<'b> Posting<'b> {
pub(crate) fn rows(&self) -> &'b [u8] {
let mut at = 0;
match read_number(self.rows, &mut at) {
Some(_) => &self.rows[at..],
None => &[],
}
}
pub(crate) fn blocks(&self) -> impl Iterator<Item = u32> + '_ {
let mut at = 0;
let count = read_number(self.blocks, &mut at).unwrap_or_default();
let mut number = 0u32;
(0..count).map(move |_| {
let delta = read_number(self.blocks, &mut at).unwrap_or_default();
number += u32::try_from(delta).unwrap_or_default();
number
})
}
}
pub(super) fn encode(list: &[Posting<'_>]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(4 * list.len());
let mut previous = 0;
for posting in list {
debug_assert!(
posting.unit > previous,
"a list holds its units in order, each once"
);
write_number(
&mut bytes,
u64::try_from(posting.unit - previous).unwrap_or_default(),
);
write_number(&mut bytes, u64::from(posting.roles));
bytes.extend_from_slice(posting.blocks);
bytes.extend_from_slice(posting.rows);
previous = posting.unit;
}
bytes
}
pub(super) fn decode(bytes: &[u8]) -> Option<Vec<Posting<'_>>> {
let mut list = Vec::new();
let (mut at, mut unit) = (0, 0i64);
while at < bytes.len() {
let delta = read_number(bytes, &mut at)?;
let roles = u32::try_from(read_number(bytes, &mut at)?).ok()?;
unit = unit.checked_add(i64::try_from(delta).ok()?)?;
if delta == 0 && !list.is_empty() {
return None;
}
let from = at;
let count = read_number(bytes, &mut at)?;
if count == 0 {
return None;
}
let mut number = 0u64;
for index in 0..count {
let delta = read_number(bytes, &mut at)?;
if delta == 0 && index > 0 {
return None;
}
number = number.checked_add(delta)?;
if number >= 1 << BLOCK_BITS {
return None;
}
}
let blocks = &bytes[from..at];
let from = at;
let length = usize::try_from(read_number(bytes, &mut at)?).ok()?;
at = at.checked_add(length).filter(|end| *end <= bytes.len())?;
list.push(Posting {
unit,
roles,
blocks,
rows: &bytes[from..at],
});
}
Some(list)
}
fn write_number(out: &mut Vec<u8>, mut value: u64) {
loop {
let byte = (value & 0x7f) as u8;
value >>= 7;
if value == 0 {
out.push(byte);
return;
}
out.push(byte | 0x80);
}
}
fn read_number(bytes: &[u8], at: &mut usize) -> Option<u64> {
let mut value = 0u64;
for shift in (0..64).step_by(7) {
let byte = *bytes.get(*at)?;
*at += 1;
value |= u64::from(byte & 0x7f) << shift;
if byte & 0x80 == 0 {
return Some(value);
}
}
None
}
pub(super) struct Pending {
added: HashMap<Box<[u8]>, Added>,
removed: HashMap<Box<[u8]>, Vec<i64>>,
count: usize,
batch: usize,
}
impl Default for Pending {
fn default() -> Self {
Self::with_batch(BATCH)
}
}
#[derive(Default)]
struct Added {
last: i64,
bytes: Vec<u8>,
}
impl Pending {
pub(super) fn with_batch(batch: usize) -> Self {
Self {
added: HashMap::default(),
removed: HashMap::default(),
count: 0,
batch,
}
}
pub(super) fn add(&mut self, name: &[u8], unit: i64, roles: u32, blocks: &[u32], rows: &[u8]) {
let added = match self.added.get_mut(name) {
Some(added) => added,
None => self.added.entry(name.into()).or_default(),
};
debug_assert!(
unit > added.last,
"an update adds units in the order it makes them"
);
debug_assert!(
!blocks.is_empty() && blocks.is_sorted_by(|a, b| a < b),
"a name is listed in some blocks, in order"
);
write_number(
&mut added.bytes,
u64::try_from(unit - added.last).unwrap_or_default(),
);
write_number(&mut added.bytes, u64::from(roles));
write_number(&mut added.bytes, blocks.len() as u64);
let mut previous = 0;
for &block in blocks {
write_number(&mut added.bytes, u64::from(block - previous));
previous = block;
}
write_number(&mut added.bytes, rows.len() as u64);
added.bytes.extend_from_slice(rows);
added.last = unit;
self.count += 1;
}
pub(super) fn remove(&mut self, name: &[u8], unit: i64) {
self.removed.entry(name.into()).or_default().push(unit);
self.count += 1;
}
pub(super) fn batch(&self) -> usize {
self.batch
}
#[cfg(test)]
pub(super) fn is_empty(&self) -> bool {
self.count == 0
}
pub(super) fn full(&self) -> bool {
self.count >= self.batch
}
pub(super) fn store(
&mut self,
transaction: &Transaction<'_>,
fresh: bool,
) -> Result<(), StoreError> {
let added = std::mem::take(&mut self.added);
let removed = std::mem::take(&mut self.removed);
self.count = 0;
let mut names: Vec<(&[u8], Option<&Added>)> = added
.iter()
.map(|(name, added)| (&name[..], Some(added)))
.chain(
removed
.keys()
.filter(|name| !added.contains_key(&name[..]))
.map(|name| (&name[..], None)),
)
.collect();
names.sort_unstable_by(|a, b| a.0.cmp(b.0));
let mut read = transaction.prepare_cached("SELECT postings FROM names WHERE name = ?1")?;
let mut write = transaction
.prepare_cached("INSERT OR REPLACE INTO names (name, postings) VALUES (?1, ?2)")?;
let mut insert =
transaction.prepare_cached("INSERT INTO names (name, postings) VALUES (?1, ?2)")?;
let mut delete = transaction.prepare_cached("DELETE FROM names WHERE name = ?1")?;
for (name, new) in names {
let gone = removed.get(name).map_or(&[][..], Vec::as_slice);
if fresh && gone.is_empty() {
if let Some(new) = new {
insert.execute(params![name, new.bytes])?;
}
continue;
}
let stored: Option<Vec<u8>> = read.query_row([name], |row| row.get(0)).optional()?;
let stored = stored.unwrap_or_default();
let mut list = decode(&stored).ok_or_else(damaged)?;
let gone: HashSet<i64> = gone.iter().copied().collect();
let mut new = match new {
Some(new) => decode(&new.bytes).ok_or_else(damaged)?,
None => Vec::new(),
};
new.retain(|posting| !gone.contains(&posting.unit));
let added: HashSet<i64> = new.iter().map(|posting| posting.unit).collect();
list.retain(|posting| !gone.contains(&posting.unit) && !added.contains(&posting.unit));
list.extend(new);
list.sort_unstable_by_key(|posting| posting.unit);
if list.is_empty() {
delete.execute([name])?;
} else {
write.execute(params![name, encode(&list)])?;
}
}
Ok(())
}
}
pub(super) fn purge(transaction: &Transaction<'_>, units: &HashSet<i64>) -> Result<(), StoreError> {
let mut changed = Vec::new();
{
let mut rows = transaction.prepare("SELECT name, postings FROM names")?;
let mut rows = rows.query([])?;
while let Some(row) = rows.next()? {
let bytes: Vec<u8> = row.get(1)?;
let mut list = decode(&bytes).ok_or_else(damaged)?;
let before = list.len();
list.retain(|posting| !units.contains(&posting.unit));
if list.len() != before {
changed.push((row.get::<_, Vec<u8>>(0)?, encode(&list)));
}
}
}
for (name, list) in changed {
if list.is_empty() {
transaction.execute("DELETE FROM names WHERE name = ?1", [&name])?;
} else {
transaction.execute(
"UPDATE names SET postings = ?2 WHERE name = ?1",
params![name, list],
)?;
}
}
Ok(())
}
pub(super) fn damaged() -> StoreError {
StoreError::Postings
}
pub(super) fn unread(error: StoreError) -> StoreError {
match error {
StoreError::Busy => StoreError::Busy,
_ => StoreError::Postings,
}
}
#[cfg(test)]
mod tests {
use super::*;
type Written = (i64, u32, Vec<u32>, Vec<u8>);
fn listed(bytes: &[u8]) -> Option<Vec<Written>> {
let list = decode(bytes)?;
Some(
list.iter()
.map(|posting| {
let blocks = posting.blocks().collect();
(posting.unit, posting.roles, blocks, posting.rows().to_vec())
})
.collect(),
)
}
#[test]
fn lists_round_trip_and_damage_is_refused() {
let list: Vec<Written> = vec![
(1, 1, vec![0], vec![]),
(2, 1 << 16, vec![3, 4, 900], vec![7; 200]),
(300, 0x2a, vec![0, 1], vec![1, 2, 3]),
(70_000, 1 << 13, vec![(1 << BLOCK_BITS) - 1], vec![]),
];
let mut pending = Pending::default();
for (unit, roles, blocks, rows) in &list {
pending.add(b"name", *unit, *roles, blocks, rows);
}
let bytes = std::mem::take(&mut pending.added.get_mut(&b"name"[..]).unwrap().bytes);
assert_eq!(listed(&bytes), Some(list));
assert_eq!(encode(&decode(&bytes).unwrap()), bytes);
assert_eq!(listed(&[]), Some(Vec::new()));
assert_eq!(decode(&bytes[..bytes.len() - 1]), None);
assert_eq!(
listed(&[5, 1, 2, 0, 2, 1, 9]),
Some(vec![(5, 1, vec![0, 2], vec![9])])
);
assert_eq!(decode(&[5, 1, 1, 0, 0, 0, 1, 1, 0, 0]), None);
assert_eq!(decode(&[5, 1, 0, 0]), None);
assert_eq!(decode(&[5, 1, 2, 3, 0, 0]), None);
assert_eq!(decode(&[5, 1, 1, 0x80, 0x80, 0x40, 0]), None);
assert_eq!(decode(&[5, 1, 1, 0, 2, 9]), None);
assert_eq!(
listed(&[5, 1, 1, 0xff, 0xff, 0x3f, 0]),
Some(vec![(5, 1, vec![(1 << 20) - 1], vec![])])
);
}
}