This commit is contained in:
Evgeniy Gatov 2015-01-28 23:45:03 +03:00
commit 694b942f67
147 changed files with 5916 additions and 2806 deletions

30
copy_headers.sh Executable file
View File

@ -0,0 +1,30 @@
#!/bin/bash -e
# Этот скрипт собирает все заголовочные файлы, нужные для компиляции некоторого translation unit-а
# и копирует их с сохранением путей в директорию DST.
# Это затем может быть использовано, чтобы скомпилировать translation unit на другом сервере,
# используя ровно такой же набор заголовочных файлов.
#
# Требуется clang, желательно наиболее свежий (trunk).
#
# Используется при сборке пакетов.
# Заголовочные файлы записываются в пакет clickhouse-server-base, в директорию /usr/share/clickhouse/headers.
#
# Если вы хотите установить их самостоятельно, без сборки пакета,
# чтобы clickhouse-server видел их там, где ожидается, выполните:
#
# sudo ./copy_headers.sh . /usr/share/clickhouse/headers/
SOURCE_PATH=${1:-.}
DST=${2:-$SOURCE_PATH/../headers};
for i in $(clang -M -xc++ -std=gnu++1y -Wall -Werror -march=native -O3 -g -fPIC \
$(cat $SOURCE_PATH/CMakeLists.txt | grep include_directories | grep -v METRICA_BINARY_DIR | sed -e "s!\${METRICA_SOURCE_DIR}!$SOURCE_PATH!; s!include_directories (!-I !; s!)!!;" | tr '\n' ' ') \
$SOURCE_PATH/dbms/include/DB/Interpreters/SpecializedAggregator.h |
tr -d '\\' |
grep -v '.o:' |
ssed -R -e 's/^.+\.cpp / /');
do
mkdir -p $DST/$(echo $i | ssed -R -e 's/\/[^/]*$/\//');
cp $i $DST/$i;
done

View File

@ -21,7 +21,7 @@ namespace DB
* Может быть в двух вариантах:
*
* 1. Владеть своими значениями - то есть, отвечать за их уничтожение.
* Столбец состоит из значений, "отданных ему на попечение" после выполнения агрегации (см. Aggregator, функция convertToBlock),
* Столбец состоит из значений, "отданных ему на попечение" после выполнения агрегации (см. Aggregator, функция convertToBlocks),
* или из значений, созданных им самим (см. метод insert).
* В этом случае, src будет равно nullptr, и столбец будет сам уничтожать (вызывать IAggregateFunction::destroy)
* состояния агрегатных функций в деструкторе.

View File

@ -21,6 +21,21 @@ inline DB::UInt64 intHash64(DB::UInt64 x)
return x;
}
/** CRC32C является не очень качественной в роли хэш функции,
* согласно avalanche и bit independence тестам, а также малым количеством бит,
* но может вести себя хорошо при использовании в хэш-таблицах,
* за счёт высокой скорости (latency 3 + 1 такт, througput 1 такт).
* Работает только при поддержке SSE 4.2.
* Используется asm вместо интринсика, чтобы не обязательно было собирать весь проект с -msse4.
*/
inline DB::UInt64 intHashCRC32(DB::UInt64 x)
{
DB::UInt64 crc = -1ULL;
asm("crc32q %[x], %[crc]\n" : [crc] "+r" (crc) : [x] "rm" (x));
return crc;
}
template <typename T> struct DefaultHash;
template <typename T>
@ -36,7 +51,7 @@ inline size_t DefaultHash64(T key)
return intHash64(u.out);
}
#define DEFAULT_HASH_64(T) \
#define DEFINE_HASH(T) \
template <> struct DefaultHash<T>\
{\
size_t operator() (T key) const\
@ -45,15 +60,53 @@ template <> struct DefaultHash<T>\
}\
};
DEFAULT_HASH_64(DB::UInt8)
DEFAULT_HASH_64(DB::UInt16)
DEFAULT_HASH_64(DB::UInt32)
DEFAULT_HASH_64(DB::UInt64)
DEFAULT_HASH_64(DB::Int8)
DEFAULT_HASH_64(DB::Int16)
DEFAULT_HASH_64(DB::Int32)
DEFAULT_HASH_64(DB::Int64)
DEFAULT_HASH_64(DB::Float32)
DEFAULT_HASH_64(DB::Float64)
DEFINE_HASH(DB::UInt8)
DEFINE_HASH(DB::UInt16)
DEFINE_HASH(DB::UInt32)
DEFINE_HASH(DB::UInt64)
DEFINE_HASH(DB::Int8)
DEFINE_HASH(DB::Int16)
DEFINE_HASH(DB::Int32)
DEFINE_HASH(DB::Int64)
DEFINE_HASH(DB::Float32)
DEFINE_HASH(DB::Float64)
#undef DEFAULT_HASH_64
#undef DEFINE_HASH
template <typename T> struct HashCRC32;
template <typename T>
inline size_t hashCRC32(T key)
{
union
{
T in;
DB::UInt64 out;
} u;
u.out = 0;
u.in = key;
return intHashCRC32(u.out);
}
#define DEFINE_HASH(T) \
template <> struct HashCRC32<T>\
{\
size_t operator() (T key) const\
{\
return hashCRC32<T>(key);\
}\
};
DEFINE_HASH(DB::UInt8)
DEFINE_HASH(DB::UInt16)
DEFINE_HASH(DB::UInt32)
DEFINE_HASH(DB::UInt64)
DEFINE_HASH(DB::Int8)
DEFINE_HASH(DB::Int16)
DEFINE_HASH(DB::Int32)
DEFINE_HASH(DB::Int64)
DEFINE_HASH(DB::Float32)
DEFINE_HASH(DB::Float64)
#undef DEFINE_HASH

View File

@ -129,7 +129,9 @@ public:
typedef typename Cell::Mapped mapped_type;
typedef typename Cell::value_type value_type;
mapped_type & operator[](Key x)
using HashTable<Key, Cell, Hash, Grower, Allocator>::HashTable;
mapped_type & ALWAYS_INLINE operator[](Key x)
{
typename HashMapTable::iterator it;
bool inserted;

View File

@ -13,6 +13,7 @@
#include <stats/IntHash.h>
#include <DB/Core/Defines.h>
#include <DB/Core/Types.h>
#include <DB/Core/Exception.h>
#include <DB/Core/ErrorCodes.h>
@ -135,7 +136,7 @@ struct HashTableCell
/** Определяет размер хэш-таблицы, а также когда и во сколько раз её надо ресайзить.
*/
template <size_t initial_size_degree = 16>
template <size_t initial_size_degree = 8>
struct HashTableGrower
{
/// Состояние этой структуры достаточно, чтобы получить размер буфера хэш-таблицы.
@ -231,6 +232,9 @@ protected:
friend class const_iterator;
friend class iterator;
template <typename, typename, typename, typename, typename, typename, size_t>
friend class TwoLevelHashTable;
typedef size_t HashValue;
typedef HashTable<Key, Cell, Hash, Grower, Allocator> Self;
typedef Cell cell_type;
@ -240,11 +244,11 @@ protected:
Grower grower;
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
mutable size_t collisions;
mutable size_t collisions = 0;
#endif
/// Найти ячейку с тем же ключём или пустую ячейку, начиная с заданного места и далее по цепочке разрешения коллизий.
size_t findCell(const Key & x, size_t hash_value, size_t place_value) const
size_t ALWAYS_INLINE findCell(const Key & x, size_t hash_value, size_t place_value) const
{
while (!buf[place_value].isZero(*this) && !buf[place_value].keyEquals(x, hash_value))
{
@ -257,6 +261,20 @@ protected:
return place_value;
}
/// Найти пустую ячейку, начиная с заданного места и далее по цепочке разрешения коллизий.
size_t ALWAYS_INLINE findEmptyCell(const Key & x, size_t hash_value, size_t place_value) const
{
while (!buf[place_value].isZero(*this))
{
place_value = grower.next(place_value);
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
++collisions;
#endif
}
return place_value;
}
void alloc(const Grower & new_grower)
{
buf = reinterpret_cast<Cell *>(Allocator::alloc(new_grower.bufSize() * sizeof(Cell)));
@ -372,10 +390,14 @@ public:
if (Cell::need_zero_value_storage)
this->zeroValue()->setZero();
alloc(grower);
}
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
collisions = 0;
#endif
HashTable(size_t reserve_for_num_elements)
{
if (Cell::need_zero_value_storage)
this->zeroValue()->setZero();
grower.set(reserve_for_num_elements);
alloc(grower);
}
~HashTable()
@ -417,6 +439,9 @@ public:
value_type & operator* () const { return ptr->getValue(); }
value_type * operator->() const { return &ptr->getValue(); }
Cell * getPtr() const { return ptr; }
size_t getHash() const { return ptr->getHash(*container); }
};
@ -450,6 +475,9 @@ public:
const value_type & operator* () const { return ptr->getValue(); }
const value_type * operator->() const { return &ptr->getValue(); }
const Cell * getPtr() const { return ptr; }
size_t getHash() const { return ptr->getHash(*container); }
};
@ -482,12 +510,14 @@ public:
protected:
const_iterator iteratorToZero() const { return const_iterator(this, this->zeroValue()); }
iterator iteratorToZero() { return iterator(this, this->zeroValue()); }
const_iterator iteratorTo(const Cell * ptr) const { return const_iterator(this, ptr); }
iterator iteratorTo(Cell * ptr) { return iterator(this, ptr); }
const_iterator iteratorToZero() const { return iteratorTo(this->zeroValue()); }
iterator iteratorToZero() { return iteratorTo(this->zeroValue()); }
/// Если ключ нулевой - вставить его в специальное место и вернуть true.
bool emplaceIfZero(Key x, iterator & it, bool & inserted)
bool ALWAYS_INLINE emplaceIfZero(Key x, iterator & it, bool & inserted)
{
/// Если утверждается, что нулевой ключ не могут вставить в таблицу.
if (!Cell::need_zero_value_storage)
@ -514,7 +544,7 @@ protected:
/// Только для ненулевых ключей. Найти нужное место, вставить туда ключ, если его ещё нет, вернуть итератор на ячейку.
void emplaceNonZero(Key x, iterator & it, bool & inserted, size_t hash_value)
void ALWAYS_INLINE emplaceNonZero(Key x, iterator & it, bool & inserted, size_t hash_value)
{
size_t place_value = findCell(x, hash_value, grower.place(hash_value));
@ -534,14 +564,14 @@ protected:
if (unlikely(grower.overflow(m_size)))
{
resize();
it = find(x);
it = find(x, hash_value);
}
}
public:
/// Вставить значение. В случае хоть сколько-нибудь сложных значений, лучше используйте функцию emplace.
std::pair<iterator, bool> insert(const value_type & x)
std::pair<iterator, bool> ALWAYS_INLINE insert(const value_type & x)
{
std::pair<iterator, bool> res;
@ -570,7 +600,7 @@ public:
* if (inserted)
* new(&it->second) Mapped(value);
*/
void emplace(Key x, iterator & it, bool & inserted)
void ALWAYS_INLINE emplace(Key x, iterator & it, bool & inserted)
{
if (!emplaceIfZero(x, it, inserted))
emplaceNonZero(x, it, inserted, hash(x));
@ -578,33 +608,64 @@ public:
/// То же самое, но с заранее вычисленным значением хэш-функции.
void emplace(Key x, iterator & it, bool & inserted, size_t hash_value)
void ALWAYS_INLINE emplace(Key x, iterator & it, bool & inserted, size_t hash_value)
{
if (!emplaceIfZero(x, it, inserted))
emplaceNonZero(x, it, inserted, hash_value);
}
iterator find(Key x)
/// Скопировать ячейку из другой хэш-таблицы. Предполагается, что ячейка не нулевая, а также, что такого ключа в таблице ещё не было.
void ALWAYS_INLINE insertUniqueNonZero(const Cell * cell, size_t hash_value)
{
size_t place_value = findEmptyCell(cell->getKey(cell->getValue()), hash_value, grower.place(hash_value));
memcpy(&buf[place_value], cell, sizeof(*cell));
++m_size;
if (unlikely(grower.overflow(m_size)))
resize();
}
iterator ALWAYS_INLINE find(Key x)
{
if (Cell::isZero(x, *this))
return this->hasZero() ? iteratorToZero() : end();
size_t hash_value = hash(x);
size_t place_value = findCell(x, hash_value, grower.place(hash_value));
return !buf[place_value].isZero(*this) ? iterator(this, &buf[place_value]) : end();
}
const_iterator find(Key x) const
const_iterator ALWAYS_INLINE find(Key x) const
{
if (Cell::isZero(x, *this))
return this->hasZero() ? iteratorToZero() : end();
size_t hash_value = hash(x);
size_t place_value = findCell(x, hash_value, grower.place(hash_value));
return !buf[place_value].isZero(*this) ? const_iterator(this, &buf[place_value]) : end();
}
iterator ALWAYS_INLINE find(Key x, size_t hash_value)
{
if (Cell::isZero(x, *this))
return this->hasZero() ? iteratorToZero() : end();
size_t place_value = findCell(x, hash_value, grower.place(hash_value));
return !buf[place_value].isZero(*this) ? iterator(this, &buf[place_value]) : end();
}
const_iterator ALWAYS_INLINE find(Key x, size_t hash_value) const
{
if (Cell::isZero(x, *this))
return this->hasZero() ? iteratorToZero() : end();
size_t place_value = findCell(x, hash_value, grower.place(hash_value));
return !buf[place_value].isZero(*this) ? const_iterator(this, &buf[place_value]) : end();
}
@ -701,6 +762,16 @@ public:
return 0 == m_size;
}
void clear()
{
if (!__has_trivial_destructor(Cell))
for (iterator it = begin(); it != end(); ++it)
it.ptr->~Cell();
memset(buf, 0, grower.bufSize() * sizeof(*buf));
m_size = 0;
}
size_t getBufferSizeInBytes() const
{
return grower.bufSize() * sizeof(Cell);

View File

@ -0,0 +1,57 @@
#pragma once
#include <DB/Common/HashTable/TwoLevelHashTable.h>
#include <DB/Common/HashTable/HashMap.h>
template
<
typename Key,
typename Cell,
typename Hash = DefaultHash<Key>,
typename Grower = TwoLevelHashTableGrower<>,
typename Allocator = HashTableAllocator
>
class TwoLevelHashMapTable : public TwoLevelHashTable<Key, Cell, Hash, Grower, Allocator, HashMapTable<Key, Cell, Hash, Grower, Allocator>>
{
public:
typedef Key key_type;
typedef typename Cell::Mapped mapped_type;
typedef typename Cell::value_type value_type;
using TwoLevelHashTable<Key, Cell, Hash, Grower, Allocator, HashMapTable<Key, Cell, Hash, Grower, Allocator> >::TwoLevelHashTable;
mapped_type & ALWAYS_INLINE operator[](Key x)
{
typename TwoLevelHashMapTable::iterator it;
bool inserted;
this->emplace(x, it, inserted);
if (!__has_trivial_constructor(mapped_type) && inserted)
new(&it->second) mapped_type();
return it->second;
}
};
template
<
typename Key,
typename Mapped,
typename Hash = DefaultHash<Key>,
typename Grower = TwoLevelHashTableGrower<>,
typename Allocator = HashTableAllocator
>
using TwoLevelHashMap = TwoLevelHashMapTable<Key, HashMapCell<Key, Mapped, Hash>, Hash, Grower, Allocator>;
template
<
typename Key,
typename Mapped,
typename Hash = DefaultHash<Key>,
typename Grower = TwoLevelHashTableGrower<>,
typename Allocator = HashTableAllocator
>
using TwoLevelHashMapWithSavedHash = TwoLevelHashMapTable<Key, HashMapCellWithSavedHash<Key, Mapped, Hash>, Hash, Grower, Allocator>;

View File

@ -4,7 +4,7 @@
/** Двухуровневая хэш-таблица.
* Представляет собой 256 маленьких хэш-таблиц (bucket-ов первого уровня).
* Представляет собой 256 (или 1 << BITS_FOR_BUCKET) маленьких хэш-таблиц (bucket-ов первого уровня).
* Для определения, какую из них использовать, берётся один из байтов хэш-функции.
*
* Обычно работает чуть-чуть медленнее простой хэш-таблицы.
@ -14,14 +14,25 @@
* - по идее, ресайзы кэш-локальны в большем диапазоне размеров.
*/
template <size_t initial_size_degree = 8>
struct TwoLevelHashTableGrower : public HashTableGrower<initial_size_degree>
{
/// Увеличить размер хэш-таблицы.
void increaseSize()
{
this->size_degree += this->size_degree >= 15 ? 1 : 2;
}
};
template
<
typename Key,
typename Cell,
typename Hash,
typename Grower,
typename Allocator,
typename ImplTable = HashTable<Key, Cell, Hash, Grower, Allocator>
typename Allocator, /// TODO WithStackMemory
typename ImplTable = HashTable<Key, Cell, Hash, Grower, Allocator>,
size_t BITS_FOR_BUCKET = 8
>
class TwoLevelHashTable :
private boost::noncopyable,
@ -36,8 +47,13 @@ protected:
public:
typedef ImplTable Impl;
static constexpr size_t NUM_BUCKETS = 1 << BITS_FOR_BUCKET;
static constexpr size_t MAX_BUCKET = NUM_BUCKETS - 1;
size_t hash(const Key & x) const { return Hash::operator()(x); }
size_t getBucketFromHash(size_t hash_value) const { return hash_value >> 56; }
/// NOTE Плохо для хэш-таблиц больше чем на 2^32 ячеек.
size_t getBucketFromHash(size_t hash_value) const { return (hash_value >> (32 - BITS_FOR_BUCKET)) & MAX_BUCKET; }
protected:
typename Impl::iterator beginOfNextNonEmptyBucket(size_t & bucket)
@ -68,11 +84,34 @@ public:
typedef typename Impl::key_type key_type;
typedef typename Impl::value_type value_type;
static constexpr size_t NUM_BUCKETS = 256;
static constexpr size_t MAX_BUCKET = NUM_BUCKETS - 1;
Impl impls[NUM_BUCKETS];
TwoLevelHashTable() {}
/// Скопировать данные из другой (обычной) хэш-таблицы. У неё должна быть такая же хэш-функция.
template <typename Source>
TwoLevelHashTable(const Source & src)
{
typename Source::const_iterator it = src.begin();
/// Предполагается, что нулевой ключ (хранящийся отдельно) при итерировании идёт первым.
if (it != src.end() && it.getPtr()->isZero(src))
{
insert(*it);
++it;
}
for (; it != src.end(); ++it)
{
const Cell * cell = it.getPtr();
size_t hash_value = cell->getHash(src);
size_t buck = getBucketFromHash(hash_value);
impls[buck].insertUniqueNonZero(cell, hash_value);
}
}
class iterator
{
Self * container;
@ -104,6 +143,9 @@ public:
value_type & operator* () const { return *current_it; }
value_type * operator->() const { return &*current_it; }
Cell * getPtr() const { return current_it.getPtr(); }
size_t getHash() const { return current_it.getHash(); }
};
@ -139,6 +181,9 @@ public:
const value_type & operator* () const { return *current_it; }
const value_type * operator->() const { return &*current_it; }
const Cell * getPtr() const { return current_it.getPtr(); }
size_t getHash() const { return current_it.getHash(); }
};
@ -161,12 +206,16 @@ public:
/// Вставить значение. В случае хоть сколько-нибудь сложных значений, лучше используйте функцию emplace.
std::pair<iterator, bool> insert(const value_type & x)
std::pair<iterator, bool> ALWAYS_INLINE insert(const value_type & x)
{
size_t hash_value = hash(Cell::getKey(x));
std::pair<iterator, bool> res;
emplace(Cell::getKey(x), res.first, res.second, hash_value);
if (res.second)
res.first.getPtr()->setMapped(x);
return res;
}
@ -186,7 +235,7 @@ public:
* if (inserted)
* new(&it->second) Mapped(value);
*/
void emplace(Key x, iterator & it, bool & inserted)
void ALWAYS_INLINE emplace(Key x, iterator & it, bool & inserted)
{
size_t hash_value = hash(x);
emplace(x, it, inserted, hash_value);
@ -194,33 +243,33 @@ public:
/// То же самое, но с заранее вычисленным значением хэш-функции.
void emplace(Key x, iterator & it, bool & inserted, size_t hash_value)
void ALWAYS_INLINE emplace(Key x, iterator & it, bool & inserted, size_t hash_value)
{
size_t buck = getBucketFromHash(hash_value);
typename Impl::iterator impl_it;
impls[buck].emplace(x, impl_it, inserted);
impls[buck].emplace(x, impl_it, inserted, hash_value);
it = iterator(this, buck, impl_it);
}
iterator find(Key x)
iterator ALWAYS_INLINE find(Key x)
{
size_t hash_value = hash(x);
size_t buck = getBucketFromHash(hash_value);
typename Impl::iterator found = impls[buck].find(x);
typename Impl::iterator found = impls[buck].find(x, hash_value);
return found != impls[buck].end()
? iterator(this, buck, found)
: end();
}
const_iterator find(Key x) const
const_iterator ALWAYS_INLINE find(Key x) const
{
size_t hash_value = hash(x);
size_t buck = getBucketFromHash(hash_value);
typename Impl::const_iterator found = impls[buck].find(x);
typename Impl::const_iterator found = impls[buck].find(x, hash_value);
return found != impls[buck].end()
? const_iterator(this, buck, found)
: end();

View File

@ -17,7 +17,8 @@
#define ROTL(x,b) (u64)( ((x) << (b)) | ( (x) >> (64 - (b))) )
#define SIPROUND \
do { \
do \
{ \
v0 += v1; v1=ROTL(v1,13); v1 ^= v0; v0=ROTL(v0,32); \
v2 += v3; v3=ROTL(v3,16); v3 ^= v2; \
v0 += v3; v3=ROTL(v3,21); v3 ^= v0; \

View File

@ -1,3 +1,5 @@
#pragma once
#include <DB/Common/HashTable/Hash.h>
#include <DB/IO/ReadHelpers.h>
#include <DB/IO/WriteHelpers.h>
@ -27,6 +29,17 @@ struct UInt128Hash
size_t operator()(UInt128 x) const { return hash64(hash64(x.first) ^ x.second); }
};
struct UInt128HashCRC32
{
size_t operator()(UInt128 x) const
{
UInt64 crc = -1ULL;
asm("crc32q %[x], %[crc]\n" : [crc] "+r" (crc) : [x] "rm" (x.first));
asm("crc32q %[x], %[crc]\n" : [crc] "+r" (crc) : [x] "rm" (x.second));
return crc;
}
};
struct UInt128TrivialHash
{
size_t operator()(UInt128 x) const { return x.first; }

View File

@ -1,19 +1,18 @@
#pragma once
#include <DB/Interpreters/Context.h>
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
#include <DB/DataStreams/OneBlockInputStream.h>
#include <DB/DataTypes/DataTypeString.h>
#include <DB/DataTypes/DataTypesNumberFixed.h>
#include <DB/Parsers/ASTIdentifier.h>
#include <DB/Parsers/ASTExpressionList.h>
#include <DB/Parsers/ASTLiteral.h>
#include <DB/Parsers/ASTSelectQuery.h>
#include <DB/Columns/ColumnString.h>
#include <set>
#include <DB/Core/Block.h>
#include <DB/Core/NamesAndTypes.h>
#include <DB/Parsers/IAST.h>
namespace DB
{
class Context;
namespace VirtualColumnUtils
{

View File

@ -5,6 +5,7 @@
#include <list>
#include <initializer_list>
#include <DB/Core/BlockInfo.h>
#include <DB/Core/ColumnWithNameAndType.h>
#include <DB/Core/NamesAndTypes.h>
#include <DB/Core/Exception.h>
@ -35,6 +36,8 @@ private:
IndexByName_t index_by_name;
public:
BlockInfo info;
Block() = default;
Block(std::initializer_list<ColumnWithNameAndType> il) : data{il}
{
@ -129,7 +132,7 @@ bool blocksHaveEqualStructure(const Block & lhs, const Block & rhs);
namespace std
{
template<> inline void swap<DB::Block>(DB::Block & one, DB::Block & another) noexcept
template<> inline void swap<DB::Block>(DB::Block & one, DB::Block & another)
{
one.swap(another);
}

View File

@ -0,0 +1,87 @@
#pragma once
#include <DB/Core/Types.h>
#include <DB/Core/Exception.h>
#include <DB/Core/ErrorCodes.h>
#include <DB/IO/ReadBuffer.h>
#include <DB/IO/WriteBuffer.h>
#include <DB/IO/VarInt.h>
#include <DB/IO/ReadHelpers.h>
#include <DB/IO/WriteHelpers.h>
namespace DB
{
/** Дополнительная информация о блоке.
*/
struct BlockInfo
{
/** is_overflows:
* После выполнения GROUP BY ... WITH TOTALS с настройками max_rows_to_group_by и group_by_overflow_mode = 'any',
* в отдельный блок засовывается строчка с аргегированными значениями, не прошедшими max_rows_to_group_by.
* Если это такой блок, то для него is_overflows выставляется в true.
*/
/** bucket_num:
* При использовании двухуровневого метода агрегации, данные с разными группами ключей раскидываются по разным корзинам.
* В таком случае здесь указывается номер корзины. Он используется для оптимизации слияния при распределённой аргегации.
* Иначе - -1.
*/
#define APPLY_FOR_INFO_FIELDS(M) \
M(bool, is_overflows, false, 1) \
M(Int32, bucket_num, -1, 2)
#define DECLARE_FIELD(TYPE, NAME, DEFAULT, FIELD_NUM) \
TYPE NAME = DEFAULT;
APPLY_FOR_INFO_FIELDS(DECLARE_FIELD)
#undef DECLARE_FIELD
/// Записать значения в бинарном виде. NOTE: Можно было бы использовать protobuf, но он был бы overkill для данного случая.
void write(WriteBuffer & out) const
{
/// Набор пар FIELD_NUM, значение в бинарном виде. Затем 0.
#define WRITE_FIELD(TYPE, NAME, DEFAULT, FIELD_NUM) \
writeVarUInt(FIELD_NUM, out); \
writeBinary(NAME, out);
APPLY_FOR_INFO_FIELDS(WRITE_FIELD);
#undef WRITE_FIELD
writeVarUInt(0, out);
}
/// Прочитать значения в бинарном виде.
void read(ReadBuffer & in)
{
UInt64 field_num = 0;
while (true)
{
readVarUInt(field_num, in);
if (field_num == 0)
break;
switch (field_num)
{
#define READ_FIELD(TYPE, NAME, DEFAULT, FIELD_NUM) \
case FIELD_NUM: \
readBinary(NAME, in); \
break;
APPLY_FOR_INFO_FIELDS(READ_FIELD);
#undef READ_FIELD
default:
throw Exception("Unknown BlockInfo field number: " + toString(field_num), ErrorCodes::UNKNOWN_BLOCK_INFO_FIELD);
}
}
}
#undef APPLY_FOR_INFO_FIELDS
};
}

View File

@ -65,6 +65,7 @@
#define DBMS_MIN_REVISION_WITH_STRING_QUERY_ID 39002
#define DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES 50264
#define DBMS_MIN_REVISION_WITH_TOTAL_ROWS_IN_PROGRESS 51554
#define DBMS_MIN_REVISION_WITH_BLOCK_INFO 51903
#define DBMS_DISTRIBUTED_DIRECTORY_MONITOR_SLEEP_TIME_MS 100

View File

@ -266,6 +266,11 @@ namespace ErrorCodes
PARTITION_ALREADY_EXISTS,
PARTITION_DOESNT_EXIST,
UNION_ALL_RESULT_STRUCTURES_MISMATCH,
UNION_ALL_COLUMN_ALIAS_MISMATCH,
CLIENT_OUTPUT_FORMAT_SPECIFIED,
UNKNOWN_BLOCK_INFO_FIELD,
BAD_COLLATION,
CANNOT_COMPILE_CODE,
POCO_EXCEPTION = 1000,
STD_EXCEPTION,

View File

@ -50,9 +50,9 @@ struct SortCursorImpl
ConstColumnPlainPtrs all_columns;
ConstColumnPlainPtrs sort_columns;
SortDescription desc;
size_t sort_columns_size;
size_t pos;
size_t rows;
size_t sort_columns_size = 0;
size_t pos = 0;
size_t rows = 0;
/** Порядок (что сравнивается), если сравниваемые столбцы равны.
* Даёт возможность предпочитать строки из нужного курсора.
@ -65,12 +65,12 @@ struct SortCursorImpl
NeedCollationFlags need_collation;
/** Есть ли хотя бы один столбец с Collator. */
bool has_collation;
bool has_collation = false;
SortCursorImpl() : sort_columns(0), pos(0), rows(0) {}
SortCursorImpl() {}
SortCursorImpl(const Block & block, const SortDescription & desc_, size_t order_ = 0)
: desc(desc_), sort_columns_size(desc.size()), order(order_), need_collation(desc.size()), has_collation(false)
: desc(desc_), sort_columns_size(desc.size()), order(order_), need_collation(desc.size())
{
reset(block);
}

View File

@ -19,9 +19,10 @@ class AggregatingBlockInputStream : public IProfilingBlockInputStream
{
public:
AggregatingBlockInputStream(BlockInputStreamPtr input_, const ColumnNumbers & keys_, AggregateDescriptions & aggregates_,
bool overflow_row_, bool final_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_)
: aggregator(new Aggregator(keys_, aggregates_, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_)),
final(final_), has_been_read(false)
bool overflow_row_, bool final_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_,
Compiler * compiler_, UInt32 min_count_to_compile_)
: aggregator(keys_, aggregates_, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_, compiler_, min_count_to_compile_),
final(final_)
{
children.push_back(input_);
}
@ -31,23 +32,32 @@ public:
* Столбцы, соответствующие keys и аргументам агрегатных функций, уже должны быть вычислены.
*/
AggregatingBlockInputStream(BlockInputStreamPtr input_, const Names & key_names, const AggregateDescriptions & aggregates,
bool overflow_row_, bool final_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_);
bool overflow_row_, bool final_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_,
Compiler * compiler_, UInt32 min_count_to_compile_)
: aggregator(key_names, aggregates, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_, compiler_, min_count_to_compile_),
final(final_)
{
children.push_back(input_);
}
String getName() const override { return "AggregatingBlockInputStream"; }
String getID() const override
{
std::stringstream res;
res << "Aggregating(" << children.back()->getID() << ", " << aggregator->getID() << ")";
res << "Aggregating(" << children.back()->getID() << ", " << aggregator.getID() << ")";
return res.str();
}
protected:
Block readImpl() override;
SharedPtr<Aggregator> aggregator;
Aggregator aggregator;
bool final;
bool has_been_read;
bool executed = false;
BlocksList blocks;
BlocksList::iterator it;
};
}

View File

@ -96,8 +96,11 @@ protected:
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)

View File

@ -0,0 +1,51 @@
#pragma once
#include <DB/DataStreams/IProfilingBlockInputStream.h>
namespace DB
{
/** Инициализировать другой источник при первом вызове read, и затем использовать его.
* Это нужно, например, для чтения из таблицы, которая будет заполнена
* после создания объекта LazyBlockInputStream, но до первого вызова read.
*/
class LazyBlockInputStream : public IProfilingBlockInputStream
{
public:
using Generator = std::function<BlockInputStreamPtr()>;
LazyBlockInputStream(Generator generator_)
: generator(generator_) {}
String getName() const override { return "LazyBlockInputStream"; }
String getID() const override
{
std::stringstream res;
res << "Lazy(" << this << ")";
return res.str();
}
protected:
Block readImpl() override
{
if (!input)
{
input = generator();
if (!input)
return Block();
children.push_back(input);
}
return input->read();
}
private:
Generator generator;
BlockInputStreamPtr input;
};
}

View File

@ -1,23 +1,74 @@
#pragma once
#include <queue>
#include <Poco/TemporaryFile.h>
#include <Yandex/logger_useful.h>
#include <DB/Core/SortDescription.h>
#include <DB/DataStreams/IProfilingBlockInputStream.h>
#include <DB/DataStreams/NativeBlockInputStream.h>
#include <DB/IO/ReadBufferFromFile.h>
#include <DB/IO/CompressedReadBuffer.h>
namespace DB
{
/** Соединяет поток сортированных по отдельности блоков в сортированный целиком поток.
* Если данных для сортировки слишком много - может использовать внешнюю сортировку, с помощью временных файлов.
*/
/** Часть реализации. Сливает набор готовых (уже прочитанных откуда-то) блоков.
* Возвращает результат слияния в виде потока блоков не более max_merged_block_size строк.
*/
class MergeSortingBlocksBlockInputStream : public IProfilingBlockInputStream
{
public:
/// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке.
MergeSortingBlocksBlockInputStream(Blocks & blocks_, SortDescription & description_,
size_t max_merged_block_size_, size_t limit_ = 0);
String getName() const override { return "MergeSortingBlocksBlockInputStream"; }
String getID() const override { return getName(); }
protected:
Block readImpl() override;
private:
Blocks & blocks;
SortDescription description;
size_t max_merged_block_size;
size_t limit;
size_t total_merged_rows = 0;
using CursorImpls = std::vector<SortCursorImpl>;
CursorImpls cursors;
bool has_collation = false;
std::priority_queue<SortCursor> queue;
std::priority_queue<SortCursorWithCollation> queue_with_collation;
/** Делаем поддержку двух разных курсоров - с Collation и без.
* Шаблоны используем вместо полиморфных SortCursor'ов и вызовов виртуальных функций.
*/
template <typename TSortCursor>
Block mergeImpl(std::priority_queue<TSortCursor> & queue);
};
class MergeSortingBlockInputStream : public IProfilingBlockInputStream
{
public:
/// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке.
MergeSortingBlockInputStream(BlockInputStreamPtr input_, SortDescription & description_, size_t limit_ = 0)
: description(description_), limit(limit_), has_been_read(false), log(&Logger::get("MergeSortingBlockInputStream"))
MergeSortingBlockInputStream(BlockInputStreamPtr input_, SortDescription & description_,
size_t max_merged_block_size_, size_t limit_,
size_t max_bytes_before_external_sort_, const std::string & tmp_path_, const DataTypeFactory & data_type_factory_)
: description(description_), max_merged_block_size(max_merged_block_size_), limit(limit_),
max_bytes_before_external_sort(max_bytes_before_external_sort_), tmp_path(tmp_path_), data_type_factory(data_type_factory_)
{
children.push_back(input_);
}
@ -41,24 +92,36 @@ protected:
private:
SortDescription description;
size_t max_merged_block_size;
size_t limit;
/// Всё было прочитано.
bool has_been_read;
size_t max_bytes_before_external_sort;
const std::string tmp_path;
const DataTypeFactory & data_type_factory;
Logger * log;
Logger * log = &Logger::get("MergeSortingBlockInputStream");
/** Слить сразу много блоков с помощью priority queue.
*/
Block merge(Blocks & blocks);
Blocks blocks;
size_t sum_bytes_in_blocks = 0;
std::unique_ptr<IBlockInputStream> impl;
typedef std::vector<SortCursorImpl> CursorImpls;
/// Всё ниже - для внешней сортировки.
std::vector<std::unique_ptr<Poco::TemporaryFile>> temporary_files;
/** Делаем поддержку двух разных курсоров - с Collation и без.
* Шаблоны используем вместо полиморфных SortCursor'ов и вызовов виртуальных функций.
*/
template <typename TSortCursor>
Block mergeImpl(Blocks & block, CursorImpls & cursors);
/// Для чтения сброшенных во временный файл данных.
struct TemporaryFileStream
{
ReadBufferFromFile file_in;
CompressedReadBuffer compressed_in;
BlockInputStreamPtr block_in;
TemporaryFileStream(const std::string & path, const DataTypeFactory & data_type_factory)
: file_in(path), compressed_in(file_in), block_in(new NativeBlockInputStream(compressed_in, data_type_factory)) {}
};
std::vector<std::unique_ptr<TemporaryFileStream>> temporary_inputs;
BlockInputStreams inputs_to_merge;
};
}

View File

@ -17,15 +17,17 @@ class MergingAggregatedBlockInputStream : public IProfilingBlockInputStream
{
public:
MergingAggregatedBlockInputStream(BlockInputStreamPtr input_, const ColumnNumbers & keys_,
const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_)
: aggregator(new Aggregator(keys_, aggregates_, overflow_row_)), final(final_), has_been_read(false)
const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_, size_t max_threads_)
: aggregator(keys_, aggregates_, overflow_row_, 0, OverflowMode::THROW, nullptr, 0),
final(final_), max_threads(max_threads_)
{
children.push_back(input_);
}
MergingAggregatedBlockInputStream(BlockInputStreamPtr input_, const Names & keys_names_,
const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_)
: aggregator(new Aggregator(keys_names_, aggregates_, overflow_row_)), final(final_), has_been_read(false)
const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_, size_t max_threads_)
: aggregator(keys_names_, aggregates_, overflow_row_, 0, OverflowMode::THROW, nullptr, 0),
final(final_), max_threads(max_threads_)
{
children.push_back(input_);
}
@ -35,7 +37,7 @@ public:
String getID() const override
{
std::stringstream res;
res << "MergingAggregated(" << children.back()->getID() << ", " << aggregator->getID() << ")";
res << "MergingAggregated(" << children.back()->getID() << ", " << aggregator.getID() << ")";
return res.str();
}
@ -43,9 +45,13 @@ protected:
Block readImpl() override;
private:
SharedPtr<Aggregator> aggregator;
Aggregator aggregator;
bool final;
bool has_been_read;
size_t max_threads;
bool executed = false;
BlocksList blocks;
BlocksList::iterator it;
};
}

View File

@ -20,8 +20,8 @@ class MergingSortedBlockInputStream : public IProfilingBlockInputStream
public:
/// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке.
MergingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, size_t max_block_size_, size_t limit_ = 0)
: description(description_), max_block_size(max_block_size_), limit(limit_), total_merged_rows(0), first(true), has_collation(false),
num_columns(0), source_blocks(inputs_.size()), cursors(inputs_.size()), log(&Logger::get("MergingSortedBlockInputStream"))
: description(description_), max_block_size(max_block_size_), limit(limit_),
source_blocks(inputs_.size()), cursors(inputs_.size())
{
children.insert(children.end(), inputs_.begin(), inputs_.end());
}
@ -65,14 +65,13 @@ protected:
SortDescription description;
size_t max_block_size;
size_t limit;
size_t total_merged_rows;
size_t total_merged_rows = 0;
bool first;
bool has_collation;
bool first = true;
bool has_collation = false;
/// Текущие сливаемые блоки.
size_t num_columns;
size_t num_columns = 0;
Blocks source_blocks;
typedef std::vector<SortCursorImpl> CursorImpls;
@ -139,7 +138,7 @@ private:
template <typename TSortCursor>
void merge(Block & merged_block, ColumnPlainPtrs & merged_columns, std::priority_queue<TSortCursor> & queue);
Logger * log;
Logger * log = &Logger::get("MergingSortedBlockInputStream");
};
}

View File

@ -13,8 +13,11 @@ namespace DB
class NativeBlockInputStream : public IProfilingBlockInputStream
{
public:
NativeBlockInputStream(ReadBuffer & istr_, const DataTypeFactory & data_type_factory_)
: istr(istr_), data_type_factory(data_type_factory_) {}
/** В случае указания ненулевой server_revision, может ожидаться и считываться дополнительная информация о блоке,
* в зависимости от поддерживаемой для указанной ревизии.
*/
NativeBlockInputStream(ReadBuffer & istr_, const DataTypeFactory & data_type_factory_, UInt64 server_revision_ = 0)
: istr(istr_), data_type_factory(data_type_factory_), server_revision(server_revision_) {}
String getName() const override { return "NativeBlockInputStream"; }
@ -31,6 +34,7 @@ protected:
private:
ReadBuffer & istr;
const DataTypeFactory & data_type_factory;
UInt64 server_revision;
};
}

View File

@ -12,13 +12,18 @@ namespace DB
class NativeBlockOutputStream : public IBlockOutputStream
{
public:
NativeBlockOutputStream(WriteBuffer & ostr_) : ostr(ostr_) {}
/** В случае указания ненулевой client_revision, может записываться дополнительная информация о блоке,
* в зависимости от поддерживаемой для указанной ревизии.
*/
NativeBlockOutputStream(WriteBuffer & ostr_, UInt64 client_revision_ = 0)
: ostr(ostr_), client_revision(client_revision_) {}
void write(const Block & block) override;
void flush() override { ostr.next(); }
private:
WriteBuffer & ostr;
UInt64 client_revision;
};
}

View File

@ -16,7 +16,7 @@ using Poco::SharedPtr;
class OneBlockInputStream : public IProfilingBlockInputStream
{
public:
OneBlockInputStream(const Block & block_) : block(block_), has_been_read(false) {}
OneBlockInputStream(const Block & block_) : block(block_) {}
String getName() const override { return "OneBlockInputStream"; }
@ -39,7 +39,7 @@ protected:
private:
Block block;
bool has_been_read;
bool has_been_read = false;
};
}

View File

@ -21,9 +21,10 @@ class ParallelAggregatingBlockInputStream : public IProfilingBlockInputStream
public:
ParallelAggregatingBlockInputStream(BlockInputStreams inputs, const ColumnNumbers & keys_,
AggregateDescriptions & aggregates_, bool overflow_row_, bool final_, size_t max_threads_,
size_t max_rows_to_group_by_ = 0, OverflowMode group_by_overflow_mode_ = OverflowMode::THROW)
: aggregator(new Aggregator(keys_, aggregates_, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_)),
has_been_read(false), final(final_), max_threads(std::min(inputs.size(), max_threads_)),
size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_,
Compiler * compiler_, UInt32 min_count_to_compile_)
: aggregator(keys_, aggregates_, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_, compiler_, min_count_to_compile_),
final(final_), max_threads(std::min(inputs.size(), max_threads_)),
keys_size(keys_.size()), aggregates_size(aggregates_.size()),
handler(*this), processor(inputs, max_threads, handler)
{
@ -34,14 +35,14 @@ public:
*/
ParallelAggregatingBlockInputStream(BlockInputStreams inputs, const Names & key_names,
const AggregateDescriptions & aggregates, bool overflow_row_, bool final_, size_t max_threads_,
size_t max_rows_to_group_by_ = 0, OverflowMode group_by_overflow_mode_ = OverflowMode::THROW)
: has_been_read(false), final(final_), max_threads(std::min(inputs.size(), max_threads_)),
size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_,
Compiler * compiler_, UInt32 min_count_to_compile_)
: aggregator(key_names, aggregates, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_, compiler_, min_count_to_compile_),
final(final_), max_threads(std::min(inputs.size(), max_threads_)),
keys_size(key_names.size()), aggregates_size(aggregates.size()),
handler(*this), processor(inputs, max_threads, handler)
{
children.insert(children.end(), inputs.begin(), inputs.end());
aggregator = new Aggregator(key_names, aggregates, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_);
}
String getName() const override { return "ParallelAggregatingBlockInputStream"; }
@ -61,7 +62,7 @@ public:
for (size_t i = 0; i < children_ids.size(); ++i)
res << (i == 0 ? "" : ", ") << children_ids[i];
res << ", " << aggregator->getID() << ")";
res << ", " << aggregator.getID() << ")";
return res.str();
}
@ -76,65 +77,29 @@ public:
protected:
Block readImpl() override
{
if (has_been_read)
return Block();
has_been_read = true;
many_data.resize(max_threads);
exceptions.resize(max_threads);
for (size_t i = 0; i < max_threads; ++i)
threads_data.emplace_back(keys_size, aggregates_size);
LOG_TRACE(log, "Aggregating");
Stopwatch watch;
for (auto & elem : many_data)
elem = new AggregatedDataVariants;
processor.process();
processor.wait();
rethrowFirstException(exceptions);
if (isCancelled())
return Block();
double elapsed_seconds = watch.elapsedSeconds();
size_t total_src_rows = 0;
size_t total_src_bytes = 0;
for (size_t i = 0; i < max_threads; ++i)
if (!executed)
{
size_t rows = many_data[i]->size();
LOG_TRACE(log, std::fixed << std::setprecision(3)
<< "Aggregated. " << threads_data[i].src_rows << " to " << rows << " rows"
<< " (from " << threads_data[i].src_bytes / 1048576.0 << " MiB)"
<< " in " << elapsed_seconds << " sec."
<< " (" << threads_data[i].src_rows / elapsed_seconds << " rows/sec., "
<< threads_data[i].src_bytes / elapsed_seconds / 1048576.0 << " MiB/sec.)");
executed = true;
AggregatedDataVariantsPtr data_variants = executeAndMerge();
total_src_rows += threads_data[i].src_rows;
total_src_bytes += threads_data[i].src_bytes;
if (data_variants)
blocks = aggregator.convertToBlocks(*data_variants, final, max_threads);
it = blocks.begin();
}
LOG_TRACE(log, std::fixed << std::setprecision(3)
<< "Total aggregated. " << total_src_rows << " rows (from " << total_src_bytes / 1048576.0 << " MiB)"
<< " in " << elapsed_seconds << " sec."
<< " (" << total_src_rows / elapsed_seconds << " rows/sec., " << total_src_bytes / elapsed_seconds / 1048576.0 << " MiB/sec.)");
AggregatedDataVariantsPtr res = aggregator->merge(many_data);
Block res;
if (isCancelled() || it == blocks.end())
return res;
if (isCancelled())
return Block();
res = *it;
++it;
return aggregator->convertToBlock(*res, final);
return res;
}
private:
SharedPtr<Aggregator> aggregator;
bool has_been_read;
Aggregator aggregator;
bool final;
size_t max_threads;
@ -148,6 +113,10 @@ private:
*/
bool no_more_keys = false;
bool executed = false;
BlocksList blocks;
BlocksList::iterator it;
Logger * log = &Logger::get("ParallelAggregatingBlockInputStream");
@ -158,7 +127,7 @@ private:
void onBlock(Block & block, size_t thread_num)
{
parent.aggregator->executeOnBlock(block, *parent.many_data[thread_num],
parent.aggregator.executeOnBlock(block, *parent.many_data[thread_num],
parent.threads_data[thread_num].key_columns, parent.threads_data[thread_num].aggregate_columns,
parent.threads_data[thread_num].key_sizes, parent.threads_data[thread_num].key, parent.no_more_keys);
@ -205,6 +174,57 @@ private:
};
std::vector<ThreadData> threads_data;
AggregatedDataVariantsPtr executeAndMerge()
{
many_data.resize(max_threads);
exceptions.resize(max_threads);
for (size_t i = 0; i < max_threads; ++i)
threads_data.emplace_back(keys_size, aggregates_size);
LOG_TRACE(log, "Aggregating");
Stopwatch watch;
for (auto & elem : many_data)
elem = new AggregatedDataVariants;
processor.process();
processor.wait();
rethrowFirstException(exceptions);
if (isCancelled())
return nullptr;
double elapsed_seconds = watch.elapsedSeconds();
size_t total_src_rows = 0;
size_t total_src_bytes = 0;
for (size_t i = 0; i < max_threads; ++i)
{
size_t rows = many_data[i]->size();
LOG_TRACE(log, std::fixed << std::setprecision(3)
<< "Aggregated. " << threads_data[i].src_rows << " to " << rows << " rows"
<< " (from " << threads_data[i].src_bytes / 1048576.0 << " MiB)"
<< " in " << elapsed_seconds << " sec."
<< " (" << threads_data[i].src_rows / elapsed_seconds << " rows/sec., "
<< threads_data[i].src_bytes / elapsed_seconds / 1048576.0 << " MiB/sec.)");
total_src_rows += threads_data[i].src_rows;
total_src_bytes += threads_data[i].src_bytes;
}
LOG_TRACE(log, std::fixed << std::setprecision(3)
<< "Total aggregated. " << total_src_rows << " rows (from " << total_src_bytes / 1048576.0 << " MiB)"
<< " in " << elapsed_seconds << " sec."
<< " (" << total_src_rows / elapsed_seconds << " rows/sec., " << total_src_bytes / elapsed_seconds / 1048576.0 << " MiB/sec.)");
if (isCancelled())
return nullptr;
return aggregator.merge(many_data, max_threads);
}
};
}

View File

@ -18,13 +18,13 @@ namespace DB
class PushingToViewsBlockOutputStream : public IBlockOutputStream
{
public:
PushingToViewsBlockOutputStream(String database_, String table_, const Context & context_, ASTPtr query_ptr_)
: database(database_), table(table_), context(context_), query_ptr(query_ptr_)
PushingToViewsBlockOutputStream(String database, String table, const Context & context_, ASTPtr query_ptr_)
: context(context_), query_ptr(query_ptr_)
{
storage = context.getTable(database, table);
addTableLock(storage->lockStructure(true));
Dependencies dependencies = context.getDependencies(DatabaseAndTableName(database, table));
Dependencies dependencies = context.getDependencies(database, table);
for (size_t i = 0; i < dependencies.size(); ++i)
{
children.push_back(new PushingToViewsBlockOutputStream(dependencies[i].first, dependencies[i].second, context, ASTPtr()));
@ -64,8 +64,6 @@ public:
private:
StoragePtr storage;
BlockOutputStreamPtr output;
String database;
String table;
Context context;
ASTPtr query_ptr;
std::vector<BlockOutputStreamPtr> children;

View File

@ -3,7 +3,9 @@
#include <Yandex/logger_useful.h>
#include <DB/DataStreams/IProfilingBlockInputStream.h>
#include <DB/DataStreams/OneBlockInputStream.h>
#include <DB/Common/VirtualColumnUtils.h>
#include <DB/Interpreters/Context.h>
#include <DB/Client/ConnectionPool.h>
@ -30,7 +32,7 @@ public:
/// Принимает готовое соединение.
RemoteBlockInputStream(Connection & connection_, const String & query_, const Settings * settings_,
const Tables & external_tables_ = Tables(), QueryProcessingStage::Enum stage_ = QueryProcessingStage::Complete,
const Context & context = Context{})
const Context & context = getDefaultContext())
: connection(&connection_), query(query_), external_tables(external_tables_), stage(stage_), context(context)
{
init(settings_);
@ -39,7 +41,7 @@ public:
/// Принимает готовое соединение. Захватывает владение соединением из пула.
RemoteBlockInputStream(ConnectionPool::Entry & pool_entry_, const String & query_, const Settings * settings_,
const Tables & external_tables_ = Tables(), QueryProcessingStage::Enum stage_ = QueryProcessingStage::Complete,
const Context & context = Context{})
const Context & context = getDefaultContext())
: pool_entry(pool_entry_), connection(&*pool_entry_), query(query_),
external_tables(external_tables_), stage(stage_), context(context)
{
@ -49,7 +51,7 @@ public:
/// Принимает пул, из которого нужно будет достать соединение.
RemoteBlockInputStream(IConnectionPool * pool_, const String & query_, const Settings * settings_,
const Tables & external_tables_ = Tables(), QueryProcessingStage::Enum stage_ = QueryProcessingStage::Complete,
const Context & context = Context{})
const Context & context = getDefaultContext())
: pool(pool_), query(query_), external_tables(external_tables_), stage(stage_), context(context)
{
init(settings_);
@ -273,6 +275,13 @@ private:
bool got_exception_from_server = false;
Logger * log = &Logger::get("RemoteBlockInputStream");
/// ITable::read requires a Context, therefore we should create one if the user can't supply it
static Context & getDefaultContext()
{
static Context instance;
return instance;
}
};
}

View File

@ -1,83 +0,0 @@
#pragma once
#include <DB/Interpreters/SplittingAggregator.h>
#include <DB/DataStreams/IProfilingBlockInputStream.h>
namespace DB
{
using Poco::SharedPtr;
class SplittingAggregatingBlockInputStream : public IProfilingBlockInputStream
{
public:
SplittingAggregatingBlockInputStream(
BlockInputStreamPtr input_, const ColumnNumbers & keys_, AggregateDescriptions & aggregates_, size_t threads_,
bool with_totals_, bool separate_totals_, bool final_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_)
: started(false), separate_totals(separate_totals_), final(final_),
aggregator(new SplittingAggregator(keys_, aggregates_, threads_, with_totals_, max_rows_to_group_by_, group_by_overflow_mode_)),
current_result(results.end())
{
children.push_back(input_);
}
/** keys берутся из GROUP BY части запроса
* Агрегатные функции ищутся везде в выражении.
* Столбцы, соответствующие keys и аргументам агрегатных функций, уже должны быть вычислены.
*/
SplittingAggregatingBlockInputStream(
BlockInputStreamPtr input_, const Names & key_names, const AggregateDescriptions & aggregates, size_t threads_,
bool with_totals_, bool separate_totals_, bool final_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_)
: started(false), separate_totals(separate_totals_), final(final_),
aggregator(new SplittingAggregator(key_names, aggregates, threads_, with_totals_, max_rows_to_group_by_, group_by_overflow_mode_)),
current_result(results.end())
{
children.push_back(input_);
}
String getName() const override { return "SplittingAggregatingBlockInputStream"; }
String getID() const override
{
std::stringstream res;
res << "SplittingAggregating(" << children.back()->getID() << ", " << aggregator->getID() << ")";
return res.str();
}
protected:
Block readImpl() override
{
if (!started)
{
started = true;
ManyAggregatedDataVariants data;
aggregator->execute(children.back(), data);
if (isCancelled())
return Block();
aggregator->convertToBlocks(data, results, final);
current_result = results.begin();
}
if (current_result == results.end())
return Block();
Block res = *current_result;
++current_result;
return res;
}
bool started;
bool separate_totals; /// TODO
bool final;
SharedPtr<SplittingAggregator> aggregator;
Blocks results;
Blocks::iterator current_result;
};
}

View File

@ -20,8 +20,8 @@ class TotalsHavingBlockInputStream : public IProfilingBlockInputStream
public:
TotalsHavingBlockInputStream(BlockInputStreamPtr input_, const Names & keys_names_,
const AggregateDescriptions & aggregates_, bool overflow_row_, ExpressionActionsPtr expression_,
const std::string & filter_column_, TotalsMode totals_mode_, float auto_include_threshold_)
: aggregator(new Aggregator(keys_names_, aggregates_, overflow_row_)), overflow_row(overflow_row_),
const std::string & filter_column_, TotalsMode totals_mode_, double auto_include_threshold_)
: overflow_row(overflow_row_),
expression(expression_), filter_column_name(filter_column_), totals_mode(totals_mode_),
auto_include_threshold(auto_include_threshold_), passed_keys(0), total_keys(0)
{
@ -33,7 +33,7 @@ public:
String getID() const override
{
std::stringstream res;
res << "TotalsHavingBlockInputStream(" << children.back()->getID() << ", " << aggregator->getID()
res << "TotalsHavingBlockInputStream(" << children.back()->getID()
<< "," << filter_column_name << ")";
return res.str();
}
@ -44,24 +44,24 @@ protected:
Block readImpl() override;
private:
SharedPtr<Aggregator> aggregator;
bool overflow_row;
ExpressionActionsPtr expression;
String filter_column_name;
TotalsMode totals_mode;
float auto_include_threshold;
double auto_include_threshold;
size_t passed_keys;
size_t total_keys;
Block current_totals;
/** Здесь находятся значения, не прошедшие max_rows_to_group_by.
* Они прибавляются или не прибавляются к current_totals в зависимости от totals_mode.
*/
Block overflow_aggregates;
void addToTotals(Block & totals, Block & block, const IColumn::Filter * filter, size_t rows);
/// Здесь накапливаются тотальные значения. После окончания работы, они будут помещены в IProfilingBlockInputStream::totals.
Block current_totals;
void addToTotals(Block & totals, Block & block, const IColumn::Filter * filter)
{
addToTotals(totals, block, filter, block.rows());
}
/// Если filter == nullptr - прибавлять все строки. Иначе - только строки, проходящие фильтр (HAVING).
void addToTotals(Block & totals, Block & block, const IColumn::Filter * filter);
};
}

View File

@ -13,7 +13,7 @@ namespace DB
/** Копирует данные из InputStream в OutputStream
* (например, из БД в консоль и т. п.)
*/
void copyData(IBlockInputStream & from, IBlockOutputStream & to);
void copyData(IBlockInputStream & from, IBlockOutputStream & to, volatile bool * is_cancelled = nullptr);
void copyData(IRowInputStream & from, IRowOutputStream & to);
}

View File

@ -463,8 +463,6 @@ public:
ErrorCodes::ILLEGAL_COLUMN);
}
};
class FunctionIPv4NumToString : public IFunction
{
public:
@ -661,6 +659,115 @@ public:
};
class FunctionIPv4NumToStringClassC : public IFunction
{
public:
static constexpr auto name = "IPv4NumToStringClassC";
static IFunction * create(const Context & context) { return new FunctionIPv4NumToStringClassC; }
/// Получить имя функции.
String getName() const
{
return name;
}
/// Получить тип результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение.
DataTypePtr getReturnType(const DataTypes & arguments) const
{
if (arguments.size() != 1)
throw Exception("Number of arguments for function " + getName() + " doesn't match: passed "
+ toString(arguments.size()) + ", should be 1.",
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
if (!typeid_cast<const DataTypeUInt32 *>(&*arguments[0]))
throw Exception("Illegal type " + arguments[0]->getName() + " of argument of function " + getName() + ", expected UInt32",
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
return new DataTypeString;
}
static void formatIP(UInt32 ip, char *& out)
{
char * begin = out;
for (auto i = 0; i < 3; ++i)
*(out++) = 'x';
/// Запишем все задом наперед.
for (size_t offset = 8; offset <= 24; offset += 8)
{
if (offset > 0)
*(out++) = '.';
/// Достаем очередной байт.
UInt32 value = (ip >> offset) & static_cast<UInt32>(255);
/// Быстрее, чем sprintf.
if (value == 0)
{
*(out++) = '0';
}
else
{
while (value > 0)
{
*(out++) = '0' + value % 10;
value /= 10;
}
}
}
/// И развернем.
std::reverse(begin, out);
*(out++) = '\0';
}
/// Выполнить функцию над блоком.
void execute(Block & block, const ColumnNumbers & arguments, size_t result)
{
const ColumnPtr column = block.getByPosition(arguments[0]).column;
if (const ColumnVector<UInt32> * col = typeid_cast<const ColumnVector<UInt32> *>(&*column))
{
const ColumnVector<UInt32>::Container_t & vec_in = col->getData();
ColumnString * col_res = new ColumnString;
block.getByPosition(result).column = col_res;
ColumnString::Chars_t & vec_res = col_res->getChars();
ColumnString::Offsets_t & offsets_res = col_res->getOffsets();
vec_res.resize(vec_in.size() * INET_ADDRSTRLEN); /// самое длинное значение: 255.255.255.255\0
offsets_res.resize(vec_in.size());
char * begin = reinterpret_cast<char *>(&vec_res[0]);
char * pos = begin;
for (size_t i = 0; i < vec_in.size(); ++i)
{
formatIP(vec_in[i], pos);
offsets_res[i] = pos - begin;
}
vec_res.resize(pos - begin);
}
else if (const ColumnConst<UInt32> * col = typeid_cast<const ColumnConst<UInt32> *>(&*column))
{
char buf[16];
char * pos = buf;
formatIP(col->getData(), pos);
ColumnConstString * col_res = new ColumnConstString(col->size(), buf);
block.getByPosition(result).column = col_res;
}
else
throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName()
+ " of argument of function " + getName(),
ErrorCodes::ILLEGAL_COLUMN);
}
};
class FunctionHex : public IFunction
{
public:

View File

@ -1379,7 +1379,7 @@ private:
src_offset = src_offsets[i];
dst_offset += src_length;
if (dst_data[dst_offset - 2] != trailing_char_str.front())
if (src_length == 1 || dst_data[dst_offset - 2] != trailing_char_str.front())
{
dst_data[dst_offset - 1] = trailing_char_str.front();
dst_data[dst_offset] = 0;
@ -1397,6 +1397,7 @@ private:
block.getByPosition(result).column = new ColumnConstString{
col->size(),
in_data.size() == 0 ? trailing_char_str :
in_data.back() == trailing_char_str.front() ? in_data : in_data + trailing_char_str
};
}

View File

@ -280,7 +280,7 @@ struct ExtractPathFull
if (nullptr != (pos = strchr(data, '/')) && pos[1] == '/' && nullptr != (pos = strchr(pos + 2, '/')))
{
/// no leading slash
res_data = pos + 1;
res_data = pos;
res_size = end - res_data;
}
}

View File

@ -42,43 +42,43 @@ struct BinaryManipReadBuffer : ReadBuffer {};
template <typename T> WriteBuffer & operator<< (WriteBuffer & buf, const T & x) { writeText(x, buf); return buf; }
/// Если не использовать манипуляторы, строка выводится без экранирования, как есть.
template <> WriteBuffer & operator<< (WriteBuffer & buf, const String & x) { writeString(x, buf); return buf; }
template <> WriteBuffer & operator<< (WriteBuffer & buf, const char & x) { writeChar(x, buf); return buf; }
template <> inline WriteBuffer & operator<< (WriteBuffer & buf, const String & x) { writeString(x, buf); return buf; }
template <> inline WriteBuffer & operator<< (WriteBuffer & buf, const char & x) { writeChar(x, buf); return buf; }
WriteBuffer & operator<< (WriteBuffer & buf, const char * x) { writeCString(x, buf); return buf; }
inline WriteBuffer & operator<< (WriteBuffer & buf, const char * x) { writeCString(x, buf); return buf; }
EscapeManipWriteBuffer & operator<< (WriteBuffer & buf, EscapeManip x) { return static_cast<EscapeManipWriteBuffer &>(buf); }
QuoteManipWriteBuffer & operator<< (WriteBuffer & buf, QuoteManip x) { return static_cast<QuoteManipWriteBuffer &>(buf); }
DoubleQuoteManipWriteBuffer & operator<< (WriteBuffer & buf, DoubleQuoteManip x) { return static_cast<DoubleQuoteManipWriteBuffer &>(buf); }
BinaryManipWriteBuffer & operator<< (WriteBuffer & buf, BinaryManip x) { return static_cast<BinaryManipWriteBuffer &>(buf); }
inline EscapeManipWriteBuffer & operator<< (WriteBuffer & buf, EscapeManip x) { return static_cast<EscapeManipWriteBuffer &>(buf); }
inline QuoteManipWriteBuffer & operator<< (WriteBuffer & buf, QuoteManip x) { return static_cast<QuoteManipWriteBuffer &>(buf); }
inline DoubleQuoteManipWriteBuffer & operator<< (WriteBuffer & buf, DoubleQuoteManip x) { return static_cast<DoubleQuoteManipWriteBuffer &>(buf); }
inline BinaryManipWriteBuffer & operator<< (WriteBuffer & buf, BinaryManip x) { return static_cast<BinaryManipWriteBuffer &>(buf); }
template <typename T> WriteBuffer & operator<< (EscapeManipWriteBuffer & buf, const T & x) { writeText(x, buf); return buf; }
template <typename T> WriteBuffer & operator<< (QuoteManipWriteBuffer & buf, const T & x) { writeQuoted(x, buf); return buf; }
template <typename T> WriteBuffer & operator<< (DoubleQuoteManipWriteBuffer & buf, const T & x) { writeDoubleQuoted(x, buf); return buf; }
template <typename T> WriteBuffer & operator<< (BinaryManipWriteBuffer & buf, const T & x) { writeBinary(x, buf); return buf; }
WriteBuffer & operator<< (EscapeManipWriteBuffer & buf, const char * x) { writeAnyEscapedString<'\''>(x, x + strlen(x), buf); return buf; }
WriteBuffer & operator<< (QuoteManipWriteBuffer & buf, const char * x) { writeAnyQuotedString<'\''>(x, x + strlen(x), buf); return buf; }
WriteBuffer & operator<< (DoubleQuoteManipWriteBuffer & buf, const char * x) { writeAnyQuotedString<'"'>(x, x + strlen(x), buf); return buf; }
WriteBuffer & operator<< (BinaryManipWriteBuffer & buf, const char * x) { writeStringBinary(x, buf); return buf; }
inline WriteBuffer & operator<< (EscapeManipWriteBuffer & buf, const char * x) { writeAnyEscapedString<'\''>(x, x + strlen(x), buf); return buf; }
inline WriteBuffer & operator<< (QuoteManipWriteBuffer & buf, const char * x) { writeAnyQuotedString<'\''>(x, x + strlen(x), buf); return buf; }
inline WriteBuffer & operator<< (DoubleQuoteManipWriteBuffer & buf, const char * x) { writeAnyQuotedString<'"'>(x, x + strlen(x), buf); return buf; }
inline WriteBuffer & operator<< (BinaryManipWriteBuffer & buf, const char * x) { writeStringBinary(x, buf); return buf; }
/// Манипулятор вызывает у WriteBuffer метод next - это делает сброс буфера. Для вложенных буферов, сброс не рекурсивный.
enum FlushManip { flush };
WriteBuffer & operator<< (WriteBuffer & buf, FlushManip x) { buf.next(); return buf; }
inline WriteBuffer & operator<< (WriteBuffer & buf, FlushManip x) { buf.next(); return buf; }
template <typename T> ReadBuffer & operator>> (ReadBuffer & buf, T & x) { readText(x, buf); return buf; }
template <> ReadBuffer & operator>> (ReadBuffer & buf, String & x) { readString(x, buf); return buf; }
template <> ReadBuffer & operator>> (ReadBuffer & buf, char & x) { readChar(x, buf); return buf; }
template <> inline ReadBuffer & operator>> (ReadBuffer & buf, String & x) { readString(x, buf); return buf; }
template <> inline ReadBuffer & operator>> (ReadBuffer & buf, char & x) { readChar(x, buf); return buf; }
/// Если указать для чтения строковый литерал, то это будет обозначать - убедиться в наличии последовательности байт и пропустить её.
ReadBuffer & operator>> (ReadBuffer & buf, const char * x) { assertString(x, buf); return buf; }
inline ReadBuffer & operator>> (ReadBuffer & buf, const char * x) { assertString(x, buf); return buf; }
EscapeManipReadBuffer & operator>> (ReadBuffer & buf, EscapeManip x) { return static_cast<EscapeManipReadBuffer &>(buf); }
QuoteManipReadBuffer & operator>> (ReadBuffer & buf, QuoteManip x) { return static_cast<QuoteManipReadBuffer &>(buf); }
DoubleQuoteManipReadBuffer & operator>> (ReadBuffer & buf, DoubleQuoteManip x) { return static_cast<DoubleQuoteManipReadBuffer &>(buf); }
BinaryManipReadBuffer & operator>> (ReadBuffer & buf, BinaryManip x) { return static_cast<BinaryManipReadBuffer &>(buf); }
inline EscapeManipReadBuffer & operator>> (ReadBuffer & buf, EscapeManip x) { return static_cast<EscapeManipReadBuffer &>(buf); }
inline QuoteManipReadBuffer & operator>> (ReadBuffer & buf, QuoteManip x) { return static_cast<QuoteManipReadBuffer &>(buf); }
inline DoubleQuoteManipReadBuffer & operator>> (ReadBuffer & buf, DoubleQuoteManip x) { return static_cast<DoubleQuoteManipReadBuffer &>(buf); }
inline BinaryManipReadBuffer & operator>> (ReadBuffer & buf, BinaryManip x) { return static_cast<BinaryManipReadBuffer &>(buf); }
template <typename T> ReadBuffer & operator>> (EscapeManipReadBuffer & buf, T & x) { readText(x, buf); return buf; }
template <typename T> ReadBuffer & operator>> (QuoteManipReadBuffer & buf, T & x) { readQuoted(x, buf); return buf; }

View File

@ -0,0 +1,22 @@
#pragma once
#include <DB/Core/ColumnNumbers.h>
#include <DB/Core/Names.h>
#include <DB/AggregateFunctions/IAggregateFunction.h>
namespace DB
{
struct AggregateDescription
{
AggregateFunctionPtr function;
Array parameters; /// Параметры (параметрической) агрегатной функции.
ColumnNumbers arguments;
Names argument_names; /// Используются, если arguments не заданы.
String column_name; /// Какое имя использовать для столбца со значениями агрегатной функции
};
typedef std::vector<AggregateDescription> AggregateDescriptions;
}

View File

@ -9,7 +9,6 @@
#include <DB/Core/Defines.h>
#include <DB/Core/StringRef.h>
#include <DB/Columns/IColumn.h>
#include <DB/Common/HashTable/HashMap.h>
template <>

View File

@ -1,23 +1,22 @@
#pragma once
#include <map>
#include <unordered_map>
#include <Poco/Mutex.h>
#include <mutex>
#include <memory>
#include <Yandex/logger_useful.h>
#include <statdaemons/threadpool.hpp>
#include <DB/Core/ColumnNumbers.h>
#include <DB/Core/Names.h>
#include <DB/Core/StringRef.h>
#include <DB/Common/Arena.h>
#include <DB/Common/HashTable/HashMap.h>
#include <DB/Common/HashTable/TwoLevelHashMap.h>
#include <DB/DataStreams/IBlockInputStream.h>
#include <DB/AggregateFunctions/IAggregateFunction.h>
#include <DB/Interpreters/AggregateDescription.h>
#include <DB/Interpreters/AggregationCommon.h>
#include <DB/Interpreters/Limits.h>
#include <DB/Interpreters/Compiler.h>
#include <DB/Columns/ColumnString.h>
#include <DB/Columns/ColumnFixedString.h>
@ -30,29 +29,36 @@ namespace DB
{
struct AggregateDescription
{
AggregateFunctionPtr function;
Array parameters; /// Параметры (параметрической) агрегатной функции.
ColumnNumbers arguments;
Names argument_names; /// Используются, если arguments не заданы.
String column_name; /// Какое имя использовать для столбца со значениями агрегатной функции
};
typedef std::vector<AggregateDescription> AggregateDescriptions;
/** Разные структуры данных, которые могут использоваться для агрегации
* Для эффективности сами данные для агрегации кладутся в пул.
* Для эффективности, сами данные для агрегации кладутся в пул.
* Владение данными (состояний агрегатных функций) и пулом
* захватывается позднее - в функции convertToBlock, объектом ColumnAggregateFunction.
* захватывается позднее - в функции convertToBlocks, объектом ColumnAggregateFunction.
*
* Большинство структур данных существует в двух вариантах: обычном и двухуровневом (TwoLevel).
* Двухуровневая хэш-таблица работает чуть медленнее при маленьком количестве различных ключей,
* но при большом количестве различных ключей лучше масштабируется, так как позволяет
* распараллелить некоторые операции (слияние, пост-обработку) естественным образом.
*
* Чтобы обеспечить эффективную работу в большом диапазоне условий,
* сначала используются одноуровневые хэш-таблицы,
* а при достижении количеством различных ключей достаточно большого размера,
* они конвертируются в двухуровневые.
*
* PS. Существует много различных подходов к эффективной реализации параллельной и распределённой агрегации,
* лучшим образом подходящих для разных случаев, и этот подход - всего лишь один из них, выбранный по совокупности причин.
*/
typedef AggregateDataPtr AggregatedDataWithoutKey;
typedef HashMap<UInt64, AggregateDataPtr> AggregatedDataWithUInt64Key;
typedef HashMap<UInt64, AggregateDataPtr, HashCRC32<UInt64>> AggregatedDataWithUInt64Key;
typedef HashMapWithSavedHash<StringRef, AggregateDataPtr> AggregatedDataWithStringKey;
typedef HashMap<UInt128, AggregateDataPtr, UInt128Hash> AggregatedDataWithKeys128;
typedef HashMap<UInt128, AggregateDataPtr, UInt128HashCRC32> AggregatedDataWithKeys128;
typedef HashMap<UInt128, std::pair<StringRef*, AggregateDataPtr>, UInt128TrivialHash> AggregatedDataHashed;
typedef TwoLevelHashMap<UInt64, AggregateDataPtr, HashCRC32<UInt64>> AggregatedDataWithUInt64KeyTwoLevel;
typedef TwoLevelHashMapWithSavedHash<StringRef, AggregateDataPtr> AggregatedDataWithStringKeyTwoLevel;
typedef TwoLevelHashMap<UInt128, AggregateDataPtr, UInt128HashCRC32> AggregatedDataWithKeys128TwoLevel;
typedef TwoLevelHashMap<UInt128, std::pair<StringRef*, AggregateDataPtr>, UInt128TrivialHash> AggregatedDataHashedTwoLevel;
/// Специализации для UInt8, UInt16.
struct TrivialHash
@ -64,7 +70,10 @@ struct TrivialHash
}
};
/// Превращает хэш-таблицу в что-то типа lookup-таблицы. Остаётся неоптимальность - в ячейках хранятся ключи.
/** Превращает хэш-таблицу в что-то типа lookup-таблицы. Остаётся неоптимальность - в ячейках хранятся ключи.
* Также компилятору не удаётся полностью удалить код хождения по цепочке разрешения коллизий, хотя он не нужен.
* TODO Переделать в полноценную lookup-таблицу.
*/
template <size_t key_bits>
struct HashTableFixedGrower
{
@ -82,89 +91,11 @@ struct HashTableFixedGrower
typedef HashMap<UInt64, AggregateDataPtr, TrivialHash, HashTableFixedGrower<8>> AggregatedDataWithUInt8Key;
typedef HashMap<UInt64, AggregateDataPtr, TrivialHash, HashTableFixedGrower<16>> AggregatedDataWithUInt16Key;
template <typename FieldType>
struct AggregatedDataWithUIntKey
{
using Type = AggregatedDataWithUInt64Key;
static constexpr bool never_overflows = false;
};
template <>
struct AggregatedDataWithUIntKey<UInt8>
{
using Type = AggregatedDataWithUInt8Key;
static constexpr bool never_overflows = true; /// Говорит о том, что в результате агрегации не может быть много записей.
};
template <typename T>
inline UInt64 unionCastToUInt64(T x) { return x; }
template <>
struct AggregatedDataWithUIntKey<UInt16>
{
using Type = AggregatedDataWithUInt16Key;
static constexpr bool never_overflows = true;
};
/// Для случая, когда есть один числовой ключ.
template <typename FieldType> /// UInt8/16/32/64 для любых типов соответствующей битности.
struct AggregationMethodOneNumber
{
typedef typename AggregatedDataWithUIntKey<FieldType>::Type Data;
typedef typename Data::key_type Key;
typedef typename Data::mapped_type Mapped;
typedef typename Data::iterator iterator;
typedef typename Data::const_iterator const_iterator;
static constexpr bool never_overflows = AggregatedDataWithUIntKey<FieldType>::never_overflows;
Data data;
const FieldType * column;
/** Вызывается в начале обработки каждого блока.
* Устанавливает переменные, необходимые для остальных методов, вызываемых во внутренних циклах.
*/
void init(ConstColumnPlainPtrs & key_columns)
{
column = &static_cast<const ColumnVector<FieldType> *>(key_columns[0])->getData()[0];
}
/// Достать из ключевых столбцов ключ для вставки в хэш-таблицу.
Key getKey(
const ConstColumnPlainPtrs & key_columns, /// Ключевые столбцы.
size_t keys_size, /// Количество ключевых столбцов.
size_t i, /// Из какой строки блока достать ключ.
const Sizes & key_sizes, /// Если ключи фиксированной длины - их длины. Не используется в методах агрегации по ключам переменной длины.
StringRefs & keys) const /// Сюда могут быть записаны ссылки на данные ключей в столбцах. Они могут быть использованы в дальнейшем.
{
return get64(column[i]);
}
/// Из значения в хэш-таблице получить AggregateDataPtr.
static AggregateDataPtr & getAggregateData(Mapped & value) { return value; }
static const AggregateDataPtr & getAggregateData(const Mapped & value) { return value; }
/** Разместить дополнительные данные, если это необходимо, в случае, когда в хэш-таблицу был вставлен новый ключ.
*/
static void onNewKey(iterator & it, size_t keys_size, size_t i, StringRefs & keys, Arena & pool)
{
}
/** Вставить ключ из хэш-таблицы в столбцы.
*/
static void insertKeyIntoColumns(const_iterator & it, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes)
{
static_cast<ColumnVector<FieldType> *>(key_columns[0])->insertData(reinterpret_cast<const char *>(&it->first), sizeof(it->first));
}
private:
UInt64 get64(FieldType x) const
{
return x;
}
};
template <>
inline UInt64 AggregationMethodOneNumber<Float64>::get64(Float64 x) const
template <> inline UInt64 unionCastToUInt64(Float64 x)
{
union
{
@ -176,8 +107,7 @@ inline UInt64 AggregationMethodOneNumber<Float64>::get64(Float64 x) const
return res;
}
template <>
inline UInt64 AggregationMethodOneNumber<Float32>::get64(Float32 x) const
template <> inline UInt64 unionCastToUInt64(Float32 x)
{
union
{
@ -191,19 +121,86 @@ inline UInt64 AggregationMethodOneNumber<Float32>::get64(Float32 x) const
}
/// Для случая, когда есть один строковый ключ.
struct AggregationMethodString
/// Для случая, когда есть один числовой ключ.
template <typename FieldType, typename TData> /// UInt8/16/32/64 для любых типов соответствующей битности.
struct AggregationMethodOneNumber
{
typedef AggregatedDataWithStringKey Data;
typedef Data::key_type Key;
typedef Data::mapped_type Mapped;
typedef Data::iterator iterator;
typedef Data::const_iterator const_iterator;
static constexpr bool never_overflows = false;
typedef TData Data;
typedef typename Data::key_type Key;
typedef typename Data::mapped_type Mapped;
typedef typename Data::iterator iterator;
typedef typename Data::const_iterator const_iterator;
Data data;
AggregationMethodOneNumber() {}
template <typename Other>
AggregationMethodOneNumber(const Other & other) : data(other.data) {}
/// Для использования одного Method в разных потоках, используйте разные State.
struct State
{
const FieldType * vec;
/** Вызывается в начале обработки каждого блока.
* Устанавливает переменные, необходимые для остальных методов, вызываемых во внутренних циклах.
*/
void init(ConstColumnPlainPtrs & key_columns)
{
vec = &static_cast<const ColumnVector<FieldType> *>(key_columns[0])->getData()[0];
}
/// Достать из ключевых столбцов ключ для вставки в хэш-таблицу.
Key getKey(
const ConstColumnPlainPtrs & key_columns, /// Ключевые столбцы.
size_t keys_size, /// Количество ключевых столбцов.
size_t i, /// Из какой строки блока достать ключ.
const Sizes & key_sizes, /// Если ключи фиксированной длины - их длины. Не используется в методах агрегации по ключам переменной длины.
StringRefs & keys) const /// Сюда могут быть записаны ссылки на данные ключей в столбцах. Они могут быть использованы в дальнейшем.
{
return unionCastToUInt64(vec[i]);
}
};
/// Из значения в хэш-таблице получить AggregateDataPtr.
static AggregateDataPtr & getAggregateData(Mapped & value) { return value; }
static const AggregateDataPtr & getAggregateData(const Mapped & value) { return value; }
/** Разместить дополнительные данные, если это необходимо, в случае, когда в хэш-таблицу был вставлен новый ключ.
*/
static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, StringRefs & keys, Arena & pool)
{
}
/** Вставить ключ из хэш-таблицы в столбцы.
*/
static void insertKeyIntoColumns(const typename Data::value_type & value, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes)
{
static_cast<ColumnVector<FieldType> *>(key_columns[0])->insertData(reinterpret_cast<const char *>(&value.first), sizeof(value.first));
}
};
/// Для случая, когда есть один строковый ключ.
template <typename TData>
struct AggregationMethodString
{
typedef TData Data;
typedef typename Data::key_type Key;
typedef typename Data::mapped_type Mapped;
typedef typename Data::iterator iterator;
typedef typename Data::const_iterator const_iterator;
Data data;
AggregationMethodString() {}
template <typename Other>
AggregationMethodString(const Other & other) : data(other.data) {}
struct State
{
const ColumnString::Offsets_t * offsets;
const ColumnString::Chars_t * chars;
@ -222,37 +219,46 @@ struct AggregationMethodString
const Sizes & key_sizes,
StringRefs & keys) const
{
return StringRef(&(*chars)[i == 0 ? 0 : (*offsets)[i - 1]], (i == 0 ? (*offsets)[i] : ((*offsets)[i] - (*offsets)[i - 1])) - 1);
return StringRef(
&(*chars)[i == 0 ? 0 : (*offsets)[i - 1]],
(i == 0 ? (*offsets)[i] : ((*offsets)[i] - (*offsets)[i - 1])) - 1);
}
};
static AggregateDataPtr & getAggregateData(Mapped & value) { return value; }
static const AggregateDataPtr & getAggregateData(const Mapped & value) { return value; }
static void onNewKey(iterator & it, size_t keys_size, size_t i, StringRefs & keys, Arena & pool)
static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, StringRefs & keys, Arena & pool)
{
it->first.data = pool.insert(it->first.data, it->first.size);
value.first.data = pool.insert(value.first.data, value.first.size);
}
static void insertKeyIntoColumns(const_iterator & it, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes)
static void insertKeyIntoColumns(const typename Data::value_type & value, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes)
{
key_columns[0]->insertData(it->first.data, it->first.size);
key_columns[0]->insertData(value.first.data, value.first.size);
}
};
/// Для случая, когда есть один строковый ключ фиксированной длины.
template <typename TData>
struct AggregationMethodFixedString
{
typedef AggregatedDataWithStringKey Data;
typedef Data::key_type Key;
typedef Data::mapped_type Mapped;
typedef Data::iterator iterator;
typedef Data::const_iterator const_iterator;
static constexpr bool never_overflows = false;
typedef TData Data;
typedef typename Data::key_type Key;
typedef typename Data::mapped_type Mapped;
typedef typename Data::iterator iterator;
typedef typename Data::const_iterator const_iterator;
Data data;
AggregationMethodFixedString() {}
template <typename Other>
AggregationMethodFixedString(const Other & other) : data(other.data) {}
struct State
{
size_t n;
const ColumnFixedString::Chars_t * chars;
@ -273,35 +279,42 @@ struct AggregationMethodFixedString
{
return StringRef(&(*chars)[i * n], n);
}
};
static AggregateDataPtr & getAggregateData(Mapped & value) { return value; }
static const AggregateDataPtr & getAggregateData(const Mapped & value) { return value; }
static void onNewKey(iterator & it, size_t keys_size, size_t i, StringRefs & keys, Arena & pool)
static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, StringRefs & keys, Arena & pool)
{
it->first.data = pool.insert(it->first.data, it->first.size);
value.first.data = pool.insert(value.first.data, value.first.size);
}
static void insertKeyIntoColumns(const_iterator & it, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes)
static void insertKeyIntoColumns(const typename Data::value_type & value, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes)
{
key_columns[0]->insertData(it->first.data, it->first.size);
key_columns[0]->insertData(value.first.data, value.first.size);
}
};
/// Для случая, когда все ключи фиксированной длины, и они помещаются в 128 бит.
template <typename TData>
struct AggregationMethodKeys128
{
typedef AggregatedDataWithKeys128 Data;
typedef Data::key_type Key;
typedef Data::mapped_type Mapped;
typedef Data::iterator iterator;
typedef Data::const_iterator const_iterator;
static constexpr bool never_overflows = false;
typedef TData Data;
typedef typename Data::key_type Key;
typedef typename Data::mapped_type Mapped;
typedef typename Data::iterator iterator;
typedef typename Data::const_iterator const_iterator;
Data data;
AggregationMethodKeys128() {}
template <typename Other>
AggregationMethodKeys128(const Other & other) : data(other.data) {}
struct State
{
void init(ConstColumnPlainPtrs & key_columns)
{
}
@ -315,21 +328,22 @@ struct AggregationMethodKeys128
{
return pack128(i, keys_size, key_columns, key_sizes);
}
};
static AggregateDataPtr & getAggregateData(Mapped & value) { return value; }
static const AggregateDataPtr & getAggregateData(const Mapped & value) { return value; }
static void onNewKey(iterator & it, size_t keys_size, size_t i, StringRefs & keys, Arena & pool)
static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, StringRefs & keys, Arena & pool)
{
}
static void insertKeyIntoColumns(const_iterator & it, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes)
static void insertKeyIntoColumns(const typename Data::value_type & value, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes)
{
size_t offset = 0;
for (size_t i = 0; i < keys_size; ++i)
{
size_t size = key_sizes[i];
key_columns[i]->insertData(reinterpret_cast<const char *>(&it->first) + offset, size);
key_columns[i]->insertData(reinterpret_cast<const char *>(&value.first) + offset, size);
offset += size;
}
}
@ -337,18 +351,24 @@ struct AggregationMethodKeys128
/// Для остальных случаев. Агрегирует по 128 битному хэшу от ключа. (При этом, строки, содержащие нули посередине, могут склеиться.)
template <typename TData>
struct AggregationMethodHashed
{
typedef AggregatedDataHashed Data;
typedef Data::key_type Key;
typedef Data::mapped_type Mapped;
typedef Data::iterator iterator;
typedef Data::const_iterator const_iterator;
static constexpr bool never_overflows = false;
typedef TData Data;
typedef typename Data::key_type Key;
typedef typename Data::mapped_type Mapped;
typedef typename Data::iterator iterator;
typedef typename Data::const_iterator const_iterator;
Data data;
AggregationMethodHashed() {}
template <typename Other>
AggregationMethodHashed(const Other & other) : data(other.data) {}
struct State
{
void init(ConstColumnPlainPtrs & key_columns)
{
}
@ -362,19 +382,20 @@ struct AggregationMethodHashed
{
return hash128(i, keys_size, key_columns, keys);
}
};
static AggregateDataPtr & getAggregateData(Mapped & value) { return value.second; }
static const AggregateDataPtr & getAggregateData(const Mapped & value) { return value.second; }
static void onNewKey(iterator & it, size_t keys_size, size_t i, StringRefs & keys, Arena & pool)
static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, StringRefs & keys, Arena & pool)
{
it->second.first = placeKeysInPool(i, keys_size, keys, pool);
value.second.first = placeKeysInPool(i, keys_size, keys, pool);
}
static void insertKeyIntoColumns(const_iterator & it, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes)
static void insertKeyIntoColumns(const typename Data::value_type & value, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes)
{
for (size_t i = 0; i < keys_size; ++i)
key_columns[i]->insertDataWithTerminatingZero(it->second.first[i].data, it->second.first[i].size);
key_columns[i]->insertDataWithTerminatingZero(value.second.first[i].data, value.second.first[i].size);
}
};
@ -386,9 +407,10 @@ struct AggregatedDataVariants : private boost::noncopyable
/** Работа с состояниями агрегатных функций в пуле устроена следующим (неудобным) образом:
* - при агрегации, состояния создаются в пуле с помощью функции IAggregateFunction::create (внутри - placement new произвольной структуры);
* - они должны быть затем уничтожены с помощью IAggregateFunction::destroy (внутри - вызов деструктора произвольной структуры);
* - если агрегация завершена, то, в функции Aggregator::convertToBlock, указатели на состояния агрегатных функций
* - если агрегация завершена, то, в функции Aggregator::convertToBlocks, указатели на состояния агрегатных функций
* записываются в ColumnAggregateFunction; ColumnAggregateFunction "захватывает владение" ими, то есть - вызывает destroy в своём деструкторе.
* - если при агрегации, до вызова Aggregator::convertToBlock вылетело исключение, то состояния агрегатных функций всё-равно должны быть уничтожены,
* - если при агрегации, до вызова Aggregator::convertToBlocks вылетело исключение,
* то состояния агрегатных функций всё-равно должны быть уничтожены,
* иначе для сложных состояний (наприемер, AggregateFunctionUniq), будут утечки памяти;
* - чтобы, в этом случае, уничтожить состояния, в деструкторе вызывается метод Aggregator::destroyAggregateStates,
* но только если переменная aggregator (см. ниже) не nullptr;
@ -412,32 +434,53 @@ struct AggregatedDataVariants : private boost::noncopyable
*/
AggregatedDataWithoutKey without_key = nullptr;
std::unique_ptr<AggregationMethodOneNumber<UInt8>> key8;
std::unique_ptr<AggregationMethodOneNumber<UInt16>> key16;
std::unique_ptr<AggregationMethodOneNumber<UInt32>> key32;
std::unique_ptr<AggregationMethodOneNumber<UInt64>> key64;
std::unique_ptr<AggregationMethodString> key_string;
std::unique_ptr<AggregationMethodFixedString> key_fixed_string;
std::unique_ptr<AggregationMethodKeys128> keys128;
std::unique_ptr<AggregationMethodHashed> hashed;
std::unique_ptr<AggregationMethodOneNumber<UInt8, AggregatedDataWithUInt8Key>> key8;
std::unique_ptr<AggregationMethodOneNumber<UInt16, AggregatedDataWithUInt16Key>> key16;
enum Type
std::unique_ptr<AggregationMethodOneNumber<UInt32, AggregatedDataWithUInt64Key>> key32;
std::unique_ptr<AggregationMethodOneNumber<UInt64, AggregatedDataWithUInt64Key>> key64;
std::unique_ptr<AggregationMethodString<AggregatedDataWithStringKey>> key_string;
std::unique_ptr<AggregationMethodFixedString<AggregatedDataWithStringKey>> key_fixed_string;
std::unique_ptr<AggregationMethodKeys128<AggregatedDataWithKeys128>> keys128;
std::unique_ptr<AggregationMethodHashed<AggregatedDataHashed>> hashed;
std::unique_ptr<AggregationMethodOneNumber<UInt32, AggregatedDataWithUInt64KeyTwoLevel>> key32_two_level;
std::unique_ptr<AggregationMethodOneNumber<UInt64, AggregatedDataWithUInt64KeyTwoLevel>> key64_two_level;
std::unique_ptr<AggregationMethodString<AggregatedDataWithStringKeyTwoLevel>> key_string_two_level;
std::unique_ptr<AggregationMethodFixedString<AggregatedDataWithStringKeyTwoLevel>> key_fixed_string_two_level;
std::unique_ptr<AggregationMethodKeys128<AggregatedDataWithKeys128TwoLevel>> keys128_two_level;
std::unique_ptr<AggregationMethodHashed<AggregatedDataHashedTwoLevel>> hashed_two_level;
/// В этом и подобных макросах, вариант without_key не учитывается.
#define APPLY_FOR_AGGREGATED_VARIANTS(M) \
M(key8, false) \
M(key16, false) \
M(key32, false) \
M(key64, false) \
M(key_string, false) \
M(key_fixed_string, false) \
M(keys128, false) \
M(hashed, false) \
M(key32_two_level, true) \
M(key64_two_level, true) \
M(key_string_two_level, true) \
M(key_fixed_string_two_level, true) \
M(keys128_two_level, true) \
M(hashed_two_level, true)
enum class Type
{
EMPTY = 0,
WITHOUT_KEY,
KEY_8,
KEY_16,
KEY_32,
KEY_64,
KEY_STRING,
KEY_FIXED_STRING,
KEYS_128,
HASHED,
without_key,
#define M(NAME, IS_TWO_LEVEL) NAME,
APPLY_FOR_AGGREGATED_VARIANTS(M)
#undef M
};
Type type = EMPTY;
Type type = Type::EMPTY;
AggregatedDataVariants() : aggregates_pools(1, new Arena), aggregates_pool(&*aggregates_pools.back()) {}
bool empty() const { return type == EMPTY; }
bool empty() const { return type == Type::EMPTY; }
~AggregatedDataVariants();
@ -447,16 +490,13 @@ struct AggregatedDataVariants : private boost::noncopyable
switch (type)
{
case EMPTY: break;
case WITHOUT_KEY: break;
case KEY_8: key8 .reset(new decltype(key8)::element_type); break;
case KEY_16: key16 .reset(new decltype(key16)::element_type); break;
case KEY_32: key32 .reset(new decltype(key32)::element_type); break;
case KEY_64: key64 .reset(new decltype(key64)::element_type); break;
case KEY_STRING: key_string .reset(new decltype(key_string)::element_type); break;
case KEY_FIXED_STRING: key_fixed_string.reset(new decltype(key_fixed_string)::element_type); break;
case KEYS_128: keys128 .reset(new decltype(keys128)::element_type); break;
case HASHED: hashed .reset(new decltype(hashed)::element_type); break;
case Type::EMPTY: break;
case Type::without_key: break;
#define M(NAME, IS_TWO_LEVEL) \
case Type::NAME: NAME.reset(new decltype(NAME)::element_type); break;
APPLY_FOR_AGGREGATED_VARIANTS(M)
#undef M
default:
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
@ -467,16 +507,13 @@ struct AggregatedDataVariants : private boost::noncopyable
{
switch (type)
{
case EMPTY: return 0;
case WITHOUT_KEY: return 1;
case KEY_8: return key8->data.size() + (without_key != nullptr);
case KEY_16: return key16->data.size() + (without_key != nullptr);
case KEY_32: return key32->data.size() + (without_key != nullptr);
case KEY_64: return key64->data.size() + (without_key != nullptr);
case KEY_STRING: return key_string->data.size() + (without_key != nullptr);
case KEY_FIXED_STRING: return key_fixed_string->data.size() + (without_key != nullptr);
case KEYS_128: return keys128->data.size() + (without_key != nullptr);
case HASHED: return hashed->data.size() + (without_key != nullptr);
case Type::EMPTY: return 0;
case Type::without_key: return 1;
#define M(NAME, IS_TWO_LEVEL) \
case Type::NAME: return NAME->data.size() + (without_key != nullptr);
APPLY_FOR_AGGREGATED_VARIANTS(M)
#undef M
default:
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
@ -487,38 +524,100 @@ struct AggregatedDataVariants : private boost::noncopyable
{
switch (type)
{
case EMPTY: return "EMPTY";
case WITHOUT_KEY: return "WITHOUT_KEY";
case KEY_8: return "KEY_8";
case KEY_16: return "KEY_16";
case KEY_32: return "KEY_32";
case KEY_64: return "KEY_64";
case KEY_STRING: return "KEY_STRING";
case KEY_FIXED_STRING: return "KEY_FIXED_STRING";
case KEYS_128: return "KEYS_128";
case HASHED: return "HASHED";
case Type::EMPTY: return "EMPTY";
case Type::without_key: return "without_key";
#define M(NAME, IS_TWO_LEVEL) \
case Type::NAME: return #NAME;
APPLY_FOR_AGGREGATED_VARIANTS(M)
#undef M
default:
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
}
}
bool isTwoLevel() const
{
switch (type)
{
case Type::EMPTY: return false;
case Type::without_key: return false;
#define M(NAME, IS_TWO_LEVEL) \
case Type::NAME: return IS_TWO_LEVEL;
APPLY_FOR_AGGREGATED_VARIANTS(M)
#undef M
default:
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
}
}
#define APPLY_FOR_VARIANTS_CONVERTIBLE_TO_TWO_LEVEL(M) \
M(key32) \
M(key64) \
M(key_string) \
M(key_fixed_string) \
M(keys128) \
M(hashed)
#define APPLY_FOR_VARIANTS_NOT_CONVERTIBLE_TO_TWO_LEVEL(M) \
M(key8) \
M(key16) \
bool isConvertibleToTwoLevel() const
{
switch (type)
{
#define M(NAME) \
case Type::NAME: return true;
APPLY_FOR_VARIANTS_CONVERTIBLE_TO_TWO_LEVEL(M)
#undef M
default:
return false;
}
}
void convertToTwoLevel();
#define APPLY_FOR_VARIANTS_TWO_LEVEL(M) \
M(key32_two_level) \
M(key64_two_level) \
M(key_string_two_level) \
M(key_fixed_string_two_level) \
M(keys128_two_level) \
M(hashed_two_level)
};
typedef SharedPtr<AggregatedDataVariants> AggregatedDataVariantsPtr;
typedef std::vector<AggregatedDataVariantsPtr> ManyAggregatedDataVariants;
/** Достать вариант агрегации по его типу. */
template <typename Method> Method & getDataVariant(AggregatedDataVariants & variants);
#define M(NAME, IS_TWO_LEVEL) \
template <> inline decltype(AggregatedDataVariants::NAME)::element_type & getDataVariant<decltype(AggregatedDataVariants::NAME)::element_type>(AggregatedDataVariants & variants) { return *variants.NAME; }
APPLY_FOR_AGGREGATED_VARIANTS(M)
#undef M
/** Агрегирует источник блоков.
*/
class Aggregator
{
public:
Aggregator(const ColumnNumbers & keys_, const AggregateDescriptions & aggregates_, bool overflow_row_,
size_t max_rows_to_group_by_ = 0, OverflowMode group_by_overflow_mode_ = OverflowMode::THROW)
size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_, Compiler * compiler_, UInt32 min_count_to_compile_)
: keys(keys_), aggregates(aggregates_), aggregates_size(aggregates.size()),
overflow_row(overflow_row_),
max_rows_to_group_by(max_rows_to_group_by_), group_by_overflow_mode(group_by_overflow_mode_),
log(&Logger::get("Aggregator"))
compiler(compiler_), min_count_to_compile(min_count_to_compile_)
{
std::sort(keys.begin(), keys.end());
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
@ -526,11 +625,11 @@ public:
}
Aggregator(const Names & key_names_, const AggregateDescriptions & aggregates_, bool overflow_row_,
size_t max_rows_to_group_by_ = 0, OverflowMode group_by_overflow_mode_ = OverflowMode::THROW)
size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_, Compiler * compiler_, UInt32 min_count_to_compile_)
: key_names(key_names_), aggregates(aggregates_), aggregates_size(aggregates.size()),
overflow_row(overflow_row_),
max_rows_to_group_by(max_rows_to_group_by_), group_by_overflow_mode(group_by_overflow_mode_),
log(&Logger::get("Aggregator"))
compiler(compiler_), min_count_to_compile(min_count_to_compile_)
{
std::sort(key_names.begin(), key_names.end());
key_names.erase(std::unique(key_names.begin(), key_names.end()), key_names.end());
@ -540,8 +639,9 @@ public:
/// Агрегировать источник. Получить результат в виде одной из структур данных.
void execute(BlockInputStreamPtr stream, AggregatedDataVariants & result);
typedef std::vector<ConstColumnPlainPtrs> AggregateColumns;
typedef std::vector<ColumnAggregateFunction::Container_t *> AggregateColumnsData;
using AggregateColumns = std::vector<ConstColumnPlainPtrs>;
using AggregateColumnsData = std::vector<ColumnAggregateFunction::Container_t *>;
using AggregateFunctionsPlainPtrs = std::vector<IAggregateFunction *>;
/// Обработать один блок. Вернуть false, если обработку следует прервать (при group_by_overflow_mode = 'break').
bool executeOnBlock(Block & block, AggregatedDataVariants & result,
@ -550,25 +650,25 @@ public:
bool & no_more_keys);
/** Преобразовать структуру данных агрегации в блок.
* Если overflow_row = true, то агрегаты для строк, не попавших в max_rows_to_group_by, кладутся в первую строчку возвращаемого блока.
* Если overflow_row = true, то агрегаты для строк, не попавших в max_rows_to_group_by, кладутся в первый блок.
*
* Если final = false, то в качестве столбцов-агрегатов создаются ColumnAggregateFunction с состоянием вычислений,
* которые могут быть затем объединены с другими состояниями (для распределённой обработки запроса).
* Если final = true, то в качестве столбцов-агрегатов создаются столбцы с готовыми значениями.
*/
Block convertToBlock(AggregatedDataVariants & data_variants, bool final);
BlocksList convertToBlocks(AggregatedDataVariants & data_variants, bool final, size_t max_threads);
/** Объединить несколько структур данных агрегации в одну. (В первый непустой элемент массива.) Все варианты агрегации должны быть одинаковыми!
* После объединения, все стркутуры агрегации (а не только те, в которую они будут слиты) должны жить, пока не будет вызвана функция convertToBlock.
/** Объединить несколько структур данных агрегации в одну. (В первый непустой элемент массива.)
* После объединения, все стркутуры агрегации (а не только те, в которую они будут слиты) должны жить,
* пока не будет вызвана функция convertToBlocks.
* Это нужно, так как в слитом результате могут остаться указатели на память в пуле, которым владеют другие структуры агрегации.
*/
AggregatedDataVariantsPtr merge(ManyAggregatedDataVariants & data_variants);
AggregatedDataVariantsPtr merge(ManyAggregatedDataVariants & data_variants, size_t max_threads);
/** Объединить несколько агрегированных блоков в одну структуру данных.
* (Доагрегировать несколько блоков, которые представляют собой результат независимых агрегаций с удалённых серверов.)
* Если overflow_row = true, то предполагается, что агрегаты для строк, не попавших в max_rows_to_group_by, расположены в первой строке каждого блока.
*/
void merge(BlockInputStreamPtr stream, AggregatedDataVariants & result);
void mergeStream(BlockInputStreamPtr stream, AggregatedDataVariants & result, size_t max_threads);
/// Для IBlockInputStream.
String getID() const;
@ -579,7 +679,7 @@ protected:
ColumnNumbers keys;
Names key_names;
AggregateDescriptions aggregates;
std::vector<IAggregateFunction *> aggregate_functions;
AggregateFunctionsPlainPtrs aggregate_functions;
size_t keys_size;
size_t aggregates_size;
/// Нужно ли класть в AggregatedDataVariants::without_key агрегаты для ключей, не попавших в max_rows_to_group_by.
@ -591,14 +691,40 @@ protected:
/// Для инициализации от первого блока при конкуррентном использовании.
bool initialized = false;
Poco::FastMutex mutex;
std::mutex mutex;
size_t max_rows_to_group_by;
OverflowMode group_by_overflow_mode;
Block sample;
Logger * log;
Logger * log = &Logger::get("Aggregator");
/** Для динамической компиляции, если предусмотрено. */
Compiler * compiler = nullptr;
UInt32 min_count_to_compile;
/** Динамически скомпилированная библиотека для агрегации, если есть.
* Смысл динамической компиляции в том, чтобы специализировать код
* под конкретный список агрегатных функций.
* Это позволяет развернуть цикл по созданию и обновлению состояний агрегатных функций,
* а также использовать вместо виртуальных вызовов inline-код.
*/
struct CompiledData
{
SharedLibraryPtr compiled_aggregator;
/// Получены с помощью dlsym. Нужно ещё сделать reinterpret_cast в указатель на функцию.
const void * compiled_method_ptr = nullptr;
const void * compiled_two_level_method_ptr = nullptr;
};
/// shared_ptr - чтобы передавать в callback, который может пережить Aggregator.
std::shared_ptr<CompiledData> compiled_data { new CompiledData };
bool compiled_if_possible = false;
void compileIfPossible(AggregatedDataVariants::Type type);
/** Если заданы только имена столбцов (key_names, а также aggregates[i].column_name), то вычислить номера столбцов.
* Сформировать блок - пример результата.
@ -618,6 +744,7 @@ protected:
void destroyAllAggregateStates(AggregatedDataVariants & result);
/// Обработать один блок данных, агрегировать данные в хэш-таблицу.
template <typename Method>
void executeImpl(
Method & method,
@ -630,30 +757,132 @@ protected:
bool no_more_keys,
AggregateDataPtr overflow_row) const;
/// Специализация для конкретного значения no_more_keys.
template <bool no_more_keys, typename Method>
void executeImplCase(
Method & method,
typename Method::State & state,
Arena * aggregates_pool,
size_t rows,
ConstColumnPlainPtrs & key_columns,
AggregateColumns & aggregate_columns,
const Sizes & key_sizes,
StringRefs & keys,
AggregateDataPtr overflow_row) const;
/// Для случая, когда нет ключей (всё агрегировать в одну строку).
void executeWithoutKeyImpl(
AggregatedDataWithoutKey & res,
size_t rows,
AggregateColumns & aggregate_columns) const;
public:
/// Шаблоны, инстанцирующиеся путём динамической компиляции кода - см. SpecializedAggregator.h
template <typename Method, typename AggregateFunctionsList>
void executeSpecialized(
Method & method,
Arena * aggregates_pool,
size_t rows,
ConstColumnPlainPtrs & key_columns,
AggregateColumns & aggregate_columns,
const Sizes & key_sizes,
StringRefs & keys,
bool no_more_keys,
AggregateDataPtr overflow_row) const;
template <bool no_more_keys, typename Method, typename AggregateFunctionsList>
void executeSpecializedCase(
Method & method,
typename Method::State & state,
Arena * aggregates_pool,
size_t rows,
ConstColumnPlainPtrs & key_columns,
AggregateColumns & aggregate_columns,
const Sizes & key_sizes,
StringRefs & keys,
AggregateDataPtr overflow_row) const;
template <typename AggregateFunctionsList>
void executeSpecializedWithoutKey(
AggregatedDataWithoutKey & res,
size_t rows,
AggregateColumns & aggregate_columns) const;
protected:
/// Слить данные из хэш-таблицы src в dst.
template <typename Method, typename Table>
void mergeDataImpl(
Table & table_dst,
Table & table_src) const;
void mergeWithoutKeyDataImpl(
ManyAggregatedDataVariants & non_empty_data) const;
template <typename Method>
void mergeSingleLevelDataImpl(
ManyAggregatedDataVariants & non_empty_data) const;
template <typename Method>
void mergeTwoLevelDataImpl(
ManyAggregatedDataVariants & many_data,
boost::threadpool::pool * thread_pool) const;
template <typename Method, typename Table>
void convertToBlockImpl(
Method & method,
Table & data,
ColumnPlainPtrs & key_columns,
AggregateColumnsData & aggregate_columns,
ColumnPlainPtrs & final_aggregate_columns,
const Sizes & key_sizes,
size_t start_row, bool final) const;
bool final) const;
template <typename Method>
void mergeDataImpl(
Method & method_dst,
Method & method_src) const;
template <typename Method>
void mergeStreamsImpl(
template <typename Method, typename Table>
void convertToBlockImplFinal(
Method & method,
Arena * aggregates_pool,
size_t start_row,
size_t rows,
ConstColumnPlainPtrs & key_columns,
Table & data,
ColumnPlainPtrs & key_columns,
ColumnPlainPtrs & final_aggregate_columns,
const Sizes & key_sizes) const;
template <typename Method, typename Table>
void convertToBlockImplNotFinal(
Method & method,
Table & data,
ColumnPlainPtrs & key_columns,
AggregateColumnsData & aggregate_columns,
const Sizes & key_sizes,
StringRefs & keys) const;
const Sizes & key_sizes) const;
template <typename Filler>
Block prepareBlockAndFill(
AggregatedDataVariants & data_variants,
bool final,
size_t rows,
Filler && filler) const;
BlocksList prepareBlocksAndFillWithoutKey(AggregatedDataVariants & data_variants, bool final) const;
BlocksList prepareBlocksAndFillSingleLevel(AggregatedDataVariants & data_variants, bool final) const;
BlocksList prepareBlocksAndFillTwoLevel(AggregatedDataVariants & data_variants, bool final, boost::threadpool::pool * thread_pool) const;
template <typename Method>
BlocksList prepareBlocksAndFillTwoLevelImpl(
AggregatedDataVariants & data_variants,
Method & method,
bool final,
boost::threadpool::pool * thread_pool) const;
template <typename Method, typename Table>
void mergeStreamsImpl(
Block & block,
AggregatedDataVariants & result,
Arena * aggregates_pool,
Method & method,
Table & data) const;
void mergeWithoutKeyStreamsImpl(
Block & block,
AggregatedDataVariants & result) const;
template <typename Method>
void destroyImpl(

View File

@ -18,8 +18,8 @@ class Cluster : private boost::noncopyable
public:
Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, const String & cluster_name);
/// Построить кластер по именам шардов и реплик, локальные обрабатываются так же как удаленные
Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, std::vector< std::vector<String> > names,
/// Построить кластер по именам шардов и реплик. Локальные обрабатываются так же как удаленные.
Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, std::vector<std::vector<String>> names,
const String & username, const String & password);
/// количество узлов clickhouse сервера, расположенных локально
@ -80,7 +80,7 @@ private:
Addresses addresses;
AddressesWithFailover addresses_with_failover;
size_t local_nodes_num;
size_t local_nodes_num = 0;
};
struct Clusters

View File

@ -0,0 +1,119 @@
#pragma once
#include <dlfcn.h>
#include <string>
#include <mutex>
#include <functional>
#include <unordered_set>
#include <unordered_map>
#include <Yandex/logger_useful.h>
#include <statdaemons/threadpool.hpp>
#include <DB/Core/Types.h>
#include <DB/Core/Exception.h>
#include <DB/Common/UInt128.h>
namespace DB
{
/** Позволяет открыть динамическую библиотеку и получить из неё указатель на функцию.
*/
class SharedLibrary : private boost::noncopyable
{
public:
SharedLibrary(const std::string & path)
{
handle = dlopen(path.c_str(), RTLD_LAZY);
if (!handle)
throw Exception(std::string("Cannot dlopen: ") + dlerror());
}
~SharedLibrary()
{
if (handle && dlclose(handle))
throw Exception("Cannot dlclose");
}
template <typename Func>
Func get(const std::string & name)
{
dlerror();
Func res = reinterpret_cast<Func>(dlsym(handle, name.c_str()));
if (char * error = dlerror())
throw Exception(std::string("Cannot dlsym: ") + error);
return res;
}
private:
void * handle;
};
using SharedLibraryPtr = std::shared_ptr<SharedLibrary>;
/** Позволяет скомпилировать кусок кода, использующий заголовочные файлы сервера, в динамическую библиотеку.
* Ведёт статистику вызовов, и инициирует компиляцию только на N-ый по счёту вызов для одного ключа.
* Компиляция выполняется асинхронно, в отдельных потоках, если есть свободные потоки.
* NOTE: Нет очистки устаревших и ненужных результатов.
*/
class Compiler
{
public:
/** path - путь к директории с временными файлами - результатами компиляции.
* Результаты компиляции сохраняются при перезапуске сервера,
* но используют в качестве части ключа номер ревизии. То есть, устаревают при обновлении сервера.
*/
Compiler(const std::string & path_, size_t threads);
~Compiler();
using HashedKey = UInt128;
using CodeGenerator = std::function<std::string()>;
using ReadyCallback = std::function<void(SharedLibraryPtr&)>;
/** Увеличить счётчик для заданного ключа key на единицу.
* Если результат компиляции уже есть (уже открыт, или есть файл с библиотекой),
* то вернуть готовую SharedLibrary.
* Иначе, если счётчик достиг min_count_to_compile,
* инициировать компиляцию в отдельном потоке, если есть свободные потоки, и вернуть nullptr.
* Иначе вернуть nullptr.
*/
SharedLibraryPtr getOrCount(
const std::string & key,
UInt32 min_count_to_compile,
CodeGenerator get_code,
ReadyCallback on_ready);
private:
using Counts = std::unordered_map<HashedKey, UInt32, UInt128Hash>;
using Libraries = std::unordered_map<HashedKey, SharedLibraryPtr, UInt128Hash>;
using Files = std::unordered_set<std::string>;
const std::string path;
boost::threadpool::pool pool;
/// Количество вызовов функции getOrCount.
Counts counts;
/// Скомпилированные и открытые библиотеки. Или nullptr для библиотек в процессе компиляции.
Libraries libraries;
/// Скомпилированные файлы, оставшиеся от предыдущих запусков, но ещё не открытые.
Files files;
std::mutex mutex;
Logger * log = &Logger::get("Compiler");
void compile(HashedKey hashed_key, std::string file_name, CodeGenerator get_code, ReadyCallback on_ready);
};
}

View File

@ -27,6 +27,7 @@
#include <DB/Interpreters/ProcessList.h>
#include <DB/Interpreters/Cluster.h>
#include <DB/Interpreters/InterserverIOHandler.h>
#include <DB/Interpreters/Compiler.h>
#include <DB/Client/ConnectionPool.h>
#include <statdaemons/ConfigProcessor.h>
#include <zkutil/ZooKeeper.h>
@ -48,8 +49,8 @@ typedef std::map<String, Tables> Databases;
/// (имя базы данных, имя таблицы)
typedef std::pair<String, String> DatabaseAndTableName;
/// таблица -> множество таблиц-вьюшек, которые селектят из нее
typedef std::map<DatabaseAndTableName, std::set<DatabaseAndTableName> > ViewDependencies;
/// Таблица -> множество таблиц-представлений, которые деляют SELECT из неё.
typedef std::map<DatabaseAndTableName, std::set<DatabaseAndTableName>> ViewDependencies;
typedef std::vector<DatabaseAndTableName> Dependencies;
/** Набор известных объектов, которые могут быть использованы в запросе.
@ -80,6 +81,7 @@ struct ContextShared
int interserver_io_port; /// и порт,
String path; /// Путь к директории с данными, со слешем на конце.
String tmp_path; /// Путь ко временным файлам, возникающим при обработке запроса.
Databases databases; /// Список БД и таблиц в них.
TableFunctionFactory table_function_factory; /// Табличные функции.
AggregateFunctionFactory aggregate_function_factory; /// Агрегатные функции.
@ -98,6 +100,7 @@ struct ContextShared
InterserverIOHandler interserver_io_handler; /// Обработчик для межсерверной передачи данных.
BackgroundProcessingPoolPtr background_pool; /// Пул потоков для фоновой работы, выполняемой таблицами.
Macros macros; /// Подстановки из конфига.
std::unique_ptr<Compiler> compiler; /// Для динамической компиляции частей запроса, при необходимости.
/// Кластеры для distributed таблиц
/// Создаются при создании Distributed таблиц, так как нужно дождаться пока будут выставлены Settings
@ -187,7 +190,9 @@ private:
public:
String getPath() const;
String getTemporaryPath() const;
void setPath(const String & path);
void setTemporaryPath(const String & path);
/** Забрать список пользователей, квот и профилей настроек из этого конфига.
* Список пользователей полностью заменяется.
@ -206,7 +211,7 @@ public:
void addDependency(const DatabaseAndTableName & from, const DatabaseAndTableName & where);
void removeDependency(const DatabaseAndTableName & from, const DatabaseAndTableName & where);
Dependencies getDependencies(const DatabaseAndTableName & from) const;
Dependencies getDependencies(const String & database_name, const String & table_name) const;
/// Проверка существования таблицы/БД. database может быть пустой - в этом случае используется текущая БД.
bool isTableExist(const String & database_name, const String & table_name) const;
@ -331,6 +336,8 @@ public:
void initClusters();
Cluster & getCluster(const std::string & cluster_name);
Compiler & getCompiler();
void shutdown() { shared->shutdown(); }
};

View File

@ -6,7 +6,7 @@
#include <DB/Parsers/ASTSelectQuery.h>
#include <DB/Interpreters/Context.h>
#include <DB/Interpreters/Aggregator.h>
#include <DB/Interpreters/AggregateDescription.h>
#include <DB/Interpreters/ExpressionActions.h>
#include <DB/Interpreters/Set.h>
#include <DB/Interpreters/Join.h>

View File

@ -63,6 +63,9 @@ public:
*/
BlockInputStreamPtr execute();
/// Выполнить запрос без объединения потоков.
const BlockInputStreams & executeWithoutUnion();
/** Выполнить запрос, записать результат в нужном формате в buf.
* BlockInputStreamPtr возвращается, чтобы можно было потом получить информацию о плане выполнения запроса.
*/
@ -72,12 +75,24 @@ public:
Block getSampleBlock();
private:
typedef Poco::SharedPtr<ExpressionAnalyzer> ExpressionAnalyzerPtr;
void init(BlockInputStreamPtr input, const NamesAndTypesList & table_column_names = NamesAndTypesList());
void init(BlockInputStreamPtr input, const Names & required_column_names = Names(), const NamesAndTypesList & table_column_names = NamesAndTypesList());
void basicInit(BlockInputStreamPtr input, const NamesAndTypesList & table_column_names);
void initQueryAnalyzer();
/// Выполнить один запрос SELECT из цепочки UNION ALL.
void executeSingleQuery(bool should_perform_union_hint = true);
void executeSingleQuery();
/** Оставить в каждом запросе цепочки UNION ALL только нужные столбцы секции SELECT.
* Однако, если используется хоть один DISTINCT в цепочке, то все столбцы считаются нужными,
* так как иначе DISTINCT работал бы по-другому.
*/
void rewriteExpressionList(const Names & required_column_names);
/// Содержит ли запрос хотя бы один астериск?
bool hasAsterisk() const;
// Переименовать столбцы каждого запроса цепочки UNION ALL в такие же имена, как в первом запросе.
void renameColumns();
/// Является ли это первым запросом цепочки UNION ALL имеющей длниу >= 2.
bool isFirstSelectInsideUnionAll() const;
@ -115,9 +130,10 @@ private:
ASTSelectQuery & query;
Context context;
Settings settings;
size_t original_max_threads; /// В settings настройка max_threads может быть изменена. В original_max_threads сохраняется изначальное значение.
QueryProcessingStage::Enum to_stage;
size_t subquery_depth;
ExpressionAnalyzerPtr query_analyzer;
std::unique_ptr<ExpressionAnalyzer> query_analyzer;
BlockInputStreams streams;
/** Цепочка UNION ALL может иметь длину 1 (в таком случае имеется просто один запрос SELECT)

View File

@ -146,7 +146,7 @@ private:
/** Блоки данных таблицы, с которой идёт соединение.
*/
Blocks blocks;
BlocksList blocks;
MapsAny maps_any;
MapsAll maps_all;

View File

@ -36,6 +36,7 @@ struct Limits
M(SettingUInt64, max_rows_to_sort, 0) \
M(SettingUInt64, max_bytes_to_sort, 0) \
M(SettingOverflowMode<false>, sort_overflow_mode, OverflowMode::THROW) \
M(SettingUInt64, max_bytes_before_external_sort, 0) \
\
/** Ограничение на размер результата. \
* Проверяются также для подзапросов и на удалённых серверах. \
@ -44,7 +45,7 @@ struct Limits
M(SettingUInt64, max_result_bytes, 0) \
M(SettingOverflowMode<false>, result_overflow_mode, OverflowMode::THROW) \
\
/* TODO: Проверять также при merge стадии сортировки, при слиянии и финализации агрегатных функций. */ \
/* TODO: Проверять также при слиянии и финализации агрегатных функций. */ \
M(SettingSeconds, max_execution_time, 0) \
M(SettingOverflowMode<false>, timeout_overflow_mode, OverflowMode::THROW) \
\

View File

@ -41,8 +41,6 @@ struct Settings
M(SettingUInt64, max_distributed_connections, DEFAULT_MAX_DISTRIBUTED_CONNECTIONS) \
/** Какую часть запроса можно прочитать в оперативку для парсинга (оставшиеся данные для INSERT, если есть, считываются позже) */ \
M(SettingUInt64, max_query_size, DEFAULT_MAX_QUERY_SIZE) \
/** Выполнять разные стадии конвейера выполнения запроса параллельно. */ \
M(SettingBool, asynchronous, false) \
/** Интервал в микросекундах для проверки, не запрошена ли остановка выполнения запроса, и отправки прогресса. */ \
M(SettingUInt64, interactive_delay, DEFAULT_INTERACTIVE_DELAY) \
M(SettingSeconds, connect_timeout, DBMS_DEFAULT_CONNECT_TIMEOUT_SEC) \
@ -62,8 +60,6 @@ struct Settings
M(SettingBool, extremes, false) \
/** Использовать ли кэш разжатых блоков. */ \
M(SettingBool, use_uncompressed_cache, true) \
/** Использовать ли SplittingAggregator вместо обычного. Он быстрее для запросов с большим состоянием агрегации. */ \
M(SettingBool, use_splitting_aggregator, false) \
/** Следует ли отменять выполняющийся запрос с таким же id, как новый. */ \
M(SettingBool, replace_running_query, false) \
/** Количество потоков, выполняющих фоновую работу для таблиц (например, слияние в merge tree). \
@ -86,6 +82,11 @@ struct Settings
\
/** Сэмплирование по умолчанию. Если равно 1, то отключено. */ \
M(SettingFloat, default_sample, 1.0) \
\
/** Включена ли компиляция запросов. */ \
M(SettingBool, compile, false) \
/** Количество одинаковых по структуре запросов перед тем, как инициируется их компиляция. */ \
M(SettingUInt64, min_count_to_compile, 0) \
/// Всевозможные ограничения на выполнение запроса.
Limits limits;

View File

@ -0,0 +1,291 @@
#include <DB/Interpreters/Aggregator.h>
#include <DB/AggregateFunctions/AggregateFunctionCount.h>
#include <DB/AggregateFunctions/AggregateFunctionSum.h>
#include <DB/AggregateFunctions/AggregateFunctionAvg.h>
#include <DB/AggregateFunctions/AggregateFunctionsMinMaxAny.h>
#include <DB/AggregateFunctions/AggregateFunctionsArgMinMax.h>
#include <DB/AggregateFunctions/AggregateFunctionUniq.h>
#include <DB/AggregateFunctions/AggregateFunctionUniqUpTo.h>
#include <DB/AggregateFunctions/AggregateFunctionGroupArray.h>
#include <DB/AggregateFunctions/AggregateFunctionGroupUniqArray.h>
#include <DB/AggregateFunctions/AggregateFunctionQuantile.h>
#include <DB/AggregateFunctions/AggregateFunctionQuantileTiming.h>
#include <DB/AggregateFunctions/AggregateFunctionIf.h>
#include <DB/AggregateFunctions/AggregateFunctionArray.h>
#include <DB/AggregateFunctions/AggregateFunctionState.h>
#include <DB/AggregateFunctions/AggregateFunctionMerge.h>
namespace DB
{
/** Шаблон цикла агрегации, позволяющий сгенерировать специализированный вариант для конкретной комбинации агрегатных функций.
* Отличается от обычного тем, что вызовы агрегатных функций должны инлайниться, а цикл обновления агрегатных функций должен развернуться.
*
* Так как возможных комбинаций слишком много, то не представляется возможным сгенерировать их все заранее.
* Этот шаблон предназначен для того, чтобы инстанцировать его в рантайме,
* путём запуска компилятора, компиляции shared library и использования её с помощью dlopen.
*/
/** Список типов - для удобного перечисления агрегатных функций.
*/
template <typename THead, typename... TTail>
struct TypeList
{
using Head = THead;
using Tail = TypeList<TTail...>;
static constexpr size_t size = 1 + sizeof...(TTail);
template <size_t I>
using At = typename std::template conditional<I == 0, Head, typename Tail::template At<I - 1>>::type;
template <typename Func, size_t index = 0>
static void ALWAYS_INLINE forEach(Func && func)
{
func.template operator()<Head, index>();
Tail::template forEach<Func, index + 1>(std::forward<Func>(func));
}
};
template <typename THead>
struct TypeList<THead>
{
using Head = THead;
static constexpr size_t size = 1;
template <size_t I>
using At = typename std::template conditional<I == 0, Head, std::nullptr_t>::type;
template <typename Func, size_t index = 0>
static void ALWAYS_INLINE forEach(Func && func)
{
func.template operator()<Head, index>();
}
};
struct EmptyTypeList
{
static constexpr size_t size = 0;
template <size_t I>
using At = std::nullptr_t;
template <typename Func, size_t index = 0>
static void forEach(Func && func)
{
}
};
struct AggregateFunctionsUpdater
{
AggregateFunctionsUpdater(
const Aggregator::AggregateFunctionsPlainPtrs & aggregate_functions_,
const Sizes & offsets_of_aggregate_states_,
Aggregator::AggregateColumns & aggregate_columns_,
AggregateDataPtr & value_,
size_t row_num_)
: aggregate_functions(aggregate_functions_),
offsets_of_aggregate_states(offsets_of_aggregate_states_),
aggregate_columns(aggregate_columns_),
value(value_), row_num(row_num_)
{
}
template <typename AggregateFunction, size_t column_num>
void operator()() ALWAYS_INLINE;
const Aggregator::AggregateFunctionsPlainPtrs & aggregate_functions;
const Sizes & offsets_of_aggregate_states;
Aggregator::AggregateColumns & aggregate_columns;
AggregateDataPtr & value;
size_t row_num;
};
template <typename AggregateFunction, size_t column_num>
void AggregateFunctionsUpdater::operator()()
{
static_cast<AggregateFunction *>(aggregate_functions[column_num])->add(
value + offsets_of_aggregate_states[column_num],
&aggregate_columns[column_num][0],
row_num);
}
struct AggregateFunctionsCreator
{
AggregateFunctionsCreator(
const Aggregator::AggregateFunctionsPlainPtrs & aggregate_functions_,
const Sizes & offsets_of_aggregate_states_,
Aggregator::AggregateColumns & aggregate_columns_,
AggregateDataPtr & aggregate_data_)
: aggregate_functions(aggregate_functions_),
offsets_of_aggregate_states(offsets_of_aggregate_states_),
aggregate_data(aggregate_data_)
{
}
template <typename AggregateFunction, size_t column_num>
void operator()() ALWAYS_INLINE;
const Aggregator::AggregateFunctionsPlainPtrs & aggregate_functions;
const Sizes & offsets_of_aggregate_states;
AggregateDataPtr & aggregate_data;
};
template <typename AggregateFunction, size_t column_num>
void AggregateFunctionsCreator::operator()()
{
AggregateFunction * func = static_cast<AggregateFunction *>(aggregate_functions[column_num]);
try
{
/** Может возникнуть исключение при нехватке памяти.
* Для того, чтобы потом всё правильно уничтожилось, "откатываем" часть созданных состояний.
* Код не очень удобный.
*/
func->create(aggregate_data + offsets_of_aggregate_states[column_num]);
}
catch (...)
{
for (size_t rollback_j = 0; rollback_j < column_num; ++rollback_j)
func->destroy(aggregate_data + offsets_of_aggregate_states[rollback_j]);
aggregate_data = nullptr;
throw;
}
}
template <typename Method, typename AggregateFunctionsList>
void NO_INLINE Aggregator::executeSpecialized(
Method & method,
Arena * aggregates_pool,
size_t rows,
ConstColumnPlainPtrs & key_columns,
AggregateColumns & aggregate_columns,
const Sizes & key_sizes,
StringRefs & keys,
bool no_more_keys,
AggregateDataPtr overflow_row) const
{
typename Method::State state;
state.init(key_columns);
if (!no_more_keys)
executeSpecializedCase<false, Method, AggregateFunctionsList>(
method, state, aggregates_pool, rows, key_columns, aggregate_columns, key_sizes, keys, overflow_row);
else
executeSpecializedCase<true, Method, AggregateFunctionsList>(
method, state, aggregates_pool, rows, key_columns, aggregate_columns, key_sizes, keys, overflow_row);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuninitialized"
template <bool no_more_keys, typename Method, typename AggregateFunctionsList>
void NO_INLINE Aggregator::executeSpecializedCase(
Method & method,
typename Method::State & state,
Arena * aggregates_pool,
size_t rows,
ConstColumnPlainPtrs & key_columns,
AggregateColumns & aggregate_columns,
const Sizes & key_sizes,
StringRefs & keys,
AggregateDataPtr overflow_row) const
{
/// Для всех строчек.
typename Method::iterator it;
typename Method::Key prev_key;
for (size_t i = 0; i < rows; ++i)
{
bool inserted; /// Вставили новый ключ, или такой ключ уже был?
bool overflow = false; /// Новый ключ не поместился в хэш-таблицу из-за no_more_keys.
/// Получаем ключ для вставки в хэш-таблицу.
typename Method::Key key = state.getKey(key_columns, keys_size, i, key_sizes, keys);
if (!no_more_keys) /// Вставляем.
{
/// Оптимизация для часто повторяющихся ключей.
if (i != 0 && key == prev_key)
{
AggregateDataPtr value = Method::getAggregateData(it->second);
/// Добавляем значения в агрегатные функции.
AggregateFunctionsList::forEach(AggregateFunctionsUpdater(
aggregate_functions, offsets_of_aggregate_states, aggregate_columns, value, i));
continue;
}
else
prev_key = key;
method.data.emplace(key, it, inserted);
}
else
{
/// Будем добавлять только если ключ уже есть.
inserted = false;
it = method.data.find(key);
if (method.data.end() == it)
overflow = true;
}
/// Если ключ не поместился, и данные не надо агрегировать в отдельную строку, то делать нечего.
if (no_more_keys && overflow && !overflow_row)
continue;
/// Если вставили новый ключ - инициализируем состояния агрегатных функций, и возможно, что-нибудь связанное с ключом.
if (inserted)
{
method.onNewKey(*it, keys_size, i, keys, *aggregates_pool);
AggregateDataPtr & aggregate_data = Method::getAggregateData(it->second);
aggregate_data = aggregates_pool->alloc(total_size_of_aggregate_states);
AggregateFunctionsList::forEach(AggregateFunctionsCreator(
aggregate_functions, offsets_of_aggregate_states, aggregate_columns, aggregate_data));
}
AggregateDataPtr value = (!no_more_keys || !overflow) ? Method::getAggregateData(it->second) : overflow_row;
/// Добавляем значения в агрегатные функции.
AggregateFunctionsList::forEach(AggregateFunctionsUpdater(
aggregate_functions, offsets_of_aggregate_states, aggregate_columns, value, i));
}
}
#pragma GCC diagnostic pop
template <typename AggregateFunctionsList>
void NO_INLINE Aggregator::executeSpecializedWithoutKey(
AggregatedDataWithoutKey & res,
size_t rows,
AggregateColumns & aggregate_columns) const
{
/// Оптимизация в случае единственной агрегатной функции count.
AggregateFunctionCount * agg_count = aggregates_size == 1
? typeid_cast<AggregateFunctionCount *>(aggregate_functions[0])
: NULL;
if (agg_count)
agg_count->addDelta(res, rows);
else
{
for (size_t i = 0; i < rows; ++i)
{
AggregateFunctionsList::forEach(AggregateFunctionsUpdater(
aggregate_functions, offsets_of_aggregate_states, aggregate_columns, res, i));
}
}
}
}

View File

@ -1,103 +0,0 @@
#pragma once
#include <statdaemons/threadpool.hpp>
#include <DB/Common/PODArray.h>
#include <DB/Interpreters/Aggregator.h>
namespace DB
{
/** Агрегирует источник блоков параллельно в нескольких потоках.
* Распараллеливание производится для каждого блока.
* Для этого, сначала параллельно вычисляет хэши от ключей всех строчек блока,
* затем агрегирует строчки с разными диапазонами хэшей в разные хэш-таблицы
* (для разделения по хэш-таблицам используются другие биты ключа или другие хэш функции, чем те, что внутри хэш-таблиц).
* Получится, что эти хэш-таблицы будут содержать разные ключи, и их не потребуется объединять.
*
* Хорошо работает при большом размере результата агрегации (количестве уникальных ключей)
* - линейно масштабируется по количеству потоков.
*
* Не работает при числе потоков больше 256.
* Плохо работает при размере хэш-таблиц больше 2^32 элементов.
*
* TODO:
* - поддержка with_totals;
* - проверить работу при распределённой обработке запроса;
* - починить rows_before_limit_at_least;
* - минимальное количество строк на один поток; если в блоке мало строк - читать и обрабатывать несколько блоков сразу;
* - определиться, в каких случаях следует использовать этот агрегатор, а в каких - нет.
*/
class SplittingAggregator : private Aggregator
{
public:
SplittingAggregator(const ColumnNumbers & keys_, const AggregateDescriptions & aggregates_, size_t threads_,
bool with_totals_, size_t max_rows_to_group_by_ = 0, OverflowMode group_by_overflow_mode_ = OverflowMode::THROW)
: Aggregator(keys_, aggregates_, with_totals_, max_rows_to_group_by_, group_by_overflow_mode_), threads(threads_), pool(threads),
log(&Logger::get("SplittingAggregator")), method(AggregatedDataVariants::EMPTY),
key_columns(keys_size), aggregate_columns(aggregates_size), rows(0), src_rows(0), src_bytes(0), size_of_all_results(0)
{
}
SplittingAggregator(const Names & key_names_, const AggregateDescriptions & aggregates_, size_t threads_,
bool with_totals_, size_t max_rows_to_group_by_ = 0, OverflowMode group_by_overflow_mode_ = OverflowMode::THROW)
: Aggregator(key_names_, aggregates_, with_totals_, max_rows_to_group_by_, group_by_overflow_mode_), threads(threads_), pool(threads),
log(&Logger::get("SplittingAggregator")), method(AggregatedDataVariants::EMPTY),
key_columns(keys_size), aggregate_columns(aggregates_size), rows(0), src_rows(0), src_bytes(0), size_of_all_results(0)
{
}
/// Агрегировать источник. Получить результат в виде одной из структур данных.
void execute(BlockInputStreamPtr stream, ManyAggregatedDataVariants & results);
void convertToBlocks(ManyAggregatedDataVariants & data_variants, Blocks & blocks, bool final);
String getID() const { return Aggregator::getID(); }
private:
size_t threads;
boost::threadpool::pool pool;
/// Вычисленные значения ключей и хэшей хэш-таблицы.
PODArray<UInt64> keys64;
PODArray<UInt64> hashes64;
PODArray<UInt128> keys128;
PODArray<UInt128> hashes128;
PODArray<StringRef> string_refs;
PODArray<UInt8> thread_nums;
Logger * log;
/// Каким способом выполняется агрегация.
AggregatedDataVariants::Type method;
ConstColumnPlainPtrs key_columns;
typedef std::vector<ConstColumnPlainPtrs> AggregateColumns;
AggregateColumns aggregate_columns;
size_t rows;
size_t src_rows;
size_t src_bytes;
Sizes key_sizes;
StringRefHash hash_func_string;
/// Для более точного контроля max_rows_to_group_by.
size_t size_of_all_results;
void calculateHashesThread(Block & block, size_t begin, size_t end, ExceptionPtr & exception, MemoryTracker * memory_tracker);
void aggregateThread(Block & block, AggregatedDataVariants & result, size_t thread_no, ExceptionPtr & exception, MemoryTracker * memory_tracker);
void convertToBlockThread(AggregatedDataVariants & data_variant, Block & block, bool final, ExceptionPtr & exception, MemoryTracker * memory_tracker);
template <typename FieldType>
void aggregateOneNumber(AggregatedDataVariants & result, size_t thread_no, bool no_more_keys);
};
}

View File

@ -4,6 +4,7 @@
#include <DB/Parsers/ASTQueryWithOutput.h>
#include <DB/Parsers/ASTExpressionList.h>
#include <DB/Parsers/ASTFunction.h>
#include <DB/Parsers/ASTAsterisk.h>
namespace DB
{
@ -52,6 +53,41 @@ public:
return false;
}
/// Содержит ли запрос астериск?
bool hasAsterisk() const
{
for (const auto & ast : select_expression_list->children)
if (typeid_cast<const ASTAsterisk *>(&*ast) != nullptr)
return true;
return false;
}
/// Переименовать столбцы запроса в такие же имена, как в исходном запросе.
void renameColumns(const ASTSelectQuery & source)
{
const ASTs & from = source.select_expression_list->children;
ASTs & to = select_expression_list->children;
if (from.size() != to.size())
throw Exception("Size mismatch in UNION ALL chain",
DB::ErrorCodes::UNION_ALL_RESULT_STRUCTURES_MISMATCH);
for (size_t i = 0; i < from.size(); ++i)
{
/// Если столбец имеет алиас, то он должен совпадать с названием исходного столбца.
/// В противном случае мы ему присваиваем алиас, если требуется.
if (!to[i]->tryGetAlias().empty())
{
if (to[i]->tryGetAlias() != from[i]->getAliasOrColumnName())
throw Exception("Column alias mismatch in UNION ALL chain",
DB::ErrorCodes::UNION_ALL_COLUMN_ALIAS_MISMATCH);
}
else if (to[i]->getColumnName() != from[i]->getAliasOrColumnName())
to[i]->setAlias(from[i]->getAliasOrColumnName());
}
}
/// Переписывает select_expression_list, чтобы вернуть только необходимые столбцы в правильном порядке.
void rewriteSelectExpressionList(const Names & column_names)
{

View File

@ -13,6 +13,9 @@ namespace DB
namespace
{
static constexpr const std::chrono::seconds max_sleep_time{30};
static constexpr const std::chrono::minutes decrease_error_count_period{5};
template <typename PoolFactory>
ConnectionPools createPoolsForAddresses(const std::string & name, PoolFactory && factory)
{
@ -57,7 +60,8 @@ class StorageDistributed::DirectoryMonitor
public:
DirectoryMonitor(StorageDistributed & storage, const std::string & name)
: storage(storage), pool{createPool(name)}, path{storage.path + name + '/'}
, sleep_time{storage.context.getSettingsRef().distributed_directory_monitor_sleep_time_ms.totalMilliseconds()}
, default_sleep_time{storage.context.getSettingsRef().distributed_directory_monitor_sleep_time_ms.totalMilliseconds()}
, sleep_time{default_sleep_time}
, log{&Logger::get(getLoggerName())}
{
}
@ -90,11 +94,22 @@ private:
catch (...)
{
do_sleep = true;
++error_count;
sleep_time = std::min(
std::chrono::milliseconds{std::int64_t(default_sleep_time.count() * std::exp2(error_count))},
std::chrono::milliseconds{max_sleep_time});
tryLogCurrentException(getLoggerName().data());
}
};
if (do_sleep)
cond.wait_for(lock, sleep_time, quit_requested);
const auto now = std::chrono::system_clock::now();
if (now - last_decrease_time > decrease_error_count_period)
{
error_count /= 2;
last_decrease_time = now;
}
}
}
@ -196,7 +211,12 @@ private:
StorageDistributed & storage;
ConnectionPoolPtr pool;
std::string path;
std::size_t error_count{};
std::chrono::milliseconds default_sleep_time;
std::chrono::milliseconds sleep_time;
std::chrono::time_point<std::chrono::system_clock> last_decrease_time{
std::chrono::system_clock::now()
};
bool quit{false};
std::mutex mutex;
std::condition_variable cond;

View File

@ -331,8 +331,39 @@ public:
String from = storage.full_path + name + "/";
String to = storage.full_path + "tmp2_" + name + "/";
Poco::File(from).renameTo(to);
Poco::File(to).remove(true);
Poco::File from_dir{from};
Poco::File to_dir{to};
if (to_dir.exists())
{
LOG_WARNING(storage.log, "Directory " << to << " (to which part must be renamed before removing) already exists."
" Most likely this is due to unclean restart. Removing it.");
try
{
to_dir.remove(true);
}
catch (...)
{
LOG_ERROR(storage.log, "Cannot remove directory " << to << ". Check owner and access rights.");
throw;
}
}
try
{
from_dir.renameTo(to);
}
catch (const Poco::FileNotFoundException & e)
{
/// Если директория уже удалена. Такое возможно лишь при ручном вмешательстве.
LOG_WARNING(storage.log, "Directory " << from << " (part to remove) doesn't exist or one of nested files has gone."
" Most likely this is due to manual removing. This should be discouraged. Ignoring.");
return;
}
to_dir.remove(true);
}
void renameTo(const String & new_name) const
@ -345,10 +376,28 @@ public:
f.renameTo(to);
}
/// Переименовывает кусок, дописав к имени префикс.
void renameAddPrefix(const String & prefix) const
/// Переименовывает кусок, дописав к имени префикс. to_detached - также перенести в директорию detached.
void renameAddPrefix(bool to_detached, const String & prefix) const
{
renameTo(prefix + name);
unsigned try_no = 0;
auto dst_name = [&, this] { return (to_detached ? "detached/" : "") + prefix + name + (try_no ? "_try" + toString(try_no) : ""); };
if (to_detached)
{
/** Если нужно отцепить кусок, и директория, в которую мы хотим его переименовать, уже существует,
* то будем переименовывать в директорию с именем, в которое добавлен суффикс в виде "_tryN".
* Это делается только в случае to_detached, потому что считается, что в этом случае, точное имя не имеет значения.
* Больше 10 попыток не делается, чтобы не оставалось слишком много мусорных директорий.
*/
while (try_no < 10 && Poco::File(dst_name()).exists())
{
LOG_WARNING(storage.log, "Directory " << dst_name() << " (to detach to) is already exist."
" Will detach to directory with '_tryN' suffix.");
++try_no;
}
}
renameTo(dst_name());
}
/// Загрузить индекс и вычислить размер. Если size=0, вычислить его тоже.

View File

@ -68,7 +68,7 @@ class MergeTreeMergeBlocker
{
public:
MergeTreeMergeBlocker(MergeTreeDataMerger & merger)
: merger(merger), was_cancelled{merger.cancelAll()} {}
: merger(merger), was_cancelled{!merger.cancelAll()} {}
~MergeTreeMergeBlocker()
{

View File

@ -320,7 +320,8 @@ private:
break;
}
max_mark_range = std::max(max_mark_range, (*marks)[right].offset_in_compressed_file - (*marks)[all_mark_ranges[i].begin].offset_in_compressed_file);
max_mark_range = std::max(max_mark_range,
(*marks)[right].offset_in_compressed_file - (*marks)[all_mark_ranges[i].begin].offset_in_compressed_file);
}
size_t buffer_size = DBMS_DEFAULT_BUFFER_SIZE < max_mark_range ? DBMS_DEFAULT_BUFFER_SIZE : max_mark_range;

View File

@ -133,7 +133,7 @@ private:
ExpressionActionsPtr sharding_key_expr;
String sharding_key_column_name;
bool write_enabled;
String path;
String path; /// Может быть пустым, если data_path_ пустой. В этом случае, директория для данных для отправки не создаётся.
class DirectoryMonitor;
std::unordered_map<std::string, std::unique_ptr<DirectoryMonitor>> directory_monitors;

View File

@ -96,10 +96,11 @@ private:
String format; /// Формат вывода результата в консоль.
size_t format_max_block_size = 0; /// Максимальный размер блока при выводе в консоль.
String current_format; /// Формат вывода результата текущего запроса в консоль.
String insert_format; /// Формат данных для INSERT-а при чтении их из stdin в batch режиме
size_t insert_format_max_block_size = 0; /// Максимальный размер блока при чтении данных INSERT-а.
bool has_vertical_output_suffix = false; /// \G указан в конце команды?
Context context;
/// Чтение из stdin для batch режима
@ -233,15 +234,12 @@ private:
<< "." << Revision::get()
<< "." << std::endl;
if (is_interactive)
{
format = config().getString("format", config().has("vertical") ? "Vertical" : "PrettyCompact");
}
if (config().has("vertical"))
format = config().getString("format", "Vertical");
else
format = config().getString("format", "TabSeparated");
format = config().getString("format", is_interactive ? "PrettyCompact" : "TabSeparated");
format_max_block_size = config().getInt("format_max_block_size", DEFAULT_BLOCK_SIZE);
current_format = format;
insert_format = "Values";
insert_format_max_block_size = config().getInt("insert_format_max_block_size", DEFAULT_INSERT_BLOCK_SIZE);
@ -358,14 +356,15 @@ private:
bool ends_with_semicolon = line[ws - 1] == ';';
bool ends_with_backslash = line[ws - 1] == '\\';
bool ends_with_format_vertical = (ws >= 2) && (line[ws - 2] == '\\') && (line[ws - 1] == 'G');
has_vertical_output_suffix = (ws >= 2) && (line[ws - 2] == '\\') && (line[ws - 1] == 'G');
if (ends_with_backslash)
line = line.substr(0, ws - 1);
query += line;
if (!ends_with_backslash && (ends_with_semicolon || ends_with_format_vertical || !config().has("multiline")))
if (!ends_with_backslash && (ends_with_semicolon || has_vertical_output_suffix || !config().has("multiline")))
{
if (query != prev_query)
{
@ -383,11 +382,8 @@ private:
prev_query = query;
}
if (ends_with_format_vertical)
{
current_format = config().getString("format", "Vertical");
if (has_vertical_output_suffix)
query = query.substr(0, query.length() - 2);
}
try
{
@ -411,7 +407,6 @@ private:
}
query = "";
current_format = format;
}
else
{
@ -815,11 +810,22 @@ private:
processed_rows += block.rows();
if (!block_std_out)
{
String current_format = format;
/// Формат может быть указан в запросе.
if (ASTQueryWithOutput * query_with_output = dynamic_cast<ASTQueryWithOutput *>(&*parsed_query))
{
if (query_with_output->format)
{
if (has_vertical_output_suffix)
throw Exception("Output format already specified", ErrorCodes::CLIENT_OUTPUT_FORMAT_SPECIFIED);
if (ASTIdentifier * id = typeid_cast<ASTIdentifier *>(&*query_with_output->format))
current_format = id->name;
}
}
if (has_vertical_output_suffix)
current_format = "Vertical";
block_std_out = context.getFormatFactory().getOutput(current_format, std_out, block);
block_std_out->writePrefix();

View File

@ -269,7 +269,7 @@ void Connection::sendData(const Block & block, const String & name)
else
maybe_compressed_out = out;
block_out = new NativeBlockOutputStream(*maybe_compressed_out);
block_out = new NativeBlockOutputStream(*maybe_compressed_out, server_revision);
}
writeVarUInt(Protocol::Client::Data, *out);
@ -444,7 +444,7 @@ void Connection::initBlockInput()
else
maybe_compressed_in = in;
block_in = new NativeBlockInputStream(*maybe_compressed_in, data_type_factory);
block_in = new NativeBlockInputStream(*maybe_compressed_in, data_type_factory, server_revision);
}
}

View File

@ -1,16 +1,12 @@
#include <DB/Common/VirtualColumnUtils.h>
#include <DB/Interpreters/Context.h>
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
#include <DB/DataStreams/OneBlockInputStream.h>
#include <DB/DataTypes/DataTypeString.h>
#include <DB/DataTypes/DataTypesNumberFixed.h>
#include <DB/Interpreters/ExpressionAnalyzer.h>
#include <DB/Parsers/ASTIdentifier.h>
#include <DB/Parsers/ASTExpressionList.h>
#include <DB/Parsers/ASTLiteral.h>
#include <DB/Parsers/ASTSelectQuery.h>
#include <DB/Storages/IStorage.h>
#include <DB/Interpreters/InterpreterSelectQuery.h>
#include <DB/Common/VirtualColumnUtils.h>
namespace DB
{

View File

@ -73,7 +73,7 @@ void localBackup(Poco::Path source_path, Poco::Path destination_path)
* Если какой-то файл удалился во время попытки сделать бэкап, то повторим попытку снова,
* так как важно учесть какие-нибудь новые файлы, который могли появиться.
*/
do
while (true)
{
try
{
@ -98,5 +98,7 @@ void localBackup(Poco::Path source_path, Poco::Path destination_path)
continue;
}
} while (false);
break;
}
}

View File

@ -8,7 +8,7 @@
#include <DB/Interpreters/AggregationCommon.h>
#include <DB/Common/HashTable/HashMap.h>
#include <DB/Common/HashTable/TwoLevelHashTable.h>
#include <DB/Common/HashTable/TwoLevelHashMap.h>
//#include <DB/Common/HashTable/HashTableWithSmallLocks.h>
//#include <DB/Common/HashTable/HashTableMerge.h>
@ -25,46 +25,6 @@ typedef UInt64 Value;
typedef std::vector<Key> Source;
typedef HashMap<Key, Value> Map;
template
<
typename Key,
typename Cell,
typename Hash = DefaultHash<Key>,
typename Grower = HashTableGrower<8>,
typename Allocator = HashTableAllocator
>
class TwoLevelHashMapTable : public TwoLevelHashTable<Key, Cell, Hash, Grower, Allocator, HashMapTable<Key, Cell, Hash, Grower, Allocator> >
{
public:
typedef Key key_type;
typedef typename Cell::Mapped mapped_type;
typedef typename Cell::value_type value_type;
mapped_type & operator[](Key x)
{
typename TwoLevelHashMapTable::iterator it;
bool inserted;
this->emplace(x, it, inserted);
if (!__has_trivial_constructor(mapped_type) && inserted)
new(&it->second) mapped_type();
return it->second;
}
};
template
<
typename Key,
typename Mapped,
typename Hash = DefaultHash<Key>,
typename Grower = HashTableGrower<8>,
typename Allocator = HashTableAllocator
>
using TwoLevelHashMap = TwoLevelHashMapTable<Key, HashMapCell<Key, Mapped, Hash>, Hash, Grower, Allocator>;
typedef TwoLevelHashMap<Key, Value> MapTwoLevel;
@ -90,7 +50,7 @@ struct __attribute__((__aligned__(64))) AlignedSmallLock : public SmallLock
};
typedef AlignedSmallLock Mutex;
typedef Poco::FastMutex Mutex;
/*typedef HashTableWithSmallLocks<
@ -109,12 +69,55 @@ void aggregate1(Map & map, Source::const_iterator begin, Source::const_iterator
++map[*it];
}
void aggregate12(Map & map, Source::const_iterator begin, Source::const_iterator end)
{
Map::iterator found;
auto prev_it = end;
for (auto it = begin; it != end; ++it)
{
if (*it == *prev_it)
{
++found->second;
continue;
}
prev_it = it;
bool inserted;
map.emplace(*it, found, inserted);
++found->second;
}
}
void aggregate2(MapTwoLevel & map, Source::const_iterator begin, Source::const_iterator end)
{
for (auto it = begin; it != end; ++it)
++map[*it];
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
void aggregate22(MapTwoLevel & map, Source::const_iterator begin, Source::const_iterator end)
{
MapTwoLevel::iterator found;
auto prev_it = end;
for (auto it = begin; it != end; ++it)
{
if (*it == *prev_it)
{
++found->second;
continue;
}
prev_it = it;
bool inserted;
map.emplace(*it, found, inserted);
++found->second;
}
}
#pragma GCC diagnostic pop
void merge2(MapTwoLevel * maps, size_t num_threads, size_t bucket)
{
for (size_t i = 1; i < num_threads; ++i)
@ -147,18 +150,51 @@ void aggregate3(Map & local_map, Map & global_map, Mutex & mutex, Source::const_
}
}
void aggregate4(Map & local_map, MapTwoLevel & global_map, Mutex * mutexes, Source::const_iterator begin, Source::const_iterator end)
void aggregate33(Map & local_map, Map & global_map, Mutex & mutex, Source::const_iterator begin, Source::const_iterator end)
{
static constexpr size_t threshold = 65536;
for (auto it = begin; it != end; ++it)
{
Map::iterator found;
bool inserted;
local_map.emplace(*it, found, inserted);
++found->second;
if (inserted && local_map.size() == threshold)
{
Poco::ScopedLock<Mutex> lock(mutex);
for (auto & value_type : local_map)
global_map[value_type.first] += value_type.second;
local_map.clear();
}
}
}
void aggregate4(Map & local_map, MapTwoLevel & global_map, Mutex * mutexes, Source::const_iterator begin, Source::const_iterator end)
{
static constexpr size_t threshold = 65536;
static constexpr size_t block_size = 8192;
auto it = begin;
while (it != end)
{
auto block_end = std::min(end, it + block_size);
if (local_map.size() < threshold)
{
for (; it != block_end; ++it)
++local_map[*it];
}
else
{
for (; it != block_end; ++it)
{
Map::iterator found = local_map.find(*it);
if (found != local_map.end())
++found->second;
else if (local_map.size() < threshold)
++local_map[*it]; /// TODO Можно было бы делать один lookup, а не два.
else
{
size_t hash_value = global_map.hash(*it);
@ -173,6 +209,8 @@ void aggregate4(Map & local_map, MapTwoLevel & global_map, Mutex * mutexes, Sour
++local_map[*it];
}
}
}
}
}
/*
void aggregate5(Map & local_map, MapSmallLocks & global_map, Source::const_iterator begin, Source::const_iterator end)
@ -286,6 +324,135 @@ int main(int argc, char ** argv)
std::cerr << "Size: " << maps[0].size() << std::endl << std::endl;
}
if (!method || method == 12)
{
/** То же самое, но с оптимизацией для подряд идущих одинаковых значений.
*/
Map maps[num_threads];
Stopwatch watch;
for (size_t i = 0; i < num_threads; ++i)
pool.schedule(std::bind(aggregate12,
std::ref(maps[i]),
data.begin() + (data.size() * i) / num_threads,
data.begin() + (data.size() * (i + 1)) / num_threads));
pool.wait();
watch.stop();
double time_aggregated = watch.elapsedSeconds();
std::cerr
<< "Aggregated in " << time_aggregated
<< " (" << n / time_aggregated << " elem/sec.)"
<< std::endl;
size_t size_before_merge = 0;
std::cerr << "Sizes: ";
for (size_t i = 0; i < num_threads; ++i)
{
std::cerr << (i == 0 ? "" : ", ") << maps[i].size();
size_before_merge += maps[i].size();
}
std::cerr << std::endl;
watch.restart();
for (size_t i = 1; i < num_threads; ++i)
for (auto it = maps[i].begin(); it != maps[i].end(); ++it)
maps[0][it->first] += it->second;
watch.stop();
double time_merged = watch.elapsedSeconds();
std::cerr
<< "Merged in " << time_merged
<< " (" << size_before_merge / time_merged << " elem/sec.)"
<< std::endl;
double time_total = time_aggregated + time_merged;
std::cerr
<< "Total in " << time_total
<< " (" << n / time_total << " elem/sec.)"
<< std::endl;
std::cerr << "Size: " << maps[0].size() << std::endl << std::endl;
}
if (!method || method == 11)
{
/** Вариант 11.
* То же, что вариант 1, но при мердже, изменён порядок циклов,
* что потенциально может дать лучшую кэш-локальность.
*
* На практике, разницы нет.
*/
Map maps[num_threads];
Stopwatch watch;
for (size_t i = 0; i < num_threads; ++i)
pool.schedule(std::bind(aggregate1,
std::ref(maps[i]),
data.begin() + (data.size() * i) / num_threads,
data.begin() + (data.size() * (i + 1)) / num_threads));
pool.wait();
watch.stop();
double time_aggregated = watch.elapsedSeconds();
std::cerr
<< "Aggregated in " << time_aggregated
<< " (" << n / time_aggregated << " elem/sec.)"
<< std::endl;
size_t size_before_merge = 0;
std::cerr << "Sizes: ";
for (size_t i = 0; i < num_threads; ++i)
{
std::cerr << (i == 0 ? "" : ", ") << maps[i].size();
size_before_merge += maps[i].size();
}
std::cerr << std::endl;
watch.restart();
Map::iterator iterators[num_threads];
for (size_t i = 1; i < num_threads; ++i)
iterators[i] = maps[i].begin();
while (true)
{
bool finish = true;
for (size_t i = 1; i < num_threads; ++i)
{
if (iterators[i] == maps[i].end())
continue;
finish = false;
maps[0][iterators[i]->first] += iterators[i]->second;
++iterators[i];
}
if (finish)
break;
}
watch.stop();
double time_merged = watch.elapsedSeconds();
std::cerr
<< "Merged in " << time_merged
<< " (" << size_before_merge / time_merged << " elem/sec.)"
<< std::endl;
double time_total = time_aggregated + time_merged;
std::cerr
<< "Total in " << time_total
<< " (" << n / time_total << " elem/sec.)"
<< std::endl;
std::cerr << "Size: " << maps[0].size() << std::endl << std::endl;
}
if (!method || method == 2)
{
/** Вариант 2.
@ -348,6 +515,60 @@ int main(int argc, char ** argv)
std::cerr << "Size: " << maps[0].size() << std::endl << std::endl;
}
if (!method || method == 22)
{
MapTwoLevel maps[num_threads];
Stopwatch watch;
for (size_t i = 0; i < num_threads; ++i)
pool.schedule(std::bind(aggregate22,
std::ref(maps[i]),
data.begin() + (data.size() * i) / num_threads,
data.begin() + (data.size() * (i + 1)) / num_threads));
pool.wait();
watch.stop();
double time_aggregated = watch.elapsedSeconds();
std::cerr
<< "Aggregated in " << time_aggregated
<< " (" << n / time_aggregated << " elem/sec.)"
<< std::endl;
size_t size_before_merge = 0;
std::cerr << "Sizes: ";
for (size_t i = 0; i < num_threads; ++i)
{
std::cerr << (i == 0 ? "" : ", ") << maps[i].size();
size_before_merge += maps[i].size();
}
std::cerr << std::endl;
watch.restart();
for (size_t i = 0; i < MapTwoLevel::NUM_BUCKETS; ++i)
pool.schedule(std::bind(merge2,
&maps[0], num_threads, i));
pool.wait();
watch.stop();
double time_merged = watch.elapsedSeconds();
std::cerr
<< "Merged in " << time_merged
<< " (" << size_before_merge / time_merged << " elem/sec.)"
<< std::endl;
double time_total = time_aggregated + time_merged;
std::cerr
<< "Total in " << time_total
<< " (" << n / time_total << " elem/sec.)"
<< std::endl;
std::cerr << "Size: " << maps[0].size() << std::endl << std::endl;
}
if (!method || method == 3)
{
/** Вариант 3.
@ -418,6 +639,72 @@ int main(int argc, char ** argv)
std::cerr << "Size: " << global_map.size() << std::endl << std::endl;
}
if (!method || method == 33)
{
/** Вариант 33.
* В разных потоках агрегируем независимо в разные хэш-таблицы,
* пока их размер не станет достаточно большим.
* Затем сбрасываем данные в глобальную хэш-таблицу, защищённую mutex-ом, и продолжаем.
*/
Map local_maps[num_threads];
Map global_map;
Mutex mutex;
Stopwatch watch;
for (size_t i = 0; i < num_threads; ++i)
pool.schedule(std::bind(aggregate33,
std::ref(local_maps[i]),
std::ref(global_map),
std::ref(mutex),
data.begin() + (data.size() * i) / num_threads,
data.begin() + (data.size() * (i + 1)) / num_threads));
pool.wait();
watch.stop();
double time_aggregated = watch.elapsedSeconds();
std::cerr
<< "Aggregated in " << time_aggregated
<< " (" << n / time_aggregated << " elem/sec.)"
<< std::endl;
size_t size_before_merge = 0;
std::cerr << "Sizes (local): ";
for (size_t i = 0; i < num_threads; ++i)
{
std::cerr << (i == 0 ? "" : ", ") << local_maps[i].size();
size_before_merge += local_maps[i].size();
}
std::cerr << std::endl;
std::cerr << "Size (global): " << global_map.size() << std::endl;
size_before_merge += global_map.size();
watch.restart();
for (size_t i = 0; i < num_threads; ++i)
for (auto it = local_maps[i].begin(); it != local_maps[i].end(); ++it)
global_map[it->first] += it->second;
pool.wait();
watch.stop();
double time_merged = watch.elapsedSeconds();
std::cerr
<< "Merged in " << time_merged
<< " (" << size_before_merge / time_merged << " elem/sec.)"
<< std::endl;
double time_total = time_aggregated + time_merged;
std::cerr
<< "Total in " << time_total
<< " (" << n / time_total << " elem/sec.)"
<< std::endl;
std::cerr << "Size: " << global_map.size() << std::endl << std::endl;
}
if (!method || method == 4)
{
/** Вариант 4.

View File

@ -0,0 +1,395 @@
#include <iostream>
#include <iomanip>
#include <mutex>
#include <atomic>
//#define DBMS_HASH_MAP_DEBUG_RESIZES
#include <DB/Interpreters/AggregationCommon.h>
#include <DB/Common/HashTable/HashMap.h>
#include <DB/Common/HashTable/TwoLevelHashMap.h>
//#include <DB/Common/HashTable/HashTableWithSmallLocks.h>
//#include <DB/Common/HashTable/HashTableMerge.h>
#include <DB/IO/ReadBufferFromFile.h>
#include <DB/IO/CompressedReadBuffer.h>
#include <statdaemons/Stopwatch.h>
#include <statdaemons/threadpool.hpp>
typedef UInt64 Key;
typedef UInt64 Value;
typedef std::vector<Key> Source;
template <typename Map>
struct AggregateIndependent
{
template <typename Creator, typename Updater>
static void NO_INLINE execute(const Source & data, size_t num_threads, std::vector<std::unique_ptr<Map>> & results,
Creator && creator, Updater && updater,
boost::threadpool::pool & pool)
{
results.reserve(num_threads);
for (size_t i = 0; i < num_threads; ++i)
results.emplace_back(new Map);
for (size_t i = 0; i < num_threads; ++i)
{
auto begin = data.begin() + (data.size() * i) / num_threads;
auto end = data.begin() + (data.size() * (i + 1)) / num_threads;
auto & map = *results[i];
pool.schedule([&, begin, end]()
{
for (auto it = begin; it != end; ++it)
{
typename Map::iterator place;
bool inserted;
map.emplace(*it, place, inserted);
if (inserted)
creator(place->second);
else
updater(place->second);
}
});
}
pool.wait();
}
};
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
template <typename Map>
struct AggregateIndependentWithSequentialKeysOptimization
{
template <typename Creator, typename Updater>
static void NO_INLINE execute(const Source & data, size_t num_threads, std::vector<std::unique_ptr<Map>> & results,
Creator && creator, Updater && updater,
boost::threadpool::pool & pool)
{
results.reserve(num_threads);
for (size_t i = 0; i < num_threads; ++i)
results.emplace_back(new Map);
for (size_t i = 0; i < num_threads; ++i)
{
auto begin = data.begin() + (data.size() * i) / num_threads;
auto end = data.begin() + (data.size() * (i + 1)) / num_threads;
auto & map = *results[i];
pool.schedule([&, begin, end]()
{
typename Map::iterator place;
Key prev_key {};
for (auto it = begin; it != end; ++it)
{
if (it != begin && *it == prev_key)
{
updater(place->second);
continue;
}
prev_key = *it;
bool inserted;
map.emplace(*it, place, inserted);
if (inserted)
creator(place->second);
else
updater(place->second);
}
});
}
pool.wait();
}
};
#pragma GCC diagnostic pop
template <typename Map>
struct MergeSequential
{
template <typename Merger>
static void NO_INLINE execute(Map ** source_maps, size_t num_maps, Map *& result_map,
Merger && merger,
boost::threadpool::pool & pool)
{
for (size_t i = 1; i < num_maps; ++i)
{
auto begin = source_maps[i]->begin();
auto end = source_maps[i]->end();
for (auto it = begin; it != end; ++it)
merger((*source_maps[0])[it->first], it->second);
}
result_map = source_maps[0];
}
};
template <typename Map>
struct MergeSequentialTransposed /// На практике не лучше обычного.
{
template <typename Merger>
static void NO_INLINE execute(Map ** source_maps, size_t num_maps, Map *& result_map,
Merger && merger,
boost::threadpool::pool & pool)
{
typename Map::iterator iterators[num_maps];
for (size_t i = 1; i < num_maps; ++i)
iterators[i] = source_maps[i]->begin();
result_map = source_maps[0];
while (true)
{
bool finish = true;
for (size_t i = 1; i < num_maps; ++i)
{
if (iterators[i] == source_maps[i]->end())
continue;
finish = false;
merger((*result_map)[iterators[i]->first], iterators[i]->second);
++iterators[i];
}
if (finish)
break;
}
}
};
template <typename Map, typename ImplMerge>
struct MergeParallelForTwoLevelTable
{
template <typename Merger>
static void NO_INLINE execute(Map ** source_maps, size_t num_maps, Map *& result_map,
Merger && merger,
boost::threadpool::pool & pool)
{
for (size_t bucket = 0; bucket < Map::NUM_BUCKETS; ++bucket)
pool.schedule([&, bucket, num_maps]
{
std::vector<typename Map::Impl *> section(num_maps);
for (size_t i = 0; i < num_maps; ++i)
section[i] = &source_maps[i]->impls[bucket];
typename Map::Impl * result_map;
ImplMerge::execute(section.data(), num_maps, result_map, merger, pool);
});
pool.wait();
result_map = source_maps[0];
}
};
template <typename Map, typename Aggregate, typename Merge>
struct Work
{
template <typename Creator, typename Updater, typename Merger>
static void NO_INLINE execute(const Source & data, size_t num_threads,
Creator && creator, Updater && updater, Merger && merger,
boost::threadpool::pool & pool)
{
std::vector<std::unique_ptr<Map>> intermediate_results;
Stopwatch watch;
Aggregate::execute(data, num_threads, intermediate_results, std::forward<Creator>(creator), std::forward<Updater>(updater), pool);
size_t num_maps = intermediate_results.size();
watch.stop();
double time_aggregated = watch.elapsedSeconds();
std::cerr
<< "Aggregated in " << time_aggregated
<< " (" << data.size() / time_aggregated << " elem/sec.)"
<< std::endl;
size_t size_before_merge = 0;
std::cerr << "Sizes: ";
for (size_t i = 0; i < num_threads; ++i)
{
std::cerr << (i == 0 ? "" : ", ") << intermediate_results[i]->size();
size_before_merge += intermediate_results[i]->size();
}
std::cerr << std::endl;
watch.restart();
std::vector<Map*> intermediate_results_ptrs(num_maps);
for (size_t i = 0; i < num_maps; ++i)
intermediate_results_ptrs[i] = intermediate_results[i].get();
Map * result_map;
Merge::execute(intermediate_results_ptrs.data(), num_maps, result_map, std::forward<Merger>(merger), pool);
watch.stop();
double time_merged = watch.elapsedSeconds();
std::cerr
<< "Merged in " << time_merged
<< " (" << size_before_merge / time_merged << " elem/sec.)"
<< std::endl;
double time_total = time_aggregated + time_merged;
std::cerr
<< "Total in " << time_total
<< " (" << data.size() / time_total << " elem/sec.)"
<< std::endl;
std::cerr << "Size: " << result_map->size() << std::endl << std::endl;
}
};
typedef HashMap<Key, Value, HashCRC32<Key>> Map;
typedef TwoLevelHashMap<Key, Value, HashCRC32<Key>> MapTwoLevel;
typedef Poco::FastMutex Mutex;
struct Creator
{
void operator()(Value & x) const {}
};
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
struct Updater
{
void operator()(Value & x) const { ++x; }
};
#pragma GCC diagnostic pop
struct Merger
{
void operator()(Value & dst, const Value & src) const { dst += src; }
};
int main(int argc, char ** argv)
{
size_t n = atoi(argv[1]);
size_t num_threads = atoi(argv[2]);
size_t method = argc <= 3 ? 0 : atoi(argv[3]);
std::cerr << std::fixed << std::setprecision(2);
boost::threadpool::pool pool(num_threads);
Source data(n);
{
Stopwatch watch;
DB::ReadBufferFromFileDescriptor in1(STDIN_FILENO);
DB::CompressedReadBuffer in2(in1);
in2.readStrict(reinterpret_cast<char*>(&data[0]), sizeof(data[0]) * n);
watch.stop();
std::cerr << std::fixed << std::setprecision(2)
<< "Vector. Size: " << n
<< ", elapsed: " << watch.elapsedSeconds()
<< " (" << n / watch.elapsedSeconds() << " elem/sec.)"
<< std::endl << std::endl;
}
Creator creator;
Updater updater;
Merger merger;
if (!method || method == 1)
Work<
Map,
AggregateIndependent<Map>,
MergeSequential<Map>
>::execute(data, num_threads, creator, updater, merger, pool);
if (!method || method == 2)
Work<
Map,
AggregateIndependentWithSequentialKeysOptimization<Map>,
MergeSequential<Map>
>::execute(data, num_threads, creator, updater, merger, pool);
if (!method || method == 3)
Work<
Map,
AggregateIndependent<Map>,
MergeSequentialTransposed<Map>
>::execute(data, num_threads, creator, updater, merger, pool);
if (!method || method == 4)
Work<
Map,
AggregateIndependentWithSequentialKeysOptimization<Map>,
MergeSequentialTransposed<Map>
>::execute(data, num_threads, creator, updater, merger, pool);
if (!method || method == 5)
Work<
MapTwoLevel,
AggregateIndependent<MapTwoLevel>,
MergeSequential<MapTwoLevel>
>::execute(data, num_threads, creator, updater, merger, pool);
if (!method || method == 6)
Work<
MapTwoLevel,
AggregateIndependentWithSequentialKeysOptimization<MapTwoLevel>,
MergeSequential<MapTwoLevel>
>::execute(data, num_threads, creator, updater, merger, pool);
if (!method || method == 7)
Work<
MapTwoLevel,
AggregateIndependent<MapTwoLevel>,
MergeSequentialTransposed<MapTwoLevel>
>::execute(data, num_threads, creator, updater, merger, pool);
if (!method || method == 8)
Work<
MapTwoLevel,
AggregateIndependentWithSequentialKeysOptimization<MapTwoLevel>,
MergeSequentialTransposed<MapTwoLevel>
>::execute(data, num_threads, creator, updater, merger, pool);
if (!method || method == 9)
Work<
MapTwoLevel,
AggregateIndependent<MapTwoLevel>,
MergeParallelForTwoLevelTable<MapTwoLevel, MergeSequential<MapTwoLevel::Impl>>
>::execute(data, num_threads, creator, updater, merger, pool);
if (!method || method == 10)
Work<
MapTwoLevel,
AggregateIndependentWithSequentialKeysOptimization<MapTwoLevel>,
MergeParallelForTwoLevelTable<MapTwoLevel, MergeSequential<MapTwoLevel::Impl>>
>::execute(data, num_threads, creator, updater, merger, pool);
if (!method || method == 13)
Work<
MapTwoLevel,
AggregateIndependent<MapTwoLevel>,
MergeParallelForTwoLevelTable<MapTwoLevel, MergeSequentialTransposed<MapTwoLevel::Impl>>
>::execute(data, num_threads, creator, updater, merger, pool);
if (!method || method == 14)
Work<
MapTwoLevel,
AggregateIndependentWithSequentialKeysOptimization<MapTwoLevel>,
MergeParallelForTwoLevelTable<MapTwoLevel, MergeSequentialTransposed<MapTwoLevel::Impl>>
>::execute(data, num_threads, creator, updater, merger, pool);
return 0;
}

View File

@ -36,6 +36,7 @@ void Block::addDefaults(const NamesAndTypesList & required_columns)
Block & Block::operator= (const Block & other)
{
info = other.info;
data = other.data;
index_by_position.resize(data.size());
@ -272,6 +273,7 @@ Block Block::cloneEmpty() const
{
Block res;
res.info = info;
for (Container_t::const_iterator it = data.begin(); it != data.end(); ++it)
res.insert(it->cloneEmpty());
@ -374,6 +376,7 @@ bool blocksHaveEqualStructure(const Block & lhs, const Block & rhs)
void Block::clear()
{
info = BlockInfo();
data.clear();
index_by_name.clear();
index_by_position.clear();
@ -381,6 +384,7 @@ void Block::clear()
void Block::swap(Block & other)
{
std::swap(info, other.info);
data.swap(other.data);
index_by_name.swap(other.index_by_name);
index_by_position.swap(other.index_by_position);

View File

@ -7,32 +7,25 @@ namespace DB
{
AggregatingBlockInputStream::AggregatingBlockInputStream(BlockInputStreamPtr input_,
const Names & key_names, const AggregateDescriptions & aggregates,
bool overflow_row_, bool final_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_)
: final(final_), has_been_read(false)
{
children.push_back(input_);
aggregator = new Aggregator(key_names, aggregates, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_);
}
Block AggregatingBlockInputStream::readImpl()
{
if (has_been_read)
return Block();
has_been_read = true;
if (!executed)
{
executed = true;
AggregatedDataVariants data_variants;
aggregator->execute(children.back(), data_variants);
aggregator.execute(children.back(), data_variants);
blocks = aggregator.convertToBlocks(data_variants, final, 1);
it = blocks.begin();
}
if (isCancelled())
return Block();
Block res;
if (isCancelled() || it == blocks.end())
return res;
return aggregator->convertToBlock(data_variants, final);
res = *it;
++it;
return res;
}

View File

@ -1,113 +1,148 @@
#include <queue>
#include <iomanip>
#include <statdaemons/Stopwatch.h>
#include <DB/DataStreams/MergeSortingBlockInputStream.h>
#include <DB/DataStreams/MergingSortedBlockInputStream.h>
#include <DB/DataStreams/NativeBlockOutputStream.h>
#include <DB/DataStreams/copyData.h>
#include <DB/IO/WriteBufferFromFile.h>
#include <DB/IO/CompressedWriteBuffer.h>
namespace DB
{
Block MergeSortingBlockInputStream::readImpl()
{
/** Достаточно простой алгоритм:
* - прочитать в оперативку все блоки;
* - объединить их всех;
/** Алгоритм:
* - читать в оперативку блоки из источника;
* - когда их становится слишком много и если возможна внешняя сортировка
* - слить блоки вместе в сортированный поток и записать его во временный файл;
* - в конце, слить вместе все сортированные потоки из временных файлов, а также из накопившихся в оперативке блоков.
*/
if (has_been_read)
return Block();
has_been_read = true;
Blocks blocks;
/// Ещё не прочитали блоки.
if (!impl)
{
while (Block block = children.back()->read())
{
blocks.push_back(block);
sum_bytes_in_blocks += block.bytes();
if (isCancelled())
/** Если блоков стало слишком много и возможна внешняя сортировка,
* то сольём вместе те блоки, которые успели накопиться, и сбросим сортированный поток во временный (сжатый) файл.
* NOTE. Возможно - проверка наличия свободного места на жёстком диске.
*/
if (max_bytes_before_external_sort && sum_bytes_in_blocks > max_bytes_before_external_sort)
{
temporary_files.emplace_back(new Poco::TemporaryFile(tmp_path));
const std::string & path = temporary_files.back()->path();
WriteBufferFromFile file_buf(path);
CompressedWriteBuffer compressed_buf(file_buf);
NativeBlockOutputStream block_out(compressed_buf);
MergeSortingBlocksBlockInputStream block_in(blocks, description, max_merged_block_size, limit);
LOG_INFO(log, "Sorting and writing part of data into temporary file " + path);
copyData(block_in, block_out, &is_cancelled); /// NOTE. Возможно, ограничение на потребление места на дисках.
LOG_INFO(log, "Done writing part of data into temporary file " + path);
blocks.clear();
sum_bytes_in_blocks = 0;
}
}
if ((blocks.empty() && temporary_files.empty()) || isCancelled())
return Block();
return merge(blocks);
if (temporary_files.empty())
{
impl.reset(new MergeSortingBlocksBlockInputStream(blocks, description, max_merged_block_size, limit));
}
else
{
/// Если были сброшены временные данные в файлы.
LOG_INFO(log, "There are " << temporary_files.size() << " temporary sorted parts to merge.");
/// Сформируем сортированные потоки для слияния.
for (const auto & file : temporary_files)
{
temporary_inputs.emplace_back(new TemporaryFileStream(file->path(), data_type_factory));
inputs_to_merge.emplace_back(temporary_inputs.back()->block_in);
}
/// Оставшиеся в оперативке блоки.
if (!blocks.empty())
inputs_to_merge.emplace_back(new MergeSortingBlocksBlockInputStream(blocks, description, max_merged_block_size, limit));
/// Будем сливать эти потоки.
impl.reset(new MergingSortedBlockInputStream(inputs_to_merge, description, max_merged_block_size, limit));
}
}
return impl->read();
}
Block MergeSortingBlockInputStream::merge(Blocks & blocks)
MergeSortingBlocksBlockInputStream::MergeSortingBlocksBlockInputStream(
Blocks & blocks_, SortDescription & description_, size_t max_merged_block_size_, size_t limit_)
: blocks(blocks_), description(description_), max_merged_block_size(max_merged_block_size_), limit(limit_)
{
Blocks nonempty_blocks;
for (const auto & block : blocks)
{
if (block.rowsInFirstColumn() == 0)
continue;
nonempty_blocks.push_back(block);
cursors.emplace_back(block, description);
has_collation |= cursors.back().has_collation;
}
blocks.swap(nonempty_blocks);
if (!has_collation)
{
for (size_t i = 0; i < cursors.size(); ++i)
queue.push(SortCursor(&cursors[i]));
}
else
{
for (size_t i = 0; i < cursors.size(); ++i)
queue_with_collation.push(SortCursorWithCollation(&cursors[i]));
}
}
Block MergeSortingBlocksBlockInputStream::readImpl()
{
if (blocks.empty())
return Block();
if (blocks.size() == 1)
return blocks[0];
Stopwatch watch;
LOG_DEBUG(log, "Merge sorting");
CursorImpls cursors(blocks.size());
bool has_collation = false;
size_t nonempty_blocks = 0;
for (Blocks::const_iterator it = blocks.begin(); it != blocks.end(); ++it)
{
if (it->rowsInFirstColumn() == 0)
continue;
cursors[nonempty_blocks] = SortCursorImpl(*it, description);
has_collation |= cursors[nonempty_blocks].has_collation;
++nonempty_blocks;
Block res = blocks[0];
blocks.clear();
return res;
}
if (nonempty_blocks == 0)
return Block();
cursors.resize(nonempty_blocks);
Block merged;
if (has_collation)
merged = mergeImpl<SortCursorWithCollation>(blocks, cursors);
else
merged = mergeImpl<SortCursor>(blocks, cursors);
watch.stop();
size_t rows_before_merge = 0;
size_t bytes_before_merge = 0;
for (const auto & block : blocks)
{
rows_before_merge += block.rowsInFirstColumn();
bytes_before_merge += block.bytes();
}
LOG_DEBUG(log, std::fixed << std::setprecision(2)
<< "Merge sorted " << blocks.size() << " blocks, from " << rows_before_merge << " to " << merged.rows() << " rows"
<< " in " << watch.elapsedSeconds() << " sec., "
<< rows_before_merge / watch.elapsedSeconds() << " rows/sec., "
<< bytes_before_merge / 1048576.0 / watch.elapsedSeconds() << " MiB/sec.");
return merged;
return !has_collation
? mergeImpl<SortCursor>(queue)
: mergeImpl<SortCursorWithCollation>(queue_with_collation);
}
template <typename TSortCursor>
Block MergeSortingBlockInputStream::mergeImpl(Blocks & blocks, CursorImpls & cursors)
Block MergeSortingBlocksBlockInputStream::mergeImpl(std::priority_queue<TSortCursor> & queue)
{
Block merged = blocks[0].cloneEmpty();
size_t num_columns = blocks[0].columns();
typedef std::priority_queue<TSortCursor> Queue;
Queue queue;
for (size_t i = 0; i < cursors.size(); ++i)
queue.push(TSortCursor(&cursors[i]));
ColumnPlainPtrs merged_columns;
for (size_t i = 0; i < num_columns; ++i) /// TODO: reserve
merged_columns.push_back(&*merged.getByPosition(i).column);
merged_columns.push_back(merged.getByPosition(i).column.get());
/// Вынимаем строки в нужном порядке и кладём в merged.
for (size_t row = 0; (!limit || row < limit) && !queue.empty(); ++row)
size_t merged_rows = 0;
while (!queue.empty())
{
TSortCursor current = queue.top();
queue.pop();
@ -120,9 +155,24 @@ Block MergeSortingBlockInputStream::mergeImpl(Blocks & blocks, CursorImpls & cur
current->next();
queue.push(current);
}
++total_merged_rows;
if (limit && total_merged_rows == limit)
{
blocks.clear();
return merged;
}
++merged_rows;
if (merged_rows == max_merged_block_size)
return merged;
}
if (merged_rows == 0)
merged.clear();
return merged;
}
}

View File

@ -9,14 +9,23 @@ namespace DB
Block MergingAggregatedBlockInputStream::readImpl()
{
if (has_been_read)
return Block();
has_been_read = true;
if (!executed)
{
executed = true;
AggregatedDataVariants data_variants;
aggregator->merge(children.back(), data_variants);
return aggregator->convertToBlock(data_variants, final);
aggregator.mergeStream(children.back(), data_variants, max_threads);
blocks = aggregator.convertToBlocks(data_variants, final, max_threads);
it = blocks.begin();
}
Block res;
if (isCancelled() || it == blocks.end())
return res;
res = *it;
++it;
return res;
}

View File

@ -72,6 +72,10 @@ Block NativeBlockInputStream::readImpl()
if (istr.eof())
return res;
/// Дополнительная информация о блоке.
if (server_revision >= DBMS_MIN_REVISION_WITH_BLOCK_INFO)
res.info.read(istr);
/// Размеры
size_t columns = 0;
size_t rows = 0;

View File

@ -49,6 +49,10 @@ static void writeData(const IDataType & type, const IColumn & column, WriteBuffe
void NativeBlockOutputStream::write(const Block & block)
{
/// Дополнительная информация о блоке.
if (client_revision >= DBMS_MIN_REVISION_WITH_BLOCK_INFO)
block.info.write(ostr);
/// Размеры
size_t columns = block.columns();
size_t rows = block.rows();

View File

@ -27,8 +27,14 @@ const Block & TotalsHavingBlockInputStream::getTotals()
/** Если totals_mode == AFTER_HAVING_AUTO, нужно решить, добавлять ли в TOTALS агрегаты для строк,
* не прошедших max_rows_to_group_by.
*/
if (overflow_aggregates && static_cast<float>(passed_keys) / total_keys >= auto_include_threshold)
if (overflow_aggregates)
{
if (totals_mode == TotalsMode::BEFORE_HAVING
|| totals_mode == TotalsMode::AFTER_HAVING_INCLUSIVE
|| (totals_mode == TotalsMode::AFTER_HAVING_AUTO
&& static_cast<double>(passed_keys) / total_keys >= auto_include_threshold))
addToTotals(current_totals, overflow_aggregates, nullptr);
}
finalize(current_totals);
totals = current_totals;
@ -50,24 +56,28 @@ Block TotalsHavingBlockInputStream::readImpl()
{
block = children[0]->read();
/// Блок со значениями, не вошедшими в max_rows_to_group_by. Отложим его.
if (overflow_row && block && block.info.is_overflows)
{
overflow_aggregates = block;
continue;
}
if (!block)
return finalized;
finalized = block;
finalize(finalized);
total_keys += finalized.rows() - (overflow_row ? 1 : 0);
total_keys += finalized.rows();
if (filter_column_name.empty() || totals_mode == TotalsMode::BEFORE_HAVING)
if (filter_column_name.empty())
{
/** Включая особую нулевую строку, если overflow_row == true.
* Предполагается, что если totals_mode == AFTER_HAVING_EXCLUSIVE, нам эту строку не дадут.
*/
addToTotals(current_totals, block, nullptr);
}
if (!filter_column_name.empty())
else
{
/// Вычисляем выражение в HAVING.
expression->execute(finalized);
size_t filter_column_pos = finalized.getPositionByName(filter_column_name);
@ -85,25 +95,13 @@ Block TotalsHavingBlockInputStream::readImpl()
IColumn::Filter & filter = filter_column->getData();
if (totals_mode != TotalsMode::BEFORE_HAVING)
{
if (overflow_row)
{
filter[0] = totals_mode == TotalsMode::AFTER_HAVING_INCLUSIVE;
addToTotals(current_totals, block, &filter);
if (totals_mode == TotalsMode::AFTER_HAVING_AUTO)
addToTotals(overflow_aggregates, block, nullptr, 1);
}
/// Прибавляем значения в totals (если это не было сделано ранее).
if (totals_mode == TotalsMode::BEFORE_HAVING)
addToTotals(current_totals, block, nullptr);
else
{
addToTotals(current_totals, block, &filter);
}
}
if (overflow_row)
filter[0] = 0;
/// Фильтруем блок по выражению в HAVING.
size_t columns = finalized.columns();
for (size_t i = 0; i < columns; ++i)
@ -117,19 +115,6 @@ Block TotalsHavingBlockInputStream::readImpl()
}
}
}
else
{
if (overflow_row)
{
/// Придется выбросить одну строку из начала всех столбцов.
size_t columns = finalized.columns();
for (size_t i = 0; i < columns; ++i)
{
ColumnWithNameAndType & current_column = finalized.getByPosition(i);
current_column.column = current_column.column->cut(1, current_column.column->size() - 1);
}
}
}
if (!finalized)
continue;
@ -139,7 +124,7 @@ Block TotalsHavingBlockInputStream::readImpl()
}
}
void TotalsHavingBlockInputStream::addToTotals(Block & totals, Block & block, const IColumn::Filter * filter, size_t rows)
void TotalsHavingBlockInputStream::addToTotals(Block & totals, Block & block, const IColumn::Filter * filter)
{
bool init = !totals;
@ -188,7 +173,7 @@ void TotalsHavingBlockInputStream::addToTotals(Block & totals, Block & block, co
}
const ColumnAggregateFunction::Container_t & vec = column->getData();
size_t size = std::min(vec.size(), rows);
size_t size = vec.size();
if (filter)
{

View File

@ -7,13 +7,21 @@
namespace DB
{
void copyData(IBlockInputStream & from, IBlockOutputStream & to)
void copyData(IBlockInputStream & from, IBlockOutputStream & to, volatile bool * is_cancelled)
{
from.readPrefix();
to.writePrefix();
while (Block block = from.read())
{
if (is_cancelled && *is_cancelled)
break;
to.write(block);
}
if (is_cancelled && *is_cancelled)
return;
/// Для вывода дополнительной информации в некоторых форматах.
if (IProfilingBlockInputStream * input = dynamic_cast<IProfilingBlockInputStream *>(&from))
@ -25,6 +33,9 @@ void copyData(IBlockInputStream & from, IBlockOutputStream & to)
to.setExtremes(input->getExtremes());
}
if (is_cancelled && *is_cancelled)
return;
from.readSuffix();
to.writeSuffix();
}

View File

@ -91,7 +91,8 @@ int main(int argc, char ** argv)
}
DB::BlockInputStreamPtr stream = new DB::OneBlockInputStream(block);
stream = new DB::AggregatingBlockInputStream(stream, key_column_numbers, aggregate_descriptions, false, true, 0, DB::OverflowMode::THROW);
stream = new DB::AggregatingBlockInputStream(stream, key_column_numbers, aggregate_descriptions, false, true,
0, DB::OverflowMode::THROW, nullptr, 0);
DB::WriteBufferFromOStream ob(std::cout);
DB::RowOutputStreamPtr row_out = new DB::TabSeparatedRowOutputStream(ob, sample);

View File

@ -44,7 +44,6 @@ int main(int argc, char ** argv)
loadMetadata(context);
context.setCurrentDatabase("default");
context.setSetting("asynchronous", Field(0UL));
context.setSetting("max_threads", 1UL);
BlockIO io1 = executeQuery(

View File

@ -22,6 +22,7 @@
#include <DB/Storages/StorageLog.h>
#include <DB/Interpreters/Context.h>
#include <Yandex/Revision.h>
int main(int argc, char ** argv)
@ -107,7 +108,7 @@ int main(int argc, char ** argv)
SharedPtr<IBlockInputStream> in = table->read(column_names, 0, Context{}, Settings(), stage)[0];
WriteBufferFromOStream out1(std::cout);
CompressedWriteBuffer out2(out1);
NativeBlockOutputStream out3(out2);
NativeBlockOutputStream out3(out2, Revision::get());
copyData(*in, out3);
}
@ -118,7 +119,7 @@ int main(int argc, char ** argv)
ReadBufferFromIStream in1(std::cin);
CompressedReadBuffer in2(in1);
NativeBlockInputStream in3(in2, factory);
NativeBlockInputStream in3(in2, factory, Revision::get());
SharedPtr<IBlockOutputStream> out = table->write(0);
copyData(in3, *out);
}

View File

@ -158,10 +158,11 @@ int main(int argc, char ** argv)
sort_columns.push_back(SortColumnDescription(3, 1));
QueryProcessingStage::Enum stage;
DataTypeFactory data_type_factory;
Poco::SharedPtr<IBlockInputStream> in = table->read(column_names, 0, Context{}, Settings(), stage, argc == 2 ? atoi(argv[1]) : 1048576)[0];
in = new PartialSortingBlockInputStream(in, sort_columns);
in = new MergeSortingBlockInputStream(in, sort_columns);
in = new MergeSortingBlockInputStream(in, sort_columns, DEFAULT_BLOCK_SIZE, 0, 0, "", data_type_factory);
//in = new LimitBlockInputStream(in, 10);
WriteBufferFromOStream ob(std::cout);

View File

@ -11,6 +11,7 @@ void registerFunctionsCoding(FunctionFactory & factory)
factory.registerFunction<FunctionIPv6StringToNum>();
factory.registerFunction<FunctionIPv4NumToString>();
factory.registerFunction<FunctionIPv4StringToNum>();
factory.registerFunction<FunctionIPv4NumToStringClassC>();
factory.registerFunction<FunctionHex>();
factory.registerFunction<FunctionUnhex>();
factory.registerFunction<FunctionBitmaskToArray>();

File diff suppressed because it is too large Load Diff

View File

@ -59,8 +59,7 @@ Clusters::Clusters(const Settings & settings, const DataTypeFactory & data_type_
}
Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, const String & cluster_name):
local_nodes_num(0)
Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, const String & cluster_name)
{
Poco::Util::AbstractConfiguration & config = Poco::Util::Application::instance().config();
Poco::Util::AbstractConfiguration::Keys config_keys;
@ -81,7 +80,7 @@ Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_fa
slot_to_shard.insert(std::end(slot_to_shard), weight, shard_info_vec.size());
if (const auto is_local = isLocal(addresses.back()))
shard_info_vec.push_back({{}, weight, is_local });
shard_info_vec.push_back({{}, weight, is_local});
else
shard_info_vec.push_back({{addressToDirName(addresses.back())}, weight, is_local});
}
@ -153,15 +152,15 @@ Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_fa
if (addresses_with_failover.size())
{
for (AddressesWithFailover::const_iterator it = addresses_with_failover.begin(); it != addresses_with_failover.end(); ++it)
for (const auto & shard : addresses_with_failover)
{
ConnectionPools replicas;
replicas.reserve(it->size());
replicas.reserve(shard.size());
bool has_local_replics = false;
for (Addresses::const_iterator jt = it->begin(); jt != it->end(); ++jt)
for (const auto & replica : shard)
{
if (isLocal(*jt))
if (isLocal(replica))
{
has_local_replics = true;
break;
@ -170,7 +169,8 @@ Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_fa
{
replicas.emplace_back(new ConnectionPool(
settings.distributed_connections_pool_size,
jt->host_port.host().toString(), jt->host_port.port(), "", jt->user, jt->password, data_type_factory, "server", Protocol::Compression::Enable,
replica.host_port.host().toString(), replica.host_port.port(), "", replica.user, replica.password,
data_type_factory, "server", Protocol::Compression::Enable,
saturate(settings.connect_timeout_with_failover_ms, settings.limits.max_execution_time),
saturate(settings.receive_timeout, settings.limits.max_execution_time),
saturate(settings.send_timeout, settings.limits.max_execution_time)));
@ -185,9 +185,9 @@ Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_fa
}
else if (addresses.size())
{
for (Addresses::const_iterator it = addresses.begin(); it != addresses.end(); ++it)
for (const auto & address : addresses)
{
if (isLocal(*it))
if (isLocal(address))
{
++local_nodes_num;
}
@ -195,7 +195,8 @@ Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_fa
{
pools.emplace_back(new ConnectionPool(
settings.distributed_connections_pool_size,
it->host_port.host().toString(), it->host_port.port(), "", it->user, it->password, data_type_factory, "server", Protocol::Compression::Enable,
address.host_port.host().toString(), address.host_port.port(), "", address.user, address.password,
data_type_factory, "server", Protocol::Compression::Enable,
saturate(settings.connect_timeout, settings.limits.max_execution_time),
saturate(settings.receive_timeout, settings.limits.max_execution_time),
saturate(settings.send_timeout, settings.limits.max_execution_time)));
@ -207,31 +208,33 @@ Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_fa
}
Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, std::vector< std::vector<String> > names,
const String & username, const String & password): local_nodes_num(0)
Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, std::vector<std::vector<String>> names,
const String & username, const String & password)
{
for (size_t i = 0; i < names.size(); ++i)
for (const auto & shard : names)
{
Addresses current;
for (size_t j = 0; j < names[i].size(); ++j)
current.emplace_back(names[i][j], username, password);
for (auto & replica : shard)
current.emplace_back(replica, username, password);
addresses_with_failover.emplace_back(current);
}
for (AddressesWithFailover::const_iterator it = addresses_with_failover.begin(); it != addresses_with_failover.end(); ++it)
for (const auto & shard : addresses_with_failover)
{
ConnectionPools replicas;
replicas.reserve(it->size());
replicas.reserve(shard.size());
for (Addresses::const_iterator jt = it->begin(); jt != it->end(); ++jt)
for (const auto & replica : shard)
{
replicas.emplace_back(new ConnectionPool(
settings.distributed_connections_pool_size,
jt->host_port.host().toString(), jt->host_port.port(), "", jt->user, jt->password, data_type_factory, "server", Protocol::Compression::Enable,
replica.host_port.host().toString(), replica.host_port.port(), "", replica.user, replica.password,
data_type_factory, "server", Protocol::Compression::Enable,
saturate(settings.connect_timeout_with_failover_ms, settings.limits.max_execution_time),
saturate(settings.receive_timeout, settings.limits.max_execution_time),
saturate(settings.send_timeout, settings.limits.max_execution_time)));
}
pools.emplace_back(new ConnectionPoolWithFailover(replicas, settings.load_balancing, settings.connections_with_failover_max_tries));
}
}
@ -259,9 +262,11 @@ bool Cluster::isLocal(const Address & address)
interfaces.end() != std::find_if(interfaces.begin(), interfaces.end(),
[&](const Poco::Net::NetworkInterface & interface) { return interface.address() == address.host_port.host(); }))
{
LOG_INFO(&Poco::Util::Application::instance().logger(), "Replica with address " << address.host_port.toString() << " will be processed as local.");
LOG_INFO(&Poco::Util::Application::instance().logger(),
"Replica with address " << address.host_port.toString() << " will be processed as local.");
return true;
}
return false;
}

View File

@ -0,0 +1,242 @@
#include <stdio.h>
#include <Poco/DirectoryIterator.h>
#include <Yandex/Revision.h>
#include <DB/Common/SipHash.h>
#include <DB/IO/Operators.h>
#include <DB/IO/WriteBufferFromString.h>
#include <DB/IO/ReadBufferFromString.h>
#include <DB/IO/ReadBufferFromFileDescriptor.h>
#include <DB/IO/copyData.h>
#include <DB/IO/WriteBufferFromFile.h>
#include <DB/Interpreters/Compiler.h>
namespace DB
{
Compiler::Compiler(const std::string & path_, size_t threads)
: path(path_), pool(threads)
{
Poco::File(path).createDirectory();
Poco::DirectoryIterator dir_end;
for (Poco::DirectoryIterator dir_it(path); dir_end != dir_it; ++dir_it)
{
std::string name = dir_it.name();
if (name.length() > strlen(".so") && 0 == name.compare(name.size() - 3, 3, ".so"))
{
files.insert(name.substr(0, name.size() - 3));
}
}
LOG_INFO(log, "Having " << files.size() << " compiled files from previous start.");
}
Compiler::~Compiler()
{
LOG_DEBUG(log, "Waiting for threads to finish.");
pool.wait();
}
static Compiler::HashedKey getHash(const std::string & key)
{
SipHash hash;
auto revision = Revision::get();
hash.update(reinterpret_cast<const char *>(&revision), sizeof(revision));
hash.update(key.data(), key.size());
Compiler::HashedKey res;
hash.get128(res.first, res.second);
return res;
}
/// Без расширения .so.
static std::string hashedKeyToFileName(Compiler::HashedKey hashed_key)
{
std::string file_name;
{
WriteBufferFromString out(file_name);
out << hashed_key.first << '_' << hashed_key.second;
}
return file_name;
}
SharedLibraryPtr Compiler::getOrCount(
const std::string & key,
UInt32 min_count_to_compile,
CodeGenerator get_code,
ReadyCallback on_ready)
{
HashedKey hashed_key = getHash(key);
std::lock_guard<std::mutex> lock(mutex);
UInt32 count = ++counts[hashed_key];
/// Есть готовая открытая библиотека? Или, если библиотека в процессе компиляции, там будет nullptr.
Libraries::iterator it = libraries.find(hashed_key);
if (libraries.end() != it)
{
if (!it->second)
LOG_INFO(log, "Library " << hashedKeyToFileName(hashed_key) << " is compiling.");
/// TODO В этом случае, после окончания компиляции, не будет дёрнут колбэк.
return it->second;
}
/// Есть файл с библиотекой, оставшийся от предыдущего запуска?
std::string file_name = hashedKeyToFileName(hashed_key);
if (files.count(file_name))
{
std::string so_file_path = path + '/' + file_name + ".so";
LOG_INFO(log, "Loading existing library " << so_file_path);
SharedLibraryPtr lib(new SharedLibrary(so_file_path));
libraries[hashed_key] = lib;
return lib;
}
/// Достигнуто ли min_count_to_compile?
if (count >= min_count_to_compile)
{
/// TODO Значение min_count_to_compile, равное нулю, обозначает необходимость синхронной компиляции.
/// Есть ли свободные потоки.
if (pool.active() < pool.size())
{
/// Обозначает, что библиотека в процессе компиляции.
libraries[hashed_key] = nullptr;
LOG_INFO(log, "Compiling code " << file_name << ", key: " << key);
pool.schedule([=]
{
try
{
compile(hashed_key, file_name, get_code, on_ready);
}
catch (...)
{
tryLogCurrentException("Compiler");
}
});
}
else
LOG_INFO(log, "All threads are busy.");
}
return nullptr;
}
struct Pipe : private boost::noncopyable
{
FILE * f;
Pipe(const std::string & command)
{
errno = 0;
f = popen(command.c_str(), "r");
if (!f)
throwFromErrno("Cannot popen");
}
~Pipe()
{
try
{
errno = 0;
if (f && -1 == pclose(f))
throwFromErrno("Cannot pclose");
}
catch (...)
{
tryLogCurrentException("Pipe");
}
}
};
void Compiler::compile(HashedKey hashed_key, std::string file_name, CodeGenerator get_code, ReadyCallback on_ready)
{
std::string prefix = path + "/" + file_name;
std::string cpp_file_path = prefix + ".cpp";
std::string so_file_path = prefix + ".so";
{
WriteBufferFromFile out(cpp_file_path);
out << get_code();
}
std::stringstream command;
/// Слегка неудобно.
command <<
"/usr/share/clickhouse/bin/clang"
" -x c++ -std=gnu++11 -O3 -g -Wall -Werror -Wnon-virtual-dtor -march=native -D NDEBUG"
" -shared -fPIC -fvisibility=hidden -fno-implement-inlines"
" -isystem /usr/share/clickhouse/headers/usr/local/include/"
" -isystem /usr/share/clickhouse/headers/usr/include/"
" -isystem /usr/share/clickhouse/headers/usr/include/c++/4.8/"
" -isystem /usr/share/clickhouse/headers/usr/include/x86_64-linux-gnu/c++/4.8/"
" -isystem /usr/share/clickhouse/headers/usr/local/lib/clang/3.6.0/include/"
" -I /usr/share/clickhouse/headers/dbms/include/"
" -I /usr/share/clickhouse/headers/libs/libcityhash/"
" -I /usr/share/clickhouse/headers/libs/libcommon/include/"
" -I /usr/share/clickhouse/headers/libs/libdouble-conversion/"
" -I /usr/share/clickhouse/headers/libs/libmysqlxx/include/"
" -I /usr/share/clickhouse/headers/libs/libstatdaemons/include/"
" -I /usr/share/clickhouse/headers/libs/libstats/include/"
" -o " << so_file_path << " " << cpp_file_path
<< " 2>&1 || echo Exit code: $?";
std::string compile_result;
{
Pipe pipe(command.str());
int pipe_fd = fileno(pipe.f);
if (-1 == pipe_fd)
throwFromErrno("Cannot fileno");
{
ReadBufferFromFileDescriptor command_output(pipe_fd);
WriteBufferFromString res(compile_result);
copyData(command_output, res);
}
}
if (!compile_result.empty())
throw Exception("Cannot compile code:\n\n" + command.str() + "\n\n" + compile_result);
/// Если до этого была ошибка, то файл с кодом остаётся для возможности просмотра.
Poco::File(cpp_file_path).remove();
SharedLibraryPtr lib(new SharedLibrary(so_file_path));
{
std::lock_guard<std::mutex> lock(mutex);
libraries[hashed_key] = lib;
}
LOG_INFO(log, "Compiled code " << file_name);
on_ready(lib);
}
}

View File

@ -20,6 +20,12 @@ String Context::getPath() const
return shared->path;
}
String Context::getTemporaryPath() const
{
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
return shared->tmp_path;
}
void Context::setPath(const String & path)
{
@ -27,6 +33,12 @@ void Context::setPath(const String & path)
shared->path = path;
}
void Context::setTemporaryPath(const String & path)
{
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
shared->tmp_path = path;
}
void Context::setUsersConfig(ConfigurationPtr config)
{
@ -81,15 +93,17 @@ void Context::removeDependency(const DatabaseAndTableName & from, const Database
shared->view_dependencies[from].erase(where);
}
Dependencies Context::getDependencies(const DatabaseAndTableName & from) const
Dependencies Context::getDependencies(const String & database_name, const String & table_name) const
{
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
ViewDependencies::const_iterator iter = shared->view_dependencies.find(from);
String db = database_name.empty() ? current_database : database_name;
ViewDependencies::const_iterator iter = shared->view_dependencies.find(DatabaseAndTableName(db, table_name));
if (iter == shared->view_dependencies.end())
return Dependencies();
const std::set<DatabaseAndTableName> &buf = iter->second;
Dependencies res(buf.begin(), buf.end());
return res;
return {};
return Dependencies(iter->second.begin(), iter->second.end());
}
bool Context::isTableExist(const String & database_name, const String & table_name) const
@ -620,4 +634,17 @@ Cluster & Context::getCluster(const std::string & cluster_name)
else
throw Poco::Exception("Failed to find cluster with name = " + cluster_name);
}
Compiler & Context::getCompiler()
{
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
if (!shared->compiler)
shared->compiler.reset(new Compiler{ shared->path + "build/", 1 });
return *shared->compiler;
}
}

View File

@ -15,6 +15,7 @@
#include <DB/DataTypes/DataTypeTuple.h>
#include <DB/DataTypes/DataTypeExpression.h>
#include <DB/DataTypes/DataTypeNested.h>
#include <DB/Columns/ColumnSet.h>
#include <DB/Columns/ColumnExpression.h>
@ -26,6 +27,7 @@
#include <DB/Storages/StorageMemory.h>
#include <DB/Storages/StorageReplicatedMergeTree.h>
#include <DB/DataStreams/LazyBlockInputStream.h>
#include <DB/DataStreams/copyData.h>
#include <DB/Common/typeid_cast.h>
@ -583,19 +585,27 @@ static SharedPtr<InterpreterSelectQuery> interpretSubquery(
ASTPtr query;
if (table)
{
String query_str = "SELECT * FROM " + backQuoteIfNeed(table->name);
const char * begin = query_str.data();
const char * end = begin + query_str.size();
const char * pos = begin;
Expected expected = "";
/// create ASTSelectQuery for "SELECT * FROM table" as if written by hand
const auto select_query = new ASTSelectQuery;
query = select_query;
bool parse_res = ParserSelectQuery().parse(pos, end, query, expected);
if (!parse_res)
throw Exception("Error in parsing SELECT query while creating set or join for table " + table->name + ".",
ErrorCodes::LOGICAL_ERROR);
const auto select_expression_list = new ASTExpressionList;
select_query->select_expression_list = select_expression_list;
select_query->children.emplace_back(select_query->select_expression_list);
/// @note it may be more appropriate to manually replace ASTAsterisk with table's columns
ExpressionAnalyzer{query, context, subquery_depth};
/// get columns list for target table
const auto & storage = context.getTable("", table->name);
const auto & columns = storage->getColumnsListNonMaterialized();
select_expression_list->children.reserve(columns.size());
/// manually substitute column names in place of asterisk
for (const auto & column : columns)
select_expression_list->children.emplace_back(new ASTIdentifier{
StringRange{}, column.name
});
select_query->table = subquery_or_table_name;
select_query->children.emplace_back(select_query->table);
}
else
query = subquery->children.at(0);
@ -629,7 +639,6 @@ void ExpressionAnalyzer::addExternalStorage(ASTPtr & subquery_or_table_name)
NamesAndTypesListPtr columns = new NamesAndTypesList(sample.getColumnsList());
String external_table_name = "_data" + toString(external_table_id);
++external_table_id;
/** Заменяем подзапрос на имя временной таблицы.
* Именно в таком виде, запрос отправится на удалённый сервер.
@ -691,7 +700,37 @@ void ExpressionAnalyzer::makeSet(ASTFunction * node, const Block & sample_block)
* - в этой функции видно выражение IN _data1.
*/
if (!subquery_for_set.source)
subquery_for_set.source = interpretSubquery(arg, context, subquery_depth)->execute();
{
auto interpreter = interpretSubquery(arg, context, subquery_depth);
subquery_for_set.source = new LazyBlockInputStream([interpreter]() mutable { return interpreter->execute(); });
/** Зачем используется LazyBlockInputStream?
*
* Дело в том, что при обработке запроса вида
* SELECT ... FROM remote_test WHERE column GLOBAL IN (subquery),
* если распределённая таблица remote_test содержит в качестве одного из серверов localhost,
* то запрос будет ещё раз интерпретирован локально (а не отправлен по TCP, как в случае удалённого сервера).
*
* Конвейер выполнения запроса будет такой:
* CreatingSets
* выполнение подзапроса subquery, заполнение временной таблицы _data1 (1)
* CreatingSets
* чтение из таблицы _data1, создание множества (2)
* чтение из таблицы, подчинённой remote_test.
*
* (Вторая часть конвейера под CreatingSets - это повторная интерпретация запроса внутри StorageDistributed,
* запрос отличается тем, что имя БД и таблицы заменены на подчинённые, а также подзапрос заменён на _data1.)
*
* Но при создании конвейера, при создании источника (2), будет обнаружено, что таблица _data1 пустая
* (потому что запрос ещё не начал выполняться), и будет возвращён в качестве источника пустой источник.
* И затем, при выполнении запроса, на шаге (2), будет создано пустое множество.
*
* Поэтому, мы делаем инициализацию шага (2) ленивой
* - чтобы она произошла только после выполнения шага (1), на котором нужная таблица будет заполнена.
*
* Замечание: это решение не очень хорошее, надо подумать лучше.
*/
}
subquery_for_set.set = ast_set->set;
arg = ast_set_ptr;
@ -1389,7 +1428,10 @@ bool ExpressionAnalyzer::appendJoin(ExpressionActionsChain & chain, bool only_ty
* - в этой функции видно выражение JOIN _data1.
*/
if (!subquery_for_set.source)
subquery_for_set.source = interpretSubquery(ast_join.table, context, subquery_depth, required_joined_columns)->execute();
{
auto interpreter = interpretSubquery(ast_join.table, context, subquery_depth, required_joined_columns);
subquery_for_set.source = new LazyBlockInputStream([interpreter]() mutable { return interpreter->execute(); });
}
subquery_for_set.join = join;
}

View File

@ -8,7 +8,6 @@
#include <DB/DataStreams/AsynchronousBlockInputStream.h>
#include <DB/DataStreams/UnionBlockInputStream.h>
#include <DB/DataStreams/ParallelAggregatingBlockInputStream.h>
#include <DB/DataStreams/SplittingAggregatingBlockInputStream.h>
#include <DB/DataStreams/DistinctBlockInputStream.h>
#include <DB/DataStreams/NullBlockInputStream.h>
#include <DB/DataStreams/TotalsHavingBlockInputStream.h>
@ -33,19 +32,53 @@
namespace DB
{
void InterpreterSelectQuery::init(BlockInputStreamPtr input_, const NamesAndTypesList & table_column_names)
void InterpreterSelectQuery::init(BlockInputStreamPtr input, const Names & required_column_names, const NamesAndTypesList & table_column_names)
{
original_max_threads = settings.max_threads;
ProfileEvents::increment(ProfileEvents::SelectQuery);
if (settings.limits.max_subquery_depth && subquery_depth > settings.limits.max_subquery_depth)
throw Exception("Too deep subqueries. Maximum: " + toString(settings.limits.max_subquery_depth),
ErrorCodes::TOO_DEEP_SUBQUERIES);
if (isFirstSelectInsideUnionAll() && hasAsterisk())
{
basicInit(input, table_column_names);
// Мы выполняем этот код именно здесь, потому что в противном случае следующего рода запрос бы не срабатывал:
// SELECT X FROM (SELECT * FROM (SELECT 1 AS X, 2 AS Y) UNION ALL SELECT 3, 4)
// из-за того, что астериски заменены столбцами только при создании объектов query_analyzer в basicInit().
renameColumns();
if (!required_column_names.empty() && (context.getColumns().size() != required_column_names.size()))
{
rewriteExpressionList(required_column_names);
/// Теперь имеется устаревшая информация для выполнения запроса. Обновляем эту информацию.
initQueryAnalyzer();
}
}
else
{
renameColumns();
if (!required_column_names.empty())
rewriteExpressionList(required_column_names);
basicInit(input, table_column_names);
}
}
void InterpreterSelectQuery::basicInit(BlockInputStreamPtr input_, const NamesAndTypesList & table_column_names)
{
if (query.table && typeid_cast<ASTSelectQuery *>(&*query.table))
{
if (table_column_names.empty())
{
/// Оптимизация: мы считаем, что запрос содержит только один SELECT, даже если это может быть
/// в самом деле цепочкой UNION ALL. Первый запрос достаточен для определения нужных столбцов.
context.setColumns(InterpreterSelectQuery(query.table, context, to_stage, subquery_depth, nullptr, false).getSampleBlock().getColumnsList());
}
}
else
{
if (query.table && typeid_cast<const ASTFunction *>(&*query.table))
@ -76,7 +109,7 @@ void InterpreterSelectQuery::init(BlockInputStreamPtr input_, const NamesAndType
if (context.getColumns().empty())
throw Exception("There are no available columns", ErrorCodes::THERE_IS_NO_COLUMN);
query_analyzer = new ExpressionAnalyzer(query_ptr, context, storage, subquery_depth, true);
query_analyzer.reset(new ExpressionAnalyzer(query_ptr, context, storage, subquery_depth, true));
/// Сохраняем в query context новые временные таблицы
for (auto & it : query_analyzer->getExternalTables())
@ -88,9 +121,9 @@ void InterpreterSelectQuery::init(BlockInputStreamPtr input_, const NamesAndType
if (isFirstSelectInsideUnionAll())
{
// Создаем цепочку запросов SELECT и проверяем, что результаты всех запросов SELECT cовместимые.
// NOTE Мы можем безопасно применить static_cast вместо typeid_cast,
// потому что знаем, что в цепочке UNION ALL имеются только деревья типа SELECT.
/// Создаем цепочку запросов SELECT и проверяем, что результаты всех запросов SELECT cовместимые.
/// NOTE Мы можем безопасно применить static_cast вместо typeid_cast,
/// потому что знаем, что в цепочке UNION ALL имеются только деревья типа SELECT.
InterpreterSelectQuery * interpreter = this;
Block first = interpreter->getSampleBlock();
for (ASTPtr tree = query.next_union_all; !tree.isNull(); tree = (static_cast<ASTSelectQuery &>(*tree)).next_union_all)
@ -99,12 +132,20 @@ void InterpreterSelectQuery::init(BlockInputStreamPtr input_, const NamesAndType
interpreter = interpreter->next_select_in_union_all.get();
Block current = interpreter->getSampleBlock();
if (!blocksHaveEqualStructure(first, current))
throw Exception("Result structures mismatch in the SELECT queries of the UNION ALL chain",
throw Exception("Result structures mismatch in the SELECT queries of the UNION ALL chain. Found result structure:\n\n" + current.dumpStructure()
+ "\n\nwhile expecting:\n\n" + first.dumpStructure() + "\n\ninstead",
ErrorCodes::UNION_ALL_RESULT_STRUCTURES_MISMATCH);
}
}
}
void InterpreterSelectQuery::initQueryAnalyzer()
{
query_analyzer.reset(new ExpressionAnalyzer(query_ptr, context, storage, subquery_depth, true));
for (auto p = next_select_in_union_all.get(); p != nullptr; p = p->next_select_in_union_all.get())
p->query_analyzer.reset(new ExpressionAnalyzer(p->query_ptr, p->context, p->storage, p->subquery_depth, true));
}
InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context & context_, QueryProcessingStage::Enum to_stage_,
size_t subquery_depth_, BlockInputStreamPtr input_, bool is_union_all_head_)
: query_ptr(query_ptr_), query(typeid_cast<ASTSelectQuery &>(*query_ptr)),
@ -123,13 +164,7 @@ InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context
is_union_all_head(true),
log(&Logger::get("InterpreterSelectQuery"))
{
/** Оставляем в запросе в секции SELECT только нужные столбцы.
* Но если используется DISTINCT, то все столбцы считаются нужными, так как иначе DISTINCT работал бы по-другому.
*/
if (!query.distinct)
query.rewriteSelectExpressionList(required_column_names_);
init(input_);
init(input_, required_column_names_);
}
InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context & context_,
@ -140,13 +175,56 @@ InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context
is_union_all_head(true),
log(&Logger::get("InterpreterSelectQuery"))
{
/** Оставляем в запросе в секции SELECT только нужные столбцы.
* Но если используется DISTINCT, то все столбцы считаются нужными, так как иначе DISTINCT работал бы по-другому.
*/
if (!query.distinct)
query.rewriteSelectExpressionList(required_column_names_);
init(input_, required_column_names_, table_column_names);
}
init(input_, table_column_names);
bool InterpreterSelectQuery::hasAsterisk() const
{
if (query.hasAsterisk())
return true;
if (isFirstSelectInsideUnionAll())
for (IAST * tree = query.next_union_all.get(); tree != nullptr; tree = static_cast<ASTSelectQuery *>(tree)->next_union_all.get())
{
const auto & next_query = static_cast<ASTSelectQuery &>(*tree);
if (next_query.hasAsterisk())
return true;
}
return false;
}
void InterpreterSelectQuery::renameColumns()
{
if (isFirstSelectInsideUnionAll())
for (IAST * tree = query.next_union_all.get(); tree != nullptr; tree = static_cast<ASTSelectQuery *>(tree)->next_union_all.get())
{
auto & ast = static_cast<ASTSelectQuery &>(*tree);
ast.renameColumns(query);
}
}
void InterpreterSelectQuery::rewriteExpressionList(const Names & required_column_names)
{
if (query.distinct)
return;
if (isFirstSelectInsideUnionAll())
for (IAST * tree = query.next_union_all.get(); tree != nullptr; tree = static_cast<ASTSelectQuery *>(tree)->next_union_all.get())
{
auto & next_query = static_cast<ASTSelectQuery &>(*tree);
if (next_query.distinct)
return;
}
query.rewriteSelectExpressionList(required_column_names);
if (isFirstSelectInsideUnionAll())
for (IAST * tree = query.next_union_all.get(); tree != nullptr; tree = static_cast<ASTSelectQuery *>(tree)->next_union_all.get())
{
auto & next_query = static_cast<ASTSelectQuery &>(*tree);
next_query.rewriteSelectExpressionList(required_column_names);
}
}
bool InterpreterSelectQuery::isFirstSelectInsideUnionAll() const
@ -205,41 +283,14 @@ Block InterpreterSelectQuery::getSampleBlock()
}
/// Превращает источник в асинхронный, если это указано.
static inline BlockInputStreamPtr maybeAsynchronous(BlockInputStreamPtr in, bool is_async)
{
return is_async
? new AsynchronousBlockInputStream(in)
: in;
}
BlockInputStreamPtr InterpreterSelectQuery::execute()
{
if (isFirstSelectInsideUnionAll())
{
executeSingleQuery(false);
for (auto p = next_select_in_union_all.get(); p != nullptr; p = p->next_select_in_union_all.get())
{
p->executeSingleQuery(false);
const auto & others = p->streams;
streams.insert(streams.end(), others.begin(), others.end());
}
(void) executeWithoutUnion();
if (streams.empty())
return new NullBlockInputStream;
for (auto & stream : streams)
stream = new MaterializingBlockInputStream(stream);
executeUnion(streams);
}
else
{
executeSingleQuery();
if (streams.empty())
return new NullBlockInputStream;
}
/// Ограничения на результат, квота на результат, а также колбек для прогресса.
if (IProfilingBlockInputStream * stream = dynamic_cast<IProfilingBlockInputStream *>(&*streams[0]))
@ -264,8 +315,28 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
return streams[0];
}
const BlockInputStreams & InterpreterSelectQuery::executeWithoutUnion()
{
if (isFirstSelectInsideUnionAll())
{
executeSingleQuery();
for (auto p = next_select_in_union_all.get(); p != nullptr; p = p->next_select_in_union_all.get())
{
p->executeSingleQuery();
const auto & others = p->streams;
streams.insert(streams.end(), others.begin(), others.end());
}
void InterpreterSelectQuery::executeSingleQuery(bool should_perform_union_hint)
for (auto & stream : streams)
stream = new MaterializingBlockInputStream(stream);
}
else
executeSingleQuery();
return streams;
}
void InterpreterSelectQuery::executeSingleQuery()
{
/** Потоки данных. При параллельном выполнении запроса, имеем несколько потоков данных.
* Если нет GROUP BY, то выполним все операции до ORDER BY и LIMIT параллельно, затем
@ -279,7 +350,7 @@ void InterpreterSelectQuery::executeSingleQuery(bool should_perform_union_hint)
* то объединение источников данных выполняется не на этом уровне, а на верхнем уровне.
*/
bool do_execute_union = should_perform_union_hint;
bool do_execute_union = false;
/** Вынем данные из Storage. from_stage - до какой стадии запрос был выполнен в Storage. */
QueryProcessingStage::Enum from_stage = executeFetchColumns(streams);
@ -450,9 +521,9 @@ void InterpreterSelectQuery::executeSingleQuery(bool should_perform_union_hint)
/// На этой стадии можно считать минимумы и максимумы, если надо.
if (settings.extremes)
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
if (IProfilingBlockInputStream * stream = dynamic_cast<IProfilingBlockInputStream *>(&**it))
stream->enableExtremes();
for (auto & stream : streams)
if (IProfilingBlockInputStream * p_stream = dynamic_cast<IProfilingBlockInputStream *>(&*stream))
p_stream->enableExtremes();
/** Оптимизация - если источников несколько и есть LIMIT, то сначала применим предварительный LIMIT,
* ограничивающий число записей в каждом до offset + limit.
@ -481,9 +552,6 @@ void InterpreterSelectQuery::executeSingleQuery(bool should_perform_union_hint)
if (streams.empty())
return;
if (do_execute_union)
executeUnion(streams);
SubqueriesForSets subqueries_for_sets = query_analyzer->getSubqueriesForSets();
if (!subqueries_for_sets.empty())
executeSubqueriesInSetsAndJoins(streams, subqueries_for_sets);
@ -580,7 +648,6 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
{
settings.max_block_size = limit_length + limit_offset;
settings.max_threads = 1;
settings.asynchronous = false;
}
QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns;
@ -605,7 +672,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
}
else
{
streams.push_back(maybeAsynchronous(interpreter_subquery->execute(), settings.asynchronous));
streams.push_back(interpreter_subquery->execute());
}
/** Если истчоников слишком много, то склеим их в max_threads источников.
@ -632,12 +699,12 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
QuotaForIntervals & quota = context.getQuota();
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
for (auto & stream : streams)
{
if (IProfilingBlockInputStream * stream = dynamic_cast<IProfilingBlockInputStream *>(&**it))
if (IProfilingBlockInputStream * p_stream = dynamic_cast<IProfilingBlockInputStream *>(&*stream))
{
stream->setLimits(limits);
stream->setQuota(quota);
p_stream->setLimits(limits);
p_stream->setQuota(quota);
}
}
}
@ -648,23 +715,19 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
void InterpreterSelectQuery::executeWhere(BlockInputStreams & streams, ExpressionActionsPtr expression)
{
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
for (auto & stream : streams)
{
BlockInputStreamPtr & stream = *it;
stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async);
stream = maybeAsynchronous(new FilterBlockInputStream(stream, query.where_expression->getColumnName()), is_async);
stream = new ExpressionBlockInputStream(stream, expression);
stream = new FilterBlockInputStream(stream, query.where_expression->getColumnName());
}
}
void InterpreterSelectQuery::executeAggregation(BlockInputStreams & streams, ExpressionActionsPtr expression, bool overflow_row, bool final)
{
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
for (auto & stream : streams)
{
BlockInputStreamPtr & stream = *it;
stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async);
stream = new ExpressionBlockInputStream(stream, expression);
}
BlockInputStreamPtr & stream = streams[0];
@ -673,36 +736,19 @@ void InterpreterSelectQuery::executeAggregation(BlockInputStreams & streams, Exp
AggregateDescriptions aggregates;
query_analyzer->getAggregateInfo(key_names, aggregates);
bool separate_totals = to_stage > QueryProcessingStage::WithMergeableState;
/// Если источников несколько, то выполняем параллельную агрегацию
if (streams.size() > 1)
{
if (!settings.use_splitting_aggregator || key_names.empty())
{
stream = maybeAsynchronous(
new ParallelAggregatingBlockInputStream(streams, key_names, aggregates, overflow_row, final,
settings.max_threads, settings.limits.max_rows_to_group_by, settings.limits.group_by_overflow_mode), settings.asynchronous);
}
else
{
if (overflow_row)
throw Exception("Splitting aggregator cannot handle queries like this yet. "
"Please change use_splitting_aggregator, remove WITH TOTALS, "
"change group_by_overflow_mode or set totals_mode to AFTER_HAVING_EXCLUSIVE.",
ErrorCodes::NOT_IMPLEMENTED);
stream = maybeAsynchronous(
new SplittingAggregatingBlockInputStream(
new UnionBlockInputStream(streams, settings.max_threads),
key_names, aggregates, settings.max_threads, query.group_by_with_totals, separate_totals, final,
settings.limits.max_rows_to_group_by, settings.limits.group_by_overflow_mode), settings.asynchronous);
}
stream = new ParallelAggregatingBlockInputStream(streams, key_names, aggregates, overflow_row, final,
settings.max_threads, settings.limits.max_rows_to_group_by, settings.limits.group_by_overflow_mode,
settings.compile ? &context.getCompiler() : nullptr, settings.min_count_to_compile);
streams.resize(1);
}
else
stream = maybeAsynchronous(new AggregatingBlockInputStream(stream, key_names, aggregates, overflow_row, final,
settings.limits.max_rows_to_group_by, settings.limits.group_by_overflow_mode), settings.asynchronous);
stream = new AggregatingBlockInputStream(stream, key_names, aggregates, overflow_row, final,
settings.limits.max_rows_to_group_by, settings.limits.group_by_overflow_mode,
settings.compile ? &context.getCompiler() : nullptr, settings.min_count_to_compile);
}
@ -720,18 +766,16 @@ void InterpreterSelectQuery::executeMergeAggregated(BlockInputStreams & streams,
Names key_names;
AggregateDescriptions aggregates;
query_analyzer->getAggregateInfo(key_names, aggregates);
streams[0] = maybeAsynchronous(new MergingAggregatedBlockInputStream(streams[0], key_names, aggregates, overflow_row, final), settings.asynchronous);
streams[0] = new MergingAggregatedBlockInputStream(streams[0], key_names, aggregates, overflow_row, final, original_max_threads);
}
void InterpreterSelectQuery::executeHaving(BlockInputStreams & streams, ExpressionActionsPtr expression)
{
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
for (auto & stream : streams)
{
BlockInputStreamPtr & stream = *it;
stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async);
stream = maybeAsynchronous(new FilterBlockInputStream(stream, query.having_expression->getColumnName()), is_async);
stream = new ExpressionBlockInputStream(stream, expression);
stream = new FilterBlockInputStream(stream, query.having_expression->getColumnName());
}
}
@ -748,20 +792,17 @@ void InterpreterSelectQuery::executeTotalsAndHaving(BlockInputStreams & streams,
Names key_names;
AggregateDescriptions aggregates;
query_analyzer->getAggregateInfo(key_names, aggregates);
streams[0] = maybeAsynchronous(new TotalsHavingBlockInputStream(
streams[0] = new TotalsHavingBlockInputStream(
streams[0], key_names, aggregates, overflow_row, expression,
has_having ? query.having_expression->getColumnName() : "", settings.totals_mode, settings.totals_auto_threshold),
settings.asynchronous);
has_having ? query.having_expression->getColumnName() : "", settings.totals_mode, settings.totals_auto_threshold);
}
void InterpreterSelectQuery::executeExpression(BlockInputStreams & streams, ExpressionActionsPtr expression)
{
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
for (auto & stream : streams)
{
BlockInputStreamPtr & stream = *it;
stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async);
stream = new ExpressionBlockInputStream(stream, expression);
}
}
@ -775,7 +816,9 @@ void InterpreterSelectQuery::executeOrder(BlockInputStreams & streams)
++it)
{
String name = (*it)->children.front()->getColumnName();
order_descr.push_back(SortColumnDescription(name, typeid_cast<ASTOrderByElement &>(**it).direction));
const ASTOrderByElement & order_by_elem = typeid_cast<const ASTOrderByElement &>(**it);
order_descr.emplace_back(name, order_by_elem.direction, order_by_elem.collator);
}
/// Если есть LIMIT и нет DISTINCT - можно делать частичную сортировку.
@ -788,10 +831,8 @@ void InterpreterSelectQuery::executeOrder(BlockInputStreams & streams)
limit = limit_length + limit_offset;
}
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
for (auto & stream : streams)
{
BlockInputStreamPtr & stream = *it;
IProfilingBlockInputStream * sorting_stream = new PartialSortingBlockInputStream(stream, order_descr, limit);
/// Ограничения на сортировку
@ -802,7 +843,7 @@ void InterpreterSelectQuery::executeOrder(BlockInputStreams & streams)
limits.read_overflow_mode = settings.limits.sort_overflow_mode;
sorting_stream->setLimits(limits);
stream = maybeAsynchronous(sorting_stream, is_async);
stream = sorting_stream;
}
BlockInputStreamPtr & stream = streams[0];
@ -814,18 +855,18 @@ void InterpreterSelectQuery::executeOrder(BlockInputStreams & streams)
streams.resize(1);
}
/// Сливаем сортированные блоки TODO: таймаут на слияние.
stream = maybeAsynchronous(new MergeSortingBlockInputStream(stream, order_descr, limit), is_async);
/// Сливаем сортированные блоки.
stream = new MergeSortingBlockInputStream(
stream, order_descr, settings.max_block_size, limit,
settings.limits.max_bytes_before_external_sort, context.getTemporaryPath(), context.getDataTypeFactory());
}
void InterpreterSelectQuery::executeProjection(BlockInputStreams & streams, ExpressionActionsPtr expression)
{
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
for (auto & stream : streams)
{
BlockInputStreamPtr & stream = *it;
stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async);
stream = new ExpressionBlockInputStream(stream, expression);
}
}
@ -844,12 +885,9 @@ void InterpreterSelectQuery::executeDistinct(BlockInputStreams & streams, bool b
if (!query.order_expression_list || !before_order)
limit_for_distinct = limit_length + limit_offset;
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
for (auto & stream : streams)
{
BlockInputStreamPtr & stream = *it;
stream = maybeAsynchronous(new DistinctBlockInputStream(
stream, settings.limits, limit_for_distinct, columns), is_async);
stream = new DistinctBlockInputStream(stream, settings.limits, limit_for_distinct, columns);
}
}
}
@ -876,9 +914,8 @@ void InterpreterSelectQuery::executePreLimit(BlockInputStreams & streams)
/// Если есть LIMIT
if (query.limit_length)
{
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
for (auto & stream : streams)
{
BlockInputStreamPtr & stream = *it;
stream = new LimitBlockInputStream(stream, limit_length + limit_offset, 0);
}
}

View File

@ -1,465 +0,0 @@
#include <iomanip>
#include <DB/Columns/ColumnString.h>
#include <DB/Columns/ColumnFixedString.h>
#include <DB/Interpreters/SplittingAggregator.h>
namespace DB
{
void SplittingAggregator::execute(BlockInputStreamPtr stream, ManyAggregatedDataVariants & results)
{
/// Читаем все данные
while (Block block = stream->read())
{
initialize(block);
src_rows += block.rows();
src_bytes += block.bytes();
for (size_t i = 0; i < aggregates_size; ++i)
aggregate_columns[i].resize(aggregates[i].arguments.size());
/// Запоминаем столбцы, с которыми будем работать
for (size_t i = 0; i < keys_size; ++i)
key_columns[i] = block.getByPosition(keys[i]).column;
for (size_t i = 0; i < aggregates_size; ++i)
{
for (size_t j = 0; j < aggregate_columns[i].size(); ++j)
{
aggregate_columns[i][j] = block.getByPosition(aggregates[i].arguments[j]).column;
/** Агрегатные функции рассчитывают, что в них передаются полноценные столбцы.
* Поэтому, стобцы-константы не разрешены в качестве аргументов агрегатных функций.
*/
if (aggregate_columns[i][j]->isConst())
throw Exception("Constants is not allowed as arguments of aggregate functions", ErrorCodes::ILLEGAL_COLUMN);
}
}
rows = block.rows();
/// Каким способом выполнять агрегацию?
if (method == AggregatedDataVariants::EMPTY)
method = chooseAggregationMethod(key_columns, key_sizes);
/// Подготавливаем массивы, куда будут складываться ключи или хэши от ключей.
if (method == AggregatedDataVariants::KEY_8 /// TODO не использовать SplittingAggregator для маленьких ключей.
|| method == AggregatedDataVariants::KEY_16
|| method == AggregatedDataVariants::KEY_32
|| method == AggregatedDataVariants::KEY_64)
{
keys64.resize(rows);
}
else if (method == AggregatedDataVariants::KEY_STRING || method == AggregatedDataVariants::KEY_FIXED_STRING)
{
hashes64.resize(rows);
string_refs.resize(rows);
}
else if (method == AggregatedDataVariants::KEYS_128)
{
keys128.resize(rows);
}
else if (method == AggregatedDataVariants::HASHED)
{
hashes128.resize(rows);
}
else
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
thread_nums.resize(rows);
if (results.empty())
{
results.resize(threads);
for (size_t i = 0; i < threads; ++i)
{
results[i] = new AggregatedDataVariants;
results[i]->init(method);
results[i]->keys_size = keys_size;
results[i]->key_sizes = key_sizes;
}
}
Exceptions exceptions(threads);
/// Параллельно вычисляем хэши и ключи.
for (size_t thread_no = 0; thread_no < threads; ++thread_no)
pool.schedule(std::bind(&SplittingAggregator::calculateHashesThread, this,
std::ref(block),
rows * thread_no / threads,
rows * (thread_no + 1) / threads,
std::ref(exceptions[thread_no]),
current_memory_tracker));
pool.wait();
rethrowFirstException(exceptions); /// TODO Заменить на future, packaged_task
/// Параллельно агрегируем в независимые хэш-таблицы
for (size_t thread_no = 0; thread_no < threads; ++thread_no)
pool.schedule(std::bind(&SplittingAggregator::aggregateThread, this,
std::ref(block),
std::ref(*results[thread_no]),
thread_no,
std::ref(exceptions[thread_no]),
current_memory_tracker));
pool.wait();
rethrowFirstException(exceptions);
/// Проверка ограничений
if (max_rows_to_group_by && size_of_all_results > max_rows_to_group_by && group_by_overflow_mode == OverflowMode::BREAK)
break;
}
}
void SplittingAggregator::convertToBlocks(ManyAggregatedDataVariants & data_variants, Blocks & blocks, bool final)
{
if (data_variants.empty())
return;
blocks.resize(data_variants.size());
Exceptions exceptions(threads);
/// Параллельно конвертируем в блоки.
for (size_t thread_no = 0; thread_no < threads; ++thread_no)
pool.schedule(std::bind(&SplittingAggregator::convertToBlockThread, this,
std::ref(*data_variants[thread_no]),
std::ref(blocks[thread_no]),
final,
std::ref(exceptions[thread_no]),
current_memory_tracker));
pool.wait();
rethrowFirstException(exceptions);
}
void SplittingAggregator::calculateHashesThread(Block & block, size_t begin, size_t end, ExceptionPtr & exception, MemoryTracker * memory_tracker)
{
current_memory_tracker = memory_tracker;
try
{
if (method == AggregatedDataVariants::KEY_8
|| method == AggregatedDataVariants::KEY_16
|| method == AggregatedDataVariants::KEY_32
|| method == AggregatedDataVariants::KEY_64)
{
const IColumn & column = *key_columns[0];
for (size_t i = begin; i < end; ++i)
{
keys64[i] = column.get64(i); /// TODO Убрать виртуальный вызов
thread_nums[i] = intHash32<0xd1f93e3190506c7cULL>(keys64[i]) % threads; /// TODO более эффективная хэш-функция
}
}
else if (method == AggregatedDataVariants::KEY_STRING)
{
const IColumn & column = *key_columns[0];
const ColumnString & column_string = typeid_cast<const ColumnString &>(column);
const ColumnString::Offsets_t & offsets = column_string.getOffsets();
const ColumnString::Chars_t & data = column_string.getChars();
for (size_t i = begin; i < end; ++i)
{
string_refs[i] = StringRef(&data[i == 0 ? 0 : offsets[i - 1]], (i == 0 ? offsets[i] : (offsets[i] - offsets[i - 1])) - 1);
hashes64[i] = hash_func_string(string_refs[i]);
thread_nums[i] = (hashes64[i] >> 32) % threads;
}
}
else if (method == AggregatedDataVariants::KEY_FIXED_STRING)
{
const IColumn & column = *key_columns[0];
const ColumnFixedString & column_string = typeid_cast<const ColumnFixedString &>(column);
size_t n = column_string.getN();
const ColumnFixedString::Chars_t & data = column_string.getChars();
for (size_t i = begin; i < end; ++i)
{
string_refs[i] = StringRef(&data[i * n], n);
hashes64[i] = hash_func_string(string_refs[i]);
thread_nums[i] = (hashes64[i] >> 32) % threads;
}
}
else if (method == AggregatedDataVariants::KEYS_128)
{
for (size_t i = begin; i < end; ++i)
{
keys128[i] = pack128(i, keys_size, key_columns, key_sizes);
thread_nums[i] = (intHash32<0xd1f93e3190506c7cULL>(intHash32<0x271e6f39e4bd34c3ULL>(keys128[i].first) ^ keys128[i].second)) % threads;
}
}
else if (method == AggregatedDataVariants::HASHED)
{
for (size_t i = begin; i < end; ++i)
{
hashes128[i] = hash128(i, keys_size, key_columns);
thread_nums[i] = hashes128[i].second % threads;
}
}
else
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
}
catch (...)
{
exception = cloneCurrentException();
}
}
template <typename FieldType>
void SplittingAggregator::aggregateOneNumber(AggregatedDataVariants & result, size_t thread_no, bool no_more_keys)
{
AggregatedDataWithUInt64Key & res = result.key64->data;
for (size_t i = 0; i < rows; ++i)
{
if (thread_nums[i] != thread_no)
continue;
/// Берём ключ
UInt64 key = keys64[i];
AggregatedDataWithUInt64Key::iterator it;
bool inserted;
if (!no_more_keys)
res.emplace(key, it, inserted);
else
{
inserted = false;
it = res.find(key);
if (res.end() == it)
continue;
}
if (inserted)
{
it->second = result.aggregates_pool->alloc(total_size_of_aggregate_states);
createAggregateStates(it->second);
}
/// Добавляем значения
for (size_t j = 0; j < aggregates_size; ++j)
aggregate_functions[j]->add(it->second + offsets_of_aggregate_states[j], &aggregate_columns[j][0], i);
}
}
void SplittingAggregator::aggregateThread(
Block & block, AggregatedDataVariants & result, size_t thread_no, ExceptionPtr & exception, MemoryTracker * memory_tracker)
{
current_memory_tracker = memory_tracker;
try
{
result.aggregator = this;
/** Используется, если есть ограничение на максимальное количество строк при агрегации,
* и если group_by_overflow_mode == ANY.
* В этом случае, новые ключи не добавляются в набор, а производится агрегация только по
* ключам, которые уже успели попасть в набор.
*/
bool no_more_keys = max_rows_to_group_by && size_of_all_results > max_rows_to_group_by;
size_t old_result_size = result.size();
if (method == AggregatedDataVariants::KEY_8)
aggregateOneNumber<UInt8>(result, thread_no, no_more_keys);
else if (method == AggregatedDataVariants::KEY_16)
aggregateOneNumber<UInt16>(result, thread_no, no_more_keys);
else if (method == AggregatedDataVariants::KEY_32)
aggregateOneNumber<UInt32>(result, thread_no, no_more_keys);
else if (method == AggregatedDataVariants::KEY_64)
aggregateOneNumber<UInt64>(result, thread_no, no_more_keys);
else if (method == AggregatedDataVariants::KEY_STRING)
{
AggregatedDataWithStringKey & res = result.key_string->data;
for (size_t i = 0; i < rows; ++i)
{
if (thread_nums[i] != thread_no)
continue;
AggregatedDataWithStringKey::iterator it;
bool inserted;
StringRef ref = string_refs[i];
if (!no_more_keys)
res.emplace(ref, it, inserted, hashes64[i]);
else
{
inserted = false;
it = res.find(ref);
if (res.end() == it)
continue;
}
if (inserted)
{
it->first.data = result.aggregates_pool->insert(ref.data, ref.size);
it->second = result.aggregates_pool->alloc(total_size_of_aggregate_states);
createAggregateStates(it->second);
}
/// Добавляем значения
for (size_t j = 0; j < aggregates_size; ++j)
aggregate_functions[j]->add(it->second + offsets_of_aggregate_states[j], &aggregate_columns[j][0], i);
}
}
else if (method == AggregatedDataVariants::KEY_FIXED_STRING)
{
AggregatedDataWithStringKey & res = result.key_fixed_string->data;
for (size_t i = 0; i < rows; ++i)
{
if (thread_nums[i] != thread_no)
continue;
AggregatedDataWithStringKey::iterator it;
bool inserted;
StringRef ref = string_refs[i];
if (!no_more_keys)
res.emplace(ref, it, inserted, hashes64[i]);
else
{
inserted = false;
it = res.find(ref);
if (res.end() == it)
continue;
}
if (inserted)
{
it->first.data = result.aggregates_pool->insert(ref.data, ref.size);
it->second = result.aggregates_pool->alloc(total_size_of_aggregate_states);
createAggregateStates(it->second);
}
/// Добавляем значения
for (size_t j = 0; j < aggregates_size; ++j)
aggregate_functions[j]->add(it->second + offsets_of_aggregate_states[j], &aggregate_columns[j][0], i);
}
}
else if (method == AggregatedDataVariants::KEYS_128)
{
AggregatedDataWithKeys128 & res = result.keys128->data;
for (size_t i = 0; i < rows; ++i)
{
if (thread_nums[i] != thread_no)
continue;
AggregatedDataWithKeys128::iterator it;
bool inserted;
UInt128 key128 = keys128[i];
if (!no_more_keys)
res.emplace(key128, it, inserted);
else
{
inserted = false;
it = res.find(key128);
if (res.end() == it)
continue;
}
if (inserted)
{
it->second = result.aggregates_pool->alloc(total_size_of_aggregate_states);
createAggregateStates(it->second);
}
/// Добавляем значения
for (size_t j = 0; j < aggregates_size; ++j)
aggregate_functions[j]->add(it->second + offsets_of_aggregate_states[j], &aggregate_columns[j][0], i);
}
}
else if (method == AggregatedDataVariants::HASHED)
{
StringRefs key(keys_size);
AggregatedDataHashed & res = result.hashed->data;
for (size_t i = 0; i < rows; ++i)
{
if (thread_nums[i] != thread_no)
continue;
AggregatedDataHashed::iterator it;
bool inserted;
UInt128 key128 = hashes128[i];
if (!no_more_keys)
res.emplace(key128, it, inserted);
else
{
inserted = false;
it = res.find(key128);
if (res.end() == it)
continue;
}
if (inserted)
{
it->second.first = extractKeysAndPlaceInPool(i, keys_size, key_columns, key, *result.aggregates_pool);
it->second.second = result.aggregates_pool->alloc(total_size_of_aggregate_states);
createAggregateStates(it->second.second);
}
/// Добавляем значения
for (size_t j = 0; j < aggregates_size; ++j)
aggregate_functions[j]->add(it->second.second + offsets_of_aggregate_states[j], &aggregate_columns[j][0], i);
}
}
else
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
/// Проверка ограничений.
size_t current_size_of_all_results = __sync_add_and_fetch(&size_of_all_results, result.size() - old_result_size);
if (max_rows_to_group_by && current_size_of_all_results > max_rows_to_group_by && group_by_overflow_mode == OverflowMode::THROW)
throw Exception("Limit for rows to GROUP BY exceeded: has " + toString(current_size_of_all_results)
+ " rows, maximum: " + toString(max_rows_to_group_by),
ErrorCodes::TOO_MUCH_ROWS);
}
catch (...)
{
exception = cloneCurrentException();
}
}
void SplittingAggregator::convertToBlockThread(
AggregatedDataVariants & data_variant, Block & block, bool final, ExceptionPtr & exception, MemoryTracker * memory_tracker)
{
current_memory_tracker = memory_tracker;
try
{
block = convertToBlock(data_variant, final);
}
catch (...)
{
exception = cloneCurrentException();
}
}
}

View File

@ -9,7 +9,13 @@ typedef std::vector<std::pair<const IColumn *, SortColumnDescription> > ColumnsW
static inline bool needCollation(const IColumn * column, const SortColumnDescription & description)
{
return !description.collator.isNull() && column->getName() == "ColumnString";
if (description.collator.isNull())
return false;
if (column->getName() != "ColumnString")
throw Exception("Collations could be specified only for String columns.", ErrorCodes::BAD_COLLATION);
return true;
}

View File

@ -73,7 +73,7 @@ int main(int argc, char ** argv)
DB::DataTypes empty_list_of_types;
aggregate_descriptions[0].function = factory.get("count", empty_list_of_types);
DB::Aggregator aggregator(key_column_numbers, aggregate_descriptions, false);
DB::Aggregator aggregator(key_column_numbers, aggregate_descriptions, false, 0, DB::OverflowMode::THROW, nullptr, 0);
{
Poco::Stopwatch stopwatch;

View File

@ -0,0 +1,30 @@
#include <Poco/ConsoleChannel.h>
#include <Poco/AutoPtr.h>
#include <DB/Interpreters/Compiler.h>
int main(int argc, char ** argv)
{
using namespace DB;
Poco::AutoPtr<Poco::ConsoleChannel> channel = new Poco::ConsoleChannel(std::cerr);
Logger::root().setChannel(channel);
Logger::root().setLevel("trace");
try
{
Compiler compiler(".", 1);
auto lib = compiler.getOrCount("xxx", 1, []() -> std::string
{
return "void f() __attribute__((__visibility__(\"default\"))); void f() {}";
}, [](SharedLibraryPtr&){});
}
catch (const DB::Exception & e)
{
std::cerr << e.displayText() << std::endl;
}
return 0;
}

View File

@ -1,10 +1,12 @@
#include <sys/resource.h>
#include <sys/file.h>
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Util/XMLConfiguration.h>
#include <Yandex/ApplicationServerExt.h>
#include <Yandex/ErrorHandlers.h>
#include <Yandex/Revision.h>
#include <statdaemons/ConfigProcessor.h>
#include <statdaemons/ext/memory.hpp>
@ -21,6 +23,11 @@
#include <DB/Storages/StorageSystemZooKeeper.h>
#include <DB/Storages/StorageSystemReplicas.h>
#include <DB/IO/copyData.h>
#include <DB/IO/LimitReadBuffer.h>
#include <DB/IO/WriteBufferFromFileDescriptor.h>
#include <DB/IO/Operators.h>
#include "Server.h"
#include "HTTPHandler.h"
#include "InterserverIOHTTPHandler.h"
@ -318,10 +325,101 @@ void UsersConfigReloader::reloadIfNewer(bool force)
}
/** Обеспечивает, что с одной директорией с данными может одновременно работать не более одного сервера.
*/
class StatusFile : private boost::noncopyable
{
public:
StatusFile(const std::string & path_)
: path(path_)
{
/// Если файл уже существует. NOTE Незначительный race condition.
if (Poco::File(path).exists())
{
std::string contents;
{
ReadBufferFromFile in(path, 1024);
LimitReadBuffer limit_in(in, 1024);
WriteBufferFromString out(contents);
copyData(limit_in, out);
}
if (!contents.empty())
LOG_INFO(&Logger::get("StatusFile"), "Status file " << path << " already exists - unclean restart. Contents:\n" << contents);
else
LOG_INFO(&Logger::get("StatusFile"), "Status file " << path << " already exists and is empty - probably unclean hardware restart.");
}
fd = open(path.c_str(), O_WRONLY | O_CREAT, 0666);
if (-1 == fd)
throwFromErrno("Cannot open file " + path);
try
{
int flock_ret = flock(fd, LOCK_EX | LOCK_NB);
if (-1 == flock_ret)
{
if (errno == EWOULDBLOCK)
throw Exception("Cannot lock file " + path + ". Another server instance in same directory is already running.");
else
throwFromErrno("Cannot lock file " + path);
}
if (0 != ftruncate(fd, 0))
throwFromErrno("Cannot ftruncate " + path);
if (0 != lseek(fd, 0, SEEK_SET))
throwFromErrno("Cannot lseek " + path);
/// Записываем в файл информацию о текущем экземпляре сервера.
{
WriteBufferFromFileDescriptor out(fd, 1024);
out
<< "PID: " << getpid() << "\n"
<< "Started at: " << mysqlxx::DateTime(time(0)) << "\n"
<< "Revision: " << Revision::get() << "\n";
}
}
catch (...)
{
close(fd);
throw;
}
}
~StatusFile()
{
char buf[128];
if (0 != close(fd))
LOG_ERROR(&Logger::get("StatusFile"), "Cannot close file " << path << ", errno: "
<< errno << ", strerror: " << strerror_r(errno, buf, sizeof(buf)));
if (0 != unlink(path.c_str()))
LOG_ERROR(&Logger::get("StatusFile"), "Cannot unlink file " << path << ", errno: "
<< errno << ", strerror: " << strerror_r(errno, buf, sizeof(buf)));
}
private:
const std::string path;
int fd = -1;
};
int Server::main(const std::vector<std::string> & args)
{
Logger * log = &logger();
std::string path = config().getString("path");
Poco::trimInPlace(path);
if (path.empty())
throw Exception("path configuration parameter is empty");
if (path.back() != '/')
path += '/';
StatusFile status{path + "status"};
/// Попробуем повысить ограничение на число открытых файлов.
{
rlimit rlim;
@ -357,7 +455,13 @@ int Server::main(const std::vector<std::string> & args)
* настройки, набор функций, типов данных, агрегатных функций, баз данных...
*/
global_context->setGlobalContext(*global_context);
global_context->setPath(config().getString("path"));
global_context->setPath(path);
/// Директория для временных файлов при обработке тяжёлых запросов.
std::string tmp_path = config().getString("tmp_path", path + "tmp/");
global_context->setTemporaryPath(tmp_path);
Poco::File(tmp_path).createDirectories();
/// TODO Очистка временных файлов. Проверка, что директория с временными файлами не совпадает и не содержит в себе основной path.
bool has_zookeeper = false;
if (config().has("zookeeper"))

View File

@ -17,6 +17,8 @@
#include <DB/IO/copyData.h>
#include <DB/DataStreams/AsynchronousBlockInputStream.h>
#include <DB/DataStreams/NativeBlockInputStream.h>
#include <DB/DataStreams/NativeBlockOutputStream.h>
#include <DB/Interpreters/executeQuery.h>
#include <DB/Storages/StorageMemory.h>
@ -597,12 +599,10 @@ void TCPHandler::initBlockInput()
else
state.maybe_compressed_in = in;
state.block_in = query_context.getFormatFactory().getInput(
"Native",
state.block_in = new NativeBlockInputStream(
*state.maybe_compressed_in,
state.io.out_sample,
query_context.getSettingsRef().max_insert_block_size, /// Реально не используется в формате Native.
query_context.getDataTypeFactory());
query_context.getDataTypeFactory(),
client_revision);
}
}
@ -616,10 +616,9 @@ void TCPHandler::initBlockOutput()
else
state.maybe_compressed_out = out;
state.block_out = query_context.getFormatFactory().getOutput(
"Native",
state.block_out = new NativeBlockOutputStream(
*state.maybe_compressed_out,
state.io.in_sample);
client_revision);
}
}

View File

@ -179,14 +179,10 @@ namespace DB
void AlterCommands::validate(IStorage * table, const Context & context)
{
auto lock = table->lockDataForAlter();
auto columns = table->getColumnsList();
columns.insert(std::end(columns), std::begin(table->alias_columns), std::end(table->alias_columns));
auto defaults = table->column_defaults;
lock.reset();
std::vector<std::pair<String, AlterCommand *>> defaulted_columns{};
ASTPtr default_expr_list{new ASTExpressionList};

View File

@ -237,7 +237,7 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks)
for (const auto & part : broken_parts_to_remove)
part->remove();
for (const auto & part : broken_parts_to_detach)
part->renameAddPrefix("detached/");
part->renameAddPrefix(true, "");
all_data_parts = data_parts;
@ -789,7 +789,7 @@ void MergeTreeData::renameAndDetachPart(const DataPartPtr & part, const String &
removePartContributionToColumnSizes(part);
data_parts.erase(part);
if (move_to_detached || !prefix.empty())
part->renameAddPrefix((move_to_detached ? "detached/" : "") + prefix);
part->renameAddPrefix(move_to_detached, prefix);
if (restore_covered)
{

View File

@ -42,23 +42,6 @@ static const double DISK_USAGE_COEFFICIENT_TO_RESERVE = 1.4;
bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & parts, String & merged_name, size_t available_disk_space,
bool merge_anything_for_old_months, bool aggressive, bool only_small, const AllowedMergingPredicate & can_merge_callback)
{
std::stringstream log_message;
log_message << "Selecting parts to merge. Available disk space: ";
if (available_disk_space == NO_LIMIT)
log_message << "no limit";
else
log_message << available_disk_space << " bytes";
log_message
<< ". Merge anything for old months: " << merge_anything_for_old_months
<< ". Aggressive: " << aggressive
<< ". Only small: " << only_small
<< ".";
LOG_TRACE(log, log_message.rdbuf());
MergeTreeData::DataParts data_parts = data.getDataParts();
DateLUT & date_lut = DateLUT::instance();
@ -92,9 +75,6 @@ bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & pa
if (only_small)
cur_max_bytes_to_merge_parts = data.settings.max_bytes_to_merge_parts_small;
LOG_TRACE(log, "Max bytes to merge parts: " << cur_max_bytes_to_merge_parts
<< (only_small ? " (only small)" : (tonight ? " (tonight)" : "")) << ".");
/// Мемоизация для функции can_merge_callback. Результат вызова can_merge_callback для этого куска и предыдущего в data_parts.
std::map<MergeTreeData::DataPartPtr, bool> can_merge_with_previous;
auto can_merge = [&can_merge_with_previous, &can_merge_callback]
@ -293,10 +273,6 @@ bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & pa
LOG_DEBUG(log, "Selected " << parts.size() << " parts from " << parts.front()->name << " to " << parts.back()->name
<< (only_small ? " (only small)" : ""));
}
else
{
LOG_TRACE(log, "No parts selected for merge.");
}
return found;
}

View File

@ -6,6 +6,7 @@
#include <DB/DataStreams/FilterBlockInputStream.h>
#include <DB/DataStreams/ConcatBlockInputStream.h>
#include <DB/DataStreams/CollapsingFinalBlockInputStream.h>
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
#include <DB/DataTypes/DataTypesNumberFixed.h>
#include <DB/Common/VirtualColumnUtils.h>

View File

@ -13,7 +13,7 @@ static String generateActiveNodeIdentifier()
struct timespec times;
if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &times))
throwFromErrno("Cannot clock_gettime.", ErrorCodes::CANNOT_CLOCK_GETTIME);
return toString(times.tv_nsec + times.tv_sec + getpid());
return "pid: " + toString(getpid()) + ", random: " + toString(times.tv_nsec + times.tv_sec + getpid());
}
@ -52,7 +52,7 @@ void ReplicatedMergeTreeRestartingThread::run()
partialShutdown();
}
do
while (true)
{
try
{
@ -62,6 +62,7 @@ void ReplicatedMergeTreeRestartingThread::run()
{
/// Исключение при попытке zookeeper_init обычно бывает, если не работает DNS. Будем пытаться сделать это заново.
tryLogCurrentException(__PRETTY_FUNCTION__);
wakeup_event.tryWait(retry_delay_ms);
continue;
}
@ -71,7 +72,9 @@ void ReplicatedMergeTreeRestartingThread::run()
wakeup_event.tryWait(retry_delay_ms);
continue;
}
} while (false);
break;
}
storage.is_readonly = false;
first_time = false;
@ -133,14 +136,21 @@ bool ReplicatedMergeTreeRestartingThread::tryStartup()
storage.queue_task_handle = storage.context.getBackgroundPool().addTask(
std::bind(&StorageReplicatedMergeTree::queueTask, &storage, std::placeholders::_1));
storage.queue_task_handle->wake();
return true;
}
catch (const zkutil::KeeperException & e)
catch (...)
{
storage.replica_is_active_node = nullptr;
storage.leader_election = nullptr;
LOG_ERROR(log, "Couldn't start replication: " << e.what() << ", " << e.displayText() << ", stack trace:\n"
<< e.getStackTrace().toString());
try
{
throw;
}
catch (const zkutil::KeeperException & e)
{
LOG_ERROR(log, "Couldn't start replication: " << e.what() << ", " << e.displayText() << ", stack trace:\n" << e.getStackTrace().toString());
return false;
}
catch (const Exception & e)
@ -148,17 +158,9 @@ bool ReplicatedMergeTreeRestartingThread::tryStartup()
if (e.code() != ErrorCodes::REPLICA_IS_ALREADY_ACTIVE)
throw;
storage.replica_is_active_node = nullptr;
storage.leader_election = nullptr;
LOG_ERROR(log, "Couldn't start replication: " << e.what() << ", " << e.displayText() << ", stack trace:\n"
<< e.getStackTrace().toString());
LOG_ERROR(log, "Couldn't start replication: " << e.what() << ", " << e.displayText() << ", stack trace:\n" << e.getStackTrace().toString());
return false;
}
catch (...)
{
storage.replica_is_active_node = nullptr;
storage.leader_election = nullptr;
throw;
}
}

View File

@ -14,7 +14,10 @@
#include <DB/DataStreams/ConcatBlockInputStream.h>
#include <DB/DataStreams/narrowBlockInputStreams.h>
#include <DB/DataStreams/AddingDefaultBlockInputStream.h>
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
#include <DB/Common/VirtualColumnUtils.h>
#include <DB/DataTypes/DataTypeString.h>
#include <DB/Columns/ColumnString.h>
namespace DB

View File

@ -6,6 +6,8 @@
#include <DB/Interpreters/InterpreterDropQuery.h>
#include <DB/Parsers/ASTDropQuery.h>
#include <DB/Common/VirtualColumnUtils.h>
#include <DB/DataTypes/DataTypeString.h>
#include <DB/Columns/ColumnString.h>
namespace DB

View File

@ -63,8 +63,8 @@ StorageDistributed::StorageDistributed(
context(context_), cluster(cluster_),
sharding_key_expr(sharding_key_ ? ExpressionAnalyzer(sharding_key_, context, *columns).getActions(false) : nullptr),
sharding_key_column_name(sharding_key_ ? sharding_key_->getColumnName() : String{}),
write_enabled(cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_),
path(data_path_ + escapeForFileName(name) + '/')
write_enabled(!data_path_.empty() && (cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_)),
path(data_path_.empty() ? "" : (data_path_ + escapeForFileName(name) + '/'))
{
createDirectoryMonitors();
}
@ -87,8 +87,8 @@ StorageDistributed::StorageDistributed(
context(context_), cluster(cluster_),
sharding_key_expr(sharding_key_ ? ExpressionAnalyzer(sharding_key_, context, *columns).getActions(false) : nullptr),
sharding_key_column_name(sharding_key_ ? sharding_key_->getColumnName() : String{}),
write_enabled(cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_),
path(data_path_ + escapeForFileName(name) + '/')
write_enabled(!data_path_.empty() && (cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_)),
path(data_path_.empty() ? "" : (data_path_ + escapeForFileName(name) + '/'))
{
createDirectoryMonitors();
}
@ -164,8 +164,7 @@ BlockInputStreams StorageDistributed::read(
for (auto & conn_pool : cluster.pools)
res.emplace_back(new RemoteBlockInputStream{
conn_pool, modified_query, &new_settings,
external_tables, processed_stage, context
});
external_tables, processed_stage, context});
/// Добавляем запросы к локальному ClickHouse.
if (cluster.getLocalNodesNum() > 0)
@ -240,6 +239,9 @@ void StorageDistributed::createDirectoryMonitor(const std::string & name)
void StorageDistributed::createDirectoryMonitors()
{
if (path.empty())
return;
Poco::File{path}.createDirectory();
Poco::DirectoryIterator end;

View File

@ -1,8 +1,13 @@
#include <DB/DataStreams/narrowBlockInputStreams.h>
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
#include <DB/Storages/StorageMerge.h>
#include <DB/Common/VirtualColumnUtils.h>
#include <DB/Interpreters/InterpreterAlterQuery.h>
#include <DB/Storages/VirtualColumnFactory.h>
#include <DB/Parsers/ASTSelectQuery.h>
#include <DB/DataTypes/DataTypeString.h>
#include <DB/Columns/ColumnString.h>
namespace DB
{

View File

@ -199,7 +199,6 @@ bool StorageMergeTree::merge(bool aggressive, BackgroundProcessingPool::Context
if (!merger.selectPartsToMerge(parts, merged_name, disk_space, false, aggressive, only_small, can_merge) &&
!merger.selectPartsToMerge(parts, merged_name, disk_space, true, aggressive, only_small, can_merge))
{
LOG_INFO(log, "No parts to merge");
return false;
}

View File

@ -8,8 +8,10 @@
#include <DB/IO/ReadBufferFromString.h>
#include <DB/Interpreters/InterpreterAlterQuery.h>
#include <DB/Common/VirtualColumnUtils.h>
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
#include <time.h>
namespace DB
{
@ -1353,6 +1355,7 @@ void StorageReplicatedMergeTree::mergeSelectingThread()
bool only_small = big_merges_current + big_merges_queued >= max_number_of_big_merges;
if (big_merges_current || merges_queued)
LOG_TRACE(log, "Currently executing big merges: " << big_merges_current
<< ". Queued big merges: " << big_merges_queued
<< ". All merges in queue: " << merges_queued
@ -1376,7 +1379,6 @@ void StorageReplicatedMergeTree::mergeSelectingThread()
if ( !merger.selectPartsToMerge(parts, merged_name, MergeTreeDataMerger::NO_LIMIT, false, false, only_small, can_merge)
&& !merger.selectPartsToMerge(parts, merged_name, MergeTreeDataMerger::NO_LIMIT, true, false, only_small, can_merge))
{
LOG_INFO(log, "No parts to merge");
break;
}

View File

@ -92,8 +92,7 @@ BlockInputStreams StorageView::read(
if (outer_select.final && !inner_select.final)
inner_select.final = outer_select.final;
return BlockInputStreams(1,
InterpreterSelectQuery(inner_query_clone, context, column_names).execute());
return InterpreterSelectQuery(inner_query_clone, context, column_names).executeWithoutUnion();
}

View File

@ -76,7 +76,7 @@ do
continue
fi
printf "%-60s" "$test_name: "
printf "%-64s" "$test_name: "
if [ $ZOOKEEPER -eq 0 ] && (echo "$test_name" | grep -q 'zookeeper'); then
echo -e "$MSG_SKIPPED - no zookeeper"

View File

@ -3,6 +3,7 @@
echo 'DROP TABLE IF EXISTS test.long_insert' | curl -sSg 'http://localhost:8123' -d @-
echo 'CREATE TABLE test.long_insert (a String) ENGINE = Memory' | curl -sSg 'http://localhost:8123' -d @-
for string_size in 1 10 100 1000 10000 100000 1000000; do
perl -we 'for my $letter ("a" .. "z") { print(($letter x '$string_size') . "\n") }' | curl -sSg 'http://localhost:8123/?query=INSERT+INTO+test.long_insert+FORMAT+TabSeparated' --data-binary @-
# Если не указать LC_ALL=C, то Perl будет ругаться на некоторых плохо настроенных системах.
LC_ALL=C perl -we 'for my $letter ("a" .. "z") { print(($letter x '$string_size') . "\n") }' | curl -sSg 'http://localhost:8123/?query=INSERT+INTO+test.long_insert+FORMAT+TabSeparated' --data-binary @-
echo 'SELECT substring(a, 1, 1) AS c, length(a) AS l FROM test.long_insert ORDER BY c, l' | curl -sSg 'http://localhost:8123' -d @-
done

View File

@ -32,6 +32,11 @@ cp ${CONFIG/config/users} .
# Запустим второй сервер.
BINARY=$(readlink /proc/$(pidof clickhouse-server | tr ' ' '\n' | head -n1)/exe || echo "/usr/bin/clickhouse-server")
if [ ! -x "$BINARY" ] && [ -n "$(pgrep memcheck)" ]; then
# В случае, если сервер был запущен под valgrind-ом.
BINARY=$(readlink /proc/$(pgrep memcheck)/cwd)/$(cat /proc/$(pgrep memcheck)/cmdline | cut -f2 -d '')
fi
if [ ! -x "$BINARY" ]; then
echo "Cannot find executable binary for running clickhouse-server" >&2
exit 1
@ -99,3 +104,5 @@ $CLIENT1 -n --query="
$CLIENT2 -n --query="
DROP TABLE test.half1;
DROP TABLE test.half2;"
rm -rf $PATH2

Some files were not shown because too many files have changed in this diff Show More