use std::sync::Arc;
use bytes::Bytes;
use slatedb::{
ByteRangeBounds, DbIterator, DbReader, DbTransaction, IterationOrder, config::ScanOptions,
};
const SCAN_READ_AHEAD_BYTES: usize = 4 * 1024 * 1024;
const SCAN_FETCH_TASKS: usize = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ScanShape {
Bulk,
Probe,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ScanOrder {
Ascending,
Descending,
}
impl ScanOrder {
pub(crate) fn from_reverse(reverse: bool) -> Self {
if reverse {
Self::Descending
} else {
Self::Ascending
}
}
fn iteration_order(self) -> IterationOrder {
match self {
Self::Ascending => IterationOrder::Ascending,
Self::Descending => IterationOrder::Descending,
}
}
}
fn bulk_scan_options() -> ScanOptions {
ScanOptions {
read_ahead_bytes: SCAN_READ_AHEAD_BYTES,
max_fetch_tasks: SCAN_FETCH_TASKS,
cache_blocks: false,
..ScanOptions::default()
}
}
fn probe_scan_options() -> ScanOptions {
ScanOptions {
read_ahead_bytes: SCAN_READ_AHEAD_BYTES,
max_fetch_tasks: SCAN_FETCH_TASKS,
cache_blocks: true,
..ScanOptions::default()
}
}
impl ScanShape {
fn options(self, order: ScanOrder) -> ScanOptions {
let mut options = match self {
Self::Bulk => bulk_scan_options(),
Self::Probe => probe_scan_options(),
};
options.order = order.iteration_order();
options
}
}
#[derive(Clone, Copy)]
pub(crate) enum ReadHandle<'a> {
Tx(&'a DbTransaction),
Reader(&'a DbReader),
}
impl ReadHandle<'_> {
pub(crate) async fn get<K: AsRef<[u8]> + Send>(
&self,
key: K,
) -> Result<Option<Bytes>, slatedb::Error> {
match self {
Self::Tx(tx) => tx.get(key).await,
Self::Reader(reader) => reader.get(key).await,
}
}
pub(crate) fn is_isolated(&self) -> bool {
matches!(self, Self::Tx(_))
}
pub(crate) async fn scan_prefix<P, T>(
&self,
prefix: P,
subrange: T,
shape: ScanShape,
) -> Result<DbIterator, slatedb::Error>
where
P: AsRef<[u8]> + Send,
T: ByteRangeBounds + Send,
{
self.scan_prefix_ordered(prefix, subrange, shape, ScanOrder::Ascending)
.await
}
pub(crate) async fn scan_prefix_ordered<P, T>(
&self,
prefix: P,
subrange: T,
shape: ScanShape,
order: ScanOrder,
) -> Result<DbIterator, slatedb::Error>
where
P: AsRef<[u8]> + Send,
T: ByteRangeBounds + Send,
{
let options = shape.options(order);
match self {
Self::Tx(tx) => {
tx.scan_prefix_with_options(prefix, subrange, &options)
.await
}
Self::Reader(reader) => {
reader
.scan_prefix_with_options(prefix, subrange, &options)
.await
}
}
}
}
pub(crate) enum ReadSession {
Tx(DbTransaction),
Reader(Arc<DbReader>),
}
impl ReadSession {
pub(crate) fn handle(&self) -> ReadHandle<'_> {
match self {
Self::Tx(tx) => ReadHandle::Tx(tx),
Self::Reader(reader) => ReadHandle::Reader(reader),
}
}
pub(crate) fn finish(self) {
if let Self::Tx(tx) = self {
tx.rollback();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bulk_scans_read_ahead_and_admit_nothing() {
let options = ScanShape::Bulk.options(ScanOrder::Ascending);
assert_eq!(options.read_ahead_bytes, SCAN_READ_AHEAD_BYTES);
assert_eq!(options.max_fetch_tasks, SCAN_FETCH_TASKS);
assert!(!options.cache_blocks);
}
#[test]
fn probe_scans_admit_their_blocks() {
let options = ScanShape::Probe.options(ScanOrder::Ascending);
assert_eq!(options.read_ahead_bytes, SCAN_READ_AHEAD_BYTES);
assert_eq!(options.max_fetch_tasks, SCAN_FETCH_TASKS);
assert!(options.cache_blocks);
}
#[test]
fn descending_probes_request_descending_iteration() {
let options = ScanShape::Probe.options(ScanOrder::Descending);
assert!(matches!(options.order, IterationOrder::Descending));
}
}