mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-09-30 13:40:50 +00:00
123 lines
3.8 KiB
C++
123 lines
3.8 KiB
C++
#include <DB/DataStreams/DistinctBlockInputStream.h>
|
||
|
||
|
||
namespace DB
|
||
{
|
||
|
||
namespace ErrorCodes
|
||
{
|
||
extern const int SET_SIZE_LIMIT_EXCEEDED;
|
||
}
|
||
|
||
|
||
DistinctBlockInputStream::DistinctBlockInputStream(BlockInputStreamPtr input_, const Limits & limits, size_t limit_, Names columns_)
|
||
: columns_names(columns_),
|
||
limit(limit_),
|
||
max_rows(limits.max_rows_in_distinct),
|
||
max_bytes(limits.max_bytes_in_distinct),
|
||
overflow_mode(limits.distinct_overflow_mode)
|
||
{
|
||
children.push_back(input_);
|
||
}
|
||
|
||
|
||
Block DistinctBlockInputStream::readImpl()
|
||
{
|
||
/// Пока не встретится блок, после фильтрации которого что-нибудь останется, или поток не закончится.
|
||
while (1)
|
||
{
|
||
/// Если уже прочитали достаточно строк - то больше читать не будем.
|
||
if (limit && set.size() >= limit)
|
||
return Block();
|
||
|
||
Block block = children[0]->read();
|
||
|
||
if (!block)
|
||
return Block();
|
||
|
||
size_t rows = block.rows();
|
||
size_t columns = columns_names.empty() ? block.columns() : columns_names.size();
|
||
|
||
ConstColumnPlainPtrs column_ptrs;
|
||
column_ptrs.reserve(columns);
|
||
|
||
for (size_t i = 0; i < columns; ++i)
|
||
{
|
||
auto & column = columns_names.empty()
|
||
? block.getByPosition(i).column
|
||
: block.getByName(columns_names[i]).column;
|
||
|
||
/// Игнорируем все константные столбцы.
|
||
if (!column->isConst())
|
||
column_ptrs.emplace_back(column.get());
|
||
}
|
||
|
||
columns = column_ptrs.size();
|
||
|
||
/// Будем фильтровать блок, оставляя там только строки, которых мы ещё не видели.
|
||
IColumn::Filter filter(rows);
|
||
|
||
size_t old_set_size = set.size();
|
||
|
||
for (size_t i = 0; i < rows; ++i)
|
||
{
|
||
/** Уникальность строк будем отслеживать с помощью множества значений SipHash128.
|
||
* Делается несколько допущений.
|
||
* 1. Допускается неточная работа в случае коллизий SipHash128.
|
||
* 2. Допускается неточная работа, если строковые поля содержат нулевые байты.
|
||
* 3. Не поддерживаются массивы.
|
||
*
|
||
* Для оптимизации, можно добавить другие методы из Set.h.
|
||
*/
|
||
|
||
UInt128 key;
|
||
SipHash hash;
|
||
|
||
for (size_t j = 0; j < columns; ++j)
|
||
{
|
||
StringRef data = column_ptrs[j]->getDataAtWithTerminatingZero(i);
|
||
hash.update(data.data, data.size);
|
||
}
|
||
|
||
hash.get128(key.first, key.second);
|
||
|
||
/// Если вставилось в множество - строчку оставляем, иначе - удаляем.
|
||
filter[i] = set.insert(key).second;
|
||
|
||
if (limit && set.size() == limit)
|
||
{
|
||
memset(&filter[i + 1], 0, (rows - (i + 1)) * sizeof(IColumn::Filter::value_type));
|
||
break;
|
||
}
|
||
}
|
||
|
||
/// Если ни одной новой строки не было в блоке - перейдём к следующему блоку.
|
||
if (set.size() == old_set_size)
|
||
continue;
|
||
|
||
if (!checkLimits())
|
||
{
|
||
if (overflow_mode == OverflowMode::THROW)
|
||
throw Exception("DISTINCT-Set size limit exceeded."
|
||
" Rows: " + toString(set.size()) +
|
||
", limit: " + toString(max_rows) +
|
||
". Bytes: " + toString(set.getBufferSizeInBytes()) +
|
||
", limit: " + toString(max_bytes) + ".",
|
||
ErrorCodes::SET_SIZE_LIMIT_EXCEEDED);
|
||
|
||
if (overflow_mode == OverflowMode::BREAK)
|
||
return Block();
|
||
|
||
throw Exception("Logical error: unknown overflow mode", ErrorCodes::LOGICAL_ERROR);
|
||
}
|
||
|
||
size_t all_columns = block.columns();
|
||
for (size_t i = 0; i < all_columns; ++i)
|
||
block.getByPosition(i).column = block.getByPosition(i).column->filter(filter, -1);
|
||
|
||
return block;
|
||
}
|
||
}
|
||
|
||
}
|