use std::time::Instant;
use tessarikv_types::{KeyspaceId, SequenceNumber, ValueKind};
use super::Db;
use super::merge::{MergedVersions, Source};
use super::table_versions::TableVersions;
use super::tables::{TableLookup, lookup};
use super::view::View;
use crate::memtable::Lookup;
use crate::statistics::{Histogram, Statistics, Ticker};
use crate::tombstones::Fragments;
use crate::{ReadOptions, Result};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ScanPage {
pub entries: Vec<(Vec<u8>, Vec<u8>)>,
pub next: Option<Vec<u8>>,
}
impl Db {
pub async fn get(
&self,
keyspace: KeyspaceId,
key: &[u8],
options: &ReadOptions,
) -> Result<Option<Vec<u8>>> {
self.check_keyspace(keyspace)?;
let view = self.view.load();
let snapshot = self.visible_sequence()?;
get_in(
&view,
keyspace,
key,
snapshot,
options.fill_cache,
self.statistics.as_deref(),
)
.await
}
pub(super) async fn get_at(
&self,
keyspace: KeyspaceId,
key: &[u8],
snapshot: SequenceNumber,
options: &ReadOptions,
) -> Result<Option<Vec<u8>>> {
self.check_keyspace(keyspace)?;
let view = self.view.load();
get_in(
&view,
keyspace,
key,
snapshot,
options.fill_cache,
self.statistics.as_deref(),
)
.await
}
pub async fn scan(
&self,
keyspace: KeyspaceId,
start: &[u8],
end: Option<&[u8]>,
limit: usize,
options: &ReadOptions,
) -> Result<ScanPage> {
self.check_keyspace(keyspace)?;
let view = self.view.load();
let snapshot = self.visible_sequence()?;
let range = ScanRange { start, end, limit };
scan_in(&view, keyspace, range, snapshot, options.fill_cache).await
}
pub(super) async fn scan_at(
&self,
keyspace: KeyspaceId,
start: &[u8],
end: Option<&[u8]>,
limit: usize,
snapshot: SequenceNumber,
options: &ReadOptions,
) -> Result<ScanPage> {
self.check_keyspace(keyspace)?;
let view = self.view.load();
let range = ScanRange { start, end, limit };
scan_in(&view, keyspace, range, snapshot, options.fill_cache).await
}
}
pub(super) async fn get_in(
view: &View,
keyspace: KeyspaceId,
key: &[u8],
snapshot: SequenceNumber,
fill_cache: bool,
statistics: Option<&Statistics>,
) -> Result<Option<Vec<u8>>> {
let Some(statistics) = statistics else {
return get_uncounted(view, keyspace, key, snapshot, fill_cache, None).await;
};
let started = Instant::now();
statistics.record(Ticker::GetCalls, 1);
let found = get_uncounted(view, keyspace, key, snapshot, fill_cache, Some(statistics)).await;
let micros = u64::try_from(started.elapsed().as_micros()).unwrap_or(u64::MAX);
statistics.observe(Histogram::GetMicros, micros);
found
}
async fn get_uncounted(
view: &View,
keyspace: KeyspaceId,
key: &[u8],
snapshot: SequenceNumber,
fill_cache: bool,
statistics: Option<&Statistics>,
) -> Result<Option<Vec<u8>>> {
let fragments = Fragments::new(&view.tombstones(keyspace.get())?, snapshot);
if !fragments.is_empty() {
return get_merged(view, keyspace, key, snapshot, &fragments, fill_cache).await;
}
let decided = view
.memtables(keyspace.get())
.find_map(|table| match table.get(key, snapshot) {
Lookup::Value(value) => Some(Some(value.to_vec())),
Lookup::Deleted => Some(None),
Lookup::Absent => None,
});
if let Some(statistics) = statistics {
let ticker = if decided.is_some() {
Ticker::MemtableHit
} else {
Ticker::MemtableMiss
};
statistics.record(ticker, 1);
}
if let Some(value) = decided {
return Ok(value);
}
for file in view.candidates(keyspace.get(), key) {
match lookup(file, key, snapshot, fill_cache, statistics).await? {
TableLookup::Value(value) => return Ok(Some(value)),
TableLookup::Deleted => return Ok(None),
TableLookup::Absent => {}
}
}
Ok(None)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScanRange<'a> {
pub start: &'a [u8],
pub end: Option<&'a [u8]>,
pub limit: usize,
}
pub(super) async fn scan_in(
view: &View,
keyspace: KeyspaceId,
range: ScanRange<'_>,
snapshot: SequenceNumber,
fill_cache: bool,
) -> Result<ScanPage> {
let ScanRange { start, end, limit } = range;
let mut page = ScanPage::default();
let fragments = Fragments::new(&view.tombstones(keyspace.get())?, snapshot);
let mut versions = MergedVersions::new(sources(view, keyspace, fill_cache));
versions.seek(start, snapshot).await;
let mut decided: Option<Vec<u8>> = None;
while let Some(version) = versions.entry() {
if end.is_some_and(|end| version.user_key >= end) {
break;
}
if version.sequence <= snapshot && decided.as_deref() != Some(version.user_key) {
let key = decided.get_or_insert_with(Vec::new);
key.clear();
key.extend_from_slice(version.user_key);
if version.kind == ValueKind::Value
&& !fragments.deletes(version.user_key, version.sequence)
{
if page.entries.len() >= limit {
page.next = Some(version.user_key.to_vec());
break;
}
page.entries
.push((version.user_key.to_vec(), version.value.to_vec()));
}
}
versions.next().await;
}
versions.finish()?;
Ok(page)
}
fn sources(view: &View, keyspace: KeyspaceId, fill_cache: bool) -> Vec<Source<'_>> {
view.memtables(keyspace.get())
.map(|table| Source::Memtable(table.iter()))
.chain(
view.files(keyspace.get())
.map(|file| Source::Table(Box::new(TableVersions::new(file, fill_cache)))),
)
.collect()
}
async fn get_merged(
view: &View,
keyspace: KeyspaceId,
key: &[u8],
snapshot: SequenceNumber,
fragments: &Fragments,
fill_cache: bool,
) -> Result<Option<Vec<u8>>> {
let mut versions = MergedVersions::new(sources(view, keyspace, fill_cache));
versions.seek(key, snapshot).await;
let value = versions
.entry()
.filter(|version| {
version.user_key == key
&& version.kind == ValueKind::Value
&& !fragments.deletes(key, version.sequence)
})
.map(|version| version.value.to_vec());
versions.finish()?;
Ok(value)
}