use std::any::Any;
use std::collections::HashSet;
use std::sync::Arc;
use datafusion::common::ScalarValue;
use datafusion::common::tree_node::{Transformed, TreeNode};
use datafusion::datasource::{provider_as_source, source_as_provider};
use datafusion::error::{DataFusionError, Result as DfResult};
use datafusion::logical_expr::expr::{Sort, WindowFunction};
use datafusion::logical_expr::utils::{conjunction, split_conjunction};
use datafusion::logical_expr::{
BinaryExpr, Expr, Filter, LogicalPlan, Operator, Projection, TableScan, WindowFunctionDefinition,
};
use datafusion::optimizer::{OptimizerConfig, OptimizerRule};
use super::provider::{Kind, TimelakeProvider};
use crate::table::Table;
#[derive(Debug)]
pub(super) struct LatestRewrite;
impl OptimizerRule for LatestRewrite {
fn name(&self) -> &'static str {
"timelake_latest_rewrite"
}
fn rewrite(&self, plan: LogicalPlan, _config: &dyn OptimizerConfig) -> DfResult<Transformed<LogicalPlan>> {
plan.transform_down(|node| {
let rewritten = match rewrite_row_number(&node)? {
Some(new) => Some(new),
None => rewrite_top1(&node)?,
};
Ok(rewritten.map_or(Transformed::no(node), Transformed::yes))
})
}
}
fn indexed_table(scan: &TableScan) -> Option<(Table, String)> {
let provider = source_as_provider(&scan.source).ok()?;
let any: &dyn Any = provider.as_ref();
let p = any.downcast_ref::<TimelakeProvider>()?;
let table = p.table();
let time_column = table.contract().time_column.clone()?;
(p.kind() == Kind::Data && table.last_value_index().is_some()).then(|| (table.clone(), time_column))
}
fn latest_scan(scan: &TableScan, table: Table) -> LogicalPlan {
let mut s = scan.clone();
s.source = provider_as_source(Arc::new(TimelakeProvider::latest(table)));
s.filters = vec![];
LogicalPlan::TableScan(s)
}
fn is_one(e: &Expr) -> bool {
use ScalarValue as S;
matches!(
e,
Expr::Literal(
S::Int8(Some(1))
| S::Int16(Some(1))
| S::Int32(Some(1))
| S::Int64(Some(1))
| S::UInt8(Some(1))
| S::UInt16(Some(1))
| S::UInt32(Some(1))
| S::UInt64(Some(1)),
_
)
)
}
fn through_projections(plan: &LogicalPlan) -> &LogicalPlan {
match plan {
LogicalPlan::Projection(p) if p.expr.iter().all(|e| matches!(e, Expr::Column(_))) => {
through_projections(&p.input)
}
other => other,
}
}
fn split_rn_equals_one(predicate: &Expr) -> (Option<&datafusion::common::Column>, Vec<Expr>) {
let mut rn = None;
let mut others = vec![];
for c in split_conjunction(predicate) {
let hit = match c {
Expr::BinaryExpr(BinaryExpr { left, op: Operator::Eq, right }) if rn.is_none() => {
match (left.as_ref(), right.as_ref()) {
(Expr::Column(col), one) | (one, Expr::Column(col)) if is_one(one) => Some(col),
_ => None,
}
}
_ => None,
};
match hit {
Some(col) => rn = Some(col),
None => others.push(c.clone()),
}
}
(rn, others)
}
fn rewrite_row_number(node: &LogicalPlan) -> DfResult<Option<LogicalPlan>> {
let LogicalPlan::Filter(filter) = node else { return Ok(None) };
let (Some(rn), others) = split_rn_equals_one(&filter.predicate) else { return Ok(None) };
if others.iter().any(|e| e.column_refs().iter().any(|c| c.name == rn.name)) {
return Ok(None);
}
let mut current = filter.input.as_ref();
let mut projections = vec![];
let window = loop {
match current {
LogicalPlan::Projection(p) => {
projections.push(p);
current = &p.input;
}
LogicalPlan::Window(w) => break w,
_ => return Ok(None),
}
};
let [window_expr] = window.window_expr.as_slice() else { return Ok(None) };
let LogicalPlan::TableScan(scan) = window.input.as_ref() else { return Ok(None) };
if !scan.filters.is_empty() || scan.fetch.is_some() {
return Ok(None);
}
let Some((table, time_column)) = indexed_table(scan) else { return Ok(None) };
let Expr::WindowFunction(wf) = window_expr.clone().unalias() else { return Ok(None) };
let WindowFunction { fun, params } = wf.as_ref();
if !matches!(fun, WindowFunctionDefinition::WindowUDF(u) if u.name() == "row_number") {
return Ok(None);
}
let partition_by: Option<Vec<&str>> = params
.partition_by
.iter()
.map(|e| match e {
Expr::Column(c) => Some(c.name.as_str()),
_ => None,
})
.collect();
if partition_by != Some(table.contract().series_key.iter().map(String::as_str).collect()) {
return Ok(None);
}
match params.order_by.as_slice() {
[Sort { expr: Expr::Column(c), asc: false, .. }] if c.name == time_column => {}
_ => return Ok(None),
}
let window_output = window.schema.fields().len() - 1;
let output_name = window.schema.field(window_output).name();
let refers = rn.name == *output_name
|| projections.iter().any(|p| {
p.expr.iter().any(|e| {
matches!(e, Expr::Alias(a) if a.name == rn.name
&& matches!(a.expr.as_ref(), Expr::Column(c) if c.name == *output_name))
})
});
if !refers {
return Ok(None);
}
let index_scan = latest_scan(scan, table);
let mut exprs: Vec<Expr> = scan.projected_schema.columns().into_iter().map(Expr::Column).collect();
let (qualifier, field) = window.schema.qualified_field(window_output);
exprs.push(Expr::Literal(ScalarValue::UInt64(Some(1)), None).alias_qualified(qualifier.cloned(), field.name()));
let mut rebuilt =
LogicalPlan::Projection(Projection::try_new_with_schema(exprs, Arc::new(index_scan), window.schema.clone())?);
for p in projections.iter().rev() {
rebuilt = LogicalPlan::Projection(Projection::try_new_with_schema(
p.expr.clone(),
Arc::new(rebuilt),
p.schema.clone(),
)?);
}
match conjunction(others) {
Some(predicate) => Ok(Some(LogicalPlan::Filter(Filter::try_new(predicate, Arc::new(rebuilt))?))),
None => Ok(Some(rebuilt)),
}
}
fn rewrite_top1(node: &LogicalPlan) -> DfResult<Option<LogicalPlan>> {
let LogicalPlan::Sort(sort) = node else { return Ok(None) };
if sort.fetch != Some(1) {
return Ok(None);
}
let mut current = through_projections(&sort.input);
let mut filters: Vec<&Expr> = vec![];
if let LogicalPlan::Filter(f) = current {
filters.push(&f.predicate);
current = through_projections(&f.input);
}
let LogicalPlan::TableScan(scan) = current else { return Ok(None) };
if scan.fetch.is_some() {
return Ok(None);
}
let Some((table, time_column)) = indexed_table(scan) else { return Ok(None) };
match sort.expr.as_slice() {
[Sort { expr: Expr::Column(c), asc: false, .. }] if c.name == time_column => {}
_ => return Ok(None),
}
let key: HashSet<&str> = table.contract().series_key.iter().map(String::as_str).collect();
let only_keys = |e: &Expr| e.column_refs().iter().all(|c| key.contains(c.name.as_str()));
if key.is_empty() || !filters.iter().all(|e| only_keys(e)) || !scan.filters.iter().all(only_keys) {
return Ok(None);
}
let scan_filters = conjunction(scan.filters.iter().cloned());
let index_scan = latest_scan(scan, table);
let input = match scan_filters {
Some(predicate) if filters.is_empty() => LogicalPlan::Filter(Filter::try_new(predicate, Arc::new(index_scan))?),
_ => index_scan,
};
let replaced = replace_scan(&sort.input, input)?;
Ok(Some(node.with_new_exprs(node.expressions(), vec![replaced])?))
}
fn replace_scan(plan: &LogicalPlan, new: LogicalPlan) -> DfResult<LogicalPlan> {
match plan {
LogicalPlan::TableScan(_) => Ok(new),
other => {
let [input] = other.inputs()[..] else {
return Err(DataFusionError::Internal("unexpected plan shape".into()));
};
let child = replace_scan(input, new)?;
other.with_new_exprs(other.expressions(), vec![child])
}
}
}
#[cfg(test)]
mod tests {
use datafusion::prelude::lit;
use super::*;
use crate::query::Session;
use crate::testing::{T0, batch, faulty_lake, opts, schema};
use crate::{AppendOptions, Lake};
#[test]
fn only_integer_one_is_one() {
for one in [lit(1i32), lit(1i64), lit(1u64), lit(1u8)] {
assert!(is_one(&one), "{one}");
}
for other in [lit("1"), lit(1.0f64), lit(2i64), lit(ScalarValue::Int64(None)), lit(true)] {
assert!(!is_one(&other), "{other}");
}
}
async fn reads_index(s: &Session, sql: &str) -> bool {
let plan = s.sql(sql).await.unwrap().into_optimized_plan().unwrap();
let mut found = false;
plan.apply(|node| {
if let LogicalPlan::TableScan(scan) = node
&& let Ok(provider) = source_as_provider(&scan.source)
&& let Some(p) = (provider.as_ref() as &dyn Any).downcast_ref::<TimelakeProvider>()
{
found |= p.kind() == Kind::Latest;
}
Ok(datafusion::common::tree_node::TreeNodeRecursion::Continue)
})
.unwrap();
found
}
async fn lake_with_data() -> Lake {
let (lake, _store) = faulty_lake();
let t = lake.create_table("iot.r", &schema(), opts()).await.unwrap();
let rows: Vec<_> =
(0..3).flat_map(|d| (0..4).map(move |i| (T0 + i, d, f64::from(d * 10) + i as f64))).collect();
t.append(&[batch(&rows)], AppendOptions::default()).await.unwrap();
lake
}
const ROW_NUMBER: &str = "(SELECT *, ROW_NUMBER() OVER (PARTITION BY device ORDER BY time DESC) AS rn FROM iot.r)";
#[tokio::test]
async fn rewrites_latest_row_shapes() {
let s = Session::new(lake_with_data().await);
let cases = [
(format!("SELECT * FROM {ROW_NUMBER} WHERE rn = 1"), true),
(format!("SELECT * FROM {ROW_NUMBER} WHERE 1 = rn"), true),
(format!("SELECT device, value FROM {ROW_NUMBER} WHERE rn = 1 AND value > 5"), true),
(format!("SELECT device FROM {ROW_NUMBER} t WHERE t.rn = 1"), true),
("SELECT * FROM iot.r WHERE device = 1 ORDER BY time DESC LIMIT 1".into(), true),
("SELECT value FROM iot.r WHERE device IN (1, 2) ORDER BY time DESC LIMIT 1".into(), true),
(format!("SELECT * FROM {ROW_NUMBER} WHERE rn = 2"), false),
(format!("SELECT * FROM {ROW_NUMBER} WHERE rn <= 1"), false),
(format!("SELECT * FROM {ROW_NUMBER} WHERE rn = 1 OR value > 5"), false),
(format!("SELECT * FROM {ROW_NUMBER} WHERE rn = 1 AND rn < 5"), false),
("SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY device ORDER BY time ASC) AS rn FROM iot.r) WHERE rn = 1".into(), false),
("SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY value ORDER BY time DESC) AS rn FROM iot.r) WHERE rn = 1".into(), false),
("SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY time DESC) AS rn FROM iot.r) WHERE rn = 1".into(), false),
("SELECT * FROM (SELECT *, RANK() OVER (PARTITION BY device ORDER BY time DESC) AS rn FROM iot.r) WHERE rn = 1".into(), false),
("SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY device ORDER BY time DESC) AS rn FROM iot.r WHERE value < 5) WHERE rn = 1".into(), false),
("SELECT * FROM iot.r WHERE value < 5 ORDER BY time DESC LIMIT 1".into(), false),
("SELECT * FROM iot.r WHERE device = 1 ORDER BY time ASC LIMIT 1".into(), false),
("SELECT * FROM iot.r WHERE device = 1 ORDER BY time DESC LIMIT 2".into(), false),
("SELECT * FROM iot.r ORDER BY value DESC LIMIT 1".into(), false),
];
for (sql, rewritten) in cases {
assert_eq!(reads_index(&s, &sql).await, rewritten, "{sql}");
}
}
#[tokio::test]
async fn string_one_gives_the_same_rows() {
let s = Session::new(lake_with_data().await);
let count = async |sql: String| {
let batches = s.sql(&format!("SELECT count(*) FROM ({sql})")).await.unwrap().collect().await.unwrap();
arrow::util::pretty::pretty_format_batches(&batches).unwrap().to_string()
};
let by_int = count(format!("SELECT * FROM {ROW_NUMBER} WHERE rn = 1")).await;
assert_eq!(count(format!("SELECT * FROM {ROW_NUMBER} WHERE rn = '1'")).await, by_int);
assert!(by_int.contains("| 3 "), "{by_int}");
}
}