use crate::volume::column::ColumnData;
use radixdb_core::{Result, Row};
pub struct TypedColumnBatch {
row_count: usize,
columns: Vec<ColumnData>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypedBatchFallbackReason {
PendingError,
RowAlreadyFetched,
RowIterationStarted,
Closed,
NotArtifactBacked,
RowFilter,
DictionaryFilter,
IndexSelection,
TypedPredicate,
ExactTypedFilter,
FilterCoveredByTypedPredicates,
RowGroupSkips,
EmptyProjection,
DuplicateProjection,
SchemaMappingMissingColumn,
UnsupportedStorageType,
UnsupportedSchemaDefault,
InvalidRange,
MergedSource,
MixedTypedAndRowSources,
UnsupportedResultShape,
}
impl TypedBatchFallbackReason {
#[inline]
pub fn as_str(self) -> &'static str {
match self {
Self::PendingError => "pending-error",
Self::RowAlreadyFetched => "row-already-fetched",
Self::RowIterationStarted => "row-iteration-started",
Self::Closed => "closed",
Self::NotArtifactBacked => "not-artifact-backed",
Self::RowFilter => "row-filter",
Self::DictionaryFilter => "dictionary-filter",
Self::IndexSelection => "index-selection",
Self::TypedPredicate => "typed-predicate",
Self::ExactTypedFilter => "exact-typed-filter",
Self::FilterCoveredByTypedPredicates => "filter-covered-by-typed-predicates",
Self::RowGroupSkips => "row-group-skips",
Self::EmptyProjection => "empty-projection",
Self::DuplicateProjection => "duplicate-projection",
Self::SchemaMappingMissingColumn => "schema-mapping-missing-column",
Self::UnsupportedStorageType => "unsupported-storage-type",
Self::UnsupportedSchemaDefault => "unsupported-schema-default",
Self::InvalidRange => "invalid-range",
Self::MergedSource => "merged-source",
Self::MixedTypedAndRowSources => "mixed-typed-and-row-sources",
Self::UnsupportedResultShape => "unsupported-result-shape",
}
}
}
impl TypedColumnBatch {
pub fn new(row_count: usize, columns: Vec<ColumnData>) -> Self {
debug_assert!(columns.iter().all(|column| column.len() == row_count));
Self { row_count, columns }
}
#[inline]
pub fn row_count(&self) -> usize {
self.row_count
}
#[inline]
pub fn columns(&self) -> &[ColumnData] {
&self.columns
}
#[inline]
pub fn into_columns(self) -> Vec<ColumnData> {
self.columns
}
}
pub trait Scanner: Send {
fn next(&mut self) -> bool;
fn row(&self) -> &Row;
fn err(&self) -> Option<&radixdb_core::Error>;
fn close(&mut self) -> Result<()>;
fn take_row(&mut self) -> Row {
self.row().clone()
}
fn estimated_count(&self) -> Option<usize> {
None
}
fn take_row_with_id(&mut self) -> Result<(i64, Row)> {
Err(radixdb_core::Error::internal(
"scanner does not expose physical row identity",
))
}
fn current_row_id(&self) -> Result<i64> {
Err(radixdb_core::Error::internal(
"scanner does not expose physical row identity",
))
}
fn collect_remaining_row_ids(&mut self, _output: &mut Vec<i64>) -> Result<bool> {
Ok(false)
}
fn warmup(&mut self) {}
fn supports_typed_batches(&self) -> bool {
false
}
fn typed_batch_fallback_reason(&self) -> Option<TypedBatchFallbackReason> {
Some(TypedBatchFallbackReason::UnsupportedResultShape)
}
fn next_typed_batch(&mut self) -> Result<Option<TypedColumnBatch>> {
Ok(None)
}
#[cfg(any(test, feature = "test-hooks"))]
fn is_warmed_for_test(&self) -> bool {
false
}
}
pub struct EmptyScanner {
empty_row: Row,
closed: bool,
}
impl EmptyScanner {
pub fn new() -> Self {
Self {
empty_row: Row::new(),
closed: false,
}
}
}
impl Default for EmptyScanner {
fn default() -> Self {
Self::new()
}
}
impl Scanner for EmptyScanner {
fn next(&mut self) -> bool {
false
}
fn row(&self) -> &Row {
&self.empty_row
}
fn err(&self) -> Option<&radixdb_core::Error> {
None
}
fn close(&mut self) -> Result<()> {
self.closed = true;
Ok(())
}
fn estimated_count(&self) -> Option<usize> {
Some(0)
}
}
pub struct VecScanner {
rows: Vec<Row>,
current_index: Option<usize>,
error: Option<radixdb_core::Error>,
closed: bool,
}
impl VecScanner {
pub fn new(rows: Vec<Row>) -> Self {
Self {
rows,
current_index: None,
error: None,
closed: false,
}
}
pub fn with_error(error: radixdb_core::Error) -> Self {
Self {
rows: Vec::new(),
current_index: None,
error: Some(error),
closed: false,
}
}
}
impl Scanner for VecScanner {
fn next(&mut self) -> bool {
if self.closed || self.error.is_some() {
return false;
}
let next_index = match self.current_index {
None => 0,
Some(i) => i + 1,
};
if next_index < self.rows.len() {
self.current_index = Some(next_index);
true
} else {
false
}
}
fn row(&self) -> &Row {
match self.current_index {
Some(i) if i < self.rows.len() => &self.rows[i],
_ => {
panic!("row() called without successful next()")
}
}
}
fn err(&self) -> Option<&radixdb_core::Error> {
self.error.as_ref()
}
fn close(&mut self) -> Result<()> {
self.closed = true;
Ok(())
}
fn estimated_count(&self) -> Option<usize> {
Some(self.rows.len())
}
}
#[cfg(test)]
mod tests {
use super::*;
use radixdb_core::Value;
#[test]
fn test_empty_scanner() {
let mut scanner = EmptyScanner::new();
assert!(!scanner.next());
assert!(scanner.err().is_none());
assert!(scanner.close().is_ok());
}
#[test]
fn test_vec_scanner_empty() {
let mut scanner = VecScanner::new(vec![]);
assert!(!scanner.next());
assert!(scanner.err().is_none());
}
#[test]
fn test_vec_scanner_with_rows() {
let rows = vec![
Row::from_values(vec![Value::Integer(1), Value::text("a")]),
Row::from_values(vec![Value::Integer(2), Value::text("b")]),
Row::from_values(vec![Value::Integer(3), Value::text("c")]),
];
let mut scanner = VecScanner::new(rows);
assert!(scanner.next());
assert_eq!(scanner.row().get(0), Some(&Value::Integer(1)));
assert!(scanner.next());
assert_eq!(scanner.row().get(0), Some(&Value::Integer(2)));
assert!(scanner.next());
assert_eq!(scanner.row().get(0), Some(&Value::Integer(3)));
assert!(!scanner.next());
assert!(scanner.err().is_none());
}
#[test]
fn test_vec_scanner_with_error() {
let mut scanner = VecScanner::with_error(radixdb_core::Error::internal("test error"));
assert!(!scanner.next());
assert!(scanner.err().is_some());
}
#[test]
fn test_vec_scanner_close() {
let rows = vec![Row::from_values(vec![Value::Integer(1)])];
let mut scanner = VecScanner::new(rows);
assert!(scanner.next());
assert!(scanner.close().is_ok());
assert!(!scanner.next());
}
}