use linkrs_core::types::Timestamp;
use linkrs_core::{DataType, StorageError, StorageResult, Value};
use crate::column_stats::ColumnStats;
use crate::encoding::ColumnEncoding;
use crate::stats::HyperLogLog;
use super::chunk::{ChunkState, ColumnChunk, DEFAULT_CHUNK_ROWS};
use super::chunk_residency::ChunkResidency;
use super::fixed_width::FixedWidthColumn;
use super::overflow::{OverflowHandle, OverflowStore, DEFAULT_OVERFLOW_THRESHOLD};
use super::variable_width::VariableWidthColumn;
use bitvec::prelude::*;
use parking_lot::{Mutex, RwLock};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
pub trait ColumnStorage: Send + Sync + std::fmt::Debug {
fn get(&self, row_idx: usize) -> Option<Value>;
fn try_get(&self, row_idx: usize) -> StorageResult<Option<Value>> {
Ok(self.get(row_idx))
}
fn set(&mut self, row_idx: usize, value: Option<&Value>) -> StorageResult<()>;
fn len(&self) -> usize;
fn is_null(&self, row_idx: usize) -> bool;
fn memory_usage(&self) -> usize;
fn reserve(&mut self, additional: usize);
fn clear(&mut self);
fn resize(&mut self, new_count: usize);
fn null_bitmap(&self) -> Option<&BitVec<u8, Lsb0>>;
fn null_count(&self) -> usize;
fn load_data_from_raw(
&mut self,
data: Vec<u8>,
offsets: Vec<u32>,
null_bitmap_raw: Option<Vec<u8>>,
bitmap_bit_len: usize,
);
fn get_flush_data(&self) -> (Vec<u8>, Vec<u32>, Option<BitVec<u8, Lsb0>>);
}
#[derive(Debug, Clone)]
pub enum ColumnInner {
Fixed(FixedWidthColumn),
Variable(VariableWidthColumn),
}
impl ColumnInner {
pub(crate) fn as_storage(&self) -> &dyn ColumnStorage {
match self {
ColumnInner::Fixed(c) => c,
ColumnInner::Variable(c) => c,
}
}
pub(crate) fn as_storage_mut(&mut self) -> &mut dyn ColumnStorage {
match self {
ColumnInner::Fixed(c) => c,
ColumnInner::Variable(c) => c,
}
}
}
pub const EVICTION_SEGMENT_BYTES: u64 = 16 * 1024 * 1024;
pub const MAX_BACKGROUND_LOAD_CHUNKS: usize = 64;
#[derive(Debug, Clone, Copy, Default)]
pub struct BufferLedger {
pub resident_bytes: usize,
pub evicted_bytes: usize,
pub overflow_bytes: usize,
pub dirty_pages: usize,
pub resident_chunks: usize,
pub evicted_chunks: usize,
}
impl BufferLedger {
pub fn total_bytes(&self) -> usize {
self.resident_bytes.saturating_add(self.evicted_bytes)
}
}
#[derive(Debug)]
pub struct Column {
pub name: String,
pub col_id: i32,
pub data_type: DataType,
pub nullable: bool,
pub(super) stats: RwLock<Option<ColumnStats>>,
pub(super) zone_stale_writes: AtomicU64,
pub(super) chunk_capacity: AtomicUsize,
pub(super) overflow_threshold: AtomicUsize,
pub(super) hll: Mutex<Option<HyperLogLog>>,
pub(super) zone: RwLock<super::zone_map::ZoneMaps>,
pub(super) overflow_store: Mutex<OverflowStore>,
pub(super) null_count_cache: parking_lot::RwLock<Option<usize>>,
pub(super) total_dirty_pages: AtomicUsize,
pub(super) chunks: RwLock<Vec<ColumnChunk>>,
}
impl Clone for Column {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
col_id: self.col_id,
data_type: self.data_type.clone(),
nullable: self.nullable,
stats: RwLock::new(self.stats.read().clone()),
zone_stale_writes: AtomicU64::new(self.zone_stale_writes.load(Ordering::Relaxed)),
chunk_capacity: AtomicUsize::new(self.chunk_capacity.load(Ordering::Relaxed)),
overflow_threshold: AtomicUsize::new(self.overflow_threshold.load(Ordering::Relaxed)),
hll: Mutex::new(self.hll.lock().clone()),
zone: RwLock::new(self.zone.read().clone()),
overflow_store: Mutex::new(self.overflow_store.lock().clone()),
null_count_cache: parking_lot::RwLock::new(*self.null_count_cache.read()),
total_dirty_pages: AtomicUsize::new(self.total_dirty_pages.load(Ordering::Relaxed)),
chunks: RwLock::new(self.chunks.read().clone()),
}
}
}
impl Column {
pub fn new(name: String, col_id: i32, data_type: DataType, nullable: bool) -> Self {
Self {
name,
col_id,
data_type,
nullable,
stats: RwLock::new(None),
zone_stale_writes: AtomicU64::new(0),
chunk_capacity: AtomicUsize::new(DEFAULT_CHUNK_ROWS),
overflow_threshold: AtomicUsize::new(DEFAULT_OVERFLOW_THRESHOLD),
hll: Mutex::new(Some(HyperLogLog::new())),
zone: RwLock::new(super::zone_map::ZoneMaps::default()),
overflow_store: Mutex::new(OverflowStore::new(DEFAULT_OVERFLOW_THRESHOLD)),
null_count_cache: parking_lot::RwLock::new(None),
total_dirty_pages: AtomicUsize::new(0),
chunks: RwLock::new(Vec::new()),
}
}
pub fn chunk_capacity(&self) -> usize {
self.chunk_capacity.load(Ordering::Relaxed)
}
pub fn set_chunk_capacity(&self, capacity: usize) {
if capacity > 0 {
self.chunk_capacity.store(capacity, Ordering::Relaxed);
}
}
pub fn overflow_threshold(&self) -> usize {
self.overflow_threshold.load(Ordering::Relaxed)
}
pub(super) fn decode_locked(&self, chunks: &[ColumnChunk], idx: usize) {
let Some(chunk) = chunks.get(idx) else {
return;
};
{
let state = chunk.read_state();
if !state.encoding.is_encoded() && state.residency.is_resident() {
return;
}
}
let (offset, count) = (chunk.row_offset, chunk.row_count);
let mut base: Vec<Option<Value>> = (offset..offset + count)
.map(|row| self.encoding_base_value_in(chunks, row))
.collect();
let mut state = chunk.write_state();
for (local, value) in state.overlay.iter() {
if let Some(slot) = base.get_mut(*local as usize) {
*slot = value.clone();
}
}
let mut num_values = 0u32;
for (local, value) in base.into_iter().enumerate() {
if value.is_some() {
num_values += 1;
}
let _ = state.raw.as_storage_mut().set(local, value.as_ref());
}
state.encoding = ColumnEncoding::None;
state.residency = ChunkResidency::Resident;
state.overlay.clear();
state.updates_since_encode = 0;
state.encoding_meta = crate::encoding::ChunkEncodingMeta {
num_values,
all_null: num_values == 0,
..Default::default()
};
}
pub(super) fn column_len(chunks: &[ColumnChunk]) -> usize {
chunks
.last()
.map(|chunk| chunk.row_offset + chunk.row_count)
.unwrap_or(0)
}
pub(super) fn ensure_coverage(&self, row_idx: usize) -> usize {
let capacity = self.chunk_capacity().max(1);
{
let chunks = self.chunks.read();
if Self::column_len(&chunks) > row_idx {
return row_idx / capacity;
}
}
let mut chunks = self.chunks.write();
let slice: &mut Vec<ColumnChunk> = &mut chunks;
while Self::column_len(slice) <= row_idx {
let tail_full = match slice.last() {
None => true,
Some(tail) => tail.row_count >= capacity,
};
if tail_full {
let offset = Self::column_len(slice);
slice.push(ColumnChunk::new(offset, 0, &self.data_type, self.nullable));
} else {
let last = slice.len() - 1;
self.decode_locked(slice, last);
}
let last = slice.len() - 1;
let window_end = (last + 1) * capacity;
let end_target = (row_idx + 1).min(window_end);
let chunk = &mut slice[last];
let end = chunk.row_offset + chunk.row_count;
if end_target > end {
chunk.row_count += end_target - end;
let row_count = chunk.row_count;
chunk.write_state().raw.as_storage_mut().resize(row_count);
}
}
row_idx / capacity
}
pub(super) fn split_raw(raw: &ColumnInner, at: usize) -> (ColumnInner, ColumnInner) {
match raw {
ColumnInner::Fixed(fixed) => {
let elem = fixed.element_size.max(1);
let (left_data, right_data) = fixed.data.split_at(at * elem);
let (left_bits, right_bits) = match &fixed.null_bitmap {
Some(b) => {
let mut l = BitVec::new();
let mut r = BitVec::new();
for (i, bit) in b.iter().by_vals().enumerate() {
if i < at {
l.push(bit);
} else {
r.push(bit);
}
}
(Some(l), Some(r))
}
None => (None, None),
};
let mut left =
FixedWidthColumn::new(fixed.data_type.clone(), fixed.null_bitmap.is_some());
left.data = left_data.to_vec();
left.null_bitmap = left_bits;
left.row_count = at;
left.null_count = left
.null_bitmap
.as_ref()
.map(|b| b.count_ones())
.unwrap_or(0);
let mut right =
FixedWidthColumn::new(fixed.data_type.clone(), fixed.null_bitmap.is_some());
right.data = right_data.to_vec();
right.null_bitmap = right_bits;
right.row_count = fixed.row_count.saturating_sub(at);
right.null_count = right
.null_bitmap
.as_ref()
.map(|b| b.count_ones())
.unwrap_or(0);
(ColumnInner::Fixed(left), ColumnInner::Fixed(right))
}
ColumnInner::Variable(var) => {
let mut left_data = Vec::new();
let mut left_offsets: Vec<u32> = Vec::new();
let mut left_bits: Option<BitVec<u8, Lsb0>> =
var.null_bitmap.as_ref().map(|_| BitVec::new());
let mut right_data = Vec::new();
let mut right_offsets: Vec<u32> = Vec::new();
let mut right_bits: Option<BitVec<u8, Lsb0>> =
var.null_bitmap.as_ref().map(|_| BitVec::new());
let bits: Vec<bool> = var
.null_bitmap
.as_ref()
.map(|b| b.iter().by_vals().collect())
.unwrap_or_default();
for local in 0..var.row_count {
let off = var.offsets.get(local).copied().unwrap_or(u32::MAX) as usize;
let bit = bits.get(local).copied().unwrap_or(false);
let (data, offsets, bits) = if local < at {
(&mut left_data, &mut left_offsets, &mut left_bits)
} else {
(&mut right_data, &mut right_offsets, &mut right_bits)
};
if off == u32::MAX as usize || off + 8 > var.data.len() {
offsets.push(u32::MAX);
} else {
let len_bytes: [u8; 8] =
var.data[off..off + 8].try_into().unwrap_or([0u8; 8]);
let len = u64::from_le_bytes(len_bytes) as usize;
if off + 8 + len > var.data.len() {
offsets.push(u32::MAX);
} else {
offsets.push(data.len() as u32);
data.extend_from_slice(&var.data[off..off + 8 + len]);
}
}
if let Some(b) = bits {
b.push(bit);
}
}
let mut left =
VariableWidthColumn::new(var.data_type.clone(), var.null_bitmap.is_some());
left.load_data_from_raw(
left_data,
left_offsets,
left_bits.map(|b| b.into_vec()),
at,
);
let mut right =
VariableWidthColumn::new(var.data_type.clone(), var.null_bitmap.is_some());
right.load_data_from_raw(
right_data,
right_offsets,
right_bits.map(|b| b.into_vec()),
var.row_count.saturating_sub(at),
);
(ColumnInner::Variable(left), ColumnInner::Variable(right))
}
}
}
pub(super) fn write_core(
&self,
chunk: &ColumnChunk,
state: &mut ChunkState,
row_idx: usize,
value: Option<&Value>,
use_chunk_layer: bool,
) -> StorageResult<bool> {
use crate::vertex::column::chunk_encoding::UpdateDecision;
let in_window = row_idx >= chunk.row_offset && row_idx < chunk.row_offset + chunk.row_count;
if !in_window {
return Err(StorageError::invalid_input(format!(
"column {} has no chunk covering row {}",
self.name, row_idx
)));
}
let local = (row_idx - chunk.row_offset) as u32;
if super::overflow::OverflowStore::routes_for(&self.data_type) {
let payload: Option<Vec<u8>> = match value {
Some(v) if !v.is_null() => super::overflow::overflow_payload_bytes(v),
_ => None,
};
if let Some(bytes) = payload {
let handle = {
let mut store = self.overflow_store.lock();
if !store.should_overflow(bytes.len()) {
None
} else {
Some(store.append(&bytes)?)
}
};
if let Some(handle) = handle {
state.overflow_rows.insert(local, handle);
state.overlay.remove(local);
let placeholder = Value::string("");
let normalized = match Some(&placeholder) {
Some(v) if v.is_null() => None,
other => other,
};
let _ = state.raw.as_storage_mut().set(local as usize, normalized);
return Ok(false);
}
}
state.overflow_rows.remove(&local);
}
if use_chunk_layer {
let decision = ColumnChunk::set_value(state, local, value.cloned(), &self.data_type);
if decision == UpdateDecision::OverlayAndRecode {
state.updates_since_encode = state
.updates_since_encode
.max(state.overlay.capacity() as u64);
}
if decision == UpdateDecision::InPlace && !state.encoding.is_encoded() {
let normalized = match value {
Some(v) if v.is_null() => None,
other => other,
};
if normalized.is_none() && !self.nullable {
return Err(StorageError::null_value_not_allowed(self.name.clone()));
}
state.raw.as_storage_mut().set(local as usize, normalized)?;
return Ok(true);
}
if value.is_none() && !self.nullable {
return Err(StorageError::null_value_not_allowed(self.name.clone()));
}
if let Some(v) = value {
if v.is_null() && !self.nullable {
return Err(StorageError::null_value_not_allowed(self.name.clone()));
}
}
return Ok(true);
}
let normalized = match value {
Some(v) if v.is_null() => None,
other => other,
};
if normalized.is_none() && !self.nullable {
return Err(StorageError::null_value_not_allowed(self.name.clone()));
}
state.raw.as_storage_mut().set(local as usize, normalized)?;
Ok(false)
}
pub(super) fn chunk_layer_routes(&self, chunks: &[ColumnChunk]) -> bool {
chunks
.iter()
.any(|c| c.evicted_encoding() != crate::encoding::EncodingType::None)
}
pub(super) fn with_resident_chunk<T>(
&self,
row_idx: usize,
f: impl FnOnce(&ColumnChunk, &mut ChunkState) -> StorageResult<T>,
) -> StorageResult<T> {
let chunk_idx = self.ensure_coverage(row_idx);
if self.chunk_index_for_row(row_idx).is_some() {
self.ensure_resident(chunk_idx)?;
}
let chunks = self.chunks.read();
let Some(chunk) = chunks.get(chunk_idx) else {
return Err(StorageError::invalid_input(format!(
"column {} has no chunk covering row {}",
self.name, row_idx
)));
};
let mut state = chunk.write_state();
f(chunk, &mut state)
}
pub(super) fn write_raw_inner(
&self,
row_idx: usize,
value: Option<&Value>,
) -> StorageResult<()> {
let normalized = match value {
Some(v) if v.is_null() => None,
other => other,
};
if normalized.is_none() && !self.nullable {
return Err(StorageError::null_value_not_allowed(self.name.clone()));
}
self.with_resident_chunk(row_idx, |chunk, state| {
let local = row_idx - chunk.row_offset;
state.raw.as_storage_mut().set(local, normalized)?;
Ok(())
})
}
pub(super) fn observe_write(&self, row_idx: usize, value: Option<&Value>) {
self.update_zone_maps(row_idx, value);
self.zone_stale_writes.fetch_add(1, Ordering::Relaxed);
*self.null_count_cache.write() = None;
if let Some(v) = value {
if !v.is_null() {
if let Some(hll) = self.hll.lock().as_mut() {
hll.add_value(v);
}
}
}
}
pub(super) fn write_value_without_dirty(
&self,
row_idx: usize,
value: Option<&Value>,
) -> StorageResult<()> {
let use_chunk_layer = self.chunk_layer_routes(&self.chunks.read());
let absorbed = self.with_resident_chunk(row_idx, |chunk, state| {
state.visibility.ensure_len(chunk.row_count);
self.write_core(chunk, state, row_idx, value, use_chunk_layer)
})?;
let _ = absorbed;
self.observe_write(row_idx, value);
Ok(())
}
pub fn set(&self, row_idx: usize, value: Option<&Value>) -> StorageResult<()> {
if value.is_none() && !self.nullable {
return Err(StorageError::null_value_not_allowed(self.name.clone()));
}
if let Some(v) = value {
if v.is_null() && !self.nullable {
return Err(StorageError::null_value_not_allowed(self.name.clone()));
}
}
let use_chunk_layer = self.chunk_layer_routes(&self.chunks.read());
let absorbed = self.with_resident_chunk(row_idx, |chunk, state| {
let local = row_idx - chunk.row_offset;
state.visibility.ensure_len(chunk.row_count);
if let Some(chains) = state.version_chains.as_mut() {
chains.remove(&local);
}
state.visibility.mark_created(local, 0);
self.write_core(chunk, state, row_idx, value, use_chunk_layer)
})?;
self.observe_write(row_idx, value);
self.mark_dirty(row_idx);
if absorbed {
if let Some(chunk_idx) = self.chunk_index_for_row(row_idx) {
let _ = self.maybe_merge_hot_chunk(chunk_idx);
}
}
Ok(())
}
pub fn set_with_timestamp(
&self,
row_idx: usize,
value: Option<&Value>,
create_ts: Timestamp,
) -> StorageResult<()> {
if value.is_none() && !self.nullable {
return Err(StorageError::null_value_not_allowed(self.name.clone()));
}
if let Some(v) = value {
if v.is_null() && !self.nullable {
return Err(StorageError::null_value_not_allowed(self.name.clone()));
}
}
let use_chunk_layer = self.chunk_layer_routes(&self.chunks.read());
let absorbed = self.with_resident_chunk(row_idx, |chunk, state| {
let local = row_idx - chunk.row_offset;
state.visibility.ensure_len(chunk.row_count);
if let Some(chains) = state.version_chains.as_mut() {
chains.remove(&local);
}
state.visibility.mark_created(local, create_ts);
self.write_core(chunk, state, row_idx, value, use_chunk_layer)
})?;
self.observe_write(row_idx, value);
self.mark_dirty(row_idx);
if absorbed {
if let Some(chunk_idx) = self.chunk_index_for_row(row_idx) {
let _ = self.maybe_merge_hot_chunk(chunk_idx);
}
}
Ok(())
}
pub fn get(&self, row_idx: usize) -> Option<Value> {
let chunks = self.chunks.read();
self.get_in(&chunks, row_idx)
}
pub fn try_get(&self, row_idx: usize) -> StorageResult<Option<Value>> {
let chunks = self.chunks.read();
self.try_get_in(&chunks, row_idx)
}
pub(super) fn try_get_in(
&self,
chunks: &[ColumnChunk],
row_idx: usize,
) -> StorageResult<Option<Value>> {
let capacity = self.chunk_capacity();
let Some(chunk) = chunks.get(row_idx / capacity.max(1)) else {
return Ok(None);
};
if row_idx < chunk.row_offset || row_idx >= chunk.row_offset + chunk.row_count {
return Ok(None);
}
let local = (row_idx - chunk.row_offset) as u32;
if super::overflow::OverflowStore::routes_for(&self.data_type) {
let handle = chunk.read_state().overflow_rows.get(&local).copied();
if let Some(handle) = handle {
let bytes = self.overflow_store.lock().get(&handle).ok_or_else(|| {
StorageError::deserialize_error(format!(
"column {} overflow payload missing at row {}",
self.name, row_idx
))
})?;
let value = super::overflow::decode_overflow_payload(&self.data_type, bytes)
.map_err(|e| {
StorageError::deserialize_error(format!(
"column {} overflow decode failed at row {}: {}",
self.name, row_idx, e
))
})?;
return Ok(Some(value));
}
}
chunk.touch();
let state = chunk.read_state();
match &state.residency {
ChunkResidency::Resident => {
if let Some(hit) = state.overlay.get(local) {
return Ok(hit);
}
if state.encoding.is_encoded() {
return Ok(self.restore_string_type(state.encoding.get(local as usize)));
}
state.raw.as_storage().try_get(local as usize).map_err(|e| {
StorageError::deserialize_error(format!(
"column {} raw decode failed at rows {}-{}: {}",
self.name,
chunk.row_offset,
chunk.row_offset + chunk.row_count,
e
))
})
}
ChunkResidency::Evicted(snapshot) => snapshot.decode_row(row_idx).map_err(|e| {
StorageError::deserialize_error(format!(
"column {} evicted snapshot decode failed at row {}: {}",
self.name, row_idx, e
))
}),
}
}
pub(super) fn get_in(&self, chunks: &[ColumnChunk], row_idx: usize) -> Option<Value> {
match self.try_get_in(chunks, row_idx) {
Ok(value) => value,
Err(e) => {
log::warn!(
"column {} row {} lenient read failed: {}; reading as missing",
self.name,
row_idx,
e
);
None
}
}
}
pub(super) fn restore_string_type(&self, value: Option<Value>) -> Option<Value> {
match (value, &self.data_type) {
(Some(Value::String(s)), DataType::FixedString(_)) => {
Some(Value::FixedString(s.to_string()))
}
(v, _) => v,
}
}
pub fn is_null(&self, row_idx: usize) -> bool {
let chunks = self.chunks.read();
Self::is_null_in(self, &chunks, row_idx)
}
fn is_null_in(column: &Column, chunks: &[ColumnChunk], row_idx: usize) -> bool {
let capacity = column.chunk_capacity();
let Some(chunk) = chunks.get(row_idx / capacity.max(1)) else {
return true;
};
if row_idx < chunk.row_offset || row_idx >= chunk.row_offset + chunk.row_count {
return true;
}
let local = (row_idx - chunk.row_offset) as u32;
if super::overflow::OverflowStore::routes_for(&column.data_type)
&& chunk.read_state().overflow_rows.contains_key(&local)
{
return false;
}
let state = chunk.read_state();
if let Some(hit) = state.overlay.get(local) {
return hit.is_none();
}
if state.residency.is_evicted() {
match state
.residency
.evicted_snapshot()
.map(|snapshot| snapshot.decode_row(row_idx))
{
Some(Ok(value)) => return value.is_none(),
Some(Err(e)) => {
log::warn!(
"column {} row {} evicted snapshot decode failed in null check: {}; treating as non-null",
column.name,
row_idx,
e
);
return false;
}
None => return true,
}
}
if state.encoding.is_encoded() {
return state.encoding.get(local as usize).is_none();
}
state.raw.as_storage().is_null(local as usize)
}
pub fn null_count(&self) -> usize {
if let Some(cached) = *self.null_count_cache.read() {
return cached;
}
let chunks = self.chunks.read();
let mut total = 0usize;
for chunk in chunks.iter() {
let state = chunk.read_state();
let fast_raw = !state.encoding.is_encoded()
&& state.residency.is_resident()
&& state.overlay.len() == 0
&& state.overflow_rows.is_empty();
drop(state);
if fast_raw {
total += chunk.read_state().raw.as_storage().null_count();
continue;
}
for offset in 0..chunk.row_count {
if Self::is_null_in(self, &chunks, chunk.row_offset + offset) {
total += 1;
}
}
}
*self.null_count_cache.write() = Some(total);
total
}
pub fn len(&self) -> usize {
Self::column_len(&self.chunks.read())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn memory_usage(&self) -> usize {
let chunks = self.chunks.read();
let mut version_bytes = 0usize;
let mut chunk_bytes = 0usize;
for chunk in chunks.iter() {
let state = chunk.read_state();
if let Some(chains) = state.version_chains.as_ref() {
for chain in chains.values() {
version_bytes += chain.len() * std::mem::size_of::<super::mvcc::VersionEntry>();
for entry in chain.iter() {
version_bytes += entry
.value
.as_ref()
.map(super::value_payload_bytes)
.unwrap_or(0);
}
}
}
version_bytes += state.visibility.memory_usage();
chunk_bytes += state.overlay.memory_usage()
+ state.encoding_meta.memory_usage()
+ state.raw.as_storage().memory_usage()
+ state.encoding.memory_usage()
+ state.overflow_rows.len() * std::mem::size_of::<(u32, OverflowHandle)>();
}
let hll_bytes = self.hll.lock().as_ref().map(|_| 64usize).unwrap_or(0);
version_bytes + chunk_bytes + hll_bytes + self.overflow_store.lock().memory_usage()
}
pub fn memory_size(&self) -> usize {
self.memory_usage() + std::mem::size_of::<Self>()
}
pub fn used_memory_size(&self) -> usize {
let elem_size = super::element_size(&self.data_type);
if elem_size == 0 {
return self.memory_usage() + std::mem::size_of::<Self>();
}
let non_null_count = self.len() - self.null_count();
non_null_count * elem_size + std::mem::size_of::<Self>()
}
pub fn clear(&self) {
self.zone_stale_writes.store(0, Ordering::Relaxed);
self.zone.write().maps.clear();
self.zone.write().complex.clear();
self.chunks.write().clear();
self.total_dirty_pages.store(0, Ordering::Relaxed);
*self.null_count_cache.write() = Some(0);
*self.hll.lock() = Some(HyperLogLog::new());
*self.overflow_store.lock() = OverflowStore::new(self.overflow_threshold());
}
pub fn reserve(&self, additional: usize) {
let capacity = self.chunk_capacity().max(1);
let mut chunks = self.chunks.write();
if chunks.is_empty() || chunks.last().is_some_and(|c| c.row_count >= capacity) {
let offset = Self::column_len(&chunks);
chunks.push(ColumnChunk::new(offset, 0, &self.data_type, self.nullable));
}
if let Some(chunk) = chunks.last() {
let mut state = chunk.write_state();
state.raw.as_storage_mut().reserve(additional);
state.visibility.ensure_len(chunk.row_count);
if let Some(chains) = state.version_chains.as_mut() {
chains.reserve(additional);
}
}
drop(chunks);
let needed =
(self.len() + additional).div_ceil(crate::persistence::dirty_page::ROWS_PER_PAGE);
self.total_dirty_pages.fetch_max(needed, Ordering::Relaxed);
}
fn shrink_blocked_by_history(&self, new_count: usize, current: usize) -> bool {
let chunks = self.chunks.read();
for chunk in chunks.iter() {
let chunk_start = chunk.row_offset;
let chunk_end = chunk.row_offset + chunk.row_count;
if chunk_end <= new_count || chunk_start >= current {
continue;
}
let state = chunk.read_state();
if let Some(chains) = state.version_chains.as_ref() {
for (local, chain) in chains.iter() {
if chunk_start + local >= new_count && !chain.is_empty() {
return true;
}
}
}
}
false
}
pub fn resize(&self, new_count: usize) -> bool {
*self.null_count_cache.write() = None;
let current = self.len();
if new_count > current {
self.ensure_coverage(new_count - 1);
} else if new_count < current {
if self.shrink_blocked_by_history(new_count, current) {
log::warn!(
"column {} refuses shrink to {} rows: version history still references truncated range",
self.name,
new_count
);
return false;
}
let mut chunks = self.chunks.write();
while let Some(chunk) = chunks.last() {
if chunk.row_offset >= new_count {
chunks.pop();
} else {
break;
}
}
if let Some(chunk) = chunks.last_mut() {
let keep = new_count.saturating_sub(chunk.row_offset);
if keep < chunk.row_count {
chunk.row_count = keep;
let mut state = chunk.write_state();
if !state.encoding.is_encoded() && state.residency.is_resident() {
state.raw.as_storage_mut().resize(keep);
}
let stale: Vec<u32> = state
.overlay
.iter()
.map(|(local, _)| *local)
.filter(|local| (*local as usize) >= keep)
.collect();
for local in stale {
state.overlay.remove(local);
}
state.visibility.truncate(keep);
if let Some(chains) = state.version_chains.as_mut() {
chains.retain(|local, _| *local < keep);
}
state
.overflow_rows
.retain(|local, _| (*local as usize) < keep);
}
}
}
let chunks = self.chunks.read();
for chunk in chunks.iter() {
let mut state = chunk.write_state();
let count = chunk.row_count;
state.visibility.ensure_len(count);
if let Some(chains) = state.version_chains.as_mut() {
chains.retain(|local, _| *local < count);
}
state
.overflow_rows
.retain(|local, _| (*local as usize) < count);
}
drop(chunks);
self.total_dirty_pages.fetch_max(
new_count.div_ceil(crate::persistence::dirty_page::ROWS_PER_PAGE),
Ordering::Relaxed,
);
true
}
pub(super) fn raw_base_value_in(
&self,
chunks: &[ColumnChunk],
row_idx: usize,
) -> Option<Value> {
if super::overflow::OverflowStore::routes_for(&self.data_type) {
let capacity = self.chunk_capacity();
if let Some(chunk) = chunks.get(row_idx / capacity.max(1)) {
if row_idx >= chunk.row_offset && row_idx < chunk.row_offset + chunk.row_count {
let local = (row_idx - chunk.row_offset) as u32;
if chunk.read_state().overflow_rows.contains_key(&local) {
return chunk
.read_state()
.raw
.as_storage()
.get(row_idx - chunk.row_offset);
}
}
}
}
self.get_in(chunks, row_idx)
}
pub(super) fn encoding_base_value_in(
&self,
chunks: &[ColumnChunk],
row_idx: usize,
) -> Option<Value> {
if super::overflow::OverflowStore::routes_for(&self.data_type) {
let capacity = self.chunk_capacity();
if let Some(chunk) = chunks.get(row_idx / capacity.max(1)) {
if row_idx >= chunk.row_offset && row_idx < chunk.row_offset + chunk.row_count {
let local = (row_idx - chunk.row_offset) as u32;
if chunk.read_state().overflow_rows.contains_key(&local) {
return match self.data_type {
DataType::Blob => Some(Value::Blob(Vec::new())),
_ => Some(Value::string("")),
};
}
}
}
}
self.get_in(chunks, row_idx)
}
pub(super) fn values_into_buffers(
&self,
values: impl Iterator<Item = Option<Value>>,
) -> (Vec<u8>, Vec<u32>, Option<BitVec<u8, Lsb0>>) {
let is_var = super::is_variable_length_type(&self.data_type);
let elem_size = super::element_size(&self.data_type);
let mut new_data = Vec::new();
let mut new_offsets = Vec::new();
let mut new_bitmap = self.nullable.then(|| BitVec::with_capacity(1024));
for value in values {
match value {
Some(v) => {
if let Some(ref mut bm) = new_bitmap {
bm.push(false);
}
if is_var {
new_offsets.push(new_data.len() as u32);
match &v {
Value::String(s) => {
let bytes = s.as_bytes();
new_data.extend_from_slice(&(bytes.len() as u64).to_le_bytes());
new_data.extend_from_slice(bytes);
}
Value::FixedString(s) => {
let bytes = s.as_bytes();
new_data.extend_from_slice(&(bytes.len() as u64).to_le_bytes());
new_data.extend_from_slice(bytes);
}
_ => {
new_offsets.pop();
new_offsets.push(u32::MAX);
}
}
} else {
let start = new_data.len();
new_data.resize(start + elem_size, 0);
if let DataType::FixedString(limit) = &self.data_type {
let _ = super::fixed_width::write_fixed_string(
&mut new_data,
start,
*limit,
&v,
);
} else {
let _ = super::fixed_width::write_fixed_value(
&mut new_data,
start,
elem_size,
&v,
);
}
}
}
None => {
if let Some(ref mut bm) = new_bitmap {
bm.push(true);
}
if is_var {
new_offsets.push(u32::MAX);
}
}
}
}
(new_data, new_offsets, new_bitmap)
}
pub fn get_flush_data(&self) -> (Vec<u8>, Vec<u32>, Option<BitVec<u8, Lsb0>>) {
let chunks = self.chunks.read();
let all_raw_resident = !chunks.is_empty()
&& chunks.iter().all(|c| {
let state = c.read_state();
!state.encoding.is_encoded() && state.residency.is_resident()
});
if all_raw_resident {
let mut data = Vec::new();
let mut offsets = Vec::new();
let mut bitmap = self.nullable.then(BitVec::<u8, Lsb0>::new);
let is_var = super::is_variable_length_type(&self.data_type);
if is_var {
for chunk in chunks.iter() {
let state = chunk.read_state();
let (chunk_data, chunk_offsets, _) = state.raw.as_storage().get_flush_data();
let base = data.len() as u32;
for off in chunk_offsets {
offsets.push(if off == u32::MAX {
u32::MAX
} else {
base.saturating_add(off)
});
}
data.extend_from_slice(&chunk_data);
if let Some(bm) = bitmap.as_mut() {
if let Some(chunk_bits) = state.raw.as_storage().null_bitmap() {
bm.extend(chunk_bits.iter().by_vals());
}
}
}
} else {
for chunk in chunks.iter() {
let state = chunk.read_state();
let (chunk_data, _, _) = state.raw.as_storage().get_flush_data();
data.extend_from_slice(&chunk_data);
if let Some(bm) = bitmap.as_mut() {
if let Some(chunk_bits) = state.raw.as_storage().null_bitmap() {
bm.extend(chunk_bits.iter().by_vals());
}
}
}
}
return (data, offsets, bitmap);
}
let row_count = Self::column_len(&chunks);
self.values_into_buffers((0..row_count).map(|row| self.raw_base_value_in(&chunks, row)))
}
pub fn has_chunks(&self) -> bool {
!self.chunks.read().is_empty()
}
pub fn chunk_count(&self) -> usize {
self.chunks.read().len()
}
pub fn chunk_index_for_row(&self, row_idx: usize) -> Option<usize> {
let chunks = self.chunks.read();
if chunks.is_empty() {
return None;
}
let idx = row_idx / self.chunk_capacity().max(1);
if idx < chunks.len() {
Some(idx)
} else {
None
}
}
pub(crate) fn has_overflow(&self) -> bool {
let chunks = self.chunks.read();
chunks
.iter()
.any(|c| !c.read_state().overflow_rows.is_empty())
}
pub(crate) fn chunk_needs_recode_for_row(&self, row_idx: usize) -> bool {
let chunks = self.chunks.read();
let capacity = self.chunk_capacity();
chunks
.get(row_idx / capacity.max(1))
.is_some_and(|chunk| chunk.needs_recode())
}
pub(crate) fn set_chunks(&self, chunks: Vec<ColumnChunk>) {
*self.chunks.write() = chunks;
}
pub fn set_overflow_threshold(&self, threshold: usize) {
self.overflow_threshold.store(threshold, Ordering::Relaxed);
self.overflow_store.lock().set_threshold(threshold);
}
}