mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-10-03 23:20:48 +00:00
Merge
This commit is contained in:
commit
694b942f67
30
copy_headers.sh
Executable file
30
copy_headers.sh
Executable 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
|
@ -21,7 +21,7 @@ namespace DB
|
|||||||
* Может быть в двух вариантах:
|
* Может быть в двух вариантах:
|
||||||
*
|
*
|
||||||
* 1. Владеть своими значениями - то есть, отвечать за их уничтожение.
|
* 1. Владеть своими значениями - то есть, отвечать за их уничтожение.
|
||||||
* Столбец состоит из значений, "отданных ему на попечение" после выполнения агрегации (см. Aggregator, функция convertToBlock),
|
* Столбец состоит из значений, "отданных ему на попечение" после выполнения агрегации (см. Aggregator, функция convertToBlocks),
|
||||||
* или из значений, созданных им самим (см. метод insert).
|
* или из значений, созданных им самим (см. метод insert).
|
||||||
* В этом случае, src будет равно nullptr, и столбец будет сам уничтожать (вызывать IAggregateFunction::destroy)
|
* В этом случае, src будет равно nullptr, и столбец будет сам уничтожать (вызывать IAggregateFunction::destroy)
|
||||||
* состояния агрегатных функций в деструкторе.
|
* состояния агрегатных функций в деструкторе.
|
||||||
|
@ -21,6 +21,21 @@ inline DB::UInt64 intHash64(DB::UInt64 x)
|
|||||||
return 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> struct DefaultHash;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -36,7 +51,7 @@ inline size_t DefaultHash64(T key)
|
|||||||
return intHash64(u.out);
|
return intHash64(u.out);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define DEFAULT_HASH_64(T) \
|
#define DEFINE_HASH(T) \
|
||||||
template <> struct DefaultHash<T>\
|
template <> struct DefaultHash<T>\
|
||||||
{\
|
{\
|
||||||
size_t operator() (T key) const\
|
size_t operator() (T key) const\
|
||||||
@ -45,15 +60,53 @@ template <> struct DefaultHash<T>\
|
|||||||
}\
|
}\
|
||||||
};
|
};
|
||||||
|
|
||||||
DEFAULT_HASH_64(DB::UInt8)
|
DEFINE_HASH(DB::UInt8)
|
||||||
DEFAULT_HASH_64(DB::UInt16)
|
DEFINE_HASH(DB::UInt16)
|
||||||
DEFAULT_HASH_64(DB::UInt32)
|
DEFINE_HASH(DB::UInt32)
|
||||||
DEFAULT_HASH_64(DB::UInt64)
|
DEFINE_HASH(DB::UInt64)
|
||||||
DEFAULT_HASH_64(DB::Int8)
|
DEFINE_HASH(DB::Int8)
|
||||||
DEFAULT_HASH_64(DB::Int16)
|
DEFINE_HASH(DB::Int16)
|
||||||
DEFAULT_HASH_64(DB::Int32)
|
DEFINE_HASH(DB::Int32)
|
||||||
DEFAULT_HASH_64(DB::Int64)
|
DEFINE_HASH(DB::Int64)
|
||||||
DEFAULT_HASH_64(DB::Float32)
|
DEFINE_HASH(DB::Float32)
|
||||||
DEFAULT_HASH_64(DB::Float64)
|
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
|
||||||
|
@ -129,7 +129,9 @@ public:
|
|||||||
typedef typename Cell::Mapped mapped_type;
|
typedef typename Cell::Mapped mapped_type;
|
||||||
typedef typename Cell::value_type value_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;
|
typename HashMapTable::iterator it;
|
||||||
bool inserted;
|
bool inserted;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
#include <stats/IntHash.h>
|
#include <stats/IntHash.h>
|
||||||
|
|
||||||
|
#include <DB/Core/Defines.h>
|
||||||
#include <DB/Core/Types.h>
|
#include <DB/Core/Types.h>
|
||||||
#include <DB/Core/Exception.h>
|
#include <DB/Core/Exception.h>
|
||||||
#include <DB/Core/ErrorCodes.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
|
struct HashTableGrower
|
||||||
{
|
{
|
||||||
/// Состояние этой структуры достаточно, чтобы получить размер буфера хэш-таблицы.
|
/// Состояние этой структуры достаточно, чтобы получить размер буфера хэш-таблицы.
|
||||||
@ -231,6 +232,9 @@ protected:
|
|||||||
friend class const_iterator;
|
friend class const_iterator;
|
||||||
friend class iterator;
|
friend class iterator;
|
||||||
|
|
||||||
|
template <typename, typename, typename, typename, typename, typename, size_t>
|
||||||
|
friend class TwoLevelHashTable;
|
||||||
|
|
||||||
typedef size_t HashValue;
|
typedef size_t HashValue;
|
||||||
typedef HashTable<Key, Cell, Hash, Grower, Allocator> Self;
|
typedef HashTable<Key, Cell, Hash, Grower, Allocator> Self;
|
||||||
typedef Cell cell_type;
|
typedef Cell cell_type;
|
||||||
@ -240,11 +244,11 @@ protected:
|
|||||||
Grower grower;
|
Grower grower;
|
||||||
|
|
||||||
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
||||||
mutable size_t collisions;
|
mutable size_t collisions = 0;
|
||||||
#endif
|
#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))
|
while (!buf[place_value].isZero(*this) && !buf[place_value].keyEquals(x, hash_value))
|
||||||
{
|
{
|
||||||
@ -257,6 +261,20 @@ protected:
|
|||||||
return place_value;
|
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)
|
void alloc(const Grower & new_grower)
|
||||||
{
|
{
|
||||||
buf = reinterpret_cast<Cell *>(Allocator::alloc(new_grower.bufSize() * sizeof(Cell)));
|
buf = reinterpret_cast<Cell *>(Allocator::alloc(new_grower.bufSize() * sizeof(Cell)));
|
||||||
@ -372,10 +390,14 @@ public:
|
|||||||
if (Cell::need_zero_value_storage)
|
if (Cell::need_zero_value_storage)
|
||||||
this->zeroValue()->setZero();
|
this->zeroValue()->setZero();
|
||||||
alloc(grower);
|
alloc(grower);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS
|
HashTable(size_t reserve_for_num_elements)
|
||||||
collisions = 0;
|
{
|
||||||
#endif
|
if (Cell::need_zero_value_storage)
|
||||||
|
this->zeroValue()->setZero();
|
||||||
|
grower.set(reserve_for_num_elements);
|
||||||
|
alloc(grower);
|
||||||
}
|
}
|
||||||
|
|
||||||
~HashTable()
|
~HashTable()
|
||||||
@ -417,6 +439,9 @@ public:
|
|||||||
|
|
||||||
value_type & operator* () const { return ptr->getValue(); }
|
value_type & operator* () const { return ptr->getValue(); }
|
||||||
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 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:
|
protected:
|
||||||
const_iterator iteratorToZero() const { return const_iterator(this, this->zeroValue()); }
|
const_iterator iteratorTo(const Cell * ptr) const { return const_iterator(this, ptr); }
|
||||||
iterator iteratorToZero() { return iterator(this, this->zeroValue()); }
|
iterator iteratorTo(Cell * ptr) { return iterator(this, ptr); }
|
||||||
|
const_iterator iteratorToZero() const { return iteratorTo(this->zeroValue()); }
|
||||||
|
iterator iteratorToZero() { return iteratorTo(this->zeroValue()); }
|
||||||
|
|
||||||
|
|
||||||
/// Если ключ нулевой - вставить его в специальное место и вернуть true.
|
/// Если ключ нулевой - вставить его в специальное место и вернуть 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)
|
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));
|
size_t place_value = findCell(x, hash_value, grower.place(hash_value));
|
||||||
|
|
||||||
@ -534,14 +564,14 @@ protected:
|
|||||||
if (unlikely(grower.overflow(m_size)))
|
if (unlikely(grower.overflow(m_size)))
|
||||||
{
|
{
|
||||||
resize();
|
resize();
|
||||||
it = find(x);
|
it = find(x, hash_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Вставить значение. В случае хоть сколько-нибудь сложных значений, лучше используйте функцию emplace.
|
/// Вставить значение. В случае хоть сколько-нибудь сложных значений, лучше используйте функцию 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;
|
std::pair<iterator, bool> res;
|
||||||
|
|
||||||
@ -570,7 +600,7 @@ public:
|
|||||||
* if (inserted)
|
* if (inserted)
|
||||||
* new(&it->second) Mapped(value);
|
* 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))
|
if (!emplaceIfZero(x, it, inserted))
|
||||||
emplaceNonZero(x, it, inserted, hash(x));
|
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))
|
if (!emplaceIfZero(x, it, inserted))
|
||||||
emplaceNonZero(x, it, inserted, hash_value);
|
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))
|
if (Cell::isZero(x, *this))
|
||||||
return this->hasZero() ? iteratorToZero() : end();
|
return this->hasZero() ? iteratorToZero() : end();
|
||||||
|
|
||||||
size_t hash_value = hash(x);
|
size_t hash_value = hash(x);
|
||||||
size_t place_value = findCell(x, hash_value, grower.place(hash_value));
|
size_t place_value = findCell(x, hash_value, grower.place(hash_value));
|
||||||
|
|
||||||
return !buf[place_value].isZero(*this) ? iterator(this, &buf[place_value]) : end();
|
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))
|
if (Cell::isZero(x, *this))
|
||||||
return this->hasZero() ? iteratorToZero() : end();
|
return this->hasZero() ? iteratorToZero() : end();
|
||||||
|
|
||||||
size_t hash_value = hash(x);
|
size_t hash_value = hash(x);
|
||||||
size_t place_value = findCell(x, hash_value, grower.place(hash_value));
|
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();
|
return !buf[place_value].isZero(*this) ? const_iterator(this, &buf[place_value]) : end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -701,6 +762,16 @@ public:
|
|||||||
return 0 == m_size;
|
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
|
size_t getBufferSizeInBytes() const
|
||||||
{
|
{
|
||||||
return grower.bufSize() * sizeof(Cell);
|
return grower.bufSize() * sizeof(Cell);
|
||||||
|
57
dbms/include/DB/Common/HashTable/TwoLevelHashMap.h
Normal file
57
dbms/include/DB/Common/HashTable/TwoLevelHashMap.h
Normal 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>;
|
@ -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
|
template
|
||||||
<
|
<
|
||||||
typename Key,
|
typename Key,
|
||||||
typename Cell,
|
typename Cell,
|
||||||
typename Hash,
|
typename Hash,
|
||||||
typename Grower,
|
typename Grower,
|
||||||
typename Allocator,
|
typename Allocator, /// TODO WithStackMemory
|
||||||
typename ImplTable = HashTable<Key, Cell, Hash, Grower, Allocator>
|
typename ImplTable = HashTable<Key, Cell, Hash, Grower, Allocator>,
|
||||||
|
size_t BITS_FOR_BUCKET = 8
|
||||||
>
|
>
|
||||||
class TwoLevelHashTable :
|
class TwoLevelHashTable :
|
||||||
private boost::noncopyable,
|
private boost::noncopyable,
|
||||||
@ -36,8 +47,13 @@ protected:
|
|||||||
public:
|
public:
|
||||||
typedef ImplTable Impl;
|
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 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:
|
protected:
|
||||||
typename Impl::iterator beginOfNextNonEmptyBucket(size_t & bucket)
|
typename Impl::iterator beginOfNextNonEmptyBucket(size_t & bucket)
|
||||||
@ -68,11 +84,34 @@ public:
|
|||||||
typedef typename Impl::key_type key_type;
|
typedef typename Impl::key_type key_type;
|
||||||
typedef typename Impl::value_type value_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];
|
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
|
class iterator
|
||||||
{
|
{
|
||||||
Self * container;
|
Self * container;
|
||||||
@ -104,6 +143,9 @@ public:
|
|||||||
|
|
||||||
value_type & operator* () const { return *current_it; }
|
value_type & operator* () const { return *current_it; }
|
||||||
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 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.
|
/// Вставить значение. В случае хоть сколько-нибудь сложных значений, лучше используйте функцию 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));
|
size_t hash_value = hash(Cell::getKey(x));
|
||||||
|
|
||||||
std::pair<iterator, bool> res;
|
std::pair<iterator, bool> res;
|
||||||
emplace(Cell::getKey(x), res.first, res.second, hash_value);
|
emplace(Cell::getKey(x), res.first, res.second, hash_value);
|
||||||
|
|
||||||
|
if (res.second)
|
||||||
|
res.first.getPtr()->setMapped(x);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +235,7 @@ public:
|
|||||||
* if (inserted)
|
* if (inserted)
|
||||||
* new(&it->second) Mapped(value);
|
* 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);
|
size_t hash_value = hash(x);
|
||||||
emplace(x, it, inserted, hash_value);
|
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);
|
size_t buck = getBucketFromHash(hash_value);
|
||||||
typename Impl::iterator impl_it;
|
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);
|
it = iterator(this, buck, impl_it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
iterator find(Key x)
|
iterator ALWAYS_INLINE find(Key x)
|
||||||
{
|
{
|
||||||
size_t hash_value = hash(x);
|
size_t hash_value = hash(x);
|
||||||
size_t buck = getBucketFromHash(hash_value);
|
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()
|
return found != impls[buck].end()
|
||||||
? iterator(this, buck, found)
|
? iterator(this, buck, found)
|
||||||
: end();
|
: end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const_iterator find(Key x) const
|
const_iterator ALWAYS_INLINE find(Key x) const
|
||||||
{
|
{
|
||||||
size_t hash_value = hash(x);
|
size_t hash_value = hash(x);
|
||||||
size_t buck = getBucketFromHash(hash_value);
|
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()
|
return found != impls[buck].end()
|
||||||
? const_iterator(this, buck, found)
|
? const_iterator(this, buck, found)
|
||||||
: end();
|
: end();
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
#define ROTL(x,b) (u64)( ((x) << (b)) | ( (x) >> (64 - (b))) )
|
#define ROTL(x,b) (u64)( ((x) << (b)) | ( (x) >> (64 - (b))) )
|
||||||
|
|
||||||
#define SIPROUND \
|
#define SIPROUND \
|
||||||
do { \
|
do \
|
||||||
|
{ \
|
||||||
v0 += v1; v1=ROTL(v1,13); v1 ^= v0; v0=ROTL(v0,32); \
|
v0 += v1; v1=ROTL(v1,13); v1 ^= v0; v0=ROTL(v0,32); \
|
||||||
v2 += v3; v3=ROTL(v3,16); v3 ^= v2; \
|
v2 += v3; v3=ROTL(v3,16); v3 ^= v2; \
|
||||||
v0 += v3; v3=ROTL(v3,21); v3 ^= v0; \
|
v0 += v3; v3=ROTL(v3,21); v3 ^= v0; \
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
#include <DB/Common/HashTable/Hash.h>
|
#include <DB/Common/HashTable/Hash.h>
|
||||||
#include <DB/IO/ReadHelpers.h>
|
#include <DB/IO/ReadHelpers.h>
|
||||||
#include <DB/IO/WriteHelpers.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); }
|
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
|
struct UInt128TrivialHash
|
||||||
{
|
{
|
||||||
size_t operator()(UInt128 x) const { return x.first; }
|
size_t operator()(UInt128 x) const { return x.first; }
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <DB/Interpreters/Context.h>
|
#include <set>
|
||||||
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
|
|
||||||
#include <DB/DataStreams/OneBlockInputStream.h>
|
#include <DB/Core/Block.h>
|
||||||
#include <DB/DataTypes/DataTypeString.h>
|
#include <DB/Core/NamesAndTypes.h>
|
||||||
#include <DB/DataTypes/DataTypesNumberFixed.h>
|
#include <DB/Parsers/IAST.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>
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
|
class Context;
|
||||||
|
|
||||||
|
|
||||||
namespace VirtualColumnUtils
|
namespace VirtualColumnUtils
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <list>
|
#include <list>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
|
|
||||||
|
#include <DB/Core/BlockInfo.h>
|
||||||
#include <DB/Core/ColumnWithNameAndType.h>
|
#include <DB/Core/ColumnWithNameAndType.h>
|
||||||
#include <DB/Core/NamesAndTypes.h>
|
#include <DB/Core/NamesAndTypes.h>
|
||||||
#include <DB/Core/Exception.h>
|
#include <DB/Core/Exception.h>
|
||||||
@ -35,6 +36,8 @@ private:
|
|||||||
IndexByName_t index_by_name;
|
IndexByName_t index_by_name;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
BlockInfo info;
|
||||||
|
|
||||||
Block() = default;
|
Block() = default;
|
||||||
Block(std::initializer_list<ColumnWithNameAndType> il) : data{il}
|
Block(std::initializer_list<ColumnWithNameAndType> il) : data{il}
|
||||||
{
|
{
|
||||||
@ -129,7 +132,7 @@ bool blocksHaveEqualStructure(const Block & lhs, const Block & rhs);
|
|||||||
|
|
||||||
namespace std
|
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);
|
one.swap(another);
|
||||||
}
|
}
|
||||||
|
87
dbms/include/DB/Core/BlockInfo.h
Normal file
87
dbms/include/DB/Core/BlockInfo.h
Normal 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
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -65,6 +65,7 @@
|
|||||||
#define DBMS_MIN_REVISION_WITH_STRING_QUERY_ID 39002
|
#define DBMS_MIN_REVISION_WITH_STRING_QUERY_ID 39002
|
||||||
#define DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES 50264
|
#define DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES 50264
|
||||||
#define DBMS_MIN_REVISION_WITH_TOTAL_ROWS_IN_PROGRESS 51554
|
#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
|
#define DBMS_DISTRIBUTED_DIRECTORY_MONITOR_SLEEP_TIME_MS 100
|
||||||
|
|
||||||
|
@ -266,6 +266,11 @@ namespace ErrorCodes
|
|||||||
PARTITION_ALREADY_EXISTS,
|
PARTITION_ALREADY_EXISTS,
|
||||||
PARTITION_DOESNT_EXIST,
|
PARTITION_DOESNT_EXIST,
|
||||||
UNION_ALL_RESULT_STRUCTURES_MISMATCH,
|
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,
|
POCO_EXCEPTION = 1000,
|
||||||
STD_EXCEPTION,
|
STD_EXCEPTION,
|
||||||
|
@ -50,9 +50,9 @@ struct SortCursorImpl
|
|||||||
ConstColumnPlainPtrs all_columns;
|
ConstColumnPlainPtrs all_columns;
|
||||||
ConstColumnPlainPtrs sort_columns;
|
ConstColumnPlainPtrs sort_columns;
|
||||||
SortDescription desc;
|
SortDescription desc;
|
||||||
size_t sort_columns_size;
|
size_t sort_columns_size = 0;
|
||||||
size_t pos;
|
size_t pos = 0;
|
||||||
size_t rows;
|
size_t rows = 0;
|
||||||
|
|
||||||
/** Порядок (что сравнивается), если сравниваемые столбцы равны.
|
/** Порядок (что сравнивается), если сравниваемые столбцы равны.
|
||||||
* Даёт возможность предпочитать строки из нужного курсора.
|
* Даёт возможность предпочитать строки из нужного курсора.
|
||||||
@ -65,12 +65,12 @@ struct SortCursorImpl
|
|||||||
NeedCollationFlags need_collation;
|
NeedCollationFlags need_collation;
|
||||||
|
|
||||||
/** Есть ли хотя бы один столбец с Collator. */
|
/** Есть ли хотя бы один столбец с 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)
|
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);
|
reset(block);
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,10 @@ class AggregatingBlockInputStream : public IProfilingBlockInputStream
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AggregatingBlockInputStream(BlockInputStreamPtr input_, const ColumnNumbers & keys_, AggregateDescriptions & aggregates_,
|
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_)
|
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_)),
|
Compiler * compiler_, UInt32 min_count_to_compile_)
|
||||||
final(final_), has_been_read(false)
|
: 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_);
|
children.push_back(input_);
|
||||||
}
|
}
|
||||||
@ -31,23 +32,32 @@ public:
|
|||||||
* Столбцы, соответствующие keys и аргументам агрегатных функций, уже должны быть вычислены.
|
* Столбцы, соответствующие keys и аргументам агрегатных функций, уже должны быть вычислены.
|
||||||
*/
|
*/
|
||||||
AggregatingBlockInputStream(BlockInputStreamPtr input_, const Names & key_names, const AggregateDescriptions & aggregates,
|
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 getName() const override { return "AggregatingBlockInputStream"; }
|
||||||
|
|
||||||
String getID() const override
|
String getID() const override
|
||||||
{
|
{
|
||||||
std::stringstream res;
|
std::stringstream res;
|
||||||
res << "Aggregating(" << children.back()->getID() << ", " << aggregator->getID() << ")";
|
res << "Aggregating(" << children.back()->getID() << ", " << aggregator.getID() << ")";
|
||||||
return res.str();
|
return res.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Block readImpl() override;
|
Block readImpl() override;
|
||||||
|
|
||||||
SharedPtr<Aggregator> aggregator;
|
Aggregator aggregator;
|
||||||
bool final;
|
bool final;
|
||||||
bool has_been_read;
|
|
||||||
|
bool executed = false;
|
||||||
|
BlocksList blocks;
|
||||||
|
BlocksList::iterator it;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -96,8 +96,11 @@ protected:
|
|||||||
filter[i] = set.insert(key).second;
|
filter[i] = set.insert(key).second;
|
||||||
|
|
||||||
if (limit && set.size() == limit)
|
if (limit && set.size() == limit)
|
||||||
|
{
|
||||||
|
memset(&filter[i + 1], 0, (rows - (i + 1)) * sizeof(IColumn::Filter::value_type));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Если ни одной новой строки не было в блоке - перейдём к следующему блоку.
|
/// Если ни одной новой строки не было в блоке - перейдём к следующему блоку.
|
||||||
if (set.size() == old_set_size)
|
if (set.size() == old_set_size)
|
||||||
|
51
dbms/include/DB/DataStreams/LazyBlockInputStream.h
Normal file
51
dbms/include/DB/DataStreams/LazyBlockInputStream.h
Normal 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -1,23 +1,74 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
#include <Poco/TemporaryFile.h>
|
||||||
|
|
||||||
#include <Yandex/logger_useful.h>
|
#include <Yandex/logger_useful.h>
|
||||||
|
|
||||||
#include <DB/Core/SortDescription.h>
|
#include <DB/Core/SortDescription.h>
|
||||||
|
|
||||||
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
||||||
|
#include <DB/DataStreams/NativeBlockInputStream.h>
|
||||||
|
|
||||||
|
#include <DB/IO/ReadBufferFromFile.h>
|
||||||
|
#include <DB/IO/CompressedReadBuffer.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
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
|
class MergeSortingBlockInputStream : public IProfilingBlockInputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке.
|
/// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке.
|
||||||
MergeSortingBlockInputStream(BlockInputStreamPtr input_, SortDescription & description_, size_t limit_ = 0)
|
MergeSortingBlockInputStream(BlockInputStreamPtr input_, SortDescription & description_,
|
||||||
: description(description_), limit(limit_), has_been_read(false), log(&Logger::get("MergeSortingBlockInputStream"))
|
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_);
|
children.push_back(input_);
|
||||||
}
|
}
|
||||||
@ -41,24 +92,36 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
SortDescription description;
|
SortDescription description;
|
||||||
|
size_t max_merged_block_size;
|
||||||
size_t limit;
|
size_t limit;
|
||||||
|
|
||||||
/// Всё было прочитано.
|
size_t max_bytes_before_external_sort;
|
||||||
bool has_been_read;
|
const std::string tmp_path;
|
||||||
|
const DataTypeFactory & data_type_factory;
|
||||||
|
|
||||||
Logger * log;
|
Logger * log = &Logger::get("MergeSortingBlockInputStream");
|
||||||
|
|
||||||
/** Слить сразу много блоков с помощью priority queue.
|
Blocks blocks;
|
||||||
*/
|
size_t sum_bytes_in_blocks = 0;
|
||||||
Block merge(Blocks & blocks);
|
std::unique_ptr<IBlockInputStream> impl;
|
||||||
|
|
||||||
typedef std::vector<SortCursorImpl> CursorImpls;
|
/// Всё ниже - для внешней сортировки.
|
||||||
|
std::vector<std::unique_ptr<Poco::TemporaryFile>> temporary_files;
|
||||||
|
|
||||||
/** Делаем поддержку двух разных курсоров - с Collation и без.
|
/// Для чтения сброшенных во временный файл данных.
|
||||||
* Шаблоны используем вместо полиморфных SortCursor'ов и вызовов виртуальных функций.
|
struct TemporaryFileStream
|
||||||
*/
|
{
|
||||||
template <typename TSortCursor>
|
ReadBufferFromFile file_in;
|
||||||
Block mergeImpl(Blocks & block, CursorImpls & cursors);
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,15 +17,17 @@ class MergingAggregatedBlockInputStream : public IProfilingBlockInputStream
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MergingAggregatedBlockInputStream(BlockInputStreamPtr input_, const ColumnNumbers & keys_,
|
MergingAggregatedBlockInputStream(BlockInputStreamPtr input_, const ColumnNumbers & keys_,
|
||||||
const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_)
|
const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_, size_t max_threads_)
|
||||||
: aggregator(new Aggregator(keys_, aggregates_, overflow_row_)), final(final_), has_been_read(false)
|
: aggregator(keys_, aggregates_, overflow_row_, 0, OverflowMode::THROW, nullptr, 0),
|
||||||
|
final(final_), max_threads(max_threads_)
|
||||||
{
|
{
|
||||||
children.push_back(input_);
|
children.push_back(input_);
|
||||||
}
|
}
|
||||||
|
|
||||||
MergingAggregatedBlockInputStream(BlockInputStreamPtr input_, const Names & keys_names_,
|
MergingAggregatedBlockInputStream(BlockInputStreamPtr input_, const Names & keys_names_,
|
||||||
const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_)
|
const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_, size_t max_threads_)
|
||||||
: aggregator(new Aggregator(keys_names_, aggregates_, overflow_row_)), final(final_), has_been_read(false)
|
: aggregator(keys_names_, aggregates_, overflow_row_, 0, OverflowMode::THROW, nullptr, 0),
|
||||||
|
final(final_), max_threads(max_threads_)
|
||||||
{
|
{
|
||||||
children.push_back(input_);
|
children.push_back(input_);
|
||||||
}
|
}
|
||||||
@ -35,7 +37,7 @@ public:
|
|||||||
String getID() const override
|
String getID() const override
|
||||||
{
|
{
|
||||||
std::stringstream res;
|
std::stringstream res;
|
||||||
res << "MergingAggregated(" << children.back()->getID() << ", " << aggregator->getID() << ")";
|
res << "MergingAggregated(" << children.back()->getID() << ", " << aggregator.getID() << ")";
|
||||||
return res.str();
|
return res.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,9 +45,13 @@ protected:
|
|||||||
Block readImpl() override;
|
Block readImpl() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SharedPtr<Aggregator> aggregator;
|
Aggregator aggregator;
|
||||||
bool final;
|
bool final;
|
||||||
bool has_been_read;
|
size_t max_threads;
|
||||||
|
|
||||||
|
bool executed = false;
|
||||||
|
BlocksList blocks;
|
||||||
|
BlocksList::iterator it;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,8 @@ class MergingSortedBlockInputStream : public IProfilingBlockInputStream
|
|||||||
public:
|
public:
|
||||||
/// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке.
|
/// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке.
|
||||||
MergingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, size_t max_block_size_, size_t limit_ = 0)
|
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),
|
: description(description_), max_block_size(max_block_size_), limit(limit_),
|
||||||
num_columns(0), source_blocks(inputs_.size()), cursors(inputs_.size()), log(&Logger::get("MergingSortedBlockInputStream"))
|
source_blocks(inputs_.size()), cursors(inputs_.size())
|
||||||
{
|
{
|
||||||
children.insert(children.end(), inputs_.begin(), inputs_.end());
|
children.insert(children.end(), inputs_.begin(), inputs_.end());
|
||||||
}
|
}
|
||||||
@ -65,14 +65,13 @@ protected:
|
|||||||
SortDescription description;
|
SortDescription description;
|
||||||
size_t max_block_size;
|
size_t max_block_size;
|
||||||
size_t limit;
|
size_t limit;
|
||||||
size_t total_merged_rows;
|
size_t total_merged_rows = 0;
|
||||||
|
|
||||||
bool first;
|
bool first = true;
|
||||||
|
bool has_collation = false;
|
||||||
bool has_collation;
|
|
||||||
|
|
||||||
/// Текущие сливаемые блоки.
|
/// Текущие сливаемые блоки.
|
||||||
size_t num_columns;
|
size_t num_columns = 0;
|
||||||
Blocks source_blocks;
|
Blocks source_blocks;
|
||||||
|
|
||||||
typedef std::vector<SortCursorImpl> CursorImpls;
|
typedef std::vector<SortCursorImpl> CursorImpls;
|
||||||
@ -139,7 +138,7 @@ private:
|
|||||||
template <typename TSortCursor>
|
template <typename TSortCursor>
|
||||||
void merge(Block & merged_block, ColumnPlainPtrs & merged_columns, std::priority_queue<TSortCursor> & queue);
|
void merge(Block & merged_block, ColumnPlainPtrs & merged_columns, std::priority_queue<TSortCursor> & queue);
|
||||||
|
|
||||||
Logger * log;
|
Logger * log = &Logger::get("MergingSortedBlockInputStream");
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,11 @@ namespace DB
|
|||||||
class NativeBlockInputStream : public IProfilingBlockInputStream
|
class NativeBlockInputStream : public IProfilingBlockInputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NativeBlockInputStream(ReadBuffer & istr_, const DataTypeFactory & data_type_factory_)
|
/** В случае указания ненулевой server_revision, может ожидаться и считываться дополнительная информация о блоке,
|
||||||
: istr(istr_), data_type_factory(data_type_factory_) {}
|
* в зависимости от поддерживаемой для указанной ревизии.
|
||||||
|
*/
|
||||||
|
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"; }
|
String getName() const override { return "NativeBlockInputStream"; }
|
||||||
|
|
||||||
@ -31,6 +34,7 @@ protected:
|
|||||||
private:
|
private:
|
||||||
ReadBuffer & istr;
|
ReadBuffer & istr;
|
||||||
const DataTypeFactory & data_type_factory;
|
const DataTypeFactory & data_type_factory;
|
||||||
|
UInt64 server_revision;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,18 @@ namespace DB
|
|||||||
class NativeBlockOutputStream : public IBlockOutputStream
|
class NativeBlockOutputStream : public IBlockOutputStream
|
||||||
{
|
{
|
||||||
public:
|
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 write(const Block & block) override;
|
||||||
void flush() override { ostr.next(); }
|
void flush() override { ostr.next(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WriteBuffer & ostr;
|
WriteBuffer & ostr;
|
||||||
|
UInt64 client_revision;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ using Poco::SharedPtr;
|
|||||||
class OneBlockInputStream : public IProfilingBlockInputStream
|
class OneBlockInputStream : public IProfilingBlockInputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
OneBlockInputStream(const Block & block_) : block(block_), has_been_read(false) {}
|
OneBlockInputStream(const Block & block_) : block(block_) {}
|
||||||
|
|
||||||
String getName() const override { return "OneBlockInputStream"; }
|
String getName() const override { return "OneBlockInputStream"; }
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Block block;
|
Block block;
|
||||||
bool has_been_read;
|
bool has_been_read = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,10 @@ class ParallelAggregatingBlockInputStream : public IProfilingBlockInputStream
|
|||||||
public:
|
public:
|
||||||
ParallelAggregatingBlockInputStream(BlockInputStreams inputs, const ColumnNumbers & keys_,
|
ParallelAggregatingBlockInputStream(BlockInputStreams inputs, const ColumnNumbers & keys_,
|
||||||
AggregateDescriptions & aggregates_, bool overflow_row_, bool final_, size_t max_threads_,
|
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)
|
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_)),
|
Compiler * compiler_, UInt32 min_count_to_compile_)
|
||||||
has_been_read(false), final(final_), max_threads(std::min(inputs.size(), max_threads_)),
|
: 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()),
|
keys_size(keys_.size()), aggregates_size(aggregates_.size()),
|
||||||
handler(*this), processor(inputs, max_threads, handler)
|
handler(*this), processor(inputs, max_threads, handler)
|
||||||
{
|
{
|
||||||
@ -34,14 +35,14 @@ public:
|
|||||||
*/
|
*/
|
||||||
ParallelAggregatingBlockInputStream(BlockInputStreams inputs, const Names & key_names,
|
ParallelAggregatingBlockInputStream(BlockInputStreams inputs, const Names & key_names,
|
||||||
const AggregateDescriptions & aggregates, bool overflow_row_, bool final_, size_t max_threads_,
|
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)
|
size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_,
|
||||||
: has_been_read(false), final(final_), max_threads(std::min(inputs.size(), max_threads_)),
|
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()),
|
keys_size(key_names.size()), aggregates_size(aggregates.size()),
|
||||||
handler(*this), processor(inputs, max_threads, handler)
|
handler(*this), processor(inputs, max_threads, handler)
|
||||||
{
|
{
|
||||||
children.insert(children.end(), inputs.begin(), inputs.end());
|
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"; }
|
String getName() const override { return "ParallelAggregatingBlockInputStream"; }
|
||||||
@ -61,7 +62,7 @@ public:
|
|||||||
for (size_t i = 0; i < children_ids.size(); ++i)
|
for (size_t i = 0; i < children_ids.size(); ++i)
|
||||||
res << (i == 0 ? "" : ", ") << children_ids[i];
|
res << (i == 0 ? "" : ", ") << children_ids[i];
|
||||||
|
|
||||||
res << ", " << aggregator->getID() << ")";
|
res << ", " << aggregator.getID() << ")";
|
||||||
return res.str();
|
return res.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,65 +77,29 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
Block readImpl() override
|
Block readImpl() override
|
||||||
{
|
{
|
||||||
if (has_been_read)
|
if (!executed)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
size_t rows = many_data[i]->size();
|
executed = true;
|
||||||
LOG_TRACE(log, std::fixed << std::setprecision(3)
|
AggregatedDataVariantsPtr data_variants = executeAndMerge();
|
||||||
<< "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;
|
if (data_variants)
|
||||||
total_src_bytes += threads_data[i].src_bytes;
|
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())
|
res = *it;
|
||||||
return Block();
|
++it;
|
||||||
|
|
||||||
return aggregator->convertToBlock(*res, final);
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SharedPtr<Aggregator> aggregator;
|
Aggregator aggregator;
|
||||||
bool has_been_read;
|
|
||||||
bool final;
|
bool final;
|
||||||
size_t max_threads;
|
size_t max_threads;
|
||||||
|
|
||||||
@ -148,6 +113,10 @@ private:
|
|||||||
*/
|
*/
|
||||||
bool no_more_keys = false;
|
bool no_more_keys = false;
|
||||||
|
|
||||||
|
bool executed = false;
|
||||||
|
BlocksList blocks;
|
||||||
|
BlocksList::iterator it;
|
||||||
|
|
||||||
Logger * log = &Logger::get("ParallelAggregatingBlockInputStream");
|
Logger * log = &Logger::get("ParallelAggregatingBlockInputStream");
|
||||||
|
|
||||||
|
|
||||||
@ -158,7 +127,7 @@ private:
|
|||||||
|
|
||||||
void onBlock(Block & block, size_t thread_num)
|
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_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);
|
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;
|
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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,13 +18,13 @@ namespace DB
|
|||||||
class PushingToViewsBlockOutputStream : public IBlockOutputStream
|
class PushingToViewsBlockOutputStream : public IBlockOutputStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PushingToViewsBlockOutputStream(String database_, String table_, const Context & context_, ASTPtr query_ptr_)
|
PushingToViewsBlockOutputStream(String database, String table, const Context & context_, ASTPtr query_ptr_)
|
||||||
: database(database_), table(table_), context(context_), query_ptr(query_ptr_)
|
: context(context_), query_ptr(query_ptr_)
|
||||||
{
|
{
|
||||||
storage = context.getTable(database, table);
|
storage = context.getTable(database, table);
|
||||||
addTableLock(storage->lockStructure(true));
|
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)
|
for (size_t i = 0; i < dependencies.size(); ++i)
|
||||||
{
|
{
|
||||||
children.push_back(new PushingToViewsBlockOutputStream(dependencies[i].first, dependencies[i].second, context, ASTPtr()));
|
children.push_back(new PushingToViewsBlockOutputStream(dependencies[i].first, dependencies[i].second, context, ASTPtr()));
|
||||||
@ -64,8 +64,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
StoragePtr storage;
|
StoragePtr storage;
|
||||||
BlockOutputStreamPtr output;
|
BlockOutputStreamPtr output;
|
||||||
String database;
|
|
||||||
String table;
|
|
||||||
Context context;
|
Context context;
|
||||||
ASTPtr query_ptr;
|
ASTPtr query_ptr;
|
||||||
std::vector<BlockOutputStreamPtr> children;
|
std::vector<BlockOutputStreamPtr> children;
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
#include <Yandex/logger_useful.h>
|
#include <Yandex/logger_useful.h>
|
||||||
|
|
||||||
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
#include <DB/DataStreams/IProfilingBlockInputStream.h>
|
||||||
|
#include <DB/DataStreams/OneBlockInputStream.h>
|
||||||
#include <DB/Common/VirtualColumnUtils.h>
|
#include <DB/Common/VirtualColumnUtils.h>
|
||||||
|
#include <DB/Interpreters/Context.h>
|
||||||
|
|
||||||
#include <DB/Client/ConnectionPool.h>
|
#include <DB/Client/ConnectionPool.h>
|
||||||
|
|
||||||
@ -30,7 +32,7 @@ public:
|
|||||||
/// Принимает готовое соединение.
|
/// Принимает готовое соединение.
|
||||||
RemoteBlockInputStream(Connection & connection_, const String & query_, const Settings * settings_,
|
RemoteBlockInputStream(Connection & connection_, const String & query_, const Settings * settings_,
|
||||||
const Tables & external_tables_ = Tables(), QueryProcessingStage::Enum stage_ = QueryProcessingStage::Complete,
|
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)
|
: connection(&connection_), query(query_), external_tables(external_tables_), stage(stage_), context(context)
|
||||||
{
|
{
|
||||||
init(settings_);
|
init(settings_);
|
||||||
@ -39,7 +41,7 @@ public:
|
|||||||
/// Принимает готовое соединение. Захватывает владение соединением из пула.
|
/// Принимает готовое соединение. Захватывает владение соединением из пула.
|
||||||
RemoteBlockInputStream(ConnectionPool::Entry & pool_entry_, const String & query_, const Settings * settings_,
|
RemoteBlockInputStream(ConnectionPool::Entry & pool_entry_, const String & query_, const Settings * settings_,
|
||||||
const Tables & external_tables_ = Tables(), QueryProcessingStage::Enum stage_ = QueryProcessingStage::Complete,
|
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_),
|
: pool_entry(pool_entry_), connection(&*pool_entry_), query(query_),
|
||||||
external_tables(external_tables_), stage(stage_), context(context)
|
external_tables(external_tables_), stage(stage_), context(context)
|
||||||
{
|
{
|
||||||
@ -49,7 +51,7 @@ public:
|
|||||||
/// Принимает пул, из которого нужно будет достать соединение.
|
/// Принимает пул, из которого нужно будет достать соединение.
|
||||||
RemoteBlockInputStream(IConnectionPool * pool_, const String & query_, const Settings * settings_,
|
RemoteBlockInputStream(IConnectionPool * pool_, const String & query_, const Settings * settings_,
|
||||||
const Tables & external_tables_ = Tables(), QueryProcessingStage::Enum stage_ = QueryProcessingStage::Complete,
|
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)
|
: pool(pool_), query(query_), external_tables(external_tables_), stage(stage_), context(context)
|
||||||
{
|
{
|
||||||
init(settings_);
|
init(settings_);
|
||||||
@ -273,6 +275,13 @@ private:
|
|||||||
bool got_exception_from_server = false;
|
bool got_exception_from_server = false;
|
||||||
|
|
||||||
Logger * log = &Logger::get("RemoteBlockInputStream");
|
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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -20,8 +20,8 @@ class TotalsHavingBlockInputStream : public IProfilingBlockInputStream
|
|||||||
public:
|
public:
|
||||||
TotalsHavingBlockInputStream(BlockInputStreamPtr input_, const Names & keys_names_,
|
TotalsHavingBlockInputStream(BlockInputStreamPtr input_, const Names & keys_names_,
|
||||||
const AggregateDescriptions & aggregates_, bool overflow_row_, ExpressionActionsPtr expression_,
|
const AggregateDescriptions & aggregates_, bool overflow_row_, ExpressionActionsPtr expression_,
|
||||||
const std::string & filter_column_, TotalsMode totals_mode_, float auto_include_threshold_)
|
const std::string & filter_column_, TotalsMode totals_mode_, double auto_include_threshold_)
|
||||||
: aggregator(new Aggregator(keys_names_, aggregates_, overflow_row_)), overflow_row(overflow_row_),
|
: overflow_row(overflow_row_),
|
||||||
expression(expression_), filter_column_name(filter_column_), totals_mode(totals_mode_),
|
expression(expression_), filter_column_name(filter_column_), totals_mode(totals_mode_),
|
||||||
auto_include_threshold(auto_include_threshold_), passed_keys(0), total_keys(0)
|
auto_include_threshold(auto_include_threshold_), passed_keys(0), total_keys(0)
|
||||||
{
|
{
|
||||||
@ -33,7 +33,7 @@ public:
|
|||||||
String getID() const override
|
String getID() const override
|
||||||
{
|
{
|
||||||
std::stringstream res;
|
std::stringstream res;
|
||||||
res << "TotalsHavingBlockInputStream(" << children.back()->getID() << ", " << aggregator->getID()
|
res << "TotalsHavingBlockInputStream(" << children.back()->getID()
|
||||||
<< "," << filter_column_name << ")";
|
<< "," << filter_column_name << ")";
|
||||||
return res.str();
|
return res.str();
|
||||||
}
|
}
|
||||||
@ -44,24 +44,24 @@ protected:
|
|||||||
Block readImpl() override;
|
Block readImpl() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SharedPtr<Aggregator> aggregator;
|
|
||||||
bool overflow_row;
|
bool overflow_row;
|
||||||
ExpressionActionsPtr expression;
|
ExpressionActionsPtr expression;
|
||||||
String filter_column_name;
|
String filter_column_name;
|
||||||
TotalsMode totals_mode;
|
TotalsMode totals_mode;
|
||||||
float auto_include_threshold;
|
double auto_include_threshold;
|
||||||
size_t passed_keys;
|
size_t passed_keys;
|
||||||
size_t total_keys;
|
size_t total_keys;
|
||||||
|
|
||||||
Block current_totals;
|
/** Здесь находятся значения, не прошедшие max_rows_to_group_by.
|
||||||
|
* Они прибавляются или не прибавляются к current_totals в зависимости от totals_mode.
|
||||||
|
*/
|
||||||
Block overflow_aggregates;
|
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)
|
/// Если filter == nullptr - прибавлять все строки. Иначе - только строки, проходящие фильтр (HAVING).
|
||||||
{
|
void addToTotals(Block & totals, Block & block, const IColumn::Filter * filter);
|
||||||
addToTotals(totals, block, filter, block.rows());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ namespace DB
|
|||||||
/** Копирует данные из InputStream в OutputStream
|
/** Копирует данные из 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);
|
void copyData(IRowInputStream & from, IRowOutputStream & to);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -463,8 +463,6 @@ public:
|
|||||||
ErrorCodes::ILLEGAL_COLUMN);
|
ErrorCodes::ILLEGAL_COLUMN);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class FunctionIPv4NumToString : public IFunction
|
class FunctionIPv4NumToString : public IFunction
|
||||||
{
|
{
|
||||||
public:
|
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
|
class FunctionHex : public IFunction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -1379,7 +1379,7 @@ private:
|
|||||||
src_offset = src_offsets[i];
|
src_offset = src_offsets[i];
|
||||||
dst_offset += src_length;
|
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 - 1] = trailing_char_str.front();
|
||||||
dst_data[dst_offset] = 0;
|
dst_data[dst_offset] = 0;
|
||||||
@ -1397,6 +1397,7 @@ private:
|
|||||||
|
|
||||||
block.getByPosition(result).column = new ColumnConstString{
|
block.getByPosition(result).column = new ColumnConstString{
|
||||||
col->size(),
|
col->size(),
|
||||||
|
in_data.size() == 0 ? trailing_char_str :
|
||||||
in_data.back() == trailing_char_str.front() ? in_data : in_data + trailing_char_str
|
in_data.back() == trailing_char_str.front() ? in_data : in_data + trailing_char_str
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -280,7 +280,7 @@ struct ExtractPathFull
|
|||||||
if (nullptr != (pos = strchr(data, '/')) && pos[1] == '/' && nullptr != (pos = strchr(pos + 2, '/')))
|
if (nullptr != (pos = strchr(data, '/')) && pos[1] == '/' && nullptr != (pos = strchr(pos + 2, '/')))
|
||||||
{
|
{
|
||||||
/// no leading slash
|
/// no leading slash
|
||||||
res_data = pos + 1;
|
res_data = pos;
|
||||||
res_size = end - res_data;
|
res_size = end - res_data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,43 +42,43 @@ struct BinaryManipReadBuffer : ReadBuffer {};
|
|||||||
|
|
||||||
template <typename T> WriteBuffer & operator<< (WriteBuffer & buf, const T & x) { writeText(x, buf); return buf; }
|
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 <> inline 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 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); }
|
inline EscapeManipWriteBuffer & operator<< (WriteBuffer & buf, EscapeManip x) { return static_cast<EscapeManipWriteBuffer &>(buf); }
|
||||||
QuoteManipWriteBuffer & operator<< (WriteBuffer & buf, QuoteManip x) { return static_cast<QuoteManipWriteBuffer &>(buf); }
|
inline QuoteManipWriteBuffer & operator<< (WriteBuffer & buf, QuoteManip x) { return static_cast<QuoteManipWriteBuffer &>(buf); }
|
||||||
DoubleQuoteManipWriteBuffer & operator<< (WriteBuffer & buf, DoubleQuoteManip x) { return static_cast<DoubleQuoteManipWriteBuffer &>(buf); }
|
inline DoubleQuoteManipWriteBuffer & operator<< (WriteBuffer & buf, DoubleQuoteManip x) { return static_cast<DoubleQuoteManipWriteBuffer &>(buf); }
|
||||||
BinaryManipWriteBuffer & operator<< (WriteBuffer & buf, BinaryManip x) { return static_cast<BinaryManipWriteBuffer &>(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<< (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<< (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<< (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; }
|
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; }
|
inline 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; }
|
inline 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; }
|
inline 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<< (BinaryManipWriteBuffer & buf, const char * x) { writeStringBinary(x, buf); return buf; }
|
||||||
|
|
||||||
/// Манипулятор вызывает у WriteBuffer метод next - это делает сброс буфера. Для вложенных буферов, сброс не рекурсивный.
|
/// Манипулятор вызывает у WriteBuffer метод next - это делает сброс буфера. Для вложенных буферов, сброс не рекурсивный.
|
||||||
enum FlushManip { flush };
|
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 <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 <> inline 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, 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); }
|
inline EscapeManipReadBuffer & operator>> (ReadBuffer & buf, EscapeManip x) { return static_cast<EscapeManipReadBuffer &>(buf); }
|
||||||
QuoteManipReadBuffer & operator>> (ReadBuffer & buf, QuoteManip x) { return static_cast<QuoteManipReadBuffer &>(buf); }
|
inline QuoteManipReadBuffer & operator>> (ReadBuffer & buf, QuoteManip x) { return static_cast<QuoteManipReadBuffer &>(buf); }
|
||||||
DoubleQuoteManipReadBuffer & operator>> (ReadBuffer & buf, DoubleQuoteManip x) { return static_cast<DoubleQuoteManipReadBuffer &>(buf); }
|
inline DoubleQuoteManipReadBuffer & operator>> (ReadBuffer & buf, DoubleQuoteManip x) { return static_cast<DoubleQuoteManipReadBuffer &>(buf); }
|
||||||
BinaryManipReadBuffer & operator>> (ReadBuffer & buf, BinaryManip x) { return static_cast<BinaryManipReadBuffer &>(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>> (EscapeManipReadBuffer & buf, T & x) { readText(x, buf); return buf; }
|
||||||
template <typename T> ReadBuffer & operator>> (QuoteManipReadBuffer & buf, T & x) { readQuoted(x, buf); return buf; }
|
template <typename T> ReadBuffer & operator>> (QuoteManipReadBuffer & buf, T & x) { readQuoted(x, buf); return buf; }
|
||||||
|
22
dbms/include/DB/Interpreters/AggregateDescription.h
Normal file
22
dbms/include/DB/Interpreters/AggregateDescription.h
Normal 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;
|
||||||
|
|
||||||
|
}
|
@ -9,7 +9,6 @@
|
|||||||
#include <DB/Core/Defines.h>
|
#include <DB/Core/Defines.h>
|
||||||
#include <DB/Core/StringRef.h>
|
#include <DB/Core/StringRef.h>
|
||||||
#include <DB/Columns/IColumn.h>
|
#include <DB/Columns/IColumn.h>
|
||||||
#include <DB/Common/HashTable/HashMap.h>
|
|
||||||
|
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <map>
|
#include <mutex>
|
||||||
#include <unordered_map>
|
#include <memory>
|
||||||
|
|
||||||
#include <Poco/Mutex.h>
|
|
||||||
|
|
||||||
#include <Yandex/logger_useful.h>
|
#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/Core/StringRef.h>
|
||||||
#include <DB/Common/Arena.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/DataStreams/IBlockInputStream.h>
|
||||||
|
|
||||||
#include <DB/AggregateFunctions/IAggregateFunction.h>
|
#include <DB/Interpreters/AggregateDescription.h>
|
||||||
|
|
||||||
#include <DB/Interpreters/AggregationCommon.h>
|
#include <DB/Interpreters/AggregationCommon.h>
|
||||||
#include <DB/Interpreters/Limits.h>
|
#include <DB/Interpreters/Limits.h>
|
||||||
|
#include <DB/Interpreters/Compiler.h>
|
||||||
|
|
||||||
#include <DB/Columns/ColumnString.h>
|
#include <DB/Columns/ColumnString.h>
|
||||||
#include <DB/Columns/ColumnFixedString.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 AggregateDataPtr AggregatedDataWithoutKey;
|
||||||
typedef HashMap<UInt64, AggregateDataPtr> AggregatedDataWithUInt64Key;
|
|
||||||
|
typedef HashMap<UInt64, AggregateDataPtr, HashCRC32<UInt64>> AggregatedDataWithUInt64Key;
|
||||||
typedef HashMapWithSavedHash<StringRef, AggregateDataPtr> AggregatedDataWithStringKey;
|
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 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.
|
/// Специализации для UInt8, UInt16.
|
||||||
struct TrivialHash
|
struct TrivialHash
|
||||||
@ -64,7 +70,10 @@ struct TrivialHash
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Превращает хэш-таблицу в что-то типа lookup-таблицы. Остаётся неоптимальность - в ячейках хранятся ключи.
|
/** Превращает хэш-таблицу в что-то типа lookup-таблицы. Остаётся неоптимальность - в ячейках хранятся ключи.
|
||||||
|
* Также компилятору не удаётся полностью удалить код хождения по цепочке разрешения коллизий, хотя он не нужен.
|
||||||
|
* TODO Переделать в полноценную lookup-таблицу.
|
||||||
|
*/
|
||||||
template <size_t key_bits>
|
template <size_t key_bits>
|
||||||
struct HashTableFixedGrower
|
struct HashTableFixedGrower
|
||||||
{
|
{
|
||||||
@ -82,89 +91,11 @@ struct HashTableFixedGrower
|
|||||||
typedef HashMap<UInt64, AggregateDataPtr, TrivialHash, HashTableFixedGrower<8>> AggregatedDataWithUInt8Key;
|
typedef HashMap<UInt64, AggregateDataPtr, TrivialHash, HashTableFixedGrower<8>> AggregatedDataWithUInt8Key;
|
||||||
typedef HashMap<UInt64, AggregateDataPtr, TrivialHash, HashTableFixedGrower<16>> AggregatedDataWithUInt16Key;
|
typedef HashMap<UInt64, AggregateDataPtr, TrivialHash, HashTableFixedGrower<16>> AggregatedDataWithUInt16Key;
|
||||||
|
|
||||||
template <typename FieldType>
|
|
||||||
struct AggregatedDataWithUIntKey
|
|
||||||
{
|
|
||||||
using Type = AggregatedDataWithUInt64Key;
|
|
||||||
static constexpr bool never_overflows = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
template <typename T>
|
||||||
struct AggregatedDataWithUIntKey<UInt8>
|
inline UInt64 unionCastToUInt64(T x) { return x; }
|
||||||
{
|
|
||||||
using Type = AggregatedDataWithUInt8Key;
|
|
||||||
static constexpr bool never_overflows = true; /// Говорит о том, что в результате агрегации не может быть много записей.
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
template <> inline UInt64 unionCastToUInt64(Float64 x)
|
||||||
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
|
|
||||||
{
|
{
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
@ -176,8 +107,7 @@ inline UInt64 AggregationMethodOneNumber<Float64>::get64(Float64 x) const
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <> inline UInt64 unionCastToUInt64(Float32 x)
|
||||||
inline UInt64 AggregationMethodOneNumber<Float32>::get64(Float32 x) const
|
|
||||||
{
|
{
|
||||||
union
|
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 TData Data;
|
||||||
typedef Data::key_type Key;
|
typedef typename Data::key_type Key;
|
||||||
typedef Data::mapped_type Mapped;
|
typedef typename Data::mapped_type Mapped;
|
||||||
typedef Data::iterator iterator;
|
typedef typename Data::iterator iterator;
|
||||||
typedef Data::const_iterator const_iterator;
|
typedef typename Data::const_iterator const_iterator;
|
||||||
|
|
||||||
static constexpr bool never_overflows = false;
|
|
||||||
|
|
||||||
Data data;
|
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::Offsets_t * offsets;
|
||||||
const ColumnString::Chars_t * chars;
|
const ColumnString::Chars_t * chars;
|
||||||
|
|
||||||
@ -222,37 +219,46 @@ struct AggregationMethodString
|
|||||||
const Sizes & key_sizes,
|
const Sizes & key_sizes,
|
||||||
StringRefs & keys) const
|
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 AggregateDataPtr & getAggregateData(Mapped & value) { return value; }
|
||||||
static const AggregateDataPtr & getAggregateData(const 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
|
struct AggregationMethodFixedString
|
||||||
{
|
{
|
||||||
typedef AggregatedDataWithStringKey Data;
|
typedef TData Data;
|
||||||
typedef Data::key_type Key;
|
typedef typename Data::key_type Key;
|
||||||
typedef Data::mapped_type Mapped;
|
typedef typename Data::mapped_type Mapped;
|
||||||
typedef Data::iterator iterator;
|
typedef typename Data::iterator iterator;
|
||||||
typedef Data::const_iterator const_iterator;
|
typedef typename Data::const_iterator const_iterator;
|
||||||
|
|
||||||
static constexpr bool never_overflows = false;
|
|
||||||
|
|
||||||
Data data;
|
Data data;
|
||||||
|
|
||||||
|
AggregationMethodFixedString() {}
|
||||||
|
|
||||||
|
template <typename Other>
|
||||||
|
AggregationMethodFixedString(const Other & other) : data(other.data) {}
|
||||||
|
|
||||||
|
struct State
|
||||||
|
{
|
||||||
size_t n;
|
size_t n;
|
||||||
const ColumnFixedString::Chars_t * chars;
|
const ColumnFixedString::Chars_t * chars;
|
||||||
|
|
||||||
@ -273,35 +279,42 @@ struct AggregationMethodFixedString
|
|||||||
{
|
{
|
||||||
return StringRef(&(*chars)[i * n], n);
|
return StringRef(&(*chars)[i * n], n);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static AggregateDataPtr & getAggregateData(Mapped & value) { return value; }
|
static AggregateDataPtr & getAggregateData(Mapped & value) { return value; }
|
||||||
static const AggregateDataPtr & getAggregateData(const 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 бит.
|
/// Для случая, когда все ключи фиксированной длины, и они помещаются в 128 бит.
|
||||||
|
template <typename TData>
|
||||||
struct AggregationMethodKeys128
|
struct AggregationMethodKeys128
|
||||||
{
|
{
|
||||||
typedef AggregatedDataWithKeys128 Data;
|
typedef TData Data;
|
||||||
typedef Data::key_type Key;
|
typedef typename Data::key_type Key;
|
||||||
typedef Data::mapped_type Mapped;
|
typedef typename Data::mapped_type Mapped;
|
||||||
typedef Data::iterator iterator;
|
typedef typename Data::iterator iterator;
|
||||||
typedef Data::const_iterator const_iterator;
|
typedef typename Data::const_iterator const_iterator;
|
||||||
|
|
||||||
static constexpr bool never_overflows = false;
|
|
||||||
|
|
||||||
Data data;
|
Data data;
|
||||||
|
|
||||||
|
AggregationMethodKeys128() {}
|
||||||
|
|
||||||
|
template <typename Other>
|
||||||
|
AggregationMethodKeys128(const Other & other) : data(other.data) {}
|
||||||
|
|
||||||
|
struct State
|
||||||
|
{
|
||||||
void init(ConstColumnPlainPtrs & key_columns)
|
void init(ConstColumnPlainPtrs & key_columns)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -315,21 +328,22 @@ struct AggregationMethodKeys128
|
|||||||
{
|
{
|
||||||
return pack128(i, keys_size, key_columns, key_sizes);
|
return pack128(i, keys_size, key_columns, key_sizes);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static AggregateDataPtr & getAggregateData(Mapped & value) { return value; }
|
static AggregateDataPtr & getAggregateData(Mapped & value) { return value; }
|
||||||
static const AggregateDataPtr & getAggregateData(const 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;
|
size_t offset = 0;
|
||||||
for (size_t i = 0; i < keys_size; ++i)
|
for (size_t i = 0; i < keys_size; ++i)
|
||||||
{
|
{
|
||||||
size_t size = key_sizes[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;
|
offset += size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -337,18 +351,24 @@ struct AggregationMethodKeys128
|
|||||||
|
|
||||||
|
|
||||||
/// Для остальных случаев. Агрегирует по 128 битному хэшу от ключа. (При этом, строки, содержащие нули посередине, могут склеиться.)
|
/// Для остальных случаев. Агрегирует по 128 битному хэшу от ключа. (При этом, строки, содержащие нули посередине, могут склеиться.)
|
||||||
|
template <typename TData>
|
||||||
struct AggregationMethodHashed
|
struct AggregationMethodHashed
|
||||||
{
|
{
|
||||||
typedef AggregatedDataHashed Data;
|
typedef TData Data;
|
||||||
typedef Data::key_type Key;
|
typedef typename Data::key_type Key;
|
||||||
typedef Data::mapped_type Mapped;
|
typedef typename Data::mapped_type Mapped;
|
||||||
typedef Data::iterator iterator;
|
typedef typename Data::iterator iterator;
|
||||||
typedef Data::const_iterator const_iterator;
|
typedef typename Data::const_iterator const_iterator;
|
||||||
|
|
||||||
static constexpr bool never_overflows = false;
|
|
||||||
|
|
||||||
Data data;
|
Data data;
|
||||||
|
|
||||||
|
AggregationMethodHashed() {}
|
||||||
|
|
||||||
|
template <typename Other>
|
||||||
|
AggregationMethodHashed(const Other & other) : data(other.data) {}
|
||||||
|
|
||||||
|
struct State
|
||||||
|
{
|
||||||
void init(ConstColumnPlainPtrs & key_columns)
|
void init(ConstColumnPlainPtrs & key_columns)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -362,19 +382,20 @@ struct AggregationMethodHashed
|
|||||||
{
|
{
|
||||||
return hash128(i, keys_size, key_columns, keys);
|
return hash128(i, keys_size, key_columns, keys);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static AggregateDataPtr & getAggregateData(Mapped & value) { return value.second; }
|
static AggregateDataPtr & getAggregateData(Mapped & value) { return value.second; }
|
||||||
static const AggregateDataPtr & getAggregateData(const 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)
|
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::create (внутри - placement new произвольной структуры);
|
||||||
* - они должны быть затем уничтожены с помощью IAggregateFunction::destroy (внутри - вызов деструктора произвольной структуры);
|
* - они должны быть затем уничтожены с помощью IAggregateFunction::destroy (внутри - вызов деструктора произвольной структуры);
|
||||||
* - если агрегация завершена, то, в функции Aggregator::convertToBlock, указатели на состояния агрегатных функций
|
* - если агрегация завершена, то, в функции Aggregator::convertToBlocks, указатели на состояния агрегатных функций
|
||||||
* записываются в ColumnAggregateFunction; ColumnAggregateFunction "захватывает владение" ими, то есть - вызывает destroy в своём деструкторе.
|
* записываются в ColumnAggregateFunction; ColumnAggregateFunction "захватывает владение" ими, то есть - вызывает destroy в своём деструкторе.
|
||||||
* - если при агрегации, до вызова Aggregator::convertToBlock вылетело исключение, то состояния агрегатных функций всё-равно должны быть уничтожены,
|
* - если при агрегации, до вызова Aggregator::convertToBlocks вылетело исключение,
|
||||||
|
* то состояния агрегатных функций всё-равно должны быть уничтожены,
|
||||||
* иначе для сложных состояний (наприемер, AggregateFunctionUniq), будут утечки памяти;
|
* иначе для сложных состояний (наприемер, AggregateFunctionUniq), будут утечки памяти;
|
||||||
* - чтобы, в этом случае, уничтожить состояния, в деструкторе вызывается метод Aggregator::destroyAggregateStates,
|
* - чтобы, в этом случае, уничтожить состояния, в деструкторе вызывается метод Aggregator::destroyAggregateStates,
|
||||||
* но только если переменная aggregator (см. ниже) не nullptr;
|
* но только если переменная aggregator (см. ниже) не nullptr;
|
||||||
@ -412,32 +434,53 @@ struct AggregatedDataVariants : private boost::noncopyable
|
|||||||
*/
|
*/
|
||||||
AggregatedDataWithoutKey without_key = nullptr;
|
AggregatedDataWithoutKey without_key = nullptr;
|
||||||
|
|
||||||
std::unique_ptr<AggregationMethodOneNumber<UInt8>> key8;
|
std::unique_ptr<AggregationMethodOneNumber<UInt8, AggregatedDataWithUInt8Key>> key8;
|
||||||
std::unique_ptr<AggregationMethodOneNumber<UInt16>> key16;
|
std::unique_ptr<AggregationMethodOneNumber<UInt16, AggregatedDataWithUInt16Key>> 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;
|
|
||||||
|
|
||||||
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,
|
EMPTY = 0,
|
||||||
WITHOUT_KEY,
|
without_key,
|
||||||
KEY_8,
|
|
||||||
KEY_16,
|
#define M(NAME, IS_TWO_LEVEL) NAME,
|
||||||
KEY_32,
|
APPLY_FOR_AGGREGATED_VARIANTS(M)
|
||||||
KEY_64,
|
#undef M
|
||||||
KEY_STRING,
|
|
||||||
KEY_FIXED_STRING,
|
|
||||||
KEYS_128,
|
|
||||||
HASHED,
|
|
||||||
};
|
};
|
||||||
Type type = EMPTY;
|
Type type = Type::EMPTY;
|
||||||
|
|
||||||
AggregatedDataVariants() : aggregates_pools(1, new Arena), aggregates_pool(&*aggregates_pools.back()) {}
|
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();
|
~AggregatedDataVariants();
|
||||||
|
|
||||||
@ -447,16 +490,13 @@ struct AggregatedDataVariants : private boost::noncopyable
|
|||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case EMPTY: break;
|
case Type::EMPTY: break;
|
||||||
case WITHOUT_KEY: break;
|
case Type::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;
|
#define M(NAME, IS_TWO_LEVEL) \
|
||||||
case KEY_32: key32 .reset(new decltype(key32)::element_type); break;
|
case Type::NAME: NAME.reset(new decltype(NAME)::element_type); break;
|
||||||
case KEY_64: key64 .reset(new decltype(key64)::element_type); break;
|
APPLY_FOR_AGGREGATED_VARIANTS(M)
|
||||||
case KEY_STRING: key_string .reset(new decltype(key_string)::element_type); break;
|
#undef M
|
||||||
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;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
|
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
|
||||||
@ -467,16 +507,13 @@ struct AggregatedDataVariants : private boost::noncopyable
|
|||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case EMPTY: return 0;
|
case Type::EMPTY: return 0;
|
||||||
case WITHOUT_KEY: return 1;
|
case Type::without_key: return 1;
|
||||||
case KEY_8: return key8->data.size() + (without_key != nullptr);
|
|
||||||
case KEY_16: return key16->data.size() + (without_key != nullptr);
|
#define M(NAME, IS_TWO_LEVEL) \
|
||||||
case KEY_32: return key32->data.size() + (without_key != nullptr);
|
case Type::NAME: return NAME->data.size() + (without_key != nullptr);
|
||||||
case KEY_64: return key64->data.size() + (without_key != nullptr);
|
APPLY_FOR_AGGREGATED_VARIANTS(M)
|
||||||
case KEY_STRING: return key_string->data.size() + (without_key != nullptr);
|
#undef M
|
||||||
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);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
|
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
|
||||||
@ -487,38 +524,100 @@ struct AggregatedDataVariants : private boost::noncopyable
|
|||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case EMPTY: return "EMPTY";
|
case Type::EMPTY: return "EMPTY";
|
||||||
case WITHOUT_KEY: return "WITHOUT_KEY";
|
case Type::without_key: return "without_key";
|
||||||
case KEY_8: return "KEY_8";
|
|
||||||
case KEY_16: return "KEY_16";
|
#define M(NAME, IS_TWO_LEVEL) \
|
||||||
case KEY_32: return "KEY_32";
|
case Type::NAME: return #NAME;
|
||||||
case KEY_64: return "KEY_64";
|
APPLY_FOR_AGGREGATED_VARIANTS(M)
|
||||||
case KEY_STRING: return "KEY_STRING";
|
#undef M
|
||||||
case KEY_FIXED_STRING: return "KEY_FIXED_STRING";
|
|
||||||
case KEYS_128: return "KEYS_128";
|
|
||||||
case HASHED: return "HASHED";
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT);
|
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 SharedPtr<AggregatedDataVariants> AggregatedDataVariantsPtr;
|
||||||
typedef std::vector<AggregatedDataVariantsPtr> ManyAggregatedDataVariants;
|
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
|
class Aggregator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Aggregator(const ColumnNumbers & keys_, const AggregateDescriptions & aggregates_, bool overflow_row_,
|
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()),
|
: keys(keys_), aggregates(aggregates_), aggregates_size(aggregates.size()),
|
||||||
overflow_row(overflow_row_),
|
overflow_row(overflow_row_),
|
||||||
max_rows_to_group_by(max_rows_to_group_by_), group_by_overflow_mode(group_by_overflow_mode_),
|
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());
|
std::sort(keys.begin(), keys.end());
|
||||||
keys.erase(std::unique(keys.begin(), keys.end()), 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_,
|
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()),
|
: key_names(key_names_), aggregates(aggregates_), aggregates_size(aggregates.size()),
|
||||||
overflow_row(overflow_row_),
|
overflow_row(overflow_row_),
|
||||||
max_rows_to_group_by(max_rows_to_group_by_), group_by_overflow_mode(group_by_overflow_mode_),
|
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());
|
std::sort(key_names.begin(), key_names.end());
|
||||||
key_names.erase(std::unique(key_names.begin(), key_names.end()), 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);
|
void execute(BlockInputStreamPtr stream, AggregatedDataVariants & result);
|
||||||
|
|
||||||
typedef std::vector<ConstColumnPlainPtrs> AggregateColumns;
|
using AggregateColumns = std::vector<ConstColumnPlainPtrs>;
|
||||||
typedef std::vector<ColumnAggregateFunction::Container_t *> AggregateColumnsData;
|
using AggregateColumnsData = std::vector<ColumnAggregateFunction::Container_t *>;
|
||||||
|
using AggregateFunctionsPlainPtrs = std::vector<IAggregateFunction *>;
|
||||||
|
|
||||||
/// Обработать один блок. Вернуть false, если обработку следует прервать (при group_by_overflow_mode = 'break').
|
/// Обработать один блок. Вернуть false, если обработку следует прервать (при group_by_overflow_mode = 'break').
|
||||||
bool executeOnBlock(Block & block, AggregatedDataVariants & result,
|
bool executeOnBlock(Block & block, AggregatedDataVariants & result,
|
||||||
@ -550,25 +650,25 @@ public:
|
|||||||
bool & no_more_keys);
|
bool & no_more_keys);
|
||||||
|
|
||||||
/** Преобразовать структуру данных агрегации в блок.
|
/** Преобразовать структуру данных агрегации в блок.
|
||||||
* Если overflow_row = true, то агрегаты для строк, не попавших в max_rows_to_group_by, кладутся в первую строчку возвращаемого блока.
|
* Если overflow_row = true, то агрегаты для строк, не попавших в max_rows_to_group_by, кладутся в первый блок.
|
||||||
*
|
*
|
||||||
* Если final = false, то в качестве столбцов-агрегатов создаются ColumnAggregateFunction с состоянием вычислений,
|
* Если final = false, то в качестве столбцов-агрегатов создаются ColumnAggregateFunction с состоянием вычислений,
|
||||||
* которые могут быть затем объединены с другими состояниями (для распределённой обработки запроса).
|
* которые могут быть затем объединены с другими состояниями (для распределённой обработки запроса).
|
||||||
* Если final = true, то в качестве столбцов-агрегатов создаются столбцы с готовыми значениями.
|
* Если 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.
|
/// Для IBlockInputStream.
|
||||||
String getID() const;
|
String getID() const;
|
||||||
@ -579,7 +679,7 @@ protected:
|
|||||||
ColumnNumbers keys;
|
ColumnNumbers keys;
|
||||||
Names key_names;
|
Names key_names;
|
||||||
AggregateDescriptions aggregates;
|
AggregateDescriptions aggregates;
|
||||||
std::vector<IAggregateFunction *> aggregate_functions;
|
AggregateFunctionsPlainPtrs aggregate_functions;
|
||||||
size_t keys_size;
|
size_t keys_size;
|
||||||
size_t aggregates_size;
|
size_t aggregates_size;
|
||||||
/// Нужно ли класть в AggregatedDataVariants::without_key агрегаты для ключей, не попавших в max_rows_to_group_by.
|
/// Нужно ли класть в AggregatedDataVariants::without_key агрегаты для ключей, не попавших в max_rows_to_group_by.
|
||||||
@ -591,14 +691,40 @@ protected:
|
|||||||
|
|
||||||
/// Для инициализации от первого блока при конкуррентном использовании.
|
/// Для инициализации от первого блока при конкуррентном использовании.
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
Poco::FastMutex mutex;
|
std::mutex mutex;
|
||||||
|
|
||||||
size_t max_rows_to_group_by;
|
size_t max_rows_to_group_by;
|
||||||
OverflowMode group_by_overflow_mode;
|
OverflowMode group_by_overflow_mode;
|
||||||
|
|
||||||
Block sample;
|
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), то вычислить номера столбцов.
|
/** Если заданы только имена столбцов (key_names, а также aggregates[i].column_name), то вычислить номера столбцов.
|
||||||
* Сформировать блок - пример результата.
|
* Сформировать блок - пример результата.
|
||||||
@ -618,6 +744,7 @@ protected:
|
|||||||
void destroyAllAggregateStates(AggregatedDataVariants & result);
|
void destroyAllAggregateStates(AggregatedDataVariants & result);
|
||||||
|
|
||||||
|
|
||||||
|
/// Обработать один блок данных, агрегировать данные в хэш-таблицу.
|
||||||
template <typename Method>
|
template <typename Method>
|
||||||
void executeImpl(
|
void executeImpl(
|
||||||
Method & method,
|
Method & method,
|
||||||
@ -630,30 +757,132 @@ protected:
|
|||||||
bool no_more_keys,
|
bool no_more_keys,
|
||||||
AggregateDataPtr overflow_row) const;
|
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>
|
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(
|
void convertToBlockImpl(
|
||||||
Method & method,
|
Method & method,
|
||||||
|
Table & data,
|
||||||
ColumnPlainPtrs & key_columns,
|
ColumnPlainPtrs & key_columns,
|
||||||
AggregateColumnsData & aggregate_columns,
|
AggregateColumnsData & aggregate_columns,
|
||||||
ColumnPlainPtrs & final_aggregate_columns,
|
ColumnPlainPtrs & final_aggregate_columns,
|
||||||
const Sizes & key_sizes,
|
const Sizes & key_sizes,
|
||||||
size_t start_row, bool final) const;
|
bool final) const;
|
||||||
|
|
||||||
template <typename Method>
|
template <typename Method, typename Table>
|
||||||
void mergeDataImpl(
|
void convertToBlockImplFinal(
|
||||||
Method & method_dst,
|
|
||||||
Method & method_src) const;
|
|
||||||
|
|
||||||
template <typename Method>
|
|
||||||
void mergeStreamsImpl(
|
|
||||||
Method & method,
|
Method & method,
|
||||||
Arena * aggregates_pool,
|
Table & data,
|
||||||
size_t start_row,
|
ColumnPlainPtrs & key_columns,
|
||||||
size_t rows,
|
ColumnPlainPtrs & final_aggregate_columns,
|
||||||
ConstColumnPlainPtrs & key_columns,
|
const Sizes & key_sizes) const;
|
||||||
|
|
||||||
|
template <typename Method, typename Table>
|
||||||
|
void convertToBlockImplNotFinal(
|
||||||
|
Method & method,
|
||||||
|
Table & data,
|
||||||
|
ColumnPlainPtrs & key_columns,
|
||||||
AggregateColumnsData & aggregate_columns,
|
AggregateColumnsData & aggregate_columns,
|
||||||
const Sizes & key_sizes,
|
const Sizes & key_sizes) const;
|
||||||
StringRefs & keys) 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>
|
template <typename Method>
|
||||||
void destroyImpl(
|
void destroyImpl(
|
||||||
|
@ -18,8 +18,8 @@ class Cluster : private boost::noncopyable
|
|||||||
public:
|
public:
|
||||||
Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, const String & cluster_name);
|
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);
|
const String & username, const String & password);
|
||||||
|
|
||||||
/// количество узлов clickhouse сервера, расположенных локально
|
/// количество узлов clickhouse сервера, расположенных локально
|
||||||
@ -80,7 +80,7 @@ private:
|
|||||||
Addresses addresses;
|
Addresses addresses;
|
||||||
AddressesWithFailover addresses_with_failover;
|
AddressesWithFailover addresses_with_failover;
|
||||||
|
|
||||||
size_t local_nodes_num;
|
size_t local_nodes_num = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Clusters
|
struct Clusters
|
||||||
|
119
dbms/include/DB/Interpreters/Compiler.h
Normal file
119
dbms/include/DB/Interpreters/Compiler.h
Normal 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -27,6 +27,7 @@
|
|||||||
#include <DB/Interpreters/ProcessList.h>
|
#include <DB/Interpreters/ProcessList.h>
|
||||||
#include <DB/Interpreters/Cluster.h>
|
#include <DB/Interpreters/Cluster.h>
|
||||||
#include <DB/Interpreters/InterserverIOHandler.h>
|
#include <DB/Interpreters/InterserverIOHandler.h>
|
||||||
|
#include <DB/Interpreters/Compiler.h>
|
||||||
#include <DB/Client/ConnectionPool.h>
|
#include <DB/Client/ConnectionPool.h>
|
||||||
#include <statdaemons/ConfigProcessor.h>
|
#include <statdaemons/ConfigProcessor.h>
|
||||||
#include <zkutil/ZooKeeper.h>
|
#include <zkutil/ZooKeeper.h>
|
||||||
@ -48,8 +49,8 @@ typedef std::map<String, Tables> Databases;
|
|||||||
/// (имя базы данных, имя таблицы)
|
/// (имя базы данных, имя таблицы)
|
||||||
typedef std::pair<String, String> DatabaseAndTableName;
|
typedef std::pair<String, String> DatabaseAndTableName;
|
||||||
|
|
||||||
/// таблица -> множество таблиц-вьюшек, которые селектят из нее
|
/// Таблица -> множество таблиц-представлений, которые деляют SELECT из неё.
|
||||||
typedef std::map<DatabaseAndTableName, std::set<DatabaseAndTableName> > ViewDependencies;
|
typedef std::map<DatabaseAndTableName, std::set<DatabaseAndTableName>> ViewDependencies;
|
||||||
typedef std::vector<DatabaseAndTableName> Dependencies;
|
typedef std::vector<DatabaseAndTableName> Dependencies;
|
||||||
|
|
||||||
/** Набор известных объектов, которые могут быть использованы в запросе.
|
/** Набор известных объектов, которые могут быть использованы в запросе.
|
||||||
@ -80,6 +81,7 @@ struct ContextShared
|
|||||||
int interserver_io_port; /// и порт,
|
int interserver_io_port; /// и порт,
|
||||||
|
|
||||||
String path; /// Путь к директории с данными, со слешем на конце.
|
String path; /// Путь к директории с данными, со слешем на конце.
|
||||||
|
String tmp_path; /// Путь ко временным файлам, возникающим при обработке запроса.
|
||||||
Databases databases; /// Список БД и таблиц в них.
|
Databases databases; /// Список БД и таблиц в них.
|
||||||
TableFunctionFactory table_function_factory; /// Табличные функции.
|
TableFunctionFactory table_function_factory; /// Табличные функции.
|
||||||
AggregateFunctionFactory aggregate_function_factory; /// Агрегатные функции.
|
AggregateFunctionFactory aggregate_function_factory; /// Агрегатные функции.
|
||||||
@ -98,6 +100,7 @@ struct ContextShared
|
|||||||
InterserverIOHandler interserver_io_handler; /// Обработчик для межсерверной передачи данных.
|
InterserverIOHandler interserver_io_handler; /// Обработчик для межсерверной передачи данных.
|
||||||
BackgroundProcessingPoolPtr background_pool; /// Пул потоков для фоновой работы, выполняемой таблицами.
|
BackgroundProcessingPoolPtr background_pool; /// Пул потоков для фоновой работы, выполняемой таблицами.
|
||||||
Macros macros; /// Подстановки из конфига.
|
Macros macros; /// Подстановки из конфига.
|
||||||
|
std::unique_ptr<Compiler> compiler; /// Для динамической компиляции частей запроса, при необходимости.
|
||||||
|
|
||||||
/// Кластеры для distributed таблиц
|
/// Кластеры для distributed таблиц
|
||||||
/// Создаются при создании Distributed таблиц, так как нужно дождаться пока будут выставлены Settings
|
/// Создаются при создании Distributed таблиц, так как нужно дождаться пока будут выставлены Settings
|
||||||
@ -187,7 +190,9 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
String getPath() const;
|
String getPath() const;
|
||||||
|
String getTemporaryPath() const;
|
||||||
void setPath(const String & path);
|
void setPath(const String & path);
|
||||||
|
void setTemporaryPath(const String & path);
|
||||||
|
|
||||||
/** Забрать список пользователей, квот и профилей настроек из этого конфига.
|
/** Забрать список пользователей, квот и профилей настроек из этого конфига.
|
||||||
* Список пользователей полностью заменяется.
|
* Список пользователей полностью заменяется.
|
||||||
@ -206,7 +211,7 @@ public:
|
|||||||
|
|
||||||
void addDependency(const DatabaseAndTableName & from, const DatabaseAndTableName & where);
|
void addDependency(const DatabaseAndTableName & from, const DatabaseAndTableName & where);
|
||||||
void removeDependency(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 может быть пустой - в этом случае используется текущая БД.
|
/// Проверка существования таблицы/БД. database может быть пустой - в этом случае используется текущая БД.
|
||||||
bool isTableExist(const String & database_name, const String & table_name) const;
|
bool isTableExist(const String & database_name, const String & table_name) const;
|
||||||
@ -331,6 +336,8 @@ public:
|
|||||||
void initClusters();
|
void initClusters();
|
||||||
Cluster & getCluster(const std::string & cluster_name);
|
Cluster & getCluster(const std::string & cluster_name);
|
||||||
|
|
||||||
|
Compiler & getCompiler();
|
||||||
|
|
||||||
void shutdown() { shared->shutdown(); }
|
void shutdown() { shared->shutdown(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
#include <DB/Parsers/ASTSelectQuery.h>
|
#include <DB/Parsers/ASTSelectQuery.h>
|
||||||
|
|
||||||
#include <DB/Interpreters/Context.h>
|
#include <DB/Interpreters/Context.h>
|
||||||
#include <DB/Interpreters/Aggregator.h>
|
#include <DB/Interpreters/AggregateDescription.h>
|
||||||
#include <DB/Interpreters/ExpressionActions.h>
|
#include <DB/Interpreters/ExpressionActions.h>
|
||||||
#include <DB/Interpreters/Set.h>
|
#include <DB/Interpreters/Set.h>
|
||||||
#include <DB/Interpreters/Join.h>
|
#include <DB/Interpreters/Join.h>
|
||||||
|
@ -63,6 +63,9 @@ public:
|
|||||||
*/
|
*/
|
||||||
BlockInputStreamPtr execute();
|
BlockInputStreamPtr execute();
|
||||||
|
|
||||||
|
/// Выполнить запрос без объединения потоков.
|
||||||
|
const BlockInputStreams & executeWithoutUnion();
|
||||||
|
|
||||||
/** Выполнить запрос, записать результат в нужном формате в buf.
|
/** Выполнить запрос, записать результат в нужном формате в buf.
|
||||||
* BlockInputStreamPtr возвращается, чтобы можно было потом получить информацию о плане выполнения запроса.
|
* BlockInputStreamPtr возвращается, чтобы можно было потом получить информацию о плане выполнения запроса.
|
||||||
*/
|
*/
|
||||||
@ -72,12 +75,24 @@ public:
|
|||||||
Block getSampleBlock();
|
Block getSampleBlock();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef Poco::SharedPtr<ExpressionAnalyzer> ExpressionAnalyzerPtr;
|
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 init(BlockInputStreamPtr input, const NamesAndTypesList & table_column_names = NamesAndTypesList());
|
void initQueryAnalyzer();
|
||||||
|
|
||||||
/// Выполнить один запрос SELECT из цепочки UNION ALL.
|
/// Выполнить один запрос 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.
|
/// Является ли это первым запросом цепочки UNION ALL имеющей длниу >= 2.
|
||||||
bool isFirstSelectInsideUnionAll() const;
|
bool isFirstSelectInsideUnionAll() const;
|
||||||
@ -115,9 +130,10 @@ private:
|
|||||||
ASTSelectQuery & query;
|
ASTSelectQuery & query;
|
||||||
Context context;
|
Context context;
|
||||||
Settings settings;
|
Settings settings;
|
||||||
|
size_t original_max_threads; /// В settings настройка max_threads может быть изменена. В original_max_threads сохраняется изначальное значение.
|
||||||
QueryProcessingStage::Enum to_stage;
|
QueryProcessingStage::Enum to_stage;
|
||||||
size_t subquery_depth;
|
size_t subquery_depth;
|
||||||
ExpressionAnalyzerPtr query_analyzer;
|
std::unique_ptr<ExpressionAnalyzer> query_analyzer;
|
||||||
BlockInputStreams streams;
|
BlockInputStreams streams;
|
||||||
|
|
||||||
/** Цепочка UNION ALL может иметь длину 1 (в таком случае имеется просто один запрос SELECT)
|
/** Цепочка UNION ALL может иметь длину 1 (в таком случае имеется просто один запрос SELECT)
|
||||||
|
@ -146,7 +146,7 @@ private:
|
|||||||
|
|
||||||
/** Блоки данных таблицы, с которой идёт соединение.
|
/** Блоки данных таблицы, с которой идёт соединение.
|
||||||
*/
|
*/
|
||||||
Blocks blocks;
|
BlocksList blocks;
|
||||||
|
|
||||||
MapsAny maps_any;
|
MapsAny maps_any;
|
||||||
MapsAll maps_all;
|
MapsAll maps_all;
|
||||||
|
@ -36,6 +36,7 @@ struct Limits
|
|||||||
M(SettingUInt64, max_rows_to_sort, 0) \
|
M(SettingUInt64, max_rows_to_sort, 0) \
|
||||||
M(SettingUInt64, max_bytes_to_sort, 0) \
|
M(SettingUInt64, max_bytes_to_sort, 0) \
|
||||||
M(SettingOverflowMode<false>, sort_overflow_mode, OverflowMode::THROW) \
|
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(SettingUInt64, max_result_bytes, 0) \
|
||||||
M(SettingOverflowMode<false>, result_overflow_mode, OverflowMode::THROW) \
|
M(SettingOverflowMode<false>, result_overflow_mode, OverflowMode::THROW) \
|
||||||
\
|
\
|
||||||
/* TODO: Проверять также при merge стадии сортировки, при слиянии и финализации агрегатных функций. */ \
|
/* TODO: Проверять также при слиянии и финализации агрегатных функций. */ \
|
||||||
M(SettingSeconds, max_execution_time, 0) \
|
M(SettingSeconds, max_execution_time, 0) \
|
||||||
M(SettingOverflowMode<false>, timeout_overflow_mode, OverflowMode::THROW) \
|
M(SettingOverflowMode<false>, timeout_overflow_mode, OverflowMode::THROW) \
|
||||||
\
|
\
|
||||||
|
@ -41,8 +41,6 @@ struct Settings
|
|||||||
M(SettingUInt64, max_distributed_connections, DEFAULT_MAX_DISTRIBUTED_CONNECTIONS) \
|
M(SettingUInt64, max_distributed_connections, DEFAULT_MAX_DISTRIBUTED_CONNECTIONS) \
|
||||||
/** Какую часть запроса можно прочитать в оперативку для парсинга (оставшиеся данные для INSERT, если есть, считываются позже) */ \
|
/** Какую часть запроса можно прочитать в оперативку для парсинга (оставшиеся данные для INSERT, если есть, считываются позже) */ \
|
||||||
M(SettingUInt64, max_query_size, DEFAULT_MAX_QUERY_SIZE) \
|
M(SettingUInt64, max_query_size, DEFAULT_MAX_QUERY_SIZE) \
|
||||||
/** Выполнять разные стадии конвейера выполнения запроса параллельно. */ \
|
|
||||||
M(SettingBool, asynchronous, false) \
|
|
||||||
/** Интервал в микросекундах для проверки, не запрошена ли остановка выполнения запроса, и отправки прогресса. */ \
|
/** Интервал в микросекундах для проверки, не запрошена ли остановка выполнения запроса, и отправки прогресса. */ \
|
||||||
M(SettingUInt64, interactive_delay, DEFAULT_INTERACTIVE_DELAY) \
|
M(SettingUInt64, interactive_delay, DEFAULT_INTERACTIVE_DELAY) \
|
||||||
M(SettingSeconds, connect_timeout, DBMS_DEFAULT_CONNECT_TIMEOUT_SEC) \
|
M(SettingSeconds, connect_timeout, DBMS_DEFAULT_CONNECT_TIMEOUT_SEC) \
|
||||||
@ -62,8 +60,6 @@ struct Settings
|
|||||||
M(SettingBool, extremes, false) \
|
M(SettingBool, extremes, false) \
|
||||||
/** Использовать ли кэш разжатых блоков. */ \
|
/** Использовать ли кэш разжатых блоков. */ \
|
||||||
M(SettingBool, use_uncompressed_cache, true) \
|
M(SettingBool, use_uncompressed_cache, true) \
|
||||||
/** Использовать ли SplittingAggregator вместо обычного. Он быстрее для запросов с большим состоянием агрегации. */ \
|
|
||||||
M(SettingBool, use_splitting_aggregator, false) \
|
|
||||||
/** Следует ли отменять выполняющийся запрос с таким же id, как новый. */ \
|
/** Следует ли отменять выполняющийся запрос с таким же id, как новый. */ \
|
||||||
M(SettingBool, replace_running_query, false) \
|
M(SettingBool, replace_running_query, false) \
|
||||||
/** Количество потоков, выполняющих фоновую работу для таблиц (например, слияние в merge tree). \
|
/** Количество потоков, выполняющих фоновую работу для таблиц (например, слияние в merge tree). \
|
||||||
@ -86,6 +82,11 @@ struct Settings
|
|||||||
\
|
\
|
||||||
/** Сэмплирование по умолчанию. Если равно 1, то отключено. */ \
|
/** Сэмплирование по умолчанию. Если равно 1, то отключено. */ \
|
||||||
M(SettingFloat, default_sample, 1.0) \
|
M(SettingFloat, default_sample, 1.0) \
|
||||||
|
\
|
||||||
|
/** Включена ли компиляция запросов. */ \
|
||||||
|
M(SettingBool, compile, false) \
|
||||||
|
/** Количество одинаковых по структуре запросов перед тем, как инициируется их компиляция. */ \
|
||||||
|
M(SettingUInt64, min_count_to_compile, 0) \
|
||||||
|
|
||||||
/// Всевозможные ограничения на выполнение запроса.
|
/// Всевозможные ограничения на выполнение запроса.
|
||||||
Limits limits;
|
Limits limits;
|
||||||
|
291
dbms/include/DB/Interpreters/SpecializedAggregator.h
Normal file
291
dbms/include/DB/Interpreters/SpecializedAggregator.h
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -4,6 +4,7 @@
|
|||||||
#include <DB/Parsers/ASTQueryWithOutput.h>
|
#include <DB/Parsers/ASTQueryWithOutput.h>
|
||||||
#include <DB/Parsers/ASTExpressionList.h>
|
#include <DB/Parsers/ASTExpressionList.h>
|
||||||
#include <DB/Parsers/ASTFunction.h>
|
#include <DB/Parsers/ASTFunction.h>
|
||||||
|
#include <DB/Parsers/ASTAsterisk.h>
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
@ -52,6 +53,41 @@ public:
|
|||||||
return false;
|
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, чтобы вернуть только необходимые столбцы в правильном порядке.
|
/// Переписывает select_expression_list, чтобы вернуть только необходимые столбцы в правильном порядке.
|
||||||
void rewriteSelectExpressionList(const Names & column_names)
|
void rewriteSelectExpressionList(const Names & column_names)
|
||||||
{
|
{
|
||||||
|
@ -13,6 +13,9 @@ namespace DB
|
|||||||
|
|
||||||
namespace
|
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>
|
template <typename PoolFactory>
|
||||||
ConnectionPools createPoolsForAddresses(const std::string & name, PoolFactory && factory)
|
ConnectionPools createPoolsForAddresses(const std::string & name, PoolFactory && factory)
|
||||||
{
|
{
|
||||||
@ -57,7 +60,8 @@ class StorageDistributed::DirectoryMonitor
|
|||||||
public:
|
public:
|
||||||
DirectoryMonitor(StorageDistributed & storage, const std::string & name)
|
DirectoryMonitor(StorageDistributed & storage, const std::string & name)
|
||||||
: storage(storage), pool{createPool(name)}, path{storage.path + 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())}
|
, log{&Logger::get(getLoggerName())}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -90,11 +94,22 @@ private:
|
|||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
do_sleep = true;
|
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());
|
tryLogCurrentException(getLoggerName().data());
|
||||||
}
|
};
|
||||||
|
|
||||||
if (do_sleep)
|
if (do_sleep)
|
||||||
cond.wait_for(lock, sleep_time, quit_requested);
|
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;
|
StorageDistributed & storage;
|
||||||
ConnectionPoolPtr pool;
|
ConnectionPoolPtr pool;
|
||||||
std::string path;
|
std::string path;
|
||||||
|
std::size_t error_count{};
|
||||||
|
std::chrono::milliseconds default_sleep_time;
|
||||||
std::chrono::milliseconds 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};
|
bool quit{false};
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
std::condition_variable cond;
|
std::condition_variable cond;
|
||||||
|
@ -331,8 +331,39 @@ public:
|
|||||||
String from = storage.full_path + name + "/";
|
String from = storage.full_path + name + "/";
|
||||||
String to = storage.full_path + "tmp2_" + name + "/";
|
String to = storage.full_path + "tmp2_" + name + "/";
|
||||||
|
|
||||||
Poco::File(from).renameTo(to);
|
Poco::File from_dir{from};
|
||||||
Poco::File(to).remove(true);
|
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
|
void renameTo(const String & new_name) const
|
||||||
@ -345,10 +376,28 @@ public:
|
|||||||
f.renameTo(to);
|
f.renameTo(to);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Переименовывает кусок, дописав к имени префикс.
|
/// Переименовывает кусок, дописав к имени префикс. to_detached - также перенести в директорию detached.
|
||||||
void renameAddPrefix(const String & prefix) const
|
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, вычислить его тоже.
|
/// Загрузить индекс и вычислить размер. Если size=0, вычислить его тоже.
|
||||||
|
@ -68,7 +68,7 @@ class MergeTreeMergeBlocker
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MergeTreeMergeBlocker(MergeTreeDataMerger & merger)
|
MergeTreeMergeBlocker(MergeTreeDataMerger & merger)
|
||||||
: merger(merger), was_cancelled{merger.cancelAll()} {}
|
: merger(merger), was_cancelled{!merger.cancelAll()} {}
|
||||||
|
|
||||||
~MergeTreeMergeBlocker()
|
~MergeTreeMergeBlocker()
|
||||||
{
|
{
|
||||||
|
@ -320,7 +320,8 @@ private:
|
|||||||
break;
|
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;
|
size_t buffer_size = DBMS_DEFAULT_BUFFER_SIZE < max_mark_range ? DBMS_DEFAULT_BUFFER_SIZE : max_mark_range;
|
||||||
|
@ -133,7 +133,7 @@ private:
|
|||||||
ExpressionActionsPtr sharding_key_expr;
|
ExpressionActionsPtr sharding_key_expr;
|
||||||
String sharding_key_column_name;
|
String sharding_key_column_name;
|
||||||
bool write_enabled;
|
bool write_enabled;
|
||||||
String path;
|
String path; /// Может быть пустым, если data_path_ пустой. В этом случае, директория для данных для отправки не создаётся.
|
||||||
|
|
||||||
class DirectoryMonitor;
|
class DirectoryMonitor;
|
||||||
std::unordered_map<std::string, std::unique_ptr<DirectoryMonitor>> directory_monitors;
|
std::unordered_map<std::string, std::unique_ptr<DirectoryMonitor>> directory_monitors;
|
||||||
|
@ -96,10 +96,11 @@ private:
|
|||||||
|
|
||||||
String format; /// Формат вывода результата в консоль.
|
String format; /// Формат вывода результата в консоль.
|
||||||
size_t format_max_block_size = 0; /// Максимальный размер блока при выводе в консоль.
|
size_t format_max_block_size = 0; /// Максимальный размер блока при выводе в консоль.
|
||||||
String current_format; /// Формат вывода результата текущего запроса в консоль.
|
|
||||||
String insert_format; /// Формат данных для INSERT-а при чтении их из stdin в batch режиме
|
String insert_format; /// Формат данных для INSERT-а при чтении их из stdin в batch режиме
|
||||||
size_t insert_format_max_block_size = 0; /// Максимальный размер блока при чтении данных INSERT-а.
|
size_t insert_format_max_block_size = 0; /// Максимальный размер блока при чтении данных INSERT-а.
|
||||||
|
|
||||||
|
bool has_vertical_output_suffix = false; /// \G указан в конце команды?
|
||||||
|
|
||||||
Context context;
|
Context context;
|
||||||
|
|
||||||
/// Чтение из stdin для batch режима
|
/// Чтение из stdin для batch режима
|
||||||
@ -233,15 +234,12 @@ private:
|
|||||||
<< "." << Revision::get()
|
<< "." << Revision::get()
|
||||||
<< "." << std::endl;
|
<< "." << std::endl;
|
||||||
|
|
||||||
if (is_interactive)
|
if (config().has("vertical"))
|
||||||
{
|
format = config().getString("format", "Vertical");
|
||||||
format = config().getString("format", config().has("vertical") ? "Vertical" : "PrettyCompact");
|
|
||||||
}
|
|
||||||
else
|
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);
|
format_max_block_size = config().getInt("format_max_block_size", DEFAULT_BLOCK_SIZE);
|
||||||
current_format = format;
|
|
||||||
|
|
||||||
insert_format = "Values";
|
insert_format = "Values";
|
||||||
insert_format_max_block_size = config().getInt("insert_format_max_block_size", DEFAULT_INSERT_BLOCK_SIZE);
|
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_semicolon = line[ws - 1] == ';';
|
||||||
bool ends_with_backslash = 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)
|
if (ends_with_backslash)
|
||||||
line = line.substr(0, ws - 1);
|
line = line.substr(0, ws - 1);
|
||||||
|
|
||||||
query += line;
|
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)
|
if (query != prev_query)
|
||||||
{
|
{
|
||||||
@ -383,11 +382,8 @@ private:
|
|||||||
prev_query = query;
|
prev_query = query;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ends_with_format_vertical)
|
if (has_vertical_output_suffix)
|
||||||
{
|
|
||||||
current_format = config().getString("format", "Vertical");
|
|
||||||
query = query.substr(0, query.length() - 2);
|
query = query.substr(0, query.length() - 2);
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -411,7 +407,6 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
query = "";
|
query = "";
|
||||||
current_format = format;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -815,11 +810,22 @@ private:
|
|||||||
processed_rows += block.rows();
|
processed_rows += block.rows();
|
||||||
if (!block_std_out)
|
if (!block_std_out)
|
||||||
{
|
{
|
||||||
|
String current_format = format;
|
||||||
|
|
||||||
/// Формат может быть указан в запросе.
|
/// Формат может быть указан в запросе.
|
||||||
if (ASTQueryWithOutput * query_with_output = dynamic_cast<ASTQueryWithOutput *>(&*parsed_query))
|
if (ASTQueryWithOutput * query_with_output = dynamic_cast<ASTQueryWithOutput *>(&*parsed_query))
|
||||||
|
{
|
||||||
if (query_with_output->format)
|
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))
|
if (ASTIdentifier * id = typeid_cast<ASTIdentifier *>(&*query_with_output->format))
|
||||||
current_format = id->name;
|
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 = context.getFormatFactory().getOutput(current_format, std_out, block);
|
||||||
block_std_out->writePrefix();
|
block_std_out->writePrefix();
|
||||||
|
@ -269,7 +269,7 @@ void Connection::sendData(const Block & block, const String & name)
|
|||||||
else
|
else
|
||||||
maybe_compressed_out = out;
|
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);
|
writeVarUInt(Protocol::Client::Data, *out);
|
||||||
@ -444,7 +444,7 @@ void Connection::initBlockInput()
|
|||||||
else
|
else
|
||||||
maybe_compressed_in = in;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
#include <DB/Common/VirtualColumnUtils.h>
|
|
||||||
|
|
||||||
#include <DB/Interpreters/Context.h>
|
#include <DB/Interpreters/Context.h>
|
||||||
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
|
#include <DB/Interpreters/ExpressionAnalyzer.h>
|
||||||
#include <DB/DataStreams/OneBlockInputStream.h>
|
|
||||||
#include <DB/DataTypes/DataTypeString.h>
|
|
||||||
#include <DB/DataTypes/DataTypesNumberFixed.h>
|
|
||||||
#include <DB/Parsers/ASTIdentifier.h>
|
#include <DB/Parsers/ASTIdentifier.h>
|
||||||
#include <DB/Parsers/ASTExpressionList.h>
|
#include <DB/Parsers/ASTExpressionList.h>
|
||||||
#include <DB/Parsers/ASTLiteral.h>
|
#include <DB/Parsers/ASTLiteral.h>
|
||||||
#include <DB/Parsers/ASTSelectQuery.h>
|
#include <DB/Parsers/ASTSelectQuery.h>
|
||||||
#include <DB/Storages/IStorage.h>
|
|
||||||
#include <DB/Interpreters/InterpreterSelectQuery.h>
|
#include <DB/Common/VirtualColumnUtils.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
@ -73,7 +73,7 @@ void localBackup(Poco::Path source_path, Poco::Path destination_path)
|
|||||||
* Если какой-то файл удалился во время попытки сделать бэкап, то повторим попытку снова,
|
* Если какой-то файл удалился во время попытки сделать бэкап, то повторим попытку снова,
|
||||||
* так как важно учесть какие-нибудь новые файлы, который могли появиться.
|
* так как важно учесть какие-нибудь новые файлы, который могли появиться.
|
||||||
*/
|
*/
|
||||||
do
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -98,5 +98,7 @@ void localBackup(Poco::Path source_path, Poco::Path destination_path)
|
|||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} while (false);
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
#include <DB/Interpreters/AggregationCommon.h>
|
#include <DB/Interpreters/AggregationCommon.h>
|
||||||
|
|
||||||
#include <DB/Common/HashTable/HashMap.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/HashTableWithSmallLocks.h>
|
||||||
//#include <DB/Common/HashTable/HashTableMerge.h>
|
//#include <DB/Common/HashTable/HashTableMerge.h>
|
||||||
|
|
||||||
@ -25,46 +25,6 @@ typedef UInt64 Value;
|
|||||||
typedef std::vector<Key> Source;
|
typedef std::vector<Key> Source;
|
||||||
|
|
||||||
typedef HashMap<Key, Value> Map;
|
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;
|
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<
|
/*typedef HashTableWithSmallLocks<
|
||||||
@ -109,12 +69,55 @@ void aggregate1(Map & map, Source::const_iterator begin, Source::const_iterator
|
|||||||
++map[*it];
|
++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)
|
void aggregate2(MapTwoLevel & map, Source::const_iterator begin, Source::const_iterator end)
|
||||||
{
|
{
|
||||||
for (auto it = begin; it != end; ++it)
|
for (auto it = begin; it != end; ++it)
|
||||||
++map[*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)
|
void merge2(MapTwoLevel * maps, size_t num_threads, size_t bucket)
|
||||||
{
|
{
|
||||||
for (size_t i = 1; i < num_threads; ++i)
|
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;
|
static constexpr size_t threshold = 65536;
|
||||||
|
|
||||||
for (auto it = begin; it != end; ++it)
|
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);
|
Map::iterator found = local_map.find(*it);
|
||||||
|
|
||||||
if (found != local_map.end())
|
if (found != local_map.end())
|
||||||
++found->second;
|
++found->second;
|
||||||
else if (local_map.size() < threshold)
|
|
||||||
++local_map[*it]; /// TODO Можно было бы делать один lookup, а не два.
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
size_t hash_value = global_map.hash(*it);
|
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];
|
++local_map[*it];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
void aggregate5(Map & local_map, MapSmallLocks & global_map, Source::const_iterator begin, Source::const_iterator end)
|
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;
|
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)
|
if (!method || method == 2)
|
||||||
{
|
{
|
||||||
/** Вариант 2.
|
/** Вариант 2.
|
||||||
@ -348,6 +515,60 @@ int main(int argc, char ** argv)
|
|||||||
std::cerr << "Size: " << maps[0].size() << std::endl << std::endl;
|
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)
|
if (!method || method == 3)
|
||||||
{
|
{
|
||||||
/** Вариант 3.
|
/** Вариант 3.
|
||||||
@ -418,6 +639,72 @@ int main(int argc, char ** argv)
|
|||||||
std::cerr << "Size: " << global_map.size() << std::endl << std::endl;
|
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)
|
if (!method || method == 4)
|
||||||
{
|
{
|
||||||
/** Вариант 4.
|
/** Вариант 4.
|
||||||
|
395
dbms/src/Common/tests/parallel_aggregation2.cpp
Normal file
395
dbms/src/Common/tests/parallel_aggregation2.cpp
Normal 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;
|
||||||
|
}
|
@ -36,6 +36,7 @@ void Block::addDefaults(const NamesAndTypesList & required_columns)
|
|||||||
|
|
||||||
Block & Block::operator= (const Block & other)
|
Block & Block::operator= (const Block & other)
|
||||||
{
|
{
|
||||||
|
info = other.info;
|
||||||
data = other.data;
|
data = other.data;
|
||||||
|
|
||||||
index_by_position.resize(data.size());
|
index_by_position.resize(data.size());
|
||||||
@ -272,6 +273,7 @@ Block Block::cloneEmpty() const
|
|||||||
{
|
{
|
||||||
Block res;
|
Block res;
|
||||||
|
|
||||||
|
res.info = info;
|
||||||
for (Container_t::const_iterator it = data.begin(); it != data.end(); ++it)
|
for (Container_t::const_iterator it = data.begin(); it != data.end(); ++it)
|
||||||
res.insert(it->cloneEmpty());
|
res.insert(it->cloneEmpty());
|
||||||
|
|
||||||
@ -374,6 +376,7 @@ bool blocksHaveEqualStructure(const Block & lhs, const Block & rhs)
|
|||||||
|
|
||||||
void Block::clear()
|
void Block::clear()
|
||||||
{
|
{
|
||||||
|
info = BlockInfo();
|
||||||
data.clear();
|
data.clear();
|
||||||
index_by_name.clear();
|
index_by_name.clear();
|
||||||
index_by_position.clear();
|
index_by_position.clear();
|
||||||
@ -381,6 +384,7 @@ void Block::clear()
|
|||||||
|
|
||||||
void Block::swap(Block & other)
|
void Block::swap(Block & other)
|
||||||
{
|
{
|
||||||
|
std::swap(info, other.info);
|
||||||
data.swap(other.data);
|
data.swap(other.data);
|
||||||
index_by_name.swap(other.index_by_name);
|
index_by_name.swap(other.index_by_name);
|
||||||
index_by_position.swap(other.index_by_position);
|
index_by_position.swap(other.index_by_position);
|
||||||
|
@ -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()
|
Block AggregatingBlockInputStream::readImpl()
|
||||||
{
|
{
|
||||||
if (has_been_read)
|
if (!executed)
|
||||||
return Block();
|
{
|
||||||
|
executed = true;
|
||||||
has_been_read = true;
|
|
||||||
|
|
||||||
AggregatedDataVariants data_variants;
|
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())
|
Block res;
|
||||||
return Block();
|
if (isCancelled() || it == blocks.end())
|
||||||
|
return res;
|
||||||
|
|
||||||
return aggregator->convertToBlock(data_variants, final);
|
res = *it;
|
||||||
|
++it;
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,113 +1,148 @@
|
|||||||
#include <queue>
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
#include <statdaemons/Stopwatch.h>
|
|
||||||
|
|
||||||
#include <DB/DataStreams/MergeSortingBlockInputStream.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
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
Block MergeSortingBlockInputStream::readImpl()
|
Block MergeSortingBlockInputStream::readImpl()
|
||||||
{
|
{
|
||||||
/** Достаточно простой алгоритм:
|
/** Алгоритм:
|
||||||
* - прочитать в оперативку все блоки;
|
* - читать в оперативку блоки из источника;
|
||||||
* - объединить их всех;
|
* - когда их становится слишком много и если возможна внешняя сортировка
|
||||||
|
* - слить блоки вместе в сортированный поток и записать его во временный файл;
|
||||||
|
* - в конце, слить вместе все сортированные потоки из временных файлов, а также из накопившихся в оперативке блоков.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (has_been_read)
|
/// Ещё не прочитали блоки.
|
||||||
return Block();
|
if (!impl)
|
||||||
|
{
|
||||||
has_been_read = true;
|
|
||||||
|
|
||||||
Blocks blocks;
|
|
||||||
while (Block block = children.back()->read())
|
while (Block block = children.back()->read())
|
||||||
|
{
|
||||||
blocks.push_back(block);
|
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 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())
|
if (blocks.empty())
|
||||||
return Block();
|
return Block();
|
||||||
|
|
||||||
if (blocks.size() == 1)
|
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)
|
Block res = blocks[0];
|
||||||
continue;
|
blocks.clear();
|
||||||
|
return res;
|
||||||
cursors[nonempty_blocks] = SortCursorImpl(*it, description);
|
|
||||||
has_collation |= cursors[nonempty_blocks].has_collation;
|
|
||||||
|
|
||||||
++nonempty_blocks;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nonempty_blocks == 0)
|
return !has_collation
|
||||||
return Block();
|
? mergeImpl<SortCursor>(queue)
|
||||||
|
: mergeImpl<SortCursorWithCollation>(queue_with_collation);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template <typename TSortCursor>
|
template <typename TSortCursor>
|
||||||
Block MergeSortingBlockInputStream::mergeImpl(Blocks & blocks, CursorImpls & cursors)
|
Block MergeSortingBlocksBlockInputStream::mergeImpl(std::priority_queue<TSortCursor> & queue)
|
||||||
{
|
{
|
||||||
Block merged = blocks[0].cloneEmpty();
|
Block merged = blocks[0].cloneEmpty();
|
||||||
size_t num_columns = blocks[0].columns();
|
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;
|
ColumnPlainPtrs merged_columns;
|
||||||
for (size_t i = 0; i < num_columns; ++i) /// TODO: reserve
|
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.
|
/// Вынимаем строки в нужном порядке и кладём в merged.
|
||||||
for (size_t row = 0; (!limit || row < limit) && !queue.empty(); ++row)
|
size_t merged_rows = 0;
|
||||||
|
while (!queue.empty())
|
||||||
{
|
{
|
||||||
TSortCursor current = queue.top();
|
TSortCursor current = queue.top();
|
||||||
queue.pop();
|
queue.pop();
|
||||||
@ -120,9 +155,24 @@ Block MergeSortingBlockInputStream::mergeImpl(Blocks & blocks, CursorImpls & cur
|
|||||||
current->next();
|
current->next();
|
||||||
queue.push(current);
|
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;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,14 +9,23 @@ namespace DB
|
|||||||
|
|
||||||
Block MergingAggregatedBlockInputStream::readImpl()
|
Block MergingAggregatedBlockInputStream::readImpl()
|
||||||
{
|
{
|
||||||
if (has_been_read)
|
if (!executed)
|
||||||
return Block();
|
{
|
||||||
|
executed = true;
|
||||||
has_been_read = true;
|
|
||||||
|
|
||||||
AggregatedDataVariants data_variants;
|
AggregatedDataVariants data_variants;
|
||||||
aggregator->merge(children.back(), data_variants);
|
aggregator.mergeStream(children.back(), data_variants, max_threads);
|
||||||
return aggregator->convertToBlock(data_variants, final);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,6 +72,10 @@ Block NativeBlockInputStream::readImpl()
|
|||||||
if (istr.eof())
|
if (istr.eof())
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
|
/// Дополнительная информация о блоке.
|
||||||
|
if (server_revision >= DBMS_MIN_REVISION_WITH_BLOCK_INFO)
|
||||||
|
res.info.read(istr);
|
||||||
|
|
||||||
/// Размеры
|
/// Размеры
|
||||||
size_t columns = 0;
|
size_t columns = 0;
|
||||||
size_t rows = 0;
|
size_t rows = 0;
|
||||||
|
@ -49,6 +49,10 @@ static void writeData(const IDataType & type, const IColumn & column, WriteBuffe
|
|||||||
|
|
||||||
void NativeBlockOutputStream::write(const Block & block)
|
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 columns = block.columns();
|
||||||
size_t rows = block.rows();
|
size_t rows = block.rows();
|
||||||
|
@ -27,8 +27,14 @@ const Block & TotalsHavingBlockInputStream::getTotals()
|
|||||||
/** Если totals_mode == AFTER_HAVING_AUTO, нужно решить, добавлять ли в TOTALS агрегаты для строк,
|
/** Если totals_mode == AFTER_HAVING_AUTO, нужно решить, добавлять ли в TOTALS агрегаты для строк,
|
||||||
* не прошедших max_rows_to_group_by.
|
* не прошедших 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);
|
addToTotals(current_totals, overflow_aggregates, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
finalize(current_totals);
|
finalize(current_totals);
|
||||||
totals = current_totals;
|
totals = current_totals;
|
||||||
@ -50,24 +56,28 @@ Block TotalsHavingBlockInputStream::readImpl()
|
|||||||
{
|
{
|
||||||
block = children[0]->read();
|
block = children[0]->read();
|
||||||
|
|
||||||
|
/// Блок со значениями, не вошедшими в max_rows_to_group_by. Отложим его.
|
||||||
|
if (overflow_row && block && block.info.is_overflows)
|
||||||
|
{
|
||||||
|
overflow_aggregates = block;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!block)
|
if (!block)
|
||||||
return finalized;
|
return finalized;
|
||||||
|
|
||||||
finalized = block;
|
finalized = block;
|
||||||
finalize(finalized);
|
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);
|
addToTotals(current_totals, block, nullptr);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (!filter_column_name.empty())
|
|
||||||
{
|
{
|
||||||
|
/// Вычисляем выражение в HAVING.
|
||||||
expression->execute(finalized);
|
expression->execute(finalized);
|
||||||
|
|
||||||
size_t filter_column_pos = finalized.getPositionByName(filter_column_name);
|
size_t filter_column_pos = finalized.getPositionByName(filter_column_name);
|
||||||
@ -85,25 +95,13 @@ Block TotalsHavingBlockInputStream::readImpl()
|
|||||||
|
|
||||||
IColumn::Filter & filter = filter_column->getData();
|
IColumn::Filter & filter = filter_column->getData();
|
||||||
|
|
||||||
if (totals_mode != TotalsMode::BEFORE_HAVING)
|
/// Прибавляем значения в totals (если это не было сделано ранее).
|
||||||
{
|
if (totals_mode == TotalsMode::BEFORE_HAVING)
|
||||||
if (overflow_row)
|
addToTotals(current_totals, block, nullptr);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
addToTotals(current_totals, block, &filter);
|
addToTotals(current_totals, block, &filter);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overflow_row)
|
|
||||||
filter[0] = 0;
|
|
||||||
|
|
||||||
|
/// Фильтруем блок по выражению в HAVING.
|
||||||
size_t columns = finalized.columns();
|
size_t columns = finalized.columns();
|
||||||
|
|
||||||
for (size_t i = 0; i < columns; ++i)
|
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)
|
if (!finalized)
|
||||||
continue;
|
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;
|
bool init = !totals;
|
||||||
|
|
||||||
@ -188,7 +173,7 @@ void TotalsHavingBlockInputStream::addToTotals(Block & totals, Block & block, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ColumnAggregateFunction::Container_t & vec = column->getData();
|
const ColumnAggregateFunction::Container_t & vec = column->getData();
|
||||||
size_t size = std::min(vec.size(), rows);
|
size_t size = vec.size();
|
||||||
|
|
||||||
if (filter)
|
if (filter)
|
||||||
{
|
{
|
||||||
|
@ -7,13 +7,21 @@
|
|||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
void copyData(IBlockInputStream & from, IBlockOutputStream & to)
|
void copyData(IBlockInputStream & from, IBlockOutputStream & to, volatile bool * is_cancelled)
|
||||||
{
|
{
|
||||||
from.readPrefix();
|
from.readPrefix();
|
||||||
to.writePrefix();
|
to.writePrefix();
|
||||||
|
|
||||||
while (Block block = from.read())
|
while (Block block = from.read())
|
||||||
|
{
|
||||||
|
if (is_cancelled && *is_cancelled)
|
||||||
|
break;
|
||||||
|
|
||||||
to.write(block);
|
to.write(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_cancelled && *is_cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
/// Для вывода дополнительной информации в некоторых форматах.
|
/// Для вывода дополнительной информации в некоторых форматах.
|
||||||
if (IProfilingBlockInputStream * input = dynamic_cast<IProfilingBlockInputStream *>(&from))
|
if (IProfilingBlockInputStream * input = dynamic_cast<IProfilingBlockInputStream *>(&from))
|
||||||
@ -25,6 +33,9 @@ void copyData(IBlockInputStream & from, IBlockOutputStream & to)
|
|||||||
to.setExtremes(input->getExtremes());
|
to.setExtremes(input->getExtremes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_cancelled && *is_cancelled)
|
||||||
|
return;
|
||||||
|
|
||||||
from.readSuffix();
|
from.readSuffix();
|
||||||
to.writeSuffix();
|
to.writeSuffix();
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,8 @@ int main(int argc, char ** argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
DB::BlockInputStreamPtr stream = new DB::OneBlockInputStream(block);
|
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::WriteBufferFromOStream ob(std::cout);
|
||||||
DB::RowOutputStreamPtr row_out = new DB::TabSeparatedRowOutputStream(ob, sample);
|
DB::RowOutputStreamPtr row_out = new DB::TabSeparatedRowOutputStream(ob, sample);
|
||||||
|
@ -44,7 +44,6 @@ int main(int argc, char ** argv)
|
|||||||
loadMetadata(context);
|
loadMetadata(context);
|
||||||
|
|
||||||
context.setCurrentDatabase("default");
|
context.setCurrentDatabase("default");
|
||||||
context.setSetting("asynchronous", Field(0UL));
|
|
||||||
context.setSetting("max_threads", 1UL);
|
context.setSetting("max_threads", 1UL);
|
||||||
|
|
||||||
BlockIO io1 = executeQuery(
|
BlockIO io1 = executeQuery(
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include <DB/Storages/StorageLog.h>
|
#include <DB/Storages/StorageLog.h>
|
||||||
|
|
||||||
#include <DB/Interpreters/Context.h>
|
#include <DB/Interpreters/Context.h>
|
||||||
|
#include <Yandex/Revision.h>
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char ** argv)
|
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];
|
SharedPtr<IBlockInputStream> in = table->read(column_names, 0, Context{}, Settings(), stage)[0];
|
||||||
WriteBufferFromOStream out1(std::cout);
|
WriteBufferFromOStream out1(std::cout);
|
||||||
CompressedWriteBuffer out2(out1);
|
CompressedWriteBuffer out2(out1);
|
||||||
NativeBlockOutputStream out3(out2);
|
NativeBlockOutputStream out3(out2, Revision::get());
|
||||||
copyData(*in, out3);
|
copyData(*in, out3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +119,7 @@ int main(int argc, char ** argv)
|
|||||||
|
|
||||||
ReadBufferFromIStream in1(std::cin);
|
ReadBufferFromIStream in1(std::cin);
|
||||||
CompressedReadBuffer in2(in1);
|
CompressedReadBuffer in2(in1);
|
||||||
NativeBlockInputStream in3(in2, factory);
|
NativeBlockInputStream in3(in2, factory, Revision::get());
|
||||||
SharedPtr<IBlockOutputStream> out = table->write(0);
|
SharedPtr<IBlockOutputStream> out = table->write(0);
|
||||||
copyData(in3, *out);
|
copyData(in3, *out);
|
||||||
}
|
}
|
||||||
|
@ -158,10 +158,11 @@ int main(int argc, char ** argv)
|
|||||||
sort_columns.push_back(SortColumnDescription(3, 1));
|
sort_columns.push_back(SortColumnDescription(3, 1));
|
||||||
|
|
||||||
QueryProcessingStage::Enum stage;
|
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];
|
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 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);
|
//in = new LimitBlockInputStream(in, 10);
|
||||||
|
|
||||||
WriteBufferFromOStream ob(std::cout);
|
WriteBufferFromOStream ob(std::cout);
|
||||||
|
@ -11,6 +11,7 @@ void registerFunctionsCoding(FunctionFactory & factory)
|
|||||||
factory.registerFunction<FunctionIPv6StringToNum>();
|
factory.registerFunction<FunctionIPv6StringToNum>();
|
||||||
factory.registerFunction<FunctionIPv4NumToString>();
|
factory.registerFunction<FunctionIPv4NumToString>();
|
||||||
factory.registerFunction<FunctionIPv4StringToNum>();
|
factory.registerFunction<FunctionIPv4StringToNum>();
|
||||||
|
factory.registerFunction<FunctionIPv4NumToStringClassC>();
|
||||||
factory.registerFunction<FunctionHex>();
|
factory.registerFunction<FunctionHex>();
|
||||||
factory.registerFunction<FunctionUnhex>();
|
factory.registerFunction<FunctionUnhex>();
|
||||||
factory.registerFunction<FunctionBitmaskToArray>();
|
factory.registerFunction<FunctionBitmaskToArray>();
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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):
|
Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, const String & cluster_name)
|
||||||
local_nodes_num(0)
|
|
||||||
{
|
{
|
||||||
Poco::Util::AbstractConfiguration & config = Poco::Util::Application::instance().config();
|
Poco::Util::AbstractConfiguration & config = Poco::Util::Application::instance().config();
|
||||||
Poco::Util::AbstractConfiguration::Keys config_keys;
|
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());
|
slot_to_shard.insert(std::end(slot_to_shard), weight, shard_info_vec.size());
|
||||||
if (const auto is_local = isLocal(addresses.back()))
|
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
|
else
|
||||||
shard_info_vec.push_back({{addressToDirName(addresses.back())}, weight, is_local});
|
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())
|
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;
|
ConnectionPools replicas;
|
||||||
replicas.reserve(it->size());
|
replicas.reserve(shard.size());
|
||||||
|
|
||||||
bool has_local_replics = false;
|
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;
|
has_local_replics = true;
|
||||||
break;
|
break;
|
||||||
@ -170,7 +169,8 @@ Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_fa
|
|||||||
{
|
{
|
||||||
replicas.emplace_back(new ConnectionPool(
|
replicas.emplace_back(new ConnectionPool(
|
||||||
settings.distributed_connections_pool_size,
|
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.connect_timeout_with_failover_ms, settings.limits.max_execution_time),
|
||||||
saturate(settings.receive_timeout, settings.limits.max_execution_time),
|
saturate(settings.receive_timeout, settings.limits.max_execution_time),
|
||||||
saturate(settings.send_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())
|
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;
|
++local_nodes_num;
|
||||||
}
|
}
|
||||||
@ -195,7 +195,8 @@ Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_fa
|
|||||||
{
|
{
|
||||||
pools.emplace_back(new ConnectionPool(
|
pools.emplace_back(new ConnectionPool(
|
||||||
settings.distributed_connections_pool_size,
|
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.connect_timeout, settings.limits.max_execution_time),
|
||||||
saturate(settings.receive_timeout, settings.limits.max_execution_time),
|
saturate(settings.receive_timeout, settings.limits.max_execution_time),
|
||||||
saturate(settings.send_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,
|
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)
|
const String & username, const String & password)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < names.size(); ++i)
|
for (const auto & shard : names)
|
||||||
{
|
{
|
||||||
Addresses current;
|
Addresses current;
|
||||||
for (size_t j = 0; j < names[i].size(); ++j)
|
for (auto & replica : shard)
|
||||||
current.emplace_back(names[i][j], username, password);
|
current.emplace_back(replica, username, password);
|
||||||
addresses_with_failover.emplace_back(current);
|
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;
|
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(
|
replicas.emplace_back(new ConnectionPool(
|
||||||
settings.distributed_connections_pool_size,
|
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.connect_timeout_with_failover_ms, settings.limits.max_execution_time),
|
||||||
saturate(settings.receive_timeout, settings.limits.max_execution_time),
|
saturate(settings.receive_timeout, settings.limits.max_execution_time),
|
||||||
saturate(settings.send_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));
|
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(),
|
interfaces.end() != std::find_if(interfaces.begin(), interfaces.end(),
|
||||||
[&](const Poco::Net::NetworkInterface & interface) { return interface.address() == address.host_port.host(); }))
|
[&](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 true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
242
dbms/src/Interpreters/Compiler.cpp
Normal file
242
dbms/src/Interpreters/Compiler.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -20,6 +20,12 @@ String Context::getPath() const
|
|||||||
return shared->path;
|
return shared->path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String Context::getTemporaryPath() const
|
||||||
|
{
|
||||||
|
Poco::ScopedLock<Poco::Mutex> lock(shared->mutex);
|
||||||
|
return shared->tmp_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Context::setPath(const String & path)
|
void Context::setPath(const String & path)
|
||||||
{
|
{
|
||||||
@ -27,6 +33,12 @@ void Context::setPath(const String & path)
|
|||||||
shared->path = 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)
|
void Context::setUsersConfig(ConfigurationPtr config)
|
||||||
{
|
{
|
||||||
@ -81,15 +93,17 @@ void Context::removeDependency(const DatabaseAndTableName & from, const Database
|
|||||||
shared->view_dependencies[from].erase(where);
|
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);
|
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())
|
if (iter == shared->view_dependencies.end())
|
||||||
return Dependencies();
|
return {};
|
||||||
const std::set<DatabaseAndTableName> &buf = iter->second;
|
|
||||||
Dependencies res(buf.begin(), buf.end());
|
return Dependencies(iter->second.begin(), iter->second.end());
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Context::isTableExist(const String & database_name, const String & table_name) const
|
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
|
else
|
||||||
throw Poco::Exception("Failed to find cluster with name = " + cluster_name);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
#include <DB/DataTypes/DataTypeTuple.h>
|
#include <DB/DataTypes/DataTypeTuple.h>
|
||||||
#include <DB/DataTypes/DataTypeExpression.h>
|
#include <DB/DataTypes/DataTypeExpression.h>
|
||||||
#include <DB/DataTypes/DataTypeNested.h>
|
#include <DB/DataTypes/DataTypeNested.h>
|
||||||
|
|
||||||
#include <DB/Columns/ColumnSet.h>
|
#include <DB/Columns/ColumnSet.h>
|
||||||
#include <DB/Columns/ColumnExpression.h>
|
#include <DB/Columns/ColumnExpression.h>
|
||||||
|
|
||||||
@ -26,6 +27,7 @@
|
|||||||
#include <DB/Storages/StorageMemory.h>
|
#include <DB/Storages/StorageMemory.h>
|
||||||
#include <DB/Storages/StorageReplicatedMergeTree.h>
|
#include <DB/Storages/StorageReplicatedMergeTree.h>
|
||||||
|
|
||||||
|
#include <DB/DataStreams/LazyBlockInputStream.h>
|
||||||
#include <DB/DataStreams/copyData.h>
|
#include <DB/DataStreams/copyData.h>
|
||||||
|
|
||||||
#include <DB/Common/typeid_cast.h>
|
#include <DB/Common/typeid_cast.h>
|
||||||
@ -583,19 +585,27 @@ static SharedPtr<InterpreterSelectQuery> interpretSubquery(
|
|||||||
ASTPtr query;
|
ASTPtr query;
|
||||||
if (table)
|
if (table)
|
||||||
{
|
{
|
||||||
String query_str = "SELECT * FROM " + backQuoteIfNeed(table->name);
|
/// create ASTSelectQuery for "SELECT * FROM table" as if written by hand
|
||||||
const char * begin = query_str.data();
|
const auto select_query = new ASTSelectQuery;
|
||||||
const char * end = begin + query_str.size();
|
query = select_query;
|
||||||
const char * pos = begin;
|
|
||||||
Expected expected = "";
|
|
||||||
|
|
||||||
bool parse_res = ParserSelectQuery().parse(pos, end, query, expected);
|
const auto select_expression_list = new ASTExpressionList;
|
||||||
if (!parse_res)
|
select_query->select_expression_list = select_expression_list;
|
||||||
throw Exception("Error in parsing SELECT query while creating set or join for table " + table->name + ".",
|
select_query->children.emplace_back(select_query->select_expression_list);
|
||||||
ErrorCodes::LOGICAL_ERROR);
|
|
||||||
|
|
||||||
/// @note it may be more appropriate to manually replace ASTAsterisk with table's columns
|
/// get columns list for target table
|
||||||
ExpressionAnalyzer{query, context, subquery_depth};
|
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
|
else
|
||||||
query = subquery->children.at(0);
|
query = subquery->children.at(0);
|
||||||
@ -629,7 +639,6 @@ void ExpressionAnalyzer::addExternalStorage(ASTPtr & subquery_or_table_name)
|
|||||||
NamesAndTypesListPtr columns = new NamesAndTypesList(sample.getColumnsList());
|
NamesAndTypesListPtr columns = new NamesAndTypesList(sample.getColumnsList());
|
||||||
|
|
||||||
String external_table_name = "_data" + toString(external_table_id);
|
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.
|
* - в этой функции видно выражение IN _data1.
|
||||||
*/
|
*/
|
||||||
if (!subquery_for_set.source)
|
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;
|
subquery_for_set.set = ast_set->set;
|
||||||
arg = ast_set_ptr;
|
arg = ast_set_ptr;
|
||||||
@ -1389,7 +1428,10 @@ bool ExpressionAnalyzer::appendJoin(ExpressionActionsChain & chain, bool only_ty
|
|||||||
* - в этой функции видно выражение JOIN _data1.
|
* - в этой функции видно выражение JOIN _data1.
|
||||||
*/
|
*/
|
||||||
if (!subquery_for_set.source)
|
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;
|
subquery_for_set.join = join;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
#include <DB/DataStreams/AsynchronousBlockInputStream.h>
|
#include <DB/DataStreams/AsynchronousBlockInputStream.h>
|
||||||
#include <DB/DataStreams/UnionBlockInputStream.h>
|
#include <DB/DataStreams/UnionBlockInputStream.h>
|
||||||
#include <DB/DataStreams/ParallelAggregatingBlockInputStream.h>
|
#include <DB/DataStreams/ParallelAggregatingBlockInputStream.h>
|
||||||
#include <DB/DataStreams/SplittingAggregatingBlockInputStream.h>
|
|
||||||
#include <DB/DataStreams/DistinctBlockInputStream.h>
|
#include <DB/DataStreams/DistinctBlockInputStream.h>
|
||||||
#include <DB/DataStreams/NullBlockInputStream.h>
|
#include <DB/DataStreams/NullBlockInputStream.h>
|
||||||
#include <DB/DataStreams/TotalsHavingBlockInputStream.h>
|
#include <DB/DataStreams/TotalsHavingBlockInputStream.h>
|
||||||
@ -33,19 +32,53 @@
|
|||||||
namespace DB
|
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);
|
ProfileEvents::increment(ProfileEvents::SelectQuery);
|
||||||
|
|
||||||
if (settings.limits.max_subquery_depth && subquery_depth > settings.limits.max_subquery_depth)
|
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),
|
throw Exception("Too deep subqueries. Maximum: " + toString(settings.limits.max_subquery_depth),
|
||||||
ErrorCodes::TOO_DEEP_SUBQUERIES);
|
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 (query.table && typeid_cast<ASTSelectQuery *>(&*query.table))
|
||||||
{
|
{
|
||||||
if (table_column_names.empty())
|
if (table_column_names.empty())
|
||||||
|
{
|
||||||
|
/// Оптимизация: мы считаем, что запрос содержит только один SELECT, даже если это может быть
|
||||||
|
/// в самом деле цепочкой UNION ALL. Первый запрос достаточен для определения нужных столбцов.
|
||||||
context.setColumns(InterpreterSelectQuery(query.table, context, to_stage, subquery_depth, nullptr, false).getSampleBlock().getColumnsList());
|
context.setColumns(InterpreterSelectQuery(query.table, context, to_stage, subquery_depth, nullptr, false).getSampleBlock().getColumnsList());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (query.table && typeid_cast<const ASTFunction *>(&*query.table))
|
if (query.table && typeid_cast<const ASTFunction *>(&*query.table))
|
||||||
@ -76,7 +109,7 @@ void InterpreterSelectQuery::init(BlockInputStreamPtr input_, const NamesAndType
|
|||||||
if (context.getColumns().empty())
|
if (context.getColumns().empty())
|
||||||
throw Exception("There are no available columns", ErrorCodes::THERE_IS_NO_COLUMN);
|
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 новые временные таблицы
|
/// Сохраняем в query context новые временные таблицы
|
||||||
for (auto & it : query_analyzer->getExternalTables())
|
for (auto & it : query_analyzer->getExternalTables())
|
||||||
@ -88,9 +121,9 @@ void InterpreterSelectQuery::init(BlockInputStreamPtr input_, const NamesAndType
|
|||||||
|
|
||||||
if (isFirstSelectInsideUnionAll())
|
if (isFirstSelectInsideUnionAll())
|
||||||
{
|
{
|
||||||
// Создаем цепочку запросов SELECT и проверяем, что результаты всех запросов SELECT cовместимые.
|
/// Создаем цепочку запросов SELECT и проверяем, что результаты всех запросов SELECT cовместимые.
|
||||||
// NOTE Мы можем безопасно применить static_cast вместо typeid_cast,
|
/// NOTE Мы можем безопасно применить static_cast вместо typeid_cast,
|
||||||
// потому что знаем, что в цепочке UNION ALL имеются только деревья типа SELECT.
|
/// потому что знаем, что в цепочке UNION ALL имеются только деревья типа SELECT.
|
||||||
InterpreterSelectQuery * interpreter = this;
|
InterpreterSelectQuery * interpreter = this;
|
||||||
Block first = interpreter->getSampleBlock();
|
Block first = interpreter->getSampleBlock();
|
||||||
for (ASTPtr tree = query.next_union_all; !tree.isNull(); tree = (static_cast<ASTSelectQuery &>(*tree)).next_union_all)
|
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();
|
interpreter = interpreter->next_select_in_union_all.get();
|
||||||
Block current = interpreter->getSampleBlock();
|
Block current = interpreter->getSampleBlock();
|
||||||
if (!blocksHaveEqualStructure(first, current))
|
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);
|
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_,
|
InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context & context_, QueryProcessingStage::Enum to_stage_,
|
||||||
size_t subquery_depth_, BlockInputStreamPtr input_, bool is_union_all_head_)
|
size_t subquery_depth_, BlockInputStreamPtr input_, bool is_union_all_head_)
|
||||||
: query_ptr(query_ptr_), query(typeid_cast<ASTSelectQuery &>(*query_ptr)),
|
: 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),
|
is_union_all_head(true),
|
||||||
log(&Logger::get("InterpreterSelectQuery"))
|
log(&Logger::get("InterpreterSelectQuery"))
|
||||||
{
|
{
|
||||||
/** Оставляем в запросе в секции SELECT только нужные столбцы.
|
init(input_, required_column_names_);
|
||||||
* Но если используется DISTINCT, то все столбцы считаются нужными, так как иначе DISTINCT работал бы по-другому.
|
|
||||||
*/
|
|
||||||
if (!query.distinct)
|
|
||||||
query.rewriteSelectExpressionList(required_column_names_);
|
|
||||||
|
|
||||||
init(input_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context & context_,
|
InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context & context_,
|
||||||
@ -140,13 +175,56 @@ InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context
|
|||||||
is_union_all_head(true),
|
is_union_all_head(true),
|
||||||
log(&Logger::get("InterpreterSelectQuery"))
|
log(&Logger::get("InterpreterSelectQuery"))
|
||||||
{
|
{
|
||||||
/** Оставляем в запросе в секции SELECT только нужные столбцы.
|
init(input_, required_column_names_, table_column_names);
|
||||||
* Но если используется DISTINCT, то все столбцы считаются нужными, так как иначе DISTINCT работал бы по-другому.
|
}
|
||||||
*/
|
|
||||||
if (!query.distinct)
|
|
||||||
query.rewriteSelectExpressionList(required_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
|
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()
|
BlockInputStreamPtr InterpreterSelectQuery::execute()
|
||||||
{
|
{
|
||||||
if (isFirstSelectInsideUnionAll())
|
(void) executeWithoutUnion();
|
||||||
{
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streams.empty())
|
if (streams.empty())
|
||||||
return new NullBlockInputStream;
|
return new NullBlockInputStream;
|
||||||
|
|
||||||
for (auto & stream : streams)
|
|
||||||
stream = new MaterializingBlockInputStream(stream);
|
|
||||||
|
|
||||||
executeUnion(streams);
|
executeUnion(streams);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
executeSingleQuery();
|
|
||||||
if (streams.empty())
|
|
||||||
return new NullBlockInputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ограничения на результат, квота на результат, а также колбек для прогресса.
|
/// Ограничения на результат, квота на результат, а также колбек для прогресса.
|
||||||
if (IProfilingBlockInputStream * stream = dynamic_cast<IProfilingBlockInputStream *>(&*streams[0]))
|
if (IProfilingBlockInputStream * stream = dynamic_cast<IProfilingBlockInputStream *>(&*streams[0]))
|
||||||
@ -264,8 +315,28 @@ BlockInputStreamPtr InterpreterSelectQuery::execute()
|
|||||||
return streams[0];
|
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 параллельно, затем
|
* Если нет 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. */
|
/** Вынем данные из Storage. from_stage - до какой стадии запрос был выполнен в Storage. */
|
||||||
QueryProcessingStage::Enum from_stage = executeFetchColumns(streams);
|
QueryProcessingStage::Enum from_stage = executeFetchColumns(streams);
|
||||||
@ -450,9 +521,9 @@ void InterpreterSelectQuery::executeSingleQuery(bool should_perform_union_hint)
|
|||||||
|
|
||||||
/// На этой стадии можно считать минимумы и максимумы, если надо.
|
/// На этой стадии можно считать минимумы и максимумы, если надо.
|
||||||
if (settings.extremes)
|
if (settings.extremes)
|
||||||
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->enableExtremes();
|
p_stream->enableExtremes();
|
||||||
|
|
||||||
/** Оптимизация - если источников несколько и есть LIMIT, то сначала применим предварительный LIMIT,
|
/** Оптимизация - если источников несколько и есть LIMIT, то сначала применим предварительный LIMIT,
|
||||||
* ограничивающий число записей в каждом до offset + limit.
|
* ограничивающий число записей в каждом до offset + limit.
|
||||||
@ -481,9 +552,6 @@ void InterpreterSelectQuery::executeSingleQuery(bool should_perform_union_hint)
|
|||||||
if (streams.empty())
|
if (streams.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (do_execute_union)
|
|
||||||
executeUnion(streams);
|
|
||||||
|
|
||||||
SubqueriesForSets subqueries_for_sets = query_analyzer->getSubqueriesForSets();
|
SubqueriesForSets subqueries_for_sets = query_analyzer->getSubqueriesForSets();
|
||||||
if (!subqueries_for_sets.empty())
|
if (!subqueries_for_sets.empty())
|
||||||
executeSubqueriesInSetsAndJoins(streams, subqueries_for_sets);
|
executeSubqueriesInSetsAndJoins(streams, subqueries_for_sets);
|
||||||
@ -580,7 +648,6 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
|
|||||||
{
|
{
|
||||||
settings.max_block_size = limit_length + limit_offset;
|
settings.max_block_size = limit_length + limit_offset;
|
||||||
settings.max_threads = 1;
|
settings.max_threads = 1;
|
||||||
settings.asynchronous = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns;
|
QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns;
|
||||||
@ -605,7 +672,7 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
streams.push_back(maybeAsynchronous(interpreter_subquery->execute(), settings.asynchronous));
|
streams.push_back(interpreter_subquery->execute());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Если истчоников слишком много, то склеим их в max_threads источников.
|
/** Если истчоников слишком много, то склеим их в max_threads источников.
|
||||||
@ -632,12 +699,12 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
|
|||||||
|
|
||||||
QuotaForIntervals & quota = context.getQuota();
|
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);
|
p_stream->setLimits(limits);
|
||||||
stream->setQuota(quota);
|
p_stream->setQuota(quota);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -648,23 +715,19 @@ QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInpu
|
|||||||
|
|
||||||
void InterpreterSelectQuery::executeWhere(BlockInputStreams & streams, ExpressionActionsPtr expression)
|
void InterpreterSelectQuery::executeWhere(BlockInputStreams & streams, ExpressionActionsPtr expression)
|
||||||
{
|
{
|
||||||
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
|
for (auto & stream : streams)
|
||||||
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
|
|
||||||
{
|
{
|
||||||
BlockInputStreamPtr & stream = *it;
|
stream = new ExpressionBlockInputStream(stream, expression);
|
||||||
stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async);
|
stream = new FilterBlockInputStream(stream, query.where_expression->getColumnName());
|
||||||
stream = maybeAsynchronous(new FilterBlockInputStream(stream, query.where_expression->getColumnName()), is_async);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void InterpreterSelectQuery::executeAggregation(BlockInputStreams & streams, ExpressionActionsPtr expression, bool overflow_row, bool final)
|
void InterpreterSelectQuery::executeAggregation(BlockInputStreams & streams, ExpressionActionsPtr expression, bool overflow_row, bool final)
|
||||||
{
|
{
|
||||||
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
|
for (auto & stream : streams)
|
||||||
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
|
|
||||||
{
|
{
|
||||||
BlockInputStreamPtr & stream = *it;
|
stream = new ExpressionBlockInputStream(stream, expression);
|
||||||
stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockInputStreamPtr & stream = streams[0];
|
BlockInputStreamPtr & stream = streams[0];
|
||||||
@ -673,36 +736,19 @@ void InterpreterSelectQuery::executeAggregation(BlockInputStreams & streams, Exp
|
|||||||
AggregateDescriptions aggregates;
|
AggregateDescriptions aggregates;
|
||||||
query_analyzer->getAggregateInfo(key_names, aggregates);
|
query_analyzer->getAggregateInfo(key_names, aggregates);
|
||||||
|
|
||||||
bool separate_totals = to_stage > QueryProcessingStage::WithMergeableState;
|
|
||||||
|
|
||||||
/// Если источников несколько, то выполняем параллельную агрегацию
|
/// Если источников несколько, то выполняем параллельную агрегацию
|
||||||
if (streams.size() > 1)
|
if (streams.size() > 1)
|
||||||
{
|
{
|
||||||
if (!settings.use_splitting_aggregator || key_names.empty())
|
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,
|
||||||
stream = maybeAsynchronous(
|
settings.compile ? &context.getCompiler() : nullptr, settings.min_count_to_compile);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
streams.resize(1);
|
streams.resize(1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
stream = maybeAsynchronous(new AggregatingBlockInputStream(stream, key_names, aggregates, overflow_row, final,
|
stream = new AggregatingBlockInputStream(stream, key_names, aggregates, overflow_row, final,
|
||||||
settings.limits.max_rows_to_group_by, settings.limits.group_by_overflow_mode), settings.asynchronous);
|
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;
|
Names key_names;
|
||||||
AggregateDescriptions aggregates;
|
AggregateDescriptions aggregates;
|
||||||
query_analyzer->getAggregateInfo(key_names, 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)
|
void InterpreterSelectQuery::executeHaving(BlockInputStreams & streams, ExpressionActionsPtr expression)
|
||||||
{
|
{
|
||||||
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
|
for (auto & stream : streams)
|
||||||
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
|
|
||||||
{
|
{
|
||||||
BlockInputStreamPtr & stream = *it;
|
stream = new ExpressionBlockInputStream(stream, expression);
|
||||||
stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async);
|
stream = new FilterBlockInputStream(stream, query.having_expression->getColumnName());
|
||||||
stream = maybeAsynchronous(new FilterBlockInputStream(stream, query.having_expression->getColumnName()), is_async);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -748,20 +792,17 @@ void InterpreterSelectQuery::executeTotalsAndHaving(BlockInputStreams & streams,
|
|||||||
Names key_names;
|
Names key_names;
|
||||||
AggregateDescriptions aggregates;
|
AggregateDescriptions aggregates;
|
||||||
query_analyzer->getAggregateInfo(key_names, aggregates);
|
query_analyzer->getAggregateInfo(key_names, aggregates);
|
||||||
streams[0] = maybeAsynchronous(new TotalsHavingBlockInputStream(
|
streams[0] = new TotalsHavingBlockInputStream(
|
||||||
streams[0], key_names, aggregates, overflow_row, expression,
|
streams[0], key_names, aggregates, overflow_row, expression,
|
||||||
has_having ? query.having_expression->getColumnName() : "", settings.totals_mode, settings.totals_auto_threshold),
|
has_having ? query.having_expression->getColumnName() : "", settings.totals_mode, settings.totals_auto_threshold);
|
||||||
settings.asynchronous);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void InterpreterSelectQuery::executeExpression(BlockInputStreams & streams, ExpressionActionsPtr expression)
|
void InterpreterSelectQuery::executeExpression(BlockInputStreams & streams, ExpressionActionsPtr expression)
|
||||||
{
|
{
|
||||||
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
|
for (auto & stream : streams)
|
||||||
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
|
|
||||||
{
|
{
|
||||||
BlockInputStreamPtr & stream = *it;
|
stream = new ExpressionBlockInputStream(stream, expression);
|
||||||
stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -775,7 +816,9 @@ void InterpreterSelectQuery::executeOrder(BlockInputStreams & streams)
|
|||||||
++it)
|
++it)
|
||||||
{
|
{
|
||||||
String name = (*it)->children.front()->getColumnName();
|
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 - можно делать частичную сортировку.
|
/// Если есть LIMIT и нет DISTINCT - можно делать частичную сортировку.
|
||||||
@ -788,10 +831,8 @@ void InterpreterSelectQuery::executeOrder(BlockInputStreams & streams)
|
|||||||
limit = limit_length + limit_offset;
|
limit = limit_length + limit_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
|
for (auto & stream : streams)
|
||||||
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
|
|
||||||
{
|
{
|
||||||
BlockInputStreamPtr & stream = *it;
|
|
||||||
IProfilingBlockInputStream * sorting_stream = new PartialSortingBlockInputStream(stream, order_descr, limit);
|
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;
|
limits.read_overflow_mode = settings.limits.sort_overflow_mode;
|
||||||
sorting_stream->setLimits(limits);
|
sorting_stream->setLimits(limits);
|
||||||
|
|
||||||
stream = maybeAsynchronous(sorting_stream, is_async);
|
stream = sorting_stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockInputStreamPtr & stream = streams[0];
|
BlockInputStreamPtr & stream = streams[0];
|
||||||
@ -814,18 +855,18 @@ void InterpreterSelectQuery::executeOrder(BlockInputStreams & streams)
|
|||||||
streams.resize(1);
|
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)
|
void InterpreterSelectQuery::executeProjection(BlockInputStreams & streams, ExpressionActionsPtr expression)
|
||||||
{
|
{
|
||||||
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
|
for (auto & stream : streams)
|
||||||
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
|
|
||||||
{
|
{
|
||||||
BlockInputStreamPtr & stream = *it;
|
stream = new ExpressionBlockInputStream(stream, expression);
|
||||||
stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -844,12 +885,9 @@ void InterpreterSelectQuery::executeDistinct(BlockInputStreams & streams, bool b
|
|||||||
if (!query.order_expression_list || !before_order)
|
if (!query.order_expression_list || !before_order)
|
||||||
limit_for_distinct = limit_length + limit_offset;
|
limit_for_distinct = limit_length + limit_offset;
|
||||||
|
|
||||||
bool is_async = settings.asynchronous && streams.size() <= settings.max_threads;
|
for (auto & stream : streams)
|
||||||
for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it)
|
|
||||||
{
|
{
|
||||||
BlockInputStreamPtr & stream = *it;
|
stream = new DistinctBlockInputStream(stream, settings.limits, limit_for_distinct, columns);
|
||||||
stream = maybeAsynchronous(new DistinctBlockInputStream(
|
|
||||||
stream, settings.limits, limit_for_distinct, columns), is_async);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -876,9 +914,8 @@ void InterpreterSelectQuery::executePreLimit(BlockInputStreams & streams)
|
|||||||
/// Если есть LIMIT
|
/// Если есть LIMIT
|
||||||
if (query.limit_length)
|
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);
|
stream = new LimitBlockInputStream(stream, limit_length + limit_offset, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -9,7 +9,13 @@ typedef std::vector<std::pair<const IColumn *, SortColumnDescription> > ColumnsW
|
|||||||
|
|
||||||
static inline bool needCollation(const IColumn * column, const SortColumnDescription & description)
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ int main(int argc, char ** argv)
|
|||||||
DB::DataTypes empty_list_of_types;
|
DB::DataTypes empty_list_of_types;
|
||||||
aggregate_descriptions[0].function = factory.get("count", 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;
|
Poco::Stopwatch stopwatch;
|
||||||
|
30
dbms/src/Interpreters/tests/compiler_test.cpp
Normal file
30
dbms/src/Interpreters/tests/compiler_test.cpp
Normal 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;
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
|
#include <sys/file.h>
|
||||||
|
|
||||||
#include <Poco/Net/HTTPServerRequest.h>
|
#include <Poco/Net/HTTPServerRequest.h>
|
||||||
#include <Poco/Util/XMLConfiguration.h>
|
#include <Poco/Util/XMLConfiguration.h>
|
||||||
|
|
||||||
#include <Yandex/ApplicationServerExt.h>
|
#include <Yandex/ApplicationServerExt.h>
|
||||||
#include <Yandex/ErrorHandlers.h>
|
#include <Yandex/ErrorHandlers.h>
|
||||||
|
#include <Yandex/Revision.h>
|
||||||
#include <statdaemons/ConfigProcessor.h>
|
#include <statdaemons/ConfigProcessor.h>
|
||||||
#include <statdaemons/ext/memory.hpp>
|
#include <statdaemons/ext/memory.hpp>
|
||||||
|
|
||||||
@ -21,6 +23,11 @@
|
|||||||
#include <DB/Storages/StorageSystemZooKeeper.h>
|
#include <DB/Storages/StorageSystemZooKeeper.h>
|
||||||
#include <DB/Storages/StorageSystemReplicas.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 "Server.h"
|
||||||
#include "HTTPHandler.h"
|
#include "HTTPHandler.h"
|
||||||
#include "InterserverIOHTTPHandler.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)
|
int Server::main(const std::vector<std::string> & args)
|
||||||
{
|
{
|
||||||
Logger * log = &logger();
|
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;
|
rlimit rlim;
|
||||||
@ -357,7 +455,13 @@ int Server::main(const std::vector<std::string> & args)
|
|||||||
* настройки, набор функций, типов данных, агрегатных функций, баз данных...
|
* настройки, набор функций, типов данных, агрегатных функций, баз данных...
|
||||||
*/
|
*/
|
||||||
global_context->setGlobalContext(*global_context);
|
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;
|
bool has_zookeeper = false;
|
||||||
if (config().has("zookeeper"))
|
if (config().has("zookeeper"))
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
#include <DB/IO/copyData.h>
|
#include <DB/IO/copyData.h>
|
||||||
|
|
||||||
#include <DB/DataStreams/AsynchronousBlockInputStream.h>
|
#include <DB/DataStreams/AsynchronousBlockInputStream.h>
|
||||||
|
#include <DB/DataStreams/NativeBlockInputStream.h>
|
||||||
|
#include <DB/DataStreams/NativeBlockOutputStream.h>
|
||||||
#include <DB/Interpreters/executeQuery.h>
|
#include <DB/Interpreters/executeQuery.h>
|
||||||
|
|
||||||
#include <DB/Storages/StorageMemory.h>
|
#include <DB/Storages/StorageMemory.h>
|
||||||
@ -597,12 +599,10 @@ void TCPHandler::initBlockInput()
|
|||||||
else
|
else
|
||||||
state.maybe_compressed_in = in;
|
state.maybe_compressed_in = in;
|
||||||
|
|
||||||
state.block_in = query_context.getFormatFactory().getInput(
|
state.block_in = new NativeBlockInputStream(
|
||||||
"Native",
|
|
||||||
*state.maybe_compressed_in,
|
*state.maybe_compressed_in,
|
||||||
state.io.out_sample,
|
query_context.getDataTypeFactory(),
|
||||||
query_context.getSettingsRef().max_insert_block_size, /// Реально не используется в формате Native.
|
client_revision);
|
||||||
query_context.getDataTypeFactory());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -616,10 +616,9 @@ void TCPHandler::initBlockOutput()
|
|||||||
else
|
else
|
||||||
state.maybe_compressed_out = out;
|
state.maybe_compressed_out = out;
|
||||||
|
|
||||||
state.block_out = query_context.getFormatFactory().getOutput(
|
state.block_out = new NativeBlockOutputStream(
|
||||||
"Native",
|
|
||||||
*state.maybe_compressed_out,
|
*state.maybe_compressed_out,
|
||||||
state.io.in_sample);
|
client_revision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,14 +179,10 @@ namespace DB
|
|||||||
|
|
||||||
void AlterCommands::validate(IStorage * table, const Context & context)
|
void AlterCommands::validate(IStorage * table, const Context & context)
|
||||||
{
|
{
|
||||||
auto lock = table->lockDataForAlter();
|
|
||||||
|
|
||||||
auto columns = table->getColumnsList();
|
auto columns = table->getColumnsList();
|
||||||
columns.insert(std::end(columns), std::begin(table->alias_columns), std::end(table->alias_columns));
|
columns.insert(std::end(columns), std::begin(table->alias_columns), std::end(table->alias_columns));
|
||||||
auto defaults = table->column_defaults;
|
auto defaults = table->column_defaults;
|
||||||
|
|
||||||
lock.reset();
|
|
||||||
|
|
||||||
std::vector<std::pair<String, AlterCommand *>> defaulted_columns{};
|
std::vector<std::pair<String, AlterCommand *>> defaulted_columns{};
|
||||||
|
|
||||||
ASTPtr default_expr_list{new ASTExpressionList};
|
ASTPtr default_expr_list{new ASTExpressionList};
|
||||||
|
@ -237,7 +237,7 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks)
|
|||||||
for (const auto & part : broken_parts_to_remove)
|
for (const auto & part : broken_parts_to_remove)
|
||||||
part->remove();
|
part->remove();
|
||||||
for (const auto & part : broken_parts_to_detach)
|
for (const auto & part : broken_parts_to_detach)
|
||||||
part->renameAddPrefix("detached/");
|
part->renameAddPrefix(true, "");
|
||||||
|
|
||||||
all_data_parts = data_parts;
|
all_data_parts = data_parts;
|
||||||
|
|
||||||
@ -789,7 +789,7 @@ void MergeTreeData::renameAndDetachPart(const DataPartPtr & part, const String &
|
|||||||
removePartContributionToColumnSizes(part);
|
removePartContributionToColumnSizes(part);
|
||||||
data_parts.erase(part);
|
data_parts.erase(part);
|
||||||
if (move_to_detached || !prefix.empty())
|
if (move_to_detached || !prefix.empty())
|
||||||
part->renameAddPrefix((move_to_detached ? "detached/" : "") + prefix);
|
part->renameAddPrefix(move_to_detached, prefix);
|
||||||
|
|
||||||
if (restore_covered)
|
if (restore_covered)
|
||||||
{
|
{
|
||||||
|
@ -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 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)
|
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();
|
MergeTreeData::DataParts data_parts = data.getDataParts();
|
||||||
|
|
||||||
DateLUT & date_lut = DateLUT::instance();
|
DateLUT & date_lut = DateLUT::instance();
|
||||||
@ -92,9 +75,6 @@ bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & pa
|
|||||||
if (only_small)
|
if (only_small)
|
||||||
cur_max_bytes_to_merge_parts = data.settings.max_bytes_to_merge_parts_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.
|
/// Мемоизация для функции can_merge_callback. Результат вызова can_merge_callback для этого куска и предыдущего в data_parts.
|
||||||
std::map<MergeTreeData::DataPartPtr, bool> can_merge_with_previous;
|
std::map<MergeTreeData::DataPartPtr, bool> can_merge_with_previous;
|
||||||
auto can_merge = [&can_merge_with_previous, &can_merge_callback]
|
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
|
LOG_DEBUG(log, "Selected " << parts.size() << " parts from " << parts.front()->name << " to " << parts.back()->name
|
||||||
<< (only_small ? " (only small)" : ""));
|
<< (only_small ? " (only small)" : ""));
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_TRACE(log, "No parts selected for merge.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <DB/DataStreams/FilterBlockInputStream.h>
|
#include <DB/DataStreams/FilterBlockInputStream.h>
|
||||||
#include <DB/DataStreams/ConcatBlockInputStream.h>
|
#include <DB/DataStreams/ConcatBlockInputStream.h>
|
||||||
#include <DB/DataStreams/CollapsingFinalBlockInputStream.h>
|
#include <DB/DataStreams/CollapsingFinalBlockInputStream.h>
|
||||||
|
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
|
||||||
#include <DB/DataTypes/DataTypesNumberFixed.h>
|
#include <DB/DataTypes/DataTypesNumberFixed.h>
|
||||||
#include <DB/Common/VirtualColumnUtils.h>
|
#include <DB/Common/VirtualColumnUtils.h>
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ static String generateActiveNodeIdentifier()
|
|||||||
struct timespec times;
|
struct timespec times;
|
||||||
if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, ×))
|
if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, ×))
|
||||||
throwFromErrno("Cannot clock_gettime.", ErrorCodes::CANNOT_CLOCK_GETTIME);
|
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();
|
partialShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
do
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -62,6 +62,7 @@ void ReplicatedMergeTreeRestartingThread::run()
|
|||||||
{
|
{
|
||||||
/// Исключение при попытке zookeeper_init обычно бывает, если не работает DNS. Будем пытаться сделать это заново.
|
/// Исключение при попытке zookeeper_init обычно бывает, если не работает DNS. Будем пытаться сделать это заново.
|
||||||
tryLogCurrentException(__PRETTY_FUNCTION__);
|
tryLogCurrentException(__PRETTY_FUNCTION__);
|
||||||
|
|
||||||
wakeup_event.tryWait(retry_delay_ms);
|
wakeup_event.tryWait(retry_delay_ms);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -71,7 +72,9 @@ void ReplicatedMergeTreeRestartingThread::run()
|
|||||||
wakeup_event.tryWait(retry_delay_ms);
|
wakeup_event.tryWait(retry_delay_ms);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} while (false);
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
storage.is_readonly = false;
|
storage.is_readonly = false;
|
||||||
first_time = false;
|
first_time = false;
|
||||||
@ -133,14 +136,21 @@ bool ReplicatedMergeTreeRestartingThread::tryStartup()
|
|||||||
storage.queue_task_handle = storage.context.getBackgroundPool().addTask(
|
storage.queue_task_handle = storage.context.getBackgroundPool().addTask(
|
||||||
std::bind(&StorageReplicatedMergeTree::queueTask, &storage, std::placeholders::_1));
|
std::bind(&StorageReplicatedMergeTree::queueTask, &storage, std::placeholders::_1));
|
||||||
storage.queue_task_handle->wake();
|
storage.queue_task_handle->wake();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (const zkutil::KeeperException & e)
|
catch (...)
|
||||||
{
|
{
|
||||||
storage.replica_is_active_node = nullptr;
|
storage.replica_is_active_node = nullptr;
|
||||||
storage.leader_election = 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;
|
return false;
|
||||||
}
|
}
|
||||||
catch (const Exception & e)
|
catch (const Exception & e)
|
||||||
@ -148,17 +158,9 @@ bool ReplicatedMergeTreeRestartingThread::tryStartup()
|
|||||||
if (e.code() != ErrorCodes::REPLICA_IS_ALREADY_ACTIVE)
|
if (e.code() != ErrorCodes::REPLICA_IS_ALREADY_ACTIVE)
|
||||||
throw;
|
throw;
|
||||||
|
|
||||||
storage.replica_is_active_node = nullptr;
|
LOG_ERROR(log, "Couldn't start replication: " << e.what() << ", " << e.displayText() << ", stack trace:\n" << e.getStackTrace().toString());
|
||||||
storage.leader_election = nullptr;
|
|
||||||
LOG_ERROR(log, "Couldn't start replication: " << e.what() << ", " << e.displayText() << ", stack trace:\n"
|
|
||||||
<< e.getStackTrace().toString());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
storage.replica_is_active_node = nullptr;
|
|
||||||
storage.leader_election = nullptr;
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,10 @@
|
|||||||
#include <DB/DataStreams/ConcatBlockInputStream.h>
|
#include <DB/DataStreams/ConcatBlockInputStream.h>
|
||||||
#include <DB/DataStreams/narrowBlockInputStreams.h>
|
#include <DB/DataStreams/narrowBlockInputStreams.h>
|
||||||
#include <DB/DataStreams/AddingDefaultBlockInputStream.h>
|
#include <DB/DataStreams/AddingDefaultBlockInputStream.h>
|
||||||
|
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
|
||||||
#include <DB/Common/VirtualColumnUtils.h>
|
#include <DB/Common/VirtualColumnUtils.h>
|
||||||
|
#include <DB/DataTypes/DataTypeString.h>
|
||||||
|
#include <DB/Columns/ColumnString.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include <DB/Interpreters/InterpreterDropQuery.h>
|
#include <DB/Interpreters/InterpreterDropQuery.h>
|
||||||
#include <DB/Parsers/ASTDropQuery.h>
|
#include <DB/Parsers/ASTDropQuery.h>
|
||||||
#include <DB/Common/VirtualColumnUtils.h>
|
#include <DB/Common/VirtualColumnUtils.h>
|
||||||
|
#include <DB/DataTypes/DataTypeString.h>
|
||||||
|
#include <DB/Columns/ColumnString.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
|
@ -63,8 +63,8 @@ StorageDistributed::StorageDistributed(
|
|||||||
context(context_), cluster(cluster_),
|
context(context_), cluster(cluster_),
|
||||||
sharding_key_expr(sharding_key_ ? ExpressionAnalyzer(sharding_key_, context, *columns).getActions(false) : nullptr),
|
sharding_key_expr(sharding_key_ ? ExpressionAnalyzer(sharding_key_, context, *columns).getActions(false) : nullptr),
|
||||||
sharding_key_column_name(sharding_key_ ? sharding_key_->getColumnName() : String{}),
|
sharding_key_column_name(sharding_key_ ? sharding_key_->getColumnName() : String{}),
|
||||||
write_enabled(cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_),
|
write_enabled(!data_path_.empty() && (cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_)),
|
||||||
path(data_path_ + escapeForFileName(name) + '/')
|
path(data_path_.empty() ? "" : (data_path_ + escapeForFileName(name) + '/'))
|
||||||
{
|
{
|
||||||
createDirectoryMonitors();
|
createDirectoryMonitors();
|
||||||
}
|
}
|
||||||
@ -87,8 +87,8 @@ StorageDistributed::StorageDistributed(
|
|||||||
context(context_), cluster(cluster_),
|
context(context_), cluster(cluster_),
|
||||||
sharding_key_expr(sharding_key_ ? ExpressionAnalyzer(sharding_key_, context, *columns).getActions(false) : nullptr),
|
sharding_key_expr(sharding_key_ ? ExpressionAnalyzer(sharding_key_, context, *columns).getActions(false) : nullptr),
|
||||||
sharding_key_column_name(sharding_key_ ? sharding_key_->getColumnName() : String{}),
|
sharding_key_column_name(sharding_key_ ? sharding_key_->getColumnName() : String{}),
|
||||||
write_enabled(cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_),
|
write_enabled(!data_path_.empty() && (cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_)),
|
||||||
path(data_path_ + escapeForFileName(name) + '/')
|
path(data_path_.empty() ? "" : (data_path_ + escapeForFileName(name) + '/'))
|
||||||
{
|
{
|
||||||
createDirectoryMonitors();
|
createDirectoryMonitors();
|
||||||
}
|
}
|
||||||
@ -164,8 +164,7 @@ BlockInputStreams StorageDistributed::read(
|
|||||||
for (auto & conn_pool : cluster.pools)
|
for (auto & conn_pool : cluster.pools)
|
||||||
res.emplace_back(new RemoteBlockInputStream{
|
res.emplace_back(new RemoteBlockInputStream{
|
||||||
conn_pool, modified_query, &new_settings,
|
conn_pool, modified_query, &new_settings,
|
||||||
external_tables, processed_stage, context
|
external_tables, processed_stage, context});
|
||||||
});
|
|
||||||
|
|
||||||
/// Добавляем запросы к локальному ClickHouse.
|
/// Добавляем запросы к локальному ClickHouse.
|
||||||
if (cluster.getLocalNodesNum() > 0)
|
if (cluster.getLocalNodesNum() > 0)
|
||||||
@ -240,6 +239,9 @@ void StorageDistributed::createDirectoryMonitor(const std::string & name)
|
|||||||
|
|
||||||
void StorageDistributed::createDirectoryMonitors()
|
void StorageDistributed::createDirectoryMonitors()
|
||||||
{
|
{
|
||||||
|
if (path.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
Poco::File{path}.createDirectory();
|
Poco::File{path}.createDirectory();
|
||||||
|
|
||||||
Poco::DirectoryIterator end;
|
Poco::DirectoryIterator end;
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
#include <DB/DataStreams/narrowBlockInputStreams.h>
|
#include <DB/DataStreams/narrowBlockInputStreams.h>
|
||||||
|
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
|
||||||
#include <DB/Storages/StorageMerge.h>
|
#include <DB/Storages/StorageMerge.h>
|
||||||
#include <DB/Common/VirtualColumnUtils.h>
|
#include <DB/Common/VirtualColumnUtils.h>
|
||||||
#include <DB/Interpreters/InterpreterAlterQuery.h>
|
#include <DB/Interpreters/InterpreterAlterQuery.h>
|
||||||
#include <DB/Storages/VirtualColumnFactory.h>
|
#include <DB/Storages/VirtualColumnFactory.h>
|
||||||
|
#include <DB/Parsers/ASTSelectQuery.h>
|
||||||
|
#include <DB/DataTypes/DataTypeString.h>
|
||||||
|
#include <DB/Columns/ColumnString.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
@ -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) &&
|
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))
|
!merger.selectPartsToMerge(parts, merged_name, disk_space, true, aggressive, only_small, can_merge))
|
||||||
{
|
{
|
||||||
LOG_INFO(log, "No parts to merge");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,10 @@
|
|||||||
#include <DB/IO/ReadBufferFromString.h>
|
#include <DB/IO/ReadBufferFromString.h>
|
||||||
#include <DB/Interpreters/InterpreterAlterQuery.h>
|
#include <DB/Interpreters/InterpreterAlterQuery.h>
|
||||||
#include <DB/Common/VirtualColumnUtils.h>
|
#include <DB/Common/VirtualColumnUtils.h>
|
||||||
|
#include <DB/DataStreams/AddingConstColumnBlockInputStream.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -1353,6 +1355,7 @@ void StorageReplicatedMergeTree::mergeSelectingThread()
|
|||||||
|
|
||||||
bool only_small = big_merges_current + big_merges_queued >= max_number_of_big_merges;
|
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
|
LOG_TRACE(log, "Currently executing big merges: " << big_merges_current
|
||||||
<< ". Queued big merges: " << big_merges_queued
|
<< ". Queued big merges: " << big_merges_queued
|
||||||
<< ". All merges in queue: " << 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)
|
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))
|
&& !merger.selectPartsToMerge(parts, merged_name, MergeTreeDataMerger::NO_LIMIT, true, false, only_small, can_merge))
|
||||||
{
|
{
|
||||||
LOG_INFO(log, "No parts to merge");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,8 +92,7 @@ BlockInputStreams StorageView::read(
|
|||||||
if (outer_select.final && !inner_select.final)
|
if (outer_select.final && !inner_select.final)
|
||||||
inner_select.final = outer_select.final;
|
inner_select.final = outer_select.final;
|
||||||
|
|
||||||
return BlockInputStreams(1,
|
return InterpreterSelectQuery(inner_query_clone, context, column_names).executeWithoutUnion();
|
||||||
InterpreterSelectQuery(inner_query_clone, context, column_names).execute());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ do
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf "%-60s" "$test_name: "
|
printf "%-64s" "$test_name: "
|
||||||
|
|
||||||
if [ $ZOOKEEPER -eq 0 ] && (echo "$test_name" | grep -q 'zookeeper'); then
|
if [ $ZOOKEEPER -eq 0 ] && (echo "$test_name" | grep -q 'zookeeper'); then
|
||||||
echo -e "$MSG_SKIPPED - no zookeeper"
|
echo -e "$MSG_SKIPPED - no zookeeper"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
echo 'DROP TABLE IF EXISTS test.long_insert' | curl -sSg 'http://localhost:8123' -d @-
|
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 @-
|
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
|
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 @-
|
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
|
done
|
||||||
|
@ -32,6 +32,11 @@ cp ${CONFIG/config/users} .
|
|||||||
# Запустим второй сервер.
|
# Запустим второй сервер.
|
||||||
BINARY=$(readlink /proc/$(pidof clickhouse-server | tr ' ' '\n' | head -n1)/exe || echo "/usr/bin/clickhouse-server")
|
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
|
if [ ! -x "$BINARY" ]; then
|
||||||
echo "Cannot find executable binary for running clickhouse-server" >&2
|
echo "Cannot find executable binary for running clickhouse-server" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@ -99,3 +104,5 @@ $CLIENT1 -n --query="
|
|||||||
$CLIENT2 -n --query="
|
$CLIENT2 -n --query="
|
||||||
DROP TABLE test.half1;
|
DROP TABLE test.half1;
|
||||||
DROP TABLE test.half2;"
|
DROP TABLE test.half2;"
|
||||||
|
|
||||||
|
rm -rf $PATH2
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user