Skip to content

Commit 6b4d7d7

Browse files
authored
Fix USING column binding for right joins (#345)
* Fix USING column binding for right joins * Add USING binding coverage for join types * Update right join USING unit test
1 parent bbc1533 commit 6b4d7d7

5 files changed

Lines changed: 160 additions & 35 deletions

File tree

src/binder/expr.rs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -623,19 +623,26 @@ impl<'a, T: Transaction, A: AsRef<[(&'static str, DataValue)]>> Binder<'a, '_, T
623623
))
624624
} else {
625625
// handle col syntax
626-
let mut got_column = Self::find_column_in_scope(
627-
&self.context,
628-
&mut self.table_schema_buf,
629-
full_name.1.as_str(),
630-
);
626+
let mut find_visible_column =
627+
|context: &BinderContext<'a, T>| -> Result<Option<ScalarExpression>, DatabaseError> {
628+
Ok(context
629+
.using
630+
.get(full_name.1.as_str())
631+
.map(|using_column| using_column.visible_expr())
632+
.transpose()?
633+
.or_else(|| {
634+
Self::find_column_in_scope(
635+
context,
636+
&mut self.table_schema_buf,
637+
full_name.1.as_str(),
638+
)
639+
}))
640+
};
641+
let mut got_column = find_visible_column(&self.context)?;
631642
if got_column.is_none() {
632643
if let Some(parent) = self.parent {
633644
self.context.mark_outer_ref();
634-
got_column = Self::find_column_in_scope(
635-
&parent.context,
636-
&mut self.table_schema_buf,
637-
full_name.1.as_str(),
638-
);
645+
got_column = find_visible_column(&parent.context)?;
639646
}
640647
}
641648
match got_column {

src/binder/mod.rs

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use sqlparser::ast::{
3939
Statement, TableObject,
4040
};
4141
use sqlparser::tokenizer::Span;
42-
use std::collections::{BTreeMap, HashMap, HashSet};
42+
use std::collections::{BTreeMap, HashMap};
4343
use std::sync::atomic::{AtomicUsize, Ordering};
4444
use std::sync::Arc;
4545

@@ -54,6 +54,7 @@ use crate::planner::{LogicalPlan, SchemaOutput};
5454
use crate::storage::{TableCache, Transaction, ViewCache};
5555
use crate::types::tuple::SchemaRef;
5656
use crate::types::value::DataValue;
57+
use crate::types::LogicalType;
5758

5859
pub enum InputRefType {
5960
AggCall,
@@ -204,6 +205,69 @@ impl BoundSource<'_> {
204205
}
205206
}
206207

208+
#[derive(Clone)]
209+
pub(crate) struct UsingColumn {
210+
join_type: JoinType,
211+
left_column: ColumnRef,
212+
left_position: usize,
213+
right_column: ColumnRef,
214+
right_position: usize,
215+
}
216+
217+
impl UsingColumn {
218+
fn new(
219+
join_type: JoinType,
220+
left_column: ColumnRef,
221+
left_position: usize,
222+
right_column: ColumnRef,
223+
right_position: usize,
224+
) -> Self {
225+
Self {
226+
join_type,
227+
left_column,
228+
left_position,
229+
right_column,
230+
right_position,
231+
}
232+
}
233+
234+
fn left_expr(&self) -> ScalarExpression {
235+
ScalarExpression::column_expr(self.left_column.clone(), self.left_position)
236+
}
237+
238+
fn right_expr(&self) -> ScalarExpression {
239+
ScalarExpression::column_expr(self.right_column.clone(), self.right_position)
240+
}
241+
242+
pub(crate) fn visible_expr(&self) -> Result<ScalarExpression, DatabaseError> {
243+
match self.join_type {
244+
JoinType::RightOuter => Ok(self.right_expr()),
245+
JoinType::Full => {
246+
let left_expr = self.left_expr();
247+
let right_expr = self.right_expr();
248+
let left_ty = left_expr.return_type();
249+
let right_ty = right_expr.return_type();
250+
let ty = LogicalType::max_logical_type(&left_ty, &right_ty)?.into_owned();
251+
252+
Ok(ScalarExpression::Coalesce {
253+
exprs: vec![left_expr, right_expr],
254+
ty,
255+
})
256+
}
257+
JoinType::Inner | JoinType::LeftOuter | JoinType::Cross => Ok(self.left_expr()),
258+
}
259+
}
260+
261+
pub(crate) fn hides_column(&self, column: &ColumnRef) -> bool {
262+
let hidden_column = if self.join_type.is_right() {
263+
&self.left_column
264+
} else {
265+
&self.right_column
266+
};
267+
hidden_column.same_column(column)
268+
}
269+
}
270+
207271
#[derive(Clone)]
208272
pub struct BinderContext<'a, T: Transaction> {
209273
pub(crate) scala_functions: &'a ScalaFunctions,
@@ -221,7 +285,7 @@ pub struct BinderContext<'a, T: Transaction> {
221285
group_by_exprs: Vec<ScalarExpression>,
222286
pub(crate) agg_calls: Vec<ScalarExpression>,
223287
// join
224-
using: HashSet<ColumnRef>,
288+
using: HashMap<String, UsingColumn>,
225289

226290
bind_step: QueryBindStep,
227291
sub_queries: HashMap<QueryBindStep, Vec<SubQueryType>>,
@@ -471,15 +535,27 @@ impl<'a, T: Transaction> BinderContext<'a, T> {
471535

472536
pub fn add_using(
473537
&mut self,
538+
name: String,
474539
join_type: JoinType,
475-
left_expr: &ColumnRef,
476-
right_expr: &ColumnRef,
477-
) {
478-
self.using.insert(if join_type.is_right() {
479-
left_expr.clone()
480-
} else {
481-
right_expr.clone()
482-
});
540+
left_column: &ColumnRef,
541+
left_position: usize,
542+
right_column: &ColumnRef,
543+
right_position: usize,
544+
) -> Result<(), DatabaseError> {
545+
if self.using.contains_key(&name) {
546+
return Err(DatabaseError::UnsupportedStmt(format!(
547+
"duplicate `USING({name})` across joins is not supported"
548+
)));
549+
}
550+
let using_column = UsingColumn::new(
551+
join_type,
552+
left_column.clone(),
553+
left_position,
554+
right_column.clone(),
555+
right_position,
556+
);
557+
self.using.insert(name, using_column);
558+
Ok(())
483559
}
484560

485561
pub fn add_alias(

src/binder/select.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,7 +1152,11 @@ impl<'a: 'b, 'b, T: Transaction, A: AsRef<[(&'static str, DataValue)]>> Binder<'
11521152
return Some(&table_name) == column.table_name();
11531153
}
11541154
is_qualified_wildcard
1155-
|| Some(&table_name) == column.table_name() && !context.using.contains(column)
1155+
|| Some(&table_name) == column.table_name()
1156+
&& !context
1157+
.using
1158+
.values()
1159+
.any(|using_column| using_column.hides_column(column))
11561160
};
11571161

11581162
let (schema_ref, position_offset) =
@@ -1909,7 +1913,14 @@ impl<'a: 'b, 'b, T: Transaction, A: AsRef<[(&'static str, DataValue)]>> Binder<'
19091913
ident,
19101914
));
19111915
};
1912-
self.context.add_using(join_type, left_column, right_column);
1916+
self.context.add_using(
1917+
name.clone(),
1918+
join_type,
1919+
left_column,
1920+
left_position,
1921+
right_column,
1922+
left_schema.len() + right_position,
1923+
)?;
19131924
on_keys.push((
19141925
ScalarExpression::column_expr(left_column.clone(), left_position),
19151926
ScalarExpression::column_expr(
@@ -1951,7 +1962,14 @@ impl<'a: 'b, 'b, T: Transaction, A: AsRef<[(&'static str, DataValue)]>> Binder<'
19511962
left_schema.len() + right_position,
19521963
);
19531964

1954-
self.context.add_using(join_type, left_column, right_column);
1965+
self.context.add_using(
1966+
name.to_string(),
1967+
join_type,
1968+
left_column,
1969+
left_position,
1970+
right_column,
1971+
left_schema.len() + right_position,
1972+
)?;
19551973
on_keys.push((left_expr, right_expr));
19561974
}
19571975
}

src/execution/dql/join/nested_loop_join.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,7 +1178,7 @@ mod test {
11781178
}
11791179

11801180
#[test]
1181-
fn test_right_join_using_keeps_left_visible_column_binding() -> Result<(), DatabaseError> {
1181+
fn test_right_join_using_binds_visible_column_to_right_side() -> Result<(), DatabaseError> {
11821182
let temp_dir = TempDir::new().expect("unable to create temporary working directory");
11831183
let db = DataBaseBuilder::path(temp_dir.path()).build_in_memory()?;
11841184

@@ -1216,9 +1216,9 @@ mod test {
12161216
Some("A".to_string()),
12171217
Some("A".to_string())
12181218
],
1219-
vec![None, None, Some("B".to_string())],
1220-
vec![None, None, Some("C".to_string())],
1221-
vec![None, None, Some("E".to_string())],
1219+
vec![Some("B".to_string()), None, Some("B".to_string())],
1220+
vec![Some("C".to_string()), None, Some("C".to_string())],
1221+
vec![Some("E".to_string()), None, Some("E".to_string())],
12221222
]
12231223
);
12241224

tests/slt/crdb/join.slt

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -705,9 +705,9 @@ query TTT
705705
SELECT s, str1.s, str2.s FROM str1 RIGHT OUTER JOIN str2 USING(s) order by str2.s
706706
----
707707
A A A
708-
null null B
709-
null null C
710-
null null E
708+
B null B
709+
C null C
710+
E null E
711711

712712
query ITIT
713713
SELECT * FROM str1 LEFT OUTER JOIN str2 ON str1.s = str2.s order by str1.a
@@ -862,6 +862,31 @@ INSERT INTO l VALUES (1, 1), (2, 1), (3, 1)
862862
statement ok
863863
INSERT INTO r VALUES (2, 1), (3, 1), (4, 1)
864864

865+
query III
866+
SELECT a, l.a, r.a FROM l INNER JOIN r USING(a) WHERE a = 2
867+
----
868+
2 2 2
869+
870+
query III
871+
SELECT a, l.a, r.a FROM l LEFT OUTER JOIN r USING(a) WHERE a = 1
872+
----
873+
1 1 null
874+
875+
query III
876+
SELECT a, l.a, r.a FROM l RIGHT OUTER JOIN r USING(a) WHERE a = 4
877+
----
878+
4 null 4
879+
880+
query III
881+
SELECT a, l.a, r.a FROM l FULL OUTER JOIN r USING(a) WHERE a = 1
882+
----
883+
1 1 null
884+
885+
query III
886+
SELECT a, l.a, r.a FROM l FULL OUTER JOIN r USING(a) WHERE a = 4
887+
----
888+
4 null 4
889+
865890
query III
866891
SELECT * FROM l LEFT OUTER JOIN r USING(a) WHERE a = 1
867892
----
@@ -877,11 +902,10 @@ SELECT * FROM l RIGHT OUTER JOIN r USING(a) WHERE a = 3
877902
----
878903
1 3 1
879904

880-
# TODO: a= 4 means x on both sides
881-
# query III
882-
# SELECT * FROM l RIGHT OUTER JOIN r USING(a) WHERE a = 4
883-
# ----
884-
# NULL 4 1
905+
query III
906+
SELECT * FROM l RIGHT OUTER JOIN r USING(a) WHERE a = 4
907+
----
908+
null 4 1
885909

886910
statement ok
887911
drop table if exists foo

0 commit comments

Comments
 (0)