use vibesql_storage::Row;
use crate::select::columnar::ColumnarBatch;
#[derive(Debug)]
pub enum PipelineInput<'a> {
Rows(&'a [Row]),
RowsOwned(Vec<Row>),
Batch(ColumnarBatch),
NativeColumnar {
table_name: String,
column_indices: Vec<usize>,
},
Empty,
}
impl<'a> PipelineInput<'a> {
#[inline]
pub fn from_rows(rows: &'a [Row]) -> Self {
PipelineInput::Rows(rows)
}
#[inline]
pub fn from_rows_owned(rows: Vec<Row>) -> Self {
PipelineInput::RowsOwned(rows)
}
#[inline]
pub fn from_batch(batch: ColumnarBatch) -> Self {
PipelineInput::Batch(batch)
}
#[inline]
pub fn empty() -> Self {
PipelineInput::Empty
}
#[inline]
pub fn native_columnar(table_name: String, column_indices: Vec<usize>) -> Self {
PipelineInput::NativeColumnar { table_name, column_indices }
}
pub fn into_rows(self) -> Vec<Row> {
match self {
PipelineInput::Rows(rows) => rows.to_vec(),
PipelineInput::RowsOwned(rows) => rows,
PipelineInput::Batch(batch) => {
batch.to_rows().unwrap_or_default()
}
PipelineInput::NativeColumnar { .. } => {
Vec::new()
}
PipelineInput::Empty => Vec::new(),
}
}
pub fn row_count(&self) -> usize {
match self {
PipelineInput::Rows(rows) => rows.len(),
PipelineInput::RowsOwned(rows) => rows.len(),
PipelineInput::Batch(batch) => batch.row_count(),
PipelineInput::NativeColumnar { .. } => 0, PipelineInput::Empty => 1,
}
}
pub fn is_empty(&self) -> bool {
match self {
PipelineInput::Rows(rows) => rows.is_empty(),
PipelineInput::RowsOwned(rows) => rows.is_empty(),
PipelineInput::Batch(batch) => batch.row_count() == 0,
PipelineInput::NativeColumnar { .. } => false, PipelineInput::Empty => false, }
}
pub fn as_rows(&self) -> Option<&[Row]> {
match self {
PipelineInput::Rows(rows) => Some(rows),
PipelineInput::RowsOwned(rows) => Some(rows),
_ => None,
}
}
pub fn as_batch(&self) -> Option<&ColumnarBatch> {
match self {
PipelineInput::Batch(batch) => Some(batch),
_ => None,
}
}
pub fn into_batch(self) -> Option<ColumnarBatch> {
match self {
PipelineInput::Batch(batch) => Some(batch),
_ => None,
}
}
#[inline]
pub fn is_batch(&self) -> bool {
matches!(self, PipelineInput::Batch(_))
}
#[inline]
pub fn is_rows(&self) -> bool {
matches!(self, PipelineInput::Rows(_) | PipelineInput::RowsOwned(_))
}
#[inline]
pub fn is_native_columnar(&self) -> bool {
matches!(self, PipelineInput::NativeColumnar { .. })
}
}
#[derive(Debug, Default)]
pub enum PipelineOutput {
Rows(Vec<Row>),
Batch(ColumnarBatch),
#[default]
Empty,
}
impl PipelineOutput {
#[inline]
pub fn from_rows(rows: Vec<Row>) -> Self {
PipelineOutput::Rows(rows)
}
#[inline]
pub fn from_batch(batch: ColumnarBatch) -> Self {
PipelineOutput::Batch(batch)
}
#[inline]
pub fn empty() -> Self {
PipelineOutput::Empty
}
pub fn into_rows(self) -> Vec<Row> {
match self {
PipelineOutput::Rows(rows) => rows,
PipelineOutput::Batch(batch) => {
batch.to_rows().unwrap_or_default()
}
PipelineOutput::Empty => Vec::new(),
}
}
pub fn row_count(&self) -> usize {
match self {
PipelineOutput::Rows(rows) => rows.len(),
PipelineOutput::Batch(batch) => batch.row_count(),
PipelineOutput::Empty => 0,
}
}
pub fn is_empty(&self) -> bool {
match self {
PipelineOutput::Rows(rows) => rows.is_empty(),
PipelineOutput::Batch(batch) => batch.row_count() == 0,
PipelineOutput::Empty => true,
}
}
pub fn as_rows(&self) -> Option<&[Row]> {
match self {
PipelineOutput::Rows(rows) => Some(rows),
_ => None,
}
}
pub fn as_batch(&self) -> Option<&ColumnarBatch> {
match self {
PipelineOutput::Batch(batch) => Some(batch),
_ => None,
}
}
pub fn into_batch(self) -> Option<ColumnarBatch> {
match self {
PipelineOutput::Batch(batch) => Some(batch),
_ => None,
}
}
#[inline]
pub fn is_batch(&self) -> bool {
matches!(self, PipelineOutput::Batch(_))
}
#[inline]
pub fn is_rows(&self) -> bool {
matches!(self, PipelineOutput::Rows(_))
}
pub fn into_input(self) -> PipelineInput<'static> {
match self {
PipelineOutput::Rows(rows) => PipelineInput::RowsOwned(rows),
PipelineOutput::Batch(batch) => PipelineInput::Batch(batch),
PipelineOutput::Empty => PipelineInput::Empty,
}
}
}
#[cfg(test)]
mod tests {
use vibesql_types::SqlValue;
use super::*;
fn make_test_row(values: Vec<i64>) -> Row {
Row::new(values.into_iter().map(SqlValue::Integer).collect::<Vec<_>>())
}
fn make_test_rows() -> Vec<Row> {
vec![
Row::new(vec![SqlValue::Integer(1), SqlValue::Double(10.5)]),
Row::new(vec![SqlValue::Integer(2), SqlValue::Double(20.5)]),
Row::new(vec![SqlValue::Integer(3), SqlValue::Double(30.5)]),
]
}
#[test]
fn test_pipeline_input_from_rows() {
let rows = vec![make_test_row(vec![1, 2]), make_test_row(vec![3, 4])];
let input = PipelineInput::from_rows(&rows);
assert_eq!(input.row_count(), 2);
assert!(!input.is_empty());
assert!(input.is_rows());
assert!(!input.is_batch());
}
#[test]
fn test_pipeline_input_from_rows_owned() {
let rows = vec![make_test_row(vec![1, 2])];
let input = PipelineInput::from_rows_owned(rows);
assert_eq!(input.row_count(), 1);
assert!(input.is_rows());
let output = input.into_rows();
assert_eq!(output.len(), 1);
}
#[test]
fn test_pipeline_input_from_batch() {
let rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&rows).unwrap();
let input = PipelineInput::from_batch(batch);
assert_eq!(input.row_count(), 3);
assert!(!input.is_empty());
assert!(input.is_batch());
assert!(!input.is_rows());
}
#[test]
fn test_pipeline_input_as_batch() {
let rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&rows).unwrap();
let input = PipelineInput::from_batch(batch);
let batch_ref = input.as_batch();
assert!(batch_ref.is_some());
assert_eq!(batch_ref.unwrap().row_count(), 3);
}
#[test]
fn test_pipeline_input_into_batch() {
let rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&rows).unwrap();
let input = PipelineInput::from_batch(batch);
let batch_owned = input.into_batch();
assert!(batch_owned.is_some());
assert_eq!(batch_owned.unwrap().row_count(), 3);
}
#[test]
fn test_pipeline_input_batch_into_rows() {
let rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&rows).unwrap();
let input = PipelineInput::from_batch(batch);
let output_rows = input.into_rows();
assert_eq!(output_rows.len(), 3);
assert_eq!(output_rows[0].get(0), Some(&SqlValue::Integer(1)));
assert_eq!(output_rows[0].get(1), Some(&SqlValue::Double(10.5)));
}
#[test]
fn test_pipeline_input_as_rows_returns_none_for_batch() {
let rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&rows).unwrap();
let input = PipelineInput::from_batch(batch);
assert!(input.as_rows().is_none());
}
#[test]
fn test_pipeline_input_as_batch_returns_none_for_rows() {
let rows = make_test_rows();
let input = PipelineInput::from_rows(&rows);
assert!(input.as_batch().is_none());
}
#[test]
fn test_pipeline_input_empty() {
let input = PipelineInput::empty();
assert_eq!(input.row_count(), 1); assert!(!input.is_empty());
assert!(!input.is_rows());
assert!(!input.is_batch());
}
#[test]
fn test_pipeline_input_native_columnar() {
let input = PipelineInput::native_columnar("test_table".to_string(), vec![0, 1, 2]);
assert!(input.is_native_columnar());
assert!(!input.is_rows());
assert!(!input.is_batch());
assert_eq!(input.row_count(), 0); }
#[test]
fn test_pipeline_output_from_rows() {
let rows = vec![make_test_row(vec![1, 2])];
let output = PipelineOutput::from_rows(rows);
assert_eq!(output.row_count(), 1);
assert!(!output.is_empty());
assert!(output.is_rows());
assert!(!output.is_batch());
}
#[test]
fn test_pipeline_output_from_batch() {
let rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&rows).unwrap();
let output = PipelineOutput::from_batch(batch);
assert_eq!(output.row_count(), 3);
assert!(!output.is_empty());
assert!(output.is_batch());
assert!(!output.is_rows());
}
#[test]
fn test_pipeline_output_as_batch() {
let rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&rows).unwrap();
let output = PipelineOutput::from_batch(batch);
let batch_ref = output.as_batch();
assert!(batch_ref.is_some());
assert_eq!(batch_ref.unwrap().row_count(), 3);
}
#[test]
fn test_pipeline_output_into_batch() {
let rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&rows).unwrap();
let output = PipelineOutput::from_batch(batch);
let batch_owned = output.into_batch();
assert!(batch_owned.is_some());
assert_eq!(batch_owned.unwrap().row_count(), 3);
}
#[test]
fn test_pipeline_output_batch_into_rows() {
let rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&rows).unwrap();
let output = PipelineOutput::from_batch(batch);
let output_rows = output.into_rows();
assert_eq!(output_rows.len(), 3);
assert_eq!(output_rows[0].get(0), Some(&SqlValue::Integer(1)));
}
#[test]
fn test_pipeline_output_as_rows_returns_none_for_batch() {
let rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&rows).unwrap();
let output = PipelineOutput::from_batch(batch);
assert!(output.as_rows().is_none());
}
#[test]
fn test_pipeline_output_chaining_rows() {
let rows = vec![make_test_row(vec![1, 2])];
let output = PipelineOutput::from_rows(rows);
let input = output.into_input();
assert_eq!(input.row_count(), 1);
assert!(input.is_rows());
}
#[test]
fn test_pipeline_output_chaining_batch() {
let rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&rows).unwrap();
let output = PipelineOutput::from_batch(batch);
let input = output.into_input();
assert_eq!(input.row_count(), 3);
assert!(input.is_batch());
}
#[test]
fn test_empty_pipeline_output() {
let output = PipelineOutput::empty();
assert!(output.is_empty());
assert_eq!(output.row_count(), 0);
assert_eq!(output.into_rows().len(), 0);
}
#[test]
fn test_pipeline_output_default() {
let output = PipelineOutput::default();
assert!(output.is_empty());
}
#[test]
fn test_rows_to_batch_to_rows_roundtrip() {
let original_rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&original_rows).unwrap();
let input = PipelineInput::from_batch(batch);
let converted_rows = input.into_rows();
assert_eq!(converted_rows.len(), original_rows.len());
for (orig, conv) in original_rows.iter().zip(converted_rows.iter()) {
assert_eq!(orig.len(), conv.len());
for i in 0..orig.len() {
assert_eq!(orig.get(i), conv.get(i));
}
}
}
#[test]
fn test_pipeline_chaining_preserves_format() {
let rows = make_test_rows();
let output_rows = PipelineOutput::from_rows(rows);
let input_from_rows = output_rows.into_input();
assert!(input_from_rows.is_rows());
let rows = make_test_rows();
let batch = ColumnarBatch::from_rows(&rows).unwrap();
let output_batch = PipelineOutput::from_batch(batch);
let input_from_batch = output_batch.into_input();
assert!(input_from_batch.is_batch());
let output_empty = PipelineOutput::empty();
let input_from_empty = output_empty.into_input();
assert!(matches!(input_from_empty, PipelineInput::Empty));
}
}