use std::any::Any;
use std::cmp::Reverse;
use std::collections::BinaryHeap;
use std::fmt;
use std::sync::Arc;
use arrow::array::{AsArray, RecordBatch};
use arrow::datatypes::{SchemaRef, TimestampMicrosecondType};
use datafusion::common::tree_node::{Transformed, TreeNode};
use datafusion::config::ConfigOptions;
use datafusion::datasource::{provider_as_source, source_as_provider};
use datafusion::error::{DataFusionError, Result as DfResult};
use datafusion::execution::TaskContext;
use datafusion::logical_expr::{Expr, LogicalPlan, Sort};
use datafusion::optimizer::{OptimizerConfig, OptimizerRule};
use datafusion::physical_expr::PhysicalExpr;
use datafusion::physical_expr::{EquivalenceProperties, Partitioning};
use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
use datafusion::physical_plan::filter_pushdown::{
ChildPushdownResult, FilterDescription, FilterPushdownPhase, FilterPushdownPropagation,
};
use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
use datafusion::physical_plan::{
DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties, SendableRecordBatchStream, execute_stream,
};
use futures::StreamExt;
use super::provider::{Kind, TimelakeProvider};
use super::settings::SessionSettings;
use crate::property;
#[derive(Debug)]
pub(super) struct NewestFirst;
impl OptimizerRule for NewestFirst {
fn name(&self) -> &'static str {
"timelake_newest_first"
}
fn rewrite(&self, plan: LogicalPlan, config: &dyn OptimizerConfig) -> DfResult<Transformed<LogicalPlan>> {
let options = config.options();
let session = options.extensions.get::<SessionSettings>().and_then(|s| s.flag(property::NEWEST_FIRST));
plan.transform_down(|node| {
let LogicalPlan::Sort(sort) = &node else { return Ok(Transformed::no(node)) };
match newest_first(sort, session)? {
Some(input) => Ok(Transformed::yes(LogicalPlan::Sort(Sort { input: Arc::new(input), ..sort.clone() }))),
None => Ok(Transformed::no(node)),
}
})
}
}
fn newest_first(sort: &Sort, session: Option<bool>) -> DfResult<Option<LogicalPlan>> {
let (Some(k), [by]) = (sort.fetch, sort.expr.as_slice()) else { return Ok(None) };
let Expr::Column(column) = &by.expr else { return Ok(None) };
if by.asc {
return Ok(None);
}
let mut input = sort.input.as_ref();
loop {
input = match input {
LogicalPlan::Filter(f) => &f.input,
LogicalPlan::SubqueryAlias(s) => &s.input,
LogicalPlan::Projection(p) if p.expr.iter().all(|e| matches!(e, Expr::Column(_))) => &p.input,
LogicalPlan::TableScan(_) => break,
_ => return Ok(None),
};
}
let LogicalPlan::TableScan(scan) = input else { return Ok(None) };
let provider = source_as_provider(&scan.source)?;
let Some(p) = (provider.as_ref() as &dyn Any).downcast_ref::<TimelakeProvider>() else { return Ok(None) };
let contract = p.table().contract();
let partitioned = contract.chunk.is_some();
if !session.unwrap_or(contract.newest_first) {
return Ok(None);
}
if p.kind() != Kind::Data || p.is_routed() || !partitioned || contract.time_column.as_deref() != Some(&column.name)
{
return Ok(None);
}
let newest = provider_as_source(Arc::new(TimelakeProvider::newest(p.table().clone(), k)));
let rewritten = sort.input.as_ref().clone().transform_up(|node| match node {
LogicalPlan::TableScan(mut s) if s.table_name == scan.table_name => {
s.source = Arc::clone(&newest);
Ok(Transformed::yes(LogicalPlan::TableScan(s)))
}
other => Ok(Transformed::no(other)),
})?;
Ok(Some(rewritten.data))
}
#[derive(Debug)]
pub(super) struct NewestFirstExec {
waves: Vec<(Arc<dyn ExecutionPlan>, i64)>,
k: usize,
time: Option<usize>,
properties: Arc<PlanProperties>,
}
impl NewestFirstExec {
pub(super) fn try_new(waves: Vec<(Arc<dyn ExecutionPlan>, i64)>, k: usize, time: &str) -> DfResult<Self> {
let schema = waves
.first()
.map(|(p, _)| p.schema())
.ok_or_else(|| DataFusionError::Internal("a newest-first scan needs at least one partition".to_string()))?;
let properties = PlanProperties::new(
EquivalenceProperties::new(Arc::clone(&schema)),
Partitioning::UnknownPartitioning(1),
EmissionType::Incremental,
Boundedness::Bounded,
);
let time = schema.index_of(time).ok();
Ok(Self { waves, k, time, properties: Arc::new(properties) })
}
fn schema(&self) -> SchemaRef {
self.waves[0].0.schema()
}
}
impl DisplayAs for NewestFirstExec {
fn fmt_as(&self, _t: DisplayFormatType, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NewestFirstExec: {} partitions, newest first, stop at {} rows", self.waves.len(), self.k)
}
}
impl ExecutionPlan for NewestFirstExec {
fn name(&self) -> &'static str {
"NewestFirstExec"
}
fn properties(&self) -> &Arc<PlanProperties> {
&self.properties
}
fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
self.waves.iter().map(|(p, _)| p).collect()
}
fn with_new_children(self: Arc<Self>, children: Vec<Arc<dyn ExecutionPlan>>) -> DfResult<Arc<dyn ExecutionPlan>> {
let waves = children.into_iter().zip(self.waves.iter().map(|(_, newest)| *newest)).collect();
Ok(Arc::new(Self { waves, k: self.k, time: self.time, properties: Arc::clone(&self.properties) }))
}
fn gather_filters_for_pushdown(
&self,
_phase: FilterPushdownPhase,
parent_filters: Vec<Arc<dyn PhysicalExpr>>,
_config: &ConfigOptions,
) -> DfResult<FilterDescription> {
FilterDescription::from_children(parent_filters, &self.children())
}
fn handle_child_pushdown_result(
&self,
_phase: FilterPushdownPhase,
child_pushdown_result: ChildPushdownResult,
_config: &ConfigOptions,
) -> DfResult<FilterPushdownPropagation<Arc<dyn ExecutionPlan>>> {
Ok(FilterPushdownPropagation::if_all(child_pushdown_result))
}
fn execute(&self, partition: usize, context: Arc<TaskContext>) -> DfResult<SendableRecordBatchStream> {
if partition != 0 {
return Err(DataFusionError::Internal(format!("NewestFirstExec has one partition, not {partition}")));
}
let state = NewestFirstRead {
waves: self.waves.clone(),
next: 0,
current: None,
newest: BinaryHeap::new(),
k: self.k,
time: self.time,
context,
};
let stream = futures::stream::try_unfold(state, NewestFirstRead::next_batch);
Ok(Box::pin(RecordBatchStreamAdapter::new(self.schema(), stream)))
}
}
struct NewestFirstRead {
waves: Vec<(Arc<dyn ExecutionPlan>, i64)>,
next: usize,
current: Option<SendableRecordBatchStream>,
newest: BinaryHeap<Reverse<i64>>,
k: usize,
time: Option<usize>,
context: Arc<TaskContext>,
}
impl NewestFirstRead {
async fn next_batch(mut self) -> DfResult<Option<(RecordBatch, Self)>> {
loop {
if let Some(stream) = &mut self.current {
match stream.next().await {
Some(batch) => {
let batch = batch?;
self.observe(&batch);
return Ok(Some((batch, self)));
}
None => self.current = None,
}
}
let Some((plan, newest)) = self.waves.get(self.next) else { return Ok(None) };
if self.time.is_some() && self.newest.len() >= self.k && self.newest.peek().is_some_and(|t| t.0 > *newest) {
return Ok(None);
}
self.current = Some(execute_stream(Arc::clone(plan), Arc::clone(&self.context))?);
self.next += 1;
}
}
fn observe(&mut self, batch: &RecordBatch) {
let Some(times) = self.time.map(|i| batch.column(i)) else { return };
let Some(times) = times.as_primitive_opt::<TimestampMicrosecondType>() else { return };
for t in times.iter().flatten() {
if self.newest.len() < self.k {
self.newest.push(Reverse(t));
} else if self.newest.peek().is_some_and(|m| t > m.0) {
self.newest.pop();
self.newest.push(Reverse(t));
}
}
}
}
#[cfg(test)]
mod tests {
use crate::testing::{T0, batch, faulty_lake, opts};
use crate::{AppendOptions, Session};
const MINUTE: i64 = 60_000_000;
#[tokio::test]
async fn newest_first_gives_the_rows_a_full_scan_gives() {
let (lake, _) = faulty_lake();
let t = lake.create_table("iot.r", &crate::testing::schema(), opts()).await.unwrap();
let rows: Vec<(i64, i32, f64)> =
(0..5 * 24 * 60).map(|i| (T0 + i * MINUTE, i32::try_from(i % 7).unwrap(), i as f64)).collect();
t.append(&[batch(&rows)], AppendOptions { maintain: Some(false), ..Default::default() }).await.unwrap();
let queries = [
"SELECT time, device, value FROM iot.r ORDER BY time DESC LIMIT 3",
"SELECT time, value FROM iot.r ORDER BY time DESC LIMIT 2000",
"SELECT * FROM iot.r WHERE time <= TIMESTAMP '2026-01-03 12:00:00' ORDER BY time DESC LIMIT 5",
"SELECT time FROM iot.r WHERE device = 3 AND value < 4000 ORDER BY time DESC LIMIT 50",
];
for sql in queries {
let mut answers = vec![];
for on in ["true", "false"] {
let s = Session::new(lake.clone());
s.sql(&format!("SET timelake.newest_first = {on}")).await.unwrap();
let plan = s.sql(&format!("EXPLAIN {sql}")).await.unwrap().collect().await.unwrap();
let plan = arrow::util::pretty::pretty_format_batches(&plan).unwrap().to_string();
assert_eq!(plan.contains("NewestFirstExec"), on == "true", "{sql}\n{plan}");
let rows = s.sql(sql).await.unwrap().collect().await.unwrap();
answers.push(arrow::util::pretty::pretty_format_batches(&rows).unwrap().to_string());
}
assert_eq!(answers[0], answers[1], "{sql}");
}
}
}