mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-30 03:22:14 +00:00
Merge branch 'master' into more-strict-tryparse
This commit is contained in:
commit
6554048624
2
.github/ISSUE_TEMPLATE/85_bug-report.md
vendored
2
.github/ISSUE_TEMPLATE/85_bug-report.md
vendored
@ -7,6 +7,8 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
> Please make sure that the version you're using is still supported (you can find the list [here](https://github.com/ClickHouse/ClickHouse/blob/master/SECURITY.md#scope-and-supported-versions)).
|
||||
|
||||
> You have to provide the following information whenever possible.
|
||||
|
||||
**Describe what's wrong**
|
||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -184,7 +184,7 @@
|
||||
url = https://github.com/ClickHouse/nanodbc
|
||||
[submodule "contrib/datasketches-cpp"]
|
||||
path = contrib/datasketches-cpp
|
||||
url = https://github.com/ClickHouse/datasketches-cpp
|
||||
url = https://github.com/apache/datasketches-cpp
|
||||
[submodule "contrib/yaml-cpp"]
|
||||
path = contrib/yaml-cpp
|
||||
url = https://github.com/ClickHouse/yaml-cpp
|
||||
|
@ -150,6 +150,7 @@
|
||||
# define TSA_ACQUIRE_SHARED(...) __attribute__((acquire_shared_capability(__VA_ARGS__))) /// function acquires a shared capability, but does not release it
|
||||
# define TSA_TRY_ACQUIRE_SHARED(...) __attribute__((try_acquire_shared_capability(__VA_ARGS__))) /// function tries to acquire a shared capability and returns a boolean value indicating success or failure
|
||||
# define TSA_RELEASE_SHARED(...) __attribute__((release_shared_capability(__VA_ARGS__))) /// function releases the given shared capability
|
||||
# define TSA_SCOPED_LOCKABLE __attribute__((scoped_lockable)) /// object of a class has scoped lockable capability
|
||||
|
||||
/// Macros for suppressing TSA warnings for specific reads/writes (instead of suppressing it for the whole function)
|
||||
/// They use a lambda function to apply function attribute to a single statement. This enable us to suppress warnings locally instead of
|
||||
@ -177,6 +178,7 @@
|
||||
# define TSA_ACQUIRE_SHARED(...)
|
||||
# define TSA_TRY_ACQUIRE_SHARED(...)
|
||||
# define TSA_RELEASE_SHARED(...)
|
||||
# define TSA_SCOPED_LOCKABLE
|
||||
|
||||
# define TSA_SUPPRESS_WARNING_FOR_READ(x) (x)
|
||||
# define TSA_SUPPRESS_WARNING_FOR_WRITE(x) (x)
|
||||
|
@ -131,3 +131,29 @@ void sort(RandomIt first, RandomIt last)
|
||||
using comparator = std::less<value_type>;
|
||||
::sort(first, last, comparator());
|
||||
}
|
||||
|
||||
/** Try to fast sort elements for common sorting patterns:
|
||||
* 1. If elements are already sorted.
|
||||
* 2. If elements are already almost sorted.
|
||||
* 3. If elements are already sorted in reverse order.
|
||||
*
|
||||
* Returns true if fast sort was performed or elements were already sorted, false otherwise.
|
||||
*/
|
||||
template <typename RandomIt, typename Compare>
|
||||
bool trySort(RandomIt first, RandomIt last, Compare compare)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
::shuffle(first, last);
|
||||
#endif
|
||||
|
||||
ComparatorWrapper<Compare> compare_wrapper = compare;
|
||||
return ::pdqsort_try_sort(first, last, compare_wrapper);
|
||||
}
|
||||
|
||||
template <typename RandomIt>
|
||||
bool trySort(RandomIt first, RandomIt last)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<RandomIt>::value_type;
|
||||
using comparator = std::less<value_type>;
|
||||
return ::trySort(first, last, comparator());
|
||||
}
|
||||
|
2
contrib/avro
vendored
2
contrib/avro
vendored
@ -1 +1 @@
|
||||
Subproject commit 7832659ec986075d560f930c288e973c64679552
|
||||
Subproject commit 2fb8a8a6ec0eab9109b68abf3b4857e8c476b918
|
2
contrib/datasketches-cpp
vendored
2
contrib/datasketches-cpp
vendored
@ -1 +1 @@
|
||||
Subproject commit 7abd49bb2e72bf9a5029993d31dcb1872da88292
|
||||
Subproject commit c3abaaefe5fa400eed99e082af07c1b61a7144db
|
2
contrib/google-protobuf
vendored
2
contrib/google-protobuf
vendored
@ -1 +1 @@
|
||||
Subproject commit c47efe2d8f6a60022b49ecd6cc23660687c8598f
|
||||
Subproject commit 2a4fa1a4e95012d754ac55d43c8bc462dd1c78a8
|
@ -36,7 +36,6 @@ set(libprotobuf_lite_files
|
||||
${protobuf_source_dir}/src/google/protobuf/arenastring.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/extension_set.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/generated_enum_util.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/generated_message_util.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/implicit_weak_message.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/io/coded_stream.cc
|
||||
@ -87,13 +86,13 @@ set(libprotobuf_files
|
||||
${protobuf_source_dir}/src/google/protobuf/field_mask.pb.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/generated_message_bases.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/generated_message_reflection.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/generated_message_table_driven.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/io/gzip_stream.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/io/printer.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/io/tokenizer.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/map_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/message.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/reflection_ops.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/repeated_ptr_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/service.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/source_context.pb.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/struct.pb.cc
|
||||
@ -143,21 +142,21 @@ add_library(protobuf::libprotobuf ALIAS _libprotobuf)
|
||||
set(libprotoc_files
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/code_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/command_line_interface.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_enum.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_enum_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_extension.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_file.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_helpers.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_map_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_message.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_message_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_padding_optimizer.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_parse_function_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_primitive_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_service.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/cpp_string_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/enum.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/enum_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/extension.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/file.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/helpers.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/map_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/message.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/message_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/padding_optimizer.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/parse_function_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/primitive_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/service.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/cpp/string_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_enum.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_enum_field.cc
|
||||
@ -174,37 +173,35 @@ set(libprotoc_files
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_repeated_primitive_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_source_generator_base.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_wrapper_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_context.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_doc_comment.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_enum.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_enum_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_enum_field_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_enum_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_extension.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_extension_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_file.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_generator_factory.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_helpers.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_kotlin_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_map_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_map_field_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_message.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_message_builder.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_message_builder_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_message_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_message_field_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_message_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_name_resolver.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_primitive_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_primitive_field_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_service.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_shared_code_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_string_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/java_string_field_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/js/js_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/js/well_known_types_embed.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/context.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/doc_comment.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/enum.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/enum_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/enum_field_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/enum_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/extension.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/extension_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/file.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/generator_factory.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/helpers.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/kotlin_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/map_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/map_field_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/message.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/message_builder.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/message_builder_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/message_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/message_field_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/message_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/name_resolver.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/primitive_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/primitive_field_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/service.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/shared_code_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/string_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/java/string_field_lite.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/objectivec/objectivec_enum.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/objectivec/objectivec_enum_field.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/objectivec/objectivec_extension.cc
|
||||
@ -220,7 +217,9 @@ set(libprotoc_files
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/php/php_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/plugin.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/plugin.pb.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/python/python_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/python/generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/python/helpers.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/python/pyi_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/ruby/ruby_generator.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/subprocess.cc
|
||||
${protobuf_source_dir}/src/google/protobuf/compiler/zip_writer.cc
|
||||
|
2
contrib/grpc
vendored
2
contrib/grpc
vendored
@ -1 +1 @@
|
||||
Subproject commit c52656e2bfcda3450bd6a7c247d2d9eeb8498524
|
||||
Subproject commit bef8212d1e01f99e406c282ceab3d42da08e09ce
|
2
contrib/orc
vendored
2
contrib/orc
vendored
@ -1 +1 @@
|
||||
Subproject commit a20d1d9d7ad4a4be7b7ba97588e16ca8b9abb2b6
|
||||
Subproject commit f31c271110a2f0dac908a152f11708193ae209ee
|
@ -54,8 +54,10 @@ namespace pdqsort_detail {
|
||||
block_size = 64,
|
||||
|
||||
// Cacheline size, assumes power of two.
|
||||
cacheline_size = 64
|
||||
cacheline_size = 64,
|
||||
|
||||
/// Try sort allowed iterations
|
||||
try_sort_iterations = 3,
|
||||
};
|
||||
|
||||
#if __cplusplus >= 201103L
|
||||
@ -501,6 +503,167 @@ namespace pdqsort_detail {
|
||||
leftmost = false;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Iter, class Compare, bool Branchless>
|
||||
inline bool pdqsort_try_sort_loop(Iter begin,
|
||||
Iter end,
|
||||
Compare comp,
|
||||
size_t bad_allowed,
|
||||
size_t iterations_allowed,
|
||||
bool force_sort = false,
|
||||
bool leftmost = true) {
|
||||
typedef typename std::iterator_traits<Iter>::difference_type diff_t;
|
||||
|
||||
// Use a while loop for tail recursion elimination.
|
||||
while (true) {
|
||||
if (!force_sort && iterations_allowed == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
diff_t size = end - begin;
|
||||
|
||||
// Insertion sort is faster for small arrays.
|
||||
if (size < insertion_sort_threshold) {
|
||||
if (leftmost) insertion_sort(begin, end, comp);
|
||||
else unguarded_insertion_sort(begin, end, comp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Choose pivot as median of 3 or pseudomedian of 9.
|
||||
diff_t s2 = size / 2;
|
||||
if (size > ninther_threshold) {
|
||||
sort3(begin, begin + s2, end - 1, comp);
|
||||
sort3(begin + 1, begin + (s2 - 1), end - 2, comp);
|
||||
sort3(begin + 2, begin + (s2 + 1), end - 3, comp);
|
||||
sort3(begin + (s2 - 1), begin + s2, begin + (s2 + 1), comp);
|
||||
std::iter_swap(begin, begin + s2);
|
||||
} else sort3(begin + s2, begin, end - 1, comp);
|
||||
|
||||
// If *(begin - 1) is the end of the right partition of a previous partition operation
|
||||
// there is no element in [begin, end) that is smaller than *(begin - 1). Then if our
|
||||
// pivot compares equal to *(begin - 1) we change strategy, putting equal elements in
|
||||
// the left partition, greater elements in the right partition. We do not have to
|
||||
// recurse on the left partition, since it's sorted (all equal).
|
||||
if (!leftmost && !comp(*(begin - 1), *begin)) {
|
||||
begin = partition_left(begin, end, comp) + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Partition and get results.
|
||||
std::pair<Iter, bool> part_result =
|
||||
Branchless ? partition_right_branchless(begin, end, comp)
|
||||
: partition_right(begin, end, comp);
|
||||
Iter pivot_pos = part_result.first;
|
||||
bool already_partitioned = part_result.second;
|
||||
|
||||
// Check for a highly unbalanced partition.
|
||||
diff_t l_size = pivot_pos - begin;
|
||||
diff_t r_size = end - (pivot_pos + 1);
|
||||
bool highly_unbalanced = l_size < size / 8 || r_size < size / 8;
|
||||
|
||||
// If we got a highly unbalanced partition we shuffle elements to break many patterns.
|
||||
if (highly_unbalanced) {
|
||||
if (!force_sort) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we had too many bad partitions, switch to heapsort to guarantee O(n log n).
|
||||
if (--bad_allowed == 0) {
|
||||
std::make_heap(begin, end, comp);
|
||||
std::sort_heap(begin, end, comp);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (l_size >= insertion_sort_threshold) {
|
||||
std::iter_swap(begin, begin + l_size / 4);
|
||||
std::iter_swap(pivot_pos - 1, pivot_pos - l_size / 4);
|
||||
|
||||
if (l_size > ninther_threshold) {
|
||||
std::iter_swap(begin + 1, begin + (l_size / 4 + 1));
|
||||
std::iter_swap(begin + 2, begin + (l_size / 4 + 2));
|
||||
std::iter_swap(pivot_pos - 2, pivot_pos - (l_size / 4 + 1));
|
||||
std::iter_swap(pivot_pos - 3, pivot_pos - (l_size / 4 + 2));
|
||||
}
|
||||
}
|
||||
|
||||
if (r_size >= insertion_sort_threshold) {
|
||||
std::iter_swap(pivot_pos + 1, pivot_pos + (1 + r_size / 4));
|
||||
std::iter_swap(end - 1, end - r_size / 4);
|
||||
|
||||
if (r_size > ninther_threshold) {
|
||||
std::iter_swap(pivot_pos + 2, pivot_pos + (2 + r_size / 4));
|
||||
std::iter_swap(pivot_pos + 3, pivot_pos + (3 + r_size / 4));
|
||||
std::iter_swap(end - 2, end - (1 + r_size / 4));
|
||||
std::iter_swap(end - 3, end - (2 + r_size / 4));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If we were decently balanced and we tried to sort an already partitioned
|
||||
// sequence try to use insertion sort.
|
||||
if (already_partitioned && partial_insertion_sort(begin, pivot_pos, comp)
|
||||
&& partial_insertion_sort(pivot_pos + 1, end, comp)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the left partition first using recursion and do tail recursion elimination for
|
||||
// the right-hand partition.
|
||||
if (pdqsort_try_sort_loop<Iter, Compare, Branchless>(begin,
|
||||
pivot_pos,
|
||||
comp,
|
||||
bad_allowed,
|
||||
iterations_allowed - 1,
|
||||
force_sort,
|
||||
leftmost)) {
|
||||
force_sort = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
--iterations_allowed;
|
||||
begin = pivot_pos + 1;
|
||||
leftmost = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class Iter, class Compare, bool Branchless>
|
||||
inline bool pdqsort_try_sort_impl(Iter begin, Iter end, Compare comp, size_t bad_allowed)
|
||||
{
|
||||
typedef typename std::iterator_traits<Iter>::difference_type diff_t;
|
||||
|
||||
static constexpr size_t iterations_allowed = pdqsort_detail::try_sort_iterations;
|
||||
static constexpr size_t num_to_try = 16;
|
||||
|
||||
diff_t size = end - begin;
|
||||
|
||||
if (size > num_to_try * 10)
|
||||
{
|
||||
size_t out_of_order_elements = 0;
|
||||
|
||||
for (size_t i = 1; i < num_to_try; ++i)
|
||||
{
|
||||
diff_t offset = size / num_to_try;
|
||||
|
||||
diff_t prev_position = offset * (i - 1);
|
||||
diff_t curr_position = offset * i;
|
||||
diff_t next_position = offset * (i + 1) - 1;
|
||||
|
||||
bool prev_less_than_curr = comp(*(begin + prev_position), *(begin + curr_position));
|
||||
bool curr_less_than_next = comp(*(begin + curr_position), *(begin + next_position));
|
||||
if ((prev_less_than_curr && curr_less_than_next) || (!prev_less_than_curr && !curr_less_than_next))
|
||||
continue;
|
||||
|
||||
++out_of_order_elements;
|
||||
if (out_of_order_elements > iterations_allowed)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return pdqsort_try_sort_loop<Iter, Compare, Branchless>(begin, end, comp, bad_allowed, iterations_allowed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -538,6 +701,41 @@ inline void pdqsort_branchless(Iter begin, Iter end) {
|
||||
pdqsort_branchless(begin, end, std::less<T>());
|
||||
}
|
||||
|
||||
template<class Iter, class Compare>
|
||||
inline bool pdqsort_try_sort(Iter begin, Iter end, Compare comp) {
|
||||
if (begin == end) return true;
|
||||
|
||||
#if __cplusplus >= 201103L
|
||||
return pdqsort_detail::pdqsort_try_sort_impl<Iter, Compare,
|
||||
pdqsort_detail::is_default_compare<typename std::decay<Compare>::type>::value &&
|
||||
std::is_arithmetic<typename std::iterator_traits<Iter>::value_type>::value>(
|
||||
begin, end, comp, pdqsort_detail::log2(end - begin));
|
||||
#else
|
||||
return pdqsort_detail::pdqsort_try_sort_impl<Iter, Compare, false>(
|
||||
begin, end, comp, pdqsort_detail::log2(end - begin));
|
||||
#endif
|
||||
}
|
||||
|
||||
template<class Iter>
|
||||
inline bool pdqsort_try_sort(Iter begin, Iter end) {
|
||||
typedef typename std::iterator_traits<Iter>::value_type T;
|
||||
return pdqsort_try_sort(begin, end, std::less<T>());
|
||||
}
|
||||
|
||||
template<class Iter, class Compare>
|
||||
inline bool pdqsort_try_sort_branchless(Iter begin, Iter end, Compare comp) {
|
||||
if (begin == end) return true;
|
||||
|
||||
return pdqsort_detail::pdqsort_try_sort_impl<Iter, Compare, true>(
|
||||
begin, end, comp, pdqsort_detail::log2(end - begin));
|
||||
}
|
||||
|
||||
template<class Iter>
|
||||
inline bool pdqsort_try_sort_branchless(Iter begin, Iter end) {
|
||||
typedef typename std::iterator_traits<Iter>::value_type T;
|
||||
return pdqsort_try_sort_branchless(begin, end, std::less<T>());
|
||||
}
|
||||
|
||||
|
||||
#undef PDQSORT_PREFER_MOVE
|
||||
|
||||
|
@ -210,7 +210,7 @@ detach
|
||||
quit
|
||||
" > script.gdb
|
||||
|
||||
gdb -batch -command script.gdb -p "$(cat /var/run/clickhouse-server/clickhouse-server.pid)" &
|
||||
gdb -batch -command script.gdb -p $server_pid &
|
||||
sleep 5
|
||||
# gdb will send SIGSTOP, spend some time loading debug info and then send SIGCONT, wait for it (up to send_timeout, 300s)
|
||||
time clickhouse-client --query "SELECT 'Connected to clickhouse-server after attaching gdb'" ||:
|
||||
@ -219,13 +219,12 @@ quit
|
||||
# to freeze and the fuzzer will fail. In debug build it can take a lot of time.
|
||||
for _ in {1..180}
|
||||
do
|
||||
sleep 1
|
||||
if clickhouse-client --query "select 1"
|
||||
then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
clickhouse-client --query "select 1" # This checks that the server is responding
|
||||
kill -0 $server_pid # This checks that it is our server that is started and not some other one
|
||||
echo 'Server started and responded'
|
||||
|
||||
|
@ -14,3 +14,8 @@ services:
|
||||
LDAP_PORT_NUMBER: ${LDAP_INTERNAL_PORT:-1389}
|
||||
ports:
|
||||
- ${LDAP_EXTERNAL_PORT:-1389}:${LDAP_INTERNAL_PORT:-1389}
|
||||
healthcheck:
|
||||
test: "ldapsearch -x -b dc=example,dc=org cn > /dev/null"
|
||||
interval: 10s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
|
@ -285,7 +285,7 @@ function run_tests
|
||||
# Use awk because bash doesn't support floating point arithmetic.
|
||||
profile_seconds=$(awk "BEGIN { print ($profile_seconds_left > 0 ? 10 : 0) }")
|
||||
|
||||
if [ "$(rg -c $(basename $test) changed-test-definitions.txt)" -gt 0 ]
|
||||
if rg --quiet "$(basename $test)" changed-test-definitions.txt
|
||||
then
|
||||
# Run all queries from changed test files to ensure that all new queries will be tested.
|
||||
max_queries=0
|
||||
|
@ -73,7 +73,7 @@ A tuple of column names or arbitrary expressions. Example: `ORDER BY (CounterID,
|
||||
|
||||
ClickHouse uses the sorting key as a primary key if the primary key is not defined explicitly by the `PRIMARY KEY` clause.
|
||||
|
||||
Use the `ORDER BY tuple()` syntax, if you do not need sorting. See [Selecting the Primary Key](#selecting-the-primary-key).
|
||||
Use the `ORDER BY tuple()` syntax, if you do not need sorting, or set `create_table_empty_primary_key_by_default` to `true` to use the `ORDER BY tuple()` syntax by default. See [Selecting the Primary Key](#selecting-the-primary-key).
|
||||
|
||||
#### PARTITION BY
|
||||
|
||||
|
@ -84,5 +84,5 @@ SELECT * FROM WatchLog;
|
||||
|
||||
**See Also**
|
||||
|
||||
- [Virtual columns](../../../engines/table-engines/special/index.md#table_engines-virtual_columns)
|
||||
- [Virtual columns](../../../engines/table-engines/index.md#table_engines-virtual_columns)
|
||||
- [merge](../../../sql-reference/table-functions/merge.md) table function
|
||||
|
@ -380,7 +380,7 @@ build.
|
||||
|
||||
### macOS-only: Install with Homebrew
|
||||
|
||||
To install ClickHouse using the popular `brew` package manager, follow the instructions listed in the [ClickHouse Homebrew tap](https://github.com/ClickHouse/homebrew-clickhouse).
|
||||
To install ClickHouse using [homebrew](https://brew.sh/), see [here](https://formulae.brew.sh/cask/clickhouse).
|
||||
|
||||
## Launch {#launch}
|
||||
|
||||
|
@ -1395,6 +1395,23 @@ For more information, see the section [Creating replicated tables](../../engines
|
||||
<macros incl="macros" optional="true" />
|
||||
```
|
||||
|
||||
## replica_group_name {#replica_group_name}
|
||||
|
||||
Replica group name for database Replicated.
|
||||
|
||||
The cluster created by Replicated database will consist of replicas in the same group.
|
||||
DDL queries will only wait for the replicas in the same group.
|
||||
|
||||
Empty by default.
|
||||
|
||||
**Example**
|
||||
|
||||
``` xml
|
||||
<replica_group_name>backups</replica_group_name>
|
||||
```
|
||||
|
||||
Default value: ``.
|
||||
|
||||
## max_open_files {#max-open-files}
|
||||
|
||||
The maximum number of open files.
|
||||
|
@ -2403,6 +2403,17 @@ See also:
|
||||
|
||||
- [optimize_functions_to_subcolumns](#optimize-functions-to-subcolumns)
|
||||
|
||||
## optimize_trivial_approximate_count_query {#optimize_trivial_approximate_count_query}
|
||||
|
||||
Use an approximate value for trivial count optimization of storages that support such estimation, for example, EmbeddedRocksDB.
|
||||
|
||||
Possible values:
|
||||
|
||||
- 0 — Optimization disabled.
|
||||
- 1 — Optimization enabled.
|
||||
|
||||
Default value: `0`.
|
||||
|
||||
## optimize_count_from_files {#optimize_count_from_files}
|
||||
|
||||
Enables or disables the optimization of counting number of rows from files in different input formats. It applies to table functions/engines `file`/`s3`/`url`/`hdfs`/`azureBlobStorage`.
|
||||
@ -4144,6 +4155,18 @@ Possible values:
|
||||
|
||||
Default value: `0`.
|
||||
|
||||
## date_time_overflow_behavior {#date_time_overflow_behavior}
|
||||
|
||||
Defines the behavior when [Date](../../sql-reference/data-types/date.md), [Date32](../../sql-reference/data-types/date32.md), [DateTime](../../sql-reference/data-types/datetime.md), [DateTime64](../../sql-reference/data-types/datetime64.md) or integers are converted into Date, Date32, DateTime or DateTime64 but the value cannot be represented in the result type.
|
||||
|
||||
Possible values:
|
||||
|
||||
- `ignore` — Silently ignore overflows. The result is random.
|
||||
- `throw` — Throw an exception in case of conversion overflow.
|
||||
- `saturate` — Silently saturate the result. If the value is smaller than the smallest value that can be represented by the target type, the result is chosen as the smallest representable value. If the value is bigger than the largest value that can be represented by the target type, the result is chosen as the largest representable value.
|
||||
|
||||
Default value: `ignore`.
|
||||
|
||||
## optimize_move_to_prewhere {#optimize_move_to_prewhere}
|
||||
|
||||
Enables or disables automatic [PREWHERE](../../sql-reference/statements/select/prewhere.md) optimization in [SELECT](../../sql-reference/statements/select/index.md) queries.
|
||||
@ -4708,18 +4731,6 @@ SELECT toFloat64('1.7091'), toFloat64('1.5008753E7') SETTINGS precise_float_pars
|
||||
└─────────────────────┴──────────────────────────┘
|
||||
```
|
||||
|
||||
## partial_result_update_duration_ms
|
||||
|
||||
Interval (in milliseconds) for sending updates with partial data about the result table to the client (in interactive mode) during query execution. Setting to 0 disables partial results. Only supported for single-threaded GROUP BY without key, ORDER BY, LIMIT and OFFSET.
|
||||
|
||||
:::note
|
||||
It's an experimental feature. Enable `allow_experimental_partial_result` setting first to use it.
|
||||
:::
|
||||
|
||||
## max_rows_in_partial_result
|
||||
|
||||
Maximum rows to show in the partial result after every real-time update while the query runs (use partial result limit + OFFSET as a value in case of OFFSET in the query).
|
||||
|
||||
## validate_tcp_client_information {#validate-tcp-client-information}
|
||||
|
||||
Determines whether validation of client information enabled when query packet is received from a client using a TCP connection.
|
||||
@ -4768,3 +4779,18 @@ a Tuple(
|
||||
l Nullable(String)
|
||||
)
|
||||
```
|
||||
|
||||
## dictionary_use_async_executor {#dictionary_use_async_executor}
|
||||
|
||||
Execute a pipeline for reading dictionary source in several threads. It's supported only by dictionaries with local CLICKHOUSE source.
|
||||
|
||||
You may specify it in `SETTINGS` section of dictionary definition:
|
||||
|
||||
```sql
|
||||
CREATE DICTIONARY t1_dict ( key String, attr UInt64 )
|
||||
PRIMARY KEY key
|
||||
SOURCE(CLICKHOUSE(QUERY `SELECT key, attr FROM t1 GROUP BY key`))
|
||||
LIFETIME(MIN 0 MAX 3600)
|
||||
LAYOUT(COMPLEX_KEY_HASHED_ARRAY())
|
||||
SETTINGS(dictionary_use_async_executor=1, max_threads=8);
|
||||
```
|
||||
|
@ -55,6 +55,7 @@ keeper foo bar
|
||||
- `rmr <path>` -- Recursively deletes path. Confirmation required
|
||||
- `flwc <command>` -- Executes four-letter-word command
|
||||
- `help` -- Prints this message
|
||||
- `get_direct_children_number [path]` -- Get numbers of direct children nodes under a specific path
|
||||
- `get_all_children_number [path]` -- Get all numbers of children nodes under a specific path
|
||||
- `get_stat [path]` -- Returns the node's stat (default `.`)
|
||||
- `find_super_nodes <threshold> [path]` -- Finds nodes with number of children larger than some threshold for the given path (default `.`)
|
||||
|
@ -1081,6 +1081,10 @@ Result:
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**See also**
|
||||
|
||||
- [arrayFold](#arrayFold)
|
||||
|
||||
## arrayReduceInRanges
|
||||
|
||||
Applies an aggregate function to array elements in given ranges and returns an array containing the result corresponding to each range. The function will return the same result as multiple `arrayReduce(agg_func, arraySlice(arr1, index, length), ...)`.
|
||||
@ -1138,17 +1142,41 @@ arrayFold(lambda_function, arr1, arr2, ..., accumulator)
|
||||
Query:
|
||||
|
||||
``` sql
|
||||
SELECT arrayFold( x,acc -> acc + x*2, [1, 2, 3, 4], toInt64(3)) AS res;
|
||||
SELECT arrayFold( acc,x -> acc + x*2, [1, 2, 3, 4], toInt64(3)) AS res;
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
``` text
|
||||
┌─arrayFold(lambda(tuple(x, acc), plus(acc, multiply(x, 2))), [1, 2, 3, 4], toInt64(3))─┐
|
||||
│ 3 │
|
||||
└───────────────────────────────────────────────────────────────────────────────────────┘
|
||||
┌─res─┐
|
||||
│ 23 │
|
||||
└─────┘
|
||||
```
|
||||
|
||||
**Example with the Fibonacci sequence**
|
||||
|
||||
```sql
|
||||
SELECT arrayFold( acc,x -> (acc.2, acc.2 + acc.1), range(number), (1::Int64, 0::Int64)).1 AS fibonacci
|
||||
FROM numbers(1,10);
|
||||
|
||||
┌─fibonacci─┐
|
||||
│ 0 │
|
||||
│ 1 │
|
||||
│ 1 │
|
||||
│ 2 │
|
||||
│ 3 │
|
||||
│ 5 │
|
||||
│ 8 │
|
||||
│ 13 │
|
||||
│ 21 │
|
||||
│ 34 │
|
||||
└───────────┘
|
||||
```
|
||||
|
||||
**See also**
|
||||
|
||||
- [arrayReduce](#arrayReduce)
|
||||
|
||||
## arrayReverse(arr)
|
||||
|
||||
Returns an array of the same size as the original array containing the elements in reverse order.
|
||||
|
@ -605,7 +605,7 @@ The first argument can also be specified as [String](../data-types/string.md) in
|
||||
|
||||
**Returned value**
|
||||
|
||||
- The day of the month (1 - 31) of the given date/time
|
||||
- The day of the week (1-7), depending on the chosen mode, of the given date/time
|
||||
|
||||
**Example**
|
||||
|
||||
@ -1910,6 +1910,7 @@ Result:
|
||||
```
|
||||
|
||||
**See Also**
|
||||
|
||||
- [subDate](#subDate)
|
||||
|
||||
## timestamp\_add
|
||||
@ -2053,6 +2054,7 @@ Result:
|
||||
Alias: `ADDDATE`
|
||||
|
||||
**See Also**
|
||||
|
||||
- [date_add](#date_add)
|
||||
|
||||
## subDate
|
||||
@ -2095,6 +2097,7 @@ Result:
|
||||
Alias: `SUBDATE`
|
||||
|
||||
**See Also**
|
||||
|
||||
- [date_sub](#date_sub)
|
||||
|
||||
## now {#now}
|
||||
@ -2388,42 +2391,50 @@ Like function `YYYYMMDDhhmmssToDate()` but produces a [DateTime64](../../sql-ref
|
||||
|
||||
Accepts an additional, optional `precision` parameter after the `timezone` parameter.
|
||||
|
||||
## addYears, addMonths, addWeeks, addDays, addHours, addMinutes, addSeconds, addQuarters
|
||||
## addYears, addQuarters, addMonths, addWeeks, addDays, addHours, addMinutes, addSeconds, addMilliseconds, addMicroseconds, addNanoseconds
|
||||
|
||||
Function adds a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime. For example:
|
||||
These functions add units of the interval specified by the function name to a date, a date with time or a string-encoded date / date with time. A date or date with time is returned.
|
||||
|
||||
Example:
|
||||
|
||||
``` sql
|
||||
WITH
|
||||
toDate('2018-01-01') AS date,
|
||||
toDateTime('2018-01-01 00:00:00') AS date_time
|
||||
toDate('2024-01-01') AS date,
|
||||
toDateTime('2024-01-01 00:00:00') AS date_time,
|
||||
'2024-01-01 00:00:00' AS date_time_string
|
||||
SELECT
|
||||
addYears(date, 1) AS add_years_with_date,
|
||||
addYears(date_time, 1) AS add_years_with_date_time
|
||||
addYears(date_time, 1) AS add_years_with_date_time,
|
||||
addYears(date_time_string, 1) AS add_years_with_date_time_string
|
||||
```
|
||||
|
||||
``` text
|
||||
┌─add_years_with_date─┬─add_years_with_date_time─┐
|
||||
│ 2019-01-01 │ 2019-01-01 00:00:00 │
|
||||
└─────────────────────┴──────────────────────────┘
|
||||
┌─add_years_with_date─┬─add_years_with_date_time─┬─add_years_with_date_time_string─┐
|
||||
│ 2025-01-01 │ 2025-01-01 00:00:00 │ 2025-01-01 00:00:00.000 │
|
||||
└─────────────────────┴──────────────────────────┴─────────────────────────────────┘
|
||||
```
|
||||
|
||||
## subtractYears, subtractMonths, subtractWeeks, subtractDays, subtractHours, subtractMinutes, subtractSeconds, subtractQuarters
|
||||
## subtractYears, subtractQuarters, subtractMonths, subtractWeeks, subtractDays, subtractHours, subtractMinutes, subtractSeconds, subtractMilliseconds, subtractMicroseconds, subtractNanoseconds
|
||||
|
||||
Function subtract a Date/DateTime interval to a Date/DateTime and then return the Date/DateTime. For example:
|
||||
These functions subtract units of the interval specified by the function name from a date, a date with time or a string-encoded date / date with time. A date or date with time is returned.
|
||||
|
||||
Example:
|
||||
|
||||
``` sql
|
||||
WITH
|
||||
toDate('2019-01-01') AS date,
|
||||
toDateTime('2019-01-01 00:00:00') AS date_time
|
||||
toDate('2024-01-01') AS date,
|
||||
toDateTime('2024-01-01 00:00:00') AS date_time,
|
||||
'2024-01-01 00:00:00' AS date_time_string
|
||||
SELECT
|
||||
subtractYears(date, 1) AS subtract_years_with_date,
|
||||
subtractYears(date_time, 1) AS subtract_years_with_date_time
|
||||
subtractYears(date_time, 1) AS subtract_years_with_date_time,
|
||||
subtractYears(date_time_string, 1) AS subtract_years_with_date_time_string
|
||||
```
|
||||
|
||||
``` text
|
||||
┌─subtract_years_with_date─┬─subtract_years_with_date_time─┐
|
||||
│ 2018-01-01 │ 2018-01-01 00:00:00 │
|
||||
└──────────────────────────┴───────────────────────────────┘
|
||||
┌─subtract_years_with_date─┬─subtract_years_with_date_time─┬─subtract_years_with_date_time_string─┐
|
||||
│ 2023-01-01 │ 2023-01-01 00:00:00 │ 2023-01-01 00:00:00.000 │
|
||||
└──────────────────────────┴───────────────────────────────┴──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## timeSlots(StartTime, Duration,\[, Size\])
|
||||
|
@ -4,7 +4,7 @@ sidebar_position: 105
|
||||
sidebar_label: JSON
|
||||
---
|
||||
|
||||
There are two sets of functions to parse JSON.
|
||||
There are two sets of functions to parse JSON.
|
||||
- `visitParam*` (`simpleJSON*`) is made to parse a special very limited subset of a JSON, but these functions are extremely fast.
|
||||
- `JSONExtract*` is made to parse normal JSON.
|
||||
|
||||
@ -367,7 +367,7 @@ SELECT JSON_EXISTS('{"hello":["world"]}', '$.hello[*]');
|
||||
SELECT JSON_EXISTS('{"hello":["world"]}', '$.hello[0]');
|
||||
```
|
||||
|
||||
:::note
|
||||
:::note
|
||||
Before version 21.11 the order of arguments was wrong, i.e. JSON_EXISTS(path, json)
|
||||
:::
|
||||
|
||||
@ -394,7 +394,7 @@ Result:
|
||||
[2]
|
||||
String
|
||||
```
|
||||
:::note
|
||||
:::note
|
||||
Before version 21.11 the order of arguments was wrong, i.e. JSON_QUERY(path, json)
|
||||
:::
|
||||
|
||||
@ -402,7 +402,7 @@ Before version 21.11 the order of arguments was wrong, i.e. JSON_QUERY(path, jso
|
||||
|
||||
Parses a JSON and extract a value as JSON scalar.
|
||||
|
||||
If the value does not exist, an empty string will be returned by default, and by SET `function_return_type_allow_nullable` = `true`, `NULL` will be returned. If the value is complex type (such as: struct, array, map), an empty string will be returned by default, and by SET `function_json_value_return_type_allow_complex` = `true`, the complex value will be returned.
|
||||
If the value does not exist, an empty string will be returned by default, and by SET `function_json_value_return_type_allow_nullable` = `true`, `NULL` will be returned. If the value is complex type (such as: struct, array, map), an empty string will be returned by default, and by SET `function_json_value_return_type_allow_complex` = `true`, the complex value will be returned.
|
||||
|
||||
Example:
|
||||
|
||||
@ -411,7 +411,7 @@ SELECT JSON_VALUE('{"hello":"world"}', '$.hello');
|
||||
SELECT JSON_VALUE('{"array":[[0, 1, 2, 3, 4, 5], [0, -1, -2, -3, -4, -5]]}', '$.array[*][0 to 2, 4]');
|
||||
SELECT JSON_VALUE('{"hello":2}', '$.hello');
|
||||
SELECT toTypeName(JSON_VALUE('{"hello":2}', '$.hello'));
|
||||
select JSON_VALUE('{"hello":"world"}', '$.b') settings function_return_type_allow_nullable=true;
|
||||
select JSON_VALUE('{"hello":"world"}', '$.b') settings function_json_value_return_type_allow_nullable=true;
|
||||
select JSON_VALUE('{"hello":{"world":"!"}}', '$.hello') settings function_json_value_return_type_allow_complex=true;
|
||||
```
|
||||
|
||||
@ -424,7 +424,7 @@ world
|
||||
String
|
||||
```
|
||||
|
||||
:::note
|
||||
:::note
|
||||
Before version 21.11 the order of arguments was wrong, i.e. JSON_VALUE(path, json)
|
||||
:::
|
||||
|
||||
@ -509,3 +509,34 @@ SELECT
|
||||
│ ᴺᵁᴸᴸ │ 3 │
|
||||
└─────────────────────┴────────────────────────────┘
|
||||
```
|
||||
|
||||
|
||||
## jsonMergePatch
|
||||
|
||||
Returns the merged JSON object string which is formed by merging multiple JSON objects.
|
||||
|
||||
**Syntax**
|
||||
|
||||
``` sql
|
||||
jsonMergePatch(json1, json2, ...)
|
||||
```
|
||||
|
||||
**Arguments**
|
||||
|
||||
- `json` — [String](../../sql-reference/data-types/string.md) with valid JSON.
|
||||
|
||||
**Returned value**
|
||||
|
||||
- If JSON object strings are valid, return the merged JSON object string.
|
||||
|
||||
Type: [String](../../sql-reference/data-types/string.md).
|
||||
|
||||
**Example**
|
||||
|
||||
``` sql
|
||||
SELECT jsonMergePatch('{"a":1}', '{"name": "joey"}', '{"name": "tom"}', '{"name": "zoey"}') AS res
|
||||
|
||||
┌─res───────────────────┐
|
||||
│ {"a":1,"name":"zoey"} │
|
||||
└───────────────────────┘
|
||||
```
|
||||
|
@ -2794,3 +2794,71 @@ message Root
|
||||
uint32 column2 = 2;
|
||||
}
|
||||
```
|
||||
|
||||
## formatQuery
|
||||
|
||||
Returns a formatted, possibly multi-line, version of the given SQL query.
|
||||
|
||||
**Syntax**
|
||||
|
||||
```sql
|
||||
formatQuery(query)
|
||||
```
|
||||
|
||||
**Arguments**
|
||||
|
||||
- `query` - The SQL query to be formatted. [String](../../sql-reference/data-types/string.md)
|
||||
|
||||
**Returned value**
|
||||
|
||||
- The formatted query. [String](../../sql-reference/data-types/string.md).
|
||||
|
||||
**Example**
|
||||
|
||||
```sql
|
||||
SELECT formatQuery('select a, b FRom tab WHERE a > 3 and b < 3');
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```result
|
||||
┌─formatQuery('select a, b FRom tab WHERE a > 3 and b < 3')─┐
|
||||
│ SELECT
|
||||
a,
|
||||
b
|
||||
FROM tab
|
||||
WHERE (a > 3) AND (b < 3) │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## formatQuerySingleLine
|
||||
|
||||
Like formatQuery() but the returned formatted string contains no line breaks.
|
||||
|
||||
**Syntax**
|
||||
|
||||
```sql
|
||||
formatQuerySingleLine(query)
|
||||
```
|
||||
|
||||
**Arguments**
|
||||
|
||||
- `query` - The SQL query to be formatted. [String](../../sql-reference/data-types/string.md)
|
||||
|
||||
**Returned value**
|
||||
|
||||
- The formatted query. [String](../../sql-reference/data-types/string.md).
|
||||
|
||||
**Example**
|
||||
|
||||
```sql
|
||||
SELECT formatQuerySingleLine('select a, b FRom tab WHERE a > 3 and b < 3');
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```result
|
||||
┌─formatQuerySingleLine('select a, b FRom tab WHERE a > 3 and b < 3')─┐
|
||||
│ SELECT a, b FROM tab WHERE (a > 3) AND (b < 3) │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
@ -1982,12 +1982,12 @@ Result:
|
||||
|
||||
## snowflakeToDateTime
|
||||
|
||||
Extracts time from [Snowflake ID](https://en.wikipedia.org/wiki/Snowflake_ID) as [DateTime](/docs/en/sql-reference/data-types/datetime.md) format.
|
||||
Extracts the timestamp component of a [Snowflake ID](https://en.wikipedia.org/wiki/Snowflake_ID) in [DateTime](/docs/en/sql-reference/data-types/datetime.md) format.
|
||||
|
||||
**Syntax**
|
||||
|
||||
``` sql
|
||||
snowflakeToDateTime(value [, time_zone])
|
||||
snowflakeToDateTime(value[, time_zone])
|
||||
```
|
||||
|
||||
**Parameters**
|
||||
@ -1997,7 +1997,7 @@ snowflakeToDateTime(value [, time_zone])
|
||||
|
||||
**Returned value**
|
||||
|
||||
- Input value converted to the [DateTime](/docs/en/sql-reference/data-types/datetime.md) data type.
|
||||
- The timestamp component of `value` as a [DateTime](/docs/en/sql-reference/data-types/datetime.md) value.
|
||||
|
||||
**Example**
|
||||
|
||||
@ -2018,12 +2018,12 @@ Result:
|
||||
|
||||
## snowflakeToDateTime64
|
||||
|
||||
Extracts time from [Snowflake ID](https://en.wikipedia.org/wiki/Snowflake_ID) as [DateTime64](/docs/en/sql-reference/data-types/datetime64.md) format.
|
||||
Extracts the timestamp component of a [Snowflake ID](https://en.wikipedia.org/wiki/Snowflake_ID) in [DateTime64](/docs/en/sql-reference/data-types/datetime64.md) format.
|
||||
|
||||
**Syntax**
|
||||
|
||||
``` sql
|
||||
snowflakeToDateTime64(value [, time_zone])
|
||||
snowflakeToDateTime64(value[, time_zone])
|
||||
```
|
||||
|
||||
**Parameters**
|
||||
@ -2033,7 +2033,7 @@ snowflakeToDateTime64(value [, time_zone])
|
||||
|
||||
**Returned value**
|
||||
|
||||
- Input value converted to the [DateTime64](/docs/en/sql-reference/data-types/datetime64.md) data type.
|
||||
- The timestamp component of `value` as a [DateTime64](/docs/en/sql-reference/data-types/datetime64.md) with scale = 3, i.e. millisecond precision.
|
||||
|
||||
**Example**
|
||||
|
||||
@ -2054,7 +2054,7 @@ Result:
|
||||
|
||||
## dateTimeToSnowflake
|
||||
|
||||
Converts [DateTime](/docs/en/sql-reference/data-types/datetime.md) value to the first [Snowflake ID](https://en.wikipedia.org/wiki/Snowflake_ID) at the giving time.
|
||||
Converts a [DateTime](/docs/en/sql-reference/data-types/datetime.md) value to the first [Snowflake ID](https://en.wikipedia.org/wiki/Snowflake_ID) at the giving time.
|
||||
|
||||
**Syntax**
|
||||
|
||||
@ -2064,7 +2064,7 @@ dateTimeToSnowflake(value)
|
||||
|
||||
**Parameters**
|
||||
|
||||
- `value` — Date and time. [DateTime](/docs/en/sql-reference/data-types/datetime.md).
|
||||
- `value` — Date with time. [DateTime](/docs/en/sql-reference/data-types/datetime.md).
|
||||
|
||||
**Returned value**
|
||||
|
||||
@ -2088,7 +2088,7 @@ Result:
|
||||
|
||||
## dateTime64ToSnowflake
|
||||
|
||||
Convert [DateTime64](/docs/en/sql-reference/data-types/datetime64.md) to the first [Snowflake ID](https://en.wikipedia.org/wiki/Snowflake_ID) at the giving time.
|
||||
Convert a [DateTime64](/docs/en/sql-reference/data-types/datetime64.md) to the first [Snowflake ID](https://en.wikipedia.org/wiki/Snowflake_ID) at the giving time.
|
||||
|
||||
**Syntax**
|
||||
|
||||
@ -2098,7 +2098,7 @@ dateTime64ToSnowflake(value)
|
||||
|
||||
**Parameters**
|
||||
|
||||
- `value` — Date and time. [DateTime64](/docs/en/sql-reference/data-types/datetime64.md).
|
||||
- `value` — Date with time. [DateTime64](/docs/en/sql-reference/data-types/datetime64.md).
|
||||
|
||||
**Returned value**
|
||||
|
||||
|
@ -5,19 +5,39 @@ sidebar_label: CHECK TABLE
|
||||
title: "CHECK TABLE Statement"
|
||||
---
|
||||
|
||||
Checks if the data in the table is corrupted.
|
||||
The `CHECK TABLE` query in ClickHouse is used to perform a validation check on a specific table or its partitions. It ensures the integrity of the data by verifying the checksums and other internal data structures.
|
||||
|
||||
``` sql
|
||||
CHECK TABLE [db.]name [PARTITION partition_expr]
|
||||
Particularly it compares actual file sizes with the expected values which are stored on the server. If the file sizes do not match the stored values, it means the data is corrupted. This can be caused, for example, by a system crash during query execution.
|
||||
|
||||
:::note
|
||||
The `CHECK TABLE`` query may read all the data in the table and hold some resources, making it resource-intensive.
|
||||
Consider the potential impact on performance and resource utilization before executing this query.
|
||||
:::
|
||||
|
||||
## Syntax
|
||||
|
||||
The basic syntax of the query is as follows:
|
||||
|
||||
```sql
|
||||
CHECK TABLE table_name [PARTITION partition_expression | PART part_name] [FORMAT format] [SETTINGS check_query_single_value_result = (0|1) [, other_settings]]
|
||||
```
|
||||
|
||||
The `CHECK TABLE` query compares actual file sizes with the expected values which are stored on the server. If the file sizes do not match the stored values, it means the data is corrupted. This can be caused, for example, by a system crash during query execution.
|
||||
- `table_name`: Specifies the name of the table that you want to check.
|
||||
- `partition_expression`: (Optional) If you want to check a specific partition of the table, you can use this expression to specify the partition.
|
||||
- `part_name`: (Optional) If you want to check a specific part in the table, you can add string literal to specify a part name.
|
||||
- `FORMAT format`: (Optional) Allows you to specify the output format of the result.
|
||||
- `SETTINGS`: (Optional) Allows additional settings.
|
||||
- **`check_query_single_value_result`**: (Optional) This setting allows you to toggle between a detailed result (`0`) or a summarized result (`1`).
|
||||
- Other settings can be applied as well. If you don't require a deterministic order for the results, you can set max_threads to a value greater than one to speed up the query.
|
||||
|
||||
The query response contains the `result` column with a single row. The row has a value of
|
||||
[Boolean](../../sql-reference/data-types/boolean.md) type:
|
||||
|
||||
- 0 - The data in the table is corrupted.
|
||||
- 1 - The data maintains integrity.
|
||||
The query response depends on the value of contains `check_query_single_value_result` setting.
|
||||
In case of `check_query_single_value_result = 1` only `result` column with a single row is returned. Value inside this row is `1` if the integrity check is passed and `0` if data is corrupted.
|
||||
|
||||
With `check_query_single_value_result = 0` the query returns the following columns:
|
||||
- `part_path`: Indicates the path to the data part or file name.
|
||||
- `is_passed`: Returns 1 if the check for this part is successful, 0 otherwise.
|
||||
- `message`: Any additional messages related to the check, such as errors or success messages.
|
||||
|
||||
The `CHECK TABLE` query supports the following table engines:
|
||||
|
||||
@ -26,30 +46,15 @@ The `CHECK TABLE` query supports the following table engines:
|
||||
- [StripeLog](../../engines/table-engines/log-family/stripelog.md)
|
||||
- [MergeTree family](../../engines/table-engines/mergetree-family/mergetree.md)
|
||||
|
||||
Performed over the tables with another table engines causes an exception.
|
||||
Performed over the tables with another table engines causes an `NOT_IMPLEMETED` exception.
|
||||
|
||||
Engines from the `*Log` family do not provide automatic data recovery on failure. Use the `CHECK TABLE` query to track data loss in a timely manner.
|
||||
|
||||
## Checking the MergeTree Family Tables
|
||||
## Examples
|
||||
|
||||
For `MergeTree` family engines, if [check_query_single_value_result](../../operations/settings/settings.md#check_query_single_value_result) = 0, the `CHECK TABLE` query shows a check status for every individual data part of a table on the local server.
|
||||
By default `CHECK TABLE` query shows the general table check status:
|
||||
|
||||
```sql
|
||||
SET check_query_single_value_result = 0;
|
||||
CHECK TABLE test_table;
|
||||
```
|
||||
|
||||
```text
|
||||
┌─part_path─┬─is_passed─┬─message─┐
|
||||
│ all_1_4_1 │ 1 │ │
|
||||
│ all_1_4_2 │ 1 │ │
|
||||
└───────────┴───────────┴─────────┘
|
||||
```
|
||||
|
||||
If `check_query_single_value_result` = 1, the `CHECK TABLE` query shows the general table check status.
|
||||
|
||||
```sql
|
||||
SET check_query_single_value_result = 1;
|
||||
CHECK TABLE test_table;
|
||||
```
|
||||
|
||||
@ -59,11 +64,86 @@ CHECK TABLE test_table;
|
||||
└────────┘
|
||||
```
|
||||
|
||||
If you want to see the check status for every individual data part you may use `check_query_single_value_result` setting.
|
||||
|
||||
Also, to check a specific partition of the table, you can use the `PARTITION` keyword.
|
||||
|
||||
```sql
|
||||
CHECK TABLE t0 PARTITION ID '201003'
|
||||
FORMAT PrettyCompactMonoBlock
|
||||
SETTINGS check_query_single_value_result = 0
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
┌─part_path────┬─is_passed─┬─message─┐
|
||||
│ 201003_7_7_0 │ 1 │ │
|
||||
│ 201003_3_3_0 │ 1 │ │
|
||||
└──────────────┴───────────┴─────────┘
|
||||
```
|
||||
|
||||
Similarly, you can check a specific part of the table by using the `PART` keyword.
|
||||
|
||||
```sql
|
||||
CHECK TABLE t0 PART '201003_7_7_0'
|
||||
FORMAT PrettyCompactMonoBlock
|
||||
SETTINGS check_query_single_value_result = 0
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
┌─part_path────┬─is_passed─┬─message─┐
|
||||
│ 201003_7_7_0 │ 1 │ │
|
||||
└──────────────┴───────────┴─────────┘
|
||||
```
|
||||
|
||||
Note that when part does not exist, the query returns an error:
|
||||
|
||||
```sql
|
||||
CHECK TABLE t0 PART '201003_111_222_0'
|
||||
```
|
||||
|
||||
```text
|
||||
DB::Exception: No such data part '201003_111_222_0' to check in table 'default.t0'. (NO_SUCH_DATA_PART)
|
||||
```
|
||||
|
||||
### Receiving a 'Corrupted' Result
|
||||
|
||||
:::warning
|
||||
Disclaimer: The procedure described here, including the manual manipulating or removing files directly from the data directory, is for experimental or development environments only. Do **not** attempt this on a production server, as it may lead to data loss or other unintended consequences.
|
||||
:::
|
||||
|
||||
Remove the existing checksum file:
|
||||
|
||||
```bash
|
||||
rm /var/lib/clickhouse-server/data/default/t0/201003_3_3_0/checksums.txt
|
||||
```
|
||||
|
||||
```sql
|
||||
CHECK TABLE t0 PARTITION ID '201003'
|
||||
FORMAT PrettyCompactMonoBlock
|
||||
SETTINGS check_query_single_value_result = 0
|
||||
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
┌─part_path────┬─is_passed─┬─message──────────────────────────────────┐
|
||||
│ 201003_7_7_0 │ 1 │ │
|
||||
│ 201003_3_3_0 │ 1 │ Checksums recounted and written to disk. │
|
||||
└──────────────┴───────────┴──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
If the checksums.txt file is missing, it can be restored. It will be recalculated and rewritten during the execution of the CHECK TABLE command for the specific partition, and the status will still be reported as 'success.'"
|
||||
|
||||
|
||||
## If the Data Is Corrupted
|
||||
|
||||
If the table is corrupted, you can copy the non-corrupted data to another table. To do this:
|
||||
|
||||
1. Create a new table with the same structure as damaged table. To do this execute the query `CREATE TABLE <new_table_name> AS <damaged_table_name>`.
|
||||
2. Set the [max_threads](../../operations/settings/settings.md#settings-max_threads) value to 1 to process the next query in a single thread. To do this run the query `SET max_threads = 1`.
|
||||
2. Set the `max_threads` value to 1 to process the next query in a single thread. To do this run the query `SET max_threads = 1`.
|
||||
3. Execute the query `INSERT INTO <new_table_name> SELECT * FROM <damaged_table_name>`. This request copies the non-corrupted data from the damaged table to another table. Only the data before the corrupted part will be copied.
|
||||
4. Restart the `clickhouse-client` to reset the `max_threads` value.
|
||||
|
@ -62,7 +62,7 @@ Materialized views store data transformed by the corresponding [SELECT](../../..
|
||||
|
||||
When creating a materialized view without `TO [db].[table]`, you must specify `ENGINE` – the table engine for storing data.
|
||||
|
||||
When creating a materialized view with `TO [db].[table]`, you must not use `POPULATE`.
|
||||
When creating a materialized view with `TO [db].[table]`, you can't also use `POPULATE`.
|
||||
|
||||
A materialized view is implemented as follows: when inserting data to the table specified in `SELECT`, part of the inserted data is converted by this `SELECT` query, and the result is inserted in the view.
|
||||
|
||||
|
@ -334,6 +334,7 @@ For multiple `JOIN` clauses in a single `SELECT` query:
|
||||
|
||||
- Taking all the columns via `*` is available only if tables are joined, not subqueries.
|
||||
- The `PREWHERE` clause is not available.
|
||||
- The `USING` clause is not available.
|
||||
|
||||
For `ON`, `WHERE`, and `GROUP BY` clauses:
|
||||
|
||||
|
@ -473,7 +473,7 @@ Shows all [users](../../guides/sre/user-management/index.md#user-account-managem
|
||||
``` sql
|
||||
SHOW ACCESS
|
||||
```
|
||||
## SHOW CLUSTER(s)
|
||||
## SHOW CLUSTER(S)
|
||||
|
||||
Returns a list of clusters. All available clusters are listed in the [system.clusters](../../operations/system-tables/clusters.md) table.
|
||||
|
||||
@ -609,6 +609,18 @@ Result:
|
||||
└──────────────────┴────────┴─────────────┘
|
||||
```
|
||||
|
||||
## SHOW SETTING
|
||||
|
||||
``` sql
|
||||
SHOW SETTING <name>
|
||||
```
|
||||
|
||||
Outputs setting value for specified setting name.
|
||||
|
||||
**See Also**
|
||||
- [system.settings](../../operations/system-tables/settings.md) table
|
||||
|
||||
|
||||
## SHOW FILESYSTEM CACHES
|
||||
|
||||
```sql
|
||||
@ -651,3 +663,47 @@ If either `LIKE` or `ILIKE` clause is specified, the query returns a list of sys
|
||||
|
||||
**See Also**
|
||||
- [system.functions](../../operations/system-tables/functions.md) table
|
||||
|
||||
## SHOW MERGES
|
||||
|
||||
Returns a list of merges. All merges are listed in the [system.merges](../../operations/system-tables/merges.md) table.
|
||||
|
||||
|
||||
**Syntax**
|
||||
|
||||
``` sql
|
||||
SHOW MERGES [[NOT] LIKE|ILIKE '<table_name_pattern>'] [LIMIT <N>]
|
||||
```
|
||||
|
||||
**Examples**
|
||||
|
||||
Query:
|
||||
|
||||
``` sql
|
||||
SHOW MERGES;
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
┌─table──────┬─database─┬─estimate_complete─┬─────elapsed─┬─progress─┬─is_mutation─┬─size─────┬─mem───────┐
|
||||
│ your_table │ default │ 0.14 │ 0.365592338 │ 0.73 │ 0 │ 5.40 MiB │ 10.25 MiB │
|
||||
└────────────┴──────────┴───────────────────┴─────────────┴──────────┴─────────────┴────────────┴─────────┘
|
||||
|
||||
```
|
||||
|
||||
Query:
|
||||
|
||||
``` sql
|
||||
SHOW MERGES LIKE 'your_t%' LIMIT 1;
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
┌─table──────┬─database─┬─estimate_complete─┬─────elapsed─┬─progress─┬─is_mutation─┬─size─────┬─mem───────┐
|
||||
│ your_table │ default │ 0.05 │ 1.727629065 │ 0.97 │ 0 │ 5.40 MiB │ 10.25 MiB │
|
||||
└────────────┴──────────┴───────────────────┴─────────────┴──────────┴─────────────┴────────────┴─────────┘
|
||||
|
||||
```
|
||||
|
||||
|
@ -6,21 +6,21 @@ sidebar_label: "Обзор архитектуры ClickHouse"
|
||||
|
||||
# Обзор архитектуры ClickHouse {#overview-of-clickhouse-architecture}
|
||||
|
||||
ClickHouse - полноценная колоночная СУБД. Данные хранятся в колонках, а в процессе обработки - в массивах (векторах или фрагментах (chunk’ах) колонок). По возможности операции выполняются на массивах, а не на индивидуальных значениях. Это называется “векторизованное выполнения запросов” (vectorized query execution), и помогает снизить стоимость фактической обработки данных.
|
||||
ClickHouse — полноценная столбцовая СУБД. Данные хранятся в столбцах, а в процессе обработки — в массивах (векторах или фрагментах столбцов — chunks). По возможности операции выполняются на массивах, а не на индивидуальных значениях. Это называется “векторизованное выполнения запросов” (vectorized query execution), и помогает снизить стоимость фактической обработки данных.
|
||||
|
||||
> Эта идея не нова. Такой подход использовался в `APL` (A programming language, 1957) и его потомках: `A +` (диалект `APL`), `J` (1990), `K` (1993) и `Q` (язык программирования Kx Systems, 2003). Программирование на массивах (Array programming) используется в научных вычислительных системах. Эта идея не является чем-то новым и для реляционных баз данных: например, она используется в системе `VectorWise` (так же известной как Actian Vector Analytic Database от Actian Corporation).
|
||||
> Эта идея не нова. Такой подход использовался в языке `APL` (A programming language, 1957) и его потомках: `A +` (диалект `APL`), `J` (1990), `K` (1993) и `Q` (язык программирования Kx Systems, 2003). Программирование на массивах (array programming) используется в научных вычислительных системах. Эта идея не является чем-то новым и для реляционных баз данных: например, она используется в системе `VectorWise` (так же известной как Actian Vector Analytic Database от Actian Corporation).
|
||||
|
||||
Существует два различных подхода для увеличения скорости обработки запросов: выполнение векторизованного запроса и генерация кода во время выполнения (runtime code generation). В последнем случае код генерируется на лету для каждого типа запроса, удаляя все косвенные и динамические обращения. Ни один из этих подходов не имеет явного преимущества. Генерация кода во время выполнения выигрывает, если объединяет большое число операций, таким образом полностью используя вычислительные блоки и конвейер CPU. Выполнение векторизованного запроса может быть менее практично потому, что задействует временные векторы данных, которые должны быть записаны и прочитаны из кэша. Если временные данные не помещаются в L2 кэш, будут проблемы. С другой стороны выполнение векторизованного запроса упрощает использование SIMD инструкций CPU. [Научная работа](http://15721.courses.cs.cmu.edu/spring2016/papers/p5-sompolski.pdf) наших друзей показывает преимущества сочетания обоих подходов. ClickHouse использует выполнение векторизованного запроса и имеет ограниченную начальную поддержку генерации кода во время выполнения.
|
||||
Существует два различных подхода для увеличения скорости обработки запросов: выполнение векторизованного запроса и генерация кода во время выполнения (runtime code generation). В последнем случае код генерируется на лету для каждого типа запроса, и удаляются все косвенные и динамические обращения. Ни один из этих подходов не имеет явного преимущества. Генерация кода во время выполнения выигрывает, если объединяет большое число операций, таким образом полностью используя вычислительные блоки и конвейер CPU. Выполнение векторизованного запроса может быть менее практично потому, что задействует временные векторы данных, которые должны быть записаны и прочитаны из кэша. Если временные данные не помещаются в L2-кэш, будут проблемы. С другой стороны выполнение векторизованного запроса упрощает использование SIMD-инструкций CPU. [Научная работа](http://15721.courses.cs.cmu.edu/spring2016/papers/p5-sompolski.pdf) наших друзей показывает преимущества сочетания обоих подходов. ClickHouse использует выполнение векторизованного запроса и имеет ограниченную начальную поддержку генерации кода во время выполнения.
|
||||
|
||||
## Колонки {#columns}
|
||||
## Столбцы {#columns}
|
||||
|
||||
Для представления столбцов в памяти (фактически, фрагментов столбцов) используется интерфейс `IColumn`. Интерфейс предоставляет вспомогательные методы для реализации различных реляционных операторов. Почти все операции иммутабельные: они не изменяют оригинальных колонок, а создают новую с измененными значениями. Например, метод `IColumn :: filter` принимает фильтр - набор байт. Он используется для реляционных операторов `WHERE` и `HAVING`. Другой пример: метод `IColumn :: permute` используется для поддержки `ORDER BY`, метод `IColumn :: cut` - `LIMIT` и т. д.
|
||||
Для представления столбцов в памяти (фактически, фрагментов столбцов) используется интерфейс `IColumn`. Интерфейс предоставляет вспомогательные методы для реализации различных реляционных операторов. Почти все операции не изменяют данные (immutable): они не изменяют содержимое столбцов, а создают новые с изменёнными значениями. Например, метод `IColumn :: filter` принимает фильтр — набор байтов. Он используется для реляционных операторов `WHERE` и `HAVING`. Другой пример: метод `IColumn :: permute` используется для поддержки `ORDER BY`, метод `IColumn :: cut` — `LIMIT` и т. д.
|
||||
|
||||
Различные реализации `IColumn` (`ColumnUInt8`, `ColumnString` и т. д.) отвечают за распределение данных колонки в памяти. Для колонок целочисленного типа это один смежный массив, такой как `std :: vector`. Для колонок типа `String` и `Array` это два вектора: один для всех элементов массивов, располагающихся смежно, второй для хранения смещения до начала каждого массива. Также существует реализация `ColumnConst`, в которой хранится только одно значение в памяти, но выглядит как колонка.
|
||||
Различные реализации `IColumn` (`ColumnUInt8`, `ColumnString` и т. д.) отвечают за распределение данных столбца в памяти. Для столбцов целочисленного типа — это один смежный массив, такой как `std :: vector`. Для столбцов типа `String` и `Array` — это два вектора: один для всех элементов массивов, располагающихся смежно, второй для хранения смещения до начала каждого массива. Также существует реализация `ColumnConst`, в которой хранится только одно значение в памяти, но выглядит как столбец.
|
||||
|
||||
## Поля {#field}
|
||||
|
||||
Тем не менее, можно работать и с индивидуальными значениями. Для представления индивидуальных значений используется `Поле` (`Field`). `Field` - размеченное объединение `UInt64`, `Int64`, `Float64`, `String` и `Array`. `IColumn` имеет метод `оператор []` для получения значения по индексу n как `Field`, а также метод insert для добавления `Field` в конец колонки. Эти методы не очень эффективны, так как требуют временных объектов `Field`, представляющих индивидуальное значение. Есть более эффективные методы, такие как `insertFrom`, `insertRangeFrom` и т.д.
|
||||
Тем не менее, можно работать и с индивидуальными значениями. Для представления индивидуальных значений используется `Поле` (`Field`). `Field` — размеченное объединение `UInt64`, `Int64`, `Float64`, `String` и `Array`. `IColumn` имеет метод `оператор []` для получения значения по индексу n как `Field`, а также метод insert для добавления `Field` в конец колонки. Эти методы не очень эффективны, так как требуют временных объектов `Field`, представляющих индивидуальное значение. Есть более эффективные методы, такие как `insertFrom`, `insertRangeFrom` и т.д.
|
||||
|
||||
`Field` не несет в себе достаточно информации о конкретном типе данных в таблице. Например, `UInt8`, `UInt16`, `UInt32` и `UInt64` в `Field` представлены как `UInt64`.
|
||||
|
||||
@ -28,12 +28,12 @@ ClickHouse - полноценная колоночная СУБД. Данные
|
||||
|
||||
`IColumn` предоставляет методы для общих реляционных преобразований данных, но они не отвечают всем потребностям. Например, `ColumnUInt64` не имеет метода для вычисления суммы двух столбцов, а `ColumnString` не имеет метода для запуска поиска по подстроке. Эти бесчисленные процедуры реализованы вне `IColumn`.
|
||||
|
||||
Различные функции на колонках могут быть реализованы обобщенным, неэффективным путем, используя `IColumn` методы для извлечения значений `Field`, или специальным путем, используя знания о внутреннем распределение данных в памяти в конкретной реализации `IColumn`. Для этого функции приводятся к конкретному типу `IColumn` и работают напрямую с его внутренним представлением. Например, в `ColumnUInt64` есть метод `getData`, который возвращает ссылку на внутренний массив, чтение и заполнение которого, выполняется отдельной процедурой напрямую. Фактически, мы имеем "дырявые абстракции", обеспечивающие эффективные специализации различных процедур.
|
||||
Различные функции на столбцах могут быть реализованы обобщённым, неэффективным путем, используя `IColumn`-методы для извлечения значений `Field`, или специальным путем, используя знания о внутреннем распределение данных в памяти в конкретной реализации `IColumn`. Для этого функции приводятся к конкретному типу `IColumn` и работают напрямую с его внутренним представлением. Например, в `ColumnUInt64` есть метод `getData`, который возвращает ссылку на внутренний массив, чтение и заполнение которого, выполняется отдельной процедурой напрямую. Фактически, мы имеем “дырявые абстракции”, обеспечивающие эффективные специализации различных процедур.
|
||||
|
||||
## Типы данных (Data Types) {#data_types}
|
||||
|
||||
`IDataType` отвечает за сериализацию и десериализацию - чтение и запись фрагментов колонок или индивидуальных значений в бинарном или текстовом формате.
|
||||
`IDataType` точно соответствует типам данных в таблицах - `DataTypeUInt32`, `DataTypeDateTime`, `DataTypeString` и т. д.
|
||||
`IDataType` отвечает за сериализацию и десериализацию — чтение и запись фрагментов столбцов или индивидуальных значений в бинарном или текстовом формате.
|
||||
`IDataType` точно соответствует типам данных в таблицах — `DataTypeUInt32`, `DataTypeDateTime`, `DataTypeString` и т. д.
|
||||
|
||||
`IDataType` и `IColumn` слабо связаны друг с другом. Различные типы данных могут быть представлены в памяти с помощью одной реализации `IColumn`. Например, и `DataTypeUInt32`, и `DataTypeDateTime` в памяти представлены как `ColumnUInt32` или `ColumnConstUInt32`. В добавок к этому, один тип данных может быть представлен различными реализациями `IColumn`. Например, `DataTypeUInt8` может быть представлен как `ColumnUInt8` и `ColumnConstUInt8`.
|
||||
|
||||
@ -43,11 +43,11 @@ ClickHouse - полноценная колоночная СУБД. Данные
|
||||
|
||||
## Блоки (Block) {#block}
|
||||
|
||||
`Block` это контейнер, который представляет фрагмент (chunk) таблицы в памяти. Это набор троек - `(IColumn, IDataType, имя колонки)`. В процессе выполнения запроса, данные обрабатываются `Block`-ами. Если у нас есть `Block`, значит у нас есть данные (в объекте `IColumn`), информация о типе (в `IDataType`), которая говорит нам, как работать с колонкой, и имя колонки (оригинальное имя колонки таблицы или служебное имя, присвоенное для получения промежуточных результатов вычислений).
|
||||
`Block` — это контейнер, который представляет фрагмент (chunk) таблицы в памяти. Это набор троек — `(IColumn, IDataType, имя столбца)`. В процессе выполнения запроса, данные обрабатываются блоками (`Block`). Если есть `Block`, значит у нас есть данные (в объекте `IColumn`), информация о типе (в `IDataType`), которая говорит, как работать со столбцом, и имя столбца (оригинальное имя столбца таблицы или служебное имя, присвоенное для получения промежуточных результатов вычислений).
|
||||
|
||||
При вычислении некоторой функции на колонках в блоке мы добавляем еще одну колонку с результатами в блок, не трогая колонки аргументов функции, потому что операции иммутабельные. Позже ненужные колонки могут быть удалены из блока, но не модифицированы. Это удобно для устранения общих подвыражений.
|
||||
При вычислении некоторой функции на столбцах в блоке добавляется ещё один столбец с результатами в блок, не трогая колонки аргументов функции, потому что операции иммутабельные. Позже ненужные столбцы могут быть удалены из блока, но не модифицированы. Это удобно для устранения общих подвыражений.
|
||||
|
||||
Блоки создаются для всех обработанных фрагментов данных. Напоминаем, что одни и те же типы вычислений, имена колонок и типы переиспользуются в разных блоках и только данные колонок изменяются. Лучше разделить данные и заголовок блока потому, что в блоках маленького размера мы имеем большой оверхэд по временным строкам при копировании умных указателей (`shared_ptrs`) и имен колонок.
|
||||
Блоки создаются для всех обработанных фрагментов данных. Напоминаем, что одни и те же типы вычислений, имена столбцов и типы переиспользуются в разных блоках и только данные колонок изменяются. Лучше разделить данные и заголовок блока потому, что в блоках маленького размера мы имеем большой оверхэд по временным строкам при копировании умных указателей (`shared_ptrs`) и имен столбцов.
|
||||
|
||||
## Потоки блоков (Block Streams) {#block-streams}
|
||||
|
||||
@ -67,19 +67,19 @@ ClickHouse - полноценная колоночная СУБД. Данные
|
||||
|
||||
## Форматы {#formats}
|
||||
|
||||
Форматы данных реализуются с помощью потоков блоков. Есть форматы представления (presentational), пригодные только для вывода данных клиенту, такие как `Pretty` формат, который предоставляет только `IBlockOutputStream`. И есть форматы ввода/вывода, такие как `TabSeparated` или `JSONEachRow`.
|
||||
Форматы данных реализуются с помощью потоков блоков. Есть форматы представления (presentational), пригодные только для вывода данных клиенту, такие как `Pretty`-формат, который предоставляет только `IBlockOutputStream`. И есть форматы ввода-вывода, такие как `TabSeparated` или `JSONEachRow`.
|
||||
|
||||
Существуют также потоки строк: `IRowInputStream` и `IRowOutputStream`. Они позволяют вытягивать/выталкивать данные отдельными строками, а не блоками. Они нужны только для упрощения реализации ориентированных на строки форматов. Обертка `BlockInputStreamFromRowInputStream` и `BlockOutputStreamFromRowOutputStream` позволяет конвертировать потоки, ориентированные на строки, в обычные потоки, ориентированные на блоки.
|
||||
Существуют также потоки строк: `IRowInputStream` и `IRowOutputStream`. Они позволяют вытягивать и выталкивать данные отдельными строками, а не блоками. Они нужны только для упрощения реализации ориентированных на строки форматов. Обертка `BlockInputStreamFromRowInputStream` и `BlockOutputStreamFromRowOutputStream` позволяет конвертировать потоки, ориентированные на строки, в обычные потоки, ориентированные на блоки.
|
||||
|
||||
## I/O {#io}
|
||||
|
||||
Для байт-ориентированных ввода/вывода существуют абстрактные классы `ReadBuffer` и `WriteBuffer`. Они используются вместо C++ `iostream`. Не волнуйтесь: каждый зрелый проект C++ использует что-то другое вместо `iostream` по уважительным причинам.
|
||||
Для байт-ориентированного ввода-вывода существуют абстрактные классы `ReadBuffer` и `WriteBuffer`. Они используются вместо `iostream`. Не волнуйтесь: каждый зрелый проект C++ использует что-то другое вместо `iostream` по уважительным причинам.
|
||||
|
||||
`ReadBuffer` и `WriteBuffer` это просто непрерывный буфер и курсор, указывающий на позицию в этом буфере. Реализации могут как владеть так и не владеть памятью буфера. Существует виртуальный метод заполнения буфера следующими данными (для `ReadBuffer`) или сброса буфера куда-нибудь (например `WriteBuffer`). Виртуальные методы редко вызываются.
|
||||
`ReadBuffer` и `WriteBuffer` — это просто непрерывный буфер и курсор, указывающий на позицию в этом буфере. Реализации могут как владеть так и не владеть памятью буфера. Существует виртуальный метод заполнения буфера следующими данными (для `ReadBuffer`) или сброса буфера куда-нибудь (например `WriteBuffer`). Виртуальные методы редко вызываются.
|
||||
|
||||
Реализации `ReadBuffer`/`WriteBuffer` используются для работы с файлами и файловыми дескрипторами, а также сетевыми сокетами, для реализации сжатия (`CompressedWriteBuffer` инициализируется вместе с другим `WriteBuffer` и осуществляет сжатие данных перед записью в него), и для других целей – названия `ConcatReadBuffer`, `LimitReadBuffer`, и `HashingWriteBuffer` говорят сами за себя.
|
||||
|
||||
Буферы чтения/записи имеют дело только с байтами. В заголовочных файлах `ReadHelpers` и `WriteHelpers` объявлены некоторые функции, чтобы помочь с форматированием ввода/вывода. Например, есть помощники для записи числа в десятичном формате.
|
||||
Буферы чтения-записи имеют дело только с байтами. В заголовочных файлах `ReadHelpers` и `WriteHelpers` объявлены некоторые функции, чтобы помочь с форматированием ввода-вывода. Например, есть помощники для записи числа в десятичном формате.
|
||||
|
||||
Давайте посмотрим, что происходит, когда вы хотите вывести результат в `JSON` формате в стандартный вывод (stdout). У вас есть результирующий набор данных, готовый к извлечению из `IBlockInputStream`. Вы создаете `WriteBufferFromFileDescriptor(STDOUT_FILENO)` чтобы записать байты в stdout. Вы создаете `JSONRowOutputStream`, инициализируете с этим `WriteBuffer`'ом, чтобы записать строки `JSON` в stdout. Кроме того вы создаете `BlockOutputStreamFromRowOutputStream`, реализуя `IBlockOutputStream`. Затем вызывается `copyData` для передачи данных из `IBlockInputStream` в `IBlockOutputStream` и все работает. Внутренний `JSONRowOutputStream` будет писать в формате `JSON` различные разделители и вызвать `IDataType::serializeTextJSON` метод со ссылкой на `IColumn` и номер строки в качестве аргументов. Следовательно, `IDataType::serializeTextJSON` вызовет метод из `WriteHelpers.h`: например, `writeText` для числовых типов и `writeJSONString` для `DataTypeString`.
|
||||
|
||||
@ -87,13 +87,13 @@ ClickHouse - полноценная колоночная СУБД. Данные
|
||||
|
||||
Интерфейс `IStorage` служит для отображения таблицы. Различные движки таблиц являются реализациями этого интерфейса. Примеры `StorageMergeTree`, `StorageMemory` и так далее. Экземпляры этих классов являются просто таблицами.
|
||||
|
||||
Ключевые методы `IStorage` это `read` и `write`. Есть и другие варианты - `alter`, `rename`, `drop` и так далее. Метод `read` принимает следующие аргументы: набор столбцов для чтения из таблицы, `AST` запрос и желаемое количество потоков для вывода. Он возвращает один или несколько объектов `IBlockInputStream` и информацию о стадии обработки данных, которая была завершена внутри табличного движка во время выполнения запроса.
|
||||
Ключевые методы `IStorage` это `read` и `write`. Есть и другие варианты — `alter`, `rename`, `drop` и так далее. Метод `read` принимает следующие аргументы: набор столбцов для чтения из таблицы, `AST` запрос и желаемое количество потоков для вывода. Он возвращает один или несколько объектов `IBlockInputStream` и информацию о стадии обработки данных, которая была завершена внутри табличного движка во время выполнения запроса.
|
||||
|
||||
В большинстве случаев метод read отвечает только за чтение указанных столбцов из таблицы, а не за дальнейшую обработку данных. Вся дальнейшая обработка данных осуществляется интерпретатором запросов и не входит в сферу ответственности `IStorage`.
|
||||
|
||||
Но есть и заметные исключения:
|
||||
|
||||
- AST запрос, передающийся в метод `read`, может использоваться движком таблицы для получения информации о возможности использования индекса и считывания меньшего количества данных из таблицы.
|
||||
- AST-запрос, передающийся в метод `read`, может использоваться движком таблицы для получения информации о возможности использования индекса и считывания меньшего количества данных из таблицы.
|
||||
- Иногда движок таблиц может сам обрабатывать данные до определенного этапа. Например, `StorageDistributed` можно отправить запрос на удаленные серверы, попросить их обработать данные до этапа, когда данные с разных удаленных серверов могут быть объединены, и вернуть эти предварительно обработанные данные. Затем интерпретатор запросов завершает обработку данных.
|
||||
|
||||
Метод `read` может возвращать несколько объектов `IBlockInputStream`, позволяя осуществлять параллельную обработку данных. Эти несколько блочных входных потоков могут считываться из таблицы параллельно. Затем вы можете обернуть эти потоки различными преобразованиями (такими как вычисление выражений или фильтрация), которые могут быть вычислены независимо, и создать `UnionBlockInputStream` поверх них, чтобы читать из нескольких потоков параллельно.
|
||||
@ -104,15 +104,15 @@ ClickHouse - полноценная колоночная СУБД. Данные
|
||||
|
||||
> В качестве результата выполнения метода `read`, `IStorage` возвращает `QueryProcessingStage` – информацию о том, какие части запроса были обработаны внутри хранилища.
|
||||
|
||||
## Парсеры (Parsers) {#parsers}
|
||||
## Разборщики (Parsers) {#parsers}
|
||||
|
||||
Написанный от руки парсер, анализирующий запрос, работает по методу рекурсивного спуска. Например, `ParserSelectQuery` просто рекурсивно вызывает нижестоящие парсеры для различных частей запроса. Парсеры создают абстрактное синтаксическое дерево (`AST`). `AST` представлен узлами, которые являются экземплярами `IAST`.
|
||||
Написанный от руки разборщик, анализирующий запрос, работает по методу рекурсивного спуска. Например, `ParserSelectQuery` просто рекурсивно вызывает нижестоящие разборщики для различных частей запроса. Разборщики создают абстрактное синтаксическое дерево (`AST`). `AST` представлен узлами, которые являются экземплярами `IAST`.
|
||||
|
||||
> Генераторы парсеров не используются по историческим причинам.
|
||||
> Генераторы разборщиков не используются по историческим причинам.
|
||||
|
||||
## Интерпретаторы {#interpreters}
|
||||
|
||||
Интерпретаторы отвечают за создание конвейера выполнения запроса из `AST`. Есть простые интерпретаторы, такие как `InterpreterExistsQuery` и `InterpreterDropQuery` или более сложный `InterpreterSelectQuery`. Конвейер выполнения запроса представляет собой комбинацию входных и выходных потоков блоков. Например, результатом интерпретации `SELECT` запроса является `IBlockInputStream` для чтения результирующего набора данных; результат интерпретации `INSERT` запроса - это `IBlockOutputStream`, для записи данных, предназначенных для вставки; результат интерпретации `INSERT SELECT` запроса - это `IBlockInputStream`, который возвращает пустой результирующий набор при первом чтении, но копирует данные из `SELECT` к `INSERT`.
|
||||
Интерпретаторы отвечают за создание конвейера выполнения запроса из `AST`. Есть простые интерпретаторы, такие как `InterpreterExistsQuery` и `InterpreterDropQuery` или более сложный `InterpreterSelectQuery`. Конвейер выполнения запроса представляет собой комбинацию входных и выходных потоков блоков. Например, результатом интерпретации `SELECT` запроса является `IBlockInputStream` для чтения результирующего набора данных; результат интерпретации `INSERT` запроса — это `IBlockOutputStream`, для записи данных, предназначенных для вставки; результат интерпретации `INSERT SELECT` запроса — это `IBlockInputStream`, который возвращает пустой результирующий набор при первом чтении, но копирует данные из `SELECT` к `INSERT`.
|
||||
|
||||
`InterpreterSelectQuery` использует `ExpressionAnalyzer` и `ExpressionActions` механизмы для анализа запросов и преобразований. Именно здесь выполняется большинство оптимизаций запросов на основе правил. `ExpressionAnalyzer` написан довольно грязно и должен быть переписан: различные преобразования запросов и оптимизации должны быть извлечены в отдельные классы, чтобы позволить модульные преобразования или запросы.
|
||||
|
||||
@ -120,9 +120,9 @@ ClickHouse - полноценная колоночная СУБД. Данные
|
||||
|
||||
Существуют обычные функции и агрегатные функции. Агрегатные функции смотрите в следующем разделе.
|
||||
|
||||
Обычные функции не изменяют число строк и работают так, как если бы обрабатывали каждую строку независимо. В действительности же, функции вызываются не к отдельным строкам, а блокам данных для реализации векторизованного выполнения запросов.
|
||||
Обычные функции не изменяют число строк и работают так, как если бы обрабатывали каждую строку независимо. В действительности же функции вызываются не к отдельным строкам, а блокам данных для реализации векторизованного выполнения запросов.
|
||||
|
||||
Некоторые функции, такие как [blockSize](../sql-reference/functions/other-functions.md#function-blocksize), [rowNumberInBlock](../sql-reference/functions/other-functions.md#function-rownumberinblock), и [runningAccumulate](../sql-reference/functions/other-functions.md#runningaccumulate), эксплуатируют блочную обработку и нарушают независимость строк.
|
||||
Некоторые функции, такие как [blockSize](../sql-reference/functions/other-functions.md#function-blocksize), [rowNumberInBlock](../sql-reference/functions/other-functions.md#function-rownumberinblock), и [runningAccumulate](../sql-reference/functions/other-functions.md#runningaccumulate), используют блочную обработку и нарушают независимость строк.
|
||||
|
||||
ClickHouse имеет сильную типизацию, поэтому нет никакого неявного преобразования типов. Если функция не поддерживает определенную комбинацию типов, она создает исключение. Но функции могут работать (перегружаться) для многих различных комбинаций типов. Например, функция `plus` (для реализации `+` оператор) работает для любой комбинации числовых типов: `UInt8` + `Float32`, `UInt16` + `Int8` и так далее. Кроме того, некоторые вариадические функции, такие как `concat`, могут принимать любое количество аргументов.
|
||||
|
||||
@ -134,7 +134,7 @@ ClickHouse имеет сильную типизацию, поэтому нет
|
||||
|
||||
## Агрегатные функции {#aggregate-functions}
|
||||
|
||||
Агрегатные функции - это функции с состоянием (stateful). Они накапливают переданные значения в некотором состоянии и позволяют получать результаты из этого состояния. Работа с ними осуществляется с помощью интерфейса `IAggregateFunction`. Состояния могут быть как простыми (состояние для `AggregateFunctionCount` это всего лишь одна переменная типа `UInt64`) так и довольно сложными (состояние `AggregateFunctionUniqCombined` представляет собой комбинацию линейного массива, хэш-таблицы и вероятностной структуры данных `HyperLogLog`).
|
||||
Агрегатные функции — это функции с состоянием (stateful). Они накапливают переданные значения в некотором состоянии и позволяют получать результаты из этого состояния. Работа с ними осуществляется с помощью интерфейса `IAggregateFunction`. Состояния могут быть как простыми (состояние для `AggregateFunctionCount` это всего лишь одна переменная типа `UInt64`) так и довольно сложными (состояние `AggregateFunctionUniqCombined` представляет собой комбинацию линейного массива, хэш-таблицы и вероятностной структуры данных `HyperLogLog`).
|
||||
|
||||
Состояния распределяются в `Arena` (пул памяти) для работы с несколькими состояниями при выполнении запроса `GROUP BY` высокой кардинальности (большим числом уникальных данных). Состояния могут иметь нетривиальный конструктор и деструктор: например, сложные агрегатные состояния могут сами аллоцировать дополнительную память. Потому к созданию и уничтожению состояний, правильной передаче владения и порядку уничтожения следует уделять больше внимание.
|
||||
|
||||
@ -146,38 +146,38 @@ ClickHouse имеет сильную типизацию, поэтому нет
|
||||
|
||||
Сервер предоставляет несколько различных интерфейсов.
|
||||
|
||||
- HTTP интерфейс для любых сторонних клиентов.
|
||||
- TCP интерфейс для родного ClickHouse клиента и межсерверной взаимодействия при выполнении распределенных запросов.
|
||||
- HTTP-интерфейс для любых сторонних клиентов.
|
||||
- TCP-интерфейс для родного ClickHouse-клиента и межсерверной взаимодействия при выполнении распределенных запросов.
|
||||
- Интерфейс для передачи данных при репликации.
|
||||
|
||||
Внутри простой многопоточный сервер без корутин (coroutines), файберов (fibers) и т.д. Поскольку сервер не предназначен для обработки большого количества простых запросов, а ориентирован на обработку сложных запросов относительно низкой интенсивности, каждый из потоков может обрабатывать огромное количество аналитических запросов.
|
||||
Внутри простой многопоточный сервер без сопрограмм (coroutines), фиберов (fibers) и т. д. Поскольку сервер не предназначен для обработки большого количества простых запросов, а ориентирован на обработку сложных запросов относительно низкой интенсивности, каждый из потоков может обрабатывать огромное количество аналитических запросов.
|
||||
|
||||
Сервер инициализирует класс `Context`, где хранит необходимое для выполнения запроса окружение: список доступных баз данных, пользователей и прав доступа, настройки, кластеры, список процессов, журнал запросов и т.д. Это окружение используется интерпретаторами.
|
||||
Сервер инициализирует класс `Context`, где хранит необходимое для выполнения запроса окружение: список доступных баз данных, пользователей и прав доступа, настройки, кластеры, список процессов, журнал запросов и т. д. Это окружение используется интерпретаторами.
|
||||
|
||||
Мы поддерживаем полную обратную и прямую совместимость для TCP интерфейса: старые клиенты могут общаться с новыми серверами, а новые клиенты могут общаться со старыми серверами. Но мы не хотим поддерживать его вечно и прекращаем поддержку старых версий примерно через год.
|
||||
Мы поддерживаем полную обратную и прямую совместимость для TCP-интерфейса: старые клиенты могут общаться с новыми серверами, а новые клиенты могут общаться со старыми серверами. Но мы не хотим поддерживать его вечно и прекращаем поддержку старых версий примерно через год.
|
||||
|
||||
:::note Примечание
|
||||
Для всех сторонних приложений мы рекомендуем использовать HTTP интерфейс, потому что он прост и удобен в использовании. TCP интерфейс тесно связан с внутренними структурами данных: он использует внутренний формат для передачи блоков данных и использует специальное кадрирование для сжатых данных. Мы не выпустили библиотеку C для этого протокола, потому что потребовала бы линковки большей части кодовой базы ClickHouse, что непрактично.
|
||||
Для всех сторонних приложений мы рекомендуем использовать HTTP-интерфейс, потому что он прост и удобен в использовании. TCP-интерфейс тесно связан с внутренними структурами данных: он использует внутренний формат для передачи блоков данных и использует специальное кадрирование для сжатых данных. Мы не выпустили библиотеку C для этого протокола, потому что потребовала бы линковки большей части кодовой базы ClickHouse, что непрактично.
|
||||
:::
|
||||
## Выполнение распределенных запросов (Distributed Query Execution) {#distributed-query-execution}
|
||||
|
||||
Сервера в кластере в основном независимы. Вы можете создать `Распределенную` (`Distributed`) таблицу на одном или всех серверах в кластере. Такая таблица сама по себе не хранит данные - она только предоставляет возможность "просмотра" всех локальных таблиц на нескольких узлах кластера. При выполнении `SELECT` распределенная таблица переписывает запрос, выбирает удаленные узлы в соответствии с настройками балансировки нагрузки и отправляет им запрос. Распределенная таблица просит удаленные сервера обработать запрос до той стадии, когда промежуточные результаты с разных серверов могут быть объединены. Затем он получает промежуточные результаты и объединяет их. Распределенная таблица пытается возложить как можно больше работы на удаленные серверы и сократить объем промежуточных данных, передаваемых по сети.
|
||||
Сервера в кластере в основном независимы. Вы можете создать `распределённую` (`Distributed`) таблицу на одном или всех серверах в кластере. Такая таблица сама по себе не хранит данные — она только предоставляет возможность “просмотра” всех локальных таблиц на нескольких узлах кластера. При выполнении `SELECT` распределенная таблица переписывает запрос, выбирает удаленные узлы в соответствии с настройками балансировки нагрузки и отправляет им запрос. Распределенная таблица просит удаленные сервера обработать запрос до той стадии, когда промежуточные результаты с разных серверов могут быть объединены. Затем он получает промежуточные результаты и объединяет их. Распределенная таблица пытается возложить как можно больше работы на удаленные серверы и сократить объем промежуточных данных, передаваемых по сети.
|
||||
|
||||
Ситуация усложняется при использовании подзапросов в случае `IN` или `JOIN`, когда каждый из них использует таблицу `Distributed`. Есть разные стратегии для выполнения таких запросов.
|
||||
|
||||
Глобального плана выполнения распределенных запросов не существует. Каждый узел имеет собственный локальный план для своей части работы. У нас есть простое однонаправленное выполнение распределенных запросов: мы отправляем запросы на удаленные узлы и затем объединяем результаты. Но это невозможно для сложных запросов `GROUP BY` высокой кардинальности или запросов с большим числом временных данных в `JOIN`: в таких случаях нам необходимо перераспределить («reshuffle») данные между серверами, что требует дополнительной координации. ClickHouse не поддерживает выполнение запросов такого рода, и нам нужно работать над этим.
|
||||
Глобального плана выполнения распределённых запросов не существует. Каждый узел имеет собственный локальный план для своей части работы. У нас есть простое однонаправленное выполнение распределенных запросов: мы отправляем запросы на удаленные узлы и затем объединяем результаты. Но это невозможно для сложных запросов `GROUP BY` высокой кардинальности или запросов с большим числом временных данных в `JOIN`: в таких случаях нам необходимо перераспределить (“reshuffle”) данные между узлами, что требует дополнительной координации. ClickHouse не поддерживает выполнение запросов такого рода, и нам нужно работать над этим.
|
||||
|
||||
## Merge Tree {#merge-tree}
|
||||
|
||||
`MergeTree` - это семейство движков хранения, поддерживающих индексацию по первичному ключу. Первичный ключ может быть произвольным набором (кортежем) столбцов или выражений. Данные в таблице `MergeTree` хранятся "частями" (“parts”). Каждая часть хранит данные отсортированные по первичному ключу (данные упорядочены лексикографически). Все столбцы таблицы хранятся в отдельных файлах `column.bin` в этих частях. Файлы состоят из сжатых блоков. Каждый блок обычно содержит от 64 КБ до 1 МБ несжатых данных, в зависимости от среднего значения размера данных. Блоки состоят из значений столбцов, расположенных последовательно один за другим. Значения столбцов находятся в одинаковом порядке для каждого столбца (порядок определяется первичным ключом), поэтому, когда вы выполняете итерацию по многим столбцам, вы получаете значения для соответствующих строк.
|
||||
`MergeTree` — это семейство движков хранения, поддерживающих индексацию по первичному ключу. Первичный ключ может быть произвольным набором (кортежем) столбцов или выражений. Данные в таблице `MergeTree` хранятся “частями” (“parts”). Каждая часть хранит данные отсортированные по первичному ключу (данные упорядочены лексикографически). Все столбцы таблицы хранятся в отдельных файлах `column.bin` в этих частях. Файлы состоят из сжатых блоков. Каждый блок обычно содержит от 64 КБ до 1 МБ несжатых данных, в зависимости от среднего значения размера данных. Блоки состоят из значений столбцов, расположенных последовательно один за другим. Значения столбцов находятся в одинаковом порядке для каждого столбца (порядок определяется первичным ключом), поэтому, когда вы выполняете итерацию по многим столбцам, вы получаете значения для соответствующих строк.
|
||||
|
||||
Сам первичный ключ является "разреженным" ("sparse"). Он не относится к каждой отдельной строке, а только к некоторым диапазонам данных. Отдельный файл «primary.idx» имеет значение первичного ключа для каждой N-й строки, где N называется гранулярностью индекса ("index_granularity", обычно N = 8192). Также для каждого столбца у нас есть файлы `column.mrk` с "метками" ("marks"), которые обозначают смещение для каждой N-й строки в файле данных. Каждая метка представляет собой пару: смещение начала сжатого блока от начала файла и смещение к началу данных в распакованном блоке. Обычно сжатые блоки выравниваются по меткам, а смещение в распакованном блоке равно нулю. Данные для `primary.idx` всегда находятся в памяти, а данные для файлов `column.mrk` кэшируются.
|
||||
Сам первичный ключ является “разреженным” (sparse). Он не относится к каждой отдельной строке, а только к некоторым диапазонам данных. Отдельный файл «primary.idx» имеет значение первичного ключа для каждой N-й строки, где N называется гранулярностью индекса (index_granularity, обычно N = 8192). Также для каждого столбца у нас есть файлы `column.mrk` с “метками” (“marks”), которые обозначают смещение для каждой N-й строки в файле данных. Каждая метка представляет собой пару: смещение начала сжатого блока от начала файла и смещение к началу данных в распакованном блоке. Обычно сжатые блоки выравниваются по меткам, а смещение в распакованном блоке равно нулю. Данные для `primary.idx` всегда находятся в памяти, а данные для файлов `column.mrk` кэшируются.
|
||||
|
||||
Когда мы собираемся читать что-то из части данных `MergeTree`, мы смотрим содержимое `primary.idx` и определяем диапазоны, которые могут содержать запрошенные данные, затем просматриваем содержимое `column.mrk` и вычисляем смещение, чтобы начать чтение этих диапазонов. Из-за разреженности могут быть прочитаны лишние данные. ClickHouse не подходит для простых точечных запросов высокой интенсивности, потому что весь диапазон строк размером `index_granularity` должен быть прочитан для каждого ключа, а сжатый блок должен быть полностью распакован для каждого столбца. Мы сделали индекс разреженным, потому что мы должны иметь возможность поддерживать триллионы строк на один сервер без существенных расходов памяти на индексацию. Кроме того, поскольку первичный ключ является разреженным, он не уникален: он не может проверить наличие ключа в таблице во время INSERT. Вы можете иметь множество строк с одним и тем же ключом в таблице.
|
||||
|
||||
При выполнении `INSERT` для группы данных в `MergeTree`, элементы группы сортируются по первичному ключу и образует новую “часть”. Фоновые потоки периодически выбирают некоторые части и объединяют их в одну отсортированную часть, чтобы сохранить относительно небольшое количество частей. Вот почему он называется `MergeTree`. Конечно, объединение приводит к повышению интенсивности записи. Все части иммутабельные: они только создаются и удаляются, но не изменяются. Когда выполняется `SELECT`, он содержит снимок таблицы (набор частей). После объединения старые части также сохраняются в течение некоторого времени, чтобы упростить восстановление после сбоя, поэтому, если мы видим, что какая-то объединенная часть, вероятно, повреждена, мы можем заменить ее исходными частями.
|
||||
|
||||
`MergeTree` не является деревом LSM (Log-structured merge-tree — журнально-структурированное дерево со слиянием), потому что оно не содержит «memtable» и «log»: вставленные данные записываются непосредственно в файловую систему. Это делает его пригодным только для вставки данных в пакетах, а не по отдельным строкам и не очень часто - примерно раз в секунду это нормально, а тысячу раз в секунду - нет. Мы сделали это для простоты и потому, что мы уже вставляем данные в пакеты в наших приложениях.
|
||||
`MergeTree` не является LSM (Log-structured merge-tree — журнально-структурированным деревом со слиянием), потому что оно не содержит «memtable» и «log»: вставленные данные записываются непосредственно в файловую систему. Это делает его пригодным только для вставки данных в пакетах, а не по отдельным строкам и не очень часто — примерно раз в секунду это нормально, а тысячу раз в секунду — нет. Мы сделали это для простоты и потому, что мы уже вставляем данные в пакеты в наших приложениях.
|
||||
|
||||
> Таблицы `MergeTree` могут иметь только один (первичный) индекс: вторичных индексов нет. Было бы неплохо разрешить несколько физических представлениям в одной логической таблице, например, хранить данные в более чем одном физическом порядке или даже разрешить представления с предварительно агрегированными данными вместе с исходными данными.
|
||||
|
||||
@ -189,16 +189,16 @@ ClickHouse имеет сильную типизацию, поэтому нет
|
||||
|
||||
Репликация реализована в движке таблицы `ReplicatedMergeTree`. Путь в `ZooKeeper` указывается в качестве параметра движка. Все таблицы с одинаковым путем в `ZooKeeper` становятся репликами друг друга: они синхронизируют свои данные и поддерживают согласованность. Реплики можно добавлять и удалять динамически, просто создавая или удаляя таблицу.
|
||||
|
||||
Репликация использует асинхронную multi-master схему. Вы можете вставить данные в любую реплику, которая имеет открытую сессию в `ZooKeeper`, и данные реплицируются на все другие реплики асинхронно. Поскольку ClickHouse не поддерживает UPDATE, репликация исключает конфликты (conflict-free replication). Поскольку подтверждение вставок кворумом не реализовано, только что вставленные данные могут быть потеряны в случае сбоя одного узла.
|
||||
Репликация использует асинхронную multi-master-схему. Вы можете вставить данные в любую реплику, которая имеет открытую сессию в `ZooKeeper`, и данные реплицируются на все другие реплики асинхронно. Поскольку ClickHouse не поддерживает UPDATE, репликация исключает конфликты (conflict-free replication). Поскольку подтверждение вставок кворумом не реализовано, только что вставленные данные могут быть потеряны в случае сбоя одного узла.
|
||||
|
||||
Метаданные для репликации хранятся в `ZooKeeper`. Существует журнал репликации, в котором перечислены действия, которые необходимо выполнить. Среди этих действий: получить часть (get the part); объединить части (merge parts); удалить партицию (drop a partition) и так далее. Каждая реплика копирует журнал репликации в свою очередь, а затем выполняет действия из очереди. Например, при вставке в журнале создается действие «получить часть» (get the part), и каждая реплика загружает эту часть. Слияния координируются между репликами, чтобы получить идентичные до байта результаты. Все части объединяются одинаково на всех репликах. Одна из реплик-лидеров инициирует новое слияние кусков первой и записывает действия «слияния частей» в журнал. Несколько реплик (или все) могут быть лидерами одновременно. Реплике можно запретить быть лидером с помощью `merge_tree` настройки `replicated_can_become_leader`.
|
||||
Метаданные для репликации хранятся в `ZooKeeper`. Существует журнал репликации, в котором перечислены действия, которые необходимо выполнить. Среди этих действий: получить часть (get the part); объединить части (merge parts); удалить партицию (drop a partition) и так далее. Каждая реплика копирует журнал репликации в свою очередь, а затем выполняет действия из очереди. Например, при вставке в журнале создается действие “получить часть” (get the part), и каждая реплика загружает эту часть. Слияния координируются между репликами, чтобы получить идентичные до байта результаты. Все части объединяются одинаково на всех репликах. Одна из реплик-лидеров инициирует новое слияние кусков первой и записывает действия “слияния частей” в журнал. Несколько реплик (или все) могут быть лидерами одновременно. Реплике можно запретить быть лидером с помощью `merge_tree` настройки `replicated_can_become_leader`.
|
||||
|
||||
Репликация является физической: между узлами передаются только сжатые части, а не запросы. Слияния обрабатываются на каждой реплике независимо, в большинстве случаев, чтобы снизить затраты на сеть, во избежание усиления роли сети. Крупные объединенные части отправляются по сети только в случае значительной задержки репликации.
|
||||
|
||||
Кроме того, каждая реплика сохраняет свое состояние в `ZooKeeper` в виде набора частей и его контрольных сумм. Когда состояние в локальной файловой системе расходится с эталонным состоянием в `ZooKeeper`, реплика восстанавливает свою согласованность путем загрузки отсутствующих и поврежденных частей из других реплик. Когда в локальной файловой системе есть неожиданные или испорченные данные, ClickHouse не удаляет их, а перемещает в отдельный каталог и забывает об этом.
|
||||
|
||||
:::note Примечание
|
||||
Кластер ClickHouse состоит из независимых шардов, а каждый шард состоит из реплик. Кластер **не является эластичным** (not elastic), поэтому после добавления нового шарда данные не будут автоматически распределены между ними. Вместо этого нужно изменить настройки, чтобы выровнять нагрузку на кластер. Эта реализация дает вам больший контроль, и вполне приемлема для относительно небольших кластеров, таких как десятки узлов. Но для кластеров с сотнями узлов, которые мы используем в эксплуатации, такой подход становится существенным недостатком. Движки таблиц, которые охватывают весь кластер с динамически реплицируемыми областями, которые могут быть автоматически разделены и сбалансированы между кластерами, еще предстоит реализовать.
|
||||
Кластер ClickHouse состоит из независимых сегментов (shards), а каждый сегмент состоит из реплик. Кластер **не является эластичным** (not elastic), поэтому после добавления нового сегмента данные не будут автоматически распределены между ними. Вместо этого нужно изменить настройки, чтобы выровнять нагрузку на кластер. Эта реализация дает вам больший контроль, и вполне приемлема для относительно небольших кластеров, таких как десятки узлов. Но для кластеров с сотнями узлов, которые мы используем в эксплуатации, такой подход становится существенным недостатком. Движки таблиц, которые охватывают весь кластер с динамически реплицируемыми областями, которые могут быть автоматически разделены и сбалансированы между кластерами, еще предстоит реализовать.
|
||||
:::
|
||||
|
||||
{## [Original article](https://clickhouse.com/docs/ru/development/architecture/) ##}
|
||||
|
@ -6,7 +6,7 @@ sidebar_label: "Что такое ClickHouse"
|
||||
|
||||
# Что такое ClickHouse {#what-is-clickhouse}
|
||||
|
||||
ClickHouse - столбцовая система управления базами данных (СУБД) для онлайн обработки аналитических запросов (OLAP).
|
||||
ClickHouse — столбцовая система управления базами данных (СУБД) для онлайн-обработки аналитических запросов (OLAP).
|
||||
|
||||
В обычной, «строковой» СУБД, данные хранятся в таком порядке:
|
||||
|
||||
@ -19,10 +19,10 @@ ClickHouse - столбцовая система управления базам
|
||||
|
||||
То есть, значения, относящиеся к одной строке, физически хранятся рядом.
|
||||
|
||||
Примеры строковых СУБД: MySQL, Postgres, MS SQL Server.
|
||||
Примеры строковых СУБД: MySQL, PostgreSQL, MS SQL Server.
|
||||
{: .grey }
|
||||
|
||||
В столбцовых СУБД, данные хранятся в таком порядке:
|
||||
В столбцовых СУБД данные хранятся в таком порядке:
|
||||
|
||||
| Строка: | #0 | #1 | #2 | #N |
|
||||
|-------------|---------------------|---------------------|---------------------|-----|
|
||||
@ -33,37 +33,37 @@ ClickHouse - столбцовая система управления базам
|
||||
| EventTime: | 2016-05-18 05:19:20 | 2016-05-18 08:10:20 | 2016-05-18 07:38:00 | … |
|
||||
|
||||
В примерах изображён только порядок расположения данных.
|
||||
То есть, значения из разных столбцов хранятся отдельно, а данные одного столбца - вместе.
|
||||
То есть значения из разных столбцов хранятся отдельно, а данные одного столбца — вместе.
|
||||
|
||||
Примеры столбцовых СУБД: Vertica, Paraccel (Actian Matrix, Amazon Redshift), Sybase IQ, Exasol, Infobright, InfiniDB, MonetDB (VectorWise, Actian Vector), LucidDB, SAP HANA, Google Dremel, Google PowerDrill, Druid, kdb+.
|
||||
{: .grey }
|
||||
|
||||
Разный порядок хранения данных лучше подходит для разных сценариев работы.
|
||||
Сценарий работы с данными - это то, какие производятся запросы, как часто и в каком соотношении; сколько читается данных на запросы каждого вида - строк, столбцов, байт; как соотносятся чтения и обновления данных; какой рабочий размер данных и насколько локально он используется; используются ли транзакции и с какой изолированностью; какие требования к дублированию данных и логической целостности; требования к задержкам на выполнение и пропускной способности запросов каждого вида и т. п.
|
||||
Сценарий работы с данными — это то, какие производятся запросы, как часто и в каком соотношении; сколько читается данных на запросы каждого вида — строк, столбцов, байтов; как соотносятся чтения и обновления данных; какой рабочий размер данных и насколько локально он используется; используются ли транзакции и с какой изолированностью; какие требования к дублированию данных и логической целостности; требования к задержкам на выполнение и пропускной способности запросов каждого вида и т. п.
|
||||
|
||||
Чем больше нагрузка на систему, тем более важной становится специализация под сценарий работы, и тем более конкретной становится эта специализация. Не существует системы, одинаково хорошо подходящей под существенно различные сценарии работы. Если система подходит под широкое множество сценариев работы, то при достаточно большой нагрузке, система будет справляться со всеми сценариями работы плохо, или справляться хорошо только с одним из сценариев работы.
|
||||
|
||||
## Ключевые особенности OLAP сценария работы {#kliuchevye-osobennosti-olap-stsenariia-raboty}
|
||||
## Ключевые особенности OLAP-сценария работы {#kliuchevye-osobennosti-olap-stsenariia-raboty}
|
||||
|
||||
- подавляющее большинство запросов - на чтение;
|
||||
- подавляющее большинство запросов — на чтение;
|
||||
- данные обновляются достаточно большими пачками (\> 1000 строк), а не по одной строке, или не обновляются вообще;
|
||||
- данные добавляются в БД, но не изменяются;
|
||||
- при чтении, вынимается достаточно большое количество строк из БД, но только небольшое подмножество столбцов;
|
||||
- таблицы являются «широкими», то есть, содержат большое количество столбцов;
|
||||
- при чтении «вынимается» достаточно большое количество строк из БД, но только небольшое подмножество столбцов;
|
||||
- таблицы являются «широкими», то есть содержат большое количество столбцов;
|
||||
- запросы идут сравнительно редко (обычно не более сотни в секунду на сервер);
|
||||
- при выполнении простых запросов, допустимы задержки в районе 50 мс;
|
||||
- значения в столбцах достаточно мелкие - числа и небольшие строки (пример - 60 байт на URL);
|
||||
- требуется высокая пропускная способность при обработке одного запроса (до миллиардов строк в секунду на один сервер);
|
||||
- значения в столбцах достаточно мелкие — числа и небольшие строки (например, 60 байт на URL);
|
||||
- требуется высокая пропускная способность при обработке одного запроса (до миллиардов строк в секунду на один узел);
|
||||
- транзакции отсутствуют;
|
||||
- низкие требования к консистентности данных;
|
||||
- в запросе одна большая таблица, все таблицы кроме одной маленькие;
|
||||
- результат выполнения запроса существенно меньше исходных данных - то есть, данные фильтруются или агрегируются; результат выполнения помещается в оперативку на одном сервере.
|
||||
- результат выполнения запроса существенно меньше исходных данных — то есть данные фильтруются или агрегируются; результат выполнения помещается в оперативную память одного узла.
|
||||
|
||||
Легко видеть, что OLAP сценарий работы существенно отличается от других распространённых сценариев работы (например, OLTP или Key-Value сценариев работы). Таким образом, не имеет никакого смысла пытаться использовать OLTP или Key-Value БД для обработки аналитических запросов, если вы хотите получить приличную производительность («выше плинтуса»). Например, если вы попытаетесь использовать для аналитики MongoDB или Redis - вы получите анекдотически низкую производительность по сравнению с OLAP-СУБД.
|
||||
Легко видеть, что OLAP-сценарий работы существенно отличается от других распространённых сценариев работы (например, OLTP или Key-Value сценариев работы). Таким образом, не имеет никакого смысла пытаться использовать OLTP-системы или системы класса «ключ — значение» для обработки аналитических запросов, если вы хотите получить приличную производительность («выше плинтуса»). Например, если вы попытаетесь использовать для аналитики MongoDB или Redis — вы получите анекдотически низкую производительность по сравнению с OLAP-СУБД.
|
||||
|
||||
## Причины, по которым столбцовые СУБД лучше подходят для OLAP сценария {#prichiny-po-kotorym-stolbtsovye-subd-luchshe-podkhodiat-dlia-olap-stsenariia}
|
||||
## Причины, по которым столбцовые СУБД лучше подходят для OLAP-сценария {#prichiny-po-kotorym-stolbtsovye-subd-luchshe-podkhodiat-dlia-olap-stsenariia}
|
||||
|
||||
Столбцовые СУБД лучше (от 100 раз по скорости обработки большинства запросов) подходят для OLAP сценария работы. Причины в деталях будут разъяснены ниже, а сам факт проще продемонстрировать визуально:
|
||||
Столбцовые СУБД лучше (от 100 раз по скорости обработки большинства запросов) подходят для OLAP-сценария работы. Причины в деталях будут разъяснены ниже, а сам факт проще продемонстрировать визуально:
|
||||
|
||||
**Строковые СУБД**
|
||||
|
||||
@ -94,6 +94,6 @@ ClickHouse - столбцовая система управления базам
|
||||
|
||||
2. Кодогенерация. Для запроса генерируется код, в котором подставлены все косвенные вызовы.
|
||||
|
||||
В «обычных» БД этого не делается, так как не имеет смысла при выполнении простых запросов. Хотя есть исключения. Например, в MemSQL кодогенерация используется для уменьшения latency при выполнении SQL запросов. Для сравнения, в аналитических СУБД требуется оптимизация throughput, а не latency.
|
||||
В «обычных» СУБД этого не делается, так как не имеет смысла при выполнении простых запросов. Хотя есть исключения. Например, в MemSQL кодогенерация используется для уменьшения времени отклика при выполнении SQL-запросов. Для сравнения: в аналитических СУБД требуется оптимизация по пропускной способности (throughput, ГБ/с), а не времени отклика (latency, с).
|
||||
|
||||
Стоит заметить, что для эффективности по CPU требуется, чтобы язык запросов был декларативным (SQL, MDX) или хотя бы векторным (J, K). То есть, чтобы запрос содержал циклы только в неявном виде, открывая возможности для оптимизации.
|
||||
Стоит заметить, что для эффективности по CPU требуется, чтобы язык запросов был декларативным (SQL, MDX) или хотя бы векторным (J, K). То есть необходимо, чтобы запрос содержал циклы только в неявном виде, открывая возможности для оптимизации.
|
||||
|
@ -8,11 +8,11 @@ sidebar_label: "Отличительные возможности ClickHouse"
|
||||
|
||||
## По-настоящему столбцовая СУБД {#po-nastoiashchemu-stolbtsovaia-subd}
|
||||
|
||||
В по-настоящему столбцовой СУБД рядом со значениями не хранится никаких лишних данных. Например, должны поддерживаться значения постоянной длины, чтобы не хранить рядом со значениями типа «число» их длины. Для примера, миллиард значений типа UInt8 должен действительно занимать в несжатом виде около 1GB, иначе это сильно ударит по эффективности использования CPU. Очень важно хранить данные компактно (без «мусора») в том числе в несжатом виде, так как скорость разжатия (использование CPU) зависит, в основном, от объёма несжатых данных.
|
||||
В по-настоящему столбцовой СУБД рядом со значениями не хранится никаких лишних данных. Например, должны поддерживаться значения постоянной длины, чтобы не хранить рядом со значениями типа «число» их длины. Для примера, миллиард значений типа UInt8 должен действительно занимать в несжатом виде около 1 ГБ, иначе это сильно ударит по эффективности использования CPU. Очень важно хранить данные компактно (без «мусора») в том числе в несжатом виде, так как скорость разжатия (использование CPU) зависит, в основном, от объёма несжатых данных.
|
||||
|
||||
Этот пункт пришлось выделить, так как существуют системы, которые могут хранить значения отдельных столбцов по отдельности, но не могут эффективно выполнять аналитические запросы в силу оптимизации под другой сценарий работы. Примеры: HBase, BigTable, Cassandra, HyperTable. В этих системах вы получите пропускную способность в районе сотен тысяч строк в секунду, но не сотен миллионов строк в секунду.
|
||||
|
||||
Также стоит заметить, что ClickHouse является системой управления базами данных, а не одной базой данных. То есть, ClickHouse позволяет создавать таблицы и базы данных в runtime, загружать данные и выполнять запросы без переконфигурирования и перезапуска сервера.
|
||||
Также стоит заметить, что ClickHouse является системой управления базами данных, а не системой для одной базой данных. То есть, ClickHouse позволяет создавать таблицы и базы данных во время выполнения (runtime), загружать данные и выполнять запросы без переконфигурирования и перезапуска сервера.
|
||||
|
||||
## Сжатие данных {#szhatie-dannykh}
|
||||
|
||||
@ -20,7 +20,7 @@ sidebar_label: "Отличительные возможности ClickHouse"
|
||||
|
||||
## Хранение данных на диске {#khranenie-dannykh-na-diske}
|
||||
|
||||
Многие столбцовые СУБД (SAP HANA, Google PowerDrill) могут работать только в оперативной памяти. Такой подход стимулирует выделять больший бюджет на оборудование, чем фактически требуется для анализа в реальном времени. ClickHouse спроектирован для работы на обычных жестких дисках, что обеспечивает низкую стоимость хранения на гигабайт данных, но SSD и дополнительная оперативная память тоже полноценно используются, если доступны.
|
||||
Многие столбцовые СУБД (SAP HANA, Google PowerDrill) могут работать только в оперативной памяти. Такой подход стимулирует выделять больший бюджет на оборудование, чем фактически требуется для анализа в реальном времени. ClickHouse спроектирован для работы на обычных жестких дисках, что обеспечивает низкую стоимость хранения на гигабайт данных. При этом твердотельные накопители (SSD) и дополнительная оперативная память тоже полноценно используются, если доступны.
|
||||
|
||||
## Параллельная обработка запроса на многих процессорных ядрах {#parallelnaia-obrabotka-zaprosa-na-mnogikh-protsessornykh-iadrakh}
|
||||
|
||||
@ -29,11 +29,11 @@ sidebar_label: "Отличительные возможности ClickHouse"
|
||||
## Распределённая обработка запроса на многих серверах {#raspredelionnaia-obrabotka-zaprosa-na-mnogikh-serverakh}
|
||||
|
||||
Почти все перечисленные ранее столбцовые СУБД не поддерживают распределённую обработку запроса.
|
||||
В ClickHouse данные могут быть расположены на разных шардах. Каждый шард может представлять собой группу реплик, которые используются для отказоустойчивости. Запрос будет выполнен на всех шардах параллельно. Это делается прозрачно для пользователя.
|
||||
В ClickHouse данные могут быть расположены на разных сегментах (shards). Каждый сегмент может представлять собой группу реплик, которые используются для отказоустойчивости. Запрос будет выполнен на всех сегментах параллельно. Это делается прозрачно для пользователя.
|
||||
|
||||
## Поддержка SQL {#sql-support}
|
||||
|
||||
ClickHouse поддерживает [декларативный язык запросов на основе SQL](../sql-reference/index.md) и во [многих случаях](../sql-reference/ansi.mdx) совпадающий с SQL стандартом.
|
||||
ClickHouse поддерживает [декларативный язык запросов на основе SQL](../sql-reference/index.md) и во [многих случаях](../sql-reference/ansi.mdx) совпадающий с SQL-стандартом.
|
||||
|
||||
Поддерживаются [GROUP BY](../sql-reference/statements/select/group-by.md), [ORDER BY](../sql-reference/statements/select/order-by.md), подзапросы в секциях [FROM](../sql-reference/statements/select/from.md), [IN](../sql-reference/operators/in.md), [JOIN](../sql-reference/statements/select/join.md), [функции window](../sql-reference/window-functions/index.mdx), а также скалярные подзапросы.
|
||||
|
||||
@ -41,17 +41,17 @@ ClickHouse поддерживает [декларативный язык зап
|
||||
|
||||
## Векторный движок {#vektornyi-dvizhok}
|
||||
|
||||
Данные не только хранятся по столбцам, но и обрабатываются по векторам - кусочкам столбцов. За счёт этого достигается высокая эффективность по CPU.
|
||||
Данные не только хранятся по столбцам, но и обрабатываются по векторам — фрагментам столбцов. За счёт этого достигается высокая эффективность по CPU.
|
||||
|
||||
## Обновление данных в реальном времени {#obnovlenie-dannykh-v-realnom-vremeni}
|
||||
|
||||
ClickHouse поддерживает таблицы с первичным ключом. Для того, чтобы можно было быстро выполнять запросы по диапазону первичного ключа, данные инкрементально сортируются с помощью merge дерева. За счёт этого, поддерживается постоянное добавление данных в таблицу. Блокировки при добавлении данных отсутствуют.
|
||||
ClickHouse поддерживает таблицы с первичным ключом. Для того, чтобы можно было быстро выполнять запросы по диапазону первичного ключа, данные инкрементально сортируются с помощью дерева со слиянием (merge tree). За счёт этого поддерживается постоянное добавление данных в таблицу. Блокировки при добавлении данных отсутствуют.
|
||||
|
||||
## Наличие индекса {#nalichie-indeksa}
|
||||
|
||||
Физическая сортировка данных по первичному ключу позволяет получать данные для конкретных его значений или их диапазонов с низкими задержками - менее десятков миллисекунд.
|
||||
Физическая сортировка данных по первичному ключу позволяет получать данные для конкретных его значений или их диапазонов с низкими задержками — менее десятков миллисекунд.
|
||||
|
||||
## Подходит для онлайн запросов {#podkhodit-dlia-onlain-zaprosov}
|
||||
## Подходит для онлайн-запросов {#podkhodit-dlia-onlain-zaprosov}
|
||||
|
||||
Низкие задержки позволяют не откладывать выполнение запроса и не подготавливать ответ заранее, а выполнять его именно в момент загрузки страницы пользовательского интерфейса. То есть, в режиме онлайн.
|
||||
|
||||
@ -60,12 +60,12 @@ ClickHouse поддерживает таблицы с первичным клю
|
||||
ClickHouse предоставляет различные способы разменять точность вычислений на производительность:
|
||||
|
||||
1. Система содержит агрегатные функции для приближённого вычисления количества различных значений, медианы и квантилей.
|
||||
2. Поддерживается возможность выполнить запрос на основе части (выборки) данных и получить приближённый результат. При этом, с диска будет считано пропорционально меньше данных.
|
||||
2. Поддерживается возможность выполнить запрос на основе части (выборки) данных и получить приближённый результат. При этом с диска будет считано пропорционально меньше данных.
|
||||
3. Поддерживается возможность выполнить агрегацию не для всех ключей, а для ограниченного количества первых попавшихся ключей. При выполнении некоторых условий на распределение ключей в данных, это позволяет получить достаточно точный результат с использованием меньшего количества ресурсов.
|
||||
|
||||
## Репликация данных и поддержка целостности {#replikatsiia-dannykh-i-podderzhka-tselostnosti}
|
||||
|
||||
Используется асинхронная multimaster репликация. После записи на любую доступную реплику, данные распространяются на все остальные реплики в фоне. Система поддерживает полную идентичность данных на разных репликах. Восстановление после большинства сбоев осуществляется автоматически, а в сложных случаях — полуавтоматически. При необходимости, можно [включить кворумную запись](../operations/settings/settings.md) данных.
|
||||
Используется асинхронная multimaster-репликация. После записи на любую доступную реплику, данные распространяются на все остальные реплики в фоне. Система поддерживает полную идентичность данных на разных репликах. Восстановление после большинства сбоев осуществляется автоматически, а в сложных случаях — полуавтоматически. При необходимости, можно [включить кворумную запись](../operations/settings/settings.md) данных.
|
||||
|
||||
Подробнее смотрите раздел [Репликация данных](../engines/table-engines/mergetree-family/replication.md).
|
||||
|
||||
|
@ -3838,6 +3838,18 @@ SELECT * FROM positional_arguments ORDER BY 2,3;
|
||||
|
||||
Значение по умолчанию: `0`.
|
||||
|
||||
## date_time_overflow_behavior {#date_time_overflow_behavior}
|
||||
|
||||
Задаёт поведение при преобразовании [Date](../../sql-reference/data-types/date.md), [Date32](../../sql-reference/data-types/date32.md), [DateTime](../../sql-reference/data-types/datetime.md), [DateTime64](../../sql-reference/data-types/datetime64.md), а также численных типов данных к Date, Date32, DateTime, DateTime64 в случае, если результат выходит за пределы диапазона значений необходимого типа.
|
||||
|
||||
Возможные значения:
|
||||
|
||||
- `ignore` — Молча игнорирует переполнение. В таком случае, результатом будет случайное значение.
|
||||
- `throw` — Выкинуть исключение при переполнении.
|
||||
- `saturate` — Молча округлить до ближайшего (то есть наибольшего или наименьшего) значения из диапазона значений результата.
|
||||
|
||||
Значение по умолчанию: `ignore`.
|
||||
|
||||
## optimize_move_to_prewhere {#optimize_move_to_prewhere}
|
||||
|
||||
Включает или отключает автоматическую оптимизацию [PREWHERE](../../sql-reference/statements/select/prewhere.md) в запросах [SELECT](../../sql-reference/statements/select/index.md).
|
||||
|
@ -46,6 +46,7 @@ CLICKHOUSE_Format=(
|
||||
ArrowStream
|
||||
Avro
|
||||
AvroConfluent
|
||||
BSONEachRow
|
||||
CSV
|
||||
CSVWithNames
|
||||
CSVWithNamesAndTypes
|
||||
@ -56,6 +57,7 @@ CLICKHOUSE_Format=(
|
||||
CustomSeparatedIgnoreSpacesWithNamesAndTypes
|
||||
CustomSeparatedWithNames
|
||||
CustomSeparatedWithNamesAndTypes
|
||||
DWARF
|
||||
HiveText
|
||||
JSON
|
||||
JSONAsObject
|
||||
@ -74,7 +76,7 @@ CLICKHOUSE_Format=(
|
||||
JSONEachRow
|
||||
JSONEachRowWithProgress
|
||||
JSONLines
|
||||
JSONStringEachRow
|
||||
JSONObjectEachRow
|
||||
JSONStrings
|
||||
JSONStringsEachRow
|
||||
JSONStringsEachRowWithProgress
|
||||
@ -90,14 +92,19 @@ CLICKHOUSE_Format=(
|
||||
Null
|
||||
ODBCDriver2
|
||||
ORC
|
||||
One
|
||||
Parquet
|
||||
ParquetMetadata
|
||||
PostgreSQLWire
|
||||
Pretty
|
||||
PrettyCompact
|
||||
PrettyCompactMonoBlock
|
||||
PrettyCompactNoEscapes
|
||||
PrettyCompactNoEscapesMonoBlock
|
||||
PrettyJSONEachRow
|
||||
PrettyJSONLines
|
||||
PrettyMonoBlock
|
||||
PrettyNDJSON
|
||||
PrettyNoEscapes
|
||||
PrettyNoEscapesMonoBlock
|
||||
PrettySpace
|
||||
@ -111,6 +118,7 @@ CLICKHOUSE_Format=(
|
||||
RawBLOB
|
||||
Regexp
|
||||
RowBinary
|
||||
RowBinaryWithDefaults
|
||||
RowBinaryWithNames
|
||||
RowBinaryWithNamesAndTypes
|
||||
SQLInsert
|
||||
@ -146,7 +154,7 @@ function _clickhouse_quote()
|
||||
# Extract every option (everything that starts with "-") from the --help dialog.
|
||||
function _clickhouse_get_options()
|
||||
{
|
||||
"$@" --help 2>&1 | awk -F '[ ,=<>]' '{ for (i=1; i <= NF; ++i) { if (substr($i, 0, 1) == "-" && length($i) > 1) print $i; } }' | sort -u
|
||||
"$@" --help 2>&1 | awk -F '[ ,=<>.]' '{ for (i=1; i <= NF; ++i) { if (substr($i, 1, 1) == "-" && length($i) > 1) print $i; } }' | sort -u
|
||||
}
|
||||
|
||||
function _complete_for_clickhouse_generic_bin_impl()
|
||||
|
@ -475,6 +475,27 @@ void FourLetterWordCommand::execute(const ASTKeeperQuery * query, KeeperClient *
|
||||
std::cout << client->executeFourLetterCommand(query->args[0].safeGet<String>()) << "\n";
|
||||
}
|
||||
|
||||
bool GetDirectChildrenNumberCommand::parse(IParser::Pos & pos, std::shared_ptr<ASTKeeperQuery> & node, Expected & expected) const
|
||||
{
|
||||
String path;
|
||||
if (!parseKeeperPath(pos, expected, path))
|
||||
path = ".";
|
||||
|
||||
node->args.push_back(std::move(path));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GetDirectChildrenNumberCommand::execute(const ASTKeeperQuery * query, KeeperClient * client) const
|
||||
{
|
||||
auto path = client->getAbsolutePath(query->args[0].safeGet<String>());
|
||||
|
||||
Coordination::Stat stat;
|
||||
client->zookeeper->get(path, &stat);
|
||||
|
||||
std::cout << stat.numChildren << "\n";
|
||||
}
|
||||
|
||||
bool GetAllChildrenNumberCommand::parse(IParser::Pos & pos, std::shared_ptr<ASTKeeperQuery> & node, Expected & expected) const
|
||||
{
|
||||
String path;
|
||||
|
@ -238,6 +238,20 @@ class FourLetterWordCommand : public IKeeperClientCommand
|
||||
String getHelpMessage() const override { return "{} <command> -- Executes four-letter-word command"; }
|
||||
};
|
||||
|
||||
class GetDirectChildrenNumberCommand : public IKeeperClientCommand
|
||||
{
|
||||
String getName() const override { return "get_direct_children_number"; }
|
||||
|
||||
bool parse(IParser::Pos & pos, std::shared_ptr<ASTKeeperQuery> & node, Expected & expected) const override;
|
||||
|
||||
void execute(const ASTKeeperQuery * query, KeeperClient * client) const override;
|
||||
|
||||
String getHelpMessage() const override
|
||||
{
|
||||
return "{} [path] -- Get numbers of direct children nodes under a specific path";
|
||||
}
|
||||
};
|
||||
|
||||
class GetAllChildrenNumberCommand : public IKeeperClientCommand
|
||||
{
|
||||
String getName() const override { return "get_all_children_number"; }
|
||||
|
@ -207,6 +207,7 @@ void KeeperClient::initialize(Poco::Util::Application & /* self */)
|
||||
std::make_shared<SyncCommand>(),
|
||||
std::make_shared<HelpCommand>(),
|
||||
std::make_shared<FourLetterWordCommand>(),
|
||||
std::make_shared<GetDirectChildrenNumberCommand>(),
|
||||
std::make_shared<GetAllChildrenNumberCommand>(),
|
||||
});
|
||||
|
||||
|
@ -926,6 +926,15 @@
|
||||
</macros>
|
||||
-->
|
||||
|
||||
<!-- Replica group name for database Replicated.
|
||||
The cluster created by Replicated database will consist of replicas in the same group.
|
||||
DDL queries will only wail for the replicas in the same group.
|
||||
Empty by default.
|
||||
-->
|
||||
<!--
|
||||
<replica_group_name><replica_group_name>
|
||||
-->
|
||||
|
||||
|
||||
<!-- Reloading interval for embedded dictionaries, in seconds. Default: 3600. -->
|
||||
<builtin_dictionaries_reload_interval>3600</builtin_dictionaries_reload_interval>
|
||||
|
@ -272,13 +272,13 @@ void ExternalAuthenticators::resetImpl()
|
||||
|
||||
void ExternalAuthenticators::reset()
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
resetImpl();
|
||||
}
|
||||
|
||||
void ExternalAuthenticators::setConfiguration(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log)
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
resetImpl();
|
||||
|
||||
Poco::Util::AbstractConfiguration::Keys all_keys;
|
||||
@ -390,7 +390,7 @@ bool ExternalAuthenticators::checkLDAPCredentials(const String & server, const B
|
||||
UInt128 params_hash = 0;
|
||||
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
|
||||
// Retrieve the server parameters.
|
||||
const auto pit = ldap_client_params_blueprint.find(server);
|
||||
@ -460,7 +460,7 @@ bool ExternalAuthenticators::checkLDAPCredentials(const String & server, const B
|
||||
// Update the cache, but only if this is the latest check and the server is still configured in a compatible way.
|
||||
if (result)
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
|
||||
// If the server was removed from the config while we were checking the password, we discard the current result.
|
||||
const auto pit = ldap_client_params_blueprint.find(server);
|
||||
@ -507,7 +507,7 @@ bool ExternalAuthenticators::checkLDAPCredentials(const String & server, const B
|
||||
|
||||
bool ExternalAuthenticators::checkKerberosCredentials(const String & realm, const GSSAcceptorContext & credentials) const
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
|
||||
if (!kerberos_params.has_value())
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Kerberos is not enabled");
|
||||
@ -526,7 +526,7 @@ bool ExternalAuthenticators::checkKerberosCredentials(const String & realm, cons
|
||||
|
||||
GSSAcceptorContext::Params ExternalAuthenticators::getKerberosParams() const
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
|
||||
if (!kerberos_params.has_value())
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Kerberos is not enabled");
|
||||
@ -536,7 +536,7 @@ GSSAcceptorContext::Params ExternalAuthenticators::getKerberosParams() const
|
||||
|
||||
HTTPAuthClientParams ExternalAuthenticators::getHTTPAuthenticationParams(const String& server) const
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
std::lock_guard lock{mutex};
|
||||
|
||||
const auto it = http_auth_servers.find(server);
|
||||
if (it == http_auth_servers.end())
|
||||
|
@ -90,7 +90,7 @@ String bufferToString(const gss_buffer_desc & buf)
|
||||
|
||||
String extractSpecificStatusMessages(OM_uint32 status_code, int status_type, const gss_OID & mech_type)
|
||||
{
|
||||
std::scoped_lock lock(gss_global_mutex);
|
||||
std::lock_guard lock(gss_global_mutex);
|
||||
|
||||
String messages;
|
||||
OM_uint32 message_context = 0;
|
||||
@ -135,7 +135,7 @@ String extractSpecificStatusMessages(OM_uint32 status_code, int status_type, con
|
||||
|
||||
String extractStatusMessages(OM_uint32 major_status_code, OM_uint32 minor_status_code, const gss_OID & mech_type)
|
||||
{
|
||||
std::scoped_lock lock(gss_global_mutex);
|
||||
std::lock_guard lock(gss_global_mutex);
|
||||
|
||||
const auto gss_messages = extractSpecificStatusMessages(major_status_code, GSS_C_GSS_CODE, mech_type);
|
||||
const auto mech_messages = extractSpecificStatusMessages(minor_status_code, GSS_C_MECH_CODE, mech_type);
|
||||
@ -158,7 +158,7 @@ String extractStatusMessages(OM_uint32 major_status_code, OM_uint32 minor_status
|
||||
|
||||
std::pair<String, String> extractNameAndRealm(const gss_name_t & name)
|
||||
{
|
||||
std::scoped_lock lock(gss_global_mutex);
|
||||
std::lock_guard lock(gss_global_mutex);
|
||||
|
||||
gss_buffer_desc name_buf;
|
||||
name_buf.length = 0;
|
||||
@ -186,7 +186,7 @@ std::pair<String, String> extractNameAndRealm(const gss_name_t & name)
|
||||
|
||||
bool equalMechanisms(const String & left_str, const gss_OID & right_oid)
|
||||
{
|
||||
std::scoped_lock lock(gss_global_mutex);
|
||||
std::lock_guard lock(gss_global_mutex);
|
||||
|
||||
gss_buffer_desc left_buf;
|
||||
left_buf.length = left_str.size();
|
||||
@ -232,7 +232,7 @@ void GSSAcceptorContext::reset()
|
||||
|
||||
void GSSAcceptorContext::resetHandles() noexcept
|
||||
{
|
||||
std::scoped_lock lock(gss_global_mutex);
|
||||
std::lock_guard lock(gss_global_mutex);
|
||||
|
||||
if (acceptor_credentials_handle != GSS_C_NO_CREDENTIAL)
|
||||
{
|
||||
@ -258,7 +258,7 @@ void GSSAcceptorContext::resetHandles() noexcept
|
||||
|
||||
void GSSAcceptorContext::initHandles()
|
||||
{
|
||||
std::scoped_lock lock(gss_global_mutex);
|
||||
std::lock_guard lock(gss_global_mutex);
|
||||
|
||||
resetHandles();
|
||||
|
||||
@ -330,7 +330,7 @@ void GSSAcceptorContext::initHandles()
|
||||
|
||||
String GSSAcceptorContext::processToken(const String & input_token, Poco::Logger * log)
|
||||
{
|
||||
std::scoped_lock lock(gss_global_mutex);
|
||||
std::lock_guard lock(gss_global_mutex);
|
||||
|
||||
String output_token;
|
||||
|
||||
|
@ -36,14 +36,14 @@ LDAPAccessStorage::LDAPAccessStorage(const String & storage_name_, AccessControl
|
||||
|
||||
String LDAPAccessStorage::getLDAPServerName() const
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
return ldap_server_name;
|
||||
}
|
||||
|
||||
|
||||
void LDAPAccessStorage::setConfiguration(const Poco::Util::AbstractConfiguration & config, const String & prefix)
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
|
||||
// TODO: switch to passing config as a ConfigurationView and remove this extra prefix once a version of Poco with proper implementation is available.
|
||||
const String prefix_str = (prefix.empty() ? "" : prefix + ".");
|
||||
@ -102,7 +102,7 @@ void LDAPAccessStorage::setConfiguration(const Poco::Util::AbstractConfiguration
|
||||
|
||||
void LDAPAccessStorage::processRoleChange(const UUID & id, const AccessEntityPtr & entity)
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
const auto role = typeid_cast<std::shared_ptr<const Role>>(entity);
|
||||
const auto it = granted_role_names.find(id);
|
||||
|
||||
@ -371,7 +371,7 @@ const char * LDAPAccessStorage::getStorageType() const
|
||||
|
||||
String LDAPAccessStorage::getStorageParamsJSON() const
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
Poco::JSON::Object params_json;
|
||||
|
||||
params_json.set("server", ldap_server_name);
|
||||
@ -417,35 +417,35 @@ String LDAPAccessStorage::getStorageParamsJSON() const
|
||||
|
||||
std::optional<UUID> LDAPAccessStorage::findImpl(AccessEntityType type, const String & name) const
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
return memory_storage.find(type, name);
|
||||
}
|
||||
|
||||
|
||||
std::vector<UUID> LDAPAccessStorage::findAllImpl(AccessEntityType type) const
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
return memory_storage.findAll(type);
|
||||
}
|
||||
|
||||
|
||||
bool LDAPAccessStorage::exists(const UUID & id) const
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
return memory_storage.exists(id);
|
||||
}
|
||||
|
||||
|
||||
AccessEntityPtr LDAPAccessStorage::readImpl(const UUID & id, bool throw_if_not_exists) const
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
return memory_storage.read(id, throw_if_not_exists);
|
||||
}
|
||||
|
||||
|
||||
std::optional<std::pair<String, AccessEntityType>> LDAPAccessStorage::readNameWithTypeImpl(const UUID & id, bool throw_if_not_exists) const
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
return memory_storage.readNameWithType(id, throw_if_not_exists);
|
||||
}
|
||||
|
||||
@ -458,7 +458,7 @@ std::optional<AuthResult> LDAPAccessStorage::authenticateImpl(
|
||||
bool /* allow_no_password */,
|
||||
bool /* allow_plaintext_password */) const
|
||||
{
|
||||
std::scoped_lock lock(mutex);
|
||||
std::lock_guard lock(mutex);
|
||||
auto id = memory_storage.find<User>(credentials.getUserName());
|
||||
UserPtr user = id ? memory_storage.read<User>(*id) : nullptr;
|
||||
|
||||
|
@ -172,7 +172,7 @@ namespace
|
||||
|
||||
void LDAPClient::handleError(int result_code, String text)
|
||||
{
|
||||
std::scoped_lock lock(ldap_global_mutex);
|
||||
std::lock_guard lock(ldap_global_mutex);
|
||||
|
||||
if (result_code != LDAP_SUCCESS)
|
||||
{
|
||||
@ -212,7 +212,7 @@ void LDAPClient::handleError(int result_code, String text)
|
||||
|
||||
bool LDAPClient::openConnection()
|
||||
{
|
||||
std::scoped_lock lock(ldap_global_mutex);
|
||||
std::lock_guard lock(ldap_global_mutex);
|
||||
|
||||
closeConnection();
|
||||
|
||||
@ -390,7 +390,7 @@ bool LDAPClient::openConnection()
|
||||
|
||||
void LDAPClient::closeConnection() noexcept
|
||||
{
|
||||
std::scoped_lock lock(ldap_global_mutex);
|
||||
std::lock_guard lock(ldap_global_mutex);
|
||||
|
||||
if (!handle)
|
||||
return;
|
||||
@ -404,7 +404,7 @@ void LDAPClient::closeConnection() noexcept
|
||||
|
||||
LDAPClient::SearchResults LDAPClient::search(const SearchParams & search_params)
|
||||
{
|
||||
std::scoped_lock lock(ldap_global_mutex);
|
||||
std::lock_guard lock(ldap_global_mutex);
|
||||
|
||||
SearchResults result;
|
||||
|
||||
|
@ -8,9 +8,6 @@
|
||||
#include <DataTypes/DataTypeIPv4andIPv6.h>
|
||||
|
||||
|
||||
static inline constexpr UInt64 TOP_K_MAX_SIZE = 0xFFFFFF;
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
@ -134,9 +131,12 @@ AggregateFunctionPtr createAggregateFunctionTopK(const std::string & name, const
|
||||
|
||||
threshold = applyVisitor(FieldVisitorConvertToNumber<UInt64>(), params[0]);
|
||||
|
||||
if (threshold > TOP_K_MAX_SIZE || load_factor > TOP_K_MAX_SIZE || threshold * load_factor > TOP_K_MAX_SIZE)
|
||||
throw Exception(ErrorCodes::ARGUMENT_OUT_OF_BOUND,
|
||||
"Too large parameter(s) for aggregate function '{}' (maximum is {})", name, toString(TOP_K_MAX_SIZE));
|
||||
if (threshold > DB::TOP_K_MAX_SIZE || load_factor > DB::TOP_K_MAX_SIZE || threshold * load_factor > DB::TOP_K_MAX_SIZE)
|
||||
throw Exception(
|
||||
ErrorCodes::ARGUMENT_OUT_OF_BOUND,
|
||||
"Too large parameter(s) for aggregate function '{}' (maximum is {})",
|
||||
name,
|
||||
toString(DB::TOP_K_MAX_SIZE));
|
||||
|
||||
if (threshold == 0)
|
||||
throw Exception(ErrorCodes::ARGUMENT_OUT_OF_BOUND, "Parameter 0 is illegal for aggregate function '{}'", name);
|
||||
|
@ -20,6 +20,12 @@ namespace DB
|
||||
{
|
||||
struct Settings;
|
||||
|
||||
static inline constexpr UInt64 TOP_K_MAX_SIZE = 0xFFFFFF;
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int ARGUMENT_OUT_OF_BOUND;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct AggregateFunctionTopKData
|
||||
@ -163,11 +169,18 @@ public:
|
||||
{
|
||||
auto & set = this->data(place).value;
|
||||
set.clear();
|
||||
set.resize(reserved);
|
||||
|
||||
// Specialized here because there's no deserialiser for StringRef
|
||||
size_t size = 0;
|
||||
readVarUInt(size, buf);
|
||||
if (unlikely(size > TOP_K_MAX_SIZE))
|
||||
throw Exception(
|
||||
ErrorCodes::ARGUMENT_OUT_OF_BOUND,
|
||||
"Too large size ({}) for aggregate function '{}' state (maximum is {})",
|
||||
size,
|
||||
getName(),
|
||||
TOP_K_MAX_SIZE);
|
||||
set.resize(size);
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
auto ref = readStringBinaryInto(*arena, buf);
|
||||
|
@ -970,6 +970,10 @@ private:
|
||||
if (!node->hasAlias())
|
||||
return;
|
||||
|
||||
// We should not resolve expressions to WindowNode
|
||||
if (node->getNodeType() == QueryTreeNodeType::WINDOW)
|
||||
return;
|
||||
|
||||
const auto & alias = node->getAlias();
|
||||
|
||||
if (is_lambda_node)
|
||||
|
@ -573,4 +573,36 @@ void replaceColumns(QueryTreeNodePtr & node,
|
||||
visitor.visit(node);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class CollectIdentifiersFullNamesVisitor : public ConstInDepthQueryTreeVisitor<CollectIdentifiersFullNamesVisitor>
|
||||
{
|
||||
public:
|
||||
explicit CollectIdentifiersFullNamesVisitor(NameSet & used_identifiers_)
|
||||
: used_identifiers(used_identifiers_) { }
|
||||
|
||||
static bool needChildVisit(const QueryTreeNodePtr &, const QueryTreeNodePtr &) { return true; }
|
||||
|
||||
void visitImpl(const QueryTreeNodePtr & node)
|
||||
{
|
||||
auto * column_node = node->as<IdentifierNode>();
|
||||
if (!column_node)
|
||||
return;
|
||||
|
||||
used_identifiers.insert(column_node->getIdentifier().getFullName());
|
||||
}
|
||||
|
||||
NameSet & used_identifiers;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
NameSet collectIdentifiersFullNames(const QueryTreeNodePtr & node)
|
||||
{
|
||||
NameSet out;
|
||||
CollectIdentifiersFullNamesVisitor visitor(out);
|
||||
visitor.visit(node);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
@ -83,4 +83,8 @@ void replaceColumns(QueryTreeNodePtr & node,
|
||||
const QueryTreeNodePtr & table_expression_node,
|
||||
const std::unordered_map<std::string, QueryTreeNodePtr> & column_name_to_node);
|
||||
|
||||
|
||||
/// Just collect all identifiers from query tree
|
||||
NameSet collectIdentifiersFullNames(const QueryTreeNodePtr & node);
|
||||
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ if (TARGET ch_contrib::nats_io)
|
||||
endif()
|
||||
|
||||
add_headers_and_sources(dbms Storages/DataLakes)
|
||||
add_headers_and_sources(dbms Storages/DataLakes/Iceberg)
|
||||
add_headers_and_sources(dbms Common/NamedCollections)
|
||||
|
||||
if (TARGET ch_contrib::amqp_cpp)
|
||||
|
@ -449,20 +449,7 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query)
|
||||
if (!block)
|
||||
return;
|
||||
|
||||
if (block.rows() == 0 && partial_result_mode == PartialResultMode::Active)
|
||||
{
|
||||
partial_result_mode = PartialResultMode::Inactive;
|
||||
if (is_interactive)
|
||||
{
|
||||
progress_indication.clearProgressOutput(*tty_buf);
|
||||
std::cout << "Full result:" << std::endl;
|
||||
progress_indication.writeProgress(*tty_buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (partial_result_mode == PartialResultMode::Inactive)
|
||||
processed_rows += block.rows();
|
||||
|
||||
processed_rows += block.rows();
|
||||
/// Even if all blocks are empty, we still need to initialize the output stream to write empty resultset.
|
||||
initOutputFormat(block, parsed_query);
|
||||
|
||||
@ -472,20 +459,13 @@ void ClientBase::onData(Block & block, ASTPtr parsed_query)
|
||||
if (block.rows() == 0 || (query_fuzzer_runs != 0 && processed_rows >= 100))
|
||||
return;
|
||||
|
||||
if (!is_interactive && partial_result_mode == PartialResultMode::Active)
|
||||
return;
|
||||
|
||||
/// If results are written INTO OUTFILE, we can avoid clearing progress to avoid flicker.
|
||||
if (need_render_progress && tty_buf && (!select_into_file || select_into_file_and_stdout))
|
||||
progress_indication.clearProgressOutput(*tty_buf);
|
||||
|
||||
try
|
||||
{
|
||||
if (partial_result_mode == PartialResultMode::Active)
|
||||
output_format->writePartialResult(materializeBlock(block));
|
||||
else
|
||||
output_format->write(materializeBlock(block));
|
||||
|
||||
output_format->write(materializeBlock(block));
|
||||
written_first_block = true;
|
||||
}
|
||||
catch (const Exception &)
|
||||
@ -549,9 +529,6 @@ void ClientBase::onProfileInfo(const ProfileInfo & profile_info)
|
||||
void ClientBase::initOutputFormat(const Block & block, ASTPtr parsed_query)
|
||||
try
|
||||
{
|
||||
if (partial_result_mode == PartialResultMode::NotInit)
|
||||
partial_result_mode = PartialResultMode::Active;
|
||||
|
||||
if (!output_format)
|
||||
{
|
||||
/// Ignore all results when fuzzing as they can be huge.
|
||||
@ -994,14 +971,6 @@ void ClientBase::processOrdinaryQuery(const String & query_to_execute, ASTPtr pa
|
||||
|
||||
const auto & settings = global_context->getSettingsRef();
|
||||
const Int32 signals_before_stop = settings.partial_result_on_first_cancel ? 2 : 1;
|
||||
bool has_partial_result_setting = settings.partial_result_update_duration_ms.totalMilliseconds() > 0;
|
||||
|
||||
if (has_partial_result_setting)
|
||||
{
|
||||
partial_result_mode = PartialResultMode::NotInit;
|
||||
if (is_interactive)
|
||||
std::cout << "Partial result:" << std::endl;
|
||||
}
|
||||
|
||||
int retries_left = 10;
|
||||
while (retries_left)
|
||||
@ -1828,7 +1797,6 @@ void ClientBase::processParsedSingleQuery(const String & full_query, const Strin
|
||||
}
|
||||
|
||||
processed_rows = 0;
|
||||
partial_result_mode = PartialResultMode::Inactive;
|
||||
written_first_block = false;
|
||||
progress_indication.resetProgress();
|
||||
profile_events.watch.restart();
|
||||
@ -1950,9 +1918,10 @@ void ClientBase::processParsedSingleQuery(const String & full_query, const Strin
|
||||
|
||||
if (is_interactive)
|
||||
{
|
||||
std::cout << std::endl
|
||||
<< processed_rows << " row" << (processed_rows == 1 ? "" : "s")
|
||||
<< " in set. Elapsed: " << progress_indication.elapsedSeconds() << " sec. ";
|
||||
std::cout << std::endl;
|
||||
if (!server_exception || processed_rows != 0)
|
||||
std::cout << processed_rows << " row" << (processed_rows == 1 ? "" : "s") << " in set. ";
|
||||
std::cout << "Elapsed: " << progress_indication.elapsedSeconds() << " sec. ";
|
||||
progress_indication.writeFinalProgress();
|
||||
std::cout << std::endl << std::endl;
|
||||
}
|
||||
|
@ -275,21 +275,6 @@ protected:
|
||||
size_t processed_rows = 0; /// How many rows have been read or written.
|
||||
bool print_num_processed_rows = false; /// Whether to print the number of processed rows at
|
||||
|
||||
enum class PartialResultMode: UInt8
|
||||
{
|
||||
/// Query doesn't show partial result before the first block with 0 rows.
|
||||
/// The first block with 0 rows initializes the output table format using its header.
|
||||
NotInit,
|
||||
|
||||
/// Query shows partial result after the first and before the second block with 0 rows.
|
||||
/// The second block with 0 rows indicates that that receiving blocks with partial result has been completed and next blocks will be with the full result.
|
||||
Active,
|
||||
|
||||
/// Query doesn't show partial result at all.
|
||||
Inactive,
|
||||
};
|
||||
PartialResultMode partial_result_mode = PartialResultMode::Inactive;
|
||||
|
||||
bool print_stack_trace = false;
|
||||
/// The last exception that was received from the server. Is used for the
|
||||
/// return code in batch mode.
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <Common/assert_cast.h>
|
||||
#include <Common/WeakHash.h>
|
||||
#include <Common/HashTable/Hash.h>
|
||||
#include <Common/RadixSort.h>
|
||||
|
||||
#include <base/unaligned.h>
|
||||
#include <base/sort.h>
|
||||
@ -15,6 +16,7 @@
|
||||
#include <Columns/ColumnDecimal.h>
|
||||
#include <Columns/ColumnCompressed.h>
|
||||
#include <Columns/MaskOperations.h>
|
||||
#include <Columns/RadixSortHelper.h>
|
||||
#include <Processors/Transforms/ColumnGathererTransform.h>
|
||||
|
||||
|
||||
@ -159,6 +161,59 @@ void ColumnDecimal<T>::getPermutation(IColumn::PermutationSortDirection directio
|
||||
return data[lhs] > data[rhs];
|
||||
};
|
||||
|
||||
size_t data_size = data.size();
|
||||
res.resize(data_size);
|
||||
|
||||
if (limit >= data_size)
|
||||
limit = 0;
|
||||
|
||||
for (size_t i = 0; i < data_size; ++i)
|
||||
res[i] = i;
|
||||
|
||||
if constexpr (is_arithmetic_v<NativeT> && !is_big_int_v<NativeT>)
|
||||
{
|
||||
if (!limit)
|
||||
{
|
||||
/// A case for radix sort
|
||||
/// LSD RadixSort is stable
|
||||
|
||||
bool reverse = direction == IColumn::PermutationSortDirection::Descending;
|
||||
bool ascending = direction == IColumn::PermutationSortDirection::Ascending;
|
||||
bool sort_is_stable = stability == IColumn::PermutationSortStability::Stable;
|
||||
|
||||
/// TODO: LSD RadixSort is currently not stable if direction is descending
|
||||
bool use_radix_sort = (sort_is_stable && ascending) || !sort_is_stable;
|
||||
|
||||
/// Thresholds on size. Lower threshold is arbitrary. Upper threshold is chosen by the type for histogram counters.
|
||||
if (data_size >= 256 && data_size <= std::numeric_limits<UInt32>::max() && use_radix_sort)
|
||||
{
|
||||
for (size_t i = 0; i < data_size; ++i)
|
||||
res[i] = i;
|
||||
|
||||
bool try_sort = false;
|
||||
|
||||
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
|
||||
try_sort = trySort(res.begin(), res.end(), comparator_ascending);
|
||||
else if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Stable)
|
||||
try_sort = trySort(res.begin(), res.end(), comparator_ascending_stable);
|
||||
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Unstable)
|
||||
try_sort = trySort(res.begin(), res.end(), comparator_descending);
|
||||
else
|
||||
try_sort = trySort(res.begin(), res.end(), comparator_descending_stable);
|
||||
|
||||
if (try_sort)
|
||||
return;
|
||||
|
||||
PaddedPODArray<ValueWithIndex<NativeT>> pairs(data_size);
|
||||
for (UInt32 i = 0; i < static_cast<UInt32>(data_size); ++i)
|
||||
pairs[i] = {data[i].value, i};
|
||||
|
||||
RadixSort<RadixSortTraits<NativeT>>::executeLSD(pairs.data(), data_size, reverse, res.data());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
|
||||
this->getPermutationImpl(limit, res, comparator_ascending, DefaultSort(), DefaultPartialSort());
|
||||
else if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Stable)
|
||||
@ -191,7 +246,37 @@ void ColumnDecimal<T>::updatePermutation(IColumn::PermutationSortDirection direc
|
||||
return data[lhs] < data[rhs];
|
||||
};
|
||||
auto equals_comparator = [this](size_t lhs, size_t rhs) { return data[lhs] == data[rhs]; };
|
||||
auto sort = [](auto begin, auto end, auto pred) { ::sort(begin, end, pred); };
|
||||
auto sort = [&](auto begin, auto end, auto pred)
|
||||
{
|
||||
bool reverse = direction == IColumn::PermutationSortDirection::Descending;
|
||||
bool ascending = direction == IColumn::PermutationSortDirection::Ascending;
|
||||
bool sort_is_stable = stability == IColumn::PermutationSortStability::Stable;
|
||||
|
||||
/// TODO: LSD RadixSort is currently not stable if direction is descending
|
||||
bool use_radix_sort = (sort_is_stable && ascending) || !sort_is_stable;
|
||||
size_t size = end - begin;
|
||||
|
||||
if (size >= 256 && size <= std::numeric_limits<UInt32>::max() && use_radix_sort)
|
||||
{
|
||||
bool try_sort = trySort(begin, end, pred);
|
||||
if (try_sort)
|
||||
return;
|
||||
|
||||
PaddedPODArray<ValueWithIndex<NativeT>> pairs(size);
|
||||
size_t index = 0;
|
||||
|
||||
for (auto * it = begin; it != end; ++it)
|
||||
{
|
||||
pairs[index] = {data[*it].value, static_cast<UInt32>(*it)};
|
||||
++index;
|
||||
}
|
||||
|
||||
RadixSort<RadixSortTraits<NativeT>>::executeLSD(pairs.data(), size, reverse, begin);
|
||||
return;
|
||||
}
|
||||
|
||||
::sort(begin, end, pred);
|
||||
};
|
||||
auto partial_sort = [](auto begin, auto mid, auto end, auto pred) { ::partial_sort(begin, mid, end, pred); };
|
||||
|
||||
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <Columns/ColumnsCommon.h>
|
||||
#include <Columns/ColumnCompressed.h>
|
||||
#include <Columns/MaskOperations.h>
|
||||
#include <Columns/RadixSortHelper.h>
|
||||
#include <Processors/Transforms/ColumnGathererTransform.h>
|
||||
#include <IO/WriteHelpers.h>
|
||||
#include <Common/Arena.h>
|
||||
@ -192,26 +193,6 @@ struct ColumnVector<T>::equals
|
||||
bool operator()(size_t lhs, size_t rhs) const { return CompareHelper<T>::equals(parent.data[lhs], parent.data[rhs], nan_direction_hint); }
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
struct ValueWithIndex
|
||||
{
|
||||
T value;
|
||||
UInt32 index;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct RadixSortTraits : RadixSortNumTraits<T>
|
||||
{
|
||||
using Element = ValueWithIndex<T>;
|
||||
using Result = size_t;
|
||||
|
||||
static T & extractKey(Element & elem) { return elem.value; }
|
||||
static size_t extractResult(Element & elem) { return elem.index; }
|
||||
};
|
||||
}
|
||||
|
||||
#if USE_EMBEDDED_COMPILER
|
||||
|
||||
template <typename T>
|
||||
@ -254,35 +235,25 @@ template <typename T>
|
||||
void ColumnVector<T>::getPermutation(IColumn::PermutationSortDirection direction, IColumn::PermutationSortStability stability,
|
||||
size_t limit, int nan_direction_hint, IColumn::Permutation & res) const
|
||||
{
|
||||
size_t s = data.size();
|
||||
res.resize(s);
|
||||
size_t data_size = data.size();
|
||||
res.resize(data_size);
|
||||
|
||||
if (s == 0)
|
||||
if (data_size == 0)
|
||||
return;
|
||||
|
||||
if (limit >= s)
|
||||
if (limit >= data_size)
|
||||
limit = 0;
|
||||
|
||||
if (limit)
|
||||
{
|
||||
for (size_t i = 0; i < s; ++i)
|
||||
res[i] = i;
|
||||
for (size_t i = 0; i < data_size; ++i)
|
||||
res[i] = i;
|
||||
|
||||
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
|
||||
::partial_sort(res.begin(), res.begin() + limit, res.end(), less(*this, nan_direction_hint));
|
||||
else if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Stable)
|
||||
::partial_sort(res.begin(), res.begin() + limit, res.end(), less_stable(*this, nan_direction_hint));
|
||||
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Unstable)
|
||||
::partial_sort(res.begin(), res.begin() + limit, res.end(), greater(*this, nan_direction_hint));
|
||||
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Stable)
|
||||
::partial_sort(res.begin(), res.begin() + limit, res.end(), greater_stable(*this, nan_direction_hint));
|
||||
}
|
||||
else
|
||||
if constexpr (is_arithmetic_v<T> && !is_big_int_v<T>)
|
||||
{
|
||||
/// A case for radix sort
|
||||
/// LSD RadixSort is stable
|
||||
if constexpr (is_arithmetic_v<T> && !is_big_int_v<T>)
|
||||
if (!limit)
|
||||
{
|
||||
/// A case for radix sort
|
||||
/// LSD RadixSort is stable
|
||||
|
||||
bool reverse = direction == IColumn::PermutationSortDirection::Descending;
|
||||
bool ascending = direction == IColumn::PermutationSortDirection::Ascending;
|
||||
bool sort_is_stable = stability == IColumn::PermutationSortStability::Stable;
|
||||
@ -291,13 +262,27 @@ void ColumnVector<T>::getPermutation(IColumn::PermutationSortDirection direction
|
||||
bool use_radix_sort = (sort_is_stable && ascending && !std::is_floating_point_v<T>) || !sort_is_stable;
|
||||
|
||||
/// Thresholds on size. Lower threshold is arbitrary. Upper threshold is chosen by the type for histogram counters.
|
||||
if (s >= 256 && s <= std::numeric_limits<UInt32>::max() && use_radix_sort)
|
||||
if (data_size >= 256 && data_size <= std::numeric_limits<UInt32>::max() && use_radix_sort)
|
||||
{
|
||||
PaddedPODArray<ValueWithIndex<T>> pairs(s);
|
||||
for (UInt32 i = 0; i < static_cast<UInt32>(s); ++i)
|
||||
bool try_sort = false;
|
||||
|
||||
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
|
||||
try_sort = trySort(res.begin(), res.end(), less(*this, nan_direction_hint));
|
||||
else if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Stable)
|
||||
try_sort = trySort(res.begin(), res.end(), less_stable(*this, nan_direction_hint));
|
||||
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Unstable)
|
||||
try_sort = trySort(res.begin(), res.end(), greater(*this, nan_direction_hint));
|
||||
else
|
||||
try_sort = trySort(res.begin(), res.end(), greater_stable(*this, nan_direction_hint));
|
||||
|
||||
if (try_sort)
|
||||
return;
|
||||
|
||||
PaddedPODArray<ValueWithIndex<T>> pairs(data_size);
|
||||
for (UInt32 i = 0; i < static_cast<UInt32>(data_size); ++i)
|
||||
pairs[i] = {data[i], i};
|
||||
|
||||
RadixSort<RadixSortTraits<T>>::executeLSD(pairs.data(), s, reverse, res.data());
|
||||
RadixSort<RadixSortTraits<T>>::executeLSD(pairs.data(), data_size, reverse, res.data());
|
||||
|
||||
/// Radix sort treats all NaNs to be greater than all numbers.
|
||||
/// If the user needs the opposite, we must move them accordingly.
|
||||
@ -305,9 +290,9 @@ void ColumnVector<T>::getPermutation(IColumn::PermutationSortDirection direction
|
||||
{
|
||||
size_t nans_to_move = 0;
|
||||
|
||||
for (size_t i = 0; i < s; ++i)
|
||||
for (size_t i = 0; i < data_size; ++i)
|
||||
{
|
||||
if (isNaN(data[res[reverse ? i : s - 1 - i]]))
|
||||
if (isNaN(data[res[reverse ? i : data_size - 1 - i]]))
|
||||
++nans_to_move;
|
||||
else
|
||||
break;
|
||||
@ -315,38 +300,35 @@ void ColumnVector<T>::getPermutation(IColumn::PermutationSortDirection direction
|
||||
|
||||
if (nans_to_move)
|
||||
{
|
||||
std::rotate(std::begin(res), std::begin(res) + (reverse ? nans_to_move : s - nans_to_move), std::end(res));
|
||||
std::rotate(std::begin(res), std::begin(res) + (reverse ? nans_to_move : data_size - nans_to_move), std::end(res));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Default sorting algorithm.
|
||||
for (size_t i = 0; i < s; ++i)
|
||||
res[i] = i;
|
||||
|
||||
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
|
||||
::sort(res.begin(), res.end(), less(*this, nan_direction_hint));
|
||||
else if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Stable)
|
||||
::sort(res.begin(), res.end(), less_stable(*this, nan_direction_hint));
|
||||
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Unstable)
|
||||
::sort(res.begin(), res.end(), greater(*this, nan_direction_hint));
|
||||
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Stable)
|
||||
::sort(res.begin(), res.end(), greater_stable(*this, nan_direction_hint));
|
||||
}
|
||||
|
||||
if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Unstable)
|
||||
this->getPermutationImpl(limit, res, less(*this, nan_direction_hint), DefaultSort(), DefaultPartialSort());
|
||||
else if (direction == IColumn::PermutationSortDirection::Ascending && stability == IColumn::PermutationSortStability::Stable)
|
||||
this->getPermutationImpl(limit, res, less_stable(*this, nan_direction_hint), DefaultSort(), DefaultPartialSort());
|
||||
else if (direction == IColumn::PermutationSortDirection::Descending && stability == IColumn::PermutationSortStability::Unstable)
|
||||
this->getPermutationImpl(limit, res, greater(*this, nan_direction_hint), DefaultSort(), DefaultPartialSort());
|
||||
else
|
||||
this->getPermutationImpl(limit, res, greater_stable(*this, nan_direction_hint), DefaultSort(), DefaultPartialSort());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ColumnVector<T>::updatePermutation(IColumn::PermutationSortDirection direction, IColumn::PermutationSortStability stability,
|
||||
size_t limit, int nan_direction_hint, IColumn::Permutation & res, EqualRanges & equal_ranges) const
|
||||
{
|
||||
bool reverse = direction == IColumn::PermutationSortDirection::Descending;
|
||||
bool ascending = direction == IColumn::PermutationSortDirection::Ascending;
|
||||
bool sort_is_stable = stability == IColumn::PermutationSortStability::Stable;
|
||||
|
||||
auto sort = [&](auto begin, auto end, auto pred)
|
||||
{
|
||||
bool reverse = direction == IColumn::PermutationSortDirection::Descending;
|
||||
bool ascending = direction == IColumn::PermutationSortDirection::Ascending;
|
||||
bool sort_is_stable = stability == IColumn::PermutationSortStability::Stable;
|
||||
|
||||
/// A case for radix sort
|
||||
if constexpr (is_arithmetic_v<T> && !is_big_int_v<T>)
|
||||
{
|
||||
@ -357,6 +339,10 @@ void ColumnVector<T>::updatePermutation(IColumn::PermutationSortDirection direct
|
||||
/// Thresholds on size. Lower threshold is arbitrary. Upper threshold is chosen by the type for histogram counters.
|
||||
if (size >= 256 && size <= std::numeric_limits<UInt32>::max() && use_radix_sort)
|
||||
{
|
||||
bool try_sort = trySort(begin, end, pred);
|
||||
if (try_sort)
|
||||
return;
|
||||
|
||||
PaddedPODArray<ValueWithIndex<T>> pairs(size);
|
||||
size_t index = 0;
|
||||
|
||||
|
25
src/Columns/RadixSortHelper.h
Normal file
25
src/Columns/RadixSortHelper.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <Common/RadixSort.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
struct ValueWithIndex
|
||||
{
|
||||
T value;
|
||||
UInt32 index;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct RadixSortTraits : RadixSortNumTraits<T>
|
||||
{
|
||||
using Element = ValueWithIndex<T>;
|
||||
using Result = size_t;
|
||||
|
||||
static T & extractKey(Element & elem) { return elem.value; }
|
||||
static size_t extractResult(Element & elem) { return elem.index; }
|
||||
};
|
||||
|
||||
}
|
@ -82,33 +82,32 @@ size_t FileChecker::getTotalSize() const
|
||||
}
|
||||
|
||||
|
||||
CheckResults FileChecker::check() const
|
||||
FileChecker::DataValidationTasksPtr FileChecker::getDataValidationTasks()
|
||||
{
|
||||
if (map.empty())
|
||||
return std::make_unique<DataValidationTasks>(map);
|
||||
}
|
||||
|
||||
std::optional<CheckResult> FileChecker::checkNextEntry(DataValidationTasksPtr & check_data_tasks) const
|
||||
{
|
||||
String name;
|
||||
size_t expected_size;
|
||||
bool is_finished = check_data_tasks->next(name, expected_size);
|
||||
if (is_finished)
|
||||
return {};
|
||||
|
||||
CheckResults results;
|
||||
String path = parentPath(files_info_path) + name;
|
||||
bool exists = fileReallyExists(path);
|
||||
auto real_size = exists ? getRealFileSize(path) : 0; /// No race condition assuming no one else is working with these files.
|
||||
|
||||
for (const auto & name_size : map)
|
||||
if (real_size != expected_size)
|
||||
{
|
||||
const String & name = name_size.first;
|
||||
String path = parentPath(files_info_path) + name;
|
||||
bool exists = fileReallyExists(path);
|
||||
auto real_size = exists ? getRealFileSize(path) : 0; /// No race condition assuming no one else is working with these files.
|
||||
|
||||
if (real_size != name_size.second)
|
||||
{
|
||||
String failure_message = exists
|
||||
? ("Size of " + path + " is wrong. Size is " + toString(real_size) + " but should be " + toString(name_size.second))
|
||||
: ("File " + path + " doesn't exist");
|
||||
results.emplace_back(name, false, failure_message);
|
||||
break;
|
||||
}
|
||||
|
||||
results.emplace_back(name, true, "");
|
||||
String failure_message = exists
|
||||
? ("Size of " + path + " is wrong. Size is " + toString(real_size) + " but should be " + toString(expected_size))
|
||||
: ("File " + path + " doesn't exist");
|
||||
return CheckResult(name, false, failure_message);
|
||||
}
|
||||
|
||||
return results;
|
||||
return CheckResult(name, true, "");
|
||||
}
|
||||
|
||||
void FileChecker::repair()
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <Storages/CheckResults.h>
|
||||
#include <map>
|
||||
#include <base/types.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace Poco { class Logger; }
|
||||
|
||||
@ -28,7 +29,11 @@ public:
|
||||
bool empty() const { return map.empty(); }
|
||||
|
||||
/// Check the files whose parameters are specified in sizes.json
|
||||
CheckResults check() const;
|
||||
/// See comment in IStorage::checkDataNext
|
||||
struct DataValidationTasks;
|
||||
using DataValidationTasksPtr = std::unique_ptr<DataValidationTasks>;
|
||||
DataValidationTasksPtr getDataValidationTasks();
|
||||
std::optional<CheckResult> checkNextEntry(DataValidationTasksPtr & check_data_tasks) const;
|
||||
|
||||
/// Truncate files that have excessive size to the expected size.
|
||||
/// Throw exception if the file size is less than expected.
|
||||
@ -41,6 +46,36 @@ public:
|
||||
/// Returns total size of all files.
|
||||
size_t getTotalSize() const;
|
||||
|
||||
struct DataValidationTasks
|
||||
{
|
||||
DataValidationTasks(const std::map<String, size_t> & map_)
|
||||
: map(map_), it(map.begin())
|
||||
{}
|
||||
|
||||
bool next(String & out_name, size_t & out_size)
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
if (it == map.end())
|
||||
return true;
|
||||
out_name = it->first;
|
||||
out_size = it->second;
|
||||
++it;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
return std::distance(it, map.end());
|
||||
}
|
||||
|
||||
const std::map<String, size_t> & map;
|
||||
|
||||
mutable std::mutex mutex;
|
||||
using Iterator = std::map<String, size_t>::const_iterator;
|
||||
Iterator it;
|
||||
};
|
||||
|
||||
private:
|
||||
void load();
|
||||
|
||||
|
27
src/Common/SharedLockGuard.h
Normal file
27
src/Common/SharedLockGuard.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <base/defines.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
/** SharedLockGuard provide RAII-style locking mechanism for acquiring shared ownership of the implementation
|
||||
* of the SharedLockable concept (for example std::shared_mutex or ContextSharedMutex) supplied as the
|
||||
* constructor argument. Think of it as std::lock_guard which locks shared.
|
||||
*
|
||||
* On construction it acquires shared ownership using `lock_shared` method.
|
||||
* On destruction shared ownership is released using `unlock_shared` method.
|
||||
*/
|
||||
template <typename Mutex>
|
||||
class TSA_SCOPED_LOCKABLE SharedLockGuard
|
||||
{
|
||||
public:
|
||||
explicit SharedLockGuard(Mutex & mutex_) TSA_ACQUIRE_SHARED(mutex_) : mutex(mutex_) { mutex_.lock_shared(); }
|
||||
|
||||
~SharedLockGuard() TSA_RELEASE() { mutex.unlock_shared(); }
|
||||
|
||||
private:
|
||||
Mutex & mutex;
|
||||
};
|
||||
|
||||
}
|
112
src/Common/SharedMutexHelper.h
Normal file
112
src/Common/SharedMutexHelper.h
Normal file
@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include <base/types.h>
|
||||
#include <base/defines.h>
|
||||
#include <Common/SharedMutex.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
/** SharedMutexHelper class allows to inject specific logic when underlying shared mutex is acquired
|
||||
* and released.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* class ProfileSharedMutex : public SharedMutexHelper<ProfileSharedMutex>
|
||||
* {
|
||||
* public:
|
||||
* size_t getLockCount() const { return lock_count; }
|
||||
*
|
||||
* size_t getSharedLockCount() const { return shared_lock_count; }
|
||||
*
|
||||
* private:
|
||||
* using Base = SharedMutexHelper<ProfileSharedMutex, SharedMutex>;
|
||||
* friend class SharedMutexHelper<ProfileSharedMutex, SharedMutex>;
|
||||
*
|
||||
* void lockImpl()
|
||||
* {
|
||||
* ++lock_count;
|
||||
* Base::lockImpl();
|
||||
* }
|
||||
*
|
||||
* void lockSharedImpl()
|
||||
* {
|
||||
* ++shared_lock_count;
|
||||
* Base::lockSharedImpl();
|
||||
* }
|
||||
*
|
||||
* std::atomic<size_t> lock_count = 0;
|
||||
* std::atomic<size_t> shared_lock_count = 0;
|
||||
* };
|
||||
*/
|
||||
template <typename Derived, typename MutexType = SharedMutex>
|
||||
class TSA_CAPABILITY("SharedMutexHelper") SharedMutexHelper
|
||||
{
|
||||
public:
|
||||
// Exclusive ownership
|
||||
void lock() TSA_ACQUIRE() /// NOLINT
|
||||
{
|
||||
static_cast<Derived *>(this)->lockImpl();
|
||||
}
|
||||
|
||||
bool try_lock() TSA_TRY_ACQUIRE(true) /// NOLINT
|
||||
{
|
||||
static_cast<Derived *>(this)->tryLockImpl();
|
||||
}
|
||||
|
||||
void unlock() TSA_RELEASE() /// NOLINT
|
||||
{
|
||||
static_cast<Derived *>(this)->unlockImpl();
|
||||
}
|
||||
|
||||
// Shared ownership
|
||||
void lock_shared() TSA_ACQUIRE_SHARED() /// NOLINT
|
||||
{
|
||||
static_cast<Derived *>(this)->lockSharedImpl();
|
||||
}
|
||||
|
||||
bool try_lock_shared() TSA_TRY_ACQUIRE_SHARED(true) /// NOLINT
|
||||
{
|
||||
static_cast<Derived *>(this)->tryLockSharedImpl();
|
||||
}
|
||||
|
||||
void unlock_shared() TSA_RELEASE_SHARED() /// NOLINT
|
||||
{
|
||||
static_cast<Derived *>(this)->unlockSharedImpl();
|
||||
}
|
||||
|
||||
protected:
|
||||
void lockImpl() TSA_NO_THREAD_SAFETY_ANALYSIS
|
||||
{
|
||||
mutex.lock();
|
||||
}
|
||||
|
||||
void tryLockImpl() TSA_NO_THREAD_SAFETY_ANALYSIS
|
||||
{
|
||||
mutex.try_lock();
|
||||
}
|
||||
|
||||
void unlockImpl() TSA_NO_THREAD_SAFETY_ANALYSIS
|
||||
{
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
void lockSharedImpl() TSA_NO_THREAD_SAFETY_ANALYSIS
|
||||
{
|
||||
mutex.lock_shared();
|
||||
}
|
||||
|
||||
void tryLockSharedImpl() TSA_NO_THREAD_SAFETY_ANALYSIS
|
||||
{
|
||||
mutex.try_lock_shared();
|
||||
}
|
||||
|
||||
void unlockSharedImpl() TSA_NO_THREAD_SAFETY_ANALYSIS
|
||||
{
|
||||
mutex.unlock_shared();
|
||||
}
|
||||
|
||||
MutexType mutex;
|
||||
};
|
||||
|
||||
}
|
16
src/Common/escapeString.cpp
Normal file
16
src/Common/escapeString.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include <Common/escapeString.h>
|
||||
|
||||
#include <IO/WriteBufferFromString.h>
|
||||
#include <IO/WriteHelpers.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
String escapeString(std::string_view value)
|
||||
{
|
||||
WriteBufferFromOwnString buf;
|
||||
writeEscapedString(value, buf);
|
||||
return buf.str();
|
||||
}
|
||||
|
||||
}
|
10
src/Common/escapeString.h
Normal file
10
src/Common/escapeString.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <base/types.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
String escapeString(std::string_view value);
|
||||
|
||||
}
|
@ -74,7 +74,7 @@ struct AsyncLoaderTest
|
||||
T randomInt(T from, T to)
|
||||
{
|
||||
std::uniform_int_distribution<T> distribution(from, to);
|
||||
std::scoped_lock lock(rng_mutex);
|
||||
std::lock_guard lock(rng_mutex);
|
||||
return distribution(rng);
|
||||
}
|
||||
|
||||
|
@ -2496,25 +2496,15 @@ void KeeperStorage::dumpSessionsAndEphemerals(WriteBufferFromOwnString & buf) co
|
||||
uint64_t KeeperStorage::getTotalWatchesCount() const
|
||||
{
|
||||
uint64_t ret = 0;
|
||||
for (const auto & [path, subscribed_sessions] : watches)
|
||||
ret += subscribed_sessions.size();
|
||||
|
||||
for (const auto & [path, subscribed_sessions] : list_watches)
|
||||
ret += subscribed_sessions.size();
|
||||
for (const auto & [session, paths] : sessions_and_watchers)
|
||||
ret += paths.size();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t KeeperStorage::getSessionsWithWatchesCount() const
|
||||
{
|
||||
std::unordered_set<int64_t> counter;
|
||||
for (const auto & [path, subscribed_sessions] : watches)
|
||||
counter.insert(subscribed_sessions.begin(), subscribed_sessions.end());
|
||||
|
||||
for (const auto & [path, subscribed_sessions] : list_watches)
|
||||
counter.insert(subscribed_sessions.begin(), subscribed_sessions.end());
|
||||
|
||||
return counter.size();
|
||||
return sessions_and_watchers.size();
|
||||
}
|
||||
|
||||
uint64_t KeeperStorage::getTotalEphemeralNodesCount() const
|
||||
|
@ -89,17 +89,14 @@ void Range::shrinkToIncludedIfPossible()
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
bool Range::equals(const Field & lhs, const Field & rhs)
|
||||
{
|
||||
inline bool equals(const Field & lhs, const Field & rhs)
|
||||
{
|
||||
return applyVisitor(FieldVisitorAccurateEquals(), lhs, rhs);
|
||||
}
|
||||
return applyVisitor(FieldVisitorAccurateEquals(), lhs, rhs);
|
||||
}
|
||||
|
||||
inline bool less(const Field & lhs, const Field & rhs)
|
||||
{
|
||||
return applyVisitor(FieldVisitorAccurateLess(), lhs, rhs);
|
||||
}
|
||||
bool Range::less(const Field & lhs, const Field & rhs)
|
||||
{
|
||||
return applyVisitor(FieldVisitorAccurateLess(), lhs, rhs);
|
||||
}
|
||||
|
||||
bool Range::empty() const
|
||||
|
@ -59,6 +59,9 @@ public:
|
||||
static Range createRightBounded(const FieldRef & right_point, bool right_included, bool with_null = false);
|
||||
static Range createLeftBounded(const FieldRef & left_point, bool left_included, bool with_null = false);
|
||||
|
||||
static ALWAYS_INLINE bool equals(const Field & lhs, const Field & rhs);
|
||||
static ALWAYS_INLINE bool less(const Field & lhs, const Field & rhs);
|
||||
|
||||
/** Optimize the range. If it has an open boundary and the Field type is "loose"
|
||||
* - then convert it to closed, narrowing by one.
|
||||
* That is, for example, turn (0,2) into [1].
|
||||
|
@ -314,10 +314,6 @@ class IColumn;
|
||||
\
|
||||
M(Bool, partial_result_on_first_cancel, false, "Allows query to return a partial result after cancel.", 0) \
|
||||
\
|
||||
M(Bool, allow_experimental_partial_result, 0, "Enable experimental feature: partial results for running queries.", 0) \
|
||||
M(Milliseconds, partial_result_update_duration_ms, 0, "Interval (in milliseconds) for sending updates with partial data about the result table to the client (in interactive mode) during query execution. Setting to 0 disables partial results. Only supported for single-threaded GROUP BY without key, ORDER BY, LIMIT and OFFSET.", 0) \
|
||||
M(UInt64, max_rows_in_partial_result, 10, "Maximum rows to show in the partial result after every real-time update while the query runs (use partial result limit + OFFSET as a value in case of OFFSET in the query).", 0) \
|
||||
\
|
||||
M(Bool, ignore_on_cluster_for_replicated_udf_queries, false, "Ignore ON CLUSTER clause for replicated UDF management queries.", 0) \
|
||||
M(Bool, ignore_on_cluster_for_replicated_access_entities_queries, false, "Ignore ON CLUSTER clause for replicated access entities management queries.", 0) \
|
||||
/** Settings for testing hedged requests */ \
|
||||
@ -546,11 +542,13 @@ class IColumn;
|
||||
M(Bool, database_atomic_wait_for_drop_and_detach_synchronously, false, "When executing DROP or DETACH TABLE in Atomic database, wait for table data to be finally dropped or detached.", 0) \
|
||||
M(Bool, enable_scalar_subquery_optimization, true, "If it is set to true, prevent scalar subqueries from (de)serializing large scalar values and possibly avoid running the same subquery more than once.", 0) \
|
||||
M(Bool, optimize_trivial_count_query, true, "Process trivial 'SELECT count() FROM table' query from metadata.", 0) \
|
||||
M(Bool, optimize_trivial_approximate_count_query, false, "Use an approximate value for trivial count optimization of storages that support such estimations.", 0) \
|
||||
M(Bool, optimize_count_from_files, true, "Optimize counting rows from files in supported input formats", 0) \
|
||||
M(Bool, use_cache_for_count_from_files, true, "Use cache to count the number of rows in files", 0) \
|
||||
M(Bool, optimize_respect_aliases, true, "If it is set to true, it will respect aliases in WHERE/GROUP BY/ORDER BY, that will help with partition pruning/secondary indexes/optimize_aggregation_in_order/optimize_read_in_order/optimize_trivial_count", 0) \
|
||||
M(UInt64, mutations_sync, 0, "Wait for synchronous execution of ALTER TABLE UPDATE/DELETE queries (mutations). 0 - execute asynchronously. 1 - wait current server. 2 - wait all replicas if they exist.", 0) \
|
||||
M(Bool, enable_lightweight_delete, true, "Enable lightweight DELETE mutations for mergetree tables.", 0) ALIAS(allow_experimental_lightweight_delete) \
|
||||
M(Bool, apply_deleted_mask, true, "Enables filtering out rows deleted with lightweight DELETE. If disabled, a query will be able to read those rows. This is useful for debugging and \"undelete\" scenarios", 0) \
|
||||
M(Bool, optimize_move_functions_out_of_any, false, "Move functions out of aggregate functions 'any', 'anyLast'.", 0) \
|
||||
M(Bool, optimize_normalize_count_variants, true, "Rewrite aggregate functions that semantically equals to count() as count().", 0) \
|
||||
M(Bool, optimize_injective_functions_inside_uniq, true, "Delete injective functions of one argument inside uniq*() functions.", 0) \
|
||||
@ -683,6 +681,7 @@ class IColumn;
|
||||
M(Bool, query_plan_aggregation_in_order, true, "Use query plan for aggregation-in-order optimisation", 0) \
|
||||
M(Bool, query_plan_remove_redundant_sorting, true, "Remove redundant sorting in query plan. For example, sorting steps related to ORDER BY clauses in subqueries", 0) \
|
||||
M(Bool, query_plan_remove_redundant_distinct, true, "Remove redundant Distinct step in query plan", 0) \
|
||||
M(Bool, query_plan_enable_multithreading_after_window_functions, true, "Enable multithreading after evaluating window functions to allow parallel stream processing", 0) \
|
||||
M(UInt64, regexp_max_matches_per_row, 1000, "Max matches of any single regexp per row, used to safeguard 'extractAllGroupsHorizontal' against consuming too much memory with greedy RE.", 0) \
|
||||
\
|
||||
M(UInt64, limit, 0, "Limit on read rows from the most 'end' result for select query, default 0 means no limit length", 0) \
|
||||
@ -812,9 +811,10 @@ class IColumn;
|
||||
M(Bool, allow_create_index_without_type, false, "Allow CREATE INDEX query without TYPE. Query will be ignored. Made for SQL compatibility tests.", 0) \
|
||||
M(Bool, create_index_ignore_unique, false, "Ignore UNIQUE keyword in CREATE UNIQUE INDEX. Made for SQL compatibility tests.", 0) \
|
||||
M(Bool, print_pretty_type_names, false, "Print pretty type names in DESCRIBE query and toTypeName() function", 0) \
|
||||
M(Bool, create_table_empty_primary_key_by_default, false, "Allow to create *MergeTree tables with empty primary key when ORDER BY and PRIMARY KEY not specified", 0) \
|
||||
|
||||
// End of COMMON_SETTINGS
|
||||
// Please add settings related to formats into the FORMAT_FACTORY_SETTINGS, move obsolete settings to OBSOLETE_SETTINGS and obsolete format settings to OBSOLETE_FORMAT_SETTINGS.
|
||||
// Please add settings related to formats into the FORMAT_FACTORY_SETTINGS, move obsolete settings to OBSOLETE_SETTINGS and obsolete format settings to OBSOLETE_FORMAT_SETTINGS.
|
||||
|
||||
#define MAKE_OBSOLETE(M, TYPE, NAME, DEFAULT) \
|
||||
M(TYPE, NAME, DEFAULT, "Obsolete setting, does nothing.", BaseSettingsHelpers::Flags::OBSOLETE)
|
||||
@ -899,6 +899,7 @@ class IColumn;
|
||||
M(Bool, input_format_allow_seeks, true, "Allow seeks while reading in ORC/Parquet/Arrow input formats", 0) \
|
||||
M(Bool, input_format_orc_allow_missing_columns, false, "Allow missing columns while reading ORC input formats", 0) \
|
||||
M(Bool, input_format_orc_use_fast_decoder, true, "Use a faster ORC decoder implementation.", 0) \
|
||||
M(Bool, input_format_orc_filter_push_down, true, "When reading ORC files, skip whole stripes or row groups based on the WHERE/PREWHERE expressions, min/max statistics or bloom filter in the ORC metadata.", 0) \
|
||||
M(Bool, input_format_parquet_allow_missing_columns, false, "Allow missing columns while reading Parquet input formats", 0) \
|
||||
M(UInt64, input_format_parquet_local_file_min_bytes_for_seek, 8192, "Min bytes required for local read (file) to do seek, instead of read with ignore in Parquet input format", 0) \
|
||||
M(Bool, input_format_arrow_allow_missing_columns, false, "Allow missing columns while reading Arrow input formats", 0) \
|
||||
@ -982,6 +983,7 @@ class IColumn;
|
||||
\
|
||||
M(Bool, output_format_json_escape_forward_slashes, true, "Controls escaping forward slashes for string outputs in JSON output format. This is intended for compatibility with JavaScript. Don't confuse with backslashes that are always escaped.", 0) \
|
||||
M(Bool, output_format_json_named_tuples_as_objects, true, "Serialize named tuple columns as JSON objects.", 0) \
|
||||
M(Bool, output_format_json_skip_null_value_in_named_tuples, false, "Skip key value pairs with null value when serialize named tuple columns as JSON objects. It is only valid when output_format_json_named_tuples_as_objects is true.", 0) \
|
||||
M(Bool, output_format_json_array_of_rows, false, "Output a JSON array of all rows in JSONEachRow(Compact) format.", 0) \
|
||||
M(Bool, output_format_json_validate_utf8, false, "Validate UTF-8 sequences in JSON output formats, doesn't impact formats JSON/JSONCompact/JSONColumnsWithMetadata, they always validate utf8", 0) \
|
||||
\
|
||||
@ -1049,6 +1051,7 @@ class IColumn;
|
||||
\
|
||||
M(Bool, output_format_orc_string_as_string, false, "Use ORC String type instead of Binary for String columns", 0) \
|
||||
M(ORCCompression, output_format_orc_compression_method, "lz4", "Compression method for ORC output format. Supported codecs: lz4, snappy, zlib, zstd, none (uncompressed)", 0) \
|
||||
M(UInt64, output_format_orc_row_index_stride, 10'000, "Target row index stride in ORC output format", 0) \
|
||||
\
|
||||
M(CapnProtoEnumComparingMode, format_capn_proto_enum_comparising_mode, FormatSettings::CapnProtoEnumComparingMode::BY_VALUES, "How to map ClickHouse Enum and CapnProto Enum", 0) \
|
||||
\
|
||||
@ -1073,8 +1076,10 @@ class IColumn;
|
||||
M(Bool, regexp_dict_flag_case_insensitive, false, "Use case-insensitive matching for a regexp_tree dictionary. Can be overridden in individual expressions with (?i) and (?-i).", 0) \
|
||||
M(Bool, regexp_dict_flag_dotall, false, "Allow '.' to match newline characters for a regexp_tree dictionary.", 0) \
|
||||
\
|
||||
M(Bool, dictionary_use_async_executor, false, "Execute a pipeline for reading from a dictionary with several threads. It's supported only by DIRECT dictionary with CLICKHOUSE source.", 0) \
|
||||
M(Bool, dictionary_use_async_executor, false, "Execute a pipeline for reading dictionary source in several threads. It's supported only by dictionaries with local CLICKHOUSE source.", 0) \
|
||||
M(Bool, precise_float_parsing, false, "Prefer more precise (but slower) float parsing algorithm", 0) \
|
||||
M(DateTimeOverflowBehavior, date_time_overflow_behavior, "ignore", "Overflow mode for Date, Date32, DateTime, DateTime64 types. Possible values: 'ignore', 'throw', 'saturate'.", 0) \
|
||||
|
||||
|
||||
// End of FORMAT_FACTORY_SETTINGS
|
||||
// Please add settings non-related to formats into the COMMON_SETTINGS above.
|
||||
|
@ -190,4 +190,9 @@ IMPLEMENT_SETTING_ENUM(ExternalCommandStderrReaction, ErrorCodes::BAD_ARGUMENTS,
|
||||
{"log_last", ExternalCommandStderrReaction::LOG_LAST},
|
||||
{"throw", ExternalCommandStderrReaction::THROW}})
|
||||
|
||||
IMPLEMENT_SETTING_ENUM(DateTimeOverflowBehavior, ErrorCodes::BAD_ARGUMENTS,
|
||||
{{"throw", FormatSettings::DateTimeOverflowBehavior::Throw},
|
||||
{"ignore", FormatSettings::DateTimeOverflowBehavior::Ignore},
|
||||
{"saturate", FormatSettings::DateTimeOverflowBehavior::Saturate}})
|
||||
|
||||
}
|
||||
|
@ -242,4 +242,6 @@ DECLARE_SETTING_ENUM(S3QueueAction)
|
||||
|
||||
DECLARE_SETTING_ENUM(ExternalCommandStderrReaction)
|
||||
|
||||
DECLARE_SETTING_ENUM_WITH_RENAME(DateTimeOverflowBehavior, FormatSettings::DateTimeOverflowBehavior)
|
||||
|
||||
}
|
||||
|
@ -452,10 +452,25 @@ inline bool isObject(const T & data_type) { return WhichDataType(data_type).isOb
|
||||
template <typename T>
|
||||
inline bool isUInt8(const T & data_type) { return WhichDataType(data_type).isUInt8(); }
|
||||
template <typename T>
|
||||
inline bool isUInt16(const T & data_type) { return WhichDataType(data_type).isUInt16(); }
|
||||
template <typename T>
|
||||
inline bool isUInt32(const T & data_type) { return WhichDataType(data_type).isUInt32(); }
|
||||
template <typename T>
|
||||
inline bool isUInt64(const T & data_type) { return WhichDataType(data_type).isUInt64(); }
|
||||
template <typename T>
|
||||
inline bool isUnsignedInteger(const T & data_type) { return WhichDataType(data_type).isUInt(); }
|
||||
|
||||
template <typename T>
|
||||
inline bool isInt8(const T & data_type) { return WhichDataType(data_type).isInt8(); }
|
||||
template <typename T>
|
||||
inline bool isInt16(const T & data_type) { return WhichDataType(data_type).isInt16(); }
|
||||
template <typename T>
|
||||
inline bool isInt32(const T & data_type) { return WhichDataType(data_type).isInt32(); }
|
||||
template <typename T>
|
||||
inline bool isInt64(const T & data_type) { return WhichDataType(data_type).isInt64(); }
|
||||
template <typename T>
|
||||
inline bool isInt(const T & data_type) { return WhichDataType(data_type).isInt(); }
|
||||
|
||||
template <typename T>
|
||||
inline bool isInteger(const T & data_type)
|
||||
{
|
||||
|
@ -163,16 +163,23 @@ void SerializationTuple::serializeTextJSON(const IColumn & column, size_t row_nu
|
||||
&& have_explicit_names)
|
||||
{
|
||||
writeChar('{', ostr);
|
||||
|
||||
bool first = true;
|
||||
for (size_t i = 0; i < elems.size(); ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
const auto & element_column = extractElementColumn(column, i);
|
||||
if (settings.json.skip_null_value_in_named_tuples && element_column.isNullAt(row_num))
|
||||
continue;
|
||||
|
||||
if (!first)
|
||||
writeChar(',', ostr);
|
||||
}
|
||||
|
||||
writeJSONString(elems[i]->getElementName(), ostr, settings);
|
||||
writeChar(':', ostr);
|
||||
elems[i]->serializeTextJSON(extractElementColumn(column, i), row_num, ostr, settings);
|
||||
elems[i]->serializeTextJSON(element_column, row_num, ostr, settings);
|
||||
first = false;
|
||||
}
|
||||
|
||||
writeChar('}', ostr);
|
||||
}
|
||||
else
|
||||
@ -194,15 +201,24 @@ void SerializationTuple::serializeTextJSONPretty(const IColumn & column, size_t
|
||||
&& have_explicit_names)
|
||||
{
|
||||
writeCString("{\n", ostr);
|
||||
|
||||
bool first = true;
|
||||
for (size_t i = 0; i < elems.size(); ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
const auto & element_column = extractElementColumn(column, i);
|
||||
if (settings.json.skip_null_value_in_named_tuples && element_column.isNullAt(row_num))
|
||||
continue;
|
||||
|
||||
if (!first)
|
||||
writeCString(",\n", ostr);
|
||||
|
||||
writeChar(' ', (indent + 1) * 4, ostr);
|
||||
writeJSONString(elems[i]->getElementName(), ostr, settings);
|
||||
writeCString(": ", ostr);
|
||||
elems[i]->serializeTextJSONPretty(extractElementColumn(column, i), row_num, ostr, settings, indent + 1);
|
||||
first = false;
|
||||
}
|
||||
|
||||
writeChar('\n', ostr);
|
||||
writeChar(' ', indent * 4, ostr);
|
||||
writeChar('}', ostr);
|
||||
|
@ -116,6 +116,8 @@ DatabaseReplicated::DatabaseReplicated(
|
||||
|
||||
if (!db_settings.collection_name.value.empty())
|
||||
fillClusterAuthInfo(db_settings.collection_name.value, context_->getConfigRef());
|
||||
|
||||
replica_group_name = context_->getConfigRef().getString("replica_group_name", "");
|
||||
}
|
||||
|
||||
String DatabaseReplicated::getFullReplicaName(const String & shard, const String & replica)
|
||||
@ -175,6 +177,7 @@ void DatabaseReplicated::setCluster(ClusterPtr && new_cluster)
|
||||
|
||||
ClusterPtr DatabaseReplicated::getClusterImpl() const
|
||||
{
|
||||
Strings unfiltered_hosts;
|
||||
Strings hosts;
|
||||
Strings host_ids;
|
||||
|
||||
@ -186,11 +189,25 @@ ClusterPtr DatabaseReplicated::getClusterImpl() const
|
||||
{
|
||||
host_ids.resize(0);
|
||||
Coordination::Stat stat;
|
||||
hosts = zookeeper->getChildren(zookeeper_path + "/replicas", &stat);
|
||||
if (hosts.empty())
|
||||
unfiltered_hosts = zookeeper->getChildren(zookeeper_path + "/replicas", &stat);
|
||||
if (unfiltered_hosts.empty())
|
||||
throw Exception(ErrorCodes::NO_ACTIVE_REPLICAS, "No replicas of database {} found. "
|
||||
"It's possible if the first replica is not fully created yet "
|
||||
"or if the last replica was just dropped or due to logical error", zookeeper_path);
|
||||
|
||||
hosts.clear();
|
||||
std::vector<String> paths;
|
||||
for (const auto & host : unfiltered_hosts)
|
||||
paths.push_back(zookeeper_path + "/replicas/" + host + "/replica_group");
|
||||
|
||||
auto replica_groups = zookeeper->tryGet(paths);
|
||||
|
||||
for (size_t i = 0; i < paths.size(); ++i)
|
||||
{
|
||||
if (replica_groups[i].data == replica_group_name)
|
||||
hosts.push_back(unfiltered_hosts[i]);
|
||||
}
|
||||
|
||||
Int32 cversion = stat.cversion;
|
||||
::sort(hosts.begin(), hosts.end());
|
||||
|
||||
@ -309,6 +326,7 @@ void DatabaseReplicated::fillClusterAuthInfo(String collection_name, const Poco:
|
||||
cluster_auth_info.cluster_secure_connection = config_ref.getBool(config_prefix + ".cluster_secure_connection", false);
|
||||
}
|
||||
|
||||
|
||||
void DatabaseReplicated::tryConnectToZooKeeperAndInitDatabase(LoadingStrictnessLevel mode)
|
||||
{
|
||||
try
|
||||
@ -348,6 +366,21 @@ void DatabaseReplicated::tryConnectToZooKeeperAndInitDatabase(LoadingStrictnessL
|
||||
"Replica {} of shard {} of replicated database at {} already exists. Replica host ID: '{}', current host ID: '{}'",
|
||||
replica_name, shard_name, zookeeper_path, replica_host_id, host_id);
|
||||
}
|
||||
|
||||
/// Check that replica_group_name in ZooKeeper matches the local one and change it if necessary.
|
||||
String zk_replica_group_name;
|
||||
if (!current_zookeeper->tryGet(replica_path + "/replica_group", zk_replica_group_name))
|
||||
{
|
||||
/// Replica groups were introduced in 23.10, so the node might not exist
|
||||
current_zookeeper->create(replica_path + "/replica_group", replica_group_name, zkutil::CreateMode::Persistent);
|
||||
if (!replica_group_name.empty())
|
||||
createEmptyLogEntry(current_zookeeper);
|
||||
}
|
||||
else if (zk_replica_group_name != replica_group_name)
|
||||
{
|
||||
current_zookeeper->set(replica_path + "/replica_group", replica_group_name, -1);
|
||||
createEmptyLogEntry(current_zookeeper);
|
||||
}
|
||||
}
|
||||
else if (is_create_query)
|
||||
{
|
||||
@ -466,14 +499,17 @@ void DatabaseReplicated::createReplicaNodesInZooKeeper(const zkutil::ZooKeeperPt
|
||||
{
|
||||
Coordination::Stat stat;
|
||||
String max_log_ptr_str = current_zookeeper->get(zookeeper_path + "/max_log_ptr", &stat);
|
||||
|
||||
Coordination::Requests ops;
|
||||
ops.emplace_back(zkutil::makeCreateRequest(replica_path, host_id, zkutil::CreateMode::Persistent));
|
||||
ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/log_ptr", "0", zkutil::CreateMode::Persistent));
|
||||
ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/digest", "0", zkutil::CreateMode::Persistent));
|
||||
ops.emplace_back(zkutil::makeCreateRequest(replica_path + "/replica_group", replica_group_name, zkutil::CreateMode::Persistent));
|
||||
/// In addition to creating the replica nodes, we record the max_log_ptr at the instant where
|
||||
/// we declared ourself as an existing replica. We'll need this during recoverLostReplica to
|
||||
/// notify other nodes that issued new queries while this node was recovering.
|
||||
ops.emplace_back(zkutil::makeCheckRequest(zookeeper_path + "/max_log_ptr", stat.version));
|
||||
|
||||
Coordination::Responses responses;
|
||||
const auto code = current_zookeeper->tryMulti(ops, responses);
|
||||
if (code == Coordination::Error::ZOK)
|
||||
@ -585,7 +621,8 @@ void DatabaseReplicated::checkQueryValid(const ASTPtr & query, ContextPtr query_
|
||||
|
||||
if (auto * create = query->as<ASTCreateQuery>())
|
||||
{
|
||||
bool replicated_table = create->storage && create->storage->engine && startsWith(create->storage->engine->name, "Replicated");
|
||||
bool replicated_table = create->storage && create->storage->engine &&
|
||||
(startsWith(create->storage->engine->name, "Replicated") || startsWith(create->storage->engine->name, "Shared"));
|
||||
if (!replicated_table || !create->storage->engine->arguments)
|
||||
return;
|
||||
|
||||
@ -704,15 +741,30 @@ BlockIO DatabaseReplicated::tryEnqueueReplicatedDDL(const ASTPtr & query, Contex
|
||||
entry.tracing_context = OpenTelemetry::CurrentContext();
|
||||
String node_path = ddl_worker->tryEnqueueAndExecuteEntry(entry, query_context);
|
||||
|
||||
Strings hosts_to_wait = getZooKeeper()->getChildren(zookeeper_path + "/replicas");
|
||||
Strings hosts_to_wait;
|
||||
Strings unfiltered_hosts = getZooKeeper()->getChildren(zookeeper_path + "/replicas");
|
||||
|
||||
std::vector<String> paths;
|
||||
for (const auto & host : unfiltered_hosts)
|
||||
paths.push_back(zookeeper_path + "/replicas/" + host + "/replica_group");
|
||||
|
||||
auto replica_groups = getZooKeeper()->tryGet(paths);
|
||||
|
||||
for (size_t i = 0; i < paths.size(); ++i)
|
||||
{
|
||||
if (replica_groups[i].data == replica_group_name)
|
||||
hosts_to_wait.push_back(unfiltered_hosts[i]);
|
||||
}
|
||||
|
||||
return getDistributedDDLStatus(node_path, entry, query_context, &hosts_to_wait);
|
||||
}
|
||||
|
||||
static UUID getTableUUIDIfReplicated(const String & metadata, ContextPtr context)
|
||||
{
|
||||
bool looks_like_replicated = metadata.find("Replicated") != std::string::npos;
|
||||
bool looks_like_shared = metadata.find("Shared") != std::string::npos;
|
||||
bool looks_like_merge_tree = metadata.find("MergeTree") != std::string::npos;
|
||||
if (!looks_like_replicated || !looks_like_merge_tree)
|
||||
if (!(looks_like_replicated || looks_like_shared) || !looks_like_merge_tree)
|
||||
return UUIDHelpers::Nil;
|
||||
|
||||
ParserCreateQuery parser;
|
||||
@ -722,7 +774,8 @@ static UUID getTableUUIDIfReplicated(const String & metadata, ContextPtr context
|
||||
const ASTCreateQuery & create = query->as<const ASTCreateQuery &>();
|
||||
if (!create.storage || !create.storage->engine)
|
||||
return UUIDHelpers::Nil;
|
||||
if (!startsWith(create.storage->engine->name, "Replicated") || !endsWith(create.storage->engine->name, "MergeTree"))
|
||||
if (!(startsWith(create.storage->engine->name, "Replicated") || startsWith(create.storage->engine->name, "Shared"))
|
||||
|| !endsWith(create.storage->engine->name, "MergeTree"))
|
||||
return UUIDHelpers::Nil;
|
||||
chassert(create.uuid != UUIDHelpers::Nil);
|
||||
return create.uuid;
|
||||
|
@ -56,6 +56,7 @@ public:
|
||||
|
||||
String getShardName() const { return shard_name; }
|
||||
String getReplicaName() const { return replica_name; }
|
||||
String getReplicaGroupName() const { return replica_group_name; }
|
||||
String getFullReplicaName() const;
|
||||
static String getFullReplicaName(const String & shard, const String & replica);
|
||||
static std::pair<String, String> parseFullReplicaName(const String & name);
|
||||
@ -126,6 +127,7 @@ private:
|
||||
String zookeeper_path;
|
||||
String shard_name;
|
||||
String replica_name;
|
||||
String replica_group_name;
|
||||
String replica_path;
|
||||
DatabaseReplicatedSettings db_settings;
|
||||
|
||||
|
@ -128,7 +128,7 @@ void DatabaseReplicatedDDLWorker::initializeReplication()
|
||||
}
|
||||
|
||||
std::lock_guard lock{database->metadata_mutex};
|
||||
if (!database->checkDigestValid(context))
|
||||
if (!database->checkDigestValid(context, false))
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "Inconsistent database metadata after reconnection to ZooKeeper");
|
||||
}
|
||||
|
||||
|
@ -10,10 +10,10 @@
|
||||
#include <Common/ProfileEvents.h>
|
||||
#include <Common/ProfilingScopedRWLock.h>
|
||||
|
||||
#include <Dictionaries//DictionarySource.h>
|
||||
#include <Dictionaries/DictionarySource.h>
|
||||
#include <Dictionaries/DictionarySourceHelpers.h>
|
||||
#include <Dictionaries/HierarchyDictionariesUtils.h>
|
||||
|
||||
#include <Processors/Executors/PullingPipelineExecutor.h>
|
||||
#include <QueryPipeline/QueryPipelineBuilder.h>
|
||||
|
||||
namespace ProfileEvents
|
||||
@ -50,8 +50,7 @@ CacheDictionary<dictionary_key_type>::CacheDictionary(
|
||||
DictionarySourcePtr source_ptr_,
|
||||
CacheDictionaryStoragePtr cache_storage_ptr_,
|
||||
CacheDictionaryUpdateQueueConfiguration update_queue_configuration_,
|
||||
DictionaryLifetime dict_lifetime_,
|
||||
bool allow_read_expired_keys_)
|
||||
CacheDictionaryConfiguration configuration_)
|
||||
: IDictionary(dict_id_)
|
||||
, dict_struct(dict_struct_)
|
||||
, source_ptr{std::move(source_ptr_)}
|
||||
@ -63,9 +62,8 @@ CacheDictionary<dictionary_key_type>::CacheDictionary(
|
||||
{
|
||||
update(unit_to_update);
|
||||
})
|
||||
, dict_lifetime(dict_lifetime_)
|
||||
, configuration(configuration_)
|
||||
, log(&Poco::Logger::get("ExternalDictionaries"))
|
||||
, allow_read_expired_keys(allow_read_expired_keys_)
|
||||
, rnd_engine(randomSeed())
|
||||
{
|
||||
if (!source_ptr->supportsSelectiveLoad())
|
||||
@ -209,7 +207,7 @@ Columns CacheDictionary<dictionary_key_type>::getColumns(
|
||||
HashMap<KeyType, size_t> requested_keys_to_fetched_columns_during_update_index;
|
||||
MutableColumns fetched_columns_during_update = request.makeAttributesResultColumns();
|
||||
|
||||
if (not_found_keys_size == 0 && expired_keys_size > 0 && allow_read_expired_keys)
|
||||
if (not_found_keys_size == 0 && expired_keys_size > 0 && configuration.allow_read_expired_keys)
|
||||
{
|
||||
/// Start async update only if allow read expired keys and all keys are found
|
||||
update_queue.tryPushToUpdateQueueOrThrow(update_unit);
|
||||
@ -314,7 +312,7 @@ ColumnUInt8::Ptr CacheDictionary<dictionary_key_type>::hasKeys(const Columns & k
|
||||
|
||||
allow_expired_keys_during_aggregation = true;
|
||||
}
|
||||
else if (not_found_keys_size == 0 && expired_keys_size > 0 && allow_read_expired_keys)
|
||||
else if (not_found_keys_size == 0 && expired_keys_size > 0 && configuration.allow_read_expired_keys)
|
||||
{
|
||||
/// Start async update only if allow read expired keys and all keys are found
|
||||
update_queue.tryPushToUpdateQueueOrThrow(update_unit);
|
||||
@ -589,7 +587,7 @@ void CacheDictionary<dictionary_key_type>::update(CacheDictionaryUpdateUnitPtr<d
|
||||
|
||||
Columns fetched_columns_during_update = fetch_request.makeAttributesResultColumnsNonMutable();
|
||||
|
||||
PullingPipelineExecutor executor(pipeline);
|
||||
DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor);
|
||||
Block block;
|
||||
while (executor.pull(block))
|
||||
{
|
||||
|
@ -24,6 +24,14 @@
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
struct CacheDictionaryConfiguration
|
||||
{
|
||||
const bool allow_read_expired_keys;
|
||||
const DictionaryLifetime lifetime;
|
||||
const bool use_async_executor = false;
|
||||
};
|
||||
|
||||
/** CacheDictionary store keys in cache storage and can asynchronous and synchronous updates during keys fetch.
|
||||
|
||||
If keys are not found in storage during fetch, dictionary start update operation with update queue.
|
||||
@ -58,8 +66,7 @@ public:
|
||||
DictionarySourcePtr source_ptr_,
|
||||
CacheDictionaryStoragePtr cache_storage_ptr_,
|
||||
CacheDictionaryUpdateQueueConfiguration update_queue_configuration_,
|
||||
DictionaryLifetime dict_lifetime_,
|
||||
bool allow_read_expired_keys_);
|
||||
CacheDictionaryConfiguration configuration_);
|
||||
|
||||
~CacheDictionary() override;
|
||||
|
||||
@ -99,13 +106,12 @@ public:
|
||||
getSourceAndUpdateIfNeeded()->clone(),
|
||||
cache_storage_ptr,
|
||||
update_queue.getConfiguration(),
|
||||
dict_lifetime,
|
||||
allow_read_expired_keys);
|
||||
configuration);
|
||||
}
|
||||
|
||||
DictionarySourcePtr getSource() const override;
|
||||
|
||||
const DictionaryLifetime & getLifetime() const override { return dict_lifetime; }
|
||||
const DictionaryLifetime & getLifetime() const override { return configuration.lifetime; }
|
||||
|
||||
const DictionaryStructure & getStructure() const override { return dict_struct; }
|
||||
|
||||
@ -194,12 +200,10 @@ private:
|
||||
CacheDictionaryStoragePtr cache_storage_ptr;
|
||||
mutable CacheDictionaryUpdateQueue<dictionary_key_type> update_queue;
|
||||
|
||||
const DictionaryLifetime dict_lifetime;
|
||||
const CacheDictionaryConfiguration configuration;
|
||||
|
||||
Poco::Logger * log;
|
||||
|
||||
const bool allow_read_expired_keys;
|
||||
|
||||
mutable pcg64 rnd_engine;
|
||||
|
||||
/// This lock is used for the inner cache state update function lock it for
|
||||
|
@ -59,6 +59,8 @@ public:
|
||||
|
||||
bool hasUpdateField() const override;
|
||||
|
||||
bool isLocal() const { return configuration.is_local; }
|
||||
|
||||
DictionarySourcePtr clone() const override { return std::make_shared<ClickHouseDictionarySource>(*this); }
|
||||
|
||||
std::string toString() const override;
|
||||
|
@ -9,11 +9,15 @@
|
||||
#include <Poco/Util/AbstractConfiguration.h>
|
||||
#include <Common/SettingsChanges.h>
|
||||
|
||||
#include <Processors/Executors/PullingPipelineExecutor.h>
|
||||
#include <Processors/Executors/PullingAsyncPipelineExecutor.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int LOGICAL_ERROR;
|
||||
extern const int SIZES_OF_COLUMNS_DOESNT_MATCH;
|
||||
}
|
||||
|
||||
@ -130,4 +134,30 @@ String TransformWithAdditionalColumns::getName() const
|
||||
{
|
||||
return "TransformWithAdditionalColumns";
|
||||
}
|
||||
|
||||
DictionaryPipelineExecutor::DictionaryPipelineExecutor(QueryPipeline & pipeline_, bool async)
|
||||
: async_executor(async ? std::make_unique<PullingAsyncPipelineExecutor>(pipeline_) : nullptr)
|
||||
, executor(async ? nullptr : std::make_unique<PullingPipelineExecutor>(pipeline_))
|
||||
{}
|
||||
|
||||
bool DictionaryPipelineExecutor::pull(Block & block)
|
||||
{
|
||||
if (async_executor)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
bool has_data = async_executor->pull(block);
|
||||
if (has_data && !block)
|
||||
continue;
|
||||
return has_data;
|
||||
}
|
||||
}
|
||||
else if (executor)
|
||||
return executor->pull(block);
|
||||
else
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "DictionaryPipelineExecutor is not initialized");
|
||||
}
|
||||
|
||||
DictionaryPipelineExecutor::~DictionaryPipelineExecutor() = default;
|
||||
|
||||
}
|
||||
|
@ -16,6 +16,10 @@ namespace DB
|
||||
struct DictionaryStructure;
|
||||
class SettingsChanges;
|
||||
|
||||
class PullingPipelineExecutor;
|
||||
class PullingAsyncPipelineExecutor;
|
||||
class QueryPipeline;
|
||||
|
||||
/// For simple key
|
||||
|
||||
Block blockForIds(
|
||||
@ -51,4 +55,17 @@ private:
|
||||
size_t current_range_index = 0;
|
||||
};
|
||||
|
||||
/// Wrapper for `Pulling(Async)PipelineExecutor` to dynamically dispatch calls to the right executor
|
||||
class DictionaryPipelineExecutor
|
||||
{
|
||||
public:
|
||||
DictionaryPipelineExecutor(QueryPipeline & pipeline_, bool async);
|
||||
bool pull(Block & block);
|
||||
|
||||
~DictionaryPipelineExecutor();
|
||||
private:
|
||||
std::unique_ptr<PullingAsyncPipelineExecutor> async_executor;
|
||||
std::unique_ptr<PullingPipelineExecutor> executor;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -366,10 +366,10 @@ Pipe DirectDictionary<dictionary_key_type>::read(const Names & /* column_names *
|
||||
template <DictionaryKeyType dictionary_key_type>
|
||||
void DirectDictionary<dictionary_key_type>::applySettings(const Settings & settings)
|
||||
{
|
||||
if (dynamic_cast<const ClickHouseDictionarySource *>(source_ptr.get()))
|
||||
if (const auto * clickhouse_source = dynamic_cast<const ClickHouseDictionarySource *>(source_ptr.get()))
|
||||
{
|
||||
/// Only applicable for CLICKHOUSE dictionary source.
|
||||
use_async_executor = settings.dictionary_use_async_executor;
|
||||
use_async_executor = settings.dictionary_use_async_executor && clickhouse_source->isLocal();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,9 +12,9 @@
|
||||
#include <Functions/FunctionHelpers.h>
|
||||
|
||||
#include <QueryPipeline/QueryPipelineBuilder.h>
|
||||
#include <Processors/Executors/PullingPipelineExecutor.h>
|
||||
|
||||
#include <Dictionaries/DictionarySource.h>
|
||||
#include <Dictionaries/DictionarySourceHelpers.h>
|
||||
#include <Dictionaries/DictionaryFactory.h>
|
||||
#include <Dictionaries/HierarchyDictionariesUtils.h>
|
||||
|
||||
@ -288,7 +288,7 @@ DictionaryHierarchyParentToChildIndexPtr FlatDictionary::getHierarchicalIndex()
|
||||
const auto & hierarchical_attribute = attributes[hierarchical_attribute_index];
|
||||
const ContainerType<UInt64> & parent_keys = std::get<ContainerType<UInt64>>(hierarchical_attribute.container);
|
||||
|
||||
HashMap<UInt64, PaddedPODArray<UInt64>> parent_to_child;
|
||||
DictionaryHierarchicalParentToChildIndex::ParentToChildIndex parent_to_child;
|
||||
parent_to_child.reserve(element_count);
|
||||
|
||||
UInt64 child_keys_size = static_cast<UInt64>(parent_keys.size());
|
||||
@ -395,7 +395,7 @@ void FlatDictionary::updateData()
|
||||
if (!update_field_loaded_block || update_field_loaded_block->rows() == 0)
|
||||
{
|
||||
QueryPipeline pipeline(source_ptr->loadUpdatedAll());
|
||||
PullingPipelineExecutor executor(pipeline);
|
||||
DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor);
|
||||
update_field_loaded_block.reset();
|
||||
Block block;
|
||||
|
||||
@ -436,7 +436,7 @@ void FlatDictionary::loadData()
|
||||
if (!source_ptr->hasUpdateField())
|
||||
{
|
||||
QueryPipeline pipeline(source_ptr->loadAll());
|
||||
PullingPipelineExecutor executor(pipeline);
|
||||
DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor);
|
||||
|
||||
Block block;
|
||||
while (executor.pull(block))
|
||||
|
@ -27,6 +27,7 @@ public:
|
||||
size_t max_array_size;
|
||||
bool require_nonempty;
|
||||
DictionaryLifetime dict_lifetime;
|
||||
bool use_async_executor = false;
|
||||
};
|
||||
|
||||
FlatDictionary(
|
||||
|
@ -7,11 +7,12 @@
|
||||
#include <Columns/ColumnNullable.h>
|
||||
#include <Functions/FunctionHelpers.h>
|
||||
|
||||
#include <Dictionaries/ClickHouseDictionarySource.h>
|
||||
#include <Dictionaries/DictionarySource.h>
|
||||
#include <Dictionaries/DictionarySourceHelpers.h>
|
||||
#include <Dictionaries/DictionaryFactory.h>
|
||||
#include <Dictionaries/HierarchyDictionariesUtils.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
@ -328,7 +329,7 @@ DictionaryHierarchicalParentToChildIndexPtr HashedArrayDictionary<dictionary_key
|
||||
for (auto & [key, value] : key_attribute_container)
|
||||
index_to_key[value] = key;
|
||||
|
||||
HashMap<UInt64, PaddedPODArray<UInt64>> parent_to_child;
|
||||
DictionaryHierarchicalParentToChildIndex::ParentToChildIndex parent_to_child;
|
||||
parent_to_child.reserve(index_to_key.size());
|
||||
|
||||
size_t parent_keys_container_size = parent_keys_container.size();
|
||||
@ -409,7 +410,7 @@ void HashedArrayDictionary<dictionary_key_type>::updateData()
|
||||
if (!update_field_loaded_block || update_field_loaded_block->rows() == 0)
|
||||
{
|
||||
QueryPipeline pipeline(source_ptr->loadUpdatedAll());
|
||||
PullingPipelineExecutor executor(pipeline);
|
||||
DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor);
|
||||
update_field_loaded_block.reset();
|
||||
Block block;
|
||||
|
||||
@ -533,12 +534,12 @@ void HashedArrayDictionary<dictionary_key_type>::blockToAttributes(const Block &
|
||||
}
|
||||
|
||||
template <DictionaryKeyType dictionary_key_type>
|
||||
void HashedArrayDictionary<dictionary_key_type>::resize(size_t added_rows)
|
||||
void HashedArrayDictionary<dictionary_key_type>::resize(size_t total_rows)
|
||||
{
|
||||
if (unlikely(!added_rows))
|
||||
if (unlikely(!total_rows))
|
||||
return;
|
||||
|
||||
key_attribute.container.reserve(added_rows);
|
||||
key_attribute.container.reserve(total_rows);
|
||||
}
|
||||
|
||||
template <DictionaryKeyType dictionary_key_type>
|
||||
@ -727,14 +728,37 @@ void HashedArrayDictionary<dictionary_key_type>::loadData()
|
||||
{
|
||||
QueryPipeline pipeline;
|
||||
pipeline = QueryPipeline(source_ptr->loadAll());
|
||||
DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor);
|
||||
|
||||
UInt64 pull_time_microseconds = 0;
|
||||
UInt64 process_time_microseconds = 0;
|
||||
|
||||
size_t total_rows = 0;
|
||||
size_t total_blocks = 0;
|
||||
|
||||
PullingPipelineExecutor executor(pipeline);
|
||||
Block block;
|
||||
while (executor.pull(block))
|
||||
while (true)
|
||||
{
|
||||
resize(block.rows());
|
||||
Stopwatch watch_pull;
|
||||
bool has_data = executor.pull(block);
|
||||
pull_time_microseconds += watch_pull.elapsedMicroseconds();
|
||||
|
||||
if (!has_data)
|
||||
break;
|
||||
|
||||
++total_blocks;
|
||||
total_rows += block.rows();
|
||||
|
||||
Stopwatch watch_process;
|
||||
resize(total_rows);
|
||||
blockToAttributes(block);
|
||||
process_time_microseconds += watch_process.elapsedMicroseconds();
|
||||
}
|
||||
|
||||
LOG_DEBUG(&Poco::Logger::get("HashedArrayDictionary"),
|
||||
"Finished {}reading {} blocks with {} rows from pipeline in {:.2f} sec and inserted into hashtable in {:.2f} sec",
|
||||
configuration.use_async_executor ? "asynchronous " : "",
|
||||
total_blocks, total_rows, pull_time_microseconds / 1000000.0, process_time_microseconds / 1000000.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -843,6 +867,7 @@ void registerDictionaryArrayHashed(DictionaryFactory & factory)
|
||||
const DictionaryStructure & dict_struct,
|
||||
const Poco::Util::AbstractConfiguration & config,
|
||||
const std::string & config_prefix,
|
||||
ContextPtr global_context,
|
||||
DictionarySourcePtr source_ptr,
|
||||
DictionaryKeyType dictionary_key_type) -> DictionaryPtr
|
||||
{
|
||||
@ -863,6 +888,12 @@ void registerDictionaryArrayHashed(DictionaryFactory & factory)
|
||||
|
||||
HashedArrayDictionaryStorageConfiguration configuration{require_nonempty, dict_lifetime};
|
||||
|
||||
ContextMutablePtr context = copyContextAndApplySettingsFromDictionaryConfig(global_context, config, config_prefix);
|
||||
const auto & settings = context->getSettingsRef();
|
||||
|
||||
const auto * clickhouse_source = dynamic_cast<const ClickHouseDictionarySource *>(source_ptr.get());
|
||||
configuration.use_async_executor = clickhouse_source && clickhouse_source->isLocal() && settings.dictionary_use_async_executor;
|
||||
|
||||
if (dictionary_key_type == DictionaryKeyType::Simple)
|
||||
return std::make_unique<HashedArrayDictionary<DictionaryKeyType::Simple>>(dict_id, dict_struct, std::move(source_ptr), configuration);
|
||||
else
|
||||
@ -872,9 +903,15 @@ void registerDictionaryArrayHashed(DictionaryFactory & factory)
|
||||
using namespace std::placeholders;
|
||||
|
||||
factory.registerLayout("hashed_array",
|
||||
[=](auto && a, auto && b, auto && c, auto && d, DictionarySourcePtr e, ContextPtr /* global_context */, bool /*created_from_ddl*/){ return create_layout(a, b, c, d, std::move(e), DictionaryKeyType::Simple); }, false);
|
||||
[=](auto && a, auto && b, auto && c, auto && d, DictionarySourcePtr e, ContextPtr global_context, bool /*created_from_ddl*/)
|
||||
{
|
||||
return create_layout(a, b, c, d, global_context, std::move(e), DictionaryKeyType::Simple);
|
||||
}, false);
|
||||
factory.registerLayout("complex_key_hashed_array",
|
||||
[=](auto && a, auto && b, auto && c, auto && d, DictionarySourcePtr e, ContextPtr /* global_context */, bool /*created_from_ddl*/){ return create_layout(a, b, c, d, std::move(e), DictionaryKeyType::Complex); }, true);
|
||||
[=](auto && a, auto && b, auto && c, auto && d, DictionarySourcePtr e, ContextPtr global_context, bool /*created_from_ddl*/)
|
||||
{
|
||||
return create_layout(a, b, c, d, global_context, std::move(e), DictionaryKeyType::Complex);
|
||||
}, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ struct HashedArrayDictionaryStorageConfiguration
|
||||
{
|
||||
const bool require_nonempty;
|
||||
const DictionaryLifetime lifetime;
|
||||
bool use_async_executor = false;
|
||||
};
|
||||
|
||||
template <DictionaryKeyType dictionary_key_type>
|
||||
@ -212,7 +213,7 @@ private:
|
||||
template <typename GetContainerFunc>
|
||||
void getAttributeContainer(size_t attribute_index, GetContainerFunc && get_container_func) const;
|
||||
|
||||
void resize(size_t added_rows);
|
||||
void resize(size_t total_rows);
|
||||
|
||||
const DictionaryStructure dict_struct;
|
||||
const DictionarySourcePtr source_ptr;
|
||||
|
@ -4,13 +4,13 @@
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <Common/ArenaUtils.h>
|
||||
#include <Common/ThreadPool.h>
|
||||
#include <Common/setThreadName.h>
|
||||
#include <Common/logger_useful.h>
|
||||
#include <Common/ConcurrentBoundedQueue.h>
|
||||
#include <Common/CurrentMetrics.h>
|
||||
#include <Common/MemoryTrackerBlockerInThread.h>
|
||||
#include <Common/ThreadPool.h>
|
||||
#include <Common/logger_useful.h>
|
||||
#include <Common/scope_guard_safe.h>
|
||||
#include <Common/setThreadName.h>
|
||||
|
||||
#include <Core/Defines.h>
|
||||
|
||||
@ -20,7 +20,9 @@
|
||||
#include <Columns/ColumnNullable.h>
|
||||
#include <Functions/FunctionHelpers.h>
|
||||
|
||||
#include <Dictionaries/ClickHouseDictionarySource.h>
|
||||
#include <Dictionaries/DictionarySource.h>
|
||||
#include <Dictionaries/DictionarySourceHelpers.h>
|
||||
#include <Dictionaries/DictionaryFactory.h>
|
||||
#include <Dictionaries/HierarchyDictionariesUtils.h>
|
||||
#include <Dictionaries/HashedDictionaryCollectionTraits.h>
|
||||
@ -600,7 +602,7 @@ DictionaryHierarchyParentToChildIndexPtr HashedDictionary<dictionary_key_type, s
|
||||
for (const auto & map : child_key_to_parent_key_maps)
|
||||
size += map.size();
|
||||
|
||||
HashMap<UInt64, PaddedPODArray<UInt64>> parent_to_child;
|
||||
DictionaryHierarchicalParentToChildIndex::ParentToChildIndex parent_to_child;
|
||||
parent_to_child.reserve(size);
|
||||
|
||||
for (const auto & map : child_key_to_parent_key_maps)
|
||||
@ -709,7 +711,7 @@ void HashedDictionary<dictionary_key_type, sparse, sharded>::updateData()
|
||||
if (!update_field_loaded_block || update_field_loaded_block->rows() == 0)
|
||||
{
|
||||
QueryPipeline pipeline(source_ptr->loadUpdatedAll());
|
||||
PullingPipelineExecutor executor(pipeline);
|
||||
DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor);
|
||||
update_field_loaded_block.reset();
|
||||
Block block;
|
||||
|
||||
@ -938,7 +940,7 @@ void HashedDictionary<dictionary_key_type, sparse, sharded>::loadData()
|
||||
|
||||
QueryPipeline pipeline = QueryPipeline(source_ptr->loadAll());
|
||||
|
||||
PullingPipelineExecutor executor(pipeline);
|
||||
DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor);
|
||||
Block block;
|
||||
DictionaryKeysArenaHolder<dictionary_key_type> arena_holder;
|
||||
|
||||
@ -1147,6 +1149,7 @@ void registerDictionaryHashed(DictionaryFactory & factory)
|
||||
const Poco::Util::AbstractConfiguration & config,
|
||||
const std::string & config_prefix,
|
||||
DictionarySourcePtr source_ptr,
|
||||
ContextPtr global_context,
|
||||
DictionaryKeyType dictionary_key_type,
|
||||
bool sparse) -> DictionaryPtr
|
||||
{
|
||||
@ -1189,12 +1192,19 @@ void registerDictionaryHashed(DictionaryFactory & factory)
|
||||
if (max_load_factor < 0.5f || max_load_factor > 0.99f)
|
||||
throw Exception(ErrorCodes::BAD_ARGUMENTS, "{}: max_load_factor parameter should be within [0.5, 0.99], got {}", full_name, max_load_factor);
|
||||
|
||||
ContextMutablePtr context = copyContextAndApplySettingsFromDictionaryConfig(global_context, config, config_prefix);
|
||||
const auto & settings = context->getSettingsRef();
|
||||
|
||||
const auto * clickhouse_source = dynamic_cast<const ClickHouseDictionarySource *>(source_ptr.get());
|
||||
bool use_async_executor = clickhouse_source && clickhouse_source->isLocal() && settings.dictionary_use_async_executor;
|
||||
|
||||
HashedDictionaryConfiguration configuration{
|
||||
static_cast<UInt64>(shards),
|
||||
static_cast<UInt64>(shard_load_queue_backlog),
|
||||
max_load_factor,
|
||||
require_nonempty,
|
||||
dict_lifetime,
|
||||
use_async_executor,
|
||||
};
|
||||
|
||||
if (source_ptr->hasUpdateField() && shards > 1)
|
||||
@ -1239,13 +1249,13 @@ void registerDictionaryHashed(DictionaryFactory & factory)
|
||||
using namespace std::placeholders;
|
||||
|
||||
factory.registerLayout("hashed",
|
||||
[=](auto && a, auto && b, auto && c, auto && d, DictionarySourcePtr e, ContextPtr /* global_context */, bool /*created_from_ddl*/){ return create_layout(a, b, c, d, std::move(e), DictionaryKeyType::Simple, /* sparse = */ false); }, false);
|
||||
[=](auto && a, auto && b, auto && c, auto && d, DictionarySourcePtr e, ContextPtr global_context, bool /*created_from_ddl*/){ return create_layout(a, b, c, d, std::move(e), global_context, DictionaryKeyType::Simple, /* sparse = */ false); }, false);
|
||||
factory.registerLayout("sparse_hashed",
|
||||
[=](auto && a, auto && b, auto && c, auto && d, DictionarySourcePtr e, ContextPtr /* global_context */, bool /*created_from_ddl*/){ return create_layout(a, b, c, d, std::move(e), DictionaryKeyType::Simple, /* sparse = */ true); }, false);
|
||||
[=](auto && a, auto && b, auto && c, auto && d, DictionarySourcePtr e, ContextPtr global_context, bool /*created_from_ddl*/){ return create_layout(a, b, c, d, std::move(e), global_context, DictionaryKeyType::Simple, /* sparse = */ true); }, false);
|
||||
factory.registerLayout("complex_key_hashed",
|
||||
[=](auto && a, auto && b, auto && c, auto && d, DictionarySourcePtr e, ContextPtr /* global_context */, bool /*created_from_ddl*/){ return create_layout(a, b, c, d, std::move(e), DictionaryKeyType::Complex, /* sparse = */ false); }, true);
|
||||
[=](auto && a, auto && b, auto && c, auto && d, DictionarySourcePtr e, ContextPtr global_context, bool /*created_from_ddl*/){ return create_layout(a, b, c, d, std::move(e), global_context, DictionaryKeyType::Complex, /* sparse = */ false); }, true);
|
||||
factory.registerLayout("complex_key_sparse_hashed",
|
||||
[=](auto && a, auto && b, auto && c, auto && d, DictionarySourcePtr e, ContextPtr /* global_context */, bool /*created_from_ddl*/){ return create_layout(a, b, c, d, std::move(e), DictionaryKeyType::Complex, /* sparse = */ true); }, true);
|
||||
[=](auto && a, auto && b, auto && c, auto && d, DictionarySourcePtr e, ContextPtr global_context, bool /*created_from_ddl*/){ return create_layout(a, b, c, d, std::move(e), global_context, DictionaryKeyType::Complex, /* sparse = */ true); }, true);
|
||||
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ struct HashedDictionaryConfiguration
|
||||
const float max_load_factor;
|
||||
const bool require_nonempty;
|
||||
const DictionaryLifetime lifetime;
|
||||
bool use_async_executor = false;
|
||||
};
|
||||
|
||||
template <DictionaryKeyType dictionary_key_type, bool sparse, bool sharded>
|
||||
|
@ -26,7 +26,12 @@ public:
|
||||
UInt32 end_index;
|
||||
};
|
||||
|
||||
explicit DictionaryHierarchicalParentToChildIndex(const HashMap<UInt64, PaddedPODArray<UInt64>> & parent_to_children_map_)
|
||||
/// By default we use initial_bytes=4096 in PodArray.
|
||||
/// It might lead to really high memory consumption when arrays are almost empty but there are a lot of them.
|
||||
using Array = PODArray<UInt64, 8 * sizeof(UInt64), Allocator<false>, PADDING_FOR_SIMD - 1, PADDING_FOR_SIMD>;
|
||||
using ParentToChildIndex = HashMap<UInt64, Array>;
|
||||
|
||||
explicit DictionaryHierarchicalParentToChildIndex(const ParentToChildIndex & parent_to_children_map_)
|
||||
{
|
||||
size_t parent_to_children_map_size = parent_to_children_map_.size();
|
||||
|
||||
|
@ -16,7 +16,9 @@
|
||||
#include <base/map.h>
|
||||
#include <base/range.h>
|
||||
#include <base/sort.h>
|
||||
#include <Dictionaries/ClickHouseDictionarySource.h>
|
||||
#include <Dictionaries/DictionarySource.h>
|
||||
#include <Dictionaries/DictionarySourceHelpers.h>
|
||||
#include <Dictionaries/DictionaryFactory.h>
|
||||
#include <Functions/FunctionHelpers.h>
|
||||
|
||||
@ -197,13 +199,11 @@ IPAddressDictionary::IPAddressDictionary(
|
||||
const StorageID & dict_id_,
|
||||
const DictionaryStructure & dict_struct_,
|
||||
DictionarySourcePtr source_ptr_,
|
||||
const DictionaryLifetime dict_lifetime_,
|
||||
bool require_nonempty_)
|
||||
IPAddressDictionary::Configuration configuration_)
|
||||
: IDictionary(dict_id_)
|
||||
, dict_struct(dict_struct_)
|
||||
, source_ptr{std::move(source_ptr_)}
|
||||
, dict_lifetime(dict_lifetime_)
|
||||
, require_nonempty(require_nonempty_)
|
||||
, configuration(configuration_)
|
||||
, access_to_key_from_attributes(dict_struct_.access_to_key_from_attributes)
|
||||
, logger(&Poco::Logger::get("IPAddressDictionary"))
|
||||
{
|
||||
@ -369,7 +369,7 @@ void IPAddressDictionary::loadData()
|
||||
|
||||
bool has_ipv6 = false;
|
||||
|
||||
PullingPipelineExecutor executor(pipeline);
|
||||
DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor);
|
||||
Block block;
|
||||
while (executor.pull(block))
|
||||
{
|
||||
@ -525,7 +525,7 @@ void IPAddressDictionary::loadData()
|
||||
|
||||
LOG_TRACE(logger, "{} ip records are read", ip_records.size());
|
||||
|
||||
if (require_nonempty && 0 == element_count)
|
||||
if (configuration.require_nonempty && 0 == element_count)
|
||||
throw Exception(ErrorCodes::DICTIONARY_IS_EMPTY, "{}: dictionary source is empty and 'require_nonempty' property is set.", getFullName());
|
||||
}
|
||||
|
||||
@ -971,7 +971,7 @@ void registerDictionaryTrie(DictionaryFactory & factory)
|
||||
const Poco::Util::AbstractConfiguration & config,
|
||||
const std::string & config_prefix,
|
||||
DictionarySourcePtr source_ptr,
|
||||
ContextPtr /* global_context */,
|
||||
ContextPtr global_context,
|
||||
bool /*created_from_ddl*/) -> DictionaryPtr
|
||||
{
|
||||
if (!dict_struct.key || dict_struct.key->size() != 1)
|
||||
@ -981,8 +981,17 @@ void registerDictionaryTrie(DictionaryFactory & factory)
|
||||
const DictionaryLifetime dict_lifetime{config, config_prefix + ".lifetime"};
|
||||
const bool require_nonempty = config.getBool(config_prefix + ".require_nonempty", false);
|
||||
|
||||
auto context = copyContextAndApplySettingsFromDictionaryConfig(global_context, config, config_prefix);
|
||||
const auto * clickhouse_source = dynamic_cast<const ClickHouseDictionarySource *>(source_ptr.get());
|
||||
bool use_async_executor = clickhouse_source && clickhouse_source->isLocal() && context->getSettingsRef().dictionary_use_async_executor;
|
||||
|
||||
IPAddressDictionary::Configuration configuration{
|
||||
.dict_lifetime = dict_lifetime,
|
||||
.require_nonempty = require_nonempty,
|
||||
.use_async_executor = use_async_executor,
|
||||
};
|
||||
// This is specialised dictionary for storing IPv4 and IPv6 prefixes.
|
||||
return std::make_unique<IPAddressDictionary>(dict_id, dict_struct, std::move(source_ptr), dict_lifetime, require_nonempty);
|
||||
return std::make_unique<IPAddressDictionary>(dict_id, dict_struct, std::move(source_ptr), configuration);
|
||||
};
|
||||
factory.registerLayout("ip_trie", create_layout, true);
|
||||
}
|
||||
|
@ -22,12 +22,18 @@ class Arena;
|
||||
class IPAddressDictionary final : public IDictionary
|
||||
{
|
||||
public:
|
||||
struct Configuration
|
||||
{
|
||||
DictionaryLifetime dict_lifetime;
|
||||
bool require_nonempty;
|
||||
bool use_async_executor = false;
|
||||
};
|
||||
|
||||
IPAddressDictionary(
|
||||
const StorageID & dict_id_,
|
||||
const DictionaryStructure & dict_struct_,
|
||||
DictionarySourcePtr source_ptr_,
|
||||
const DictionaryLifetime dict_lifetime_, /// NOLINT
|
||||
bool require_nonempty_);
|
||||
Configuration configuration_);
|
||||
|
||||
std::string getKeyDescription() const { return key_description; }
|
||||
|
||||
@ -53,12 +59,12 @@ public:
|
||||
|
||||
std::shared_ptr<const IExternalLoadable> clone() const override
|
||||
{
|
||||
return std::make_shared<IPAddressDictionary>(getDictionaryID(), dict_struct, source_ptr->clone(), dict_lifetime, require_nonempty);
|
||||
return std::make_shared<IPAddressDictionary>(getDictionaryID(), dict_struct, source_ptr->clone(), configuration);
|
||||
}
|
||||
|
||||
DictionarySourcePtr getSource() const override { return source_ptr; }
|
||||
|
||||
const DictionaryLifetime & getLifetime() const override { return dict_lifetime; }
|
||||
const DictionaryLifetime & getLifetime() const override { return configuration.dict_lifetime; }
|
||||
|
||||
const DictionaryStructure & getStructure() const override { return dict_struct; }
|
||||
|
||||
@ -199,8 +205,7 @@ private:
|
||||
|
||||
DictionaryStructure dict_struct;
|
||||
const DictionarySourcePtr source_ptr;
|
||||
const DictionaryLifetime dict_lifetime;
|
||||
const bool require_nonempty;
|
||||
const Configuration configuration;
|
||||
const bool access_to_key_from_attributes;
|
||||
const std::string key_description{dict_struct.getKeyDescription()};
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <Processors/Sources/SourceFromSingleChunk.h>
|
||||
#include <Dictionaries/DictionaryFactory.h>
|
||||
#include <Dictionaries/DictionarySource.h>
|
||||
#include <Dictionaries/DictionarySourceHelpers.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
@ -231,7 +232,7 @@ void IPolygonDictionary::loadData()
|
||||
{
|
||||
QueryPipeline pipeline(source_ptr->loadAll());
|
||||
|
||||
PullingPipelineExecutor executor(pipeline);
|
||||
DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor);
|
||||
Block block;
|
||||
while (executor.pull(block))
|
||||
blockToAttributes(block);
|
||||
|
@ -56,6 +56,8 @@ public:
|
||||
|
||||
/// Store polygon key column. That will allow to read columns from polygon dictionary.
|
||||
bool store_polygon_key_column = false;
|
||||
|
||||
bool use_async_executor = false;
|
||||
};
|
||||
|
||||
IPolygonDictionary(
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include <DataTypes/DataTypeArray.h>
|
||||
#include <DataTypes/DataTypeTuple.h>
|
||||
#include <DataTypes/DataTypesNumber.h>
|
||||
#include <Dictionaries/ClickHouseDictionarySource.h>
|
||||
#include <Dictionaries/DictionarySourceHelpers.h>
|
||||
|
||||
#include <Common/logger_useful.h>
|
||||
|
||||
@ -161,7 +163,7 @@ DictionaryPtr createLayout(const std::string & ,
|
||||
const Poco::Util::AbstractConfiguration & config,
|
||||
const std::string & config_prefix,
|
||||
DictionarySourcePtr source_ptr,
|
||||
ContextPtr /* global_context */,
|
||||
ContextPtr global_context,
|
||||
bool /*created_from_ddl*/)
|
||||
{
|
||||
const String database = config.getString(config_prefix + ".database", "");
|
||||
@ -219,11 +221,16 @@ DictionaryPtr createLayout(const std::string & ,
|
||||
config.keys(layout_prefix, keys);
|
||||
const auto & dict_prefix = layout_prefix + "." + keys.front();
|
||||
|
||||
ContextMutablePtr context = copyContextAndApplySettingsFromDictionaryConfig(global_context, config, config_prefix);
|
||||
const auto * clickhouse_source = dynamic_cast<const ClickHouseDictionarySource *>(source_ptr.get());
|
||||
bool use_async_executor = clickhouse_source && clickhouse_source->isLocal() && context->getSettingsRef().dictionary_use_async_executor;
|
||||
|
||||
IPolygonDictionary::Configuration configuration
|
||||
{
|
||||
.input_type = input_type,
|
||||
.point_type = point_type,
|
||||
.store_polygon_key_column = config.getBool(dict_prefix + ".store_polygon_key_column", false)
|
||||
.store_polygon_key_column = config.getBool(dict_prefix + ".store_polygon_key_column", false),
|
||||
.use_async_executor = use_async_executor,
|
||||
};
|
||||
|
||||
if (dict_struct.range_min || dict_struct.range_max)
|
||||
|
@ -29,7 +29,9 @@
|
||||
#include <Functions/FunctionHelpers.h>
|
||||
#include <Interpreters/castColumn.h>
|
||||
|
||||
#include <Dictionaries/ClickHouseDictionarySource.h>
|
||||
#include <Dictionaries/DictionarySource.h>
|
||||
#include <Dictionaries/DictionarySourceHelpers.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
@ -56,6 +58,7 @@ struct RangeHashedDictionaryConfiguration
|
||||
bool convert_null_range_bound_to_open;
|
||||
RangeHashedDictionaryLookupStrategy lookup_strategy;
|
||||
bool require_nonempty;
|
||||
bool use_async_executor = false;
|
||||
};
|
||||
|
||||
template <DictionaryKeyType dictionary_key_type>
|
||||
@ -655,7 +658,7 @@ void RangeHashedDictionary<dictionary_key_type>::loadData()
|
||||
if (!source_ptr->hasUpdateField())
|
||||
{
|
||||
QueryPipeline pipeline(source_ptr->loadAll());
|
||||
PullingPipelineExecutor executor(pipeline);
|
||||
DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor);
|
||||
Block block;
|
||||
|
||||
while (executor.pull(block))
|
||||
@ -919,7 +922,7 @@ void RangeHashedDictionary<dictionary_key_type>::updateData()
|
||||
if (!update_field_loaded_block || update_field_loaded_block->rows() == 0)
|
||||
{
|
||||
QueryPipeline pipeline(source_ptr->loadUpdatedAll());
|
||||
PullingPipelineExecutor executor(pipeline);
|
||||
DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor);
|
||||
update_field_loaded_block.reset();
|
||||
Block block;
|
||||
|
||||
|
@ -310,7 +310,7 @@ void RegExpTreeDictionary::loadData()
|
||||
if (!source_ptr->hasUpdateField())
|
||||
{
|
||||
QueryPipeline pipeline(source_ptr->loadAll());
|
||||
PullingPipelineExecutor executor(pipeline);
|
||||
DictionaryPipelineExecutor executor(pipeline, configuration.use_async_executor);
|
||||
|
||||
Block block;
|
||||
while (executor.pull(block))
|
||||
@ -867,12 +867,17 @@ void registerDictionaryRegExpTree(DictionaryFactory & factory)
|
||||
String dictionary_layout_prefix = config_prefix + ".layout" + ".regexp_tree";
|
||||
const DictionaryLifetime dict_lifetime{config, config_prefix + ".lifetime"};
|
||||
|
||||
RegExpTreeDictionary::Configuration configuration{
|
||||
.require_nonempty = config.getBool(config_prefix + ".require_nonempty", false), .lifetime = dict_lifetime};
|
||||
|
||||
const auto dict_id = StorageID::fromDictionaryConfig(config, config_prefix);
|
||||
|
||||
auto context = copyContextAndApplySettingsFromDictionaryConfig(global_context, config, config_prefix);
|
||||
const auto * clickhouse_source = typeid_cast<const ClickHouseDictionarySource *>(source_ptr.get());
|
||||
bool use_async_executor = clickhouse_source && clickhouse_source->isLocal() && context->getSettingsRef().dictionary_use_async_executor;
|
||||
|
||||
RegExpTreeDictionary::Configuration configuration{
|
||||
.require_nonempty = config.getBool(config_prefix + ".require_nonempty", false),
|
||||
.lifetime = dict_lifetime,
|
||||
.use_async_executor = use_async_executor,
|
||||
};
|
||||
|
||||
return std::make_unique<RegExpTreeDictionary>(
|
||||
dict_id,
|
||||
|
@ -40,6 +40,7 @@ public:
|
||||
{
|
||||
bool require_nonempty;
|
||||
DictionaryLifetime lifetime;
|
||||
bool use_async_executor = false;
|
||||
};
|
||||
|
||||
const std::string name = "RegExpTree";
|
||||
|
@ -2,7 +2,10 @@
|
||||
#include "CacheDictionaryStorage.h"
|
||||
#include "SSDCacheDictionaryStorage.h"
|
||||
#include <Common/filesystemHelpers.h>
|
||||
|
||||
#include <Dictionaries/ClickHouseDictionarySource.h>
|
||||
#include <Dictionaries/DictionaryFactory.h>
|
||||
#include <Dictionaries/DictionarySourceHelpers.h>
|
||||
#include <Interpreters/Context.h>
|
||||
|
||||
namespace DB
|
||||
@ -222,6 +225,16 @@ DictionaryPtr createCacheDictionaryLayout(
|
||||
storage = std::make_shared<SSDCacheDictionaryStorage<dictionary_key_type>>(storage_configuration);
|
||||
}
|
||||
#endif
|
||||
ContextMutablePtr context = copyContextAndApplySettingsFromDictionaryConfig(global_context, config, config_prefix);
|
||||
const auto & settings = context->getSettingsRef();
|
||||
|
||||
const auto * clickhouse_source = dynamic_cast<const ClickHouseDictionarySource *>(source_ptr.get());
|
||||
bool use_async_executor = clickhouse_source && clickhouse_source->isLocal() && settings.dictionary_use_async_executor;
|
||||
CacheDictionaryConfiguration configuration{
|
||||
allow_read_expired_keys,
|
||||
dict_lifetime,
|
||||
use_async_executor,
|
||||
};
|
||||
|
||||
auto dictionary = std::make_unique<CacheDictionary<dictionary_key_type>>(
|
||||
dictionary_identifier,
|
||||
@ -229,8 +242,7 @@ DictionaryPtr createCacheDictionaryLayout(
|
||||
std::move(source_ptr),
|
||||
std::move(storage),
|
||||
update_queue_configuration,
|
||||
dict_lifetime,
|
||||
allow_read_expired_keys);
|
||||
configuration);
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ static DictionaryPtr createRangeHashedDictionary(const std::string & full_name,
|
||||
const DictionaryStructure & dict_struct,
|
||||
const Poco::Util::AbstractConfiguration & config,
|
||||
const std::string & config_prefix,
|
||||
ContextPtr global_context,
|
||||
DictionarySourcePtr source_ptr)
|
||||
{
|
||||
static constexpr auto layout_name = dictionary_key_type == DictionaryKeyType::Simple ? "range_hashed" : "complex_key_range_hashed";
|
||||
@ -52,11 +53,16 @@ static DictionaryPtr createRangeHashedDictionary(const std::string & full_name,
|
||||
else if (range_lookup_strategy == "max")
|
||||
lookup_strategy = RangeHashedDictionaryLookupStrategy::max;
|
||||
|
||||
auto context = copyContextAndApplySettingsFromDictionaryConfig(global_context, config, config_prefix);
|
||||
const auto * clickhouse_source = dynamic_cast<const ClickHouseDictionarySource *>(source_ptr.get());
|
||||
bool use_async_executor = clickhouse_source && clickhouse_source->isLocal() && context->getSettingsRef().dictionary_use_async_executor;
|
||||
|
||||
RangeHashedDictionaryConfiguration configuration
|
||||
{
|
||||
.convert_null_range_bound_to_open = convert_null_range_bound_to_open,
|
||||
.lookup_strategy = lookup_strategy,
|
||||
.require_nonempty = require_nonempty
|
||||
.require_nonempty = require_nonempty,
|
||||
.use_async_executor = use_async_executor,
|
||||
};
|
||||
|
||||
DictionaryPtr result = std::make_unique<RangeHashedDictionary<dictionary_key_type>>(
|
||||
@ -76,10 +82,10 @@ void registerDictionaryRangeHashed(DictionaryFactory & factory)
|
||||
const Poco::Util::AbstractConfiguration & config,
|
||||
const std::string & config_prefix,
|
||||
DictionarySourcePtr source_ptr,
|
||||
ContextPtr /* global_context */,
|
||||
ContextPtr global_context,
|
||||
bool /*created_from_ddl*/) -> DictionaryPtr
|
||||
{
|
||||
return createRangeHashedDictionary<DictionaryKeyType::Simple>(full_name, dict_struct, config, config_prefix, std::move(source_ptr));
|
||||
return createRangeHashedDictionary<DictionaryKeyType::Simple>(full_name, dict_struct, config, config_prefix, global_context, std::move(source_ptr));
|
||||
};
|
||||
|
||||
factory.registerLayout("range_hashed", create_layout_simple, false);
|
||||
@ -89,10 +95,10 @@ void registerDictionaryRangeHashed(DictionaryFactory & factory)
|
||||
const Poco::Util::AbstractConfiguration & config,
|
||||
const std::string & config_prefix,
|
||||
DictionarySourcePtr source_ptr,
|
||||
ContextPtr /* context */,
|
||||
ContextPtr global_context,
|
||||
bool /*created_from_ddl*/) -> DictionaryPtr
|
||||
{
|
||||
return createRangeHashedDictionary<DictionaryKeyType::Complex>(full_name, dict_struct, config, config_prefix, std::move(source_ptr));
|
||||
return createRangeHashedDictionary<DictionaryKeyType::Complex>(full_name, dict_struct, config, config_prefix, global_context, std::move(source_ptr));
|
||||
};
|
||||
|
||||
factory.registerLayout("complex_key_range_hashed", create_layout_complex, true);
|
||||
|
@ -173,7 +173,7 @@ TEST(HierarchyDictionariesUtils, getIsInHierarchy)
|
||||
TEST(HierarchyDictionariesUtils, getDescendants)
|
||||
{
|
||||
{
|
||||
HashMap<UInt64, PaddedPODArray<UInt64>> parent_to_child;
|
||||
DictionaryHierarchicalParentToChildIndex::ParentToChildIndex parent_to_child;
|
||||
parent_to_child[0].emplace_back(1);
|
||||
parent_to_child[1].emplace_back(2);
|
||||
parent_to_child[1].emplace_back(3);
|
||||
@ -221,7 +221,7 @@ TEST(HierarchyDictionariesUtils, getDescendants)
|
||||
}
|
||||
}
|
||||
{
|
||||
HashMap<UInt64, PaddedPODArray<UInt64>> parent_to_child;
|
||||
DictionaryHierarchicalParentToChildIndex::ParentToChildIndex parent_to_child;
|
||||
parent_to_child[1].emplace_back(2);
|
||||
parent_to_child[2].emplace_back(1);
|
||||
|
||||
|
@ -100,6 +100,7 @@ FormatSettings getFormatSettings(ContextPtr context, const Settings & settings)
|
||||
format_settings.json.array_of_rows = settings.output_format_json_array_of_rows;
|
||||
format_settings.json.escape_forward_slashes = settings.output_format_json_escape_forward_slashes;
|
||||
format_settings.json.write_named_tuples_as_objects = settings.output_format_json_named_tuples_as_objects;
|
||||
format_settings.json.skip_null_value_in_named_tuples = settings.output_format_json_skip_null_value_in_named_tuples;
|
||||
format_settings.json.read_named_tuples_as_objects = settings.input_format_json_named_tuples_as_objects;
|
||||
format_settings.json.defaults_for_missing_elements_in_named_tuple = settings.input_format_json_defaults_for_missing_elements_in_named_tuple;
|
||||
format_settings.json.ignore_unknown_keys_in_named_tuple = settings.input_format_json_ignore_unknown_keys_in_named_tuple;
|
||||
@ -193,7 +194,9 @@ FormatSettings getFormatSettings(ContextPtr context, const Settings & settings)
|
||||
format_settings.orc.case_insensitive_column_matching = settings.input_format_orc_case_insensitive_column_matching;
|
||||
format_settings.orc.output_string_as_string = settings.output_format_orc_string_as_string;
|
||||
format_settings.orc.output_compression_method = settings.output_format_orc_compression_method;
|
||||
format_settings.orc.output_row_index_stride = settings.output_format_orc_row_index_stride;
|
||||
format_settings.orc.use_fast_decoder = settings.input_format_orc_use_fast_decoder;
|
||||
format_settings.orc.filter_push_down = settings.input_format_orc_filter_push_down;
|
||||
format_settings.defaults_for_omitted_fields = settings.input_format_defaults_for_omitted_fields;
|
||||
format_settings.capn_proto.enum_comparing_mode = settings.format_capn_proto_enum_comparising_mode;
|
||||
format_settings.capn_proto.skip_fields_with_unsupported_types_in_schema_inference = settings.input_format_capn_proto_skip_fields_with_unsupported_types_in_schema_inference;
|
||||
@ -224,6 +227,7 @@ FormatSettings getFormatSettings(ContextPtr context, const Settings & settings)
|
||||
format_settings.native.allow_types_conversion = settings.input_format_native_allow_types_conversion;
|
||||
format_settings.max_parser_depth = context->getSettingsRef().max_parser_depth;
|
||||
format_settings.client_protocol_version = context->getClientProtocolVersion();
|
||||
format_settings.date_time_overflow_behavior = settings.date_time_overflow_behavior;
|
||||
|
||||
/// Validate avro_schema_registry_url with RemoteHostFilter when non-empty and in Server context
|
||||
if (format_settings.schema.is_server)
|
||||
|
@ -88,6 +88,15 @@ struct FormatSettings
|
||||
IntervalOutputFormat output_format = IntervalOutputFormat::Numeric;
|
||||
} interval;
|
||||
|
||||
enum class DateTimeOverflowBehavior
|
||||
{
|
||||
Ignore,
|
||||
Throw,
|
||||
Saturate
|
||||
};
|
||||
|
||||
DateTimeOverflowBehavior date_time_overflow_behavior = DateTimeOverflowBehavior::Ignore;
|
||||
|
||||
bool input_format_ipv4_default_on_conversion_error = false;
|
||||
bool input_format_ipv6_default_on_conversion_error = false;
|
||||
|
||||
@ -188,6 +197,7 @@ struct FormatSettings
|
||||
bool escape_forward_slashes = true;
|
||||
bool read_named_tuples_as_objects = false;
|
||||
bool write_named_tuples_as_objects = false;
|
||||
bool skip_null_value_in_named_tuples = false;
|
||||
bool defaults_for_missing_elements_in_named_tuple = false;
|
||||
bool ignore_unknown_keys_in_named_tuple = false;
|
||||
bool serialize_as_strings = false;
|
||||
@ -363,6 +373,8 @@ struct FormatSettings
|
||||
bool output_string_as_string = false;
|
||||
ORCCompression output_compression_method = ORCCompression::NONE;
|
||||
bool use_fast_decoder = true;
|
||||
bool filter_push_down = true;
|
||||
UInt64 output_row_index_stride = 10'000;
|
||||
} orc;
|
||||
|
||||
/// For capnProto format we should determine how to
|
||||
|
@ -22,5 +22,4 @@ void throwDate32IsNotSupported(const char * name)
|
||||
{
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type Date32 of argument for function {}", name);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,12 +23,15 @@ namespace DB
|
||||
static constexpr auto microsecond_multiplier = 1000000;
|
||||
static constexpr auto millisecond_multiplier = 1000;
|
||||
|
||||
static constexpr FormatSettings::DateTimeOverflowBehavior default_date_time_overflow_behavior = FormatSettings::DateTimeOverflowBehavior::Ignore;
|
||||
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int CANNOT_CONVERT_TYPE;
|
||||
extern const int DECIMAL_OVERFLOW;
|
||||
extern const int ILLEGAL_COLUMN;
|
||||
extern const int ILLEGAL_TYPE_OF_ARGUMENT;
|
||||
extern const int VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE;
|
||||
}
|
||||
|
||||
/** Transformations.
|
||||
@ -44,6 +47,12 @@ namespace ErrorCodes
|
||||
* factor-transformation F is "round to the nearest month" (2015-02-03 -> 2015-02-01).
|
||||
*/
|
||||
|
||||
constexpr time_t MAX_DATETIME64_TIMESTAMP = 10413791999LL; // 1900-01-01 00:00:00 UTC
|
||||
constexpr time_t MIN_DATETIME64_TIMESTAMP = -2208988800LL; // 2299-12-31 23:59:59 UTC
|
||||
constexpr time_t MAX_DATETIME_TIMESTAMP = 0xFFFFFFFF;
|
||||
constexpr time_t MAX_DATE_TIMESTAMP = 5662310399; // 2149-06-06 23:59:59 UTC
|
||||
constexpr time_t MAX_DATETIME_DAY_NUM = 49710; // 2106-02-07
|
||||
|
||||
[[noreturn]] void throwDateIsNotSupported(const char * name);
|
||||
[[noreturn]] void throwDateTimeIsNotSupported(const char * name);
|
||||
[[noreturn]] void throwDate32IsNotSupported(const char * name);
|
||||
@ -57,25 +66,51 @@ struct ZeroTransform
|
||||
static UInt16 execute(UInt16, const DateLUTImpl &) { return 0; }
|
||||
};
|
||||
|
||||
template <FormatSettings::DateTimeOverflowBehavior date_time_overflow_behavior = default_date_time_overflow_behavior>
|
||||
struct ToDateImpl
|
||||
{
|
||||
static constexpr auto name = "toDate";
|
||||
|
||||
static UInt16 execute(const DecimalUtils::DecimalComponents<DateTime64> & t, const DateLUTImpl & time_zone)
|
||||
{
|
||||
return static_cast<UInt16>(time_zone.toDayNum(t.whole));
|
||||
return execute(t.whole, time_zone);
|
||||
}
|
||||
|
||||
static UInt16 execute(Int64 t, const DateLUTImpl & time_zone)
|
||||
{
|
||||
return UInt16(time_zone.toDayNum(t));
|
||||
if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Saturate)
|
||||
{
|
||||
if (t < 0)
|
||||
t = 0;
|
||||
else if (t > MAX_DATE_TIMESTAMP)
|
||||
t = MAX_DATE_TIMESTAMP;
|
||||
}
|
||||
else if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
|
||||
{
|
||||
if (t < 0 || t > MAX_DATE_TIMESTAMP) [[unlikely]]
|
||||
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Value {} is out of bounds of type Date", t);
|
||||
}
|
||||
return static_cast<UInt16>(time_zone.toDayNum(t));
|
||||
}
|
||||
static UInt16 execute(UInt32 t, const DateLUTImpl & time_zone)
|
||||
{
|
||||
return UInt16(time_zone.toDayNum(t));
|
||||
return UInt16(time_zone.toDayNum(t)); /// never causes overflow by design
|
||||
}
|
||||
static UInt16 execute(Int32, const DateLUTImpl &)
|
||||
static UInt16 execute(Int32 t, const DateLUTImpl &)
|
||||
{
|
||||
throwDateIsNotSupported(name);
|
||||
if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Saturate)
|
||||
{
|
||||
if (t < 0)
|
||||
return UInt16(0);
|
||||
else if (t > DATE_LUT_MAX_DAY_NUM)
|
||||
return UInt16(DATE_LUT_MAX_DAY_NUM);
|
||||
}
|
||||
else if constexpr (date_time_overflow_behavior == FormatSettings::DateTimeOverflowBehavior::Throw)
|
||||
{
|
||||
if (t < 0 || t > DATE_LUT_MAX_DAY_NUM) [[unlikely]]
|
||||
throw Exception(ErrorCodes::VALUE_IS_OUT_OF_RANGE_OF_DATA_TYPE, "Value {} is out of bounds of type Date", t);
|
||||
}
|
||||
return static_cast<UInt16>(t);
|
||||
}
|
||||
static UInt16 execute(UInt16 d, const DateLUTImpl &)
|
||||
{
|
||||
@ -750,7 +785,7 @@ struct ToTimeImpl
|
||||
}
|
||||
static constexpr bool hasPreimage() { return false; }
|
||||
|
||||
using FactorTransform = ToDateImpl;
|
||||
using FactorTransform = ToDateImpl<>;
|
||||
};
|
||||
|
||||
struct ToStartOfMinuteImpl
|
||||
@ -1401,7 +1436,7 @@ struct ToHourImpl
|
||||
}
|
||||
static constexpr bool hasPreimage() { return false; }
|
||||
|
||||
using FactorTransform = ToDateImpl;
|
||||
using FactorTransform = ToDateImpl<>;
|
||||
};
|
||||
|
||||
struct TimezoneOffsetImpl
|
||||
|
@ -8,7 +8,9 @@
|
||||
#include <DataTypes/DataTypeDate32.h>
|
||||
#include <DataTypes/DataTypeDateTime.h>
|
||||
#include <DataTypes/DataTypeDateTime64.h>
|
||||
#include <DataTypes/DataTypeString.h>
|
||||
|
||||
#include <Columns/ColumnString.h>
|
||||
#include <Columns/ColumnsNumber.h>
|
||||
|
||||
#include <Functions/IFunction.h>
|
||||
@ -16,7 +18,9 @@
|
||||
#include <Functions/castTypeToEither.h>
|
||||
#include <Functions/extractTimeZoneFromFunctionArguments.h>
|
||||
|
||||
#include <IO/ReadBufferFromString.h>
|
||||
#include <IO/WriteHelpers.h>
|
||||
#include <IO/parseDateTimeBestEffort.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
@ -47,312 +51,345 @@ struct AddNanosecondsImpl
|
||||
{
|
||||
static constexpr auto name = "addNanoseconds";
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64
|
||||
execute(DateTime64 t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(DateTime64 t, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16 scale)
|
||||
{
|
||||
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(9 - scale);
|
||||
return DateTime64(DecimalUtils::multiplyAdd(t.value, multiplier, delta));
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(UInt32 t, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(9);
|
||||
return DateTime64(DecimalUtils::multiplyAdd(static_cast<Int64>(t), multiplier, delta));
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int8 execute(UInt16, Int64, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int8 execute(UInt16, Int64, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "addNanoseconds() cannot be used with Date");
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int8 execute(Int32, Int64, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int8 execute(Int32, Int64, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "addNanoseconds() cannot be used with Date32");
|
||||
}
|
||||
static DateTime64 execute(std::string_view s, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl & utc_time_zone, UInt16 scale)
|
||||
{
|
||||
ReadBufferFromString buf(s);
|
||||
DateTime64 t;
|
||||
parseDateTime64BestEffort(t, scale, buf, time_zone, utc_time_zone);
|
||||
return execute(t, delta, time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
struct AddMicrosecondsImpl
|
||||
{
|
||||
static constexpr auto name = "addMicroseconds";
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64
|
||||
execute(DateTime64 t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(DateTime64 t, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16 scale)
|
||||
{
|
||||
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(std::abs(6 - scale));
|
||||
return DateTime64(scale <= 6
|
||||
? DecimalUtils::multiplyAdd(t.value, multiplier, delta)
|
||||
: DecimalUtils::multiplyAdd(delta, multiplier, t.value));
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(UInt32 t, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(6);
|
||||
return DateTime64(DecimalUtils::multiplyAdd(static_cast<Int64>(t), multiplier, delta));
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int8 execute(UInt16, Int64, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int8 execute(UInt16, Int64, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "addMicroseconds() cannot be used with Date");
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int8 execute(Int32, Int64, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int8 execute(Int32, Int64, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "addMicroseconds() cannot be used with Date32");
|
||||
}
|
||||
static DateTime64 execute(std::string_view s, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl & utc_time_zone, UInt16 scale)
|
||||
{
|
||||
ReadBufferFromString buf(s);
|
||||
DateTime64 t;
|
||||
parseDateTime64BestEffort(t, scale, buf, time_zone, utc_time_zone);
|
||||
return execute(t, delta, time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
struct AddMillisecondsImpl
|
||||
{
|
||||
static constexpr auto name = "addMilliseconds";
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64
|
||||
execute(DateTime64 t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(DateTime64 t, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16 scale)
|
||||
{
|
||||
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(std::abs(3 - scale));
|
||||
return DateTime64(scale <= 3
|
||||
? DecimalUtils::multiplyAdd(t.value, multiplier, delta)
|
||||
: DecimalUtils::multiplyAdd(delta, multiplier, t.value));
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(UInt32 t, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
Int64 multiplier = DecimalUtils::scaleMultiplier<DateTime64>(3);
|
||||
return DateTime64(DecimalUtils::multiplyAdd(static_cast<Int64>(t), multiplier, delta));
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int8 execute(UInt16, Int64, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int8 execute(UInt16, Int64, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "addMilliseconds() cannot be used with Date");
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int8 execute(Int32, Int64, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int8 execute(Int32, Int64, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "addMilliseconds() cannot be used with Date32");
|
||||
}
|
||||
static DateTime64 execute(std::string_view s, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl & utc_time_zone, UInt16 scale)
|
||||
{
|
||||
ReadBufferFromString buf(s);
|
||||
DateTime64 t;
|
||||
parseDateTime64BestEffort(t, scale, buf, time_zone, utc_time_zone);
|
||||
return execute(t, delta, time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
struct AddSecondsImpl
|
||||
{
|
||||
static constexpr auto name = "addSeconds";
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64
|
||||
execute(DateTime64 t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(DateTime64 t, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16 scale)
|
||||
{
|
||||
return DateTime64(DecimalUtils::multiplyAdd(delta, DecimalUtils::scaleMultiplier<DateTime64>(scale), t.value));
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<UInt32>(t + delta);
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
// use default datetime64 scale
|
||||
static_assert(DataTypeDateTime64::default_scale == 3, "");
|
||||
static_assert(DataTypeDateTime64::default_scale == 3);
|
||||
return (time_zone.fromDayNum(ExtendedDayNum(d)) + delta) * 1000;
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<UInt32>(time_zone.fromDayNum(DayNum(d)) + delta);
|
||||
}
|
||||
static DateTime64 execute(std::string_view s, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl & utc_time_zone, UInt16 scale)
|
||||
{
|
||||
ReadBufferFromString buf(s);
|
||||
DateTime64 t;
|
||||
parseDateTime64BestEffort(t, scale, buf, time_zone, utc_time_zone);
|
||||
return execute(t, delta, time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
struct AddMinutesImpl
|
||||
{
|
||||
static constexpr auto name = "addMinutes";
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64
|
||||
execute(DateTime64 t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(DateTime64 t, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16 scale)
|
||||
{
|
||||
return t + 60 * delta * DecimalUtils::scaleMultiplier<DateTime64>(scale);
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<UInt32>(t + delta * 60);
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
// use default datetime64 scale
|
||||
static_assert(DataTypeDateTime64::default_scale == 3, "");
|
||||
static_assert(DataTypeDateTime64::default_scale == 3);
|
||||
return (time_zone.fromDayNum(ExtendedDayNum(d)) + delta * 60) * 1000;
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<UInt32>(time_zone.fromDayNum(DayNum(d)) + delta * 60);
|
||||
}
|
||||
static DateTime64 execute(std::string_view s, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl & utc_time_zone, UInt16 scale)
|
||||
{
|
||||
ReadBufferFromString buf(s);
|
||||
DateTime64 t;
|
||||
parseDateTime64BestEffort(t, scale, buf, time_zone, utc_time_zone);
|
||||
return execute(t, delta, time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
struct AddHoursImpl
|
||||
{
|
||||
static constexpr auto name = "addHours";
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64
|
||||
execute(DateTime64 t, Int64 delta, const DateLUTImpl &, UInt16 scale = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(DateTime64 t, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16 scale)
|
||||
{
|
||||
return t + 3600 * delta * DecimalUtils::scaleMultiplier<DateTime64>(scale);
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<UInt32>(t + delta * 3600);
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int64 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
// use default datetime64 scale
|
||||
static_assert(DataTypeDateTime64::default_scale == 3, "");
|
||||
static_assert(DataTypeDateTime64::default_scale == 3);
|
||||
return (time_zone.fromDayNum(ExtendedDayNum(d)) + delta * 3600) * 1000;
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt32 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<UInt32>(time_zone.fromDayNum(DayNum(d)) + delta * 3600);
|
||||
}
|
||||
static DateTime64 execute(std::string_view s, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl & utc_time_zone, UInt16 scale)
|
||||
{
|
||||
ReadBufferFromString buf(s);
|
||||
DateTime64 t;
|
||||
parseDateTime64BestEffort(t, scale, buf, time_zone, utc_time_zone);
|
||||
return execute(t, delta, time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
struct AddDaysImpl
|
||||
{
|
||||
static constexpr auto name = "addDays";
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64
|
||||
execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16 scale)
|
||||
{
|
||||
auto multiplier = DecimalUtils::scaleMultiplier<DateTime64>(scale);
|
||||
auto d = std::div(t, multiplier);
|
||||
return time_zone.addDays(d.quot, delta) * multiplier + d.rem;
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<UInt32>(time_zone.addDays(t, delta));
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return d + delta;
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int64 delta, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<Int32>(d + delta);
|
||||
}
|
||||
static DateTime64 execute(std::string_view s, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl & utc_time_zone, UInt16 scale)
|
||||
{
|
||||
ReadBufferFromString buf(s);
|
||||
DateTime64 t;
|
||||
parseDateTime64BestEffort(t, scale, buf, time_zone, utc_time_zone);
|
||||
return execute(t, delta, time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
struct AddWeeksImpl
|
||||
{
|
||||
static constexpr auto name = "addWeeks";
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64
|
||||
execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16 scale)
|
||||
{
|
||||
auto multiplier = DecimalUtils::scaleMultiplier<DateTime64>(scale);
|
||||
auto d = std::div(t, multiplier);
|
||||
return time_zone.addDays(d.quot, delta * 7) * multiplier + d.rem;
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<UInt32>(time_zone.addWeeks(t, delta));
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<UInt16>(d + delta * 7);
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int64 delta, const DateLUTImpl &, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int64 delta, const DateLUTImpl &, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<Int32>(d + delta * 7);
|
||||
}
|
||||
static DateTime64 execute(std::string_view s, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl & utc_time_zone, UInt16 scale)
|
||||
{
|
||||
ReadBufferFromString buf(s);
|
||||
DateTime64 t;
|
||||
parseDateTime64BestEffort(t, scale, buf, time_zone, utc_time_zone);
|
||||
return execute(t, delta, time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
struct AddMonthsImpl
|
||||
{
|
||||
static constexpr auto name = "addMonths";
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64
|
||||
execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16 scale)
|
||||
{
|
||||
auto multiplier = DecimalUtils::scaleMultiplier<DateTime64>(scale);
|
||||
auto d = std::div(t, multiplier);
|
||||
return time_zone.addMonths(d.quot, delta) * multiplier + d.rem;
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<UInt32>(time_zone.addMonths(t, delta));
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return time_zone.addMonths(DayNum(d), delta);
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return time_zone.addMonths(ExtendedDayNum(d), delta);
|
||||
}
|
||||
static DateTime64 execute(std::string_view s, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl & utc_time_zone, UInt16 scale)
|
||||
{
|
||||
ReadBufferFromString buf(s);
|
||||
DateTime64 t;
|
||||
parseDateTime64BestEffort(t, scale, buf, time_zone, utc_time_zone);
|
||||
return execute(t, delta, time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
struct AddQuartersImpl
|
||||
{
|
||||
static constexpr auto name = "addQuarters";
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64
|
||||
execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16 scale)
|
||||
{
|
||||
auto multiplier = DecimalUtils::scaleMultiplier<DateTime64>(scale);
|
||||
auto d = std::div(t, multiplier);
|
||||
return time_zone.addQuarters(d.quot, delta) * multiplier + d.rem;
|
||||
}
|
||||
|
||||
static inline UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<UInt32>(time_zone.addQuarters(t, delta));
|
||||
}
|
||||
|
||||
static inline UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return time_zone.addQuarters(DayNum(d), delta);
|
||||
}
|
||||
|
||||
static inline Int32 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static Int32 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return time_zone.addQuarters(ExtendedDayNum(d), delta);
|
||||
}
|
||||
static DateTime64 execute(std::string_view s, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl & utc_time_zone, UInt16 scale)
|
||||
{
|
||||
ReadBufferFromString buf(s);
|
||||
DateTime64 t;
|
||||
parseDateTime64BestEffort(t, scale, buf, time_zone, utc_time_zone);
|
||||
return execute(t, delta, time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
struct AddYearsImpl
|
||||
{
|
||||
static constexpr auto name = "addYears";
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED DateTime64
|
||||
execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale = 0)
|
||||
static NO_SANITIZE_UNDEFINED DateTime64 execute(DateTime64 t, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16 scale)
|
||||
{
|
||||
auto multiplier = DecimalUtils::scaleMultiplier<DateTime64>(scale);
|
||||
auto d = std::div(t, multiplier);
|
||||
return time_zone.addYears(d.quot, delta) * multiplier + d.rem;
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt32 execute(UInt32 t, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return static_cast<UInt32>(time_zone.addYears(t, delta));
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED UInt16 execute(UInt16 d, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return time_zone.addYears(DayNum(d), delta);
|
||||
}
|
||||
|
||||
static inline NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, UInt16 = 0)
|
||||
static NO_SANITIZE_UNDEFINED Int32 execute(Int32 d, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl &, UInt16)
|
||||
{
|
||||
return time_zone.addYears(ExtendedDayNum(d), delta);
|
||||
}
|
||||
static DateTime64 execute(std::string_view s, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl & utc_time_zone, UInt16 scale)
|
||||
{
|
||||
ReadBufferFromString buf(s);
|
||||
DateTime64 t;
|
||||
parseDateTime64BestEffort(t, scale, buf, time_zone, utc_time_zone);
|
||||
return execute(t, delta, time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Transform>
|
||||
@ -361,10 +398,10 @@ struct SubtractIntervalImpl : public Transform
|
||||
using Transform::Transform;
|
||||
|
||||
template <typename T>
|
||||
inline NO_SANITIZE_UNDEFINED auto execute(T t, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale) const
|
||||
NO_SANITIZE_UNDEFINED auto execute(T t, Int64 delta, const DateLUTImpl & time_zone, const DateLUTImpl & utc_time_zone, UInt16 scale) const
|
||||
{
|
||||
/// Signed integer overflow is Ok.
|
||||
return Transform::execute(t, -delta, time_zone, scale);
|
||||
return Transform::execute(t, -delta, time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
@ -382,52 +419,67 @@ struct SubtractYearsImpl : SubtractIntervalImpl<AddYearsImpl> { static constexpr
|
||||
|
||||
|
||||
template <typename Transform>
|
||||
struct Adder
|
||||
struct Processor
|
||||
{
|
||||
const Transform transform;
|
||||
|
||||
explicit Adder(Transform transform_)
|
||||
explicit Processor(Transform transform_)
|
||||
: transform(std::move(transform_))
|
||||
{}
|
||||
|
||||
template <typename FromVectorType, typename ToVectorType>
|
||||
void NO_INLINE vectorConstant(const FromVectorType & vec_from, ToVectorType & vec_to, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale) const
|
||||
template <typename FromColumnType, typename ToColumnType>
|
||||
void NO_INLINE vectorConstant(const FromColumnType & col_from, ToColumnType & col_to, Int64 delta, const DateLUTImpl & time_zone, UInt16 scale) const
|
||||
{
|
||||
size_t size = vec_from.size();
|
||||
vec_to.resize(size);
|
||||
static const DateLUTImpl & utc_time_zone = DateLUT::instance("UTC");
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
vec_to[i] = transform.execute(vec_from[i], checkOverflow(delta), time_zone, scale);
|
||||
if constexpr (std::is_same_v<FromColumnType, ColumnString>)
|
||||
{
|
||||
const auto & offsets_from = col_from.getOffsets();
|
||||
auto & vec_to = col_to.getData();
|
||||
|
||||
size_t size = offsets_from.size();
|
||||
vec_to.resize(size);
|
||||
|
||||
for (size_t i = 0 ; i < size; ++i)
|
||||
{
|
||||
std::string_view from = col_from.getDataAt(i).toView();
|
||||
vec_to[i] = transform.execute(from, checkOverflow(delta), time_zone, utc_time_zone, scale);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto & vec_from = col_from.getData();
|
||||
auto & vec_to = col_to.getData();
|
||||
|
||||
size_t size = vec_from.size();
|
||||
vec_to.resize(size);
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
vec_to[i] = transform.execute(vec_from[i], checkOverflow(delta), time_zone, utc_time_zone, scale);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FromVectorType, typename ToVectorType>
|
||||
void vectorVector(const FromVectorType & vec_from, ToVectorType & vec_to, const IColumn & delta, const DateLUTImpl & time_zone, UInt16 scale) const
|
||||
template <typename FromColumnType, typename ToColumnType>
|
||||
void vectorVector(const FromColumnType & col_from, ToColumnType & col_to, const IColumn & delta, const DateLUTImpl & time_zone, UInt16 scale) const
|
||||
{
|
||||
size_t size = vec_from.size();
|
||||
vec_to.resize(size);
|
||||
|
||||
castTypeToEither<
|
||||
ColumnUInt8, ColumnUInt16, ColumnUInt32, ColumnUInt64,
|
||||
ColumnInt8, ColumnInt16, ColumnInt32, ColumnInt64,
|
||||
ColumnFloat32, ColumnFloat64>(
|
||||
&delta, [&](const auto & column){ vectorVector(vec_from, vec_to, column, time_zone, scale, size); return true; });
|
||||
&delta, [&](const auto & column){ vectorVector(col_from, col_to, column, time_zone, scale); return true; });
|
||||
}
|
||||
|
||||
template <typename FromType, typename ToVectorType>
|
||||
void constantVector(const FromType & from, ToVectorType & vec_to, const IColumn & delta, const DateLUTImpl & time_zone, UInt16 scale) const
|
||||
template <typename FromType, typename ToColumnType>
|
||||
void constantVector(const FromType & from, ToColumnType & col_to, const IColumn & delta, const DateLUTImpl & time_zone, UInt16 scale) const
|
||||
{
|
||||
size_t size = delta.size();
|
||||
vec_to.resize(size);
|
||||
|
||||
castTypeToEither<
|
||||
ColumnUInt8, ColumnUInt16, ColumnUInt32, ColumnUInt64,
|
||||
ColumnInt8, ColumnInt16, ColumnInt32, ColumnInt64,
|
||||
ColumnFloat32, ColumnFloat64>(
|
||||
&delta, [&](const auto & column){ constantVector(from, vec_to, column, time_zone, scale, size); return true; });
|
||||
&delta, [&](const auto & column){ constantVector(from, col_to, column, time_zone, scale); return true; });
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template <typename Value>
|
||||
static Int64 checkOverflow(Value val)
|
||||
{
|
||||
@ -437,20 +489,52 @@ private:
|
||||
throw DB::Exception(ErrorCodes::DECIMAL_OVERFLOW, "Numeric overflow");
|
||||
}
|
||||
|
||||
template <typename FromVectorType, typename ToVectorType, typename DeltaColumnType>
|
||||
template <typename FromColumnType, typename ToColumnType, typename DeltaColumnType>
|
||||
NO_INLINE NO_SANITIZE_UNDEFINED void vectorVector(
|
||||
const FromVectorType & vec_from, ToVectorType & vec_to, const DeltaColumnType & delta, const DateLUTImpl & time_zone, UInt16 scale, size_t size) const
|
||||
const FromColumnType & col_from, ToColumnType & col_to, const DeltaColumnType & delta, const DateLUTImpl & time_zone, UInt16 scale) const
|
||||
{
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
vec_to[i] = transform.execute(vec_from[i], checkOverflow(delta.getData()[i]), time_zone, scale);
|
||||
static const DateLUTImpl & utc_time_zone = DateLUT::instance("UTC");
|
||||
|
||||
if constexpr (std::is_same_v<FromColumnType, ColumnString>)
|
||||
{
|
||||
const auto & offsets_from = col_from.getOffsets();
|
||||
auto & vec_to = col_to.getData();
|
||||
|
||||
size_t size = offsets_from.size();
|
||||
vec_to.resize(size);
|
||||
|
||||
for (size_t i = 0 ; i < size; ++i)
|
||||
{
|
||||
std::string_view from = col_from.getDataAt(i).toView();
|
||||
vec_to[i] = transform.execute(from, checkOverflow(delta.getData()[i]), time_zone, utc_time_zone, scale);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto & vec_from = col_from.getData();
|
||||
auto & vec_to = col_to.getData();
|
||||
|
||||
size_t size = vec_from.size();
|
||||
vec_to.resize(size);
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
vec_to[i] = transform.execute(vec_from[i], checkOverflow(delta.getData()[i]), time_zone, utc_time_zone, scale);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FromType, typename ToVectorType, typename DeltaColumnType>
|
||||
template <typename FromType, typename ToColumnType, typename DeltaColumnType>
|
||||
NO_INLINE NO_SANITIZE_UNDEFINED void constantVector(
|
||||
const FromType & from, ToVectorType & vec_to, const DeltaColumnType & delta, const DateLUTImpl & time_zone, UInt16 scale, size_t size) const
|
||||
const FromType & from, ToColumnType & col_to, const DeltaColumnType & delta, const DateLUTImpl & time_zone, UInt16 scale) const
|
||||
{
|
||||
static const DateLUTImpl & utc_time_zone = DateLUT::instance("UTC");
|
||||
|
||||
auto & vec_to = col_to.getData();
|
||||
|
||||
size_t size = delta.size();
|
||||
vec_to.resize(size);
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
vec_to[i] = transform.execute(from, checkOverflow(delta.getData()[i]), time_zone, scale);
|
||||
vec_to[i] = transform.execute(from, checkOverflow(delta.getData()[i]), time_zone, utc_time_zone, scale);
|
||||
}
|
||||
};
|
||||
|
||||
@ -458,34 +542,34 @@ private:
|
||||
template <typename FromDataType, typename ToDataType, typename Transform>
|
||||
struct DateTimeAddIntervalImpl
|
||||
{
|
||||
static ColumnPtr execute(Transform transform, const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, UInt16 scale = 0)
|
||||
static ColumnPtr execute(Transform transform, const ColumnsWithTypeAndName & arguments, const DataTypePtr & result_type, UInt16 scale)
|
||||
{
|
||||
using FromValueType = typename FromDataType::FieldType;
|
||||
using FromColumnType = typename FromDataType::ColumnType;
|
||||
using ToColumnType = typename ToDataType::ColumnType;
|
||||
|
||||
auto op = Adder<Transform>{std::move(transform)};
|
||||
const IColumn & source_column = *arguments[0].column;
|
||||
const IColumn & delta_column = *arguments[1].column;
|
||||
|
||||
const DateLUTImpl & time_zone = extractTimeZoneFromFunctionArguments(arguments, 2, 0);
|
||||
|
||||
const ColumnPtr source_col = arguments[0].column;
|
||||
|
||||
auto result_col = result_type->createColumn();
|
||||
auto col_to = assert_cast<ToColumnType *>(result_col.get());
|
||||
|
||||
const IColumn & delta_column = *arguments[1].column;
|
||||
if (const auto * sources = checkAndGetColumn<FromColumnType>(source_col.get()))
|
||||
auto processor = Processor<Transform>{std::move(transform)};
|
||||
|
||||
if (const auto * sources = checkAndGetColumn<FromColumnType>(&source_column))
|
||||
{
|
||||
if (const auto * delta_const_column = typeid_cast<const ColumnConst *>(&delta_column))
|
||||
op.vectorConstant(sources->getData(), col_to->getData(), delta_const_column->getInt(0), time_zone, scale);
|
||||
processor.vectorConstant(*sources, *col_to, delta_const_column->getInt(0), time_zone, scale);
|
||||
else
|
||||
op.vectorVector(sources->getData(), col_to->getData(), delta_column, time_zone, scale);
|
||||
processor.vectorVector(*sources, *col_to, delta_column, time_zone, scale);
|
||||
}
|
||||
else if (const auto * sources_const = checkAndGetColumnConst<FromColumnType>(source_col.get()))
|
||||
else if (const auto * sources_const = checkAndGetColumnConst<FromColumnType>(&source_column))
|
||||
{
|
||||
op.constantVector(
|
||||
processor.constantVector(
|
||||
sources_const->template getValue<FromValueType>(),
|
||||
col_to->getData(), delta_column, time_zone, scale);
|
||||
*col_to, delta_column, time_zone, scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -506,7 +590,7 @@ template <> struct ResultDataTypeMap<UInt32> { using ResultDataType = DataTy
|
||||
template <> struct ResultDataTypeMap<Int32> { using ResultDataType = DataTypeDate32; };
|
||||
template <> struct ResultDataTypeMap<DateTime64> { using ResultDataType = DataTypeDateTime64; };
|
||||
template <> struct ResultDataTypeMap<Int64> { using ResultDataType = DataTypeDateTime64; };
|
||||
template <> struct ResultDataTypeMap<Int8> { using ResultDataType = DataTypeInt8; }; // error
|
||||
template <> struct ResultDataTypeMap<Int8> { using ResultDataType = DataTypeInt8; }; // error
|
||||
}
|
||||
|
||||
template <typename Transform>
|
||||
@ -516,10 +600,7 @@ public:
|
||||
static constexpr auto name = Transform::name;
|
||||
static FunctionPtr create(ContextPtr) { return std::make_shared<FunctionDateOrDateTimeAddInterval>(); }
|
||||
|
||||
String getName() const override
|
||||
{
|
||||
return name;
|
||||
}
|
||||
String getName() const override { return name; }
|
||||
|
||||
bool isVariadic() const override { return true; }
|
||||
size_t getNumberOfArguments() const override { return 0; }
|
||||
@ -532,30 +613,28 @@ public:
|
||||
"Number of arguments for function {} doesn't match: passed {}, should be 2 or 3",
|
||||
getName(), arguments.size());
|
||||
|
||||
if (!isNativeNumber(arguments[1].type))
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Second argument for function {} (delta) must be a number",
|
||||
getName());
|
||||
|
||||
if (arguments.size() == 2)
|
||||
{
|
||||
if (!isDate(arguments[0].type) && !isDate32(arguments[0].type) && !isDateTime(arguments[0].type) && !isDateTime64(arguments[0].type))
|
||||
if (!isDateOrDate32OrDateTimeOrDateTime64(arguments[0].type) && !isString(arguments[0].type))
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of first argument of function {}. "
|
||||
"Should be a date or a date with time", arguments[0].type->getName(), getName());
|
||||
"Must be a date, a date with time or a String", arguments[0].type->getName(), getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!WhichDataType(arguments[0].type).isDateTime()
|
||||
|| !WhichDataType(arguments[2].type).isString())
|
||||
{
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Function {} supports 2 or 3 arguments. "
|
||||
"The 1st argument must be of type Date or DateTime. "
|
||||
"The 2nd argument must be a number. "
|
||||
"The 3rd argument (optional) must be a constant string with timezone name. "
|
||||
if (!WhichDataType(arguments[0].type).isDateTime())
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of first argument of function {}. "
|
||||
"Must be a DateTime", arguments[0].type->getName(), getName());
|
||||
|
||||
if (!WhichDataType(arguments[2].type).isString())
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of third argument of function {}. "
|
||||
"The 3rd argument must be a constant string with a timezone name. "
|
||||
"The timezone argument is allowed only when the 1st argument has the type DateTime",
|
||||
getName());
|
||||
}
|
||||
arguments[2].type->getName(), getName());
|
||||
}
|
||||
|
||||
if (!isNativeNumber(arguments[1].type))
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Second argument for function {} must be a number", getName());
|
||||
|
||||
switch (arguments[0].type->getTypeId())
|
||||
{
|
||||
case TypeIndex::Date:
|
||||
@ -566,18 +645,18 @@ public:
|
||||
return resolveReturnType<DataTypeDateTime>(arguments);
|
||||
case TypeIndex::DateTime64:
|
||||
return resolveReturnType<DataTypeDateTime64>(arguments);
|
||||
case TypeIndex::String:
|
||||
return resolveReturnType<DataTypeString>(arguments);
|
||||
default:
|
||||
{
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Invalid type of 1st argument of function {}: "
|
||||
"{}, expected: Date, DateTime or DateTime64.", getName(), arguments[0].type->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper templates to deduce return type based on argument type, since some overloads may promote or denote types,
|
||||
/// e.g. addSeconds(Date, 1) => DateTime
|
||||
template <typename FieldType>
|
||||
using TransformExecuteReturnType = decltype(std::declval<Transform>().execute(FieldType(), 0, std::declval<DateLUTImpl>(), 0));
|
||||
using TransformExecuteReturnType = decltype(std::declval<Transform>().execute(FieldType(), 0, std::declval<DateLUTImpl>(), std::declval<DateLUTImpl>(), 0));
|
||||
|
||||
// Deduces RETURN DataType from INPUT DataType, based on return type of Transform{}.execute(INPUT_TYPE, UInt64, DateLUTImpl).
|
||||
// e.g. for Transform-type that has execute()-overload with 'UInt16' input and 'UInt32' return,
|
||||
@ -591,17 +670,11 @@ public:
|
||||
using ResultDataType = TransformResultDataType<FromDataType>;
|
||||
|
||||
if constexpr (std::is_same_v<ResultDataType, DataTypeDate>)
|
||||
{
|
||||
return std::make_shared<DataTypeDate>();
|
||||
}
|
||||
else if constexpr (std::is_same_v<ResultDataType, DataTypeDate32>)
|
||||
{
|
||||
return std::make_shared<DataTypeDate32>();
|
||||
}
|
||||
else if constexpr (std::is_same_v<ResultDataType, DataTypeDateTime>)
|
||||
{
|
||||
return std::make_shared<DataTypeDateTime>(extractTimeZoneNameFromFunctionArguments(arguments, 2, 0, false));
|
||||
}
|
||||
else if constexpr (std::is_same_v<ResultDataType, DataTypeDateTime64>)
|
||||
{
|
||||
static constexpr auto target_scale = std::invoke(
|
||||
@ -627,9 +700,7 @@ public:
|
||||
return std::make_shared<DataTypeDateTime64>(target_scale.value_or(DataTypeDateTime64::default_scale), std::move(timezone));
|
||||
}
|
||||
else if constexpr (std::is_same_v<ResultDataType, DataTypeInt8>)
|
||||
{
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "{} cannot be used with {}", getName(), arguments[0].type->getName());
|
||||
}
|
||||
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "Unexpected result type in datetime add interval function");
|
||||
}
|
||||
@ -643,29 +714,21 @@ public:
|
||||
WhichDataType which(from_type);
|
||||
|
||||
if (which.isDate())
|
||||
{
|
||||
return DateTimeAddIntervalImpl<DataTypeDate, TransformResultDataType<DataTypeDate>, Transform>::execute(
|
||||
Transform{}, arguments, result_type);
|
||||
}
|
||||
return DateTimeAddIntervalImpl<DataTypeDate, TransformResultDataType<DataTypeDate>, Transform>::execute(Transform{}, arguments, result_type, 0);
|
||||
else if (which.isDate32())
|
||||
{
|
||||
return DateTimeAddIntervalImpl<DataTypeDate32, TransformResultDataType<DataTypeDate32>, Transform>::execute(
|
||||
Transform{}, arguments, result_type);
|
||||
}
|
||||
return DateTimeAddIntervalImpl<DataTypeDate32, TransformResultDataType<DataTypeDate32>, Transform>::execute(Transform{}, arguments, result_type, 0);
|
||||
else if (which.isDateTime())
|
||||
return DateTimeAddIntervalImpl<DataTypeDateTime, TransformResultDataType<DataTypeDateTime>, Transform>::execute(Transform{}, arguments, result_type, 0);
|
||||
else if (which.isDateTime64())
|
||||
{
|
||||
return DateTimeAddIntervalImpl<DataTypeDateTime, TransformResultDataType<DataTypeDateTime>, Transform>::execute(
|
||||
Transform{}, arguments, result_type);
|
||||
}
|
||||
else if (const auto * datetime64_type = assert_cast<const DataTypeDateTime64 *>(from_type))
|
||||
{
|
||||
const auto * datetime64_type = assert_cast<const DataTypeDateTime64 *>(from_type);
|
||||
auto from_scale = datetime64_type->getScale();
|
||||
return DateTimeAddIntervalImpl<DataTypeDateTime64, TransformResultDataType<DataTypeDateTime64>, Transform>::execute(
|
||||
Transform{}, arguments, result_type, from_scale);
|
||||
return DateTimeAddIntervalImpl<DataTypeDateTime64, TransformResultDataType<DataTypeDateTime64>, Transform>::execute(Transform{}, arguments, result_type, from_scale);
|
||||
}
|
||||
else if (which.isString())
|
||||
return DateTimeAddIntervalImpl<DataTypeString, DataTypeDateTime64, Transform>::execute(Transform{}, arguments, result_type, 3);
|
||||
else
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of first argument of function {}",
|
||||
arguments[0].type->getName(), getName());
|
||||
throw Exception(ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT, "Illegal type {} of first argument of function {}", arguments[0].type->getName(), getName());
|
||||
}
|
||||
};
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user