Add converting step for 'join using'

This commit is contained in:
vdimir 2021-01-31 22:25:47 +03:00
parent 4650dcdbb0
commit 4c36cd1737
No known key found for this signature in database
GPG Key ID: F57B3E10A21DBB31
10 changed files with 509 additions and 30 deletions

2
contrib/hyperscan vendored

@ -1 +1 @@
Subproject commit e9f08df0213fc637aac0a5bbde9beeaeba2fe9fa
Subproject commit 3907fd00ee8b2538739768fa9533f8635a276531

View File

@ -42,14 +42,13 @@
#include <DataTypes/DataTypeFactory.h>
#include <Parsers/parseQuery.h>
#include <Interpreters/interpretSubquery.h>
#include <Interpreters/DatabaseAndTableWithAlias.h>
#include <Interpreters/misc.h>
#include <Interpreters/ActionsVisitor.h>
#include <Interpreters/GlobalSubqueriesVisitor.h>
#include <Interpreters/GetAggregatesVisitor.h>
#include <Interpreters/GlobalSubqueriesVisitor.h>
#include <Interpreters/interpretSubquery.h>
#include <Interpreters/join_common.h>
#include <Interpreters/misc.h>
#include <IO/Operators.h>
#include <IO/WriteBufferFromString.h>
@ -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<ExpressionActions>(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<ExpressionActionsChain::ExpressionActionsStep>(converting_actions.first));
chain.addStep();
}
ExpressionActionsChain::Step & step = chain.lastStep(columns_after_array_join);
chain.steps.push_back(std::make_unique<ExpressionActionsChain::JoinStep>(
syntax->analyzed_join, table_join, step.getResultColumns()));
chain.steps.push_back(std::make_unique<ExpressionActionsChain::JoinStep>(syntax->analyzed_join, table_join, step.getResultColumns()));
chain.addStep();
return table_join;
}
@ -795,7 +803,9 @@ static std::shared_ptr<IJoin> makeJoin(std::shared_ptr<TableJoin> analyzed_join,
return std::make_shared<JoinSwitcher>(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<ExpressionActions>(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();
}

View File

@ -1,15 +1,17 @@
#pragma once
#include <DataStreams/IBlockStream_fwd.h>
#include <Columns/FilterDescription.h>
#include <DataStreams/IBlockStream_fwd.h>
#include <Interpreters/AggregateDescription.h>
#include <Interpreters/WindowDescription.h>
#include <Interpreters/TreeRewriter.h>
#include <Interpreters/DatabaseCatalog.h>
#include <Interpreters/SubqueryForSet.h>
#include <Interpreters/TreeRewriter.h>
#include <Interpreters/join_common.h>
#include <Parsers/IAST_fwd.h>
#include <Storages/IStorage_fwd.h>
#include <Storages/SelectQueryInfo.h>
#include <Interpreters/DatabaseCatalog.h>
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();

View File

@ -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<JoinStep>(
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<LazyNonJoinedBlockInputStream>(*join, join_result_sample, settings.max_block_size);
auto source = std::make_shared<SourceFromInputStream>(std::move(stream));
auto add_non_joined_rows_step = std::make_unique<AddingDelayedSourceStep>(

View File

@ -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<ExpressionActions>(new_dag);
}
}
bool SubqueryForSet::insertJoinedBlock(Block & block)

View File

@ -40,7 +40,7 @@ struct SubqueryForSet
void makeSource(std::shared_ptr<InterpreterSelectWithUnionQuery> & interpreter,
NamesWithAliases && joined_block_aliases_);
void setJoinActions(ExpressionActionsPtr actions);
void addJoinActions(ExpressionActionsPtr actions);
bool insertJoinedBlock(Block & block);
void setTotals(Block totals);

View File

@ -1,9 +1,11 @@
#include <Interpreters/join_common.h>
#include <Interpreters/TableJoin.h>
#include <Interpreters/ActionsDAG.h>
#include <Columns/ColumnNullable.h>
#include <Columns/ColumnLowCardinality.h>
#include <DataTypes/DataTypeNullable.h>
#include <DataTypes/DataTypeLowCardinality.h>
#include <DataTypes/getLeastSupertype.h>
#include <DataStreams/materializeBlock.h>
#include <IO/WriteHelpers.h>
@ -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<std::string_view> visited_left;
std::unordered_set<std::string_view> 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);
}
}

View File

@ -2,6 +2,8 @@
#include <Core/Block.h>
#include <Interpreters/IJoin.h>
#include <Interpreters/ActionsDAG.h>
#include <Interpreters/ExpressionActions.h>
namespace DB
{
@ -14,6 +16,8 @@ using ColumnRawPtrs = std::vector<const IColumn *>;
namespace JoinCommon
{
using JoinConvertActions = std::pair<ActionsDAGPtr, ActionsDAGPtr>;
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.

View File

@ -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

View File

@ -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;