use super::*;
pub enum BlockingOperation<'a> {
Distinct,
Sort(&'a [OrderKey]),
Aggregate {
keys: &'a [SlotProjection],
columns: &'a [AggregateColumn],
},
}
#[derive(Clone, Copy)]
enum Operation {
Distinct,
Sort,
Aggregate,
}
pub struct BlockingRows<'v, 'm, 'g, O, E = RuntimeError> {
child: O,
schema: Schema<'m, 'g>,
input: Rows<'v, 'm, 'g>,
output: RowSource<'v, 'm, 'g>,
batch: RowBatch<'v, 'm, 'g>,
keys: QueryArena<'m, 'g, SlotProjection>,
aggregates: QueryArena<'m, 'g, AggregateColumn>,
order: QueryArena<'m, 'g, OrderKey>,
operation: Operation,
node: PlanNodeId,
output_capacity: StorageCapacity,
started: bool,
ready: bool,
_error: std::marker::PhantomData<fn() -> E>,
_control: QueryReservation<'m, 'g>,
}
impl<'v, 'm, 'g, O: RowOperator<'v, 'm, 'g, E>, E> BlockingRows<'v, 'm, 'g, O, E> {
#[allow(
clippy::too_many_arguments,
reason = "explicit operator and three distinct capacity owners"
)]
pub fn new(
context: &RuntimeContext<'v, 'm, 'g>,
node: PlanNodeId,
child: O,
operation: BlockingOperation<'_>,
batch: StorageCapacity,
input: StorageCapacity,
output: StorageCapacity,
) -> Result<Self, RuntimeError> {
let (kind, keys, aggregates, order) = match operation {
BlockingOperation::Distinct => (Operation::Distinct, &[][..], &[][..], &[][..]),
BlockingOperation::Sort(order) => (Operation::Sort, &[][..], &[][..], order),
BlockingOperation::Aggregate { keys, columns } => {
(Operation::Aggregate, keys, columns, &[][..])
}
};
if keys
.len()
.checked_add(aggregates.len())
.is_none_or(|n| n > 256)
|| order.len() > 256
{
return Err(RuntimeError::Batch);
}
let mut owned_keys = QueryArena::new(context.memory(), keys.len())?;
let mut owned_aggregates = QueryArena::new(context.memory(), aggregates.len())?;
let mut owned_order = QueryArena::new(context.memory(), order.len())?;
let mut outputs = QueryArena::new(
context.memory(),
if matches!(kind, Operation::Aggregate) {
keys.len() + aggregates.len()
} else {
child.schema().slots().len()
},
)?;
for key in keys {
context.checkpoint()?;
child.schema().column(key.source)?;
owned_keys.push(*key)?;
outputs.push(key.output)?;
}
for aggregate in aggregates {
context.checkpoint()?;
match aggregate.operation {
Aggregate::CountAll => {}
Aggregate::Count { slot, .. }
| Aggregate::Collect { slot, .. }
| Aggregate::Sum { slot, .. }
| Aggregate::Avg { slot, .. }
| Aggregate::Min { slot }
| Aggregate::Max { slot } => {
child.schema().column(slot)?;
}
}
owned_aggregates.push(*aggregate)?;
outputs.push(aggregate.output)?;
}
for key in order {
context.checkpoint()?;
child.schema().column(key.slot)?;
owned_order.push(*key)?;
}
if !matches!(kind, Operation::Aggregate) {
for slot in child.schema().slots() {
outputs.push(*slot)?;
}
}
let schema = Schema::new(context, outputs.as_slice())?;
let rows = Rows::new(context, child.schema().slots(), input)?;
let empty_capacity = StorageCapacity {
rows: 0,
max_rows: 0,
payload_bytes: 0,
variable: ArenaCapacity::default(),
};
let empty_output =
Rows::new(context, schema.slots(), empty_capacity)?.into_source(node, context)?;
let batch = RowBatch::with_arenas(
context,
child.schema().slots().len(),
batch.rows,
batch.payload_bytes,
batch.variable,
)?;
let control = context.memory().reserve(
std::mem::size_of::<Self>()
- std::mem::size_of::<O>()
- std::mem::size_of::<Schema<'_, '_>>()
- std::mem::size_of::<Rows<'_, '_, '_>>()
- std::mem::size_of::<RowSource<'_, '_, '_>>()
- std::mem::size_of::<RowBatch<'_, '_, '_>>()
- std::mem::size_of::<QueryArena<'_, '_, SlotProjection>>()
- std::mem::size_of::<QueryArena<'_, '_, AggregateColumn>>()
- std::mem::size_of::<QueryArena<'_, '_, OrderKey>>(),
)?;
Ok(Self {
child,
schema,
input: rows,
output: empty_output,
batch,
keys: owned_keys,
aggregates: owned_aggregates,
order: owned_order,
operation: kind,
node,
output_capacity: output,
started: false,
ready: false,
_error: std::marker::PhantomData,
_control: control,
})
}
}
impl<'v, 'm, 'g, O: RowOperator<'v, 'm, 'g, E>, E: From<RuntimeError>> RowOperator<'v, 'm, 'g, E>
for BlockingRows<'v, 'm, 'g, O, E>
{
fn schema(&self) -> &Schema<'m, 'g> {
&self.schema
}
}
impl<'v, 'm, 'g, O: RowOperator<'v, 'm, 'g, E>, E: From<RuntimeError>> PullOperator<'v, 'm, 'g, E>
for BlockingRows<'v, 'm, 'g, O, E>
{
fn node(&self) -> PlanNodeId {
self.node
}
fn prepare_search(
&mut self,
node: PlanNodeId,
context: &mut RuntimeContext<'v, 'm, 'g>,
) -> Result<(), E> {
self.child.prepare_search(node, context)
}
fn pull(
&mut self,
context: &mut RuntimeContext<'v, 'm, 'g>,
output: &mut RowBatch<'v, 'm, 'g>,
) -> Result<PullState, E> {
context.checkpoint()?;
if !self.batch.belongs_to(context) || output.columns() != self.schema.slots().len() {
return Err(RuntimeError::Batch.into());
}
if !self.started {
self.started = true;
let placeholder = Rows::new(
context,
&[],
StorageCapacity {
rows: 0,
max_rows: 0,
payload_bytes: 0,
variable: ArenaCapacity::default(),
},
)?;
let mut input = std::mem::replace(&mut self.input, placeholder);
let mut aggregate = if matches!(self.operation, Operation::Aggregate) {
Some(streaming::StreamAggregate::new(
std::mem::replace(
&mut input,
Rows::new(
context,
&[],
StorageCapacity::new(0, 0, ArenaCapacity::default()),
)?,
),
self.keys.as_slice(),
self.aggregates.as_slice(),
self.output_capacity,
context,
)?)
} else {
None
};
let mut distinct = streaming::StreamIndex::new(context)?;
let mut columns = QueryArena::new(context.memory(), self.batch.columns())
.map_err(RuntimeError::Memory)?;
for column in 0..self.batch.columns() {
columns.push(column).map_err(RuntimeError::Memory)?;
}
loop {
self.batch.clear();
let state = self.child.pull(context, &mut self.batch)?;
if state == PullState::More && self.batch.rows() == 0 {
return Err(RuntimeError::Batch.into());
}
for row in 0..self.batch.rows() {
context.charge(WorkKind::OperatorRows, 1)?;
context.charge(WorkKind::RowsIn, 1)?;
let mut values = QueryArena::new(context.memory(), self.batch.columns())
.map_err(RuntimeError::Memory)?;
for column in 0..self.batch.columns() {
values
.push(self.batch.value(row, column).ok_or(RuntimeError::Batch)?)
.map_err(RuntimeError::Memory)?;
}
if let Some(aggregate) = &mut aggregate {
aggregate.push(values.as_slice(), self.aggregates.as_slice(), context)?;
} else if matches!(self.operation, Operation::Distinct) {
let (hash, found) = distinct.locate(
values.as_slice(),
&input,
columns.as_slice(),
context,
)?;
if found.is_none() {
distinct.insert(hash, input.len(), context)?;
input.push(values.as_slice(), context)?;
}
} else {
input.push(values.as_slice(), context)?;
}
}
if state == PullState::Done {
break;
}
}
self.batch.clear();
let rows = match self.operation {
Operation::Distinct => input,
Operation::Sort => input.sort(self.order.as_slice(), context)?,
Operation::Aggregate => aggregate.ok_or(RuntimeError::Batch)?.finish(
self.keys.as_slice(),
self.aggregates.as_slice(),
context,
)?,
};
self.output = rows.into_source(self.node, context)?;
self.ready = true;
}
if !self.ready {
return Err(RuntimeError::Batch.into());
}
self.output.pull(context, output).map_err(E::from)
}
}