use std::sync::Arc;
use vibesql_storage::Row;
use vibesql_types::SqlValue;
use super::SelectionVector;
use crate::{
errors::ExecutorError,
select::columnar::{ColumnArray, ColumnarBatch},
};
#[derive(Debug, Clone)]
pub enum SourceData {
Rows(Arc<Vec<Row>>),
Columnar(Arc<ColumnarBatch>),
}
impl SourceData {
pub fn row_count(&self) -> usize {
match self {
SourceData::Rows(rows) => rows.len(),
SourceData::Columnar(batch) => batch.row_count(),
}
}
pub fn column_count(&self) -> usize {
match self {
SourceData::Rows(rows) => rows.first().map(|r| r.len()).unwrap_or(0),
SourceData::Columnar(batch) => batch.column_count(),
}
}
pub fn get_value(&self, row_idx: usize, col_idx: usize) -> Result<SqlValue, ExecutorError> {
match self {
SourceData::Rows(rows) => rows
.get(row_idx)
.and_then(|row| row.get(col_idx))
.cloned()
.ok_or(ExecutorError::ColumnIndexOutOfBounds { index: col_idx }),
SourceData::Columnar(batch) => batch.get_value(row_idx, col_idx),
}
}
pub fn get_row(&self, row_idx: usize) -> Result<Row, ExecutorError> {
match self {
SourceData::Rows(rows) => rows
.get(row_idx)
.cloned()
.ok_or(ExecutorError::ColumnIndexOutOfBounds { index: row_idx }),
SourceData::Columnar(batch) => {
let mut values = Vec::with_capacity(batch.column_count());
for col_idx in 0..batch.column_count() {
values.push(batch.get_value(row_idx, col_idx)?);
}
Ok(Row::new(values))
}
}
}
}
#[derive(Debug, Clone)]
pub struct LazyMaterializedBatch {
source: SourceData,
selection: SelectionVector,
column_names: Option<Vec<String>>,
}
impl LazyMaterializedBatch {
pub fn new(source: SourceData) -> Self {
let row_count = source.row_count();
Self { source, selection: SelectionVector::all(row_count), column_names: None }
}
pub fn with_selection(source: SourceData, selection: SelectionVector) -> Self {
Self { source, selection, column_names: None }
}
pub fn from_rows(rows: Vec<Row>) -> Self {
Self::new(SourceData::Rows(Arc::new(rows)))
}
pub fn from_columnar(batch: ColumnarBatch) -> Self {
Self::new(SourceData::Columnar(Arc::new(batch)))
}
pub fn with_column_names(mut self, names: Vec<String>) -> Self {
self.column_names = Some(names);
self
}
pub fn source(&self) -> &SourceData {
&self.source
}
pub fn selection(&self) -> &SelectionVector {
&self.selection
}
pub fn column_names(&self) -> Option<&[String]> {
self.column_names.as_deref()
}
pub fn len(&self) -> usize {
self.selection.len()
}
pub fn is_empty(&self) -> bool {
self.selection.is_empty()
}
pub fn column_count(&self) -> usize {
self.source.column_count()
}
pub fn source_row_count(&self) -> usize {
self.source.row_count()
}
pub fn filter(&self, bitmap: &[bool]) -> Self {
let new_selection = self.selection.filter(bitmap, |&idx| bitmap[idx as usize]);
Self {
source: self.source.clone(),
selection: new_selection,
column_names: self.column_names.clone(),
}
}
pub fn filter_with<F>(&self, predicate: F) -> Self
where
F: Fn(usize) -> bool,
{
let new_indices: Vec<u32> =
self.selection.iter().filter(|&idx| predicate(idx as usize)).collect();
Self {
source: self.source.clone(),
selection: SelectionVector::from_indices(new_indices),
column_names: self.column_names.clone(),
}
}
pub fn intersect_selection(&self, other: &SelectionVector) -> Self {
Self {
source: self.source.clone(),
selection: self.selection.intersect(other),
column_names: self.column_names.clone(),
}
}
pub fn union_selection(&self, other: &SelectionVector) -> Self {
Self {
source: self.source.clone(),
selection: self.selection.union(other),
column_names: self.column_names.clone(),
}
}
pub fn materialize(&self) -> Result<Vec<Row>, ExecutorError> {
let mut rows = Vec::with_capacity(self.selection.len());
for idx in self.selection.iter() {
let row = self.source.get_row(idx as usize)?;
rows.push(row);
}
Ok(rows)
}
pub fn materialize_columns(&self, column_indices: &[usize]) -> Result<Vec<Row>, ExecutorError> {
let mut rows = Vec::with_capacity(self.selection.len());
for idx in self.selection.iter() {
let mut values = Vec::with_capacity(column_indices.len());
for &col_idx in column_indices {
let value = self.source.get_value(idx as usize, col_idx)?;
values.push(value);
}
rows.push(Row::new(values));
}
Ok(rows)
}
pub fn materialize_column(&self, column_idx: usize) -> Result<Vec<SqlValue>, ExecutorError> {
let mut values = Vec::with_capacity(self.selection.len());
for idx in self.selection.iter() {
let value = self.source.get_value(idx as usize, column_idx)?;
values.push(value);
}
Ok(values)
}
pub fn get_selected_value(
&self,
selection_idx: usize,
column_idx: usize,
) -> Result<SqlValue, ExecutorError> {
let row_idx = self
.selection
.get(selection_idx)
.ok_or(ExecutorError::ColumnIndexOutOfBounds { index: selection_idx })?;
self.source.get_value(row_idx as usize, column_idx)
}
pub fn iter_indices(&self) -> impl Iterator<Item = u32> + '_ {
self.selection.iter()
}
pub fn remap_selection(&self, child_selection: &SelectionVector) -> Self {
let remapped = child_selection.remap(&self.selection);
Self {
source: self.source.clone(),
selection: remapped,
column_names: self.column_names.clone(),
}
}
pub fn column_array(&self, column_idx: usize) -> Option<&ColumnArray> {
match &self.source {
SourceData::Columnar(batch) => batch.column(column_idx),
SourceData::Rows(_) => None,
}
}
pub fn is_columnar(&self) -> bool {
matches!(&self.source, SourceData::Columnar(_))
}
pub fn to_columnar(&self) -> Result<LazyMaterializedBatch, ExecutorError> {
match &self.source {
SourceData::Columnar(_) => Ok(self.clone()),
SourceData::Rows(rows) => {
let batch = ColumnarBatch::from_rows(rows)?;
Ok(LazyMaterializedBatch {
source: SourceData::Columnar(Arc::new(batch)),
selection: self.selection.clone(),
column_names: self.column_names.clone(),
})
}
}
}
pub fn selectivity(&self) -> f64 {
self.selection.selectivity(self.source_row_count())
}
}
pub struct JoinedLazyBatchBuilder {
left_source: SourceData,
right_source: SourceData,
left_indices: Vec<u32>,
right_indices: Vec<u32>,
}
impl JoinedLazyBatchBuilder {
pub fn new(left_source: SourceData, right_source: SourceData) -> Self {
Self { left_source, right_source, left_indices: Vec::new(), right_indices: Vec::new() }
}
pub fn add_match(&mut self, left_idx: u32, right_idx: u32) {
self.left_indices.push(left_idx);
self.right_indices.push(right_idx);
}
pub fn add_left_only(&mut self, left_idx: u32) {
self.left_indices.push(left_idx);
self.right_indices.push(u32::MAX); }
pub fn add_right_only(&mut self, right_idx: u32) {
self.left_indices.push(u32::MAX); self.right_indices.push(right_idx);
}
pub fn left_indices(&self) -> &[u32] {
&self.left_indices
}
pub fn right_indices(&self) -> &[u32] {
&self.right_indices
}
pub fn result_count(&self) -> usize {
self.left_indices.len()
}
pub fn materialize(&self) -> Result<Vec<Row>, ExecutorError> {
let left_cols = self.left_source.column_count();
let right_cols = self.right_source.column_count();
let total_cols = left_cols + right_cols;
let mut rows = Vec::with_capacity(self.left_indices.len());
for (&left_idx, &right_idx) in self.left_indices.iter().zip(&self.right_indices) {
let mut values = Vec::with_capacity(total_cols);
if left_idx == u32::MAX {
for _ in 0..left_cols {
values.push(SqlValue::Null);
}
} else {
for col_idx in 0..left_cols {
values.push(self.left_source.get_value(left_idx as usize, col_idx)?);
}
}
if right_idx == u32::MAX {
for _ in 0..right_cols {
values.push(SqlValue::Null);
}
} else {
for col_idx in 0..right_cols {
values.push(self.right_source.get_value(right_idx as usize, col_idx)?);
}
}
rows.push(Row::new(values));
}
Ok(rows)
}
pub fn materialize_columns(
&self,
left_columns: &[usize],
right_columns: &[usize],
) -> Result<Vec<Row>, ExecutorError> {
let total_cols = left_columns.len() + right_columns.len();
let mut rows = Vec::with_capacity(self.left_indices.len());
for (&left_idx, &right_idx) in self.left_indices.iter().zip(&self.right_indices) {
let mut values = Vec::with_capacity(total_cols);
if left_idx == u32::MAX {
for _ in 0..left_columns.len() {
values.push(SqlValue::Null);
}
} else {
for &col_idx in left_columns {
values.push(self.left_source.get_value(left_idx as usize, col_idx)?);
}
}
if right_idx == u32::MAX {
for _ in 0..right_columns.len() {
values.push(SqlValue::Null);
}
} else {
for &col_idx in right_columns {
values.push(self.right_source.get_value(right_idx as usize, col_idx)?);
}
}
rows.push(Row::new(values));
}
Ok(rows)
}
}
#[cfg(test)]
mod lazy_batch_tests {
use super::*;
fn sample_rows() -> Vec<Row> {
vec![
Row::new(vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Alice"))]),
Row::new(vec![SqlValue::Integer(2), SqlValue::Varchar(arcstr::ArcStr::from("Bob"))]),
Row::new(vec![SqlValue::Integer(3), SqlValue::Varchar(arcstr::ArcStr::from("Carol"))]),
Row::new(vec![SqlValue::Integer(4), SqlValue::Varchar(arcstr::ArcStr::from("Dave"))]),
Row::new(vec![SqlValue::Integer(5), SqlValue::Varchar(arcstr::ArcStr::from("Eve"))]),
]
}
#[test]
fn test_lazy_batch_creation() {
let rows = sample_rows();
let batch = LazyMaterializedBatch::from_rows(rows);
assert_eq!(batch.len(), 5);
assert_eq!(batch.column_count(), 2);
assert_eq!(batch.source_row_count(), 5);
}
#[test]
fn test_lazy_batch_filter() {
let rows = sample_rows();
let batch = LazyMaterializedBatch::from_rows(rows);
let filtered = batch.filter_with(|idx| {
if let SourceData::Rows(rows) = batch.source() {
if let Some(SqlValue::Integer(id)) = rows[idx].get(0) {
return *id > 2;
}
}
false
});
assert_eq!(filtered.len(), 3); }
#[test]
fn test_lazy_batch_materialize() {
let rows = sample_rows();
let batch = LazyMaterializedBatch::from_rows(rows);
let materialized = batch.materialize().unwrap();
assert_eq!(materialized.len(), 5);
assert_eq!(materialized[0].get(0), Some(&SqlValue::Integer(1)));
}
#[test]
fn test_lazy_batch_materialize_columns() {
let rows = sample_rows();
let batch = LazyMaterializedBatch::from_rows(rows);
let names = batch.materialize_columns(&[1]).unwrap();
assert_eq!(names.len(), 5);
assert_eq!(names[0].len(), 1); assert_eq!(names[0].get(0), Some(&SqlValue::Varchar(arcstr::ArcStr::from("Alice"))));
}
#[test]
fn test_lazy_batch_with_selection() {
let rows = sample_rows();
let selection = SelectionVector::from_indices(vec![0, 2, 4]); let batch =
LazyMaterializedBatch::with_selection(SourceData::Rows(Arc::new(rows)), selection);
assert_eq!(batch.len(), 3);
let materialized = batch.materialize().unwrap();
assert_eq!(materialized[0].get(1), Some(&SqlValue::Varchar(arcstr::ArcStr::from("Alice"))));
assert_eq!(materialized[1].get(1), Some(&SqlValue::Varchar(arcstr::ArcStr::from("Carol"))));
assert_eq!(materialized[2].get(1), Some(&SqlValue::Varchar(arcstr::ArcStr::from("Eve"))));
}
#[test]
fn test_joined_batch_builder() {
let left_rows = vec![
Row::new(vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("A"))]),
Row::new(vec![SqlValue::Integer(2), SqlValue::Varchar(arcstr::ArcStr::from("B"))]),
];
let right_rows = vec![
Row::new(vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("X"))]),
Row::new(vec![SqlValue::Integer(1), SqlValue::Varchar(arcstr::ArcStr::from("Y"))]),
];
let mut builder = JoinedLazyBatchBuilder::new(
SourceData::Rows(Arc::new(left_rows)),
SourceData::Rows(Arc::new(right_rows)),
);
builder.add_match(0, 0);
builder.add_match(0, 1);
builder.add_left_only(1);
assert_eq!(builder.result_count(), 3);
let rows = builder.materialize().unwrap();
assert_eq!(rows.len(), 3);
assert_eq!(rows[0].get(0), Some(&SqlValue::Integer(1)));
assert_eq!(rows[0].get(2), Some(&SqlValue::Integer(1)));
assert_eq!(rows[0].get(3), Some(&SqlValue::Varchar(arcstr::ArcStr::from("X"))));
assert_eq!(rows[1].get(3), Some(&SqlValue::Varchar(arcstr::ArcStr::from("Y"))));
assert_eq!(rows[2].get(0), Some(&SqlValue::Integer(2)));
assert_eq!(rows[2].get(2), Some(&SqlValue::Null));
assert_eq!(rows[2].get(3), Some(&SqlValue::Null));
}
#[test]
fn test_selectivity() {
let rows = sample_rows();
let selection = SelectionVector::from_indices(vec![0, 2]); let batch =
LazyMaterializedBatch::with_selection(SourceData::Rows(Arc::new(rows)), selection);
assert!((batch.selectivity() - 0.4).abs() < 0.001);
}
}