use crate::storage::lsm::columnar::{ColumnTypeTag, ColumnarSSTable, FixedSegment, TextSegment};
use parking_lot::Mutex;
use std::sync::Arc;
use std::time::Instant;
enum CachedCol {
#[allow(dead_code)]
Fixed(FixedSegment),
Text(TextSegment),
}
const COL_CACHE_CAP: usize = 16;
const TEXT_PAGE_ROWS: usize = 512;
struct TextPageCache {
offset_pages: std::collections::VecDeque<(usize, usize, Vec<u32>)>,
string_pages: std::collections::VecDeque<(usize, Vec<u8>, u32)>, }
impl TextPageCache {
fn new() -> Self {
Self {
offset_pages: std::collections::VecDeque::with_capacity(4),
string_pages: std::collections::VecDeque::with_capacity(4),
}
}
fn get_offsets(&self, col_idx: usize, page_idx: usize) -> Option<&[u32]> {
for (ci, pi, offsets) in &self.offset_pages {
if *ci == col_idx && *pi == page_idx {
return Some(offsets);
}
}
None
}
fn put_offsets(&mut self, col_idx: usize, page_idx: usize, offsets: Vec<u32>) {
while self.offset_pages.len() >= 4 {
self.offset_pages.pop_back();
}
self.offset_pages
.retain(|(c, p, _)| !(*c == col_idx && *p == page_idx));
self.offset_pages.push_front((col_idx, page_idx, offsets));
}
fn get_strings(&self, col_idx: usize) -> Option<(&[u8], u32)> {
for (ci, data, base) in &self.string_pages {
if *ci == col_idx {
return Some((data, *base));
}
}
None
}
fn clear(&mut self) {
self.offset_pages.clear();
self.string_pages.clear();
}
}
struct BoundedColCache {
entries: std::collections::VecDeque<(usize, CachedCol)>,
}
impl BoundedColCache {
fn new() -> Self {
Self {
entries: std::collections::VecDeque::with_capacity(COL_CACHE_CAP),
}
}
#[allow(dead_code)]
fn get(&mut self, col_idx: usize) -> Option<&CachedCol> {
if let Some(pos) = self.entries.iter().position(|(k, _)| *k == col_idx) {
if pos != 0 {
if let Some(entry) = self.entries.remove(pos) {
self.entries.push_front(entry);
}
}
return self.entries.front().map(|(_, v)| v);
}
None
}
fn insert(&mut self, col_idx: usize, val: CachedCol) {
while self.entries.len() >= COL_CACHE_CAP {
self.entries.pop_back();
}
self.entries.retain(|(k, _)| *k != col_idx);
self.entries.push_front((col_idx, val));
}
fn clear(&mut self) {
self.entries.clear();
}
}
pub struct Segment {
pub sst: Arc<ColumnarSSTable>,
pub id: u64,
pub row_count: usize,
pub created_at: Instant,
col_cache: Mutex<BoundedColCache>,
text_page_cache: Mutex<TextPageCache>,
}
impl Segment {
pub fn clear_cache(&self) {
self.col_cache.lock().clear();
}
pub fn clear_all_caches(&self) {
self.col_cache.lock().clear();
self.text_page_cache.lock().clear();
}
pub fn read_fixed_cached(&self, col_idx: usize) -> Option<FixedSegment> {
{
let mut cache = self.col_cache.lock();
if let Some(cached) = cache.get(col_idx) {
if let CachedCol::Fixed(ref f) = cached {
return Some(f.clone());
}
}
}
let seg = self.sst.read_fixed_i64(col_idx).ok()?;
self.col_cache
.lock()
.insert(col_idx, CachedCol::Fixed(seg.clone()));
Some(seg)
}
pub fn read_text_cached(&self, col_idx: usize) -> Option<TextSegment> {
{
let mut cache = self.col_cache.lock();
if let Some(cached) = cache.get(col_idx) {
if let CachedCol::Text(ref t) = cached {
return Some(t.clone());
}
}
}
let seg = self.sst.read_text(col_idx).ok()?;
self.col_cache
.lock()
.insert(col_idx, CachedCol::Text(seg.clone()));
Some(seg)
}
pub fn release_pages(&self) {
self.sst.release_pages();
}
pub fn open(path: &std::path::Path, id: u64) -> crate::Result<Self> {
let sst = ColumnarSSTable::open(path)?;
let row_count = sst.num_rows;
Ok(Self {
sst: Arc::new(sst),
id,
row_count,
created_at: Instant::now(),
col_cache: Mutex::new(BoundedColCache::new()),
text_page_cache: Mutex::new(TextPageCache::new()),
})
}
pub fn get_row_cached(
&self,
key: u64,
col_types: &[crate::types::ColumnType],
) -> Option<Vec<crate::types::Value>> {
let idx = self.sst.find_row_by_key(key)?;
if self.sst.row_map.is_deleted(idx) {
return None;
}
Some(self.decode_row_at(idx, col_types, true))
}
pub fn get_row_at_idx(
&self,
idx: usize,
col_types: &[crate::types::ColumnType],
) -> Vec<crate::types::Value> {
self.decode_row_at(idx, col_types, true)
}
pub fn get_row_for_scan(
&self,
key: u64,
col_types: &[crate::types::ColumnType],
) -> Option<Vec<crate::types::Value>> {
self.get_row_inner(key, col_types, false)
}
fn get_row_inner(
&self,
key: u64,
col_types: &[crate::types::ColumnType],
point_query: bool,
) -> Option<Vec<crate::types::Value>> {
let idx = self.sst.find_row_by_key(key)?;
if self.sst.row_map.is_deleted(idx) {
return None;
}
Some(self.decode_row_at(idx, col_types, point_query))
}
fn decode_row_at(
&self,
idx: usize,
col_types: &[crate::types::ColumnType],
point_query: bool,
) -> Vec<crate::types::Value> {
use crate::types::Value;
let mut row = Vec::with_capacity(col_types.len());
for (ci, ct) in col_types.iter().enumerate() {
let tag = self.sst.column_tags.get(ci).copied();
if matches!(tag, Some(t) if t.is_fixed()) {
match self.sst.read_fixed_i64_at(ci, idx) {
Ok(Some(v)) => {
push_fixed_value(&mut row, v, ct);
continue;
}
Ok(None) => {
row.push(Value::Null);
continue;
}
Err(_) => {
}
}
{
let mut cache = self.col_cache.lock();
if let Some(cached) = cache.get(ci) {
row.push(decode_cached_value(cached, idx, ct));
continue;
}
}
if let Ok(seg) = self.sst.read_fixed_i64(ci) {
let cached = CachedCol::Fixed(seg);
row.push(decode_cached_value(&cached, idx, ct));
self.col_cache.lock().insert(ci, cached);
} else {
row.push(Value::Null);
}
continue;
}
if matches!(tag, Some(ColumnTypeTag::Text)) {
if point_query {
let val = self.read_text_paged(ci, idx);
match val {
Some(s) => row.push(Value::Text(s.into())),
None => row.push(Value::Null),
}
continue;
}
{
let mut cache = self.col_cache.lock();
if let Some(cached) = cache.get(ci) {
row.push(decode_cached_value(cached, idx, ct));
continue;
}
}
let decoded = self.sst.read_text(ci).ok().map(CachedCol::Text);
if let Some(d) = decoded {
row.push(decode_cached_value(&d, idx, ct));
self.col_cache.lock().insert(ci, d);
} else {
row.push(Value::Null);
}
continue;
}
row.push(Value::Null);
}
row
}
fn read_text_paged(&self, col_idx: usize, row_idx: usize) -> Option<String> {
let entry = &self.sst.column_index.get(col_idx)?;
let num_rows = self.sst.num_rows;
let null_bytes = num_rows.div_ceil(8);
let flag = if !self.sst.file_data.is_empty() {
self.sst
.file_data
.get(entry.offset as usize)
.copied()
.unwrap_or(0)
} else {
let mut buf = [0u8; 1];
if self.sst.read_raw(entry.offset as usize, &mut buf).is_err() {
return None;
}
buf[0]
};
if flag == 1 {
{
let mut cache = self.col_cache.lock();
if let Some(cached) = cache.get(col_idx) {
return match cached {
CachedCol::Text(t) => t.get_str(row_idx).map(|s| s.to_string()),
_ => None,
};
}
}
if let Ok(t) = self.sst.read_text(col_idx) {
let result = t.get_str(row_idx).map(|s| s.to_string());
self.col_cache.lock().insert(col_idx, CachedCol::Text(t));
return result;
}
return None;
}
let data_base = entry.offset as usize + 1; let offsets_region = data_base + null_bytes;
let strings_region = offsets_region + (num_rows + 1) * 4;
let null_off = data_base + row_idx / 8;
let null_byte = if !self.sst.file_data.is_empty() {
self.sst.file_data.get(null_off).copied().unwrap_or(0)
} else {
let mut buf = [0u8; 1];
if self.sst.read_raw(null_off, &mut buf).is_err() {
return None;
}
buf[0]
};
if (null_byte >> (row_idx % 8)) & 1 != 0 {
return None; }
let off_pos = offsets_region + row_idx * 4;
let (start, end) = if !self.sst.file_data.is_empty() {
if off_pos + 8 > self.sst.file_data.len() {
return None;
}
let s = u32::from_le_bytes(self.sst.file_data[off_pos..off_pos + 4].try_into().unwrap())
as usize;
let e = u32::from_le_bytes(
self.sst.file_data[off_pos + 4..off_pos + 8]
.try_into()
.unwrap(),
) as usize;
(s, e)
} else {
let page_idx = row_idx / TEXT_PAGE_ROWS;
{
let cache = self.text_page_cache.lock();
if let Some(offsets) = cache.get_offsets(col_idx, page_idx) {
let local_idx = row_idx - page_idx * TEXT_PAGE_ROWS;
if local_idx + 1 < offsets.len() {
let start = offsets[local_idx] as usize;
let end = offsets[local_idx + 1] as usize;
if let Some((sdata, sbase)) = cache.get_strings(col_idx) {
if start >= sbase as usize && end <= sbase as usize + sdata.len() {
let bytes = &sdata[start as usize - sbase as usize
..end as usize - sbase as usize];
return Some(String::from_utf8_lossy(bytes).into_owned());
}
}
let str_pos = strings_region + start;
let len = end.saturating_sub(start);
if len == 0 {
return Some(String::new());
}
if len > 65536 {
return None;
}
let mut str_buf = vec![0u8; len];
if self.sst.read_raw(str_pos, &mut str_buf).is_ok() {
return Some(String::from_utf8_lossy(&str_buf).into_owned());
}
return None;
}
}
}
let window_start = page_idx * TEXT_PAGE_ROWS;
let window_end = (window_start + TEXT_PAGE_ROWS + 1).min(num_rows + 1);
let window_count = window_end - window_start;
let buf_start = offsets_region + window_start * 4;
let mut off_buf = vec![0u8; window_count * 4];
if self.sst.read_raw(buf_start, &mut off_buf).is_err() {
let mut buf8 = [0u8; 8];
if self.sst.read_raw(off_pos, &mut buf8).is_err() {
return None;
}
let s = u32::from_le_bytes([buf8[0], buf8[1], buf8[2], buf8[3]]) as usize;
let e = u32::from_le_bytes([buf8[4], buf8[5], buf8[6], buf8[7]]) as usize;
(s, e)
} else {
let offsets: Vec<u32> = off_buf
.chunks_exact(4)
.map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
let local_idx = row_idx - window_start;
let start = offsets.get(local_idx).copied().unwrap_or(0) as usize;
let end = offsets.get(local_idx + 1).copied().unwrap_or(0) as usize;
self.text_page_cache
.lock()
.put_offsets(col_idx, page_idx, offsets);
(start, end)
}
};
let len = end.saturating_sub(start);
if len == 0 {
return Some(String::new());
}
if len > 65536 {
return None;
}
let str_pos = strings_region + start;
if !self.sst.file_data.is_empty() {
if str_pos + len <= self.sst.file_data.len() {
return Some(
String::from_utf8_lossy(&self.sst.file_data[str_pos..str_pos + len])
.into_owned(),
);
}
} else {
let mut str_buf = vec![0u8; len];
if self.sst.read_raw(str_pos, &mut str_buf).is_ok() {
return Some(String::from_utf8_lossy(&str_buf).into_owned());
}
}
None
}
}
fn push_fixed_value(row: &mut Vec<crate::types::Value>, v: i64, ct: &crate::types::ColumnType) {
use crate::types::{ColumnType, Value};
match ct {
ColumnType::Integer => row.push(Value::Integer(v)),
ColumnType::Float => row.push(Value::Float(f64::from_bits(v as u64))),
ColumnType::Boolean => row.push(Value::Bool(v != 0)),
ColumnType::Timestamp => {
row.push(Value::Timestamp(crate::types::Timestamp::from_micros(v)))
}
_ => row.push(Value::Null),
}
}
fn decode_cached_value(
cached: &CachedCol,
idx: usize,
ct: &crate::types::ColumnType,
) -> crate::types::Value {
use crate::types::{ColumnType, Value};
match (cached, ct) {
(CachedCol::Fixed(f), ColumnType::Integer) => {
f.get_i64(idx).map(Value::Integer).unwrap_or(Value::Null)
}
(CachedCol::Fixed(f), ColumnType::Float) => {
f.get_f64(idx).map(Value::Float).unwrap_or(Value::Null)
}
(CachedCol::Fixed(f), ColumnType::Boolean) => {
f.get_bool(idx).map(Value::Bool).unwrap_or(Value::Null)
}
(CachedCol::Fixed(f), ColumnType::Timestamp) => f
.get_i64(idx)
.map(|v| Value::Timestamp(crate::types::Timestamp::from_micros(v)))
.unwrap_or(Value::Null),
(CachedCol::Text(t), ColumnType::Text) => t
.get_str(idx)
.map(|s| Value::Text(s.into()))
.unwrap_or(Value::Null),
_ => Value::Null,
}
}