Merge remote-tracking branch 'upstream/master' into fix23

This commit is contained in:
proller 2019-03-06 22:58:47 +03:00
commit 86d9876221
9 changed files with 155 additions and 105 deletions

View File

@ -498,6 +498,15 @@ bool DataTypeArray::equals(const IDataType & rhs) const
}
size_t DataTypeArray::getNumberOfDimensions() const
{
const DataTypeArray * nested_array = typeid_cast<const DataTypeArray *>(nested.get());
if (!nested_array)
return 1;
return 1 + nested_array->getNumberOfDimensions(); /// Every modern C++ compiler optimizes tail recursion.
}
static DataTypePtr create(const ASTPtr & arguments)
{
if (!arguments || arguments->children.size() != 1)

View File

@ -112,6 +112,9 @@ public:
}
const DataTypePtr & getNestedType() const { return nested; }
/// 1 for plain array, 2 for array of arrays and so on.
size_t getNumberOfDimensions() const;
};
}

View File

@ -4,6 +4,7 @@
namespace DB
{
class FunctionArrayEnumerateDenseRanked : public FunctionArrayEnumerateRankedExtended<FunctionArrayEnumerateDenseRanked>
{
using Base = FunctionArrayEnumerateRankedExtended<FunctionArrayEnumerateDenseRanked>;

View File

@ -1,61 +1,53 @@
#include <algorithm>
#include <Columns/ColumnConst.h>
#include "arrayEnumerateRanked.h"
namespace DB
{
ArraysDepths getArraysDepths(const ColumnsWithTypeAndName & arguments)
{
const size_t num_arguments = arguments.size();
DepthType clear_depth = 1;
DepthType max_array_depth = 0;
DepthTypes depths;
/// function signature is the following:
/// f(c0, arr1, c1, arr2, c2, ...)
///
/// c0 is something called "clear_depth" here.
/// cN... - how deep to look into the corresponding arrN, (called "depths" here)
/// may be omitted - then it means "look at the full depth".
size_t array_num = 0;
DepthType last_array_depth = 0;
DepthType prev_array_depth = 0;
for (size_t i = 0; i < num_arguments; ++i)
{
const auto type = arguments[i].type;
const DataTypePtr & type = arguments[i].type;
const DataTypeArray * type_array = typeid_cast<const DataTypeArray *>(type.get());
if (isArray(type))
if (type_array)
{
if (depths.size() < array_num && last_array_depth)
if (depths.size() < array_num && prev_array_depth)
{
depths.emplace_back(last_array_depth);
last_array_depth = 0;
depths.emplace_back(prev_array_depth);
prev_array_depth = 0;
}
DepthType depth = 0;
auto sub_type = type;
do
{
auto sub_type_array = typeid_cast<const DataTypeArray *>(sub_type.get());
if (!sub_type_array)
break;
sub_type = sub_type_array->getNestedType();
++depth;
} while (isArray(sub_type));
last_array_depth = depth;
prev_array_depth = type_array->getNumberOfDimensions();
++array_num;
}
if (!arguments[i].column)
continue;
const IColumn * non_const = nullptr;
if (auto const_array_column = typeid_cast<const ColumnConst *>(arguments[i].column.get()))
non_const = const_array_column->getDataColumnPtr().get();
const auto array = typeid_cast<const ColumnArray *>(non_const ? non_const : arguments[i].column.get());
if (!array)
else
{
const auto & depth_column = arguments[i].column;
if (depth_column && depth_column->isColumnConst())
{
auto value = depth_column->getInt(0);
if (value <= 0)
throw Exception(
"Arguments for function arrayEnumerateUniqRanked/arrayEnumerateDenseRanked incorrect: depth ("
+ std::to_string(value) + ") cant be less or equal 0.",
UInt64 value = static_cast<const ColumnConst &>(*depth_column).getValue<UInt64>();
if (!value)
throw Exception("Incorrect arguments for function arrayEnumerateUniqRanked or arrayEnumerateDenseRanked: depth ("
+ std::to_string(value) + ") cannot be less or equal 0.",
ErrorCodes::BAD_ARGUMENTS);
if (i == 0)
@ -65,17 +57,15 @@ ArraysDepths getArraysDepths(const ColumnsWithTypeAndName & arguments)
else
{
if (depths.size() >= array_num)
{
throw Exception(
"Arguments for function arrayEnumerateUniqRanked/arrayEnumerateDenseRanked incorrect: depth ("
+ std::to_string(value) + ") for missing array.",
throw Exception("Incorrect arguments for function arrayEnumerateUniqRanked or arrayEnumerateDenseRanked: depth ("
+ std::to_string(value) + ") for missing array.",
ErrorCodes::BAD_ARGUMENTS);
}
if (value > last_array_depth)
if (value > prev_array_depth)
throw Exception(
"Arguments for function arrayEnumerateUniqRanked/arrayEnumerateDenseRanked incorrect: depth="
+ std::to_string(value) + " for array with depth=" + std::to_string(last_array_depth) + ".",
+ std::to_string(value) + " for array with depth=" + std::to_string(prev_array_depth) + ".",
ErrorCodes::BAD_ARGUMENTS);
depths.emplace_back(value);
}
}
@ -83,25 +73,19 @@ ArraysDepths getArraysDepths(const ColumnsWithTypeAndName & arguments)
}
if (depths.size() < array_num)
{
depths.emplace_back(last_array_depth);
}
for (auto & depth : depths)
{
if (max_array_depth < depth)
max_array_depth = depth;
}
depths.emplace_back(prev_array_depth);
if (depths.empty())
throw Exception(
"Arguments for function arrayEnumerateUniqRanked/arrayEnumerateDenseRanked incorrect: At least one array should be passed.",
throw Exception("Incorrect arguments for function arrayEnumerateUniqRanked or arrayEnumerateDenseRanked: at least one array should be passed.",
ErrorCodes::BAD_ARGUMENTS);
DepthType max_array_depth = 0;
for (auto depth : depths)
max_array_depth = std::max(depth, max_array_depth);
if (clear_depth > max_array_depth)
throw Exception(
"Arguments for function arrayEnumerateUniqRanked/arrayEnumerateDenseRanked incorrect: clear_depth ("
+ std::to_string(clear_depth) + ") cant be larger than max_array_depth (" + std::to_string(max_array_depth) + ").",
throw Exception("Incorrect arguments for function arrayEnumerateUniqRanked or arrayEnumerateDenseRanked: clear_depth ("
+ std::to_string(clear_depth) + ") cant be larger than max_array_depth (" + std::to_string(max_array_depth) + ").",
ErrorCodes::BAD_ARGUMENTS);
return {clear_depth, depths, max_array_depth};

View File

@ -12,6 +12,47 @@
#include <Common/HashTable/ClearableHashMap.h>
/** The function will enumerate distinct values of the passed multidimensional arrays looking inside at the specified depths.
* This is very unusual function made as a special order for Yandex.Metrica.
*
* arrayEnumerateUniqRanked(['hello', 'world', 'hello']) = [1, 1, 2]
* - it returns similar structured array containing number of occurence of the corresponding value.
*
* arrayEnumerateUniqRanked([['hello', 'world'], ['hello'], ['hello']], 1) = [1, 1, 2]
* - look at the depth 1 by default. Elements are ['hello', 'world'], ['hello'], ['hello'].
*
* arrayEnumerateUniqRanked([['hello', 'world'], ['hello'], ['hello']]) = [[1,1],[2],[3]]
* - look at the depth 2. Return similar structured array.
* arrayEnumerateUniqRanked([['hello', 'world'], ['hello'], ['hello']], 2) = [[1,1],[2],[3]]
* - look at the maximum depth by default.
*
* We may pass multiple array arguments. Their elements will be processed as zipped to tuple.
*
* arrayEnumerateUniqRanked(['hello', 'hello', 'world', 'world'], ['a', 'b', 'b', 'b']) = [1, 1, 1, 2]
*
* We may provide arrays of different depths to look at different arguments.
*
* arrayEnumerateUniqRanked([['hello', 'world'], ['hello'], ['world'], ['world']], ['a', 'b', 'b', 'b']) = [[1,1],[1],[1],[2]]
* arrayEnumerateUniqRanked([['hello', 'world'], ['hello'], ['world'], ['world']], 1, ['a', 'b', 'b', 'b'], 1) = [1, 1, 1, 2]
*
* When depths are different, we process less deep arrays as promoted to deeper arrays of similar structure by duplicating elements.
*
* arrayEnumerateUniqRanked(
* [['hello', 'world'], ['hello'], ['world'], ['world']],
* ['a', 'b', 'b', 'b'])
* = arrayEnumerateUniqRanked(
* [['hello', 'world'], ['hello'], ['world'], ['world']],
* [['a', 'a'], ['b'], ['b'], ['b']])
*
* Finally, we can provide extra first argument named "clear_depth" (it can be considered as 1 by default).
* Array elements at the clear_depth will be enumerated as separate elements (enumeration counter is reset for each new element).
*
* SELECT arrayEnumerateUniqRanked(1, [['hello', 'world'], ['hello'], ['world'], ['world']]) = [[1,1],[2],[2],[3]]
* SELECT arrayEnumerateUniqRanked(2, [['hello', 'world'], ['hello'], ['world'], ['world']]) = [[1,1],[1],[1],[1]]
* SELECT arrayEnumerateUniqRanked(1, [['hello', 'world', 'hello'], ['hello'], ['world'], ['world']]) = [[1,1,2],[3],[2],[3]]
* SELECT arrayEnumerateUniqRanked(2, [['hello', 'world', 'hello'], ['hello'], ['world'], ['world']]) = [[1,1,2],[1],[1],[1]]
*/
namespace DB
{
namespace ErrorCodes
@ -27,12 +68,21 @@ class FunctionArrayEnumerateDenseRanked;
using DepthType = uint32_t;
using DepthTypes = std::vector<DepthType>;
struct ArraysDepths
{
/// Enumerate elements at the specified level separately.
DepthType clear_depth;
/// Effective depth is the array depth by default or lower value, specified as a constant argument following the array.
/// f([[1, 2], [3]]) - effective depth is 2.
/// f([[1, 2], [3]], 1) - effective depth is 1.
DepthTypes depths;
/// Maximum effective depth.
DepthType max_array_depth;
};
/// Return depth info about passed arrays
ArraysDepths getArraysDepths(const ColumnsWithTypeAndName & arguments);
@ -55,7 +105,9 @@ public:
+ ", should be at least 1.",
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
const auto & arrays_depths = getArraysDepths(arguments);
const ArraysDepths arrays_depths = getArraysDepths(arguments);
/// Return type is the array of the depth as the maximum effective depth of arguments, containing UInt32.
DataTypePtr type = std::make_shared<DataTypeUInt32>();
for (DepthType i = 0; i < arrays_depths.max_array_depth; ++i)
@ -79,15 +131,15 @@ private:
/// Hash a set of keys into a UInt128 value.
static inline UInt128 ALWAYS_INLINE hash128depths(const std::vector<size_t> & indexes, const ColumnRawPtrs & key_columns)
static inline UInt128 ALWAYS_INLINE hash128depths(const std::vector<size_t> & indices, const ColumnRawPtrs & key_columns)
{
UInt128 key;
SipHash hash;
for (size_t j = 0, keys_size = key_columns.size(); j < keys_size; ++j)
{
// Debug: const auto & field = (*key_columns[j])[indexes[j]]; DUMP(j, indexes[j], field);
key_columns[j]->updateHashWithValue(indexes[j], hash);
// Debug: const auto & field = (*key_columns[j])[indices[j]]; DUMP(j, indices[j], field);
key_columns[j]->updateHashWithValue(indices[j], hash);
}
hash.get128(key.low, key.high);
@ -111,9 +163,11 @@ void FunctionArrayEnumerateRankedExtended<Derived>::executeImpl(
for (size_t i = 0; i < arguments.size(); ++i)
args.emplace_back(block.getByPosition(arguments[i]));
const auto & arrays_depths = getArraysDepths(args);
const ArraysDepths arrays_depths = getArraysDepths(args);
auto get_array_column = [&](const auto & column) -> const DB::ColumnArray * {
/// If the column is Array - return it. If the const Array - materialize it, keep ownership and return.
auto get_array_column = [&](const auto & column) -> const DB::ColumnArray *
{
const ColumnArray * array = checkAndGetColumn<ColumnArray>(column);
if (!array)
{
@ -146,7 +200,7 @@ void FunctionArrayEnumerateRankedExtended<Derived>::executeImpl(
if (*offsets_by_depth[0] != array->getOffsets())
{
throw Exception(
"Lengths and depths of all arrays passed to " + getName() + " must be equal.",
"Lengths and effective depths of all arrays passed to " + getName() + " must be equal.",
ErrorCodes::SIZES_OF_ARRAYS_DOESNT_MATCH);
}
}
@ -170,7 +224,7 @@ void FunctionArrayEnumerateRankedExtended<Derived>::executeImpl(
if (*offsets_by_depth[col_depth] != array->getOffsets())
{
throw Exception(
"Lengths and depths of all arrays passed to " + getName() + " must be equal.",
"Lengths and effective depths of all arrays passed to " + getName() + " must be equal.",
ErrorCodes::SIZES_OF_ARRAYS_DOESNT_MATCH);
}
}
@ -180,7 +234,7 @@ void FunctionArrayEnumerateRankedExtended<Derived>::executeImpl(
{
throw Exception(
getName() + ": Passed array number " + std::to_string(array_num) + " depth ("
+ std::to_string(arrays_depths.depths[array_num]) + ") more than actual array depth (" + std::to_string(col_depth)
+ std::to_string(arrays_depths.depths[array_num]) + ") is more than the actual array depth (" + std::to_string(col_depth)
+ ").",
ErrorCodes::SIZES_OF_ARRAYS_DOESNT_MATCH);
}
@ -251,6 +305,7 @@ void FunctionArrayEnumerateRankedExtended<Derived>::executeMethodImpl(
const ArraysDepths & arrays_depths,
ColumnUInt32::Container & res_values)
{
/// Offsets at the depth we want to look.
const size_t current_offset_depth = arrays_depths.max_array_depth;
const auto & offsets = *offsets_by_depth[current_offset_depth - 1];
@ -264,22 +319,24 @@ void FunctionArrayEnumerateRankedExtended<Derived>::executeMethodImpl(
HashTableAllocatorWithStackMemory<(1ULL << INITIAL_SIZE_DEGREE) * sizeof(UInt128)>>;
Map indices;
std::vector<size_t> indexes_by_depth(arrays_depths.max_array_depth);
std::vector<size_t> indices_by_depth(arrays_depths.max_array_depth);
std::vector<size_t> current_offset_n_by_depth(arrays_depths.max_array_depth);
UInt32 rank = 0;
std::vector<size_t> columns_indexes(columns.size());
std::vector<size_t> columns_indices(columns.size());
for (size_t off : offsets)
{
bool want_clear = false;
/// For each element at the depth we want to look.
for (size_t j = prev_off; j < off; ++j)
{
for (size_t col_n = 0; col_n < columns.size(); ++col_n)
columns_indexes[col_n] = indexes_by_depth[arrays_depths.depths[col_n] - 1];
columns_indices[col_n] = indices_by_depth[arrays_depths.depths[col_n] - 1];
auto hash = hash128depths(columns_indexes, columns);
auto hash = hash128depths(columns_indices, columns);
if constexpr (std::is_same_v<Derived, FunctionArrayEnumerateUniqRanked>)
{
@ -297,13 +354,13 @@ void FunctionArrayEnumerateRankedExtended<Derived>::executeMethodImpl(
res_values[j] = idx;
}
// Debug: DUMP(off, prev_off, j, columns_indexes, res_values[j], columns);
// Debug: DUMP(off, prev_off, j, columns_indices, res_values[j], columns);
for (int depth = current_offset_depth - 1; depth >= 0; --depth)
{
++indexes_by_depth[depth];
++indices_by_depth[depth];
if (indexes_by_depth[depth] == (*offsets_by_depth[depth])[current_offset_n_by_depth[depth]])
if (indices_by_depth[depth] == (*offsets_by_depth[depth])[current_offset_n_by_depth[depth]])
{
if (static_cast<int>(arrays_depths.clear_depth) == depth + 1)
want_clear = true;
@ -315,6 +372,7 @@ void FunctionArrayEnumerateRankedExtended<Derived>::executeMethodImpl(
}
}
}
if (want_clear)
{
want_clear = false;

View File

@ -178,5 +178,3 @@ arrayEnumerateUniq(a1, a2) =
[1,2]
[1,1]
[[[[[[[[[[1]]]]]]]]]]
[1,2,1,3]
[1,2,1,3]

View File

@ -170,13 +170,13 @@ SELECT arrayEnumerateUniqRanked([1,2], 1, 2); -- { serverError 36 }
SELECT arrayEnumerateUniqRanked([1,2], 1, 3, 4, 5); -- { serverError 36 }
SELECT arrayEnumerateUniqRanked([1,2], 1, 3, [4], 5); -- { serverError 36 }
SELECT arrayEnumerateDenseRanked([[[[[[[[[[42]]]]]]]]]]);
SELECT arrayEnumerateUniqRanked('wat', [1,2]); -- { serverError 48 }
SELECT arrayEnumerateUniqRanked(1, [1,2], 'boom'); -- { serverError 48 }
SELECT arrayEnumerateDenseRanked(['\0'], -8363126); -- { serverError 36 }
SELECT arrayEnumerateDenseRanked(-10, ['\0'], -8363126); -- { serverError 36 }
SELECT arrayEnumerateDenseRanked(1, ['\0'], -8363126); -- { serverError 36 }
SELECT arrayEnumerateDenseRanked(-101, ['\0']); -- { serverError 36 }
SELECT arrayEnumerateDenseRanked(1.1, [10,20,10,30]);
SELECT arrayEnumerateDenseRanked([10,20,10,30], 0.4); -- { serverError 36 }
SELECT arrayEnumerateDenseRanked([10,20,10,30], 1.8);
SELECT arrayEnumerateUniqRanked('wat', [1,2]); -- { serverError 170 }
SELECT arrayEnumerateUniqRanked(1, [1,2], 'boom'); -- { serverError 170 }
SELECT arrayEnumerateDenseRanked(['\0'], -8363126); -- { serverError 170 }
SELECT arrayEnumerateDenseRanked(-10, ['\0'], -8363126); -- { serverError 170 }
SELECT arrayEnumerateDenseRanked(1, ['\0'], -8363126); -- { serverError 170 }
SELECT arrayEnumerateDenseRanked(-101, ['\0']); -- { serverError 170 }
SELECT arrayEnumerateDenseRanked(1.1, [10,20,10,30]); -- { serverError 170 }
SELECT arrayEnumerateDenseRanked([10,20,10,30], 0.4); -- { serverError 170 }
SELECT arrayEnumerateDenseRanked([10,20,10,30], 1.8); -- { serverError 170 }
SELECT arrayEnumerateUniqRanked(1, [], 1000000000); -- { serverError 36 }

View File

@ -31,5 +31,4 @@ For more information about queries related to partition manipulations, see the [
A third-party tool is available to automate this approach: [clickhouse-backup](https://github.com/AlexAkulov/clickhouse-backup).
[Original article](https://clickhouse.yandex/docs/en/operations/backup/) <!--hide-->

View File

@ -14,7 +14,6 @@ ClickHouse может принимать (`INSERT`) и отдавать (`SELECT
[CSVWithNames](#csvwithnames) | ✔ | ✔ |
[Values](#values) | ✔ | ✔ |
[Vertical](#vertical) | ✗ | ✔ |
[VerticalRaw](#verticalraw) | ✗ | ✔ |
[JSON](#json) | ✗ | ✔ |
[JSONCompact](#jsoncompact) | ✗ | ✔ |
[JSONEachRow](#jsoneachrow) | ✔ | ✔ |
@ -354,10 +353,22 @@ SELECT * FROM t_null
└───┴──────┘
```
В форматах `Pretty*` строки выводятся без экранирования. Ниже приведен пример для формата [PrettyCompact](#prettycompact):
``` sql
SELECT 'String with \'quotes\' and \t character' AS Escaping_test
```
```
┌─Escaping_test────────────────────────┐
│ String with 'quotes' and character │
└──────────────────────────────────────┘
```
Для защиты от вываливания слишком большого количества данных в терминал, выводится только первые 10 000 строк. Если строк больше или равно 10 000, то будет написано "Showed first 10 000."
Этот формат подходит только для вывода результата выполнения запроса, но не для парсинга (приёма данных для вставки в таблицу).
Формат Pretty поддерживает вывод тотальных значений (при использовании WITH TOTALS) и экстремальных значений (при настройке extremes выставленной в 1). В этих случаях, после основных данных выводятся тотальные значения, и экстремальные значения, в отдельных табличках. Пример (показан для формата PrettyCompact):
Формат `Pretty` поддерживает вывод тотальных значений (при использовании WITH TOTALS) и экстремальных значений (при настройке extremes выставленной в 1). В этих случаях, после основных данных выводятся тотальные значения, и экстремальные значения, в отдельных табличках. Пример (показан для формата [PrettyCompact](#prettycompact)):
``` sql
SELECT EventDate, count() AS c FROM test.hits GROUP BY EventDate WITH TOTALS ORDER BY EventDate FORMAT PrettyCompact
@ -388,7 +399,7 @@ Extremes:
## PrettyCompact {#prettycompact}
Отличается от `Pretty` тем, что не рисуется сетка между строками - результат более компактный.
Отличается от [Pretty](#pretty) тем, что не рисуется сетка между строками - результат более компактный.
Этот формат используется по умолчанию в клиенте командной строки в интерактивном режиме.
## PrettyCompactMonoBlock {#prettycompactmonoblock}
@ -433,6 +444,7 @@ FixedString представлены просто как последовате
Array представлены как длина в формате varint (unsigned [LEB128](https://en.wikipedia.org/wiki/LEB128)), а затем элементы массива, подряд.
Для поддержки [NULL](../query_language/syntax.md#null-literal) перед каждым значением типа [Nullable](../data_types/nullable.md
## Values
Выводит каждую строку в скобках. Строки разделены запятыми. После последней строки запятой нет. Значения внутри скобок также разделены запятыми. Числа выводятся в десятичном виде без кавычек. Массивы выводятся в квадратных скобках. Строки, даты, даты-с-временем выводятся в кавычках. Правила экранирования и особенности парсинга аналогичны формату [TabSeparated](#tabseparated). При форматировании, лишние пробелы не ставятся, а при парсинге - допустимы и пропускаются (за исключением пробелов внутри значений типа массив, которые недопустимы). [NULL](../query_language/syntax.md) представляется как `NULL`.
@ -459,34 +471,20 @@ x: 1
y: ᴺᵁᴸᴸ
```
Этот формат подходит только для вывода результата выполнения запроса, но не для парсинга (приёма данных для вставки в таблицу).
В формате `Vertical` строки выводятся без экранирования. Например:
## VerticalRaw {#verticalraw}
Отличается от формата `Vertical` тем, что строки выводятся без экранирования.
Этот формат подходит только для вывода результата выполнения запроса, но не для парсинга (приёма данных для вставки в таблицу).
Примеры:
``` sql
SELECT 'string with \'quotes\' and \t with some special \n characters' AS test FORMAT Vertical
```
:) SHOW CREATE TABLE geonames FORMAT VerticalRaw;
Row 1:
──────
statement: CREATE TABLE default.geonames ( geonameid UInt32, date Date DEFAULT CAST('2017-12-08' AS Date)) ENGINE = MergeTree(date, geonameid, 8192)
:) SELECT 'string with \'quotes\' and \t with some special \n characters' AS test FORMAT VerticalRaw;
```
Row 1:
──────
test: string with 'quotes' and with some special
test: string with 'quotes' and with some special
characters
```
Для сравнения - формат Vertical:
```
:) SELECT 'string with \'quotes\' and \t with some special \n characters' AS test FORMAT Vertical;
Row 1:
──────
test: string with \'quotes\' and \t with some special \n characters
```
Этот формат подходит только для вывода результата выполнения запроса, но не для парсинга (приёма данных для вставки в таблицу).
## XML {#xml}