use std::sync::Arc;
use futures::StreamExt;
use tracing::instrument;
use crate::exec::{
AccessMode, ContextLevel, EvalContext, ExecOperator, ExecutionContext, FlowResult,
OperatorMetrics, PhysicalExpr, ValueBatchStream, buffer_stream, monitor_stream,
};
use crate::expr::ControlFlow;
use crate::val::Value;
#[derive(Debug, Clone)]
pub struct Limit {
pub(crate) input: Arc<dyn ExecOperator>,
pub(crate) limit: Option<Arc<dyn PhysicalExpr>>,
pub(crate) offset: Option<Arc<dyn PhysicalExpr>>,
pub(crate) metrics: Arc<OperatorMetrics>,
}
impl Limit {
pub(crate) fn new(
input: Arc<dyn ExecOperator>,
limit: Option<Arc<dyn PhysicalExpr>>,
offset: Option<Arc<dyn PhysicalExpr>>,
) -> Self {
Self {
input,
limit,
offset,
metrics: Arc::new(OperatorMetrics::new()),
}
}
fn coerce_to_usize(value: &Value) -> Result<usize, String> {
match value {
Value::Number(n) => {
let i = n.to_int();
if i >= 0 {
Ok(i as usize)
} else {
Err(format!("expected non-negative integer, got {}", i))
}
}
Value::None | Value::Null => Ok(0),
_ => Err(format!("expected integer, got {:?}", value)),
}
}
}
impl ExecOperator for Limit {
fn name(&self) -> &'static str {
"Limit"
}
fn attrs(&self) -> Vec<(String, String)> {
let mut attrs = Vec::with_capacity(2);
if let Some(limit) = &self.limit {
attrs.push(("limit".to_string(), limit.to_sql()));
}
if let Some(offset) = &self.offset {
attrs.push(("offset".to_string(), offset.to_sql()));
}
attrs
}
fn required_context(&self) -> ContextLevel {
let exprs_ctx = [self.limit.as_ref(), self.offset.as_ref()]
.into_iter()
.flatten()
.map(|e| e.required_context())
.max()
.unwrap_or(ContextLevel::Root);
exprs_ctx.max(self.input.required_context())
}
fn access_mode(&self) -> AccessMode {
[self.limit.as_ref(), self.offset.as_ref()]
.into_iter()
.flatten()
.map(|e| e.access_mode())
.fold(self.input.access_mode(), AccessMode::combine)
}
fn children(&self) -> Vec<&Arc<dyn ExecOperator>> {
vec![&self.input]
}
fn metrics(&self) -> Option<&OperatorMetrics> {
Some(&self.metrics)
}
fn expressions(&self) -> Vec<(&str, &Arc<dyn PhysicalExpr>)> {
let mut exprs = Vec::with_capacity(2);
if let Some(limit) = &self.limit {
exprs.push(("limit", limit));
}
if let Some(offset) = &self.offset {
exprs.push(("offset", offset));
}
exprs
}
fn output_ordering(&self) -> crate::exec::OutputOrdering {
self.input.output_ordering()
}
#[instrument(name = "Limit::execute", level = "trace", skip_all)]
fn execute(&self, ctx: &ExecutionContext) -> FlowResult<ValueBatchStream> {
let input_stream = buffer_stream(
self.input.execute(ctx)?,
self.input.access_mode(),
self.input.cardinality_hint(),
ctx.root().ctx.config.operator_buffer_size,
);
let limit_expr = self.limit.clone();
let offset_expr = self.offset.clone();
let ctx = ctx.clone();
let limited = async_stream::try_stream! {
let eval_ctx = EvalContext::from_exec_ctx(&ctx);
let limit_value = match &limit_expr {
Some(expr) => {
let value = expr.evaluate(eval_ctx.clone()).await.map_err(|e| {
ControlFlow::Err(anyhow::anyhow!("Failed to evaluate LIMIT: {}", e))
})?;
Some(Limit::coerce_to_usize(&value).map_err(|e| {
ControlFlow::Err(anyhow::anyhow!("LIMIT must be a non-negative integer: {}", e))
})?)
}
None => None,
};
let offset = match &offset_expr {
Some(expr) => {
let value = expr.evaluate(eval_ctx).await.map_err(|e| {
ControlFlow::Err(anyhow::anyhow!("Failed to evaluate START: {}", e))
})?;
Limit::coerce_to_usize(&value).map_err(|e| {
ControlFlow::Err(anyhow::anyhow!("START must be a non-negative integer: {}", e))
})?
}
None => 0,
};
let mut skipped = 0usize;
let mut emitted = 0usize;
futures::pin_mut!(input_stream);
while let Some(batch_result) = input_stream.next().await {
let mut batch = batch_result?;
if skipped < offset {
let to_skip = (offset - skipped).min(batch.values.len());
skipped += to_skip;
if to_skip >= batch.values.len() {
continue;
}
batch.values.drain(..to_skip);
}
if let Some(limit) = limit_value {
batch.values.truncate(limit.saturating_sub(emitted));
}
emitted += batch.values.len();
if !batch.values.is_empty() {
yield batch;
}
if limit_value.is_some_and(|l| emitted >= l) {
break;
}
}
};
Ok(monitor_stream(Box::pin(limited), "Limit", &self.metrics))
}
}