Skip to content

Commit 556282a

Browse files
authored
Support for SQL Natural Join (apache#4863)
* Support for SQL Natural Join * Remove comments
1 parent 1eb46df commit 556282a

File tree

2 files changed

+70
-5
lines changed

2 files changed

+70
-5
lines changed

datafusion/sql/src/relation/join.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use datafusion_common::{Column, DFSchemaRef, DataFusionError, Result};
2121
use datafusion_expr::expr_rewriter::normalize_col_with_schemas;
2222
use datafusion_expr::{Expr, JoinType, LogicalPlan, LogicalPlanBuilder};
2323
use sqlparser::ast::{Join, JoinConstraint, JoinOperator, TableWithJoins};
24-
use std::collections::HashMap;
24+
use std::collections::{HashMap, HashSet};
2525

2626
impl<'a, S: ContextProvider> SqlToRel<'a, S> {
2727
pub(crate) fn plan_table_with_joins(
@@ -142,10 +142,27 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
142142
.build()
143143
}
144144
JoinConstraint::Natural => {
145-
// https://issues.apache.org/jira/browse/ARROW-10727
146-
Err(DataFusionError::NotImplemented(
147-
"NATURAL JOIN is not supported (https://issues.apache.org/jira/browse/ARROW-10727)".to_string(),
148-
))
145+
let left_cols: HashSet<&String> = left
146+
.schema()
147+
.fields()
148+
.iter()
149+
.map(|f| f.field().name())
150+
.collect();
151+
let keys: Vec<Column> = right
152+
.schema()
153+
.fields()
154+
.iter()
155+
.map(|f| f.field().name())
156+
.filter(|f| left_cols.contains(f))
157+
.map(Column::from_name)
158+
.collect();
159+
if keys.is_empty() {
160+
self.parse_cross_join(left, right)
161+
} else {
162+
LogicalPlanBuilder::from(left)
163+
.join_using(right, join_type, keys)?
164+
.build()
165+
}
149166
}
150167
JoinConstraint::None => Err(DataFusionError::NotImplemented(
151168
"NONE constraint is not supported".to_string(),

datafusion/sql/tests/integration_test.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,54 @@ fn join_with_ambiguous_column() {
395395
quick_test(sql, expected);
396396
}
397397

398+
#[test]
399+
fn natural_join() {
400+
let sql = "SELECT * FROM lineitem a NATURAL JOIN lineitem b";
401+
let expected = "Projection: a.l_item_id, a.l_description, a.price\
402+
\n Inner Join: Using a.l_item_id = b.l_item_id, a.l_description = b.l_description, a.price = b.price\
403+
\n SubqueryAlias: a\
404+
\n TableScan: lineitem\
405+
\n SubqueryAlias: b\
406+
\n TableScan: lineitem";
407+
quick_test(sql, expected);
408+
}
409+
410+
#[test]
411+
fn natural_left_join() {
412+
let sql = "SELECT l_item_id FROM lineitem a NATURAL LEFT JOIN lineitem b";
413+
let expected = "Projection: a.l_item_id\
414+
\n Left Join: Using a.l_item_id = b.l_item_id, a.l_description = b.l_description, a.price = b.price\
415+
\n SubqueryAlias: a\
416+
\n TableScan: lineitem\
417+
\n SubqueryAlias: b\
418+
\n TableScan: lineitem";
419+
quick_test(sql, expected);
420+
}
421+
422+
#[test]
423+
fn natural_right_join() {
424+
let sql = "SELECT l_item_id FROM lineitem a NATURAL RIGHT JOIN lineitem b";
425+
let expected = "Projection: a.l_item_id\
426+
\n Right Join: Using a.l_item_id = b.l_item_id, a.l_description = b.l_description, a.price = b.price\
427+
\n SubqueryAlias: a\
428+
\n TableScan: lineitem\
429+
\n SubqueryAlias: b\
430+
\n TableScan: lineitem";
431+
quick_test(sql, expected);
432+
}
433+
434+
#[test]
435+
fn natural_join_no_common_becomes_cross_join() {
436+
let sql = "SELECT * FROM person a NATURAL JOIN lineitem b";
437+
let expected = "Projection: a.id, a.first_name, a.last_name, a.age, a.state, a.salary, a.birth_date, a.😀, b.l_item_id, b.l_description, b.price\
438+
\n CrossJoin:\
439+
\n SubqueryAlias: a\
440+
\n TableScan: person\
441+
\n SubqueryAlias: b\
442+
\n TableScan: lineitem";
443+
quick_test(sql, expected);
444+
}
445+
398446
#[test]
399447
fn using_join_multiple_keys() {
400448
let sql = "SELECT * FROM person a join person b using (id, age)";

0 commit comments

Comments
 (0)