diff --git a/copy_headers.sh b/copy_headers.sh new file mode 100755 index 00000000000..29ee37e3606 --- /dev/null +++ b/copy_headers.sh @@ -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 diff --git a/dbms/include/DB/Columns/ColumnAggregateFunction.h b/dbms/include/DB/Columns/ColumnAggregateFunction.h index 16a6649c651..0fc394419f9 100644 --- a/dbms/include/DB/Columns/ColumnAggregateFunction.h +++ b/dbms/include/DB/Columns/ColumnAggregateFunction.h @@ -21,7 +21,7 @@ namespace DB * Может быть в двух вариантах: * * 1. Владеть своими значениями - то есть, отвечать за их уничтожение. - * Столбец состоит из значений, "отданных ему на попечение" после выполнения агрегации (см. Aggregator, функция convertToBlock), + * Столбец состоит из значений, "отданных ему на попечение" после выполнения агрегации (см. Aggregator, функция convertToBlocks), * или из значений, созданных им самим (см. метод insert). * В этом случае, src будет равно nullptr, и столбец будет сам уничтожать (вызывать IAggregateFunction::destroy) * состояния агрегатных функций в деструкторе. diff --git a/dbms/include/DB/Common/HashTable/Hash.h b/dbms/include/DB/Common/HashTable/Hash.h index 4c4b9e52abd..6948c1f8685 100644 --- a/dbms/include/DB/Common/HashTable/Hash.h +++ b/dbms/include/DB/Common/HashTable/Hash.h @@ -21,6 +21,21 @@ inline DB::UInt64 intHash64(DB::UInt64 x) return x; } +/** CRC32C является не очень качественной в роли хэш функции, + * согласно avalanche и bit independence тестам, а также малым количеством бит, + * но может вести себя хорошо при использовании в хэш-таблицах, + * за счёт высокой скорости (latency 3 + 1 такт, througput 1 такт). + * Работает только при поддержке SSE 4.2. + * Используется asm вместо интринсика, чтобы не обязательно было собирать весь проект с -msse4. + */ +inline DB::UInt64 intHashCRC32(DB::UInt64 x) +{ + DB::UInt64 crc = -1ULL; + asm("crc32q %[x], %[crc]\n" : [crc] "+r" (crc) : [x] "rm" (x)); + return crc; +} + + template struct DefaultHash; template @@ -36,7 +51,7 @@ inline size_t DefaultHash64(T key) return intHash64(u.out); } -#define DEFAULT_HASH_64(T) \ +#define DEFINE_HASH(T) \ template <> struct DefaultHash\ {\ size_t operator() (T key) const\ @@ -45,15 +60,53 @@ template <> struct DefaultHash\ }\ }; -DEFAULT_HASH_64(DB::UInt8) -DEFAULT_HASH_64(DB::UInt16) -DEFAULT_HASH_64(DB::UInt32) -DEFAULT_HASH_64(DB::UInt64) -DEFAULT_HASH_64(DB::Int8) -DEFAULT_HASH_64(DB::Int16) -DEFAULT_HASH_64(DB::Int32) -DEFAULT_HASH_64(DB::Int64) -DEFAULT_HASH_64(DB::Float32) -DEFAULT_HASH_64(DB::Float64) +DEFINE_HASH(DB::UInt8) +DEFINE_HASH(DB::UInt16) +DEFINE_HASH(DB::UInt32) +DEFINE_HASH(DB::UInt64) +DEFINE_HASH(DB::Int8) +DEFINE_HASH(DB::Int16) +DEFINE_HASH(DB::Int32) +DEFINE_HASH(DB::Int64) +DEFINE_HASH(DB::Float32) +DEFINE_HASH(DB::Float64) -#undef DEFAULT_HASH_64 +#undef DEFINE_HASH + + +template struct HashCRC32; + +template +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\ +{\ + size_t operator() (T key) const\ + {\ + return hashCRC32(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 diff --git a/dbms/include/DB/Common/HashTable/HashMap.h b/dbms/include/DB/Common/HashTable/HashMap.h index 82664e21a59..f9257d68557 100644 --- a/dbms/include/DB/Common/HashTable/HashMap.h +++ b/dbms/include/DB/Common/HashTable/HashMap.h @@ -129,7 +129,9 @@ public: typedef typename Cell::Mapped mapped_type; typedef typename Cell::value_type value_type; - mapped_type & operator[](Key x) + using HashTable::HashTable; + + mapped_type & ALWAYS_INLINE operator[](Key x) { typename HashMapTable::iterator it; bool inserted; diff --git a/dbms/include/DB/Common/HashTable/HashTable.h b/dbms/include/DB/Common/HashTable/HashTable.h index 16f07e526a2..6863012440c 100644 --- a/dbms/include/DB/Common/HashTable/HashTable.h +++ b/dbms/include/DB/Common/HashTable/HashTable.h @@ -13,6 +13,7 @@ #include +#include #include #include #include @@ -135,7 +136,7 @@ struct HashTableCell /** Определяет размер хэш-таблицы, а также когда и во сколько раз её надо ресайзить. */ -template +template struct HashTableGrower { /// Состояние этой структуры достаточно, чтобы получить размер буфера хэш-таблицы. @@ -231,6 +232,9 @@ protected: friend class const_iterator; friend class iterator; + template + friend class TwoLevelHashTable; + typedef size_t HashValue; typedef HashTable Self; typedef Cell cell_type; @@ -240,11 +244,11 @@ protected: Grower grower; #ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - mutable size_t collisions; + mutable size_t collisions = 0; #endif /// Найти ячейку с тем же ключём или пустую ячейку, начиная с заданного места и далее по цепочке разрешения коллизий. - size_t findCell(const Key & x, size_t hash_value, size_t place_value) const + size_t ALWAYS_INLINE findCell(const Key & x, size_t hash_value, size_t place_value) const { while (!buf[place_value].isZero(*this) && !buf[place_value].keyEquals(x, hash_value)) { @@ -257,6 +261,20 @@ protected: return place_value; } + /// Найти пустую ячейку, начиная с заданного места и далее по цепочке разрешения коллизий. + size_t ALWAYS_INLINE findEmptyCell(const Key & x, size_t hash_value, size_t place_value) const + { + while (!buf[place_value].isZero(*this)) + { + place_value = grower.next(place_value); +#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS + ++collisions; +#endif + } + + return place_value; + } + void alloc(const Grower & new_grower) { buf = reinterpret_cast(Allocator::alloc(new_grower.bufSize() * sizeof(Cell))); @@ -372,10 +390,14 @@ public: if (Cell::need_zero_value_storage) this->zeroValue()->setZero(); alloc(grower); + } -#ifdef DBMS_HASH_MAP_COUNT_COLLISIONS - collisions = 0; -#endif + HashTable(size_t reserve_for_num_elements) + { + if (Cell::need_zero_value_storage) + this->zeroValue()->setZero(); + grower.set(reserve_for_num_elements); + alloc(grower); } ~HashTable() @@ -417,6 +439,9 @@ public: value_type & operator* () const { return ptr->getValue(); } value_type * operator->() const { return &ptr->getValue(); } + + Cell * getPtr() const { return ptr; } + size_t getHash() const { return ptr->getHash(*container); } }; @@ -450,6 +475,9 @@ public: const value_type & operator* () const { return ptr->getValue(); } const value_type * operator->() const { return &ptr->getValue(); } + + const Cell * getPtr() const { return ptr; } + size_t getHash() const { return ptr->getHash(*container); } }; @@ -482,12 +510,14 @@ public: protected: - const_iterator iteratorToZero() const { return const_iterator(this, this->zeroValue()); } - iterator iteratorToZero() { return iterator(this, this->zeroValue()); } + const_iterator iteratorTo(const Cell * ptr) const { return const_iterator(this, ptr); } + iterator iteratorTo(Cell * ptr) { return iterator(this, ptr); } + const_iterator iteratorToZero() const { return iteratorTo(this->zeroValue()); } + iterator iteratorToZero() { return iteratorTo(this->zeroValue()); } /// Если ключ нулевой - вставить его в специальное место и вернуть true. - bool emplaceIfZero(Key x, iterator & it, bool & inserted) + bool ALWAYS_INLINE emplaceIfZero(Key x, iterator & it, bool & inserted) { /// Если утверждается, что нулевой ключ не могут вставить в таблицу. if (!Cell::need_zero_value_storage) @@ -514,7 +544,7 @@ protected: /// Только для ненулевых ключей. Найти нужное место, вставить туда ключ, если его ещё нет, вернуть итератор на ячейку. - void emplaceNonZero(Key x, iterator & it, bool & inserted, size_t hash_value) + void ALWAYS_INLINE emplaceNonZero(Key x, iterator & it, bool & inserted, size_t hash_value) { size_t place_value = findCell(x, hash_value, grower.place(hash_value)); @@ -534,14 +564,14 @@ protected: if (unlikely(grower.overflow(m_size))) { resize(); - it = find(x); + it = find(x, hash_value); } } public: /// Вставить значение. В случае хоть сколько-нибудь сложных значений, лучше используйте функцию emplace. - std::pair insert(const value_type & x) + std::pair ALWAYS_INLINE insert(const value_type & x) { std::pair res; @@ -570,7 +600,7 @@ public: * if (inserted) * new(&it->second) Mapped(value); */ - void emplace(Key x, iterator & it, bool & inserted) + void ALWAYS_INLINE emplace(Key x, iterator & it, bool & inserted) { if (!emplaceIfZero(x, it, inserted)) emplaceNonZero(x, it, inserted, hash(x)); @@ -578,33 +608,64 @@ public: /// То же самое, но с заранее вычисленным значением хэш-функции. - void emplace(Key x, iterator & it, bool & inserted, size_t hash_value) + void ALWAYS_INLINE emplace(Key x, iterator & it, bool & inserted, size_t hash_value) { if (!emplaceIfZero(x, it, inserted)) emplaceNonZero(x, it, inserted, hash_value); } - iterator find(Key x) + /// Скопировать ячейку из другой хэш-таблицы. Предполагается, что ячейка не нулевая, а также, что такого ключа в таблице ещё не было. + void ALWAYS_INLINE insertUniqueNonZero(const Cell * cell, size_t hash_value) + { + size_t place_value = findEmptyCell(cell->getKey(cell->getValue()), hash_value, grower.place(hash_value)); + + memcpy(&buf[place_value], cell, sizeof(*cell)); + ++m_size; + + if (unlikely(grower.overflow(m_size))) + resize(); + } + + + iterator ALWAYS_INLINE find(Key x) { if (Cell::isZero(x, *this)) return this->hasZero() ? iteratorToZero() : end(); size_t hash_value = hash(x); size_t place_value = findCell(x, hash_value, grower.place(hash_value)); - return !buf[place_value].isZero(*this) ? iterator(this, &buf[place_value]) : end(); } - const_iterator find(Key x) const + const_iterator ALWAYS_INLINE find(Key x) const { if (Cell::isZero(x, *this)) return this->hasZero() ? iteratorToZero() : end(); size_t hash_value = hash(x); size_t place_value = findCell(x, hash_value, grower.place(hash_value)); + return !buf[place_value].isZero(*this) ? const_iterator(this, &buf[place_value]) : end(); + } + + iterator ALWAYS_INLINE find(Key x, size_t hash_value) + { + if (Cell::isZero(x, *this)) + return this->hasZero() ? iteratorToZero() : end(); + + size_t place_value = findCell(x, hash_value, grower.place(hash_value)); + return !buf[place_value].isZero(*this) ? iterator(this, &buf[place_value]) : end(); + } + + + const_iterator ALWAYS_INLINE find(Key x, size_t hash_value) const + { + if (Cell::isZero(x, *this)) + return this->hasZero() ? iteratorToZero() : end(); + + size_t place_value = findCell(x, hash_value, grower.place(hash_value)); return !buf[place_value].isZero(*this) ? const_iterator(this, &buf[place_value]) : end(); } @@ -701,6 +762,16 @@ public: return 0 == m_size; } + void clear() + { + if (!__has_trivial_destructor(Cell)) + for (iterator it = begin(); it != end(); ++it) + it.ptr->~Cell(); + + memset(buf, 0, grower.bufSize() * sizeof(*buf)); + m_size = 0; + } + size_t getBufferSizeInBytes() const { return grower.bufSize() * sizeof(Cell); diff --git a/dbms/include/DB/Common/HashTable/TwoLevelHashMap.h b/dbms/include/DB/Common/HashTable/TwoLevelHashMap.h new file mode 100644 index 00000000000..2c22de7eb58 --- /dev/null +++ b/dbms/include/DB/Common/HashTable/TwoLevelHashMap.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + + +template +< + typename Key, + typename Cell, + typename Hash = DefaultHash, + typename Grower = TwoLevelHashTableGrower<>, + typename Allocator = HashTableAllocator +> +class TwoLevelHashMapTable : public TwoLevelHashTable> +{ +public: + typedef Key key_type; + typedef typename Cell::Mapped mapped_type; + typedef typename Cell::value_type value_type; + + using TwoLevelHashTable >::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, + typename Grower = TwoLevelHashTableGrower<>, + typename Allocator = HashTableAllocator +> +using TwoLevelHashMap = TwoLevelHashMapTable, Hash, Grower, Allocator>; + + +template +< + typename Key, + typename Mapped, + typename Hash = DefaultHash, + typename Grower = TwoLevelHashTableGrower<>, + typename Allocator = HashTableAllocator +> +using TwoLevelHashMapWithSavedHash = TwoLevelHashMapTable, Hash, Grower, Allocator>; diff --git a/dbms/include/DB/Common/HashTable/TwoLevelHashTable.h b/dbms/include/DB/Common/HashTable/TwoLevelHashTable.h index 686f8fb0cf3..c1c60933d19 100644 --- a/dbms/include/DB/Common/HashTable/TwoLevelHashTable.h +++ b/dbms/include/DB/Common/HashTable/TwoLevelHashTable.h @@ -4,7 +4,7 @@ /** Двухуровневая хэш-таблица. - * Представляет собой 256 маленьких хэш-таблиц (bucket-ов первого уровня). + * Представляет собой 256 (или 1 << BITS_FOR_BUCKET) маленьких хэш-таблиц (bucket-ов первого уровня). * Для определения, какую из них использовать, берётся один из байтов хэш-функции. * * Обычно работает чуть-чуть медленнее простой хэш-таблицы. @@ -14,14 +14,25 @@ * - по идее, ресайзы кэш-локальны в большем диапазоне размеров. */ +template +struct TwoLevelHashTableGrower : public HashTableGrower +{ + /// Увеличить размер хэш-таблицы. + void increaseSize() + { + this->size_degree += this->size_degree >= 15 ? 1 : 2; + } +}; + template < typename Key, typename Cell, typename Hash, typename Grower, - typename Allocator, - typename ImplTable = HashTable + typename Allocator, /// TODO WithStackMemory + typename ImplTable = HashTable, + size_t BITS_FOR_BUCKET = 8 > class TwoLevelHashTable : private boost::noncopyable, @@ -36,8 +47,13 @@ protected: public: typedef ImplTable Impl; + static constexpr size_t NUM_BUCKETS = 1 << BITS_FOR_BUCKET; + static constexpr size_t MAX_BUCKET = NUM_BUCKETS - 1; + size_t hash(const Key & x) const { return Hash::operator()(x); } - size_t getBucketFromHash(size_t hash_value) const { return hash_value >> 56; } + + /// NOTE Плохо для хэш-таблиц больше чем на 2^32 ячеек. + size_t getBucketFromHash(size_t hash_value) const { return (hash_value >> (32 - BITS_FOR_BUCKET)) & MAX_BUCKET; } protected: typename Impl::iterator beginOfNextNonEmptyBucket(size_t & bucket) @@ -68,11 +84,34 @@ public: typedef typename Impl::key_type key_type; typedef typename Impl::value_type value_type; - static constexpr size_t NUM_BUCKETS = 256; - static constexpr size_t MAX_BUCKET = NUM_BUCKETS - 1; Impl impls[NUM_BUCKETS]; + TwoLevelHashTable() {} + + /// Скопировать данные из другой (обычной) хэш-таблицы. У неё должна быть такая же хэш-функция. + template + TwoLevelHashTable(const Source & src) + { + typename Source::const_iterator it = src.begin(); + + /// Предполагается, что нулевой ключ (хранящийся отдельно) при итерировании идёт первым. + if (it != src.end() && it.getPtr()->isZero(src)) + { + insert(*it); + ++it; + } + + for (; it != src.end(); ++it) + { + const Cell * cell = it.getPtr(); + size_t hash_value = cell->getHash(src); + size_t buck = getBucketFromHash(hash_value); + impls[buck].insertUniqueNonZero(cell, hash_value); + } + } + + class iterator { Self * container; @@ -104,6 +143,9 @@ public: value_type & operator* () const { return *current_it; } value_type * operator->() const { return &*current_it; } + + Cell * getPtr() const { return current_it.getPtr(); } + size_t getHash() const { return current_it.getHash(); } }; @@ -139,6 +181,9 @@ public: const value_type & operator* () const { return *current_it; } const value_type * operator->() const { return &*current_it; } + + const Cell * getPtr() const { return current_it.getPtr(); } + size_t getHash() const { return current_it.getHash(); } }; @@ -161,12 +206,16 @@ public: /// Вставить значение. В случае хоть сколько-нибудь сложных значений, лучше используйте функцию emplace. - std::pair insert(const value_type & x) + std::pair ALWAYS_INLINE insert(const value_type & x) { size_t hash_value = hash(Cell::getKey(x)); std::pair res; emplace(Cell::getKey(x), res.first, res.second, hash_value); + + if (res.second) + res.first.getPtr()->setMapped(x); + return res; } @@ -186,7 +235,7 @@ public: * if (inserted) * new(&it->second) Mapped(value); */ - void emplace(Key x, iterator & it, bool & inserted) + void ALWAYS_INLINE emplace(Key x, iterator & it, bool & inserted) { size_t hash_value = hash(x); emplace(x, it, inserted, hash_value); @@ -194,33 +243,33 @@ public: /// То же самое, но с заранее вычисленным значением хэш-функции. - void emplace(Key x, iterator & it, bool & inserted, size_t hash_value) + void ALWAYS_INLINE emplace(Key x, iterator & it, bool & inserted, size_t hash_value) { size_t buck = getBucketFromHash(hash_value); typename Impl::iterator impl_it; - impls[buck].emplace(x, impl_it, inserted); + impls[buck].emplace(x, impl_it, inserted, hash_value); it = iterator(this, buck, impl_it); } - iterator find(Key x) + iterator ALWAYS_INLINE find(Key x) { size_t hash_value = hash(x); size_t buck = getBucketFromHash(hash_value); - typename Impl::iterator found = impls[buck].find(x); + typename Impl::iterator found = impls[buck].find(x, hash_value); return found != impls[buck].end() ? iterator(this, buck, found) : end(); } - const_iterator find(Key x) const + const_iterator ALWAYS_INLINE find(Key x) const { size_t hash_value = hash(x); size_t buck = getBucketFromHash(hash_value); - typename Impl::const_iterator found = impls[buck].find(x); + typename Impl::const_iterator found = impls[buck].find(x, hash_value); return found != impls[buck].end() ? const_iterator(this, buck, found) : end(); diff --git a/dbms/include/DB/Common/PODArray.h b/dbms/include/DB/Common/PODArray.h index 2bacfb5a119..5e16e49a87b 100644 --- a/dbms/include/DB/Common/PODArray.h +++ b/dbms/include/DB/Common/PODArray.h @@ -160,7 +160,7 @@ public: struct iterator : public boost::iterator_adaptor { iterator() {} - iterator(T * ptr_) : iterator::iterator_adaptor_(ptr_) {} + iterator(T * ptr_) : iterator::iterator_adaptor_(ptr_) {} }; struct const_iterator : public boost::iterator_adaptor diff --git a/dbms/include/DB/Common/SipHash.h b/dbms/include/DB/Common/SipHash.h index 4903114f04d..bcfe81b93ed 100644 --- a/dbms/include/DB/Common/SipHash.h +++ b/dbms/include/DB/Common/SipHash.h @@ -16,13 +16,14 @@ #define ROTL(x,b) (u64)( ((x) << (b)) | ( (x) >> (64 - (b))) ) -#define SIPROUND \ - do { \ - v0 += v1; v1=ROTL(v1,13); v1 ^= v0; v0=ROTL(v0,32); \ - v2 += v3; v3=ROTL(v3,16); v3 ^= v2; \ - v0 += v3; v3=ROTL(v3,21); v3 ^= v0; \ - v2 += v1; v1=ROTL(v1,17); v1 ^= v2; v2=ROTL(v2,32); \ - } while(0) +#define SIPROUND \ + do \ + { \ + v0 += v1; v1=ROTL(v1,13); v1 ^= v0; v0=ROTL(v0,32); \ + v2 += v3; v3=ROTL(v3,16); v3 ^= v2; \ + v0 += v3; v3=ROTL(v3,21); v3 ^= v0; \ + v2 += v1; v1=ROTL(v1,17); v1 ^= v2; v2=ROTL(v2,32); \ + } while(0) class SipHash diff --git a/dbms/include/DB/Common/UInt128.h b/dbms/include/DB/Common/UInt128.h index 8c6c02a73b4..ea8bdec1fcf 100644 --- a/dbms/include/DB/Common/UInt128.h +++ b/dbms/include/DB/Common/UInt128.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -27,6 +29,17 @@ struct UInt128Hash size_t operator()(UInt128 x) const { return hash64(hash64(x.first) ^ x.second); } }; +struct UInt128HashCRC32 +{ + size_t operator()(UInt128 x) const + { + UInt64 crc = -1ULL; + asm("crc32q %[x], %[crc]\n" : [crc] "+r" (crc) : [x] "rm" (x.first)); + asm("crc32q %[x], %[crc]\n" : [crc] "+r" (crc) : [x] "rm" (x.second)); + return crc; + } +}; + struct UInt128TrivialHash { size_t operator()(UInt128 x) const { return x.first; } diff --git a/dbms/include/DB/Common/VirtualColumnUtils.h b/dbms/include/DB/Common/VirtualColumnUtils.h index 0d873ce3cba..a4848d5e17b 100644 --- a/dbms/include/DB/Common/VirtualColumnUtils.h +++ b/dbms/include/DB/Common/VirtualColumnUtils.h @@ -1,19 +1,18 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include + namespace DB { +class Context; + + namespace VirtualColumnUtils { diff --git a/dbms/include/DB/Core/Block.h b/dbms/include/DB/Core/Block.h index 66e5b4ec75f..a8eb9c085a3 100644 --- a/dbms/include/DB/Core/Block.h +++ b/dbms/include/DB/Core/Block.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -35,6 +36,8 @@ private: IndexByName_t index_by_name; public: + BlockInfo info; + Block() = default; Block(std::initializer_list il) : data{il} { @@ -129,7 +132,7 @@ bool blocksHaveEqualStructure(const Block & lhs, const Block & rhs); namespace std { - template<> inline void swap(DB::Block & one, DB::Block & another) noexcept + template<> inline void swap(DB::Block & one, DB::Block & another) { one.swap(another); } diff --git a/dbms/include/DB/Core/BlockInfo.h b/dbms/include/DB/Core/BlockInfo.h new file mode 100644 index 00000000000..eaa4cabae9f --- /dev/null +++ b/dbms/include/DB/Core/BlockInfo.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + + +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 +}; + +} diff --git a/dbms/include/DB/Core/Defines.h b/dbms/include/DB/Core/Defines.h index b89e1e86c87..0056ad72515 100644 --- a/dbms/include/DB/Core/Defines.h +++ b/dbms/include/DB/Core/Defines.h @@ -65,6 +65,7 @@ #define DBMS_MIN_REVISION_WITH_STRING_QUERY_ID 39002 #define DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES 50264 #define DBMS_MIN_REVISION_WITH_TOTAL_ROWS_IN_PROGRESS 51554 +#define DBMS_MIN_REVISION_WITH_BLOCK_INFO 51903 #define DBMS_DISTRIBUTED_DIRECTORY_MONITOR_SLEEP_TIME_MS 100 diff --git a/dbms/include/DB/Core/ErrorCodes.h b/dbms/include/DB/Core/ErrorCodes.h index 7f972fa87a8..0466349be83 100644 --- a/dbms/include/DB/Core/ErrorCodes.h +++ b/dbms/include/DB/Core/ErrorCodes.h @@ -266,6 +266,11 @@ namespace ErrorCodes PARTITION_ALREADY_EXISTS, PARTITION_DOESNT_EXIST, UNION_ALL_RESULT_STRUCTURES_MISMATCH, + UNION_ALL_COLUMN_ALIAS_MISMATCH, + CLIENT_OUTPUT_FORMAT_SPECIFIED, + UNKNOWN_BLOCK_INFO_FIELD, + BAD_COLLATION, + CANNOT_COMPILE_CODE, POCO_EXCEPTION = 1000, STD_EXCEPTION, diff --git a/dbms/include/DB/Core/SortDescription.h b/dbms/include/DB/Core/SortDescription.h index 5d16a6a1856..8f65a5d15ce 100644 --- a/dbms/include/DB/Core/SortDescription.h +++ b/dbms/include/DB/Core/SortDescription.h @@ -50,9 +50,9 @@ struct SortCursorImpl ConstColumnPlainPtrs all_columns; ConstColumnPlainPtrs sort_columns; SortDescription desc; - size_t sort_columns_size; - size_t pos; - size_t rows; + size_t sort_columns_size = 0; + size_t pos = 0; + size_t rows = 0; /** Порядок (что сравнивается), если сравниваемые столбцы равны. * Даёт возможность предпочитать строки из нужного курсора. @@ -65,12 +65,12 @@ struct SortCursorImpl NeedCollationFlags need_collation; /** Есть ли хотя бы один столбец с Collator. */ - bool has_collation; + bool has_collation = false; - SortCursorImpl() : sort_columns(0), pos(0), rows(0) {} + SortCursorImpl() {} SortCursorImpl(const Block & block, const SortDescription & desc_, size_t order_ = 0) - : desc(desc_), sort_columns_size(desc.size()), order(order_), need_collation(desc.size()), has_collation(false) + : desc(desc_), sort_columns_size(desc.size()), order(order_), need_collation(desc.size()) { reset(block); } diff --git a/dbms/include/DB/DataStreams/AggregatingBlockInputStream.h b/dbms/include/DB/DataStreams/AggregatingBlockInputStream.h index 37e8a6de4b5..6ddef77e6e3 100644 --- a/dbms/include/DB/DataStreams/AggregatingBlockInputStream.h +++ b/dbms/include/DB/DataStreams/AggregatingBlockInputStream.h @@ -19,9 +19,10 @@ class AggregatingBlockInputStream : public IProfilingBlockInputStream { public: AggregatingBlockInputStream(BlockInputStreamPtr input_, const ColumnNumbers & keys_, AggregateDescriptions & aggregates_, - bool overflow_row_, bool final_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_) - : aggregator(new Aggregator(keys_, aggregates_, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_)), - final(final_), has_been_read(false) + bool overflow_row_, bool final_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_, + Compiler * compiler_, UInt32 min_count_to_compile_) + : aggregator(keys_, aggregates_, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_, compiler_, min_count_to_compile_), + final(final_) { children.push_back(input_); } @@ -31,23 +32,32 @@ public: * Столбцы, соответствующие keys и аргументам агрегатных функций, уже должны быть вычислены. */ AggregatingBlockInputStream(BlockInputStreamPtr input_, const Names & key_names, const AggregateDescriptions & aggregates, - bool overflow_row_, bool final_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_); + bool overflow_row_, bool final_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_, + Compiler * compiler_, UInt32 min_count_to_compile_) + : aggregator(key_names, aggregates, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_, compiler_, min_count_to_compile_), + final(final_) + { + children.push_back(input_); + } String getName() const override { return "AggregatingBlockInputStream"; } String getID() const override { std::stringstream res; - res << "Aggregating(" << children.back()->getID() << ", " << aggregator->getID() << ")"; + res << "Aggregating(" << children.back()->getID() << ", " << aggregator.getID() << ")"; return res.str(); } protected: Block readImpl() override; - SharedPtr aggregator; + Aggregator aggregator; bool final; - bool has_been_read; + + bool executed = false; + BlocksList blocks; + BlocksList::iterator it; }; } diff --git a/dbms/include/DB/DataStreams/DistinctBlockInputStream.h b/dbms/include/DB/DataStreams/DistinctBlockInputStream.h index 0779509bc2f..3f4914b3725 100644 --- a/dbms/include/DB/DataStreams/DistinctBlockInputStream.h +++ b/dbms/include/DB/DataStreams/DistinctBlockInputStream.h @@ -96,7 +96,10 @@ protected: filter[i] = set.insert(key).second; if (limit && set.size() == limit) + { + memset(&filter[i + 1], 0, (rows - (i + 1)) * sizeof(IColumn::Filter::value_type)); break; + } } /// Если ни одной новой строки не было в блоке - перейдём к следующему блоку. diff --git a/dbms/include/DB/DataStreams/LazyBlockInputStream.h b/dbms/include/DB/DataStreams/LazyBlockInputStream.h new file mode 100644 index 00000000000..81793586727 --- /dev/null +++ b/dbms/include/DB/DataStreams/LazyBlockInputStream.h @@ -0,0 +1,51 @@ +#pragma once + +#include + + +namespace DB +{ + +/** Инициализировать другой источник при первом вызове read, и затем использовать его. + * Это нужно, например, для чтения из таблицы, которая будет заполнена + * после создания объекта LazyBlockInputStream, но до первого вызова read. + */ +class LazyBlockInputStream : public IProfilingBlockInputStream +{ +public: + using Generator = std::function; + + 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; +}; + +} diff --git a/dbms/include/DB/DataStreams/MergeSortingBlockInputStream.h b/dbms/include/DB/DataStreams/MergeSortingBlockInputStream.h index e188243c21b..a87d81a01f3 100644 --- a/dbms/include/DB/DataStreams/MergeSortingBlockInputStream.h +++ b/dbms/include/DB/DataStreams/MergeSortingBlockInputStream.h @@ -1,23 +1,74 @@ #pragma once +#include +#include + #include #include #include +#include + +#include +#include 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; + CursorImpls cursors; + + bool has_collation = false; + + std::priority_queue queue; + std::priority_queue queue_with_collation; + + /** Делаем поддержку двух разных курсоров - с Collation и без. + * Шаблоны используем вместо полиморфных SortCursor'ов и вызовов виртуальных функций. + */ + template + Block mergeImpl(std::priority_queue & queue); +}; + + class MergeSortingBlockInputStream : public IProfilingBlockInputStream { public: /// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке. - MergeSortingBlockInputStream(BlockInputStreamPtr input_, SortDescription & description_, size_t limit_ = 0) - : description(description_), limit(limit_), has_been_read(false), log(&Logger::get("MergeSortingBlockInputStream")) + MergeSortingBlockInputStream(BlockInputStreamPtr input_, SortDescription & description_, + size_t max_merged_block_size_, size_t limit_, + size_t max_bytes_before_external_sort_, const std::string & tmp_path_, const DataTypeFactory & data_type_factory_) + : description(description_), max_merged_block_size(max_merged_block_size_), limit(limit_), + max_bytes_before_external_sort(max_bytes_before_external_sort_), tmp_path(tmp_path_), data_type_factory(data_type_factory_) { children.push_back(input_); } @@ -41,24 +92,36 @@ protected: private: SortDescription description; + size_t max_merged_block_size; size_t limit; - /// Всё было прочитано. - bool has_been_read; + size_t max_bytes_before_external_sort; + const std::string tmp_path; + const DataTypeFactory & data_type_factory; - Logger * log; + Logger * log = &Logger::get("MergeSortingBlockInputStream"); - /** Слить сразу много блоков с помощью priority queue. - */ - Block merge(Blocks & blocks); + Blocks blocks; + size_t sum_bytes_in_blocks = 0; + std::unique_ptr impl; - typedef std::vector CursorImpls; + /// Всё ниже - для внешней сортировки. + std::vector> temporary_files; - /** Делаем поддержку двух разных курсоров - с Collation и без. - * Шаблоны используем вместо полиморфных SortCursor'ов и вызовов виртуальных функций. - */ - template - Block mergeImpl(Blocks & block, CursorImpls & cursors); + /// Для чтения сброшенных во временный файл данных. + struct TemporaryFileStream + { + ReadBufferFromFile file_in; + CompressedReadBuffer compressed_in; + BlockInputStreamPtr block_in; + + TemporaryFileStream(const std::string & path, const DataTypeFactory & data_type_factory) + : file_in(path), compressed_in(file_in), block_in(new NativeBlockInputStream(compressed_in, data_type_factory)) {} + }; + + std::vector> temporary_inputs; + + BlockInputStreams inputs_to_merge; }; } diff --git a/dbms/include/DB/DataStreams/MergingAggregatedBlockInputStream.h b/dbms/include/DB/DataStreams/MergingAggregatedBlockInputStream.h index 62d1169c67a..72c8fae8b75 100644 --- a/dbms/include/DB/DataStreams/MergingAggregatedBlockInputStream.h +++ b/dbms/include/DB/DataStreams/MergingAggregatedBlockInputStream.h @@ -17,15 +17,17 @@ class MergingAggregatedBlockInputStream : public IProfilingBlockInputStream { public: MergingAggregatedBlockInputStream(BlockInputStreamPtr input_, const ColumnNumbers & keys_, - const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_) - : aggregator(new Aggregator(keys_, aggregates_, overflow_row_)), final(final_), has_been_read(false) + const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_, size_t max_threads_) + : aggregator(keys_, aggregates_, overflow_row_, 0, OverflowMode::THROW, nullptr, 0), + final(final_), max_threads(max_threads_) { children.push_back(input_); } MergingAggregatedBlockInputStream(BlockInputStreamPtr input_, const Names & keys_names_, - const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_) - : aggregator(new Aggregator(keys_names_, aggregates_, overflow_row_)), final(final_), has_been_read(false) + const AggregateDescriptions & aggregates_, bool overflow_row_, bool final_, size_t max_threads_) + : aggregator(keys_names_, aggregates_, overflow_row_, 0, OverflowMode::THROW, nullptr, 0), + final(final_), max_threads(max_threads_) { children.push_back(input_); } @@ -35,7 +37,7 @@ public: String getID() const override { std::stringstream res; - res << "MergingAggregated(" << children.back()->getID() << ", " << aggregator->getID() << ")"; + res << "MergingAggregated(" << children.back()->getID() << ", " << aggregator.getID() << ")"; return res.str(); } @@ -43,9 +45,13 @@ protected: Block readImpl() override; private: - SharedPtr aggregator; + Aggregator aggregator; bool final; - bool has_been_read; + size_t max_threads; + + bool executed = false; + BlocksList blocks; + BlocksList::iterator it; }; } diff --git a/dbms/include/DB/DataStreams/MergingSortedBlockInputStream.h b/dbms/include/DB/DataStreams/MergingSortedBlockInputStream.h index 27dd479dfb8..37a9d571bb7 100644 --- a/dbms/include/DB/DataStreams/MergingSortedBlockInputStream.h +++ b/dbms/include/DB/DataStreams/MergingSortedBlockInputStream.h @@ -20,8 +20,8 @@ class MergingSortedBlockInputStream : public IProfilingBlockInputStream public: /// limit - если не 0, то можно выдать только первые limit строк в сортированном порядке. MergingSortedBlockInputStream(BlockInputStreams inputs_, const SortDescription & description_, size_t max_block_size_, size_t limit_ = 0) - : description(description_), max_block_size(max_block_size_), limit(limit_), total_merged_rows(0), first(true), has_collation(false), - num_columns(0), source_blocks(inputs_.size()), cursors(inputs_.size()), log(&Logger::get("MergingSortedBlockInputStream")) + : description(description_), max_block_size(max_block_size_), limit(limit_), + source_blocks(inputs_.size()), cursors(inputs_.size()) { children.insert(children.end(), inputs_.begin(), inputs_.end()); } @@ -65,14 +65,13 @@ protected: SortDescription description; size_t max_block_size; size_t limit; - size_t total_merged_rows; + size_t total_merged_rows = 0; - bool first; - - bool has_collation; + bool first = true; + bool has_collation = false; /// Текущие сливаемые блоки. - size_t num_columns; + size_t num_columns = 0; Blocks source_blocks; typedef std::vector CursorImpls; @@ -139,7 +138,7 @@ private: template void merge(Block & merged_block, ColumnPlainPtrs & merged_columns, std::priority_queue & queue); - Logger * log; + Logger * log = &Logger::get("MergingSortedBlockInputStream"); }; } diff --git a/dbms/include/DB/DataStreams/NativeBlockInputStream.h b/dbms/include/DB/DataStreams/NativeBlockInputStream.h index 1ec7729dd38..c2da2c6bae0 100644 --- a/dbms/include/DB/DataStreams/NativeBlockInputStream.h +++ b/dbms/include/DB/DataStreams/NativeBlockInputStream.h @@ -13,8 +13,11 @@ namespace DB class NativeBlockInputStream : public IProfilingBlockInputStream { public: - NativeBlockInputStream(ReadBuffer & istr_, const DataTypeFactory & data_type_factory_) - : istr(istr_), data_type_factory(data_type_factory_) {} + /** В случае указания ненулевой server_revision, может ожидаться и считываться дополнительная информация о блоке, + * в зависимости от поддерживаемой для указанной ревизии. + */ + NativeBlockInputStream(ReadBuffer & istr_, const DataTypeFactory & data_type_factory_, UInt64 server_revision_ = 0) + : istr(istr_), data_type_factory(data_type_factory_), server_revision(server_revision_) {} String getName() const override { return "NativeBlockInputStream"; } @@ -31,6 +34,7 @@ protected: private: ReadBuffer & istr; const DataTypeFactory & data_type_factory; + UInt64 server_revision; }; } diff --git a/dbms/include/DB/DataStreams/NativeBlockOutputStream.h b/dbms/include/DB/DataStreams/NativeBlockOutputStream.h index 70c1410107c..445f843f059 100644 --- a/dbms/include/DB/DataStreams/NativeBlockOutputStream.h +++ b/dbms/include/DB/DataStreams/NativeBlockOutputStream.h @@ -12,13 +12,18 @@ namespace DB class NativeBlockOutputStream : public IBlockOutputStream { public: - NativeBlockOutputStream(WriteBuffer & ostr_) : ostr(ostr_) {} + /** В случае указания ненулевой client_revision, может записываться дополнительная информация о блоке, + * в зависимости от поддерживаемой для указанной ревизии. + */ + NativeBlockOutputStream(WriteBuffer & ostr_, UInt64 client_revision_ = 0) + : ostr(ostr_), client_revision(client_revision_) {} void write(const Block & block) override; void flush() override { ostr.next(); } private: WriteBuffer & ostr; + UInt64 client_revision; }; } diff --git a/dbms/include/DB/DataStreams/OneBlockInputStream.h b/dbms/include/DB/DataStreams/OneBlockInputStream.h index b75ef5f898b..18f05415f12 100644 --- a/dbms/include/DB/DataStreams/OneBlockInputStream.h +++ b/dbms/include/DB/DataStreams/OneBlockInputStream.h @@ -16,7 +16,7 @@ using Poco::SharedPtr; class OneBlockInputStream : public IProfilingBlockInputStream { public: - OneBlockInputStream(const Block & block_) : block(block_), has_been_read(false) {} + OneBlockInputStream(const Block & block_) : block(block_) {} String getName() const override { return "OneBlockInputStream"; } @@ -39,7 +39,7 @@ protected: private: Block block; - bool has_been_read; + bool has_been_read = false; }; } diff --git a/dbms/include/DB/DataStreams/ParallelAggregatingBlockInputStream.h b/dbms/include/DB/DataStreams/ParallelAggregatingBlockInputStream.h index d2625f7da3c..a0b6ad2fc26 100644 --- a/dbms/include/DB/DataStreams/ParallelAggregatingBlockInputStream.h +++ b/dbms/include/DB/DataStreams/ParallelAggregatingBlockInputStream.h @@ -21,9 +21,10 @@ class ParallelAggregatingBlockInputStream : public IProfilingBlockInputStream public: ParallelAggregatingBlockInputStream(BlockInputStreams inputs, const ColumnNumbers & keys_, AggregateDescriptions & aggregates_, bool overflow_row_, bool final_, size_t max_threads_, - size_t max_rows_to_group_by_ = 0, OverflowMode group_by_overflow_mode_ = OverflowMode::THROW) - : aggregator(new Aggregator(keys_, aggregates_, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_)), - has_been_read(false), final(final_), max_threads(std::min(inputs.size(), max_threads_)), + size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_, + Compiler * compiler_, UInt32 min_count_to_compile_) + : aggregator(keys_, aggregates_, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_, compiler_, min_count_to_compile_), + final(final_), max_threads(std::min(inputs.size(), max_threads_)), keys_size(keys_.size()), aggregates_size(aggregates_.size()), handler(*this), processor(inputs, max_threads, handler) { @@ -34,14 +35,14 @@ public: */ ParallelAggregatingBlockInputStream(BlockInputStreams inputs, const Names & key_names, const AggregateDescriptions & aggregates, bool overflow_row_, bool final_, size_t max_threads_, - size_t max_rows_to_group_by_ = 0, OverflowMode group_by_overflow_mode_ = OverflowMode::THROW) - : has_been_read(false), final(final_), max_threads(std::min(inputs.size(), max_threads_)), + size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_, + Compiler * compiler_, UInt32 min_count_to_compile_) + : aggregator(key_names, aggregates, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_, compiler_, min_count_to_compile_), + final(final_), max_threads(std::min(inputs.size(), max_threads_)), keys_size(key_names.size()), aggregates_size(aggregates.size()), handler(*this), processor(inputs, max_threads, handler) { children.insert(children.end(), inputs.begin(), inputs.end()); - - aggregator = new Aggregator(key_names, aggregates, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_); } String getName() const override { return "ParallelAggregatingBlockInputStream"; } @@ -61,7 +62,7 @@ public: for (size_t i = 0; i < children_ids.size(); ++i) res << (i == 0 ? "" : ", ") << children_ids[i]; - res << ", " << aggregator->getID() << ")"; + res << ", " << aggregator.getID() << ")"; return res.str(); } @@ -76,65 +77,29 @@ public: protected: Block readImpl() override { - if (has_been_read) - return Block(); - - has_been_read = true; - - many_data.resize(max_threads); - exceptions.resize(max_threads); - - for (size_t i = 0; i < max_threads; ++i) - threads_data.emplace_back(keys_size, aggregates_size); - - LOG_TRACE(log, "Aggregating"); - - Stopwatch watch; - - for (auto & elem : many_data) - elem = new AggregatedDataVariants; - - processor.process(); - processor.wait(); - - rethrowFirstException(exceptions); - - if (isCancelled()) - return Block(); - - double elapsed_seconds = watch.elapsedSeconds(); - - size_t total_src_rows = 0; - size_t total_src_bytes = 0; - for (size_t i = 0; i < max_threads; ++i) + if (!executed) { - size_t rows = many_data[i]->size(); - LOG_TRACE(log, std::fixed << std::setprecision(3) - << "Aggregated. " << threads_data[i].src_rows << " to " << rows << " rows" - << " (from " << threads_data[i].src_bytes / 1048576.0 << " MiB)" - << " in " << elapsed_seconds << " sec." - << " (" << threads_data[i].src_rows / elapsed_seconds << " rows/sec., " - << threads_data[i].src_bytes / elapsed_seconds / 1048576.0 << " MiB/sec.)"); + executed = true; + AggregatedDataVariantsPtr data_variants = executeAndMerge(); - total_src_rows += threads_data[i].src_rows; - total_src_bytes += threads_data[i].src_bytes; + if (data_variants) + blocks = aggregator.convertToBlocks(*data_variants, final, max_threads); + + it = blocks.begin(); } - LOG_TRACE(log, std::fixed << std::setprecision(3) - << "Total aggregated. " << total_src_rows << " rows (from " << total_src_bytes / 1048576.0 << " MiB)" - << " in " << elapsed_seconds << " sec." - << " (" << total_src_rows / elapsed_seconds << " rows/sec., " << total_src_bytes / elapsed_seconds / 1048576.0 << " MiB/sec.)"); - AggregatedDataVariantsPtr res = aggregator->merge(many_data); + Block res; + if (isCancelled() || it == blocks.end()) + return res; - if (isCancelled()) - return Block(); + res = *it; + ++it; - return aggregator->convertToBlock(*res, final); + return res; } private: - SharedPtr aggregator; - bool has_been_read; + Aggregator aggregator; bool final; size_t max_threads; @@ -148,6 +113,10 @@ private: */ bool no_more_keys = false; + bool executed = false; + BlocksList blocks; + BlocksList::iterator it; + Logger * log = &Logger::get("ParallelAggregatingBlockInputStream"); @@ -158,7 +127,7 @@ private: void onBlock(Block & block, size_t thread_num) { - parent.aggregator->executeOnBlock(block, *parent.many_data[thread_num], + parent.aggregator.executeOnBlock(block, *parent.many_data[thread_num], parent.threads_data[thread_num].key_columns, parent.threads_data[thread_num].aggregate_columns, parent.threads_data[thread_num].key_sizes, parent.threads_data[thread_num].key, parent.no_more_keys); @@ -205,6 +174,57 @@ private: }; std::vector 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); + } }; } diff --git a/dbms/include/DB/DataStreams/ProhibitColumnsBlockOutputStream.h b/dbms/include/DB/DataStreams/ProhibitColumnsBlockOutputStream.h index 296aa202fe5..71a6a495e8e 100644 --- a/dbms/include/DB/DataStreams/ProhibitColumnsBlockOutputStream.h +++ b/dbms/include/DB/DataStreams/ProhibitColumnsBlockOutputStream.h @@ -19,8 +19,8 @@ public: private: void write(const Block & block) override { - for (const auto & column : columns) - if (block.has(column.name)) + for (const auto & column : columns) + if (block.has(column.name)) throw Exception{"Cannot insert column " + column.name, ErrorCodes::ILLEGAL_COLUMN}; output->write(block); diff --git a/dbms/include/DB/DataStreams/PushingToViewsBlockOutputStream.h b/dbms/include/DB/DataStreams/PushingToViewsBlockOutputStream.h index 071aa0bd77a..a442f477aa2 100644 --- a/dbms/include/DB/DataStreams/PushingToViewsBlockOutputStream.h +++ b/dbms/include/DB/DataStreams/PushingToViewsBlockOutputStream.h @@ -18,13 +18,13 @@ namespace DB class PushingToViewsBlockOutputStream : public IBlockOutputStream { public: - PushingToViewsBlockOutputStream(String database_, String table_, const Context & context_, ASTPtr query_ptr_) - : database(database_), table(table_), context(context_), query_ptr(query_ptr_) + PushingToViewsBlockOutputStream(String database, String table, const Context & context_, ASTPtr query_ptr_) + : context(context_), query_ptr(query_ptr_) { storage = context.getTable(database, table); addTableLock(storage->lockStructure(true)); - Dependencies dependencies = context.getDependencies(DatabaseAndTableName(database, table)); + Dependencies dependencies = context.getDependencies(database, table); for (size_t i = 0; i < dependencies.size(); ++i) { children.push_back(new PushingToViewsBlockOutputStream(dependencies[i].first, dependencies[i].second, context, ASTPtr())); @@ -64,8 +64,6 @@ public: private: StoragePtr storage; BlockOutputStreamPtr output; - String database; - String table; Context context; ASTPtr query_ptr; std::vector children; diff --git a/dbms/include/DB/DataStreams/RemoteBlockInputStream.h b/dbms/include/DB/DataStreams/RemoteBlockInputStream.h index 336e836207d..d31f1e68ea7 100644 --- a/dbms/include/DB/DataStreams/RemoteBlockInputStream.h +++ b/dbms/include/DB/DataStreams/RemoteBlockInputStream.h @@ -3,7 +3,9 @@ #include #include +#include #include +#include #include @@ -30,7 +32,7 @@ public: /// Принимает готовое соединение. RemoteBlockInputStream(Connection & connection_, const String & query_, const Settings * settings_, const Tables & external_tables_ = Tables(), QueryProcessingStage::Enum stage_ = QueryProcessingStage::Complete, - const Context & context = Context{}) + const Context & context = getDefaultContext()) : connection(&connection_), query(query_), external_tables(external_tables_), stage(stage_), context(context) { init(settings_); @@ -39,7 +41,7 @@ public: /// Принимает готовое соединение. Захватывает владение соединением из пула. RemoteBlockInputStream(ConnectionPool::Entry & pool_entry_, const String & query_, const Settings * settings_, const Tables & external_tables_ = Tables(), QueryProcessingStage::Enum stage_ = QueryProcessingStage::Complete, - const Context & context = Context{}) + const Context & context = getDefaultContext()) : pool_entry(pool_entry_), connection(&*pool_entry_), query(query_), external_tables(external_tables_), stage(stage_), context(context) { @@ -49,7 +51,7 @@ public: /// Принимает пул, из которого нужно будет достать соединение. RemoteBlockInputStream(IConnectionPool * pool_, const String & query_, const Settings * settings_, const Tables & external_tables_ = Tables(), QueryProcessingStage::Enum stage_ = QueryProcessingStage::Complete, - const Context & context = Context{}) + const Context & context = getDefaultContext()) : pool(pool_), query(query_), external_tables(external_tables_), stage(stage_), context(context) { init(settings_); @@ -273,6 +275,13 @@ private: bool got_exception_from_server = false; Logger * log = &Logger::get("RemoteBlockInputStream"); + + /// ITable::read requires a Context, therefore we should create one if the user can't supply it + static Context & getDefaultContext() + { + static Context instance; + return instance; + } }; } diff --git a/dbms/include/DB/DataStreams/SplittingAggregatingBlockInputStream.h b/dbms/include/DB/DataStreams/SplittingAggregatingBlockInputStream.h deleted file mode 100644 index 9e14e2db1c4..00000000000 --- a/dbms/include/DB/DataStreams/SplittingAggregatingBlockInputStream.h +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#include -#include - - -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 aggregator; - Blocks results; - Blocks::iterator current_result; -}; - -} diff --git a/dbms/include/DB/DataStreams/TotalsHavingBlockInputStream.h b/dbms/include/DB/DataStreams/TotalsHavingBlockInputStream.h index 7c740b6ecb5..1b3ee309c9d 100644 --- a/dbms/include/DB/DataStreams/TotalsHavingBlockInputStream.h +++ b/dbms/include/DB/DataStreams/TotalsHavingBlockInputStream.h @@ -20,8 +20,8 @@ class TotalsHavingBlockInputStream : public IProfilingBlockInputStream public: TotalsHavingBlockInputStream(BlockInputStreamPtr input_, const Names & keys_names_, const AggregateDescriptions & aggregates_, bool overflow_row_, ExpressionActionsPtr expression_, - const std::string & filter_column_, TotalsMode totals_mode_, float auto_include_threshold_) - : aggregator(new Aggregator(keys_names_, aggregates_, overflow_row_)), overflow_row(overflow_row_), + const std::string & filter_column_, TotalsMode totals_mode_, double auto_include_threshold_) + : overflow_row(overflow_row_), expression(expression_), filter_column_name(filter_column_), totals_mode(totals_mode_), auto_include_threshold(auto_include_threshold_), passed_keys(0), total_keys(0) { @@ -33,7 +33,7 @@ public: String getID() const override { std::stringstream res; - res << "TotalsHavingBlockInputStream(" << children.back()->getID() << ", " << aggregator->getID() + res << "TotalsHavingBlockInputStream(" << children.back()->getID() << "," << filter_column_name << ")"; return res.str(); } @@ -44,24 +44,24 @@ protected: Block readImpl() override; private: - SharedPtr aggregator; bool overflow_row; ExpressionActionsPtr expression; String filter_column_name; TotalsMode totals_mode; - float auto_include_threshold; + double auto_include_threshold; size_t passed_keys; size_t total_keys; - Block current_totals; + /** Здесь находятся значения, не прошедшие max_rows_to_group_by. + * Они прибавляются или не прибавляются к current_totals в зависимости от totals_mode. + */ Block overflow_aggregates; - void addToTotals(Block & totals, Block & block, const IColumn::Filter * filter, size_t rows); + /// Здесь накапливаются тотальные значения. После окончания работы, они будут помещены в IProfilingBlockInputStream::totals. + Block current_totals; - void addToTotals(Block & totals, Block & block, const IColumn::Filter * filter) - { - addToTotals(totals, block, filter, block.rows()); - } + /// Если filter == nullptr - прибавлять все строки. Иначе - только строки, проходящие фильтр (HAVING). + void addToTotals(Block & totals, Block & block, const IColumn::Filter * filter); }; } diff --git a/dbms/include/DB/DataStreams/copyData.h b/dbms/include/DB/DataStreams/copyData.h index ee9f231b27c..0f2014594b3 100644 --- a/dbms/include/DB/DataStreams/copyData.h +++ b/dbms/include/DB/DataStreams/copyData.h @@ -13,7 +13,7 @@ namespace DB /** Копирует данные из InputStream в OutputStream * (например, из БД в консоль и т. п.) */ -void copyData(IBlockInputStream & from, IBlockOutputStream & to); +void copyData(IBlockInputStream & from, IBlockOutputStream & to, volatile bool * is_cancelled = nullptr); void copyData(IRowInputStream & from, IRowOutputStream & to); } diff --git a/dbms/include/DB/Functions/FunctionsCoding.h b/dbms/include/DB/Functions/FunctionsCoding.h index f404fca4668..0feaaf25d8b 100644 --- a/dbms/include/DB/Functions/FunctionsCoding.h +++ b/dbms/include/DB/Functions/FunctionsCoding.h @@ -463,8 +463,6 @@ public: ErrorCodes::ILLEGAL_COLUMN); } }; - - class FunctionIPv4NumToString : public IFunction { public: @@ -661,6 +659,115 @@ public: }; +class FunctionIPv4NumToStringClassC : public IFunction +{ +public: + static constexpr auto name = "IPv4NumToStringClassC"; + static IFunction * create(const Context & context) { return new FunctionIPv4NumToStringClassC; } + + /// Получить имя функции. + String getName() const + { + return name; + } + + /// Получить тип результата по типам аргументов. Если функция неприменима для данных аргументов - кинуть исключение. + DataTypePtr getReturnType(const DataTypes & arguments) const + { + if (arguments.size() != 1) + throw Exception("Number of arguments for function " + getName() + " doesn't match: passed " + + toString(arguments.size()) + ", should be 1.", + ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH); + + if (!typeid_cast(&*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(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 * col = typeid_cast *>(&*column)) + { + const ColumnVector::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(&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 * col = typeid_cast *>(&*column)) + { + char buf[16]; + char * pos = buf; + formatIP(col->getData(), pos); + + ColumnConstString * col_res = new ColumnConstString(col->size(), buf); + block.getByPosition(result).column = col_res; + } + else + throw Exception("Illegal column " + block.getByPosition(arguments[0]).column->getName() + + " of argument of function " + getName(), + ErrorCodes::ILLEGAL_COLUMN); + } +}; + + class FunctionHex : public IFunction { public: diff --git a/dbms/include/DB/Functions/FunctionsString.h b/dbms/include/DB/Functions/FunctionsString.h index 308f43012b8..055870dc95f 100644 --- a/dbms/include/DB/Functions/FunctionsString.h +++ b/dbms/include/DB/Functions/FunctionsString.h @@ -1379,7 +1379,7 @@ private: src_offset = src_offsets[i]; dst_offset += src_length; - if (dst_data[dst_offset - 2] != trailing_char_str.front()) + if (src_length == 1 || dst_data[dst_offset - 2] != trailing_char_str.front()) { dst_data[dst_offset - 1] = trailing_char_str.front(); dst_data[dst_offset] = 0; @@ -1397,7 +1397,8 @@ private: block.getByPosition(result).column = new ColumnConstString{ col->size(), - in_data.back() == trailing_char_str.front() ? in_data : in_data + trailing_char_str + in_data.size() == 0 ? trailing_char_str : + in_data.back() == trailing_char_str.front() ? in_data : in_data + trailing_char_str }; } else diff --git a/dbms/include/DB/Functions/FunctionsURL.h b/dbms/include/DB/Functions/FunctionsURL.h index 357b5d904a8..a0ee16dfb08 100644 --- a/dbms/include/DB/Functions/FunctionsURL.h +++ b/dbms/include/DB/Functions/FunctionsURL.h @@ -280,7 +280,7 @@ struct ExtractPathFull if (nullptr != (pos = strchr(data, '/')) && pos[1] == '/' && nullptr != (pos = strchr(pos + 2, '/'))) { /// no leading slash - res_data = pos + 1; + res_data = pos; res_size = end - res_data; } } diff --git a/dbms/include/DB/IO/Operators.h b/dbms/include/DB/IO/Operators.h index 7dcae0c04ab..284963c527c 100644 --- a/dbms/include/DB/IO/Operators.h +++ b/dbms/include/DB/IO/Operators.h @@ -42,43 +42,43 @@ struct BinaryManipReadBuffer : ReadBuffer {}; template WriteBuffer & operator<< (WriteBuffer & buf, const T & x) { writeText(x, buf); return buf; } /// Если не использовать манипуляторы, строка выводится без экранирования, как есть. -template <> WriteBuffer & operator<< (WriteBuffer & buf, const String & x) { writeString(x, buf); return buf; } -template <> WriteBuffer & operator<< (WriteBuffer & buf, const char & x) { writeChar(x, buf); return buf; } +template <> inline WriteBuffer & operator<< (WriteBuffer & buf, const String & x) { writeString(x, buf); return buf; } +template <> inline WriteBuffer & operator<< (WriteBuffer & buf, const char & x) { writeChar(x, buf); return buf; } -WriteBuffer & operator<< (WriteBuffer & buf, const char * x) { writeCString(x, buf); return buf; } +inline WriteBuffer & operator<< (WriteBuffer & buf, const char * x) { writeCString(x, buf); return buf; } -EscapeManipWriteBuffer & operator<< (WriteBuffer & buf, EscapeManip x) { return static_cast(buf); } -QuoteManipWriteBuffer & operator<< (WriteBuffer & buf, QuoteManip x) { return static_cast(buf); } -DoubleQuoteManipWriteBuffer & operator<< (WriteBuffer & buf, DoubleQuoteManip x) { return static_cast(buf); } -BinaryManipWriteBuffer & operator<< (WriteBuffer & buf, BinaryManip x) { return static_cast(buf); } +inline EscapeManipWriteBuffer & operator<< (WriteBuffer & buf, EscapeManip x) { return static_cast(buf); } +inline QuoteManipWriteBuffer & operator<< (WriteBuffer & buf, QuoteManip x) { return static_cast(buf); } +inline DoubleQuoteManipWriteBuffer & operator<< (WriteBuffer & buf, DoubleQuoteManip x) { return static_cast(buf); } +inline BinaryManipWriteBuffer & operator<< (WriteBuffer & buf, BinaryManip x) { return static_cast(buf); } template WriteBuffer & operator<< (EscapeManipWriteBuffer & buf, const T & x) { writeText(x, buf); return buf; } template WriteBuffer & operator<< (QuoteManipWriteBuffer & buf, const T & x) { writeQuoted(x, buf); return buf; } template WriteBuffer & operator<< (DoubleQuoteManipWriteBuffer & buf, const T & x) { writeDoubleQuoted(x, buf); return buf; } template WriteBuffer & operator<< (BinaryManipWriteBuffer & buf, const T & x) { writeBinary(x, buf); return buf; } -WriteBuffer & operator<< (EscapeManipWriteBuffer & buf, const char * x) { writeAnyEscapedString<'\''>(x, x + strlen(x), buf); return buf; } -WriteBuffer & operator<< (QuoteManipWriteBuffer & buf, const char * x) { writeAnyQuotedString<'\''>(x, x + strlen(x), buf); return buf; } -WriteBuffer & operator<< (DoubleQuoteManipWriteBuffer & buf, const char * x) { writeAnyQuotedString<'"'>(x, x + strlen(x), buf); return buf; } -WriteBuffer & operator<< (BinaryManipWriteBuffer & buf, const char * x) { writeStringBinary(x, buf); return buf; } +inline WriteBuffer & operator<< (EscapeManipWriteBuffer & buf, const char * x) { writeAnyEscapedString<'\''>(x, x + strlen(x), buf); return buf; } +inline WriteBuffer & operator<< (QuoteManipWriteBuffer & buf, const char * x) { writeAnyQuotedString<'\''>(x, x + strlen(x), buf); return buf; } +inline WriteBuffer & operator<< (DoubleQuoteManipWriteBuffer & buf, const char * x) { writeAnyQuotedString<'"'>(x, x + strlen(x), buf); return buf; } +inline WriteBuffer & operator<< (BinaryManipWriteBuffer & buf, const char * x) { writeStringBinary(x, buf); return buf; } /// Манипулятор вызывает у WriteBuffer метод next - это делает сброс буфера. Для вложенных буферов, сброс не рекурсивный. enum FlushManip { flush }; -WriteBuffer & operator<< (WriteBuffer & buf, FlushManip x) { buf.next(); return buf; } +inline WriteBuffer & operator<< (WriteBuffer & buf, FlushManip x) { buf.next(); return buf; } -template ReadBuffer & operator>> (ReadBuffer & buf, T & x) { readText(x, buf); return buf; } -template <> ReadBuffer & operator>> (ReadBuffer & buf, String & x) { readString(x, buf); return buf; } -template <> ReadBuffer & operator>> (ReadBuffer & buf, char & x) { readChar(x, buf); return buf; } +template ReadBuffer & operator>> (ReadBuffer & buf, T & x) { readText(x, buf); return buf; } +template <> inline ReadBuffer & operator>> (ReadBuffer & buf, String & x) { readString(x, buf); return buf; } +template <> inline ReadBuffer & operator>> (ReadBuffer & buf, char & x) { readChar(x, buf); return buf; } /// Если указать для чтения строковый литерал, то это будет обозначать - убедиться в наличии последовательности байт и пропустить её. -ReadBuffer & operator>> (ReadBuffer & buf, const char * x) { assertString(x, buf); return buf; } +inline ReadBuffer & operator>> (ReadBuffer & buf, const char * x) { assertString(x, buf); return buf; } -EscapeManipReadBuffer & operator>> (ReadBuffer & buf, EscapeManip x) { return static_cast(buf); } -QuoteManipReadBuffer & operator>> (ReadBuffer & buf, QuoteManip x) { return static_cast(buf); } -DoubleQuoteManipReadBuffer & operator>> (ReadBuffer & buf, DoubleQuoteManip x) { return static_cast(buf); } -BinaryManipReadBuffer & operator>> (ReadBuffer & buf, BinaryManip x) { return static_cast(buf); } +inline EscapeManipReadBuffer & operator>> (ReadBuffer & buf, EscapeManip x) { return static_cast(buf); } +inline QuoteManipReadBuffer & operator>> (ReadBuffer & buf, QuoteManip x) { return static_cast(buf); } +inline DoubleQuoteManipReadBuffer & operator>> (ReadBuffer & buf, DoubleQuoteManip x) { return static_cast(buf); } +inline BinaryManipReadBuffer & operator>> (ReadBuffer & buf, BinaryManip x) { return static_cast(buf); } template ReadBuffer & operator>> (EscapeManipReadBuffer & buf, T & x) { readText(x, buf); return buf; } template ReadBuffer & operator>> (QuoteManipReadBuffer & buf, T & x) { readQuoted(x, buf); return buf; } diff --git a/dbms/include/DB/Interpreters/AggregateDescription.h b/dbms/include/DB/Interpreters/AggregateDescription.h new file mode 100644 index 00000000000..49118a6cffa --- /dev/null +++ b/dbms/include/DB/Interpreters/AggregateDescription.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + + +namespace DB +{ + +struct AggregateDescription +{ + AggregateFunctionPtr function; + Array parameters; /// Параметры (параметрической) агрегатной функции. + ColumnNumbers arguments; + Names argument_names; /// Используются, если arguments не заданы. + String column_name; /// Какое имя использовать для столбца со значениями агрегатной функции +}; + +typedef std::vector AggregateDescriptions; + +} diff --git a/dbms/include/DB/Interpreters/AggregationCommon.h b/dbms/include/DB/Interpreters/AggregationCommon.h index f1f56a81aa6..23c092da299 100644 --- a/dbms/include/DB/Interpreters/AggregationCommon.h +++ b/dbms/include/DB/Interpreters/AggregationCommon.h @@ -9,7 +9,6 @@ #include #include #include -#include template <> diff --git a/dbms/include/DB/Interpreters/Aggregator.h b/dbms/include/DB/Interpreters/Aggregator.h index 7530c74c1cb..67606bde4a0 100644 --- a/dbms/include/DB/Interpreters/Aggregator.h +++ b/dbms/include/DB/Interpreters/Aggregator.h @@ -1,23 +1,22 @@ #pragma once -#include -#include - -#include +#include +#include #include +#include -#include -#include #include #include +#include +#include #include -#include - +#include #include #include +#include #include #include @@ -30,29 +29,36 @@ namespace DB { -struct AggregateDescription -{ - AggregateFunctionPtr function; - Array parameters; /// Параметры (параметрической) агрегатной функции. - ColumnNumbers arguments; - Names argument_names; /// Используются, если arguments не заданы. - String column_name; /// Какое имя использовать для столбца со значениями агрегатной функции -}; - -typedef std::vector AggregateDescriptions; - - /** Разные структуры данных, которые могут использоваться для агрегации - * Для эффективности сами данные для агрегации кладутся в пул. + * Для эффективности, сами данные для агрегации кладутся в пул. * Владение данными (состояний агрегатных функций) и пулом - * захватывается позднее - в функции convertToBlock, объектом ColumnAggregateFunction. + * захватывается позднее - в функции convertToBlocks, объектом ColumnAggregateFunction. + * + * Большинство структур данных существует в двух вариантах: обычном и двухуровневом (TwoLevel). + * Двухуровневая хэш-таблица работает чуть медленнее при маленьком количестве различных ключей, + * но при большом количестве различных ключей лучше масштабируется, так как позволяет + * распараллелить некоторые операции (слияние, пост-обработку) естественным образом. + * + * Чтобы обеспечить эффективную работу в большом диапазоне условий, + * сначала используются одноуровневые хэш-таблицы, + * а при достижении количеством различных ключей достаточно большого размера, + * они конвертируются в двухуровневые. + * + * PS. Существует много различных подходов к эффективной реализации параллельной и распределённой агрегации, + * лучшим образом подходящих для разных случаев, и этот подход - всего лишь один из них, выбранный по совокупности причин. */ typedef AggregateDataPtr AggregatedDataWithoutKey; -typedef HashMap AggregatedDataWithUInt64Key; + +typedef HashMap> AggregatedDataWithUInt64Key; typedef HashMapWithSavedHash AggregatedDataWithStringKey; -typedef HashMap AggregatedDataWithKeys128; +typedef HashMap AggregatedDataWithKeys128; typedef HashMap, UInt128TrivialHash> AggregatedDataHashed; +typedef TwoLevelHashMap> AggregatedDataWithUInt64KeyTwoLevel; +typedef TwoLevelHashMapWithSavedHash AggregatedDataWithStringKeyTwoLevel; +typedef TwoLevelHashMap AggregatedDataWithKeys128TwoLevel; +typedef TwoLevelHashMap, UInt128TrivialHash> AggregatedDataHashedTwoLevel; + /// Специализации для UInt8, UInt16. struct TrivialHash @@ -64,7 +70,10 @@ struct TrivialHash } }; -/// Превращает хэш-таблицу в что-то типа lookup-таблицы. Остаётся неоптимальность - в ячейках хранятся ключи. +/** Превращает хэш-таблицу в что-то типа lookup-таблицы. Остаётся неоптимальность - в ячейках хранятся ключи. + * Также компилятору не удаётся полностью удалить код хождения по цепочке разрешения коллизий, хотя он не нужен. + * TODO Переделать в полноценную lookup-таблицу. + */ template struct HashTableFixedGrower { @@ -82,89 +91,11 @@ struct HashTableFixedGrower typedef HashMap> AggregatedDataWithUInt8Key; typedef HashMap> AggregatedDataWithUInt16Key; -template -struct AggregatedDataWithUIntKey -{ - using Type = AggregatedDataWithUInt64Key; - static constexpr bool never_overflows = false; -}; -template <> -struct AggregatedDataWithUIntKey -{ - using Type = AggregatedDataWithUInt8Key; - static constexpr bool never_overflows = true; /// Говорит о том, что в результате агрегации не может быть много записей. -}; +template +inline UInt64 unionCastToUInt64(T x) { return x; } -template <> -struct AggregatedDataWithUIntKey -{ - using Type = AggregatedDataWithUInt16Key; - static constexpr bool never_overflows = true; -}; - - -/// Для случая, когда есть один числовой ключ. -template /// UInt8/16/32/64 для любых типов соответствующей битности. -struct AggregationMethodOneNumber -{ - typedef typename AggregatedDataWithUIntKey::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::never_overflows; - - Data data; - - const FieldType * column; - - /** Вызывается в начале обработки каждого блока. - * Устанавливает переменные, необходимые для остальных методов, вызываемых во внутренних циклах. - */ - void init(ConstColumnPlainPtrs & key_columns) - { - column = &static_cast *>(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 *>(key_columns[0])->insertData(reinterpret_cast(&it->first), sizeof(it->first)); - } - -private: - UInt64 get64(FieldType x) const - { - return x; - } -}; - -template <> -inline UInt64 AggregationMethodOneNumber::get64(Float64 x) const +template <> inline UInt64 unionCastToUInt64(Float64 x) { union { @@ -176,8 +107,7 @@ inline UInt64 AggregationMethodOneNumber::get64(Float64 x) const return res; } -template <> -inline UInt64 AggregationMethodOneNumber::get64(Float32 x) const +template <> inline UInt64 unionCastToUInt64(Float32 x) { union { @@ -191,145 +121,229 @@ inline UInt64 AggregationMethodOneNumber::get64(Float32 x) const } -/// Для случая, когда есть один строковый ключ. -struct AggregationMethodString +/// Для случая, когда есть один числовой ключ. +template /// UInt8/16/32/64 для любых типов соответствующей битности. +struct AggregationMethodOneNumber { - typedef AggregatedDataWithStringKey Data; - typedef Data::key_type Key; - typedef Data::mapped_type Mapped; - typedef Data::iterator iterator; - typedef Data::const_iterator const_iterator; - - static constexpr bool never_overflows = false; + typedef TData Data; + typedef typename Data::key_type Key; + typedef typename Data::mapped_type Mapped; + typedef typename Data::iterator iterator; + typedef typename Data::const_iterator const_iterator; Data data; - const ColumnString::Offsets_t * offsets; - const ColumnString::Chars_t * chars; + AggregationMethodOneNumber() {} - void init(ConstColumnPlainPtrs & key_columns) + template + AggregationMethodOneNumber(const Other & other) : data(other.data) {} + + /// Для использования одного Method в разных потоках, используйте разные State. + struct State + { + const FieldType * vec; + + /** Вызывается в начале обработки каждого блока. + * Устанавливает переменные, необходимые для остальных методов, вызываемых во внутренних циклах. + */ + void init(ConstColumnPlainPtrs & key_columns) + { + vec = &static_cast *>(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) { - const IColumn & column = *key_columns[0]; - const ColumnString & column_string = static_cast(column); - offsets = &column_string.getOffsets(); - chars = &column_string.getChars(); } - Key getKey( - const ConstColumnPlainPtrs & key_columns, - size_t keys_size, - size_t i, - const Sizes & key_sizes, - StringRefs & keys) const + /** Вставить ключ из хэш-таблицы в столбцы. + */ + static void insertKeyIntoColumns(const typename Data::value_type & value, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes) { - return StringRef(&(*chars)[i == 0 ? 0 : (*offsets)[i - 1]], (i == 0 ? (*offsets)[i] : ((*offsets)[i] - (*offsets)[i - 1])) - 1); + static_cast *>(key_columns[0])->insertData(reinterpret_cast(&value.first), sizeof(value.first)); } +}; + + +/// Для случая, когда есть один строковый ключ. +template +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 + AggregationMethodString(const Other & other) : data(other.data) {} + + struct State + { + const ColumnString::Offsets_t * offsets; + const ColumnString::Chars_t * chars; + + void init(ConstColumnPlainPtrs & key_columns) + { + const IColumn & column = *key_columns[0]; + const ColumnString & column_string = static_cast(column); + offsets = &column_string.getOffsets(); + chars = &column_string.getChars(); + } + + Key getKey( + const ConstColumnPlainPtrs & key_columns, + size_t keys_size, + size_t i, + const Sizes & key_sizes, + StringRefs & keys) const + { + return StringRef( + &(*chars)[i == 0 ? 0 : (*offsets)[i - 1]], + (i == 0 ? (*offsets)[i] : ((*offsets)[i] - (*offsets)[i - 1])) - 1); + } + }; static AggregateDataPtr & getAggregateData(Mapped & value) { return value; } static const AggregateDataPtr & getAggregateData(const Mapped & value) { return value; } - static void onNewKey(iterator & it, size_t keys_size, size_t i, StringRefs & keys, Arena & pool) + static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, StringRefs & keys, Arena & pool) { - it->first.data = pool.insert(it->first.data, it->first.size); + value.first.data = pool.insert(value.first.data, value.first.size); } - static void insertKeyIntoColumns(const_iterator & it, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes) + static void insertKeyIntoColumns(const typename Data::value_type & value, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes) { - key_columns[0]->insertData(it->first.data, it->first.size); + key_columns[0]->insertData(value.first.data, value.first.size); } }; /// Для случая, когда есть один строковый ключ фиксированной длины. +template struct AggregationMethodFixedString { - typedef AggregatedDataWithStringKey Data; - typedef Data::key_type Key; - typedef Data::mapped_type Mapped; - typedef Data::iterator iterator; - typedef Data::const_iterator const_iterator; - - static constexpr bool never_overflows = false; + typedef TData Data; + typedef typename Data::key_type Key; + typedef typename Data::mapped_type Mapped; + typedef typename Data::iterator iterator; + typedef typename Data::const_iterator const_iterator; Data data; - size_t n; - const ColumnFixedString::Chars_t * chars; + AggregationMethodFixedString() {} - void init(ConstColumnPlainPtrs & key_columns) - { - const IColumn & column = *key_columns[0]; - const ColumnFixedString & column_string = static_cast(column); - n = column_string.getN(); - chars = &column_string.getChars(); - } + template + AggregationMethodFixedString(const Other & other) : data(other.data) {} - Key getKey( - const ConstColumnPlainPtrs & key_columns, - size_t keys_size, - size_t i, - const Sizes & key_sizes, - StringRefs & keys) const + struct State { - return StringRef(&(*chars)[i * n], n); - } + size_t n; + const ColumnFixedString::Chars_t * chars; + + void init(ConstColumnPlainPtrs & key_columns) + { + const IColumn & column = *key_columns[0]; + const ColumnFixedString & column_string = static_cast(column); + n = column_string.getN(); + chars = &column_string.getChars(); + } + + Key getKey( + const ConstColumnPlainPtrs & key_columns, + size_t keys_size, + size_t i, + const Sizes & key_sizes, + StringRefs & keys) const + { + return StringRef(&(*chars)[i * n], n); + } + }; static AggregateDataPtr & getAggregateData(Mapped & value) { return value; } static const AggregateDataPtr & getAggregateData(const Mapped & value) { return value; } - static void onNewKey(iterator & it, size_t keys_size, size_t i, StringRefs & keys, Arena & pool) + static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, StringRefs & keys, Arena & pool) { - it->first.data = pool.insert(it->first.data, it->first.size); + value.first.data = pool.insert(value.first.data, value.first.size); } - static void insertKeyIntoColumns(const_iterator & it, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes) + static void insertKeyIntoColumns(const typename Data::value_type & value, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes) { - key_columns[0]->insertData(it->first.data, it->first.size); + key_columns[0]->insertData(value.first.data, value.first.size); } }; /// Для случая, когда все ключи фиксированной длины, и они помещаются в 128 бит. +template struct AggregationMethodKeys128 { - typedef AggregatedDataWithKeys128 Data; - typedef Data::key_type Key; - typedef Data::mapped_type Mapped; - typedef Data::iterator iterator; - typedef Data::const_iterator const_iterator; - - static constexpr bool never_overflows = false; + typedef TData Data; + typedef typename Data::key_type Key; + typedef typename Data::mapped_type Mapped; + typedef typename Data::iterator iterator; + typedef typename Data::const_iterator const_iterator; Data data; - void init(ConstColumnPlainPtrs & key_columns) - { - } + AggregationMethodKeys128() {} - Key getKey( - const ConstColumnPlainPtrs & key_columns, - size_t keys_size, - size_t i, - const Sizes & key_sizes, - StringRefs & keys) const + template + AggregationMethodKeys128(const Other & other) : data(other.data) {} + + struct State { - return pack128(i, keys_size, key_columns, key_sizes); - } + void init(ConstColumnPlainPtrs & key_columns) + { + } + + Key getKey( + const ConstColumnPlainPtrs & key_columns, + size_t keys_size, + size_t i, + const Sizes & key_sizes, + StringRefs & keys) const + { + return pack128(i, keys_size, key_columns, key_sizes); + } + }; static AggregateDataPtr & getAggregateData(Mapped & value) { return value; } static const AggregateDataPtr & getAggregateData(const Mapped & value) { return value; } - static void onNewKey(iterator & it, size_t keys_size, size_t i, StringRefs & keys, Arena & pool) + static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, StringRefs & keys, Arena & pool) { } - static void insertKeyIntoColumns(const_iterator & it, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes) + static void insertKeyIntoColumns(const typename Data::value_type & value, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes) { size_t offset = 0; for (size_t i = 0; i < keys_size; ++i) { size_t size = key_sizes[i]; - key_columns[i]->insertData(reinterpret_cast(&it->first) + offset, size); + key_columns[i]->insertData(reinterpret_cast(&value.first) + offset, size); offset += size; } } @@ -337,44 +351,51 @@ struct AggregationMethodKeys128 /// Для остальных случаев. Агрегирует по 128 битному хэшу от ключа. (При этом, строки, содержащие нули посередине, могут склеиться.) +template struct AggregationMethodHashed { - typedef AggregatedDataHashed Data; - typedef Data::key_type Key; - typedef Data::mapped_type Mapped; - typedef Data::iterator iterator; - typedef Data::const_iterator const_iterator; - - static constexpr bool never_overflows = false; + typedef TData Data; + typedef typename Data::key_type Key; + typedef typename Data::mapped_type Mapped; + typedef typename Data::iterator iterator; + typedef typename Data::const_iterator const_iterator; Data data; - void init(ConstColumnPlainPtrs & key_columns) - { - } + AggregationMethodHashed() {} - Key getKey( - const ConstColumnPlainPtrs & key_columns, - size_t keys_size, - size_t i, - const Sizes & key_sizes, - StringRefs & keys) const + template + AggregationMethodHashed(const Other & other) : data(other.data) {} + + struct State { - return hash128(i, keys_size, key_columns, keys); - } + void init(ConstColumnPlainPtrs & key_columns) + { + } + + Key getKey( + const ConstColumnPlainPtrs & key_columns, + size_t keys_size, + size_t i, + const Sizes & key_sizes, + StringRefs & keys) const + { + return hash128(i, keys_size, key_columns, keys); + } + }; static AggregateDataPtr & getAggregateData(Mapped & value) { return value.second; } static const AggregateDataPtr & getAggregateData(const Mapped & value) { return value.second; } - static void onNewKey(iterator & it, size_t keys_size, size_t i, StringRefs & keys, Arena & pool) + static void onNewKey(typename Data::value_type & value, size_t keys_size, size_t i, StringRefs & keys, Arena & pool) { - it->second.first = placeKeysInPool(i, keys_size, keys, pool); + value.second.first = placeKeysInPool(i, keys_size, keys, pool); } - static void insertKeyIntoColumns(const_iterator & it, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes) + static void insertKeyIntoColumns(const typename Data::value_type & value, ColumnPlainPtrs & key_columns, size_t keys_size, const Sizes & key_sizes) { for (size_t i = 0; i < keys_size; ++i) - key_columns[i]->insertDataWithTerminatingZero(it->second.first[i].data, it->second.first[i].size); + key_columns[i]->insertDataWithTerminatingZero(value.second.first[i].data, value.second.first[i].size); } }; @@ -386,9 +407,10 @@ struct AggregatedDataVariants : private boost::noncopyable /** Работа с состояниями агрегатных функций в пуле устроена следующим (неудобным) образом: * - при агрегации, состояния создаются в пуле с помощью функции IAggregateFunction::create (внутри - placement new произвольной структуры); * - они должны быть затем уничтожены с помощью IAggregateFunction::destroy (внутри - вызов деструктора произвольной структуры); - * - если агрегация завершена, то, в функции Aggregator::convertToBlock, указатели на состояния агрегатных функций + * - если агрегация завершена, то, в функции Aggregator::convertToBlocks, указатели на состояния агрегатных функций * записываются в ColumnAggregateFunction; ColumnAggregateFunction "захватывает владение" ими, то есть - вызывает destroy в своём деструкторе. - * - если при агрегации, до вызова Aggregator::convertToBlock вылетело исключение, то состояния агрегатных функций всё-равно должны быть уничтожены, + * - если при агрегации, до вызова Aggregator::convertToBlocks вылетело исключение, + * то состояния агрегатных функций всё-равно должны быть уничтожены, * иначе для сложных состояний (наприемер, AggregateFunctionUniq), будут утечки памяти; * - чтобы, в этом случае, уничтожить состояния, в деструкторе вызывается метод Aggregator::destroyAggregateStates, * но только если переменная aggregator (см. ниже) не nullptr; @@ -412,32 +434,53 @@ struct AggregatedDataVariants : private boost::noncopyable */ AggregatedDataWithoutKey without_key = nullptr; - std::unique_ptr> key8; - std::unique_ptr> key16; - std::unique_ptr> key32; - std::unique_ptr> key64; - std::unique_ptr key_string; - std::unique_ptr key_fixed_string; - std::unique_ptr keys128; - std::unique_ptr hashed; + std::unique_ptr> key8; + std::unique_ptr> key16; - enum Type + std::unique_ptr> key32; + std::unique_ptr> key64; + std::unique_ptr> key_string; + std::unique_ptr> key_fixed_string; + std::unique_ptr> keys128; + std::unique_ptr> hashed; + + std::unique_ptr> key32_two_level; + std::unique_ptr> key64_two_level; + std::unique_ptr> key_string_two_level; + std::unique_ptr> key_fixed_string_two_level; + std::unique_ptr> keys128_two_level; + std::unique_ptr> hashed_two_level; + + /// В этом и подобных макросах, вариант without_key не учитывается. + #define APPLY_FOR_AGGREGATED_VARIANTS(M) \ + M(key8, false) \ + M(key16, false) \ + M(key32, false) \ + M(key64, false) \ + M(key_string, false) \ + M(key_fixed_string, false) \ + M(keys128, false) \ + M(hashed, false) \ + M(key32_two_level, true) \ + M(key64_two_level, true) \ + M(key_string_two_level, true) \ + M(key_fixed_string_two_level, true) \ + M(keys128_two_level, true) \ + M(hashed_two_level, true) + + enum class Type { EMPTY = 0, - WITHOUT_KEY, - KEY_8, - KEY_16, - KEY_32, - KEY_64, - KEY_STRING, - KEY_FIXED_STRING, - KEYS_128, - HASHED, + without_key, + + #define M(NAME, IS_TWO_LEVEL) NAME, + APPLY_FOR_AGGREGATED_VARIANTS(M) + #undef M }; - Type type = EMPTY; + Type type = Type::EMPTY; AggregatedDataVariants() : aggregates_pools(1, new Arena), aggregates_pool(&*aggregates_pools.back()) {} - bool empty() const { return type == EMPTY; } + bool empty() const { return type == Type::EMPTY; } ~AggregatedDataVariants(); @@ -447,16 +490,13 @@ struct AggregatedDataVariants : private boost::noncopyable switch (type) { - case EMPTY: break; - case WITHOUT_KEY: break; - case KEY_8: key8 .reset(new decltype(key8)::element_type); break; - case KEY_16: key16 .reset(new decltype(key16)::element_type); break; - case KEY_32: key32 .reset(new decltype(key32)::element_type); break; - case KEY_64: key64 .reset(new decltype(key64)::element_type); break; - case KEY_STRING: key_string .reset(new decltype(key_string)::element_type); break; - case KEY_FIXED_STRING: key_fixed_string.reset(new decltype(key_fixed_string)::element_type); break; - case KEYS_128: keys128 .reset(new decltype(keys128)::element_type); break; - case HASHED: hashed .reset(new decltype(hashed)::element_type); break; + case Type::EMPTY: break; + case Type::without_key: break; + + #define M(NAME, IS_TWO_LEVEL) \ + case Type::NAME: NAME.reset(new decltype(NAME)::element_type); break; + APPLY_FOR_AGGREGATED_VARIANTS(M) + #undef M default: throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); @@ -467,16 +507,13 @@ struct AggregatedDataVariants : private boost::noncopyable { switch (type) { - case EMPTY: return 0; - case WITHOUT_KEY: return 1; - case KEY_8: return key8->data.size() + (without_key != nullptr); - case KEY_16: return key16->data.size() + (without_key != nullptr); - case KEY_32: return key32->data.size() + (without_key != nullptr); - case KEY_64: return key64->data.size() + (without_key != nullptr); - case KEY_STRING: return key_string->data.size() + (without_key != nullptr); - case KEY_FIXED_STRING: return key_fixed_string->data.size() + (without_key != nullptr); - case KEYS_128: return keys128->data.size() + (without_key != nullptr); - case HASHED: return hashed->data.size() + (without_key != nullptr); + case Type::EMPTY: return 0; + case Type::without_key: return 1; + + #define M(NAME, IS_TWO_LEVEL) \ + case Type::NAME: return NAME->data.size() + (without_key != nullptr); + APPLY_FOR_AGGREGATED_VARIANTS(M) + #undef M default: throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); @@ -487,38 +524,100 @@ struct AggregatedDataVariants : private boost::noncopyable { switch (type) { - case EMPTY: return "EMPTY"; - case WITHOUT_KEY: return "WITHOUT_KEY"; - case KEY_8: return "KEY_8"; - case KEY_16: return "KEY_16"; - case KEY_32: return "KEY_32"; - case KEY_64: return "KEY_64"; - case KEY_STRING: return "KEY_STRING"; - case KEY_FIXED_STRING: return "KEY_FIXED_STRING"; - case KEYS_128: return "KEYS_128"; - case HASHED: return "HASHED"; + case Type::EMPTY: return "EMPTY"; + case Type::without_key: return "without_key"; + + #define M(NAME, IS_TWO_LEVEL) \ + case Type::NAME: return #NAME; + APPLY_FOR_AGGREGATED_VARIANTS(M) + #undef M default: throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); } } + + bool isTwoLevel() const + { + switch (type) + { + case Type::EMPTY: return false; + case Type::without_key: return false; + + #define M(NAME, IS_TWO_LEVEL) \ + case Type::NAME: return IS_TWO_LEVEL; + APPLY_FOR_AGGREGATED_VARIANTS(M) + #undef M + + default: + throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); + } + } + + #define APPLY_FOR_VARIANTS_CONVERTIBLE_TO_TWO_LEVEL(M) \ + M(key32) \ + M(key64) \ + M(key_string) \ + M(key_fixed_string) \ + M(keys128) \ + M(hashed) + + #define APPLY_FOR_VARIANTS_NOT_CONVERTIBLE_TO_TWO_LEVEL(M) \ + M(key8) \ + M(key16) \ + + bool isConvertibleToTwoLevel() const + { + switch (type) + { + #define M(NAME) \ + case Type::NAME: return true; + + APPLY_FOR_VARIANTS_CONVERTIBLE_TO_TWO_LEVEL(M) + + #undef M + default: + return false; + } + } + + void convertToTwoLevel(); + + #define APPLY_FOR_VARIANTS_TWO_LEVEL(M) \ + M(key32_two_level) \ + M(key64_two_level) \ + M(key_string_two_level) \ + M(key_fixed_string_two_level) \ + M(keys128_two_level) \ + M(hashed_two_level) }; typedef SharedPtr AggregatedDataVariantsPtr; typedef std::vector ManyAggregatedDataVariants; +/** Достать вариант агрегации по его типу. */ +template Method & getDataVariant(AggregatedDataVariants & variants); + +#define M(NAME, IS_TWO_LEVEL) \ + template <> inline decltype(AggregatedDataVariants::NAME)::element_type & getDataVariant(AggregatedDataVariants & variants) { return *variants.NAME; } + +APPLY_FOR_AGGREGATED_VARIANTS(M) + +#undef M + + /** Агрегирует источник блоков. */ class Aggregator { public: Aggregator(const ColumnNumbers & keys_, const AggregateDescriptions & aggregates_, bool overflow_row_, - size_t max_rows_to_group_by_ = 0, OverflowMode group_by_overflow_mode_ = OverflowMode::THROW) + size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_, Compiler * compiler_, UInt32 min_count_to_compile_) : keys(keys_), aggregates(aggregates_), aggregates_size(aggregates.size()), overflow_row(overflow_row_), max_rows_to_group_by(max_rows_to_group_by_), group_by_overflow_mode(group_by_overflow_mode_), - log(&Logger::get("Aggregator")) + compiler(compiler_), min_count_to_compile(min_count_to_compile_) { std::sort(keys.begin(), keys.end()); keys.erase(std::unique(keys.begin(), keys.end()), keys.end()); @@ -526,11 +625,11 @@ public: } Aggregator(const Names & key_names_, const AggregateDescriptions & aggregates_, bool overflow_row_, - size_t max_rows_to_group_by_ = 0, OverflowMode group_by_overflow_mode_ = OverflowMode::THROW) + size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_, Compiler * compiler_, UInt32 min_count_to_compile_) : key_names(key_names_), aggregates(aggregates_), aggregates_size(aggregates.size()), overflow_row(overflow_row_), max_rows_to_group_by(max_rows_to_group_by_), group_by_overflow_mode(group_by_overflow_mode_), - log(&Logger::get("Aggregator")) + compiler(compiler_), min_count_to_compile(min_count_to_compile_) { std::sort(key_names.begin(), key_names.end()); key_names.erase(std::unique(key_names.begin(), key_names.end()), key_names.end()); @@ -540,8 +639,9 @@ public: /// Агрегировать источник. Получить результат в виде одной из структур данных. void execute(BlockInputStreamPtr stream, AggregatedDataVariants & result); - typedef std::vector AggregateColumns; - typedef std::vector AggregateColumnsData; + using AggregateColumns = std::vector; + using AggregateColumnsData = std::vector; + using AggregateFunctionsPlainPtrs = std::vector; /// Обработать один блок. Вернуть false, если обработку следует прервать (при group_by_overflow_mode = 'break'). bool executeOnBlock(Block & block, AggregatedDataVariants & result, @@ -550,25 +650,25 @@ public: bool & no_more_keys); /** Преобразовать структуру данных агрегации в блок. - * Если overflow_row = true, то агрегаты для строк, не попавших в max_rows_to_group_by, кладутся в первую строчку возвращаемого блока. + * Если overflow_row = true, то агрегаты для строк, не попавших в max_rows_to_group_by, кладутся в первый блок. * * Если final = false, то в качестве столбцов-агрегатов создаются ColumnAggregateFunction с состоянием вычислений, * которые могут быть затем объединены с другими состояниями (для распределённой обработки запроса). * Если final = true, то в качестве столбцов-агрегатов создаются столбцы с готовыми значениями. */ - Block convertToBlock(AggregatedDataVariants & data_variants, bool final); + BlocksList convertToBlocks(AggregatedDataVariants & data_variants, bool final, size_t max_threads); - /** Объединить несколько структур данных агрегации в одну. (В первый непустой элемент массива.) Все варианты агрегации должны быть одинаковыми! - * После объединения, все стркутуры агрегации (а не только те, в которую они будут слиты) должны жить, пока не будет вызвана функция convertToBlock. + /** Объединить несколько структур данных агрегации в одну. (В первый непустой элемент массива.) + * После объединения, все стркутуры агрегации (а не только те, в которую они будут слиты) должны жить, + * пока не будет вызвана функция convertToBlocks. * Это нужно, так как в слитом результате могут остаться указатели на память в пуле, которым владеют другие структуры агрегации. */ - AggregatedDataVariantsPtr merge(ManyAggregatedDataVariants & data_variants); + AggregatedDataVariantsPtr merge(ManyAggregatedDataVariants & data_variants, size_t max_threads); /** Объединить несколько агрегированных блоков в одну структуру данных. * (Доагрегировать несколько блоков, которые представляют собой результат независимых агрегаций с удалённых серверов.) - * Если overflow_row = true, то предполагается, что агрегаты для строк, не попавших в max_rows_to_group_by, расположены в первой строке каждого блока. */ - void merge(BlockInputStreamPtr stream, AggregatedDataVariants & result); + void mergeStream(BlockInputStreamPtr stream, AggregatedDataVariants & result, size_t max_threads); /// Для IBlockInputStream. String getID() const; @@ -579,7 +679,7 @@ protected: ColumnNumbers keys; Names key_names; AggregateDescriptions aggregates; - std::vector aggregate_functions; + AggregateFunctionsPlainPtrs aggregate_functions; size_t keys_size; size_t aggregates_size; /// Нужно ли класть в AggregatedDataVariants::without_key агрегаты для ключей, не попавших в max_rows_to_group_by. @@ -591,14 +691,40 @@ protected: /// Для инициализации от первого блока при конкуррентном использовании. bool initialized = false; - Poco::FastMutex mutex; + std::mutex mutex; size_t max_rows_to_group_by; OverflowMode group_by_overflow_mode; Block sample; - Logger * log; + Logger * log = &Logger::get("Aggregator"); + + + /** Для динамической компиляции, если предусмотрено. */ + Compiler * compiler = nullptr; + UInt32 min_count_to_compile; + + /** Динамически скомпилированная библиотека для агрегации, если есть. + * Смысл динамической компиляции в том, чтобы специализировать код + * под конкретный список агрегатных функций. + * Это позволяет развернуть цикл по созданию и обновлению состояний агрегатных функций, + * а также использовать вместо виртуальных вызовов inline-код. + */ + struct CompiledData + { + SharedLibraryPtr compiled_aggregator; + + /// Получены с помощью dlsym. Нужно ещё сделать reinterpret_cast в указатель на функцию. + const void * compiled_method_ptr = nullptr; + const void * compiled_two_level_method_ptr = nullptr; + }; + /// shared_ptr - чтобы передавать в callback, который может пережить Aggregator. + std::shared_ptr compiled_data { new CompiledData }; + + bool compiled_if_possible = false; + void compileIfPossible(AggregatedDataVariants::Type type); + /** Если заданы только имена столбцов (key_names, а также aggregates[i].column_name), то вычислить номера столбцов. * Сформировать блок - пример результата. @@ -618,6 +744,7 @@ protected: void destroyAllAggregateStates(AggregatedDataVariants & result); + /// Обработать один блок данных, агрегировать данные в хэш-таблицу. template void executeImpl( Method & method, @@ -630,30 +757,132 @@ protected: bool no_more_keys, AggregateDataPtr overflow_row) const; + /// Специализация для конкретного значения no_more_keys. + template + 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 + 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 + 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 + void executeSpecializedWithoutKey( + AggregatedDataWithoutKey & res, + size_t rows, + AggregateColumns & aggregate_columns) const; + +protected: + /// Слить данные из хэш-таблицы src в dst. + template + void mergeDataImpl( + Table & table_dst, + Table & table_src) const; + + void mergeWithoutKeyDataImpl( + ManyAggregatedDataVariants & non_empty_data) const; + template + void mergeSingleLevelDataImpl( + ManyAggregatedDataVariants & non_empty_data) const; + + template + void mergeTwoLevelDataImpl( + ManyAggregatedDataVariants & many_data, + boost::threadpool::pool * thread_pool) const; + + template void convertToBlockImpl( Method & method, + Table & data, ColumnPlainPtrs & key_columns, AggregateColumnsData & aggregate_columns, ColumnPlainPtrs & final_aggregate_columns, const Sizes & key_sizes, - size_t start_row, bool final) const; + bool final) const; - template - void mergeDataImpl( - Method & method_dst, - Method & method_src) const; - - template - void mergeStreamsImpl( + template + void convertToBlockImplFinal( Method & method, - Arena * aggregates_pool, - size_t start_row, - size_t rows, - ConstColumnPlainPtrs & key_columns, + Table & data, + ColumnPlainPtrs & key_columns, + ColumnPlainPtrs & final_aggregate_columns, + const Sizes & key_sizes) const; + + template + void convertToBlockImplNotFinal( + Method & method, + Table & data, + ColumnPlainPtrs & key_columns, AggregateColumnsData & aggregate_columns, - const Sizes & key_sizes, - StringRefs & keys) const; + const Sizes & key_sizes) const; + + template + 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 + BlocksList prepareBlocksAndFillTwoLevelImpl( + AggregatedDataVariants & data_variants, + Method & method, + bool final, + boost::threadpool::pool * thread_pool) const; + + template + void mergeStreamsImpl( + Block & block, + AggregatedDataVariants & result, + Arena * aggregates_pool, + Method & method, + Table & data) const; + + void mergeWithoutKeyStreamsImpl( + Block & block, + AggregatedDataVariants & result) const; template void destroyImpl( diff --git a/dbms/include/DB/Interpreters/Cluster.h b/dbms/include/DB/Interpreters/Cluster.h index 9766cadbc0e..d3b28e07a2c 100644 --- a/dbms/include/DB/Interpreters/Cluster.h +++ b/dbms/include/DB/Interpreters/Cluster.h @@ -18,8 +18,8 @@ class Cluster : private boost::noncopyable public: Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, const String & cluster_name); - /// Построить кластер по именам шардов и реплик, локальные обрабатываются так же как удаленные - Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, std::vector< std::vector > names, + /// Построить кластер по именам шардов и реплик. Локальные обрабатываются так же как удаленные. + Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, std::vector> names, const String & username, const String & password); /// количество узлов clickhouse сервера, расположенных локально @@ -80,7 +80,7 @@ private: Addresses addresses; AddressesWithFailover addresses_with_failover; - size_t local_nodes_num; + size_t local_nodes_num = 0; }; struct Clusters diff --git a/dbms/include/DB/Interpreters/Compiler.h b/dbms/include/DB/Interpreters/Compiler.h new file mode 100644 index 00000000000..c3a1d0dccfd --- /dev/null +++ b/dbms/include/DB/Interpreters/Compiler.h @@ -0,0 +1,119 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +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 + Func get(const std::string & name) + { + dlerror(); + + Func res = reinterpret_cast(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; + + +/** Позволяет скомпилировать кусок кода, использующий заголовочные файлы сервера, в динамическую библиотеку. + * Ведёт статистику вызовов, и инициирует компиляцию только на N-ый по счёту вызов для одного ключа. + * Компиляция выполняется асинхронно, в отдельных потоках, если есть свободные потоки. + * NOTE: Нет очистки устаревших и ненужных результатов. + */ +class Compiler +{ +public: + /** path - путь к директории с временными файлами - результатами компиляции. + * Результаты компиляции сохраняются при перезапуске сервера, + * но используют в качестве части ключа номер ревизии. То есть, устаревают при обновлении сервера. + */ + Compiler(const std::string & path_, size_t threads); + ~Compiler(); + + using HashedKey = UInt128; + + using CodeGenerator = std::function; + using ReadyCallback = std::function; + + /** Увеличить счётчик для заданного ключа 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; + using Libraries = std::unordered_map; + using Files = std::unordered_set; + + 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); +}; + +} diff --git a/dbms/include/DB/Interpreters/Context.h b/dbms/include/DB/Interpreters/Context.h index 9cd96ae0526..c3f08b52fcd 100644 --- a/dbms/include/DB/Interpreters/Context.h +++ b/dbms/include/DB/Interpreters/Context.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -48,8 +49,8 @@ typedef std::map Databases; /// (имя базы данных, имя таблицы) typedef std::pair DatabaseAndTableName; -/// таблица -> множество таблиц-вьюшек, которые селектят из нее -typedef std::map > ViewDependencies; +/// Таблица -> множество таблиц-представлений, которые деляют SELECT из неё. +typedef std::map> ViewDependencies; typedef std::vector Dependencies; /** Набор известных объектов, которые могут быть использованы в запросе. @@ -80,6 +81,7 @@ struct ContextShared int interserver_io_port; /// и порт, String path; /// Путь к директории с данными, со слешем на конце. + String tmp_path; /// Путь ко временным файлам, возникающим при обработке запроса. Databases databases; /// Список БД и таблиц в них. TableFunctionFactory table_function_factory; /// Табличные функции. AggregateFunctionFactory aggregate_function_factory; /// Агрегатные функции. @@ -98,6 +100,7 @@ struct ContextShared InterserverIOHandler interserver_io_handler; /// Обработчик для межсерверной передачи данных. BackgroundProcessingPoolPtr background_pool; /// Пул потоков для фоновой работы, выполняемой таблицами. Macros macros; /// Подстановки из конфига. + std::unique_ptr compiler; /// Для динамической компиляции частей запроса, при необходимости. /// Кластеры для distributed таблиц /// Создаются при создании Distributed таблиц, так как нужно дождаться пока будут выставлены Settings @@ -187,7 +190,9 @@ private: public: String getPath() const; + String getTemporaryPath() const; void setPath(const String & path); + void setTemporaryPath(const String & path); /** Забрать список пользователей, квот и профилей настроек из этого конфига. * Список пользователей полностью заменяется. @@ -206,7 +211,7 @@ public: void addDependency(const DatabaseAndTableName & from, const DatabaseAndTableName & where); void removeDependency(const DatabaseAndTableName & from, const DatabaseAndTableName & where); - Dependencies getDependencies(const DatabaseAndTableName & from) const; + Dependencies getDependencies(const String & database_name, const String & table_name) const; /// Проверка существования таблицы/БД. database может быть пустой - в этом случае используется текущая БД. bool isTableExist(const String & database_name, const String & table_name) const; @@ -331,6 +336,8 @@ public: void initClusters(); Cluster & getCluster(const std::string & cluster_name); + Compiler & getCompiler(); + void shutdown() { shared->shutdown(); } }; diff --git a/dbms/include/DB/Interpreters/ExpressionAnalyzer.h b/dbms/include/DB/Interpreters/ExpressionAnalyzer.h index 22aa6b900c2..0f49c5f2339 100644 --- a/dbms/include/DB/Interpreters/ExpressionAnalyzer.h +++ b/dbms/include/DB/Interpreters/ExpressionAnalyzer.h @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include diff --git a/dbms/include/DB/Interpreters/InterpreterSelectQuery.h b/dbms/include/DB/Interpreters/InterpreterSelectQuery.h index c00347b10eb..664a0d2159f 100644 --- a/dbms/include/DB/Interpreters/InterpreterSelectQuery.h +++ b/dbms/include/DB/Interpreters/InterpreterSelectQuery.h @@ -15,124 +15,140 @@ namespace DB class InterpreterSelectQuery { public: - /** to_stage - * - стадия, до которой нужно выполнить запрос. По-умолчанию - до конца. - * Можно выполнить до промежуточного состояния агрегации, которые объединяются с разных серверов при распределённой обработке запроса. - * - * subquery_depth - * - для контроля ограничений на глубину вложенности подзапросов. Для подзапросов передаётся значение, увеличенное на единицу. - * - * input - * - если задан - читать не из таблицы, указанной в запросе, а из готового источника. - * - * required_column_names - * - удалить из запроса все столбцы кроме указанных - используется для удаления ненужных столбцов из подзапросов. - * - * table_column_names - * - поместить в контекст в качестве известных столбцов только указанные столбцы, а не все столбцы таблицы. - * Используется, например, совместно с указанием input. - */ + /** to_stage + * - стадия, до которой нужно выполнить запрос. По-умолчанию - до конца. + * Можно выполнить до промежуточного состояния агрегации, которые объединяются с разных серверов при распределённой обработке запроса. + * + * subquery_depth + * - для контроля ограничений на глубину вложенности подзапросов. Для подзапросов передаётся значение, увеличенное на единицу. + * + * input + * - если задан - читать не из таблицы, указанной в запросе, а из готового источника. + * + * required_column_names + * - удалить из запроса все столбцы кроме указанных - используется для удаления ненужных столбцов из подзапросов. + * + * table_column_names + * - поместить в контекст в качестве известных столбцов только указанные столбцы, а не все столбцы таблицы. + * Используется, например, совместно с указанием input. + */ - InterpreterSelectQuery( - ASTPtr query_ptr_, - const Context & context_, - QueryProcessingStage::Enum to_stage_ = QueryProcessingStage::Complete, - size_t subquery_depth_ = 0, - BlockInputStreamPtr input = nullptr, - bool is_union_all_head_ = true); + InterpreterSelectQuery( + ASTPtr query_ptr_, + const Context & context_, + QueryProcessingStage::Enum to_stage_ = QueryProcessingStage::Complete, + size_t subquery_depth_ = 0, + BlockInputStreamPtr input = nullptr, + bool is_union_all_head_ = true); - InterpreterSelectQuery( - ASTPtr query_ptr_, - const Context & context_, - const Names & required_column_names, - QueryProcessingStage::Enum to_stage_ = QueryProcessingStage::Complete, - size_t subquery_depth_ = 0, - BlockInputStreamPtr input = nullptr); + InterpreterSelectQuery( + ASTPtr query_ptr_, + const Context & context_, + const Names & required_column_names, + QueryProcessingStage::Enum to_stage_ = QueryProcessingStage::Complete, + size_t subquery_depth_ = 0, + BlockInputStreamPtr input = nullptr); - InterpreterSelectQuery( - ASTPtr query_ptr_, - const Context & context_, - const Names & required_column_names, - const NamesAndTypesList & table_column_names, - QueryProcessingStage::Enum to_stage_ = QueryProcessingStage::Complete, - size_t subquery_depth_ = 0, - BlockInputStreamPtr input = nullptr); + InterpreterSelectQuery( + ASTPtr query_ptr_, + const Context & context_, + const Names & required_column_names, + const NamesAndTypesList & table_column_names, + QueryProcessingStage::Enum to_stage_ = QueryProcessingStage::Complete, + size_t subquery_depth_ = 0, + BlockInputStreamPtr input = nullptr); - /** Выполнить запрос, возможно являющиийся цепочкой UNION ALL. - * Получить поток блоков для чтения - */ - BlockInputStreamPtr execute(); + /** Выполнить запрос, возможно являющиийся цепочкой UNION ALL. + * Получить поток блоков для чтения + */ + BlockInputStreamPtr execute(); - /** Выполнить запрос, записать результат в нужном формате в buf. - * BlockInputStreamPtr возвращается, чтобы можно было потом получить информацию о плане выполнения запроса. - */ - BlockInputStreamPtr executeAndFormat(WriteBuffer & buf); + /// Выполнить запрос без объединения потоков. + const BlockInputStreams & executeWithoutUnion(); - DataTypes getReturnTypes(); - Block getSampleBlock(); + /** Выполнить запрос, записать результат в нужном формате в buf. + * BlockInputStreamPtr возвращается, чтобы можно было потом получить информацию о плане выполнения запроса. + */ + BlockInputStreamPtr executeAndFormat(WriteBuffer & buf); + + DataTypes getReturnTypes(); + Block getSampleBlock(); private: - typedef Poco::SharedPtr 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 initQueryAnalyzer(); - void init(BlockInputStreamPtr input, const NamesAndTypesList & table_column_names = NamesAndTypesList()); + /// Выполнить один запрос SELECT из цепочки UNION ALL. + void executeSingleQuery(); - /// Выполнить один запрос SELECT из цепочки UNION ALL. - void executeSingleQuery(bool should_perform_union_hint = true); + /** Оставить в каждом запросе цепочки UNION ALL только нужные столбцы секции SELECT. + * Однако, если используется хоть один DISTINCT в цепочке, то все столбцы считаются нужными, + * так как иначе DISTINCT работал бы по-другому. + */ + void rewriteExpressionList(const Names & required_column_names); - /// Является ли это первым запросом цепочки UNION ALL имеющей длниу >= 2. - bool isFirstSelectInsideUnionAll() const; + /// Содержит ли запрос хотя бы один астериск? + bool hasAsterisk() const; - /** Из какой таблицы читать. JOIN-ы не поддерживаются. - */ - void getDatabaseAndTableNames(String & database_name, String & table_name); + // Переименовать столбцы каждого запроса цепочки UNION ALL в такие же имена, как в первом запросе. + void renameColumns(); - /** Выбрать из списка столбцов какой-нибудь, лучше - минимального размера. - */ - String getAnyColumn(); + /// Является ли это первым запросом цепочки UNION ALL имеющей длниу >= 2. + bool isFirstSelectInsideUnionAll() const; - /// Разные стадии выполнения запроса. + /** Из какой таблицы читать. JOIN-ы не поддерживаются. + */ + void getDatabaseAndTableNames(String & database_name, String & table_name); - /// Вынимает данные из таблицы. Возвращает стадию, до которой запрос был обработан в Storage. - QueryProcessingStage::Enum executeFetchColumns(BlockInputStreams & streams); + /** Выбрать из списка столбцов какой-нибудь, лучше - минимального размера. + */ + String getAnyColumn(); - void executeWhere( BlockInputStreams & streams, ExpressionActionsPtr expression); - void executeAggregation( BlockInputStreams & streams, ExpressionActionsPtr expression, - bool overflow_row, bool final); - void executeMergeAggregated( BlockInputStreams & streams, bool overflow_row, bool final); - void executeTotalsAndHaving( BlockInputStreams & streams, bool has_having, ExpressionActionsPtr expression, - bool overflow_row); - void executeHaving( BlockInputStreams & streams, ExpressionActionsPtr expression); - void executeExpression( BlockInputStreams & streams, ExpressionActionsPtr expression); - void executeOrder( BlockInputStreams & streams); - void executePreLimit( BlockInputStreams & streams); - void executeUnion( BlockInputStreams & streams); - void executeLimit( BlockInputStreams & streams); - void executeProjection( BlockInputStreams & streams, ExpressionActionsPtr expression); - void executeDistinct( BlockInputStreams & streams, bool before_order, Names columns); - void executeSubqueriesInSetsAndJoins(BlockInputStreams & streams, SubqueriesForSets & subqueries_for_sets); + /// Разные стадии выполнения запроса. - ASTPtr query_ptr; - ASTSelectQuery & query; - Context context; - Settings settings; - QueryProcessingStage::Enum to_stage; - size_t subquery_depth; - ExpressionAnalyzerPtr query_analyzer; - BlockInputStreams streams; - - /** Цепочка UNION ALL может иметь длину 1 (в таком случае имеется просто один запрос SELECT) - * или больше. Этот флаг установлен, если это первый запрос, возможно единственный, этой цепочки. - */ - bool is_union_all_head; + /// Вынимает данные из таблицы. Возвращает стадию, до которой запрос был обработан в Storage. + QueryProcessingStage::Enum executeFetchColumns(BlockInputStreams & streams); - /// Следующий запрос SELECT в цепочке UNION ALL. - std::unique_ptr next_select_in_union_all; - - /// Таблица, откуда читать данные, если не подзапрос. - StoragePtr storage; - IStorage::TableStructureReadLockPtr table_lock; + void executeWhere( BlockInputStreams & streams, ExpressionActionsPtr expression); + void executeAggregation( BlockInputStreams & streams, ExpressionActionsPtr expression, + bool overflow_row, bool final); + void executeMergeAggregated( BlockInputStreams & streams, bool overflow_row, bool final); + void executeTotalsAndHaving( BlockInputStreams & streams, bool has_having, ExpressionActionsPtr expression, + bool overflow_row); + void executeHaving( BlockInputStreams & streams, ExpressionActionsPtr expression); + void executeExpression( BlockInputStreams & streams, ExpressionActionsPtr expression); + void executeOrder( BlockInputStreams & streams); + void executePreLimit( BlockInputStreams & streams); + void executeUnion( BlockInputStreams & streams); + void executeLimit( BlockInputStreams & streams); + void executeProjection( BlockInputStreams & streams, ExpressionActionsPtr expression); + void executeDistinct( BlockInputStreams & streams, bool before_order, Names columns); + void executeSubqueriesInSetsAndJoins(BlockInputStreams & streams, SubqueriesForSets & subqueries_for_sets); - Logger * log; + ASTPtr query_ptr; + ASTSelectQuery & query; + Context context; + Settings settings; + size_t original_max_threads; /// В settings настройка max_threads может быть изменена. В original_max_threads сохраняется изначальное значение. + QueryProcessingStage::Enum to_stage; + size_t subquery_depth; + std::unique_ptr query_analyzer; + BlockInputStreams streams; + + /** Цепочка UNION ALL может иметь длину 1 (в таком случае имеется просто один запрос SELECT) + * или больше. Этот флаг установлен, если это первый запрос, возможно единственный, этой цепочки. + */ + bool is_union_all_head; + + /// Следующий запрос SELECT в цепочке UNION ALL. + std::unique_ptr next_select_in_union_all; + + /// Таблица, откуда читать данные, если не подзапрос. + StoragePtr storage; + IStorage::TableStructureReadLockPtr table_lock; + + Logger * log; }; } diff --git a/dbms/include/DB/Interpreters/Join.h b/dbms/include/DB/Interpreters/Join.h index 4a0f562c0e1..47901cef988 100644 --- a/dbms/include/DB/Interpreters/Join.h +++ b/dbms/include/DB/Interpreters/Join.h @@ -146,7 +146,7 @@ private: /** Блоки данных таблицы, с которой идёт соединение. */ - Blocks blocks; + BlocksList blocks; MapsAny maps_any; MapsAll maps_all; diff --git a/dbms/include/DB/Interpreters/Limits.h b/dbms/include/DB/Interpreters/Limits.h index 6ab1a07a201..6f6ee03b483 100644 --- a/dbms/include/DB/Interpreters/Limits.h +++ b/dbms/include/DB/Interpreters/Limits.h @@ -36,6 +36,7 @@ struct Limits M(SettingUInt64, max_rows_to_sort, 0) \ M(SettingUInt64, max_bytes_to_sort, 0) \ M(SettingOverflowMode, sort_overflow_mode, OverflowMode::THROW) \ + M(SettingUInt64, max_bytes_before_external_sort, 0) \ \ /** Ограничение на размер результата. \ * Проверяются также для подзапросов и на удалённых серверах. \ @@ -44,7 +45,7 @@ struct Limits M(SettingUInt64, max_result_bytes, 0) \ M(SettingOverflowMode, result_overflow_mode, OverflowMode::THROW) \ \ - /* TODO: Проверять также при merge стадии сортировки, при слиянии и финализации агрегатных функций. */ \ + /* TODO: Проверять также при слиянии и финализации агрегатных функций. */ \ M(SettingSeconds, max_execution_time, 0) \ M(SettingOverflowMode, timeout_overflow_mode, OverflowMode::THROW) \ \ @@ -145,7 +146,7 @@ struct Limits private: friend struct Settings; - + /// Записать все настройки в буфер. (В отличие от соответствующего метода в Settings, пустая строка на конце не пишется). void serialize(WriteBuffer & buf) const { diff --git a/dbms/include/DB/Interpreters/Settings.h b/dbms/include/DB/Interpreters/Settings.h index 2790df2a2a3..40a644c4e9e 100644 --- a/dbms/include/DB/Interpreters/Settings.h +++ b/dbms/include/DB/Interpreters/Settings.h @@ -41,8 +41,6 @@ struct Settings M(SettingUInt64, max_distributed_connections, DEFAULT_MAX_DISTRIBUTED_CONNECTIONS) \ /** Какую часть запроса можно прочитать в оперативку для парсинга (оставшиеся данные для INSERT, если есть, считываются позже) */ \ M(SettingUInt64, max_query_size, DEFAULT_MAX_QUERY_SIZE) \ - /** Выполнять разные стадии конвейера выполнения запроса параллельно. */ \ - M(SettingBool, asynchronous, false) \ /** Интервал в микросекундах для проверки, не запрошена ли остановка выполнения запроса, и отправки прогресса. */ \ M(SettingUInt64, interactive_delay, DEFAULT_INTERACTIVE_DELAY) \ M(SettingSeconds, connect_timeout, DBMS_DEFAULT_CONNECT_TIMEOUT_SEC) \ @@ -62,8 +60,6 @@ struct Settings M(SettingBool, extremes, false) \ /** Использовать ли кэш разжатых блоков. */ \ M(SettingBool, use_uncompressed_cache, true) \ - /** Использовать ли SplittingAggregator вместо обычного. Он быстрее для запросов с большим состоянием агрегации. */ \ - M(SettingBool, use_splitting_aggregator, false) \ /** Следует ли отменять выполняющийся запрос с таким же id, как новый. */ \ M(SettingBool, replace_running_query, false) \ /** Количество потоков, выполняющих фоновую работу для таблиц (например, слияние в merge tree). \ @@ -86,6 +82,11 @@ struct Settings \ /** Сэмплирование по умолчанию. Если равно 1, то отключено. */ \ M(SettingFloat, default_sample, 1.0) \ + \ + /** Включена ли компиляция запросов. */ \ + M(SettingBool, compile, false) \ + /** Количество одинаковых по структуре запросов перед тем, как инициируется их компиляция. */ \ + M(SettingUInt64, min_count_to_compile, 0) \ /// Всевозможные ограничения на выполнение запроса. Limits limits; diff --git a/dbms/include/DB/Interpreters/SpecializedAggregator.h b/dbms/include/DB/Interpreters/SpecializedAggregator.h new file mode 100644 index 00000000000..f553624d9dd --- /dev/null +++ b/dbms/include/DB/Interpreters/SpecializedAggregator.h @@ -0,0 +1,291 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace DB +{ + + +/** Шаблон цикла агрегации, позволяющий сгенерировать специализированный вариант для конкретной комбинации агрегатных функций. + * Отличается от обычного тем, что вызовы агрегатных функций должны инлайниться, а цикл обновления агрегатных функций должен развернуться. + * + * Так как возможных комбинаций слишком много, то не представляется возможным сгенерировать их все заранее. + * Этот шаблон предназначен для того, чтобы инстанцировать его в рантайме, + * путём запуска компилятора, компиляции shared library и использования её с помощью dlopen. + */ + + +/** Список типов - для удобного перечисления агрегатных функций. + */ +template +struct TypeList +{ + using Head = THead; + using Tail = TypeList; + + static constexpr size_t size = 1 + sizeof...(TTail); + + template + using At = typename std::template conditional>::type; + + template + static void ALWAYS_INLINE forEach(Func && func) + { + func.template operator()(); + Tail::template forEach(std::forward(func)); + } +}; + + +template +struct TypeList +{ + using Head = THead; + + static constexpr size_t size = 1; + + template + using At = typename std::template conditional::type; + + template + static void ALWAYS_INLINE forEach(Func && func) + { + func.template operator()(); + } +}; + + +struct EmptyTypeList +{ + static constexpr size_t size = 0; + + template + using At = std::nullptr_t; + + template + 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 + 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 +void AggregateFunctionsUpdater::operator()() +{ + static_cast(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 + void operator()() ALWAYS_INLINE; + + const Aggregator::AggregateFunctionsPlainPtrs & aggregate_functions; + const Sizes & offsets_of_aggregate_states; + AggregateDataPtr & aggregate_data; +}; + +template +void AggregateFunctionsCreator::operator()() +{ + AggregateFunction * func = static_cast(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 +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( + method, state, aggregates_pool, rows, key_columns, aggregate_columns, key_sizes, keys, overflow_row); + else + executeSpecializedCase( + method, state, aggregates_pool, rows, key_columns, aggregate_columns, key_sizes, keys, overflow_row); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" + +template +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 +void NO_INLINE Aggregator::executeSpecializedWithoutKey( + AggregatedDataWithoutKey & res, + size_t rows, + AggregateColumns & aggregate_columns) const +{ + /// Оптимизация в случае единственной агрегатной функции count. + AggregateFunctionCount * agg_count = aggregates_size == 1 + ? typeid_cast(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)); + } + } +} + +} diff --git a/dbms/include/DB/Interpreters/SplittingAggregator.h b/dbms/include/DB/Interpreters/SplittingAggregator.h deleted file mode 100644 index ca2ba4224b3..00000000000 --- a/dbms/include/DB/Interpreters/SplittingAggregator.h +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -#include - -#include -#include - - -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 keys64; - PODArray hashes64; - PODArray keys128; - PODArray hashes128; - PODArray string_refs; - - PODArray thread_nums; - - Logger * log; - - /// Каким способом выполняется агрегация. - AggregatedDataVariants::Type method; - - ConstColumnPlainPtrs key_columns; - - typedef std::vector 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 - void aggregateOneNumber(AggregatedDataVariants & result, size_t thread_no, bool no_more_keys); -}; - - -} diff --git a/dbms/include/DB/Parsers/ASTSelectQuery.h b/dbms/include/DB/Parsers/ASTSelectQuery.h index f310b132d99..c4d19b19fdc 100644 --- a/dbms/include/DB/Parsers/ASTSelectQuery.h +++ b/dbms/include/DB/Parsers/ASTSelectQuery.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace DB { @@ -52,6 +53,41 @@ public: return false; } + /// Содержит ли запрос астериск? + bool hasAsterisk() const + { + for (const auto & ast : select_expression_list->children) + if (typeid_cast(&*ast) != nullptr) + return true; + + return false; + } + + /// Переименовать столбцы запроса в такие же имена, как в исходном запросе. + void renameColumns(const ASTSelectQuery & source) + { + const ASTs & from = source.select_expression_list->children; + ASTs & to = select_expression_list->children; + + if (from.size() != to.size()) + throw Exception("Size mismatch in UNION ALL chain", + DB::ErrorCodes::UNION_ALL_RESULT_STRUCTURES_MISMATCH); + + for (size_t i = 0; i < from.size(); ++i) + { + /// Если столбец имеет алиас, то он должен совпадать с названием исходного столбца. + /// В противном случае мы ему присваиваем алиас, если требуется. + if (!to[i]->tryGetAlias().empty()) + { + if (to[i]->tryGetAlias() != from[i]->getAliasOrColumnName()) + throw Exception("Column alias mismatch in UNION ALL chain", + DB::ErrorCodes::UNION_ALL_COLUMN_ALIAS_MISMATCH); + } + else if (to[i]->getColumnName() != from[i]->getAliasOrColumnName()) + to[i]->setAlias(from[i]->getAliasOrColumnName()); + } + } + /// Переписывает select_expression_list, чтобы вернуть только необходимые столбцы в правильном порядке. void rewriteSelectExpressionList(const Names & column_names) { diff --git a/dbms/include/DB/Storages/Distributed/DirectoryMonitor.h b/dbms/include/DB/Storages/Distributed/DirectoryMonitor.h index 3438dfd9bb2..d7c3d163d9b 100644 --- a/dbms/include/DB/Storages/Distributed/DirectoryMonitor.h +++ b/dbms/include/DB/Storages/Distributed/DirectoryMonitor.h @@ -13,6 +13,9 @@ namespace DB namespace { + static constexpr const std::chrono::seconds max_sleep_time{30}; + static constexpr const std::chrono::minutes decrease_error_count_period{5}; + template ConnectionPools createPoolsForAddresses(const std::string & name, PoolFactory && factory) { @@ -57,7 +60,8 @@ class StorageDistributed::DirectoryMonitor public: DirectoryMonitor(StorageDistributed & storage, const std::string & name) : storage(storage), pool{createPool(name)}, path{storage.path + name + '/'} - , sleep_time{storage.context.getSettingsRef().distributed_directory_monitor_sleep_time_ms.totalMilliseconds()} + , default_sleep_time{storage.context.getSettingsRef().distributed_directory_monitor_sleep_time_ms.totalMilliseconds()} + , sleep_time{default_sleep_time} , log{&Logger::get(getLoggerName())} { } @@ -90,11 +94,22 @@ private: catch (...) { do_sleep = true; + ++error_count; + sleep_time = std::min( + std::chrono::milliseconds{std::int64_t(default_sleep_time.count() * std::exp2(error_count))}, + std::chrono::milliseconds{max_sleep_time}); tryLogCurrentException(getLoggerName().data()); - } + }; if (do_sleep) cond.wait_for(lock, sleep_time, quit_requested); + + const auto now = std::chrono::system_clock::now(); + if (now - last_decrease_time > decrease_error_count_period) + { + error_count /= 2; + last_decrease_time = now; + } } } @@ -196,7 +211,12 @@ private: StorageDistributed & storage; ConnectionPoolPtr pool; std::string path; + std::size_t error_count{}; + std::chrono::milliseconds default_sleep_time; std::chrono::milliseconds sleep_time; + std::chrono::time_point last_decrease_time{ + std::chrono::system_clock::now() + }; bool quit{false}; std::mutex mutex; std::condition_variable cond; diff --git a/dbms/include/DB/Storages/IStorage.h b/dbms/include/DB/Storages/IStorage.h index a4153f165bf..6155c569bb5 100644 --- a/dbms/include/DB/Storages/IStorage.h +++ b/dbms/include/DB/Storages/IStorage.h @@ -86,8 +86,8 @@ public: TableStructureReadLock(StoragePtr storage_, bool lock_structure, bool lock_data) : storage(storage_), - data_lock(lock_data ? new Poco::ScopedReadRWLock(storage-> data_lock) : nullptr), - structure_lock(lock_structure ? new Poco::ScopedReadRWLock(storage->structure_lock) : nullptr) {} + data_lock(lock_data ? new Poco::ScopedReadRWLock(storage-> data_lock) : nullptr), + structure_lock(lock_structure ? new Poco::ScopedReadRWLock(storage->structure_lock) : nullptr) {} }; typedef Poco::SharedPtr TableStructureReadLockPtr; diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h index 1cd28dfc2c9..aa22838cb78 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeData.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeData.h @@ -331,8 +331,39 @@ public: String from = storage.full_path + name + "/"; String to = storage.full_path + "tmp2_" + name + "/"; - Poco::File(from).renameTo(to); - Poco::File(to).remove(true); + Poco::File from_dir{from}; + Poco::File to_dir{to}; + + if (to_dir.exists()) + { + LOG_WARNING(storage.log, "Directory " << to << " (to which part must be renamed before removing) already exists." + " Most likely this is due to unclean restart. Removing it."); + + try + { + to_dir.remove(true); + } + catch (...) + { + LOG_ERROR(storage.log, "Cannot remove directory " << to << ". Check owner and access rights."); + throw; + } + } + + try + { + from_dir.renameTo(to); + } + catch (const Poco::FileNotFoundException & e) + { + /// Если директория уже удалена. Такое возможно лишь при ручном вмешательстве. + LOG_WARNING(storage.log, "Directory " << from << " (part to remove) doesn't exist or one of nested files has gone." + " Most likely this is due to manual removing. This should be discouraged. Ignoring."); + + return; + } + + to_dir.remove(true); } void renameTo(const String & new_name) const @@ -345,10 +376,28 @@ public: f.renameTo(to); } - /// Переименовывает кусок, дописав к имени префикс. - void renameAddPrefix(const String & prefix) const + /// Переименовывает кусок, дописав к имени префикс. to_detached - также перенести в директорию detached. + void renameAddPrefix(bool to_detached, const String & prefix) const { - renameTo(prefix + name); + unsigned try_no = 0; + auto dst_name = [&, this] { return (to_detached ? "detached/" : "") + prefix + name + (try_no ? "_try" + toString(try_no) : ""); }; + + if (to_detached) + { + /** Если нужно отцепить кусок, и директория, в которую мы хотим его переименовать, уже существует, + * то будем переименовывать в директорию с именем, в которое добавлен суффикс в виде "_tryN". + * Это делается только в случае to_detached, потому что считается, что в этом случае, точное имя не имеет значения. + * Больше 10 попыток не делается, чтобы не оставалось слишком много мусорных директорий. + */ + while (try_no < 10 && Poco::File(dst_name()).exists()) + { + LOG_WARNING(storage.log, "Directory " << dst_name() << " (to detach to) is already exist." + " Will detach to directory with '_tryN' suffix."); + ++try_no; + } + } + + renameTo(dst_name()); } /// Загрузить индекс и вычислить размер. Если size=0, вычислить его тоже. diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h b/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h index 8498478ac73..b5fc5e34e44 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeDataMerger.h @@ -68,7 +68,7 @@ class MergeTreeMergeBlocker { public: MergeTreeMergeBlocker(MergeTreeDataMerger & merger) - : merger(merger), was_cancelled{merger.cancelAll()} {} + : merger(merger), was_cancelled{!merger.cancelAll()} {} ~MergeTreeMergeBlocker() { diff --git a/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h b/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h index f2a193587d4..dde138d9926 100644 --- a/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h +++ b/dbms/include/DB/Storages/MergeTree/MergeTreeReader.h @@ -320,7 +320,8 @@ private: break; } - max_mark_range = std::max(max_mark_range, (*marks)[right].offset_in_compressed_file - (*marks)[all_mark_ranges[i].begin].offset_in_compressed_file); + max_mark_range = std::max(max_mark_range, + (*marks)[right].offset_in_compressed_file - (*marks)[all_mark_ranges[i].begin].offset_in_compressed_file); } size_t buffer_size = DBMS_DEFAULT_BUFFER_SIZE < max_mark_range ? DBMS_DEFAULT_BUFFER_SIZE : max_mark_range; diff --git a/dbms/include/DB/Storages/StorageDistributed.h b/dbms/include/DB/Storages/StorageDistributed.h index c0c9367f424..7c947adc9aa 100644 --- a/dbms/include/DB/Storages/StorageDistributed.h +++ b/dbms/include/DB/Storages/StorageDistributed.h @@ -133,7 +133,7 @@ private: ExpressionActionsPtr sharding_key_expr; String sharding_key_column_name; bool write_enabled; - String path; + String path; /// Может быть пустым, если data_path_ пустой. В этом случае, директория для данных для отправки не создаётся. class DirectoryMonitor; std::unordered_map> directory_monitors; diff --git a/dbms/src/Client/Client.cpp b/dbms/src/Client/Client.cpp index 85963eb88da..68b83b2f062 100644 --- a/dbms/src/Client/Client.cpp +++ b/dbms/src/Client/Client.cpp @@ -96,10 +96,11 @@ private: String format; /// Формат вывода результата в консоль. size_t format_max_block_size = 0; /// Максимальный размер блока при выводе в консоль. - String current_format; /// Формат вывода результата текущего запроса в консоль. String insert_format; /// Формат данных для INSERT-а при чтении их из stdin в batch режиме size_t insert_format_max_block_size = 0; /// Максимальный размер блока при чтении данных INSERT-а. + bool has_vertical_output_suffix = false; /// \G указан в конце команды? + Context context; /// Чтение из stdin для batch режима @@ -233,15 +234,12 @@ private: << "." << Revision::get() << "." << std::endl; - if (is_interactive) - { - format = config().getString("format", config().has("vertical") ? "Vertical" : "PrettyCompact"); - } + if (config().has("vertical")) + format = config().getString("format", "Vertical"); else - format = config().getString("format", "TabSeparated"); + format = config().getString("format", is_interactive ? "PrettyCompact" : "TabSeparated"); format_max_block_size = config().getInt("format_max_block_size", DEFAULT_BLOCK_SIZE); - current_format = format; insert_format = "Values"; insert_format_max_block_size = config().getInt("insert_format_max_block_size", DEFAULT_INSERT_BLOCK_SIZE); @@ -358,14 +356,15 @@ private: bool ends_with_semicolon = line[ws - 1] == ';'; bool ends_with_backslash = line[ws - 1] == '\\'; - bool ends_with_format_vertical = (ws >= 2) && (line[ws - 2] == '\\') && (line[ws - 1] == 'G'); + + has_vertical_output_suffix = (ws >= 2) && (line[ws - 2] == '\\') && (line[ws - 1] == 'G'); if (ends_with_backslash) line = line.substr(0, ws - 1); query += line; - if (!ends_with_backslash && (ends_with_semicolon || ends_with_format_vertical || !config().has("multiline"))) + if (!ends_with_backslash && (ends_with_semicolon || has_vertical_output_suffix || !config().has("multiline"))) { if (query != prev_query) { @@ -382,12 +381,9 @@ private: prev_query = query; } - - if (ends_with_format_vertical) - { - current_format = config().getString("format", "Vertical"); + + if (has_vertical_output_suffix) query = query.substr(0, query.length() - 2); - } try { @@ -411,7 +407,6 @@ private: } query = ""; - current_format = format; } else { @@ -815,12 +810,23 @@ private: processed_rows += block.rows(); if (!block_std_out) { + String current_format = format; + /// Формат может быть указан в запросе. if (ASTQueryWithOutput * query_with_output = dynamic_cast(&*parsed_query)) + { if (query_with_output->format) + { + if (has_vertical_output_suffix) + throw Exception("Output format already specified", ErrorCodes::CLIENT_OUTPUT_FORMAT_SPECIFIED); if (ASTIdentifier * id = typeid_cast(&*query_with_output->format)) current_format = id->name; - + } + } + + if (has_vertical_output_suffix) + current_format = "Vertical"; + block_std_out = context.getFormatFactory().getOutput(current_format, std_out, block); block_std_out->writePrefix(); } diff --git a/dbms/src/Client/Connection.cpp b/dbms/src/Client/Connection.cpp index 4242d451702..3f14786085d 100644 --- a/dbms/src/Client/Connection.cpp +++ b/dbms/src/Client/Connection.cpp @@ -269,7 +269,7 @@ void Connection::sendData(const Block & block, const String & name) else maybe_compressed_out = out; - block_out = new NativeBlockOutputStream(*maybe_compressed_out); + block_out = new NativeBlockOutputStream(*maybe_compressed_out, server_revision); } writeVarUInt(Protocol::Client::Data, *out); @@ -444,7 +444,7 @@ void Connection::initBlockInput() else maybe_compressed_in = in; - block_in = new NativeBlockInputStream(*maybe_compressed_in, data_type_factory); + block_in = new NativeBlockInputStream(*maybe_compressed_in, data_type_factory, server_revision); } } diff --git a/dbms/src/Common/VirtualColumnUtils.cpp b/dbms/src/Common/VirtualColumnUtils.cpp index bec2bca6787..d7d69eaa293 100644 --- a/dbms/src/Common/VirtualColumnUtils.cpp +++ b/dbms/src/Common/VirtualColumnUtils.cpp @@ -1,16 +1,12 @@ -#include - #include -#include -#include -#include -#include +#include #include #include #include #include -#include -#include + +#include + namespace DB { diff --git a/dbms/src/Common/localBackup.cpp b/dbms/src/Common/localBackup.cpp index 64ee6f7dfa2..51e93736550 100644 --- a/dbms/src/Common/localBackup.cpp +++ b/dbms/src/Common/localBackup.cpp @@ -73,7 +73,7 @@ void localBackup(Poco::Path source_path, Poco::Path destination_path) * Если какой-то файл удалился во время попытки сделать бэкап, то повторим попытку снова, * так как важно учесть какие-нибудь новые файлы, который могли появиться. */ - do + while (true) { try { @@ -98,5 +98,7 @@ void localBackup(Poco::Path source_path, Poco::Path destination_path) continue; } - } while (false); + + break; + } } diff --git a/dbms/src/Common/tests/parallel_aggregation.cpp b/dbms/src/Common/tests/parallel_aggregation.cpp index b9c6eec1c98..a17bdae35be 100644 --- a/dbms/src/Common/tests/parallel_aggregation.cpp +++ b/dbms/src/Common/tests/parallel_aggregation.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include //#include //#include @@ -25,46 +25,6 @@ typedef UInt64 Value; typedef std::vector Source; typedef HashMap Map; - - -template -< - typename Key, - typename Cell, - typename Hash = DefaultHash, - typename Grower = HashTableGrower<8>, - typename Allocator = HashTableAllocator -> -class TwoLevelHashMapTable : public TwoLevelHashTable > -{ -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, - typename Grower = HashTableGrower<8>, - typename Allocator = HashTableAllocator -> -using TwoLevelHashMap = TwoLevelHashMapTable, Hash, Grower, Allocator>; - typedef TwoLevelHashMap MapTwoLevel; @@ -90,7 +50,7 @@ struct __attribute__((__aligned__(64))) AlignedSmallLock : public SmallLock }; -typedef AlignedSmallLock Mutex; +typedef Poco::FastMutex Mutex; /*typedef HashTableWithSmallLocks< @@ -109,12 +69,55 @@ void aggregate1(Map & map, Source::const_iterator begin, Source::const_iterator ++map[*it]; } +void aggregate12(Map & map, Source::const_iterator begin, Source::const_iterator end) +{ + Map::iterator found; + auto prev_it = end; + for (auto it = begin; it != end; ++it) + { + if (*it == *prev_it) + { + ++found->second; + continue; + } + prev_it = it; + + bool inserted; + map.emplace(*it, found, inserted); + ++found->second; + } +} + void aggregate2(MapTwoLevel & map, Source::const_iterator begin, Source::const_iterator end) { for (auto it = begin; it != end; ++it) ++map[*it]; } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + +void aggregate22(MapTwoLevel & map, Source::const_iterator begin, Source::const_iterator end) +{ + MapTwoLevel::iterator found; + auto prev_it = end; + for (auto it = begin; it != end; ++it) + { + if (*it == *prev_it) + { + ++found->second; + continue; + } + prev_it = it; + + bool inserted; + map.emplace(*it, found, inserted); + ++found->second; + } +} + +#pragma GCC diagnostic pop + void merge2(MapTwoLevel * maps, size_t num_threads, size_t bucket) { for (size_t i = 1; i < num_threads; ++i) @@ -147,30 +150,65 @@ void aggregate3(Map & local_map, Map & global_map, Mutex & mutex, Source::const_ } } -void aggregate4(Map & local_map, MapTwoLevel & global_map, Mutex * mutexes, Source::const_iterator begin, Source::const_iterator end) +void aggregate33(Map & local_map, Map & global_map, Mutex & mutex, Source::const_iterator begin, Source::const_iterator end) { static constexpr size_t threshold = 65536; for (auto it = begin; it != end; ++it) { - Map::iterator found = local_map.find(*it); + Map::iterator found; + bool inserted; + local_map.emplace(*it, found, inserted); + ++found->second; - if (found != local_map.end()) - ++found->second; - else if (local_map.size() < threshold) - ++local_map[*it]; /// TODO Можно было бы делать один lookup, а не два. + if (inserted && local_map.size() == threshold) + { + Poco::ScopedLock 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 { - size_t hash_value = global_map.hash(*it); - size_t bucket = global_map.getBucketFromHash(hash_value); - - if (mutexes[bucket].tryLock()) + for (; it != block_end; ++it) { - ++global_map.impls[bucket][*it]; - mutexes[bucket].unlock(); + Map::iterator found = local_map.find(*it); + + if (found != local_map.end()) + ++found->second; + else + { + size_t hash_value = global_map.hash(*it); + size_t bucket = global_map.getBucketFromHash(hash_value); + + if (mutexes[bucket].tryLock()) + { + ++global_map.impls[bucket][*it]; + mutexes[bucket].unlock(); + } + else + ++local_map[*it]; + } } - else - ++local_map[*it]; } } } @@ -286,6 +324,135 @@ int main(int argc, char ** argv) std::cerr << "Size: " << maps[0].size() << std::endl << std::endl; } + if (!method || method == 12) + { + /** То же самое, но с оптимизацией для подряд идущих одинаковых значений. + */ + + Map maps[num_threads]; + + Stopwatch watch; + + for (size_t i = 0; i < num_threads; ++i) + pool.schedule(std::bind(aggregate12, + std::ref(maps[i]), + data.begin() + (data.size() * i) / num_threads, + data.begin() + (data.size() * (i + 1)) / num_threads)); + + pool.wait(); + + watch.stop(); + double time_aggregated = watch.elapsedSeconds(); + std::cerr + << "Aggregated in " << time_aggregated + << " (" << n / time_aggregated << " elem/sec.)" + << std::endl; + + size_t size_before_merge = 0; + std::cerr << "Sizes: "; + for (size_t i = 0; i < num_threads; ++i) + { + std::cerr << (i == 0 ? "" : ", ") << maps[i].size(); + size_before_merge += maps[i].size(); + } + std::cerr << std::endl; + + watch.restart(); + + for (size_t i = 1; i < num_threads; ++i) + for (auto it = maps[i].begin(); it != maps[i].end(); ++it) + maps[0][it->first] += it->second; + + watch.stop(); + double time_merged = watch.elapsedSeconds(); + std::cerr + << "Merged in " << time_merged + << " (" << size_before_merge / time_merged << " elem/sec.)" + << std::endl; + + double time_total = time_aggregated + time_merged; + std::cerr + << "Total in " << time_total + << " (" << n / time_total << " elem/sec.)" + << std::endl; + std::cerr << "Size: " << maps[0].size() << std::endl << std::endl; + } + + if (!method || method == 11) + { + /** Вариант 11. + * То же, что вариант 1, но при мердже, изменён порядок циклов, + * что потенциально может дать лучшую кэш-локальность. + * + * На практике, разницы нет. + */ + + Map maps[num_threads]; + + Stopwatch watch; + + for (size_t i = 0; i < num_threads; ++i) + pool.schedule(std::bind(aggregate1, + std::ref(maps[i]), + data.begin() + (data.size() * i) / num_threads, + data.begin() + (data.size() * (i + 1)) / num_threads)); + + pool.wait(); + + watch.stop(); + double time_aggregated = watch.elapsedSeconds(); + std::cerr + << "Aggregated in " << time_aggregated + << " (" << n / time_aggregated << " elem/sec.)" + << std::endl; + + size_t size_before_merge = 0; + std::cerr << "Sizes: "; + for (size_t i = 0; i < num_threads; ++i) + { + std::cerr << (i == 0 ? "" : ", ") << maps[i].size(); + size_before_merge += maps[i].size(); + } + std::cerr << std::endl; + + watch.restart(); + + Map::iterator iterators[num_threads]; + for (size_t i = 1; i < num_threads; ++i) + iterators[i] = maps[i].begin(); + + while (true) + { + bool finish = true; + for (size_t i = 1; i < num_threads; ++i) + { + if (iterators[i] == maps[i].end()) + continue; + + finish = false; + maps[0][iterators[i]->first] += iterators[i]->second; + ++iterators[i]; + } + + if (finish) + break; + } + + watch.stop(); + double time_merged = watch.elapsedSeconds(); + std::cerr + << "Merged in " << time_merged + << " (" << size_before_merge / time_merged << " elem/sec.)" + << std::endl; + + double time_total = time_aggregated + time_merged; + std::cerr + << "Total in " << time_total + << " (" << n / time_total << " elem/sec.)" + << std::endl; + std::cerr << "Size: " << maps[0].size() << std::endl << std::endl; + } + if (!method || method == 2) { /** Вариант 2. @@ -348,6 +515,60 @@ int main(int argc, char ** argv) std::cerr << "Size: " << maps[0].size() << std::endl << std::endl; } + if (!method || method == 22) + { + MapTwoLevel maps[num_threads]; + + Stopwatch watch; + + for (size_t i = 0; i < num_threads; ++i) + pool.schedule(std::bind(aggregate22, + std::ref(maps[i]), + data.begin() + (data.size() * i) / num_threads, + data.begin() + (data.size() * (i + 1)) / num_threads)); + + pool.wait(); + + watch.stop(); + double time_aggregated = watch.elapsedSeconds(); + std::cerr + << "Aggregated in " << time_aggregated + << " (" << n / time_aggregated << " elem/sec.)" + << std::endl; + + size_t size_before_merge = 0; + std::cerr << "Sizes: "; + for (size_t i = 0; i < num_threads; ++i) + { + std::cerr << (i == 0 ? "" : ", ") << maps[i].size(); + size_before_merge += maps[i].size(); + } + std::cerr << std::endl; + + watch.restart(); + + for (size_t i = 0; i < MapTwoLevel::NUM_BUCKETS; ++i) + pool.schedule(std::bind(merge2, + &maps[0], num_threads, i)); + + pool.wait(); + + watch.stop(); + double time_merged = watch.elapsedSeconds(); + std::cerr + << "Merged in " << time_merged + << " (" << size_before_merge / time_merged << " elem/sec.)" + << std::endl; + + double time_total = time_aggregated + time_merged; + std::cerr + << "Total in " << time_total + << " (" << n / time_total << " elem/sec.)" + << std::endl; + + std::cerr << "Size: " << maps[0].size() << std::endl << std::endl; + } + if (!method || method == 3) { /** Вариант 3. @@ -418,6 +639,72 @@ int main(int argc, char ** argv) std::cerr << "Size: " << global_map.size() << std::endl << std::endl; } + if (!method || method == 33) + { + /** Вариант 33. + * В разных потоках агрегируем независимо в разные хэш-таблицы, + * пока их размер не станет достаточно большим. + * Затем сбрасываем данные в глобальную хэш-таблицу, защищённую mutex-ом, и продолжаем. + */ + + Map local_maps[num_threads]; + Map global_map; + Mutex mutex; + + Stopwatch watch; + + for (size_t i = 0; i < num_threads; ++i) + pool.schedule(std::bind(aggregate33, + std::ref(local_maps[i]), + std::ref(global_map), + std::ref(mutex), + data.begin() + (data.size() * i) / num_threads, + data.begin() + (data.size() * (i + 1)) / num_threads)); + + pool.wait(); + + watch.stop(); + double time_aggregated = watch.elapsedSeconds(); + std::cerr + << "Aggregated in " << time_aggregated + << " (" << n / time_aggregated << " elem/sec.)" + << std::endl; + + size_t size_before_merge = 0; + std::cerr << "Sizes (local): "; + for (size_t i = 0; i < num_threads; ++i) + { + std::cerr << (i == 0 ? "" : ", ") << local_maps[i].size(); + size_before_merge += local_maps[i].size(); + } + std::cerr << std::endl; + std::cerr << "Size (global): " << global_map.size() << std::endl; + size_before_merge += global_map.size(); + + watch.restart(); + + for (size_t i = 0; i < num_threads; ++i) + for (auto it = local_maps[i].begin(); it != local_maps[i].end(); ++it) + global_map[it->first] += it->second; + + pool.wait(); + + watch.stop(); + double time_merged = watch.elapsedSeconds(); + std::cerr + << "Merged in " << time_merged + << " (" << size_before_merge / time_merged << " elem/sec.)" + << std::endl; + + double time_total = time_aggregated + time_merged; + std::cerr + << "Total in " << time_total + << " (" << n / time_total << " elem/sec.)" + << std::endl; + + std::cerr << "Size: " << global_map.size() << std::endl << std::endl; + } + if (!method || method == 4) { /** Вариант 4. diff --git a/dbms/src/Common/tests/parallel_aggregation2.cpp b/dbms/src/Common/tests/parallel_aggregation2.cpp new file mode 100644 index 00000000000..947d0a03663 --- /dev/null +++ b/dbms/src/Common/tests/parallel_aggregation2.cpp @@ -0,0 +1,395 @@ +#include +#include +#include +#include + +//#define DBMS_HASH_MAP_DEBUG_RESIZES + +#include + +#include +#include +//#include +//#include + +#include +#include + +#include +#include + + +typedef UInt64 Key; +typedef UInt64 Value; +typedef std::vector Source; + + +template +struct AggregateIndependent +{ + template + static void NO_INLINE execute(const Source & data, size_t num_threads, std::vector> & 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 +struct AggregateIndependentWithSequentialKeysOptimization +{ + template + static void NO_INLINE execute(const Source & data, size_t num_threads, std::vector> & 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 +struct MergeSequential +{ + template + 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 +struct MergeSequentialTransposed /// На практике не лучше обычного. +{ + template + 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 +struct MergeParallelForTwoLevelTable +{ + template + 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 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 +struct Work +{ + template + static void NO_INLINE execute(const Source & data, size_t num_threads, + Creator && creator, Updater && updater, Merger && merger, + boost::threadpool::pool & pool) + { + std::vector> intermediate_results; + + Stopwatch watch; + + Aggregate::execute(data, num_threads, intermediate_results, std::forward(creator), std::forward(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 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), 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> Map; +typedef TwoLevelHashMap> 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(&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, + MergeSequential + >::execute(data, num_threads, creator, updater, merger, pool); + + if (!method || method == 2) + Work< + Map, + AggregateIndependentWithSequentialKeysOptimization, + MergeSequential + >::execute(data, num_threads, creator, updater, merger, pool); + + if (!method || method == 3) + Work< + Map, + AggregateIndependent, + MergeSequentialTransposed + >::execute(data, num_threads, creator, updater, merger, pool); + + if (!method || method == 4) + Work< + Map, + AggregateIndependentWithSequentialKeysOptimization, + MergeSequentialTransposed + >::execute(data, num_threads, creator, updater, merger, pool); + + if (!method || method == 5) + Work< + MapTwoLevel, + AggregateIndependent, + MergeSequential + >::execute(data, num_threads, creator, updater, merger, pool); + + if (!method || method == 6) + Work< + MapTwoLevel, + AggregateIndependentWithSequentialKeysOptimization, + MergeSequential + >::execute(data, num_threads, creator, updater, merger, pool); + + if (!method || method == 7) + Work< + MapTwoLevel, + AggregateIndependent, + MergeSequentialTransposed + >::execute(data, num_threads, creator, updater, merger, pool); + + if (!method || method == 8) + Work< + MapTwoLevel, + AggregateIndependentWithSequentialKeysOptimization, + MergeSequentialTransposed + >::execute(data, num_threads, creator, updater, merger, pool); + + if (!method || method == 9) + Work< + MapTwoLevel, + AggregateIndependent, + MergeParallelForTwoLevelTable> + >::execute(data, num_threads, creator, updater, merger, pool); + + if (!method || method == 10) + Work< + MapTwoLevel, + AggregateIndependentWithSequentialKeysOptimization, + MergeParallelForTwoLevelTable> + >::execute(data, num_threads, creator, updater, merger, pool); + + if (!method || method == 13) + Work< + MapTwoLevel, + AggregateIndependent, + MergeParallelForTwoLevelTable> + >::execute(data, num_threads, creator, updater, merger, pool); + + if (!method || method == 14) + Work< + MapTwoLevel, + AggregateIndependentWithSequentialKeysOptimization, + MergeParallelForTwoLevelTable> + >::execute(data, num_threads, creator, updater, merger, pool); + + return 0; +} diff --git a/dbms/src/Core/Block.cpp b/dbms/src/Core/Block.cpp index 44c5f271c9a..a030bfd011e 100644 --- a/dbms/src/Core/Block.cpp +++ b/dbms/src/Core/Block.cpp @@ -36,6 +36,7 @@ void Block::addDefaults(const NamesAndTypesList & required_columns) Block & Block::operator= (const Block & other) { + info = other.info; data = other.data; index_by_position.resize(data.size()); @@ -272,6 +273,7 @@ Block Block::cloneEmpty() const { Block res; + res.info = info; for (Container_t::const_iterator it = data.begin(); it != data.end(); ++it) res.insert(it->cloneEmpty()); @@ -363,7 +365,7 @@ bool blocksHaveEqualStructure(const Block & lhs, const Block & rhs) { const IDataType & lhs_type = *lhs.getByPosition(i).type; const IDataType & rhs_type = *rhs.getByPosition(i).type; - + if (lhs_type.getName() != rhs_type.getName()) return false; } @@ -374,6 +376,7 @@ bool blocksHaveEqualStructure(const Block & lhs, const Block & rhs) void Block::clear() { + info = BlockInfo(); data.clear(); index_by_name.clear(); index_by_position.clear(); @@ -381,6 +384,7 @@ void Block::clear() void Block::swap(Block & other) { + std::swap(info, other.info); data.swap(other.data); index_by_name.swap(other.index_by_name); index_by_position.swap(other.index_by_position); diff --git a/dbms/src/DataStreams/AggregatingBlockInputStream.cpp b/dbms/src/DataStreams/AggregatingBlockInputStream.cpp index 0641fe23c3d..4542a9b6825 100644 --- a/dbms/src/DataStreams/AggregatingBlockInputStream.cpp +++ b/dbms/src/DataStreams/AggregatingBlockInputStream.cpp @@ -7,32 +7,25 @@ namespace DB { -AggregatingBlockInputStream::AggregatingBlockInputStream(BlockInputStreamPtr input_, - const Names & key_names, const AggregateDescriptions & aggregates, - bool overflow_row_, bool final_, size_t max_rows_to_group_by_, OverflowMode group_by_overflow_mode_) - : final(final_), has_been_read(false) -{ - children.push_back(input_); - - aggregator = new Aggregator(key_names, aggregates, overflow_row_, max_rows_to_group_by_, group_by_overflow_mode_); -} - - - Block AggregatingBlockInputStream::readImpl() { - if (has_been_read) - return Block(); + if (!executed) + { + executed = true; + AggregatedDataVariants data_variants; + aggregator.execute(children.back(), data_variants); + blocks = aggregator.convertToBlocks(data_variants, final, 1); + it = blocks.begin(); + } - has_been_read = true; + Block res; + if (isCancelled() || it == blocks.end()) + return res; - AggregatedDataVariants data_variants; - aggregator->execute(children.back(), data_variants); + res = *it; + ++it; - if (isCancelled()) - return Block(); - - return aggregator->convertToBlock(data_variants, final); + return res; } diff --git a/dbms/src/DataStreams/MergeSortingBlockInputStream.cpp b/dbms/src/DataStreams/MergeSortingBlockInputStream.cpp index f56f528eb41..1ed7eb40c03 100644 --- a/dbms/src/DataStreams/MergeSortingBlockInputStream.cpp +++ b/dbms/src/DataStreams/MergeSortingBlockInputStream.cpp @@ -1,113 +1,148 @@ -#include -#include - -#include - #include +#include +#include +#include +#include +#include namespace DB { + Block MergeSortingBlockInputStream::readImpl() { - /** Достаточно простой алгоритм: - * - прочитать в оперативку все блоки; - * - объединить их всех; + /** Алгоритм: + * - читать в оперативку блоки из источника; + * - когда их становится слишком много и если возможна внешняя сортировка + * - слить блоки вместе в сортированный поток и записать его во временный файл; + * - в конце, слить вместе все сортированные потоки из временных файлов, а также из накопившихся в оперативке блоков. */ - if (has_been_read) - return Block(); + /// Ещё не прочитали блоки. + if (!impl) + { + while (Block block = children.back()->read()) + { + blocks.push_back(block); + sum_bytes_in_blocks += block.bytes(); - has_been_read = true; + /** Если блоков стало слишком много и возможна внешняя сортировка, + * то сольём вместе те блоки, которые успели накопиться, и сбросим сортированный поток во временный (сжатый) файл. + * 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); - Blocks blocks; - while (Block block = children.back()->read()) - blocks.push_back(block); + 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); - if (isCancelled()) - return Block(); + blocks.clear(); + sum_bytes_in_blocks = 0; + } + } - return merge(blocks); + if ((blocks.empty() && temporary_files.empty()) || isCancelled()) + return Block(); + + if (temporary_files.empty()) + { + impl.reset(new MergeSortingBlocksBlockInputStream(blocks, description, max_merged_block_size, limit)); + } + else + { + /// Если были сброшены временные данные в файлы. + + LOG_INFO(log, "There are " << temporary_files.size() << " temporary sorted parts to merge."); + + /// Сформируем сортированные потоки для слияния. + for (const auto & file : temporary_files) + { + temporary_inputs.emplace_back(new TemporaryFileStream(file->path(), data_type_factory)); + inputs_to_merge.emplace_back(temporary_inputs.back()->block_in); + } + + /// Оставшиеся в оперативке блоки. + if (!blocks.empty()) + inputs_to_merge.emplace_back(new MergeSortingBlocksBlockInputStream(blocks, description, max_merged_block_size, limit)); + + /// Будем сливать эти потоки. + impl.reset(new MergingSortedBlockInputStream(inputs_to_merge, description, max_merged_block_size, limit)); + } + } + + return impl->read(); } -Block MergeSortingBlockInputStream::merge(Blocks & blocks) + +MergeSortingBlocksBlockInputStream::MergeSortingBlocksBlockInputStream( + Blocks & blocks_, SortDescription & description_, size_t max_merged_block_size_, size_t limit_) + : blocks(blocks_), description(description_), max_merged_block_size(max_merged_block_size_), limit(limit_) +{ + Blocks nonempty_blocks; + for (const auto & block : blocks) + { + if (block.rowsInFirstColumn() == 0) + continue; + + nonempty_blocks.push_back(block); + cursors.emplace_back(block, description); + has_collation |= cursors.back().has_collation; + } + + blocks.swap(nonempty_blocks); + + if (!has_collation) + { + for (size_t i = 0; i < cursors.size(); ++i) + queue.push(SortCursor(&cursors[i])); + } + else + { + for (size_t i = 0; i < cursors.size(); ++i) + queue_with_collation.push(SortCursorWithCollation(&cursors[i])); + } +} + + +Block MergeSortingBlocksBlockInputStream::readImpl() { if (blocks.empty()) return Block(); if (blocks.size() == 1) - return blocks[0]; - - Stopwatch watch; - - LOG_DEBUG(log, "Merge sorting"); - - CursorImpls cursors(blocks.size()); - - bool has_collation = false; - - size_t nonempty_blocks = 0; - for (Blocks::const_iterator it = blocks.begin(); it != blocks.end(); ++it) { - if (it->rowsInFirstColumn() == 0) - continue; - - cursors[nonempty_blocks] = SortCursorImpl(*it, description); - has_collation |= cursors[nonempty_blocks].has_collation; - - ++nonempty_blocks; + Block res = blocks[0]; + blocks.clear(); + return res; } - if (nonempty_blocks == 0) - return Block(); - - cursors.resize(nonempty_blocks); - - Block merged; - - if (has_collation) - merged = mergeImpl(blocks, cursors); - else - merged = mergeImpl(blocks, cursors); - - watch.stop(); - - size_t rows_before_merge = 0; - size_t bytes_before_merge = 0; - for (const auto & block : blocks) - { - rows_before_merge += block.rowsInFirstColumn(); - bytes_before_merge += block.bytes(); - } - - LOG_DEBUG(log, std::fixed << std::setprecision(2) - << "Merge sorted " << blocks.size() << " blocks, from " << rows_before_merge << " to " << merged.rows() << " rows" - << " in " << watch.elapsedSeconds() << " sec., " - << rows_before_merge / watch.elapsedSeconds() << " rows/sec., " - << bytes_before_merge / 1048576.0 / watch.elapsedSeconds() << " MiB/sec."); - - return merged; + return !has_collation + ? mergeImpl(queue) + : mergeImpl(queue_with_collation); } + template -Block MergeSortingBlockInputStream::mergeImpl(Blocks & blocks, CursorImpls & cursors) +Block MergeSortingBlocksBlockInputStream::mergeImpl(std::priority_queue & queue) { Block merged = blocks[0].cloneEmpty(); size_t num_columns = blocks[0].columns(); - typedef std::priority_queue Queue; - Queue queue; - - for (size_t i = 0; i < cursors.size(); ++i) - queue.push(TSortCursor(&cursors[i])); - ColumnPlainPtrs merged_columns; for (size_t i = 0; i < num_columns; ++i) /// TODO: reserve - merged_columns.push_back(&*merged.getByPosition(i).column); + merged_columns.push_back(merged.getByPosition(i).column.get()); /// Вынимаем строки в нужном порядке и кладём в merged. - for (size_t row = 0; (!limit || row < limit) && !queue.empty(); ++row) + size_t merged_rows = 0; + while (!queue.empty()) { TSortCursor current = queue.top(); queue.pop(); @@ -120,9 +155,24 @@ Block MergeSortingBlockInputStream::mergeImpl(Blocks & blocks, CursorImpls & cur current->next(); queue.push(current); } + + ++total_merged_rows; + if (limit && total_merged_rows == limit) + { + blocks.clear(); + return merged; + } + + ++merged_rows; + if (merged_rows == max_merged_block_size) + return merged; } + if (merged_rows == 0) + merged.clear(); + return merged; } + } diff --git a/dbms/src/DataStreams/MergingAggregatedBlockInputStream.cpp b/dbms/src/DataStreams/MergingAggregatedBlockInputStream.cpp index 3839c8c5b25..d833ed6209b 100644 --- a/dbms/src/DataStreams/MergingAggregatedBlockInputStream.cpp +++ b/dbms/src/DataStreams/MergingAggregatedBlockInputStream.cpp @@ -9,14 +9,23 @@ namespace DB Block MergingAggregatedBlockInputStream::readImpl() { - if (has_been_read) - return Block(); + if (!executed) + { + executed = true; + AggregatedDataVariants data_variants; + aggregator.mergeStream(children.back(), data_variants, max_threads); + blocks = aggregator.convertToBlocks(data_variants, final, max_threads); + it = blocks.begin(); + } - has_been_read = true; - - AggregatedDataVariants data_variants; - aggregator->merge(children.back(), data_variants); - return aggregator->convertToBlock(data_variants, final); + Block res; + if (isCancelled() || it == blocks.end()) + return res; + + res = *it; + ++it; + + return res; } diff --git a/dbms/src/DataStreams/NativeBlockInputStream.cpp b/dbms/src/DataStreams/NativeBlockInputStream.cpp index 25918dc911e..f0a2936fd34 100644 --- a/dbms/src/DataStreams/NativeBlockInputStream.cpp +++ b/dbms/src/DataStreams/NativeBlockInputStream.cpp @@ -72,6 +72,10 @@ Block NativeBlockInputStream::readImpl() if (istr.eof()) return res; + /// Дополнительная информация о блоке. + if (server_revision >= DBMS_MIN_REVISION_WITH_BLOCK_INFO) + res.info.read(istr); + /// Размеры size_t columns = 0; size_t rows = 0; diff --git a/dbms/src/DataStreams/NativeBlockOutputStream.cpp b/dbms/src/DataStreams/NativeBlockOutputStream.cpp index 4275aa7f29e..5b214623650 100644 --- a/dbms/src/DataStreams/NativeBlockOutputStream.cpp +++ b/dbms/src/DataStreams/NativeBlockOutputStream.cpp @@ -49,6 +49,10 @@ static void writeData(const IDataType & type, const IColumn & column, WriteBuffe void NativeBlockOutputStream::write(const Block & block) { + /// Дополнительная информация о блоке. + if (client_revision >= DBMS_MIN_REVISION_WITH_BLOCK_INFO) + block.info.write(ostr); + /// Размеры size_t columns = block.columns(); size_t rows = block.rows(); diff --git a/dbms/src/DataStreams/TotalsHavingBlockInputStream.cpp b/dbms/src/DataStreams/TotalsHavingBlockInputStream.cpp index 8aa2fd69c9b..558e0ca6571 100644 --- a/dbms/src/DataStreams/TotalsHavingBlockInputStream.cpp +++ b/dbms/src/DataStreams/TotalsHavingBlockInputStream.cpp @@ -27,8 +27,14 @@ const Block & TotalsHavingBlockInputStream::getTotals() /** Если totals_mode == AFTER_HAVING_AUTO, нужно решить, добавлять ли в TOTALS агрегаты для строк, * не прошедших max_rows_to_group_by. */ - if (overflow_aggregates && static_cast(passed_keys) / total_keys >= auto_include_threshold) - addToTotals(current_totals, overflow_aggregates, nullptr); + if (overflow_aggregates) + { + if (totals_mode == TotalsMode::BEFORE_HAVING + || totals_mode == TotalsMode::AFTER_HAVING_INCLUSIVE + || (totals_mode == TotalsMode::AFTER_HAVING_AUTO + && static_cast(passed_keys) / total_keys >= auto_include_threshold)) + addToTotals(current_totals, overflow_aggregates, nullptr); + } finalize(current_totals); totals = current_totals; @@ -50,24 +56,28 @@ Block TotalsHavingBlockInputStream::readImpl() { block = children[0]->read(); + /// Блок со значениями, не вошедшими в max_rows_to_group_by. Отложим его. + if (overflow_row && block && block.info.is_overflows) + { + overflow_aggregates = block; + continue; + } + if (!block) return finalized; finalized = block; finalize(finalized); - total_keys += finalized.rows() - (overflow_row ? 1 : 0); + total_keys += finalized.rows(); - if (filter_column_name.empty() || totals_mode == TotalsMode::BEFORE_HAVING) + if (filter_column_name.empty()) { - /** Включая особую нулевую строку, если overflow_row == true. - * Предполагается, что если totals_mode == AFTER_HAVING_EXCLUSIVE, нам эту строку не дадут. - */ addToTotals(current_totals, block, nullptr); } - - if (!filter_column_name.empty()) + else { + /// Вычисляем выражение в HAVING. expression->execute(finalized); size_t filter_column_pos = finalized.getPositionByName(filter_column_name); @@ -85,25 +95,13 @@ Block TotalsHavingBlockInputStream::readImpl() IColumn::Filter & filter = filter_column->getData(); - if (totals_mode != TotalsMode::BEFORE_HAVING) - { - if (overflow_row) - { - filter[0] = totals_mode == TotalsMode::AFTER_HAVING_INCLUSIVE; - addToTotals(current_totals, block, &filter); - - if (totals_mode == TotalsMode::AFTER_HAVING_AUTO) - addToTotals(overflow_aggregates, block, nullptr, 1); - } - else - { - addToTotals(current_totals, block, &filter); - } - } - - if (overflow_row) - filter[0] = 0; + /// Прибавляем значения в totals (если это не было сделано ранее). + if (totals_mode == TotalsMode::BEFORE_HAVING) + addToTotals(current_totals, block, nullptr); + else + addToTotals(current_totals, block, &filter); + /// Фильтруем блок по выражению в HAVING. size_t columns = finalized.columns(); for (size_t i = 0; i < columns; ++i) @@ -117,19 +115,6 @@ Block TotalsHavingBlockInputStream::readImpl() } } } - else - { - if (overflow_row) - { - /// Придется выбросить одну строку из начала всех столбцов. - size_t columns = finalized.columns(); - for (size_t i = 0; i < columns; ++i) - { - ColumnWithNameAndType & current_column = finalized.getByPosition(i); - current_column.column = current_column.column->cut(1, current_column.column->size() - 1); - } - } - } if (!finalized) continue; @@ -139,7 +124,7 @@ Block TotalsHavingBlockInputStream::readImpl() } } -void TotalsHavingBlockInputStream::addToTotals(Block & totals, Block & block, const IColumn::Filter * filter, size_t rows) +void TotalsHavingBlockInputStream::addToTotals(Block & totals, Block & block, const IColumn::Filter * filter) { bool init = !totals; @@ -188,7 +173,7 @@ void TotalsHavingBlockInputStream::addToTotals(Block & totals, Block & block, co } const ColumnAggregateFunction::Container_t & vec = column->getData(); - size_t size = std::min(vec.size(), rows); + size_t size = vec.size(); if (filter) { diff --git a/dbms/src/DataStreams/copyData.cpp b/dbms/src/DataStreams/copyData.cpp index c78cb41af72..6d1b3197cd6 100644 --- a/dbms/src/DataStreams/copyData.cpp +++ b/dbms/src/DataStreams/copyData.cpp @@ -7,13 +7,21 @@ namespace DB { -void copyData(IBlockInputStream & from, IBlockOutputStream & to) +void copyData(IBlockInputStream & from, IBlockOutputStream & to, volatile bool * is_cancelled) { from.readPrefix(); to.writePrefix(); while (Block block = from.read()) + { + if (is_cancelled && *is_cancelled) + break; + to.write(block); + } + + if (is_cancelled && *is_cancelled) + return; /// Для вывода дополнительной информации в некоторых форматах. if (IProfilingBlockInputStream * input = dynamic_cast(&from)) @@ -25,6 +33,9 @@ void copyData(IBlockInputStream & from, IBlockOutputStream & to) to.setExtremes(input->getExtremes()); } + if (is_cancelled && *is_cancelled) + return; + from.readSuffix(); to.writeSuffix(); } @@ -45,7 +56,7 @@ void copyData(IRowInputStream & from, IRowOutputStream & to) from.readRowBetweenDelimiter(); to.writeRowBetweenDelimiter(); } - + Row row; bool has_rows = from.read(row); if (!has_rows) diff --git a/dbms/src/DataStreams/tests/aggregating_stream.cpp b/dbms/src/DataStreams/tests/aggregating_stream.cpp index ce88041c040..1fb11ded9da 100644 --- a/dbms/src/DataStreams/tests/aggregating_stream.cpp +++ b/dbms/src/DataStreams/tests/aggregating_stream.cpp @@ -91,7 +91,8 @@ int main(int argc, char ** argv) } DB::BlockInputStreamPtr stream = new DB::OneBlockInputStream(block); - stream = new DB::AggregatingBlockInputStream(stream, key_column_numbers, aggregate_descriptions, false, true, 0, DB::OverflowMode::THROW); + stream = new DB::AggregatingBlockInputStream(stream, key_column_numbers, aggregate_descriptions, false, true, + 0, DB::OverflowMode::THROW, nullptr, 0); DB::WriteBufferFromOStream ob(std::cout); DB::RowOutputStreamPtr row_out = new DB::TabSeparatedRowOutputStream(ob, sample); diff --git a/dbms/src/DataStreams/tests/glue_streams.cpp b/dbms/src/DataStreams/tests/glue_streams.cpp index 432a69d6943..f19b31fac33 100644 --- a/dbms/src/DataStreams/tests/glue_streams.cpp +++ b/dbms/src/DataStreams/tests/glue_streams.cpp @@ -44,7 +44,6 @@ int main(int argc, char ** argv) loadMetadata(context); context.setCurrentDatabase("default"); - context.setSetting("asynchronous", Field(0UL)); context.setSetting("max_threads", 1UL); BlockIO io1 = executeQuery( diff --git a/dbms/src/DataStreams/tests/native_streams.cpp b/dbms/src/DataStreams/tests/native_streams.cpp index 1e58e1dde7e..2c8348ef252 100644 --- a/dbms/src/DataStreams/tests/native_streams.cpp +++ b/dbms/src/DataStreams/tests/native_streams.cpp @@ -22,6 +22,7 @@ #include #include +#include int main(int argc, char ** argv) @@ -107,7 +108,7 @@ int main(int argc, char ** argv) SharedPtr in = table->read(column_names, 0, Context{}, Settings(), stage)[0]; WriteBufferFromOStream out1(std::cout); CompressedWriteBuffer out2(out1); - NativeBlockOutputStream out3(out2); + NativeBlockOutputStream out3(out2, Revision::get()); copyData(*in, out3); } @@ -118,7 +119,7 @@ int main(int argc, char ** argv) ReadBufferFromIStream in1(std::cin); CompressedReadBuffer in2(in1); - NativeBlockInputStream in3(in2, factory); + NativeBlockInputStream in3(in2, factory, Revision::get()); SharedPtr out = table->write(0); copyData(in3, *out); } diff --git a/dbms/src/DataStreams/tests/sorting_stream.cpp b/dbms/src/DataStreams/tests/sorting_stream.cpp index b5381676cb9..23ab3efa995 100644 --- a/dbms/src/DataStreams/tests/sorting_stream.cpp +++ b/dbms/src/DataStreams/tests/sorting_stream.cpp @@ -158,10 +158,11 @@ int main(int argc, char ** argv) sort_columns.push_back(SortColumnDescription(3, 1)); QueryProcessingStage::Enum stage; + DataTypeFactory data_type_factory; Poco::SharedPtr in = table->read(column_names, 0, Context{}, Settings(), stage, argc == 2 ? atoi(argv[1]) : 1048576)[0]; in = new PartialSortingBlockInputStream(in, sort_columns); - in = new MergeSortingBlockInputStream(in, sort_columns); + in = new MergeSortingBlockInputStream(in, sort_columns, DEFAULT_BLOCK_SIZE, 0, 0, "", data_type_factory); //in = new LimitBlockInputStream(in, 10); WriteBufferFromOStream ob(std::cout); diff --git a/dbms/src/Functions/FunctionsCoding.cpp b/dbms/src/Functions/FunctionsCoding.cpp index 36d42f873b8..5bef94fe93a 100644 --- a/dbms/src/Functions/FunctionsCoding.cpp +++ b/dbms/src/Functions/FunctionsCoding.cpp @@ -11,6 +11,7 @@ void registerFunctionsCoding(FunctionFactory & factory) factory.registerFunction(); factory.registerFunction(); factory.registerFunction(); + factory.registerFunction(); factory.registerFunction(); factory.registerFunction(); factory.registerFunction(); diff --git a/dbms/src/Interpreters/Aggregator.cpp b/dbms/src/Interpreters/Aggregator.cpp index 50c873c7a10..ecd2e9ee20c 100644 --- a/dbms/src/Interpreters/Aggregator.cpp +++ b/dbms/src/Interpreters/Aggregator.cpp @@ -1,4 +1,8 @@ #include +#include +#include + +#include #include @@ -29,9 +33,33 @@ AggregatedDataVariants::~AggregatedDataVariants() } +void AggregatedDataVariants::convertToTwoLevel() +{ + if (aggregator) + LOG_TRACE(aggregator->log, "Converting aggregation data to two-level."); + + switch (type) + { + #define M(NAME) \ + case Type::NAME: \ + NAME ## _two_level.reset(new decltype(NAME ## _two_level)::element_type(*NAME)); \ + NAME.reset(); \ + type = Type::NAME ## _two_level; \ + break; + + APPLY_FOR_VARIANTS_CONVERTIBLE_TO_TWO_LEVEL(M) + + #undef M + + default: + throw Exception("Wrong data variant passed.", ErrorCodes::LOGICAL_ERROR); + } +} + + void Aggregator::initialize(Block & block) { - Poco::ScopedLock lock(mutex); + std::lock_guard lock(mutex); if (initialized) return; @@ -101,8 +129,186 @@ void Aggregator::initialize(Block & block) } +void Aggregator::compileIfPossible(AggregatedDataVariants::Type type) +{ + std::lock_guard lock(mutex); + + if (compiled_if_possible) + return; + + compiled_if_possible = true; + + std::string method_typename; + std::string method_typename_two_level; + + if (false) {} +#define M(NAME) \ + else if (type == AggregatedDataVariants::Type::NAME) \ + { \ + method_typename = "decltype(AggregatedDataVariants::" #NAME ")::element_type"; \ + method_typename_two_level = "decltype(AggregatedDataVariants::" #NAME "_two_level)::element_type"; \ + } + + APPLY_FOR_VARIANTS_CONVERTIBLE_TO_TWO_LEVEL(M) +#undef M + +#define M(NAME) \ + else if (type == AggregatedDataVariants::Type::NAME) \ + method_typename = "decltype(AggregatedDataVariants::" #NAME ")::element_type"; + + APPLY_FOR_VARIANTS_NOT_CONVERTIBLE_TO_TWO_LEVEL(M) +#undef M + else if (type == AggregatedDataVariants::Type::without_key) {} + else + throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); + + /// Список типов агрегатных функций. + std::stringstream aggregate_functions_typenames_str; + for (size_t i = 0; i < aggregates_size; ++i) + { + int status = 0; + char * type_name_ptr = abi::__cxa_demangle(typeid(*aggregate_functions[i]).name(), 0, 0, &status); + std::string type_name = type_name_ptr; + free(type_name_ptr); + + if (status) + throw Exception("Cannot compile code: cannot demangle name " + String(typeid(*aggregate_functions[i]).name()) + + ", status: " + toString(status), ErrorCodes::CANNOT_COMPILE_CODE); + + aggregate_functions_typenames_str << ((i != 0) ? ", " : "") << type_name; + } + + std::string aggregate_functions_typenames = aggregate_functions_typenames_str.str(); + + std::stringstream key_str; + key_str << "Aggregate: "; + if (!method_typename.empty()) + key_str << method_typename + ", "; + key_str << aggregate_functions_typenames; + std::string key = key_str.str(); + + auto get_code = [method_typename, method_typename_two_level, aggregate_functions_typenames] + { + /// Короткий кусок кода, представляющий собой явное инстанцирование шаблона. + std::stringstream code; + code << + "#include \n" + "\n" + "namespace DB\n" + "{\n" + "\n"; + + /// Может быть до двух инстанцирований шаблона - для обычного и two_level вариантов. + auto append_code_for_specialization = + [&code, &aggregate_functions_typenames] (const std::string & method_typename, const std::string & suffix) + { + code << + "template void Aggregator::executeSpecialized<\n" + "\t" << method_typename << ", TypeList<" << aggregate_functions_typenames << ">>(\n" + "\t" << method_typename << " &, Arena *, size_t, ConstColumnPlainPtrs &,\n" + "\tAggregateColumns &, const Sizes &, StringRefs &, bool, AggregateDataPtr) const;\n" + "\n" + "static void wrapper" << suffix << "(\n" + "\tconst Aggregator & aggregator,\n" + "\t" << method_typename << " & method,\n" + "\tArena * arena,\n" + "\tsize_t rows,\n" + "\tConstColumnPlainPtrs & key_columns,\n" + "\tAggregator::AggregateColumns & aggregate_columns,\n" + "\tconst Sizes & key_sizes,\n" + "\tStringRefs & keys,\n" + "\tbool no_more_keys,\n" + "\tAggregateDataPtr overflow_row)\n" + "{\n" + "\taggregator.executeSpecialized<\n" + "\t\t" << method_typename << ", TypeList<" << aggregate_functions_typenames << ">>(\n" + "\t\tmethod, arena, rows, key_columns, aggregate_columns, key_sizes, keys, no_more_keys, overflow_row);\n" + "}\n" + "\n" + "const void * getPtr" << suffix << "() __attribute__((__visibility__(\"default\")));\n" + "const void * getPtr" << suffix << "()\n" /// Без этой обёртки непонятно, как достать нужный символ из скомпилированной библиотеки. + "{\n" + "\treturn reinterpret_cast(&wrapper" << suffix << ");\n" + "}\n"; + }; + + if (!method_typename.empty()) + append_code_for_specialization(method_typename, ""); + else + { + /// Для метода without_key. + code << + "template void Aggregator::executeSpecializedWithoutKey<\n" + "\t" << "TypeList<" << aggregate_functions_typenames << ">>(\n" + "\tAggregatedDataWithoutKey &, size_t, AggregateColumns &) const;\n" + "\n" + "static void wrapper(\n" + "\tconst Aggregator & aggregator,\n" + "\tAggregatedDataWithoutKey & method,\n" + "\tsize_t rows,\n" + "\tAggregator::AggregateColumns & aggregate_columns)\n" + "{\n" + "\taggregator.executeSpecializedWithoutKey<\n" + "\t\tTypeList<" << aggregate_functions_typenames << ">>(\n" + "\t\tmethod, rows, aggregate_columns);\n" + "}\n" + "\n" + "const void * getPtr() __attribute__((__visibility__(\"default\")));\n" + "const void * getPtr()\n" + "{\n" + "\treturn reinterpret_cast(&wrapper);\n" + "}\n"; + } + + if (!method_typename_two_level.empty()) + append_code_for_specialization(method_typename_two_level, "TwoLevel"); + else + { + /// Заглушка. + code << + "const void * getPtrTwoLevel() __attribute__((__visibility__(\"default\")));\n" + "const void * getPtrTwoLevel()\n" + "{\n" + "\treturn nullptr;\n" + "}\n"; + } + + code << + "}\n"; + + return code.str(); + }; + + auto compiled_data_owned_by_callback = compiled_data; + auto on_ready = [compiled_data_owned_by_callback] (SharedLibraryPtr & lib) + { + if (compiled_data_owned_by_callback.unique()) /// Aggregator уже уничтожен. + return; + + compiled_data_owned_by_callback->compiled_aggregator = lib; + compiled_data_owned_by_callback->compiled_method_ptr = lib->get("_ZN2DB6getPtrEv")(); + compiled_data_owned_by_callback->compiled_two_level_method_ptr = lib->get("_ZN2DB14getPtrTwoLevelEv")(); + }; + + /** Если библиотека уже была скомпилирована, то возвращается ненулевой SharedLibraryPtr. + * Если библиотека не была скомпилирована, то увеличивается счётчик, и возвращается nullptr. + * Если счётчик достигнул значения min_count_to_compile, то асинхронно (в отдельном потоке) запускается компиляция, + * по окончании которой вызывается колбэк on_ready. + */ + SharedLibraryPtr lib = compiler->getOrCount(key, min_count_to_compile, get_code, on_ready); + + /// Если результат уже готов. + if (lib) + on_ready(lib); +} + + AggregatedDataVariants::Type Aggregator::chooseAggregationMethod(const ConstColumnPlainPtrs & key_columns, Sizes & key_sizes) { + /** Возвращает обычные (не two-level) методы, так как обработка начинается с них. + * Затем, в процессе работы, данные могут быть переконвертированы в two-level структуру, если их становится много. + */ + bool keys_fit_128_bits = true; size_t keys_bytes = 0; key_sizes.resize(keys_size); @@ -121,36 +327,36 @@ AggregatedDataVariants::Type Aggregator::chooseAggregationMethod(const ConstColu /// Если ключей нет if (keys_size == 0) - return AggregatedDataVariants::WITHOUT_KEY; + return AggregatedDataVariants::Type::without_key; /// Если есть один числовой ключ, который помещается в 64 бита if (keys_size == 1 && key_columns[0]->isNumeric()) { size_t size_of_field = key_columns[0]->sizeOfField(); if (size_of_field == 1) - return AggregatedDataVariants::KEY_8; + return AggregatedDataVariants::Type::key8; if (size_of_field == 2) - return AggregatedDataVariants::KEY_16; + return AggregatedDataVariants::Type::key16; if (size_of_field == 4) - return AggregatedDataVariants::KEY_32; + return AggregatedDataVariants::Type::key32; if (size_of_field == 8) - return AggregatedDataVariants::KEY_64; + return AggregatedDataVariants::Type::key64; throw Exception("Logical error: numeric column has sizeOfField not in 1, 2, 4, 8.", ErrorCodes::LOGICAL_ERROR); } /// Если ключи помещаются в 128 бит, будем использовать хэш-таблицу по упакованным в 128-бит ключам if (keys_fit_128_bits) - return AggregatedDataVariants::KEYS_128; + return AggregatedDataVariants::Type::keys128; /// Если есть один строковый ключ, то используем хэш-таблицу с ним if (keys_size == 1 && typeid_cast(key_columns[0])) - return AggregatedDataVariants::KEY_STRING; + return AggregatedDataVariants::Type::key_string; if (keys_size == 1 && typeid_cast(key_columns[0])) - return AggregatedDataVariants::KEY_FIXED_STRING; + return AggregatedDataVariants::Type::key_fixed_string; /// Иначе будем агрегировать по хэшу от ключей. - return AggregatedDataVariants::HASHED; + return AggregatedDataVariants::Type::hashed; } @@ -194,20 +400,60 @@ void NO_INLINE Aggregator::executeImpl( bool no_more_keys, AggregateDataPtr overflow_row) const { - method.init(key_columns); + typename Method::State state; + state.init(key_columns); + + if (!no_more_keys) + executeImplCase(method, state, aggregates_pool, rows, key_columns, aggregate_columns, key_sizes, keys, overflow_row); + else + executeImplCase(method, state, aggregates_pool, rows, key_columns, aggregate_columns, key_sizes, keys, overflow_row); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + +template +void NO_INLINE Aggregator::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 +{ + /// NOTE При редактировании этого кода, обратите также внимание на SpecializedAggregator.h. /// Для всех строчек. + typename Method::iterator it; + typename Method::Key prev_key; for (size_t i = 0; i < rows; ++i) { - typename Method::iterator it; bool inserted; /// Вставили новый ключ, или такой ключ уже был? bool overflow = false; /// Новый ключ не поместился в хэш-таблицу из-за no_more_keys. /// Получаем ключ для вставки в хэш-таблицу. - typename Method::Key key = method.getKey(key_columns, keys_size, i, key_sizes, 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); + for (size_t j = 0; j < aggregates_size; ++j) /// NOTE: Заменить индекс на два указателя? + aggregate_functions[j]->add(value + offsets_of_aggregate_states[j], &aggregate_columns[j][0], i); + + continue; + } + else + prev_key = key; - if (Method::never_overflows || !no_more_keys) /// Вставляем. method.data.emplace(key, it, inserted); + } else { /// Будем добавлять только если ключ уже есть. @@ -218,20 +464,20 @@ void NO_INLINE Aggregator::executeImpl( } /// Если ключ не поместился, и данные не надо агрегировать в отдельную строку, то делать нечего. - if (!Method::never_overflows && overflow && !overflow_row) + if (no_more_keys && overflow && !overflow_row) continue; /// Если вставили новый ключ - инициализируем состояния агрегатных функций, и возможно, что-нибудь связанное с ключом. if (inserted) { - method.onNewKey(it, keys_size, i, keys, *aggregates_pool); + 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); createAggregateStates(aggregate_data); } - AggregateDataPtr value = (Method::never_overflows || !overflow) ? Method::getAggregateData(it->second) : overflow_row; + AggregateDataPtr value = (!no_more_keys || !overflow) ? Method::getAggregateData(it->second) : overflow_row; /// Добавляем значения в агрегатные функции. for (size_t j = 0; j < aggregates_size; ++j) @@ -239,139 +485,33 @@ void NO_INLINE Aggregator::executeImpl( } } +#pragma GCC diagnostic pop -template -void NO_INLINE Aggregator::convertToBlockImpl( - Method & method, - ColumnPlainPtrs & key_columns, - AggregateColumnsData & aggregate_columns, - ColumnPlainPtrs & final_aggregate_columns, - const Sizes & key_sizes, - size_t start_row, - bool final) const + +void NO_INLINE Aggregator::executeWithoutKeyImpl( + AggregatedDataWithoutKey & res, + size_t rows, + AggregateColumns & aggregate_columns) const { - if (!final) - { - size_t j = start_row; - for (typename Method::const_iterator it = method.data.begin(); it != method.data.end(); ++it, ++j) - { - method.insertKeyIntoColumns(it, key_columns, keys_size, key_sizes); + /// Оптимизация в случае единственной агрегатной функции count. + AggregateFunctionCount * agg_count = aggregates_size == 1 + ? typeid_cast(aggregate_functions[0]) + : NULL; - for (size_t i = 0; i < aggregates_size; ++i) - (*aggregate_columns[i])[j] = Method::getAggregateData(it->second) + offsets_of_aggregate_states[i]; - } - } + if (agg_count) + agg_count->addDelta(res, rows); else { - for (typename Method::const_iterator it = method.data.begin(); it != method.data.end(); ++it) + for (size_t i = 0; i < rows; ++i) { - method.insertKeyIntoColumns(it, key_columns, keys_size, key_sizes); - - for (size_t i = 0; i < aggregates_size; ++i) - aggregate_functions[i]->insertResultInto( - Method::getAggregateData(it->second) + offsets_of_aggregate_states[i], - *final_aggregate_columns[i]); + /// Добавляем значения + for (size_t j = 0; j < aggregates_size; ++j) + aggregate_functions[j]->add(res + offsets_of_aggregate_states[j], &aggregate_columns[j][0], i); } } } -template -void NO_INLINE Aggregator::mergeDataImpl( - Method & method_dst, - Method & method_src) const -{ - for (typename Method::iterator it = method_src.data.begin(); it != method_src.data.end(); ++it) - { - typename Method::iterator res_it; - bool inserted; - method_dst.data.emplace(it->first, res_it, inserted); - - if (!inserted) - { - for (size_t i = 0; i < aggregates_size; ++i) - aggregate_functions[i]->merge( - Method::getAggregateData(res_it->second) + offsets_of_aggregate_states[i], - Method::getAggregateData(it->second) + offsets_of_aggregate_states[i]); - - for (size_t i = 0; i < aggregates_size; ++i) - aggregate_functions[i]->destroy( - Method::getAggregateData(it->second) + offsets_of_aggregate_states[i]); - - Method::getAggregateData(it->second) = nullptr; - } - else - { - res_it->second = it->second; - } - } -} - - -template -void NO_INLINE Aggregator::mergeStreamsImpl( - Method & method, - Arena * aggregates_pool, - size_t start_row, - size_t rows, - ConstColumnPlainPtrs & key_columns, - AggregateColumnsData & aggregate_columns, - const Sizes & key_sizes, - StringRefs & keys) const -{ - method.init(key_columns); - - /// Для всех строчек. - for (size_t i = start_row; i < rows; ++i) - { - typename Method::iterator it; - bool inserted; /// Вставили новый ключ, или такой ключ уже был? - - /// Получаем ключ для вставки в хэш-таблицу. - typename Method::Key key = method.getKey(key_columns, keys_size, i, key_sizes, keys); - - method.data.emplace(key, it, inserted); - - 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); - createAggregateStates(aggregate_data); - } - - /// Мерджим состояния агрегатных функций. - for (size_t j = 0; j < aggregates_size; ++j) - aggregate_functions[j]->merge( - Method::getAggregateData(it->second) + offsets_of_aggregate_states[j], - (*aggregate_columns[j])[i]); - } -} - - -template -void NO_INLINE Aggregator::destroyImpl( - Method & method) const -{ - for (typename Method::const_iterator it = method.data.begin(); it != method.data.end(); ++it) - { - char * data = Method::getAggregateData(it->second); - - /** Если исключение (обычно нехватка памяти, кидается MemoryTracker-ом) возникло - * после вставки ключа в хэш-таблицу, но до создания всех состояний агрегатных функций, - * то data будет равен nullptr-у. - */ - if (nullptr == data) - continue; - - for (size_t i = 0; i < aggregates_size; ++i) - if (!aggregate_functions[i]->isState()) - aggregate_functions[i]->destroy(data + offsets_of_aggregate_states[i]); - } -} - - bool Aggregator::executeOnBlock(Block & block, AggregatedDataVariants & result, ConstColumnPlainPtrs & key_columns, AggregateColumns & aggregate_columns, Sizes & key_sizes, StringRefs & key, @@ -418,75 +558,104 @@ bool Aggregator::executeOnBlock(Block & block, AggregatedDataVariants & result, result.keys_size = keys_size; result.key_sizes = key_sizes; LOG_TRACE(log, "Aggregation method: " << result.getMethodName()); + + if (compiler) + compileIfPossible(result.type); } - if (overflow_row && !result.without_key) + if ((overflow_row || result.type == AggregatedDataVariants::Type::without_key) && !result.without_key) { result.without_key = result.aggregates_pool->alloc(total_size_of_aggregate_states); createAggregateStates(result.without_key); } - if (result.type == AggregatedDataVariants::WITHOUT_KEY) + /// Выбираем один из методов агрегации и вызываем его. + + /// Для случая, когда нет ключей (всё агегировать в одну строку). + if (result.type == AggregatedDataVariants::Type::without_key) { - AggregatedDataWithoutKey & res = result.without_key; - if (!res) + /// Если есть динамически скомпилированный код. + if (compiled_data->compiled_method_ptr) { - res = result.aggregates_pool->alloc(total_size_of_aggregate_states); - createAggregateStates(res); + reinterpret_cast< + void (*)(const Aggregator &, AggregatedDataWithoutKey &, size_t, AggregateColumns &)> + (compiled_data->compiled_method_ptr)(*this, result.without_key, rows, aggregate_columns); } + else + executeWithoutKeyImpl(result.without_key, rows, aggregate_columns); + } + else + { + /// Сюда пишутся данные, не поместившиеся в max_rows_to_group_by при group_by_overflow_mode = any. + AggregateDataPtr overflow_row_ptr = overflow_row ? result.without_key : nullptr; - /// Оптимизация в случае единственной агрегатной функции count. - AggregateFunctionCount * agg_count = aggregates_size == 1 - ? typeid_cast(aggregate_functions[0]) - : NULL; + bool is_two_level = result.isTwoLevel(); - if (agg_count) - agg_count->addDelta(res, rows); + /// Скомпилированный код, для обычной структуры. + if (!is_two_level && compiled_data->compiled_method_ptr) + { + #define M(NAME, IS_TWO_LEVEL) \ + else if (result.type == AggregatedDataVariants::Type::NAME) \ + reinterpret_cast(compiled_data->compiled_method_ptr) \ + (*this, *result.NAME, result.aggregates_pool, rows, key_columns, aggregate_columns, \ + result.key_sizes, key, no_more_keys, overflow_row_ptr); + + if (false) {} + APPLY_FOR_AGGREGATED_VARIANTS(M) + #undef M + } + /// Скомпилированный код, для two-level структуры. + else if (is_two_level && compiled_data->compiled_two_level_method_ptr) + { + #define M(NAME) \ + else if (result.type == AggregatedDataVariants::Type::NAME) \ + reinterpret_cast(compiled_data->compiled_two_level_method_ptr) \ + (*this, *result.NAME, result.aggregates_pool, rows, key_columns, aggregate_columns, \ + result.key_sizes, key, no_more_keys, overflow_row_ptr); + + if (false) {} + APPLY_FOR_VARIANTS_TWO_LEVEL(M) + #undef M + } + /// Когда нет динамически скомпилированного кода. else { - for (size_t i = 0; i < rows; ++i) - { - /// Добавляем значения - for (size_t j = 0; j < aggregates_size; ++j) - aggregate_functions[j]->add(res + offsets_of_aggregate_states[j], &aggregate_columns[j][0], i); - } + #define M(NAME, IS_TWO_LEVEL) \ + else if (result.type == AggregatedDataVariants::Type::NAME) \ + executeImpl(*result.NAME, result.aggregates_pool, rows, key_columns, aggregate_columns, \ + result.key_sizes, key, no_more_keys, overflow_row_ptr); + + if (false) {} + APPLY_FOR_AGGREGATED_VARIANTS(M) + #undef M } } - AggregateDataPtr overflow_row_ptr = overflow_row ? result.without_key : nullptr; + size_t result_size = result.size(); - if (result.type == AggregatedDataVariants::KEY_8) - executeImpl(*result.key8, result.aggregates_pool, rows, key_columns, aggregate_columns, - result.key_sizes, key, no_more_keys, overflow_row_ptr); - else if (result.type == AggregatedDataVariants::KEY_16) - executeImpl(*result.key16, result.aggregates_pool, rows, key_columns, aggregate_columns, - result.key_sizes, key, no_more_keys, overflow_row_ptr); - else if (result.type == AggregatedDataVariants::KEY_32) - executeImpl(*result.key32, result.aggregates_pool, rows, key_columns, aggregate_columns, - result.key_sizes, key, no_more_keys, overflow_row_ptr); - else if (result.type == AggregatedDataVariants::KEY_64) - executeImpl(*result.key64, result.aggregates_pool, rows, key_columns, aggregate_columns, - result.key_sizes, key, no_more_keys, overflow_row_ptr); - else if (result.type == AggregatedDataVariants::KEY_STRING) - executeImpl(*result.key_string, result.aggregates_pool, rows, key_columns, aggregate_columns, - result.key_sizes, key, no_more_keys, overflow_row_ptr); - else if (result.type == AggregatedDataVariants::KEY_FIXED_STRING) - executeImpl(*result.key_fixed_string, result.aggregates_pool, rows, key_columns, aggregate_columns, - result.key_sizes, key, no_more_keys, overflow_row_ptr); - else if (result.type == AggregatedDataVariants::KEYS_128) - executeImpl(*result.keys128, result.aggregates_pool, rows, key_columns, aggregate_columns, - result.key_sizes, key, no_more_keys, overflow_row_ptr); - else if (result.type == AggregatedDataVariants::HASHED) - executeImpl(*result.hashed, result.aggregates_pool, rows, key_columns, aggregate_columns, - result.key_sizes, key, no_more_keys, overflow_row_ptr); - else if (result.type != AggregatedDataVariants::WITHOUT_KEY) - throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); + /// Если результат уже достаточно большой, и его можно сконвертировать в двухуровневую хэш-таблицу. + constexpr auto TWO_LEVEL_HASH_TABLE_THRESHOLD = 30000; + + /** Почему выбрано 30 000? Потому что при таком количестве элементов, в TwoLevelHashTable, + * скорее всего, хватит места на все ключи, с размером таблицы по-умолчанию + * (256 корзин по 256 ячеек, fill factor = 0.5) + * TODO Не конвертировать, если запрос выполняется в один поток. + */ + + if (result.isConvertibleToTwoLevel() && result_size >= TWO_LEVEL_HASH_TABLE_THRESHOLD) + result.convertToTwoLevel(); /// Проверка ограничений. - if (!no_more_keys && max_rows_to_group_by && result.size() > max_rows_to_group_by) + if (!no_more_keys && max_rows_to_group_by && result_size > max_rows_to_group_by) { if (group_by_overflow_mode == OverflowMode::THROW) - throw Exception("Limit for rows to GROUP BY exceeded: has " + toString(result.size()) + throw Exception("Limit for rows to GROUP BY exceeded: has " + toString(result_size) + " rows, maximum: " + toString(max_rows_to_group_by), ErrorCodes::TOO_MUCH_ROWS); else if (group_by_overflow_mode == OverflowMode::BREAK) @@ -545,18 +714,69 @@ void Aggregator::execute(BlockInputStreamPtr stream, AggregatedDataVariants & re } -Block Aggregator::convertToBlock(AggregatedDataVariants & data_variants, bool final) +template +void Aggregator::convertToBlockImpl( + Method & method, + Table & data, + ColumnPlainPtrs & key_columns, + AggregateColumnsData & aggregate_columns, + ColumnPlainPtrs & final_aggregate_columns, + const Sizes & key_sizes, + bool final) const +{ + if (final) + convertToBlockImplFinal(method, data, key_columns, final_aggregate_columns, key_sizes); + else + convertToBlockImplNotFinal(method, data, key_columns, aggregate_columns, key_sizes); +} + + +template +void NO_INLINE Aggregator::convertToBlockImplFinal( + Method & method, + Table & data, + ColumnPlainPtrs & key_columns, + ColumnPlainPtrs & final_aggregate_columns, + const Sizes & key_sizes) const +{ + for (typename Table::const_iterator it = data.begin(); it != data.end(); ++it) + { + method.insertKeyIntoColumns(*it, key_columns, keys_size, key_sizes); + + for (size_t i = 0; i < aggregates_size; ++i) + aggregate_functions[i]->insertResultInto( + Method::getAggregateData(it->second) + offsets_of_aggregate_states[i], + *final_aggregate_columns[i]); + } +} + +template +void NO_INLINE Aggregator::convertToBlockImplNotFinal( + Method & method, + Table & data, + ColumnPlainPtrs & key_columns, + AggregateColumnsData & aggregate_columns, + const Sizes & key_sizes) const +{ + size_t j = 0; + for (typename Table::const_iterator it = data.begin(); it != data.end(); ++it, ++j) + { + method.insertKeyIntoColumns(*it, key_columns, keys_size, key_sizes); + + for (size_t i = 0; i < aggregates_size; ++i) + (*aggregate_columns[i])[j] = Method::getAggregateData(it->second) + offsets_of_aggregate_states[i]; + } +} + + +template +Block Aggregator::prepareBlockAndFill( + AggregatedDataVariants & data_variants, + bool final, + size_t rows, + Filler && filler) const { Block res = sample.cloneEmpty(); - size_t rows = data_variants.size(); - - LOG_TRACE(log, "Converting aggregated data to block"); - - Stopwatch watch; - - /// В какой структуре данных агрегированы данные? - if (data_variants.empty()) - return Block(); ColumnPlainPtrs key_columns(keys_size); AggregateColumnsData aggregate_columns(aggregates_size); @@ -603,49 +823,7 @@ Block Aggregator::convertToBlock(AggregatedDataVariants & data_variants, bool fi } } - if (data_variants.type == AggregatedDataVariants::WITHOUT_KEY || overflow_row) - { - AggregatedDataWithoutKey & data = data_variants.without_key; - - for (size_t i = 0; i < aggregates_size; ++i) - if (!final) - (*aggregate_columns[i])[0] = data + offsets_of_aggregate_states[i]; - else - aggregate_functions[i]->insertResultInto(data + offsets_of_aggregate_states[i], *final_aggregate_columns[i]); - - if (overflow_row) - for (size_t i = 0; i < keys_size; ++i) - key_columns[i]->insertDefault(); - } - - size_t start_row = overflow_row ? 1 : 0; - - if (data_variants.type == AggregatedDataVariants::KEY_8) - convertToBlockImpl(*data_variants.key8, key_columns, aggregate_columns, - final_aggregate_columns, data_variants.key_sizes, start_row, final); - else if (data_variants.type == AggregatedDataVariants::KEY_16) - convertToBlockImpl(*data_variants.key16, key_columns, aggregate_columns, - final_aggregate_columns, data_variants.key_sizes, start_row, final); - else if (data_variants.type == AggregatedDataVariants::KEY_32) - convertToBlockImpl(*data_variants.key32, key_columns, aggregate_columns, - final_aggregate_columns, data_variants.key_sizes, start_row, final); - else if (data_variants.type == AggregatedDataVariants::KEY_64) - convertToBlockImpl(*data_variants.key64, key_columns, aggregate_columns, - final_aggregate_columns, data_variants.key_sizes, start_row, final); - else if (data_variants.type == AggregatedDataVariants::KEY_STRING) - convertToBlockImpl(*data_variants.key_string, key_columns, aggregate_columns, - final_aggregate_columns, data_variants.key_sizes, start_row, final); - else if (data_variants.type == AggregatedDataVariants::KEY_FIXED_STRING) - convertToBlockImpl(*data_variants.key_fixed_string, key_columns, aggregate_columns, - final_aggregate_columns, data_variants.key_sizes, start_row, final); - else if (data_variants.type == AggregatedDataVariants::KEYS_128) - convertToBlockImpl(*data_variants.keys128, key_columns, aggregate_columns, - final_aggregate_columns, data_variants.key_sizes, start_row, final); - else if (data_variants.type == AggregatedDataVariants::HASHED) - convertToBlockImpl(*data_variants.hashed, key_columns, aggregate_columns, - final_aggregate_columns, data_variants.key_sizes, start_row, final); - else if (data_variants.type != AggregatedDataVariants::WITHOUT_KEY) - throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); + filler(key_columns, aggregate_columns, final_aggregate_columns, data_variants.key_sizes, final); } catch (...) { @@ -663,30 +841,370 @@ Block Aggregator::convertToBlock(AggregatedDataVariants & data_variants, bool fi throw; } - if (!final) - { - /// data_variants не будет уничтожать состояния агрегатных функций в деструкторе. Теперь состояниями владеют ColumnAggregateFunction. - data_variants.aggregator = nullptr; - } - /// Изменяем размер столбцов-констант в блоке. size_t columns = res.columns(); for (size_t i = 0; i < columns; ++i) if (res.getByPosition(i).column->isConst()) res.getByPosition(i).column = res.getByPosition(i).column->cut(0, rows); - double elapsed_seconds = watch.elapsedSeconds(); - LOG_TRACE(log, std::fixed << std::setprecision(3) - << "Converted aggregated data to block. " - << rows << " rows, " << res.bytes() / 1048576.0 << " MiB" - << " in " << elapsed_seconds << " sec." - << " (" << rows / elapsed_seconds << " rows/sec., " << res.bytes() / elapsed_seconds / 1048576.0 << " MiB/sec.)"); - return res; } -AggregatedDataVariantsPtr Aggregator::merge(ManyAggregatedDataVariants & data_variants) +BlocksList Aggregator::prepareBlocksAndFillWithoutKey(AggregatedDataVariants & data_variants, bool final) const +{ + size_t rows = 1; + + auto filler = [&data_variants, this]( + ColumnPlainPtrs & key_columns, + AggregateColumnsData & aggregate_columns, + ColumnPlainPtrs & final_aggregate_columns, + const Sizes & key_sizes, + bool final) + { + if (data_variants.type == AggregatedDataVariants::Type::without_key || overflow_row) + { + AggregatedDataWithoutKey & data = data_variants.without_key; + + for (size_t i = 0; i < aggregates_size; ++i) + if (!final) + (*aggregate_columns[i])[0] = data + offsets_of_aggregate_states[i]; + else + aggregate_functions[i]->insertResultInto(data + offsets_of_aggregate_states[i], *final_aggregate_columns[i]); + + if (overflow_row) + for (size_t i = 0; i < keys_size; ++i) + key_columns[i]->insertDefault(); + } + }; + + Block block = prepareBlockAndFill(data_variants, final, rows, filler); + if (overflow_row) + block.info.is_overflows = true; + + BlocksList blocks; + blocks.emplace_back(std::move(block)); + return blocks; +} + +BlocksList Aggregator::prepareBlocksAndFillSingleLevel(AggregatedDataVariants & data_variants, bool final) const +{ + size_t rows = data_variants.size(); + + auto filler = [&data_variants, this]( + ColumnPlainPtrs & key_columns, + AggregateColumnsData & aggregate_columns, + ColumnPlainPtrs & final_aggregate_columns, + const Sizes & key_sizes, + bool final) + { + #define M(NAME, IS_TWO_LEVEL) \ + else if (data_variants.type == AggregatedDataVariants::Type::NAME) \ + convertToBlockImpl(*data_variants.NAME, data_variants.NAME->data, \ + key_columns, aggregate_columns, final_aggregate_columns, data_variants.key_sizes, final); + + if (false) {} + APPLY_FOR_AGGREGATED_VARIANTS(M) + #undef M + else + throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); + }; + + BlocksList blocks; + blocks.emplace_back(prepareBlockAndFill(data_variants, final, rows, filler)); + return blocks; +} + + +BlocksList Aggregator::prepareBlocksAndFillTwoLevel(AggregatedDataVariants & data_variants, bool final, boost::threadpool::pool * thread_pool) const +{ +#define M(NAME) \ + else if (data_variants.type == AggregatedDataVariants::Type::NAME) \ + return prepareBlocksAndFillTwoLevelImpl(data_variants, *data_variants.NAME, final, thread_pool); + + if (false) {} + APPLY_FOR_VARIANTS_TWO_LEVEL(M) +#undef M + else + throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); +} + + +template +BlocksList Aggregator::prepareBlocksAndFillTwoLevelImpl( + AggregatedDataVariants & data_variants, + Method & method, + bool final, + boost::threadpool::pool * thread_pool) const +{ + auto filler = [&method, this]( + ColumnPlainPtrs & key_columns, + AggregateColumnsData & aggregate_columns, + ColumnPlainPtrs & final_aggregate_columns, + const Sizes & key_sizes, + bool final, + size_t bucket) + { + convertToBlockImpl(method, method.data.impls[bucket], + key_columns, aggregate_columns, final_aggregate_columns, key_sizes, final); + }; + + auto converter = [&](size_t bucket, MemoryTracker * memory_tracker) + { + current_memory_tracker = memory_tracker; + + Block block = prepareBlockAndFill(data_variants, final, method.data.impls[bucket].size(), + [bucket, &filler] ( + ColumnPlainPtrs & key_columns, + AggregateColumnsData & aggregate_columns, + ColumnPlainPtrs & final_aggregate_columns, + const Sizes & key_sizes, + bool final) + { + filler(key_columns, aggregate_columns, final_aggregate_columns, key_sizes, final, bucket); + }); + + block.info.bucket_num = bucket; + return block; + }; + + /// packaged_task используются, чтобы исключения автоматически прокидывались в основной поток. + + std::vector> tasks; + tasks.reserve(Method::Data::NUM_BUCKETS); + + for (size_t bucket = 0; bucket < Method::Data::NUM_BUCKETS; ++bucket) + { + if (method.data.impls[bucket].empty()) + continue; + + tasks.emplace_back(std::bind(converter, bucket, current_memory_tracker)); + + if (thread_pool) + thread_pool->schedule([bucket, &tasks] { tasks[bucket](); }); + else + tasks[bucket](); + } + + if (thread_pool) + thread_pool->wait(); + + BlocksList blocks; + + /** Если был хотя бы один эксепшен, то следует "откатить" владение состояниями агрегатных функций в ColumnAggregateFunction-ах + * - то есть, очистить их (см. комментарий в функции prepareBlockAndFill.) + */ + std::exception_ptr first_exception; + for (auto & task : tasks) + { + try + { + blocks.emplace_back(task.get_future().get()); + } + catch (...) + { + if (!first_exception) + first_exception = std::current_exception(); + } + } + + if (first_exception) + { + for (auto & block : blocks) + { + for (size_t column_num = keys_size; column_num < keys_size + aggregates_size; ++column_num) + { + IColumn & col = *block.getByPosition(column_num).column; + if (ColumnAggregateFunction * col_aggregate = typeid_cast(&col)) + col_aggregate->getData().clear(); + } + } + std::rethrow_exception(first_exception); + } + + return blocks; +} + + +BlocksList Aggregator::convertToBlocks(AggregatedDataVariants & data_variants, bool final, size_t max_threads) +{ + LOG_TRACE(log, "Converting aggregated data to block"); + + Stopwatch watch; + + BlocksList blocks; + + /// В какой структуре данных агрегированы данные? + if (data_variants.empty()) + return blocks; + + std::unique_ptr thread_pool; + if (max_threads > 1 && data_variants.size() > 100000 /// TODO Сделать настраиваемый порог. + && data_variants.isTwoLevel()) /// TODO Использовать общий тред-пул с функцией merge. + thread_pool.reset(new boost::threadpool::pool(max_threads)); + + if (data_variants.type == AggregatedDataVariants::Type::without_key || overflow_row) + blocks.splice(blocks.end(), prepareBlocksAndFillWithoutKey(data_variants, final)); + + if (data_variants.type != AggregatedDataVariants::Type::without_key) + { + if (!data_variants.isTwoLevel()) + blocks.splice(blocks.end(), prepareBlocksAndFillSingleLevel(data_variants, final)); + else + blocks.splice(blocks.end(), prepareBlocksAndFillTwoLevel(data_variants, final, thread_pool.get())); + } + + if (!final) + { + /// data_variants не будет уничтожать состояния агрегатных функций в деструкторе. Теперь состояниями владеют ColumnAggregateFunction. + data_variants.aggregator = nullptr; + } + + size_t rows = 0; + size_t bytes = 0; + + for (const auto & block : blocks) + { + rows += block.rowsInFirstColumn(); + bytes += block.bytes(); + } + + double elapsed_seconds = watch.elapsedSeconds(); + LOG_TRACE(log, std::fixed << std::setprecision(3) + << "Converted aggregated data to block. " + << rows << " rows, " << bytes / 1048576.0 << " MiB" + << " in " << elapsed_seconds << " sec." + << " (" << rows / elapsed_seconds << " rows/sec., " << bytes / elapsed_seconds / 1048576.0 << " MiB/sec.)"); + + return blocks; +} + + +template +void NO_INLINE Aggregator::mergeDataImpl( + Table & table_dst, + Table & table_src) const +{ + for (auto it = table_src.begin(); it != table_src.end(); ++it) + { + decltype(it) res_it; + bool inserted; + table_dst.emplace(it->first, res_it, inserted, it.getHash()); + + if (!inserted) + { + for (size_t i = 0; i < aggregates_size; ++i) + aggregate_functions[i]->merge( + Method::getAggregateData(res_it->second) + offsets_of_aggregate_states[i], + Method::getAggregateData(it->second) + offsets_of_aggregate_states[i]); + + for (size_t i = 0; i < aggregates_size; ++i) + aggregate_functions[i]->destroy( + Method::getAggregateData(it->second) + offsets_of_aggregate_states[i]); + + Method::getAggregateData(it->second) = nullptr; + } + else + { + res_it->second = it->second; + } + } +} + + +void NO_INLINE Aggregator::mergeWithoutKeyDataImpl( + ManyAggregatedDataVariants & non_empty_data) const +{ + AggregatedDataVariantsPtr & res = non_empty_data[0]; + + /// Все результаты агрегации соединяем с первым. + for (size_t i = 1, size = non_empty_data.size(); i < size; ++i) + { + AggregatedDataWithoutKey & res_data = res->without_key; + AggregatedDataWithoutKey & current_data = non_empty_data[i]->without_key; + + for (size_t i = 0; i < aggregates_size; ++i) + aggregate_functions[i]->merge(res_data + offsets_of_aggregate_states[i], current_data + offsets_of_aggregate_states[i]); + + for (size_t i = 0; i < aggregates_size; ++i) + aggregate_functions[i]->destroy(current_data + offsets_of_aggregate_states[i]); + + current_data = nullptr; + } +} + + +template +void NO_INLINE Aggregator::mergeSingleLevelDataImpl( + ManyAggregatedDataVariants & non_empty_data) const +{ + AggregatedDataVariantsPtr & res = non_empty_data[0]; + + /// Все результаты агрегации соединяем с первым. + for (size_t i = 1, size = non_empty_data.size(); i < size; ++i) + { + AggregatedDataVariants & current = *non_empty_data[i]; + + mergeDataImpl( + getDataVariant(*res).data, + getDataVariant(current).data); + + /// current не будет уничтожать состояния агрегатных функций в деструкторе + current.aggregator = nullptr; + } +} + + +template +void NO_INLINE Aggregator::mergeTwoLevelDataImpl( + ManyAggregatedDataVariants & non_empty_data, + boost::threadpool::pool * thread_pool) const +{ + AggregatedDataVariantsPtr & res = non_empty_data[0]; + + /// Слияние распараллеливается по корзинам - первому уровню TwoLevelHashMap. + auto merge_bucket = [&non_empty_data, &res, this](size_t bucket, MemoryTracker * memory_tracker) + { + current_memory_tracker = memory_tracker; + + /// Все результаты агрегации соединяем с первым. + for (size_t i = 1, size = non_empty_data.size(); i < size; ++i) + { + AggregatedDataVariants & current = *non_empty_data[i]; + + mergeDataImpl( + getDataVariant(*res).data.impls[bucket], + getDataVariant(current).data.impls[bucket]); + + /// current не будет уничтожать состояния агрегатных функций в деструкторе + current.aggregator = nullptr; + } + }; + + /// packaged_task используются, чтобы исключения автоматически прокидывались в основной поток. + + std::vector> tasks; + tasks.reserve(Method::Data::NUM_BUCKETS); + + for (size_t bucket = 0; bucket < Method::Data::NUM_BUCKETS; ++bucket) + { + tasks.emplace_back(std::bind(merge_bucket, bucket, current_memory_tracker)); + + if (thread_pool) + thread_pool->schedule([bucket, &tasks] { tasks[bucket](); }); + else + tasks[bucket](); + } + + if (thread_pool) + thread_pool->wait(); + + for (auto & task : tasks) + task.get_future().get(); +} + + +AggregatedDataVariantsPtr Aggregator::merge(ManyAggregatedDataVariants & data_variants, size_t max_threads) { if (data_variants.empty()) throw Exception("Empty data passed to Aggregator::merge().", ErrorCodes::EMPTY_DATA_PASSED); @@ -695,67 +1213,91 @@ AggregatedDataVariantsPtr Aggregator::merge(ManyAggregatedDataVariants & data_va Stopwatch watch; - AggregatedDataVariantsPtr res = data_variants[0]; + ManyAggregatedDataVariants non_empty_data; + non_empty_data.reserve(data_variants.size()); + for (auto & data : data_variants) + if (!data->empty()) + non_empty_data.push_back(data); - /// Все результаты агрегации соединяем с первым. - size_t rows = res->size(); - for (size_t i = 1, size = data_variants.size(); i < size; ++i) + if (non_empty_data.empty()) + return data_variants[0]; + + if (non_empty_data.size() == 1) + return non_empty_data[0]; + + /// Если хотя бы один из вариантов двухуровневый, то переконвертируем все варианты в двухуровневые, если есть не такие. + /// Замечание - возможно, было бы более оптимально не конвертировать одноуровневые варианты перед мерджем, а мерджить их отдельно, в конце. + + bool has_at_least_one_two_level = false; + for (const auto & variant : non_empty_data) { - rows += data_variants[i]->size(); - AggregatedDataVariants & current = *data_variants[i]; - - res->aggregates_pools.insert(res->aggregates_pools.end(), current.aggregates_pools.begin(), current.aggregates_pools.end()); - - if (current.empty()) - continue; - - if (res->empty()) + if (variant->isTwoLevel()) { - res = data_variants[i]; - continue; + has_at_least_one_two_level = true; + break; } + } + + if (has_at_least_one_two_level) + for (auto & variant : non_empty_data) + if (!variant->isTwoLevel()) + variant->convertToTwoLevel(); + + AggregatedDataVariantsPtr & res = non_empty_data[0]; + + size_t rows = res->size(); + for (size_t i = 1, size = non_empty_data.size(); i < size; ++i) + { + rows += non_empty_data[i]->size(); + AggregatedDataVariants & current = *non_empty_data[i]; if (res->type != current.type) throw Exception("Cannot merge different aggregated data variants.", ErrorCodes::CANNOT_MERGE_DIFFERENT_AGGREGATED_DATA_VARIANTS); - /// В какой структуре данных агрегированы данные? - if (res->type == AggregatedDataVariants::WITHOUT_KEY || overflow_row) - { - AggregatedDataWithoutKey & res_data = res->without_key; - AggregatedDataWithoutKey & current_data = current.without_key; - - for (size_t i = 0; i < aggregates_size; ++i) - aggregate_functions[i]->merge(res_data + offsets_of_aggregate_states[i], current_data + offsets_of_aggregate_states[i]); - - for (size_t i = 0; i < aggregates_size; ++i) - aggregate_functions[i]->destroy(current_data + offsets_of_aggregate_states[i]); - - current_data = nullptr; - } - - if (res->type == AggregatedDataVariants::KEY_8) - mergeDataImpl(*res->key8, *current.key8); - else if (res->type == AggregatedDataVariants::KEY_16) - mergeDataImpl(*res->key16, *current.key16); - else if (res->type == AggregatedDataVariants::KEY_32) - mergeDataImpl(*res->key32, *current.key32); - else if (res->type == AggregatedDataVariants::KEY_64) - mergeDataImpl(*res->key64, *current.key64); - else if (res->type == AggregatedDataVariants::KEY_STRING) - mergeDataImpl(*res->key_string, *current.key_string); - else if (res->type == AggregatedDataVariants::KEY_FIXED_STRING) - mergeDataImpl(*res->key_fixed_string, *current.key_fixed_string); - else if (res->type == AggregatedDataVariants::KEYS_128) - mergeDataImpl(*res->keys128, *current.keys128); - else if (res->type == AggregatedDataVariants::HASHED) - mergeDataImpl(*res->hashed, *current.hashed); - else if (res->type != AggregatedDataVariants::WITHOUT_KEY) - throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); - - /// current не будет уничтожать состояния агрегатных функций в деструкторе - current.aggregator = nullptr; + res->aggregates_pools.insert(res->aggregates_pools.end(), current.aggregates_pools.begin(), current.aggregates_pools.end()); } + /// В какой структуре данных агрегированы данные? + if (res->type == AggregatedDataVariants::Type::without_key || overflow_row) + mergeWithoutKeyDataImpl(non_empty_data); + + std::unique_ptr thread_pool; + if (max_threads > 1 && rows > 100000 /// TODO Сделать настраиваемый порог. + && res->isTwoLevel()) + thread_pool.reset(new boost::threadpool::pool(max_threads)); + + /// TODO Упростить. + if (res->type == AggregatedDataVariants::Type::key8) + mergeSingleLevelDataImplkey8)::element_type>(non_empty_data); + else if (res->type == AggregatedDataVariants::Type::key16) + mergeSingleLevelDataImplkey16)::element_type>(non_empty_data); + else if (res->type == AggregatedDataVariants::Type::key32) + mergeSingleLevelDataImplkey32)::element_type>(non_empty_data); + else if (res->type == AggregatedDataVariants::Type::key64) + mergeSingleLevelDataImplkey64)::element_type>(non_empty_data); + else if (res->type == AggregatedDataVariants::Type::key_string) + mergeSingleLevelDataImplkey_string)::element_type>(non_empty_data); + else if (res->type == AggregatedDataVariants::Type::key_fixed_string) + mergeSingleLevelDataImplkey_fixed_string)::element_type>(non_empty_data); + else if (res->type == AggregatedDataVariants::Type::keys128) + mergeSingleLevelDataImplkeys128)::element_type>(non_empty_data); + else if (res->type == AggregatedDataVariants::Type::hashed) + mergeSingleLevelDataImplhashed)::element_type>(non_empty_data); + else if (res->type == AggregatedDataVariants::Type::key32_two_level) + mergeTwoLevelDataImplkey32_two_level)::element_type>(non_empty_data, thread_pool.get()); + else if (res->type == AggregatedDataVariants::Type::key64_two_level) + mergeTwoLevelDataImplkey64_two_level)::element_type>(non_empty_data, thread_pool.get()); + else if (res->type == AggregatedDataVariants::Type::key_string_two_level) + mergeTwoLevelDataImplkey_string_two_level)::element_type>(non_empty_data, thread_pool.get()); + else if (res->type == AggregatedDataVariants::Type::key_fixed_string_two_level) + mergeTwoLevelDataImplkey_fixed_string_two_level)::element_type>(non_empty_data, thread_pool.get()); + else if (res->type == AggregatedDataVariants::Type::keys128_two_level) + mergeTwoLevelDataImplkeys128_two_level)::element_type>(non_empty_data, thread_pool.get()); + else if (res->type == AggregatedDataVariants::Type::hashed_two_level) + mergeTwoLevelDataImplhashed_two_level)::element_type>(non_empty_data, thread_pool.get()); + else if (res->type != AggregatedDataVariants::Type::without_key) + throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); + double elapsed_seconds = watch.elapsedSeconds(); size_t res_rows = res->size(); @@ -769,7 +1311,81 @@ AggregatedDataVariantsPtr Aggregator::merge(ManyAggregatedDataVariants & data_va } -void Aggregator::merge(BlockInputStreamPtr stream, AggregatedDataVariants & result) +template +void NO_INLINE Aggregator::mergeStreamsImpl( + Block & block, + AggregatedDataVariants & result, + Arena * aggregates_pool, + Method & method, + Table & data) const +{ + ConstColumnPlainPtrs key_columns(keys_size); + AggregateColumnsData aggregate_columns(aggregates_size); + + /// Запоминаем столбцы, с которыми будем работать + for (size_t i = 0; i < keys_size; ++i) + key_columns[i] = block.getByPosition(i).column; + + for (size_t i = 0; i < aggregates_size; ++i) + aggregate_columns[i] = &typeid_cast(*block.getByPosition(keys_size + i).column).getData(); + + typename Method::State state; + state.init(key_columns); + + /// Для всех строчек. + StringRefs keys(keys_size); + size_t rows = block.rowsInFirstColumn(); + for (size_t i = 0; i < rows; ++i) + { + typename Table::iterator it; + bool inserted; /// Вставили новый ключ, или такой ключ уже был? + + /// Получаем ключ для вставки в хэш-таблицу. + auto key = state.getKey(key_columns, keys_size, i, result.key_sizes, keys); + + data.emplace(key, it, inserted); + + 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); + createAggregateStates(aggregate_data); + } + + /// Мерджим состояния агрегатных функций. + for (size_t j = 0; j < aggregates_size; ++j) + aggregate_functions[j]->merge( + Method::getAggregateData(it->second) + offsets_of_aggregate_states[j], + (*aggregate_columns[j])[i]); + } +} + +void NO_INLINE Aggregator::mergeWithoutKeyStreamsImpl( + Block & block, + AggregatedDataVariants & result) const +{ + AggregateColumnsData aggregate_columns(aggregates_size); + + /// Запоминаем столбцы, с которыми будем работать + for (size_t i = 0; i < aggregates_size; ++i) + aggregate_columns[i] = &typeid_cast(*block.getByPosition(keys_size + i).column).getData(); + + AggregatedDataWithoutKey & res = result.without_key; + if (!res) + { + res = result.aggregates_pool->alloc(total_size_of_aggregate_states); + createAggregateStates(res); + } + + /// Добавляем значения + for (size_t i = 0; i < aggregates_size; ++i) + aggregate_functions[i]->merge(res + offsets_of_aggregate_states[i], (*aggregate_columns[i])[0]); +} + + +void Aggregator::mergeStream(BlockInputStreamPtr stream, AggregatedDataVariants & result, size_t max_threads) { StringRefs key(keys_size); ConstColumnPlainPtrs key_columns(keys_size); @@ -782,71 +1398,162 @@ void Aggregator::merge(BlockInputStreamPtr stream, AggregatedDataVariants & resu /// result будет уничтожать состояния агрегатных функций в деструкторе result.aggregator = this; - /// Читаем все данные + /** Если на удалённых серверах использовался двухуровневый метод агрегации, + * то в блоках будет расположена информация о номере корзины. + * Тогда вычисления можно будет распараллелить по корзинам. + * Разложим блоки по указанным в них номерам корзин. + */ + using BucketToBlocks = std::map; + BucketToBlocks bucket_to_blocks; + + /// Читаем все данные. TODO memory-savvy режим, при котором в один момент времени обрабатывается только одна корзина. + LOG_TRACE(log, "Reading blocks of partially aggregated data."); + + size_t total_input_rows = 0; + size_t total_input_blocks = 0; while (Block block = stream->read()) { - LOG_TRACE(log, "Merging aggregated block"); + total_input_rows += block.rowsInFirstColumn(); + ++total_input_blocks; + bucket_to_blocks[block.info.bucket_num].emplace_back(std::move(block)); + } - if (!sample) - for (size_t i = 0; i < keys_size + aggregates_size; ++i) - sample.insert(block.getByPosition(i).cloneEmpty()); + LOG_TRACE(log, "Read " << total_input_blocks << " blocks of partially aggregated data, total " << total_input_rows << " rows."); - /// Запоминаем столбцы, с которыми будем работать - for (size_t i = 0; i < keys_size; ++i) - key_columns[i] = block.getByPosition(i).column; + if (bucket_to_blocks.empty()) + return; + + if (!sample) + sample = bucket_to_blocks.begin()->second.front().cloneEmpty(); + + /// Каким способом выполнять агрегацию? + for (size_t i = 0; i < keys_size; ++i) + key_columns[i] = sample.getByPosition(i).column; + + Sizes key_sizes; + AggregatedDataVariants::Type method = chooseAggregationMethod(key_columns, key_sizes); + + /** Минус единицей обозначается отсутствие информации о корзине + * - в случае одноуровневой агрегации, а также для блоков с "переполнившимися" значениями. + * Если есть хотя бы один блок с номером корзины больше нуля, значит была двухуровневая агрегация. + */ + size_t has_two_level = bucket_to_blocks.rbegin()->first > 0; + + if (has_two_level) + { + #define M(NAME) \ + if (method == AggregatedDataVariants::Type::NAME) \ + method = AggregatedDataVariants::Type::NAME ## _two_level; + + APPLY_FOR_VARIANTS_CONVERTIBLE_TO_TWO_LEVEL(M) + + #undef M + } + + result.init(method); + result.keys_size = keys_size; + result.key_sizes = key_sizes; + + /// Сначала параллельно мерджим для отдельных bucket-ов. Затем домердживаем данные, не распределённые по bucket-ам. + if (has_two_level) + { + LOG_TRACE(log, "Merging partially aggregated two-level data."); + + std::unique_ptr thread_pool; + if (max_threads > 1 && total_input_rows > 100000 /// TODO Сделать настраиваемый порог. + && has_two_level) + thread_pool.reset(new boost::threadpool::pool(max_threads)); + + auto merge_bucket = [&bucket_to_blocks, &result, this](size_t bucket, Arena * aggregates_pool, MemoryTracker * memory_tracker) + { + current_memory_tracker = memory_tracker; + + for (Block & block : bucket_to_blocks[bucket]) + { + #define M(NAME) \ + else if (result.type == AggregatedDataVariants::Type::NAME) \ + mergeStreamsImpl(block, result, aggregates_pool, *result.NAME, result.NAME->data.impls[bucket]); + + if (false) {} + APPLY_FOR_VARIANTS_TWO_LEVEL(M) + #undef M + else + throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); + } + }; + + /// packaged_task используются, чтобы исключения автоматически прокидывались в основной поток. + + std::vector> tasks; + tasks.reserve(bucket_to_blocks.size()); + + for (auto & bucket_blocks : bucket_to_blocks) + { + size_t bucket = bucket_blocks.first; + result.aggregates_pools.push_back(new Arena); + Arena * aggregates_pool = result.aggregates_pools.back().get(); + + tasks.emplace_back(std::bind(merge_bucket, bucket, aggregates_pool, current_memory_tracker)); + + if (thread_pool) + thread_pool->schedule([bucket, &tasks] { tasks[bucket](); }); + else + tasks[bucket](); + } + + if (thread_pool) + thread_pool->wait(); + + for (auto & task : tasks) + task.get_future().get(); + + LOG_TRACE(log, "Merged partially aggregated two-level data."); + } + + if (bucket_to_blocks.count(-1)) + { + LOG_TRACE(log, "Merging partially aggregated single-level data."); + + BlocksList & blocks = bucket_to_blocks[-1]; + for (Block & block : blocks) + { + if (result.type == AggregatedDataVariants::Type::without_key || block.info.is_overflows) + mergeWithoutKeyStreamsImpl(block, result); + + #define M(NAME, IS_TWO_LEVEL) \ + else if (result.type == AggregatedDataVariants::Type::NAME) \ + mergeStreamsImpl(block, result, result.aggregates_pool, *result.NAME, result.NAME->data); + + if (false) {} + APPLY_FOR_AGGREGATED_VARIANTS(M) + #undef M + else if (result.type != AggregatedDataVariants::Type::without_key) + throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); + } + + LOG_TRACE(log, "Merged partially aggregated single-level data."); + } +} + + +template +void NO_INLINE Aggregator::destroyImpl( + Method & method) const +{ + for (typename Method::const_iterator it = method.data.begin(); it != method.data.end(); ++it) + { + char * data = Method::getAggregateData(it->second); + + /** Если исключение (обычно нехватка памяти, кидается MemoryTracker-ом) возникло + * после вставки ключа в хэш-таблицу, но до создания всех состояний агрегатных функций, + * то data будет равен nullptr-у. + */ + if (nullptr == data) + continue; for (size_t i = 0; i < aggregates_size; ++i) - aggregate_columns[i] = &typeid_cast(*block.getByPosition(keys_size + i).column).getData(); - - size_t rows = block.rows(); - - /// Каким способом выполнять агрегацию? - Sizes key_sizes; - AggregatedDataVariants::Type method = chooseAggregationMethod(key_columns, key_sizes); - - if (result.empty()) - { - result.init(method); - result.keys_size = keys_size; - result.key_sizes = key_sizes; - } - - if (result.type == AggregatedDataVariants::WITHOUT_KEY || overflow_row) - { - AggregatedDataWithoutKey & res = result.without_key; - if (!res) - { - res = result.aggregates_pool->alloc(total_size_of_aggregate_states); - createAggregateStates(res); - } - - /// Добавляем значения - for (size_t i = 0; i < aggregates_size; ++i) - aggregate_functions[i]->merge(res + offsets_of_aggregate_states[i], (*aggregate_columns[i])[0]); - } - - size_t start_row = overflow_row ? 1 : 0; - - if (result.type == AggregatedDataVariants::KEY_8) - mergeStreamsImpl(*result.key8, result.aggregates_pool, start_row, rows, key_columns, aggregate_columns, key_sizes, key); - else if (result.type == AggregatedDataVariants::KEY_16) - mergeStreamsImpl(*result.key16, result.aggregates_pool, start_row, rows, key_columns, aggregate_columns, key_sizes, key); - else if (result.type == AggregatedDataVariants::KEY_32) - mergeStreamsImpl(*result.key32, result.aggregates_pool, start_row, rows, key_columns, aggregate_columns, key_sizes, key); - else if (result.type == AggregatedDataVariants::KEY_64) - mergeStreamsImpl(*result.key64, result.aggregates_pool, start_row, rows, key_columns, aggregate_columns, key_sizes, key); - else if (result.type == AggregatedDataVariants::KEY_STRING) - mergeStreamsImpl(*result.key_string, result.aggregates_pool, start_row, rows, key_columns, aggregate_columns, key_sizes, key); - else if (result.type == AggregatedDataVariants::KEY_FIXED_STRING) - mergeStreamsImpl(*result.key_fixed_string, result.aggregates_pool, start_row, rows, key_columns, aggregate_columns, key_sizes, key); - else if (result.type == AggregatedDataVariants::KEYS_128) - mergeStreamsImpl(*result.keys128, result.aggregates_pool, start_row, rows, key_columns, aggregate_columns, key_sizes, key); - else if (result.type == AggregatedDataVariants::HASHED) - mergeStreamsImpl(*result.hashed, result.aggregates_pool, start_row, rows, key_columns, aggregate_columns, key_sizes, key); - else if (result.type != AggregatedDataVariants::WITHOUT_KEY) - throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); - - LOG_TRACE(log, "Merged aggregated block"); + if (!aggregate_functions[i]->isState()) + aggregate_functions[i]->destroy(data + offsets_of_aggregate_states[i]); } } @@ -859,7 +1566,7 @@ void Aggregator::destroyAllAggregateStates(AggregatedDataVariants & result) LOG_TRACE(log, "Destroying aggregate states"); /// В какой структуре данных агрегированы данные? - if (result.type == AggregatedDataVariants::WITHOUT_KEY || overflow_row) + if (result.type == AggregatedDataVariants::Type::without_key || overflow_row) { AggregatedDataWithoutKey & res_data = result.without_key; @@ -869,23 +1576,14 @@ void Aggregator::destroyAllAggregateStates(AggregatedDataVariants & result) aggregate_functions[i]->destroy(res_data + offsets_of_aggregate_states[i]); } - if (result.type == AggregatedDataVariants::KEY_8) - destroyImpl(*result.key8); - else if (result.type == AggregatedDataVariants::KEY_16) - destroyImpl(*result.key16); - else if (result.type == AggregatedDataVariants::KEY_32) - destroyImpl(*result.key32); - else if (result.type == AggregatedDataVariants::KEY_64) - destroyImpl(*result.key64); - else if (result.type == AggregatedDataVariants::KEY_STRING) - destroyImpl(*result.key_string); - else if (result.type == AggregatedDataVariants::KEY_FIXED_STRING) - destroyImpl(*result.key_fixed_string); - else if (result.type == AggregatedDataVariants::KEYS_128) - destroyImpl(*result.keys128); - else if (result.type == AggregatedDataVariants::HASHED) - destroyImpl(*result.hashed); - else if (result.type != AggregatedDataVariants::WITHOUT_KEY) +#define M(NAME, IS_TWO_LEVEL) \ + else if (result.type == AggregatedDataVariants::Type::NAME) \ + destroyImpl(*result.NAME); + + if (false) {} + APPLY_FOR_AGGREGATED_VARIANTS(M) +#undef M + else if (result.type != AggregatedDataVariants::Type::without_key) throw Exception("Unknown aggregated data variant.", ErrorCodes::UNKNOWN_AGGREGATED_DATA_VARIANT); } diff --git a/dbms/src/Interpreters/Cluster.cpp b/dbms/src/Interpreters/Cluster.cpp index c02c6edb2cc..f18be042568 100644 --- a/dbms/src/Interpreters/Cluster.cpp +++ b/dbms/src/Interpreters/Cluster.cpp @@ -59,8 +59,7 @@ Clusters::Clusters(const Settings & settings, const DataTypeFactory & data_type_ } -Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, const String & cluster_name): - local_nodes_num(0) +Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, const String & cluster_name) { Poco::Util::AbstractConfiguration & config = Poco::Util::Application::instance().config(); Poco::Util::AbstractConfiguration::Keys config_keys; @@ -81,7 +80,7 @@ Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_fa slot_to_shard.insert(std::end(slot_to_shard), weight, shard_info_vec.size()); if (const auto is_local = isLocal(addresses.back())) - shard_info_vec.push_back({{}, weight, is_local }); + shard_info_vec.push_back({{}, weight, is_local}); else shard_info_vec.push_back({{addressToDirName(addresses.back())}, weight, is_local}); } @@ -149,89 +148,93 @@ Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_fa } if (!addresses_with_failover.empty() && !addresses.empty()) - throw Exception("There must be either 'node' or 'shard' elements in config", ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG); + throw Exception("There must be either 'node' or 'shard' elements in config", ErrorCodes::EXCESSIVE_ELEMENT_IN_CONFIG); - if (addresses_with_failover.size()) + if (addresses_with_failover.size()) + { + for (const auto & shard : addresses_with_failover) { - for (AddressesWithFailover::const_iterator it = addresses_with_failover.begin(); it != addresses_with_failover.end(); ++it) - { - ConnectionPools replicas; - replicas.reserve(it->size()); + ConnectionPools replicas; + replicas.reserve(shard.size()); - bool has_local_replics = false; - for (Addresses::const_iterator jt = it->begin(); jt != it->end(); ++jt) - { - if (isLocal(*jt)) - { - has_local_replics = true; - break; - } - else - { - replicas.emplace_back(new ConnectionPool( - settings.distributed_connections_pool_size, - jt->host_port.host().toString(), jt->host_port.port(), "", jt->user, jt->password, data_type_factory, "server", Protocol::Compression::Enable, - saturate(settings.connect_timeout_with_failover_ms, settings.limits.max_execution_time), - saturate(settings.receive_timeout, settings.limits.max_execution_time), - saturate(settings.send_timeout, settings.limits.max_execution_time))); - } - } - - if (has_local_replics) - ++local_nodes_num; - else - pools.emplace_back(new ConnectionPoolWithFailover(replicas, settings.load_balancing, settings.connections_with_failover_max_tries)); - } - } - else if (addresses.size()) - { - for (Addresses::const_iterator it = addresses.begin(); it != addresses.end(); ++it) + bool has_local_replics = false; + for (const auto & replica : shard) { - if (isLocal(*it)) + if (isLocal(replica)) { - ++local_nodes_num; + has_local_replics = true; + break; } else { - pools.emplace_back(new ConnectionPool( + replicas.emplace_back(new ConnectionPool( settings.distributed_connections_pool_size, - it->host_port.host().toString(), it->host_port.port(), "", it->user, it->password, data_type_factory, "server", Protocol::Compression::Enable, - saturate(settings.connect_timeout, settings.limits.max_execution_time), + replica.host_port.host().toString(), replica.host_port.port(), "", replica.user, replica.password, + data_type_factory, "server", Protocol::Compression::Enable, + saturate(settings.connect_timeout_with_failover_ms, settings.limits.max_execution_time), saturate(settings.receive_timeout, settings.limits.max_execution_time), saturate(settings.send_timeout, settings.limits.max_execution_time))); } } + + if (has_local_replics) + ++local_nodes_num; + else + pools.emplace_back(new ConnectionPoolWithFailover(replicas, settings.load_balancing, settings.connections_with_failover_max_tries)); } - else - throw Exception("No addresses listed in config", ErrorCodes::NO_ELEMENTS_IN_CONFIG); + } + else if (addresses.size()) + { + for (const auto & address : addresses) + { + if (isLocal(address)) + { + ++local_nodes_num; + } + else + { + pools.emplace_back(new ConnectionPool( + settings.distributed_connections_pool_size, + address.host_port.host().toString(), address.host_port.port(), "", address.user, address.password, + data_type_factory, "server", Protocol::Compression::Enable, + saturate(settings.connect_timeout, settings.limits.max_execution_time), + saturate(settings.receive_timeout, settings.limits.max_execution_time), + saturate(settings.send_timeout, settings.limits.max_execution_time))); + } + } + } + else + throw Exception("No addresses listed in config", ErrorCodes::NO_ELEMENTS_IN_CONFIG); } -Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, std::vector< std::vector > names, - const String & username, const String & password): local_nodes_num(0) +Cluster::Cluster(const Settings & settings, const DataTypeFactory & data_type_factory, std::vector> names, + const String & username, const String & password) { - for (size_t i = 0; i < names.size(); ++i) + for (const auto & shard : names) { Addresses current; - for (size_t j = 0; j < names[i].size(); ++j) - current.emplace_back(names[i][j], username, password); + for (auto & replica : shard) + current.emplace_back(replica, username, password); addresses_with_failover.emplace_back(current); } - for (AddressesWithFailover::const_iterator it = addresses_with_failover.begin(); it != addresses_with_failover.end(); ++it) + for (const auto & shard : addresses_with_failover) { ConnectionPools replicas; - replicas.reserve(it->size()); + replicas.reserve(shard.size()); - for (Addresses::const_iterator jt = it->begin(); jt != it->end(); ++jt) + for (const auto & replica : shard) { replicas.emplace_back(new ConnectionPool( settings.distributed_connections_pool_size, - jt->host_port.host().toString(), jt->host_port.port(), "", jt->user, jt->password, data_type_factory, "server", Protocol::Compression::Enable, + replica.host_port.host().toString(), replica.host_port.port(), "", replica.user, replica.password, + data_type_factory, "server", Protocol::Compression::Enable, saturate(settings.connect_timeout_with_failover_ms, settings.limits.max_execution_time), saturate(settings.receive_timeout, settings.limits.max_execution_time), saturate(settings.send_timeout, settings.limits.max_execution_time))); } + pools.emplace_back(new ConnectionPoolWithFailover(replicas, settings.load_balancing, settings.connections_with_failover_max_tries)); } } @@ -259,9 +262,11 @@ bool Cluster::isLocal(const Address & address) interfaces.end() != std::find_if(interfaces.begin(), interfaces.end(), [&](const Poco::Net::NetworkInterface & interface) { return interface.address() == address.host_port.host(); })) { - LOG_INFO(&Poco::Util::Application::instance().logger(), "Replica with address " << address.host_port.toString() << " will be processed as local."); + LOG_INFO(&Poco::Util::Application::instance().logger(), + "Replica with address " << address.host_port.toString() << " will be processed as local."); return true; } + return false; } diff --git a/dbms/src/Interpreters/Compiler.cpp b/dbms/src/Interpreters/Compiler.cpp new file mode 100644 index 00000000000..e6bb9f12fdb --- /dev/null +++ b/dbms/src/Interpreters/Compiler.cpp @@ -0,0 +1,242 @@ +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + + +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(&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 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 lock(mutex); + libraries[hashed_key] = lib; + } + + LOG_INFO(log, "Compiled code " << file_name); + + on_ready(lib); +} + + +} diff --git a/dbms/src/Interpreters/Context.cpp b/dbms/src/Interpreters/Context.cpp index a18906ca356..1a2e66b6887 100644 --- a/dbms/src/Interpreters/Context.cpp +++ b/dbms/src/Interpreters/Context.cpp @@ -20,6 +20,12 @@ String Context::getPath() const return shared->path; } +String Context::getTemporaryPath() const +{ + Poco::ScopedLock lock(shared->mutex); + return shared->tmp_path; +} + void Context::setPath(const String & path) { @@ -27,6 +33,12 @@ void Context::setPath(const String & path) shared->path = path; } +void Context::setTemporaryPath(const String & path) +{ + Poco::ScopedLock lock(shared->mutex); + shared->tmp_path = path; +} + void Context::setUsersConfig(ConfigurationPtr config) { @@ -81,15 +93,17 @@ void Context::removeDependency(const DatabaseAndTableName & from, const Database shared->view_dependencies[from].erase(where); } -Dependencies Context::getDependencies(const DatabaseAndTableName & from) const +Dependencies Context::getDependencies(const String & database_name, const String & table_name) const { Poco::ScopedLock lock(shared->mutex); - ViewDependencies::const_iterator iter = shared->view_dependencies.find(from); + + String db = database_name.empty() ? current_database : database_name; + + ViewDependencies::const_iterator iter = shared->view_dependencies.find(DatabaseAndTableName(db, table_name)); if (iter == shared->view_dependencies.end()) - return Dependencies(); - const std::set &buf = iter->second; - Dependencies res(buf.begin(), buf.end()); - return res; + return {}; + + return Dependencies(iter->second.begin(), iter->second.end()); } bool Context::isTableExist(const String & database_name, const String & table_name) const @@ -620,4 +634,17 @@ Cluster & Context::getCluster(const std::string & cluster_name) else throw Poco::Exception("Failed to find cluster with name = " + cluster_name); } + + +Compiler & Context::getCompiler() +{ + Poco::ScopedLock lock(shared->mutex); + + if (!shared->compiler) + shared->compiler.reset(new Compiler{ shared->path + "build/", 1 }); + + return *shared->compiler; +} + + } diff --git a/dbms/src/Interpreters/ExpressionAnalyzer.cpp b/dbms/src/Interpreters/ExpressionAnalyzer.cpp index 34fe41eda15..b1ccc9a9f0a 100644 --- a/dbms/src/Interpreters/ExpressionAnalyzer.cpp +++ b/dbms/src/Interpreters/ExpressionAnalyzer.cpp @@ -15,6 +15,7 @@ #include #include #include + #include #include @@ -26,6 +27,7 @@ #include #include +#include #include #include @@ -583,19 +585,27 @@ static SharedPtr interpretSubquery( ASTPtr query; if (table) { - String query_str = "SELECT * FROM " + backQuoteIfNeed(table->name); - const char * begin = query_str.data(); - const char * end = begin + query_str.size(); - const char * pos = begin; - Expected expected = ""; + /// create ASTSelectQuery for "SELECT * FROM table" as if written by hand + const auto select_query = new ASTSelectQuery; + query = select_query; - bool parse_res = ParserSelectQuery().parse(pos, end, query, expected); - if (!parse_res) - throw Exception("Error in parsing SELECT query while creating set or join for table " + table->name + ".", - ErrorCodes::LOGICAL_ERROR); + const auto select_expression_list = new ASTExpressionList; + select_query->select_expression_list = select_expression_list; + select_query->children.emplace_back(select_query->select_expression_list); - /// @note it may be more appropriate to manually replace ASTAsterisk with table's columns - ExpressionAnalyzer{query, context, subquery_depth}; + /// get columns list for target table + const auto & storage = context.getTable("", table->name); + const auto & columns = storage->getColumnsListNonMaterialized(); + select_expression_list->children.reserve(columns.size()); + + /// manually substitute column names in place of asterisk + for (const auto & column : columns) + select_expression_list->children.emplace_back(new ASTIdentifier{ + StringRange{}, column.name + }); + + select_query->table = subquery_or_table_name; + select_query->children.emplace_back(select_query->table); } else query = subquery->children.at(0); @@ -629,7 +639,6 @@ void ExpressionAnalyzer::addExternalStorage(ASTPtr & subquery_or_table_name) NamesAndTypesListPtr columns = new NamesAndTypesList(sample.getColumnsList()); String external_table_name = "_data" + toString(external_table_id); - ++external_table_id; /** Заменяем подзапрос на имя временной таблицы. * Именно в таком виде, запрос отправится на удалённый сервер. @@ -691,7 +700,37 @@ void ExpressionAnalyzer::makeSet(ASTFunction * node, const Block & sample_block) * - в этой функции видно выражение IN _data1. */ if (!subquery_for_set.source) - subquery_for_set.source = interpretSubquery(arg, context, subquery_depth)->execute(); + { + auto interpreter = interpretSubquery(arg, context, subquery_depth); + subquery_for_set.source = new LazyBlockInputStream([interpreter]() mutable { return interpreter->execute(); }); + + /** Зачем используется LazyBlockInputStream? + * + * Дело в том, что при обработке запроса вида + * SELECT ... FROM remote_test WHERE column GLOBAL IN (subquery), + * если распределённая таблица remote_test содержит в качестве одного из серверов localhost, + * то запрос будет ещё раз интерпретирован локально (а не отправлен по TCP, как в случае удалённого сервера). + * + * Конвейер выполнения запроса будет такой: + * CreatingSets + * выполнение подзапроса subquery, заполнение временной таблицы _data1 (1) + * CreatingSets + * чтение из таблицы _data1, создание множества (2) + * чтение из таблицы, подчинённой remote_test. + * + * (Вторая часть конвейера под CreatingSets - это повторная интерпретация запроса внутри StorageDistributed, + * запрос отличается тем, что имя БД и таблицы заменены на подчинённые, а также подзапрос заменён на _data1.) + * + * Но при создании конвейера, при создании источника (2), будет обнаружено, что таблица _data1 пустая + * (потому что запрос ещё не начал выполняться), и будет возвращён в качестве источника пустой источник. + * И затем, при выполнении запроса, на шаге (2), будет создано пустое множество. + * + * Поэтому, мы делаем инициализацию шага (2) ленивой + * - чтобы она произошла только после выполнения шага (1), на котором нужная таблица будет заполнена. + * + * Замечание: это решение не очень хорошее, надо подумать лучше. + */ + } subquery_for_set.set = ast_set->set; arg = ast_set_ptr; @@ -1389,7 +1428,10 @@ bool ExpressionAnalyzer::appendJoin(ExpressionActionsChain & chain, bool only_ty * - в этой функции видно выражение JOIN _data1. */ if (!subquery_for_set.source) - subquery_for_set.source = interpretSubquery(ast_join.table, context, subquery_depth, required_joined_columns)->execute(); + { + auto interpreter = interpretSubquery(ast_join.table, context, subquery_depth, required_joined_columns); + subquery_for_set.source = new LazyBlockInputStream([interpreter]() mutable { return interpreter->execute(); }); + } subquery_for_set.join = join; } @@ -1754,7 +1796,7 @@ void ExpressionAnalyzer::collectJoinedColumns(NameSet & joined_columns, NamesAnd else if (typeid_cast(node.table.get())) { const auto & table = node.table->children.at(0); - nested_result_sample = ExpressionAnalyzer(table, context, subquery_depth + 1).getSelectSampleBlock(); + nested_result_sample = ExpressionAnalyzer(table, context, subquery_depth + 1).getSelectSampleBlock(); } auto & keys = typeid_cast(*node.using_expr_list); diff --git a/dbms/src/Interpreters/InterpreterSelectQuery.cpp b/dbms/src/Interpreters/InterpreterSelectQuery.cpp index 99b218694ad..341a6fee32f 100644 --- a/dbms/src/Interpreters/InterpreterSelectQuery.cpp +++ b/dbms/src/Interpreters/InterpreterSelectQuery.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -33,895 +32,933 @@ 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) { - ProfileEvents::increment(ProfileEvents::SelectQuery); + original_max_threads = settings.max_threads; - if (settings.limits.max_subquery_depth && subquery_depth > settings.limits.max_subquery_depth) - throw Exception("Too deep subqueries. Maximum: " + toString(settings.limits.max_subquery_depth), - ErrorCodes::TOO_DEEP_SUBQUERIES); + ProfileEvents::increment(ProfileEvents::SelectQuery); - if (query.table && typeid_cast(&*query.table)) - { - if (table_column_names.empty()) - context.setColumns(InterpreterSelectQuery(query.table, context, to_stage, subquery_depth, nullptr, false).getSampleBlock().getColumnsList()); - } - else - { - if (query.table && typeid_cast(&*query.table)) - { - /// Получить табличную функцию - TableFunctionPtr table_function_ptr = context.getTableFunctionFactory().get(typeid_cast(&*query.table)->name, context); - /// Выполнить ее и запомнить результат - storage = table_function_ptr->execute(query.table, context); - } - else - { - String database_name; - String table_name; + if (settings.limits.max_subquery_depth && subquery_depth > settings.limits.max_subquery_depth) + throw Exception("Too deep subqueries. Maximum: " + toString(settings.limits.max_subquery_depth), + ErrorCodes::TOO_DEEP_SUBQUERIES); - getDatabaseAndTableNames(database_name, table_name); + if (isFirstSelectInsideUnionAll() && hasAsterisk()) + { + basicInit(input, table_column_names); - storage = context.getTable(database_name, table_name); - } + // Мы выполняем этот код именно здесь, потому что в противном случае следующего рода запрос бы не срабатывал: + // SELECT X FROM (SELECT * FROM (SELECT 1 AS X, 2 AS Y) UNION ALL SELECT 3, 4) + // из-за того, что астериски заменены столбцами только при создании объектов query_analyzer в basicInit(). + renameColumns(); - table_lock = storage->lockStructure(false); - if (table_column_names.empty()) - context.setColumns(storage->getColumnsListNonMaterialized()); - } + 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); - if (!table_column_names.empty()) - context.setColumns(table_column_names); + basicInit(input, table_column_names); + } +} - if (context.getColumns().empty()) - throw Exception("There are no available columns", ErrorCodes::THERE_IS_NO_COLUMN); +void InterpreterSelectQuery::basicInit(BlockInputStreamPtr input_, const NamesAndTypesList & table_column_names) +{ + if (query.table && typeid_cast(&*query.table)) + { + if (table_column_names.empty()) + { + /// Оптимизация: мы считаем, что запрос содержит только один SELECT, даже если это может быть + /// в самом деле цепочкой UNION ALL. Первый запрос достаточен для определения нужных столбцов. + context.setColumns(InterpreterSelectQuery(query.table, context, to_stage, subquery_depth, nullptr, false).getSampleBlock().getColumnsList()); + } + } + else + { + if (query.table && typeid_cast(&*query.table)) + { + /// Получить табличную функцию + TableFunctionPtr table_function_ptr = context.getTableFunctionFactory().get(typeid_cast(&*query.table)->name, context); + /// Выполнить ее и запомнить результат + storage = table_function_ptr->execute(query.table, context); + } + else + { + String database_name; + String table_name; - query_analyzer = new ExpressionAnalyzer(query_ptr, context, storage, subquery_depth, true); + getDatabaseAndTableNames(database_name, table_name); - /// Сохраняем в query context новые временные таблицы - for (auto & it : query_analyzer->getExternalTables()) - if (!context.tryGetExternalTable(it.first)) - context.addExternalTable(it.first, it.second); + storage = context.getTable(database_name, table_name); + } - if (input_) - streams.push_back(input_); - - if (isFirstSelectInsideUnionAll()) - { - // Создаем цепочку запросов SELECT и проверяем, что результаты всех запросов SELECT cовместимые. - // NOTE Мы можем безопасно применить static_cast вместо typeid_cast, - // потому что знаем, что в цепочке UNION ALL имеются только деревья типа SELECT. - InterpreterSelectQuery * interpreter = this; - Block first = interpreter->getSampleBlock(); - for (ASTPtr tree = query.next_union_all; !tree.isNull(); tree = (static_cast(*tree)).next_union_all) - { - interpreter->next_select_in_union_all.reset(new InterpreterSelectQuery(tree, context, to_stage, subquery_depth, nullptr, false)); - interpreter = interpreter->next_select_in_union_all.get(); - Block current = interpreter->getSampleBlock(); - if (!blocksHaveEqualStructure(first, current)) - throw Exception("Result structures mismatch in the SELECT queries of the UNION ALL chain", - ErrorCodes::UNION_ALL_RESULT_STRUCTURES_MISMATCH); - } - } + table_lock = storage->lockStructure(false); + if (table_column_names.empty()) + context.setColumns(storage->getColumnsListNonMaterialized()); + } + + if (!table_column_names.empty()) + context.setColumns(table_column_names); + + if (context.getColumns().empty()) + throw Exception("There are no available columns", ErrorCodes::THERE_IS_NO_COLUMN); + + query_analyzer.reset(new ExpressionAnalyzer(query_ptr, context, storage, subquery_depth, true)); + + /// Сохраняем в query context новые временные таблицы + for (auto & it : query_analyzer->getExternalTables()) + if (!context.tryGetExternalTable(it.first)) + context.addExternalTable(it.first, it.second); + + if (input_) + streams.push_back(input_); + + if (isFirstSelectInsideUnionAll()) + { + /// Создаем цепочку запросов SELECT и проверяем, что результаты всех запросов SELECT cовместимые. + /// NOTE Мы можем безопасно применить static_cast вместо typeid_cast, + /// потому что знаем, что в цепочке UNION ALL имеются только деревья типа SELECT. + InterpreterSelectQuery * interpreter = this; + Block first = interpreter->getSampleBlock(); + for (ASTPtr tree = query.next_union_all; !tree.isNull(); tree = (static_cast(*tree)).next_union_all) + { + interpreter->next_select_in_union_all.reset(new InterpreterSelectQuery(tree, context, to_stage, subquery_depth, nullptr, false)); + interpreter = interpreter->next_select_in_union_all.get(); + Block current = interpreter->getSampleBlock(); + if (!blocksHaveEqualStructure(first, current)) + throw Exception("Result structures mismatch in the SELECT queries of the UNION ALL chain. Found result structure:\n\n" + current.dumpStructure() + + "\n\nwhile expecting:\n\n" + first.dumpStructure() + "\n\ninstead", + ErrorCodes::UNION_ALL_RESULT_STRUCTURES_MISMATCH); + } + } +} + +void InterpreterSelectQuery::initQueryAnalyzer() +{ + query_analyzer.reset(new ExpressionAnalyzer(query_ptr, context, storage, subquery_depth, true)); + for (auto p = next_select_in_union_all.get(); p != nullptr; p = p->next_select_in_union_all.get()) + p->query_analyzer.reset(new ExpressionAnalyzer(p->query_ptr, p->context, p->storage, p->subquery_depth, true)); } InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context & context_, QueryProcessingStage::Enum to_stage_, - size_t subquery_depth_, BlockInputStreamPtr input_, bool is_union_all_head_) - : query_ptr(query_ptr_), query(typeid_cast(*query_ptr)), - context(context_), settings(context.getSettings()), to_stage(to_stage_), subquery_depth(subquery_depth_), - is_union_all_head(is_union_all_head_), - log(&Logger::get("InterpreterSelectQuery")) + size_t subquery_depth_, BlockInputStreamPtr input_, bool is_union_all_head_) + : query_ptr(query_ptr_), query(typeid_cast(*query_ptr)), + context(context_), settings(context.getSettings()), to_stage(to_stage_), subquery_depth(subquery_depth_), + is_union_all_head(is_union_all_head_), + log(&Logger::get("InterpreterSelectQuery")) { - init(input_); + init(input_); } InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context & context_, - const Names & required_column_names_, - QueryProcessingStage::Enum to_stage_, size_t subquery_depth_, BlockInputStreamPtr input_) - : query_ptr(query_ptr_), query(typeid_cast(*query_ptr)), - context(context_), settings(context.getSettings()), to_stage(to_stage_), subquery_depth(subquery_depth_), - is_union_all_head(true), - log(&Logger::get("InterpreterSelectQuery")) + const Names & required_column_names_, + QueryProcessingStage::Enum to_stage_, size_t subquery_depth_, BlockInputStreamPtr input_) + : query_ptr(query_ptr_), query(typeid_cast(*query_ptr)), + context(context_), settings(context.getSettings()), to_stage(to_stage_), subquery_depth(subquery_depth_), + is_union_all_head(true), + log(&Logger::get("InterpreterSelectQuery")) { - /** Оставляем в запросе в секции SELECT только нужные столбцы. - * Но если используется DISTINCT, то все столбцы считаются нужными, так как иначе DISTINCT работал бы по-другому. - */ - if (!query.distinct) - query.rewriteSelectExpressionList(required_column_names_); - - init(input_); + init(input_, required_column_names_); } InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context & context_, - const Names & required_column_names_, - const NamesAndTypesList & table_column_names, QueryProcessingStage::Enum to_stage_, size_t subquery_depth_, BlockInputStreamPtr input_) - : query_ptr(query_ptr_), query(typeid_cast(*query_ptr)), - context(context_), settings(context.getSettings()), to_stage(to_stage_), subquery_depth(subquery_depth_), - is_union_all_head(true), - log(&Logger::get("InterpreterSelectQuery")) + const Names & required_column_names_, + const NamesAndTypesList & table_column_names, QueryProcessingStage::Enum to_stage_, size_t subquery_depth_, BlockInputStreamPtr input_) + : query_ptr(query_ptr_), query(typeid_cast(*query_ptr)), + context(context_), settings(context.getSettings()), to_stage(to_stage_), subquery_depth(subquery_depth_), + is_union_all_head(true), + log(&Logger::get("InterpreterSelectQuery")) { - /** Оставляем в запросе в секции SELECT только нужные столбцы. - * Но если используется DISTINCT, то все столбцы считаются нужными, так как иначе DISTINCT работал бы по-другому. - */ - if (!query.distinct) - query.rewriteSelectExpressionList(required_column_names_); + init(input_, required_column_names_, table_column_names); +} - init(input_, table_column_names); +bool InterpreterSelectQuery::hasAsterisk() const +{ + if (query.hasAsterisk()) + return true; + + if (isFirstSelectInsideUnionAll()) + for (IAST * tree = query.next_union_all.get(); tree != nullptr; tree = static_cast(tree)->next_union_all.get()) + { + const auto & next_query = static_cast(*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(tree)->next_union_all.get()) + { + auto & ast = static_cast(*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(tree)->next_union_all.get()) + { + auto & next_query = static_cast(*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(tree)->next_union_all.get()) + { + auto & next_query = static_cast(*tree); + next_query.rewriteSelectExpressionList(required_column_names); + } } bool InterpreterSelectQuery::isFirstSelectInsideUnionAll() const { - return is_union_all_head && !query.next_union_all.isNull(); + return is_union_all_head && !query.next_union_all.isNull(); } void InterpreterSelectQuery::getDatabaseAndTableNames(String & database_name, String & table_name) { - /** Если таблица не указана - используем таблицу system.one. - * Если база данных не указана - используем текущую базу данных. - */ - if (query.database) - database_name = typeid_cast(*query.database).name; - if (query.table) - table_name = typeid_cast(*query.table).name; + /** Если таблица не указана - используем таблицу system.one. + * Если база данных не указана - используем текущую базу данных. + */ + if (query.database) + database_name = typeid_cast(*query.database).name; + if (query.table) + table_name = typeid_cast(*query.table).name; - if (!query.table) - { - database_name = "system"; - table_name = "one"; - } - else if (!query.database) - { - if (context.tryGetTable("", table_name)) - database_name = ""; - else - database_name = context.getCurrentDatabase(); - } + if (!query.table) + { + database_name = "system"; + table_name = "one"; + } + else if (!query.database) + { + if (context.tryGetTable("", table_name)) + database_name = ""; + else + database_name = context.getCurrentDatabase(); + } } DataTypes InterpreterSelectQuery::getReturnTypes() { - DataTypes res; - NamesAndTypesList columns = query_analyzer->getSelectSampleBlock().getColumnsList(); - for (NamesAndTypesList::iterator it = columns.begin(); it != columns.end(); ++it) - { - res.push_back(it->type); - } - return res; + DataTypes res; + NamesAndTypesList columns = query_analyzer->getSelectSampleBlock().getColumnsList(); + for (NamesAndTypesList::iterator it = columns.begin(); it != columns.end(); ++it) + { + res.push_back(it->type); + } + return res; } Block InterpreterSelectQuery::getSampleBlock() { - Block block = query_analyzer->getSelectSampleBlock(); - /// создадим ненулевые колонки, чтобы SampleBlock можно было - /// писать (читать) с помощью BlockOut(In)putStream'ов - for (size_t i = 0; i < block.columns(); ++i) - { - ColumnWithNameAndType & col = block.getByPosition(i); - col.column = col.type->createColumn(); - } - return block; -} - - -/// Превращает источник в асинхронный, если это указано. -static inline BlockInputStreamPtr maybeAsynchronous(BlockInputStreamPtr in, bool is_async) -{ - return is_async - ? new AsynchronousBlockInputStream(in) - : in; + Block block = query_analyzer->getSelectSampleBlock(); + /// создадим ненулевые колонки, чтобы SampleBlock можно было + /// писать (читать) с помощью BlockOut(In)putStream'ов + for (size_t i = 0; i < block.columns(); ++i) + { + ColumnWithNameAndType & col = block.getByPosition(i); + col.column = col.type->createColumn(); + } + return block; } BlockInputStreamPtr InterpreterSelectQuery::execute() -{ - if (isFirstSelectInsideUnionAll()) - { - executeSingleQuery(false); - for (auto p = next_select_in_union_all.get(); p != nullptr; p = p->next_select_in_union_all.get()) - { - p->executeSingleQuery(false); - const auto & others = p->streams; - streams.insert(streams.end(), others.begin(), others.end()); - } - - if (streams.empty()) - return new NullBlockInputStream; - - for (auto & stream : streams) - stream = new MaterializingBlockInputStream(stream); - - executeUnion(streams); - } - else - { - executeSingleQuery(); - if (streams.empty()) - return new NullBlockInputStream; - } - - /// Ограничения на результат, квота на результат, а также колбек для прогресса. - if (IProfilingBlockInputStream * stream = dynamic_cast(&*streams[0])) - { - stream->setProgressCallback(context.getProgressCallback()); - stream->setProcessListElement(context.getProcessListElement()); +{ + (void) executeWithoutUnion(); - /// Ограничения действуют только на конечный результат. - if (to_stage == QueryProcessingStage::Complete) - { - IProfilingBlockInputStream::LocalLimits limits; - limits.mode = IProfilingBlockInputStream::LIMITS_CURRENT; - limits.max_rows_to_read = settings.limits.max_result_rows; - limits.max_bytes_to_read = settings.limits.max_result_bytes; - limits.read_overflow_mode = settings.limits.result_overflow_mode; + if (streams.empty()) + return new NullBlockInputStream; - stream->setLimits(limits); - stream->setQuota(context.getQuota()); - } - } - - return streams[0]; + executeUnion(streams); + + /// Ограничения на результат, квота на результат, а также колбек для прогресса. + if (IProfilingBlockInputStream * stream = dynamic_cast(&*streams[0])) + { + stream->setProgressCallback(context.getProgressCallback()); + stream->setProcessListElement(context.getProcessListElement()); + + /// Ограничения действуют только на конечный результат. + if (to_stage == QueryProcessingStage::Complete) + { + IProfilingBlockInputStream::LocalLimits limits; + limits.mode = IProfilingBlockInputStream::LIMITS_CURRENT; + limits.max_rows_to_read = settings.limits.max_result_rows; + limits.max_bytes_to_read = settings.limits.max_result_bytes; + limits.read_overflow_mode = settings.limits.result_overflow_mode; + + stream->setLimits(limits); + stream->setQuota(context.getQuota()); + } + } + + return streams[0]; } - -void InterpreterSelectQuery::executeSingleQuery(bool should_perform_union_hint) +const BlockInputStreams & InterpreterSelectQuery::executeWithoutUnion() { - /** Потоки данных. При параллельном выполнении запроса, имеем несколько потоков данных. - * Если нет GROUP BY, то выполним все операции до ORDER BY и LIMIT параллельно, затем - * если есть ORDER BY, то склеим потоки с помощью UnionBlockInputStream, а затем MergеSortingBlockInputStream, - * если нет, то склеим с помощью UnionBlockInputStream, - * затем применим LIMIT. - * Если есть GROUP BY, то выполним все операции до GROUP BY, включительно, параллельно; - * параллельный GROUP BY склеит потоки в один, - * затем выполним остальные операции с одним получившимся потоком. - * Если запрос является членом цепочки UNION ALL и не содержит GROUP BY, ORDER BY, DISTINCT, или LIMIT, - * то объединение источников данных выполняется не на этом уровне, а на верхнем уровне. - */ + 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()); + } - bool do_execute_union = should_perform_union_hint; - - /** Вынем данные из Storage. from_stage - до какой стадии запрос был выполнен в Storage. */ - QueryProcessingStage::Enum from_stage = executeFetchColumns(streams); + for (auto & stream : streams) + stream = new MaterializingBlockInputStream(stream); + } + else + executeSingleQuery(); - LOG_TRACE(log, QueryProcessingStage::toString(from_stage) << " -> " << QueryProcessingStage::toString(to_stage)); + return streams; +} - if (to_stage > QueryProcessingStage::FetchColumns) - { - bool has_where = false; - bool need_aggregate = false; - bool has_having = false; - bool has_order_by = false; - - ExpressionActionsPtr array_join; - ExpressionActionsPtr before_where; - ExpressionActionsPtr before_aggregation; - ExpressionActionsPtr before_having; - ExpressionActionsPtr before_order_and_select; - ExpressionActionsPtr final_projection; +void InterpreterSelectQuery::executeSingleQuery() +{ + /** Потоки данных. При параллельном выполнении запроса, имеем несколько потоков данных. + * Если нет GROUP BY, то выполним все операции до ORDER BY и LIMIT параллельно, затем + * если есть ORDER BY, то склеим потоки с помощью UnionBlockInputStream, а затем MergеSortingBlockInputStream, + * если нет, то склеим с помощью UnionBlockInputStream, + * затем применим LIMIT. + * Если есть GROUP BY, то выполним все операции до GROUP BY, включительно, параллельно; + * параллельный GROUP BY склеит потоки в один, + * затем выполним остальные операции с одним получившимся потоком. + * Если запрос является членом цепочки UNION ALL и не содержит GROUP BY, ORDER BY, DISTINCT, или LIMIT, + * то объединение источников данных выполняется не на этом уровне, а на верхнем уровне. + */ - /// Столбцы из списка SELECT, до переименования в алиасы. - Names selected_columns; + bool do_execute_union = false; - /// Нужно ли выполнять первую часть конвейера - выполняемую на удаленных серверах при распределенной обработке. - bool first_stage = from_stage < QueryProcessingStage::WithMergeableState - && to_stage >= QueryProcessingStage::WithMergeableState; - /// Нужно ли выполнять вторую часть конвейера - выполняемую на сервере-инициаторе при распределенной обработке. - bool second_stage = from_stage <= QueryProcessingStage::WithMergeableState - && to_stage > QueryProcessingStage::WithMergeableState; + /** Вынем данные из Storage. from_stage - до какой стадии запрос был выполнен в Storage. */ + QueryProcessingStage::Enum from_stage = executeFetchColumns(streams); - /** Сначала составим цепочку действий и запомним нужные шаги из нее. - * Независимо от from_stage и to_stage составим полную последовательность действий, чтобы выполнять оптимизации и - * выбрасывать ненужные столбцы с учетом всего запроса. В ненужных частях запроса не будем выполнять подзапросы. - */ + LOG_TRACE(log, QueryProcessingStage::toString(from_stage) << " -> " << QueryProcessingStage::toString(to_stage)); - { - ExpressionActionsChain chain; + if (to_stage > QueryProcessingStage::FetchColumns) + { + bool has_where = false; + bool need_aggregate = false; + bool has_having = false; + bool has_order_by = false; - need_aggregate = query_analyzer->hasAggregation(); + ExpressionActionsPtr array_join; + ExpressionActionsPtr before_where; + ExpressionActionsPtr before_aggregation; + ExpressionActionsPtr before_having; + ExpressionActionsPtr before_order_and_select; + ExpressionActionsPtr final_projection; - query_analyzer->appendArrayJoin(chain, !first_stage); - query_analyzer->appendJoin(chain, !first_stage); + /// Столбцы из списка SELECT, до переименования в алиасы. + Names selected_columns; - if (query_analyzer->appendWhere(chain, !first_stage)) - { - has_where = true; - before_where = chain.getLastActions(); - chain.addStep(); - } + /// Нужно ли выполнять первую часть конвейера - выполняемую на удаленных серверах при распределенной обработке. + bool first_stage = from_stage < QueryProcessingStage::WithMergeableState + && to_stage >= QueryProcessingStage::WithMergeableState; + /// Нужно ли выполнять вторую часть конвейера - выполняемую на сервере-инициаторе при распределенной обработке. + bool second_stage = from_stage <= QueryProcessingStage::WithMergeableState + && to_stage > QueryProcessingStage::WithMergeableState; - if (need_aggregate) - { - query_analyzer->appendGroupBy(chain, !first_stage); - query_analyzer->appendAggregateFunctionsArguments(chain, !first_stage); - before_aggregation = chain.getLastActions(); + /** Сначала составим цепочку действий и запомним нужные шаги из нее. + * Независимо от from_stage и to_stage составим полную последовательность действий, чтобы выполнять оптимизации и + * выбрасывать ненужные столбцы с учетом всего запроса. В ненужных частях запроса не будем выполнять подзапросы. + */ - chain.finalize(); - chain.clear(); + { + ExpressionActionsChain chain; - if (query_analyzer->appendHaving(chain, !second_stage)) - { - has_having = true; - before_having = chain.getLastActions(); - chain.addStep(); - } - } + need_aggregate = query_analyzer->hasAggregation(); - /// Если есть агрегация, выполняем выражения в SELECT и ORDER BY на инициировавшем сервере, иначе - на серверах-источниках. - query_analyzer->appendSelect(chain, need_aggregate ? !second_stage : !first_stage); - selected_columns = chain.getLastStep().required_output; - has_order_by = query_analyzer->appendOrderBy(chain, need_aggregate ? !second_stage : !first_stage); - before_order_and_select = chain.getLastActions(); - chain.addStep(); + query_analyzer->appendArrayJoin(chain, !first_stage); + query_analyzer->appendJoin(chain, !first_stage); - query_analyzer->appendProjectResult(chain, !second_stage); - final_projection = chain.getLastActions(); + if (query_analyzer->appendWhere(chain, !first_stage)) + { + has_where = true; + before_where = chain.getLastActions(); + chain.addStep(); + } - chain.finalize(); - chain.clear(); - } + if (need_aggregate) + { + query_analyzer->appendGroupBy(chain, !first_stage); + query_analyzer->appendAggregateFunctionsArguments(chain, !first_stage); + before_aggregation = chain.getLastActions(); - /** Если данных нет. - * Эта проверка специально вынесена чуть ниже, чем она могла бы быть (сразу после executeFetchColumns), - * чтобы запрос был проанализирован, и в нём могли бы быть обнаружены ошибки (например, несоответствия типов). - * Иначе мог бы вернуться пустой результат на некорректный запрос. - */ - if (streams.empty()) - return; + chain.finalize(); + chain.clear(); - /// Перед выполнением HAVING уберем из блока лишние столбцы (в основном, ключи агрегации). - if (has_having) - before_having->prependProjectInput(); + if (query_analyzer->appendHaving(chain, !second_stage)) + { + has_having = true; + before_having = chain.getLastActions(); + chain.addStep(); + } + } - /// Теперь составим потоки блоков, выполняющие нужные действия. + /// Если есть агрегация, выполняем выражения в SELECT и ORDER BY на инициировавшем сервере, иначе - на серверах-источниках. + query_analyzer->appendSelect(chain, need_aggregate ? !second_stage : !first_stage); + selected_columns = chain.getLastStep().required_output; + has_order_by = query_analyzer->appendOrderBy(chain, need_aggregate ? !second_stage : !first_stage); + before_order_and_select = chain.getLastActions(); + chain.addStep(); - /// Нужно ли агрегировать в отдельную строку строки, не прошедшие max_rows_to_group_by. - bool aggregate_overflow_row = - need_aggregate && - query.group_by_with_totals && - settings.limits.max_rows_to_group_by && - settings.limits.group_by_overflow_mode == OverflowMode::ANY && - settings.totals_mode != TotalsMode::AFTER_HAVING_EXCLUSIVE; - /// Нужно ли после агрегации сразу финализировать агрегатные функции. - bool aggregate_final = - need_aggregate && - to_stage > QueryProcessingStage::WithMergeableState && - !query.group_by_with_totals; + query_analyzer->appendProjectResult(chain, !second_stage); + final_projection = chain.getLastActions(); - - if (need_aggregate || has_order_by) - do_execute_union = true; - - if (first_stage) - { - if (has_where) - executeWhere(streams, before_where); + chain.finalize(); + chain.clear(); + } - if (need_aggregate) - executeAggregation(streams, before_aggregation, aggregate_overflow_row, aggregate_final); - else - { - executeExpression(streams, before_order_and_select); - executeDistinct(streams, true, selected_columns); - } + /** Если данных нет. + * Эта проверка специально вынесена чуть ниже, чем она могла бы быть (сразу после executeFetchColumns), + * чтобы запрос был проанализирован, и в нём могли бы быть обнаружены ошибки (например, несоответствия типов). + * Иначе мог бы вернуться пустой результат на некорректный запрос. + */ + if (streams.empty()) + return; - /** Оптимизация - при распределённой обработке запроса, - * если не указаны GROUP, HAVING, ORDER, но указан LIMIT, - * то выполним предварительный LIMIT на удалёном сервере. - */ - if (!second_stage - && !need_aggregate && !has_having && !has_order_by - && query.limit_length) - { - executePreLimit(streams); - do_execute_union = true; - } - } - - if (second_stage) - { - bool need_second_distinct_pass = true; + /// Перед выполнением HAVING уберем из блока лишние столбцы (в основном, ключи агрегации). + if (has_having) + before_having->prependProjectInput(); - if (need_aggregate) - { - /// Если нужно объединить агрегированные результаты с нескольких серверов - if (!first_stage) - executeMergeAggregated(streams, aggregate_overflow_row, aggregate_final); + /// Теперь составим потоки блоков, выполняющие нужные действия. - if (!aggregate_final) - executeTotalsAndHaving(streams, has_having, before_having, aggregate_overflow_row); - else if (has_having) - executeHaving(streams, before_having); + /// Нужно ли агрегировать в отдельную строку строки, не прошедшие max_rows_to_group_by. + bool aggregate_overflow_row = + need_aggregate && + query.group_by_with_totals && + settings.limits.max_rows_to_group_by && + settings.limits.group_by_overflow_mode == OverflowMode::ANY && + settings.totals_mode != TotalsMode::AFTER_HAVING_EXCLUSIVE; + /// Нужно ли после агрегации сразу финализировать агрегатные функции. + bool aggregate_final = + need_aggregate && + to_stage > QueryProcessingStage::WithMergeableState && + !query.group_by_with_totals; - executeExpression(streams, before_order_and_select); - executeDistinct(streams, true, selected_columns); - need_second_distinct_pass = streams.size() > 1; - } - else if (query.group_by_with_totals && !aggregate_final) - { - executeTotalsAndHaving(streams, false, nullptr, aggregate_overflow_row); - } + if (need_aggregate || has_order_by) + do_execute_union = true; - if (has_order_by) - executeOrder(streams); + if (first_stage) + { + if (has_where) + executeWhere(streams, before_where); - executeProjection(streams, final_projection); + if (need_aggregate) + executeAggregation(streams, before_aggregation, aggregate_overflow_row, aggregate_final); + else + { + executeExpression(streams, before_order_and_select); + executeDistinct(streams, true, selected_columns); + } - /// На этой стадии можно считать минимумы и максимумы, если надо. - if (settings.extremes) - for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it) - if (IProfilingBlockInputStream * stream = dynamic_cast(&**it)) - stream->enableExtremes(); + /** Оптимизация - при распределённой обработке запроса, + * если не указаны GROUP, HAVING, ORDER, но указан LIMIT, + * то выполним предварительный LIMIT на удалёном сервере. + */ + if (!second_stage + && !need_aggregate && !has_having && !has_order_by + && query.limit_length) + { + executePreLimit(streams); + do_execute_union = true; + } + } - /** Оптимизация - если источников несколько и есть LIMIT, то сначала применим предварительный LIMIT, - * ограничивающий число записей в каждом до offset + limit. - */ - if (query.limit_length && streams.size() > 1 && !query.distinct) - { - executePreLimit(streams); - do_execute_union = true; - } - - if (need_second_distinct_pass) - do_execute_union = true; - - if (do_execute_union) - executeUnion(streams); - - /// Если было более одного источника - то нужно выполнить DISTINCT ещё раз после их слияния. - if (need_second_distinct_pass) - executeDistinct(streams, false, Names()); + if (second_stage) + { + bool need_second_distinct_pass = true; - executeLimit(streams); - } - } + if (need_aggregate) + { + /// Если нужно объединить агрегированные результаты с нескольких серверов + if (!first_stage) + executeMergeAggregated(streams, aggregate_overflow_row, aggregate_final); - /** Если данных нет. */ - if (streams.empty()) - return; + if (!aggregate_final) + executeTotalsAndHaving(streams, has_having, before_having, aggregate_overflow_row); + else if (has_having) + executeHaving(streams, before_having); - if (do_execute_union) - executeUnion(streams); + executeExpression(streams, before_order_and_select); + executeDistinct(streams, true, selected_columns); - SubqueriesForSets subqueries_for_sets = query_analyzer->getSubqueriesForSets(); - if (!subqueries_for_sets.empty()) - executeSubqueriesInSetsAndJoins(streams, subqueries_for_sets); + need_second_distinct_pass = streams.size() > 1; + } + else if (query.group_by_with_totals && !aggregate_final) + { + executeTotalsAndHaving(streams, false, nullptr, aggregate_overflow_row); + } + + if (has_order_by) + executeOrder(streams); + + executeProjection(streams, final_projection); + + /// На этой стадии можно считать минимумы и максимумы, если надо. + if (settings.extremes) + for (auto & stream : streams) + if (IProfilingBlockInputStream * p_stream = dynamic_cast(&*stream)) + p_stream->enableExtremes(); + + /** Оптимизация - если источников несколько и есть LIMIT, то сначала применим предварительный LIMIT, + * ограничивающий число записей в каждом до offset + limit. + */ + if (query.limit_length && streams.size() > 1 && !query.distinct) + { + executePreLimit(streams); + do_execute_union = true; + } + + if (need_second_distinct_pass) + do_execute_union = true; + + if (do_execute_union) + executeUnion(streams); + + /// Если было более одного источника - то нужно выполнить DISTINCT ещё раз после их слияния. + if (need_second_distinct_pass) + executeDistinct(streams, false, Names()); + + executeLimit(streams); + } + } + + /** Если данных нет. */ + if (streams.empty()) + return; + + SubqueriesForSets subqueries_for_sets = query_analyzer->getSubqueriesForSets(); + if (!subqueries_for_sets.empty()) + executeSubqueriesInSetsAndJoins(streams, subqueries_for_sets); } static void getLimitLengthAndOffset(ASTSelectQuery & query, size_t & length, size_t & offset) { - length = 0; - offset = 0; - if (query.limit_length) - { - length = safeGet(typeid_cast(*query.limit_length).value); - if (query.limit_offset) - offset = safeGet(typeid_cast(*query.limit_offset).value); - } + length = 0; + offset = 0; + if (query.limit_length) + { + length = safeGet(typeid_cast(*query.limit_length).value); + if (query.limit_offset) + offset = safeGet(typeid_cast(*query.limit_offset).value); + } } QueryProcessingStage::Enum InterpreterSelectQuery::executeFetchColumns(BlockInputStreams & streams) { - if (!streams.empty()) - return QueryProcessingStage::FetchColumns; + if (!streams.empty()) + return QueryProcessingStage::FetchColumns; - /// Интерпретатор подзапроса, если подзапрос - SharedPtr interpreter_subquery; + /// Интерпретатор подзапроса, если подзапрос + SharedPtr interpreter_subquery; - /// Список столбцов, которых нужно прочитать, чтобы выполнить запрос. - Names required_columns = query_analyzer->getRequiredColumns(); + /// Список столбцов, которых нужно прочитать, чтобы выполнить запрос. + Names required_columns = query_analyzer->getRequiredColumns(); - if (query.table && typeid_cast(&*query.table)) - { - /** Для подзапроса не действуют ограничения на максимальный размер результата. - * Так как результат поздапроса - ещё не результат всего запроса. - */ - Context subquery_context = context; - Settings subquery_settings = context.getSettings(); - subquery_settings.limits.max_result_rows = 0; - subquery_settings.limits.max_result_bytes = 0; - /// Вычисление extremes не имеет смысла и не нужно (если его делать, то в результате всего запроса могут взяться extremes подзапроса). - subquery_settings.extremes = 0; - subquery_context.setSettings(subquery_settings); + if (query.table && typeid_cast(&*query.table)) + { + /** Для подзапроса не действуют ограничения на максимальный размер результата. + * Так как результат поздапроса - ещё не результат всего запроса. + */ + Context subquery_context = context; + Settings subquery_settings = context.getSettings(); + subquery_settings.limits.max_result_rows = 0; + subquery_settings.limits.max_result_bytes = 0; + /// Вычисление extremes не имеет смысла и не нужно (если его делать, то в результате всего запроса могут взяться extremes подзапроса). + subquery_settings.extremes = 0; + subquery_context.setSettings(subquery_settings); - interpreter_subquery = new InterpreterSelectQuery( - query.table, subquery_context, required_columns, QueryProcessingStage::Complete, subquery_depth + 1); - } + interpreter_subquery = new InterpreterSelectQuery( + query.table, subquery_context, required_columns, QueryProcessingStage::Complete, subquery_depth + 1); + } - /// если в настройках установлен default_sample != 1, то все запросы выполняем с сэмплингом - /// если таблица не поддерживает сэмплинг получим исключение - /// поэтому запросы типа SHOW TABLES работать с включенном default_sample не будут - if (!query.sample_size && settings.default_sample != 1) - query.sample_size = new ASTLiteral(StringRange(), Float64(settings.default_sample)); + /// если в настройках установлен default_sample != 1, то все запросы выполняем с сэмплингом + /// если таблица не поддерживает сэмплинг получим исключение + /// поэтому запросы типа SHOW TABLES работать с включенном default_sample не будут + if (!query.sample_size && settings.default_sample != 1) + query.sample_size = new ASTLiteral(StringRange(), Float64(settings.default_sample)); - if (query.sample_size && (!storage || !storage->supportsSampling())) - throw Exception("Illegal SAMPLE: table doesn't support sampling", ErrorCodes::SAMPLING_NOT_SUPPORTED); + if (query.sample_size && (!storage || !storage->supportsSampling())) + throw Exception("Illegal SAMPLE: table doesn't support sampling", ErrorCodes::SAMPLING_NOT_SUPPORTED); - if (query.final && (!storage || !storage->supportsFinal())) - throw Exception(storage ? "Storage " + storage->getName() + " doesn't support FINAL" : "Illegal FINAL", ErrorCodes::ILLEGAL_FINAL); + if (query.final && (!storage || !storage->supportsFinal())) + throw Exception(storage ? "Storage " + storage->getName() + " doesn't support FINAL" : "Illegal FINAL", ErrorCodes::ILLEGAL_FINAL); - if (query.prewhere_expression && (!storage || !storage->supportsPrewhere())) - throw Exception(storage ? "Storage " + storage->getName() + " doesn't support PREWHERE" : "Illegal PREWHERE", ErrorCodes::ILLEGAL_PREWHERE); + if (query.prewhere_expression && (!storage || !storage->supportsPrewhere())) + throw Exception(storage ? "Storage " + storage->getName() + " doesn't support PREWHERE" : "Illegal PREWHERE", ErrorCodes::ILLEGAL_PREWHERE); - /** При распределённой обработке запроса, в потоках почти не делается вычислений, - * а делается ожидание и получение данных с удалённых серверов. - * Если у нас 20 удалённых серверов, а max_threads = 8, то было бы не очень хорошо - * соединяться и опрашивать только по 8 серверов одновременно. - * Чтобы одновременно опрашивалось больше удалённых серверов, - * вместо max_threads используется max_distributed_connections. - * - * Сохраним изначальное значение max_threads в settings_for_storage - * - эти настройки будут переданы на удалённые серверы при распределённой обработке запроса, - * и там должно быть оригинальное значение max_threads, а не увеличенное. - */ - Settings settings_for_storage = settings; - if (storage && storage->isRemote()) - settings.max_threads = settings.max_distributed_connections; + /** При распределённой обработке запроса, в потоках почти не делается вычислений, + * а делается ожидание и получение данных с удалённых серверов. + * Если у нас 20 удалённых серверов, а max_threads = 8, то было бы не очень хорошо + * соединяться и опрашивать только по 8 серверов одновременно. + * Чтобы одновременно опрашивалось больше удалённых серверов, + * вместо max_threads используется max_distributed_connections. + * + * Сохраним изначальное значение max_threads в settings_for_storage + * - эти настройки будут переданы на удалённые серверы при распределённой обработке запроса, + * и там должно быть оригинальное значение max_threads, а не увеличенное. + */ + Settings settings_for_storage = settings; + if (storage && storage->isRemote()) + settings.max_threads = settings.max_distributed_connections; - /// Ограничение на количество столбцов для чтения. - if (settings.limits.max_columns_to_read && required_columns.size() > settings.limits.max_columns_to_read) - throw Exception("Limit for number of columns to read exceeded. " - "Requested: " + toString(required_columns.size()) - + ", maximum: " + toString(settings.limits.max_columns_to_read), - ErrorCodes::TOO_MUCH_COLUMNS); + /// Ограничение на количество столбцов для чтения. + if (settings.limits.max_columns_to_read && required_columns.size() > settings.limits.max_columns_to_read) + throw Exception("Limit for number of columns to read exceeded. " + "Requested: " + toString(required_columns.size()) + + ", maximum: " + toString(settings.limits.max_columns_to_read), + ErrorCodes::TOO_MUCH_COLUMNS); - size_t limit_length = 0; - size_t limit_offset = 0; - getLimitLengthAndOffset(query, limit_length, limit_offset); + size_t limit_length = 0; + size_t limit_offset = 0; + getLimitLengthAndOffset(query, limit_length, limit_offset); - /** Оптимизация - если не указаны DISTINCT, WHERE, GROUP, HAVING, ORDER, но указан LIMIT, и limit + offset < max_block_size, - * то в качестве размера блока будем использовать limit + offset (чтобы не читать из таблицы больше, чем запрошено), - * а также установим количество потоков в 1 и отменим асинхронное выполнение конвейера запроса. - */ - if (!query.distinct && !query.prewhere_expression && !query.where_expression && !query.group_expression_list && !query.having_expression && !query.order_expression_list - && query.limit_length && !query_analyzer->hasAggregation() && limit_length + limit_offset < settings.max_block_size) - { - settings.max_block_size = limit_length + limit_offset; - settings.max_threads = 1; - settings.asynchronous = false; - } + /** Оптимизация - если не указаны DISTINCT, WHERE, GROUP, HAVING, ORDER, но указан LIMIT, и limit + offset < max_block_size, + * то в качестве размера блока будем использовать limit + offset (чтобы не читать из таблицы больше, чем запрошено), + * а также установим количество потоков в 1 и отменим асинхронное выполнение конвейера запроса. + */ + if (!query.distinct && !query.prewhere_expression && !query.where_expression && !query.group_expression_list && !query.having_expression && !query.order_expression_list + && query.limit_length && !query_analyzer->hasAggregation() && limit_length + limit_offset < settings.max_block_size) + { + settings.max_block_size = limit_length + limit_offset; + settings.max_threads = 1; + } - QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns; + QueryProcessingStage::Enum from_stage = QueryProcessingStage::FetchColumns; - query_analyzer->makeSetsForIndex(); + query_analyzer->makeSetsForIndex(); - /// Инициализируем изначальные потоки данных, на которые накладываются преобразования запроса. Таблица или подзапрос? - if (!interpreter_subquery) - { - /** При распределённой обработке запроса, на все удалённые серверы отправляются временные таблицы, - * полученные из глобальных подзапросов - GLOBAL IN/JOIN. - */ - if (storage && storage->isRemote()) - storage->storeExternalTables(query_analyzer->getExternalTables()); + /// Инициализируем изначальные потоки данных, на которые накладываются преобразования запроса. Таблица или подзапрос? + if (!interpreter_subquery) + { + /** При распределённой обработке запроса, на все удалённые серверы отправляются временные таблицы, + * полученные из глобальных подзапросов - GLOBAL IN/JOIN. + */ + if (storage && storage->isRemote()) + storage->storeExternalTables(query_analyzer->getExternalTables()); streams = storage->read(required_columns, query_ptr, - context, settings_for_storage, from_stage, - settings.max_block_size, settings.max_threads); + context, settings_for_storage, from_stage, + settings.max_block_size, settings.max_threads); - for (auto & stream : streams) - stream->addTableLock(table_lock); - } - else - { - streams.push_back(maybeAsynchronous(interpreter_subquery->execute(), settings.asynchronous)); - } + for (auto & stream : streams) + stream->addTableLock(table_lock); + } + else + { + streams.push_back(interpreter_subquery->execute()); + } - /** Если истчоников слишком много, то склеим их в max_threads источников. - * (Иначе действия в каждом маленьком источнике, а затем объединение состояний, слишком неэффективно.) - */ - if (streams.size() > settings.max_threads) - streams = narrowBlockInputStreams(streams, settings.max_threads); + /** Если истчоников слишком много, то склеим их в max_threads источников. + * (Иначе действия в каждом маленьком источнике, а затем объединение состояний, слишком неэффективно.) + */ + if (streams.size() > settings.max_threads) + streams = narrowBlockInputStreams(streams, settings.max_threads); - /** Установка ограничений и квоты на чтение данных, скорость и время выполнения запроса. - * Такие ограничения проверяются на сервере-инициаторе запроса, а не на удалённых серверах. - * Потому что сервер-инициатор имеет суммарные данные о выполнении запроса на всех серверах. - */ - if (storage && to_stage == QueryProcessingStage::Complete) - { - IProfilingBlockInputStream::LocalLimits limits; - limits.mode = IProfilingBlockInputStream::LIMITS_TOTAL; - limits.max_rows_to_read = settings.limits.max_rows_to_read; - limits.max_bytes_to_read = settings.limits.max_bytes_to_read; - limits.read_overflow_mode = settings.limits.read_overflow_mode; - limits.max_execution_time = settings.limits.max_execution_time; - limits.timeout_overflow_mode = settings.limits.timeout_overflow_mode; - limits.min_execution_speed = settings.limits.min_execution_speed; - limits.timeout_before_checking_execution_speed = settings.limits.timeout_before_checking_execution_speed; + /** Установка ограничений и квоты на чтение данных, скорость и время выполнения запроса. + * Такие ограничения проверяются на сервере-инициаторе запроса, а не на удалённых серверах. + * Потому что сервер-инициатор имеет суммарные данные о выполнении запроса на всех серверах. + */ + if (storage && to_stage == QueryProcessingStage::Complete) + { + IProfilingBlockInputStream::LocalLimits limits; + limits.mode = IProfilingBlockInputStream::LIMITS_TOTAL; + limits.max_rows_to_read = settings.limits.max_rows_to_read; + limits.max_bytes_to_read = settings.limits.max_bytes_to_read; + limits.read_overflow_mode = settings.limits.read_overflow_mode; + limits.max_execution_time = settings.limits.max_execution_time; + limits.timeout_overflow_mode = settings.limits.timeout_overflow_mode; + limits.min_execution_speed = settings.limits.min_execution_speed; + limits.timeout_before_checking_execution_speed = settings.limits.timeout_before_checking_execution_speed; - QuotaForIntervals & quota = context.getQuota(); + QuotaForIntervals & quota = context.getQuota(); - for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it) - { - if (IProfilingBlockInputStream * stream = dynamic_cast(&**it)) - { - stream->setLimits(limits); - stream->setQuota(quota); - } - } - } + for (auto & stream : streams) + { + if (IProfilingBlockInputStream * p_stream = dynamic_cast(&*stream)) + { + p_stream->setLimits(limits); + p_stream->setQuota(quota); + } + } + } - return from_stage; + return from_stage; } void InterpreterSelectQuery::executeWhere(BlockInputStreams & streams, ExpressionActionsPtr expression) { - bool is_async = settings.asynchronous && streams.size() <= settings.max_threads; - for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it) - { - BlockInputStreamPtr & stream = *it; - stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async); - stream = maybeAsynchronous(new FilterBlockInputStream(stream, query.where_expression->getColumnName()), is_async); - } + for (auto & stream : streams) + { + stream = new ExpressionBlockInputStream(stream, expression); + stream = new FilterBlockInputStream(stream, query.where_expression->getColumnName()); + } } void InterpreterSelectQuery::executeAggregation(BlockInputStreams & streams, ExpressionActionsPtr expression, bool overflow_row, bool final) { - bool is_async = settings.asynchronous && streams.size() <= settings.max_threads; - for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it) - { - BlockInputStreamPtr & stream = *it; - stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async); - } + for (auto & stream : streams) + { + stream = new ExpressionBlockInputStream(stream, expression); + } - BlockInputStreamPtr & stream = streams[0]; + BlockInputStreamPtr & stream = streams[0]; - Names key_names; - AggregateDescriptions aggregates; - query_analyzer->getAggregateInfo(key_names, aggregates); + Names key_names; + AggregateDescriptions aggregates; + query_analyzer->getAggregateInfo(key_names, aggregates); - bool separate_totals = to_stage > QueryProcessingStage::WithMergeableState; + /// Если источников несколько, то выполняем параллельную агрегацию + if (streams.size() > 1) + { + stream = new ParallelAggregatingBlockInputStream(streams, key_names, aggregates, overflow_row, final, + settings.max_threads, settings.limits.max_rows_to_group_by, settings.limits.group_by_overflow_mode, + settings.compile ? &context.getCompiler() : nullptr, settings.min_count_to_compile); - /// Если источников несколько, то выполняем параллельную агрегацию - if (streams.size() > 1) - { - if (!settings.use_splitting_aggregator || key_names.empty()) - { - stream = maybeAsynchronous( - new ParallelAggregatingBlockInputStream(streams, key_names, aggregates, overflow_row, final, - settings.max_threads, settings.limits.max_rows_to_group_by, settings.limits.group_by_overflow_mode), settings.asynchronous); - } - else - { - if (overflow_row) - throw Exception("Splitting aggregator cannot handle queries like this yet. " - "Please change use_splitting_aggregator, remove WITH TOTALS, " - "change group_by_overflow_mode or set totals_mode to AFTER_HAVING_EXCLUSIVE.", - ErrorCodes::NOT_IMPLEMENTED); - stream = maybeAsynchronous( - new SplittingAggregatingBlockInputStream( - new UnionBlockInputStream(streams, settings.max_threads), - key_names, aggregates, settings.max_threads, query.group_by_with_totals, separate_totals, final, - settings.limits.max_rows_to_group_by, settings.limits.group_by_overflow_mode), settings.asynchronous); - } - - streams.resize(1); - } - else - stream = maybeAsynchronous(new AggregatingBlockInputStream(stream, key_names, aggregates, overflow_row, final, - settings.limits.max_rows_to_group_by, settings.limits.group_by_overflow_mode), settings.asynchronous); + streams.resize(1); + } + else + stream = new AggregatingBlockInputStream(stream, key_names, aggregates, overflow_row, final, + settings.limits.max_rows_to_group_by, settings.limits.group_by_overflow_mode, + settings.compile ? &context.getCompiler() : nullptr, settings.min_count_to_compile); } void InterpreterSelectQuery::executeMergeAggregated(BlockInputStreams & streams, bool overflow_row, bool final) { - /// Если объединять нечего - if (streams.size() == 1) - return; + /// Если объединять нечего + if (streams.size() == 1) + return; - /// Склеим несколько источников в один - streams[0] = new UnionBlockInputStream(streams, settings.max_threads); - streams.resize(1); + /// Склеим несколько источников в один + streams[0] = new UnionBlockInputStream(streams, settings.max_threads); + streams.resize(1); - /// Теперь объединим агрегированные блоки - Names key_names; - AggregateDescriptions aggregates; - query_analyzer->getAggregateInfo(key_names, aggregates); - streams[0] = maybeAsynchronous(new MergingAggregatedBlockInputStream(streams[0], key_names, aggregates, overflow_row, final), settings.asynchronous); + /// Теперь объединим агрегированные блоки + Names key_names; + AggregateDescriptions aggregates; + query_analyzer->getAggregateInfo(key_names, aggregates); + streams[0] = new MergingAggregatedBlockInputStream(streams[0], key_names, aggregates, overflow_row, final, original_max_threads); } void InterpreterSelectQuery::executeHaving(BlockInputStreams & streams, ExpressionActionsPtr expression) { - bool is_async = settings.asynchronous && streams.size() <= settings.max_threads; - for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it) - { - BlockInputStreamPtr & stream = *it; - stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async); - stream = maybeAsynchronous(new FilterBlockInputStream(stream, query.having_expression->getColumnName()), is_async); - } + for (auto & stream : streams) + { + stream = new ExpressionBlockInputStream(stream, expression); + stream = new FilterBlockInputStream(stream, query.having_expression->getColumnName()); + } } void InterpreterSelectQuery::executeTotalsAndHaving(BlockInputStreams & streams, bool has_having, - ExpressionActionsPtr expression, bool overflow_row) + ExpressionActionsPtr expression, bool overflow_row) { - if (streams.size() > 1) - { - streams[0] = new UnionBlockInputStream(streams, settings.max_threads); - streams.resize(1); - } + if (streams.size() > 1) + { + streams[0] = new UnionBlockInputStream(streams, settings.max_threads); + streams.resize(1); + } - Names key_names; - AggregateDescriptions aggregates; - query_analyzer->getAggregateInfo(key_names, aggregates); - streams[0] = maybeAsynchronous(new TotalsHavingBlockInputStream( - streams[0], key_names, aggregates, overflow_row, expression, - has_having ? query.having_expression->getColumnName() : "", settings.totals_mode, settings.totals_auto_threshold), - settings.asynchronous); + Names key_names; + AggregateDescriptions aggregates; + query_analyzer->getAggregateInfo(key_names, aggregates); + streams[0] = new TotalsHavingBlockInputStream( + streams[0], key_names, aggregates, overflow_row, expression, + has_having ? query.having_expression->getColumnName() : "", settings.totals_mode, settings.totals_auto_threshold); } void InterpreterSelectQuery::executeExpression(BlockInputStreams & streams, ExpressionActionsPtr expression) { - bool is_async = settings.asynchronous && streams.size() <= settings.max_threads; - for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it) - { - BlockInputStreamPtr & stream = *it; - stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async); - } + for (auto & stream : streams) + { + stream = new ExpressionBlockInputStream(stream, expression); + } } void InterpreterSelectQuery::executeOrder(BlockInputStreams & streams) { - SortDescription order_descr; - order_descr.reserve(query.order_expression_list->children.size()); - for (ASTs::iterator it = query.order_expression_list->children.begin(); - it != query.order_expression_list->children.end(); - ++it) - { - String name = (*it)->children.front()->getColumnName(); - order_descr.push_back(SortColumnDescription(name, typeid_cast(**it).direction)); - } + SortDescription order_descr; + order_descr.reserve(query.order_expression_list->children.size()); + for (ASTs::iterator it = query.order_expression_list->children.begin(); + it != query.order_expression_list->children.end(); + ++it) + { + String name = (*it)->children.front()->getColumnName(); + const ASTOrderByElement & order_by_elem = typeid_cast(**it); - /// Если есть LIMIT и нет DISTINCT - можно делать частичную сортировку. - size_t limit = 0; - if (!query.distinct) - { - size_t limit_length = 0; - size_t limit_offset = 0; - getLimitLengthAndOffset(query, limit_length, limit_offset); - limit = limit_length + limit_offset; - } + order_descr.emplace_back(name, order_by_elem.direction, order_by_elem.collator); + } - bool is_async = settings.asynchronous && streams.size() <= settings.max_threads; - for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it) - { - BlockInputStreamPtr & stream = *it; - IProfilingBlockInputStream * sorting_stream = new PartialSortingBlockInputStream(stream, order_descr, limit); + /// Если есть LIMIT и нет DISTINCT - можно делать частичную сортировку. + size_t limit = 0; + if (!query.distinct) + { + size_t limit_length = 0; + size_t limit_offset = 0; + getLimitLengthAndOffset(query, limit_length, limit_offset); + limit = limit_length + limit_offset; + } - /// Ограничения на сортировку - IProfilingBlockInputStream::LocalLimits limits; - limits.mode = IProfilingBlockInputStream::LIMITS_TOTAL; - limits.max_rows_to_read = settings.limits.max_rows_to_sort; - limits.max_bytes_to_read = settings.limits.max_bytes_to_sort; - limits.read_overflow_mode = settings.limits.sort_overflow_mode; - sorting_stream->setLimits(limits); + for (auto & stream : streams) + { + IProfilingBlockInputStream * sorting_stream = new PartialSortingBlockInputStream(stream, order_descr, limit); - stream = maybeAsynchronous(sorting_stream, is_async); - } + /// Ограничения на сортировку + IProfilingBlockInputStream::LocalLimits limits; + limits.mode = IProfilingBlockInputStream::LIMITS_TOTAL; + limits.max_rows_to_read = settings.limits.max_rows_to_sort; + limits.max_bytes_to_read = settings.limits.max_bytes_to_sort; + limits.read_overflow_mode = settings.limits.sort_overflow_mode; + sorting_stream->setLimits(limits); - BlockInputStreamPtr & stream = streams[0]; + stream = sorting_stream; + } - /// Если потоков несколько, то объединяем их в один - if (streams.size() > 1) - { - stream = new UnionBlockInputStream(streams, settings.max_threads); - streams.resize(1); - } + BlockInputStreamPtr & stream = streams[0]; - /// Сливаем сортированные блоки TODO: таймаут на слияние. - stream = maybeAsynchronous(new MergeSortingBlockInputStream(stream, order_descr, limit), is_async); + /// Если потоков несколько, то объединяем их в один + if (streams.size() > 1) + { + stream = new UnionBlockInputStream(streams, settings.max_threads); + streams.resize(1); + } + + /// Сливаем сортированные блоки. + stream = new MergeSortingBlockInputStream( + stream, order_descr, settings.max_block_size, limit, + settings.limits.max_bytes_before_external_sort, context.getTemporaryPath(), context.getDataTypeFactory()); } void InterpreterSelectQuery::executeProjection(BlockInputStreams & streams, ExpressionActionsPtr expression) { - bool is_async = settings.asynchronous && streams.size() <= settings.max_threads; - for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it) - { - BlockInputStreamPtr & stream = *it; - stream = maybeAsynchronous(new ExpressionBlockInputStream(stream, expression), is_async); - } + for (auto & stream : streams) + { + stream = new ExpressionBlockInputStream(stream, expression); + } } void InterpreterSelectQuery::executeDistinct(BlockInputStreams & streams, bool before_order, Names columns) { - if (query.distinct) - { - size_t limit_length = 0; - size_t limit_offset = 0; - getLimitLengthAndOffset(query, limit_length, limit_offset); + if (query.distinct) + { + size_t limit_length = 0; + size_t limit_offset = 0; + getLimitLengthAndOffset(query, limit_length, limit_offset); - size_t limit_for_distinct = 0; + size_t limit_for_distinct = 0; - /// Если после этой стадии DISTINCT не будет выполняться ORDER BY, то можно достать не более limit_length + limit_offset различных строк. - if (!query.order_expression_list || !before_order) - limit_for_distinct = limit_length + limit_offset; + /// Если после этой стадии DISTINCT не будет выполняться ORDER BY, то можно достать не более limit_length + limit_offset различных строк. + if (!query.order_expression_list || !before_order) + limit_for_distinct = limit_length + limit_offset; - bool is_async = settings.asynchronous && streams.size() <= settings.max_threads; - for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it) - { - BlockInputStreamPtr & stream = *it; - stream = maybeAsynchronous(new DistinctBlockInputStream( - stream, settings.limits, limit_for_distinct, columns), is_async); - } - } + for (auto & stream : streams) + { + stream = new DistinctBlockInputStream(stream, settings.limits, limit_for_distinct, columns); + } + } } void InterpreterSelectQuery::executeUnion(BlockInputStreams & streams) { - /// Если до сих пор есть несколько потоков, то объединяем их в один - if (streams.size() > 1) - { - streams[0] = new UnionBlockInputStream(streams, settings.max_threads); - streams.resize(1); - } + /// Если до сих пор есть несколько потоков, то объединяем их в один + if (streams.size() > 1) + { + streams[0] = new UnionBlockInputStream(streams, settings.max_threads); + streams.resize(1); + } } /// Предварительный LIMIT - применяется в каждом источнике, если источников несколько, до их объединения. void InterpreterSelectQuery::executePreLimit(BlockInputStreams & streams) { - size_t limit_length = 0; - size_t limit_offset = 0; - getLimitLengthAndOffset(query, limit_length, limit_offset); + size_t limit_length = 0; + size_t limit_offset = 0; + getLimitLengthAndOffset(query, limit_length, limit_offset); - /// Если есть LIMIT - if (query.limit_length) - { - for (BlockInputStreams::iterator it = streams.begin(); it != streams.end(); ++it) - { - BlockInputStreamPtr & stream = *it; - stream = new LimitBlockInputStream(stream, limit_length + limit_offset, 0); - } - } + /// Если есть LIMIT + if (query.limit_length) + { + for (auto & stream : streams) + { + stream = new LimitBlockInputStream(stream, limit_length + limit_offset, 0); + } + } } void InterpreterSelectQuery::executeLimit(BlockInputStreams & streams) { - size_t limit_length = 0; - size_t limit_offset = 0; - getLimitLengthAndOffset(query, limit_length, limit_offset); + size_t limit_length = 0; + size_t limit_offset = 0; + getLimitLengthAndOffset(query, limit_length, limit_offset); - /// Если есть LIMIT - if (query.limit_length) - { - BlockInputStreamPtr & stream = streams[0]; - stream = new LimitBlockInputStream(stream, limit_length, limit_offset); - } + /// Если есть LIMIT + if (query.limit_length) + { + BlockInputStreamPtr & stream = streams[0]; + stream = new LimitBlockInputStream(stream, limit_length, limit_offset); + } } void InterpreterSelectQuery::executeSubqueriesInSetsAndJoins(BlockInputStreams & streams, SubqueriesForSets & subqueries_for_sets) { - /// Если запрос не распределённый, то удалим создание временных таблиц из подзапросов (предназначавшихся для отправки на удалённые серверы). - if (!(storage && storage->isRemote())) - for (auto & elem : subqueries_for_sets) - elem.second.table.reset(); + /// Если запрос не распределённый, то удалим создание временных таблиц из подзапросов (предназначавшихся для отправки на удалённые серверы). + if (!(storage && storage->isRemote())) + for (auto & elem : subqueries_for_sets) + elem.second.table.reset(); - streams[0] = new CreatingSetsBlockInputStream(streams[0], subqueries_for_sets, settings.limits); + streams[0] = new CreatingSetsBlockInputStream(streams[0], subqueries_for_sets, settings.limits); } BlockInputStreamPtr InterpreterSelectQuery::executeAndFormat(WriteBuffer & buf) { - Block sample = getSampleBlock(); - String format_name = query.format ? typeid_cast(*query.format).name : context.getDefaultFormat(); + Block sample = getSampleBlock(); + String format_name = query.format ? typeid_cast(*query.format).name : context.getDefaultFormat(); - BlockInputStreamPtr in = execute(); - BlockOutputStreamPtr out = context.getFormatFactory().getOutput(format_name, buf, sample); + BlockInputStreamPtr in = execute(); + BlockOutputStreamPtr out = context.getFormatFactory().getOutput(format_name, buf, sample); - copyData(*in, *out); + copyData(*in, *out); - return in; + return in; } diff --git a/dbms/src/Interpreters/SplittingAggregator.cpp b/dbms/src/Interpreters/SplittingAggregator.cpp deleted file mode 100644 index 7850ef5de3b..00000000000 --- a/dbms/src/Interpreters/SplittingAggregator.cpp +++ /dev/null @@ -1,465 +0,0 @@ -#include - -#include -#include - -#include - - -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(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(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 -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(result, thread_no, no_more_keys); - else if (method == AggregatedDataVariants::KEY_16) - aggregateOneNumber(result, thread_no, no_more_keys); - else if (method == AggregatedDataVariants::KEY_32) - aggregateOneNumber(result, thread_no, no_more_keys); - else if (method == AggregatedDataVariants::KEY_64) - aggregateOneNumber(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(); - } -} - - -} diff --git a/dbms/src/Interpreters/sortBlock.cpp b/dbms/src/Interpreters/sortBlock.cpp index acb18f90d1e..f1b7405bd4f 100644 --- a/dbms/src/Interpreters/sortBlock.cpp +++ b/dbms/src/Interpreters/sortBlock.cpp @@ -9,7 +9,13 @@ typedef std::vector > ColumnsW static inline bool needCollation(const IColumn * column, const SortColumnDescription & description) { - return !description.collator.isNull() && column->getName() == "ColumnString"; + if (description.collator.isNull()) + return false; + + if (column->getName() != "ColumnString") + throw Exception("Collations could be specified only for String columns.", ErrorCodes::BAD_COLLATION); + + return true; } diff --git a/dbms/src/Interpreters/tests/aggregate.cpp b/dbms/src/Interpreters/tests/aggregate.cpp index cb96c19cb7b..1f88b3e1f83 100644 --- a/dbms/src/Interpreters/tests/aggregate.cpp +++ b/dbms/src/Interpreters/tests/aggregate.cpp @@ -23,7 +23,7 @@ int main(int argc, char ** argv) size_t n = argc == 2 ? atoi(argv[1]) : 10; DB::Block block; - + DB::ColumnWithNameAndType column_x; column_x.name = "x"; column_x.type = new DB::DataTypeInt16; @@ -73,8 +73,8 @@ int main(int argc, char ** argv) DB::DataTypes empty_list_of_types; aggregate_descriptions[0].function = factory.get("count", empty_list_of_types); - DB::Aggregator aggregator(key_column_numbers, aggregate_descriptions, false); - + DB::Aggregator aggregator(key_column_numbers, aggregate_descriptions, false, 0, DB::OverflowMode::THROW, nullptr, 0); + { Poco::Stopwatch stopwatch; stopwatch.start(); diff --git a/dbms/src/Interpreters/tests/compiler_test.cpp b/dbms/src/Interpreters/tests/compiler_test.cpp new file mode 100644 index 00000000000..208b441d66d --- /dev/null +++ b/dbms/src/Interpreters/tests/compiler_test.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include + + +int main(int argc, char ** argv) +{ + using namespace DB; + + Poco::AutoPtr 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; +} diff --git a/dbms/src/Parsers/formatAST.cpp b/dbms/src/Parsers/formatAST.cpp index 32c490202a5..97f333d12db 100644 --- a/dbms/src/Parsers/formatAST.cpp +++ b/dbms/src/Parsers/formatAST.cpp @@ -238,17 +238,17 @@ void formatAST(const ASTSelectQuery & ast, std::ostream & s, size_t indent, bo s << (hilite ? hilite_keyword : "") << nl_or_ws << indent_str << "FORMAT " << (hilite ? hilite_none : ""); formatAST(*ast.format, s, indent, hilite, one_line); } - - if (ast.next_union_all) - { - s << (hilite ? hilite_keyword : "") << nl_or_ws << indent_str << "UNION ALL " << nl_or_ws << (hilite ? hilite_none : ""); - // NOTE Мы можем безопасно применить static_cast вместо typeid_cast, потому что знаем, что в цепочке UNION ALL - // имеются только деревья типа SELECT. - const ASTSelectQuery & next_ast = static_cast(*ast.next_union_all); + if (ast.next_union_all) + { + s << (hilite ? hilite_keyword : "") << nl_or_ws << indent_str << "UNION ALL " << nl_or_ws << (hilite ? hilite_none : ""); - formatAST(next_ast, s, indent, hilite, one_line, need_parens); - } + // NOTE Мы можем безопасно применить static_cast вместо typeid_cast, потому что знаем, что в цепочке UNION ALL + // имеются только деревья типа SELECT. + const ASTSelectQuery & next_ast = static_cast(*ast.next_union_all); + + formatAST(next_ast, s, indent, hilite, one_line, need_parens); + } } void formatAST(const ASTSubquery & ast, std::ostream & s, size_t indent, bool hilite, bool one_line, bool need_parens) diff --git a/dbms/src/Server/Server.cpp b/dbms/src/Server/Server.cpp index 0a5004ef401..394e796e205 100644 --- a/dbms/src/Server/Server.cpp +++ b/dbms/src/Server/Server.cpp @@ -1,10 +1,12 @@ #include +#include #include #include #include #include +#include #include #include @@ -21,6 +23,11 @@ #include #include +#include +#include +#include +#include + #include "Server.h" #include "HTTPHandler.h" #include "InterserverIOHTTPHandler.h" @@ -318,10 +325,101 @@ void UsersConfigReloader::reloadIfNewer(bool force) } +/** Обеспечивает, что с одной директорией с данными может одновременно работать не более одного сервера. + */ +class StatusFile : private boost::noncopyable +{ +public: + StatusFile(const std::string & path_) + : path(path_) + { + /// Если файл уже существует. NOTE Незначительный race condition. + if (Poco::File(path).exists()) + { + std::string contents; + { + ReadBufferFromFile in(path, 1024); + LimitReadBuffer limit_in(in, 1024); + WriteBufferFromString out(contents); + copyData(limit_in, out); + } + + if (!contents.empty()) + LOG_INFO(&Logger::get("StatusFile"), "Status file " << path << " already exists - unclean restart. Contents:\n" << contents); + else + LOG_INFO(&Logger::get("StatusFile"), "Status file " << path << " already exists and is empty - probably unclean hardware restart."); + } + + fd = open(path.c_str(), O_WRONLY | O_CREAT, 0666); + + if (-1 == fd) + throwFromErrno("Cannot open file " + path); + + try + { + int flock_ret = flock(fd, LOCK_EX | LOCK_NB); + if (-1 == flock_ret) + { + if (errno == EWOULDBLOCK) + throw Exception("Cannot lock file " + path + ". Another server instance in same directory is already running."); + else + throwFromErrno("Cannot lock file " + path); + } + + if (0 != ftruncate(fd, 0)) + throwFromErrno("Cannot ftruncate " + path); + + if (0 != lseek(fd, 0, SEEK_SET)) + throwFromErrno("Cannot lseek " + path); + + /// Записываем в файл информацию о текущем экземпляре сервера. + { + WriteBufferFromFileDescriptor out(fd, 1024); + out + << "PID: " << getpid() << "\n" + << "Started at: " << mysqlxx::DateTime(time(0)) << "\n" + << "Revision: " << Revision::get() << "\n"; + } + } + catch (...) + { + close(fd); + throw; + } + } + + ~StatusFile() + { + char buf[128]; + + if (0 != close(fd)) + LOG_ERROR(&Logger::get("StatusFile"), "Cannot close file " << path << ", errno: " + << errno << ", strerror: " << strerror_r(errno, buf, sizeof(buf))); + + if (0 != unlink(path.c_str())) + LOG_ERROR(&Logger::get("StatusFile"), "Cannot unlink file " << path << ", errno: " + << errno << ", strerror: " << strerror_r(errno, buf, sizeof(buf))); + } + +private: + const std::string path; + int fd = -1; +}; + + int Server::main(const std::vector & args) { Logger * log = &logger(); + std::string path = config().getString("path"); + Poco::trimInPlace(path); + if (path.empty()) + throw Exception("path configuration parameter is empty"); + if (path.back() != '/') + path += '/'; + + StatusFile status{path + "status"}; + /// Попробуем повысить ограничение на число открытых файлов. { rlimit rlim; @@ -357,7 +455,13 @@ int Server::main(const std::vector & args) * настройки, набор функций, типов данных, агрегатных функций, баз данных... */ global_context->setGlobalContext(*global_context); - global_context->setPath(config().getString("path")); + global_context->setPath(path); + + /// Директория для временных файлов при обработке тяжёлых запросов. + std::string tmp_path = config().getString("tmp_path", path + "tmp/"); + global_context->setTemporaryPath(tmp_path); + Poco::File(tmp_path).createDirectories(); + /// TODO Очистка временных файлов. Проверка, что директория с временными файлами не совпадает и не содержит в себе основной path. bool has_zookeeper = false; if (config().has("zookeeper")) diff --git a/dbms/src/Server/TCPHandler.cpp b/dbms/src/Server/TCPHandler.cpp index b73a4e25a97..377c694c695 100644 --- a/dbms/src/Server/TCPHandler.cpp +++ b/dbms/src/Server/TCPHandler.cpp @@ -17,6 +17,8 @@ #include #include +#include +#include #include #include @@ -597,12 +599,10 @@ void TCPHandler::initBlockInput() else state.maybe_compressed_in = in; - state.block_in = query_context.getFormatFactory().getInput( - "Native", + state.block_in = new NativeBlockInputStream( *state.maybe_compressed_in, - state.io.out_sample, - query_context.getSettingsRef().max_insert_block_size, /// Реально не используется в формате Native. - query_context.getDataTypeFactory()); + query_context.getDataTypeFactory(), + client_revision); } } @@ -616,10 +616,9 @@ void TCPHandler::initBlockOutput() else state.maybe_compressed_out = out; - state.block_out = query_context.getFormatFactory().getOutput( - "Native", + state.block_out = new NativeBlockOutputStream( *state.maybe_compressed_out, - state.io.in_sample); + client_revision); } } diff --git a/dbms/src/Storages/AlterCommands.cpp b/dbms/src/Storages/AlterCommands.cpp index 4125b2bc7a3..9d7ae11a1b0 100644 --- a/dbms/src/Storages/AlterCommands.cpp +++ b/dbms/src/Storages/AlterCommands.cpp @@ -179,14 +179,10 @@ namespace DB void AlterCommands::validate(IStorage * table, const Context & context) { - auto lock = table->lockDataForAlter(); - auto columns = table->getColumnsList(); columns.insert(std::end(columns), std::begin(table->alias_columns), std::end(table->alias_columns)); auto defaults = table->column_defaults; - lock.reset(); - std::vector> defaulted_columns{}; ASTPtr default_expr_list{new ASTExpressionList}; diff --git a/dbms/src/Storages/MergeTree/MergeTreeData.cpp b/dbms/src/Storages/MergeTree/MergeTreeData.cpp index b0c42fa9cf7..468a0d2e0b8 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeData.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeData.cpp @@ -237,7 +237,7 @@ void MergeTreeData::loadDataParts(bool skip_sanity_checks) for (const auto & part : broken_parts_to_remove) part->remove(); for (const auto & part : broken_parts_to_detach) - part->renameAddPrefix("detached/"); + part->renameAddPrefix(true, ""); all_data_parts = data_parts; @@ -789,7 +789,7 @@ void MergeTreeData::renameAndDetachPart(const DataPartPtr & part, const String & removePartContributionToColumnSizes(part); data_parts.erase(part); if (move_to_detached || !prefix.empty()) - part->renameAddPrefix((move_to_detached ? "detached/" : "") + prefix); + part->renameAddPrefix(move_to_detached, prefix); if (restore_covered) { diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp index 58d7e45adfb..8bfd831fd1b 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataMerger.cpp @@ -42,23 +42,6 @@ static const double DISK_USAGE_COEFFICIENT_TO_RESERVE = 1.4; bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & parts, String & merged_name, size_t available_disk_space, bool merge_anything_for_old_months, bool aggressive, bool only_small, const AllowedMergingPredicate & can_merge_callback) { - std::stringstream log_message; - - log_message << "Selecting parts to merge. Available disk space: "; - - if (available_disk_space == NO_LIMIT) - log_message << "no limit"; - else - log_message << available_disk_space << " bytes"; - - log_message - << ". Merge anything for old months: " << merge_anything_for_old_months - << ". Aggressive: " << aggressive - << ". Only small: " << only_small - << "."; - - LOG_TRACE(log, log_message.rdbuf()); - MergeTreeData::DataParts data_parts = data.getDataParts(); DateLUT & date_lut = DateLUT::instance(); @@ -92,9 +75,6 @@ bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & pa if (only_small) cur_max_bytes_to_merge_parts = data.settings.max_bytes_to_merge_parts_small; - LOG_TRACE(log, "Max bytes to merge parts: " << cur_max_bytes_to_merge_parts - << (only_small ? " (only small)" : (tonight ? " (tonight)" : "")) << "."); - /// Мемоизация для функции can_merge_callback. Результат вызова can_merge_callback для этого куска и предыдущего в data_parts. std::map can_merge_with_previous; auto can_merge = [&can_merge_with_previous, &can_merge_callback] @@ -293,10 +273,6 @@ bool MergeTreeDataMerger::selectPartsToMerge(MergeTreeData::DataPartsVector & pa LOG_DEBUG(log, "Selected " << parts.size() << " parts from " << parts.front()->name << " to " << parts.back()->name << (only_small ? " (only small)" : "")); } - else - { - LOG_TRACE(log, "No parts selected for merge."); - } return found; } diff --git a/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp b/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp index e2367f44dcb..df0175ef925 100644 --- a/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp +++ b/dbms/src/Storages/MergeTree/MergeTreeDataSelectExecutor.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include diff --git a/dbms/src/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.cpp b/dbms/src/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.cpp index d4429d1b070..e0d6f643af6 100644 --- a/dbms/src/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.cpp +++ b/dbms/src/Storages/MergeTree/ReplicatedMergeTreeRestartingThread.cpp @@ -13,7 +13,7 @@ static String generateActiveNodeIdentifier() struct timespec times; if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, ×)) throwFromErrno("Cannot clock_gettime.", ErrorCodes::CANNOT_CLOCK_GETTIME); - return toString(times.tv_nsec + times.tv_sec + getpid()); + return "pid: " + toString(getpid()) + ", random: " + toString(times.tv_nsec + times.tv_sec + getpid()); } @@ -52,7 +52,7 @@ void ReplicatedMergeTreeRestartingThread::run() partialShutdown(); } - do + while (true) { try { @@ -62,6 +62,7 @@ void ReplicatedMergeTreeRestartingThread::run() { /// Исключение при попытке zookeeper_init обычно бывает, если не работает DNS. Будем пытаться сделать это заново. tryLogCurrentException(__PRETTY_FUNCTION__); + wakeup_event.tryWait(retry_delay_ms); continue; } @@ -71,7 +72,9 @@ void ReplicatedMergeTreeRestartingThread::run() wakeup_event.tryWait(retry_delay_ms); continue; } - } while (false); + + break; + } storage.is_readonly = false; first_time = false; @@ -133,32 +136,31 @@ bool ReplicatedMergeTreeRestartingThread::tryStartup() storage.queue_task_handle = storage.context.getBackgroundPool().addTask( std::bind(&StorageReplicatedMergeTree::queueTask, &storage, std::placeholders::_1)); storage.queue_task_handle->wake(); - return true; - } - catch (const zkutil::KeeperException & e) - { - storage.replica_is_active_node = nullptr; - storage.leader_election = nullptr; - LOG_ERROR(log, "Couldn't start replication: " << e.what() << ", " << e.displayText() << ", stack trace:\n" - << e.getStackTrace().toString()); - return false; - } - catch (const Exception & e) - { - if (e.code() != ErrorCodes::REPLICA_IS_ALREADY_ACTIVE) - throw; - storage.replica_is_active_node = nullptr; - storage.leader_election = nullptr; - LOG_ERROR(log, "Couldn't start replication: " << e.what() << ", " << e.displayText() << ", stack trace:\n" - << e.getStackTrace().toString()); - return false; + return true; } catch (...) { - storage.replica_is_active_node = nullptr; - storage.leader_election = nullptr; - throw; + storage.replica_is_active_node = nullptr; + storage.leader_election = nullptr; + + try + { + throw; + } + catch (const zkutil::KeeperException & e) + { + LOG_ERROR(log, "Couldn't start replication: " << e.what() << ", " << e.displayText() << ", stack trace:\n" << e.getStackTrace().toString()); + return false; + } + catch (const Exception & e) + { + if (e.code() != ErrorCodes::REPLICA_IS_ALREADY_ACTIVE) + throw; + + LOG_ERROR(log, "Couldn't start replication: " << e.what() << ", " << e.displayText() << ", stack trace:\n" << e.getStackTrace().toString()); + return false; + } } } diff --git a/dbms/src/Storages/StorageChunkMerger.cpp b/dbms/src/Storages/StorageChunkMerger.cpp index 7462efa9f20..0c3b66615d7 100644 --- a/dbms/src/Storages/StorageChunkMerger.cpp +++ b/dbms/src/Storages/StorageChunkMerger.cpp @@ -14,7 +14,10 @@ #include #include #include +#include #include +#include +#include namespace DB diff --git a/dbms/src/Storages/StorageChunks.cpp b/dbms/src/Storages/StorageChunks.cpp index c3aad961a39..6617e1788eb 100644 --- a/dbms/src/Storages/StorageChunks.cpp +++ b/dbms/src/Storages/StorageChunks.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include namespace DB diff --git a/dbms/src/Storages/StorageDistributed.cpp b/dbms/src/Storages/StorageDistributed.cpp index 17a6cf02aee..ca777c69da7 100644 --- a/dbms/src/Storages/StorageDistributed.cpp +++ b/dbms/src/Storages/StorageDistributed.cpp @@ -63,8 +63,8 @@ StorageDistributed::StorageDistributed( context(context_), cluster(cluster_), sharding_key_expr(sharding_key_ ? ExpressionAnalyzer(sharding_key_, context, *columns).getActions(false) : nullptr), sharding_key_column_name(sharding_key_ ? sharding_key_->getColumnName() : String{}), - write_enabled(cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_), - path(data_path_ + escapeForFileName(name) + '/') + write_enabled(!data_path_.empty() && (cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_)), + path(data_path_.empty() ? "" : (data_path_ + escapeForFileName(name) + '/')) { createDirectoryMonitors(); } @@ -87,8 +87,8 @@ StorageDistributed::StorageDistributed( context(context_), cluster(cluster_), sharding_key_expr(sharding_key_ ? ExpressionAnalyzer(sharding_key_, context, *columns).getActions(false) : nullptr), sharding_key_column_name(sharding_key_ ? sharding_key_->getColumnName() : String{}), - write_enabled(cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_), - path(data_path_ + escapeForFileName(name) + '/') + write_enabled(!data_path_.empty() && (cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_)), + path(data_path_.empty() ? "" : (data_path_ + escapeForFileName(name) + '/')) { createDirectoryMonitors(); } @@ -164,8 +164,7 @@ BlockInputStreams StorageDistributed::read( for (auto & conn_pool : cluster.pools) res.emplace_back(new RemoteBlockInputStream{ conn_pool, modified_query, &new_settings, - external_tables, processed_stage, context - }); + external_tables, processed_stage, context}); /// Добавляем запросы к локальному ClickHouse. if (cluster.getLocalNodesNum() > 0) @@ -240,6 +239,9 @@ void StorageDistributed::createDirectoryMonitor(const std::string & name) void StorageDistributed::createDirectoryMonitors() { + if (path.empty()) + return; + Poco::File{path}.createDirectory(); Poco::DirectoryIterator end; diff --git a/dbms/src/Storages/StorageMerge.cpp b/dbms/src/Storages/StorageMerge.cpp index fcb99636408..309826e9059 100644 --- a/dbms/src/Storages/StorageMerge.cpp +++ b/dbms/src/Storages/StorageMerge.cpp @@ -1,8 +1,13 @@ #include +#include #include #include #include #include +#include +#include +#include + namespace DB { diff --git a/dbms/src/Storages/StorageMergeTree.cpp b/dbms/src/Storages/StorageMergeTree.cpp index 35be27c513d..57ffe018a8c 100644 --- a/dbms/src/Storages/StorageMergeTree.cpp +++ b/dbms/src/Storages/StorageMergeTree.cpp @@ -199,7 +199,6 @@ bool StorageMergeTree::merge(bool aggressive, BackgroundProcessingPool::Context if (!merger.selectPartsToMerge(parts, merged_name, disk_space, false, aggressive, only_small, can_merge) && !merger.selectPartsToMerge(parts, merged_name, disk_space, true, aggressive, only_small, can_merge)) { - LOG_INFO(log, "No parts to merge"); return false; } diff --git a/dbms/src/Storages/StorageReplicatedMergeTree.cpp b/dbms/src/Storages/StorageReplicatedMergeTree.cpp index 3d778bb5c1d..715aabf6011 100644 --- a/dbms/src/Storages/StorageReplicatedMergeTree.cpp +++ b/dbms/src/Storages/StorageReplicatedMergeTree.cpp @@ -8,8 +8,10 @@ #include #include #include +#include #include + namespace DB { @@ -1353,11 +1355,12 @@ void StorageReplicatedMergeTree::mergeSelectingThread() bool only_small = big_merges_current + big_merges_queued >= max_number_of_big_merges; - LOG_TRACE(log, "Currently executing big merges: " << big_merges_current - << ". Queued big merges: " << big_merges_queued - << ". All merges in queue: " << merges_queued - << ". Max number of big merges: " << max_number_of_big_merges - << (only_small ? ". So, will select only small parts to merge." : ".")); + if (big_merges_current || merges_queued) + LOG_TRACE(log, "Currently executing big merges: " << big_merges_current + << ". Queued big merges: " << big_merges_queued + << ". All merges in queue: " << merges_queued + << ". Max number of big merges: " << max_number_of_big_merges + << (only_small ? ". So, will select only small parts to merge." : ".")); do { @@ -1376,7 +1379,6 @@ void StorageReplicatedMergeTree::mergeSelectingThread() if ( !merger.selectPartsToMerge(parts, merged_name, MergeTreeDataMerger::NO_LIMIT, false, false, only_small, can_merge) && !merger.selectPartsToMerge(parts, merged_name, MergeTreeDataMerger::NO_LIMIT, true, false, only_small, can_merge)) { - LOG_INFO(log, "No parts to merge"); break; } diff --git a/dbms/src/Storages/StorageView.cpp b/dbms/src/Storages/StorageView.cpp index 50b00df5832..763023dd347 100644 --- a/dbms/src/Storages/StorageView.cpp +++ b/dbms/src/Storages/StorageView.cpp @@ -92,8 +92,7 @@ BlockInputStreams StorageView::read( if (outer_select.final && !inner_select.final) inner_select.final = outer_select.final; - return BlockInputStreams(1, - InterpreterSelectQuery(inner_query_clone, context, column_names).execute()); + return InterpreterSelectQuery(inner_query_clone, context, column_names).executeWithoutUnion(); } diff --git a/dbms/tests/clickhouse-test b/dbms/tests/clickhouse-test index 92375f645c4..e583a16c3e0 100755 --- a/dbms/tests/clickhouse-test +++ b/dbms/tests/clickhouse-test @@ -76,7 +76,7 @@ do continue fi - printf "%-60s" "$test_name: " + printf "%-64s" "$test_name: " if [ $ZOOKEEPER -eq 0 ] && (echo "$test_name" | grep -q 'zookeeper'); then echo -e "$MSG_SKIPPED - no zookeeper" diff --git a/dbms/tests/queries/0_stateless/00039_inserts_through_http.sh b/dbms/tests/queries/0_stateless/00039_inserts_through_http.sh index 5264f6e1a18..1aa32a83062 100755 --- a/dbms/tests/queries/0_stateless/00039_inserts_through_http.sh +++ b/dbms/tests/queries/0_stateless/00039_inserts_through_http.sh @@ -3,6 +3,7 @@ echo 'DROP TABLE IF EXISTS test.long_insert' | curl -sSg 'http://localhost:8123' -d @- echo 'CREATE TABLE test.long_insert (a String) ENGINE = Memory' | curl -sSg 'http://localhost:8123' -d @- for string_size in 1 10 100 1000 10000 100000 1000000; do - perl -we 'for my $letter ("a" .. "z") { print(($letter x '$string_size') . "\n") }' | curl -sSg 'http://localhost:8123/?query=INSERT+INTO+test.long_insert+FORMAT+TabSeparated' --data-binary @- + # Если не указать LC_ALL=C, то Perl будет ругаться на некоторых плохо настроенных системах. + LC_ALL=C perl -we 'for my $letter ("a" .. "z") { print(($letter x '$string_size') . "\n") }' | curl -sSg 'http://localhost:8123/?query=INSERT+INTO+test.long_insert+FORMAT+TabSeparated' --data-binary @- echo 'SELECT substring(a, 1, 1) AS c, length(a) AS l FROM test.long_insert ORDER BY c, l' | curl -sSg 'http://localhost:8123' -d @- done diff --git a/dbms/tests/queries/0_stateless/00058_global_in.sh b/dbms/tests/queries/0_stateless/00058_global_in.sh index 46bef821ae8..446ee30e32e 100755 --- a/dbms/tests/queries/0_stateless/00058_global_in.sh +++ b/dbms/tests/queries/0_stateless/00058_global_in.sh @@ -32,6 +32,11 @@ cp ${CONFIG/config/users} . # Запустим второй сервер. BINARY=$(readlink /proc/$(pidof clickhouse-server | tr ' ' '\n' | head -n1)/exe || echo "/usr/bin/clickhouse-server") +if [ ! -x "$BINARY" ] && [ -n "$(pgrep memcheck)" ]; then + # В случае, если сервер был запущен под valgrind-ом. + BINARY=$(readlink /proc/$(pgrep memcheck)/cwd)/$(cat /proc/$(pgrep memcheck)/cmdline | cut -f2 -d '') +fi + if [ ! -x "$BINARY" ]; then echo "Cannot find executable binary for running clickhouse-server" >&2 exit 1 @@ -99,3 +104,5 @@ $CLIENT1 -n --query=" $CLIENT2 -n --query=" DROP TABLE test.half1; DROP TABLE test.half2;" + +rm -rf $PATH2 diff --git a/dbms/tests/queries/0_stateless/00074_replicated_attach_race_condition.reference b/dbms/tests/queries/0_stateless/00074_replicated_attach_race_condition_zookeeper.reference similarity index 100% rename from dbms/tests/queries/0_stateless/00074_replicated_attach_race_condition.reference rename to dbms/tests/queries/0_stateless/00074_replicated_attach_race_condition_zookeeper.reference diff --git a/dbms/tests/queries/0_stateless/00074_replicated_attach_race_condition.sql b/dbms/tests/queries/0_stateless/00074_replicated_attach_race_condition_zookeeper.sql similarity index 100% rename from dbms/tests/queries/0_stateless/00074_replicated_attach_race_condition.sql rename to dbms/tests/queries/0_stateless/00074_replicated_attach_race_condition_zookeeper.sql diff --git a/dbms/tests/queries/0_stateless/00079_defaulted_columns.sql b/dbms/tests/queries/0_stateless/00079_defaulted_columns.sql index b5d86303d11..a2c4f3dfcbb 100644 --- a/dbms/tests/queries/0_stateless/00079_defaulted_columns.sql +++ b/dbms/tests/queries/0_stateless/00079_defaulted_columns.sql @@ -20,7 +20,6 @@ desc table defaulted_test; select *, payload_length from defaulted_test; insert into defaulted_test (payload) values ('some string'); select *, payload_length from defaulted_test order by payload; -optimize table defaulted_test; select *, payload_length from defaulted_test order by payload; alter table defaulted_test modify column payload_length default length(payload); desc table defaulted_test; diff --git a/dbms/tests/queries/0_stateless/00098_1_union_all.sql b/dbms/tests/queries/0_stateless/00098_1_union_all.sql index 619371a6496..7c05af6de98 100644 --- a/dbms/tests/queries/0_stateless/00098_1_union_all.sql +++ b/dbms/tests/queries/0_stateless/00098_1_union_all.sql @@ -1,8 +1,10 @@ DROP TABLE IF EXISTS data2013; DROP TABLE IF EXISTS data2014; +DROP TABLE IF EXISTS data2015; CREATE TABLE data2013 (name String, value UInt32) ENGINE = Memory; CREATE TABLE data2014 (name String, value UInt32) ENGINE = Memory; +CREATE TABLE data2015 (data_name String, data_value UInt32) ENGINE = Memory; INSERT INTO data2013(name,value) VALUES('Alice', 1000); INSERT INTO data2013(name,value) VALUES('Bob', 2000); @@ -12,6 +14,9 @@ INSERT INTO data2014(name,value) VALUES('Alice', 2000); INSERT INTO data2014(name,value) VALUES('Bob', 2000); INSERT INTO data2014(name,value) VALUES('Dennis', 35000); +INSERT INTO data2015(data_name, data_value) VALUES('Foo', 42); +INSERT INTO data2015(data_name, data_value) VALUES('Bar', 1); + SELECT val FROM (SELECT value AS val FROM data2013 WHERE name = 'Alice' UNION ALL diff --git a/dbms/tests/queries/0_stateless/00098_7_union_all.reference b/dbms/tests/queries/0_stateless/00098_7_union_all.reference new file mode 100644 index 00000000000..1191247b6d9 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_7_union_all.reference @@ -0,0 +1,2 @@ +1 +2 diff --git a/dbms/tests/queries/0_stateless/00098_7_union_all.sql b/dbms/tests/queries/0_stateless/00098_7_union_all.sql new file mode 100644 index 00000000000..2798b1a28e6 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_7_union_all.sql @@ -0,0 +1 @@ +SELECT DomainID FROM (SELECT 1 AS DomainID, 'abc' AS Domain UNION ALL SELECT 2 AS DomainID, 'def' AS Domain) ORDER BY DomainID ASC diff --git a/dbms/tests/queries/0_stateless/00098_8_union_all.reference b/dbms/tests/queries/0_stateless/00098_8_union_all.reference new file mode 100644 index 00000000000..1191247b6d9 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_8_union_all.reference @@ -0,0 +1,2 @@ +1 +2 diff --git a/dbms/tests/queries/0_stateless/00098_8_union_all.sql b/dbms/tests/queries/0_stateless/00098_8_union_all.sql new file mode 100644 index 00000000000..86e88516f97 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_8_union_all.sql @@ -0,0 +1 @@ +SELECT DomainID FROM (SELECT DISTINCT 1 AS DomainID, 'abc' AS Domain UNION ALL SELECT 2 AS DomainID, 'def' AS Domain) ORDER BY DomainID ASC diff --git a/dbms/tests/queries/0_stateless/00098_9_union_all.reference b/dbms/tests/queries/0_stateless/00098_9_union_all.reference new file mode 100644 index 00000000000..1191247b6d9 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_9_union_all.reference @@ -0,0 +1,2 @@ +1 +2 diff --git a/dbms/tests/queries/0_stateless/00098_9_union_all.sql b/dbms/tests/queries/0_stateless/00098_9_union_all.sql new file mode 100644 index 00000000000..781556ffa7b --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_9_union_all.sql @@ -0,0 +1 @@ +SELECT * FROM (SELECT 1 UNION ALL SELECT 2) ORDER BY 1 ASC; diff --git a/dbms/tests/queries/0_stateless/00098_a_union_all.reference b/dbms/tests/queries/0_stateless/00098_a_union_all.reference new file mode 100644 index 00000000000..1191247b6d9 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_a_union_all.reference @@ -0,0 +1,2 @@ +1 +2 diff --git a/dbms/tests/queries/0_stateless/00098_a_union_all.sql b/dbms/tests/queries/0_stateless/00098_a_union_all.sql new file mode 100644 index 00000000000..23affc04cb6 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_a_union_all.sql @@ -0,0 +1 @@ +SELECT * FROM (SELECT 1 AS X UNION ALL SELECT 2) ORDER BY X ASC; diff --git a/dbms/tests/queries/0_stateless/00098_b_union_all.reference b/dbms/tests/queries/0_stateless/00098_b_union_all.reference new file mode 100644 index 00000000000..01e79c32a8c --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_b_union_all.reference @@ -0,0 +1,3 @@ +1 +2 +3 diff --git a/dbms/tests/queries/0_stateless/00098_b_union_all.sql b/dbms/tests/queries/0_stateless/00098_b_union_all.sql new file mode 100644 index 00000000000..837f0b50da4 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_b_union_all.sql @@ -0,0 +1 @@ +SELECT * FROM (SELECT 1 AS X UNION ALL SELECT 2 UNION ALL SELECT 3 AS X) ORDER BY X ASC; diff --git a/dbms/tests/queries/0_stateless/00098_c_union_all.reference b/dbms/tests/queries/0_stateless/00098_c_union_all.reference new file mode 100644 index 00000000000..4cef767ea61 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_c_union_all.reference @@ -0,0 +1,11 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +12345678902 diff --git a/dbms/tests/queries/0_stateless/00098_c_union_all.sql b/dbms/tests/queries/0_stateless/00098_c_union_all.sql new file mode 100644 index 00000000000..1211be558a4 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_c_union_all.sql @@ -0,0 +1 @@ +SELECT X + 1 FROM (SELECT 12345678901 AS X UNION ALL SELECT number FROM system.numbers LIMIT 10) ORDER BY X ASC; diff --git a/dbms/tests/queries/0_stateless/00098_d_union_all.reference b/dbms/tests/queries/0_stateless/00098_d_union_all.reference new file mode 100644 index 00000000000..9f0145f0f0b --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_d_union_all.reference @@ -0,0 +1,5 @@ +Alice +Bar +Bob +Carol +Foo diff --git a/dbms/tests/queries/0_stateless/00098_d_union_all.sql b/dbms/tests/queries/0_stateless/00098_d_union_all.sql new file mode 100644 index 00000000000..282d46e81a6 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_d_union_all.sql @@ -0,0 +1 @@ +SELECT name FROM (SELECT name FROM data2013 UNION ALL SELECT data_name FROM data2015) ORDER BY name ASC; diff --git a/dbms/tests/queries/0_stateless/00098_e_union_all.reference b/dbms/tests/queries/0_stateless/00098_e_union_all.reference new file mode 100644 index 00000000000..9f0145f0f0b --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_e_union_all.reference @@ -0,0 +1,5 @@ +Alice +Bar +Bob +Carol +Foo diff --git a/dbms/tests/queries/0_stateless/00098_e_union_all.sql b/dbms/tests/queries/0_stateless/00098_e_union_all.sql new file mode 100644 index 00000000000..68b26eac5d9 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_e_union_all.sql @@ -0,0 +1 @@ +SELECT X FROM (SELECT name AS X FROM data2013 UNION ALL SELECT data_name FROM data2015) ORDER BY X ASC; diff --git a/dbms/tests/queries/0_stateless/00098_f_union_all.reference b/dbms/tests/queries/0_stateless/00098_f_union_all.reference new file mode 100644 index 00000000000..9f0145f0f0b --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_f_union_all.reference @@ -0,0 +1,5 @@ +Alice +Bar +Bob +Carol +Foo diff --git a/dbms/tests/queries/0_stateless/00098_f_union_all.sql b/dbms/tests/queries/0_stateless/00098_f_union_all.sql new file mode 100644 index 00000000000..561d2398869 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_f_union_all.sql @@ -0,0 +1 @@ +SELECT name FROM (SELECT name FROM data2013 UNION ALL SELECT data_name AS name FROM data2015) ORDER BY name ASC; diff --git a/dbms/tests/queries/0_stateless/00098_g_union_all.reference b/dbms/tests/queries/0_stateless/00098_g_union_all.reference new file mode 100644 index 00000000000..2b2f2e1b926 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_g_union_all.reference @@ -0,0 +1,2 @@ +1 +3 diff --git a/dbms/tests/queries/0_stateless/00098_g_union_all.sql b/dbms/tests/queries/0_stateless/00098_g_union_all.sql new file mode 100644 index 00000000000..8f86d81bbb0 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_g_union_all.sql @@ -0,0 +1 @@ +SELECT X FROM (SELECT * FROM (SELECT 1 AS X, 2 AS Y) UNION ALL SELECT 3, 4) ORDER BY X ASC; diff --git a/dbms/tests/queries/0_stateless/00098_h_union_all.reference b/dbms/tests/queries/0_stateless/00098_h_union_all.reference new file mode 100644 index 00000000000..2b2f2e1b926 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_h_union_all.reference @@ -0,0 +1,2 @@ +1 +3 diff --git a/dbms/tests/queries/0_stateless/00098_h_union_all.sql b/dbms/tests/queries/0_stateless/00098_h_union_all.sql new file mode 100644 index 00000000000..e85aaf2b77d --- /dev/null +++ b/dbms/tests/queries/0_stateless/00098_h_union_all.sql @@ -0,0 +1 @@ +SELECT X FROM (SELECT 1 AS X, 2 AS Y UNION ALL SELECT * FROM (SELECT 3, 4)) ORDER BY X ASC; diff --git a/dbms/tests/queries/0_stateless/00099_join_many_blocks_segfault.reference b/dbms/tests/queries/0_stateless/00099_join_many_blocks_segfault.reference new file mode 100644 index 00000000000..5bfd8060b89 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00099_join_many_blocks_segfault.reference @@ -0,0 +1 @@ +1 abc diff --git a/dbms/tests/queries/0_stateless/00099_join_many_blocks_segfault.sql b/dbms/tests/queries/0_stateless/00099_join_many_blocks_segfault.sql new file mode 100644 index 00000000000..63f31da852f --- /dev/null +++ b/dbms/tests/queries/0_stateless/00099_join_many_blocks_segfault.sql @@ -0,0 +1,12 @@ +SELECT + 1 AS DomainID, + Domain ANY LEFT JOIN +( + SELECT + 1 AS DomainID, + 'abc' AS Domain + UNION ALL + SELECT + 2 AS DomainID, + 'def' AS Domain +) USING DomainID; diff --git a/dbms/tests/queries/0_stateless/00100_subquery_table_identifier.reference b/dbms/tests/queries/0_stateless/00100_subquery_table_identifier.reference new file mode 100644 index 00000000000..9864cf5dad0 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00100_subquery_table_identifier.reference @@ -0,0 +1,5 @@ +0 +1 +1 +0 +0 diff --git a/dbms/tests/queries/0_stateless/00100_subquery_table_identifier.sh b/dbms/tests/queries/0_stateless/00100_subquery_table_identifier.sh new file mode 100755 index 00000000000..e14e3afa614 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00100_subquery_table_identifier.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +clickhouse-client --query="SELECT sum(dummy) FROM remote('[::]:9000', system, one) WHERE 1 GLOBAL IN (SELECT 1)" +echo '1' | clickhouse-client --external --file=- --types=UInt8 --query="SELECT 1 IN _data" +echo '1' | clickhouse-client --external --file=- --types=UInt8 --query="SELECT 1 IN (SELECT * FROM _data)" +echo '1' | clickhouse-client --external --file=- --types=UInt8 --query="SELECT dummy FROM remote('[::]:9000', system, one) WHERE 1 GLOBAL IN _data" +echo '1' | clickhouse-client --external --file=- --types=UInt8 --query="SELECT dummy FROM remote('[::]:9000', system, one) WHERE 1 IN _data" diff --git a/dbms/tests/queries/0_stateless/00101_materialized_views_and_insert_without_explicit_database.reference b/dbms/tests/queries/0_stateless/00101_materialized_views_and_insert_without_explicit_database.reference new file mode 100644 index 00000000000..8fb767d89d5 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00101_materialized_views_and_insert_without_explicit_database.reference @@ -0,0 +1,2 @@ +2014-01-02 0 0 0000-00-00 00:00:00 2014-01-02 03:04:06 +1 2014-01-02 03:04:06 diff --git a/dbms/tests/queries/0_stateless/00101_materialized_views_and_insert_without_explicit_database.sql b/dbms/tests/queries/0_stateless/00101_materialized_views_and_insert_without_explicit_database.sql new file mode 100644 index 00000000000..a3f475d2f25 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00101_materialized_views_and_insert_without_explicit_database.sql @@ -0,0 +1,13 @@ +DROP TABLE IF EXISTS default.test_table; +DROP TABLE IF EXISTS default.test_view; + +CREATE TABLE default.test_table (EventDate Date, CounterID UInt32, UserID UInt64, EventTime DateTime, UTCEventTime DateTime) ENGINE = Memory; +CREATE MATERIALIZED VIEW default.test_view (Rows UInt64, MaxHitTime DateTime) ENGINE = Memory AS SELECT count() AS Rows, max(UTCEventTime) AS MaxHitTime FROM default.test_table; + +INSERT INTO test_table (EventDate, UTCEventTime) VALUES ('2014-01-02', '2014-01-02 03:04:06'); + +SELECT * FROM default.test_table; +SELECT * FROM default.test_view; + +DROP TABLE default.test_table; +DROP TABLE default.test_view; diff --git a/dbms/tests/queries/0_stateless/00102_insert_into_temporary_table.reference b/dbms/tests/queries/0_stateless/00102_insert_into_temporary_table.reference new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00102_insert_into_temporary_table.reference @@ -0,0 +1 @@ +1 diff --git a/dbms/tests/queries/0_stateless/00102_insert_into_temporary_table.sql b/dbms/tests/queries/0_stateless/00102_insert_into_temporary_table.sql new file mode 100644 index 00000000000..4ef44cdf95d --- /dev/null +++ b/dbms/tests/queries/0_stateless/00102_insert_into_temporary_table.sql @@ -0,0 +1,3 @@ +CREATE TEMPORARY TABLE t (a UInt8); +INSERT INTO t VALUES (1); +SELECT * FROM t; diff --git a/dbms/tests/queries/0_stateless/00103_ipv4_num_to_string_class_c.reference b/dbms/tests/queries/0_stateless/00103_ipv4_num_to_string_class_c.reference new file mode 100644 index 00000000000..98fb6a68656 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00103_ipv4_num_to_string_class_c.reference @@ -0,0 +1,4 @@ +1 +1 +1 +1 diff --git a/dbms/tests/queries/0_stateless/00103_ipv4_num_to_string_class_c.sql b/dbms/tests/queries/0_stateless/00103_ipv4_num_to_string_class_c.sql new file mode 100644 index 00000000000..ed6db2cd300 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00103_ipv4_num_to_string_class_c.sql @@ -0,0 +1,4 @@ +select IPv4NumToStringClassC(toUInt32(0)) = '0.0.0.xxx'; +select IPv4NumToStringClassC(0x7f000001) = '127.0.0.xxx'; +select sum(IPv4NumToStringClassC(materialize(toUInt32(0))) = '0.0.0.xxx') = count() array join range(1024) as n; +select sum(IPv4NumToStringClassC(materialize(0x7f000001)) = '127.0.0.xxx') = count() array join range(1024) as n; diff --git a/dbms/tests/queries/0_stateless/00104_totals_having_mode.reference b/dbms/tests/queries/0_stateless/00104_totals_having_mode.reference new file mode 100644 index 00000000000..4777d66c114 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00104_totals_having_mode.reference @@ -0,0 +1,12 @@ +0 1 + +0 100000 +0 1 + +0 56310 +0 1 + +0 21846 +0 1 + +0 21846 diff --git a/dbms/tests/queries/0_stateless/00104_totals_having_mode.sql b/dbms/tests/queries/0_stateless/00104_totals_having_mode.sql new file mode 100644 index 00000000000..96e83b263c3 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00104_totals_having_mode.sql @@ -0,0 +1,17 @@ +SET max_threads = 1; +SET max_block_size = 65536; +SET max_rows_to_group_by = 65535; +SET group_by_overflow_mode = 'any'; + +SET totals_mode = 'before_having'; +SELECT number, count() FROM (SELECT * FROM system.numbers LIMIT 100000) GROUP BY number WITH TOTALS HAVING number % 3 = 0 ORDER BY number LIMIT 1; + +SET totals_mode = 'after_having_inclusive'; +SELECT number, count() FROM (SELECT * FROM system.numbers LIMIT 100000) GROUP BY number WITH TOTALS HAVING number % 3 = 0 ORDER BY number LIMIT 1; + +SET totals_mode = 'after_having_exclusive'; +SELECT number, count() FROM (SELECT * FROM system.numbers LIMIT 100000) GROUP BY number WITH TOTALS HAVING number % 3 = 0 ORDER BY number LIMIT 1; + +SET totals_mode = 'after_having_auto'; +SET totals_auto_threshold = 0.5; +SELECT number, count() FROM (SELECT * FROM system.numbers LIMIT 100000) GROUP BY number WITH TOTALS HAVING number % 3 = 0 ORDER BY number LIMIT 1; diff --git a/dbms/tests/queries/0_stateless/00105_collations.reference b/dbms/tests/queries/0_stateless/00105_collations.reference new file mode 100644 index 00000000000..3ff09ff2f2d --- /dev/null +++ b/dbms/tests/queries/0_stateless/00105_collations.reference @@ -0,0 +1,250 @@ +Ё +А +Я +а +я +ё +а +А +ё +Ё +я +Я +а +а +А +А +ё +ё +Ё +Ё +я +я +Я +Я +A +A +B +B +C +C +D +D +E +E +F +F +G +G +H +H +I +I +J +J +K +K +L +L +M +M +N +N +O +O +P +P +Q +R +R +S +S +T +T +U +U +V +V +W +X +Y +Y +Z +Z +a +a +b +b +c +c +d +d +e +e +f +f +g +g +h +h +i +i +j +j +k +k +l +l +m +m +n +n +o +o +p +p +q +r +r +s +s +t +t +u +u +v +v +w +x +y +y +z +z +Ç +Ö +Ü +ç +ö +ü +Ğ +ğ +İ +ı +Ş +ş +a +a +A +A +b +b +B +B +c +c +C +C +ç +Ç +d +d +D +D +e +e +E +E +f +f +F +F +g +g +G +G +ğ +Ğ +h +h +H +H +ı +I +I +i +i +İ +j +j +J +J +k +k +K +K +l +l +L +L +m +m +M +M +n +n +N +N +o +o +O +O +ö +Ö +p +p +P +P +q +Q +r +r +R +R +s +s +S +S +ş +Ş +t +t +T +T +u +u +U +U +ü +Ü +v +v +V +V +w +W +x +X +y +y +Y +Y +z +z +Z +Z +а 1 +А 4 +ё 3 +Ё 6 +я 2 +Я 5 diff --git a/dbms/tests/queries/0_stateless/00105_collations.sql b/dbms/tests/queries/0_stateless/00105_collations.sql new file mode 100644 index 00000000000..beb55d90d89 --- /dev/null +++ b/dbms/tests/queries/0_stateless/00105_collations.sql @@ -0,0 +1,6 @@ +SELECT arrayJoin(['а', 'я', 'ё', 'А', 'Я', 'Ё']) AS x ORDER BY x; +SELECT arrayJoin(['а', 'я', 'ё', 'А', 'Я', 'Ё']) AS x ORDER BY x COLLATE 'ru'; +SELECT arrayJoin(['а', 'я', 'ё', 'А', 'Я', 'Ё']) AS x FROM remote('127.0.0.{1,2}', system, one) ORDER BY x COLLATE 'ru'; +SELECT arrayJoin(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'ç', 'd', 'e', 'f', 'g', 'ğ', 'h', 'ı', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'ö', 'p', 'r', 's', 'ş', 't', 'u', 'ü', 'v', 'y', 'z', 'A', 'B', 'C', 'Ç', 'D', 'E', 'F', 'G', 'Ğ', 'H', 'I', 'İ', 'J', 'K', 'L', 'M', 'N', 'O', 'Ö', 'P', 'R', 'S', 'Ş', 'T', 'U', 'Ü', 'V', 'Y', 'Z']) AS x ORDER BY x; +SELECT arrayJoin(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'ç', 'd', 'e', 'f', 'g', 'ğ', 'h', 'ı', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'ö', 'p', 'r', 's', 'ş', 't', 'u', 'ü', 'v', 'y', 'z', 'A', 'B', 'C', 'Ç', 'D', 'E', 'F', 'G', 'Ğ', 'H', 'I', 'İ', 'J', 'K', 'L', 'M', 'N', 'O', 'Ö', 'P', 'R', 'S', 'Ş', 'T', 'U', 'Ü', 'V', 'Y', 'Z']) AS x ORDER BY x COLLATE 'tr'; +SELECT x, n FROM (SELECT ['а', 'я', 'ё', 'А', 'Я', 'Ё'] AS arr) ARRAY JOIN arr AS x, arrayEnumerate(arr) AS n ORDER BY x COLLATE 'ru', n; diff --git a/libs/libcommon/src/create_revision.sh.cmake b/libs/libcommon/src/create_revision.sh.cmake index 352849862f1..a6acfad3f1d 100644 --- a/libs/libcommon/src/create_revision.sh.cmake +++ b/libs/libcommon/src/create_revision.sh.cmake @@ -1,26 +1,20 @@ #!/bin/bash -mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/src -echo "#ifndef REVISION" > ${CMAKE_CURRENT_BINARY_DIR}/src/revision.h -echo -n "#define REVISION " >> ${CMAKE_CURRENT_BINARY_DIR}/src/revision.h +mkdir -p "${CMAKE_CURRENT_BINARY_DIR}/src" +echo "#ifndef REVISION" > "${CMAKE_CURRENT_BINARY_DIR}/src/revision.h" +echo -n "#define REVISION " >> "${CMAKE_CURRENT_BINARY_DIR}/src/revision.h" -cd ${CMAKE_CURRENT_SOURCE_DIR}; +cd "${CMAKE_CURRENT_SOURCE_DIR}"; -if (git rev-parse --is-inside-work-tree &> /dev/null) -then - # GIT - git fetch --tags; +# GIT +git fetch --tags; - # берем последний тэг из текущего коммита - revision=$(git tag --points-at HEAD 2> /dev/null | tail -1) +# берем последний тэг из текущего коммита +revision=$(git tag --points-at HEAD 2> /dev/null | tail -1) - # или ближайший тэг если в данном комите нет тэгов - if [[ "$revision" = "" ]]; then - revision=$( ( git describe --tags || echo 1 ) | cut -d "-" -f 1 ) - fi - echo $revision >> ${CMAKE_CURRENT_BINARY_DIR}/src/revision.h; -else - #SVN - echo && (LC_ALL=C svn info ${PROJECT_SOURCE_DIR}/ 2>/dev/null || echo Revision 1) | grep Revision | cut -d " " -f 2 >> ${CMAKE_CURRENT_BINARY_DIR}/src/revision.h; +# или ближайший тэг если в данном комите нет тэгов +if [[ "$revision" = "" ]]; then + revision=$( ( git describe --tags || echo 1 ) | cut -d "-" -f 1 ) fi +echo $revision >> "${CMAKE_CURRENT_BINARY_DIR}/src/revision.h"; -echo "#endif" >> ${CMAKE_CURRENT_BINARY_DIR}/src/revision.h +echo "#endif" >> "${CMAKE_CURRENT_BINARY_DIR}/src/revision.h" diff --git a/libs/libzkutil/include/zkutil/Lock.h b/libs/libzkutil/include/zkutil/Lock.h index a6a9357b23a..d008972c3cd 100644 --- a/libs/libzkutil/include/zkutil/Lock.h +++ b/libs/libzkutil/include/zkutil/Lock.h @@ -11,12 +11,19 @@ namespace zkutil public: /// lock_prefix - относительный путь до блокировки в ZK. Начинается со слеша /// lock_name - имя ноды блокировки в ZK - Lock(zkutil::ZooKeeperPtr zk, const std::string & lock_prefix_, const std::string & lock_name_, const std::string & lock_message_ = "") : + Lock(zkutil::ZooKeeperPtr zk, const std::string & lock_prefix_, const std::string & lock_name_, const std::string & lock_message_ = "", + bool create_parent_path = false) : zookeeper(zk), lock_path(lock_prefix_ + "/" + lock_name_), lock_message(lock_message_), log(&Logger::get("zkutil::Lock")) { + if (create_parent_path) + zookeeper->createAncestors(lock_prefix_); + zookeeper->createIfNotExists(lock_prefix_, ""); } + Lock(const Lock &) = delete; + Lock & operator=(const Lock &) = delete; + ~Lock() { try diff --git a/libs/libzkutil/include/zkutil/ZooKeeper.h b/libs/libzkutil/include/zkutil/ZooKeeper.h index 7344b813719..fd9d9879004 100644 --- a/libs/libzkutil/include/zkutil/ZooKeeper.h +++ b/libs/libzkutil/include/zkutil/ZooKeeper.h @@ -12,6 +12,7 @@ namespace zkutil { const UInt32 DEFAULT_SESSION_TIMEOUT = 30000; +const UInt32 MEDIUM_SESSION_TIMEOUT = 120000; const UInt32 BIG_SESSION_TIMEOUT = 600000; const UInt32 DEFAULT_RETRY_NUM = 3; diff --git a/tools/init.d/template b/tools/init.d/template index 47696e1c5d3..b2ecbe66dd8 100755 --- a/tools/init.d/template +++ b/tools/init.d/template @@ -108,13 +108,10 @@ start() echo -n "Start $PROGRAM service: " - [ -f "$LOCKFILE" ] && sleep 1 if [[ $(running_processes) -eq $NUMBER_OF_PROCESSES ]]; then echo -n "already running " EXIT_STATUS=1 else - # Clean stale lock files - rm -f "$LOCKFILE" mkdir -p $LOGDIR mkdir -p $PIDDIR chown -R $USER:$GROUP $LOGDIR @@ -124,8 +121,8 @@ start() for i in $(seq 1 $NUMBER_OF_PROCESSES); do if ! is_running $(generate_pid_name $i); then rm -f $(generate_pid_name $i) - # TODO может лучше передавать демону номер процесса, а демон сам будет делать все необходимые подстановки - su -l $USER -s $SHELL -c "exec -a $(generate_program_name $i) \"$BINDIR/$PROGRAM\" --daemon --pid-file=\"$(generate_pid_name $i)\" --config-file=\"$CNFFILE\" $(specific_log_file_for_each_process $i)" + # чтобы лок не удерживался в течении времени жизни дочернего процесса, освободим лок + su -l $USER -s $SHELL -c "flock -u 9; exec -a $(generate_program_name $i) \"$BINDIR/$PROGRAM\" --daemon --pid-file=\"$(generate_pid_name $i)\" --config-file=\"$CNFFILE\" $(specific_log_file_for_each_process $i)" EXIT_STATUS=$? if [[ $EXIT_STATUS -ne 0 ]]; then break @@ -135,7 +132,6 @@ start() fi if [[ $EXIT_STATUS -eq 0 ]]; then - touch "$LOCKFILE" echo "DONE" else echo "FAILED" @@ -151,21 +147,11 @@ stop() echo -n "Stop $PROGRAM service: " - if any_runs; then - if [ -f "$LOCKFILE" ]; then - for pid_file in $(find_pid_files); do - kill -TERM `cat "$pid_file"` - done + for pid_file in $(find_pid_files); do + kill -TERM `cat "$pid_file"` + done - wait4done - rm -f "$LOCKFILE" - else - echo "has been stopping already" - return 1 - fi - else - rm -f "$LOCKFILE" - fi + wait4done echo "DONE" return $EXIT_STATUS @@ -184,19 +170,11 @@ forcestop() echo -n "Stop $PROGRAM service: " - if any_runs; then - if [ -f "$LOCKFILE" ]; then - for pid_file in $(find_pid_files); do - kill -9 `cat "$pid_file"` - done + for pid_file in $(find_pid_files); do + kill -9 `cat "$pid_file"` + done - wait4done - rm -f "$LOCKFILE" - else - echo "has been stopping already" - return 1 - fi - fi + wait4done echo "DONE" return $EXIT_STATUS @@ -218,9 +196,16 @@ disable_cron() sed -i 's/^#*/#/' "$CRONFILE" } -# See how we were called. -EXIT_STATUS=0 -case "$1" in +is_cron_disabled() +{ + [[ `grep -E "^#.*" $CRONFILE` == `cat $CRONFILE` ]]; +} + +main() +{ + # See how we were called. + EXIT_STATUS=0 + case "$1" in start) start && enable_cron ;; @@ -231,7 +216,7 @@ case "$1" in if [[ $(running_processes) -eq $NUMBER_OF_PROCESSES ]]; then echo "$PROGRAM service is running" else - if [ ! -e $LOCKFILE ]; then + if is_cron_disabled; then echo "$PROGRAM service is stopped"; else echo "$PROGRAM: $(($NUMBER_OF_PROCESSES - $(running_processes))) of $NUMBER_OF_PROCESSES processes unexpectedly terminated" @@ -265,6 +250,15 @@ case "$1" in *) echo "Usage: ${0##*/} {start|stop|status|restart|forcestop|forcerestart|reload|condstart|condstop|condrestart|condreload}" EXIT_STATUS=2 -esac + esac -exit $EXIT_STATUS + exit $EXIT_STATUS +} + +( + if flock -n 9; then + main "$@" + else + echo "Init script is already running" && exit 1 + fi +) 9> $LOCKFILE