use std::sync::Arc;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::expression::Expression;
use crate::timestamp::get_fast_timestamp;
use crate::traits::table::{
index_values_for_row, AggregateOp, DeferredSum, GroupKey, GroupedAggregateResult,
IntegerPrimaryKeyRange, ScanPlan,
};
use crate::traits::{Index, QueryResult, Scanner, Table};
use radixdb_core::{time_compat::Instant, CompactArc};
use radixdb_core::{DataType, IndexType, Result, Row, RowVec, Schema, Value, ValueMap, ValueSet};
use super::manifest::{ColdSegment, SegmentManager};
use super::scanner::VolumeScanner;
use super::writer::FrozenVolume;
use super::{
column::{ColumnData, ZoneMap},
writer::ColSource,
};
use crate::v6::ArtifactColumnBatch;
const COLD_INDEX_CURSOR_CANDIDATE_LIMIT: usize = 65_536;
const METADATA_PK_COUNT_CANDIDATE_LIMIT: usize = 65_536;
const ARTIFACT_COLUMNAR_GROUP_DIRECT_ARRAY_MAX_WIDTH: u64 = 1_000_000;
type GroupKeyMap<V> = ahash::AHashMap<GroupKey, V>;
struct ColdAggregateProjection {
columns: Vec<usize>,
positions: Vec<Option<usize>>,
aggregates: Vec<(AggregateOp, usize)>,
}
struct ArtifactColumnarGroupPlan {
group_data_type: DataType,
group_projection_pos: usize,
physical_projection: Vec<usize>,
aggregates: Vec<ArtifactColumnarGroupAggregate>,
}
struct ArtifactColumnarGroupAggregate {
operation: AggregateOp,
data_type: DataType,
projection_pos: Option<usize>,
}
fn exact_integer_sum_value(sum: i128) -> Option<Value> {
if let Ok(integer) = i64::try_from(sum) {
return Some(Value::Integer(integer));
}
let precision = u8::try_from(sum.to_string().trim_start_matches('-').len()).ok()?;
Value::try_decimal(sum, precision, 0).ok()
}
#[derive(Clone)]
struct ArtifactColumnarAccum {
count: i64,
int_sum: i128,
float_sum: f64,
min_i64: Option<i64>,
max_i64: Option<i64>,
min_f64: Option<f64>,
max_f64: Option<f64>,
}
impl Default for ArtifactColumnarAccum {
fn default() -> Self {
Self {
count: 0,
int_sum: 0,
float_sum: 0.0,
min_i64: None,
max_i64: None,
min_f64: None,
max_f64: None,
}
}
}
enum ArtifactColumnarGroupState {
HashMap {
groups: FxHashMap<Option<i64>, Vec<ArtifactColumnarAccum>>,
},
DirectArray {
min_key: i64,
slots: Vec<Option<Vec<ArtifactColumnarAccum>>>,
null_group: Option<Vec<ArtifactColumnarAccum>>,
non_null_groups: usize,
},
}
struct ArtifactColumnarGroupSegmentResult {
groups: ArtifactColumnarGroupState,
row_groups: u64,
selected_blocks: u64,
input_rows: u64,
}
impl ArtifactColumnarGroupState {
fn new(bounds: Option<(i64, i64)>) -> Self {
let Some((min_key, max_key)) = bounds else {
return Self::hash_map();
};
let Some(width) = max_key
.checked_sub(min_key)
.and_then(|delta| delta.checked_add(1))
.and_then(|width| u64::try_from(width).ok())
else {
return Self::hash_map();
};
if width == 0 || width > ARTIFACT_COLUMNAR_GROUP_DIRECT_ARRAY_MAX_WIDTH {
return Self::hash_map();
}
let Ok(width) = usize::try_from(width) else {
return Self::hash_map();
};
Self::DirectArray {
min_key,
slots: vec![None; width],
null_group: None,
non_null_groups: 0,
}
}
fn hash_map() -> Self {
Self::HashMap {
groups: FxHashMap::default(),
}
}
fn is_direct_array(&self) -> bool {
matches!(self, Self::DirectArray { .. })
}
fn accums_for_key(
&mut self,
key: Option<i64>,
aggregate_count: usize,
) -> Option<&mut Vec<ArtifactColumnarAccum>> {
match self {
Self::HashMap { groups } => Some(
groups
.entry(key)
.or_insert_with(|| vec![ArtifactColumnarAccum::default(); aggregate_count]),
),
Self::DirectArray {
min_key,
slots,
null_group,
non_null_groups,
} => {
let Some(key) = key else {
return Some(null_group.get_or_insert_with(|| {
vec![ArtifactColumnarAccum::default(); aggregate_count]
}));
};
let offset = key.checked_sub(*min_key)?;
let offset = usize::try_from(offset).ok()?;
let slot = slots.get_mut(offset)?;
if slot.is_none() {
*non_null_groups = non_null_groups.saturating_add(1);
*slot = Some(vec![ArtifactColumnarAccum::default(); aggregate_count]);
}
slot.as_mut()
}
}
}
fn merge_from(&mut self, other: ArtifactColumnarGroupState, aggregate_count: usize) -> bool {
for (key, source_accums) in other.into_key_accums() {
let Some(target_accums) = self.accums_for_key(key, aggregate_count) else {
return false;
};
if target_accums.len() != source_accums.len() {
return false;
}
for (target, source) in target_accums.iter_mut().zip(source_accums) {
target.merge_from(source);
}
}
true
}
fn group_count(&self) -> usize {
match self {
Self::HashMap { groups } => groups.len(),
Self::DirectArray {
null_group,
non_null_groups,
..
} => non_null_groups.saturating_add(usize::from(null_group.is_some())),
}
}
fn into_key_accums(self) -> Vec<(Option<i64>, Vec<ArtifactColumnarAccum>)> {
match self {
Self::HashMap { groups } => groups.into_iter().collect(),
Self::DirectArray {
min_key,
slots,
null_group,
..
} => {
let mut groups = Vec::with_capacity(
slots.iter().filter(|slot| slot.is_some()).count()
+ usize::from(null_group.is_some()),
);
if let Some(accums) = null_group {
groups.push((None, accums));
}
for (offset, slot) in slots.into_iter().enumerate() {
if let Some(accums) = slot {
let Some(key) = i64::try_from(offset)
.ok()
.and_then(|offset| min_key.checked_add(offset))
else {
continue;
};
groups.push((Some(key), accums));
}
}
groups
}
}
}
}
impl ArtifactColumnarAccum {
fn merge_from(&mut self, other: ArtifactColumnarAccum) {
self.count = self.count.saturating_add(other.count);
self.int_sum = self.int_sum.saturating_add(other.int_sum);
self.float_sum += other.float_sum;
self.min_i64 = match (self.min_i64, other.min_i64) {
(Some(left), Some(right)) => Some(left.min(right)),
(None, value) | (value, None) => value,
};
self.max_i64 = match (self.max_i64, other.max_i64) {
(Some(left), Some(right)) => Some(left.max(right)),
(None, value) | (value, None) => value,
};
self.min_f64 = match (self.min_f64, other.min_f64) {
(Some(left), Some(right)) => Some(left.min(right)),
(None, value) | (value, None) => value,
};
self.max_f64 = match (self.max_f64, other.max_f64) {
(Some(left), Some(right)) => Some(left.max(right)),
(None, value) | (value, None) => value,
};
}
}
pub struct SegmentedTable {
hot: Box<dyn Table>,
segment_mgr: Arc<SegmentManager>,
snapshot_seq: Option<u64>,
}
struct ColdIndexRemovalStatementGuard {
manager: Arc<SegmentManager>,
txn_id: i64,
checkpoint: usize,
armed: bool,
}
impl ColdIndexRemovalStatementGuard {
fn new(manager: Arc<SegmentManager>, txn_id: i64) -> Self {
let checkpoint = manager.cold_index_removal_checkpoint(txn_id);
Self {
manager,
txn_id,
checkpoint,
armed: true,
}
}
fn finish(mut self) {
self.armed = false;
}
}
impl Drop for ColdIndexRemovalStatementGuard {
fn drop(&mut self) {
if self.armed {
self.manager
.rollback_cold_index_removals_to_checkpoint(self.txn_id, self.checkpoint);
}
}
}
#[derive(Clone)]
struct ColdCompositeExactPlan {
index_name: String,
declared_column_count: usize,
columns: Vec<String>,
column_indices: Vec<usize>,
values: Vec<Value>,
conditions: Vec<String>,
}
#[derive(Clone)]
struct ColdCompositeOrderedPlan {
index_name: String,
declared_column_count: usize,
columns: Vec<String>,
column_indices: Vec<usize>,
equality_values: Vec<Value>,
min: Option<(i64, bool)>,
max: Option<(i64, bool)>,
conditions: Vec<String>,
covered_columns: FxHashSet<String>,
}
#[derive(Clone)]
struct ColdExactSetPlan {
index_name: String,
declared_column_count: usize,
column: String,
values: Vec<Value>,
}
struct ExactIndexCandidates {
column_index: usize,
requested_values: ValueSet,
row_ids: Vec<i64>,
requires_recheck: bool,
}
mod aggregate;
mod candidates;
mod constraints;
mod contract;
mod mutation;
mod read;
#[cfg(test)]
mod tests;