Support join on constant

This commit is contained in:
vdimir 2021-11-08 15:44:13 +03:00
parent 1e85ea50fa
commit 56bc802ee2
No known key found for this signature in database
GPG Key ID: 9B404D301C0CC7EB
10 changed files with 383 additions and 50 deletions

View File

@ -27,6 +27,7 @@
#include <Core/ColumnNumbers.h>
#include <Common/typeid_cast.h>
#include <Common/assert_cast.h>
namespace DB
{
@ -289,13 +290,11 @@ HashJoin::HashJoin(std::shared_ptr<TableJoin> table_join_, const Block & right_s
if (table_join->getDictionaryReader())
{
assert(disjuncts_num == 1);
LOG_DEBUG(log, "Performing join over dict");
data->type = Type::DICT;
data->maps.resize(disjuncts_num);
std::get<MapsOne>(data->maps[0]).create(Type::DICT);
key_sizes.resize(1);
chooseMethod(key_columns, key_sizes[0]); /// init key_sizes
chooseMethod(kind, key_columns, key_sizes.emplace_back()); /// init key_sizes
}
else if (strictness == ASTTableJoin::Strictness::Asof)
{
@ -321,13 +320,13 @@ HashJoin::HashJoin(std::shared_ptr<TableJoin> table_join_, const Block & right_s
/// Therefore, add it back in such that it can be extracted appropriately from the full stored
/// key_columns and key_sizes
auto & asof_key_sizes = key_sizes.emplace_back();
data->type = chooseMethod(key_columns, asof_key_sizes);
data->type = chooseMethod(kind, key_columns, asof_key_sizes);
asof_key_sizes.push_back(asof_size);
}
else
{
/// Choose data structure to use for JOIN.
auto current_join_method = chooseMethod(key_columns, key_sizes.emplace_back());
auto current_join_method = chooseMethod(kind, key_columns, key_sizes.emplace_back());
if (data->type == Type::EMPTY)
data->type = current_join_method;
else if (data->type != current_join_method)
@ -337,14 +336,20 @@ HashJoin::HashJoin(std::shared_ptr<TableJoin> table_join_, const Block & right_s
for (auto & maps : data->maps)
dataMapInit(maps);
LOG_DEBUG(log, "Join type: {}, kind: {}, strictness: {}", data->type, kind, strictness);
}
HashJoin::Type HashJoin::chooseMethod(const ColumnRawPtrs & key_columns, Sizes & key_sizes)
HashJoin::Type HashJoin::chooseMethod(ASTTableJoin::Kind kind, const ColumnRawPtrs & key_columns, Sizes & key_sizes)
{
size_t keys_size = key_columns.size();
if (keys_size == 0)
return Type::CROSS;
{
if (isCrossOrComma(kind))
return Type::CROSS;
return Type::EMPTY;
}
bool all_fixed = true;
size_t keys_bytes = 0;
@ -446,6 +451,23 @@ private:
std::vector<size_t> positions;
};
/// Dummy key getter, always find nothing, used for JOIN ON NULL
template <typename Mapped>
class KeyGetterEmpty
{
public:
struct MappedType
{
using mapped_type = Mapped;
};
using FindResult = ColumnsHashing::columns_hashing_impl::FindResultImpl<Mapped, true>;
KeyGetterEmpty() = default;
FindResult findKey(MappedType, size_t, const Arena &) { return FindResult(); }
};
template <HashJoin::Type type, typename Value, typename Mapped>
struct KeyGetterForTypeImpl;
@ -723,8 +745,6 @@ Block HashJoin::structureRightBlock(const Block & block) const
bool HashJoin::addJoinedBlock(const Block & source_block, bool check_limits)
{
if (empty())
throw Exception("Logical error: HashJoin was not initialized", ErrorCodes::LOGICAL_ERROR);
if (overDictionary())
throw Exception("Logical error: insert into hash-map in HashJoin over dictionary", ErrorCodes::LOGICAL_ERROR);
@ -1373,12 +1393,28 @@ IColumn::Filter switchJoinRightColumns(
constexpr bool is_asof_join = STRICTNESS == ASTTableJoin::Strictness::Asof;
switch (type)
{
case HashJoin::Type::EMPTY:
{
if constexpr (!is_asof_join)
{
using KeyGetter = KeyGetterEmpty<typename Maps::MappedType>;
std::vector<KeyGetter> key_getter_vector;
key_getter_vector.emplace_back();
using MapTypeVal = typename KeyGetter::MappedType;
std::vector<const MapTypeVal *> a_map_type_vector;
a_map_type_vector.emplace_back();
return joinRightColumnsSwitchNullability<KIND, STRICTNESS, KeyGetter>(
std::move(key_getter_vector), a_map_type_vector, added_columns, used_flags);
}
throw Exception(ErrorCodes::UNSUPPORTED_JOIN_KEYS, "Unsupported JOIN keys. Type: {}", type);
}
#define M(TYPE) \
case HashJoin::Type::TYPE: \
{ \
using MapTypeVal = const typename std::remove_reference_t<decltype(Maps::TYPE)>::element_type; \
using KeyGetter = typename KeyGetterForType<HashJoin::Type::TYPE, MapTypeVal>::Type; \
std::vector<const MapTypeVal*> a_map_type_vector(mapv.size()); \
std::vector<const MapTypeVal *> a_map_type_vector(mapv.size()); \
std::vector<KeyGetter> key_getter_vector; \
for (size_t d = 0; d < added_columns.join_on_keys.size(); ++d) \
{ \
@ -1393,7 +1429,7 @@ IColumn::Filter switchJoinRightColumns(
#undef M
default:
throw Exception("Unsupported JOIN keys. Type: " + toString(static_cast<UInt32>(type)), ErrorCodes::UNSUPPORTED_JOIN_KEYS);
throw Exception(ErrorCodes::UNSUPPORTED_JOIN_KEYS, "Unsupported JOIN keys (type: {})", type);
}
}
@ -1828,7 +1864,7 @@ class NotJoinedHash final : public NotJoinedBlocks::RightColumnsFiller
{
public:
NotJoinedHash(const HashJoin & parent_, UInt64 max_block_size_)
: parent(parent_), max_block_size(max_block_size_)
: parent(parent_), max_block_size(max_block_size_), current_block_start(0)
{}
Block getEmptyBlock() override { return parent.savedBlockSample().cloneEmpty(); }
@ -1836,13 +1872,20 @@ public:
size_t fillColumns(MutableColumns & columns_right) override
{
size_t rows_added = 0;
auto fill_callback = [&](auto, auto strictness, auto & map)
if (unlikely(parent.data->type == HashJoin::Type::EMPTY))
{
rows_added = fillColumnsFromMap<strictness>(map, columns_right);
};
rows_added = fillColumnsFromData(parent.data->blocks, columns_right);
}
else
{
auto fill_callback = [&](auto, auto strictness, auto & map)
{
rows_added = fillColumnsFromMap<strictness>(map, columns_right);
};
if (!joinDispatch(parent.kind, parent.strictness, parent.data->maps.front(), fill_callback))
throw Exception(ErrorCodes::LOGICAL_ERROR, "Unknown JOIN strictness '{}' (must be on of: ANY, ALL, ASOF)", parent.strictness);
if (!joinDispatch(parent.kind, parent.strictness, parent.data->maps.front(), fill_callback))
throw Exception(ErrorCodes::LOGICAL_ERROR, "Unknown JOIN strictness '{}' (must be on of: ANY, ALL, ASOF)", parent.strictness);
}
if constexpr (!multiple_disjuncts)
{
@ -1856,10 +1899,48 @@ private:
const HashJoin & parent;
UInt64 max_block_size;
size_t current_block_start;
std::any position;
std::optional<HashJoin::BlockNullmapList::const_iterator> nulls_position;
std::optional<BlocksList::const_iterator> used_position;
size_t fillColumnsFromData(const BlocksList & blocks, MutableColumns & columns_right)
{
if (!position.has_value())
position = std::make_any<BlocksList::const_iterator>(blocks.begin());
auto & block_it = std::any_cast<BlocksList::const_iterator &>(position);
auto end = blocks.end();
size_t rows_added = 0;
for (; block_it != end; ++block_it)
{
size_t rows_from_block = std::min<size_t>(max_block_size - rows_added, block_it->rows() - current_block_start);
for (size_t j = 0; j < columns_right.size(); ++j)
{
const auto & col = block_it->getByPosition(j).column;
columns_right[j]->insertRangeFrom(*col, current_block_start, rows_from_block);
}
rows_added += rows_from_block;
if (rows_added >= max_block_size)
{
/// How many rows have been read
current_block_start += rows_from_block;
if (block_it->rows() <= current_block_start)
{
/// current block was fully read
++block_it;
current_block_start = 0;
}
break;
}
current_block_start = 0;
}
return rows_added;
}
template <ASTTableJoin::Strictness STRICTNESS, typename Maps>
size_t fillColumnsFromMap(const Maps & maps, MutableColumns & columns_keys_and_right)
{
@ -1871,8 +1952,7 @@ private:
APPLY_FOR_JOIN_VARIANTS(M)
#undef M
default:
throw Exception("Unsupported JOIN keys. Type: " + toString(static_cast<UInt32>(parent.data->type)),
ErrorCodes::UNSUPPORTED_JOIN_KEYS);
throw Exception(ErrorCodes::UNSUPPORTED_JOIN_KEYS, "Unsupported JOIN keys (type: {})", parent.data->type) ;
}
__builtin_unreachable();

View File

@ -231,6 +231,7 @@ public:
template <typename Mapped>
struct MapsTemplate
{
using MappedType = Mapped;
std::unique_ptr<FixedHashMap<UInt8, Mapped>> key8;
std::unique_ptr<FixedHashMap<UInt16, Mapped>> key16;
std::unique_ptr<HashMap<UInt32, Mapped, HashCRC32<UInt32>>> key32;
@ -411,7 +412,7 @@ private:
void joinBlockImplCross(Block & block, ExtraBlockPtr & not_processed) const;
static Type chooseMethod(const ColumnRawPtrs & key_columns, Sizes & key_sizes);
static Type chooseMethod(ASTTableJoin::Kind kind, const ColumnRawPtrs & key_columns, Sizes & key_sizes);
bool empty() const;
bool overDictionary() const;

View File

@ -108,6 +108,16 @@ TableJoin::TableJoin(const Settings & settings, VolumePtr tmp_volume_)
{
}
void TableJoin::resetKeys()
{
clauses.clear();
key_asts_left.clear();
key_asts_right.clear();
left_type_map.clear();
right_type_map.clear();
}
void TableJoin::resetCollected()
{
clauses.clear();
@ -224,6 +234,13 @@ Names TableJoin::requiredJoinedNames() const
for (const auto & joined_column : columns_added_by_join)
required_columns_set.insert(joined_column.name);
/*
* In case of `SELECT count() FROM ... JOIN .. ON NULL` required columns set for right table is empty.
* But we have to get at least one column from right table to know the number of rows.
*/
if (required_columns_set.empty() && !columns_from_joined_table.empty())
return {columns_from_joined_table.begin()->name};
return Names(required_columns_set.begin(), required_columns_set.end());
}
@ -352,9 +369,7 @@ bool TableJoin::sameStrictnessAndKind(ASTTableJoin::Strictness strictness_, ASTT
bool TableJoin::oneDisjunct() const
{
if (!isCrossOrComma(kind()))
assert(!clauses.empty());
return clauses.size() <= 1;
return clauses.size() == 1;
}
bool TableJoin::allowMergeJoin() const
@ -650,4 +665,10 @@ void TableJoin::assertHasOneOnExpr() const
}
}
void TableJoin::resetToCross()
{
this->resetKeys();
this->table_join.kind = ASTTableJoin::Kind::Cross;
}
}

View File

@ -48,7 +48,6 @@ enum class JoinTableSide
class TableJoin
{
public:
using NameToTypeMap = std::unordered_map<String, DataTypePtr>;
@ -285,6 +284,10 @@ public:
Block getRequiredRightKeys(const Block & right_table_keys, std::vector<String> & keys_sources) const;
String renamedRightColumnName(const String & name) const;
void resetKeys();
void resetToCross();
std::unordered_map<String, String> leftToRightKeyRemap() const;
void setStorageJoin(std::shared_ptr<StorageJoin> storage);

View File

@ -1,3 +1,4 @@
#include <algorithm>
#include <Core/Settings.h>
#include <Core/NamesAndTypes.h>
@ -22,6 +23,7 @@
#include <Interpreters/getTableExpressions.h>
#include <Interpreters/TreeOptimizer.h>
#include <Interpreters/replaceAliasColumnsInQuery.h>
#include <Interpreters/evaluateConstantExpression.h>
#include <Interpreters/PredicateExpressionsOptimizer.h>
#include <Parsers/ASTExpressionList.h>
@ -33,6 +35,8 @@
#include <DataTypes/NestedUtils.h>
#include <DataTypes/DataTypeNullable.h>
#include <DataTypes/DataTypeLowCardinality.h>
#include <DataTypes/DataTypesNumber.h>
#include <IO/WriteHelpers.h>
#include <Storages/IStorage.h>
@ -564,9 +568,68 @@ void setJoinStrictness(ASTSelectQuery & select_query, JoinStrictness join_defaul
out_table_join = table_join;
}
/// Evaluate expression and return boolean value if it can be interpreted as bool.
/// Only UInt8 or NULL are allowed.
/// Returns `false` for 0 or NULL values, `true` for any non-negative value.
std::optional<bool> tryEvaluateConstCondition(ASTPtr expr, ContextPtr context)
{
if (!expr)
return {};
Field eval_res;
DataTypePtr eval_res_type;
try
{
std::tie(eval_res, eval_res_type) = evaluateConstantExpression(expr, context);
}
catch (DB::Exception &)
{
/// not a constant expression
return {};
}
/// UInt8, maybe Nullable, maybe LowCardinality, and NULL are allowed
eval_res_type = removeNullable(removeLowCardinality(eval_res_type));
if (auto which = WhichDataType(eval_res_type); !which.isUInt8() && !which.isNothing())
return {};
if (eval_res.isNull())
return false;
UInt8 res = eval_res.template safeGet<UInt8>();
return res > 0;
}
bool tryJoinOnConst(TableJoin & analyzed_join, ASTPtr & on_expression, ContextPtr context)
{
bool join_on_value;
if (auto eval_const_res = tryEvaluateConstCondition(on_expression, context))
join_on_value = *eval_const_res;
else
return false;
if (!analyzed_join.forceHashJoin())
throw Exception(ErrorCodes::NOT_IMPLEMENTED,
"JOIN ON constant ({}) supported only with join algorithm 'hash'",
queryToString(on_expression));
on_expression = nullptr;
if (join_on_value)
{
LOG_DEBUG(&Poco::Logger::get("TreeRewriter"), "Join on constant executed as cross join");
analyzed_join.resetToCross();
}
else
{
LOG_DEBUG(&Poco::Logger::get("TreeRewriter"), "Join on constant executed as empty join");
analyzed_join.resetKeys();
}
return true;
}
/// Find the columns that are obtained by JOIN.
void collectJoinedColumns(TableJoin & analyzed_join, const ASTTableJoin & table_join,
const TablesWithColumns & tables, const Aliases & aliases)
void collectJoinedColumns(TableJoin & analyzed_join, ASTTableJoin & table_join,
const TablesWithColumns & tables, const Aliases & aliases, ContextPtr context)
{
assert(tables.size() >= 2);
@ -599,29 +662,41 @@ void collectJoinedColumns(TableJoin & analyzed_join, const ASTTableJoin & table_
assert(analyzed_join.oneDisjunct());
}
if (analyzed_join.getClauses().empty())
auto check_keys_empty = [] (auto e) { return e.key_names_left.empty(); };
/// All clauses should to have keys or be empty simultaneously
bool all_keys_empty = std::all_of(analyzed_join.getClauses().begin(), analyzed_join.getClauses().end(), check_keys_empty);
if (all_keys_empty)
{
/// Try join on constant (cross or empty join) or fail
if (is_asof)
throw Exception(ErrorCodes::INVALID_JOIN_ON_EXPRESSION,
"Cannot get JOIN keys from JOIN ON section: {}", queryToString(table_join.on_expression));
bool join_on_const_ok = tryJoinOnConst(analyzed_join, table_join.on_expression, context);
if (!join_on_const_ok)
throw Exception(ErrorCodes::INVALID_JOIN_ON_EXPRESSION,
"Cannot get JOIN keys from JOIN ON section: {}", queryToString(table_join.on_expression));
}
else
{
bool any_keys_empty = std::any_of(analyzed_join.getClauses().begin(), analyzed_join.getClauses().end(), check_keys_empty);
if (any_keys_empty)
throw DB::Exception(ErrorCodes::INVALID_JOIN_ON_EXPRESSION,
"Cannot get JOIN keys from JOIN ON section: '{}'",
queryToString(table_join.on_expression));
for (const auto & onexpr : analyzed_join.getClauses())
{
if (onexpr.key_names_left.empty())
throw DB::Exception(ErrorCodes::INVALID_JOIN_ON_EXPRESSION,
"Cannot get JOIN keys from JOIN ON section: '{}'",
queryToString(table_join.on_expression));
if (is_asof)
{
if (!analyzed_join.oneDisjunct())
throw DB::Exception(ErrorCodes::NOT_IMPLEMENTED, "ASOF join doesn't support multiple ORs for keys in JOIN ON section");
data.asofToJoinKeys();
}
if (!analyzed_join.oneDisjunct() && !analyzed_join.forceHashJoin())
throw DB::Exception(ErrorCodes::NOT_IMPLEMENTED, "Only `hash` join supports multiple ORs for keys in JOIN ON section");
}
if (is_asof)
{
if (!analyzed_join.oneDisjunct())
throw DB::Exception(ErrorCodes::NOT_IMPLEMENTED, "ASOF join doesn't support multiple ORs for keys in JOIN ON section");
data.asofToJoinKeys();
}
if (!analyzed_join.oneDisjunct() && !analyzed_join.forceHashJoin())
throw DB::Exception(ErrorCodes::NOT_IMPLEMENTED, "Only `hash` join supports multiple ORs for keys in JOIN ON section");
}
}
@ -1052,7 +1127,7 @@ TreeRewriterResultPtr TreeRewriter::analyzeSelect(
auto * table_join_ast = select_query->join() ? select_query->join()->table_join->as<ASTTableJoin>() : nullptr;
if (table_join_ast && tables_with_columns.size() >= 2)
collectJoinedColumns(*result.analyzed_join, *table_join_ast, tables_with_columns, result.aliases);
collectJoinedColumns(*result.analyzed_join, *table_join_ast, tables_with_columns, result.aliases, getContext());
result.aggregates = getAggregates(query, *select_query);
result.window_function_asts = getWindowFunctions(query, *select_query);

View File

@ -580,11 +580,10 @@ NotJoinedBlocks::NotJoinedBlocks(std::unique_ptr<RightColumnsFiller> filler_,
}
if (column_indices_left.size() + column_indices_right.size() + same_result_keys.size() != result_sample_block.columns())
throw Exception("Error in columns mapping in RIGHT|FULL JOIN. Left: " + toString(column_indices_left.size()) +
", right: " + toString(column_indices_right.size()) +
", same: " + toString(same_result_keys.size()) +
", result: " + toString(result_sample_block.columns()),
ErrorCodes::LOGICAL_ERROR);
throw Exception(ErrorCodes::LOGICAL_ERROR,
"Error in columns mapping in RIGHT|FULL JOIN. Left: {}, right: {}, same: {}, result: {}",
column_indices_left.size(), column_indices_right.size(),
same_result_keys.size(), result_sample_block.columns());
}
void NotJoinedBlocks::setRightIndex(size_t right_pos, size_t result_position)

View File

@ -0,0 +1,31 @@
1
1
1
1
1
1
- ON NULL -
- inner -
- left -
1 0
2 0
- right -
0 2
0 3
- full -
0 2
0 3
1 0
2 0
- inner -
- left -
1 \N
2 \N
- right -
\N 2
\N 3
- full -
\N 2
\N 3
1 \N
2 \N

View File

@ -0,0 +1,55 @@
DROP TABLE IF EXISTS t1;
DROP TABLE IF EXISTS t2;
CREATE TABLE t1 (id Int) ENGINE = Memory;
CREATE TABLE t2 (id Int) ENGINE = Memory;
INSERT INTO t1 VALUES (1), (2);
INSERT INTO t2 VALUES (2), (3);
SELECT 70 = 10 * sum(t1.id) + sum(t2.id) AND count() == 4 FROM t1 JOIN t2 ON 1 = 1;
SELECT 70 = 10 * sum(t1.id) + sum(t2.id) AND count() == 4 FROM t1 JOIN t2 ON 1;
SELECT 70 = 10 * sum(t1.id) + sum(t2.id) AND count() == 4 FROM t1 JOIN t2 ON 2 = 2 AND 3 = 3;
SELECT 70 = 10 * sum(t1.id) + sum(t2.id) AND count() == 4 FROM t1 INNER ANY JOIN t2 ON toNullable(1);
SELECT 70 = 10 * sum(t1.id) + sum(t2.id) AND count() == 4 FROM t1 INNER ANY JOIN t2 ON toLowCardinality(1);
SELECT 70 = 10 * sum(t1.id) + sum(t2.id) AND count() == 4 FROM t1 INNER ANY JOIN t2 ON toLowCardinality(toNullable(1));
SELECT * FROM t1 INNER ANY JOIN t2 ON toNullable(toLowCardinality(1)); -- { serverError 403 }
SELECT * FROM t1 INNER ANY JOIN t2 ON toUInt16(1); -- { serverError 403 }
SELECT * FROM t1 INNER ANY JOIN t2 ON toInt8(1); -- { serverError 403 }
SELECT * FROM t1 INNER ANY JOIN t2 ON 256; -- { serverError 403 }
SELECT * FROM t1 INNER ANY JOIN t2 ON -1; -- { serverError 403 }
SELECT * FROM t1 INNER ANY JOIN t2 ON toString(1); -- { serverError 403 }
SELECT '- ON NULL -';
SELECT '- inner -';
SELECT * FROM t1 INNER ANY JOIN t2 ON NULL;
SELECT * FROM t1 INNER ANY JOIN t2 ON 0;
SELECT * FROM t1 INNER ANY JOIN t2 ON 1 = 2;
SELECT '- left -';
SELECT * FROM t1 LEFT JOIN t2 ON NULL ORDER BY t1.id, t2.id;
SELECT '- right -';
SELECT * FROM t1 RIGHT JOIN t2 ON NULL ORDER BY t1.id, t2.id;
SELECT '- full -';
SELECT * FROM t1 FULL JOIN t2 ON NULL ORDER BY t1.id, t2.id;
SELECT '- inner -';
SELECT * FROM t1 INNER ANY JOIN t2 ON NULL ORDER BY t1.id NULLS FIRST, t2.id SETTINGS join_use_nulls = 1;
SELECT '- left -';
SELECT * FROM t1 LEFT JOIN t2 ON NULL ORDER BY t1.id NULLS FIRST, t2.id SETTINGS join_use_nulls = 1;
SELECT '- right -';
SELECT * FROM t1 RIGHT JOIN t2 ON NULL ORDER BY t1.id NULLS FIRST, t2.id SETTINGS join_use_nulls = 1;
SELECT '- full -';
SELECT * FROM t1 FULL JOIN t2 ON NULL ORDER BY t1.id NULLS FIRST, t2.id SETTINGS join_use_nulls = 1;
SELECT * FROM t1 JOIN t2 ON 1 = 1 SETTINGS join_algorithm = 'partial_merge'; -- { serverError 48 }
SELECT * FROM t1 JOIN t2 ON 1 = 1 SETTINGS join_algorithm = 'auto'; -- { serverError 48 }
SELECT * FROM t1 JOIN t2 ON NULL SETTINGS join_algorithm = 'partial_merge'; -- { serverError 48 }
SELECT * FROM t1 LEFT JOIN t2 ON NULL SETTINGS join_algorithm = 'partial_merge'; -- { serverError 48 }
SELECT * FROM t1 RIGHT JOIN t2 ON NULL SETTINGS join_algorithm = 'auto'; -- { serverError 48 }
SELECT * FROM t1 FULL JOIN t2 ON NULL SETTINGS join_algorithm = 'partial_merge'; -- { serverError 48 }
DROP TABLE IF EXISTS t1;
DROP TABLE IF EXISTS t2;

View File

@ -0,0 +1,41 @@
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1

View File

@ -0,0 +1,27 @@
DROP TABLE IF EXISTS t1;
DROP TABLE IF EXISTS t2;
CREATE TABLE t1 (id Int) ENGINE = MergeTree ORDER BY id;
CREATE TABLE t2 (id Int) ENGINE = MergeTree ORDER BY id;
INSERT INTO t1 VALUES (1), (2);
INSERT INTO t2 SELECT number + 5 AS x FROM (SELECT * FROM system.numbers LIMIT 1111);
SET max_block_size = 100;
SELECT count() == 2222 FROM t1 JOIN t2 ON 1 = 1;
{% for bs in [90, 95, 99, 100, 101, 110, 111, 128] -%}
SET max_block_size = {{ bs }};
SELECT count() == 0 FROM t1 JOIN t2 ON 1 = 2;
SELECT count() == 2 FROM t1 LEFT JOIN t2 ON 1 = 2;
SELECT count() == 1111 FROM t1 RIGHT JOIN t2 ON 1 = 2;
SELECT count() == 1113 FROM t1 FULL JOIN t2 ON 1 = 2;
SELECT max(blockSize()) <= {{ bs }} FROM t1 FULL JOIN t2 ON 1 = 2;
{% endfor %}
DROP TABLE IF EXISTS t1;
DROP TABLE IF EXISTS t2;