use super::Tree;
use crate::comparator::SharedComparator;
use crate::merge_operator::MergeOperator;
use crate::table::probe_stats::PlanCounts;
use crate::table::{BlockHandle, BlockTaskPlan, DataBlock};
use crate::version::{SnapshotRef, Version};
use crate::{InternalValue, SeqNo, Table, UserValue, fs::FsFile};
use alloc::sync::Arc;
use alloc::vec::Vec;
pub enum Values {
Inline,
Blob(crate::blob_tree::BlobSource),
}
pub struct ReadCtx<K> {
pub super_version: SnapshotRef,
pub keys: Vec<K>,
pub seqno: SeqNo,
pub comparator: SharedComparator,
pub merge_operator: Option<Arc<dyn MergeOperator>>,
pub merge_base: Option<crate::blob_tree::BlobSource>,
pub values: Values,
pub metadata_budget: u64,
}
impl<K: AsRef<[u8]>> ReadCtx<K> {
pub(super) fn version(&self) -> &Version {
&self.super_version.version
}
pub(super) fn key(&self, idx: usize) -> &[u8] {
self.keys.get(idx).map_or(&[], AsRef::as_ref)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct TableAt {
pub(super) level: usize,
pub(super) run: usize,
pub(super) pos: usize,
}
impl TableAt {
pub(super) fn table(self, version: &Version) -> Option<&Table> {
version.level(self.level)?.get(self.run)?.get(self.pos)
}
}
fn misplaced() -> crate::Error {
crate::Error::Io(crate::io::Error::new(
crate::io::ErrorKind::Other,
"a read job names a table its version does not hold",
))
}
pub(super) struct BlockRequest<'a> {
pub(super) tag: usize,
pub(super) fs: &'a Arc<dyn crate::fs::Fs>,
pub(super) cache: &'a crate::cache::Cache,
pub(super) file: Arc<dyn FsFile>,
pub(super) offset: u64,
pub(super) buf: Vec<u8>,
}
pub(super) enum Job {
Open { tag: usize, table: TableAt },
Plan {
at: usize,
table: TableAt,
keys: Vec<(usize, u64)>,
},
Load {
index: usize,
table: TableAt,
handle: BlockHandle,
},
Serial {
level: usize,
remaining: Vec<(usize, u64)>,
},
Merge { idx: usize },
Blob { idx: usize, item: InternalValue },
OpenBlob {
tag: usize,
blob_file: crate::vlog::BlobFileId,
},
MergeLocate {
tag: usize,
idx: usize,
table: TableAt,
},
MergeFinish {
idx: usize,
read: crate::range::MergeRead,
},
}
pub(super) enum MergeLocated {
Versions(Vec<InternalValue>),
Blocks {
filter: crate::range::TableFilter,
file: Arc<dyn FsFile>,
blocks: Vec<BlockHandle>,
},
}
pub(super) enum JobDone {
Opened {
tag: usize,
file: crate::Result<Arc<dyn FsFile>>,
},
Planned {
at: usize,
plan: crate::Result<Option<BlockTaskPlan>>,
tally: PlanCounts,
},
Loaded {
index: usize,
block: crate::Result<Option<DataBlock>>,
},
Serial {
result: crate::Result<super::tables_read::SerialLevel>,
},
Value {
idx: usize,
value: crate::Result<Option<UserValue>>,
},
MergeLocated {
tag: usize,
located: crate::Result<MergeLocated>,
},
}
impl Job {
pub(super) fn run<K: AsRef<[u8]>>(self, ctx: &ReadCtx<K>) -> JobDone {
let version = ctx.version();
match self {
Self::Open { tag, table } => JobDone::Opened {
tag,
file: table
.table(version)
.ok_or_else(misplaced)
.and_then(Table::open_file),
},
Self::Plan { at, table, keys } => {
let mut tally = PlanCounts::default();
let batch: Vec<(&[u8], u64)> = keys
.iter()
.map(|&(idx, hash)| (ctx.key(idx), hash))
.collect();
let plan = table
.table(version)
.ok_or_else(misplaced)
.and_then(|table| table.plan_block_tasks(&batch, ctx.seqno, &mut tally));
JobDone::Planned { at, plan, tally }
}
Self::Load {
index,
table,
handle,
} => JobDone::Loaded {
index,
block: table
.table(version)
.ok_or_else(misplaced)
.and_then(|table| {
table.load_data_block(&handle, &crate::table::util::LookupFd::default())
}),
},
Self::Serial { level, remaining } => JobDone::Serial {
result: version.level(level).ok_or_else(misplaced).and_then(|on| {
Tree::resolve_level_serially(
level == 0,
on,
remaining,
&ctx.keys,
ctx.seqno,
ctx.comparator.as_ref(),
)
}),
},
Self::Merge { idx } => JobDone::Value {
idx,
value: match &ctx.merge_operator {
Some(merge_operator) => Tree::resolve_point_merge(
&ctx.super_version,
ctx.key(idx),
ctx.seqno,
merge_operator,
ctx.merge_base.as_ref(),
),
None => Err(misplaced()),
},
},
Self::Blob { idx, item } => JobDone::Value {
idx,
value: match &ctx.values {
Values::Blob(source) => source.value(version, item).map(Some),
Values::Inline => Ok(Some(item.value)),
},
},
Self::OpenBlob { tag, blob_file } => JobDone::Opened {
tag,
file: match (&ctx.values, version.blob_files.get(blob_file)) {
(Values::Blob(source), Some(blob_file)) => source.open_blob_file(blob_file),
_ => Err(misplaced()),
},
},
Self::MergeLocate { tag, idx, table } => JobDone::MergeLocated {
tag,
located: table
.table(version)
.ok_or_else(misplaced)
.and_then(|table| locate(table, ctx.key(idx), ctx.seqno)),
},
Self::MergeFinish { idx, read } => JobDone::Value {
idx,
value: match &ctx.merge_operator {
Some(operator) => read
.finish(&ctx.super_version, operator, ctx.merge_base.as_ref())
.map(|entry| entry.map(|entry| entry.value)),
None => Err(misplaced()),
},
},
}
}
}
fn locate(table: &Table, key: &[u8], seqno: SeqNo) -> crate::Result<MergeLocated> {
let filter = crate::range::TableFilter::of(table, key);
if filter.absent() || table.is_chunk_special() || table.is_restricted() {
let mut versions = Vec::new();
for version in filter.versions(table, key, seqno) {
let version = version?;
let base = !version.key.value_type.is_merge_operand();
versions.push(version);
if base {
break;
}
}
return Ok(MergeLocated::Versions(versions));
}
let (file, blocks) = table.key_blocks(key)?;
Ok(MergeLocated::Blocks {
filter,
file,
blocks,
})
}
pub(super) trait ReadSink<'a> {
fn job(&mut self, job: Job);
fn read(&mut self, read: BlockRequest<'a>);
fn leads_loads(&self) -> bool {
false
}
}
pub(super) struct ReadWork<'a> {
pub(super) jobs: Vec<Job>,
pub(super) reads: Vec<BlockRequest<'a>>,
}
impl ReadWork<'_> {
pub(super) const fn new() -> Self {
Self {
jobs: Vec::new(),
reads: Vec::new(),
}
}
}
impl<'a> ReadSink<'a> for ReadWork<'a> {
fn job(&mut self, job: Job) {
self.jobs.push(job);
}
fn read(&mut self, read: BlockRequest<'a>) {
self.reads.push(read);
}
}
pub(super) trait ReadMachine<'a> {
type Output;
fn pump<S: ReadSink<'a>>(&mut self, out: &mut S) -> Option<Self::Output>;
fn job_done<S: ReadSink<'a>>(&mut self, done: JobDone, out: &mut S);
fn read_done(&mut self, done: crate::fs::ReadDone);
}