use yo_kv::Db;
use yo_kv::hash::Text;
use yo_search::Source;
use super::Server;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Change {
Nothing,
Fields,
Taken,
Twice,
}
impl Change {
pub(super) fn when(wrote: bool) -> Self {
if wrote { Self::Fields } else { Self::Nothing }
}
pub(super) fn taken(went: bool) -> Self {
if went { Self::Taken } else { Self::Nothing }
}
fn rounds(self) -> usize {
match self {
Self::Nothing => 0,
Self::Twice => 2,
_ => 1,
}
}
}
#[derive(Debug, Default)]
struct Document {
bytes: Vec<u8>,
ends: Vec<usize>,
}
impl Document {
fn push(&mut self, text: Text<'_>) {
match text {
Text::Str(s) => self.bytes.extend_from_slice(s),
Text::Int(n) => yo_common::num::push_i64(&mut self.bytes, n),
}
self.ends.push(self.bytes.len());
}
fn pairs(&self) -> Vec<(&[u8], &[u8])> {
let mut at = 0;
let mut out = Vec::with_capacity(self.ends.len() / 2);
let mut parts = self.ends.iter().map(|&end| {
let part = &self.bytes[at..end];
at = end;
part
});
while let (Some(field), Some(value)) = (parts.next(), parts.next()) {
out.push((field, value));
}
out
}
}
fn read(db: &Db, key: &[u8]) -> Option<Document> {
let mut doc = Document::default();
let mut held = db.hold(key);
let found = held.hgetall(key, |field, value| {
doc.push(field);
doc.push(value);
});
drop(held);
match found {
Ok(true) => Some(doc),
_ => None,
}
}
pub(super) fn changed(server: &Server, db: usize, key: &[u8], change: Change) {
for _ in 0..change.rounds() {
round(server, db, key, change);
}
}
fn round(server: &Server, db: usize, key: &[u8], change: Change) {
let follows = {
let search = server.search.lock();
if !search.watching() {
return;
}
search.follows(Source::Hash, key)
};
if !follows {
server.search.lock().went(key);
return;
}
let doc = read(&server.dbs[db], key);
let mut search = server.search.lock();
match doc {
Some(doc) => search.wrote(Source::Hash, key, &doc.pairs()),
None if change == Change::Taken => search.vanished(Source::Hash, key),
None => {
search.wrote(Source::Hash, key, &[]);
search.went(key);
}
}
}
pub(super) fn scan(server: &Server, db: usize, name: &[u8]) {
if !server.search.lock().scanning(name) {
return;
}
let mut keys = Vec::new();
server.dbs[db].keys(|key| keys.push(key.to_vec()));
{
let search = server.search.lock();
keys.retain(|key| search.wants(name, Source::Hash, key));
}
for key in keys {
if let Some(doc) = read(&server.dbs[db], &key) {
server
.search
.lock()
.filled(name, Source::Hash, &key, &doc.pairs());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_document_hands_back_the_pairs_it_was_given() {
let mut doc = Document::default();
doc.push(Text::Str(b"t"));
doc.push(Text::Str(b"alpha"));
doc.push(Text::Str(b"n"));
doc.push(Text::Int(-42));
assert_eq!(
doc.pairs(),
vec![(&b"t"[..], &b"alpha"[..]), (&b"n"[..], &b"-42"[..])]
);
}
#[test]
fn a_document_with_nothing_in_it_is_no_pairs() {
assert!(Document::default().pairs().is_empty());
let mut odd = Document::default();
odd.push(Text::Str(b"t"));
assert!(odd.pairs().is_empty());
}
}