From 4c36cd17373ee373029397d5da8d50c7616689b2 Mon Sep 17 00:00:00 2001 From: vdimir Date: Sun, 31 Jan 2021 22:25:47 +0300 Subject: [PATCH] Add converting step for 'join using' --- contrib/hyperscan | 2 +- src/Interpreters/ExpressionAnalyzer.cpp | 54 ++-- src/Interpreters/ExpressionAnalyzer.h | 13 +- src/Interpreters/InterpreterSelectQuery.cpp | 5 +- src/Interpreters/SubqueryForSet.cpp | 14 +- src/Interpreters/SubqueryForSet.h | 2 +- src/Interpreters/join_common.cpp | 78 ++++++ src/Interpreters/join_common.h | 7 + .../01674_join_implicit_cast.reference | 236 ++++++++++++++++++ .../0_stateless/01674_join_implicit_cast.sql | 128 ++++++++++ 10 files changed, 509 insertions(+), 30 deletions(-) create mode 100644 tests/queries/0_stateless/01674_join_implicit_cast.reference create mode 100644 tests/queries/0_stateless/01674_join_implicit_cast.sql diff --git a/contrib/hyperscan b/contrib/hyperscan index e9f08df0213..3907fd00ee8 160000 --- a/contrib/hyperscan +++ b/contrib/hyperscan @@ -1 +1 @@ -Subproject commit e9f08df0213fc637aac0a5bbde9beeaeba2fe9fa +Subproject commit 3907fd00ee8b2538739768fa9533f8635a276531 diff --git a/src/Interpreters/ExpressionAnalyzer.cpp b/src/Interpreters/ExpressionAnalyzer.cpp index 660718549b3..057be7467f0 100644 --- a/src/Interpreters/ExpressionAnalyzer.cpp +++ b/src/Interpreters/ExpressionAnalyzer.cpp @@ -42,14 +42,13 @@ #include #include -#include -#include -#include #include - -#include #include +#include +#include +#include +#include #include #include @@ -714,23 +713,32 @@ ArrayJoinActionPtr SelectQueryExpressionAnalyzer::appendArrayJoin(ExpressionActi return array_join; } -bool SelectQueryExpressionAnalyzer::appendJoinLeftKeys(ExpressionActionsChain & chain, bool only_types) +bool SelectQueryExpressionAnalyzer::appendJoinLeftKeys(ExpressionActionsChain & chain, bool only_types, Block & block) { ExpressionActionsChain::Step & step = chain.lastStep(columns_after_array_join); getRootActions(analyzedJoin().leftKeysList(), only_types, step.actions()); + ExpressionActionsPtr actions = std::make_shared(step.actions()); + actions->execute(block); return true; } -JoinPtr SelectQueryExpressionAnalyzer::appendJoin(ExpressionActionsChain & chain) +JoinPtr +SelectQueryExpressionAnalyzer::appendJoin(ExpressionActionsChain & chain, const Block & sample_block, ActionsDAGPtr & before_join_dag) { - JoinPtr table_join = makeTableJoin(*syntax->ast_join); + JoinCommon::JoinConvertActions converting_actions; + JoinPtr table_join = makeTableJoin(*syntax->ast_join, sample_block, converting_actions); + + if (converting_actions.first) + { + before_join_dag = ActionsDAG::merge(std::move(*before_join_dag->clone()), std::move(*converting_actions.first->clone())); + + chain.steps.push_back(std::make_unique(converting_actions.first)); + chain.addStep(); + } ExpressionActionsChain::Step & step = chain.lastStep(columns_after_array_join); - - chain.steps.push_back(std::make_unique( - syntax->analyzed_join, table_join, step.getResultColumns())); - + chain.steps.push_back(std::make_unique(syntax->analyzed_join, table_join, step.getResultColumns())); chain.addStep(); return table_join; } @@ -795,7 +803,9 @@ static std::shared_ptr makeJoin(std::shared_ptr analyzed_join, return std::make_shared(analyzed_join, sample_block); } -JoinPtr SelectQueryExpressionAnalyzer::makeTableJoin(const ASTTablesInSelectQueryElement & join_element) +JoinPtr SelectQueryExpressionAnalyzer::makeTableJoin(const ASTTablesInSelectQueryElement & join_element, + const Block & left_sample_block, + JoinCommon::JoinConvertActions & converting_actions) { /// Two JOINs are not supported with the same subquery, but different USINGs. auto join_hash = join_element.getTreeHash(); @@ -831,7 +841,17 @@ JoinPtr SelectQueryExpressionAnalyzer::makeTableJoin(const ASTTablesInSelectQuer } /// TODO You do not need to set this up when JOIN is only needed on remote servers. - subquery_for_join.setJoinActions(joined_block_actions); /// changes subquery_for_join.sample_block inside + subquery_for_join.addJoinActions(joined_block_actions); /// changes subquery_for_join.sample_block inside + + const Block & right_sample_block = subquery_for_join.sample_block; + bool has_using = syntax->analyzed_join->hasUsing(); + converting_actions = JoinCommon::columnsNeedConvert( + left_sample_block, syntax->analyzed_join->keyNamesLeft(), + right_sample_block, syntax->analyzed_join->keyNamesRight(), + has_using); + if (converting_actions.second) + subquery_for_join.addJoinActions(std::make_shared(converting_actions.second)); + subquery_for_join.join = makeJoin(syntax->analyzed_join, subquery_for_join.sample_block, context); /// Do not make subquery for join over dictionary. @@ -1425,10 +1445,10 @@ ExpressionAnalysisResult::ExpressionAnalysisResult( if (query_analyzer.hasTableJoin()) { - query_analyzer.appendJoinLeftKeys(chain, only_types || !first_stage); - + Block left_block_sample = source_header; + query_analyzer.appendJoinLeftKeys(chain, only_types || !first_stage, left_block_sample); before_join = chain.getLastActions(); - join = query_analyzer.appendJoin(chain); + join = query_analyzer.appendJoin(chain, left_block_sample, before_join); chain.addStep(); } diff --git a/src/Interpreters/ExpressionAnalyzer.h b/src/Interpreters/ExpressionAnalyzer.h index 319be9c1409..13561d128d4 100644 --- a/src/Interpreters/ExpressionAnalyzer.h +++ b/src/Interpreters/ExpressionAnalyzer.h @@ -1,15 +1,17 @@ #pragma once -#include #include +#include #include #include #include +#include #include +#include +#include #include #include #include -#include namespace DB { @@ -313,7 +315,8 @@ private: /// Create Set-s that we make from IN section to use index on them. void makeSetsForIndex(const ASTPtr & node); - JoinPtr makeTableJoin(const ASTTablesInSelectQueryElement & join_element); + JoinPtr makeTableJoin(const ASTTablesInSelectQueryElement & join_element, const Block & left_sample_block, + JoinCommon::JoinConvertActions & converting_actions); const ASTSelectQuery * getAggregatingQuery() const; @@ -333,8 +336,8 @@ private: /// Before aggregation: ArrayJoinActionPtr appendArrayJoin(ExpressionActionsChain & chain, ActionsDAGPtr & before_array_join, bool only_types); - bool appendJoinLeftKeys(ExpressionActionsChain & chain, bool only_types); - JoinPtr appendJoin(ExpressionActionsChain & chain); + bool appendJoinLeftKeys(ExpressionActionsChain & chain, bool only_types, Block & block); + JoinPtr appendJoin(ExpressionActionsChain & chain, const Block & sample_block, ActionsDAGPtr & before_join_dag); /// Add preliminary rows filtration. Actions are created in other expression analyzer to prevent any possible alias injection. void appendPreliminaryFilter(ExpressionActionsChain & chain, ActionsDAGPtr actions_dag, String column_name); /// remove_filter is set in ExpressionActionsChain::finalize(); diff --git a/src/Interpreters/InterpreterSelectQuery.cpp b/src/Interpreters/InterpreterSelectQuery.cpp index 9f97160f77f..47addcc8d6c 100644 --- a/src/Interpreters/InterpreterSelectQuery.cpp +++ b/src/Interpreters/InterpreterSelectQuery.cpp @@ -994,12 +994,8 @@ void InterpreterSelectQuery::executeImpl(QueryPlan & query_plan, const BlockInpu if (expressions.hasJoin()) { - Block join_result_sample; JoinPtr join = expressions.join; - join_result_sample = JoiningTransform::transformHeader( - query_plan.getCurrentDataStream().header, expressions.join); - QueryPlanStepPtr join_step = std::make_unique( query_plan.getCurrentDataStream(), expressions.join); @@ -1009,6 +1005,7 @@ void InterpreterSelectQuery::executeImpl(QueryPlan & query_plan, const BlockInpu if (expressions.join_has_delayed_stream) { + const Block & join_result_sample = query_plan.getCurrentDataStream().header; auto stream = std::make_shared(*join, join_result_sample, settings.max_block_size); auto source = std::make_shared(std::move(stream)); auto add_non_joined_rows_step = std::make_unique( diff --git a/src/Interpreters/SubqueryForSet.cpp b/src/Interpreters/SubqueryForSet.cpp index 6ca0ecc50c8..c81b7a710ae 100644 --- a/src/Interpreters/SubqueryForSet.cpp +++ b/src/Interpreters/SubqueryForSet.cpp @@ -39,10 +39,20 @@ void SubqueryForSet::renameColumns(Block & block) } } -void SubqueryForSet::setJoinActions(ExpressionActionsPtr actions) +void SubqueryForSet::addJoinActions(ExpressionActionsPtr actions) { actions->execute(sample_block); - joined_block_actions = actions; + if (joined_block_actions == nullptr) + { + joined_block_actions = actions; + } + else + { + auto new_dag = ActionsDAG::merge( + std::move(*joined_block_actions->getActionsDAG().clone()), + std::move(*actions->getActionsDAG().clone())); + joined_block_actions = std::make_shared(new_dag); + } } bool SubqueryForSet::insertJoinedBlock(Block & block) diff --git a/src/Interpreters/SubqueryForSet.h b/src/Interpreters/SubqueryForSet.h index fd073500dc2..a42bf296d6c 100644 --- a/src/Interpreters/SubqueryForSet.h +++ b/src/Interpreters/SubqueryForSet.h @@ -40,7 +40,7 @@ struct SubqueryForSet void makeSource(std::shared_ptr & interpreter, NamesWithAliases && joined_block_aliases_); - void setJoinActions(ExpressionActionsPtr actions); + void addJoinActions(ExpressionActionsPtr actions); bool insertJoinedBlock(Block & block); void setTotals(Block totals); diff --git a/src/Interpreters/join_common.cpp b/src/Interpreters/join_common.cpp index a4c39a45efa..17ff5666ec2 100644 --- a/src/Interpreters/join_common.cpp +++ b/src/Interpreters/join_common.cpp @@ -1,9 +1,11 @@ #include #include +#include #include #include #include #include +#include #include #include @@ -283,6 +285,82 @@ void addDefaultValues(IColumn & column, const DataTypePtr & type, size_t count) type->insertDefaultInto(column); } +bool typesEqualUpToNullability(DataTypePtr left_type, DataTypePtr right_type) +{ + DataTypePtr left_type_strict = removeNullable(recursiveRemoveLowCardinality(left_type)); + DataTypePtr right_type_strict = removeNullable(recursiveRemoveLowCardinality(right_type)); + return left_type_strict->equals(*right_type_strict); +} + +JoinConvertActions columnsNeedConvert(const Block & left_block, const Names & left_keys, + const Block & right_block, const Names & right_keys, + bool has_using) +{ + assert(left_keys.size() == right_keys.size()); + + /// only JOIN USING supported + if (!has_using) + return {}; + + Block left_block_dst = left_block; + Block right_block_dst = right_block; + + std::unordered_set visited_left; + std::unordered_set visited_right; + bool any_need_cast = false; + for (size_t i = 0; i < left_keys.size(); ++i) + { + if (visited_left.contains(left_keys[i]) || visited_right.contains(right_keys[i])) + { + /// if one column joined with multiple different others do not perform conversion + /// e.g. `JOIN ... ON t1.a == t2.a AND t1.a == t2.b` + return {}; + } + visited_left.insert(left_keys[i]); + visited_right.insert(right_keys[i]); + + DataTypePtr ltype = left_block.getByName(left_keys[i]).type; + DataTypePtr rtype = right_block.getByName(right_keys[i]).type; + + if (typesEqualUpToNullability(ltype, rtype)) + continue; + + any_need_cast = true; + DataTypePtr supertype; + try + { + supertype = DB::getLeastSupertype({ltype, rtype}); + } + catch (DB::Exception &) + { + throw Exception("Type mismatch of columns to JOIN by: " + + left_keys[i] + ": " + ltype->getName() + " at left, " + + right_keys[i] + ": " + rtype->getName() + " at right", + ErrorCodes::TYPE_MISMATCH); + } + auto & lcol_dst = left_block_dst.getByName(left_keys[i]); + auto & rcol_dst = right_block_dst.getByName(right_keys[i]); + lcol_dst.column = rcol_dst.column = nullptr; + lcol_dst.type = rcol_dst.type = supertype; + } + + if (!any_need_cast) + return {}; + + auto convert_left_actions_dag = ActionsDAG::makeConvertingActions( + left_block.getColumnsWithTypeAndName(), + left_block_dst.getColumnsWithTypeAndName(), + ActionsDAG::MatchColumnsMode::Name, + true); + auto convert_right_actions_dag = ActionsDAG::makeConvertingActions( + right_block.getColumnsWithTypeAndName(), + right_block_dst.getColumnsWithTypeAndName(), + ActionsDAG::MatchColumnsMode::Name, + true); + + return std::make_pair(convert_left_actions_dag, convert_right_actions_dag); +} + } diff --git a/src/Interpreters/join_common.h b/src/Interpreters/join_common.h index 6f9f7dd1210..3115b4ae2d2 100644 --- a/src/Interpreters/join_common.h +++ b/src/Interpreters/join_common.h @@ -2,6 +2,8 @@ #include #include +#include +#include namespace DB { @@ -14,6 +16,8 @@ using ColumnRawPtrs = std::vector; namespace JoinCommon { +using JoinConvertActions = std::pair; + void convertColumnToNullable(ColumnWithTypeAndName & column, bool low_card_nullability = false); void convertColumnsToNullable(Block & block, size_t starting_pos = 0); void removeColumnNullability(ColumnWithTypeAndName & column); @@ -36,6 +40,9 @@ void joinTotals(const Block & totals, const Block & columns_to_add, const Names void addDefaultValues(IColumn & column, const DataTypePtr & type, size_t count); +JoinConvertActions columnsNeedConvert(const Block & left_block, const Names & left_keys, + const Block & right_block, const Names & right_keys, + bool has_using); } /// Creates result from right table data in RIGHT and FULL JOIN when keys are not present in left table. diff --git a/tests/queries/0_stateless/01674_join_implicit_cast.reference b/tests/queries/0_stateless/01674_join_implicit_cast.reference new file mode 100644 index 00000000000..644e7a3aa3b --- /dev/null +++ b/tests/queries/0_stateless/01674_join_implicit_cast.reference @@ -0,0 +1,236 @@ +--- hash --- +- full - +-4 0 196 +-3 0 197 +-2 0 198 +-1 0 199 +0 0 200 +1 101 201 +2 102 202 +3 103 203 +4 104 204 +5 105 205 +6 106 \N +7 107 \N +8 108 \N +9 109 \N +10 110 \N +- left - +1 101 201 +2 102 202 +3 103 203 +4 104 204 +5 105 205 +6 106 \N +7 107 \N +8 108 \N +9 109 \N +10 110 \N +- right - +-4 0 196 +-3 0 197 +-2 0 198 +-1 0 199 +0 0 200 +1 101 201 +2 102 202 +3 103 203 +4 104 204 +5 105 205 +- inner - +1 101 201 +2 102 202 +3 103 203 +4 104 204 +5 105 205 +- full - +0 0 -4 +0 0 -3 +0 0 -2 +0 0 -1 +0 0 0 +1 1 1 +2 2 2 +3 3 3 +4 4 4 +5 5 5 +6 6 0 +7 7 0 +8 8 0 +9 9 0 +10 10 0 +- left - +1 1 1 +2 2 2 +3 3 3 +4 4 4 +5 5 5 +6 6 0 +7 7 0 +8 8 0 +9 9 0 +10 10 0 +- right - +0 0 -4 +0 0 -3 +0 0 -2 +0 0 -1 +0 0 0 +1 1 1 +2 2 2 +3 3 3 +4 4 4 +5 5 5 +- inner - +1 1 1 +2 2 2 +3 3 3 +4 4 4 +5 5 5 +- types - +1 +1 +1 +1 +--- partial_merge --- +- full - +-4 0 196 +-3 0 197 +-2 0 198 +-1 0 199 +0 0 200 +1 101 201 +2 102 202 +3 103 203 +4 104 204 +5 105 205 +6 106 \N +7 107 \N +8 108 \N +9 109 \N +10 110 \N +- left - +1 101 201 +2 102 202 +3 103 203 +4 104 204 +5 105 205 +6 106 \N +7 107 \N +8 108 \N +9 109 \N +10 110 \N +- right - +-4 0 196 +-3 0 197 +-2 0 198 +-1 0 199 +0 0 200 +1 101 201 +2 102 202 +3 103 203 +4 104 204 +5 105 205 +- inner - +1 101 201 +2 102 202 +3 103 203 +4 104 204 +5 105 205 +- full - +0 0 -4 +0 0 -3 +0 0 -2 +0 0 -1 +0 0 0 +1 1 1 +2 2 2 +3 3 3 +4 4 4 +5 5 5 +6 6 0 +7 7 0 +8 8 0 +9 9 0 +10 10 0 +- left - +1 1 1 +2 2 2 +3 3 3 +4 4 4 +5 5 5 +6 6 0 +7 7 0 +8 8 0 +9 9 0 +10 10 0 +- right - +0 0 -4 +0 0 -3 +0 0 -2 +0 0 -1 +0 0 0 +1 1 1 +2 2 2 +3 3 3 +4 4 4 +5 5 5 +- inner - +1 1 1 +2 2 2 +3 3 3 +4 4 4 +5 5 5 +- types - +1 +1 +1 +1 +--- hash --- +- full - +1 1 +2 2 +-1 1 +1 \N +1 257 +1 -1 +- left - +1 1 +2 2 +- right - +1 1 +-1 1 +1 \N +1 257 +1 -1 +- inner - +1 1 +- types - +1 +1 +1 +1 +--- partial_merge --- +- full - +1 1 +2 2 +-1 1 +1 \N +1 257 +1 -1 +- left - +1 1 +2 2 +- right - +1 1 +-1 1 +1 \N +1 257 +1 -1 +- inner - +1 1 +- types - +1 +1 +1 +1 diff --git a/tests/queries/0_stateless/01674_join_implicit_cast.sql b/tests/queries/0_stateless/01674_join_implicit_cast.sql new file mode 100644 index 00000000000..45bffbf1808 --- /dev/null +++ b/tests/queries/0_stateless/01674_join_implicit_cast.sql @@ -0,0 +1,128 @@ +CREATE DATABASE IF NOT EXISTS test_01655; +USE test_01655; + +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; + +CREATE TABLE t1 (a UInt16, b UInt16) ENGINE = TinyLog; +CREATE TABLE t2 (a Int16, b Nullable(Int64)) ENGINE = TinyLog; + +INSERT INTO t1 SELECT number as a, 100 + number as b FROM system.numbers LIMIT 1, 10; +INSERT INTO t2 SELECT number - 5 as a, 200 + number - 5 as b FROM system.numbers LIMIT 1, 10; + +SELECT '--- hash ---'; +SET join_algorithm = 'hash'; + +SELECT '- full -'; +SELECT a, b, t2.b FROM t1 FULL JOIN t2 USING (a) ORDER BY (a); +SELECT '- left -'; +SELECT a, b, t2.b FROM t1 LEFT JOIN t2 USING (a) ORDER BY (a); +SELECT '- right -'; +SELECT a, b, t2.b FROM t1 RIGHT JOIN t2 USING (a) ORDER BY (a); +SELECT '- inner -'; +SELECT a, b, t2.b FROM t1 INNER JOIN t2 USING (a) ORDER BY (a); + +SELECT '- full -'; +SELECT a, t1.a, t2.a FROM t1 FULL JOIN t2 USING (a) ORDER BY (t1.a, t2.a); +SELECT '- left -'; +SELECT a, t1.a, t2.a FROM t1 LEFT JOIN t2 USING (a) ORDER BY (t1.a, t2.a); +SELECT '- right -'; +SELECT a, t1.a, t2.a FROM t1 RIGHT JOIN t2 USING (a) ORDER BY (t1.a, t2.a); +SELECT '- inner -'; +SELECT a, t1.a, t2.a FROM t1 INNER JOIN t2 USING (a) ORDER BY (t1.a, t2.a); + +SELECT '- types -'; +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(t2.a)) == 'Int32' FROM t1 FULL JOIN t2 USING (a); +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(t2.a)) == 'Int32' FROM t1 LEFT JOIN t2 USING (a); +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(t2.a)) == 'Int32' FROM t1 RIGHT JOIN t2 USING (a); +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(t2.a)) == 'Int32' FROM t1 INNER JOIN t2 USING (a); + +SELECT * FROM t1 FULL JOIN t2 ON (t1.a == t2.a) ORDER BY (a); -- { serverError 53 } +SELECT * FROM t1 LEFT JOIN t2 ON(t1.a == t2.a) ORDER BY (a); -- { serverError 53 } +SELECT * FROM t1 RIGHT JOIN t2 ON (t1.a == t2.a) ORDER BY (a); -- { serverError 53 } +SELECT * FROM t1 INNER JOIN t2 ON (t1.a == t2.a) ORDER BY (a); -- { serverError 53 } + +SELECT '--- partial_merge ---'; + +SET join_algorithm = 'partial_merge'; + +SELECT '- full -'; +SELECT a, b, t2.b FROM t1 FULL JOIN t2 USING (a) ORDER BY (a); +SELECT '- left -'; +SELECT a, b, t2.b FROM t1 LEFT JOIN t2 USING (a) ORDER BY (a); +SELECT '- right -'; +SELECT a, b, t2.b FROM t1 RIGHT JOIN t2 USING (a) ORDER BY (a); +SELECT '- inner -'; +SELECT a, b, t2.b FROM t1 INNER JOIN t2 USING (a) ORDER BY (a); + + +SELECT '- full -'; +SELECT a, t1.a, t2.a FROM t1 FULL JOIN t2 USING (a) ORDER BY (t1.a, t2.a); +SELECT '- left -'; +SELECT a, t1.a, t2.a FROM t1 LEFT JOIN t2 USING (a) ORDER BY (t1.a, t2.a); +SELECT '- right -'; +SELECT a, t1.a, t2.a FROM t1 RIGHT JOIN t2 USING (a) ORDER BY (t1.a, t2.a); +SELECT '- inner -'; +SELECT a, t1.a, t2.a FROM t1 INNER JOIN t2 USING (a) ORDER BY (t1.a, t2.a); + +SELECT '- types -'; +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(t2.a)) == 'Int32' FROM t1 FULL JOIN t2 USING (a); +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(t2.a)) == 'Int32' FROM t1 LEFT JOIN t2 USING (a); +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(t2.a)) == 'Int32' FROM t1 RIGHT JOIN t2 USING (a); +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(t2.a)) == 'Int32' FROM t1 INNER JOIN t2 USING (a); + +SELECT * FROM t1 FULL JOIN t2 ON (t1.a == t2.a) ORDER BY (a); -- { serverError 53 } +SELECT * FROM t1 LEFT JOIN t2 ON(t1.a == t2.a) ORDER BY (a); -- { serverError 53 } +SELECT * FROM t1 RIGHT JOIN t2 ON (t1.a == t2.a) ORDER BY (a); -- { serverError 53 } +SELECT * FROM t1 INNER JOIN t2 ON (t1.a == t2.a) ORDER BY (a); -- { serverError 53 } + +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; + +CREATE TABLE t1 (id Nullable(Int32), a UInt16, b UInt8) ENGINE = TinyLog; +CREATE TABLE t2 (id Nullable(Int32), a Int16, b Nullable(Int64)) ENGINE = TinyLog; +INSERT INTO t1 VALUES (0, 1, 1), (1, 2, 2); +INSERT INTO t2 VALUES (2, -1, 1), (3, 1, NULL), (4, 1, 257), (5, 1, -1), (6, 1, 1); + +SELECT '--- hash ---'; + +SELECT '- full -'; +SELECT a, b FROM t1 FULL JOIN t2 USING (a, b) ORDER BY ifNull(t1.id, t2.id); +SELECT '- left -'; +SELECT a, b FROM t1 LEFT JOIN t2 USING (a, b) ORDER BY ifNull(t1.id, t2.id); +SELECT '- right -'; +SELECT a, b FROM t1 RIGHT JOIN t2 USING (a, b) ORDER BY ifNull(t1.id, t2.id); +SELECT '- inner -'; +SELECT a, b FROM t1 INNER JOIN t2 USING (a, b) ORDER BY ifNull(t1.id, t2.id); + +SELECT '- types -'; + +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(b)) == 'Nullable(Int64)' FROM t1 FULL JOIN t2 USING (a, b); +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(b)) == 'Nullable(Int64)' FROM t1 LEFT JOIN t2 USING (a, b); +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(b)) == 'Nullable(Int64)' FROM t1 RIGHT JOIN t2 USING (a, b); +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(b)) == 'Nullable(Int64)' FROM t1 INNER JOIN t2 USING (a, b); + +SELECT '--- partial_merge ---'; + +SET join_algorithm = 'partial_merge'; + +SELECT '- full -'; +SELECT a, b FROM t1 FULL JOIN t2 USING (a, b) ORDER BY ifNull(t1.id, t2.id); +SELECT '- left -'; +SELECT a, b FROM t1 LEFT JOIN t2 USING (a, b) ORDER BY ifNull(t1.id, t2.id); +SELECT '- right -'; +SELECT a, b FROM t1 RIGHT JOIN t2 USING (a, b) ORDER BY ifNull(t1.id, t2.id); +SELECT '- inner -'; +SELECT a, b FROM t1 INNER JOIN t2 USING (a, b) ORDER BY ifNull(t1.id, t2.id); + +SELECT '- types -'; + +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(b)) == 'Nullable(Int64)' FROM t1 FULL JOIN t2 USING (a, b); +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(b)) == 'Nullable(Int64)' FROM t1 LEFT JOIN t2 USING (a, b); +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(b)) == 'Nullable(Int64)' FROM t1 RIGHT JOIN t2 USING (a, b); +SELECT any(toTypeName(a)) == 'Int32' AND any(toTypeName(b)) == 'Nullable(Int64)' FROM t1 INNER JOIN t2 USING (a, b); + +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; + +DROP DATABASE IF EXISTS test_01655;