use crate::error::{DbError, DbResult};
use crate::storage::RocksDb as DB;
use dashmap::DashMap;
use once_cell::sync::Lazy;
use rust_rocksdb::{
BoundColumnFamily, DBPinnableSlice, Direction, Error as RocksError, IteratorMode, ReadOptions,
WriteBatch,
};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Weak};
pub const SHARED_CF: &str = "__keyspaces__";
pub const BARE_DB_ID: u32 = 0;
pub const RESERVED_DB_ID: u32 = u32::MAX;
pub type KsNum = u64;
pub fn ks_num(db_id: u32, coll_id: u32) -> KsNum {
((db_id as u64) << 32) | coll_id as u64
}
pub fn ks_db_id(ks: KsNum) -> u32 {
(ks >> 32) as u32
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct KsPrefix {
bytes: [u8; 8],
len: u8,
}
impl KsPrefix {
pub const LEGACY: KsPrefix = KsPrefix {
bytes: [0; 8],
len: 0,
};
pub fn shared(ks: KsNum) -> Self {
KsPrefix {
bytes: ks.to_be_bytes(),
len: 8,
}
}
pub fn is_legacy(&self) -> bool {
self.len == 0
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len as usize]
}
#[inline]
pub fn key(&self, logical: &[u8]) -> Vec<u8> {
if self.len == 0 {
return logical.to_vec();
}
let mut k = Vec::with_capacity(8 + logical.len());
k.extend_from_slice(self.as_bytes());
k.extend_from_slice(logical);
k
}
pub fn upper(&self) -> Option<Vec<u8>> {
if self.len == 0 {
return None;
}
let n = u64::from_be_bytes(self.bytes);
Some((n + 1).to_be_bytes().to_vec())
}
#[inline]
fn strip(&self, physical: Box<[u8]>) -> Box<[u8]> {
if self.len == 0 {
physical
} else {
physical[self.len as usize..].into()
}
}
}
#[derive(Debug, Default)]
pub struct KsState {
dead: AtomicBool,
}
impl KsState {
pub fn is_dead(&self) -> bool {
self.dead.load(Ordering::Acquire)
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum KsId {
Legacy(Arc<str>),
Shared(KsNum),
}
static STATES: Lazy<DashMap<(usize, KsId), Weak<KsState>>> = Lazy::new(DashMap::new);
fn db_key(db: &Arc<DB>) -> usize {
Arc::as_ptr(db) as usize
}
fn intern_state(db: &Arc<DB>, id: &KsId) -> Arc<KsState> {
let key = (db_key(db), id.clone());
if let Some(existing) = STATES.get(&key).and_then(|w| w.upgrade()) {
return existing;
}
let mut entry = STATES.entry(key).or_default();
if let Some(existing) = entry.upgrade() {
return existing;
}
let state = Arc::new(KsState::default());
*entry = Arc::downgrade(&state);
state
}
pub fn mark_dead(db: &Arc<DB>, id: &KsId) {
if let Some(state) = STATES
.get(&(db_key(db), id.clone()))
.and_then(|w| w.upgrade())
{
state.dead.store(true, Ordering::Release);
}
}
pub fn mark_database_dead(db: &Arc<DB>, db_id: u32) {
let me = db_key(db);
for entry in STATES.iter() {
let (ptr, id) = entry.key();
if *ptr != me {
continue;
}
if let KsId::Shared(n) = id {
if ks_db_id(*n) == db_id {
if let Some(state) = entry.value().upgrade() {
state.dead.store(true, Ordering::Release);
}
}
}
}
}
pub fn prune_states() {
STATES.retain(|_, w| w.strong_count() > 0);
}
#[derive(Clone, Debug)]
pub struct Keyspace {
cf_name: Arc<str>,
prefix: KsPrefix,
id: KsId,
state: Arc<KsState>,
}
impl Keyspace {
pub fn legacy(db: &Arc<DB>, cf_name: &str) -> Self {
let cf_name: Arc<str> = Arc::from(cf_name);
let id = KsId::Legacy(cf_name.clone());
Keyspace {
state: intern_state(db, &id),
cf_name,
prefix: KsPrefix::LEGACY,
id,
}
}
pub fn shared(db: &Arc<DB>, ks: KsNum) -> Self {
let id = KsId::Shared(ks);
Keyspace {
state: intern_state(db, &id),
cf_name: Arc::from(SHARED_CF),
prefix: KsPrefix::shared(ks),
id,
}
}
pub fn id(&self) -> &KsId {
&self.id
}
pub fn prefix(&self) -> KsPrefix {
self.prefix
}
pub fn is_legacy(&self) -> bool {
self.prefix.is_legacy()
}
pub fn cf_name(&self) -> &str {
&self.cf_name
}
pub fn is_dead(&self) -> bool {
self.state.is_dead()
}
pub fn handle<'a>(&self, db: &'a DB) -> Option<KsCf<'a>> {
if self.state.is_dead() {
return None;
}
db.cf_handle(&self.cf_name).map(|cf| KsCf {
cf,
prefix: self.prefix,
})
}
pub fn live<'a>(&self, db: &'a DB, what: &str) -> DbResult<KsCf<'a>> {
self.handle(db)
.ok_or_else(|| DbError::CollectionNotFound(format!("{} (dropped mid-operation)", what)))
}
pub fn physical_range(&self, db: &DB) -> Option<(Vec<u8>, Vec<u8>)> {
if let Some(hi) = self.prefix.upper() {
return Some((self.prefix.as_bytes().to_vec(), hi));
}
let cf = db.cf_handle(&self.cf_name)?;
let first = db.iterator_cf(&cf, IteratorMode::Start).next()?.ok()?.0;
let mut last = db
.iterator_cf(&cf, IteratorMode::End)
.next()?
.ok()?
.0
.to_vec();
last.push(0);
Some((first.to_vec(), last))
}
pub fn approximate_size(&self, db: &DB) -> u64 {
let Some(cf) = db.cf_handle(&self.cf_name) else {
return 0;
};
let Some((lo, hi)) = self.physical_range(db) else {
return 0;
};
db.get_approximate_sizes_cf(&cf, &[rust_rocksdb::Range::new(&lo, &hi)])
.first()
.copied()
.unwrap_or(0)
}
}
pub struct KsCf<'a> {
pub(crate) cf: Arc<BoundColumnFamily<'a>>,
pub(crate) prefix: KsPrefix,
}
impl KsCf<'_> {
pub fn prefix(&self) -> KsPrefix {
self.prefix
}
pub fn raw(&self) -> &Arc<BoundColumnFamily<'_>> {
&self.cf
}
#[inline]
pub fn key(&self, logical: &[u8]) -> Vec<u8> {
self.prefix.key(logical)
}
}
pub struct KsIter<'a> {
inner: rust_rocksdb::DBIteratorWithThreadMode<'a, DB>,
prefix: KsPrefix,
}
impl Iterator for KsIter<'_> {
type Item = Result<(Box<[u8]>, Box<[u8]>), RocksError>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let prefix = self.prefix;
self.inner
.next()
.map(|r| r.map(|(k, v)| (prefix.strip(k), v)))
}
}
impl std::iter::FusedIterator for KsIter<'_> {}
fn bounded_opts(prefix: KsPrefix, lower: Option<&[u8]>, upper: Option<&[u8]>) -> ReadOptions {
let mut opts = ReadOptions::default();
match (lower, prefix.is_legacy()) {
(Some(lo), _) => opts.set_iterate_lower_bound(prefix.key(lo)),
(None, false) => opts.set_iterate_lower_bound(prefix.as_bytes().to_vec()),
(None, true) => {}
}
match (upper, prefix.upper()) {
(Some(hi), _) => opts.set_iterate_upper_bound(prefix.key(hi)),
(None, Some(end)) => opts.set_iterate_upper_bound(end),
(None, None) => {}
}
opts
}
pub trait KsDbExt {
fn get_ks<K: AsRef<[u8]>>(&self, cf: &KsCf, key: K) -> Result<Option<Vec<u8>>, RocksError>;
fn get_pinned_ks<K: AsRef<[u8]>>(
&self,
cf: &KsCf,
key: K,
) -> Result<Option<DBPinnableSlice<'_>>, RocksError>;
fn put_ks<K: AsRef<[u8]>, V: AsRef<[u8]>>(
&self,
cf: &KsCf,
key: K,
value: V,
) -> Result<(), RocksError>;
fn delete_ks<K: AsRef<[u8]>>(&self, cf: &KsCf, key: K) -> Result<(), RocksError>;
fn multi_get_ks<K: AsRef<[u8]>, I: IntoIterator<Item = K>>(
&self,
cf: &KsCf,
keys: I,
) -> Vec<Result<Option<Vec<u8>>, RocksError>>;
fn prefix_iterator_ks<P: AsRef<[u8]>>(&self, cf: &KsCf, prefix: P) -> KsIter<'_>;
fn iterator_ks(&self, cf: &KsCf, mode: IteratorMode) -> KsIter<'_>;
fn iterator_ks_bounded(
&self,
cf: &KsCf,
mode: IteratorMode,
lower: Option<&[u8]>,
upper: Option<&[u8]>,
) -> KsIter<'_>;
fn iterator_ks_opt(&self, cf: &KsCf, opts: ReadOptions, mode: IteratorMode) -> KsIter<'_>;
fn compact_range_ks(&self, cf: &KsCf, start: Option<&[u8]>, end: Option<&[u8]>);
}
impl KsDbExt for DB {
fn get_ks<K: AsRef<[u8]>>(&self, cf: &KsCf, key: K) -> Result<Option<Vec<u8>>, RocksError> {
if cf.prefix.is_legacy() {
return self.get_cf(&cf.cf, key);
}
self.get_cf(&cf.cf, cf.key(key.as_ref()))
}
fn get_pinned_ks<K: AsRef<[u8]>>(
&self,
cf: &KsCf,
key: K,
) -> Result<Option<DBPinnableSlice<'_>>, RocksError> {
if cf.prefix.is_legacy() {
return self.get_pinned_cf(&cf.cf, key);
}
self.get_pinned_cf(&cf.cf, cf.key(key.as_ref()))
}
fn put_ks<K: AsRef<[u8]>, V: AsRef<[u8]>>(
&self,
cf: &KsCf,
key: K,
value: V,
) -> Result<(), RocksError> {
if cf.prefix.is_legacy() {
return self.put_cf(&cf.cf, key, value);
}
self.put_cf(&cf.cf, cf.key(key.as_ref()), value)
}
fn delete_ks<K: AsRef<[u8]>>(&self, cf: &KsCf, key: K) -> Result<(), RocksError> {
if cf.prefix.is_legacy() {
return self.delete_cf(&cf.cf, key);
}
self.delete_cf(&cf.cf, cf.key(key.as_ref()))
}
fn multi_get_ks<K: AsRef<[u8]>, I: IntoIterator<Item = K>>(
&self,
cf: &KsCf,
keys: I,
) -> Vec<Result<Option<Vec<u8>>, RocksError>> {
let physical: Vec<Vec<u8>> = keys.into_iter().map(|k| cf.key(k.as_ref())).collect();
self.multi_get_cf(physical.iter().map(|k| (&cf.cf, k)))
}
fn prefix_iterator_ks<P: AsRef<[u8]>>(&self, cf: &KsCf, prefix: P) -> KsIter<'_> {
self.iterator_ks(cf, IteratorMode::From(prefix.as_ref(), Direction::Forward))
}
fn iterator_ks(&self, cf: &KsCf, mode: IteratorMode) -> KsIter<'_> {
self.iterator_ks_bounded(cf, mode, None, None)
}
fn iterator_ks_bounded(
&self,
cf: &KsCf,
mode: IteratorMode,
lower: Option<&[u8]>,
upper: Option<&[u8]>,
) -> KsIter<'_> {
let opts = bounded_opts(cf.prefix, lower, upper);
let inner = match mode {
IteratorMode::From(k, dir) => {
let physical = cf.key(k);
self.iterator_cf_opt(&cf.cf, opts, IteratorMode::From(&physical, dir))
}
other => self.iterator_cf_opt(&cf.cf, opts, other),
};
KsIter {
inner,
prefix: cf.prefix,
}
}
fn iterator_ks_opt(&self, cf: &KsCf, mut opts: ReadOptions, mode: IteratorMode) -> KsIter<'_> {
if let Some(end) = cf.prefix.upper() {
opts.set_iterate_lower_bound(cf.prefix.as_bytes().to_vec());
opts.set_iterate_upper_bound(end);
}
let inner = match mode {
IteratorMode::From(k, dir) => {
let physical = cf.key(k);
self.iterator_cf_opt(&cf.cf, opts, IteratorMode::From(&physical, dir))
}
other => self.iterator_cf_opt(&cf.cf, opts, other),
};
KsIter {
inner,
prefix: cf.prefix,
}
}
fn compact_range_ks(&self, cf: &KsCf, start: Option<&[u8]>, end: Option<&[u8]>) {
if cf.prefix.is_legacy() {
self.compact_range_cf(&cf.cf, start, end);
return;
}
let lo = start.map_or_else(|| cf.prefix.as_bytes().to_vec(), |s| cf.key(s));
let hi = end.map_or_else(|| cf.prefix.upper().unwrap_or_default(), |e| cf.key(e));
self.compact_range_cf(&cf.cf, Some(lo), Some(hi));
}
}
pub trait KsBatchExt {
fn put_ks<K: AsRef<[u8]>, V: AsRef<[u8]>>(&mut self, cf: &KsCf, key: K, value: V);
fn delete_ks<K: AsRef<[u8]>>(&mut self, cf: &KsCf, key: K);
fn delete_range_ks<K: AsRef<[u8]>>(&mut self, cf: &KsCf, from: K, to: K);
}
impl KsBatchExt for WriteBatch {
fn put_ks<K: AsRef<[u8]>, V: AsRef<[u8]>>(&mut self, cf: &KsCf, key: K, value: V) {
if cf.prefix.is_legacy() {
self.put_cf(&cf.cf, key, value);
} else {
self.put_cf(&cf.cf, cf.key(key.as_ref()), value);
}
}
fn delete_ks<K: AsRef<[u8]>>(&mut self, cf: &KsCf, key: K) {
if cf.prefix.is_legacy() {
self.delete_cf(&cf.cf, key);
} else {
self.delete_cf(&cf.cf, cf.key(key.as_ref()));
}
}
fn delete_range_ks<K: AsRef<[u8]>>(&mut self, cf: &KsCf, from: K, to: K) {
self.delete_range_cf(&cf.cf, cf.key(from.as_ref()), cf.key(to.as_ref()));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ks_numbers_and_prefixes() {
let ks = ks_num(7, 3);
assert_eq!(ks_db_id(ks), 7);
let p = KsPrefix::shared(ks);
assert_eq!(p.as_bytes(), &[0, 0, 0, 7, 0, 0, 0, 3]);
assert_eq!(p.key(b"doc:a"), b"\0\0\0\x07\0\0\0\x03doc:a".to_vec());
assert_eq!(p.upper().unwrap(), vec![0, 0, 0, 7, 0, 0, 0, 4]);
let last = KsPrefix::shared(ks_num(7, u32::MAX));
assert_eq!(last.upper().unwrap(), vec![0, 0, 0, 8, 0, 0, 0, 0]);
assert!(KsPrefix::LEGACY.upper().is_none());
assert_eq!(KsPrefix::LEGACY.key(b"doc:a"), b"doc:a".to_vec());
}
}