Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 80cdd45

Browse files
committed
feat(query): support ASOF JOIN USING
1 parent 63faecd commit 80cdd45

2 files changed

Lines changed: 92 additions & 9 deletions

File tree

src/query/sql/src/planner/binder/bind_table_reference/bind_join.rs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ use crate::planner::semantic::NameResolutionContext;
4646
use crate::plans::BoundColumnRef;
4747
use crate::plans::EvalScalar;
4848
use crate::plans::Filter;
49+
use crate::plans::FunctionCall;
4950
use crate::plans::HashJoinBuildCacheInfo;
5051
use crate::plans::Join;
5152
use crate::plans::JoinEquiCondition;
@@ -61,6 +62,8 @@ pub struct JoinConditions {
6162
pub(crate) other_conditions: Vec<ScalarExpr>,
6263
}
6364

65+
const ASOF_USING_RANGE_FUNC: &str = "gte";
66+
6467
impl Binder {
6568
pub(crate) fn bind_join(
6669
&mut self,
@@ -732,6 +735,7 @@ impl<'a> JoinConditionResolver<'a> {
732735
using_columns,
733736
left_join_conditions,
734737
right_join_conditions,
738+
non_equi_conditions,
735739
join_op,
736740
)?;
737741
}
@@ -746,6 +750,7 @@ impl<'a> JoinConditionResolver<'a> {
746750
using_columns,
747751
left_join_conditions,
748752
right_join_conditions,
753+
non_equi_conditions,
749754
join_op,
750755
)?
751756
}
@@ -907,6 +912,7 @@ impl<'a> JoinConditionResolver<'a> {
907912
using_columns: Vec<(Span, String)>,
908913
left_join_conditions: &mut Vec<ScalarExpr>,
909914
right_join_conditions: &mut Vec<ScalarExpr>,
915+
non_equi_conditions: &mut Vec<ScalarExpr>,
910916
join_op: &JoinOperator,
911917
) -> Result<()> {
912918
bind_join_columns(
@@ -915,7 +921,18 @@ impl<'a> JoinConditionResolver<'a> {
915921
self.join_context,
916922
);
917923
let left_columns_len = self.left_column_bindings.len();
918-
for (span, join_key) in using_columns.iter() {
924+
let asof_range_column = if matches!(
925+
join_op,
926+
JoinOperator::Asof
927+
| JoinOperator::LeftAsof
928+
| JoinOperator::RightAsof
929+
| JoinOperator::FullAsof
930+
) {
931+
using_columns.len().checked_sub(1)
932+
} else {
933+
None
934+
};
935+
for (index, (span, join_key)) in using_columns.iter().enumerate() {
919936
let join_key_name = join_key.as_str();
920937
let left_scalar = if let Some(col_binding) = self.join_context.columns
921938
[0..left_columns_len]
@@ -950,7 +967,8 @@ impl<'a> JoinConditionResolver<'a> {
950967
))
951968
.set_span(*span));
952969
};
953-
let idx = !matches!(join_op, JoinOperator::RightOuter) as usize;
970+
let idx =
971+
!matches!(join_op, JoinOperator::RightOuter | JoinOperator::RightAsof) as usize;
954972
if let Some(col_binding) = self
955973
.join_context
956974
.columns
@@ -961,16 +979,25 @@ impl<'a> JoinConditionResolver<'a> {
961979
})
962980
.nth(idx)
963981
{
964-
// Always make the second using column in the join_context invisible in unqualified wildcard.
982+
// Make the duplicate USING column invisible in unqualified wildcard.
965983
col_binding.visibility = Visibility::UnqualifiedWildcardInVisible;
966984
}
967985

968-
self.add_equi_conditions(
969-
left_scalar,
970-
right_scalar,
971-
left_join_conditions,
972-
right_join_conditions,
973-
)?;
986+
if Some(index) == asof_range_column {
987+
non_equi_conditions.push(ScalarExpr::FunctionCall(FunctionCall {
988+
span: *span,
989+
func_name: ASOF_USING_RANGE_FUNC.to_string(),
990+
params: vec![],
991+
arguments: vec![left_scalar, right_scalar],
992+
}));
993+
} else {
994+
self.add_equi_conditions(
995+
left_scalar,
996+
right_scalar,
997+
left_join_conditions,
998+
right_join_conditions,
999+
)?;
1000+
}
9741001
}
9751002
Ok(())
9761003
}

tests/sqllogictests/suites/duckdb/join/asof/test_asof_join_eq_hash_fast_path.test

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ order by t.id
5252
3 20
5353
4 11
5454

55+
# ASOF USING treats the final USING column as the range key and the
56+
# preceding USING columns as equality keys.
57+
query I?II
58+
select *
59+
from asof_eq_trades t
60+
asof join asof_eq_prices p
61+
using (symbol, wh)
62+
order by id
63+
----
64+
1 2020-01-01 00:00:01.000000 1 10
65+
2 2020-01-01 00:00:03.000000 2 20
66+
3 2020-01-01 00:00:05.000000 2 20
67+
4 2020-01-01 00:00:06.000000 1 11
68+
5569
# Left ASOF should preserve unmatched rows as NULL while keeping the same
5670
# nearest-predecessor semantics for matched rows.
5771
query II
@@ -68,6 +82,20 @@ order by t.id
6882
5 NULL
6983
6 NULL
7084

85+
query II
86+
select t.id, p.price
87+
from asof_eq_trades t
88+
asof left join asof_eq_prices p
89+
using (symbol, wh)
90+
order by t.id
91+
----
92+
1 10
93+
2 20
94+
3 20
95+
4 11
96+
5 NULL
97+
6 NULL
98+
7199
# Right ASOF should preserve unmatched build-side rows from the original
72100
# `prices` table after the ASOF rewrite swaps the physical children.
73101
query II
@@ -100,6 +128,34 @@ order by t.id, p.price
100128
6 NULL
101129
NULL 21
102130

131+
query II
132+
select p.price, t.id
133+
from asof_eq_trades t
134+
asof right join asof_eq_prices p
135+
using (symbol, wh)
136+
order by p.price, t.id
137+
----
138+
10 1
139+
11 4
140+
20 2
141+
20 3
142+
21 NULL
143+
144+
query II
145+
select t.id, p.price
146+
from asof_eq_trades t
147+
asof full join asof_eq_prices p
148+
using (symbol, wh)
149+
order by t.id, p.price
150+
----
151+
1 10
152+
2 20
153+
3 20
154+
4 11
155+
5 NULL
156+
6 NULL
157+
NULL 21
158+
103159
statement ok
104160
drop table asof_eq_trades
105161

0 commit comments

Comments
 (0)