use byteorder::{ByteOrder, LittleEndian, ReadBytesExt};
use std::fmt;
use std::hash::Hasher;
use std::io::Cursor;
use twox_hash::XxHash32;
use super::identifiers::*;
pub struct StoreKeyerBuilder;
pub struct StoreKeyer {
key: StoreKeyerKey,
}
pub struct StoreKeyerHasher;
enum StoreKeyerIdx<'a> {
MetaToValue(&'a StoreMetaKey),
TermToIIDs(StoreTermHashed),
OIDToIID(StoreObjectOID<'a>),
IIDToOID(StoreObjectIID),
IIDToTerms(StoreObjectIID),
}
pub type StoreKeyerKey = [u8; 9];
pub type StoreKeyerPrefix = [u8; 5];
impl<'a> StoreKeyerIdx<'a> {
pub fn to_index(&self) -> u8 {
match self {
StoreKeyerIdx::MetaToValue(_) => 0,
StoreKeyerIdx::TermToIIDs(_) => 1,
StoreKeyerIdx::OIDToIID(_) => 2,
StoreKeyerIdx::IIDToOID(_) => 3,
StoreKeyerIdx::IIDToTerms(_) => 4,
}
}
}
impl StoreKeyerBuilder {
pub fn meta_to_value<'a>(bucket: &'a str, meta: &'a StoreMetaKey) -> StoreKeyer {
Self::make(StoreKeyerIdx::MetaToValue(meta), bucket)
}
pub fn term_to_iids(bucket: &str, term_hash: StoreTermHashed) -> StoreKeyer {
Self::make(StoreKeyerIdx::TermToIIDs(term_hash), bucket)
}
pub fn oid_to_iid<'a>(bucket: &'a str, oid: StoreObjectOID<'a>) -> StoreKeyer {
Self::make(StoreKeyerIdx::OIDToIID(oid), bucket)
}
pub fn iid_to_oid(bucket: &str, iid: StoreObjectIID) -> StoreKeyer {
Self::make(StoreKeyerIdx::IIDToOID(iid), bucket)
}
pub fn iid_to_terms(bucket: &str, iid: StoreObjectIID) -> StoreKeyer {
Self::make(StoreKeyerIdx::IIDToTerms(iid), bucket)
}
fn make<'a>(idx: StoreKeyerIdx<'a>, bucket: &'a str) -> StoreKeyer {
StoreKeyer {
key: Self::build_key(idx, bucket),
}
}
fn build_key<'a>(idx: StoreKeyerIdx<'a>, bucket: &'a str) -> StoreKeyerKey {
let (mut bucket_encoded, mut route_encoded) = ([0; 4], [0; 4]);
LittleEndian::write_u32(&mut bucket_encoded, StoreKeyerHasher::to_compact(bucket));
LittleEndian::write_u32(&mut route_encoded, Self::route_to_compact(&idx));
[
idx.to_index(),
bucket_encoded[0],
bucket_encoded[1],
bucket_encoded[2],
bucket_encoded[3],
route_encoded[0],
route_encoded[1],
route_encoded[2],
route_encoded[3],
]
}
fn route_to_compact(idx: &StoreKeyerIdx) -> u32 {
match idx {
StoreKeyerIdx::MetaToValue(route) => route.as_u32(),
StoreKeyerIdx::TermToIIDs(route) => *route,
StoreKeyerIdx::OIDToIID(route) => StoreKeyerHasher::to_compact(route),
StoreKeyerIdx::IIDToOID(route) => *route,
StoreKeyerIdx::IIDToTerms(route) => *route,
}
}
}
impl StoreKeyer {
pub fn as_bytes(&self) -> StoreKeyerKey {
self.key
}
pub fn as_prefix(&self) -> StoreKeyerPrefix {
[
self.key[0],
self.key[1],
self.key[2],
self.key[3],
self.key[4],
]
}
}
impl StoreKeyerHasher {
#![allow(clippy::wrong_self_convention)]
pub fn to_compact(part: &str) -> u32 {
let mut hasher = XxHash32::with_seed(0);
hasher.write(part.as_bytes());
hasher.finish() as u32
}
}
impl fmt::Display for StoreKeyer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (key_bucket, key_route) = (
Cursor::new(&self.key[1..5])
.read_u32::<LittleEndian>()
.unwrap_or(0),
Cursor::new(&self.key[5..9])
.read_u32::<LittleEndian>()
.unwrap_or(0),
);
write!(
f,
"'{}:{:x?}:{:x?}' {:?}",
self.key[0], key_bucket, key_route, self.key
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_keys_meta_to_value() {
assert_eq!(
StoreKeyerBuilder::meta_to_value("bucket:1", &StoreMetaKey::IIDIncr).as_bytes(),
[0, 108, 244, 29, 93, 0, 0, 0, 0]
);
}
#[test]
fn it_keys_term_to_iids() {
assert_eq!(
StoreKeyerBuilder::term_to_iids("bucket:2", 772137347).as_bytes(),
[1, 50, 220, 166, 65, 131, 225, 5, 46]
);
assert_eq!(
StoreKeyerBuilder::term_to_iids("bucket:2", 3582484684).as_bytes(),
[1, 50, 220, 166, 65, 204, 96, 136, 213]
);
}
#[test]
fn it_keys_oid_to_iid() {
assert_eq!(
StoreKeyerBuilder::oid_to_iid("bucket:3", &"conversation:6501e83a".to_string())
.as_bytes(),
[2, 171, 194, 213, 57, 31, 156, 118, 213]
);
}
#[test]
fn it_keys_iid_to_oid() {
assert_eq!(
StoreKeyerBuilder::iid_to_oid("bucket:4", 10292198).as_bytes(),
[3, 105, 12, 54, 147, 230, 11, 157, 0]
);
}
#[test]
fn it_keys_iid_to_terms() {
assert_eq!(
StoreKeyerBuilder::iid_to_terms("bucket:5", 1).as_bytes(),
[4, 137, 142, 73, 67, 1, 0, 0, 0]
);
assert_eq!(
StoreKeyerBuilder::iid_to_terms("bucket:5", 20).as_bytes(),
[4, 137, 142, 73, 67, 20, 0, 0, 0]
);
}
#[test]
fn it_hashes_compact() {
assert_eq!(StoreKeyerHasher::to_compact("key:1"), 3370353088);
assert_eq!(StoreKeyerHasher::to_compact("key:2"), 1042559698);
}
#[test]
fn it_formats_key() {
assert_eq!(
&format!("{}", StoreKeyerBuilder::term_to_iids("bucket:6", 72137347)),
"'1:71198b49:44cba83' [1, 73, 139, 25, 113, 131, 186, 76, 4]"
);
assert_eq!(
&format!(
"{}",
StoreKeyerBuilder::meta_to_value("bucket:6", &StoreMetaKey::IIDIncr)
),
"'0:71198b49:0' [0, 73, 139, 25, 113, 0, 0, 0, 0]"
);
}
}
#[cfg(all(feature = "benchmark", test))]
mod benches {
extern crate test;
use super::*;
use test::Bencher;
#[bench]
fn bench_hash_compact_short(b: &mut Bencher) {
b.iter(|| StoreKeyerHasher::to_compact("key:bench:1"));
}
#[bench]
fn bench_hash_compact_long(b: &mut Bencher) {
b.iter(|| {
StoreKeyerHasher::to_compact(
"key:bench:2:long:long:long:long:long:long:long:long:long:long:long:long:long:long",
)
});
}
#[bench]
fn bench_key_meta_to_value(b: &mut Bencher) {
b.iter(|| StoreKeyerBuilder::meta_to_value("bucket:bench:1", &StoreMetaKey::IIDIncr));
}
#[bench]
fn bench_key_term_to_iids(b: &mut Bencher) {
b.iter(|| StoreKeyerBuilder::term_to_iids("bucket:bench:2", 772137347));
}
#[bench]
fn bench_key_oid_to_iid(b: &mut Bencher) {
let key = "conversation:6501e83a".to_string();
b.iter(|| StoreKeyerBuilder::oid_to_iid("bucket:bench:3", &key));
}
#[bench]
fn bench_key_iid_to_oid(b: &mut Bencher) {
b.iter(|| StoreKeyerBuilder::iid_to_oid("bucket:bench:4", 10292198));
}
#[bench]
fn bench_key_iid_to_terms(b: &mut Bencher) {
b.iter(|| StoreKeyerBuilder::iid_to_terms("bucket:bench:5", 1));
}
}