This is an automated email from the ASF dual-hosted git repository.
comphead pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git
The following commit(s) were added to refs/heads/main by this push:
new 7ae1ccb497 Improve push down filter of join (#13184)
7ae1ccb497 is described below
commit 7ae1ccb4971474d376e5bed1d7116fcaf23f906f
Author: JasonLi <[email protected]>
AuthorDate: Fri Nov 1 02:11:32 2024 +0800
Improve push down filter of join (#13184)
* improve push down filter of join
---
datafusion/optimizer/src/push_down_filter.rs | 337 +++++++++---
datafusion/optimizer/src/utils.rs | 8 +
.../test_files/join_disable_repartition_joins.slt | 23 +-
.../test_files/push_down_filter_join.slt | 612 +++++++++++++++++++++
4 files changed, 909 insertions(+), 71 deletions(-)
diff --git a/datafusion/optimizer/src/push_down_filter.rs
b/datafusion/optimizer/src/push_down_filter.rs
index a0262d7d95..3b3693cc94 100644
--- a/datafusion/optimizer/src/push_down_filter.rs
+++ b/datafusion/optimizer/src/push_down_filter.rs
@@ -36,7 +36,9 @@ use datafusion_expr::{
};
use crate::optimizer::ApplyOrder;
-use crate::utils::{has_all_column_refs, is_restrict_null_predicate};
+use crate::utils::{
+ contain_all_columns, has_all_column_refs, is_restrict_null_predicate,
+};
use crate::{OptimizerConfig, OptimizerRule};
/// Optimizer rule for pushing (moving) filter expressions down in a plan so
@@ -214,19 +216,19 @@ impl<'a> ColumnChecker<'a> {
}
/// Return true if the expression references only columns from the left
side of the join
- fn is_left_only(&mut self, predicate: &Expr) -> bool {
+ fn left_only(&mut self, column_refs: &HashSet<&Column>) -> bool {
if self.left_columns.is_none() {
self.left_columns = Some(schema_columns(self.left_schema));
}
- has_all_column_refs(predicate, self.left_columns.as_ref().unwrap())
+ contain_all_columns(column_refs, self.left_columns.as_ref().unwrap())
}
/// Return true if the expression references only columns from the right
side of the join
- fn is_right_only(&mut self, predicate: &Expr) -> bool {
+ fn right_only(&mut self, column_refs: &HashSet<&Column>) -> bool {
if self.right_columns.is_none() {
self.right_columns = Some(schema_columns(self.right_schema));
}
- has_all_column_refs(predicate, self.right_columns.as_ref().unwrap())
+ contain_all_columns(column_refs, self.right_columns.as_ref().unwrap())
}
}
@@ -410,10 +412,13 @@ fn extract_or_clause(expr: &Expr, schema_columns:
&HashSet<Column>) -> Option<Ex
/// push down join/cross-join
fn push_down_all_join(
predicates: Vec<Expr>,
- inferred_join_predicates: Vec<Expr>,
+ inferred_from_predicates: Vec<Expr>,
mut join: Join,
on_filter: Vec<Expr>,
+ inferred_from_on_filter: Vec<Expr>,
) -> Result<Transformed<LogicalPlan>> {
+ assert_ne!(join.join_type, JoinType::Full);
+
let is_inner_join = join.join_type == JoinType::Inner;
// Get pushable predicates from current optimizer state
let (left_preserved, right_preserved) = lr_is_preserved(join.join_type);
@@ -429,14 +434,24 @@ fn push_down_all_join(
let mut keep_predicates = vec![];
let mut join_conditions = vec![];
let mut checker = ColumnChecker::new(left_schema, right_schema);
+
for predicate in predicates {
- if left_preserved && checker.is_left_only(&predicate) {
+ let columns = predicate.column_refs();
+ macro_rules! restrict_null {
+ () => {{
+ let predicate_cloned = predicate.clone();
+ let cols = columns.iter().cloned();
+ is_restrict_null_predicate(predicate_cloned,
cols).unwrap_or(false)
+ }};
+ }
+
+ if checker.left_only(&columns) && (left_preserved || restrict_null!())
{
left_push.push(predicate);
- } else if right_preserved && checker.is_right_only(&predicate) {
+ } else if checker.right_only(&columns) && (right_preserved ||
restrict_null!()) {
right_push.push(predicate);
} else if is_inner_join && can_evaluate_as_join_condition(&predicate)?
{
- // Here we do not differ it is eq or non-eq predicate,
ExtractEquijoinPredicate will extract the eq predicate
- // and convert to the join on condition
+ // Here we do not differ it is eq or non-eq predicate,
ExtractEquijoinPredicate will
+ // extract the eq predicate and convert to the join on condition
join_conditions.push(predicate);
} else {
keep_predicates.push(predicate);
@@ -444,10 +459,13 @@ fn push_down_all_join(
}
// For infer predicates, if they can not push through join, just drop them
- for predicate in inferred_join_predicates {
- if left_preserved && checker.is_left_only(&predicate) {
+ // Because we check whether it is_restrict_null in the process of Infer,
there is no need to
+ // check again
+ for predicate in inferred_from_predicates {
+ let columns = predicate.column_refs();
+ if checker.left_only(&columns) {
left_push.push(predicate);
- } else if right_preserved && checker.is_right_only(&predicate) {
+ } else if checker.right_only(&columns) {
right_push.push(predicate);
}
}
@@ -455,15 +473,24 @@ fn push_down_all_join(
let mut on_filter_join_conditions = vec![];
let (on_left_preserved, on_right_preserved) =
on_lr_is_preserved(join.join_type);
- if !on_filter.is_empty() {
- for on in on_filter {
- if on_left_preserved && checker.is_left_only(&on) {
- left_push.push(on)
- } else if on_right_preserved && checker.is_right_only(&on) {
- right_push.push(on)
- } else {
- on_filter_join_conditions.push(on)
- }
+ for on in on_filter {
+ let columns = on.column_refs();
+ if on_left_preserved && checker.left_only(&columns) {
+ left_push.push(on)
+ } else if on_right_preserved && checker.right_only(&columns) {
+ right_push.push(on)
+ } else {
+ on_filter_join_conditions.push(on)
+ }
+ }
+
+ // For infer predicates, if they can not push through join, just drop them
+ for on in inferred_from_on_filter {
+ let columns = on.column_refs();
+ if on_left_preserved && checker.left_only(&columns) {
+ left_push.push(on)
+ } else if on_right_preserved && checker.right_only(&columns) {
+ right_push.push(on)
}
}
@@ -519,6 +546,17 @@ fn push_down_join(
join: Join,
parent_predicate: Option<&Expr>,
) -> Result<Transformed<LogicalPlan>> {
+ if matches!(join.join_type, JoinType::Full) {
+ let plan = LogicalPlan::Join(join);
+ return Ok(match parent_predicate {
+ Some(predicate) =>
Transformed::yes(LogicalPlan::Filter(Filter::try_new(
+ predicate.clone(),
+ Arc::new(plan),
+ )?)),
+ None => Transformed::no(plan),
+ });
+ }
+
// Split the parent predicate into individual conjunctive parts.
let predicates = parent_predicate
.map_or_else(Vec::new, |pred| split_conjunction_owned(pred.clone()));
@@ -530,17 +568,24 @@ fn push_down_join(
.map_or_else(Vec::new, |filter|
split_conjunction_owned(filter.clone()));
// Are there any new join predicates that can be inferred from the filter
expressions?
- let inferred_join_predicates =
+ let (inferred_from_predicates, inferred_from_on_filter) =
infer_join_predicates(&join, &predicates, &on_filters)?;
if on_filters.is_empty()
+ && inferred_from_on_filter.is_empty()
&& predicates.is_empty()
- && inferred_join_predicates.is_empty()
+ && inferred_from_predicates.is_empty()
{
return Ok(Transformed::no(LogicalPlan::Join(join)));
}
- push_down_all_join(predicates, inferred_join_predicates, join, on_filters)
+ push_down_all_join(
+ predicates,
+ inferred_from_predicates,
+ join,
+ on_filters,
+ inferred_from_on_filter,
+ )
}
/// Extracts any equi-join join predicates from the given filter expressions.
@@ -553,11 +598,13 @@ fn push_down_join(
/// * `on_filters` filters from the join ON clause that have not already been
/// identified as join predicates
///
+/// # Return Value
+/// A tuple of Expr Vec - (inferred_from_predicates, inferred_from_on_filters).
fn infer_join_predicates(
join: &Join,
predicates: &[Expr],
on_filters: &[Expr],
-) -> Result<Vec<Expr>> {
+) -> Result<(Vec<Expr>, Vec<Expr>)> {
// Only allow both side key is column.
let join_col_keys = join
.on
@@ -578,6 +625,7 @@ fn infer_join_predicates(
predicates,
&mut inferred_predicates,
)?;
+ let inferred_from_predicates = inferred_predicates.take_all();
infer_join_predicates_from_on_filters(
&join_col_keys,
@@ -585,8 +633,9 @@ fn infer_join_predicates(
on_filters,
&mut inferred_predicates,
)?;
+ let inferred_from_on_filters = inferred_predicates.predicates;
- Ok(inferred_predicates.predicates)
+ Ok((inferred_from_predicates, inferred_from_on_filters))
}
/// Inferred predicates collector.
@@ -610,6 +659,12 @@ impl InferredPredicates {
}
}
+ fn take_all(&mut self) -> Vec<Expr> {
+ let mut temp = vec![];
+ std::mem::swap(&mut self.predicates, &mut temp);
+ temp
+ }
+
fn try_build_predicate(
&mut self,
predicate: Expr,
@@ -2107,11 +2162,10 @@ mod tests {
// filter not duplicated nor pushed down - i.e. noop
let expected = "\
- Filter: test2.a <= Int64(1)\
- \n Left Join: Using test.a = test2.a\
- \n TableScan: test, full_filters=[test.a <= Int64(1)]\
- \n Projection: test2.a\
- \n TableScan: test2";
+ Left Join: Using test.a = test2.a\
+ \n TableScan: test, full_filters=[test.a <= Int64(1)]\
+ \n Projection: test2.a\
+ \n TableScan: test2, full_filters=[test2.a <= Int64(1)]";
assert_optimized_plan_eq(plan, expected)
}
@@ -2145,11 +2199,10 @@ mod tests {
// filter not duplicated nor pushed down - i.e. noop
let expected = "\
- Filter: test.a <= Int64(1)\
- \n Right Join: Using test.a = test2.a\
- \n TableScan: test\
- \n Projection: test2.a\
- \n TableScan: test2, full_filters=[test2.a <= Int64(1)]";
+ Right Join: Using test.a = test2.a\
+ \n TableScan: test, full_filters=[test.a <= Int64(1)]\
+ \n Projection: test2.a\
+ \n TableScan: test2, full_filters=[test2.a <= Int64(1)]";
assert_optimized_plan_eq(plan, expected)
}
@@ -2187,7 +2240,7 @@ mod tests {
Left Join: Using test.a = test2.a\
\n TableScan: test, full_filters=[test.a <= Int64(1)]\
\n Projection: test2.a\
- \n TableScan: test2";
+ \n TableScan: test2, full_filters=[test2.a <= Int64(1)]";
assert_optimized_plan_eq(plan, expected)
}
@@ -2223,7 +2276,7 @@ mod tests {
// filter sent to right side of join, not duplicated to the left
let expected = "\
Right Join: Using test.a = test2.a\
- \n TableScan: test\
+ \n TableScan: test, full_filters=[test.a <= Int64(1)]\
\n Projection: test2.a\
\n TableScan: test2, full_filters=[test2.a <= Int64(1)]";
assert_optimized_plan_eq(plan, expected)
@@ -2272,6 +2325,47 @@ mod tests {
assert_optimized_plan_eq(plan, expected)
}
+ #[test]
+ fn join_with_non_restrict_null_predicate() -> Result<()> {
+ let table_scan = test_table_scan()?;
+ let left = LogicalPlanBuilder::from(table_scan)
+ .project(vec![col("a"), col("b"), col("c")])?
+ .build()?;
+ let right_table_scan = test_table_scan_with_name("test2")?;
+ let right = LogicalPlanBuilder::from(right_table_scan)
+ .project(vec![col("a"), col("b"), col("c")])?
+ .build()?;
+ let filter = col("test.b").is_null().and(col("test2.b").is_null());
+ let plan = LogicalPlanBuilder::from(left)
+ .join(
+ right,
+ JoinType::Inner,
+ (vec![Column::from_name("a")], vec![Column::from_name("a")]),
+ None,
+ )?
+ .filter(filter)?
+ .build()?;
+
+ // not part of the test, just good to know:
+ assert_eq!(
+ format!("{plan}"),
+ "Filter: test.b IS NULL AND test2.b IS NULL\
+ \n Inner Join: test.a = test2.a\
+ \n Projection: test.a, test.b, test.c\
+ \n TableScan: test\
+ \n Projection: test2.a, test2.b, test2.c\
+ \n TableScan: test2"
+ );
+
+ let expected = "\
+ Inner Join: test.a = test2.a\
+ \n Projection: test.a, test.b, test.c\
+ \n TableScan: test, full_filters=[test.b IS NULL]\
+ \n Projection: test2.a, test2.b, test2.c\
+ \n TableScan: test2, full_filters=[test2.b IS NULL]";
+ assert_optimized_plan_eq(plan, expected)
+ }
+
/// join filter should be completely removed after pushdown
#[test]
fn join_filter_removed() -> Result<()> {
@@ -2393,7 +2487,49 @@ mod tests {
\n Projection: test.a, test.b, test.c\
\n TableScan: test\
\n Projection: test2.a, test2.b, test2.c\
- \n TableScan: test2, full_filters=[test2.c > UInt32(4)]";
+ \n TableScan: test2, full_filters=[test2.c > UInt32(4), test2.a >
UInt32(1)]";
+ assert_optimized_plan_eq(plan, expected)
+ }
+
+ #[test]
+ fn left_join_with_non_restrict_null_predicate() -> Result<()> {
+ let table_scan = test_table_scan()?;
+ let left = LogicalPlanBuilder::from(table_scan)
+ .project(vec![col("a"), col("b"), col("c")])?
+ .build()?;
+ let right_table_scan = test_table_scan_with_name("test2")?;
+ let right = LogicalPlanBuilder::from(right_table_scan)
+ .project(vec![col("a"), col("b"), col("c")])?
+ .build()?;
+ let filter = col("test.b").is_null().and(col("test2.b").is_null());
+ let plan = LogicalPlanBuilder::from(left)
+ .join(
+ right,
+ JoinType::Left,
+ (vec![Column::from_name("a")], vec![Column::from_name("a")]),
+ None,
+ )?
+ .filter(filter)?
+ .build()?;
+
+ // not part of the test, just good to know:
+ assert_eq!(
+ format!("{plan}"),
+ "Filter: test.b IS NULL AND test2.b IS NULL\
+ \n Left Join: test.a = test2.a\
+ \n Projection: test.a, test.b, test.c\
+ \n TableScan: test\
+ \n Projection: test2.a, test2.b, test2.c\
+ \n TableScan: test2"
+ );
+
+ let expected = "\
+ Filter: test2.b IS NULL\
+ \n Left Join: test.a = test2.a\
+ \n Projection: test.a, test.b, test.c\
+ \n TableScan: test, full_filters=[test.b IS NULL]\
+ \n Projection: test2.a, test2.b, test2.c\
+ \n TableScan: test2";
assert_optimized_plan_eq(plan, expected)
}
@@ -2440,6 +2576,87 @@ mod tests {
assert_optimized_plan_eq(plan, expected)
}
+ #[test]
+ fn right_join_with_non_restrict_null_predicate() -> Result<()> {
+ let table_scan = test_table_scan()?;
+ let left = LogicalPlanBuilder::from(table_scan)
+ .project(vec![col("a"), col("b"), col("c")])?
+ .build()?;
+ let right_table_scan = test_table_scan_with_name("test2")?;
+ let right = LogicalPlanBuilder::from(right_table_scan)
+ .project(vec![col("a"), col("b"), col("c")])?
+ .build()?;
+ let filter = col("test.b").is_null().and(col("test2.b").is_null());
+ let plan = LogicalPlanBuilder::from(left)
+ .join(
+ right,
+ JoinType::Right,
+ (vec![Column::from_name("a")], vec![Column::from_name("a")]),
+ None,
+ )?
+ .filter(filter)?
+ .build()?;
+
+ // not part of the test, just good to know:
+ assert_eq!(
+ format!("{plan}"),
+ "Filter: test.b IS NULL AND test2.b IS NULL\
+ \n Right Join: test.a = test2.a\
+ \n Projection: test.a, test.b, test.c\
+ \n TableScan: test\
+ \n Projection: test2.a, test2.b, test2.c\
+ \n TableScan: test2"
+ );
+
+ let expected = "\
+ Filter: test.b IS NULL\
+ \n Right Join: test.a = test2.a\
+ \n Projection: test.a, test.b, test.c\
+ \n TableScan: test\
+ \n Projection: test2.a, test2.b, test2.c\
+ \n TableScan: test2, full_filters=[test2.b IS NULL]";
+ assert_optimized_plan_eq(plan, expected)
+ }
+
+ #[test]
+ fn full_join() -> Result<()> {
+ let table_scan = test_table_scan()?;
+ let left = LogicalPlanBuilder::from(table_scan)
+ .project(vec![col("a"), col("b"), col("c")])?
+ .build()?;
+ let right_table_scan = test_table_scan_with_name("test2")?;
+ let right = LogicalPlanBuilder::from(right_table_scan)
+ .project(vec![col("a"), col("b"), col("c")])?
+ .build()?;
+ let filter = col("test.a")
+ .gt(lit(1u32))
+ .and(col("test.b").lt(col("test2.b")))
+ .and(col("test2.c").gt(lit(4u32)));
+ let plan = LogicalPlanBuilder::from(left)
+ .join(
+ right,
+ JoinType::Full,
+ (vec![Column::from_name("a")], vec![Column::from_name("a")]),
+ None,
+ )?
+ .filter(filter)?
+ .build()?;
+
+ // not part of the test, just good to know:
+ assert_eq!(
+ format!("{plan}"),
+ "Filter: test.a > UInt32(1) AND test.b < test2.b AND test2.c >
UInt32(4)\
+ \n Full Join: test.a = test2.a\
+ \n Projection: test.a, test.b, test.c\
+ \n TableScan: test\
+ \n Projection: test2.a, test2.b, test2.c\
+ \n TableScan: test2"
+ );
+
+ let expected = &format!("{plan}");
+ assert_optimized_plan_eq(plan, expected)
+ }
+
/// single table predicate parts of ON condition should not be pushed
#[test]
fn full_join_on_with_filter() -> Result<()> {
@@ -2963,11 +3180,10 @@ Projection: a, b
// Inferred the predicate `test1.a <= Int64(1)` and push it down to
the left side.
let expected = "\
- Filter: test2.a <= Int64(1)\
- \n LeftSemi Join: test1.a = test2.a\
- \n TableScan: test1, full_filters=[test1.a <= Int64(1)]\
- \n Projection: test2.a, test2.b\
- \n TableScan: test2";
+ LeftSemi Join: test1.a = test2.a\
+ \n TableScan: test1, full_filters=[test1.a <= Int64(1)]\
+ \n Projection: test2.a, test2.b\
+ \n TableScan: test2, full_filters=[test2.a <= Int64(1)]";
assert_optimized_plan_eq(plan, expected)
}
@@ -3044,11 +3260,10 @@ Projection: a, b
// Inferred the predicate `test2.a <= Int64(1)` and push it down to
the right side.
let expected = "\
- Filter: test1.a <= Int64(1)\
- \n RightSemi Join: test1.a = test2.a\
- \n TableScan: test1\
- \n Projection: test2.a, test2.b\
- \n TableScan: test2, full_filters=[test2.a <= Int64(1)]";
+ RightSemi Join: test1.a = test2.a\
+ \n TableScan: test1, full_filters=[test1.a <= Int64(1)]\
+ \n Projection: test2.a, test2.b\
+ \n TableScan: test2, full_filters=[test2.a <= Int64(1)]";
assert_optimized_plan_eq(plan, expected)
}
@@ -3129,12 +3344,11 @@ Projection: a, b
// For left anti, filter of the right side filter can be pushed down.
let expected = "\
- Filter: test2.a > UInt32(2)\
- \n LeftAnti Join: test1.a = test2.a\
- \n Projection: test1.a, test1.b\
- \n TableScan: test1, full_filters=[test1.a > UInt32(2)]\
- \n Projection: test2.a, test2.b\
- \n TableScan: test2";
+ LeftAnti Join: test1.a = test2.a\
+ \n Projection: test1.a, test1.b\
+ \n TableScan: test1, full_filters=[test1.a > UInt32(2)]\
+ \n Projection: test2.a, test2.b\
+ \n TableScan: test2, full_filters=[test2.a > UInt32(2)]";
assert_optimized_plan_eq(plan, expected)
}
@@ -3220,12 +3434,11 @@ Projection: a, b
// For right anti, filter of the left side can be pushed down.
let expected = "\
- Filter: test1.a > UInt32(2)\
- \n RightAnti Join: test1.a = test2.a\
- \n Projection: test1.a, test1.b\
- \n TableScan: test1\
- \n Projection: test2.a, test2.b\
- \n TableScan: test2, full_filters=[test2.a > UInt32(2)]";
+ RightAnti Join: test1.a = test2.a\
+ \n Projection: test1.a, test1.b\
+ \n TableScan: test1, full_filters=[test1.a > UInt32(2)]\
+ \n Projection: test2.a, test2.b\
+ \n TableScan: test2, full_filters=[test2.a > UInt32(2)]";
assert_optimized_plan_eq(plan, expected)
}
diff --git a/datafusion/optimizer/src/utils.rs
b/datafusion/optimizer/src/utils.rs
index 9f325bc01b..9f8d7b7f97 100644
--- a/datafusion/optimizer/src/utils.rs
+++ b/datafusion/optimizer/src/utils.rs
@@ -79,6 +79,14 @@ pub fn optimize_children(
/// Returns true if `expr` contains all columns in `schema_cols`
pub(crate) fn has_all_column_refs(expr: &Expr, schema_cols: &HashSet<Column>)
-> bool {
let column_refs = expr.column_refs();
+ contain_all_columns(&column_refs, schema_cols)
+}
+
+/// Returns true if `column_refs` contains all columns in `schema_cols`
+pub(crate) fn contain_all_columns(
+ column_refs: &HashSet<&Column>,
+ schema_cols: &HashSet<Column>,
+) -> bool {
// note can't use HashSet::intersect because of different types (owned vs
References)
schema_cols
.iter()
diff --git
a/datafusion/sqllogictest/test_files/join_disable_repartition_joins.slt
b/datafusion/sqllogictest/test_files/join_disable_repartition_joins.slt
index c56c59b1bd..eb47ccdd43 100644
--- a/datafusion/sqllogictest/test_files/join_disable_repartition_joins.slt
+++ b/datafusion/sqllogictest/test_files/join_disable_repartition_joins.slt
@@ -89,20 +89,25 @@ logical_plan
02)--Projection: t2.a AS a2, t2.b
03)----RightSemi Join: t1.d = t2.d, t1.c = t2.c
04)------SubqueryAlias: t1
-05)--------TableScan: annotated_data projection=[c, d]
-06)------SubqueryAlias: t2
-07)--------Filter: annotated_data.d = Int32(3)
-08)----------TableScan: annotated_data projection=[a, b, c, d],
partial_filters=[annotated_data.d = Int32(3)]
+05)--------Filter: annotated_data.d = Int32(3)
+06)----------TableScan: annotated_data projection=[c, d],
partial_filters=[annotated_data.d = Int32(3)]
+07)------SubqueryAlias: t2
+08)--------Filter: annotated_data.d = Int32(3)
+09)----------TableScan: annotated_data projection=[a, b, c, d],
partial_filters=[annotated_data.d = Int32(3)]
physical_plan
01)SortPreservingMergeExec: [a2@0 ASC NULLS LAST,b@1 ASC NULLS LAST], fetch=10
02)--ProjectionExec: expr=[a@0 as a2, b@1 as b]
03)----CoalesceBatchesExec: target_batch_size=8192, fetch=10
04)------HashJoinExec: mode=CollectLeft, join_type=RightSemi, on=[(d@1, d@3),
(c@0, c@2)], projection=[a@0, b@1]
-05)--------CsvExec: file_groups={1 group:
[[WORKSPACE_ROOT/datafusion/core/tests/data/window_2.csv]]}, projection=[c, d],
has_header=true
-06)--------CoalesceBatchesExec: target_batch_size=8192
-07)----------FilterExec: d@3 = 3
-08)------------RepartitionExec: partitioning=RoundRobinBatch(4),
input_partitions=1
-09)--------------CsvExec: file_groups={1 group:
[[WORKSPACE_ROOT/datafusion/core/tests/data/window_2.csv]]}, projection=[a, b,
c, d], output_ordering=[a@0 ASC NULLS LAST, b@1 ASC NULLS LAST, c@2 ASC NULLS
LAST], has_header=true
+05)--------CoalescePartitionsExec
+06)----------CoalesceBatchesExec: target_batch_size=8192
+07)------------FilterExec: d@1 = 3
+08)--------------RepartitionExec: partitioning=RoundRobinBatch(4),
input_partitions=1
+09)----------------CsvExec: file_groups={1 group:
[[WORKSPACE_ROOT/datafusion/core/tests/data/window_2.csv]]}, projection=[c, d],
has_header=true
+10)--------CoalesceBatchesExec: target_batch_size=8192
+11)----------FilterExec: d@3 = 3
+12)------------RepartitionExec: partitioning=RoundRobinBatch(4),
input_partitions=1
+13)--------------CsvExec: file_groups={1 group:
[[WORKSPACE_ROOT/datafusion/core/tests/data/window_2.csv]]}, projection=[a, b,
c, d], output_ordering=[a@0 ASC NULLS LAST, b@1 ASC NULLS LAST, c@2 ASC NULLS
LAST], has_header=true
# preserve_right_semi_join
query II nosort
diff --git a/datafusion/sqllogictest/test_files/push_down_filter_join.slt
b/datafusion/sqllogictest/test_files/push_down_filter_join.slt
new file mode 100644
index 0000000000..f687c542a6
--- /dev/null
+++ b/datafusion/sqllogictest/test_files/push_down_filter_join.slt
@@ -0,0 +1,612 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Contents
+# Inner Join
+# `WHERE` clause
+# `ON` clause
+# Left Outer Join
+# `WHERE` clause
+# `ON` clause
+# Right Outer Join
+# `WHERE` clause
+# `ON` clause
+# Full Outer Join
+# `WHERE` clause
+# `ON` clause
+
+# Create table t1
+statement ok
+CREATE TABLE t1(t1_id INT, t1_name VARCHAR) AS VALUES
+(11, 'a'),
+(22, 'b'),
+(33, 'c'),
+(44, 'd'),
+(77, 'e'),
+(88, NULL),
+(99, NULL)
+
+# Create table t2
+statement ok
+CREATE TABLE t2(t2_id INT, t2_name VARCHAR) AS VALUES
+(11, 'z'),
+(22, NULL),
+(44, 'x'),
+(55, 'w'),
+(99, 'u')
+
+# Inner Join
+
+## `WHERE` clause
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id ORDER BY t1_id, t1_name,
t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+44 d 44 x
+99 NULL 99 u
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_name = 'a'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_name = 'z'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+99 NULL 99 u
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_id <= 22
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_id <= 22
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_id IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_id IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+
+## `ON` clause
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_name = 'a'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_name = 'z'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+99 NULL 99 u
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+99 NULL 99 u
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_id <= 22 ORDER
BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 INNER JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_id <= 22 ORDER
BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+
+# Left Outer Join
+
+## `WHERE` clause
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id ORDER BY t1_id, t1_name,
t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+33 c NULL NULL
+44 d 44 x
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL 99 u
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_name = 'a'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_name = 'z'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+88 NULL NULL NULL
+99 NULL 99 u
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+22 b 22 NULL
+33 c NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_id <= 22
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_id <= 22
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_id IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_id IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+33 c NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+
+## `ON` clause
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_name = 'a'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b NULL NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL NULL NULL
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_name = 'z'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b NULL NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL NULL NULL
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a NULL NULL
+22 b NULL NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL 99 u
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a NULL NULL
+22 b 22 NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL NULL NULL
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a NULL NULL
+22 b NULL NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL 99 u
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a NULL NULL
+22 b 22 NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL NULL NULL
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_id <= 22 ORDER
BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL NULL NULL
+
+query ITIT
+SELECT * FROM t1 LEFT JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_id <= 22 ORDER
BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL NULL NULL
+
+# Right Outer Join
+
+## `WHERE` clause
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id ORDER BY t1_id, t1_name,
t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+44 d 44 x
+99 NULL 99 u
+NULL NULL 55 w
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_name = 'a'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_name = 'z'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+99 NULL 99 u
+NULL NULL 55 w
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_id <= 22
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_id <= 22
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_id IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+NULL NULL 55 w
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_id IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+
+## `ON` clause
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_name = 'a'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+NULL NULL 22 NULL
+NULL NULL 44 x
+NULL NULL 55 w
+NULL NULL 99 u
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_name = 'z'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+NULL NULL 22 NULL
+NULL NULL 44 x
+NULL NULL 55 w
+NULL NULL 99 u
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+99 NULL 99 u
+NULL NULL 11 z
+NULL NULL 22 NULL
+NULL NULL 44 x
+NULL NULL 55 w
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+22 b 22 NULL
+NULL NULL 11 z
+NULL NULL 44 x
+NULL NULL 55 w
+NULL NULL 99 u
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+99 NULL 99 u
+NULL NULL 11 z
+NULL NULL 22 NULL
+NULL NULL 44 x
+NULL NULL 55 w
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+22 b 22 NULL
+NULL NULL 11 z
+NULL NULL 44 x
+NULL NULL 55 w
+NULL NULL 99 u
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_id <= 22 ORDER
BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+NULL NULL 44 x
+NULL NULL 55 w
+NULL NULL 99 u
+
+query ITIT
+SELECT * FROM t1 RIGHT JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_id <= 22 ORDER
BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+NULL NULL 44 x
+NULL NULL 55 w
+NULL NULL 99 u
+
+# Full Outer Join
+
+## `WHERE` clause
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id ORDER BY t1_id, t1_name,
t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+33 c NULL NULL
+44 d 44 x
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL 99 u
+NULL NULL 55 w
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_name = 'a'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_name = 'z'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+88 NULL NULL NULL
+99 NULL 99 u
+NULL NULL 55 w
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+22 b 22 NULL
+33 c NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_id <= 22
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_id <= 22
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id WHERE t1.t1_id IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+NULL NULL 55 w
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id WHERE t2.t2_id IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+33 c NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+
+## `ON` clause
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_name = 'a'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b NULL NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL NULL NULL
+NULL NULL 22 NULL
+NULL NULL 44 x
+NULL NULL 55 w
+NULL NULL 99 u
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_name = 'z'
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b NULL NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL NULL NULL
+NULL NULL 22 NULL
+NULL NULL 44 x
+NULL NULL 55 w
+NULL NULL 99 u
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a NULL NULL
+22 b NULL NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL 99 u
+NULL NULL 11 z
+NULL NULL 22 NULL
+NULL NULL 44 x
+NULL NULL 55 w
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a NULL NULL
+22 b 22 NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL NULL NULL
+NULL NULL 11 z
+NULL NULL 44 x
+NULL NULL 55 w
+NULL NULL 99 u
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a NULL NULL
+22 b NULL NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL 99 u
+NULL NULL 11 z
+NULL NULL 22 NULL
+NULL NULL 44 x
+NULL NULL 55 w
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_name IS NULL
ORDER BY t1_id, t1_name, t2_id, t2_name
+----
+11 a NULL NULL
+22 b 22 NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL NULL NULL
+NULL NULL 11 z
+NULL NULL 44 x
+NULL NULL 55 w
+NULL NULL 99 u
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id AND t1.t1_id <= 22 ORDER
BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL NULL NULL
+NULL NULL 44 x
+NULL NULL 55 w
+NULL NULL 99 u
+
+query ITIT
+SELECT * FROM t1 FULL JOIN t2 ON t1.t1_id = t2.t2_id AND t2.t2_id <= 22 ORDER
BY t1_id, t1_name, t2_id, t2_name
+----
+11 a 11 z
+22 b 22 NULL
+33 c NULL NULL
+44 d NULL NULL
+77 e NULL NULL
+88 NULL NULL NULL
+99 NULL NULL NULL
+NULL NULL 44 x
+NULL NULL 55 w
+NULL NULL 99 u
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]