Skip to content

Commit 2185842

Browse files
gruuyajonahgao
andauthored
Implement DISTINCT ON from Postgres (#7981)
* Initial DISTINT ON implementation * Add a couple more tests * Add comments in the replace_distinct_aggregate optimizer * Run cargo fmt to fix CI * Make DISTINCT ON planning more robust to support arbitrary selection expressions * Add DISTINCT ON + join SLT * Handle no DISTINCT ON expressions and extend the docs for the replace_distinct_aggregate optimizer * Remove misleading DISTINCT ON SLT comment * Add an EXPLAIN SLT for a basic DISTINCT ON query * Revise comment in CommonSubexprEliminate::try_optimize_aggregate * Implement qualified expression alias and extend test coverage * Update datafusion/proto/proto/datafusion.proto Co-authored-by: Jonah Gao <[email protected]> * Accompanying generated changes to alias proto tag revision * Remove obsolete comment --------- Co-authored-by: Jonah Gao <[email protected]>
1 parent 7889bf9 commit 2185842

File tree

25 files changed

+879
-99
lines changed

25 files changed

+879
-99
lines changed

datafusion/expr/src/expr.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use crate::Operator;
2828
use crate::{aggregate_function, ExprSchemable};
2929
use arrow::datatypes::DataType;
3030
use datafusion_common::tree_node::{Transformed, TreeNode};
31-
use datafusion_common::{internal_err, DFSchema};
31+
use datafusion_common::{internal_err, DFSchema, OwnedTableReference};
3232
use datafusion_common::{plan_err, Column, DataFusionError, Result, ScalarValue};
3333
use std::collections::HashSet;
3434
use std::fmt;
@@ -187,13 +187,20 @@ pub enum Expr {
187187
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
188188
pub struct Alias {
189189
pub expr: Box<Expr>,
190+
pub relation: Option<OwnedTableReference>,
190191
pub name: String,
191192
}
192193

193194
impl Alias {
194-
pub fn new(expr: Expr, name: impl Into<String>) -> Self {
195+
/// Create an alias with an optional schema/field qualifier.
196+
pub fn new(
197+
expr: Expr,
198+
relation: Option<impl Into<OwnedTableReference>>,
199+
name: impl Into<String>,
200+
) -> Self {
195201
Self {
196202
expr: Box::new(expr),
203+
relation: relation.map(|r| r.into()),
197204
name: name.into(),
198205
}
199206
}
@@ -844,7 +851,27 @@ impl Expr {
844851
asc,
845852
nulls_first,
846853
}) => Expr::Sort(Sort::new(Box::new(expr.alias(name)), asc, nulls_first)),
847-
_ => Expr::Alias(Alias::new(self, name.into())),
854+
_ => Expr::Alias(Alias::new(self, None::<&str>, name.into())),
855+
}
856+
}
857+
858+
/// Return `self AS name` alias expression with a specific qualifier
859+
pub fn alias_qualified(
860+
self,
861+
relation: Option<impl Into<OwnedTableReference>>,
862+
name: impl Into<String>,
863+
) -> Expr {
864+
match self {
865+
Expr::Sort(Sort {
866+
expr,
867+
asc,
868+
nulls_first,
869+
}) => Expr::Sort(Sort::new(
870+
Box::new(expr.alias_qualified(relation, name)),
871+
asc,
872+
nulls_first,
873+
)),
874+
_ => Expr::Alias(Alias::new(self, relation, name.into())),
848875
}
849876
}
850877

datafusion/expr/src/expr_schema.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,13 @@ impl ExprSchemable for Expr {
305305
self.nullable(input_schema)?,
306306
)
307307
.with_metadata(self.metadata(input_schema)?)),
308+
Expr::Alias(Alias { relation, name, .. }) => Ok(DFField::new(
309+
relation.clone(),
310+
name,
311+
self.get_type(input_schema)?,
312+
self.nullable(input_schema)?,
313+
)
314+
.with_metadata(self.metadata(input_schema)?)),
308315
_ => Ok(DFField::new_unqualified(
309316
&self.display_name()?,
310317
self.get_type(input_schema)?,

datafusion/expr/src/logical_plan/builder.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ use crate::expr_rewriter::{
3232
rewrite_sort_cols_by_aggs,
3333
};
3434
use crate::logical_plan::{
35-
Aggregate, Analyze, CrossJoin, Distinct, EmptyRelation, Explain, Filter, Join,
36-
JoinConstraint, JoinType, Limit, LogicalPlan, Partitioning, PlanType, Prepare,
35+
Aggregate, Analyze, CrossJoin, Distinct, DistinctOn, EmptyRelation, Explain, Filter,
36+
Join, JoinConstraint, JoinType, Limit, LogicalPlan, Partitioning, PlanType, Prepare,
3737
Projection, Repartition, Sort, SubqueryAlias, TableScan, Union, Unnest, Values,
3838
Window,
3939
};
@@ -551,16 +551,29 @@ impl LogicalPlanBuilder {
551551
let left_plan: LogicalPlan = self.plan;
552552
let right_plan: LogicalPlan = plan;
553553

554-
Ok(Self::from(LogicalPlan::Distinct(Distinct {
555-
input: Arc::new(union(left_plan, right_plan)?),
556-
})))
554+
Ok(Self::from(LogicalPlan::Distinct(Distinct::All(Arc::new(
555+
union(left_plan, right_plan)?,
556+
)))))
557557
}
558558

559559
/// Apply deduplication: Only distinct (different) values are returned)
560560
pub fn distinct(self) -> Result<Self> {
561-
Ok(Self::from(LogicalPlan::Distinct(Distinct {
562-
input: Arc::new(self.plan),
563-
})))
561+
Ok(Self::from(LogicalPlan::Distinct(Distinct::All(Arc::new(
562+
self.plan,
563+
)))))
564+
}
565+
566+
/// Project first values of the specified expression list according to the provided
567+
/// sorting expressions grouped by the `DISTINCT ON` clause expressions.
568+
pub fn distinct_on(
569+
self,
570+
on_expr: Vec<Expr>,
571+
select_expr: Vec<Expr>,
572+
sort_expr: Option<Vec<Expr>>,
573+
) -> Result<Self> {
574+
Ok(Self::from(LogicalPlan::Distinct(Distinct::On(
575+
DistinctOn::try_new(on_expr, select_expr, sort_expr, Arc::new(self.plan))?,
576+
))))
564577
}
565578

566579
/// Apply a join to `right` using explicitly specified columns and an

datafusion/expr/src/logical_plan/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ pub use ddl::{
3333
};
3434
pub use dml::{DmlStatement, WriteOp};
3535
pub use plan::{
36-
Aggregate, Analyze, CrossJoin, DescribeTable, Distinct, EmptyRelation, Explain,
37-
Extension, Filter, Join, JoinConstraint, JoinType, Limit, LogicalPlan, Partitioning,
38-
PlanType, Prepare, Projection, Repartition, Sort, StringifiedPlan, Subquery,
39-
SubqueryAlias, TableScan, ToStringifiedPlan, Union, Unnest, Values, Window,
36+
Aggregate, Analyze, CrossJoin, DescribeTable, Distinct, DistinctOn, EmptyRelation,
37+
Explain, Extension, Filter, Join, JoinConstraint, JoinType, Limit, LogicalPlan,
38+
Partitioning, PlanType, Prepare, Projection, Repartition, Sort, StringifiedPlan,
39+
Subquery, SubqueryAlias, TableScan, ToStringifiedPlan, Union, Unnest, Values, Window,
4040
};
4141
pub use statement::{
4242
SetVariable, Statement, TransactionAccessMode, TransactionConclusion, TransactionEnd,

datafusion/expr/src/logical_plan/plan.rs

Lines changed: 148 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use std::sync::Arc;
2525
use super::dml::CopyTo;
2626
use super::DdlStatement;
2727
use crate::dml::CopyOptions;
28-
use crate::expr::{Alias, Exists, InSubquery, Placeholder};
29-
use crate::expr_rewriter::create_col_from_scalar_expr;
28+
use crate::expr::{Alias, Exists, InSubquery, Placeholder, Sort as SortExpr};
29+
use crate::expr_rewriter::{create_col_from_scalar_expr, normalize_cols};
3030
use crate::logical_plan::display::{GraphvizVisitor, IndentVisitor};
3131
use crate::logical_plan::extension::UserDefinedLogicalNode;
3232
use crate::logical_plan::{DmlStatement, Statement};
@@ -163,7 +163,8 @@ impl LogicalPlan {
163163
}) => projected_schema,
164164
LogicalPlan::Projection(Projection { schema, .. }) => schema,
165165
LogicalPlan::Filter(Filter { input, .. }) => input.schema(),
166-
LogicalPlan::Distinct(Distinct { input }) => input.schema(),
166+
LogicalPlan::Distinct(Distinct::All(input)) => input.schema(),
167+
LogicalPlan::Distinct(Distinct::On(DistinctOn { schema, .. })) => schema,
167168
LogicalPlan::Window(Window { schema, .. }) => schema,
168169
LogicalPlan::Aggregate(Aggregate { schema, .. }) => schema,
169170
LogicalPlan::Sort(Sort { input, .. }) => input.schema(),
@@ -367,6 +368,16 @@ impl LogicalPlan {
367368
LogicalPlan::Unnest(Unnest { column, .. }) => {
368369
f(&Expr::Column(column.clone()))
369370
}
371+
LogicalPlan::Distinct(Distinct::On(DistinctOn {
372+
on_expr,
373+
select_expr,
374+
sort_expr,
375+
..
376+
})) => on_expr
377+
.iter()
378+
.chain(select_expr.iter())
379+
.chain(sort_expr.clone().unwrap_or(vec![]).iter())
380+
.try_for_each(f),
370381
// plans without expressions
371382
LogicalPlan::EmptyRelation(_)
372383
| LogicalPlan::Subquery(_)
@@ -377,7 +388,7 @@ impl LogicalPlan {
377388
| LogicalPlan::Analyze(_)
378389
| LogicalPlan::Explain(_)
379390
| LogicalPlan::Union(_)
380-
| LogicalPlan::Distinct(_)
391+
| LogicalPlan::Distinct(Distinct::All(_))
381392
| LogicalPlan::Dml(_)
382393
| LogicalPlan::Ddl(_)
383394
| LogicalPlan::Copy(_)
@@ -405,7 +416,9 @@ impl LogicalPlan {
405416
LogicalPlan::Union(Union { inputs, .. }) => {
406417
inputs.iter().map(|arc| arc.as_ref()).collect()
407418
}
408-
LogicalPlan::Distinct(Distinct { input }) => vec![input],
419+
LogicalPlan::Distinct(
420+
Distinct::All(input) | Distinct::On(DistinctOn { input, .. }),
421+
) => vec![input],
409422
LogicalPlan::Explain(explain) => vec![&explain.plan],
410423
LogicalPlan::Analyze(analyze) => vec![&analyze.input],
411424
LogicalPlan::Dml(write) => vec![&write.input],
@@ -461,8 +474,11 @@ impl LogicalPlan {
461474
Ok(Some(agg.group_expr.as_slice()[0].clone()))
462475
}
463476
}
477+
LogicalPlan::Distinct(Distinct::On(DistinctOn { select_expr, .. })) => {
478+
Ok(Some(select_expr[0].clone()))
479+
}
464480
LogicalPlan::Filter(Filter { input, .. })
465-
| LogicalPlan::Distinct(Distinct { input, .. })
481+
| LogicalPlan::Distinct(Distinct::All(input))
466482
| LogicalPlan::Sort(Sort { input, .. })
467483
| LogicalPlan::Limit(Limit { input, .. })
468484
| LogicalPlan::Repartition(Repartition { input, .. })
@@ -823,10 +839,29 @@ impl LogicalPlan {
823839
inputs: inputs.iter().cloned().map(Arc::new).collect(),
824840
schema: schema.clone(),
825841
})),
826-
LogicalPlan::Distinct(Distinct { .. }) => {
827-
Ok(LogicalPlan::Distinct(Distinct {
828-
input: Arc::new(inputs[0].clone()),
829-
}))
842+
LogicalPlan::Distinct(distinct) => {
843+
let distinct = match distinct {
844+
Distinct::All(_) => Distinct::All(Arc::new(inputs[0].clone())),
845+
Distinct::On(DistinctOn {
846+
on_expr,
847+
select_expr,
848+
..
849+
}) => {
850+
let sort_expr = expr.split_off(on_expr.len() + select_expr.len());
851+
let select_expr = expr.split_off(on_expr.len());
852+
Distinct::On(DistinctOn::try_new(
853+
expr,
854+
select_expr,
855+
if !sort_expr.is_empty() {
856+
Some(sort_expr)
857+
} else {
858+
None
859+
},
860+
Arc::new(inputs[0].clone()),
861+
)?)
862+
}
863+
};
864+
Ok(LogicalPlan::Distinct(distinct))
830865
}
831866
LogicalPlan::Analyze(a) => {
832867
assert!(expr.is_empty());
@@ -1064,7 +1099,9 @@ impl LogicalPlan {
10641099
LogicalPlan::Subquery(_) => None,
10651100
LogicalPlan::SubqueryAlias(SubqueryAlias { input, .. }) => input.max_rows(),
10661101
LogicalPlan::Limit(Limit { fetch, .. }) => *fetch,
1067-
LogicalPlan::Distinct(Distinct { input }) => input.max_rows(),
1102+
LogicalPlan::Distinct(
1103+
Distinct::All(input) | Distinct::On(DistinctOn { input, .. }),
1104+
) => input.max_rows(),
10681105
LogicalPlan::Values(v) => Some(v.values.len()),
10691106
LogicalPlan::Unnest(_) => None,
10701107
LogicalPlan::Ddl(_)
@@ -1667,9 +1704,21 @@ impl LogicalPlan {
16671704
LogicalPlan::Statement(statement) => {
16681705
write!(f, "{}", statement.display())
16691706
}
1670-
LogicalPlan::Distinct(Distinct { .. }) => {
1671-
write!(f, "Distinct:")
1672-
}
1707+
LogicalPlan::Distinct(distinct) => match distinct {
1708+
Distinct::All(_) => write!(f, "Distinct:"),
1709+
Distinct::On(DistinctOn {
1710+
on_expr,
1711+
select_expr,
1712+
sort_expr,
1713+
..
1714+
}) => write!(
1715+
f,
1716+
"DistinctOn: on_expr=[[{}]], select_expr=[[{}]], sort_expr=[[{}]]",
1717+
expr_vec_fmt!(on_expr),
1718+
expr_vec_fmt!(select_expr),
1719+
if let Some(sort_expr) = sort_expr { expr_vec_fmt!(sort_expr) } else { "".to_string() },
1720+
),
1721+
},
16731722
LogicalPlan::Explain { .. } => write!(f, "Explain"),
16741723
LogicalPlan::Analyze { .. } => write!(f, "Analyze"),
16751724
LogicalPlan::Union(_) => write!(f, "Union"),
@@ -2132,9 +2181,93 @@ pub struct Limit {
21322181

21332182
/// Removes duplicate rows from the input
21342183
#[derive(Clone, PartialEq, Eq, Hash)]
2135-
pub struct Distinct {
2184+
pub enum Distinct {
2185+
/// Plain `DISTINCT` referencing all selection expressions
2186+
All(Arc<LogicalPlan>),
2187+
/// The `Postgres` addition, allowing separate control over DISTINCT'd and selected columns
2188+
On(DistinctOn),
2189+
}
2190+
2191+
/// Removes duplicate rows from the input
2192+
#[derive(Clone, PartialEq, Eq, Hash)]
2193+
pub struct DistinctOn {
2194+
/// The `DISTINCT ON` clause expression list
2195+
pub on_expr: Vec<Expr>,
2196+
/// The selected projection expression list
2197+
pub select_expr: Vec<Expr>,
2198+
/// The `ORDER BY` clause, whose initial expressions must match those of the `ON` clause when
2199+
/// present. Note that those matching expressions actually wrap the `ON` expressions with
2200+
/// additional info pertaining to the sorting procedure (i.e. ASC/DESC, and NULLS FIRST/LAST).
2201+
pub sort_expr: Option<Vec<Expr>>,
21362202
/// The logical plan that is being DISTINCT'd
21372203
pub input: Arc<LogicalPlan>,
2204+
/// The schema description of the DISTINCT ON output
2205+
pub schema: DFSchemaRef,
2206+
}
2207+
2208+
impl DistinctOn {
2209+
/// Create a new `DistinctOn` struct.
2210+
pub fn try_new(
2211+
on_expr: Vec<Expr>,
2212+
select_expr: Vec<Expr>,
2213+
sort_expr: Option<Vec<Expr>>,
2214+
input: Arc<LogicalPlan>,
2215+
) -> Result<Self> {
2216+
if on_expr.is_empty() {
2217+
return plan_err!("No `ON` expressions provided");
2218+
}
2219+
2220+
let on_expr = normalize_cols(on_expr, input.as_ref())?;
2221+
2222+
let schema = DFSchema::new_with_metadata(
2223+
exprlist_to_fields(&select_expr, &input)?,
2224+
input.schema().metadata().clone(),
2225+
)?;
2226+
2227+
let mut distinct_on = DistinctOn {
2228+
on_expr,
2229+
select_expr,
2230+
sort_expr: None,
2231+
input,
2232+
schema: Arc::new(schema),
2233+
};
2234+
2235+
if let Some(sort_expr) = sort_expr {
2236+
distinct_on = distinct_on.with_sort_expr(sort_expr)?;
2237+
}
2238+
2239+
Ok(distinct_on)
2240+
}
2241+
2242+
/// Try to update `self` with a new sort expressions.
2243+
///
2244+
/// Validates that the sort expressions are a super-set of the `ON` expressions.
2245+
pub fn with_sort_expr(mut self, sort_expr: Vec<Expr>) -> Result<Self> {
2246+
let sort_expr = normalize_cols(sort_expr, self.input.as_ref())?;
2247+
2248+
// Check that the left-most sort expressions are the same as the `ON` expressions.
2249+
let mut matched = true;
2250+
for (on, sort) in self.on_expr.iter().zip(sort_expr.iter()) {
2251+
match sort {
2252+
Expr::Sort(SortExpr { expr, .. }) => {
2253+
if on != &**expr {
2254+
matched = false;
2255+
break;
2256+
}
2257+
}
2258+
_ => return plan_err!("Not a sort expression: {sort}"),
2259+
}
2260+
}
2261+
2262+
if self.on_expr.len() > sort_expr.len() || !matched {
2263+
return plan_err!(
2264+
"SELECT DISTINCT ON expressions must match initial ORDER BY expressions"
2265+
);
2266+
}
2267+
2268+
self.sort_expr = Some(sort_expr);
2269+
Ok(self)
2270+
}
21382271
}
21392272

21402273
/// Aggregates its input based on a set of grouping and aggregate

datafusion/expr/src/tree_node/expr.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,11 @@ impl TreeNode for Expr {
157157
let mut transform = transform;
158158

159159
Ok(match self {
160-
Expr::Alias(Alias { expr, name, .. }) => {
161-
Expr::Alias(Alias::new(transform(*expr)?, name))
162-
}
160+
Expr::Alias(Alias {
161+
expr,
162+
relation,
163+
name,
164+
}) => Expr::Alias(Alias::new(transform(*expr)?, relation, name)),
163165
Expr::Column(_) => self,
164166
Expr::OuterReferenceColumn(_, _) => self,
165167
Expr::Exists { .. } => self,

0 commit comments

Comments
 (0)